PageRenderTime 148ms CodeModel.GetById 80ms app.highlight 37ms RepoModel.GetById 23ms app.codeStats 1ms

/library/Zend/Ldap.php

https://bitbucket.org/baruffaldi/website-2008-computer-shopping-3
PHP | 759 lines | 553 code | 49 blank | 157 comment | 48 complexity | 1c6307f998b16f45fe51b2699561f527 MD5 | raw file
  1<?php
  2
  3/**
  4 * Zend Framework
  5 *
  6 * LICENSE
  7 *
  8 * This source file is subject to the new BSD license that is bundled
  9 * with this package in the file LICENSE.txt.
 10 * It is also available through the world-wide-web at this URL:
 11 * http://framework.zend.com/license/new-bsd
 12 * If you did not receive a copy of the license and are unable to
 13 * obtain it through the world-wide-web, please send an email
 14 * to license@zend.com so we can send you a copy immediately.
 15 *
 16 * @category   Zend
 17 * @package    Zend_Ldap
 18 * @copyright  Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com)
 19 * @license    http://framework.zend.com/license/new-bsd     New BSD License
 20 * @version    $Id: Ldap.php 11765 2008-10-09 01:53:43Z miallen $
 21 */
 22
 23
 24/**
 25 * @category   Zend
 26 * @package    Zend_Ldap
 27 * @copyright  Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com)
 28 * @license    http://framework.zend.com/license/new-bsd     New BSD License
 29 */
 30class Zend_Ldap
 31{
 32
 33    const ACCTNAME_FORM_DN        = 1;
 34    const ACCTNAME_FORM_USERNAME  = 2;
 35    const ACCTNAME_FORM_BACKSLASH = 3;
 36    const ACCTNAME_FORM_PRINCIPAL = 4;
 37
 38    /**
 39     * String used with ldap_connect for error handling purposes.
 40     *
 41     * @var string
 42     */
 43    private $_connectString;
 44
 45    /**
 46     * The raw LDAP extension resource.
 47     *
 48     * @var resource
 49     */
 50    protected $_resource = null;
 51
 52    /**
 53     * @param  string $str The string to escape.
 54     * @return string The escaped string
 55     */
 56    public static function filterEscape($str)
 57    {
 58        $ret = '';
 59        $len = strlen($str);
 60        for ($si = 0; $si < $len; $si++) {
 61            $ch = $str[$si];
 62            $ord = ord($ch);
 63            if ($ord < 0x20 || $ord > 0x7e || strstr('*()\/', $ch)) {
 64                $ch = '\\' . dechex($ord);
 65            }
 66            $ret .= $ch;
 67        }
 68        return $ret;
 69    }
 70
 71    /**
 72     * @param  string $dn   The DN to parse
 73     * @param  array  $keys An optional array to receive DN keys (e.g. CN, OU, DC, ...)
 74     * @param  array  $vals An optional array to receive DN values
 75     * @return bool   True if the DN was successfully parsed or false if the string is not a valid DN.
 76     */
 77    public static function explodeDn($dn, array &$keys = null, array &$vals = null)
 78    {
 79        /* This is a classic state machine parser. Each iteration of the
 80         * loop processes one character. State 1 collects the key. When equals (=)
 81         * is encountered the state changes to 2 where the value is collected
 82         * until a comma (,) or semicolon (;) is encountered after which we switch back
 83         * to state 1. If a backslash (\) is encountered, state 3 is used to collect the
 84         * following character without engaging the logic of other states.
 85         */
 86        $key = null;
 87        $slen = strlen($dn);
 88        $state = 1;
 89        $ko = $vo = 0;
 90        for ($di = 0; $di <= $slen; $di++) {
 91            $ch = $di == $slen ? 0 : $dn[$di];
 92            switch ($state) {
 93                case 1: // collect key
 94                    if ($ch === '=') {
 95                        $key = trim(substr($dn, $ko, $di - $ko));
 96                        if ($keys !== null) {
 97                            $keys[] = $key; 
 98                        }
 99                        $state = 2;
100                        $vo = $di + 1;
101                    } else if ($ch === ',' || $ch === ';') {
102                        return false;
103                    }
104                    break;
105                case 2: // collect value
106                    if ($ch === '\\') {
107                        $state = 3;
108                    } else if ($ch === ',' || $ch === ';' || $ch === 0) {
109                        if ($vals !== null) {
110                            $vals[] = trim(substr($dn, $vo, $di - $vo));
111                        }
112                        $state = 1;
113                        $ko = $di + 1;
114                    } else if ($ch === '=') {
115                        return false;
116                    }
117                    break;
118                case 3: // escaped
119                    $state = 2;
120                    break;
121            }
122        }
123
124        return $state === 1 && $ko > 0; 
125    }
126
127    /**
128     * @param  array $options Options used in connecting, binding, etc.
129     * @return void
130     */
131    public function __construct(array $options = array())
132    {
133        $this->setOptions($options);
134    }
135
136    /**
137     * Sets the options used in connecting, binding, etc.
138     *
139     * Valid option keys:
140     *  host
141     *  port
142     *  useSsl
143     *  username
144     *  password
145     *  bindRequiresDn
146     *  baseDn
147     *  accountCanonicalForm
148     *  accountDomainName
149     *  accountDomainNameShort
150     *  accountFilterFormat
151     *  allowEmptyPassword
152     *  useStartTls
153     *  optRefferals
154     *
155     * @param  array $options Options used in connecting, binding, etc.
156     * @return Zend_Ldap Provides a fluent interface
157     * @throws Zend_Ldap_Exception
158     */
159    public function setOptions(array $options)
160    {
161        $permittedOptions = array(
162            'host'                      => null,
163            'port'                      => null,
164            'useSsl'                    => null,
165            'username'                  => null,
166            'password'                  => null,
167            'bindRequiresDn'            => null,
168            'baseDn'                    => null,
169            'accountCanonicalForm'      => null,
170            'accountDomainName'         => null,
171            'accountDomainNameShort'    => null,
172            'accountFilterFormat'       => null,
173            'allowEmptyPassword'        => null,
174            'useStartTls'               => null,
175            'optReferrals'              => null,
176        );
177
178        $diff = array_diff_key($options, $permittedOptions);
179        if ($diff) {
180            list($key, $val) = each($diff);
181            require_once 'Zend/Ldap/Exception.php';
182            throw new Zend_Ldap_Exception(null, "Unknown Zend_Ldap option: $key");
183        }
184
185        foreach ($permittedOptions as $key => $val) {
186            if (!array_key_exists($key, $options)) {
187                $options[$key] = null;
188            } else {
189                /* Enforce typing. This eliminates issues like Zend_Config_Ini
190                 * returning '1' as a string (ZF-3163).
191                 */
192                switch ($key) {
193                    case 'port':
194                    case 'accountCanonicalForm':
195                        $options[$key] = (int)$options[$key];
196                        break;
197                    case 'useSsl':
198                    case 'bindRequiresDn':
199                    case 'allowEmptyPassword':
200                    case 'useStartTls':
201                    case 'optReferrals':
202                        $val = $options[$key];
203                        $options[$key] = $val === true ||
204                                $val === '1' ||
205                                strcasecmp($val, 'true') == 0;
206                        break;
207                }
208            }
209        }
210
211        $this->_options = $options;
212
213        return $this;
214    }
215
216    /**
217     * @return array The current options.
218     */
219    public function getOptions()
220    {
221        return $this->_options;
222    }
223
224    /**
225     * @return resource The raw LDAP extension resource.
226     */
227    public function getResource()
228    {
229        /**
230         * @todo by reference?
231         */
232        return $this->_resource;
233    }
234
235    /**
236     * @return string The hostname of the LDAP server being used to authenticate accounts
237     */
238    protected function _getHost()
239    {
240        return $this->_options['host'];
241    }
242
243    /**
244     * @return int The port of the LDAP server or 0 to indicate that no port value is set
245     */
246    protected function _getPort()
247    {
248        if ($this->_options['port'])
249            return $this->_options['port'];
250        return 0;
251    }
252
253    /**
254     * @return string The default acctname for binding
255     */
256    protected function _getUsername()
257    {
258        return $this->_options['username'];
259    }
260
261    /**
262     * @return string The default password for binding
263     */
264    protected function _getPassword()
265    {
266        return $this->_options['password'];
267    }
268
269    /**
270     * @return boolean The default SSL / TLS encrypted transport control
271     */
272    protected function _getUseSsl()
273    {
274        return $this->_options['useSsl'];
275    }
276
277    /**
278     * @return string The default base DN under which objects of interest are located
279     */
280    protected function _getBaseDn()
281    {
282        return $this->_options['baseDn'];
283    }
284
285    /**
286     * @return string Either ACCTNAME_FORM_BACKSLASH, ACCTNAME_FORM_PRINCIPAL or ACCTNAME_FORM_USERNAME indicating the form usernames should be canonicalized to.
287     */
288    protected function _getAccountCanonicalForm()
289    {
290        /* Account names should always be qualified with a domain. In some scenarios
291         * using non-qualified account names can lead to security vulnerabilities. If
292         * no account canonical form is specified, we guess based in what domain
293         * names have been supplied.
294         */
295
296        $accountCanonicalForm = $this->_options['accountCanonicalForm'];
297        if (!$accountCanonicalForm) {
298            $accountDomainName = $this->_options['accountDomainName'];
299            $accountDomainNameShort = $this->_options['accountDomainNameShort'];
300            if ($accountDomainNameShort) {
301                $accountCanonicalForm = Zend_Ldap::ACCTNAME_FORM_BACKSLASH;
302            } else if ($accountDomainName) {
303                $accountCanonicalForm = Zend_Ldap::ACCTNAME_FORM_PRINCIPAL;
304            } else {
305                $accountCanonicalForm = Zend_Ldap::ACCTNAME_FORM_USERNAME;
306            }
307        }
308
309        return $accountCanonicalForm;
310    }
311
312
313    /**
314     * @return string A format string for building an LDAP search filter to match an account
315     */
316    protected function _getAccountFilterFormat()
317    {
318        return $this->_options['accountFilterFormat'];
319    }
320
321    /**
322     * @return string The LDAP search filter for matching directory accounts
323     */
324    protected function _getAccountFilter($acctname)
325    {
326        $this->_splitName($acctname, $dname, $aname);
327        $accountFilterFormat = $this->_getAccountFilterFormat();
328        $aname = Zend_Ldap::filterEscape($aname);
329        if ($accountFilterFormat)
330            return sprintf($accountFilterFormat, $aname);
331        if (!$this->_options['bindRequiresDn']) {
332            // is there a better way to detect this?
333            return "(&(objectClass=user)(sAMAccountName=$aname))";
334        }
335        return "(&(objectClass=posixAccount)(uid=$aname))";
336    }
337
338    /**
339     * @param string $name The name to split
340     * @param string $dname The resulting domain name (this is an out parameter)
341     * @param string $aname The resulting account name (this is an out parameter)
342     */
343    protected function _splitName($name, &$dname, &$aname)
344    {
345        $dname = NULL;
346        $aname = $name;
347
348        $pos = strpos($name, '@');
349        if ($pos) {
350            $dname = substr($name, $pos + 1);
351            $aname = substr($name, 0, $pos);
352        } else {
353            $pos = strpos($name, '\\');
354            if ($pos) {
355                $dname = substr($name, 0, $pos);
356                $aname = substr($name, $pos + 1);
357            }
358        }
359    }
360
361    /**
362     * @param string $acctname The name of the account
363     * @return string The DN of the specified account
364     * @throws Zend_Ldap_Exception
365     */
366    protected function _getAccountDn($acctname)
367    {
368        if (Zend_Ldap::explodeDn($acctname))
369            return $acctname;
370        $acctname = $this->getCanonicalAccountName($acctname, Zend_Ldap::ACCTNAME_FORM_USERNAME);
371        $acct = $this->_getAccount($acctname, array('dn'));
372        return $acct['dn'];
373    }
374
375    /**
376     * @param string $dname The domain name to check
377     * @return bool
378     */
379    protected function _isPossibleAuthority($dname)
380    {
381        if ($dname === null)
382            return true;
383        $accountDomainName = $this->_options['accountDomainName'];
384        $accountDomainNameShort = $this->_options['accountDomainNameShort'];
385        if ($accountDomainName === null && $accountDomainNameShort === null)
386            return true;
387        if (strcasecmp($dname, $accountDomainName) == 0)
388            return true;
389        if (strcasecmp($dname, $accountDomainNameShort) == 0)
390            return true;
391        return false;
392    }
393
394    /**
395     * @param string $acctname The name to canonicalize
396     * @param int $type The desired form of canonicalization
397     * @return string The canonicalized name in the desired form
398     * @throws Zend_Ldap_Exception
399     */
400    public function getCanonicalAccountName($acctname, $form = 0)
401    {
402        $this->_splitName($acctname, $dname, $uname);
403
404        if (!$this->_isPossibleAuthority($dname)) {
405            /**
406             * @see Zend_Ldap_Exception
407             */
408            require_once 'Zend/Ldap/Exception.php';
409            throw new Zend_Ldap_Exception(null,
410                    "Binding domain is not an authority for user: $acctname",
411                    Zend_Ldap_Exception::LDAP_X_DOMAIN_MISMATCH);
412        }
413
414        if ($form === Zend_Ldap::ACCTNAME_FORM_DN)
415            return $this->_getAccountDn($acctname);
416
417        if (!$uname) {
418            /**
419             * @see Zend_Ldap_Exception
420             */
421            require_once 'Zend/Ldap/Exception.php';
422            throw new Zend_Ldap_Exception(null, "Invalid account name syntax: $acctname");
423        }
424
425        $uname = strtolower($uname);
426
427        if ($form === 0)
428            $form = $this->_getAccountCanonicalForm();
429
430        switch ($form) {
431            case Zend_Ldap::ACCTNAME_FORM_USERNAME:
432                return $uname;
433            case Zend_Ldap::ACCTNAME_FORM_BACKSLASH:
434                $accountDomainNameShort = $this->_options['accountDomainNameShort'];
435                if (!$accountDomainNameShort) {
436                    /**
437                     * @see Zend_Ldap_Exception
438                     */
439                    require_once 'Zend/Ldap/Exception.php';
440                    throw new Zend_Ldap_Exception(null, 'Option required: accountDomainNameShort');
441                }
442                return "$accountDomainNameShort\\$uname";
443            case Zend_Ldap::ACCTNAME_FORM_PRINCIPAL:
444                $accountDomainName = $this->_options['accountDomainName'];
445                if (!$accountDomainName) {
446                    /**
447                     * @see Zend_Ldap_Exception
448                     */
449                    require_once 'Zend/Ldap/Exception.php';
450                    throw new Zend_Ldap_Exception(null, 'Option required: accountDomainName');
451                }
452                return "$uname@$accountDomainName";
453            default:
454                /**
455                 * @see Zend_Ldap_Exception
456                 */
457                require_once 'Zend/Ldap/Exception.php';
458                throw new Zend_Ldap_Exception(null, "Unknown canonical name form: $form");
459        }
460    }
461
462    /**
463     * @param array $attrs An array of names of desired attributes
464     * @return array An array of the attributes representing the account
465     * @throws Zend_Ldap_Exception
466     */
467    private function _getAccount($acctname, array $attrs = null)
468    {
469        $baseDn = $this->_getBaseDn();
470        if (!$baseDn) {
471            /**
472             * @see Zend_Ldap_Exception
473             */
474            require_once 'Zend/Ldap/Exception.php';
475            throw new Zend_Ldap_Exception(null, 'Base DN not set');
476        }
477
478        $accountFilter = $this->_getAccountFilter($acctname);
479        if (!$accountFilter) {
480            /**
481             * @see Zend_Ldap_Exception
482             */
483            require_once 'Zend/Ldap/Exception.php';
484            throw new Zend_Ldap_Exception(null, 'Invalid account filter');
485        }
486
487        if (!is_resource($this->_resource))
488            $this->bind();
489
490        $resource = $this->_resource;
491        $str = $accountFilter;
492        $code = 0;
493
494        /**
495         * @todo break out search operation into simple function (private for now)
496         */
497
498        if (!extension_loaded('ldap')) {
499            /**
500             * @see Zend_Ldap_Exception
501             */
502            require_once 'Zend/Ldap/Exception.php';
503            throw new Zend_Ldap_Exception(null, 'LDAP extension not loaded');
504        }
505
506        $result = @ldap_search($resource,
507                        $baseDn,
508                        $accountFilter,
509                        $attrs);
510        if (is_resource($result) === true) {
511            $count = @ldap_count_entries($resource, $result);
512            if ($count == 1) {
513                $entry = @ldap_first_entry($resource, $result);
514                if ($entry) {
515                    $acct = array('dn' => @ldap_get_dn($resource, $entry));
516                    $name = @ldap_first_attribute($resource, $entry, $berptr);
517                    while ($name) {
518                        $data = @ldap_get_values_len($resource, $entry, $name);
519                        $acct[$name] = $data;
520                        $name = @ldap_next_attribute($resource, $entry, $berptr);
521                    }
522                    @ldap_free_result($result);
523                    return $acct;
524                }
525            } else if ($count == 0) {
526                /**
527                 * @see Zend_Ldap_Exception
528                 */
529                require_once 'Zend/Ldap/Exception.php';
530                $code = Zend_Ldap_Exception::LDAP_NO_SUCH_OBJECT;
531            } else {
532
533                /**
534                 * @todo limit search to 1 record and remove some of this logic?
535                 */
536
537                $resource = null;
538                $str = "$accountFilter: Unexpected result count: $count";
539                /**
540                 * @see Zend_Ldap_Exception
541                 */
542                require_once 'Zend/Ldap/Exception.php';
543                $code = Zend_Ldap_Exception::LDAP_OPERATIONS_ERROR;
544            }
545            @ldap_free_result($result);
546        }
547
548        /**
549         * @see Zend_Ldap_Exception
550         */
551        require_once 'Zend/Ldap/Exception.php';
552        throw new Zend_Ldap_Exception($resource, $str, $code);
553    }
554
555    /**
556     * @return Zend_Ldap Provides a fluent interface
557     */
558    public function disconnect()
559    {
560        if (is_resource($this->_resource)) {
561            if (!extension_loaded('ldap')) {
562                /**
563                 * @see Zend_Ldap_Exception
564                 */
565                require_once 'Zend/Ldap/Exception.php';
566                throw new Zend_Ldap_Exception(null, 'LDAP extension not loaded');
567            }
568            @ldap_unbind($this->_resource);
569        }
570        $this->_resource = null;
571        return $this;
572    }
573
574    /**
575     * @param string $host The hostname of the LDAP server to connect to
576     * @param int $port The port number of the LDAP server to connect to
577     * @return Zend_Ldap Provides a fluent interface
578     * @throws Zend_Ldap_Exception
579     */
580    public function connect($host = null, $port = 0, $useSsl = false)
581    {
582        if ($host === null)
583            $host = $this->_getHost();
584        if ($port === 0)
585            $port = $this->_getPort();
586        if ($useSsl === false)
587            $useSsl = $this->_getUseSsl();
588
589        if (!$host) {
590            /**
591             * @see Zend_Ldap_Exception
592             */
593            require_once 'Zend/Ldap/Exception.php';
594            throw new Zend_Ldap_Exception(null, 'A host parameter is required');
595        }
596
597        /* To connect using SSL it seems the client tries to verify the server
598         * certificate by default. One way to disable this behavior is to set
599         * 'TLS_REQCERT never' in OpenLDAP's ldap.conf and restarting Apache. Or,
600         * if you really care about the server's cert you can put a cert on the
601         * web server.
602         */
603        $url = $useSsl ? "ldaps://$host" : "ldap://$host";
604        if ($port) {
605            $url .= ":$port";
606        }
607
608        /* Because ldap_connect doesn't really try to connect, any connect error
609         * will actually occur during the ldap_bind call. Therefore, we save the
610         * connect string here for reporting it in error handling in bind().
611         */
612        $this->_connectString = $url;
613
614        $this->disconnect();
615
616        if (!extension_loaded('ldap')) {
617            /**
618             * @see Zend_Ldap_Exception
619             */
620            require_once 'Zend/Ldap/Exception.php';
621            throw new Zend_Ldap_Exception(null, 'LDAP extension not loaded');
622        }
623
624        /* Only OpenLDAP 2.2 + supports URLs so if SSL is not requested, just
625         * use the old form.
626         */
627        $resource = $useSsl ? @ldap_connect($url) : @ldap_connect($host, $port);
628
629        if (is_resource($resource) === true) {
630
631            $this->_resource = $resource;
632
633            $optReferrals = $this->_options['optReferrals'] ? 1 : 0;
634
635            if (@ldap_set_option($resource, LDAP_OPT_PROTOCOL_VERSION, 3) &&
636                        @ldap_set_option($resource, LDAP_OPT_REFERRALS, $optReferrals)) {
637                if ($useSsl ||
638                            $this->_options['useStartTls'] !== true ||
639                            @ldap_start_tls($resource)) {
640                    return $this;
641                }
642            }
643
644            /**
645             * @see Zend_Ldap_Exception
646             */
647            require_once 'Zend/Ldap/Exception.php';
648
649            $zle = new Zend_Ldap_Exception($resource, "$host:$port");
650            $this->disconnect();
651            throw $zle;
652        }
653        /**
654         * @see Zend_Ldap_Exception
655         */
656        require_once 'Zend/Ldap/Exception.php';
657        throw new Zend_Ldap_Exception("Failed to connect to LDAP server: $host:$port");
658    }
659
660    /**
661     * @param string $username The username for authenticating the bind
662     * @param string $password The password for authenticating the bind
663     * @return Zend_Ldap Provides a fluent interface
664     * @throws Zend_Ldap_Exception
665     */
666    public function bind($username = null, $password = null)
667    {
668        $moreCreds = true;
669
670        if ($username === null) {
671            $username = $this->_getUsername();
672            $password = $this->_getPassword();
673            $moreCreds = false;
674        }
675
676        if ($username === NULL) {
677            /* Perform anonymous bind
678             */
679            $password = NULL;
680        } else {
681            /* Check to make sure the username is in DN form.
682             */
683            if (!Zend_Ldap::explodeDn($username)) {
684                if ($this->_options['bindRequiresDn']) {
685                    /* moreCreds stops an infinite loop if _getUsername does not
686                     * return a DN and the bind requires it
687                     */
688                    if ($moreCreds) {
689                        try {
690                            $username = $this->_getAccountDn($username);
691                        } catch (Zend_Ldap_Exception $zle) {
692                            /**
693                             * @todo Temporary measure to deal with exception thrown for ldap extension not loaded
694                             */
695                            if (strpos($zle->getMessage(), 'LDAP extension not loaded') !== false) {
696                                throw $zle;
697                            }
698                            // end temporary measure
699                            switch ($zle->getCode()) {
700                                case Zend_Ldap_Exception::LDAP_NO_SUCH_OBJECT:
701                                case Zend_Ldap_Exception::LDAP_X_DOMAIN_MISMATCH:
702                                    throw $zle;
703                            }
704                            throw new Zend_Ldap_Exception(null,
705                                        'Failed to retrieve DN for account: ' . $zle->getMessage(),
706                                        Zend_Ldap_Exception::LDAP_OPERATIONS_ERROR);
707                        }
708                    } else {
709                        /**
710                         * @see Zend_Ldap_Exception
711                         */
712                        require_once 'Zend/Ldap/Exception.php';
713                        throw new Zend_Ldap_Exception(null, 'Binding requires username in DN form');
714                    }
715                } else {
716                    $username = $this->getCanonicalAccountName($username,
717                                Zend_Ldap::ACCTNAME_FORM_PRINCIPAL);
718                }
719            }
720        }
721
722        if (!is_resource($this->_resource))
723            $this->connect();
724
725        if ($username !== null &&
726                    $password === '' &&
727                    $this->_options['allowEmptyPassword'] !== true) {
728            /**
729             * @see Zend_Ldap_Exception
730             */
731            require_once 'Zend/Ldap/Exception.php';
732
733            $zle = new Zend_Ldap_Exception(null,
734                    'Empty password not allowed - see allowEmptyPassword option.');
735        } else {
736            if (@ldap_bind($this->_resource, $username, $password))
737                return $this;
738
739            $message = $username === null ? $this->_connectString : $username;
740
741            /**
742             * @see Zend_Ldap_Exception
743             */
744            require_once 'Zend/Ldap/Exception.php';
745    
746            switch (Zend_Ldap_Exception::getLdapCode($this)) {
747                case Zend_Ldap_Exception::LDAP_SERVER_DOWN:
748                    /* If the error is related to establishing a connection rather than binding,
749                     * the connect string is more informative than the username.
750                     */
751                    $message = $this->_connectString;
752            }
753    
754            $zle = new Zend_Ldap_Exception($this->_resource, $message);
755        }
756        $this->disconnect();
757        throw $zle;
758    }
759}