1<?php
2
3/**
4 * DirectAdmin Password Driver
5 *
6 * Driver to change passwords via DirectAdmin Control Panel
7 *
8 * @version 2.2
9 * @author Victor Benincasa <vbenincasa @ gmail.com>
10 *
11 * Copyright (C) The Roundcube Dev Team
12 *
13 * This program is free software: you can redistribute it and/or modify
14 * it under the terms of the GNU General Public License as published by
15 * the Free Software Foundation, either version 3 of the License, or
16 * (at your option) any later version.
17 *
18 * This program is distributed in the hope that it will be useful,
19 * but WITHOUT ANY WARRANTY; without even the implied warranty of
20 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 * GNU General Public License for more details.
22 *
23 * You should have received a copy of the GNU General Public License
24 * along with this program. If not, see http://www.gnu.org/licenses/.
25 */
26
27class rcube_directadmin_password
28{
29    public function save($curpass, $passwd)
30    {
31        $rcmail = rcmail::get_instance();
32        $Socket = new HTTPSocket;
33
34        $da_user    = $_SESSION['username'];
35        $da_curpass = $curpass;
36        $da_newpass = $passwd;
37        $da_host    = $rcmail->config->get('password_directadmin_host');
38        $da_port    = $rcmail->config->get('password_directadmin_port');
39
40        if (strpos($da_user, '@') === false) {
41            return ['code' => PASSWORD_ERROR, 'message' => 'Change the SYSTEM user password through control panel!'];
42        }
43
44        $da_host = str_replace('%h', $_SESSION['imap_host'], $da_host);
45        $da_host = str_replace('%d', $rcmail->user->get_username('domain'), $da_host);
46
47        $Socket->connect($da_host,$da_port);
48        $Socket->set_method('POST');
49        $Socket->query('/CMD_CHANGE_EMAIL_PASSWORD', [
50                'email'         => $da_user,
51                'oldpassword'   => $da_curpass,
52                'password1'     => $da_newpass,
53                'password2'     => $da_newpass,
54                'api'           => '1'
55        ]);
56
57        $response = $Socket->fetch_parsed_body();
58
59        //DEBUG
60        //rcube::console("Password Plugin: [USER: $da_user] [HOST: $da_host] - Response: [SOCKET: ".$Socket->result_status_code."] [DA ERROR: ".strip_tags($response['error'])."] [TEXT: ".$response[text]."]");
61
62        if ($Socket->result_status_code != 200) {
63            return ['code' => PASSWORD_CONNECT_ERROR, 'message' => $Socket->error[0]];
64        }
65
66        if ($response['error'] == 1) {
67            return ['code' => PASSWORD_ERROR, 'message' => strip_tags($response['text'])];
68        }
69
70        return PASSWORD_SUCCESS;
71    }
72}
73
74
75/**
76 * Socket communication class.
77 *
78 * Originally designed for use with DirectAdmin's API, this class will fill any HTTP socket need.
79 *
80 * Very, very basic usage:
81 *   $Socket = new HTTPSocket;
82 *   echo $Socket->get('http://user:pass@somesite.com/somedir/some.file?query=string&this=that');
83 *
84 * @author Phi1 'l0rdphi1' Stier <l0rdphi1@liquenox.net>
85 * @package HTTPSocket
86 * @version 3.0.2
87 */
88class HTTPSocket
89{
90    var $version = '3.0.2';
91
92    // all vars are private except $error, $query_cache, and $doFollowLocationHeader
93
94    var $method = 'GET';
95
96    var $remote_host;
97    var $remote_port;
98    var $remote_uname;
99    var $remote_passwd;
100
101    var $result;
102    var $result_header;
103    var $result_body;
104    var $result_status_code;
105
106    var $lastTransferSpeed;
107    var $bind_host;
108    var $error       = [];
109    var $warn        = [];
110    var $query_cache = [];
111    var $doFollowLocationHeader = true;
112    var $redirectURL;
113    var $max_redirects = 5;
114    var $ssl_setting_message = 'DirectAdmin appears to be using SSL. Change your script to connect to ssl://';
115    var $extra_headers = [];
116
117    /**
118     * Create server "connection".
119     *
120     */
121    function connect($host, $port = '')
122    {
123        if (!is_numeric($port)) {
124            $port = 2222;
125        }
126
127        $this->remote_host = $host;
128        $this->remote_port = $port;
129    }
130
131    function bind($ip = '')
132    {
133        if ($ip == '') {
134            $ip = $_SERVER['SERVER_ADDR'];
135        }
136
137        $this->bind_host = $ip;
138    }
139
140    /**
141     * Change the method being used to communicate.
142     *
143     * @param string|null request method. supports GET, POST, and HEAD. default is GET
144     */
145    function set_method($method = 'GET')
146    {
147        $this->method = strtoupper($method);
148    }
149
150    /**
151     * Specify a username and password.
152     *
153     * @param string|null username. default is null
154     * @param string|null password. default is null
155     */
156    function set_login($uname = '', $passwd = '')
157    {
158        if (strlen($uname) > 0) {
159            $this->remote_uname = $uname;
160        }
161
162        if (strlen($passwd) > 0) {
163            $this->remote_passwd = $passwd;
164        }
165    }
166
167    /**
168     * Query the server
169     *
170     * @param string containing properly formatted server API. See DA API docs and examples. Http:// URLs O.K. too.
171     * @param string|array query to pass to url
172     */
173    function query($request, $content = '')
174    {
175        $this->error = $this->warn = [];
176        $this->result_status_code  = null;
177
178        $is_ssl = false;
179
180        // is our request a http:// ... ?
181        if (preg_match('!^http://!i',$request) || preg_match('!^https://!i',$request)) {
182            $location = parse_url($request);
183            if (preg_match('!^https://!i',$request)) {
184                $this->connect('https://'.$location['host'],$location['port']);
185            }
186            else {
187                $this->connect('http://'.$location['host'],$location['port']);
188            }
189
190            $this->set_login($location['user'], $location['pass']);
191
192            $request = $location['path'];
193            $content = $location['query'];
194
195            if (strlen($request) < 1) {
196                $request = '/';
197            }
198        }
199
200        if (preg_match('!^ssl://!i', $this->remote_host)) {
201            $this->remote_host = 'https://'.substr($this->remote_host, 6);
202        }
203
204        if (preg_match('!^tcp://!i', $this->remote_host)) {
205            $this->remote_host = 'http://'.substr($this->remote_host, 6);
206        }
207
208        if (preg_match('!^https://!i', $this->remote_host)) {
209            $is_ssl = true;
210        }
211
212        $array_headers = [
213            'Host'       => $this->remote_port == 80 ? $this->remote_host : "$this->remote_host:$this->remote_port",
214            'Accept'     => '*/*',
215            'Connection' => 'Close'
216        ];
217
218        foreach ($this->extra_headers as $key => $value) {
219            $array_headers[$key] = $value;
220        }
221
222        $this->result = $this->result_header = $this->result_body = '';
223
224        // was content sent as an array? if so, turn it into a string
225        if (is_array($content)) {
226            $pairs = [];
227
228            foreach ($content as $key => $value) {
229                $pairs[] = "$key=".urlencode($value);
230            }
231
232            $content = join('&',$pairs);
233            unset($pairs);
234        }
235
236        $OK = true;
237
238        if ($this->method == 'GET') {
239            $request .= '?'.$content;
240        }
241
242        $ch = curl_init($this->remote_host.':'.$this->remote_port.$request);
243
244        if ($is_ssl) {
245            curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); //1
246            curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false); //2
247            //curl_setopt($ch, CURLOPT_SSLVERSION, CURL_SSLVERSION_TLSv1_2);
248        }
249
250        curl_setopt($ch, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
251        curl_setopt($ch, CURLOPT_RETURNTRANSFER,1);
252        curl_setopt($ch, CURLOPT_USERAGENT, "HTTPSocket/$this->version");
253        curl_setopt($ch, CURLOPT_FORBID_REUSE, 1);
254        curl_setopt($ch, CURLOPT_TIMEOUT, 100);
255        curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 10);
256        curl_setopt($ch, CURLOPT_HEADER, 1);
257
258        curl_setopt($ch, CURLOPT_LOW_SPEED_LIMIT, 512);
259        curl_setopt($ch, CURLOPT_LOW_SPEED_TIME, 120);
260
261        // instance connection
262        if ($this->bind_host) {
263            curl_setopt($ch, CURLOPT_INTERFACE, $this->bind_host);
264        }
265
266        // if we have a username and password, add the header
267        if (isset($this->remote_uname) && isset($this->remote_passwd)) {
268            curl_setopt($ch, CURLOPT_USERPWD, $this->remote_uname.':'.$this->remote_passwd);
269        }
270
271        // for DA skins: if $this->remote_passwd is NULL, try to use the login key system
272        if (isset($this->remote_uname) && $this->remote_passwd == NULL) {
273            $array_headers['Cookie'] = "session={$_SERVER['SESSION_ID']}; key={$_SERVER['SESSION_KEY']}";
274        }
275
276        // if method is POST, add content length & type headers
277        if ($this->method == 'POST') {
278            curl_setopt($ch, CURLOPT_POST, 1);
279            curl_setopt($ch, CURLOPT_POSTFIELDS, $content);
280
281            //$array_headers['Content-type'] = 'application/x-www-form-urlencoded';
282            $array_headers['Content-length'] = strlen($content);
283        }
284
285        curl_setopt($ch, CURLOPT_HTTPHEADER, $array_headers);
286
287        if (!($this->result = curl_exec($ch))) {
288            $this->error[] = curl_error($ch);
289            $OK = false;
290        }
291
292        $header_size              = curl_getinfo($ch, CURLINFO_HEADER_SIZE);
293        $this->result_header      = substr($this->result, 0, $header_size);
294        $this->result_body        = substr($this->result, $header_size);
295        $this->result_status_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
296        $this->lastTransferSpeed  = curl_getinfo($ch, CURLINFO_SPEED_DOWNLOAD) / 1024;
297
298        curl_close($ch);
299
300        $this->query_cache[] = $this->remote_host.':'.$this->remote_port.$request;
301
302        $headers = $this->fetch_header();
303
304        // did we get the full file?
305        if (!empty($headers['content-length']) && $headers['content-length'] != strlen($this->result_body)) {
306            $this->result_status_code = 206;
307        }
308
309        // now, if we're being passed a location header, should we follow it?
310        if ($this->doFollowLocationHeader) {
311            //dont bother if we didn't even setup the script correctly
312            if (isset($headers['x-use-https']) && $headers['x-use-https'] == 'yes') {
313                die($this->ssl_setting_message);
314            }
315
316            if (isset($headers['location'])) {
317                if ($this->max_redirects <= 0) {
318                    die("Too many redirects on: ".$headers['location']);
319                }
320
321                $this->max_redirects--;
322                $this->redirectURL = $headers['location'];
323                $this->query($headers['location']);
324            }
325        }
326    }
327
328    function getTransferSpeed()
329    {
330        return $this->lastTransferSpeed;
331    }
332
333    /**
334     * The quick way to get a URL's content :)
335     *
336     * @param string $location URL
337     * @param bool   $asArray  return as array? (like PHP's file() command)
338     *
339     * @return string result body
340     */
341    function get($location, $asArray = false)
342    {
343        $this->query($location);
344
345        if ($this->get_status_code() == 200) {
346            if ($asArray) {
347                return preg_split("/\n/", $this->fetch_body());
348            }
349
350            return $this->fetch_body();
351        }
352
353        return false;
354    }
355
356    /**
357     * Returns the last status code.
358     * 200 = OK;
359     * 403 = FORBIDDEN;
360     * etc.
361     *
362     * @return int status code
363     */
364    function get_status_code()
365    {
366        return $this->result_status_code;
367    }
368
369    /**
370     * Adds a header, sent with the next query.
371     *
372     * @param string header name
373     * @param string header value
374     */
375    function add_header($key, $value)
376    {
377        $this->extra_headers[$key] = $value;
378    }
379
380    /**
381     * Clears any extra headers.
382     *
383     */
384    function clear_headers()
385    {
386        $this->extra_headers = [];
387    }
388
389    /**
390     * Return the result of a query.
391     *
392     * @return string result
393     */
394    function fetch_result()
395    {
396        return $this->result;
397    }
398
399    /**
400     * Return the header of result (stuff before body).
401     *
402     * @param string (optional) header to return
403     * @return array result header
404     */
405    function fetch_header($header = '')
406    {
407        $array_headers = preg_split("/\r\n/", $this->result_header);
408
409        $array_return = [0 => $array_headers[0]];
410        unset($array_headers[0]);
411
412        foreach ($array_headers as $pair) {
413            if ($pair == '' || $pair == "\r\n") continue;
414            list($key,$value) = preg_split("/: /", $pair, 2);
415            $array_return[strtolower($key)] = $value;
416        }
417
418        if ($header != '') {
419            return $array_return[strtolower($header)];
420        }
421
422        return $array_return;
423    }
424
425    /**
426     * Return the body of result (stuff after header).
427     *
428     * @return string result body
429     */
430    function fetch_body()
431    {
432        return $this->result_body;
433    }
434
435    /**
436     * Return parsed body in array format.
437     *
438     * @return array result parsed
439     */
440    function fetch_parsed_body()
441    {
442        parse_str($this->result_body, $x);
443        return $x;
444    }
445
446    /**
447     * Set a specific message on how to change the SSL setting, in the event that it's not set correctly.
448     */
449    function set_ssl_setting_message($str)
450    {
451        $this->ssl_setting_message = $str;
452    }
453}
454