PageRenderTime 57ms CodeModel.GetById 30ms RepoModel.GetById 0ms app.codeStats 0ms

/core/ldap_api.php

http://github.com/mantisbt/mantisbt
PHP | 515 lines | 301 code | 68 blank | 146 comment | 50 complexity | d134ccc3c254dc86a8a48a702af52570 MD5 | raw file
Possible License(s): GPL-2.0, LGPL-2.1
  1. <?php
  2. # MantisBT - A PHP based bugtracking system
  3. # MantisBT is free software: you can redistribute it and/or modify
  4. # it under the terms of the GNU General Public License as published by
  5. # the Free Software Foundation, either version 2 of the License, or
  6. # (at your option) any later version.
  7. #
  8. # MantisBT is distributed in the hope that it will be useful,
  9. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  10. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  11. # GNU General Public License for more details.
  12. #
  13. # You should have received a copy of the GNU General Public License
  14. # along with MantisBT. If not, see <http://www.gnu.org/licenses/>.
  15. /**
  16. * LDAP API
  17. *
  18. * @package CoreAPI
  19. * @subpackage LDAPAPI
  20. * @copyright Copyright 2000 - 2002 Kenzaburo Ito - kenito@300baud.org
  21. * @copyright Copyright 2002 MantisBT Team - mantisbt-dev@lists.sourceforge.net
  22. * @link http://www.mantisbt.org
  23. *
  24. * @uses config_api.php
  25. * @uses constant_inc.php
  26. * @uses logging_api.php
  27. * @uses user_api.php
  28. * @uses utility_api.php
  29. */
  30. require_api( 'config_api.php' );
  31. require_api( 'constant_inc.php' );
  32. require_api( 'logging_api.php' );
  33. require_api( 'user_api.php' );
  34. require_api( 'utility_api.php' );
  35. /**
  36. * @var array $g_cache_ldap_data LDAP attributes cache, indexed by username
  37. */
  38. $g_cache_ldap_data = array();
  39. /**
  40. * Logs the most recent LDAP error
  41. * @param resource $p_ds LDAP resource identifier returned by ldap_connect.
  42. * @return void
  43. */
  44. function ldap_log_error( $p_ds ) {
  45. log_event( LOG_LDAP, 'ERROR #' . ldap_errno( $p_ds ) . ': ' . ldap_error( $p_ds ) );
  46. }
  47. /**
  48. * Connect and bind to the LDAP directory
  49. * @param string $p_binddn DN to use for LDAP bind.
  50. * @param string $p_password Password to use for LDAP bind.
  51. * @return resource|false
  52. */
  53. function ldap_connect_bind( $p_binddn = '', $p_password = '' ) {
  54. if( !extension_loaded( 'ldap' ) ) {
  55. log_event( LOG_LDAP, 'Error: LDAP extension missing in php' );
  56. trigger_error( ERROR_LDAP_EXTENSION_NOT_LOADED, ERROR );
  57. }
  58. $t_ldap_server = config_get_global( 'ldap_server' );
  59. log_event( LOG_LDAP, 'Attempting connection to LDAP server/URI \'' . $t_ldap_server . '\'.' );
  60. $t_ds = @ldap_connect( $t_ldap_server );
  61. if( $t_ds === false ) {
  62. log_event( LOG_LDAP, 'Connection to LDAP server failed' );
  63. trigger_error( ERROR_LDAP_SERVER_CONNECT_FAILED, ERROR );
  64. # Return required as function may be called with error suppressed
  65. return false;
  66. }
  67. log_event( LOG_LDAP, 'Connection accepted by LDAP server' );
  68. $t_network_timeout = config_get_global( 'ldap_network_timeout' );
  69. if( $t_network_timeout > 0 ) {
  70. log_event( LOG_LDAP, "Setting LDAP network timeout to " . $t_network_timeout );
  71. $t_result = @ldap_set_option( $t_ds, LDAP_OPT_NETWORK_TIMEOUT, $t_network_timeout );
  72. if( !$t_result ) {
  73. ldap_log_error( $t_ds );
  74. }
  75. }
  76. $t_protocol_version = config_get_global( 'ldap_protocol_version' );
  77. if( $t_protocol_version > 0 ) {
  78. log_event( LOG_LDAP, 'Setting LDAP protocol version to ' . $t_protocol_version );
  79. $t_result = @ldap_set_option( $t_ds, LDAP_OPT_PROTOCOL_VERSION, $t_protocol_version );
  80. if( !$t_result ) {
  81. ldap_log_error( $t_ds );
  82. }
  83. }
  84. # Set referrals flag.
  85. $t_follow_referrals = ON == config_get_global( 'ldap_follow_referrals' );
  86. $t_result = @ldap_set_option( $t_ds, LDAP_OPT_REFERRALS, $t_follow_referrals );
  87. if( !$t_result ) {
  88. ldap_log_error( $t_ds );
  89. }
  90. # If no Bind DN and Password is set, attempt to login as the configured
  91. # Bind DN.
  92. if( is_blank( $p_binddn ) && is_blank( $p_password ) ) {
  93. $p_binddn = config_get_global( 'ldap_bind_dn', '' );
  94. $p_password = config_get_global( 'ldap_bind_passwd', '' );
  95. }
  96. if( !is_blank( $p_binddn ) && !is_blank( $p_password ) ) {
  97. log_event( LOG_LDAP, "Attempting bind to ldap server as '$p_binddn'" );
  98. $t_br = @ldap_bind( $t_ds, $p_binddn, $p_password );
  99. } else {
  100. # Either the Bind DN or the Password are empty, so attempt an anonymous bind.
  101. log_event( LOG_LDAP, 'Attempting anonymous bind to ldap server' );
  102. $t_br = @ldap_bind( $t_ds );
  103. }
  104. if( !$t_br ) {
  105. ldap_log_error( $t_ds );
  106. log_event( LOG_LDAP, 'Bind to ldap server failed' );
  107. trigger_error( ERROR_LDAP_SERVER_CONNECT_FAILED, ERROR );
  108. } else {
  109. log_event( LOG_LDAP, 'Bind to ldap server successful' );
  110. }
  111. return $t_ds;
  112. }
  113. /**
  114. * returns an email address from LDAP, given a userid
  115. * @param integer $p_user_id A valid user identifier.
  116. * @return string
  117. */
  118. function ldap_email( $p_user_id ) {
  119. return ldap_email_from_username( user_get_username( $p_user_id ) );
  120. }
  121. /**
  122. * Return an email address from LDAP, given a username
  123. * @param string $p_username The username of a user to lookup.
  124. * @return string
  125. */
  126. function ldap_email_from_username( $p_username ) {
  127. if( ldap_simulation_is_enabled() ) {
  128. $t_email = ldap_simulation_email_from_username( $p_username );
  129. } else {
  130. $t_email = (string)ldap_get_field_from_username( $p_username, 'mail' );
  131. }
  132. return $t_email;
  133. }
  134. /**
  135. * Gets a user's real name (common name) given the id.
  136. *
  137. * @param integer $p_user_id The user id.
  138. * @return string real name.
  139. */
  140. function ldap_realname( $p_user_id ) {
  141. return ldap_realname_from_username( user_get_username( $p_user_id ) );
  142. }
  143. /**
  144. * Gets a user real name given their user name.
  145. * @param string $p_username The user's name.
  146. * @return string The user's real name.
  147. */
  148. function ldap_realname_from_username( $p_username ) {
  149. if( ldap_simulation_is_enabled() ) {
  150. $t_realname = ldap_simulatiom_realname_from_username( $p_username );
  151. } else {
  152. $t_ldap_realname_field = config_get_global( 'ldap_realname_field' );
  153. $t_realname = (string)ldap_get_field_from_username( $p_username, $t_ldap_realname_field );
  154. }
  155. return $t_realname;
  156. }
  157. /**
  158. * Escapes the LDAP string to disallow injection.
  159. *
  160. * @param string $p_string The string to escape.
  161. * @return string The escaped string.
  162. */
  163. function ldap_escape_string( $p_string ) {
  164. $t_find = array( '\\', '*', '(', ')', '/', "\x00" );
  165. $t_replace = array( '\5c', '\2a', '\28', '\29', '\2f', '\00' );
  166. $t_string = str_replace( $t_find, $t_replace, $p_string );
  167. return $t_string;
  168. }
  169. /**
  170. * Retrieves user data from LDAP and stores it in cache.
  171. *
  172. * Uses a single LDAP query to retrieve the following fields:
  173. * - email (mail)
  174. * - realname {@see $g_ldap_realname_field}
  175. *
  176. * @param string $p_username The username.
  177. *
  178. * @return array|false User data, false if not found or errors occurred
  179. */
  180. function ldap_cache_user_data( $p_username ) {
  181. global $g_cache_ldap_data;
  182. # Returne cached data if available
  183. if( isset( $g_cache_ldap_data[$p_username] ) ) {
  184. return $g_cache_ldap_data[$p_username];
  185. }
  186. log_event( LOG_LDAP, "Retrieving data for '$p_username' from LDAP server" );
  187. # Bind and connect.
  188. # We suppress errors, because failing to connect is not blocking in this
  189. # context, it just means we won't be able to retrieve user data from LDAP.
  190. $t_ds = @ldap_connect_bind();
  191. if( $t_ds === false ) {
  192. log_event( LOG_LDAP, "ERROR: could not bind to LDAP server" );
  193. return false;
  194. }
  195. # Search
  196. $t_ldap_organization = config_get_global( 'ldap_organization' );
  197. $t_ldap_root_dn = config_get_global( 'ldap_root_dn' );
  198. $t_ldap_uid_field = config_get_global( 'ldap_uid_field' );
  199. $t_search_filter = '(&' . $t_ldap_organization
  200. . '(' . $t_ldap_uid_field . '=' . ldap_escape_string( $p_username ) . '))';
  201. $t_search_attrs = array(
  202. 'mail',
  203. config_get_global( 'ldap_realname_field' )
  204. );
  205. log_event( LOG_LDAP, 'Searching for ' . $t_search_filter );
  206. $t_sr = @ldap_search( $t_ds, $t_ldap_root_dn, $t_search_filter, $t_search_attrs );
  207. if( $t_sr === false ) {
  208. ldap_log_error( $t_ds );
  209. ldap_unbind( $t_ds );
  210. log_event( LOG_LDAP, "Search '$t_search_filter' failed" );
  211. return false;
  212. }
  213. # Get results
  214. $t_entry = ldap_first_entry( $t_ds, $t_sr );
  215. if( $t_entry === false ) {
  216. log_event( LOG_LDAP, 'No matches found.' );
  217. $g_cache_ldap_data[$p_username] = false;
  218. return false;
  219. }
  220. $t_data = false;
  221. foreach( $t_search_attrs as $t_attr ) {
  222. # Suppress error to avoid Warning in case an invalid attribute was specified
  223. $t_value = @ldap_get_values( $t_ds, $t_entry, $t_attr );
  224. if( $t_value === false ) {
  225. log_event( LOG_LDAP, "WARNING: field '$t_attr' does not exist" );
  226. continue;
  227. }
  228. $t_data[$t_attr] = $t_value[0];
  229. }
  230. # Store data in the cache
  231. $g_cache_ldap_data[$p_username] = $t_data;
  232. # Unbind
  233. log_event( LOG_LDAP, 'Unbinding from LDAP server' );
  234. ldap_unbind( $t_ds );
  235. return $t_data;
  236. }
  237. /**
  238. * Gets the value of a specific LDAP field given the user name.
  239. *
  240. * Values are retrieved from the LDAP cache.
  241. * {@see ldap_cache_user_data()} for the list of valid field names.
  242. *
  243. * @param string $p_username The user name.
  244. * @param string $p_field The LDAP field name.
  245. *
  246. * @return string The field value or null if not found.
  247. */
  248. function ldap_get_field_from_username( $p_username, $p_field ) {
  249. log_event( LOG_LDAP, "Retrieving field '$p_field' for '$p_username'" );
  250. $t_ldap_data = ldap_cache_user_data( $p_username );
  251. # Make sure LDAP data is available and the requested field exists
  252. if( !$t_ldap_data || !isset( $t_ldap_data[$p_field] ) ) {
  253. return null;
  254. }
  255. return $t_ldap_data[$p_field];
  256. }
  257. /**
  258. * Attempt to authenticate the user against the LDAP directory
  259. * return true on successful authentication, false otherwise
  260. * @param integer $p_user_id A valid user identifier.
  261. * @param string $p_password A password to test against the user user.
  262. * @return boolean
  263. */
  264. function ldap_authenticate( $p_user_id, $p_password ) {
  265. # if password is empty and ldap allows anonymous login, then
  266. # the user will be able to login, hence, we need to check
  267. # for this special case.
  268. if( is_blank( $p_password ) ) {
  269. return false;
  270. }
  271. $t_username = user_get_username( $p_user_id );
  272. return ldap_authenticate_by_username( $t_username, $p_password );
  273. }
  274. /**
  275. * Authenticates a user via LDAP given the username and password.
  276. *
  277. * @param string $p_username The user name.
  278. * @param string $p_password The password.
  279. * @return true: authenticated, false: failed to authenticate.
  280. */
  281. function ldap_authenticate_by_username( $p_username, $p_password ) {
  282. if( ldap_simulation_is_enabled() ) {
  283. log_event( LOG_LDAP, 'Authenticating via LDAP simulation' );
  284. $t_authenticated = ldap_simulation_authenticate_by_username( $p_username, $p_password );
  285. } else {
  286. $c_username = ldap_escape_string( $p_username );
  287. $t_ldap_organization = config_get_global( 'ldap_organization' );
  288. $t_ldap_root_dn = config_get_global( 'ldap_root_dn' );
  289. $t_ldap_uid_field = config_get_global( 'ldap_uid_field', 'uid' );
  290. $t_search_filter = '(&' . $t_ldap_organization . '(' . $t_ldap_uid_field . '=' . $c_username . '))';
  291. $t_search_attrs = array(
  292. $t_ldap_uid_field,
  293. 'dn',
  294. );
  295. # Bind and connect.
  296. # No need to check for failures, as ldap_connect_bind() throws errors.
  297. log_event( LOG_LDAP, 'Binding to LDAP server' );
  298. $t_ds = ldap_connect_bind();
  299. # Search for the user id
  300. log_event( LOG_LDAP, 'Searching for ' . $t_search_filter );
  301. $t_sr = ldap_search( $t_ds, $t_ldap_root_dn, $t_search_filter, $t_search_attrs );
  302. if( $t_sr === false ) {
  303. ldap_log_error( $t_ds );
  304. ldap_unbind( $t_ds );
  305. log_event( LOG_LDAP, "Search '$t_search_filter' failed" );
  306. trigger_error( ERROR_LDAP_AUTH_FAILED, ERROR );
  307. }
  308. $t_info = @ldap_get_entries( $t_ds, $t_sr );
  309. if( $t_info === false ) {
  310. ldap_log_error( $t_ds );
  311. ldap_unbind( $t_ds );
  312. trigger_error( ERROR_LDAP_AUTH_FAILED, ERROR );
  313. }
  314. $t_authenticated = false;
  315. if( $t_info['count'] > 0 ) {
  316. # Try to authenticate to each until we get a match
  317. for( $i = 0; $i < $t_info['count']; $i++ ) {
  318. $t_dn = $t_info[$i]['dn'];
  319. log_event( LOG_LDAP, 'Checking ' . $t_info[$i]['dn'] );
  320. # Attempt to bind with the DN and password
  321. if( @ldap_bind( $t_ds, $t_dn, $p_password ) ) {
  322. $t_authenticated = true;
  323. break;
  324. }
  325. }
  326. } else {
  327. log_event( LOG_LDAP, 'No matching entries found' );
  328. }
  329. log_event( LOG_LDAP, 'Unbinding from LDAP server' );
  330. ldap_unbind( $t_ds );
  331. }
  332. # If user authenticated successfully then update the local DB with information
  333. # from LDAP. This will allow us to use the local data after login without
  334. # having to go back to LDAP. This will also allow fallback to DB if LDAP is down.
  335. if( $t_authenticated ) {
  336. $t_user_id = user_get_id_by_name( $p_username );
  337. if( false !== $t_user_id ) {
  338. $t_fields_to_update = array('password' => md5( $p_password ));
  339. if( ON == config_get_global( 'use_ldap_realname' ) ) {
  340. $t_fields_to_update['realname'] = ldap_realname_from_username( $p_username );
  341. }
  342. if( ON == config_get_global( 'use_ldap_email' ) ) {
  343. $t_fields_to_update['email'] = ldap_email_from_username( $p_username );
  344. }
  345. user_set_fields( $t_user_id, $t_fields_to_update );
  346. }
  347. log_event( LOG_LDAP, 'User \'' . $p_username . '\' authenticated' );
  348. } else {
  349. log_event( LOG_LDAP, 'Authentication failed' );
  350. }
  351. return $t_authenticated;
  352. }
  353. /**
  354. * Checks if the LDAP simulation mode is enabled.
  355. *
  356. * @return boolean true if enabled, false otherwise.
  357. */
  358. function ldap_simulation_is_enabled() {
  359. $t_filename = config_get_global( 'ldap_simulation_file_path' );
  360. return !is_blank( $t_filename );
  361. }
  362. /**
  363. * Gets a user from LDAP simulation mode given the username.
  364. *
  365. * @param string $p_username The user name.
  366. * @return array|null An associate array with user information or null if not found.
  367. */
  368. function ldap_simulation_get_user( $p_username ) {
  369. $t_filename = config_get_global( 'ldap_simulation_file_path' );
  370. $t_lines = file( $t_filename );
  371. if( $t_lines === false ) {
  372. log_event( LOG_LDAP, 'ldap_simulation_get_user: could not read simulation data from ' . $t_filename );
  373. trigger_error( ERROR_LDAP_SERVER_CONNECT_FAILED, ERROR );
  374. }
  375. foreach ( $t_lines as $t_line ) {
  376. $t_line = trim( $t_line, " \t\r\n" );
  377. $t_row = explode( ',', $t_line );
  378. if( $t_row[0] != $p_username ) {
  379. continue;
  380. }
  381. $t_user = array();
  382. $t_user['username'] = $t_row[0];
  383. $t_user['realname'] = $t_row[1];
  384. $t_user['email'] = $t_row[2];
  385. $t_user['password'] = $t_row[3];
  386. return $t_user;
  387. }
  388. log_event( LOG_LDAP, 'ldap_simulation_get_user: user \'' . $p_username . '\' not found.' );
  389. return null;
  390. }
  391. /**
  392. * Given a username, gets the email address or empty address if user is not found.
  393. *
  394. * @param string $p_username The user name.
  395. * @return string The email address or blank if user is not found.
  396. */
  397. function ldap_simulation_email_from_username( $p_username ) {
  398. $t_user = ldap_simulation_get_user( $p_username );
  399. if( $t_user === null ) {
  400. log_event( LOG_LDAP, 'ldap_simulation_email_from_username: user \'' . $p_username . '\' not found.' );
  401. return '';
  402. }
  403. log_event( LOG_LDAP, 'ldap_simulation_email_from_username: user \'' . $p_username . '\' has email \'' . $t_user['email'] .'\'.' );
  404. return $t_user['email'];
  405. }
  406. /**
  407. * Given a username, this methods gets the realname or empty string if not found.
  408. *
  409. * @param string $p_username The username.
  410. * @return string The real name or an empty string if not found.
  411. */
  412. function ldap_simulatiom_realname_from_username( $p_username ) {
  413. $t_user = ldap_simulation_get_user( $p_username );
  414. if( $t_user === null ) {
  415. log_event( LOG_LDAP, 'ldap_simulatiom_realname_from_username: user \'' . $p_username . '\' not found.' );
  416. return '';
  417. }
  418. log_event( LOG_LDAP, 'ldap_simulatiom_realname_from_username: user \'' . $p_username . '\' has real name \'' . $t_user['realname'] . '\'.' );
  419. return $t_user['realname'];
  420. }
  421. /**
  422. * Authenticates the specified user id / password based on the simulation data.
  423. *
  424. * @param string $p_username The username.
  425. * @param string $p_password The password.
  426. * @return boolean true for authenticated, false otherwise.
  427. */
  428. function ldap_simulation_authenticate_by_username( $p_username, $p_password ) {
  429. $c_username = ldap_escape_string( $p_username );
  430. $t_user = ldap_simulation_get_user( $c_username );
  431. if( $t_user === null ) {
  432. log_event( LOG_LDAP, 'ldap_simulation_authenticate: user \'' . $p_username . '\' not found.' );
  433. return false;
  434. }
  435. if( $t_user['password'] != $p_password ) {
  436. log_event( LOG_LDAP, 'ldap_simulation_authenticate: expected password \'' . $t_user['password'] . '\' and got \'' . $p_password . '\'.' );
  437. return false;
  438. }
  439. log_event( LOG_LDAP, 'ldap_simulation_authenticate: authentication successful for user \'' . $p_username . '\'.' );
  440. return true;
  441. }