1<?php
2
3
4/**
5 * DHCP_kea class to work with isc-dhcp server
6 *
7 *  It will be called form class.DHCP.php wrapper it kea is selected as DHCP type
8 *
9 *  http://kea.isc.org/wiki
10 *
11 *
12 */
13class DHCP_kea extends Common_functions {
14
15    /**
16     * Location of kea config file
17     *
18     * (default value: false)
19     *
20     * @var bool
21     * @access private
22     */
23    private $kea_config_file = "/etc/kea/kea.conf";
24
25    /**
26     * Settings to be provided to process kea files
27     *
28     * (default value: array())
29     *
30     * @var array
31     * @access private
32     */
33    private $kea_settings = array();
34
35    /**
36     * Raw config file
37     *
38     * (default value: "")
39     *
40     * @var string
41     * @access public
42     */
43    public $config_raw = "";
44
45    /**
46     * Parsed config file
47     *
48     * (default value: false)
49     *
50     * @var array|bool
51     * @access public
52     */
53    public $config = false;
54
55    /**
56     * Falg if ipv4 is used
57     *
58     * (default value: false)
59     *
60     * @var bool
61     * @access public
62     */
63    public $ipv4_used = false;
64
65    /**
66     * Flag if ipv6 is used
67     *
68     * (default value: false)
69     *
70     * @var bool
71     * @access public
72     */
73    public $ipv6_used = false;
74
75    /**
76     * Array to store DHCP subnets, parsed from config file
77     *
78     *  Format:
79     *      $subnets[] = array (pools=>array());
80     *
81     * (default value: array())
82     *
83     * @var array
84     * @access public
85     */
86    public $subnets4 = array();
87
88    /**
89     * Array to store DHCP subnets, parsed from config file
90     *
91     *  Format:
92     *      $subnets[] = array (pools=>array());
93     *
94     * (default value: array())
95     *
96     * @var array
97     * @access public
98     */
99    public $subnets6 = array();
100
101    /**
102     * set available lease database types
103     *
104     * (default value: array("memfile", "mysql", "postgresql"))
105     *
106     * @var string
107     * @access public
108     */
109    public $lease_types = array("memfile", "mysql", "postgresql");
110
111    /**
112     * List of active leases
113     *
114     * (default value: array())
115     *
116     * @var array
117     * @access public
118     */
119    public $leases4 = array();
120
121    /**
122     * List of active leases
123     *
124     * (default value: array())
125     *
126     * @var array
127     * @access public
128     */
129    public $leases6 = array();
130
131    /**
132     * Available reservation methods
133     *
134     * (default value: array("mysql"))
135     *
136     * @var string
137     * @access public
138     */
139    public $reservation_types = array("file", "mysql");
140
141    /**
142     * Definition of hosts reservations
143     *
144     * (default value: array())
145     *
146     * @var array
147     * @access public
148     */
149    public $reservations4 = array();
150    public $reservations6 = array();
151
152    /**
153     * Database object for leases and hosts
154     *
155     * (default value: false)
156     *
157     * @var bool
158     * @access protected
159     */
160    protected $Database_kea = false;
161
162
163
164    /**
165     * __construct function.
166     *
167     * @access public
168     * @param array $kea_settings (default: array())
169     * @return void
170     */
171    public function __construct($kea_settings = array()) {
172        // save settings
173        if (is_array($kea_settings))            { $this->kea_settings = $kea_settings; }
174        else                                    { throw new exception ("Invalid kea settings"); }
175
176        // set file
177        if(isset($this->kea_settings['file']))  { $this->kea_config_file = $this->kea_settings['file']; }
178
179        // parse config file on startup
180        $this->parse_config ();
181        // parse and save subnets
182        $this->parse_subnets ();
183    }
184
185    /**
186     * Opens database connection if needed for leases and hosts
187     *
188     * @access private
189     * @param mixed $username
190     * @param mixed $password
191     * @param mixed $host
192     * @param mixed $port
193     * @param mixed $dbname
194     * @param mixed $charset
195     * @return void
196     */
197    private function init_database_conection ($username, $password, $host, $port, $dbname) {
198        // open
199        $this->Database_kea = new Database_PDO ($username, $password, $host, $port, $dbname);
200    }
201
202
203
204
205
206
207    /**
208     * This function parses config file and returns it as array.
209     *
210     * @access private
211     * @return void
212     */
213    private function parse_config () {
214        // get file to array
215        if(file_exists($this->kea_config_file)) {
216            $config = file($this->kea_config_file);
217            // save
218            $this->config_raw = implode("\n",array_filter($config));
219        }
220        else {
221            throw new exception ("Cannot access config file ".$this->kea_config_file);
222        }
223
224        // loop and remove comments (contains #) and replace multilpe spaces
225        $out   = array();
226        foreach ($config as $k=>$f) {
227            if (strpos($f, "#")!==false || strlen($f)==0) {}
228            else {
229                if(strlen($f)>0) {
230                    $out[] = $f;
231                }
232            }
233        }
234
235        // join to line
236        $config = implode("", $out);
237
238		// validate json
239		if ($this->validate_json_string ($config)===false) {
240    		throw new exception ("JSON config file error: $this->json_error");
241		}
242
243        // save config
244        $this->config = json_decode($config, true);
245        // save IPv4 / IPv6 flags
246        if(isset($this->config['Dhcp4']))   { $this->ipv4_used = true; }
247        if(isset($this->config['Dhcp6']))   { $this->ipv6_used = true; }
248    }
249
250    /**
251     * Saves subnets definition to $subnets object
252     *
253     * @access private
254     * @return void
255     */
256    private function parse_subnets () {
257        // save to subnets4 object
258        $this->subnets4 = @$this->config['Dhcp4']['subnet4'];
259        // save to subnets6 object
260        $this->subnets6 = @$this->config['Dhcp6']['subnet6'];
261    }
262
263
264
265
266
267
268
269
270
271    /* @leases --------------- */
272
273    /**
274     * Saves leases to $leases object as array.
275     *
276     * @access public
277     * @param string $type (default: "IPv4")
278     * @return void
279     */
280    public function get_leases ($type = "IPv4") {
281        // first check where they are stored - mysql, postgres or file
282        if ($type=="IPv4") {
283            $lease_database = $this->config['Dhcp4']['lease-database'];
284        }
285        else {
286            $lease_database = $this->config['Dhcp6']['lease-database'];
287        }
288
289        // set lease type
290        $lease_database_type = $lease_database['type'];
291
292        // validate database type
293        if (!in_array($lease_database_type, $this->lease_types)) {
294            throw new exception ("Invalid lease database type");
295        }
296
297        // get leases
298        $lease_type = "get_leases_".$lease_database_type;
299        $this->{$lease_type} ($lease_database, $type);
300    }
301
302    /**
303     * Fetches leases from memfile.
304     *
305     *  First line is structure
306     *      address,hwaddr,client_id,valid_lifetime,expire,subnet_id,fqdn_fwd,fqdn_rev,hostname,state
307     *
308     * @access private
309     * @param mixed $lease_database
310     * @param string $type (default: "IPv4")
311     * @return void
312     */
313    private function get_leases_memfile ($lease_database, $type) {
314        // read file to array
315        $leases_from_file = @file($lease_database['name']);
316        // first item are titles
317        unset($leases_from_file[0]);
318        // if leases are present format to array
319        if (sizeof($leases_from_file)>0 && $leases_from_file!==false) {
320            // init array
321            $leases_parsed = array();
322            // loop and save leases
323            foreach ($leases_from_file as $l) {
324                if(strlen($l)>1) {
325                    // to array
326                    $l = explode(",", $l);
327
328                    // set state
329                    switch ($l[9]) {
330                        case 0:
331                            $l[9] = "default";
332                            break;
333                        case 1:
334                            $l[9] = "declined";
335                            break;
336                        case 2:
337                            $l[9] = "expired-reclaimed";
338                            break;
339                    }
340                    // save only active
341                    if ($l[4] > time() ) {
342                        $leases_parsed[] = array(
343                        					"address" => $l[0],
344                        					"hwaddr" => $l[1],
345                        					"client_id" => $l[2],
346                        					"valid_lifetime" => $l[3],
347                        					"expire" => date("Y-m-d H:i:s", $l[4]),
348                        					"subnet_id" => $l[5],
349                        					"fqdn_fwd" => $l[6],
350                        					"fqdn_rev" => $l[7],
351                        					"hostname" => $l[8],
352                        					"state" => $l[9]
353                        				);
354                    }
355                }
356            }
357        }
358        else {
359            throw new exception("Cannot read leases file ".$lease_database['name']);
360        }
361
362        // save result
363        if ($type=="IPv4")  { $this->leases4 = $leases_parsed; }
364        else                { $this->leases6 = $leases_parsed; }
365    }
366
367    /**
368     * Fetches leases from mysql database.
369     *
370     * @access private
371     * @param mixed $lease_database
372     * @param string $type (default: "IPv4")
373     * @return void
374     */
375    private function get_leases_mysql ($lease_database, $type) {
376        // if host not specified assume localhost
377        if (strlen($lease_database['host'])==0) { $lease_database['host'] = "localhost"; }
378        // open DB connection
379        $this->init_database_conection ($lease_database['user'], $lease_database['password'], $lease_database['host'], 3306, $lease_database['name']);
380        // set query
381        if($type=="IPv4") {
382            $query  = "select ";
383            $query .= "INET_NTOA(address) as `address`, hex(hwaddr) as hwaddr, hex(`client_id`) as client_id,`subnet_id`,`valid_lifetime`,`expire`,`name` as `state`,`fqdn_fwd`,`fqdn_rev`,`hostname` from `lease4` as a, ";
384            $query .= "`lease_state` as s where a.`state` = s.`state`;";
385        }
386        else {
387            throw new Exception("IPv6 leases not yet!");
388        }
389        // fetch leases
390		try { $leases = $this->Database_kea->getObjectsQuery($query); }
391		catch (Exception $e) {
392			throw new Exception($e->getMessage());
393		}
394		// save leases
395		if (sizeof($leases)>0) {
396    		// we need array
397    		$result = array();
398    		// loop
399    		foreach ($leases as $k=>$l) {
400        		$result[$k] = (array) $l;
401    		}
402
403    		// save
404    		if($type=="IPv4") {
405        		$this->leases4 = $result;
406            }
407            else {
408        		$this->leases6 = $result;
409            }
410		}
411    }
412
413    /**
414     * Fetches leases from postgres SQL.
415     *
416     * @access private
417     * @param mixed $lease_database
418     * @return void
419     */
420    private function get_leases_postgresql ($lease_database) {
421        throw new exception ("PostgresSQL not supported");
422    }
423
424
425
426
427
428
429
430
431
432    /* @reservations --------------- */
433
434    /**
435     * Saves reservations to $reservations object as array.
436     *
437     *  Note:
438     *      For IPv4 reservations KEA by default uses `reservations` item under subnet4 > reservations array.
439     *      It can also use hosts-database in MySQL, if hosts-database is set
440     *
441     *
442     *  For KEA v 1.0 only MySQL is supported. If needed later item can be added to $reservation_types and new method created
443     *
444     * @access public
445     * @param string $type (default: "IPv4")
446     * @return void
447     */
448    public function get_reservations ($type = "IPv4") {
449        // first check where they are stored - mysql, postgres or file
450        if($type=="IPv4") {
451            if (isset($this->config['Dhcp4']['hosts-database'])) {
452                $reservations_database = $this->config['Dhcp4']['hosts-database'];
453            }
454            else {
455                $reservations_database = false;
456            }
457        }
458        else {
459            if (isset($this->config['Dhcp4']['hosts-database'])) {
460                $reservations_database = $this->config['Dhcp6']['hosts-database'];
461            }
462            else {
463                $reservations_database = false;
464            }
465        }
466
467
468        // first check reservations under subnet > reservations, can be both
469        $this->get_reservations_config_file ($type);
470
471        // if set in config check also database
472        if ($reservations_database!==false) {
473            // set lease type
474            $reservations_database_type = $reservations_database['type'];
475
476            // id database type is set and valid check it also
477            if (!in_array($reservations_database_type, $this->reservation_types)) {
478                throw new exception ("Invalid reservations database type");
479            }
480            else {
481                // get leases
482                $type_l = "get_reservations_".$reservations_database_type;
483                $this->{$type_l} ($reservations_database, $type);
484            }
485        }
486    }
487
488    /**
489     * Fetches leases from memfile.
490     *
491     *  https://kea.isc.org/wiki/HostReservationDesign
492     *
493     * @access private
494     * @param mixed $type
495     * @return void
496     */
497    private function get_reservations_config_file ($type) {
498        // read file
499        if($type=="IPv4") {
500            // check if set
501            if (isset($this->config['Dhcp4']['subnet4'])) {
502                foreach ($this->config['Dhcp4']['subnet4'] as $s) {
503                    // set
504                    if (isset($s['reservations'])) {
505                        // save id
506                        unset($s_id);
507                        $s_id = isset($s['id']) ? $s['id'] : "";
508                        // init array
509                        $this->reservations4 = array();
510                        $m=0;
511                        // loop
512                        foreach ($s['reservations'] as $r) {
513                            $this->reservations4[$m] = array(
514                                                    "location"       => "Config file",
515                                                    "hw-address"     => $r['hw-address'],
516                                                    "ip-address"     => $r['ip-address'],
517                                                    "hostname"       => $r['hostname'],
518                                                    "dhcp4_subnet_id"=> $s_id,
519                                                    "subnet"         => $s['subnet']
520                                                    );
521                            // options
522                            if(isset($r['options'])) {
523                                $this->reservations4[$m]['options'] = array();
524                                foreach ($r['options'] as $o) {
525                                     $this->reservations4[$m]['options'][$o['name']] = $o['data'];
526                                }
527                            }
528                            // classes
529                            if(isset($r['client-classes'])) {
530                                $this->reservations4[$m]['classes'] = array();
531                                foreach ($r['client-classes'] as $c) {
532                                     $this->reservations4[$m]['classes'][] = $c;
533                                }
534                            }
535
536                            // reformat
537                            $this->reservations4[$m] = $this->reformat_empty_array_fields ($this->reservations4[$m], "/");
538
539                            // next index
540                            $m++;
541                        }
542                    }
543                }
544            }
545        }
546        else {
547            $this->reservations6 = file($reservations_database['name']);
548        }
549    }
550
551    /**
552     * Fetches leases from mysql database.
553     *
554     * @access private
555     * @param mixed $reservations_database  //database details
556     * @param mixed $type                   //ipv4 / ipv6
557     * @return void
558     */
559    private function get_reservations_mysql ($reservations_database, $type) {
560        // if host not specified assume localhost
561        if (strlen($reservations_database['host'])==0) { $reservations_database['host'] = "localhost"; }
562        // open DB connection
563        $this->init_database_conection ($reservations_database['user'], $reservations_database['password'], $reservations_database['host'], 3306, $reservations_database['name']);
564        // set query
565        if($type=="IPv4") {
566            $query = "select 'MySQL' as 'location', `dhcp4_subnet_id`, `ipv4_address` as `ip-address`, HEX(`dhcp_identifier`) as `hw-address`, `hostname` from `hosts`;";
567        }
568        else {
569            $query = "select * from `hosts`;";
570        }
571        // fetch leases
572		try { $reservations = $this->Database_kea->getObjectsQuery($query); }
573		catch (Exception $e) {
574			throw new Exception($e->getMessage());
575		}
576		// save leases
577		if (sizeof($reservations)>0) {
578    		// we need array
579    		$result = array();
580    		// loop
581    		foreach ($reservations as $k=>$l) {
582        		// check for subnet
583        		if ($l->dhcp4_subnet_id!==0 && strlen($l->dhcp4_subnet_id)>0) {
584            		if($type=="IPv4") {
585                		foreach($this->subnets4 as $s) {
586                    		if($s['id']==$l->dhcp4_subnet_id) {
587                        		$l->subnet = $s['subnet'];
588                    		}
589                		}
590            		}
591            		else {
592                		foreach($this->subnets6 as $s) {
593                    		if($s['id']==$l->dhcp6_subnet_id) {
594                        		$l->subnet = $s['subnet'];
595                    		}
596                		}
597                    }
598        		}
599
600        		// save
601        		if($type=="IPv4") {
602            		$this->reservations4[] = (array) $l;
603        		}
604        		else {
605            		$this->reservations6[] = (array) $l;
606        		}
607    		}
608		}
609    }
610
611
612
613
614
615
616
617    public function read_statistics () {
618        $sock = stream_socket_client('unix:///var/lib/kea/socket', $errno, $errstr);
619
620        $cmd = array("command"=>"list-commands");
621
622        fwrite($sock, json_encode($cmd)."\r\n");
623
624        echo fread($sock, 4096)."\n";
625
626        fclose($sock);
627    }
628}
629