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