Subversion Repositories camp_sysinfo_client_3

Rev

Rev 233 | Details | Compare with Previous | Last modification | View Log | RSS feed

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