1<?php
2/**
3 * An object to represent lots of information about an RPC-peer machine
4 *
5 * @author  Donal McMullan  donal@catalyst.net.nz
6 * @version 0.0.1
7 * @license http://www.gnu.org/copyleft/gpl.html GNU Public License
8 * @package mnet
9 */
10
11require_once($CFG->libdir . '/filelib.php'); // download_file_content() used here
12
13class mnet_peer {
14
15    /** No SSL verification. */
16    const SSL_NONE = 0;
17
18    /** SSL verification for host. */
19    const SSL_HOST = 1;
20
21    /** SSL verification for host and peer. */
22    const SSL_HOST_AND_PEER = 2;
23
24    var $id                 = 0;
25    var $wwwroot            = '';
26    var $ip_address         = '';
27    var $name               = '';
28    var $public_key         = '';
29    var $public_key_expires = 0;
30    var $last_connect_time  = 0;
31    var $last_log_id        = 0;
32    var $force_theme        = 0;
33    var $theme              = '';
34    var $applicationid      = 1; // Default of 1 == Moodle
35    var $keypair            = array();
36    var $error              = array();
37    var $bootstrapped       = false; // set when the object is populated
38
39    /** @var int $sslverification The level of SSL verification to apply. */
40    public $sslverification = self::SSL_HOST_AND_PEER;
41
42    /*
43     * Fetch information about a peer identified by wwwroot
44     * If information does not preexist in db, collect it together based on
45     * supplied information
46     *
47     * @param string $wwwroot - address of peer whose details we want
48     * @param string $pubkey - to use if we add a record to db for new peer
49     * @param int $application - table id - what kind of peer are we talking to
50     * @return bool - indication of success or failure
51     */
52    function bootstrap($wwwroot, $pubkey, $application) {
53        global $DB;
54
55        if (substr($wwwroot, -1, 1) == '/') {
56            $wwwroot = substr($wwwroot, 0, -1);
57        }
58
59        // If a peer record already exists for this address,
60        // load that info and return
61        if ($this->set_wwwroot($wwwroot)) {
62            return true;
63        }
64
65        $hostname = mnet_get_hostname_from_uri($wwwroot);
66        // Get the IP address for that host - if this fails, it will return the hostname string
67        $ip_address = gethostbyname($hostname);
68
69        // Couldn't find the IP address?
70        if ($ip_address === $hostname && !preg_match('/^\d+\.\d+\.\d+.\d+$/',$hostname)) {
71            throw new moodle_exception('noaddressforhost', 'mnet', '', $hostname);
72        }
73
74        $this->name = $wwwroot;
75
76        // TODO: In reality, this will be prohibitively slow... need another
77        // default - maybe blank string
78        $homepage = download_file_content($wwwroot);
79        if (!empty($homepage)) {
80            $count = preg_match("@<title>(.*)</title>@siU", $homepage, $matches);
81            if ($count > 0) {
82                $this->name = $matches[1];
83            }
84        }
85
86        $this->wwwroot              = $wwwroot;
87        $this->ip_address           = $ip_address;
88        $this->deleted              = 0;
89
90        $this->application = $DB->get_record('mnet_application', array('name'=>$application));
91        if (empty($this->application)) {
92            $this->application = $DB->get_record('mnet_application', array('name'=>'moodle'));
93        }
94
95        $this->applicationid = $this->application->id;
96
97        if(empty($pubkey)) {
98            $this->public_key           = clean_param(mnet_get_public_key($this->wwwroot, $this->application), PARAM_PEM);
99        } else {
100            $this->public_key           = clean_param($pubkey, PARAM_PEM);
101        }
102        $this->public_key_expires   = $this->check_common_name($this->public_key);
103        $this->last_connect_time    = 0;
104        $this->last_log_id          = 0;
105        if ($this->public_key_expires == false) {
106            $this->public_key == '';
107            return false;
108        }
109        $this->bootstrapped = true;
110    }
111
112    /*
113     * Delete mnet peer
114     * the peer is marked as deleted in the database
115     * we delete current sessions.
116     * @return bool - success
117     */
118    function delete() {
119        global $DB;
120
121        if ($this->deleted) {
122            return true;
123        }
124
125        $this->delete_all_sessions();
126
127        $this->deleted = 1;
128        return $this->commit();
129    }
130
131    function count_live_sessions() {
132        global $DB;
133        $obj = $this->delete_expired_sessions();
134        return $DB->count_records('mnet_session', array('mnethostid'=>$this->id));
135    }
136
137    function delete_expired_sessions() {
138        global $DB;
139        $now = time();
140        return $DB->delete_records_select('mnet_session', " mnethostid = ? AND expires < ? ", array($this->id, $now));
141    }
142
143    function delete_all_sessions() {
144        global $CFG, $DB;
145        // TODO: Expires each PHP session individually
146        $sessions = $DB->get_records('mnet_session', array('mnethostid'=>$this->id));
147
148        if (count($sessions) > 0 && file_exists($CFG->dirroot.'/auth/mnet/auth.php')) {
149            require_once($CFG->dirroot.'/auth/mnet/auth.php');
150            $auth = new auth_plugin_mnet();
151            $auth->end_local_sessions($sessions);
152        }
153
154        $deletereturn = $DB->delete_records('mnet_session', array('mnethostid'=>$this->id));
155        return true;
156    }
157
158    function check_common_name($key) {
159        $credentials = $this->check_credentials($key);
160        return $credentials['validTo_time_t'];
161    }
162
163    function check_credentials($key) {
164        $credentials = openssl_x509_parse($key);
165        if ($credentials == false) {
166            $this->error[] = array('code' => 3, 'text' => get_string("nonmatchingcert", 'mnet', array('subject' => '','host' => '')));
167            return false;
168        } elseif (array_key_exists('subjectAltName', $credentials['subject']) && $credentials['subject']['subjectAltName'] != $this->wwwroot) {
169            $a['subject'] = $credentials['subject']['subjectAltName'];
170            $a['host'] = $this->wwwroot;
171            $this->error[] = array('code' => 5, 'text' => get_string("nonmatchingcert", 'mnet', $a));
172            return false;
173        } else if ($credentials['subject']['CN'] !== substr($this->wwwroot, 0, 64)) {
174            $a['subject'] = $credentials['subject']['CN'];
175            $a['host'] = $this->wwwroot;
176            $this->error[] = array('code' => 4, 'text' => get_string("nonmatchingcert", 'mnet', $a));
177            return false;
178        } else {
179            if (array_key_exists('subjectAltName', $credentials['subject'])) {
180                $credentials['wwwroot'] = $credentials['subject']['subjectAltName'];
181            } else {
182                $credentials['wwwroot'] = $credentials['subject']['CN'];
183            }
184            return $credentials;
185        }
186    }
187
188    function commit() {
189        global $DB;
190        $obj = new stdClass();
191
192        $obj->wwwroot               = $this->wwwroot;
193        $obj->ip_address            = $this->ip_address;
194        $obj->name                  = $this->name;
195        $obj->public_key            = $this->public_key;
196        $obj->public_key_expires    = $this->public_key_expires;
197        $obj->deleted               = $this->deleted;
198        $obj->last_connect_time     = $this->last_connect_time;
199        $obj->last_log_id           = $this->last_log_id;
200        $obj->force_theme           = $this->force_theme;
201        $obj->theme                 = $this->theme;
202        $obj->applicationid         = $this->applicationid;
203        $obj->sslverification       = $this->sslverification;
204
205        if (isset($this->id) && $this->id > 0) {
206            $obj->id = $this->id;
207            return $DB->update_record('mnet_host', $obj);
208        } else {
209            $this->id = $DB->insert_record('mnet_host', $obj);
210            return $this->id > 0;
211        }
212    }
213
214    function touch() {
215        $this->last_connect_time = time();
216        $this->commit();
217    }
218
219    function set_name($newname) {
220        if (is_string($newname) && strlen($newname <= 80)) {
221            $this->name = $newname;
222            return true;
223        }
224        return false;
225    }
226
227    function set_applicationid($applicationid) {
228        if (is_numeric($applicationid) && $applicationid == intval($applicationid)) {
229            $this->applicationid = $applicationid;
230            return true;
231        }
232        return false;
233    }
234
235    /**
236     * Load information from db about an mnet peer into this object's properties
237     *
238     * @param string $wwwroot - address of peer whose details we want to load
239     * @return bool - indication of success or failure
240     */
241    function set_wwwroot($wwwroot) {
242        global $CFG, $DB;
243
244        $hostinfo = $DB->get_record('mnet_host', array('wwwroot'=>$wwwroot));
245
246        if ($hostinfo != false) {
247            $this->populate($hostinfo);
248            return true;
249        }
250        return false;
251    }
252
253    function set_id($id) {
254        global $CFG, $DB;
255
256        if (clean_param($id, PARAM_INT) != $id) {
257            $this->errno[]  = 1;
258            $this->errmsg[] = 'Your id ('.$id.') is not legal';
259            return false;
260        }
261
262        $sql = "
263                SELECT
264                    h.*
265                FROM
266                    {mnet_host} h
267                WHERE
268                    h.id = ?";
269
270        if ($hostinfo = $DB->get_record_sql($sql, array($id))) {
271            $this->populate($hostinfo);
272            return true;
273        }
274        return false;
275    }
276
277    /**
278     * Several methods can be used to get an 'mnet_host' record. They all then
279     * send it to this private method to populate this object's attributes.
280     *
281     * @param   object  $hostinfo   A database record from the mnet_host table
282     * @return  void
283     */
284    function populate($hostinfo) {
285        global $DB;
286        $this->id                   = $hostinfo->id;
287        $this->wwwroot              = $hostinfo->wwwroot;
288        $this->ip_address           = $hostinfo->ip_address;
289        $this->name                 = $hostinfo->name;
290        $this->deleted              = $hostinfo->deleted;
291        $this->public_key           = $hostinfo->public_key;
292        $this->public_key_expires   = $hostinfo->public_key_expires;
293        $this->last_connect_time    = $hostinfo->last_connect_time;
294        $this->last_log_id          = $hostinfo->last_log_id;
295        $this->force_theme          = $hostinfo->force_theme;
296        $this->theme                = $hostinfo->theme;
297        $this->applicationid        = $hostinfo->applicationid;
298        $this->sslverification      = $hostinfo->sslverification;
299        $this->application = $DB->get_record('mnet_application', array('id'=>$this->applicationid));
300        $this->bootstrapped = true;
301    }
302
303    function get_public_key() {
304        if (isset($this->public_key_ref)) return $this->public_key_ref;
305        $this->public_key_ref = openssl_pkey_get_public($this->public_key);
306        return $this->public_key_ref;
307    }
308}
309