You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

rcube_ldap_generic.php 10KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365
  1. <?php
  2. /**
  3. +-----------------------------------------------------------------------+
  4. | Roundcube/rcube_ldap_generic.php |
  5. | |
  6. | This file is part of the Roundcube Webmail client |
  7. | Copyright (C) 2006-2014, The Roundcube Dev Team |
  8. | Copyright (C) 2012-2015, Kolab Systems AG |
  9. | |
  10. | Licensed under the GNU General Public License version 3 or |
  11. | any later version with exceptions for skins & plugins. |
  12. | See the README file for a full license statement. |
  13. | |
  14. | PURPOSE: |
  15. | Provide basic functionality for accessing LDAP directories |
  16. | |
  17. +-----------------------------------------------------------------------+
  18. | Author: Thomas Bruederli <roundcube@gmail.com> |
  19. | Aleksander Machniak <machniak@kolabsys.com> |
  20. +-----------------------------------------------------------------------+
  21. */
  22. /**
  23. * Model class to access an LDAP directories
  24. *
  25. * @package Framework
  26. * @subpackage LDAP
  27. */
  28. class rcube_ldap_generic extends Net_LDAP3
  29. {
  30. /** private properties */
  31. protected $cache = null;
  32. protected $attributes = array('dn');
  33. protected $error;
  34. function __construct($config = null)
  35. {
  36. parent::__construct($config);
  37. $this->config_set('log_hook', array($this, 'log'));
  38. }
  39. /**
  40. * Establish a connection to the LDAP server
  41. */
  42. public function connect($host = null)
  43. {
  44. // Net_LDAP3 does not support IDNA yet
  45. // also parse_host() here is very Roundcube specific
  46. $host = rcube_utils::idn_to_ascii(rcube_utils::parse_host($host));
  47. return parent::connect($host);
  48. }
  49. /**
  50. * Get a specific LDAP entry, identified by its DN
  51. *
  52. * @param string $dn Record identifier
  53. * @param array $attributes Attributes to return
  54. *
  55. * @return array Hash array
  56. */
  57. function get_entry($dn, $attributes = array())
  58. {
  59. return parent::get_entry($dn, !empty($attributes) ? $attributes : $this->attributes);
  60. }
  61. /**
  62. * Prints debug/error info to the log
  63. */
  64. public function log($level, $msg)
  65. {
  66. $msg = implode("\n", $msg);
  67. switch ($level) {
  68. case LOG_DEBUG:
  69. case LOG_INFO:
  70. case LOG_NOTICE:
  71. if ($this->config['debug']) {
  72. rcube::write_log('ldap', $msg);
  73. }
  74. break;
  75. case LOG_EMERGE:
  76. case LOG_ALERT:
  77. case LOG_CRIT:
  78. rcube::raise_error($msg, true, true);
  79. break;
  80. case LOG_ERR:
  81. case LOG_WARNING:
  82. $this->error = $msg;
  83. rcube::raise_error($msg, true, false);
  84. break;
  85. }
  86. }
  87. /**
  88. * Returns the last LDAP error occurred
  89. *
  90. * @return mixed Error message string or null if no error occured
  91. */
  92. function get_error()
  93. {
  94. return $this->error;
  95. }
  96. /**
  97. * @deprecated
  98. */
  99. public function set_debug($dbg = true)
  100. {
  101. $this->config['debug'] = (bool) $dbg;
  102. }
  103. /**
  104. * @deprecated
  105. */
  106. public function set_cache($cache_engine)
  107. {
  108. $this->config['cache'] = $cache_engine;
  109. }
  110. /**
  111. * @deprecated
  112. */
  113. public static function scope2func($scope, &$ns_function = null)
  114. {
  115. return self::scope_to_function($scope, $ns_function);
  116. }
  117. /**
  118. * @deprecated
  119. */
  120. public function set_config($opt, $val = null)
  121. {
  122. $this->config_set($opt, $val);
  123. }
  124. /**
  125. * @deprecated
  126. */
  127. public function add($dn, $entry)
  128. {
  129. return $this->add_entry($dn, $entry);
  130. }
  131. /**
  132. * @deprecated
  133. */
  134. public function delete($dn)
  135. {
  136. return $this->delete_entry($dn);
  137. }
  138. /**
  139. * Wrapper for ldap_mod_replace()
  140. *
  141. * @see ldap_mod_replace()
  142. */
  143. public function mod_replace($dn, $entry)
  144. {
  145. $this->_debug("C: Replace $dn: ".print_r($entry, true));
  146. if (!ldap_mod_replace($this->conn, $dn, $entry)) {
  147. $this->_error("ldap_mod_replace() failed with " . ldap_error($this->conn));
  148. return false;
  149. }
  150. $this->_debug("S: OK");
  151. return true;
  152. }
  153. /**
  154. * Wrapper for ldap_mod_add()
  155. *
  156. * @see ldap_mod_add()
  157. */
  158. public function mod_add($dn, $entry)
  159. {
  160. $this->_debug("C: Add $dn: ".print_r($entry, true));
  161. if (!ldap_mod_add($this->conn, $dn, $entry)) {
  162. $this->_error("ldap_mod_add() failed with " . ldap_error($this->conn));
  163. return false;
  164. }
  165. $this->_debug("S: OK");
  166. return true;
  167. }
  168. /**
  169. * Wrapper for ldap_mod_del()
  170. *
  171. * @see ldap_mod_del()
  172. */
  173. public function mod_del($dn, $entry)
  174. {
  175. $this->_debug("C: Delete $dn: ".print_r($entry, true));
  176. if (!ldap_mod_del($this->conn, $dn, $entry)) {
  177. $this->_error("ldap_mod_del() failed with " . ldap_error($this->conn));
  178. return false;
  179. }
  180. $this->_debug("S: OK");
  181. return true;
  182. }
  183. /**
  184. * Wrapper for ldap_rename()
  185. *
  186. * @see ldap_rename()
  187. */
  188. public function rename($dn, $newrdn, $newparent = null, $deleteoldrdn = true)
  189. {
  190. $this->_debug("C: Rename $dn to $newrdn");
  191. if (!ldap_rename($this->conn, $dn, $newrdn, $newparent, $deleteoldrdn)) {
  192. $this->_error("ldap_rename() failed with " . ldap_error($this->conn));
  193. return false;
  194. }
  195. $this->_debug("S: OK");
  196. return true;
  197. }
  198. /**
  199. * Wrapper for ldap_list() + ldap_get_entries()
  200. *
  201. * @see ldap_list()
  202. * @see ldap_get_entries()
  203. */
  204. public function list_entries($dn, $filter, $attributes = array('dn'))
  205. {
  206. $list = array();
  207. $this->_debug("C: List $dn [{$filter}]");
  208. if ($result = ldap_list($this->conn, $dn, $filter, $attributes)) {
  209. $list = ldap_get_entries($this->conn, $result);
  210. if ($list === false) {
  211. $this->_error("ldap_get_entries() failed with " . ldap_error($this->conn));
  212. return array();
  213. }
  214. $count = $list['count'];
  215. unset($list['count']);
  216. $this->_debug("S: $count record(s)");
  217. }
  218. else {
  219. $this->_error("ldap_list() failed with " . ldap_error($this->conn));
  220. }
  221. return $list;
  222. }
  223. /**
  224. * Wrapper for ldap_read() + ldap_get_entries()
  225. *
  226. * @see ldap_read()
  227. * @see ldap_get_entries()
  228. */
  229. public function read_entries($dn, $filter, $attributes = null)
  230. {
  231. $this->_debug("C: Read $dn [{$filter}]");
  232. if ($this->conn && $dn) {
  233. $result = @ldap_read($this->conn, $dn, $filter, $attributes, 0, (int)$this->config['sizelimit'], (int)$this->config['timelimit']);
  234. if ($result === false) {
  235. $this->_error("ldap_read() failed with " . ldap_error($this->conn));
  236. return false;
  237. }
  238. $this->_debug("S: OK");
  239. return ldap_get_entries($this->conn, $result);
  240. }
  241. return false;
  242. }
  243. /**
  244. * Turn an LDAP entry into a regular PHP array with attributes as keys.
  245. *
  246. * @param array $entry Attributes array as retrieved from ldap_get_attributes() or ldap_get_entries()
  247. * @param bool $flat Convert one-element-array values into strings (not implemented)
  248. *
  249. * @return array Hash array with attributes as keys
  250. */
  251. public static function normalize_entry($entry, $flat = false)
  252. {
  253. if (!isset($entry['count'])) {
  254. return $entry;
  255. }
  256. $rec = array();
  257. for ($i=0; $i < $entry['count']; $i++) {
  258. $attr = $entry[$i];
  259. if ($entry[$attr]['count'] == 1) {
  260. switch ($attr) {
  261. case 'objectclass':
  262. $rec[$attr] = array(strtolower($entry[$attr][0]));
  263. break;
  264. default:
  265. $rec[$attr] = $entry[$attr][0];
  266. break;
  267. }
  268. }
  269. else {
  270. for ($j=0; $j < $entry[$attr]['count']; $j++) {
  271. $rec[$attr][$j] = $entry[$attr][$j];
  272. }
  273. }
  274. }
  275. return $rec;
  276. }
  277. /**
  278. * Compose an LDAP filter string matching all words from the search string
  279. * in the given list of attributes.
  280. *
  281. * @param string $value Search value
  282. * @param mixed $attrs List of LDAP attributes to search
  283. * @param int $mode Matching mode:
  284. * 0 - partial (*abc*),
  285. * 1 - strict (=),
  286. * 2 - prefix (abc*)
  287. * @return string LDAP filter
  288. */
  289. public static function fulltext_search_filter($value, $attributes, $mode = 1)
  290. {
  291. if (empty($attributes)) {
  292. $attributes = array('cn');
  293. }
  294. $groups = array();
  295. $value = str_replace('*', '', $value);
  296. $words = $mode == 0 ? rcube_utils::tokenize_string($value, 1) : array($value);
  297. // set wildcards
  298. $wp = $ws = '';
  299. if ($mode != 1) {
  300. $ws = '*';
  301. $wp = !$mode ? '*' : '';
  302. }
  303. // search each word in all listed attributes
  304. foreach ($words as $word) {
  305. $parts = array();
  306. foreach ($attributes as $attr) {
  307. $parts[] = "($attr=$wp" . self::quote_string($word) . "$ws)";
  308. }
  309. $groups[] = '(|' . join('', $parts) . ')';
  310. }
  311. return count($groups) > 1 ? '(&' . join('', $groups) . ')' : join('', $groups);
  312. }
  313. }
  314. // for backward compat.
  315. class rcube_ldap_result extends Net_LDAP3_Result {}