~ubuntu-branches/ubuntu/maverick/php-net-sieve/maverick

« back to all changes in this revision

Viewing changes to Net_Sieve-1.1.6/Sieve.php

  • Committer: Bazaar Package Importer
  • Author(s): Federico Gimenez Nieto
  • Date: 2009-11-22 13:01:11 UTC
  • mfrom: (1.1.4 upstream) (3.1.2 squeeze)
  • Revision ID: james.westby@ubuntu.com-20091122130111-2422vwo7mxys0pz1
Tags: 1.1.7-1
* New maintainer (Closes: #529688)
* New upstream release (Closes: #544393)
* debian/control:
  - Fixed section (web->php)
  - Updated Standards Version: 3.8.3
  - Removed uploader at his request
  - Added ${misc:Depends} to Depends field
  - More detailed description
  - Removed php5-cli from Suggests field
* debian/copyright: 
  - Added new author and copyright owner
  - Added copyright notice for packagers
* debian/rules: prevent installation of empty directory 
* Added patch (following 3.0 (quilt) source format) to modify 
  Net_Sieve-1.1.7/Sieve.php in order to group all the import-related
  statements at the beginning of the file. Previously PEAR.php was 
  included in one particular method, but its functionality was used 
  in other places. It also modifies package.xml to fix the md5sum value 
  of Net_Sieve-1.1.7/Sieve.php
* Added debian/source/format with value "3.0 (quilt)"

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
<?php
2
 
// +-----------------------------------------------------------------------+
3
 
// | Copyright (c) 2002-2003, Richard Heyes                                |
4
 
// | Copyright (c) 2006, Anish Mistry                                      |
5
 
// | All rights reserved.                                                  |
6
 
// |                                                                       |
7
 
// | Redistribution and use in source and binary forms, with or without    |
8
 
// | modification, are permitted provided that the following conditions    |
9
 
// | are met:                                                              |
10
 
// |                                                                       |
11
 
// | o Redistributions of source code must retain the above copyright      |
12
 
// |   notice, this list of conditions and the following disclaimer.       |
13
 
// | o Redistributions in binary form must reproduce the above copyright   |
14
 
// |   notice, this list of conditions and the following disclaimer in the |
15
 
// |   documentation and/or other materials provided with the distribution.|
16
 
// | o The names of the authors may not be used to endorse or promote      |
17
 
// |   products derived from this software without specific prior written  |
18
 
// |   permission.                                                         |
19
 
// |                                                                       |
20
 
// | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS   |
21
 
// | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT     |
22
 
// | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
23
 
// | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT  |
24
 
// | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
25
 
// | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT      |
26
 
// | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
27
 
// | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
28
 
// | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT   |
29
 
// | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
30
 
// | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.  |
31
 
// |                                                                       |
32
 
// +-----------------------------------------------------------------------+
33
 
// | Author: Richard Heyes <richard@phpguru.org>                           |
34
 
// | Co-Author: Damian Fernandez Sosa <damlists@cnba.uba.ar>               |
35
 
// | Co-Author: Anish Mistry <amistry@am-productions.biz>                  |
36
 
// +-----------------------------------------------------------------------+
37
 
 
38
 
require_once('Net/Socket.php');
39
 
 
40
 
/**
41
 
* TODO
42
 
*
43
 
* o supportsAuthMech()
44
 
*/
45
 
 
46
 
/**
47
 
* Disconnected state
48
 
* @const NET_SIEVE_STATE_DISCONNECTED
49
 
*/
50
 
define('NET_SIEVE_STATE_DISCONNECTED',  1, true);
51
 
 
52
 
/**
53
 
* Authorisation state
54
 
* @const NET_SIEVE_STATE_AUTHORISATION
55
 
*/
56
 
define('NET_SIEVE_STATE_AUTHORISATION', 2, true);
57
 
 
58
 
/**
59
 
* Transaction state
60
 
* @const NET_SIEVE_STATE_TRANSACTION
61
 
*/
62
 
define('NET_SIEVE_STATE_TRANSACTION',   3, true);
63
 
 
64
 
/**
65
 
* A class for talking to the timsieved server which
66
 
* comes with Cyrus IMAP.
67
 
*
68
 
* SIEVE: RFC3028 http://www.ietf.org/rfc/rfc3028.txt
69
 
* MANAGE-SIEVE: http://www.ietf.org/internet-drafts/draft-martin-managesieve-07.txt
70
 
*
71
 
* @author  Richard Heyes <richard@php.net>
72
 
* @author  Damian Fernandez Sosa <damlists@cnba.uba.ar>
73
 
* @author  Anish Mistry <amistry@am-productions.biz>
74
 
* @access  public
75
 
* @version 1.2.0
76
 
* @package Net_Sieve
77
 
*/
78
 
 
79
 
class Net_Sieve
80
 
{
81
 
    /**
82
 
    * The socket object
83
 
    * @var object
84
 
    */
85
 
    var $_sock;
86
 
 
87
 
    /**
88
 
    * Info about the connect
89
 
    * @var array
90
 
    */
91
 
    var $_data;
92
 
 
93
 
    /**
94
 
    * Current state of the connection
95
 
    * @var integer
96
 
    */
97
 
    var $_state;
98
 
 
99
 
    /**
100
 
    * Constructor error is any
101
 
    * @var object
102
 
    */
103
 
    var $_error;
104
 
 
105
 
    /**
106
 
    * To allow class debuging
107
 
    * @var boolean
108
 
    */
109
 
    var $_debug = false;
110
 
 
111
 
    /**
112
 
    * Allows picking up of an already established connection
113
 
    * @var boolean
114
 
    */
115
 
    var $_bypassAuth = false;
116
 
 
117
 
    /**
118
 
    * Whether to use TLS if available
119
 
    * @var boolean
120
 
    */
121
 
    var $_useTLS = true;
122
 
 
123
 
    /**
124
 
    * The auth methods this class support
125
 
    * @var array
126
 
    */
127
 
    var $supportedAuthMethods=array('DIGEST-MD5', 'CRAM-MD5', 'PLAIN' , 'LOGIN');
128
 
    //if you have problems using DIGEST-MD5 authentication  please comment the line above and uncomment the following line
129
 
    //var $supportedAuthMethods=array( 'CRAM-MD5', 'PLAIN' , 'LOGIN');
130
 
 
131
 
    //var $supportedAuthMethods=array( 'PLAIN' , 'LOGIN');
132
 
 
133
 
    /**
134
 
    * The auth methods this class support
135
 
    * @var array
136
 
    */
137
 
    var $supportedSASLAuthMethods=array('DIGEST-MD5', 'CRAM-MD5');
138
 
 
139
 
    /**
140
 
    * Handles posible referral loops
141
 
    * @var array
142
 
    */
143
 
    var $_maxReferralCount = 15;
144
 
 
145
 
    /**
146
 
    * Constructor
147
 
    * Sets up the object, connects to the server and logs in. stores
148
 
    * any generated error in $this->_error, which can be retrieved
149
 
    * using the getError() method.
150
 
    *
151
 
    * @param  string $user      Login username
152
 
    * @param  string $pass      Login password
153
 
    * @param  string $host      Hostname of server
154
 
    * @param  string $port      Port of server
155
 
    * @param  string $logintype Type of login to perform
156
 
    * @param  string $euser     Effective User (if $user=admin, login as $euser)
157
 
    * @param  string $bypassAuth Skip the authentication phase.  Useful if the socket
158
 
                                  is already open.
159
 
    * @param  boolean $useTLS Use TLS if available
160
 
    */
161
 
    function Net_Sieve($user = null , $pass  = null , $host = 'localhost', $port = 2000, $logintype = '', $euser = '', $debug = false, $bypassAuth = false, $useTLS = true)
162
 
    {
163
 
        $this->_state = NET_SIEVE_STATE_DISCONNECTED;
164
 
        $this->_data['user'] = $user;
165
 
        $this->_data['pass'] = $pass;
166
 
        $this->_data['host'] = $host;
167
 
        $this->_data['port'] = $port;
168
 
        $this->_data['logintype'] = $logintype;
169
 
        $this->_data['euser'] = $euser;
170
 
        $this->_sock = &new Net_Socket();
171
 
        $this->_debug = $debug;
172
 
        $this->_bypassAuth = $bypassAuth;
173
 
        $this->_useTLS = $useTLS;
174
 
        /*
175
 
        * Include the Auth_SASL package.  If the package is not available,
176
 
        * we disable the authentication methods that depend upon it.
177
 
        */
178
 
        if ((@include_once 'Auth/SASL.php') === false) {
179
 
            if($this->_debug){
180
 
                echo "AUTH_SASL NOT PRESENT!\n";
181
 
            }
182
 
            foreach($this->supportedSASLAuthMethods as $SASLMethod){
183
 
                $pos = array_search( $SASLMethod, $this->supportedAuthMethods );
184
 
                if($this->_debug){
185
 
                    echo "DISABLING METHOD $SASLMethod\n";
186
 
                }
187
 
                unset($this->supportedAuthMethods[$pos]);
188
 
            }
189
 
        }
190
 
        if( ($user != null) && ($pass != null) ){
191
 
            $this->_error = $this->_handleConnectAndLogin();
192
 
        }
193
 
    }
194
 
 
195
 
    /**
196
 
    * Handles the errors the class can find
197
 
    * on the server
198
 
    *
199
 
    * @access private
200
 
    * @param mixed $msg  Text error message or PEAR error object
201
 
    * @param integer $code  Numeric error code
202
 
    * @return PEAR_Error
203
 
    */
204
 
    function _raiseError($msg, $code)
205
 
    {
206
 
        include_once 'PEAR.php';
207
 
        return PEAR::raiseError($msg, $code);
208
 
    }
209
 
 
210
 
    /**
211
 
    * Handles connect and login.
212
 
    * on the server
213
 
    *
214
 
    * @access private
215
 
    * @return mixed Indexed array of scriptnames or PEAR_Error on failure
216
 
    */
217
 
    function _handleConnectAndLogin()
218
 
    {
219
 
        if (PEAR::isError($res = $this->connect($this->_data['host'] , $this->_data['port'], null, $this->_useTLS ))) {
220
 
            return $res;
221
 
        }
222
 
        if($this->_bypassAuth === false) {
223
 
           if (PEAR::isError($res = $this->login($this->_data['user'], $this->_data['pass'], $this->_data['logintype'] , $this->_data['euser'] , $this->_bypassAuth) ) ) {
224
 
                return $res;
225
 
            }
226
 
        }
227
 
        return true;
228
 
    }
229
 
 
230
 
    /**
231
 
    * Returns an indexed array of scripts currently
232
 
    * on the server
233
 
    *
234
 
    * @return mixed Indexed array of scriptnames or PEAR_Error on failure
235
 
    */
236
 
    function listScripts()
237
 
    {
238
 
        if (is_array($scripts = $this->_cmdListScripts())) {
239
 
            $this->_active = $scripts[1];
240
 
            return $scripts[0];
241
 
        } else {
242
 
            return $scripts;
243
 
        }
244
 
    }
245
 
 
246
 
    /**
247
 
    * Returns the active script
248
 
    *
249
 
    * @return mixed The active scriptname or PEAR_Error on failure
250
 
    */
251
 
    function getActive()
252
 
    {
253
 
        if (!empty($this->_active)) {
254
 
            return $this->_active;
255
 
 
256
 
        } elseif (is_array($scripts = $this->_cmdListScripts())) {
257
 
            $this->_active = $scripts[1];
258
 
            return $scripts[1];
259
 
        }
260
 
    }
261
 
 
262
 
    /**
263
 
    * Sets the active script
264
 
    *
265
 
    * @param  string $scriptname The name of the script to be set as active
266
 
    * @return mixed              true on success, PEAR_Error on failure
267
 
    */
268
 
    function setActive($scriptname)
269
 
    {
270
 
        return $this->_cmdSetActive($scriptname);
271
 
    }
272
 
 
273
 
    /**
274
 
    * Retrieves a script
275
 
    *
276
 
    * @param  string $scriptname The name of the script to be retrieved
277
 
    * @return mixed              The script on success, PEAR_Error on failure
278
 
    */
279
 
    function getScript($scriptname)
280
 
    {
281
 
        return $this->_cmdGetScript($scriptname);
282
 
    }
283
 
 
284
 
    /**
285
 
    * Adds a script to the server
286
 
    *
287
 
    * @param  string $scriptname Name of the script
288
 
    * @param  string $script     The script
289
 
    * @param  boolean $makeactive Whether to make this the active script
290
 
    * @return mixed              true on success, PEAR_Error on failure
291
 
    */
292
 
    function installScript($scriptname, $script, $makeactive = false)
293
 
    {
294
 
        if (PEAR::isError($res = $this->_cmdPutScript($scriptname, $script))) {
295
 
            return $res;
296
 
 
297
 
        } elseif ($makeactive) {
298
 
            return $this->_cmdSetActive($scriptname);
299
 
 
300
 
        } else {
301
 
            return true;
302
 
        }
303
 
    }
304
 
 
305
 
    /**
306
 
    * Removes a script from the server
307
 
    *
308
 
    * @param  string $scriptname Name of the script
309
 
    * @return mixed              True on success, PEAR_Error on failure
310
 
    */
311
 
    function removeScript($scriptname)
312
 
    {
313
 
        return $this->_cmdDeleteScript($scriptname);
314
 
    }
315
 
 
316
 
    /**
317
 
    * Returns any error that may have been generated in the
318
 
    * constructor
319
 
    *
320
 
    * @return mixed False if no error, PEAR_Error otherwise
321
 
    */
322
 
    function getError()
323
 
    {
324
 
        return PEAR::isError($this->_error) ? $this->_error : false;
325
 
    }
326
 
 
327
 
    /**
328
 
    * Handles connecting to the server and checking the
329
 
    * response is valid.
330
 
    *
331
 
    * @access private
332
 
    * @param  string $host Hostname of server
333
 
    * @param  string $port Port of server
334
 
    * @param  array  $options List of options to pass to connect
335
 
    * @param  boolean $useTLS Use TLS if available
336
 
    * @return mixed        True on success, PEAR_Error otherwise
337
 
    */
338
 
    function connect($host, $port, $options = null, $useTLS = true)
339
 
    {
340
 
        if (NET_SIEVE_STATE_DISCONNECTED != $this->_state) {
341
 
            $msg='Not currently in DISCONNECTED state';
342
 
            $code=1;
343
 
            return $this->_raiseError($msg,$code);
344
 
        }
345
 
 
346
 
        if (PEAR::isError($res = $this->_sock->connect($host, $port, false, 5, $options))) {
347
 
            return $res;
348
 
        }
349
 
 
350
 
        if($this->_bypassAuth === false) {
351
 
            $this->_state = NET_SIEVE_STATE_AUTHORISATION;
352
 
            if (PEAR::isError($res = $this->_doCmd())) {
353
 
                return $res;
354
 
            }
355
 
        } else {
356
 
            $this->_state = NET_SIEVE_STATE_TRANSACTION;
357
 
        }
358
 
 
359
 
        // Explicitly ask for the capabilities in case the connection
360
 
        // is picked up from an existing connection.
361
 
        if(PEAR::isError($res = $this->_cmdCapability() )) {
362
 
            $msg='Failed to connect, server said: ' . $res->getMessage();
363
 
            $code=2;
364
 
            return $this->_raiseError($msg,$code);
365
 
        }
366
 
 
367
 
        // Get logon greeting/capability and parse
368
 
        $this->_parseCapability($res);
369
 
 
370
 
        if($useTLS === true) {
371
 
            // check if we can enable TLS via STARTTLS
372
 
            if(isset($this->_capability['starttls']) && function_exists('stream_socket_enable_crypto') === true) {
373
 
                if (PEAR::isError($res = $this->_startTLS())) {
374
 
                    return $res;
375
 
                }
376
 
            }
377
 
        }
378
 
 
379
 
        return true;
380
 
    }
381
 
 
382
 
    /**
383
 
    * Logs into server.
384
 
    *
385
 
    * @param  string  $user          Login username
386
 
    * @param  string  $pass          Login password
387
 
    * @param  string  $logintype     Type of login method to use
388
 
    * @param  string  $euser         Effective UID (perform on behalf of $euser)
389
 
    * @param  boolean $bypassAuth    Do not perform authentication
390
 
    * @return mixed                  True on success, PEAR_Error otherwise
391
 
    */
392
 
    function login($user, $pass, $logintype = null , $euser = '', $bypassAuth = false)
393
 
    {
394
 
        if (NET_SIEVE_STATE_AUTHORISATION != $this->_state) {
395
 
            $msg='Not currently in AUTHORISATION state';
396
 
            $code=1;
397
 
            return $this->_raiseError($msg,$code);
398
 
        }
399
 
 
400
 
        if( $bypassAuth === false ){
401
 
            if(PEAR::isError($res=$this->_cmdAuthenticate($user , $pass , $logintype, $euser ) ) ){
402
 
                return $res;
403
 
            }
404
 
        }
405
 
        $this->_state = NET_SIEVE_STATE_TRANSACTION;
406
 
        return true;
407
 
    }
408
 
 
409
 
    /**
410
 
     * Handles the authentication using any known method
411
 
     *
412
 
     * @param string $uid The userid to authenticate as.
413
 
     * @param string $pwd The password to authenticate with.
414
 
     * @param string $userMethod The method to use ( if $userMethod == '' then the class chooses the best method (the stronger is the best ) )
415
 
     * @param string $euser The effective uid to authenticate as.
416
 
     *
417
 
     * @return mixed  string or PEAR_Error
418
 
     *
419
 
     * @access private
420
 
     * @since  1.0
421
 
     */
422
 
    function _cmdAuthenticate($uid , $pwd , $userMethod = null , $euser = '' )
423
 
    {
424
 
        if ( PEAR::isError( $method = $this->_getBestAuthMethod($userMethod) ) ) {
425
 
            return $method;
426
 
        }
427
 
        switch ($method) {
428
 
            case 'DIGEST-MD5':
429
 
                $result = $this->_authDigest_MD5( $uid , $pwd , $euser );
430
 
                return $result;
431
 
                break;
432
 
            case 'CRAM-MD5':
433
 
                $result = $this->_authCRAM_MD5( $uid , $pwd, $euser);
434
 
                break;
435
 
            case 'LOGIN':
436
 
                $result = $this->_authLOGIN( $uid , $pwd , $euser );
437
 
                break;
438
 
            case 'PLAIN':
439
 
                $result = $this->_authPLAIN( $uid , $pwd , $euser );
440
 
                break;
441
 
            default :
442
 
                $result = new PEAR_Error( "$method is not a supported authentication method" );
443
 
                break;
444
 
        }
445
 
 
446
 
        if (PEAR::isError($res = $this->_doCmd() )) {
447
 
            return $res;
448
 
        }
449
 
        return $result;
450
 
    }
451
 
 
452
 
    /**
453
 
     * Authenticates the user using the PLAIN method.
454
 
     *
455
 
     * @param string $user The userid to authenticate as.
456
 
     * @param string $pass The password to authenticate with.
457
 
     * @param string $euser The effective uid to authenticate as.
458
 
     *
459
 
     * @return array Returns an array containing the response
460
 
     *
461
 
     * @access private
462
 
     * @since  1.0
463
 
     */
464
 
    function _authPLAIN($user, $pass , $euser )
465
 
    {
466
 
        if ($euser != '') {
467
 
            $cmd=sprintf('AUTHENTICATE "PLAIN" "%s"', base64_encode($euser . chr(0) . $user . chr(0) . $pass ) ) ;
468
 
        } else {
469
 
            $cmd=sprintf('AUTHENTICATE "PLAIN" "%s"', base64_encode( chr(0) . $user . chr(0) . $pass ) );
470
 
        }
471
 
        return  $this->_sendCmd( $cmd ) ;
472
 
    }
473
 
 
474
 
    /**
475
 
     * Authenticates the user using the PLAIN method.
476
 
     *
477
 
     * @param string $user The userid to authenticate as.
478
 
     * @param string $pass The password to authenticate with.
479
 
     * @param string $euser The effective uid to authenticate as.
480
 
     *
481
 
     * @return array Returns an array containing the response
482
 
     *
483
 
     * @access private
484
 
     * @since  1.0
485
 
     */
486
 
    function _authLOGIN($user, $pass , $euser )
487
 
    {
488
 
        $this->_sendCmd('AUTHENTICATE "LOGIN"');
489
 
        $this->_doCmd(sprintf('"%s"', base64_encode($user)));
490
 
        $this->_doCmd(sprintf('"%s"', base64_encode($pass)));
491
 
    }
492
 
 
493
 
    /**
494
 
     * Authenticates the user using the CRAM-MD5 method.
495
 
     *
496
 
     * @param string $uid The userid to authenticate as.
497
 
     * @param string $pwd The password to authenticate with.
498
 
     * @param string $euser The effective uid to authenticate as.
499
 
     *
500
 
     * @return array Returns an array containing the response
501
 
     *
502
 
     * @access private
503
 
     * @since  1.0
504
 
     */
505
 
    function _authCRAM_MD5($uid, $pwd, $euser)
506
 
    {
507
 
        if ( PEAR::isError( $challenge = $this->_doCmd( 'AUTHENTICATE "CRAM-MD5"' ) ) ) {
508
 
            $this->_error=$challenge;
509
 
            return $challenge;
510
 
        }
511
 
        $challenge=trim($challenge);
512
 
        $challenge = base64_decode( trim($challenge) );
513
 
        $cram = &Auth_SASL::factory('crammd5');
514
 
        if ( PEAR::isError($resp=$cram->getResponse( $uid , $pwd , $challenge ) ) ) {
515
 
            $this->_error=$resp;
516
 
            return $resp;
517
 
        }
518
 
        $auth_str = base64_encode( $resp );
519
 
        if ( PEAR::isError($error = $this->_sendStringResponse( $auth_str  ) ) ) {
520
 
            $this->_error=$error;
521
 
            return $error;
522
 
        }
523
 
 
524
 
    }
525
 
 
526
 
    /**
527
 
     * Authenticates the user using the DIGEST-MD5 method.
528
 
     *
529
 
     * @param string $uid The userid to authenticate as.
530
 
     * @param string $pwd The password to authenticate with.
531
 
     * @param string $euser The effective uid to authenticate as.
532
 
     *
533
 
     * @return array Returns an array containing the response
534
 
     *
535
 
     * @access private
536
 
     * @since  1.0
537
 
     */
538
 
    function _authDigest_MD5($uid, $pwd, $euser)
539
 
    {
540
 
        if ( PEAR::isError( $challenge = $this->_doCmd('AUTHENTICATE "DIGEST-MD5"') ) ) {
541
 
            $this->_error= $challenge;
542
 
            return $challenge;
543
 
        }
544
 
        $challenge = base64_decode( $challenge );
545
 
        $digest = &Auth_SASL::factory('digestmd5');
546
 
 
547
 
        if(PEAR::isError($param=$digest->getResponse($uid, $pwd, $challenge, "localhost", "sieve" , $euser) )) {
548
 
            return $param;
549
 
        }
550
 
        $auth_str = base64_encode($param);
551
 
 
552
 
        if ( PEAR::isError($error = $this->_sendStringResponse( $auth_str  ) ) ) {
553
 
            $this->_error=$error;
554
 
            return $error;
555
 
        }
556
 
 
557
 
        if ( PEAR::isError( $challenge = $this->_doCmd() ) ) {
558
 
            $this->_error=$challenge ;
559
 
            return $challenge ;
560
 
        }
561
 
 
562
 
        if( strtoupper(substr($challenge,0,2))== 'OK' ){
563
 
                return true;
564
 
        }
565
 
 
566
 
        /**
567
 
        * We don't use the protocol's third step because SIEVE doesn't allow
568
 
        * subsequent authentication, so we just silently ignore it.
569
 
        */
570
 
        if ( PEAR::isError($error = $this->_sendStringResponse( '' ) ) ) {
571
 
            $this->_error=$error;
572
 
            return $error;
573
 
        }
574
 
 
575
 
        if (PEAR::isError($res = $this->_doCmd() )) {
576
 
            return $res;
577
 
        }
578
 
    }
579
 
 
580
 
    /**
581
 
    * Removes a script from the server
582
 
    *
583
 
    * @access private
584
 
    * @param  string $scriptname Name of the script to delete
585
 
    * @return mixed              True on success, PEAR_Error otherwise
586
 
    */
587
 
    function _cmdDeleteScript($scriptname)
588
 
    {
589
 
        if (NET_SIEVE_STATE_TRANSACTION != $this->_state) {
590
 
            $msg='Not currently in AUTHORISATION state';
591
 
            $code=1;
592
 
            return $this->_raiseError($msg,$code);
593
 
        }
594
 
        if (PEAR::isError($res = $this->_doCmd(sprintf('DELETESCRIPT "%s"', $scriptname) ) )) {
595
 
            return $res;
596
 
        }
597
 
        return true;
598
 
    }
599
 
 
600
 
    /**
601
 
    * Retrieves the contents of the named script
602
 
    *
603
 
    * @access private
604
 
    * @param  string $scriptname Name of the script to retrieve
605
 
    * @return mixed              The script if successful, PEAR_Error otherwise
606
 
    */
607
 
    function _cmdGetScript($scriptname)
608
 
    {
609
 
        if (NET_SIEVE_STATE_TRANSACTION != $this->_state) {
610
 
            $msg='Not currently in AUTHORISATION state';
611
 
            $code=1;
612
 
            return $this->_raiseError($msg,$code);
613
 
        }
614
 
 
615
 
        if (PEAR::isError($res = $this->_doCmd(sprintf('GETSCRIPT "%s"', $scriptname) ) ) ) {
616
 
            return $res;
617
 
        }
618
 
 
619
 
        return preg_replace('/{[0-9]+}\r\n/', '', $res);
620
 
    }
621
 
 
622
 
    /**
623
 
    * Sets the ACTIVE script, ie the one that gets run on new mail
624
 
    * by the server
625
 
    *
626
 
    * @access private
627
 
    * @param  string $scriptname The name of the script to mark as active
628
 
    * @return mixed              True on success, PEAR_Error otherwise
629
 
    */
630
 
    function _cmdSetActive($scriptname)
631
 
    {
632
 
        if (NET_SIEVE_STATE_TRANSACTION != $this->_state) {
633
 
            $msg='Not currently in AUTHORISATION state';
634
 
            $code=1;
635
 
            return $this->_raiseError($msg,$code);
636
 
        }
637
 
 
638
 
        if (PEAR::isError($res = $this->_doCmd(sprintf('SETACTIVE "%s"', $scriptname) ) ) ) {
639
 
            return $res;
640
 
        }
641
 
 
642
 
        $this->_activeScript = $scriptname;
643
 
        return true;
644
 
    }
645
 
 
646
 
    /**
647
 
    * Sends the LISTSCRIPTS command
648
 
    *
649
 
    * @access private
650
 
    * @return mixed Two item array of scripts, and active script on success,
651
 
    *               PEAR_Error otherwise.
652
 
    */
653
 
    function _cmdListScripts()
654
 
    {
655
 
        if (NET_SIEVE_STATE_TRANSACTION != $this->_state) {
656
 
            $msg='Not currently in AUTHORISATION state';
657
 
            $code=1;
658
 
            return $this->_raiseError($msg,$code);
659
 
        }
660
 
 
661
 
        $scripts = array();
662
 
        $activescript = null;
663
 
 
664
 
        if (PEAR::isError($res = $this->_doCmd('LISTSCRIPTS'))) {
665
 
            return $res;
666
 
        }
667
 
 
668
 
        $res = explode("\r\n", $res);
669
 
 
670
 
        foreach ($res as $value) {
671
 
            if (preg_match('/^"(.*)"( ACTIVE)?$/i', $value, $matches)) {
672
 
                $scripts[] = $matches[1];
673
 
                if (!empty($matches[2])) {
674
 
                    $activescript = $matches[1];
675
 
                }
676
 
            }
677
 
        }
678
 
 
679
 
        return array($scripts, $activescript);
680
 
    }
681
 
 
682
 
    /**
683
 
    * Sends the PUTSCRIPT command to add a script to
684
 
    * the server.
685
 
    *
686
 
    * @access private
687
 
    * @param  string $scriptname Name of the new script
688
 
    * @param  string $scriptdata The new script
689
 
    * @return mixed              True on success, PEAR_Error otherwise
690
 
    */
691
 
    function _cmdPutScript($scriptname, $scriptdata)
692
 
    {
693
 
        if (NET_SIEVE_STATE_TRANSACTION != $this->_state) {
694
 
            $msg='Not currently in TRANSACTION state';
695
 
            $code=1;
696
 
            return $this->_raiseError($msg,$code);
697
 
        }
698
 
 
699
 
        $stringLength = $this->_getLineLength($scriptdata);
700
 
 
701
 
        if (PEAR::isError($res = $this->_doCmd(sprintf("PUTSCRIPT \"%s\" {%d+}\r\n%s", $scriptname, $stringLength, $scriptdata) ))) {
702
 
            return $res;
703
 
        }
704
 
 
705
 
        return true;
706
 
    }
707
 
 
708
 
    /**
709
 
    * Sends the LOGOUT command and terminates the connection
710
 
    *
711
 
    * @access private
712
 
    * @param boolean $sendLogoutCMD True to send LOGOUT command before disconnecting
713
 
    * @return mixed True on success, PEAR_Error otherwise
714
 
    */
715
 
    function _cmdLogout($sendLogoutCMD=true)
716
 
    {
717
 
        if (NET_SIEVE_STATE_DISCONNECTED === $this->_state) {
718
 
            $msg='Not currently connected';
719
 
            $code=1;
720
 
            return $this->_raiseError($msg,$code);
721
 
            //return PEAR::raiseError('Not currently connected');
722
 
        }
723
 
 
724
 
        if($sendLogoutCMD){
725
 
            if (PEAR::isError($res = $this->_doCmd('LOGOUT'))) {
726
 
                return $res;
727
 
            }
728
 
        }
729
 
 
730
 
        $this->_sock->disconnect();
731
 
        $this->_state = NET_SIEVE_STATE_DISCONNECTED;
732
 
        return true;
733
 
    }
734
 
 
735
 
    /**
736
 
    * Sends the CAPABILITY command
737
 
    *
738
 
    * @access private
739
 
    * @return mixed True on success, PEAR_Error otherwise
740
 
    */
741
 
    function _cmdCapability()
742
 
    {
743
 
        if (NET_SIEVE_STATE_DISCONNECTED === $this->_state) {
744
 
            $msg='Not currently connected';
745
 
            $code=1;
746
 
            return $this->_raiseError($msg,$code);
747
 
        }
748
 
 
749
 
        if (PEAR::isError($res = $this->_doCmd('CAPABILITY'))) {
750
 
            return $res;
751
 
        }
752
 
        $this->_parseCapability($res);
753
 
        return true;
754
 
    }
755
 
 
756
 
    /**
757
 
    * Checks if the server has space to store the script
758
 
    * by the server
759
 
    *
760
 
    * @param  string $scriptname The name of the script to mark as active
761
 
    * @param integer $size The size of the script
762
 
    * @return mixed              True on success, PEAR_Error otherwise
763
 
    */
764
 
    function haveSpace($scriptname,$size)
765
 
    {
766
 
        if (NET_SIEVE_STATE_TRANSACTION != $this->_state) {
767
 
            $msg='Not currently in TRANSACTION state';
768
 
            $code=1;
769
 
            return $this->_raiseError($msg,$code);
770
 
        }
771
 
 
772
 
        if (PEAR::isError($res = $this->_doCmd(sprintf('HAVESPACE "%s" %d', $scriptname, $size) ) ) ) {
773
 
            return $res;
774
 
        }
775
 
 
776
 
        return true;
777
 
    }
778
 
 
779
 
    /**
780
 
    * Parses the response from the capability command. Stores
781
 
    * the result in $this->_capability
782
 
    *
783
 
    * @access private
784
 
    * @param string $data The response from the capability command
785
 
    */
786
 
    function _parseCapability($data)
787
 
    {
788
 
        $data = preg_split('/\r?\n/', $data, -1, PREG_SPLIT_NO_EMPTY);
789
 
 
790
 
        for ($i = 0; $i < count($data); $i++) {
791
 
            if (preg_match('/^"([a-z]+)"( "(.*)")?$/i', $data[$i], $matches)) {
792
 
                switch (strtolower($matches[1])) {
793
 
                    case 'implementation':
794
 
                        $this->_capability['implementation'] = $matches[3];
795
 
                        break;
796
 
 
797
 
                    case 'sasl':
798
 
                        $this->_capability['sasl'] = preg_split('/\s+/', $matches[3]);
799
 
                        break;
800
 
 
801
 
                    case 'sieve':
802
 
                        $this->_capability['extensions'] = preg_split('/\s+/', $matches[3]);
803
 
                        break;
804
 
 
805
 
                    case 'starttls':
806
 
                        $this->_capability['starttls'] = true;
807
 
                        break;
808
 
                }
809
 
            }
810
 
        }
811
 
    }
812
 
 
813
 
    /**
814
 
    * Sends a command to the server
815
 
    *
816
 
    * @access private
817
 
    * @param string $cmd The command to send
818
 
    */
819
 
    function _sendCmd($cmd)
820
 
    {
821
 
        $status = $this->_sock->getStatus();
822
 
        if (PEAR::isError($status) || $status['eof']) {
823
 
            return new PEAR_Error( 'Failed to write to socket: (connection lost!) ' );
824
 
        }
825
 
        if ( PEAR::isError( $error = $this->_sock->write( $cmd . "\r\n" ) ) ) {
826
 
            return new PEAR_Error( 'Failed to write to socket: ' . $error->getMessage() );
827
 
        }
828
 
 
829
 
        if( $this->_debug ){
830
 
            // C: means this data was sent by  the client (this class)
831
 
            echo "C:$cmd\n";
832
 
        }
833
 
        return true;
834
 
    }
835
 
 
836
 
    /**
837
 
    * Sends a string response to the server
838
 
    *
839
 
    * @access private
840
 
    * @param string $cmd The command to send
841
 
    */
842
 
    function _sendStringResponse($str)
843
 
    {
844
 
        $response='{' .  $this->_getLineLength($str) . "+}\r\n" . $str  ;
845
 
        return $this->_sendCmd($response);
846
 
    }
847
 
 
848
 
    function _recvLn()
849
 
    {
850
 
        $lastline='';
851
 
        if (PEAR::isError( $lastline = $this->_sock->gets( 8192 ) ) ) {
852
 
            return new PEAR_Error( 'Failed to write to socket: ' . $lastline->getMessage() );
853
 
        }
854
 
        $lastline=rtrim($lastline);
855
 
        if($this->_debug){
856
 
            // S: means this data was sent by  the IMAP Server
857
 
            echo "S:$lastline\n" ;
858
 
        }
859
 
 
860
 
        if( $lastline === '' ) {
861
 
            return new PEAR_Error( 'Failed to receive from the socket' );
862
 
        }
863
 
 
864
 
        return $lastline;
865
 
    }
866
 
 
867
 
    /**
868
 
    * Send a command and retrieves a response from the server.
869
 
    *
870
 
    *
871
 
    * @access private
872
 
    * @param string $cmd The command to send
873
 
    * @return mixed Reponse string if an OK response, PEAR_Error if a NO response
874
 
    */
875
 
    function _doCmd($cmd = '' )
876
 
    {
877
 
        $referralCount=0;
878
 
        while($referralCount < $this->_maxReferralCount ){
879
 
 
880
 
            if($cmd != '' ){
881
 
                if(PEAR::isError($error = $this->_sendCmd($cmd) )) {
882
 
                    return $error;
883
 
                }
884
 
            }
885
 
            $response = '';
886
 
 
887
 
            while (true) {
888
 
                    if(PEAR::isError( $line=$this->_recvLn() )){
889
 
                        return $line;
890
 
                    }
891
 
                    if ('ok' === strtolower(substr($line, 0, 2))) {
892
 
                        $response .= $line;
893
 
                        return rtrim($response);
894
 
 
895
 
                    } elseif ('no' === strtolower(substr($line, 0, 2))) {
896
 
                        // Check for string literal error message
897
 
                        if (preg_match('/^no {([0-9]+)\+?}/i', $line, $matches)) {
898
 
                            $line .= str_replace("\r\n", ' ', $this->_sock->read($matches[1] + 2 ));
899
 
                            if($this->_debug){
900
 
                                echo "S:$line\n";
901
 
                            }
902
 
                        }
903
 
                        $msg=trim($response . substr($line, 2));
904
 
                        $code=3;
905
 
                        return $this->_raiseError($msg,$code);
906
 
                    } elseif ('bye' === strtolower(substr($line, 0, 3))) {
907
 
 
908
 
                        if(PEAR::isError($error = $this->disconnect(false) ) ){
909
 
                            $msg="Can't handle bye, The error was= " . $error->getMessage() ;
910
 
                            $code=4;
911
 
                            return $this->_raiseError($msg,$code);
912
 
                        }
913
 
                        //if (preg_match('/^bye \(referral "([^"]+)/i', $line, $matches)) {
914
 
                        if (preg_match('/^bye \(referral "(sieve:\/\/)?([^"]+)/i', $line, $matches)) {
915
 
                            // Check for referral, then follow it.  Otherwise, carp an error.
916
 
                            // Replace the old host with the referral host preserving any protocol prefix
917
 
                            $this->_data['host'] = preg_replace('/\w+(?!(\w|\:\/\/)).*/',$matches[2],$this->_data['host']);
918
 
                           if (PEAR::isError($error = $this->_handleConnectAndLogin() ) ){
919
 
                                $msg="Can't follow referral to " . $this->_data['host'] . ", The error was= " . $error->getMessage() ;
920
 
                                $code=5;
921
 
                                return $this->_raiseError($msg,$code);
922
 
                            }
923
 
                            break;
924
 
                            // Retry the command
925
 
                            if(PEAR::isError($error = $this->_sendCmd($cmd) )) {
926
 
                                return $error;
927
 
                            }
928
 
                            continue;
929
 
                        }
930
 
                        $msg=trim($response . $line);
931
 
                        $code=6;
932
 
                        return $this->_raiseError($msg,$code);
933
 
                    } elseif (preg_match('/^{([0-9]+)\+?}/i', $line, $matches)) {
934
 
                        // Matches String Responses.
935
 
                        //$line = str_replace("\r\n", ' ', $this->_sock->read($matches[1] + 2 ));
936
 
                        $str_size = $matches[1] + 2;
937
 
                        $line = '';
938
 
                        $line_length = 0;
939
 
                        while ($line_length < $str_size) {
940
 
                            $line .= $this->_sock->read($str_size - $line_length);
941
 
                            $line_length = $this->_getLineLength($line);
942
 
                        }
943
 
                        if($this->_debug){
944
 
                            echo "S:$line\n";
945
 
                        }
946
 
                        if($this->_state != NET_SIEVE_STATE_AUTHORISATION) {
947
 
                            // receive the pending OK only if we aren't authenticating
948
 
                            // since string responses during authentication don't need an
949
 
                            // OK.
950
 
                            $this->_recvLn();
951
 
                        }
952
 
                        return $line;
953
 
                    }
954
 
                    $response .= $line . "\r\n";
955
 
                    $referralCount++;
956
 
                }
957
 
        }
958
 
        $msg="Max referral count reached ($referralCount times) Cyrus murder loop error?";
959
 
        $code=7;
960
 
        return $this->_raiseError($msg,$code);
961
 
    }
962
 
 
963
 
    /**
964
 
    * Sets the debug state
965
 
    *
966
 
    * @param boolean $debug
967
 
    * @return void
968
 
    */
969
 
    function setDebug($debug = true)
970
 
    {
971
 
        $this->_debug = $debug;
972
 
    }
973
 
 
974
 
    /**
975
 
    * Disconnect from the Sieve server
976
 
    *
977
 
    * @param  string $scriptname The name of the script to be set as active
978
 
    * @return mixed              true on success, PEAR_Error on failure
979
 
    */
980
 
    function disconnect($sendLogoutCMD=true)
981
 
    {
982
 
        return $this->_cmdLogout($sendLogoutCMD);
983
 
    }
984
 
 
985
 
    /**
986
 
     * Returns the name of the best authentication method that the server
987
 
     * has advertised.
988
 
     *
989
 
     * @param string if !=null,authenticate with this method ($userMethod).
990
 
     *
991
 
     * @return mixed    Returns a string containing the name of the best
992
 
     *                  supported authentication method or a PEAR_Error object
993
 
     *                  if a failure condition is encountered.
994
 
     * @access private
995
 
     * @since  1.0
996
 
     */
997
 
    function _getBestAuthMethod($userMethod = null)
998
 
    {
999
 
       if( isset($this->_capability['sasl']) ){
1000
 
           $serverMethods=$this->_capability['sasl'];
1001
 
       }else{
1002
 
           // if the server don't send an sasl capability fallback to login auth
1003
 
           //return 'LOGIN';
1004
 
           return new PEAR_Error("This server don't support any Auth methods SASL problem?");
1005
 
       }
1006
 
 
1007
 
        if($userMethod != null ){
1008
 
            $methods = array();
1009
 
            $methods[] = $userMethod;
1010
 
        }else{
1011
 
 
1012
 
            $methods = $this->supportedAuthMethods;
1013
 
        }
1014
 
        if( ($methods != null) && ($serverMethods != null)){
1015
 
            foreach ( $methods as $method ) {
1016
 
                if ( in_array( $method , $serverMethods ) ) {
1017
 
                    return $method;
1018
 
                }
1019
 
            }
1020
 
            $serverMethods=implode(',' , $serverMethods );
1021
 
            $myMethods=implode(',' ,$this->supportedAuthMethods);
1022
 
            return new PEAR_Error("$method NOT supported authentication method!. This server " .
1023
 
                "supports these methods= $serverMethods, but I support $myMethods");
1024
 
        }else{
1025
 
            return new PEAR_Error("This server don't support any Auth methods");
1026
 
        }
1027
 
    }
1028
 
 
1029
 
    /**
1030
 
    * Return the list of extensions the server supports
1031
 
    *
1032
 
    * @return mixed              array  on success, PEAR_Error on failure
1033
 
    */
1034
 
    function getExtensions()
1035
 
    {
1036
 
        if (NET_SIEVE_STATE_DISCONNECTED === $this->_state) {
1037
 
            $msg='Not currently connected';
1038
 
            $code=7;
1039
 
            return $this->_raiseError($msg,$code);
1040
 
        }
1041
 
 
1042
 
        return $this->_capability['extensions'];
1043
 
    }
1044
 
 
1045
 
    /**
1046
 
    * Return true if tyhe server has that extension
1047
 
    *
1048
 
    * @param string  the extension to compare
1049
 
    * @return mixed              array  on success, PEAR_Error on failure
1050
 
    */
1051
 
    function hasExtension($extension)
1052
 
    {
1053
 
        if (NET_SIEVE_STATE_DISCONNECTED === $this->_state) {
1054
 
            $msg='Not currently connected';
1055
 
            $code=7;
1056
 
            return $this->_raiseError($msg,$code);
1057
 
        }
1058
 
 
1059
 
        if(is_array($this->_capability['extensions'] ) ){
1060
 
            foreach( $this->_capability['extensions'] as $ext){
1061
 
                if( trim( strtolower( $ext ) ) === trim( strtolower( $extension ) ) )
1062
 
                    return true;
1063
 
            }
1064
 
        }
1065
 
        return false;
1066
 
    }
1067
 
 
1068
 
    /**
1069
 
    * Return the list of auth methods the server supports
1070
 
    *
1071
 
    * @return mixed              array  on success, PEAR_Error on failure
1072
 
    */
1073
 
    function getAuthMechs()
1074
 
    {
1075
 
        if (NET_SIEVE_STATE_DISCONNECTED === $this->_state) {
1076
 
            $msg='Not currently connected';
1077
 
            $code=7;
1078
 
            return $this->_raiseError($msg,$code);
1079
 
        }
1080
 
        if(!isset($this->_capability['sasl']) ){
1081
 
            $this->_capability['sasl']=array();
1082
 
        }
1083
 
        return $this->_capability['sasl'];
1084
 
    }
1085
 
 
1086
 
    /**
1087
 
    * Return true if the server has that extension
1088
 
    *
1089
 
    * @param string  the extension to compare
1090
 
    * @return mixed              array  on success, PEAR_Error on failure
1091
 
    */
1092
 
    function hasAuthMech($method)
1093
 
    {
1094
 
        if (NET_SIEVE_STATE_DISCONNECTED === $this->_state) {
1095
 
            $msg='Not currently connected';
1096
 
            $code=7;
1097
 
            return $this->_raiseError($msg,$code);
1098
 
            //return PEAR::raiseError('Not currently connected');
1099
 
        }
1100
 
 
1101
 
        if(is_array($this->_capability['sasl'] ) ){
1102
 
            foreach( $this->_capability['sasl'] as $ext){
1103
 
                if( trim( strtolower( $ext ) ) === trim( strtolower( $method ) ) )
1104
 
                    return true;
1105
 
            }
1106
 
        }
1107
 
        return false;
1108
 
    }
1109
 
 
1110
 
    /**
1111
 
    * Return true if the TLS negotiation was successful
1112
 
    *
1113
 
    * @access private
1114
 
    * @return mixed              true on success, PEAR_Error on failure
1115
 
    */
1116
 
    function _startTLS()
1117
 
    {
1118
 
        if (PEAR::isError($res = $this->_doCmd("STARTTLS"))) {
1119
 
            return $res;
1120
 
        }
1121
 
 
1122
 
        if(stream_socket_enable_crypto($this->_sock->fp, true, STREAM_CRYPTO_METHOD_TLS_CLIENT) == false) {
1123
 
            $msg='Failed to establish TLS connection';
1124
 
            $code=2;
1125
 
            return $this->_raiseError($msg,$code);
1126
 
        }
1127
 
 
1128
 
        if($this->_debug === true) {
1129
 
            echo "STARTTLS Negotiation Successful\n";
1130
 
        }
1131
 
 
1132
 
        // RFC says we need to query the server capabilities again
1133
 
        if(PEAR::isError($res = $this->_cmdCapability() )) {
1134
 
            $msg='Failed to connect, server said: ' . $res->getMessage();
1135
 
            $code=2;
1136
 
            return $this->_raiseError($msg,$code);
1137
 
        }
1138
 
        return true;
1139
 
    }
1140
 
 
1141
 
    function _getLineLength($string) {
1142
 
        if (extension_loaded('mbstring') || @dl(PHP_SHLIB_PREFIX.'mbstring.'.PHP_SHLIB_SUFFIX)) {
1143
 
          return mb_strlen($string,'latin1');
1144
 
        } else {
1145
 
          return strlen($string);
1146
 
        }
1147
 
    }
1148
 
}
1149
 
?>