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

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