1<?php 2// 3// Pear DB LDAP2 - Database independent query interface definition 4// for PHP's LDAP extension with protocol version 2. 5// 6// Copyright (C) 2002-2003 Piotr Roszatycki <dexter@debian.org> 7// 8// Based on ldap.php 9// Copyright (C) 2002 Ludovico Magnocavallo <ludo@sumatrasolutions.com> 10// 11// This library is free software; you can redistribute it and/or 12// modify it under the terms of the GNU Lesser General Public 13// License as published by the Free Software Foundation; either 14// version 2.1 of the License, or (at your option) any later version. 15// 16// This library is distributed in the hope that it will be useful, 17// but WITHOUT ANY WARRANTY; without even the implied warranty of 18// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 19// Lesser General Public License for more details. 20// 21// You should have received a copy of the GNU Lesser General Public 22// License along with this library; if not, write to the Free Software 23// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 24// 25// $Id: ldap2.php 302688 2010-08-23 11:10:32Z clockwerx $ 26// 27 28require_once 'DB/common.php'; 29 30/** 31 * LDAP2 DB interface class 32 * 33 * DB_ldap2 extends DB_common to provide DB compliant 34 * access to LDAP servers with protocol version 2. 35 * 36 * @author Piotr Roszatycki <dexter@debian.org> 37 * @version $Revision: 302688 $ 38 * @package DB_ldap2 39 */ 40 41class DB_ldap2 extends DB_common 42{ 43 // {{{ properties 44 45 /** 46 * LDAP connection handler 47 * @access private 48 */ 49 var $connection; 50 51 /** 52 * list of actions which manipulate data 53 * @access private 54 */ 55 var $action_manip = array( 56 'add', 'compare', 'delete', 'modify', 'mod_add', 'mod_del', 57 'mod_replace', 'rename'); 58 59 /** 60 * list of parameters for search actions 61 * @access private 62 */ 63 var $param_search = array( 64 'action', 'base_dn', 'attributes', 'attrsonly', 'sizelimit', 65 'timelimit', 'deref','sort'); 66 67 /** 68 * list of parameters for modify actions 69 * @access private 70 */ 71 var $param_modify = array( 72 'action', 'attribute', 'value', 'newrdn', 'newparent', 73 'deleteoldrdn'); 74 75 /** 76 * default parameters for query 77 * @access private 78 */ 79 var $param = array(); 80 81 /** 82 * parameters for last query 83 * @access private 84 */ 85 var $last_param = array(); 86 87 /** 88 * array contained row counters for last query 89 * @access private 90 */ 91 var $row = array(); 92 93 /** 94 * array contained number of rows for last query 95 * @access private 96 */ 97 var $num_rows = array(); 98 99 /** 100 * array contained entry handlers for last query 101 * @access private 102 */ 103 var $entry = array(); 104 105 /** 106 * array contained number of rows affected by last query 107 * @access private 108 */ 109 var $affected = 0; 110 111 // }}} 112 // {{{ constructor 113 114 /** 115 * Constructor, calls DB_common constructor 116 * 117 * @see DB_common::DB_common() 118 */ 119 function DB_ldap2() 120 { 121 $this->DB_common(); 122 $this->phptype = 'ldap2'; 123 $this->dbsyntax = 'ldap2'; 124 $this->features = array( 125 'prepare' => false, 126 'pconnect' => false, 127 'transactions' => false, 128 'limit' => false 129 ); 130 $this->errorcode_map = array( 131 0x10 => DB_ERROR_NOSUCHFIELD, // LDAP_NO_SUCH_ATTRIBUTE 132 0x11 => DB_ERROR_NOSUCHFIELD, // LDAP_UNDEFINED_TYPE 133 0x12 => DB_ERROR_CONSTRAINT, // LDAP_INAPPROPRIATE_MATCHING 134 0x13 => DB_ERROR_CONSTRAINT, // LDAP_CONSTRAINT_VIOLATION 135 0x14 => DB_ERROR_ALREADY_EXISTS, // LDAP_TYPE_OR_VALUE_EXISTS 136 0x15 => DB_ERROR_INVALID, // LDAP_INVALID_SYNTAX 137 0x20 => DB_ERROR_NOSUCHTABLE, // LDAP_NO_SUCH_OBJECT 138 0x21 => DB_ERROR_NOSUCHTABLE, // LDAP_ALIAS_PROBLEM 139 0x22 => DB_ERROR_INVALID, // LDAP_INVALID_DN_SYNTAX 140 0x23 => DB_ERROR_INVALID, // LDAP_IS_LEAF 141 0x24 => DB_ERROR_INVALID, // LDAP_ALIAS_DEREF_PROBLEM 142 0x30 => DB_ERROR_ACCESS_VIOLATION, // LDAP_INAPPROPRIATE_AUTH 143 0x31 => DB_ERROR_ACCESS_VIOLATION, // LDAP_INVALID_CREDENTIALS 144 0x32 => DB_ERROR_ACCESS_VIOLATION, // LDAP_INSUFFICIENT_ACCESS 145 0x40 => DB_ERROR_MISMATCH, // LDAP_NAMING_VIOLATION 146 0x41 => DB_ERROR_CONSTRAINT, // LDAP_OBJECT_CLASS_VIOLATION 147 0x44 => DB_ERROR_ALREADY_EXISTS, // LDAP_ALREADY_EXISTS 148 0x51 => DB_ERROR_CONNECT_FAILED, // LDAP_SERVER_DOWN 149 0x57 => DB_ERROR_SYNTAX // LDAP_FILTER_ERROR 150 ); 151 } 152 153 // }}} 154 // {{{ connect() 155 156 /** 157 * Connect and bind to LDAPv2 server with either anonymous 158 * or authenticated bind depending on dsn info 159 * 160 * The format of the supplied DSN: 161 * 162 * ldap2://binddn:bindpw@host:port/basedn 163 * 164 * I.e.: 165 * 166 * ldap2://uid=dexter,ou=People,dc=example,dc=net:secret@127.0.0.1/dc=example,dc=net 167 * 168 * @param $dsn the data source name (see DB::parseDSN for syntax) 169 * @param boolean $persistent kept for interface compatibility 170 * @return int DB_OK if successfully connected. 171 * A DB error code is returned on failure. 172 */ 173 function connect($dsninfo, $persistent = false) 174 { 175 if (!PEAR::loadExtension('ldap')) { 176 return $this->raiseError(DB_ERROR_EXTENSION_NOT_FOUND); 177 } 178 179 $this->dsn = $dsninfo; 180 $type = $dsninfo['phptype']; 181 $user = $dsninfo['username']; 182 $pw = $dsninfo['password']; 183 $host = $dsninfo['hostspec']; 184 $port = empty($dsninfo['port']) ? 389 : $dsninfo['port']; 185 186 $this->param = array( 187 'action' => 'search', 188 'base_dn' => $this->base_dn = $dsninfo['database'], 189 'attributes' => array(), 190 'attrsonly' => 0, 191 'sizelimit' => 0, 192 'timelimit' => 0, 193 'deref' => LDAP_DEREF_NEVER, 194 'attribute' => '', 195 'value' => '', 196 'newrdn' => '', 197 'newparent' => '', 198 'deleteoldrdn'=>false, 199 'sort' => '' 200 ); 201 $this->last_param = $this->param; 202 $this->setOption("seqname_format", "sn=%s," . $dsninfo['database']); 203 $this->fetchmode = DB_FETCHMODE_ASSOC; 204 205 if ($host) { 206 $conn = @ldap_connect($host, $port); 207 } else { 208 return $this->raiseError("unknown host $host"); 209 } 210 if (!$conn) { 211 return $this->raiseError(DB_ERROR_CONNECT_FAILED); 212 } 213 if ($user && $pw) { 214 $bind = @ldap_bind($conn, $user, $pw); 215 } else { 216 $bind = @ldap_bind($conn); 217 } 218 if (!$bind) { 219 return $this->raiseError(DB_ERROR_CONNECT_FAILED); 220 } 221 $this->connection = $conn; 222 return DB_OK; 223 } 224 225 // }}} 226 // {{{ disconnect() 227 228 /** 229 * Unbinds from LDAP server 230 * 231 * @return int ldap_unbind() return value 232 */ 233 function disconnect() 234 { 235 $ret = @ldap_unbind($this->connection); 236 $this->connection = null; 237 return $ret; 238 } 239 240 // }}} 241 // {{{ simpleQuery() 242 243 /** 244 * Performs a request against the LDAP server 245 * 246 * The type of request depend on $query parameter. If $query is string, 247 * perform simple searching query with filter in $query parameter. 248 * If $query is array, the first element of array is filter string 249 * (for reading operations) or data array (for writing operations). 250 * Another elements of $query array are query parameters which overrides 251 * the default parameters. 252 * 253 * The following parameters can be passed for search queries:<br /> 254 * <li />base_dn 255 * <li />attributes - array, the attributes that shall be returned 256 * <li />attrsonly 257 * <li />sizelimit - integer, the max number of results to be returned 258 * <li />timelimit - integer, the timelimit after which to stop searching 259 * <li />deref - 260 * <li/>sort - string, which tells the attribute name by which to sort 261 * 262 * 263 * I.e.: 264 * <code> 265 * // search queries 266 * // 'base_dn' is not given, so the one passed to connect() will be used 267 * $db->simpleQuery("uid=dexter"); 268 * 269 * // base_dn is given 270 * // the 'attributes' key defines the attributes that shall be returned 271 * // 'sort' defines the sort order of the data 272 * $db->simpleQuery(array( 273 * 'uid=dexter', 274 * 'base_dn' => 'ou=People,dc=example,dc=net', 275 * 'attributes'=>array('dn','o','l'), 276 * 'sort'=>'o' 277 * )); 278 * 279 * // use this kind of query for adding data 280 * $db->simpleQuery( 281 * array( 282 * array( 283 * 'dn' => 'cn=Piotr Roszatycki,dc=example,dc=com', 284 * 'objectClass' => array('top', 'person'), 285 * 'cn' => 'Piotr Roszatycki', 286 * 'sn' => 'Roszatycki'), 287 * 'action' => 'add' 288 * )); 289 * 290 * @param mixed $query the ldap query 291 * @return int result from LDAP function for failure queries, 292 * DB_OK for successful queries or DB Error object if wrong syntax 293 */ 294 function simpleQuery( $query) 295 { 296 if (is_array($query)) { 297 $last_param = $query; 298 $query = (isset($query[0]) ? $query[0] : 'objectClass=*'); 299 unset($last_param[0]); 300 } else { 301 $last_param = array(); 302 } 303 $action = (isset($last_param['action']) ? $last_param['action'] : $this->param['action']); 304 // check if the given action is a valid modifier action, i.e. 'search' 305 if (!$this->isManip($action)) { 306 $this->last_param = $this->param; 307 foreach($this->param_search as $k) { 308 if (isset($last_param[$k])) { 309 $this->last_param[$k] = $last_param[$k]; 310 } 311 } 312 extract($this->last_param); 313 // double escape char for filter: '(o=Przedsi\C4\99biorstwo)' => '(o=Przedsi\\C4\\99biorstwo)' 314 $this->last_query = $query; 315 $filter = str_replace('\\', '\\\\', $query); 316 switch ($action) { 317 // ldap_search, *list, *read have the same arguments 318 case 'search': 319 case 'list': 320 case 'read': 321 $ldap_action = "ldap_$action"; 322 $result = @$ldap_action($this->connection, $base_dn, $filter, $attributes, $attrsonly, $sizelimit, $timelimit, $deref); 323 break; 324 default: 325 return $this->ldapRaiseError(DB_ERROR_SYNTAX); 326 } 327 if (!$result) { 328 return $this->ldapRaiseError(); 329 } 330 $this->row[$result] = 0; // reset the row counter. 331 $numrows = $this->numrows($result); 332 if (is_object($numrows)) { 333 return $numrows; 334 } 335 $this->num_rows[$result] = $numrows; 336 $this->affected = 0; 337 if ($sort) { 338 ldap_sort($this->connection,$result,$sort); 339 } 340 return $result; 341 } else { 342 // If first argument is an array, it contains the entry with DN. 343 if (is_array($query)) { 344 $entry = $query; 345 $dn = $entry['dn']; 346 unset($entry['dn']); 347 } else { 348 $entry = array(); 349 $dn = $query; 350 } 351 $this->last_param = $this->param; 352 foreach($this->param_modify as $k) { 353 if (isset($last_param[$k])) { 354 $this->last_param[$k] = $last_param[$k]; 355 } 356 } 357 extract($this->last_param); 358 $this->last_query = $query; 359 switch ($action) { 360 case 'add': 361 case 'modify': 362 case 'mod_add': 363 case 'mod_del': 364 case 'mod_replace': 365 $ldap_action = "ldap_$action"; 366 $result = @$ldap_action($this->connection, $dn, $entry); 367 break; 368 case 'compare': 369 $result = @ldap_compare($this->connection, $dn, $attribute, $value); 370 break; 371 case 'delete': 372 $result = @ldap_delete($this->connection, $dn); 373 break; 374 case 'rename': 375 $result = @ldap_rename($this->connection, $dn, $newrdn, $newparent, $deleteoldrdn); 376 break; 377 default: 378 return $this->ldapRaiseError(DB_ERROR_SYNTAX); 379 } 380 if (!$result) { 381 return $this->ldapRaiseError(); 382 } 383 $this->affected = 1; 384 return DB_OK; 385 } 386 } 387 388 // }}} 389 // {{{ nextResult() 390 391 /** 392 * Move the internal ldap result pointer to the next available result 393 * 394 * @param a valid ldap result resource 395 * 396 * @access public 397 * 398 * @return true if a result is available otherwise return false 399 */ 400 function nextResult($result) 401 { 402 return @ldap_next_entry($result); 403 } 404 405 // }}} 406 // {{{ fetchRow() 407 408 /** 409 * Fetch and return a row of data (it uses fetchInto for that) 410 * @param $result LDAP result identifier 411 * @param $fetchmode format of fetched row array 412 * @param $rownum the absolute row number to fetch 413 * 414 * @return array a row of data, or false on error 415 */ 416 function fetchRow($result, $fetchmode = DB_FETCHMODE_DEFAULT, $rownum=null) 417 { 418 if ($fetchmode == DB_FETCHMODE_DEFAULT) { 419 $fetchmode = $this->fetchmode; 420 } 421 $res = $this->fetchInto($result, $arr, $fetchmode, $rownum); 422 if ($res !== DB_OK) { 423 return $res; 424 } 425 return $arr; 426 } 427 428 // }}} 429 // {{{ fetchInto() 430 431 /** 432 * Fetch a row and insert the data into an existing array. 433 * 434 * DB_FETCHMODE_ORDERED returns a flat array of values 435 * ("value", "val1", "val2"). 436 * 437 * DB_FETCHMODE_ASSOC returns an array of structuralized data 438 * ("field_name1" => "value", "field_name2" => array("val1", "val2")). 439 * 440 * @param $result PostgreSQL result identifier 441 * @param $arr (reference) array where data from the row is stored 442 * @param $fetchmode how the array data should be indexed 443 * @param $rownum the row number to fetch 444 * 445 * @return int DB_OK on success, a DB error code on failure 446 */ 447 function fetchInto($result, &$arr, $fetchmode, $rownum=null) 448 { 449 if ($rownum !== null) { 450 // $rownum is unimplemented, yet 451 return null; 452 } 453 $rownum = $this->row[$result]; 454 if ($rownum >= $this->num_rows[$result]) { 455 return null; 456 } 457 if ($rownum == 0) { 458 $entry = @ldap_first_entry($this->connection, $result); 459 } else { 460 $entry = @ldap_next_entry($this->connection, $this->entry[$result]); 461 } 462 $this->entry[$result] = $entry; 463 if (!$entry) { 464 $errno = ldap_errno($this->connection); 465 if (!$err) { 466 return null; 467 } 468 return $this->ldapRaiseError(); 469 } 470 471 switch ($fetchmode) { 472 case DB_FETCHMODE_ORDERED: 473 $arr = array(); 474 if (!($attr = @ldap_get_attributes($this->connection, $entry))) { 475 $errno = ldap_errno($this->connection); 476 if (!$err) { 477 return null; 478 } 479 return $this->ldapRaiseError(); 480 } 481 if ($attr["count"] == 0) { 482 if (!($arr[] = @ldap_get_dn($this->connection, $entry))) { 483 $errno = ldap_errno($this->connection); 484 if (!$err) { 485 return null; 486 } 487 return $this->ldapRaiseError(); 488 } 489 } else { 490 while (list($attr_name, $attr_val) = each($attr)) { 491 if ($attr_val["count"] == 1) { 492 $arr[] = $attr_val[0]; 493 } elseif ($attr_val["count"] > 1) { 494 for ($i=0; $i<$attr_val["count"]; $i++) { 495 $arr[] = $attr_val[$i]; 496 } 497 } 498 } 499 } 500 break; 501 case DB_FETCHMODE_ASSOC: 502 $arr = array(); 503 if (!($arr["dn"] = @ldap_get_dn($this->connection, $entry))) { 504 $errno = ldap_errno($this->connection); 505 if (!$err) { 506 return null; 507 } 508 return $this->ldapRaiseError(); 509 } 510 if (!($attr = @ldap_get_attributes($this->connection, $entry))) { 511 $errno = ldap_errno($this->connection); 512 if (!$err) { 513 return null; 514 } 515 return $this->ldapRaiseError(); 516 } 517 while (list($attr_name, $attr_val) = each($attr)) { 518 if ($attr_val["count"] == 1) { 519 $arr[strtolower($attr_name)] = $attr_val[0]; 520 } elseif ($attr_val["count"] > 1) { 521 for ($i=0; $i<$attr_val["count"]; $i++) { 522 $arr[strtolower($attr_name)][$i] = $attr_val[$i]; 523 } 524 } 525 } 526 break; 527 } 528 529 $this->row[$result] = ++$rownum; 530 return DB_OK; 531 } 532 533 // }}} 534 // {{{ freeResult() 535 536 /** 537 * Free the internal resources associated with $result. 538 * 539 * @param $result int LDAP result identifier or DB statement identifier 540 * 541 * @return bool TRUE on success, FALSE if $result is invalid 542 */ 543 function freeResult($result) 544 { 545 if (is_resource($result)) { 546 return @ldap_free_result($result); 547 } 548 if (!isset($this->prepare_tokens[(int)$result])) { 549 return false; 550 } 551 unset($this->prepare_tokens[(int)$result]); 552 unset($this->prepare_types[(int)$result]); 553 unset($this->prepared_queries[(int)$result]); 554 unset($this->row[(int)$result]); 555 unset($this->num_rows[(int)$result]); 556 unset($this->entry[(int)$result]); 557 $this->affected = 0; 558 $this->last_param = $this->param; 559 $this->attributes = null; 560 $this->sorting = ''; 561 return true; 562 } 563 564 // }}} 565 // {{{ quote() 566 567 /** 568 * Quote the given string so it can be safely used within string delimiters 569 * in a query. 570 * 571 * @param $string mixed Data to be quoted 572 * 573 * @return mixed "NULL" string, quoted string or original data 574 */ 575 function quote($str = null) 576 { 577 $str = str_replace(array('\\', '"'), array('\\\\', '\\"'), $str); 578 return $str; 579 } 580 581 // }}} 582 // {{{ numCols() 583 584 /** 585 * Get the number of columns in a result set. This function 586 * is used only for compatibility reasons. 587 * 588 * @param $result resource LDAP result identifier 589 * 590 * @return int DB_ERROR_NOT_CAPABLE error code 591 */ 592 function numCols($result) 593 { 594 return $this->ldapRaiseError(DB_ERROR_NOT_CAPABLE); 595 } 596 597 // }}} 598 // {{{ numRows() 599 600 /** 601 * Get the number of rows in a result set. 602 * 603 * @param $result resource LDAP result identifier 604 * 605 * @return int the number of rows in $result 606 */ 607 function numRows($result) 608 { 609 $rows = @ldap_count_entries($this->connection, $result); 610 if ($rows === null) { 611 return $this->ldapRaiseError(); 612 } 613 return $rows; 614 } 615 616 // }}} 617 // {{{ errorNative() 618 619 /** 620 * Get the native error code of the last error (if any) that 621 * occured on the current connection. 622 * 623 * @return int native LDAP error code 624 */ 625 function errorNative() 626 { 627 return ldap_error($this->connection); 628 } 629 630 // }}} 631 // {{{ affectedRows() 632 633 /** 634 * Gets the number of rows affected by the last query. 635 * if the last query was a select, returns 0. 636 * 637 * @return int number of rows affected by the last query or DB_ERROR 638 */ 639 function affectedRows() 640 { 641 return $this->affected; 642 } 643 644 // }}} 645 // {{{ getTables() 646 647 /** 648 * @deprecated 649 */ 650 function getTables() 651 { 652 return $this->ldapRaiseError(DB_ERROR_NOT_CAPABLE); 653 } 654 655 // }}} 656 // {{{ getListOf() 657 658 /** 659 * Returns the query needed to get some backend info. This function is 660 * used only for compatibility reasons. 661 * 662 * @return int DB_ERROR_NOT_CAPABLE error code 663 */ 664 function getListOf($type) 665 { 666 return $this->ldapRaiseError(DB_ERROR_NOT_CAPABLE); 667 } 668 669 // }}} 670 // {{{ isManip() 671 672 /** 673 * Tell whether an action is a data manipulation action (add, compare, 674 * delete, modify, mod_add, mod_del, mod_replace, rename) 675 * 676 * @param string $action the query 677 * 678 * @return boolean whether $query is a data manipulation action 679 */ 680 function isManip($action) 681 { 682 return(in_array($action, $this->action_manip)); 683 } 684 685 // }}} 686 // {{{ base() 687 688 /** 689 * @deprecated 690 */ 691 function base($base_dn = null) 692 { 693 $this->q_base_dn = ($base_dn !== null) ? $base_dn : null; 694 return true; 695 } 696 697 // }}} 698 // {{{ ldapSetBaseDN() 699 700 /** 701 * @deprecated 702 */ 703 function ldapSetBaseDN($base_dn = null) 704 { 705 $this->base_dn = ($base_dn !== null) ? $base_dn : $this->d_base_dn; 706 $this->q_base_dn = ''; 707 return true; 708 } 709 710 // }}} 711 // {{{ ldapSetAction() 712 713 /** 714 * @deprecated 715 */ 716 function ldapSetAction($action = 'search') 717 { 718 $this->action = $action; 719 $this->q_action = ''; 720 return true; 721 } 722 723 // }}} 724 // {{{ nextId() 725 726 /** 727 * Get the next value in a sequence. 728 * 729 * LDAP provides transactions for only one entry and we need to 730 * prevent race condition. If unique value before and after modify 731 * aren't equal then wait and try again. 732 * 733 * @param string $seq_name the sequence name 734 * @param bool $ondemand whether to create the sequence on demand 735 * 736 * @return a sequence integer, or a DB error 737 */ 738 function nextId($seq_name, $ondemand = true) 739 { 740 $seq_dn = $this->getSequenceName($seq_name); 741 $repeat = 0; 742 do { 743 // Get the sequence entry 744 $this->expectError(DB_ERROR_NOSUCHTABLE); 745 $data = $this->getRow(array('objectClass=*', 'action'=>'read', 'base_dn'=>$seq_dn)); 746 $this->popExpect(); 747 748 if (DB::isError($data)) { 749 if ($ondemand && $repeat == 0 750 && $data->getCode() == DB_ERROR_NOSUCHTABLE) { 751 // Try to create sequence and repeat 752 $repeat = 1; 753 $data = $this->createSequence($seq_name); 754 if (DB::isError($data)) { 755 return $this->ldapRaiseError($data); 756 } 757 } else { 758 // Other error 759 return $this->ldapRaiseError($data); 760 } 761 } else { 762 // Increment sequence value 763 $data['cn']++; 764 // Unique identificator of transaction 765 $seq_unique = mt_rand(); 766 $data['uid'] = $seq_unique; 767 // Modify the LDAP entry 768 $data = $this->simpleQuery(array($data, 'action'=>'modify')); 769 if (DB::isError($data)) { 770 return $this->ldapRaiseError($data); 771 } 772 // Get the entry and check if it contains our unique value 773 $data = $this->getRow(array('objectClass=*', 'action'=>'read', 'base_dn'=>$seq_dn)); 774 if (DB::isError($data)) { 775 return $this->ldapRaiseError($data); 776 } 777 if ($data['uid'] != $seq_unique) { 778 // It is not our entry. Wait a little time and repeat 779 sleep(1); 780 $repeat = 1; 781 } else { 782 $repeat = 0; 783 } 784 } 785 } while ($repeat); 786 787 if (DB::isError($data)) { 788 return $data; 789 } 790 return $data['cn']; 791 } 792 793 // }}} 794 // {{{ createSequence() 795 796 /** 797 * Create the sequence 798 * 799 * The sequence entry is based on core schema with extensibleObject, 800 * so it should work with any LDAP server which doesn't check schema 801 * or supports extensibleObject object class. 802 * 803 * Format of the entry: 804 * 805 * dn: $seq_dn 806 * objectClass: top 807 * objectClass: extensibleObject 808 * sn: $seq_id 809 * cn: $seq_value 810 * uid: $seq_uniq 811 * 812 * @param string $seq_name the sequence name 813 * 814 * @return mixed DB_OK on success or DB error on error 815 */ 816 function createSequence($seq_name) 817 { 818 $seq_dn = $this->getSequenceName($seq_name); 819 820 // Create the sequence entry 821 $data = array( 822 'dn' => $seq_dn, 823 'objectclass' => array('top', 'extensibleObject'), 824 'sn' => $seq_name, 825 'cn' => 0, 826 'uid' => 0 827 ); 828 829 // Add the LDAP entry 830 $data = $this->simpleQuery(array($data, 'action'=>'add')); 831 return $data; 832 } 833 834 // }}} 835 // {{{ dropSequence() 836 837 /** 838 * Drop a sequence 839 * 840 * @param string $seq_name the sequence name 841 * 842 * @return mixed DB_OK on success or DB error on error 843 */ 844 function dropSequence($seq_name) 845 { 846 $seq_dn = $this->getSequenceName($seq_name); 847 848 // Delete the sequence entry 849 $data = array( 850 'dn' => $seq_dn, 851 ); 852 $data = $this->simpleQuery(array($data, 'action'=>'delete')); 853 return $data; 854 } 855 856 // }}} 857 // {{{ ldapRaiseError() 858 859 /** 860 * Generate error message for LDAP errors. 861 * 862 * @param int $errno error number 863 * 864 * @return mixed DB_OK on success or DB error on error 865 */ 866 function ldapRaiseError($errno = null) 867 { 868 if ($errno === null) { 869 $errno = $this->errorCode(ldap_errno($this->connection)); 870 } 871 if ($this->last_param['action'] !== null) { 872 return $this->raiseError($errno, null, null, 873 sprintf('%s base="%s" filter="%s"', 874 $this->last_param['action'] ? $this->last_param['action'] : $this->param['action'], 875 $this->last_param['base_dn'] ? $this->last_param['base_dn'] : $this->param['base_dn'], 876 is_array($this->last_query) ? "" : $this->last_query 877 ), 878 $errno == @ldap_error($this->connection) 879 ); 880 } else { 881 return $this->raiseError($errno, null, null, "???", 882 @ldap_error($this->connection)); 883 } 884 } 885 886 // }}} 887 // {{{ prepare() 888 889 890 /** 891 * Prepares a query for multiple execution with execute(). 892 * This behaviour is emulated for LDAP backend. 893 * prepare() requires a generic query as an array with special 894 * characters (wildcards) as values. 895 * 896 * Types of wildcards: 897 * ? - a quoted scalar value, i.e. strings, integers 898 * & - requires a file name, the content of the file 899 * insert into the query (i.e. saving binary data 900 * in a db) 901 * ! - value is inserted 'as is' 902 * 903 * Example: 904 * 905 * $sth = $dbh->prepare( 906 * array( 907 * array( 908 * 'dn' => '?', 909 * 'objectClass' => '?', 910 * 'cn' => '?', 911 * 'sn' => '?', 912 * 'description' => '&' 913 * ), 914 * 'action' => 'add' 915 * ); 916 * ); 917 * 918 * $sigfile = "/home/dexter/.signature"; 919 * $res = $dbh->execute($sth, array( 920 * 'cn=Piotr Roszatycki,dc=example,dc=com', 921 * array('top', 'person'), 922 * 'Piotr Roszatycki', 'Roszatycki', $sigfile 923 * )); 924 * 925 * @param mixed the query to prepare 926 * 927 * @return resource handle for the query 928 * 929 * @see execute 930 */ 931 function prepare($query) 932 { 933 if (!is_array($query)) { 934 return parent::prepare($query); 935 } elseif (is_array($query) && isset($query[0]) && 936 !$this->isManip(isset($query['action']) ? $query['action'] : $this->param['action']) 937 ) { 938 $filter = $query[0]; 939 $tokens = preg_split("/[\&\?\!]/", $filter); 940 $token = 0; 941 $types = array(); 942 943 for ($i = 0; $i < strlen($filter); $i++) { 944 switch ($filter[$i]) { 945 case '?': 946 $types[$token++] = DB_PARAM_SCALAR; 947 break; 948 case '&': 949 $types[$token++] = DB_PARAM_OPAQUE; 950 break; 951 case '!': 952 $types[$token++] = DB_PARAM_MISC; 953 break; 954 } 955 } 956 957 $this->prepare_tokens[] = &$tokens; 958 end($this->prepare_tokens); 959 960 $k = key($this->prepare_tokens); 961 $this->prepare_types[$k] = $types; 962 $this->prepared_queries[$k] = &$query; 963 964 return $k; 965 } elseif(is_array($query) && isset($query[0]) && is_array($query[0])) { 966 $tokens = array(); 967 $types = array(); 968 969 foreach ($query[0] as $k=>$v) { 970 $tokens[$k] = $v; 971 switch ($v) { 972 case '?': 973 $types[$k] = DB_PARAM_SCALAR; 974 break; 975 case '&': 976 $types[$k] = DB_PARAM_OPAQUE; 977 break; 978 case '!': 979 $types[$k] = DB_PARAM_MISC; 980 break; 981 default: 982 $types[$k] = null; 983 } 984 } 985 986 $this->prepare_tokens[] = &$tokens; 987 end($this->prepare_tokens); 988 989 $k = key($this->prepare_tokens); 990 $this->prepare_types[$k] = $types; 991 $this->prepared_queries[$k] = &$query; 992 993 return $k; 994 } else { 995 return parent::prepare($query); 996 } 997 } 998 999 // }}} 1000 // {{{ executeEmulateQuery() 1001 1002 /** 1003 * Emulates the execute statement. 1004 * 1005 * @param resource $stmt query handle from prepare() 1006 * @param array $data numeric array containing the 1007 * data to insert into the query 1008 * 1009 * @return mixed an array containing the real query run when emulating 1010 * prepare/execute. A DB error code is returned on failure. 1011 * 1012 * @see execute() 1013 */ 1014 function executeEmulateQuery($stmt, $data = false) 1015 { 1016 $query = &$this->prepared_queries[$stmt]; 1017 1018 if (!is_array($query)) { 1019 return parent::executeEmulateQuery($stmt, $data); 1020 } elseif (is_array($query) && isset($query[0]) && 1021 !$this->isManip(isset($query['action']) ? $query['action'] : $this->param['action']) 1022 ) { 1023 $p = &$this->prepare_tokens; 1024 1025 if (!isset($this->prepare_tokens[$stmt]) || 1026 !is_array($this->prepare_tokens[$stmt]) || 1027 !sizeof($this->prepare_tokens[$stmt])) 1028 { 1029 return $this->raiseError(DB_ERROR_INVALID); 1030 } 1031 1032 $qq = &$this->prepare_tokens[$stmt]; 1033 $qp = sizeof($qq) - 1; 1034 1035 if ((!$data && $qp > 0) || 1036 (!is_array($data) && $qp > 1) || 1037 (is_array($data) && $qp > sizeof($data))) 1038 { 1039 $this->last_query = $this->prepared_queries[$stmt]; 1040 return $this->raiseError(DB_ERROR_NEED_MORE_DATA); 1041 } 1042 1043 $realquery = $query; 1044 $realquery[0] = $qq[0]; 1045 for ($i = 0; $i < $qp; $i++) { 1046 $type = $this->prepare_types[$stmt][$i]; 1047 if ($type == DB_PARAM_OPAQUE) { 1048 if (is_array($data)) { 1049 $fp = fopen($data[$i], 'r'); 1050 } else { 1051 $fp = fopen($data, 'r'); 1052 } 1053 1054 $pdata = ''; 1055 1056 if ($fp) { 1057 while (($buf = fread($fp, 4096)) != false) { 1058 $pdata .= $buf; 1059 } 1060 } 1061 } else { 1062 if (is_array($data)) { 1063 $pdata = &$data[$i]; 1064 } else { 1065 $pdata = &$data; 1066 } 1067 } 1068 1069 $realquery[0] .= ($type != DB_PARAM_MISC) ? $this->quote($pdata) : $pdata; 1070 $realquery[0] .= $qq[$i + 1]; 1071 } 1072 1073 return $realquery; 1074 1075 } elseif(is_array($query) && isset($query[0]) && is_array($query[0])) { 1076 1077 $p = &$this->prepare_tokens; 1078 1079 if (!isset($this->prepare_tokens[$stmt]) || 1080 !is_array($this->prepare_tokens[$stmt]) || 1081 !sizeof($this->prepare_tokens[$stmt])) 1082 { 1083 return $this->raiseError(DB_ERROR_INVALID); 1084 } 1085 1086 $qq = &$this->prepare_tokens[$stmt]; 1087 $realquery = $query; 1088 1089 $i = 0; 1090 foreach ($qq as $k=>$v) { 1091 $type = $this->prepare_types[$stmt][$k]; 1092 1093 if ($type !== null) { 1094 1095 if (!isset($data) || 1096 (is_array($data) && !isset($data[$i])) 1097 ) { 1098 $this->last_query = $this->prepared_queries[$stmt]; 1099 return $this->raiseError(DB_ERROR_NEED_MORE_DATA); 1100 } 1101 1102 if ($type == DB_PARAM_OPAQUE) { 1103 if (is_array($data)) { 1104 $fp = fopen($data[$i++], 'r'); 1105 } else { 1106 $fp = fopen($data, 'r'); 1107 } 1108 1109 $pdata = ''; 1110 1111 if ($fp) { 1112 while (($buf = fread($fp, 4096)) != false) { 1113 $pdata .= $buf; 1114 } 1115 } 1116 } elseif ($type !== null) { 1117 if (is_array($data)) { 1118 $pdata = &$data[$i++]; 1119 } else { 1120 $pdata = &$data; 1121 } 1122 } 1123 1124 $realquery[0][$k] = $pdata; 1125 } 1126 } 1127 1128 return $realquery; 1129 1130 } else { 1131 return parent::executeEmulateQuery($stmt, $data); 1132 } 1133 } 1134 1135 // }}} 1136 // {{{ ldapSetParam() 1137 1138 /** 1139 * Sets the default parameters for query. 1140 * 1141 * @param string $param the name of parameter for search actions (action, 1142 * base_dn, attributes, attrsonly, sizelimit, timelimit, deref) or 1143 * modify actions (action, attribute, value, newrdn, newparent, 1144 * deleteoldrdn). 1145 * @param string $value the value of parameter 1146 * 1147 * @return mixed DB_OK on success or DB error on error 1148 * 1149 * @see ldapGetParam() 1150 */ 1151 function ldapSetParam($param, $value) 1152 { 1153 if (isset($this->param[$param])) { 1154 $this->param[$param] = $value; 1155 return DB_OK; 1156 } 1157 return $this->raiseError("unknown LDAP parameter $param"); 1158 } 1159 1160 // }}} 1161 // {{{ ldapGetParam() 1162 1163 /** 1164 * Gets the default parameters for query. 1165 * 1166 * @param string $param the name of parameter for search or modify 1167 * actions. 1168 * 1169 * @return mixed value of parameter on success or DB error on error 1170 * 1171 * @see ldapSetParam() 1172 */ 1173 function ldapGetParam($param) 1174 { 1175 if (isset($this->param[$param])) { 1176 return $this->param[$param]; 1177 } 1178 return $this->raiseError("unknown LDAP parameter $param"); 1179 } 1180 1181 // }}} 1182 // {{{ ldapSetOption() 1183 1184 /** 1185 * Sets the value of the given option. 1186 * 1187 * @param int $option the specified option 1188 * @param mixed $newval the value of specified option 1189 * 1190 * @return bool DB_OK on success or DB error on error 1191 * 1192 * @see ldapGetOption() 1193 */ 1194 function ldapSetOption($option, $newval) 1195 { 1196 if (@ldap_set_option($this->connection, $option, $newval)) { 1197 return DB_OK; 1198 } 1199 return $this->raiseError("failed to set LDAP option"); 1200 } 1201 1202 // }}} 1203 // {{{ ldapGetOption() 1204 1205 /** 1206 * Gets the current value for given option. 1207 * 1208 * @param int $option the specified option 1209 * @param mixed $retval (reference) the new value of specified option 1210 * 1211 * @return bool DB_OK on success or DB error on error 1212 * 1213 * @see ldapSetOption() 1214 */ 1215 function ldapGetOption($option, &$retval) 1216 { 1217 if (@ldap_get_option($this->connection, $option, $retval)) { 1218 return DB_OK; 1219 } 1220 return $this->raiseError("failed to get LDAP option"); 1221 } 1222 1223 // }}} 1224 // {{{ ldapExplodeDN() 1225 1226 /** 1227 * Splits the DN and breaks it up into its component parts. 1228 * Each part is known as Relative Distinguished Name, or RDN. 1229 * 1230 * @param string $dn the DN to split 1231 * @param int $with_attrib 0 to get RDNs with the attributes 1232 * or 1 to get only values. 1233 * 1234 * @return array an array of all those components 1235 */ 1236 function ldapExplodeDN($dn, $with_attrib = 0) 1237 { 1238 $arr = ldap_explode_dn($dn, $with_attrib ? 1 : 0); 1239 unset($arr['count']); 1240 return $arr; 1241 } 1242 1243} 1244 1245?> 1246