abook_ldap_server.php 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377
  1. <?php
  2. /**
  3. * abook_ldap_server.php
  4. *
  5. * Copyright (c) 1999-2005 The SquirrelMail Project Team
  6. * Licensed under the GNU GPL. For full terms see the file COPYING.
  7. *
  8. * Address book backend for LDAP server
  9. *
  10. * @version $Id$
  11. * @package squirrelmail
  12. * @subpackage addressbook
  13. */
  14. /**
  15. * Address book backend for LDAP server
  16. *
  17. * An array with the following elements must be passed to
  18. * the class constructor (elements marked ? are optional):
  19. * <pre>
  20. * host => LDAP server hostname/IP-address
  21. * base => LDAP server root (base dn). Empty string allowed.
  22. * ? port => LDAP server TCP port number (default: 389)
  23. * ? charset => LDAP server charset (default: utf-8)
  24. * ? name => Name for LDAP server (default "LDAP: hostname")
  25. * Used to tag the result data
  26. * ? maxrows => Maximum # of rows in search result
  27. * ? timeout => Timeout for LDAP operations (in seconds, default: 30)
  28. * Might not work for all LDAP libraries or servers.
  29. * ? binddn => LDAP Bind DN.
  30. * ? bindpw => LDAP Bind Password.
  31. * ? protocol => LDAP Bind protocol.
  32. * </pre>
  33. * NOTE. This class should not be used directly. Use the
  34. * "AddressBook" class instead.
  35. * @package squirrelmail
  36. * @subpackage addressbook
  37. */
  38. class abook_ldap_server extends addressbook_backend {
  39. /**
  40. * @var string backend type
  41. */
  42. var $btype = 'remote';
  43. /**
  44. * @var string backend name
  45. */
  46. var $bname = 'ldap_server';
  47. /* Parameters changed by class */
  48. /**
  49. * @var string displayed name
  50. */
  51. var $sname = 'LDAP'; /* Service name */
  52. /**
  53. * @var string LDAP server name or address or url
  54. */
  55. var $server = '';
  56. /**
  57. * @var integer LDAP server port
  58. */
  59. var $port = 389;
  60. /**
  61. * @var string LDAP base DN
  62. */
  63. var $basedn = '';
  64. /**
  65. * @var string charset used for entries in LDAP server
  66. */
  67. var $charset = 'utf-8';
  68. /**
  69. * @var object PHP LDAP link ID
  70. */
  71. var $linkid = false;
  72. /**
  73. * @var bool True if LDAP server is bound
  74. */
  75. var $bound = false;
  76. /**
  77. * @var integer max rows in result
  78. */
  79. var $maxrows = 250;
  80. /**
  81. * @var integer timeout of LDAP operations (in seconds)
  82. */
  83. var $timeout = 30;
  84. /**
  85. * @var string DN to bind to (non-anonymous bind)
  86. * @since 1.5.0 and 1.4.3
  87. */
  88. var $binddn = '';
  89. /**
  90. * @var string password to bind with (non-anonymous bind)
  91. * @since 1.5.0 and 1.4.3
  92. */
  93. var $bindpw = '';
  94. /**
  95. * @var integer protocol used to connect to ldap server
  96. * @since 1.5.0 and 1.4.3
  97. */
  98. var $protocol = '';
  99. /**
  100. * Constructor. Connects to database
  101. * @param array connection options
  102. */
  103. function abook_ldap_server($param) {
  104. if(!function_exists('ldap_connect')) {
  105. $this->set_error('LDAP support missing from PHP');
  106. return;
  107. }
  108. if(is_array($param)) {
  109. $this->server = $param['host'];
  110. $this->basedn = $param['base'];
  111. if(!empty($param['port'])) {
  112. $this->port = $param['port'];
  113. }
  114. if(!empty($param['charset'])) {
  115. $this->charset = strtolower($param['charset']);
  116. }
  117. if(isset($param['maxrows'])) {
  118. $this->maxrows = $param['maxrows'];
  119. }
  120. if(isset($param['timeout'])) {
  121. $this->timeout = $param['timeout'];
  122. }
  123. if(isset($param['binddn'])) {
  124. $this->binddn = $param['binddn'];
  125. }
  126. if(isset($param['bindpw'])) {
  127. $this->bindpw = $param['bindpw'];
  128. }
  129. if(isset($param['protocol'])) {
  130. $this->protocol = $param['protocol'];
  131. }
  132. if(empty($param['name'])) {
  133. $this->sname = 'LDAP: ' . $param['host'];
  134. }
  135. else {
  136. $this->sname = $param['name'];
  137. }
  138. $this->open(true);
  139. } else {
  140. $this->set_error('Invalid argument to constructor');
  141. }
  142. }
  143. /**
  144. * Open the LDAP server.
  145. * @param bool $new is it a new connection
  146. * @return bool
  147. */
  148. function open($new = false) {
  149. $this->error = '';
  150. /* Connection is already open */
  151. if($this->linkid != false && !$new) {
  152. return true;
  153. }
  154. $this->linkid = @ldap_connect($this->server, $this->port);
  155. if(!$this->linkid) {
  156. if(function_exists('ldap_error')) {
  157. return $this->set_error(ldap_error($this->linkid));
  158. } else {
  159. return $this->set_error('ldap_connect failed');
  160. }
  161. }
  162. if(!empty($this->protocol)) {
  163. if(!@ldap_set_option($this->linkid, LDAP_OPT_PROTOCOL_VERSION, $this->protocol)) {
  164. if(function_exists('ldap_error')) {
  165. return $this->set_error(ldap_error($this->linkid));
  166. } else {
  167. return $this->set_error('ldap_set_option failed');
  168. }
  169. }
  170. }
  171. if(!empty($this->binddn)) {
  172. if(!@ldap_bind($this->linkid, $this->binddn, $this->bindpw)) {
  173. if(function_exists('ldap_error')) {
  174. return $this->set_error(ldap_error($this->linkid));
  175. } else {
  176. return $this->set_error('authenticated ldap_bind failed');
  177. }
  178. }
  179. } else {
  180. if(!@ldap_bind($this->linkid)) {
  181. if(function_exists('ldap_error')) {
  182. return $this->set_error(ldap_error($this->linkid));
  183. } else {
  184. return $this->set_error('anonymous ldap_bind failed');
  185. }
  186. }
  187. }
  188. $this->bound = true;
  189. return true;
  190. }
  191. /**
  192. * Encode string to the charset used by this LDAP server
  193. * @param string string that has to be encoded
  194. * @return string encoded string
  195. */
  196. function charset_encode($str) {
  197. global $default_charset;
  198. if($this->charset != $default_charset) {
  199. return charset_convert($default_charset,$str,$this->charset,false);
  200. } else {
  201. return $str;
  202. }
  203. }
  204. /**
  205. * Decode from charset used by this LDAP server to charset used by translation
  206. *
  207. * Uses SquirrelMail charset_decode functions
  208. * @param string string that has to be decoded
  209. * @return string decoded string
  210. */
  211. function charset_decode($str) {
  212. global $default_charset;
  213. if ($this->charset != $default_charset) {
  214. return charset_convert($this->charset,$str,$default_charset,false);
  215. } else {
  216. return $str;
  217. }
  218. }
  219. /**
  220. * Sanitizes ldap search strings.
  221. * See rfc2254
  222. * @link http://www.faqs.org/rfcs/rfc2254.html
  223. * @since 1.5.1
  224. * @param string $string
  225. * @return string sanitized string
  226. */
  227. function ldapspecialchars($string) {
  228. $sanitized=array('\\' => '\5c',
  229. '*' => '\2a',
  230. '(' => '\28',
  231. ')' => '\29',
  232. "\x00" => '\00');
  233. return str_replace(array_keys($sanitized),array_values($sanitized),$string);
  234. }
  235. /* ========================== Public ======================== */
  236. /**
  237. * Search the LDAP server
  238. * @param string $expr search expression
  239. * @return array search results
  240. */
  241. function search($expr) {
  242. /* To be replaced by advanded search expression parsing */
  243. if(is_array($expr)) return false;
  244. /* Encode the expression */
  245. $expr = $this->charset_encode($expr);
  246. /*
  247. * allow use of one asterisk in search.
  248. * Don't allow any ldap special chars if search is different
  249. */
  250. if($expr!='*') {
  251. $expr = '*' . $this->ldapspecialchars($expr) . '*';
  252. }
  253. $expression = "cn=$expr";
  254. /* Make sure connection is there */
  255. if(!$this->open()) {
  256. return false;
  257. }
  258. $sret = @ldap_search($this->linkid, $this->basedn, $expression,
  259. array('dn', 'o', 'ou', 'sn', 'givenname', 'cn', 'mail'),
  260. 0, $this->maxrows, $this->timeout);
  261. /* Should get error from server using the ldap_error() function,
  262. * but it only exist in the PHP LDAP documentation. */
  263. if(!$sret) {
  264. if(function_exists('ldap_error')) {
  265. return $this->set_error(ldap_error($this->linkid));
  266. } else {
  267. return $this->set_error('ldap_search failed');
  268. }
  269. }
  270. if(@ldap_count_entries($this->linkid, $sret) <= 0) {
  271. return array();
  272. }
  273. /* Get results */
  274. $ret = array();
  275. $returned_rows = 0;
  276. $res = @ldap_get_entries($this->linkid, $sret);
  277. for($i = 0 ; $i < $res['count'] ; $i++) {
  278. $row = $res[$i];
  279. /* Extract data common for all e-mail addresses
  280. * of an object. Use only the first name */
  281. $nickname = $this->charset_decode($row['dn']);
  282. $fullname = $this->charset_decode($row['cn'][0]);
  283. if(!empty($row['ou'][0])) {
  284. $label = $this->charset_decode($row['ou'][0]);
  285. }
  286. else if(!empty($row['o'][0])) {
  287. $label = $this->charset_decode($row['o'][0]);
  288. } else {
  289. $label = '';
  290. }
  291. if(empty($row['givenname'][0])) {
  292. $firstname = '';
  293. } else {
  294. $firstname = $this->charset_decode($row['givenname'][0]);
  295. }
  296. if(empty($row['sn'][0])) {
  297. $surname = '';
  298. } else {
  299. $surname = $this->charset_decode($row['sn'][0]);
  300. }
  301. /* Add one row to result for each e-mail address */
  302. if(isset($row['mail']['count'])) {
  303. for($j = 0 ; $j < $row['mail']['count'] ; $j++) {
  304. array_push($ret, array('nickname' => $nickname,
  305. 'name' => $fullname,
  306. 'firstname' => $firstname,
  307. 'lastname' => $surname,
  308. 'email' => $row['mail'][$j],
  309. 'label' => $label,
  310. 'backend' => $this->bnum,
  311. 'source' => &$this->sname));
  312. // Limit number of hits
  313. $returned_rows++;
  314. if(($returned_rows >= $this->maxrows) &&
  315. ($this->maxrows > 0) ) {
  316. ldap_free_result($sret);
  317. return $ret;
  318. }
  319. } // for($j ...)
  320. } // isset($row['mail']['count'])
  321. }
  322. ldap_free_result($sret);
  323. return $ret;
  324. } /* end search() */
  325. /**
  326. * List all entries present in LDAP server
  327. *
  328. * If you run a tiny LDAP server and you want the "List All" button
  329. * to show EVERYONE, disable first return call and enable the second one.
  330. * Remember that maxrows setting might limit list of returned entries.
  331. *
  332. * Careful with this -- it could get quite large for big sites.
  333. * @return array all entries in ldap server
  334. */
  335. function list_addr() {
  336. return array();
  337. // return $this->search('*');
  338. }
  339. }
  340. ?>