1<?php
2
3/**
4 *	phpIPAM IP addresses class
5 */
6
7class Addresses extends Common_functions {
8
9
10	/**
11	 * (array of objects) to store addresses, address ID is array index
12	 *
13	 * (default value: array)
14	 *
15	 * @var mixed
16	 * @access public
17	 */
18	public $addresses = array();
19
20	/**
21	 * Address types array
22	 *
23	 * @var mixed
24	 * @access public
25	 */
26	public $address_types = array();
27
28	/**
29	 * Mail changelog or not
30	 *
31	 * (default value: true)
32	 *
33	 * @var bool
34	 * @access public
35	 */
36	public $mail_changelog = true;
37
38    /**
39     * Last insert id
40     *
41     * (default value: false)
42     *
43     * @var bool
44     * @access public
45     */
46    public $lastId = false;
47
48	/**
49	 * Subnets object
50	 *
51	 * @var mixed
52	 * @access protected
53	 */
54	protected $Subnets;
55
56	/**
57	 * PowerDNS object
58	 *
59	 * @var mixed
60	 * @access private
61	 */
62	private $PowerDNS;
63
64
65
66
67	/**
68	 * __construct function
69	 *
70	 * @access public
71	 */
72	public function __construct (Database_PDO $Database) {
73		parent::__construct();
74
75		# Save database object
76		$this->Database = $Database;
77		# initialize Result
78		$this->Result = new Result ();
79
80		# Log object
81		$this->Log = new Logging ($this->Database);
82	}
83
84
85
86
87
88
89
90
91
92	/**
93	* @address tag methods
94	* -------------------------------
95	*/
96
97	/**
98	 * Returns array of address types.
99	 *
100	 * @access public
101	 * @return array of address types and parameters
102	 */
103	public function addresses_types_fetch () {
104    	# fetch
105    	$types = $this->fetch_all_objects ("ipTags", "id");
106
107		# save to array
108		$types_out = array();
109		foreach($types as $t) {
110			$types_out[$t->id] = (array) $t;
111		}
112		# save
113		$this->address_types = $types_out;
114		# return
115		return $types_out;
116	}
117
118	/**
119	 * Sets address tag
120	 *
121	 * @access public
122	 * @param int $state
123	 * @return mixed tag
124	 */
125	public function address_type_format_tag ($state) {
126		# fetch address states
127		$this->addresses_types_fetch();
128		# result
129		if(!isset($this->address_types[$state]))	{
130			return "";
131		}
132		else {
133			if($this->address_types[$state]['showtag']==1) {
134				return "<i class='fa fa-".$this->address_types[$state]['type']." fa-tag state' rel='tooltip' style='color:".$this->address_types[$state]['bgcolor']."' title='"._($this->address_types[$state]['type'])."'></i>";
135			}
136		}
137	}
138
139	/**
140	 * returns address type from index
141	 *
142	 *		1 > Offline
143	 *
144	 * @access public
145	 * @param int $index
146	 * @return mixed address type
147	 */
148	public function address_type_index_to_type ($index) {
149		# fetch address states
150		$this->addresses_types_fetch();
151		# return
152		if(isset($this->address_types[$index])) {
153			return $this->address_types[$index]['type'];
154		}
155		else {
156			return $index;
157		}
158	}
159
160	/**
161	 * Returns address index from type
162	 *
163	 *	Offline > 1
164	 *
165	 * @access public
166	 * @param mixed $type
167	 * @return void
168	 */
169	public function address_type_type_to_index ($type = "Used") {
170		# null of no length
171		$type = strlen($type)==0 || is_null($type) ? "Used" : $type;
172		# fetch address states
173		$this->addresses_types_fetch();
174		# reindex
175		$states_assoc = array();
176		foreach($this->address_types as $s) {
177			$states_assoc[$s['type']] = $s;
178		}
179		# return
180		if(isset($states_assoc[$type])) {
181			return $states_assoc[$type]['id'];
182		}
183		else {
184			return $type;
185		}
186	}
187
188
189
190
191
192
193
194
195
196
197
198
199	/**
200	* @address methods
201	* -------------------------------
202	*/
203
204	/**
205	 * Fetches address by specified method
206	 *
207	 * @access public
208	 * @param string $method (default: "id")
209	 * @param mixed $id
210	 * @return object address
211	 */
212	public function fetch_address ($method, $id) {
213		# null method
214		$method = is_null($method) ? "id" : $method;
215		# check cache first
216		if(isset($this->addresses[$id]))	{
217			return $this->addresses[$id];
218		}
219		else {
220			try { $address = $this->Database->getObjectQuery("SELECT * FROM `ipaddresses` where `$method` = ? limit 1;", array($id)); }
221			catch (Exception $e) {
222				$this->Result->show("danger", _("Error: ").$e->getMessage());
223				return false;
224			}
225			# save to addresses cache
226			if(!is_null($address)) {
227				# add decimal format
228				$address->ip = $this->transform_to_dotted ($address->ip_addr);
229				# save to subnets
230				$this->addresses[$id] = (object) $address;
231			}
232			#result
233			return !is_null($address) ? $address : false;
234		}
235	}
236
237	/**
238	 * Fetch addresses on int ip_addr and subnetId
239	 *
240	 * @access public
241	 * @param mixed $ip_addr
242	 * @param mixed $subnetId
243	 * @return void
244	 */
245	public function fetch_address_multiple_criteria ($ip_addr, $subnetId) {
246		try { $address = $this->Database->getObjectQuery("SELECT * FROM `ipaddresses` where `ip_addr` = ? and `subnetId` = ? limit 1;", array($ip_addr, $subnetId)); }
247		catch (Exception $e) {
248			$this->Result->show("danger", _("Error: ").$e->getMessage());
249			return false;
250		}
251		# save to addresses cache
252		if(is_object($address)) {
253			# add decimal format
254			$address->ip = $this->transform_to_dotted ($address->ip_addr);
255			# save to subnets
256			$this->addresses[$address->id] = (object) $address;
257		}
258		#result
259		return !is_null($address) ? $address : false;
260	}
261
262	/**
263	 * Bulk fetch similar addresses.
264	 *
265	 * The subnets details page will call search_similar_addresses() for EVERY IP in the subnet.
266	 * Bulk request and cache this information on the first call. Sort returned IPs by ip_addr.
267	 *
268	 * Returns an array indexed by $value.  $bulk_search[$value] = Array of similar IPs with same $value
269	 *
270	 * @access public
271	 * @param object $address
272	 * @param mixed $linked_field
273	 * @param mixed $value
274	 * @return void
275	 */
276	private function bulk_fetch_similar_addresses($address, $linked_field, $value) {
277		// Check cache
278		$cached_item = $this->cache_check("similar_addresses", "f=$linked_field id=$address->subnetId");
279		if (is_object($cached_item))
280			return $cached_item->result;
281
282		// Fetch all similar addresses for entire subnet.
283		try {
284			$query = "SELECT * FROM `ipaddresses` WHERE `state`<>4 AND `$linked_field` IN (SELECT `$linked_field` FROM `ipaddresses` WHERE `subnetId`=? AND LENGTH(`$linked_field`)>0) ORDER BY LPAD(ip_addr,39,0)";
285			$linked_subnet_addrs = $this->Database->getObjectsQuery($query, array($address->subnetId));
286		} catch (Exception $e) {
287			$this->Result->show("danger", _("Error: ").$e->getMessage());
288			return false;
289		}
290
291		$bulk_search = [];
292		if (is_array($linked_subnet_addrs)) {
293			foreach($linked_subnet_addrs as $linked) {
294				// Index by $linked->{$linked_field} for easy searching.
295				$bulk_search[$linked->{$linked_field}][] = $linked;
296			}
297		}
298
299		// Save to cache and return
300		$this->cache_write ("similar_addresses", (object) ["id"=>"f=$linked_field id=$address->subnetId", "result" => $bulk_search]);
301		return $bulk_search;
302	}
303
304	/**
305	 * Searches database for similar addresses
306	 *
307	 * @access public
308	 * @param object $address
309	 * @param mixed $linked_field
310	 * @param mixed $value
311	 * @return void
312	 */
313	public function search_similar_addresses ($address, $linked_field, $value) {
314		// sanity checks
315		if(!is_object($address) || !property_exists($address, $linked_field) || strlen($value)==0)
316			return false;
317
318		$bulk_search = $this->bulk_fetch_similar_addresses($address, $linked_field, $value);
319
320		// Check if similar addresses exist with the specifed $value
321		if (!isset($bulk_search[$address->{$linked_field}]))
322			return false;
323
324		$results = $bulk_search[$address->{$linked_field}];
325		// Remove $address from results
326		foreach ($results as $i => $similar) {
327			if ($similar->id == $address->id) unset($results[$i]);
328		}
329
330		return !empty($results) ? $results : false;
331	}
332
333	/**
334	 * Address modification
335	 *
336	 * @access public
337	 * @param mixed $address
338	 * @param bool $mail_changelog (default: true)
339	 * @return void
340	 */
341	public function modify_address ($address, $mail_changelog = true) {
342		# save changelog
343		$this->mail_changelog  = $mail_changelog;
344		# null empty values
345		$address = $this->reformat_empty_array_fields ($address, null);
346		# strip tags
347		$address = $this->strip_input_tags ($address);
348		# execute based on action
349		if($address['action']=="add")			{ return $this->modify_address_add ($address); }							//create new address
350		elseif($address['action']=="edit")		{ return $this->modify_address_edit ($address); }							//modify existing address
351		elseif($address['action']=="delete")	{ return $this->modify_address_delete ($address); }							//delete address
352		elseif($address['action']=="move")		{ return $this->modify_address_move ($address); }							//move to new subnet
353		else									{ return $this->Result->show("danger", _("Invalid action"), true); }
354	}
355
356	/**
357	 * Inserts new IP address to table
358	 *
359	 * @access protected
360	 * @param array $address
361	 * @return boolean success/failure
362	 */
363	protected function modify_address_add ($address) {
364		# user - permissions
365		$User = new User ($this->Database);
366		# set insert array
367		$insert = array(
368						"ip_addr"               => $this->transform_address($address['ip_addr'],"decimal"),
369						"subnetId"              => $address['subnetId'],
370						"description"           => @$address['description'],
371						"hostname"              => @$address['hostname'],
372						"mac"                   => @$address['mac'],
373						"owner"                 => @$address['owner'],
374						"state"                 => @$address['state'],
375						"port"                  => @$address['port'],
376						"note"                  => @$address['note'],
377						"is_gateway"            => @$address['is_gateway'],
378						"excludePing"           => @$address['excludePing'],
379						"PTRignore"             => @$address['PTRignore'],
380						"firewallAddressObject" => @$address['firewallAddressObject'],
381						"lastSeen"              => @$address['lastSeen']
382						);
383		# permissions
384		if($this->api===true || $User->get_module_permissions ("devices")>1) {
385			if (array_key_exists('switch', $address)) {
386				if (empty($address['switch']) || is_numeric($address['switch']))
387					$insert['switch'] = $address['switch'] > 0 ? $address['switch'] : NULL;
388			}
389		}
390		# customer
391		if($this->api===true || $User->get_module_permissions ("customers")>1) {
392			if (array_key_exists('customer_id', $address)) {
393				if (empty($address['customer_id']) || is_numeric($address['customer_id']))
394					$insert['customer_id'] = $address['customer_id'] > 0 ? $address['customer_id'] : NULL;
395			}
396		}
397		# location
398		if ($this->api===true || $User->get_module_permissions ("locations")>1) {
399			if (array_key_exists('location_item', $address)) {
400				if (empty($address['location_item']) || is_numeric($address['location_item']))
401					$insert['location'] = $address['location_item'] > 0 ? $address['location_item'] : NULL;
402			}
403			if (array_key_exists('location', $address)) {
404				if (empty($address['location']) || is_numeric($address['location']))
405					$insert['location'] = $address['location'] > 0 ? $address['location'] : NULL;
406			}
407		}
408		# custom fields, append to array
409		foreach($this->set_custom_fields() as $c) {
410			$insert[$c['name']] = !empty($address[$c['name']]) ? $address[$c['name']] : $c['Default'];
411		}
412
413		# null empty values
414		$insert = $this->reformat_empty_array_fields ($insert, null);
415
416		# remove gateway
417		if($address['is_gateway']==1)	{ $this->remove_gateway ($address['subnetId']); }
418
419		# execute
420		try { $this->Database->insertObject("ipaddresses", $insert); }
421		catch (Exception $e) {
422			$this->Log->write( "Address create", "Failed to create new address<hr>".$e->getMessage()."<hr>".$this->array_to_log($this->reformat_empty_array_fields ($address, "NULL")), 2);
423			$this->Result->show("danger", _("Error: ").$e->getMessage(), false);
424			return false;
425		}
426		# save id
427		$this->lastId = $this->Database->lastInsertId();
428
429		# log and changelog
430		$address['id'] = $this->lastId;
431		$this->Log->write( "Address created", "New address created<hr>".$this->array_to_log($this->reformat_empty_array_fields ($address, "NULL")), 0);
432		$this->Log->write_changelog('ip_addr', "add", 'success', array(), $address, $this->mail_changelog);
433
434		# edit DNS PTR record
435		$this->ptr_modify ("add", $insert);
436
437		# threshold alert
438		$this->threshold_check($address);
439
440		# ok
441		return true;
442	}
443
444	/**
445	 * Modifies address in table or whole range if requested
446	 *
447	 * @access protected
448	 * @param array $address
449	 * @return boolean success/failure
450	 */
451	protected function modify_address_edit ($address) {
452		# fetch old details for logging
453		$address_old = $this->fetch_address (null, $address['id']);
454		if (isset($address['section'])) $address_old->section = $address['section'];
455		# user - permissions
456		$User = new User ($this->Database);
457		# set update array
458		$insert = array(
459						"id"          =>$address['id'],
460						"subnetId"    =>$address['subnetId'],
461						"ip_addr"     =>$this->transform_address($address['ip_addr'], "decimal"),
462						"description" =>@$address['description'],
463						"hostname"    =>@$address['hostname'],
464						"mac"         =>@$address['mac'],
465						"owner"       =>@$address['owner'],
466						"state"       =>@$address['state'],
467						"port"        =>@$address['port'],
468						"note"        =>@$address['note'],
469						"is_gateway"  =>@$address['is_gateway'],
470						"excludePing" =>@$address['excludePing'],
471						"PTRignore"   =>@$address['PTRignore'],
472						"lastSeen"    =>@$address['lastSeen']
473						);
474		# permissions
475		if($this->api===true || $User->get_module_permissions ("devices")>1) {
476			if (array_key_exists('switch', $address)) {
477				if (empty($address['switch']) || is_numeric($address['switch']))
478					$insert['switch'] = $address['switch'] > 0 ? $address['switch'] : NULL;
479			}
480		}
481		# customer
482		if($this->api===true || $User->get_module_permissions ("customers")>1) {
483			if (array_key_exists('customer_id', $address)) {
484				if (empty($address['customer_id']) || is_numeric($address['customer_id']))
485					$insert['customer_id'] = $address['customer_id'] > 0 ? $address['customer_id'] : NULL;
486			}
487		}
488		# location
489		if ($this->api===true || $User->get_module_permissions ("locations")>1) {
490			if (array_key_exists('location_item', $address)) {
491				if (empty($address['location_item']) || is_numeric($address['location_item']))
492					$insert['location'] = $address['location_item'] > 0 ? $address['location_item'] : NULL;
493			}
494			if (array_key_exists('location', $address)) {
495				if (empty($address['location']) || is_numeric($address['location']))
496					$insert['location'] = $address['location'] > 0 ? $address['location'] : NULL;
497			}
498		}
499		# custom fields, append to array
500		foreach($this->set_custom_fields() as $c) {
501			$insert[$c['name']] = !empty($address[$c['name']]) ? $address[$c['name']] : $c['Default'];
502		}
503
504		# set primary key for update
505		if($address['type']=="series") {
506			$id1 = "subnetId";
507			$id2 = "ip_addr";
508			unset($insert['id']);
509		} else {
510			$id1 = "id";
511			$id2 = null;
512		}
513
514		# remove gateway
515		if($address['is_gateway']==1)	{ $this->remove_gateway ($address['subnetId']); }
516
517		# execute
518		try { $this->Database->updateObject("ipaddresses", $insert, $id1, $id2); }
519		catch (Exception $e) {
520			$this->Log->write( "Address edit", "Failed to edit address $address[ip_addr]<hr>".$e->getMessage()."<hr>".$this->array_to_log($this->reformat_empty_array_fields ($address, "NULL")), 2);
521			$this->Result->show("danger", _("Error: ").$e->getMessage(), false);
522			return false;
523		}
524
525		# set the firewall address object to avoid logging
526		$address['firewallAddressObject'] = $address_old->firewallAddressObject;
527
528 		# log and changelog
529		$this->Log->write( "Address updated", "Address $address[ip_addr] updated<hr>".$this->array_to_log($this->reformat_empty_array_fields ($address, "NULL")), 0);
530		$this->Log->write_changelog('ip_addr', "edit", 'success', (array) $address_old, $address, $this->mail_changelog);
531
532		# edit DNS PTR record
533		$insert['PTR']=@$address['PTR'];
534		$this->ptr_modify ("edit", $insert);
535
536		# ok
537		return true;
538	}
539
540	/**
541	 * Deletes address or address range.
542	 *
543	 * @access protected
544	 * @param array $address
545	 * @return boolean success/failure
546	 */
547	protected function modify_address_delete ($address) {
548		# fetch old details for logging
549		$address_old = $this->fetch_address (null, $address['id']);
550		if (isset($address['section'])) $address_old->section = $address['section'];
551
552		# series?
553		if($address['type']=="series") {
554			$field  = "subnetId";	$value  = $address['subnetId'];
555			$field2 = "ip_addr";	$value2 = $this->transform_address ($address['ip_addr'], "decimal");
556		} else {
557			$field  = "id";			$value  = $address['id'];
558			$field2 = null;			$value2 = null;
559		}
560		# execute
561		try { $this->Database->deleteRow("ipaddresses", $field, $value, $field2, $value2); }
562		catch (Exception $e) {
563			$this->Log->write( "Address delete", "Failed to delete address $address[ip_addr]<hr>".$e->getMessage()."<hr>".$this->array_to_log((array) $address_old), 2);
564			$this->Result->show("danger", _("Error: ").$e->getMessage(), false);
565			return false;
566		}
567
568		# log and changelog
569		$this->Log->write( "Address deleted", "Address $address[ip_addr] deleted<hr>".$this->array_to_log((array) $address_old), 0);
570		$this->Log->write_changelog('ip_addr', "delete", 'success', (array) $address_old, array(), $this->mail_changelog);
571
572		# edit DNS PTR record
573		$this->ptr_modify ("delete", $address);
574
575		# remove all referenced records
576		if(@$address['remove_all_dns_records']=="1") {
577    		$this->pdns_remove_ip_and_hostname_records ($address);
578        }
579        # remove from NAT
580        $this->remove_address_nat_items ($address['id'], true);
581		# ok
582		return true;
583	}
584
585	/**
586	 * Moves address to new subnet
587	 *
588	 * @access protected
589	 * @param array $address
590	 * @return boolean success/failure
591	 */
592	protected function modify_address_move ($address) {
593		# execute
594		try { $this->Database->updateObject("ipaddresses", array("subnetId"=>$address['newSubnet'], "id"=>$address['id'])); }
595		catch (Exception $e) {
596			$this->Result->show("danger", _("Error: ").$e->getMessage(), false);
597			return false;
598		}
599		# ok
600		return true;
601	}
602
603	/**
604	 * Remove item from nat when item is removed
605	 *
606	 * @method remove_nat_item
607	 *
608	 * @param  int $obj_id
609	 * @param  bool $print
610	 *
611	 * @return int
612	 */
613	public function remove_address_nat_items ($obj_id = 0, $print = true) {
614		# set found flag for returns
615		$found = 0;
616		# fetch all nats
617		try { $all_nats = $this->Database->getObjectsQuery ("select * from `nat` where `src` like :id or `dst` like :id", array ("id"=>'%"'.$obj_id.'"%')); }
618		catch (Exception $e) {
619			$this->Result->show("danger", _("Error: ").$e->getMessage());
620			return false;
621		}
622		# loop and check for object ids
623		if(!empty($all_nats)) {
624			# init admin object
625			$Admin = new Admin ($this->Database, false);
626			# loop
627			foreach ($all_nats as $nat) {
628			    # remove item from nat
629			    $s = json_decode($nat->src, true);
630			    $d = json_decode($nat->dst, true);
631
632			    if(is_array($s['ipaddresses']))
633			    $s['ipaddresses'] = array_diff($s['ipaddresses'], array($obj_id));
634			    if(is_array($d['ipaddresses']))
635			    $d['ipaddresses'] = array_diff($d['ipaddresses'], array($obj_id));
636
637			    # save back and update
638			    $src_new = json_encode(array_filter($s));
639			    $dst_new = json_encode(array_filter($d));
640
641			    # update only if diff found
642			    if($s!=$src_new || $d!=$dst_new) {
643			    	$found++;
644
645				    if($Admin->object_modify ("nat", "edit", "id", array("id"=>$nat->id, "src"=>$src_new, "dst"=>$dst_new))!==false) {
646				    	if($print) {
647					        $this->Result->show("success", "Address removed from NAT", false);
648						}
649				    }
650			    }
651			}
652		}
653		# return
654		return $found;
655	}
656
657	/**
658	 * Updates hostname for IP addresses
659	 *
660	 * @method update_address_hostname
661	 *
662	 * @param  mixed $ip
663	 * @param  int $id
664	 * @param  string $hostname
665	 *
666	 * @return void
667	 */
668	public function update_address_hostname ($ip, $id, $hostname = "") {
669		if(is_numeric($id) && strlen($hostname)>0) {
670			try { $this->Database->updateObject("ipaddresses", array("id"=>$id, "hostname"=>$hostname)); }
671			catch (Exception $e) {
672				return false;
673			}
674			// save log
675			$this->Log->write( "Address DNS resolved", "Address $ip resolved<hr>".$this->array_to_log((array) $hostname), 0);
676			$this->Log->write_changelog('ip_addr', "edit", 'success', array ("id"=>$id, "hostname"=>""), array("id"=>$id, "hostname"=>$hostname), $this->mail_changelog);
677		}
678	}
679
680	/**
681	 * Checks if subnet usage is over threshold and sends alert
682	 *
683	 * @access private
684	 * @param mixed $address
685	 * @return void
686	 */
687	private function threshold_check ($address) {
688    	$address = (object) $address;
689    	$content = array();
690    	$content_plain = array();
691
692        # fetch settings
693        $this->get_settings ();
694    	# enabled ?
695    	if ($this->settings->enableThreshold=="1") {
696        	# object
697        	if (!is_object($this->Subnets)) {
698            	$this->Subnets = new Subnets ($this->Database);
699        	}
700        	# fetch subnet
701        	$subnet = $this->Subnets->fetch_subnet("id", $address->subnetId);
702        	# threshold set ?
703        	if ($subnet->threshold>0) {
704            	# calculate subnet usage
705            	$subnet_usage = $this->Subnets->calculate_subnet_usage ($subnet);
706            	# if over send mail
707            	if (gmp_strval(gmp_sub(100,(int) round($subnet_usage['freehosts_percent'], 0))) > $subnet->threshold) {
708                	// fetch mail settings
709                	$Tools = new Tools ($this->Database);
710                	$admins        = $Tools->fetch_multiple_objects ("users", "role", "Administrator");
711                	// if some recipients
712                	if ($admins !== false) {
713						# try to send
714						try {
715	                    	// mail settings
716	                        $mail_settings = $Tools->fetch_object ("settingsMail", "id", 1);
717	                    	// mail class
718	                    	$phpipam_mail = new phpipam_mail ($this->settings, $mail_settings);
719
720	                        // set parameters
721	                        $subject = "Subnet threshold limit reached"." (".$this->transform_address($subnet->subnet,"dotted")."/".$subnet->mask.")";
722	                        $content[] = "<table style='margin-left:10px;margin-top:5px;width:auto;padding:0px;border-collapse:collapse;'>";
723	                        $content[] = "<tr><td style='padding:5px;margin:0px;color:#333;font-size:16px;text-shadow:1px 1px 1px white;border-bottom:1px solid #eeeeee;' colspan='2'>$this->mail_font_style<strong>$subject</font></td></tr>";
724	                        $content[] = '<tr><td style="padding: 0px;padding-left:10px;margin:0px;line-height:18px;text-align:left;">'.$this->mail_font_style.''._('Subnet').'</a></font></td>	<td style="padding: 0px;padding-left:15px;margin:0px;line-height:18px;text-align:left;padding-top:10px;"><a href="'.$this->createURL().''.create_link("subnets",$subnet->sectionId, $subnet->id).'">'.$this->mail_font_style_href . $this->transform_address($subnet->subnet,"dotted")."/".$subnet->mask .'</font></a></td></tr>';
725	                        $content[] = '<tr><td style="padding: 0px;padding-left:10px;margin:0px;line-height:18px;text-align:left;">'.$this->mail_font_style.''._('Description').'</font></td>	  	<td style="padding: 0px;padding-left:15px;margin:0px;line-height:18px;text-align:left;">'.$this->mail_font_style.''. $subnet->description .'</font></td></tr>';
726	                        $content[] = '<tr><td style="padding: 0px;padding-left:10px;margin:0px;line-height:18px;text-align:left;">'.$this->mail_font_style.''._('Usage').' (%)</font></td>	<td style="padding: 0px;padding-left:15px;margin:0px;line-height:18px;text-align:left;">'.$this->mail_font_style.''. gmp_strval(gmp_sub(100,(int) round($subnet_usage['freehosts_percent'], 0))) .'</font></td></tr>';
727	                        $content[] = "</table>";
728	                        // plain
729	                        $content_plain[] = "$subject"."\r\n------------------------------\r\n";
730	                        $content_plain[] = _("Subnet").": ".$this->transform_address($subnet->subnet,"dotted")."/".$subnet->mask;
731	                        $content_plain[] = _("Usage")." (%) : ".gmp_strval(gmp_sub(100,(int) round($subnet_usage['freehosts_percent'], 0)));
732
733	                        # set content
734	                        $content 		= $phpipam_mail->generate_message (implode("\r\n", $content));
735	                        $content_plain 	= implode("\r\n",$content_plain);
736
737                        	$phpipam_mail->Php_mailer->setFrom($mail_settings->mAdminMail, $mail_settings->mAdminName);
738                        	//add all admins to CC
739                        	$recipients = $this->changelog_mail_get_recipients ($subnet->id);
740
741                        	if ($recipients!==false) {
742                        		foreach($recipients as $a) {
743                    			    $phpipam_mail->Php_mailer->addAddress($a->email);
744                        		}
745
746                            	$phpipam_mail->Php_mailer->Subject = $subject;
747                            	$phpipam_mail->Php_mailer->msgHTML($content);
748                            	$phpipam_mail->Php_mailer->AltBody = $content_plain;
749                            	//send
750                            	$phpipam_mail->Php_mailer->send();
751                        	}
752                        	else {
753                            	return true;
754                        	}
755                        } catch (phpmailerException $e) {
756                        	$this->Result->show("danger", "Mailer Error: ".$e->errorMessage(), true);
757                        } catch (Exception $e) {
758                        	$this->Result->show("danger", "Mailer Error: ".$e->getMessage(), true);
759                        }
760                    }
761            	}
762        	}
763        	else {
764            	return true;
765        	}
766    	}
767    	else {
768        	return true;
769    	}
770	}
771
772	/**
773	 * Removes gateway if it exists
774	 *
775	 * @access public
776	 * @param mixed $subnetId
777	 * @return void
778	 */
779	public function remove_gateway ($subnetId) {
780		try { $this->Database->updateObject("ipaddresses", array("subnetId"=>$subnetId, "is_gateway"=>0), "subnetId"); }
781		catch (Exception $e) {
782			$this->Result->show("danger", _("Error: ").$e->getMessage());
783			return false;
784		}
785	}
786
787	/**
788	 * Fetches custom IP address fields
789	 *
790	 * @access public
791	 * @return object custom address fields
792	 */
793	public function set_custom_fields () {
794		# Tools object
795		$Tools = new Tools ($this->Database);
796		# fetch
797		return $Tools->fetch_custom_fields ('ipaddresses');
798	}
799
800	/**
801	 * Checks if address already exists in subnet
802	 *
803	 *	if cnt is false we will return id if it exists and false ifnot
804	 *
805	 * @access public
806	 * @param int $address
807	 * @param int $subnetId
808	 * @param int $subnetId
809	 * @return boolean success/failure
810	 */
811	public function address_exists ($address, $subnetId, $cnt = true) {
812		# make sure it is in decimal format
813		$address = $this->transform_address($address, "decimal");
814		# check
815		if($cnt===true) { $query = "select count(*) as `cnt` from `ipaddresses` where `subnetId`=? and `ip_addr`=?;"; }
816		else			{ $query = "select `id` from `ipaddresses` where `subnetId`=? and `ip_addr`=?;";  }
817		# fetch
818		try { $count = $this->Database->getObjectQuery($query, array($subnetId, $address)); }
819		catch (Exception $e) {
820			$this->Result->show("danger", _("Error: ").$e->getMessage());
821			return false;
822		}
823		# result
824		if ($cnt===true)	{ return $count->cnt==0 ? false : true; }
825		else				{ return is_null($count->id) ? false : $count->id; }
826	}
827
828	/**
829	 * Calculates diff between two IP addresses
830	 *
831	 * @access public
832	 * @param int $ip1
833	 * @param int $ip2
834	 * @return void
835	 */
836	public function calculate_address_diff ($ip1, $ip2) {
837		return gmp_strval(gmp_sub($ip2, $ip1));
838	}
839
840
841	/**
842	 * Returns first available subnet address, false if none
843	 *
844	 * @access public
845	 * @param int $subnetId
846	 * @param obj $Subnets
847	 * @return int / false
848	 */
849	public function get_first_available_address ($subnetId, $Subnets) {
850
851		# fetch all addresses in subnet and subnet
852		$addresses = $this->fetch_subnet_addresses ($subnetId, "ip_addr", "asc", array("ip_addr"));
853		if (!is_array($addresses)) { $addresses = array(); }
854		$subnet = (array) $Subnets->fetch_subnet(null, $subnetId);
855
856		# if folder return false
857		if ($subnet['isFolder']=="1")                                                                   { return false; }
858
859		# false if slaves
860		$this->Subnets = new Subnets ($this->Database);
861		if($this->Subnets->has_slaves ($subnetId))                                                      { return false; }
862
863	    # get max hosts
864	    $max_hosts = $Subnets->get_max_hosts ($subnet['mask'], $this->identify_address($subnet['subnet']));
865
866		# full subnet?
867		if(sizeof($addresses)>=$max_hosts)																{ return false; } 	//full subnet
868
869		# set type
870		$ip_version = $this->identify_address ($subnet['subnet']);
871	    # get first diff > 1
872	    if(sizeof($addresses)>0) {
873		    foreach($addresses as $k=>$ipaddress) {
874			    # check subnet and first IP
875			    if($k==0) {
876				    # /31 fix
877				    if($subnet['mask']==31)	{
878					    if(gmp_strval(gmp_sub($addresses[$k]->ip_addr, $subnet['subnet']))>0) 			{ return gmp_strval($subnet['subnet']); }
879				    } else {
880					    if(gmp_strval(gmp_sub($addresses[$k]->ip_addr, $subnet['subnet']))>1) 			{ return gmp_strval(gmp_add($subnet['subnet'], 1)); }
881					    elseif($ip_version=="IPv6") {
882						    if(sizeof($addresses)==1) {
883							    if(gmp_strval(gmp_sub($addresses[$k]->ip_addr, $subnet['subnet']))==0)	{ return gmp_strval(gmp_add($subnet['subnet'], 1)); }
884						    }
885					    }
886				    }
887			    }
888			    else {
889				    if(gmp_strval(gmp_sub($addresses[$k]->ip_addr, $addresses[$k-1]->ip_addr))>1) 		{ return gmp_strval(gmp_add($addresses[$k-1]->ip_addr, 1)); }
890			    }
891		    }
892		    # all consecutive, last + 1
893		    																							{ return gmp_strval(gmp_add($addresses[$k]->ip_addr, 1)); }
894	    }
895	    # no addresses
896	    else {
897		    # /32, /31
898		    if($subnet['mask']==32 || $subnet['mask']==31 || $ip_version=="IPv6") 						{ return $subnet['subnet']; }
899		    else																						{ return gmp_strval(gmp_add($subnet['subnet'], 1)); }
900	    }
901	}
902
903
904
905
906
907
908
909
910
911	/**
912	 * @powerDNS
913	 * -------------------------------
914	 */
915
916	/**
917	 * Modifes powerDNS PTR record
918	 *
919	 * @access public
920	 * @param mixed $action
921	 * @param mixed $address
922	 * @param bool $print_error (default: true)
923	 * @return void
924	 */
925	public function ptr_modify ($action, $address, $print_error = true) {
926        // fetch settings
927        $this->get_settings ();
928        //check if powerdns enabled
929        if ($this->settings->enablePowerDNS!=1) {
930            return false;
931        }
932        //enabled, proceed
933        else {
934    		// first check if subnet selected for PTR records
935    		$this->initialize_subnets_object ();
936    		$subnet = $this->Subnets->fetch_subnet ("id", $address['subnetId']);
937    		if ($subnet->DNSrecursive!="1") { return false; }
938
939    		// ignore if PTRignore set
940    		if ($address['PTRignore']=="1")	{
941				// validate db
942				$this->pdns_validate_connection ();
943				// remove if it exists
944				if ($this->ptr_exists ($address['PTR'])) {
945					$this->ptr_delete ($address, false);
946										{ return false; }
947				}
948				else {
949										{ return true; }
950				}
951    		}
952    		// validate db
953    		$this->pdns_validate_connection ();
954
955    		// to object
956    		$address = (object) $address;
957    		# execute based on action
958    		if($action=="add")				{ return $this->ptr_add ($address, $print_error); }							//create new PTR
959    		elseif($action=="edit")			{ return $this->ptr_edit ($address, $print_error); }						//modify existing PTR
960    		elseif($action=="delete")		{ return $this->ptr_delete ($address, $print_error); }						//delete PTR
961    		else							{ return $this->Result->show("danger", _("Invalid PDNS action"), true); }
962        }
963	}
964
965	/**
966	 * This function removes all records - ip and hostname referenced by address.
967	 *
968	 * @access public
969	 * @param mixed $address
970	 * @return void
971	 */
972	public function pdns_remove_ip_and_hostname_records ($address) {
973        // fetch settings
974        $this->get_settings ();
975        //check if powerdns enabled
976        if ($this->settings->enablePowerDNS!=1) {
977            return false;
978        }
979		// validate db
980		$this->pdns_validate_connection ();
981		// execute
982		return $this->PowerDNS->pdns_remove_ip_and_hostname_records ($address['hostname'], $address['ip_addr']);
983	}
984
985	/**
986	 *  Validates pdns database connection
987	 *
988	 * @access public
989	 * @param bool $die (default: false)
990	 * @return void
991	 */
992	public function pdns_validate_connection ($die = true) {
993		# powerDNS class
994		$this->PowerDNS = new PowerDNS ($this->Database);
995		# add API info
996		if(isset($this->api)) {
997			$this->PowerDNS->api = $this->api;
998		}
999		# check connection
1000		if($this->PowerDNS->db_check()===false && $die) { $this->Result->show("danger", _("Cannot connect to powerDNS database"), true); }
1001		# get settings
1002		$this->get_settings ();
1003	}
1004
1005	/**
1006	 * Set zone name and fetch domain details
1007	 *
1008	 * @access private
1009	 * @param mixed $subnet_id
1010	 * @return array|false
1011	 */
1012	private function pdns_fetch_domain ($subnet_id) {
1013		# initialize subnets
1014		$this->initialize_subnets_object ();
1015		// fetch subnet
1016		$subnet = $this->Subnets->fetch_subnet ("id", $subnet_id);
1017		if($subnet===false)							{  $this->Result->show("danger", _("Invalid subnet Id"), true); }
1018
1019		// set PTR zone name from IP/mash
1020		$zone = $this->PowerDNS->get_ptr_zone_name ($this->transform_address ($subnet->subnet, "dotted"), $subnet->mask);
1021		// try to fetch
1022		return  $this->PowerDNS->fetch_domain_by_name ($zone);
1023	}
1024
1025	/**
1026	 * Create new PTR record when adding new IP address
1027	 *
1028	 * @access public
1029	 * @param mixed $address
1030	 * @param mixed $print_error (default: true)
1031	 * @param mixed $id (default: NULL)
1032	 * @return void
1033	 */
1034	public function ptr_add ($address, $print_error = true, $id = null) {
1035		// decode values
1036		$values = json_decode($this->settings->powerDNS);
1037
1038    	// set default hostname for PTR if set
1039    	if (strlen($address->hostname)==0) {
1040        	if (strlen($values->def_ptr_domain)>0) {
1041            	$address->hostname = $values->def_ptr_domain;
1042        	}
1043    	}
1044		// validate hostname
1045		if ($this->validate_hostname ($address->hostname)===false)		{ return false; }
1046		// fetch domain
1047		$domain = $this->pdns_fetch_domain ($address->subnetId);
1048
1049		// formulate new record
1050		$record = $this->PowerDNS->formulate_new_record ($domain->id, $this->PowerDNS->get_ip_ptr_name ($this->transform_address ($address->ip_addr, "dotted")), "PTR", $address->hostname, $values->ttl);
1051		// insert record
1052		$this->PowerDNS->add_domain_record ($record, false);
1053		// link to address
1054		$id = $id===null ? $this->lastId : $id;
1055		$this->ptr_link ($id, $this->PowerDNS->lastId);
1056		// ok
1057		if ($print_error && php_sapi_name()!="cli")
1058		$this->Result->show("success", "PTR record created", false);
1059
1060		return true;
1061	}
1062
1063	/**
1064	 * Edits PTR
1065	 *
1066	 * @access public
1067	 * @param mixed $address
1068	 * @param mixed $print_error (default: true)
1069	 * @return void
1070	 */
1071	public function ptr_edit ($address, $print_error = true) {
1072		// validate hostname
1073		if ($this->validate_hostname ($address->hostname)===false)	{
1074			// remove pointer if it exists!
1075			if ($this->ptr_exists ($address->PTR)===true)	{ $this->ptr_delete ($address, $print_error); }
1076			else											{ return false; }
1077		}
1078
1079		// new record
1080 		if ($this->ptr_exists ($address->PTR)===false) {
1081	 		// fake lastid
1082	 		$this->lastId = $address->id;
1083	 		// new ptr record
1084	 		$this->ptr_add ($address, true);
1085 		}
1086 		// update PTR
1087 		else {
1088			// fetch domain
1089			$domain = $this->pdns_fetch_domain ($address->subnetId);
1090
1091			// fetch old
1092			$old_record = $this->PowerDNS->fetch_record ($address->PTR);
1093
1094			// create insert array
1095			$update = $this->PowerDNS->formulate_update_record ($this->PowerDNS->get_ip_ptr_name ($this->transform_address ($address->ip_addr, "dotted")), null, $address->hostname, null, null, null, $old_record->change_date);
1096			$update['id'] = $address->PTR;
1097
1098			// update
1099			$this->PowerDNS->update_domain_record ($domain->id, $update, $print_error);
1100			// ok
1101			if ($print_error && php_sapi_name()!="cli")
1102			$this->Result->show("success", "PTR record updated", false);
1103 		}
1104	}
1105
1106	/**
1107	 * Remove PTR from database
1108	 *
1109	 * @access public
1110	 * @param mixed $address
1111	 * @param mixed $print_error
1112	 * @return void
1113	 */
1114	public function ptr_delete ($address, $print_error) {
1115		$address = (object) $address;
1116
1117		// remove link from ipaddresses
1118		$this->ptr_unlink ($address->id);
1119
1120		// exists
1121		if ($this->ptr_exists ($address->PTR)!==false)	{
1122			// fetch domain
1123			$domain = $this->pdns_fetch_domain ($address->subnetId);
1124			//remove
1125			$this->PowerDNS->remove_domain_record ($domain->id, $address->PTR);
1126    		// ok
1127    		if ($print_error && php_sapi_name()!="cli")
1128    		$this->Result->show("success", "PTR record removed", false);
1129		}
1130	}
1131
1132	/**
1133	 * Links PTR record with address record
1134	 *
1135	 * @access public
1136	 * @param mixed $address_id
1137	 * @param mixed $ptr_id
1138	 * @return void
1139	 */
1140	public function ptr_link ($address_id, $ptr_id) {
1141		# execute
1142		try { $this->Database->updateObject("ipaddresses", array("id"=>$address_id, "PTR"=>$ptr_id)); }
1143		catch (Exception $e) {
1144			$this->Result->show("danger", _("Error: ").$e->getMessage(), false);
1145			return false;
1146		}
1147	}
1148
1149	/**
1150	 * Remove PTR link if it exists
1151	 *
1152	 * @access private
1153	 * @param mixed $address_id
1154	 * @return void
1155	 */
1156	private function ptr_unlink ($address_id) {
1157		# execute
1158		try { $this->Database->updateObject("ipaddresses", array("id"=>$address_id, "PTR"=>0)); }
1159		catch (Exception $e) {
1160			$this->Result->show("danger", _("Error: ").$e->getMessage(), false);
1161			return false;
1162		}
1163	}
1164
1165	/**
1166	 * Removes all PTR references for all hosts in subnet
1167	 *
1168	 * @access public
1169	 * @param mixed $subnet_id
1170	 * @return void
1171	 */
1172	public function ptr_unlink_subnet_addresses ($subnet_id) {
1173		try { $this->Database->runQuery("update `ipaddresses` set `PTR` = 0 where `subnetId` = ?;", array($subnet_id)); }
1174		catch (Exception $e) {
1175			$this->Result->show("danger", _("Error: ").$e->getMessage());
1176			return false;
1177		}
1178		#result
1179		return true;
1180	}
1181
1182	/**
1183	 * Checks if PTR record exists
1184	 *
1185	 * @access private
1186	 * @param mixed $ptr_id (default: 0)
1187	 * @return void
1188	 */
1189	private function ptr_exists ($ptr_id = 0) {
1190		return $this->PowerDNS->record_id_exists ($ptr_id);
1191	}
1192
1193	/**
1194	 * Returns array of all ptr indexes in surrent subnet
1195	 *
1196	 * @access public
1197	 * @param mixed $subnetId
1198	 * @return void
1199	 */
1200	public function ptr_get_subnet_indexes ($subnetId) {
1201		try { $indexes = $this->Database->getObjectsQuery("select `PTR` from `ipaddresses` where `PTR` != 0 and `subnetId` = ?;", array($subnetId)); }
1202		catch (Exception $e) {
1203			$this->Result->show("danger", _("Error: ").$e->getMessage());
1204			return false;
1205		}
1206		# parse
1207		if (sizeof($indexes)>0) {
1208    		$out = array();
1209    		// loop
1210    		foreach ($indexes as $i) {
1211        		$out[] = $i->PTR;
1212    		}
1213    		return $out;
1214		}
1215		else {
1216    		return array();
1217		}
1218	}
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233	/**
1234	* @import address methods
1235	* -------------------------------
1236	*/
1237
1238	/**
1239	 * Import single line from csv to database
1240	 *
1241	 * @access public
1242	 * @param array $address
1243	 * @param int $subnetId
1244	 * @return void
1245	 */
1246	public function import_address_from_csv ($address, $subnetId) {
1247		# Subnets object
1248		$this->initialize_subnets_object ();
1249
1250	    # fetch subnet details
1251	    $subnet = (array) $this->Subnets->fetch_subnet(null, $subnetId);
1252
1253	    # verify address
1254	    if($this->verify_address( $address[0], $this->transform_to_dotted($subnet['subnet'])."/".$subnet['mask'], false, false)!==false) { return false; }
1255	    # check for duplicates
1256	    if ($this->address_exists($address[0], $subnetId)) { return _('IP address already exists').' - '.$address[0]; }
1257
1258		# format insert array
1259		$address_insert = array("subnetId"=>$subnetId,
1260								"ip_addr"=>$address[0],
1261								"state"=>$address[1],
1262								"description"=>$address[2],
1263								"hostname"=>$address[3],
1264								"mac"=>$address[4],
1265								"owner"=>$address[5],
1266								"switch"=>$address[6],
1267								"port"=>$address[7],
1268								"note"=>$address[8]
1269								);
1270
1271		# switch to 0, state to active
1272		$address_insert['switch'] = strlen($address_insert['switch'])==0 ? 0 : $address_insert['switch'];
1273		$address_insert['state']  = strlen($address_insert['state'])==0 ?  1 : $address_insert['state'];
1274
1275		# custom fields, append to array
1276		$m=9;
1277		$custom_fields = $this->set_custom_fields();
1278		if(sizeof($custom_fields) > 0) {
1279			foreach($custom_fields as $c) {
1280				$address_insert[$c['name']] = $address[$m];
1281				$m++;
1282			}
1283		}
1284
1285		# insert
1286		return $this->modify_address_add ($address_insert);
1287	}
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298	/**
1299	* @address subnet methods
1300	* -------------------------------
1301	*/
1302
1303	/**
1304	 * Opens new Subnets connection if not already opened
1305	 *
1306	 * @access private
1307	 * @return void
1308	 */
1309	private function initialize_subnets_object () {
1310		if(!is_object($this->Subnets)) { $this->Subnets = new Subnets ($this->Database); }
1311	}
1312
1313	/**
1314	 * Fetches all IP addresses in subnet
1315	 *
1316	 * @access public
1317	 * @param mixed $subnetId
1318	 * @param mixed $order (default: null)
1319	 * @param mixed $order_direction (default: null)
1320	 * @param string $fields (default: "*")
1321	 * @return void
1322	 */
1323	public function fetch_subnet_addresses ($subnetId, $order=null, $order_direction=null, $fields = "*") {
1324		# set order
1325		if(!is_null($order)) 	{ $order = array($order, $order_direction); }
1326		else 					{ $order = array("ip_addr", "asc"); }
1327
1328		# fields
1329		if($fields!="*") {
1330    		$fields = implode(",", $fields);
1331		}
1332
1333		# escape ordering
1334		$order[0] = $this->Database->escape ($order[0]);
1335		$order[1] = $this->Database->escape ($order[1]);
1336
1337		try { $addresses = $this->Database->getObjectsQuery("SELECT $fields FROM `ipaddresses` where `subnetId` = ? order by `$order[0]` $order[1];", array($subnetId)); }
1338		catch (Exception $e) {
1339			$this->Result->show("danger", _("Error: ").$e->getMessage());
1340			return false;
1341		}
1342		# save to addresses cache
1343		if(sizeof($addresses)>0) {
1344			foreach($addresses as $k=>$address) {
1345				# add decimal format
1346				$address->ip = $this->transform_to_dotted ($address->ip_addr);
1347				# save to subnets
1348				$this->addresses[$address->id] = (object) $address;
1349				$addresses[$k]->ip = $address->ip;
1350			}
1351		}
1352		# result
1353		return sizeof($addresses)>0 ? $addresses : array();
1354	}
1355
1356	/**
1357	 * Count number of IP addresses in subnet
1358	 *
1359	 * Returns number of addresses in subnet
1360	 *
1361	 * @access public
1362	 * @param int $subnetId
1363	 * @return int
1364	 */
1365	public function count_subnet_addresses ($subnetId) {
1366		try { $count = $this->Database->numObjectsFilter("ipaddresses", "subnetId", $subnetId); }
1367		catch (Exception $e) {
1368			$this->Result->show("danger", _("Error: ").$e->getMessage());
1369			return false;
1370		}
1371		# result
1372		return (int) $count;
1373	}
1374
1375	/**
1376	 * Count number of addresses in multiple subnets
1377	 *
1378	 *	we provide array of all subnet ids
1379	 *
1380	 * @access public
1381	 * @param mixed $subnets
1382	 * @return void
1383	 */
1384	public function count_addresses_in_multiple_subnets ($subnets) {
1385		# empty
1386		if(empty($subnets)) { return 0; }
1387
1388		# create query
1389		$tmp = array();
1390		foreach($subnets as $k=>$s) {
1391			if (is_object($s))	{ $tmp[] = " `subnetId`=$s->id "; }
1392			else				{ $tmp[] = " `subnetId`=$s "; }
1393		}
1394		$query  = "select count(*) as `cnt` from `ipaddresses` where ".implode("or", $tmp).";";
1395
1396		# fetch
1397		try { $addresses = $this->Database->getObjectsQuery($query); }
1398		catch (Exception $e) {
1399			$this->Result->show("danger", _("Error: ").$e->getMessage());
1400			return false;
1401		}
1402		# return count
1403	    return $addresses[0]->cnt;
1404	}
1405
1406
1407	/**
1408	 * Fetch IP addresses for all recursive slaves
1409	 *
1410	 *	count returns count only, else whole subnets
1411	 *
1412	 * @access public
1413	 * @param int $subnetId
1414	 * @param bool $count
1415	 * @return void
1416	 */
1417	public function fetch_subnet_addresses_recursive ($subnetId, $count = false, $order=null, $order_direction=null ) {
1418		# initialize subnets
1419		$this->initialize_subnets_object ();
1420		$this->Subnets = new Subnets ($this->Database);
1421		$this->Subnets->reset_subnet_slaves_recursive();				//reset array of slaves before continuing
1422	    $this->Subnets->fetch_subnet_slaves_recursive($subnetId);		//fetch array of slaves
1423	    $this->Subnets->slaves = array_unique($this->Subnets->slaves);	//remove possible duplicates
1424
1425		# ip address order
1426		if(!is_null($order)) 	{ $order_addr = array($order, $order_direction); }
1427		else 					{ $order_addr = array("ip_addr", "asc"); }
1428
1429		# escape ordering
1430		$order_addr[0] = $this->Database->escape ($order_addr[0]);
1431		$order_addr[1] = $this->Database->escape ($order_addr[1]);
1432
1433		$ids = array();
1434		$ids[] = $subnetId;
1435
1436	    # set query to fetch all ip addresses for specified subnets or just count
1437		if($count) 	{ $query = 'select count(*) as cnt from `ipaddresses` where `subnetId` = ? '; }
1438		else	 	{ $query = 'select * from `ipaddresses` where `subnetId` = ? '; }
1439	    foreach($this->Subnets->slaves as $subnetId2) {
1440		    # ignore orphaned
1441	    	if($subnetId2 != $subnetId) {
1442				$query  .= " or `subnetId` = ? ";
1443		    	$ids[] = $subnetId2;
1444			}
1445		}
1446
1447	    $query      .= "order by `$order_addr[0]` $order_addr[1];";
1448		# fetch
1449		try { $addresses = $this->Database->getObjectsQuery($query, $ids); }
1450		catch (Exception $e) {
1451			$this->Result->show("danger", _("Error: ").$e->getMessage());
1452			return false;
1453		}
1454	    # return ip address array or just count
1455	    return $count ? (int) $addresses[0]->cnt : $addresses;
1456	}
1457
1458	/**
1459	 * Search for unused address space between 2 IP addresses
1460	 *
1461	 * possible unused addresses by type
1462	 *
1463	 * @access public
1464	 * @param int $address1
1465	 * @param int $address2
1466	 * @param int $netmask
1467	 * @param bool $empty (default: false)
1468	 * @param bool $is_subnet (default: false)
1469	 * @param bool $is_broadcast (default: false)
1470	 * @return void
1471	 */
1472	public function find_unused_addresses ($address1, $address2, $netmask, $empty=false, $is_subnet=false, $is_broadcast=false) {
1473		# make sure addresses are in decimal format
1474		$address1 = $this->transform_address ($address1, "decimal");
1475		$address2 = $this->transform_address ($address2, "decimal");
1476		# check for space
1477		return $this->identify_address($address1)=="IPv6" ? $this->find_unused_addresses_IPv6 ($address1, $address2, $netmask, $empty, $is_subnet, $is_broadcast) : $this->find_unused_addresses_IPv4 ($address1, $address2, $netmask, $empty);
1478	}
1479
1480	/**
1481	 * Search for unused address space between 2 IPv4 addresses.
1482	 *
1483	 * unused address range or false if none available
1484	 *
1485	 * @access protected
1486	 * @param int $address1
1487	 * @param int $address2
1488	 * @param int $netmask
1489	 * @param bool $empty
1490	 * @return void
1491	 */
1492	protected function find_unused_addresses_IPv4 ($address1, $address2, $netmask, $empty) {
1493		# calculate diff
1494		$diff = $this->calculate_address_diff ($address1, $address2);
1495		# 32 subnets
1496		if($netmask==32) {
1497			if($empty) {
1498				return array("ip"=>$this->transform_to_dotted($address1), "hosts"=>1);
1499			}
1500			else {
1501				return false;
1502			}
1503		}
1504		# 31 subnets
1505		elseif($netmask==31) {
1506
1507			if($empty) {
1508				return array("ip"=>$this->transform_to_dotted($address1), "hosts"=>2);
1509			}
1510			elseif($diff==1) {
1511				if($this->is_network($address1, $netmask)) {
1512					return array("ip"=>$this->transform_to_dotted($address2), "hosts"=>1);
1513				}
1514				elseif($this->is_broadcast($address2, $netmask)) {
1515					return array("ip"=>$this->transform_to_dotted($address1), "hosts"=>1);
1516				}
1517				else {
1518					return false;
1519				}
1520			}
1521			else {
1522				return false;
1523			}
1524		}
1525		# if diff is less than 2 return false */
1526		elseif ( $diff < 2 ) {
1527        		return false;
1528    	}
1529		# if diff is 2 return 1 IP address in the middle */
1530		elseif ( $diff == 2 ) {
1531				return array("ip"=>$this->transform_to_dotted($address1+1), "hosts"=>1);
1532    	}
1533		# if diff is more than 2 return pool */
1534		else {
1535            	return array("ip"=>$this->transform_to_dotted($address1+1)." - ".$this->transform_to_dotted(($address2-1)), "hosts"=>gmp_strval(gmp_sub($diff, 1)));
1536    	}
1537    	# default false
1538    	return false;
1539	}
1540
1541	/**
1542	 * Search for unused address space between 2 IPv6 addresses
1543	 *
1544	 * Return unused address range or false if none available
1545	 *
1546	 * @access protected
1547	 * @param int $address1
1548	 * @param int $address2
1549	 * @param int $netmask
1550	 * @param bool $empty (default: false)
1551	 * @param bool $is_subnet (default: false)
1552	 * @param bool $is_broadcast (default: false)
1553	 * @return void
1554	 */
1555	protected function find_unused_addresses_IPv6 ($address1, $address2, $netmask, $empty = false, $is_subnet = false, $is_broadcast = false) {
1556		# Initialize PEAR NET object
1557		$this->initialize_pear_net_IPv6 ();
1558
1559		if($empty) {
1560    		$Subnets = new Subnets ($this->Database);
1561    		return array("ip"=>$this->transform_to_dotted(gmp_strval($address1))." - ".$this->transform_to_dotted(gmp_strval($address2)), "hosts"=>$Subnets->get_max_hosts ($netmask, "IPv6"));
1562		}
1563        else {
1564    		# calculate diff
1565    		$diff = $this->calculate_address_diff ($address1, $address2);
1566
1567    		# /128
1568    		if($netmask == 128) {
1569        		if($diff>1) {
1570                    return array("ip"=>$this->transform_to_dotted(gmp_strval($address1)), "hosts"=>1);
1571                }
1572        	}
1573    		# /127
1574    	    elseif($netmask == 127) {
1575        	    if($diff==1 && $this->is_network($address1, $netmask)) {
1576    				return array("ip"=>$this->transform_to_dotted($address2), "hosts"=>1);
1577    			}
1578    			elseif($diff==1 && $this->is_broadcast($address2, $netmask)) {
1579    				return array("ip"=>$this->transform_to_dotted($address1), "hosts"=>1);
1580    			}
1581    			elseif($diff==0) {
1582        			return false;
1583    			}
1584    			else {
1585    				return array("ip"=>$this->transform_to_dotted($address1), "hosts"=>2);
1586    			}
1587    	    }
1588    	    # null
1589    	    elseif ($diff==0) {
1590        	    return false;
1591    	    }
1592    	    # diff 1
1593    	    elseif ($diff==1) {
1594         		if($is_subnet) {
1595                    return array("ip"=>$this->transform_to_dotted(gmp_strval($address1)), "hosts"=>1);
1596        		}
1597        		elseif($is_broadcast) {
1598                    return array("ip"=>$this->transform_to_dotted(gmp_strval($address2)), "hosts"=>1);
1599        		}
1600        		else {
1601            		return false;
1602                }
1603    	    }
1604    	    # diff 2
1605    	    elseif ($diff==2 && !$is_subnet && !$is_broadcast) {
1606                return array("ip"=>$this->transform_to_dotted(gmp_strval(gmp_add($address1,1))), "hosts"=>1);
1607    	    }
1608    	    # default
1609    	    else {
1610        		if($is_subnet) {
1611                    return array("ip"=>$this->transform_to_dotted(gmp_strval($address1))." - ".$this->transform_to_dotted(gmp_strval(gmp_sub($address2,1))), "hosts"=>$this->reformat_number(gmp_strval(gmp_sub($diff,0))));
1612        		}
1613        		elseif($is_broadcast) {
1614                    return array("ip"=>$this->transform_to_dotted(gmp_strval(gmp_add($address1,1)))." - ".$this->transform_to_dotted(gmp_strval($address2)), "hosts"=>$this->reformat_number(gmp_strval(gmp_sub($diff,0))));
1615        		}
1616        		else {
1617                    return array("ip"=>$this->transform_to_dotted(gmp_strval(gmp_add($address1,1)))." - ".$this->transform_to_dotted(gmp_strval(gmp_sub($address2,1))), "hosts"=>$this->reformat_number(gmp_strval(gmp_strval(gmp_sub($diff,1)))));
1618                }
1619        	}
1620
1621        	# default false
1622        	return false;
1623    	}
1624	}
1625
1626
1627
1628
1629
1630
1631
1632
1633
1634
1635
1636	/**
1637	* @address verification methods
1638	* -------------------------------
1639	*/
1640
1641	/**
1642	 * Verify IP address
1643	 *
1644	 * @access public
1645	 * @param int $address
1646	 * @param mixed $subnet (CIDR)
1647	 * @param bool $no_strict (default: false)
1648	 * @param bool $die (default: false)
1649	 * @return boolean
1650	 */
1651	public function verify_address( $address, $subnet, $no_strict = false, $die=true ) {
1652		# subnet should be in CIDR format
1653		$this->initialize_subnets_object ();
1654		if(strlen($error = $this->Subnets->verify_cidr ($subnet))>1)				{ $this->Result->show("danger", $error, $die); return true; }
1655
1656		# make checks
1657		return $this->identify_address ($address)=="IPv6" ? $this->verify_address_IPv6 ($address, $subnet, $die) : $this->verify_address_IPv4 ($address, $subnet, $no_strict, $die);
1658	}
1659
1660	/**
1661	 * Verify IPv4 address
1662	 *
1663	 * @access public
1664	 * @param int $address
1665	 * @param mixed $subnet (CIDR)
1666	 * @param bool $no_strict
1667	 * @param bool $die
1668	 * @return boolean
1669	 */
1670	public function verify_address_IPv4 ($address, $subnet, $no_strict, $die) {
1671		# Initialize PEAR NET object
1672		$this->initialize_pear_net_IPv4 ();
1673        # fetch mask part
1674        $mask = explode("/", $subnet);
1675
1676		# is address valid?
1677		if (!$this->Net_IPv4->validateIP($address)) 						{ $this->Result->show("danger", _("IP address not valid")."! ($address)", $die); return true; }
1678		# is address in provided subnet
1679		elseif (!$this->Net_IPv4->ipInNetwork($address, $subnet)) 			{ $this->Result->show("danger", _("IP address not in selected subnet")."! ($address)", $die); return true; }
1680		# ignore  /31 and /32 subnet broadcast and subnet checks!
1681		elseif ($mask[1] == 31 || $mask[1] == 32 || $no_strict == true) 	{ }
1682		# It cannot be subnet or broadcast
1683		else {
1684            $net = $this->Net_IPv4->parseAddress($subnet);
1685
1686            if ($net->network == $address) 									{ $this->Result->show("danger", _("Cannot add subnet as IP address!"), $die); return true; }
1687            elseif ($net->broadcast == $address)							{ $this->Result->show("danger", _("Cannot add broadcast as IP address!"), $die); return true; }
1688		}
1689		# default
1690		return false;
1691	}
1692
1693	/**
1694	 * Verify IPv6 address
1695	 *
1696	 * @access public
1697	 * @param int $address
1698	 * @param mixed $subnet (CIDR)
1699	 * @param bool $die
1700	 * @return boolean
1701	 */
1702	public function verify_address_IPv6 ($address, $subnet, $die) {
1703		# Initialize PEAR NET object
1704		$this->initialize_pear_net_IPv6 ();
1705
1706		# is it valid?
1707		if (!$this->Net_IPv6->checkIPv6($address)) 							{ $this->Result->show("danger", _("IP address not valid")."! ($address)", $die); return true; }
1708		# it must be in provided subnet
1709		elseif (!$this->Net_IPv6->isInNetmask($address, $subnet)) 			{ $this->Result->show("danger", _("IP address not in selected subnet")."! ($address)", $die); return true; }
1710		# default
1711		return false;
1712	}
1713
1714	/**
1715	 * Validates IP address
1716	 *
1717	 * @access public
1718	 * @param mixed $address
1719	 * @return void
1720	 */
1721	public function validate_address ($address) {
1722		# Initialize PEAR NET object
1723		$this->initialize_pear_net_IPv4 ();
1724		$this->initialize_pear_net_IPv6 ();
1725
1726		// no null
1727		if($this->transform_address ($address, "decimal")==0) {
1728			return false;
1729		}
1730		// transform
1731		$address = $this->transform_address ($address, "dotted");
1732		// ipv6
1733		if($this->identify_address ($address)=="IPv6") {
1734			return $this->Net_IPv6->checkIPv6($address) ?  true : false;
1735		}
1736		// ipv4
1737		else {
1738			return $this->Net_IPv4->validateIP($address) ? true : false;
1739		}
1740	}
1741
1742	/**
1743	 * Checks if address is subnet for IPv4 addresses
1744	 *
1745	 * @access public
1746	 * @param mixed $address
1747	 * @param int $netmask
1748	 * @return boolean
1749	 */
1750	public function is_network ($address, $netmask) {
1751		$this->initialize_subnets_object ();
1752		$boundaries = $this->Subnets->get_network_boundaries ($address, $netmask);
1753		return $this->transform_address($address,"dotted")==$boundaries['network'] ? true : false;
1754	}
1755
1756	/**
1757	 * Checks if address is broadcast for IPv4 addresses
1758	 *
1759	 * @access public
1760	 * @param mixed $address
1761	 * @param int $netmask
1762	 * @return boolean
1763	 */
1764	public function is_broadcast ($address, $netmask) {
1765		$this->initialize_subnets_object ();
1766		$boundaries = $this->Subnets->get_network_boundaries ($address, $netmask);
1767		return $this->transform_address($address,"dotted")==$boundaries['broadcast'] ? true : false;
1768	}
1769
1770	/**
1771	 * Checks if hostname in database is unique
1772	 *
1773	 * @access public
1774	 * @param mixed $hostname
1775	 * @return boolean
1776	 */
1777	public function is_hostname_unique ($hostname) {
1778		try { $cnt = $this->Database->numObjectsFilter("ipaddresses", "hostname", $hostname); }
1779		catch (Exception $e) {
1780			$this->Result->show("danger", _("Error: ").$e->getMessage());
1781			return false;
1782		}
1783		return $cnt==0 ? true : false;
1784	}
1785
1786
1787
1788
1789
1790
1791
1792
1793
1794
1795
1796
1797
1798	/**
1799	* @transform address methods
1800	* -------------------------------
1801	*/
1802
1803	/**
1804	 * This function compresses all ranges
1805	 *
1806	 *	input is array of ip addresses
1807	 *	output compresses address range
1808	 *
1809	 * @access public
1810	 * @param array $addresses
1811	 * @return void
1812	 */
1813	public function compress_address_ranges ($addresses, $state=4) {
1814    	# set size
1815    	$size = sizeof($addresses);
1816    	// vars
1817    	$addresses_formatted = array();
1818
1819		# loop through IP addresses
1820		for($c=0; $c<$size; $c++) {
1821			# ignore already comressed range
1822			if($addresses[$c]->class!="compressed-range") {
1823				# gap between this and previous
1824				if(gmp_strval( @gmp_sub($addresses[$c]->ip_addr, $addresses[$c-1]->ip_addr)) != 1) {
1825					# remove index flag
1826					unset($fIndex);
1827					# save IP address
1828					$addresses_formatted[$c] = $addresses[$c];
1829					$addresses_formatted[$c]->class = "ip";
1830
1831					# no gap this -> next
1832					if(gmp_strval( @gmp_sub($addresses[$c]->ip_addr, $addresses[$c+1]->ip_addr)) == -1 && $addresses[$c]->state==$state) {
1833						//is state the same?
1834						if($addresses[$c]->state==$addresses[$c+1]->state) {
1835							$fIndex = $c;
1836							$addresses_formatted[$fIndex]->startIP = $addresses[$c]->ip_addr;
1837							$addresses_formatted[$c]->class = "compressed-range";
1838						}
1839					}
1840				}
1841				# no gap between this and previous
1842				else {
1843					# is state same as previous?
1844					if($addresses[$c]->state==$addresses[$c-1]->state && $addresses[$c]->state==$state) {
1845						$addresses_formatted[$fIndex]->stopIP = $addresses[$c]->ip_addr;	//adds dhcp state
1846						$addresses_formatted[$fIndex]->numHosts = gmp_strval( gmp_add(@gmp_sub($addresses[$c]->ip_addr, $addresses_formatted[$fIndex]->ip_addr),1));	//add number of hosts
1847					}
1848					# different state
1849					else {
1850						# remove index flag
1851						unset($fIndex);
1852						# save IP address
1853						$addresses_formatted[$c] = $addresses[$c];
1854						$addresses_formatted[$c]->class = "ip";
1855						# check if state is same as next to start range
1856						if($addresses[$c]->state==@$addresses[$c+1]->state &&  gmp_strval( @gmp_sub($addresses[$c]->ip_addr, $addresses[$c+1]->ip_addr)) == -1 && $addresses[$c]->state==$state) {
1857							$fIndex = $c;
1858							$addresses_formatted[$fIndex]->startIP = $addresses[$c]->ip_addr;
1859							$addresses_formatted[$c]->class = "compressed-range";
1860						}
1861					}
1862				}
1863			}
1864			else {
1865				# save already compressed
1866				$addresses_formatted[$c] = $addresses[$c];
1867			}
1868		}
1869		# overrwrite ipaddresses and rekey
1870		$addresses = @array_values($addresses_formatted);
1871		# return
1872		return $addresses;
1873	}
1874
1875	/**
1876	 * Finds invalid addresses - that have subnetId that does not exist
1877	 *
1878	 * @access public
1879	 * @return void
1880	 */
1881	public function find_invalid_addresses () {
1882    	// init
1883    	$false = array();
1884		// find unique ids
1885		$ids = $this->find_unique_subnetids ();
1886
1887		// validate
1888		if (!is_array($ids))
1889			return false;
1890
1891		foreach ($ids as $id) {
1892			if ($this->verify_subnet_id ($id->subnetId)===false) {
1893				$false[] = $this->fetch_subnet_addresses ($id->subnetId);
1894			}
1895		}
1896		// filter
1897		$false = array_filter($false);
1898		// return
1899		return sizeof($false)>0 ? $false : false;
1900	}
1901
1902	/**
1903	 * Finds all unique master subnet ids
1904	 *
1905	 * @access private
1906	 * @return void
1907	 */
1908	private function find_unique_subnetids () {
1909		try { $res = $this->Database->getObjectsQuery("select distinct(`subnetId`) from `ipaddresses` order by `subnetId` asc;"); }
1910		catch (Exception $e) {
1911			$this->Result->show("danger", _("Error: ").$e->getMessage());
1912			return false;
1913		}
1914		# return
1915		return sizeof($res)>0 ? $res : false;
1916	}
1917
1918	/**
1919	 * Verifies that subnetid exists
1920	 *
1921	 * @access private
1922	 * @param mixed $id
1923	 * @return void
1924	 */
1925	private function verify_subnet_id ($id) {
1926		try { $res = $this->Database->numObjectsFilter("subnets", "id", $id ); }
1927		catch (Exception $e) {
1928			$this->Result->show("danger", _("Error: ").$e->getMessage());
1929			return false;
1930		}
1931		# return
1932		return $res==0 ? false : true;
1933	}
1934
1935
1936
1937
1938
1939
1940
1941
1942
1943
1944
1945	/**
1946	* @permission address methods
1947	* -------------------------------
1948	*/
1949
1950	/**
1951	 * Checks permission for specified subnet
1952	 *
1953	 *	we provide user details and subnetId
1954	 *
1955	 * @access public
1956	 * @param object $user
1957	 * @param int $subnetId
1958	 * @return int permission level
1959	 */
1960	public function check_permission ($user, $subnetId) {
1961
1962		# get all user groups
1963		$groups = json_decode($user->groups);
1964
1965		# if user is admin then return 3, otherwise check
1966		if($user->role == "Administrator")	{ return 3; }
1967
1968    	# object
1969    	if (!is_object($this->Subnets)) {
1970        	$this->Subnets = new Subnets ($this->Database);
1971    	}
1972        # fetch subnet
1973        $subnet = $this->Subnets->fetch_subnet("id", $subnetId);
1974		# set subnet permissions
1975		$subnetP = json_decode($subnet->permissions);
1976
1977		# set section permissions
1978		$Section = new Section ($this->Database);
1979		$section = $Section->fetch_section ("id", $subnet->sectionId);
1980		$sectionP = json_decode($section->permissions);
1981
1982		# default permission
1983		$out = 0;
1984
1985		# for each group check permissions, save highest to $out
1986		if(sizeof($sectionP) > 0) {
1987			foreach($sectionP as $sk=>$sp) {
1988				# check each group if user is in it and if so check for permissions for that group
1989				foreach($groups as $uk=>$up) {
1990					if($uk == $sk) {
1991						if($sp > $out) { $out = $sp; }
1992					}
1993				}
1994			}
1995		}
1996		else {
1997			return 0;
1998		}
1999
2000		# if section permission == 0 then return 0
2001		if($out == 0) {
2002			return 0;
2003		}
2004		else {
2005			$out = 0;
2006			# ok, user has section access, check also for any higher access from subnet
2007			if(sizeof($subnetP) > 0) {
2008				foreach($subnetP as $sk=>$sp) {
2009					# check each group if user is in it and if so check for permissions for that group
2010					foreach($groups as $uk=>$up) {
2011						if($uk == $sk) {
2012							if($sp > $out) { $out = $sp; }
2013						}
2014					}
2015				}
2016			}
2017		}
2018
2019		# return result
2020		return $out;
2021	}
2022
2023
2024
2025
2026
2027
2028
2029
2030
2031
2032	/**
2033	* @misc address methods
2034	* -------------------------------
2035	*/
2036
2037	/**
2038	 * Present numbers in pow 10, only for IPv6
2039	 *
2040	 * @access public
2041	 * @param mixed $number
2042	 * @return void
2043	 */
2044	public function reformat_number ($number) {
2045		$length = strlen($number);
2046		$pos	= $length - 3;
2047
2048		if ($length > 8) {
2049			$number = "~". substr($number, 0, $length - $pos) . "&middot;10^<sup>". $pos ."</sup>";
2050		}
2051		return $number;
2052	}
2053
2054
2055
2056
2057
2058
2059
2060
2061
2062
2063	/**
2064	* @nat methods
2065	* -------------------------------
2066	*/
2067	/**
2068	 * Prints nat link
2069	 *
2070	 * @access public
2071	 * @param array $all_nats
2072	 * @param array $all_nats_per_object
2073	 * @param object $subnet
2074	 * @param object $address
2075	 * @param mixed $address
2076	 * @return void
2077	 */
2078	public function print_nat_link ($all_nats, $all_nats_per_object, $subnet, $address, $type="ipaddress") {
2079    	// cast
2080    	$subnet = (object) $subnet;
2081    	$address = (object) $address;
2082
2083    	// cnt
2084    	$html = array();
2085    	$html[] = '<table class="popover_table">';
2086
2087    	$cnt = 0;
2088
2089    	// subnets
2090        if(isset($all_nats_per_object['subnets'][$subnet->id])) {
2091            foreach ($all_nats_per_object['subnets'][$subnet->id] as $nat) {
2092                // set object
2093                $n = $all_nats[$nat];
2094                // print
2095                $html[] = str_replace("'", "\"", $this->print_nat_link_line ($n, false, "subnets", $subnet->id));
2096            }
2097            $cnt++;
2098        }
2099
2100    	// addresses
2101    	if(isset($all_nats_per_object['ipaddresses'][$address->id])) {
2102            foreach ($all_nats_per_object['ipaddresses'][$address->id] as $nat) {
2103                // set object
2104                $n = $all_nats[$nat];
2105                // print
2106                $html[] = str_replace("'", "\"", $this->print_nat_link_line ($n, false, "ipaddresses", $address->id));
2107                $cnt++;
2108            }
2109    	}
2110
2111        // print if some
2112        if ($cnt>0) {
2113            $html[] = "</table>";
2114            if($type=="subnet") {
2115                print  " <a href='".create_link("subnets",$subnet->sectionId, $subnet->id, "nat")."' class='btn btn-xs btn-default show_popover fa fa-exchange' style='font-size:11px;margin-top:-3px;padding:1px 3px;' data-toggle='popover' title='"._('Object is Natted')."' data-trigger='hover' data-html='true' data-content='".implode("\n", $html)."'></a>";
2116            }
2117            else {
2118                print  " <a href='".create_link("subnets",$subnet->sectionId, $subnet->id, "address-details", $address->id, "nat")."' class='btn btn-xs btn-default show_popover fa fa-exchange' style='font-size:11px;margin-top:-3px;padding:1px 3px;' data-toggle='popover' title='"._('Object is Natted')."' data-trigger='hover' data-html='true' data-content='".implode("\n", $html)."'></a>";
2119            }
2120        }
2121	}
2122
2123    /**
2124     * Prints single NAT for display in devices, subnets, addresses.
2125     *
2126     * @access public
2127     * @param mixed $n
2128     * @param bool|int $nat_id (default: false)
2129     * @param bool|mixed $object_type (default: false)
2130     * @param bool $object_id (default: false)
2131     * @return void
2132     */
2133    public function print_nat_link_line ($n, $nat_id = false, $object_type = false, $object_id=false) {
2134        // cast to object to be sure if array provided
2135        $n = (object) $n;
2136
2137        // translate json to array, links etc
2138        $sources      = $this->translate_nat_objects_for_popup ($n->src, $nat_id, false, $object_type, $object_id);
2139        $destinations = $this->translate_nat_objects_for_popup ($n->dst, $nat_id, false, $object_type, $object_id);
2140
2141        // no src/dst
2142        if ($sources===false)
2143            $sources = array("<span class='badge badge1 badge5 alert-danger'>"._("None")."</span>");
2144        if ($destinations===false)
2145            $destinations = array("<span class='badge badge1 badge5 alert-danger'>"._("None")."</span>");
2146
2147
2148        // icon
2149        $icon =  $n->type=="static" ? "fa-arrows-h" : "fa-long-arrow-right";
2150
2151        // to html
2152        $html = array();
2153        $html[] = "<tr>";
2154        $html[] = "<td colspan='3'>";
2155        $html[] = "<strong>$n->name</strong> <span class='badge badge1 badge5'>".ucwords($n->type)."</span>";
2156        $html[] = "</td>";
2157        $html[] = "</tr>";
2158
2159        // append ports
2160        if(($n->type=="static" || $n->type=="destination") && (strlen($n->src_port)>0 && strlen($n->dst_port)>0)) {
2161            $sources      = implode("<br>", $sources)." /".$n->src_port;
2162            $destinations = implode("<br>", $destinations)." /".$n->dst_port;
2163        }
2164        else {
2165            $sources      = implode("<br>", $sources);
2166            $destinations = implode("<br>", $destinations);
2167        }
2168
2169        $html[] = "<tr>";
2170        $html[] = "<td>$sources</td>";
2171        $html[] = "<td><i class='fa $icon'></i></td>";
2172        $html[] = "<td>$destinations</td>";
2173        $html[] = "</tr>";
2174        $html[] = "<tr><td colspan='3' style='padding-top:20px;'></td></tr>";
2175
2176        $html[] = "<tr>";
2177        $html[] = "<td colspan='3'><hr></td>";
2178        $html[] = "</tr>";
2179
2180        // return
2181        return implode("\n", $html);
2182    }
2183
2184    /**
2185     * Translates NAT objects to be shown on page
2186     *
2187     * @access public
2188     * @param json $json_objects
2189     * @param int|bool $nat_id (default: false)
2190     * @param bool $json_objects (default: false)
2191     * @param bool $object_type (default: false) - to bold it (ipaddresses / subnets)
2192     * @param int|bool object_id (default: false) - to bold it
2193     * @return void
2194     */
2195    public function translate_nat_objects_for_popup ($json_objects, $nat_id = false, $admin = false, $object_type = false, $object_id=false) {
2196        // to array "subnets"=>array(1,2,3)
2197        $objects = json_decode($json_objects, true);
2198        // init out array
2199        $out = array();
2200        // check
2201        if(is_array($objects)) {
2202            if(sizeof($objects)>0) {
2203                foreach ($objects as $ot=>$ids) {
2204                    if (sizeof($ids)>0) {
2205                        foreach ($ids as $id) {
2206                            // fetch
2207                            $item = $this->fetch_object($ot, "id", $id);
2208                            if($item!==false) {
2209                                // bold
2210                                $bold = $item->id==$object_id && $ot==$object_type ? "<span class='strong'>" : "<span>";
2211                                // subnets
2212                                if ($ot=="subnets") {
2213                                    $out[] = "$bold".$this->transform_address($item->subnet, "dotted")."/".$item->mask."</span></span>";
2214                                }
2215                                // addresses
2216                                else {
2217                                    $out[] = "$bold".$this->transform_address($item->ip_addr, "dotted")."</span>";
2218                                }
2219                            }
2220                        }
2221                    }
2222                }
2223            }
2224        }
2225        // result
2226        return sizeof($out)>0 ? $out : false;
2227    }
2228
2229}
2230