#!/usr/bin/perl $VERSION = "0.17"; $CVS_VERSION = '$Revision: 1.1 $ $Date: 2005-03-11 16:57:22 $ $Author: jonas $'; # # Get a machine's critical features, And mail them to the Linux Counter # # Copyright (c) Harald Tveit Alvestrand, the Linux Counter Project # License: GNU Copyleft - see bottom of file. # # As a matter of courtesy, if you change this file on your own, # make sure it does NOT mail to the counter!!!!!!!!!!!! # $HELP = < EoF # use POSIX; # Predecarations sub Debug; sub ErrorInfo; sub DebugInfo; # Some variables are for internal use, and never prompted for in # the loop of askquestions %dontask = ( "uniqueid", 1, # Internal use "manual", 1, "method", 1, "owner", 1, # These 2 are always prompted for "key", 1, "uptime", 1, # We think we know how to get these ); # Configuration: Where to fetch information from $cpufile = "/proc/cpuinfo"; $kcorefile = "/proc/kcore"; # Make sure nothing happens, so that the script's routines # can be debugged from another file if ($IsInTestHarness) { return 1; } preparation(); options(); if ($option{crontab}) { installcrontab(); exit(0) } elsif ($option{uncrontab}) { uninstallcrontab(); exit(0); } readfile(); checkconfig(); if ($option{ask}) { askquestions(); } else { copymanuals(); } writefile(); sendfile(); sub preparation { die "No HOME environment variable\n" if (!$ENV{HOME}); die "No home diretory\n" if ! -d $ENV{HOME}; $infodir = "$ENV{HOME}/.linuxcounter"; if (! -d $infodir) { mkdir($infodir, 0766) || die "Unable to make $infodir\n"; } # Keep track of where I am; need it to install crontab entry # progname is a global. $progname = $0; if ($progname !~ /^\//) { my $progdir = `pwd`; chop $progdir; $progname = "$progdir/$progname"; $progname =~ s!/./!/!; } chdir($infodir) || die "Unable to change to $infodir\n"; ($sysname, $nodename, $release, $version, $machine ) = POSIX::uname(); if (! -f $nodename) { print STDERR "Machine-update $VERSION. Use $0 -l to display license.\n"; print STDERR "Creating the infofile for your computer.\n"; # Create the infodir open(INFO, ">$nodename"); print INFO "uniqueid: ", randomnumber(), "\n"; close INFO; } } sub options { while ($ARGV[0] =~ /^-/) { $opt = shift @ARGV; $opt =~ /i/ && ($option{ask}=1) ; $opt =~ /d/ && ($option{DEBUG}+=1) && (print STDERR "Debug is $option{DEBUG}\n"); $opt =~ /l/ && do {license(); exit(0)}; $opt =~ /t/ && ($option{mail}=0); $opt =~ /m/ && ($option{mail}=1); $opt =~ /x/ && ($option{info}=1); $opt =~ /c/ && ($option{crontab}=1); $opt =~ /u/ && ($option{uncrontab}=1); $opt =~ /v/ && die "\n\t Linux Counter machine-update version $VERSION\n"; $opt =~ /h/ && die $HELP; } } sub askquestions { return if ! -t STDIN || ! -t STDOUT; $| = 1; print "Here you can specify some info that the script can't know for itself\n"; $values{owner} = askone("Your Linux Counter reg#, if any", $values{owner}); $values{key} = askone("Your machine's counter reg#, if any", $values{key}); print "Here is what the program has found:\n"; for $key (keys(%values)) { if (!$dontask{$key}) { printf "%-10s%1s: %s\n", $key, $dontask{$key}?"*":" ", $values{$key}; } } $domore = askone("Do you want to override some of the found values?", "no"); if ($domore =~ /^Y/i) { my $manual; for $key (keys(%values)) { next if $dontask{$key}; my $value = askone($key, $oldvalues{$key}, $values{$key}); if ($values{$key} eq $value) { # go to automatic Debug "auto value: $key\n"; } else { Debug "still manual value: $key\n"; $manual .= " $key"; } $values{$key} = $value; } $values{manual} = $manual; } else { delete $values{manual}; } } sub askone { my $prompt = shift; my $default = shift; my $probed = shift; print $prompt; if (!defined($default) && defined($probed)) { $default = $probed; } if (defined($default)) { print " [$default]"; } if (defined($probed) && $probed ne $default) { print "(program found $probed)"; } print ":"; $ans = ; chop $ans; Debug "Answer was $ans\n"; if (length($ans) == 0) { $ans = $default; } $ans; } sub copymanuals { my %keeps = map {$_ => 1} split(" ", $values{manual}); Debug "Keeping ", join(" ", keys(%keeps)), "\n"; for $key (keys(%keeps)) { $values{$key} = $oldvalues{$key}; } } sub readfile { open(INFO, $nodename) || die "Did not find infofile $nodename\n"; while () { chop; s/#.*//; if (/^(\S+): *(.+)/) { Debug "Read $1: $2\n"; $values{$1} = $2; } else { print STDERR "Unparsed info line: $_ - discarded\n"; } } close INFO; %oldvalues = %values; } sub writefile { open(INFO, ">$nodename.new"); for $val (sort keys(%values)) { Debug "Saving $val: $values{$val}\n"; print INFO "$val: $values{$val}\n"; } close INFO; rename("$nodename.new", $nodename) || die "Rename failed\n"; } sub sendfile { if ($option{mail}) { open(MAIL, "|/usr/lib/sendmail machine-registration\@counter.li.org") || die "Unable to open sendmail\n"; } else { warn "--------------------------------------------------------\n"; warn "This is what will be sent to the Linux Counter if you\n"; warn "run the program with the -m switch. Now, NOTHING IS SENT\n"; warn "--------------------------------------------------------\n"; open(MAIL, ">&STDOUT"); } # note that $ENV{USER} isn't (always) set in a cron job... $user = (getpwuid($<))[0]; $user = "unknown-id-$<" if !$user; print MAIL < # Blame is, of course, all mine - HTA - open (TMP,"df -l |"); $HD=0; while () { @line=split(/\s+/); ($line[1]=~ /blocks$/) || ($HD+=$line[1]); } Debug "$HD kbytes of disk found\n"; $HD/=1024; $values{disk} = sprintf("%d", $HD); $values{accounts} = accounts(); $values{users} = active_users(); $uptime = `uptime`; if ($uptime =~ /up\s+(.*),\s+\d+ user/) { $values{uptime} = $1; } else { ErrorInfo "Can't parse uptime output: $uptime\n"; } if ($option{info}) { DebugInfo "***** Uptime output *****\n$uptime"; } # Not sure this is a Right Thing...so not saving it for the moment $s{mailaddr}=$s{hostname}; if ( -f "/etc/sendmail.cf" ) { $values{mailer} = "sendmail"; open (TMP,") { if (/^DM(.+)/) { $s{mailaddr}=$1; Debug "Found $s{mailaddr} in /etc/sendmail.cf\n"; } } } if (-f $kcorefile) { $values{memory} = ((-s $kcorefile) - 4096) / (1024*1024); } else { ErrorInfo "No /proc/kcore file\n"; } cpuinfo(); } sub cpuinfo { # This procedure is likely to get tacky enough that I want to # isolate it from the rest.... my %interesting = ( # 2.0 and 2.2 kernels "bogomips" => "+bogomips", "processor" => "1+processors", "vendor_id" => "cpu_vendor", # 2.0 kernels "cpu" => "cpu_only", "model" => "cpu_model", "model name" => "cpu_model_name", # 2.2 kernels "cpu MHz" => "cpu_mhz", "cpu family" => "cpu_family", # from an Alpha processor "cycle frequency [Hz]" => "cpu_hz", "BogoMIPS" => "+bogomips", "cpu model" => "cpu_model", "system type" => "cpu_system_type", "cpus detected" => "processors", # from a PowerMAC "machine" => "cpu_machine", "clock" => "cpu_clock", "motherboard" => "cpu_motherboard", ); # Zero out the accumulative values $values{bogomips} = 0; $values{processors} = 0; if (open (TMP,"<$cpufile")) { DebugInfo "**** Contents of $cpufile ****\n"; while () { # Save /proc/cpuinfo to debugdata if -d DebugInfo $_; chop; # A bizarre selection of names are "interesting". # Make a data-driven pick routine if (/^(\S+[^:]+\S)\s+: /) { $name = $1; $value = $'; if ($interesting{$name}) { if ($interesting{$name} =~ /^\+/) { $values{$'} += $value; } elsif ($interesting{$name} =~ /^1\+/) { $values{$'} += 1; } else { $values{$interesting{$name}} = $value; } } } } } else { ErrorInfo "Could not open $cpufile\n"; } } sub accounts { my $s; my $niss; open (TMP," /dev/null|") || do {$errrordata .= "ypcat failed: $!\n"}; $niss = passwdscan(); $s += $niss; close TMP; Debug "Status of ypcat: $?\n"; $option{DEBUG} && do { print STDERR "Found $niss accounts in ypcat passwd\n"; }; $option{DEBUG} && do { print STDERR "Sysaccounts: ", join(" ", keys(%is_sys_account)), "\n"; print STDERR "Found $s accounts total\n"; }; return $s } sub passwdscan { # I suppose this is as good as it gets - # Usually user accounts have UID > 100 and # "system accounts" have UID < 100, but there is no guarantee that # this will hold for pseudo-users like "postgress" etc. # Also nobody is usually 99 on linux, but -1 on "standard" unices. my @line; my $s=0; while () { @line=split(":"); if ($line[2] > 100) { $s++; $is_account{$line[0]} = 1; } else { $is_sys_account{$line[0]} = 1; } } $s; } sub active_users { # This is kind of alpha, but please test it. # It calculates the number of "active" users based on the "wtmp" entries # This guys shouldn't be counted. Who else? for $q (qw(reboot wtmp runlevel)) { $is_sys_account{$q} = 1; } open( TMP, "/usr/bin/last|"); while (){ chop; @tmp=split; $name = $tmp[0]; if ($is_sys_account{$name}) { # do nothing } elsif ($is_account{$name}) { $is_user{$name}=1; } elsif (/^\s*$/) { # blank line - do nothing } elsif ($#tmp == 9) { # OK line, but unknown user $option{DEBUG} && do { if (!$userslisted) { print STDERR "Know users are: ", join(" ", keys(%is_account)), "\n"; $userslisted = 1; }; print STDERR "Unknown user: $name\n"; } } else { $option{DEBUG} && print STDERR "Strange line: $_\n"; } } close TMP; $i=0; foreach $user (sort keys %is_user) { $i++; $option{DEBUG} && printf "Active user %3d: %s\n", $i, $user; } $option{DEBUG} && print "$i active users found.\n"; return $i; } sub installcrontab { print STDERR "Installing start of script into your crontab\n"; $hour = int(rand(24)); $min = int(rand(60)); $day = int(rand(7)); # Weekday. This version runs once a week. # Previous versions ran once a month. if (open(CRON, "crontab -l |")) { $option{DEBUG} && print "Checking crontab for machine-update\n"; $option{DEBUG} && print "Want to install as $progname\n"; while () { if (/^#/ && $. <= 3) { # initial comment Debug "Skipping comment: $_"; next; } if (/machine-update/) { if (/ $progname -m/) { die "Crontab entry already installed: $_\n"; } else { die "Another entry with machine-update: $_\n"; } } $cron .= $_; } close CRON; Debug "Result from crontab -l: ", $? / 256, "\n"; if ($? == 0) { Debug "Crontab successfully read\n"; } elsif ($? == 256) { warn "You don't seem to have a crontab. I will create one.\n"; } else { die "Failed to read your crontab. Please report this as a bug: $?\n"; } } else { Debug "Result from crontab open(): $?\n"; die "Unable to execute crontab command. Please check your system\n"; } open(CRON, "|crontab -"); print CRON $cron; print CRON "$min $hour * * $day $progname -m\n"; close CRON; Debug "Result from crontab: $?\n"; if ($?) { print <) { if (/^#/ && $. <= 3) { # initial comment Debug "Skipping comment: $_"; next; } if (/machine-update/) { if (/ $progname -m/) { print STDERR "Crontab entry found and removed\n"; $found = 1; next; # skip stuff at end.... } else { die "Another entry with machine-update: $_\nUninstall manually?\n"; } } $cron .= $_; } close CRON; Debug "Result from crontab -l: $?\n"; if ($?) { die "Failed to read your crontab. You may not have one?\n"; } if ($found) { open(CRON, "|crontab -"); print CRON $cron; close CRON; Debug "Result from crontab: $?\n"; if ($?) { print <