Subversion Repositories computer_asset_manager_v1

Rev

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