Subversion Repositories camp_sysinfo_client_3

Rev

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

Rev Author Line No. Line
16 rodolico 1
#!/usr/bin/env perl
20 rodolico 2
use warnings;
26 rodolico 3
use strict;  
2 rodolico 4
 
5
# sysinfo
6
# Author: R. W. Rodolico
7
# Primary client portion of sysinfo system. Will collect information about its current
8
# host and create a report containing the information. This report can then be processed
9
# by process_sysinfo.pl on the collection computer.
112 rodolico 10
# output file consists of a YAML file of the form:
2 rodolico 11
#  <sysinfo3.0.0>
12
#    <diskinfo name='/dev/xvda3'>
13
#      <fstype>ext3</fstype>
14
#      <mount>/home</mount>
15
#      <size>51606140</size>
16
#      <used>331472</used>
17
#    </diskinfo>
18
#    <network name='eth0'>
19
#      <address>192.168.1.3</address>
20
#      <ip6address>fe80::216:3eff:fefb:4e10</ip6address>
21
#      <ip6networkbits>64</ip6networkbits>
22
#      <mac>00:16:3e:fb:4e:10</mac>
23
#      <mtu>1500</mtu>
24
#      <netmask>255.255.255.0</netmask>
25
#    </network>
26
#    <operatingsystem>
27
#      <codename>squeeze</codename>
28
#      <description>Debian GNU/Linux 6.0.4 (squeeze)</description>
29
#      <distribution>Debian</distribution>
30
#      <kernel>2.6.32-5-xen-686</kernel>
31
#      <os_name>Linux</os_name>
32
#      <os_version>Linux version 2.6.32-5-xen-686 (Debian 2.6.32-41) (ben@decadent.org.uk) (gcc version 4.3.5 (Debian 4.3.5-4) ) #1 SMP Mon Jan 16 19:46:09 UTC 2012</os_version>
33
#      <release>6.0.4</release>
34
#    </operatingsystem>
35
#    <pci name='0000:00:00.0'>
36
#      <class>RAM memory</class>
37
#      <device>MCP55 Memory Controller</device>
38
#      <rev>a2</rev>
39
#      <sdevice>Device cb84</sdevice>
40
#      <slot>0000:00:00.0</slot>
41
#      <svendor>nVidia Corporation</svendor>
42
#      <vendor>nVidia Corporation</vendor>
43
#    </pci>
44
#    <report>
45
#      <client>Staffmasters</client>
46
#      <date>2012-05-01 03:00</date>
47
#      <version>2.0.0</version>
48
#    </report>
49
#    <software name='aptitude'>
50
#      <description>terminal-based package manager (terminal interface only)</description>
51
#      <version>0.6.3-3.2+squeeze1</version>
52
#    </software>
53
#    <system>
54
#      <cpu_speed>1800.103</cpu_speed>
55
#      <cpu_sub>i686</cpu_sub>
56
#      <cpu_type>GenuineIntel</cpu_type>
57
#      <hostname>backup.staffmasters.local</hostname>
58
#      <last_boot>1333259809</last_boot>
59
#      <memory>520852</memory>
60
#      <num_cpu>1</num_cpu>
61
#    </system>
62
#  </sysinfo3.0.0>
63
 
64
 
65
#
66
# Version 1.3 20071104
67
# added capability of e-mailing the results by itself and external configuration file
68
 
69
# Version 1.3.1 20071110
70
# added du -sk to explicitly do directory sizes in 'k'. Also, fixed some documentation
71
 
72
# Version 1.3.3 20081104
73
# modified hostname to hostname -f, and allowed user to place custom value in configuration file
74
# also, modified to go with Debian standards in preparation to creating a debian package.
75
 
76
# Version 2.0 20081208
77
# Modified to use different libraries for different OS's in preparation to porting to Windows
78
# Uses different packages based on which OS it is on.
79
 
80
# Version 3.0 20120923
81
# Major revision. Most internal intelligence pulled out and put into modules and data transfer format has been changed to YAML
82
#
83
# Base system only pulls client name, machine name and machine number, all of which can be set in the configuration file
84
# if the value is not set, it attempts various means to determine the values and, if it fails, aborts with an error message
85
#    client name -- REQUIRED, must come from configuration file
86
#    machine name --  REQUIRED, if not set via conf file, attempts hostname command (hostname -f) or module getHostName
87
#    machine number -- REQUIRED, if not set via conf file, attempts "echo `hostname -f`-clientname | md5sum" or module getSerial
88
# modules are stored in "configuration directory/modules" (/etc/sysinfo/modules on most Linux systems) and are processed in 
89
# standard sort order (case sensitive). 
90
# Module filenames may contain alpha-numeric, underscore and the period only (files containing other characters are ignored).
91
# Modules should set their exit code to 0 for success, and non-zero for failure
92
# Modules should return 0 or more tab delimited, newline terminated strings, processed as one record per line
93
# A module return string line is processed as follows:
94
#     category \t [category \t ...] \t key \t value
95
# example:
96
#    System \t num_cpu \t 1
97
#    System \t Users \t root \t /root/
98
# (note, if non-zero exit code returned, return value is assumed to be error message and is printed to STDERR) 
99
# sysinfo stores the result in a hash, using categories as the keys (case sensitive), thus, the above results in
100
# $store{'System'}{'num_cpu'} = '1';
101
# $store{'System'}{'Users'}{'root'} = '/root';
102
# upon completion, sysinfo converts the $store hash into an XML or YAML string for transfer
103
# It then sends it to the main server as defined in the conf file.
104
# NOTE: YAML is hand crafted to kill any requirements for external libraries
105
# see sub hashToYAML for details
106
 
9 rodolico 107
# Version 3.0.1 20160321
108
# Renamed to sysinfo-client to not conflict with Linux package sysinfo
109
# created installer in Perl to not rely on package managers
110
# default path for configuration file changed to /etc/camp/sysinfo-client.conf
111
# $VERSION changed to $DATA_VERSION to not conflict with $main::VERSION (script version vs data format version)
13 rodolico 112
#
113
# Version 3.1.0 20160401
114
# module and script dirs now arrays to be searched. Idea is that default
115
#    modules/scripts are in installdir/modules or installdir/scripts, and
116
#    user supplied are in /etc/scripts and /etc/modules
14 rodolico 117
# Tightened up the file systems checks, requiring all scripts and modules
118
#    be set 0700 at least, and owned by root
18 rodolico 119
# Transport layers now an array, and if one fails to send the report, the others
120
#    are tried in turn
14 rodolico 121
# Worked on logic for sendReport to give better error checking.
122
# Doing a search for the configuration file matching cwd, then /etc/camp, then /usr/local/etc/camp
21 rodolico 123
# Self documenting, ie a key for software\tsysinfo-client\version\current version is inserted
28 rodolico 124
#
125
# Version 3.1.1 20160915 RWR
126
# set use strict and use warnings, then fixed errors
37 rodolico 127
#
128
# Version 3.1.2 20160922 RWR
129
# $exitCode 1 (not applicable to this machine) does not throw warning
130
#
42 rodolico 131
# Version 3.1.3 20161010 RWR
132
# Removed extra use warnings
47 rodolico 133
#
134
# Version 3.1.4 20161023 RWR
135
# Would error out if moduledir does not exist, added a return
51 rodolico 136
#
137
# Version 3.1.5 20170327 RWR
138
# On freeBSD systems, was looking in wrong place for configuration file
59 rodolico 139
#
140
# Version 3.2.0 20180320 RWR
62 rodolico 141
# Major change in the configuration file format; All entries are loaded into 
142
# hash %configuration, so clientname is no longer $clientname, but is now
143
# $configuration{'clientname'}
144
# NOT backwards compatible
59 rodolico 145
# changed configuration to be loaded into hash (vs directly loaded into variables)
146
# added UUID to configuration file
62 rodolico 147
#
148
# Version 3.2.1 20180424 RWR
149
# Finally got a semi-stable version of this running. Fixed a bunch of bugs
150
# and appears to be working correctly.
112 rodolico 151
#
152
# Version 3.3.0 20190419 RWR
153
# Converted to use YAML config file
135 rodolico 154
#
155
# Version 3.4.0 20191111 RWR
156
# adding logging with priority. logging is a hash inside of %cvonfiguration which contains the following
157
# $configuration{ 'logging' } = {
158
#    'log type'  => 'string',
159
#    'log level' => #,
160
#    'other params' => something,
161
# };
138 rodolico 162
#
163
# The default log type is cache, which builds an array of all messages passed. When the log type is changed, the cache is
164
# checked for values and, if they exist, they are dumped to the log, then removed.
165
#
135 rodolico 166
# Currently, the only log type is 'file', which has one other additional parameter, 'log path' which
167
# points to the actual log to be created. The log is NOT limited in size, so use something else to
168
# do that.
169
# log level is an integer which is compared the a priority passed to the logging function. The
170
# higher log level is set, the more verbose the log.
171
# 0 - Normal, basically logs when the program starts and ends, and any warnings.
172
# 1 - a little more information about flow
173
# 2 - Gives ending information on structures
174
# 3 - Gives a lot of info about structures when they are initialized and at the end
175
# 4 - Crazy. Dumps just about every structure every time they are changed
176
#
177
# $TESTING has been set to a binary. If true, the report is not sent via the transports, but is dumped to /tmp/sysinfo.testing.yaml
144 rodolico 178
#
179
# Version 3.4.1 20191117 RWR
180
# Added syslog as a possible option for logging.
181
#
135 rodolico 182
 
9 rodolico 183
 
112 rodolico 184
# find our location and use it for searching for libraries
185
BEGIN {
186
   use FindBin;
187
   use File::Spec;
188
   use lib File::Spec->catdir($FindBin::Bin);
189
}
62 rodolico 190
 
112 rodolico 191
use YAML::Tiny;
138 rodolico 192
use Data::Dumper;
112 rodolico 193
 
2 rodolico 194
# Following are global variables overridden if configuration file exists
195
 
135 rodolico 196
my $TESTING = 0; # if set to 1, will do everything, but will dump output to /tmp/sysinfo.testing.yaml
9 rodolico 197
 
144 rodolico 198
our $VERSION = '3.4.1';
135 rodolico 199
 
2 rodolico 200
my $indentLevel = 2; # number of spaces to indent per level in XML or YAML
201
 
13 rodolico 202
# paths to search for configuration file
51 rodolico 203
my @confFileSearchPath = ( '.', '/etc/camp/sysinfo-client', '/etc/camp', '/usr/local/etc/camp/sysinfo-client' );
2 rodolico 204
 
112 rodolico 205
my $configurationFile = 'sysinfo-client.yaml'; # name of the configuration file
2 rodolico 206
 
135 rodolico 207
my $reportDate = &timeStamp(); # set report date
2 rodolico 208
 
59 rodolico 209
my %configuration = (
142 rodolico 210
   'logging' => { 'log type' => 'cache', 'log level' => 0 },    # if set, will point to logging
61 rodolico 211
   'moduleDirs' => [], # search paths for modules
212
   'scriptDirs' => [], # search paths for scripts
59 rodolico 213
   'clientName' => '',  # Required!! Must be set in conf file (no defaults)
214
   'serialNumber' => '', # serial number of machine
215
   'UUID'         => '', # UUID of machine
61 rodolico 216
   'transports'   => [], # hash with various transports
217
   'hostname' => &getHostName() # fully qualified host name of machine
59 rodolico 218
)
219
;
2 rodolico 220
 
9 rodolico 221
my $DATA_VERSION = '3.0.0'; # used in sending the data file. sets version of XML/YAML data file
2 rodolico 222
 
13 rodolico 223
 
136 rodolico 224
#######################################################
225
#
226
# timeStamp
227
#
228
# return current system date as YYYY-MM-DD HH:MM:SS
229
#
230
#######################################################
231
sub timeStamp {
232
   my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst)=localtime(time);
142 rodolico 233
   return sprintf "%4d-%02d-%02d %02d:%02d:%02d",$year+1900,$mon+1,$mday,$hour,$min,$sec;
136 rodolico 234
}
235
 
138 rodolico 236
#######################################################
237
# function to simply log things
135 rodolico 238
# first parameter is the priority, if <= $logDef->{'log level'} will print
239
# all subsequent parameters assumed to be strings to sent to the log
240
# returns 0 on failure
241
#         1 on success
242
#         2 if priority > log level
243
#        -1 if $logDef is unset
244
# currently, only logs to a file
138 rodolico 245
#######################################################
135 rodolico 246
sub logIt {
247
   my $priority = shift;
13 rodolico 248
 
135 rodolico 249
   return -1 unless exists $configuration{'logging'};
250
   return 2 unless $priority <= $configuration{'logging'}{'log level'};
138 rodolico 251
   if ( $configuration{'logging'}{'log type'} eq 'cache' ) {
252
      push @{ $configuration{'logging'}{'cache'} }, @_;
253
      return;
254
   } elsif ( defined( $configuration{'logging'}{'cache'} ) ) {
255
      unshift @_, @{ $configuration{'logging'}{'cache'} };
256
      delete $configuration{'logging'}{'cache'};
257
   }
135 rodolico 258
   if ( $configuration{'logging'}{'log type'} eq 'file' ) {
259
      if ( open LOG, '>>' . $configuration{'logging'}{'log path'} ) {
260
         while ( my $t = shift ) {
261
            print LOG &timeStamp() . "\t$t\n";
262
         }
263
         close LOG;
264
      }
144 rodolico 265
   } elsif ( $configuration{'logging'}{'log type'} eq 'syslog' ) {
266
      use Sys::Syslog;                        # all except setlogsock()
267
      use Sys::Syslog qw(:standard :macros);  # standard functions & macros
268
 
269
      my $syslogName = 'sysinfo-client';
270
      my $logopt = 'nofatal';
271
      my $facility = 'LOG_LOCAL0';
272
      my $priority = 'LOG_NOTICE';
273
 
274
      openlog( $syslogName, $logopt, $facility);
275
      syslog($priority, '%s', @_ );
276
      closelog();
135 rodolico 277
   } else {
278
      warn "Log type $configuration{'logging'} incorrectly configured\n";
279
      return 0;
280
   }
281
   return 1;
282
}
283
 
284
 
2 rodolico 285
#######################################################
286
#
13 rodolico 287
# findFile( $filename, @directories )
288
#
289
# Locates a file by searching sequentially in one or more
290
# directories, returning the first one found
291
# 
292
# Returns '' if not found
293
#
294
#######################################################
295
 
296
sub findFile {
59 rodolico 297
   my ( $filename, $directories ) = @_;
138 rodolico 298
   &logIt( 3, "Looking for $filename in findFile" );
59 rodolico 299
   for ( my $i = 0; $i < scalar( @{$directories} ); $i++ ) {
300
      my $confFile = $$directories[$i] . '/' . $filename;
138 rodolico 301
      &logIt( 4, "Looking for $filename in $confFile" );
13 rodolico 302
      return $confFile if ( -f $confFile );
303
   }
304
   return '';
305
}
306
 
307
 
308
#######################################################
309
#
2 rodolico 310
# loadConfigurationFile($confFile)
311
#
312
# Loads configuration file defined by $configurationFile, and dies if not available
112 rodolico 313
# This is a YAML file containing serialized contents of 
2 rodolico 314
# Parameters: configuration file fully path/file name
315
# NOTE: conf file must be a valid Perl file
316
#
317
#######################################################
318
 
135 rodolico 319
sub loadConfigurationFile {   
14 rodolico 320
   my ( $fileName, @searchPath ) = @_;
141 rodolico 321
   &logIt( 2, "Looking for config file $fileName in " . join( ', ', @searchPath ) );
13 rodolico 322
   my $confFile;
59 rodolico 323
   if ( $confFile = &findFile( $fileName, \@searchPath ) ) {
138 rodolico 324
      &logIt( 3, "Opening configuration from $confFile" );
112 rodolico 325
      my $yaml = YAML::Tiny->read( $confFile );
138 rodolico 326
      &logIt( 4, "Configuration file contents\n$yaml" );
112 rodolico 327
      return $yaml->[0];
13 rodolico 328
   }
14 rodolico 329
   die "Can not find $fileName in any of " . join( "\n\t", @searchPath ) . "\n";
2 rodolico 330
}
331
 
332
#######################################################
333
#
334
# sendResults( $parameters, $message, $scriptDirectory )
335
#
336
# Sends results of run to server using external script. If external
337
# script not defined, just print to STDOUT
338
#
339
# Parameters
340
#  $parameters - a hash containing the information necessary to make the transfer
341
#  $message - the message to be sent
342
#  $scriptDirectory - path (not filename) of script to be executed
343
# 
344
# $parameters contains different key/value pairs depending on the script used
345
#             for example, a stand-alone SMTP script may need a username/password,
346
#             smtp server name, port number, from and to address
347
#             while an http transfer may only need a script name
348
#             See the individual scripts to determine what parameters need to be
349
#             filled in.
350
#             The only required parameter is 'sendScript' which must contain the
351
#             name of the script to execute (and it must be located in $scriptDirectory)
352
# SCRIPT must contain one sub named doit, that accepts three parameters, the hash, 
353
#       the message, and, optionally, the script directory
354
#
355
# If script not defined, just dump to STDOUT. With a properly set up cron job, the output
356
# would then be sent via e-mail to an administrative account, possibly root
357
#
358
#######################################################
359
sub sendResults {
62 rodolico 360
   my ( $globals, $transports, $message, $scriptDirectory ) = @_;
135 rodolico 361
   &logIt( 3, "Entering sendResults" );
113 rodolico 362
   foreach my $key ( sort { $a <=> $b } %$transports ) {
363
      if ( $transports->{$key}->{'sendScript'} ) {
135 rodolico 364
         &logIt( 3, "Trying to find file " . $transports->{$key}->{'sendScript'} . " in " . join( "\n\t", @{$scriptDirectory} ) );
113 rodolico 365
         my $sendScript = &findFile( $transports->{$key}->{'sendScript'}, $scriptDirectory );
19 rodolico 366
         if ( $sendScript ) {
18 rodolico 367
            # load the chosen script into memory
368
            require $sendScript;
19 rodolico 369
            # merge the globals in
370
            while ( my ( $gkey, $value ) = each %$globals ) { 
113 rodolico 371
               $transports->{$key}->{$gkey} = $value; 
19 rodolico 372
            }
20 rodolico 373
            # do variable substitution for any values which need it
113 rodolico 374
            foreach my $thisOne ( keys %{$transports->{$key}} ) {
135 rodolico 375
               &logIt( 4, "$thisOne" );
113 rodolico 376
               if ( $transports->{$key}->{$thisOne} =~ m/(\$configuration\{'hostname'\})|(\$reportDate)|(\$configuration\{'clientName'\})|(\$configuration\{'serialNumber'\})/ ) {
377
                  $transports->{$key}->{$thisOne} = eval "\"$transports->{$key}->{$thisOne}\"";
20 rodolico 378
               }
379
            }
380
 
62 rodolico 381
            #%$transports{$key}{keys %$globals} = values %$globals;
382
            #print Dumper( $$transports[$key] );
20 rodolico 383
            #next;
18 rodolico 384
            # execute the "doit" sub from that script
135 rodolico 385
            &logIt( 3, $message );
386
            my $return = &doit( $transports->{$key}, $message );
387
            return $return if ( $return == 1 );
18 rodolico 388
         } else {
135 rodolico 389
            &logIt( 0,"Could not find " . $$transports[$key]{'sendScript'} . ", trying next transport" );
18 rodolico 390
         } # if..else
391
      } # if
392
   } # foreach
393
   # if we made it here, we have not sent the report, so just return it to the user
85 rodolico 394
   # if called from a cron job, it will (hopefully) be sent to root
135 rodolico 395
   &logIt( 0, 'Error, reached ' . __LINE__ . " which should not happen, message was\n$message" );
85 rodolico 396
   print $message;
16 rodolico 397
   return 1;
2 rodolico 398
}
399
 
400
#######################################################
401
#
402
# getHostName
403
#
404
# return hostname from hostname -f
405
#
406
#######################################################
407
sub getHostName {
135 rodolico 408
   &logIt( 3, "Entering getHostName" );
28 rodolico 409
   my $hostname = `hostname -f`;
2 rodolico 410
   chomp $hostname;
411
   return $hostname;
412
}
413
 
414
#######################################################
415
#
18 rodolico 416
# escapeForYAML
2 rodolico 417
#
18 rodolico 418
# Escapes values put into YAML report
2 rodolico 419
#
112 rodolico 420
# DEPRECATED AS OF VERSION 3.3.0
421
# uses YAML::Tiny
422
#
2 rodolico 423
#######################################################
112 rodolico 424
#sub escapeForYAML {
425
#   my $value = shift;
426
#   $value =~ s/'/\\'/gi; # escape single quotes
427
#   $value =~ s/"/\\"/gi; # escape double quotes
428
#   # pound sign indicates start of a comment and thus loses part
429
#   # of strings. Surrounding it by double quotes in next statement
430
#   # allows 
431
#   $value = '"' . $value . '"' if ( $value =~ m/[#:]/ );
432
#   return $value;
433
#}
2 rodolico 434
 
435
#######################################################
436
#
437
# hashToYAML( $hashRef, $indent )
438
#
439
# Converts a hash to a YAML string
440
#
441
# NOTE: This routine recursively calls itself for every level
442
#       in the hash
443
#
444
# Parameters
445
#     $hashref - reference (address) of a hash
446
#     $indent  - current indent level, defaults to 0
447
#
448
# Even though there are some very good libraries that do this
449
# I chose to hand-code it so sysinfo can be run with no libraries
450
# loaded. I chose to NOT do a full implementation, so special chars
451
# that would normally be escaped are not in here. 
452
# However, I followed all the RFC for the values that were given, so
453
# assume any YAML reader can parse this
454
# NOTE: YAML appears to give a resulting file 1/3 smaller than the above
455
#       XML, and compresses down in like manner
456
#
112 rodolico 457
# DEPRECATED AS OF VERSION 3.3.0
458
# uses YAML::Tiny
459
#
2 rodolico 460
#######################################################
112 rodolico 461
#sub hashToYAML {
462
#   my ($hashRef, $indent) = @_;
463
#   $indent = 0 unless $indent; # default to 0 if not defined
464
#   
465
#   my $output; # where the output is stored
466
#   foreach my $key ( keys %$hashRef ) { # for each key in the current reference
467
#      print "Looking at $key\n" if $TESTING > 3;
468
#      # see http://www.perlmonks.org/?node_id=175651 for isa function
469
#      if ( UNIVERSAL::isa( $$hashRef{$key}, 'HASH' ) ) { # is the value another hash?
470
#            # NOTE: unlike xml, indentation is NOT optional in YAML, so the following line verifies $indentlevel is non-zero
471
#            #       and, if it is, uses a default 3 character indentation
472
#            $output .= (' ' x $indent ) . &escapeForYAML($key) . ":\n" . # key, plus colon, plus newline
473
#                    &hashToYAML( $$hashRef{$key}, $indent+($indentLevel ? $indentLevel : 3) ) . # add results of recursive call
474
#                    "\n";
475
#      } elsif ( UNIVERSAL::isa( $$hashRef{$key}, 'ARRAY' ) ) { # is it an array? ignore it
476
#      } else { # it is a scalar, so just do <key>value</key>
477
#         $output .= (' ' x $indent ) . &escapeForYAML($key) . ': ' . &escapeForYAML($$hashRef{$key}) . "\n";
478
#      }
479
#   }
480
#   return $output;
481
#}
2 rodolico 482
 
483
 
484
#######################################################
485
#
486
# tabDelimitedToHash ($hashRef, $tabdelim)
487
#
488
# Takes a tab delimited multi line string and adds it
489
# to a hash. The final field in each line is considered to
490
# be the value, and all prior fields are considered to be
491
# hierachial keys.
492
#
493
# Parameters
494
#     $hashref - reference (address) of a hash
495
#     $tabdelim - A tab delimited, newline terminated set of records
496
#
497
#
498
#######################################################
499
sub tabDelimitedToHash {
500
   my ($hashRef, $tabdelim) = @_;
135 rodolico 501
   &logIt( 3, "Entering tabDelimitedToHash" );
2 rodolico 502
   foreach my $line ( split( "\n", $tabdelim ) ) { # split on newlines, then process each line in turn
503
      $line =~ s/'/\\'/gi; # escape single quotes
28 rodolico 504
      my @fields = split( / *\t */, $line ); # get all the field values into array
2 rodolico 505
      my $theValue = pop @fields; # the last one is the value, so save it
506
      # now, we build a Perl statement that would create the assignment. The goal is
507
      # to have a string that says something like $$hashRef{'key'}{'key'} = $value;
508
      # then, eval that.
509
      my $command = '$$hashRef'; # start with the name of the dereferenced hash (parameter 1)
510
      while (my $key = shift @fields) { # while we have a key, from left to right
511
         $command .= '{' . "'$key'" . '}'; # build it as {'key'} concated to string
512
      }
513
      $command .= "='$theValue';"; # add the assignment
514
      #print STDERR "$command\n"; 
515
      eval $command; # eval the string to make the actual assignment
516
   }
517
}
518
 
519
#######################################################
520
#
13 rodolico 521
# validatePermission ( $file )
522
#
523
# Checks that file is owned by root, and has permission
524
# 0700 or less
525
# 
526
# Returns empty string on success, error message
527
# on failure
528
#
529
#######################################################
530
 
531
sub validatePermission {
532
   my $file = shift;
135 rodolico 533
   &logIt( 3, "Entering validatePermission with $file" );
14 rodolico 534
   my $return;
13 rodolico 535
   # must be owned by root
28 rodolico 536
   my $owner = (stat($file))[4];
13 rodolico 537
   $return .= " - Bad Owner [$owner]" if $owner;
538
   # must not have any permissions for group or world
539
   # ie, 0700 or less
28 rodolico 540
   my $mode = (stat($file))[2];
13 rodolico 541
   $mode = sprintf( '%04o', $mode & 07777 );
542
   $return .= " - Bad Permission [$mode]" unless $mode =~ m/0.00/;
543
   return $return ? $file . $return : '';
544
}
545
 
546
#######################################################
547
#
2 rodolico 548
# ProcessModules ( $system, $moduleDir )
549
#
550
# Processes all modules in $moduleDir, adding result to $system hash
551
# 
552
# Parameters
553
#     $system - reference (address) of a hash
554
#     $moduleDir - full path to a directory containing executable scripts
555
#  
556
# Each file in the $moduleDir directory that matches the regex in the grep
557
# and is executable is run. It is assumed the script will return 0 on success
558
# or a non-zero on failure
559
# The output of the script is assumed to be a tab delimited, newline separated
560
# list of records that should be added to the hash $system. This is done by calling 
561
# &parseModule above.
562
# on failure, the returned output of the script is assumed to be an error message
563
# and is displayed on STDERR
564
#######################################################
565
sub ProcessModules {
566
   my ( $system, $moduleDir ) = @_;
135 rodolico 567
   &logIt( 3, "Entering processModules" );
2 rodolico 568
   # open the module directory
47 rodolico 569
   return unless -d $moduleDir;
2 rodolico 570
   opendir( my $dh, $moduleDir ) || die "Module Directory $moduleDir can not be opened: $!\n";
571
   # and get all files which are executable and contain nothing but alpha-numerics and underscores (must begin with alpha-numeric)
572
   my @modules = grep { /^[a-zA-Z0-9][a-zA-Z0-9_]+$/ && -x "$moduleDir/$_" } readdir( $dh );
573
   closedir $dh;
28 rodolico 574
   foreach my $modFile ( sort @modules ) { # for each valid script
14 rodolico 575
      if ( my $error = &validatePermission( "$moduleDir$modFile" ) ) {
13 rodolico 576
         print STDERR "Not Processed: $error\n";
577
         next;
578
      }
135 rodolico 579
      &logIt( 3, "Processing module $moduleDir$modFile");
2 rodolico 580
      my $output = qx/$moduleDir$modFile $moduleDir/; # execute it and grab the output
581
      my $exitCode = $? >> 8; # process the exitCode
37 rodolico 582
      # exitCode 0 - processed normally
583
      # exitCode 1 - not applicable to this machine
584
      if ( $exitCode && $exitCode > 1) { # if non-zero, error, so show an error message
2 rodolico 585
         warn "Error in $moduleDir$modFile, [$output]\n";
135 rodolico 586
         &logIt( 0, "Error in $moduleDir$modFile, [$output]" );
2 rodolico 587
      } else { # otherwise, call tabDelimitedToHash to save the data
588
         &tabDelimitedToHash( $system, $output );
21 rodolico 589
      } # if
590
   } # foreach
591
   # add sysinfo-client (me) to the software list, since we're obviously installed
592
   &tabDelimitedToHash( $system, "software\tsysinfo-client\tversion\t$main::VERSION\n" );
2 rodolico 593
}
594
 
20 rodolico 595
sub processParameters {
596
   while ( my $parameter = shift ) {
597
      if ( $parameter eq '-v' ) {
598
         print "$main::VERSION\n";
599
         exit;
153 rodolico 600
      } elsif ( $parameter eq 'firstrun' ) {
601
         `touch /tmp/sysinfo.firstrun`;
20 rodolico 602
      }
603
   } # while
604
}
605
 
606
&processParameters( @ARGV );
607
 
14 rodolico 608
# load the configuration file
112 rodolico 609
%configuration = %{ &loadConfigurationFile( $configurationFile, @confFileSearchPath) };
135 rodolico 610
 
2 rodolico 611
# user did not define a serial number, so make something up
59 rodolico 612
$configuration{'serialNumber'} = '' unless $configuration{'serialNumber'};
2 rodolico 613
# oops, no client name (required) so tell them and exit
61 rodolico 614
die "No client name defined in $configurationFile" unless $configuration{'clientName'};
2 rodolico 615
 
135 rodolico 616
&logIt( 0, 'Starting sysinfo Run' );
617
&logIt( 3, "Configuration is\n" . Data::Dumper->Dump( [\%configuration], [ qw($configuration) ] ) );
618
 
61 rodolico 619
$TESTING = $configuration{'TESTING'} if defined $configuration{'TESTING'};
620
 
135 rodolico 621
&logIt( 0, "Testing => $TESTING" ) if $TESTING;
76 rodolico 622
 
623
 
2 rodolico 624
my $System; # hash reference that will store all info we are going to send to the server
625
# some defaults.
135 rodolico 626
$System->{'report'}->{'version'} = $DATA_VERSION;
627
$System->{'report'}->{'date'} = $reportDate;
628
$System->{'report'}->{'client'} = $configuration{'clientName'};
629
$System->{'system'}->{'hostname'} = $configuration{'hostname'};
630
$System->{'system'}->{'serial'} = $configuration{'serialNumber'};
631
$System->{'system'}->{'UUID'} = $configuration{'UUID'};
2 rodolico 632
 
135 rodolico 633
&logIt( 3, "Initial System\n" . Data::Dumper->Dump( [$System], [qw( $System )] ) );
634
 
2 rodolico 635
# process any modules in the system
59 rodolico 636
foreach my $moduleDir ( @{$configuration{'moduleDirs'}} ) {
135 rodolico 637
   &logIt( 3, "Processing modules from $moduleDir" );
13 rodolico 638
   &ProcessModules( $System, "$moduleDir/" );
639
}
2 rodolico 640
 
135 rodolico 641
&logIt( 4, "After processing modules\n" . Data::Dumper->Dump( [$System], [qw( $System )] ) );
642
 
2 rodolico 643
# now, everything ins in $System, so convert it to the proper output format
112 rodolico 644
#my $out = "#sysinfo: $VERSION YAML\n---\n" . &hashToYAML( $System ) . "...\n";
2 rodolico 645
 
112 rodolico 646
my $out =  "#sysinfo: $VERSION YAML\n" . Dump( $System );
647
 
135 rodolico 648
&logIt( 4, 'At line number ' . __LINE__ . "\n" . Data::Dumper->Dump([$System],[qw($System)]) );
2 rodolico 649
 
19 rodolico 650
# load some global values for use in the script, if required
651
my $globals = { 
652
      'data version' => $DATA_VERSION,
653
      'report date'  => $reportDate,
59 rodolico 654
      'client name'  => $configuration{'clientName'},
655
      'host name'    => $configuration{'hostname'},
61 rodolico 656
      'serial number'=> $configuration{'serialNumber'},
657
      'UUID'         => $configuration{'UUID'}
19 rodolico 658
      };
62 rodolico 659
 
135 rodolico 660
&logIt( 4, "Globals initialized\n" . Data::Dumper->Dump([$globals],[qw($globals)]) );
661
 
662
if ( $TESTING ) {
663
   open DATA, ">/tmp/sysinfo.testing.yaml" or die "Could not write to /tmp/sysinfo.testing.yaml: $!\n";
664
   print DATA $out;
665
   close DATA;
666
} else {
667
   # and send the results to the server
668
   if ( my $success = &sendResults( $globals, $configuration{'transports'}, $out, $configuration{'scriptDirs'} ) != 1 ) {
669
      &logIt( 0, "Error $success while sending report from $configuration{'hostname'}" );
670
   }
16 rodolico 671
}
2 rodolico 672
 
135 rodolico 673
&logIt( 0, 'Ending sysinfo Run' );
674
 
9 rodolico 675
1;