Subversion Repositories computer_asset_manager_v1

Rev

Rev 3 | Rev 6 | Go to most recent revision | Details | Compare with Previous | Last modification | View Log | RSS feed

Rev Author Line No. Line
3 rodolico 1
<?php
2
 
3
/*
4
 * Requires php-pear and libyaml under Debian Wheezy
5
 * apt-get install php-pear php5-dev libyaml0dev
6
 * I don't know if you also need libyaml-0-2 libyaml-0-2-dbg
7
 * pecl install yaml
5 rodolico 8
 *
9
 * V0.9 20160203 RWR
10
 * Changed to use yaml for the configuration file. Had to take the
11
 * anonymous functions which parsed the different file types and convert
12
 * them to simpl eval() blocks.
13
 * 
3 rodolico 14
*/
15
 
16
require 'library.php';
5 rodolico 17
$VERSION='0.9';
3 rodolico 18
 
19
 
20
class sysinfo {
21
   private $report; // this will hold an entire report
22
   private $messages = array();
23
   private $FATAL = false;
24
   private $parsingArray;   // this is a required array that teaches how to properly parse the input
25
 
26
   // following are used internally to track important information
27
   private $clientID;   // id in database
28
   private $clientName; // name from report
29
   private $machineID;  // id in database
30
   private $machineName;// name in report
31
   private $reportDate; // unix timestamp of report
32
   private $reportDateSQL; // used for inserts and compares in the database
33
   private $serialNumber; // the serial number, which is assumed to be unique
34
   private $fileContents;
35
   private $processDuplicates = false; // used for testing; allows duplicate reports
36
   private $returnCode = 0; // how we returned from this
37
   private $errors = array( 1 => 'Could not process file, not xml, yaml or ini',
38
                            2 => 'Invalid report, does not have one or more of report date, client name or computer name',
39
                            3 => 'Invalid Report, invalid machine name',
40
                            4 => 'Duplicate Report',
41
                            5 => 'New Report, must be added to CAMP',
42
                            6 => 'Report is in queue for addition after updates to CAMP'
43
                          );
44
 
45
   /*
46
    * $parsingArray contains one or more rows, each of which have three rows, starttag, endtag and eval
47
    * starttag and endtag are regex's which match the beginning and end of a document (ie, --- and ... for yaml)
48
    * eval is an anonymous function which will evaluate a particular type of document, returning it as an
49
    * array which we store in $report
50
    */
51
 
52
   function __construct( $parsingArray ) {
53
      $this->parsingArray = $parsingArray;
54
   }
55
 
56
   public function processReport () {
57
      $this->validateReport(); // validate the report appears to be ok
58
      // prepend the name of the report to messages BEFORE any error messages that may have appeared
59
      array_unshift( $this->messages,
60
            "Report on $this->reportDateSQL, client $this->clientName, system $this->machineName ($this->fileType)");
61
      // now, we do every step in turn. If they set the FATAL flag, we exit.
62
      if ( $this->FATAL ) return $this->returnCode;
63
      $this->getMachineID();
64
      if ( empty( $this->machineID ) ) return $this->returnCode;
65
      $this->recordReport();
66
      if ( $this->FATAL) return $this->returnCode;
67
      $this->updateComputerMakeup();
68
      $this->updateOS();
69
      $this->updateBootTime();
70
      $this->doIPAddresses();
71
      $this->processPCI();
72
      $this->processSoftwarePackages();
73
      return $this->returnCode;;
74
   } // function processReport
75
 
76
   /* 
77
    * Name: loadFromFile
78
    * Parameters: filename (path to load file from)
79
    * Returns: true if on success, false if not
80
    * Modifies: 
81
    *    report - added messages on why the report is not valid
82
    *    FATAL    - set to true if any of the required values do not exist
83
    */
84
 
85
   public function loadFromFile ( $filename ) {
86
      // load file into memory. Additionally, remove any \r's from it (leaving \n's for newlines)
87
      $this->fileContents = str_replace("\r","", file_get_contents( $filename ) );
88
      // try to parse the body out
89
      $this->getBody();
90
      if ( isset( $this->report ) ) {
91
         if ( $this->fileType == 'xml' ) $this->fixXML();
92
         if ( $this->fileType == 'ini' ) {
93
            $this->messages[] = 'We do not process ini files, but storing it instead';
94
         }
95
         return $this->fileType;
96
      } else { // we don't know what it is
97
         $this->FATAL = true;
98
         $this->messages[] = "Unable to parse [$filename] using YAML, XML or INI";
99
         return false;
100
      }
101
      //unset( $this->fileContents ); // free up memory
102
   } // function loadFromFile
103
 
104
 
105
   // once the file has been loaded, we need to figure out what kind of file it is
106
   // then use the function embedded in $parsingArray to import it.
107
   private function getBody ( ) {
108
      foreach ( $this->parsingArray as $key => $regexes ) {
109
         $matches;
110
         $pattern = $regexes['startTag'] . '.*' . $regexes['endTag'];
111
         if ( preg_match( "/$pattern/ms", $this->fileContents, $matches ) ) {
112
            $this->fileType = $key;
113
            $this->fileContents = $matches[0];
5 rodolico 114
            $body = $this->fileContents;
115
            $this->report = eval( $regexes['eval'] );
116
            if ( $this->report ) return true;
3 rodolico 117
         } // if
118
      } // foreach
119
      return false;
120
   }
121
 
122
   /*
123
    * Name: fixXML
124
    * Parameters: None
125
    * Returns: nothing
126
    * Modifies: $this->report
127
    * YAML creates the entries as ['network']['interface name']
128
    * XML  creates the entries as ["network"][x][name]=>
129
    * We will simply copy the XML information into a duplicate
130
    * in YAML style
131
    */
132
   private function fixXML() {
133
       $oldInfo = $this->report['network'];
134
       unset ($this->report['network']);
135
       foreach ( $oldInfo as $entry ) {
136
          #$this->messages[] = "Duplicating network entry for $entry[name]";
137
          foreach ($entry as $key => $value ) {
138
             $this->report['network'][$entry['name']][$key] = $value;
139
             #$this->messages[] = "  Adding  $value as value for network.name.$key";
140
          } // inner foreach
141
       } // outer foreach
142
   } // fixXML
143
 
144
 
145
   private function processINI() {
146
      return false;
147
   }
148
 
149
   public function dumpMessages () {
150
      $return = implode( "\n", $this->messages );
151
      $return .= "\n";
152
      return $return;
153
   }
154
 
155
   /* 
156
    * Name: validateReport
157
    * Parameters: none
158
    * Returns: true if valid, false if not
159
    * Modifies: 
160
    *    messages - added messages on why the report is not valid
161
    *    FATAL    - set to true if any of the required values do not exist
162
    */
163
 
164
      public function validateReport () {
165
      /*
166
       * A report is considered valid if it has a date, client name and hostname
167
       * No check is made at this time for validity of that data
168
       */
169
      if ( empty( $this->report['report']['date']) ) {
170
         $this->messages[] = 'No report date';
171
      } else {
172
         $this->reportDate = strtotime( $this->report['report']['date'] ); // store in Unix timestamp
173
         $this->reportDateSQL = strftime( '%F %T', $this->reportDate ); // save a copy of the date in SQL format
174
         if ( empty( $this->reportDate ) )
175
            $this->messages[] = "Unable to parse report date [$this->report['report']['date']]";
176
      }
177
 
178
      if ( empty( $this->report['report']['client'] ) ) {
179
         $this->messages[] = 'No client name';
180
      } else {
181
         $this->clientName = $this->report['report']['client'];
182
      }
183
 
184
      if ( empty ( $this->report['system']['hostname'] ) ) {
185
         $this->messages[] = 'No Computer Name';
186
      } else {
187
         $this->machineName = $this->report['system']['hostname'];
188
      }
189
      $this->FATAL = ! empty( $this->messages );
190
 
191
      // serial number is fairly new, so it is not a critical error
192
      // it will be set in getMachineID if we have it here, but not there
193
      if ( empty ( $this->report['system']['serial'] ) ) {
194
         $this->messages[] = 'No Serial Number';
195
      } else {
196
         $this->serialNumber = $this->report['system']['serial'];
197
      }
198
 
199
   } // function validateReport
200
 
201
 
202
   /* 
203
    * Name: getMachineID
204
    * Parameters: none
205
    * Returns: true if found, false if not
206
    * Modifies: 
207
    *    clientID - index value of client in database
208
    *    machineID - index value of machine in database
209
    */
210
   private function getMachineID () {
211
      $this->clientID = getClientID( $this->clientName );
212
      if ( $this->clientID ) {
213
         $this->machineID = getMachineID( $this->machineName, $this->clientID, $this->serialNumber );
214
      }
215
      if ( empty( $this->machineID ) ) {
216
         $this->messages[] = $this->makeUnknown( $this->clientName, $this->machineName, $this->reportDateSQL );
217
         return false;
218
      }
219
      if ( $this->machineID == -1 ) {
220
         $this->messages[] = "Duplicate serial number found for serial [$this->serialNumber], aborting";
221
         return false;
222
      }
223
      return true;
224
   } // function getMachineID
225
 
226
   /* 
227
    * checks for a duplicate report, ie one that has already been run.
228
    * if this computer has a report already for this date/time
229
    */ 
230
   private function recordReport() {
231
      $count = getOneDBValue("select count(*) from sysinfo_report where device_id = $this->machineID and report_date = '$this->reportDateSQL'");
232
      if ( $this->processDuplicates ) { $count = 0; } // for testing
233
      if ( $count ) {
234
         $this->messages[] = "Duplicate Report for $this->machineName (id $this->machineID) on $this->reportDateSQL";
235
         $this->returnCode = 4;
236
         $this->FATAL = true;
237
         return false;
238
      }
239
      $version = $this->report['report']['version'];
240
      # if we made it this far, we are ok, so just add the report id
241
      queryDatabaseExtended("insert into sysinfo_report(device_id,version,report_date,added_date) values ($this->machineID ,'$version','$this->reportDateSQL',now())");
242
      $this->reportID = getOneDBValue("select max( sysinfo_report_id ) from sysinfo_report where device_id = $this->machineID and report_date = '$this->reportDateSQL'");
243
      return true;
244
   }
245
 
246
 
247
   function makeUnknown( $clientName, $machineName, $reportDate ) {
248
      $result = getOneDBValue("select report_date from unknown_entry where client_name = '$clientName' and device_name = '$machineName'");
249
      if ( empty( $result ) ) {
250
         queryDatabaseExtended( "insert into unknown_entry(client_name,device_name,report_date) values ('$clientName', '$machineName', '$reportDate')");
251
         $this->returnCode = 5;
252
         return "New entry, client=$clientName, device=$machineName. You must update Camp before this report can be processed";
253
      } else {
254
         $this->returnCode = 6;
255
         return "New entry detected, but entry already made. You must update Camp before this can be processed";
256
      }
257
   }
258
 
259
   # simply used to get an attrib_id. If it does not exit, will create it
260
 
261
   private function getAttributeID ( $attributeName, $reportDate ) {
262
      $attributeName = makeSafeSQLValue( $attributeName );
263
      $result = getOneDBValue( "select attrib_id from attrib where name = $attributeName and removed_date is null" );
264
      if ( !isset( $result) ) {
265
         $result = queryDatabaseExtended( "insert into attrib (name,added_date) values ($attributeName,'$reportDate')");
266
         $this->messages[] = "Added a new attribute type [$attributeName]";
267
         $result = $result['insert_id'];
268
      }
269
      return $result;
270
   }
271
 
272
   /*
273
    * Checks an attribute from the device_attriburtes table. If the value has
274
    * changed, sets the old one's removed_date to this report date, then adds
275
    * new record for new value.
276
    * If the record does not exist, simply creates it
277
    */
278
   public function checkAndUpdateAttribute( $attribute, $value ) {
279
      if ( !isset($attribute) || !isset($value ) ) {
280
         $this->messages[] = "Error: attempt to use null value for [$attribute], value [$value] for ID $this->machineID in checkAndUPdateAttribute";
281
         return false;
282
      }
283
      $value = makeSafeSQLValue($value); # we want to always quote the value on this particular one
284
      //$attribute = makeSafeSQLValue( $attribute );
285
      $attrib_id = $this->getAttributeID( $attribute, $this->reportDateSQL );
286
      $result = getOneDBValue( "select device_attrib.value
287
         from device_attrib join attrib using (attrib_id)
288
         where device_attrib.device_id = $this->machineID
289
               and device_attrib.removed_date is null
290
               and attrib.attrib_id = $attrib_id
291
         ");
292
      $result = makeSafeSQLValue( $result ); # we must do this since we are comparing to $value which has had this done
293
      if ( isset( $result ) ) { # got it, now see if it compares ok
294
         if ( $value != $result ) { # nope, this has changed. Note, must use fixDatabaseValue for escapes already in $value
295
            $this->messages[] = "New value [$value] for $attribute for device $this->machineID, voiding previous";
296
            queryDatabaseExtended( "update device_attrib
297
                                       set removed_date = '$this->reportDateSQL'
298
                                    where device_id = $this->machineID
299
                                          and attrib_id = $attrib_id
300
                                          and removed_date is null");
301
            unset( $result ); # this will force the insert in the next block of code
302
         } # if $result ne $value
303
      } # if ($result)
304
      if ( ! isset( $result ) ) { # we have no valid entry for this attribute
305
         //$this->messages[] = "In checkAndUpdateAttribute, adding new record with insert into device_attrib(device_id,attrib_id,value,added_date) values ($this->machineID,$attrib_id,$value,$this->reportDateSQL)";
306
         queryDatabaseExtended( "insert into device_attrib(device_id,attrib_id,value,added_date)
307
                                 values ($this->machineID,$attrib_id,$value,'$this->reportDateSQL')");
308
         return true;
309
      }
310
      return false;
311
   } // checkAndUpdateAttribute
312
 
313
   # simply verifies some attributes of the computer
314
   private function updateComputerMakeup() {
315
      $this->checkAndUpdateAttribute('Memory',        $this->report['system']['memory']);
316
      $this->checkAndUpdateAttribute('Number of CPUs',$this->report['system']['num_cpu']);
317
      $this->checkAndUpdateAttribute('CPU Type',      $this->report['system']['cpu_type']);
318
      $this->checkAndUpdateAttribute('CPU SubType',   $this->report['system']['cpu_sub']);
319
      $this->checkAndUpdateAttribute('CPU Speed',     $this->report['system']['cpu_speed']);
320
   } // updateComputerMakeup
321
 
322
   // This is kind of funky, because column = null does not work, it must be column is null, so we have to look for it.
323
   function makeSQLEquals ( $field, $value ) {
324
      return "$field " . ( $value == 'null' ? 'is null' : '=' . $value );
325
   }
326
 
327
 
328
   private function updateOS () {
329
      // find os
330
      $osName =         makeSafeSQLValue( isset($this->report['operatingsystem']['os_name'])         ? $this->report['operatingsystem']['os_name'] : '');
331
      $kernel =         makeSafeSQLValue( isset($this->report['operatingsystem']['kernel'])          ? $this->report['operatingsystem']['kernel'] : '');
332
      $distro_name =    makeSafeSQLValue( isset($this->report['operatingsystem']['os_distribution']) ? $this->report['operatingsystem']['distribution'] : '');
333
      $release =        makeSafeSQLValue( isset($this->report['operatingsystem']['os_release'])      ? $this->report['operatingsystem']['release'] : '');
334
      $version =        makeSafeSQLValue( isset($this->report['operatingsystem']['os_version'])      ? $this->report['operatingsystem']['os_version'] : '');
335
      $description =    makeSafeSQLValue( isset($this->report['operatingsystem']['description'])     ? $this->report['operatingsystem']['description'] : '');
336
      $codename =       makeSafeSQLValue( isset($this->report['operatingsystem']['codename'])        ? $this->report['operatingsystem']['codename'] : '');
337
 
338
      $osID = getOneDBValue( "select operating_system_id from operating_system
339
               where " . $this->makeSQLEquals( 'name', $osName ) . "
340
                  and " . $this->makeSQLEquals( 'kernel', $kernel ) . "
341
                  and " . $this->makeSQLEquals( 'distro', $distro_name ) . "
342
                  and " . $this->makeSQLEquals( 'distro_release', $release ) );
343
      if ( !isset( $osID ) ) {
344
         $this->messages[] = "Creating a new OS with name = $osName, kernel = $kernel, distro = $distro_name,  distro_release = $release";
345
         $result = queryDatabaseExtended( "insert into operating_system (name,version,kernel,distro,distro_description,distro_release,distro_codename, added_date) values
346
                                          ($osName,$version,$kernel,$distro_name,$description,$release,$codename, '$this->reportDateSQL')" );
347
         $osID = $result['insert_id'];
348
      }
349
 
350
      # verify the operating system
351
      $registeredOS = getOneDBValue( "select operating_system_id from device_operating_system where device_id = $this->machineID and removed_date is null" );
352
      if ( ! $registeredOS || $registeredOS != $osID ) {
353
         if ( $registeredOS ) { #we have the same computer, but a new OS???
354
            queryDatabaseExtended( "update device_operating_system set removed_date = '$this->reportDateSQL' where device_id = $this->machineID and removed_date is null");
355
            $this->messages[] = "Computer $this->machineName has a new OS";
356
         }
357
         queryDatabaseExtended( "insert into device_operating_system( device_id,operating_system_id,added_date) values ($this->machineID,$osID,'$this->reportDateSQL')");
358
      }
359
   }
360
 
361
   # every time we get a report, we need to see if the computer was rebooted
362
   # if last reboot date is not the same as what our report shows, we will
363
   # remove the existing entry, then add a new one
364
   private function updateBootTime() {
365
      $lastReboot;
366
      if ( isset( $this->report['system']['last_boot'] ) ) {
367
         $lastReboot = strftime( '%F %T',$this->report['system']['last_boot'] ); // convert unix timestamp to sql value
368
         if ( isset( $lastReboot ) ) {
369
            if ( ! getOneDBValue( "select computer_uptime_id from computer_uptime where device_id = $this->machineID and last_reboot = '$lastReboot'" ) ) {
370
               $this->messages[] = "Computer was rebooted at $lastReboot";
371
               queryDatabaseExtended( "update computer_uptime set removed_date = '$this->reportDateSQL' where device_id = $this->machineID and removed_date is null" );
372
               queryDatabaseExtended( "insert into computer_uptime (device_id,added_date,last_reboot) values ($this->machineID,'$this->reportDateSQL','$lastReboot')");
373
            }
374
         } else {
375
            $this->messages[] = 'Invalid reboot time [' . $this->Report['system']['last_boot'] . ']';
376
         }
377
      } else {
378
         $this->messages[] = 'No Boot time given';
379
      }
380
   }
381
 
382
 
383
   // checks if the report and the database values are the same
384
   private function checkInterfaceEquals( $report, $database ) {
385
      //var_dump( $report );
386
      //var_dump( $database );
387
      return ( 
388
         $report['address'] == $database['address'] and
389
         $report['netmask'] == $database['netmask'] and
390
         $report['ip6address'] == $database['ip6'] and
391
         $report['ip6networkbits'] == $database['ip6net'] and
392
         $report['mtu'] == $database['mtu'] and
393
         $report['mac'] == $database['mac'] 
394
         );
395
   } // checkInterfaceEquals
396
 
397
 
398
   /*
399
    * routine will check for all IP addresses reported and check against those recorded in the
400
    * database. It will remove any no longer in the database, and add any new ones
401
    */
402
   private function doIPAddresses () {
403
      /*
404
       * get all interfaces from the database
405
       * remove any interfaces from database which are not in report
406
       * add any interfaces from report which are not in database or which have changed
407
       */
408
      $network = $this->report['network']; // make a copy so we don't modify the original report
409
      // we won't process lo
410
      unset ($network['lo']);
411
      // at this point, we could get rid of tun's also, if we wanted to
412
 
413
      // let's ensure that all rows in report have the minimum required data
414
      foreach ( array_keys( $network ) as $interface ) {
415
         foreach ( array( 'address','netmask','ip6address','ip6networkbits','mtu','mac' ) as $required ) {
416
            if ( ! isset( $network[$interface][$required] ) ) { 
417
               $network[$interface][$required]  = NULL; 
418
            }
419
         } // checking all keys
420
      } // checking all report rows
421
      // var_dump( $network );   
422
 
423
      // get current information on all active interfaces in the database
424
      $dbInterfaces = queryDatabaseExtended( "select network_id,interface,address,netmask,ip6,ip6net,mac,mtu from network where device_id = $this->machineID and removed_date is null" );
425
      //print "select network_id,interface,address,netmask,ip6,ip6net,mac,mtu from network where device_id = $this->machineID and removed_date is null\n";
426
      //var_dump($dbInterfaces);
427
 
428
      // now, get rid of any interfaces which are no longer in the database
429
      // get a list of interfaces being passed in by report as a comma delimited list for processing.
430
      foreach ( array_keys( $network) as $temp ) {
431
         $interfaces[] = "'$temp'";
432
      }
433
      $interfaces = implode( ',', $interfaces );
434
      if ( $interfaces ) { // are there any? Then simply remove anything not in their set.
435
         queryDatabaseExtended( "update network set removed_date = '$this->reportDateSQL' where device_id = $this->machineID and removed_date is null and interface not in ($interfaces)");
436
         // print "update network set removed_date = '$this->reportDateSQL' where device_id = $this->machineID and removed_date is null and interface not in ($interfaces)\n";
437
         // reload the database results to exclude the ones we removed above
438
         $dbInterfaces = queryDatabaseExtended( "select network_id,interface,address,netmask,ip6,ip6net,mac,mtu from network where device_id = $this->machineID and removed_date is null" );
439
         // print "select network_id,interface,address,netmask,ip6,ip6net,mac,mtu from network where device_id = $this->machineID and removed_date is null\n";
440
      } // if
441
      // at this point, the database is a subset of the report, ie there are no entries in the database which are not also in the report.
442
      // let's remove the ones which are the same from both sets.
443
      // we can take a shortcut since the database is a subset of the report, ie we can remove entries from the report as we process
444
      if ( $dbInterfaces ) { // do we have anything in the database?
445
         foreach ( $dbInterfaces['data'] as $row => $value ) {
446
            // print "checking if " . $network[$value['interface']] . " equals $value\n";
447
            if ( $this->checkInterfaceEquals( $network[$value['interface']], $value ) ) { // compare the two entries
448
               // print "\tIt Does, so deleting it from updates\n";
449
               unset( $network[$value['interface']] );
450
            } else { // they are not equal. We will simply void out the existing one and it will treat the report entry as a new entry.
451
               // print "\tit is an update, so setting to removed in database\n";
452
               queryDatabaseExtended( "update network set removed_date = '$this->reportDateSQL' where device_id = $this->machineID and removed_date is null and interface = '" . $value['interface'] . "'");
453
               // print "update network set removed_date = '$this->reportDateSQL' where device_id = $this->machineID and removed_date is null and interface = '" . $value['interface'] . "'\n";
454
            } // if..else
455
         } // foreach
456
      }
457
      // finally, we should have only new entries, or entries which have changed and have been voided
458
      // a new row should be created for anything left.
459
      foreach ( $network as $interface => $values ) {
460
         //var_dump( $values );
461
         $sql = implode ( ',', array( 
462
                              $this->machineID,
463
                              makeSafeSQLValue($this->reportDateSQL),
464
                              makeSafeSQLValue($interface),
465
                              makeSafeSQLValue($values['address']),
466
                              makeSafeSQLValue($values['netmask']),
467
                              makeSafeSQLValue($values['ip6address']),
468
                              makeSafeSQLValue($values['ip6networkbits']),
469
                              makeSafeSQLValue($values['mtu']),
470
                              makeSafeSQLValue($values['mac'])
471
                              )
472
                         );
473
         $sql = "insert into network (device_id,added_date,interface,address,netmask,ip6,ip6net,mtu,mac) values (" . $sql . ")";
474
         //$this->messages[] = $sql;
475
         queryDatabaseExtended( $sql );
476
         $this->messages[] = "Network Device $interface was added/modified";
477
      }
478
   } // doIPAddresses
479
 
480
 
481
   /*
482
    * function looks through every element of $report, for a subkey
483
    * named 'keyfield'. It turns those into a string appropriate for
484
    * inclusion in a query as 
485
    * where fieldname in ( $keylist )
486
    * For example, with values a, b, c, 54, q'
487
    * returns 'a','b','c','54','q\''
488
    * where $keylist is generated by this function
489
    * returns null if no values found
490
    */ 
491
   function getKeyList ( $report ) {
492
      $keys = array();
493
      $keyset = null;
494
      // every row should have a key 'keyfield'.Since we don't know 
495
      // what they are, be sure to make sure they are SQL Safe
496
      foreach ( $report as $key => $value ) {
497
         $keys[] = makeSafeSQLValue( $value['keyfield'] );
498
      }
499
      return $keys ? implode( ",\n", $keys ) : null;
500
   }
501
 
502
   /*
503
    * function checks all keys in $first and $second to see if they
504
    * match. If they do, returns true, else returns false
505
    * ignores any keys passed in as array $ignore
506
    */
507
   function rowMatches ( $first, $second, $ignore = array() ) {
508
      foreach ( $first as $fkey => $fValue ) {
509
         if ( in_array( $fkey, $ignore ) )
510
            continue;
511
         if ( $fValue == $second[$fkey] )
512
            continue;
513
         return false;
514
      }
515
      return true;
516
   } // rowMatches
517
 
518
   /* Generic function that will 
519
    * delete items in database not found in report
520
    * Clean up report to contain only those items which are to be added
521
    * Return a copy of the report.
522
    * 
523
    * NOTE: since we are deleting items not found in the report, we will then
524
    * subsequently add them. This is the update process since we never actually
525
    * "delete" anything from the database.
526
    * 
527
    * Requires the report, and report must have a key for each entry named
528
    * 'keyfield' which the query below may use (as special tag <keys>
529
    * Also uses the following queries. If a query does not exist, that
530
    * process is ignored
531
    * 'remove old' - should have an entry as <device_id> which will have the current
532
    *       machine ID placed in it
533
    * 'delete' - <ids> which will be replaced with a list of keys from the 
534
    *         report.
535
    * 'find' - should return a column named 'id' which is a primary key on the
536
    *          table (used by delete query), and a column named 'keyfield'
537
    *          which matches the same one in the report. Any other fields in
538
    *          the query are checked against the report, and the report and
539
    *          database entry are only considered matching if all query fields
540
    *          match a report entry.
541
    * 
542
    * Before passing in report, you should go through the entries and create
543
    * a new one called 'keyfield' for each entry which will be a unique key
544
    * for an entry. For example, software packages may be the package name
545
    * and the version.
546
    */
547
   private function AddDeleteUpdate ( $report, $queries, $reportLabel, $testing=false ) {
548
 
549
      if ( $testing ) {
550
         print "*****************Starting\n"; 
551
         print_r( $report ); 
552
         print "*****************Starting\n";
553
      }
554
      $database = null;
555
      $keySet = $this->getKeyList( $report );
556
      if ( is_null( $keySet ) ) // bail if we don't have any data
557
         return null;
558
 
559
      // find values which are in database but not in report and delete them from database
560
      if ( $queries['remove old'] ) {
561
         $queries['remove old'] = str_replace( '<keys>', $keySet, $queries['remove old'] );
562
         $queries['remove old'] = str_replace( '<device_id>', $this->machineID, $queries['remove old'] );
563
         if ( $testing )
564
            print "Remove Query***************************\n" . $queries['remove old'] . "\n***********************************\n";
565
         else {
566
            $database = queryDatabaseExtended( $queries['remove old'] );
567
            if ( $database and $database['affected_rows'] ) 
568
               $this->messages[] = $database['affected_rows'] . " $reportLabel removed from database because they were removed for superceded";
569
         } // else
570
      } // if
571
 
572
      // now, let's get all the values left in the database
573
      if ( $queries['find'] ) {
574
         $queries['find'] = str_replace( '<keys>', $keySet, $queries['find'] );
575
         $queries['find'] = str_replace( '<device_id>', $this->machineID, $queries['find'] );
576
         if ( $testing )
577
            print "Find Query***************************\n" . $queries['find'] . "\n***********************************\n";
578
         else
579
            $database = queryDatabaseExtended( $queries['find'] );
580
      }
581
      if ( $database ) { // there were entries in the database still
582
         $database = $database['data']; // get to the data
583
         $keysToDelete = array();
584
         // find values which are different in report, and delete the database entry
585
         foreach ( $database as $row => $entries ) {
586
            foreach ( $report as $key => $values ) {
587
               if ( $report[$key]['keyfield'] != $entries['keyfield'] )
588
                  continue;
589
               // if we made it here, our keyfields match, so we check
590
               // the contents of the other fields
591
               if ( $this->rowMatches( $entries, $values, array( 'keyfield', 'id' ) ) ) {
592
                  // they match, so remove it from the report
593
                  unset( $report[$key] );
594
               } else { // they are different in the report, so remove from database
595
                  $keysToDelete[] = $entries['id'];
596
               }
597
            } // inner foreach
598
         } // outer foreach
599
         if ( $keysToDelete and $queries['delete'] ) {
600
            $queries['delete'] = str_replace( '<ids>', implode( ',', $keysToDelete ), $queries['delete'] );
601
            if ( $testing )
602
               print "Delete Query***************************\n" . $queries['delete'] . "\n***********************************\n";
603
            else {
604
               $updates = queryDatabaseExtended( $queries['delete'] );
605
               $this->messages[] = $updates['affected_rows'] . " $reportLabel modified";
606
            }
607
         }
608
      } // if $database
609
      // return items to be added
610
      if ( $testing ) {
611
         print "*****************Ending\n"; 
612
         print_r( $report ); 
613
         print "*****************Ending\n";
614
      }
615
      return $report;
616
   } // function AddDeleteUpdate
617
 
618
 
619
   /*
620
    * routine to ensure the hardware returned as PCI hardware is in the attributes area
621
    */ 
622
   private function processPCI ( ) {
623
      // following are some of the synonyms we will use from the PCI
624
      // tables for the "name" of the device. They are listed in the
625
      // order of priority (ie, first one found is chosen).
626
      $nameSynomyms = array('name','device','sdevice','device0');
627
 
628
      // query which removes devices from database that are not in report
629
      // we assume if the report doesn't have it, it has been removed
630
      $queries['remove old'] = 
631
                    "update device
632
                        set removed_date = '$this->reportDateSQL'
633
                        where device_id in (
634
                           select device_id from (
635
                              select
636
                                    device_id
637
                                 from
638
                                    device join device_type using (device_type_id)
639
                                 where
640
                                    device.part_of = <device_id>
641
                                    and device.removed_date is null
642
                                    and device.name not in ( <keys> )
643
                              ) as b
644
                              )";
645
 
646
 
647
      $queries['find'] = 
648
                    "select
649
                        device_id id,
650
                        device.name  'keyfield',
651
                        device.notes 'info'
652
                     from device join device_type using (device_type_id) 
653
                     where
654
                        device_type.name = 'PCI Card' 
655
                        and device.removed_date is null
656
                        and device.part_of = <device_id>";
657
 
658
      $queries['delete'] =
659
                    "update device
660
                        set removed_date = '$this->reportDateSQL'
661
                        where device_id in ( <ids> )";
662
 
663
 
664
      if ( ! isset( $this->report['pci'] ) ) return;
665
      // make a local copy so we can modify as needed
666
      $pciHash = $this->report['pci'];
667
      # normalize the data
668
      foreach ( array_keys( $pciHash ) as $key ) {
669
         if ( ! is_array( $pciHash[$key] ) ) {
670
            $this->messages[] = 'Invalid PCI format, not recording';
671
            return false;
672
         }
673
         if ( ! isset( $pciHash[$key]['slot'] ) ) { // doesn't have a slot field
674
            $slotField = '';
675
            foreach ( array_keys( $pciHash[$key] ) as $subkey ) { // scan through all keys and see if there is something with a "slot looking" value in it
676
               if ( preg_match ( '/^[0-9a-f:.]+$/' , $pciHash[$key][$subkey] ) )
677
                  $slotField = $subkey;
678
            }
679
            if ( $slotField ) {
680
               $pciHash[$key]['slot'] = $pciHash[$key][$slotField];
681
            } else {
682
               $pciHash[$key]['slot'] = 'Unknown';
683
            }
684
         }
685
         // Each entry must have a name. Use 'device' if it doesn't exist
686
         // Basically, we try each option in turn, with the first test having
687
         // priority.
688
 
689
         if ( ! isset( $pciHash[$key]['name'] ) ) {
690
            // if there is no name, try to use one of the synonyms
691
            foreach ( $nameSynomyms as $testName ) {
692
               if ( isset ( $pciHash[$key][$testName] ) ) {
693
                  $pciHash[$key]['name'] = $pciHash[$key][$testName];
694
                  break;
695
               } // if
696
            } // foreach
697
            if ( empty( $pciHash[$key]['name'] ) ) {
698
               $this->messages[] = "No name given for one or more PCI devices at normalize, Computer ID: [$this->machineID], Report Date: [$this->reportDate]";
699
               //print_r( $pciHash ); 
700
               //die;
701
               return;
702
            }
703
         } elseif ( $pciHash[$key]['name'] == $pciHash[$key]['slot'] ) {
704
            $pciHash[$key]['name'] = $pciHash[$key]['device'];
705
         } // if..else
706
         // Following is what will actually be put in the device table, ie device.name
707
         $pciHash[$key]['keyfield'] = $pciHash[$key]['slot'] . ' - ' . $pciHash[$key]['name'];
708
         ksort( $pciHash[$key] );
709
         $ignore = array( 'keyfield' );
710
         $info = '';
711
         foreach ( $pciHash[$key] as $subkey => $value ) {
712
            if ( in_array( $subkey, $ignore ) )
713
               continue;
714
            $info .= "[$subkey]=$value\n";
715
         }
716
         $pciHash[$key]['info'] = $info;
717
      } // foreach
718
      // at this point, we should have a slot and a name field in all pci devices
719
      $toAdd = $this->AddDeleteUpdate( $pciHash, $queries, 'PCI Devices' );
720
      if ( $toAdd ) {
721
         $pciCardID = getOneDBValue( "select device_type_id from device_type where name = 'PCI Card'");
722
         $count = 0;
723
         foreach ( $toAdd as $entry => $values ) {
724
            $keyfield = makeSafeSQLValue( $values['keyfield'] );
725
            $info = makeSafeSQLValue( $values['info'] );
726
            $query = "insert into device 
727
                        select 
728
                           null,
729
                           site_id,
730
                           $pciCardID,
731
                           $keyfield,
732
                           $info,
733
                           device_id,
734
                           '$this->reportDateSQL',
735
                           null,
736
                           null 
737
                        from device 
738
                        where device_id = $this->machineID";
739
            queryDatabaseExtended( $query );
740
            $count++;
741
         } // foreach
742
         $this->messages[] = "$count PCI devices added/modified";
743
      } // if
744
   } // processPCI
745
 
746
   function getSoftwareID ( $packageName,$versionInfo,$description ) {
747
      $packageName = makeSafeSQLValue( $packageName );
748
      $versionInfo = makeSafeSQLValue( $versionInfo );
749
      $description = makeSafeSQLValue( $description );
750
      // does the package exist?
751
      $packageID = getOneDBValue("select software_id from software where package_name = $packageName and removed_date is null");
752
      if ( !$packageID ) { # NO, package doesn't exist, so add it to the database
753
         $packageID = queryDatabaseExtended( "insert into software (package_name,description, added_date) values ($packageName,$description, '$this->reportDateSQL')");
754
         if ( $packageID['insert_id'] )
755
            $packageID = $packageID['insert_id'];
756
         else {
757
            $this->messages[] = "ERROR: Software Packages - Could not find version $packageName and could not add to database";
758
            $packageID = null;
759
         }
760
      }
761
      // does this version number exist?
762
      $versionID = getOneDBValue( "select software_version_id from software_version where version = $versionInfo and removed_date is null" );
763
      if ( ! $versionID ) { # nope, so add it
764
         $versionID = queryDatabaseExtended( "insert into software_version ( version,added_date ) values ($versionInfo,'$this->reportDateSQL')");
765
         if ( $versionID['insert_id'] )
766
            $versionID = $versionID['insert_id'];
767
         else {
768
            $this->messages[] = "ERROR: Software Packages - Could not find version $versionInfo and could not add to database";
769
            $versionID = null;
770
         }
771
      }
772
      return array( 'package id' => $packageID,'version id' => $versionID);
773
   } // getSoftwareID
774
 
775
 
776
   function processSoftwarePackages (  ) {
777
      // query which removes devices from database that are not in report
778
      // we assume if the report doesn't have it, it has been removed
779
      $queries['remove old'] = 
780
                    "update installed_packages
781
                        set removed_date = '$this->reportDateSQL'
782
                        where installed_packages_id in (
783
                           select installed_packages_id from (
784
                              select
785
                                    installed_packages_id
786
                              from 
787
                                 installed_packages 
788
                                 join software using ( software_id )
789
                                 join software_version using (software_version_id)
790
                              where
791
                                    installed_packages.device_id = <device_id>
792
                                    and installed_packages.removed_date is null
793
                                    and concat(software.package_name,software_version.version) not in ( <keys> )
794
                              ) as b
795
                              )";
796
      $queries['find'] = 
797
           "select 
798
               installed_packages.installed_packages_id 'id',
799
               concat(software.package_name,software_version.version) 'keyfield'
800
            from 
801
               installed_packages 
802
               join software using ( software_id )
803
               join software_version using (software_version_id)
804
            where 
805
               device_id = <device_id>
806
               and installed_packages.removed_date is null";
807
      $queries['delete'] =
808
                    "update installed_packages
809
                        set removed_date = '$this->reportDateSQL'
810
                        where installed_packages_id in ( <ids> )";
811
 
812
      if ( ! isset( $this->report['software'] ) ) return;
813
      // make a local copy so we can modify as needed
814
      $softwareHash = $this->report['software'];
815
      foreach ( array_keys( $softwareHash ) as $key ) {
816
         if ( ! isset( $softwareHash[$key]['name'] ) )
817
            $softwareHash[$key]['name'] = $key;
818
         if ( ! isset( $softwareHash[$key]['description'] ) )
819
            $softwareHash[$key]['description'] = '';
820
         // This is needed for matching
821
         $softwareHash[$key]['keyfield'] = $softwareHash[$key]['name'] . $softwareHash[$key]['version'];
822
      } // foreach
823
      // at this point, we should have a slot and a name field in all pci devices
824
      $toAdd = $this->AddDeleteUpdate( $softwareHash, $queries, 'Software Packages' );
825
      if ( $toAdd ) {
826
         $count = 0;
827
         foreach ( $toAdd as $key => $value ) {
828
            $ids = $this->getSoftwareID ( $value['name'],$value['version'],$value['description'] );
829
            if ( is_null( $ids['package id'] ) or is_null( $ids['version id'] ) ) {
830
               $this->messages[] = "Could not create entry for package " . $value['name'] . ", version " . $value['version'];
831
            } else {
832
               $insertValues = implode( ',', array( $this->machineID, $ids['package id'], $ids['version id'], "'$this->reportDateSQL'" ) );
833
               $query = "insert into installed_packages
834
                           (device_id,software_id,software_version_id,added_date) 
835
                           values 
836
                           ( $insertValues )";
837
               $database = queryDatabaseExtended( $query );
838
               if ( $database )
839
                  $count++;
840
            } // if..else
841
         } // foreach
842
         if ( $count )
843
            $this->messages[] = "$count software packages added/updated";
844
      }
845
   } // processSoftwarePackages
846
 
847
} // class sysinfo
848
 
849
 
850
 
5 rodolico 851
/*
852
 * we don't know where the configuration file will be, so we have a list
853
 * below (searchPaths) to look for it. It will load the first one it 
854
 * finds, then exit. THUS, you could have multiple configuration files
855
 * but only the first one in the list will be used
856
 */
3 rodolico 857
 
5 rodolico 858
$confFileName = "sysinfoRead.conf.yaml";
859
$searchPaths = array( '/etc/camp', '/opt/camp', '/opt/camp/sysinfo', $_SERVER['SCRIPT_FILENAME'], getcwd() );
860
$configuration = array();
861
foreach ( $searchPaths as $path ) {
862
   if ( file_exists( "$path/$confFileName" ) ) {
863
      #print "Found $path/$confFileName\n";
864
      $configuration = yaml_parse_file( "$path/$confFileName" );
865
      #require "$path/$confFileName";
866
      break; // exit out of the loop; we don't try to load it more than once
867
   } // if
868
} // foreach
3 rodolico 869
 
5 rodolico 870
mysql_connect( $configuration['database']['databaseServer'], $configuration['database']['databaseUsername'], $configuration['database']['databasePassword'] ) or die(mysql_error());
871
mysql_select_db( $configuration['database']['database'] ) or die(mysql_error());
872
 
873
 
874
$thisReport = new sysinfo( $configuration['bodyContents'] );
3 rodolico 875
$result; 
876
 
877
$result = $thisReport->loadFromFile( $argc > 1 ? $argv[1] : "php://stdin" );
878
if ( $result == 'ini' ) {
879
   print $thisReport->dumpMessages();
880
   exit(0);
881
}
882
if ( ! $result )
883
   exit ( 1 );
884
$result = $thisReport->processReport();
885
print $thisReport->dumpMessages();
886
//var_dump( $thisReport );
887
exit ( $result );
888
 
889
?>