1############################################################################### 2## Copyright 2005-2016 OCSInventory-NG/OCSInventory-Server contributors. 3## See the Contributors file for more details about them. 4## 5## This file is part of OCSInventory-NG/OCSInventory-ocsreports. 6## 7## OCSInventory-NG/OCSInventory-Server is free software: you can redistribute 8## it and/or modify it under the terms of the GNU General Public License as 9## published by the Free Software Foundation, either version 2 of the License, 10## or (at your option) any later version. 11## 12## OCSInventory-NG/OCSInventory-Server is distributed in the hope that it 13## will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty 14## of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15## GNU General Public License for more details. 16## 17## You should have received a copy of the GNU General Public License 18## along with OCSInventory-NG/OCSInventory-ocsreports. if not, write to the 19## Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, 20## MA 02110-1301, USA. 21################################################################################ 22package Apache::Ocsinventory::Interface::Internals; 23 24use strict; 25 26require Exporter; 27 28# If SOAP lite doesn't decode xml entities 29eval { 30 require XML::Entities; 31}; 32if($@){ 33 print STDERR "[".localtime()."] OCSINVENTORY: (SOAP): Cannot find XML::Entities\n"; 34} 35 36use Apache::Ocsinventory::Map; 37use Apache::Ocsinventory::Interface::Database; 38use XML::Simple; 39 40our @ISA = qw /Exporter/; 41 42our @EXPORT = qw / 43 build_xml_standard_section 44 decode_xml 45 encode_xml 46 search_engine 47 send_error 48 get_custom_field_name_map 49 get_custom_fields_values_map 50/; 51 52sub decode_xml{ 53 my $data = shift; 54 unless( $data =~ /^</ ){ 55 return XML::Entities::decode('all', $data); 56 } 57 return $data; 58} 59 60sub search_engine{ 61 # Available search engines 62 my $engine = shift; 63 my %search_engines = ( 64 'first' => \&engine_first, 65 ); 66 &{ $search_engines{ (lc $engine) } }( @_ ); 67} 68 69sub engine_first { 70 my ($request, $ids, $begin, $main_table, $accountinfo_table, $deviceid_column, $pk, $sort_by, $sort_dir) = @_; 71 my $parsed_request = XML::Simple::XMLin( $request, ForceArray => ['ID', 'EXCLUDE_ID', 'TAG', 'EXCLUDE_TAG', 'USERID'], SuppressEmpty => 1 ) or die; 72 my ($id, $name, $userid, $checksum, $tag); 73 74 # Database ids criteria 75 die("BAD_REQUEST") if ( $parsed_request->{ID} and $parsed_request->{EXCLUDE_ID} ); 76 77 if( $parsed_request->{ID} ){ 78 if( my @ids = untaint_int_lst( @{ $parsed_request->{ID} } )){ 79 $id .= ' AND'; 80 $id .= ' '.$main_table.'.ID IN('.join(',', @ids ).')'; 81 } 82 } 83 84 if( $parsed_request->{EXCLUDE_ID} ){ 85 if( my @exclude_ids = untaint_int_lst( @{ $parsed_request->{EXCLUDE_ID} } )){ 86 $id .= ' AND'; 87 $id .= ' '.$main_table.'.ID NOT IN('.join(',', @exclude_ids ).')'; 88 } 89 } 90 91 # Tag criteria 92 die("BAD_REQUEST") if ( $parsed_request->{TAG} and $parsed_request->{EXCLUDE_TAG} ); 93 94 if( $parsed_request->{TAG} ){ 95 if( my @tags = untaint_dbstring_lst( @{ $parsed_request->{TAG} } )){ 96 $tag .= ' AND'; 97 $tag .= ' '.$accountinfo_table.'.TAG IN("'.join('","', @tags ).'")'; 98 } 99 } 100 101 if( $parsed_request->{EXCLUDE_TAG} ){ 102 if( my @exclude_tags = untaint_dbstring_lst( @{ $parsed_request->{EXCLUDE_TAG} } )){ 103 $tag .= ' AND'; 104 $tag .= ' '.$accountinfo_table.'.TAG NOT IN("'.join('","', @exclude_tags ).'")'; 105 } 106 } 107 108 # Checksum criteria (only positive "&" will match 109 if( $parsed_request->{CHECKSUM} ){ 110 die("BAD_CHECKSUM") if !untaint_int( $parsed_request->{CHECKSUM} ); 111 $checksum = ' AND ('.$parsed_request->{CHECKSUM}.' & '.$main_table.'.CHECKSUM)'; 112 } 113 # Associated user criteria 114 if( $parsed_request->{USERID} && $main_table =~ /^hardware$/){ 115 if( my @users_id = untaint_dbstring_lst( @{ $parsed_request->{USERID} } ) ){ 116 $userid .= ' AND'; 117 $userid .= ' '.$main_table.'.USERID IN("'.join('","', @users_id ).'")'; 118 } 119 } 120 # Generate sql string 121 my $search_string = "SELECT DISTINCT $main_table.ID FROM $main_table,$accountinfo_table WHERE $main_table.$deviceid_column NOT LIKE '\\_%' AND $main_table.ID=$accountinfo_table.$pk $id $name $userid $checksum $tag ORDER BY hardware.$sort_by $sort_dir limit $begin,$ENV{OCS_OPT_WEB_SERVICE_RESULTS_LIMIT}"; 122 # Play it 123 my $sth = get_sth($search_string); 124 # Get ids 125 while( my $row = $sth->fetchrow_hashref() ){ 126 push @{$ids}, $row->{ID}; 127 } 128 # Destroy request object 129 $sth->finish(); 130} 131 132 133# Build a database mapped inventory section 134sub build_xml_standard_section{ 135 my ($id, $main_table, $xml_ref, $section) = @_; 136 my %element; 137 my @tmp; 138 139 my %get_table_pk_functions = ( 140 'hardware' => \&get_hardware_table_pk, 141 'snmp' => \&get_snmp_table_pk 142 ); 143 144 # Request database 145 my $deviceid = &{ $get_table_pk_functions{ $main_table } }($section); 146 my $sth = get_sth("SELECT * FROM $section WHERE $deviceid=?", $id); 147 148 # Build data structure... 149 while ( my $row = $sth->fetchrow_hashref() ){ 150 for( keys(%{$DATA_MAP{ $section }->{fields}}) ){ 151 next if $DATA_MAP{ $section }->{fields}->{$_}->{noSql}; 152 # New DB schema support 153 if( $DATA_MAP{ $section }->{fields}->{$_}->{type} ){ 154 my $field = $_; 155 $field =~ s/_ID//g; #We delete the _ID pattern to be in concordance with table name 156 $row->{ $_ } = get_type_name($section, $field, $row->{ $_ }); 157 $element{$field} = [ $row->{ $_ } ]; 158 } 159 else { 160 $element{$_} = [ $row->{ $_ } ]; 161 } 162 } 163 164 push @tmp, { %element }; 165 %element = (); 166 } 167 $section =~ s/$section/\U$&/g; 168 $xml_ref->{$section}=[ @tmp ]; 169 @tmp = (); 170 $sth->finish; 171} 172 173sub reset_checksum { 174 my( $checksum, $ref ) = @_; 175 my $where = join(',', @$ref); 176 return do_sql("UPDATE hardware SET CHECKSUM=? WHERE ID IN ($where)", $checksum); 177} 178sub send_error{ 179 my $error = shift; 180 return XMLout ( 181 { 'ERROR' => [ $error ] }, 182 RootName => 'RESULT' 183 ); 184} 185 186sub untaint_int_lst{ 187 my @list = @_; 188 my @cleared; 189 for (@list){ 190 push @cleared, $_ if untaint_int($_); 191 } 192 return @cleared; 193} 194 195sub untaint_int{ 196 my $int = shift; 197 return $int =~ /^\d+$/; 198} 199 200# Helper for resolving custom field names in ACCOUNTINFO 201sub get_custom_field_name_map { 202 my $account_type = shift; 203 my $name_map = {}; 204 205 # Request database 206 my $sth = get_sth('SELECT ID, NAME FROM accountinfo_config WHERE NAME_ACCOUNTINFO IS NULL AND ACCOUNT_TYPE=?', $account_type); 207 # Build data structure... 208 my $rows = $sth->fetchall_arrayref(); 209 foreach my $row ( @$rows ) { 210 $name_map->{ "fields_" . $row->[0] } = $row->[1]; 211 } 212 $sth->finish; 213 return $name_map; 214} 215 216# Helper for resolving textual values of custom fields 217# of type CHECKBOX, RADIOBUTTON, SELECT 218sub get_custom_fields_values_map { 219 my $account_value = shift; 220 my $values_map = {}; 221 222 # Request database 223 my $sth = get_sth('SELECT NAME, IVALUE, TVALUE FROM config WHERE NAME LIKE ?', $account_value."_%_%"); 224 my $rows = $sth->fetchall_arrayref(); 225 226 my $regexp = $account_value."_(.*)_[0-9]+"; 227 228 foreach my $row ( @$rows ) { 229 if ($row->[0] =~ /^$regexp$/) { 230 $values_map->{ $1 }->{ $row->[1] } = $row->[2]; 231 } 232 } 233 $sth->finish; 234 return $values_map; 235} 236 2371; 238