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