????

Your IP : 216.73.216.121


Current Path : /usr/lib/sonarpush/SonarPush/Providers/
Upload File :
Current File : //usr/lib/sonarpush/SonarPush/Providers/State.pm

package SonarPush::Providers::State;
use base qw(SonarPush::Providers);
use strict;

use SonarPush::Util;
use POSIX;
use JSON::Tiny qw(from_json to_json);

use constant CPANEL_EMAIL_FILE => '/usr/local/lp/var/sonarpush/emailaccountexport';

=head1 NAME

SonarPush::Providers::State - collects state information

=head1 DESCRIPTION

class of tasks for collecing various information about the
current state of services or process's on a machine.

=cut

sub configurations {
	my $self = shift;

	my $provider = 'configurations';

	my $xml = {
		name       => 'configurations',
		attributes => {
			providedby => $provider,
			datatype   => 'None'
		},
	};

	my $content = do {
		local $/ = undef;
		open my $file, '<', '/etc/resolv.conf';
		<$file>;
	};

	push(@{ $xml->{value} }, {
			name       => 'resolv-conf',
			attributes => {
				providedby => $provider,
				datatype   => "Text",
			},
			value => SonarPush::Util::encode_entities($content),
	});

	return $xml;
}

sub cpanelUserData {
	my ($self) = @_;
	my $provider = 'cpanelUserData';

	my %attrs = (
		textConfig => {
			providedby => $provider,
			datatype   => 'Text',
		},
		numConfig => {
			providedby   => $provider,
			datatype     => 'Numeric',
			incrementing => 'N',
			exponent     => 0,
			unit         => '',
		},
		noConfig => {
			providedby => $provider,
			datatype   => 'None',
		},
	);

	my @subNodes = ();
	my $struct   = {
		name       => $provider,
		attributes => $attrs{noConfig},
		value      => \@subNodes,
	};

	my $userData = $self->extractCpanelUserDetails();

	return unless $userData;

	push(@subNodes, {
			name       => 'count',
			attributes => $attrs{numConfig},
			value      => scalar keys %$userData,
	});

	push(@subNodes, {
			name       => 'list',
			attributes => $attrs{textConfig},
			value      => join(',', sort keys %$userData),
	});

	return $self->cpanelDataWrapper($provider, $struct);
}

sub cpanelEmailData {
	my ($self) = @_;
	my $provider = 'cpanelMailboxData';

	my %attrs = (
		noConfig => {
			providedby => $provider,
			dataType   => 'None',
		},
		numConfig => {
			providedby   => $provider,
			dataType     => 'Numeric',
			incrementing => 'N',
			exponent     => 0,
			unit         => '',
		},
		textConfig => {
			providedby => $provider,
			dataType   => 'Text',
		},
		sizeConfig => {
			providedby   => $provider,
			dataType     => 'Numeric',
			incrementing => 'N',
			exponent     => 0,
			unit         => 'Bytes',
		},
	);

	unless (-e CPANEL_EMAIL_FILE) {
		return;
	}

	open(my $file, '<', CPANEL_EMAIL_FILE);
	my $json = <$file>;
	close($file);

	# prevent "Missing or empty input" error from from_json when CPANEL_EMAIL_FILE
	# exists but is empty.
	return unless ($json);

	my $data = from_json($json);
	my $serverMailboxCount;
	my $serverMailboxSize;
	foreach my $user (keys %$data) {
		foreach my $domainData (@{ $data->{$user} }) {
			$serverMailboxCount += $domainData->{'total_mailboxes'};
			$serverMailboxSize  += $domainData->{'total_mailbox_size'};
		}
	}

	my @nodes;
	push(@nodes, {
			name       => 'count',
			attributes => $attrs{numConfig},
			value      => $serverMailboxCount,
	});

	push(@nodes, {
			name       => 'size',
			attributes => $attrs{sizeConfig},
			value      => $serverMailboxSize,
	});
	push(@nodes, {
			name       => 'mailbox_json_data',
			attributes => $attrs{textConfig},
			value      => $json,
	});
	my $struct = {
		name       => $provider,
		attributes => $attrs{noConfig},
		value      => \@nodes,
	};

	return $self->cpanelDataWrapper($provider, $struct);
}

sub cpanelDomainData {
	my ($self) = @_;
	my $provider = 'cpanelDomainData';

	my %attrs = (
		textConfig => {
			providedby => $provider,
			datatype   => 'Text',
		},
		numConfig => {
			providedby   => $provider,
			datatype     => 'Numeric',
			incrementing => 'N',
			exponent     => 0,
			unit         => '',
		},
		noConfig => {
			providedby => $provider,
			datatype   => 'None',
		},
	);

	my @subNodes = ();
	my $struct   = {
		name       => $provider,
		attributes => $attrs{noConfig},
		value      => \@subNodes,
	};

	my $userData = $self->extractCpanelUserDetails();

	return unless $userData;

	push(@subNodes, {
			name       => 'count',
			attributes => $attrs{numConfig},
			value      => scalar map { @{ $_->{domains} } } values %$userData,
	});

	push(@subNodes, {
			name       => 'list',
			attributes => $attrs{textConfig},
			value      => join(',', sort map { @{ $_->{domains} } } values %$userData),
	});

	return $self->cpanelDataWrapper($provider, $struct);
}

sub diskusage {
	my $self = shift;

	my $DF_BINARY = '/bin/df';
	my @buffers = split(/\n/, `$DF_BINARY --exclude-type=nfs -P| /bin/grep -v shm | /bin/grep -v Filesystem | /bin/grep -v volume_group`);

	my $partitions;
	foreach my $buff (@buffers) {
		if ($buff =~ /^(\S+)\s*(\S+)\s*(\S+)\s*(\S+)\s*(\S+)\s*(\S+)\s*$/) {
			$partitions->{$6}{'BSize'}      = $2;
			$partitions->{$6}{'BUsed'}      = $3;
			$partitions->{$6}{'BAvailable'} = $4;
		}
	}

	my @inodes = split(/\n/, `$DF_BINARY --exclude-type=nfs -iP | /bin/grep -v shm | /bin/grep -v Filesystem | /bin/grep -v volume_group`);
	foreach my $inode (@inodes) {
		if ($inode =~ /^(\S+)\s*(\S+)\s*(\S+)\s*(\S+)\s*(\S+)\s*(\S+)\s*$/) {
			$partitions->{$6}{'Icount'}     = $2;
			$partitions->{$6}{'IUsed'}      = $3;
			$partitions->{$6}{'IAvailable'} = $4;
		}
	}

	my $mapping = {
		blocks => {
			BSize      => 'total',
			BUsed      => 'used',
			BAvailable => 'available'
		},
		inodes => {
			Icount     => 'total',
			IUsed      => 'used',
			IAvailable => 'available',
		}
	};

	my $provider = 'diskusage';
	my $xml      = {
		name       => 'diskusage',
		attributes => {
			providedby => $provider,
			datatype   => 'None'
		},
		value => [],
	};

	foreach my $partname (sort keys %$partitions) {
		push(@{ $xml->{value} }, {
				name       => 'ihavebadchars',
				attributes => {
					realname   => $partname,
					providedby => $provider,
					datatype   => 'None',
				},
				value => [ {
						name       => 'blocks',
						attributes => {
							providedby => $provider,
							datatype   => 'None',
						},
						value => [ map {
								{
									name       => $mapping->{blocks}{$_},
									attributes => {
										providedby   => $provider,
										datatype     => 'Numeric',
										unit         => '1k blocks',
										exponent     => 3,
										incrementing => 'N'
									},
									value => $partitions->{$partname}{$_}
								}
								} qw/BSize BUsed BAvailable/ ],
					},
					{
						name       => 'inodes',
						attributes => {
							providedby => $provider,
							datatype   => 'None',
						},
						value => [ map {
								{
									name       => $mapping->{inodes}{$_},
									attributes => {
										providedby   => $provider,
										datatype     => 'Numeric',
										unit         => 'inodes',
										exponent     => 0,
										incrementing => 'N'
									},
									value => $partitions->{$partname}{$_}
								}
								} qw/Icount IUsed IAvailable/ ],
					} ],
		});
	}
	return $xml;
}

sub loadaverage {
	my $self     = shift;
	my $provider = 'loadaverage';

	my $data;
	open(my $loadavg, '<', '/proc/loadavg') or $self->sonar->printlog("can't open /proc/loadavg for reading!\n", 1);
	while (<$loadavg>) {
		if ($_ =~ /^(\S+)\s+(\S+)\s+(\S+)\s+(\d+)\/(\d+)\s+(\d+)/) {
			$data->{loadaverage} = {
				'one-min'     => $1,
				'five-min'    => $2,
				'fifteen-min' => $3,
			};
			$data->{processes} = {
				running => $4,
				total   => $5,
			};
		}
	}

	my @xml;
	foreach my $field (sort keys %$data) {
		my $struct = {
			name       => $field,
			attributes => {
				providedby => $provider,
				dataType   => 'None',
			},
			value => [ map {
					{
						name       => $_,
						attributes => {
							exponent     => 0,
							providedby   => $provider,
							type         => 'Numeric',
							incrementing => 'N',
							unit         => ''
						},
						value => $data->{$field}{$_}
					}
					} sort keys %{ $data->{$field} } ],
		};
		push(@xml, $struct);
	}
	return @xml;
}

sub lvm {
	my ($self)   = @_;
	my $provider = 'lvm';
	my %attrs    = (
		config => {
			providedby => $provider,
			datatype   => "Text",
		},
		noConfig => {
			providedby => $provider,
			datatype   => "None",
		},
	);
	my $xml = {
		name       => $provider,
		attributes => $attrs{noConfig},
		value      => [],
	};

	my @lvmFields = qw(
		volumegroupaccess  volumegroupstatus  volumegroupnumber  maxlogicalvolumes
		currentlogicalvolumes openlogicalvolumes maxlogicalvolumesize maxphysicalvolumes
		currentphysicalvolumes actualphysicalvolumes volumegroupsize physicalextentsize
		totalphysicalextents allocatedphysicalextents freephysicalextents uuid
	);

	my %propertyNameMap = (
		'LV Name'         => 'lvname',
		'LV Write Access' => 'lvaccess',
		'LV Status'       => 'lvstatus',
		'# open'          => 'numopen',
		'LV Size'         => 'lvsize',
		'Block device'    => 'Block device',
		'LV UUID'         => 'lvuuid',
	);

	if (-x "/sbin/vgdisplay") { #and -r "/etc/lvmtab" )
		my @inputarray = split(/\n/, `vgdisplay -c | grep ':'`);
		foreach my $input (@inputarray) {
			my $VGName;
			my %vgData;

			$input =~ s/^\s+|\s+$//g;
			($VGName, @vgData{@lvmFields}) = split(/:/, $input);

			my $vgNode = { name => $VGName, attributes => $attrs{config}, value => [] };
			@{ $vgNode->{value} } = map { { name => $_, value => $vgData{$_}, attributes => $attrs{config} } } sort keys %vgData;

			if ($VGName =~ /^([\w_-]+)$/) {
				$VGName = $1;
			}
			else {
				next;
			}

			my $vgDisplayRaw = `vgdisplay -v $VGName`;
			my @volumes = split(/\n   \n/, $vgDisplayRaw);

		VOL: for my $volume (@volumes) {
				my @properties = split(/\n/, $volume);
				my $type = shift(@properties);
				next VOL unless $type =~ /Logical/;
				s/^\s*|\s*$//g for @properties;
				my %lvDetails;
			PROP: for my $property (@properties) {
					my ($field, $value) = split(/\s{4,}/, $property);
					next PROP unless $propertyNameMap{$field};
					$lvDetails{$field} = $value;
				}
				my $nodeName = delete $lvDetails{'Block device'};
				$nodeName =~ s/\d+:(\d+)/$1/;
				my $lvNode = { name => "lv-$nodeName", attributes => $attrs{noConfig}, value => [] };
				@{ $lvNode->{value} } = map { { name => $propertyNameMap{$_}, value => $lvDetails{$_}, attributes => $attrs{config} } } sort keys %lvDetails;
				push(@{ $vgNode->{value} }, $lvNode);
			}
			push(@{ $xml->{value} }, $vgNode);
		}
	}

	return $xml;
}

sub lwbackup {
	my $self = shift;

	my $content = do {
		local $/;

		my $input;
		eval {
			open($input, "<", "/usr/local/lp/logs/backup.summary.xml") or die $!;
		};
		if ($@) {
			open($input, "<", "/usr/local/backup/summary.xml");
		}
		<$input>
	};

	my $xml;
	if ($content) {

		# we have a successful open, let's get to work
		my $stats;
		if ($content =~ /<backupDirectory>(.*)<\/backupDirectory>/) {
			$stats->{backupDirectory} = $1;
		}
		if ($content =~ /<backupDevice>(.*)<\/backupDevice>/) {
			$stats->{backupDevice} = $1;
		}
		if ($content =~ /<diskUsageLimit>(.*)<\/diskUsageLimit>/) {
			$stats->{diskUsageLimit} = $1;
		}
		if ($content =~ /<diskUsageLimitType>(.*)<\/diskUsageLimitType>/) {
			$stats->{diskUsageLimitType} = $1;
		}
		if ($content =~ /<allowedVariance>(.*)<\/allowedVariance>/) {
			$stats->{allowedVariance} = $1;
		}
		if ($content =~ /<diskSize>(.*)<\/diskSize>/) {
			$stats->{diskSize} = $1;
		}
		if ($content =~ /<gigsFree>(.*)<\/gigsFree>/) {
			$stats->{gigsFree} = $1;
		}
		if ($content =~ /<percentFree>(.*)<\/percentFree>/) {
			$stats->{percentFree} = $1;
		}
		$stats->{NewestBackup} = 0;
		$stats->{OldestBackup} = 9999999999999;
		$stats->{backupCount}  = 0;

		while ($content =~ /<backup date=\"(\d{1,2})\/(\d{1,2})\/(\d{4})\" time=\"(\d{1,2}):(\d{1,2})\" \/>/g) {

			#create the unix timestamp for this backup
			my $Stamp = mktime(0, $5, $4, $2, ($1 - 1), ($3 - 1900));
			if ($Stamp > $stats->{NewestBackup}) {
				$stats->{NewestBackup} = $Stamp;
			}

			if ($Stamp < $stats->{OldestBackup}) {
				$stats->{OldestBackup} = $Stamp;
			}
			$stats->{backupCount}++;
		}

		$xml = {
			name       => 'lwbackup',
			attributes => {
				providedby => 'lwbackup',
				datatype   => 'None',
			},
			value => [ map { { name => $_, attributes => { providedby => 'lwbackup', datatype => 'Text' }, value => $stats->{$_} } } sort keys %$stats ],
		};
	}

	return $xml;
}

sub memory {
	my ($self)   = @_;
	my $provider = 'memory';
	my %attrs    = (
		config => {
			providedby   => $provider,
			datatype     => "Numeric",
			incrementing => 'N',
			unit         => 'Bytes',
			exponent     => 3,
		},
		noConfig => {
			providedby => $provider,
			datatype   => "None",
		},
	);
	my $xml = {
		name       => $provider,
		attributes => $attrs{noConfig},
		value      => [],
	};
	my %memInfo = %{ getMemInfo() };

	if (%memInfo) {

		my %keyNameMap = (
			MemTotal     => 'total',
			TotalMemUsed => 'used',
			Buffers      => 'buffers',
			Cached       => 'cached',
			MemUsed      => 'realused',
			SwapTotal    => 'total',
			SwapUsed     => 'used',
		);
		my $physicalNode = {
			name       => 'physical',
			attributes => $attrs{noConfig},
			value      => undef,
		};
		my $swapNode = {
			name       => 'swap',
			attributes => $attrs{noConfig},
			value      => undef,
		};

		@{ $physicalNode->{value} } = map { { name => $keyNameMap{$_}, value => $memInfo{$_}, attributes => $attrs{config} } }
			qw(MemTotal TotalMemUsed Buffers Cached MemUsed);
		@{ $swapNode->{value} } = map { { name => $keyNameMap{$_}, value => $memInfo{$_}, attributes => $attrs{config} } }
			qw(SwapTotal SwapUsed);

		push(@{ $xml->{value} }, $physicalNode, $swapNode);
	}

	return $xml;
}

sub network {
	my $self = shift;

	my $content = do {
		local $/ = undef;
		open(my $input, "< /proc/net/dev");
		<$input>;
	};

	my $xml = {
		name       => 'network',
		attributes => {
			providedby => 'network',
			datatype   => 'None',
		},
		value => [],
	};

	if ($content) {
		while ($content =~ /\s*(.*?):\s*([0-9]*)\s*([0-9]*)\s*([0-9]*)\s*([0-9]*)\s*([0-9]*)\s*([0-9]*)\s*([0-9]*)\s*([0-9]*)\s*([0-9]*)\s*([0-9]*)\s*([0-9]*)\s*([0-9]*)\s*([0-9]*)\s*([0-9]*)\s*([0-9]*)\s*([0-9]*)/g) {

			# $interface needs to be uppercased because the data parsing doesn't uppercase ihavebadchars nodes
			my $interface = uc($1);

			my $parent = {
				name       => 'ihavebadchars',
				attributes => {
					realname   => $interface,
					datatype   => 'None',
					providedby => 'network',
				},
				value => [],
			};

			my $receive = {
				name       => 'receive',
				attributes => {
					providedby => 'network',
					datatype   => 'None',
				},
				value => [],
			};

			push(@{ $receive->{value} }, {
					name       => 'bytes',
					attributes => { providedby => 'network', datatype => 'Numeric', exponent => '0', Incrementing => 'Y', unit => 'Bytes' },
					value      => $2,
			});

			push(@{ $receive->{value} }, {
					name       => 'packets',
					attributes => { providedby => 'network', datatype => 'Numeric', exponent => '0', Incrementing => 'Y', unit => 'Packets' },
					value      => $3,
			});

			push(@{ $receive->{value} }, {
					name       => 'errs',
					attributes => { providedby => 'network', datatype => 'Numeric', exponent => '0', Incrementing => 'Y', unit => '' },
					value      => $4,
			});
			push(@{ $parent->{value} }, $receive);

			my $transmit = {
				name       => 'transmit',
				attributes => {
					providedby => 'network',
					datatype   => 'None',
				},
				value => [],
			};

			push(@{ $transmit->{value} }, {
					name       => 'bytes',
					attributes => { providedby => 'network', datatype => 'Numeric', exponent => '0', Incrementing => 'Y', unit => 'Bytes' },
					value      => $10,
			});

			push(@{ $transmit->{value} }, {
					name       => 'packets',
					attributes => { providedby => 'network', datatype => 'Numeric', exponent => '0', Incrementing => 'Y', unit => 'Packets' },
					value      => $11,
			});

			push(@{ $transmit->{value} }, {
					name       => 'errs',
					attributes => { providedby => 'network', datatype => 'Numeric', exponent => '0', Incrementing => 'Y', unit => '' },
					value      => $12,
			});
			push(@{ $parent->{value} }, $transmit);
			push(@{ $xml->{value} },    $parent);
		}
	}

	my @xml;

	my $conntrack = $self->conntrack;
	foreach my $struct ($xml, $conntrack) {
		push(@xml, $struct);
	}
	return @xml;
}

sub conntrack {
	my $self = shift;

	my $provider = 'network';

	my $xml = {
		name       => 'conntrack',
		attributes => {
			providedby => $provider,
			datatype   => 'None',
		},
		value => [],
	};

	my %files;
	foreach my $type (qw/buckets count max/) {
		$files{$type}{$_} = "/proc/sys/net/" . join('/', $_ eq 'old' ? ('ipv4', 'netfilter', 'ip') : ('netfilter', 'nf')) . "_conntrack_$type" for (qw/new old/);
	}

	if ((-r $files{'buckets'}{'new'}) or (-r $files{'buckets'}{'old'})) {
		my $nftype = "unknown";
		if (-r $files{'buckets'}{'new'}) {
			$nftype = "new";
		}
		elsif (-r $files{'buckets'}{'old'}) {
			$nftype = "old";
		}

		if ($nftype ne "unknown") {
			my @list = ("buckets", "count", "max");
			foreach my $key (@list) {
				my $file = "file_" . $key . "_" . $nftype;

				my $content = do {
					local $/;
					open(my $input, "< $files{$key}{$nftype}");
					<$input>;
				};

				if ($content && $content =~ /^(\d+)$/) {
					push(@{ $xml->{value} }, {
							name       => $key,
							attributes => {
								providedby   => $provider,
								datatype     => 'Numeric',
								exponent     => 0,
								Incrementing => 'N',
								unit         => '',
							},
							value => $1,
					});
				}
			}
		}
	}
	return $xml;
}

sub timezone {
	my $self = shift;

	my $provider = 'timezone';
	my $xml      = {
		name       => 'timezone',
		attributes => {
			providedby => $provider,
			datatype   => 'None'
		},
	};

	my $field1 = strftime "%z", localtime;
	my $field2 = strftime "%Z", localtime;

	my $datetime = ($field1 =~ /^[+-]?[0-9]{3,4}$/) ? { offset => $field1, abbreviation => $field2 } :
		($field2 =~ /^[+-]?[0-9]{3,4}$/) ? { offset => $field2, abbreviation => $field1 } : {};

	my @values;
	foreach my $field (sort keys %$datetime) {
		push(@values, {
				name       => $field,
				attributes => {
					providedby => $provider,
					datatype   => "Text",
				},
				value => $datetime->{$field},
		});
	}
	$xml->{value} = \@values;

	return $xml;
}

sub uptime {
	my $self = shift;

	my $content = do {
		local $/ = undef;
		open my $file, '<', '/proc/uptime';
		<$file>;
	};

	my $xml;
	if ($content && $content =~ /([0-9]*.[0-9]{0,2})\s*([0-9]*.[0-9]{0,2})/) {

		my $Field1 = $1;
		my $Field2 = $2;

		$xml = {
			name       => 'uptime',
			attributes => {
				providedby => 'uptime',
				datatype   => 'None',
			},
			value => [ {
					name       => 'seconds',
					attributes => {
						providedby => 'uptime',
						datatype   => 'Text',
					},
					value => $1,
				} ],
		};

		my $hms = {};
		if ($Field1 >= (24 * 60 * 60)) {
			my $Days = floor(int($Field1) / int(24 * 60 * 60));
			$Field1 = floor(int($Field1) % int(24 * 60 * 60));
			$hms->{days} = $Days . "D";
		}

		if ($Field1 > (60 * 60)) {
			my $Hours = floor(int($Field1) / int(60 * 60));
			$Field1 = floor(int($Field1) % int(60 * 60));
			$hms->{hours} = $Hours . "H";
		}
		if ($Field1 > 60) {
			my $Minutes = floor(int($Field1) / int(60));
			$Field1 = floor(int($Field1) % int(60));
			$hms->{minutes} = $Minutes . "M";
		}
		$hms->{seconds} = $Field1 . "S";
		push(@{ $xml->{value} }, {
				name       => 'human',
				attributes => {
					providedby => 'uptime',
					datatype   => 'Text',
				},
				value => "$hms->{days} $hms->{hours} $hms->{minutes} $hms->{seconds}",
		});
	}
	return $xml;
}

sub vzbackup {
	my $self     = shift;
	my $provider = 'vzbackup';
	my %attrs    = (
		config => {
			providedby => $provider,
			datatype   => "Text",
		},
		noConfig => {
			providedby => $provider,
			datatype   => "None",
		},
	);
	my $xml = {
		name       => 'vzbackup2',
		attributes => $attrs{noConfig},
		value      => [],
	};

	my %backuphash;
	my $file = "/usr/local/lp/etc/vps-backup-manifest";

	if (-e $file and -r $file) {
		if (open(MANIFESTINPUT, "< $file")) {
			while (<MANIFESTINPUT>) {
				if ($_ =~ /^(\S+),(\S+)$/) {

					my $Field1 = $1;
					my $Field2 = $2;

					my $file = "/usr/local/lp/logs/vpsbackup/backup-status-$Field1";
					if (-e $file and -r $file) {
						if (open(STATUSINPUT, "< $file")) {
							my $backupNode = {
								name       => 'ihavebadchars',
								attributes => { %{ $attrs{noConfig} }, realname => $Field2 },
								value      => []
							};
							push(@{ $backupNode->{value} }, { name => 'server', value => $1, attributes => $attrs{config} });
							%backuphash = undef;

							while (<STATUSINPUT>) {
								if ($_ =~ /^BACKUP(\d)(START|FINISH|STATUS)=(\S+)$/) {
									$backuphash{"$1"}{"$2"} = "$3";
								}
							}
							close(STATUSINPUT);

							for (my $x = 0; $x < 7; $x++) {
								my $backup = {
									name       => "backup$x",
									attributes => $attrs{noConfig},
									value      => [
										map { {
												name       => $_,
												value      => $backuphash{$x}{$_},
												attributes => $attrs{config}
											} } sort keys %{ $backuphash{$x} } ],
								};
								push(@{ $backupNode->{value} }, $backup);
							}
							push(@{ $xml->{value} }, $backupNode);
						}
					}
				}
			}
			close(MANIFESTINPUT);
		}
	}
	return $xml;
}

=head1   Daemon state providers

These functions form a psuedo-group of providers
which report on the state of a select set of running
services.  These methods return data wrapped in a
daemon state node, for ease of display.

=cut

sub pagentStatus {
	my ($self)        = @_;
	my $provider      = 'pagentStatus';
	my $pagentPidFile = '/usr/local/lp/var/p-agent.pid';

	my @subNodes;
	my $xml = {
		name       => 'pagentStatus',
		attributes => {
			providedby => $provider,
			datatype   => 'None'
		},
		value => \@subNodes,
	};

	if ((-e $pagentPidFile) and (-r $pagentPidFile)) {
		open(my $file, '<', $pagentPidFile);
		my ($pid) = <$file>;
		close($file);
		chomp($pid);

		if ($pid =~ /^(\d+)$/) {
			$pid = $1;
		}
		else {
			return;
		}

		push(@subNodes, $self->memInfoForPID($pid, $provider, 'pagent'));
		push(@subNodes, $self->runtimeForPID($pid, $provider, 'pagent'));
	}
	else {
		return;
	}

	return $self->daemonStateWrapper($provider, $xml);
}

sub eximstatus {
	my ($self)   = @_;
	my $provider = 'eximstatus';
	my %attrs    = (
		emails => {
			unit         => "Email",
			exponent     => "1",
			incrementing => "N",
			providedby   => $provider,
			datatype     => "Numeric",
		},
		noConfig => {
			providedby => $provider,
			datatype   => "None",
		},
	);
	my @subNodes;
	my $topXML = {
		name       => $provider,
		attributes => $attrs{noConfig},
		value      => \@subNodes,
	};

	unless (-e '/usr/local/lp/var/sonarpush/eximdata.json') {
		return;
	}

	open(my $file, '<', '/usr/local/lp/var/sonarpush/eximdata.json') or die $!;
	my $json = <$file>;
	close($file);

	# prevent "Missing or empty input" error from from_json when file
	# exists but is empty.
	return unless ($json);

	my $data = from_json($json);

	my %fields = (
		queue_length => 'emails'
	);

	for my $field (keys %fields) {
		if (exists $data->{$field}) {
			push(@subNodes, {
				name       => $field,
				value      => $data->{$field},
				attributes => $attrs{$fields{$field}},
			});
		}
	}

	return $self->daemonStateWrapper($provider, $topXML);
}

sub sonarpushStatus {
	my ($self)   = @_;
	my $provider = 'sonarpushStatus';
	my $xml      = {
		name       => 'sonarpushStatus',
		attributes => {
			providedby => $provider,
			datatype   => 'None'
		},
	};
	my @subNodes;

	my $memNode = $self->memInfoForPID($$, $provider, 'sonarpush');
	my $uptimeNode = $self->runtimeForPID($$, $provider, 'sonarpush');

	push(@subNodes, $memNode, $uptimeNode);

	push(@{ $uptimeNode->{value} },
		{
			name       => 'started',
			attributes => {
				providedby => $provider,
				datatype   => "Text",
			},
			value => $self->sonar->{Started},
		});
	my $updateAttrs = {
		providedby   => $provider,
		datatype     => "Numeric",
		unit         => "",
		exponent     => "0",
		incrementing => "Y"
	};
	for my $nameSpace (sort keys %{ $self->sonar->{status} }) {
		my $values = $self->sonar->{status}{$nameSpace};
		push(@subNodes, {
				name       => "sonarpush$nameSpace",
				attributes => $xml->{attributes},
				value      => [
					map { { name => $_, attributes => $updateAttrs, value => $values->{$_} } } sort keys %{$values}
				],
			}
		);
	}

	$xml->{value} = \@subNodes;
	return $self->daemonStateWrapper($provider, $xml);
}

my %WEBSERVERNAMES = (
	apache => [qw(httpd apache2 apache)],
	nginx  => [qw(nginx)],
	lightspeed => [qw(lshttpd)],
	lighttpd   => [qw(lighttpd)],
);

sub webserverStatus {
	my ($self) = @_;
	my $provider = 'webserverStatus';

	my $serverType;
	my $serverPid;
	my @topLevelProcs = `ps -o comm,pid --ppid 1`;
	splice(@topLevelProcs, 0, 1);
	my %daemons = map { /^(\S+)\s+(\d+)$/g } map { s/^\s+|\s+$//r } @topLevelProcs;
TYPE:	for my $type (sort keys %WEBSERVERNAMES) {
		my @serverNames = @{$WEBSERVERNAMES{$type}};
		for my $serverName (@serverNames) {
			if (my $pid = $daemons{$serverName}) {
				$serverType = $type;
				$serverPid  = $pid;
				last TYPE;
			}
		}
	}
	return unless $serverPid;

	my @httpProcs = `ps -o pid,pcpu,pmem,rss,etime,comm --ppid $serverPid $serverPid`;
	splice(@httpProcs, 0, 1);

	return unless @httpProcs;

	my @fieldNames = qw(pid percentCPU percentMEM totalMEM runtime command);
	my $procLeader;
	my @breakdown;
	for my $proc (@httpProcs) {
		$proc =~ s/^\s+|\s+$//;
		my @fields = split(/\s+/, $proc);
		my %namedFields;
		@namedFields{@fieldNames} = @fields;

		my ($days, $hours, $minutes, $seconds) = (delete $namedFields{runtime}) =~ /(?:(?:(\d+)-)?(\d+):)?(\d+):(\d+)/;
		$days  ||= '00';
		$hours ||= '00';
		my $totalSeconds = (($days * 24 + $hours) * 60 + $minutes) * 60 + $seconds;
		$namedFields{runtime}     = $totalSeconds;
		$namedFields{humanUptime} = "${days}D ${hours}H ${minutes}M ${seconds}S";

		if ($namedFields{pid} eq $serverPid) {
			$procLeader = \%namedFields;
		}
		else {
			push(@breakdown, \%namedFields);
			delete $namedFields{humanUptime};
		}
		delete $namedFields{command};
		delete $namedFields{pid};
	}
	return unless $procLeader;

	my %aggregate = map { $_ => 0 } qw(percentCPU percentMEM totalMEM runtime);
	for my $proc (@breakdown) {
		map { $aggregate{$_} += $proc->{$_} } sort keys %{$proc};
	}

	$aggregate{uptime}      = $procLeader->{runtime};
	$aggregate{avgRuntime}  = (delete $aggregate{runtime}) / (scalar(@breakdown) || 1);
	$aggregate{avgMEM}      = (delete($aggregate{totalMEM}) + $procLeader->{totalMEM}) / (scalar(@breakdown) + 1);
	$aggregate{humanUptime} = $procLeader->{humanUptime};

	$aggregate{numServers} = scalar(@breakdown);
	$aggregate{serverType} = $serverType;

	my %attrs = (
		serverType  => { datatype => "Text" },
		humanUptime => { datatype => "Text" },
		avgMEM      => {
			datatype     => "Numeric",
			unit         => "Bytes",
			exponent     => "3",
			incrementing => "N"
		},
		numServers => {
			datatype     => "Numeric",
			unit         => "",
			exponent     => "1",
			incrementing => "N"
		},
		uptime => {
			datatype     => "Numeric",
			unit         => "Seconds",
			incrementing => "N",
			exponent     => "1",
		},
		percentMEM => {
			datatype     => "Numeric",
			unit         => "Percent",
			exponent     => "1",
			incrementing => "N"
		},
		avgRuntime => {
			datatype     => "Numeric",
			unit         => "Seconds",
			incrementing => "N",
			exponent     => "1",
		},
		percentCPU => {
			datatype     => "Numeric",
			unit         => "Percent",
			exponent     => "1",
			incrementing => "N"
		},
	);
	$_->{providedby} = $provider for values %attrs;
	my $xml = {
		name       => $provider,
		attributes => {
			providedby => $provider,
			datatype   => 'None',
		},
		value => [ map { { name => $_, value => $aggregate{$_}, attributes => $attrs{$_} } } sort keys %aggregate ],
	};
	return $self->daemonStateWrapper($provider, $xml);
}

sub iostatData {
	my ($self)   = @_;
	my $provider = 'iostatData';
	my %attrs    = (
		cpuTime => {
			unit         => "Seconds",
			exponent     => "1",
			incrementing => "Y",
			providedby   => $provider,
			datatype     => "Numeric",
		},
		percent => {
			unit         => "Percent",
			exponent     => "1",
			incrementing => "N",
			providedby   => $provider,
			datatype     => "Numeric",
		},
		sectors => {
			unit         => "Sectors",
			exponent     => "1",
			incrementing => "Y",
			providedby   => $provider,
			datatype     => "Numeric",
		},
		time => {
			unit         => "Seconds",
			exponent     => "1",
			incrementing => "Y",
			providedby   => $provider,
			datatype     => "Numeric",
		},
		reads => {
			unit         => "Reads",
			exponent     => "1",
			incrementing => "Y",
			providedby   => $provider,
			datatype     => "Numeric",
		},
		writes => {
			unit         => "Writes",
			exponent     => "1",
			incrementing => "Y",
			providedby   => $provider,
			datatype     => "Numeric",
		},
		ops => {
			unit         => "Ops",
			exponent     => "1",
			incrementing => "N",
			providedby   => $provider,
			datatype     => "Numeric",
		},
		noConfig => {
			providedby => $provider,
			datatype   => "None",
		},
	);
	my %devicePropertyAttrs = (
		sectors_read     => $attrs{sectors},
		sectors_written  => $attrs{sectors},
		time_reading     => $attrs{time},
		time_writing     => $attrs{time},
		io_time          => $attrs{time},
		io_time_weighted => $attrs{time},
		reads_completed  => $attrs{reads},
		reads_merged     => $attrs{reads},
		writes_completed => $attrs{writes},
		writes_merged    => $attrs{writes},
		curr_io_ops      => $attrs{ops},
	);
	my @subNodes;
	my $topXML = {
		name       => $provider,
		attributes => $attrs{noConfig},
		value      => \@subNodes,
	};


	# We're going to be doing some number crunching
	# on this data that involves how it changes over a defined interval,
	# as opposed to "from boot", which is what the files actually track.
	# We can find the time interval more precisely using the data in the files,
	# but we need to give it some time to change, hence the sleep.
	my $stat1        = readProcStat();
	my $diskStatsPre = readProcDiskStat();
	sleep(1);
	my $stat2     = readProcStat();
	my $diskStats = readProcDiskStat();

	my $timeDiff;

	my @cpuNodes;
	my $cpuXML = {
		name       => 'CpuIostats',
		attributes => $attrs{noConfig},
		value      => \@cpuNodes,
	};
	push(@subNodes, $cpuXML);

	for my $cpuLabel (sort keys %$stat2) {
		# Omit Individual Proc stats
		next unless $cpuLabel eq 'cpu-total';

		my @cpuProperties;
		my $cpuStatXML = {
			name       => $cpuLabel,
			attributes => $attrs{noConfig},
			value      => \@cpuProperties,
		};
		push(@cpuNodes, $cpuStatXML);
		my %statDeltas;
		my $elapsed1 = 0;
		my $elapsed2 = 0;
		for my $propertyName (sort keys %{$stat2->{$cpuLabel}}) {
			$elapsed1 += $stat1->{$cpuLabel}{$propertyName};
			$elapsed2 += $stat2->{$cpuLabel}{$propertyName};
			$statDeltas{$propertyName} = $stat2->{$cpuLabel}{$propertyName} - $stat1->{$cpuLabel}{$propertyName};
			push(@cpuProperties, {
				name       => $propertyName,
				value      => sprintf('%0.09f', ($statDeltas{$propertyName})/100),
				attributes => $attrs{cpuTime},
			});
		}
		$timeDiff = $elapsed2 - $elapsed1;
		for my $property (sort keys %statDeltas) {
			push(@cpuProperties, {
				name       => "${property}_percentage",
				value      => sprintf('%0.09f', 100* ($statDeltas{$property} / $timeDiff)),
				attributes => $attrs{percent},
			});
		}
	}

	my @diskNodes;
	my $diskXML = {
		name       => 'DiskIostats',
		attributes => $attrs{noConfig},
		value      => \@diskNodes,
	};
	push(@subNodes, $diskXML);

	my $deviceMap = parseProcDevice();
	my $diskTypes = extractDiskType();
	my %devicesByType;

	for my $diskLabel (sort grep { !/^loop\d*$/ } keys %$diskStats) {
		my $diskProperties = $diskStats->{$diskLabel};
		my $deviceType = $deviceMap->{$diskProperties->{major}};
		my $diskType = $diskTypes->{$diskLabel} || 'other';

		delete @{ $diskProperties }{qw(major minor)};

		my $anyActivity = 0;
		$anyActivity += $_ for values %$diskProperties;
		next unless $anyActivity;

		next unless $deviceType;
		next if $deviceType eq 'device-mapper';
		next if $diskType eq 'iscsi';

		my @deviceProperties;
		my $deviceXML = {
			name       => $diskLabel,
			attributes => $attrs{noConfig},
			value      => \@deviceProperties,
		};
		push(@{$devicesByType{$diskType}}, $deviceXML);

		for my $propertyName (sort keys %{ $diskProperties }) {
			push(@deviceProperties, {
				name       => $propertyName,
				value      => sprintf('%0.09f', $diskProperties->{$propertyName} / ($devicePropertyAttrs{$propertyName}{unit} eq 'Seconds'? 1000 : 1)),
				attributes => $devicePropertyAttrs{$propertyName},
			});
		}
		for my $propertyName (qw(reads_completed writes_completed reads_merged writes_merged sectors_read sectors_written)) {
			my $value = $diskProperties->{$propertyName} - $diskStatsPre->{$diskLabel}{$propertyName};
			push(@deviceProperties, {
				name       => "${propertyName}_per_second",
				value      => $value,
				attributes => {
					unit         => "OpsPerSecond",
					exponent     => "1",
					incrementing => "N",
					providedby   => $provider,
					datatype     => "Numeric",
				},
			});
		}
		for my $propertyName (qw(io_time time_reading time_writing)) {
			my $value = $diskProperties->{$propertyName} - $diskStatsPre->{$diskLabel}{$propertyName};
			push(@deviceProperties, {
				name       => "${propertyName}_per_second",
				value      => sprintf('%0.09f', $value/1000),
				attributes => $attrs{time},
			});
			push(@deviceProperties, {
				name       => "${propertyName}_percent_busy",
				value      => 10 * ($value / $timeDiff),
				attributes => $attrs{percent},
			});
		}
		my %adjustmentMap = (
			time_reading => 'reads_completed',
			time_writing => 'writes_completed',
		);
		my $iotimeDiff = $diskProperties->{io_time} - $diskStatsPre->{$diskLabel}{io_time};
		for my $propertyName (qw(time_reading time_writing)) {
			my $totalValue            = $diskProperties->{$propertyName} - $diskStatsPre->{$diskLabel}{$propertyName};
			my $adjustmentFactor      = $adjustmentMap{$propertyName};
			my $adjustmentFactorDelta = $diskProperties->{$adjustmentFactor} - $diskStatsPre->{$diskLabel}{$adjustmentFactor};
			my $value                 = $totalValue / ($adjustmentFactorDelta || 1);
			push(@deviceProperties, {
				name       => "${propertyName}_adj_per_second",
				value      => sprintf('%0.09f', $value/1000),
				attributes => $attrs{time},
			});
			push(@deviceProperties, {
				name       => "${propertyName}_adj_percent_iotime",
				value      => $iotimeDiff && $value ? 100 * ($value / $iotimeDiff) : 0,
				attributes => $attrs{percent},
			});
			push(@deviceProperties, {
				name       => "${propertyName}_adj_percent_busy",
				value      => 10 * ($value / $timeDiff),
				attributes => $attrs{percent},
			});
		}
	}

	for my $deviceType (sort keys %devicesByType) {
		push(@diskNodes, {
			name       => $deviceType,
			attributes => $attrs{noConfig},
			value      => $devicesByType{$deviceType},
		});
	}

        return $topXML;
}

=head1 Helper functions

=cut

sub extractDiskType {
	my $devPathDir = '/dev/disk/by-path/';
	my %diskType;

	if (-d $devPathDir) {
		opendir(my $pathDir, $devPathDir) or die $!;
		my @files = grep { -l "$devPathDir/$_" } readdir($pathDir);
		for my $diskPath (@files) {
			my @pathParts = split("-", $diskPath);
			my $type = $pathParts[2];
			my $deviceRelativePath = readlink("$devPathDir/$diskPath");
			my ($deviceName) = $deviceRelativePath =~ m{/([\w\d]+?)$}g;
			$diskType{$deviceName} = $type;
		}
	}

	return \%diskType;
};

sub parseProcDevice {
	my $deviceFile = '/proc/devices';
	my %deviceData;
	my %deviceMap;
	if ((-e $deviceFile) && (-r $deviceFile)) {
		open(my $fh, '<', $deviceFile) or die $!;
		my @deviceLines = <$fh>;
		close($fh);
		chomp for @deviceLines;
		for my $device (@deviceLines) {
			my ($majorNum, $type) = $device =~ /^\s*(\d+)\s+(\S+)$/g;
			if ($majorNum) {
				$deviceMap{$majorNum}=$type;
			}
		}
	}
	return \%deviceMap;
}

my @cpuPropertyNames = qw(
	user
	nice
	system
	idle
	iowait
	irq
	softirq
	steal
	guest
	guest_nice
);
sub readProcStat {
	my $cpuStatFile  = '/proc/stat';
	my %cpuStats;
	if ((-e $cpuStatFile) && (-r $cpuStatFile)) {
		open(my $statFile, '<', $cpuStatFile)  or die $!;
		my @statLines = <$statFile>;
		close($statFile);
		chomp for @statLines;
		for my $statLine (@statLines) {
			my @fields = split(/\s+/, $statLine);
			my $name = shift(@fields);
			if ($name =~ /^cpu(\d+)?$/) {
				my $cpuNumber = defined($1) ? $1 : 'total';
				for my $propertyName (@cpuPropertyNames) {
					$cpuStats{"cpu-$cpuNumber"}{$propertyName} = shift(@fields);
				}
			}
		}
	}
	return \%cpuStats;
}

my @devicePropertyNames = qw(
	reads_completed
	reads_merged
	sectors_read
	time_reading
	writes_completed
	writes_merged
	sectors_written
	time_writing
	curr_io_ops
	io_time
	io_time_weighted
);
sub readProcDiskStat {
	my $regex = '(sd|hd|vd|xvd)[a-z]{1,2}\d+$';
	my $diskStatFile = '/proc/diskstats';
	my %diskStats;
	if ((-e $diskStatFile) && (-r $diskStatFile)) {
		open(my $statFile, '<', $diskStatFile)  or die $!;
		my @statLines = <$statFile>;
		close($statFile);
		chomp for @statLines;
		for my $statLine (@statLines) {
			my @fields = split(/\s+/, $statLine);
			shift(@fields);

			my ($major, $minor, $deviceName) = @fields;
			# Skip partitions
			next if lc($deviceName) =~ /$regex/;

			$diskStats{$deviceName}{major} = $major;
			$diskStats{$deviceName}{minor} = $minor;

			# Throw away unneeded fields
			@fields = @fields[ 3 .. 13 ];

			for my $propertyName (@devicePropertyNames) {
				$diskStats{$deviceName}{$propertyName} = shift(@fields);
			}
		}
	}
	return \%diskStats;
}

=head2 memInfoForPID

=over 4

=item Description:

helper function to generate the structure to report on memory information for a given pid
as found in proc/$pid/status

=item Parameters

pid      = the pid you want meminfo for
provider = the name of the provider that this info is being gathered for
name     = the name of the service or program that is being reported on.
           the name of the returned node will be ${name}Memory

=item Returns

an encodeXML compatible datastructure containing memory information for $pid

=back

=cut

sub memInfoForPID {
	my ($self, $pid, $provider, $name) = @_;

	return if is_tainted($pid);

	my $memNode = {
		name       => "${name}Memory",
		attributes => {
			providedby => $provider,
			datatype   => 'None'
		},
		value => []
	};
	my $attrHash = {
		providedby   => $provider,
		datatype     => "Numeric",
		unit         => "Bytes",
		exponent     => "3",
		incrementing => "N"
	};

	my $file = "/proc/$pid/status";

	if ((-e $file) and (-r $file)) {
		open(my $status, "< $file") or print "can't open '$file' for reading!\n";
		my @statusLines = grep {/^Vm\w+:/} <$status>;
		close($status);

		for my $vmLine (@statusLines) {
			my ($field, $value) = $vmLine =~ /^(Vm\w+):\s+(\d+).+$/;
			push(@{ $memNode->{value} }, {
					name       => $field,
					value      => $value,
					attributes => $attrHash,
				}
			);
		}
	}

	return $memNode;
}

sub runtimeForPID {
	my ($self, $pid, $provider, $name) = @_;

	return if is_tainted($pid);

	my $runNode = {
		name       => "${name}Uptime",
		attributes => {
			providedby => $provider,
			datatype   => 'None'
		},
		value => []
	};

	my $uptime = (`ps -o etime $pid`)[1];
	$uptime =~ s/\s//g;
	my ($days, $hours, $minutes, $seconds) = $uptime =~ /(?:(?:(\d+)-)?(\d+):)?(\d+):(\d+)/;
	$days  ||= '00';
	$hours ||= '00';
	my $totalSeconds  = (($days * 24 + $hours) * 60 + $minutes) * 60 + $seconds;
	my $humanReadable = "${days}D ${hours}H ${minutes}M ${seconds}S";

	push(@{ $runNode->{value} },
		{
			name       => 'seconds',
			attributes => {
				providedby   => $provider,
				datatype     => "Numeric",
				unit         => "Seconds",
				incrementing => "N",
				exponent     => "0",
			},
			value => $totalSeconds,
		},
		{
			name       => 'humanReadable',
			attributes => {
				providedby => $provider,
				datatype   => "Text"
			},
			value => "${days}D ${hours}H ${minutes}M ${seconds}S",
		});
	return $runNode;
}

=head2 getMemInfo

=over 4

=item Description:

helper function to gather general system memory usage,
as defined in /proc/meminfo

=item Parameters

none

=item Returns

a hashref containing the information in /proc/meminfo

=back

=cut

sub getMemInfo {
	my $file = "/proc/meminfo";
	if ((-e $file) and (-r $file)) {
		my %memHash = ();
		open(my $memInfo, "< $file") or print "can't open '$file' for reading!\n";
		my @memInfoLines = <$memInfo>;
		close($memInfo);

		for my $memFact (@memInfoLines) {
			my ($field, $value) = $memFact =~ /^(\S+):\s+(\d+).+$/;
			next unless defined $field && defined $value;
			$memHash{$field} = $value;
		}

		unless (grep { !defined $memHash{$_} } qw(MemTotal MemFree Cached Buffers)) {
			$memHash{MemUsed} = $memHash{MemTotal} - ($memHash{MemFree} + $memHash{Cached} + $memHash{Buffers});
		}

		unless (grep { !defined $memHash{$_} } qw(MemTotal MemFree)) {
			$memHash{TotalMemUsed} = $memHash{MemTotal} - $memHash{MemFree};
		}

		unless (grep { !defined $memHash{$_} } qw(SwapFree SwapTotal)) {
			$memHash{SwapUsed} = $memHash{SwapTotal} - $memHash{SwapFree};
		}

		return \%memHash;
	}
	return;
}

=head2 daemonStateWrapper

=over 4

=item Description:

helper function to generate an xml wrapper for ensuring that
software state providers are correctly grouped together in
the tree that radar uses to display information

=item Parameters

provider = the name of the provider that the wrapped info is being gathered for
nodeData = an encodeXML compatible structure to be wrapped in a 'daemonState' node.

=item Returns

a string of xml date, which is the encoding of nodeDate wrapped for daemonState

=back

=cut

sub daemonStateWrapper {
	my ($self, $provider, $nodeData) = @_;

	my $xml = {
		name       => 'daemonState',
		attributes => { providedby => $provider, datatype => 'None' },
		value      => $nodeData,
	};
	return $xml;
}

=head2 cpanelDataWrapper

=over 4

=item Description:

helper function to generate an xml wrapper for ensuring that
cpanel data providers are correctly grouped together in
the tree that radar uses to display information

=item Parameters

provider = the name of the provider that the wrapped info is being gathered for
nodeData = an encodeXML compatible structure to be wrapped in a 'cpanelData' node.

=item Returns

a string of xml data, which is the encoding of nodeDate wrapped for cpanelData

=back

=cut

sub cpanelDataWrapper {
	my ($self, $provider, $nodeData) = @_;

	my $xml = {
		name       => 'cpanelData',
		value      => $nodeData,
		attributes => {
			providedby => $provider,
			datatype   => 'None'
		},
	};
	return $xml;
}

sub extractCpanelUserDetails {
	my ($self) = @_;

	unless (-e '/usr/local/lp/var/sonarpush/accountexport') {
		return;
	}

	my @domainUsers = do {
		open(my $file, '<', '/usr/local/lp/var/sonarpush/accountexport');
		<$file>;
	};

	chomp for @domainUsers;

	my %userData;
	for my $domainUserEntry (@domainUsers) {
		my ($domain, $user) = split(':', $domainUserEntry, 2);
		$domain =~ s/(^\s+)|(\s+$)//g;
		$user =~ s/(^\s+)|(\s+$)//g;

		# cPanel adds a catchall entry pointing to
		# user 'nobody' that we needn't track.
		next if $domain eq '*';
		next unless $user;

		push(@{ $userData{$user}{domains} }, $domain);
	}

	return \%userData;
}

sub is_tainted {
	local $@;
	return !eval { eval("#" . substr(join("", @_), 0, 0)); 1 };
}

1;