Subversion Repositories computer_asset_manager_v1

Rev

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