killp.pl - Perl script to kill processes safely
#!/usr/local/bin/perl 
#
# /usr/local/bin/killp - kill user processes with confirmation
#
# Author: Jim Wildman, 3X HealthCare Solutions Group 
# jawildman@cfanet.com, jim.wildman@3x.com
# Date: 01/28/2000
# Updated: 02/04/2000
#
# On the Sigma1 system, this is wrapped in a script called 'killp' 
# which consists of 'sudo /usr/local/bin/killp.pl -u $1'
#
# The Syslog module requires that the Perl header files have been built
# with 'cd /usr/include; h2ph * sys/*'
#
# On the HPUX 10.20 boxes, this process generated some bad Perl header files.
# The Perl header files will be identified the first time you run the 
# script.  Edit them at the lines indicated and change "" to " in the
# die() statements.  The h2ph man page warns that it isn't perfect, so 
# this doesn't really seem to be a bug.  More likely it is something
# dicey in the HP header files, but we won't go there.
#
# The /etc/syslog.conf file could be edited to log all of this stuff
# somewhere else.  You would need to change the openlog() call to use
# a facility other than 'user'.  But why create more log files to
# rotate?

use Getopt::Std;
use Sys::Syslog;

# This is the list of logins/users that we can't kill with this script
@notkillable = ("root","sigma","mqm","daemon","bin","sys","adm","uucp",
        "lp","nuucp","hpdb","www","tftp");

# Right now the -u is required.  This is partly due to my inexperience
# with perl, partly because I want people to think about it before they
# do the dirty deed.
getopts('hsu:') || die "Usage: killp [-h] | [-s]  -u <Userstringto_kill>\n"; 

if(($opt_u eq "") || ($opt_h)) {
        printf("Usage: killu [-h] | [-s] [-p processtokill] \n");
        printf("        List, select and kill processes by user id\n");
        printf("        -h display this help message\n");
        printf("        -s simulate, don't kill anything\n");
        printf("        -u actually kill the processes\n");
        exit;
}

# This sets up the syslog command.  Syslog entries will have 
#       ident = killp
#       log_option = pid
#       facility = user
# See man syslog or man syslogd for more details
openlog( 'killp', 'pid', 'user' );

# set our kill string to match on
$kill_string = $opt_u;

# Walk the notkillable list checking the kill string against each entry
# This depends on all of the 'system' ids being in the notkillable array
# The getpwuid call doesn't really work right because the process is run
# with sudo.  But sudo logs everything anyway, so we get the id. 
foreach $i (@notkillable) {
        if($kill_string =~ $notkillable[$i]) {
                $myowner = getpwuid $<;

                # syslog requires a properly formatted string, so set up
                # $mstring as a message string
                $mstring = $myowner . " tried to kill a restricted process" ;

                # log the attempt
                syslog( 'info', $mstring);
                syslog( 'info', $kill_string);

                # Yell at the user
                print( "NO!  You are not allowed to kill system processes\n");
                print("System processes have to be killed from a root login\n\n");
                exit();
        }
}
 
# Get the uid of the kill_string from the password file
$kuserid = "" ;
while( ($name,$passw,$uid) = getpwent) {
        if($kill_string =~ $name) {
                $kuserid = $uid;
        }
}

# If we don't match against the password file, then we quit.  This means
# no partial user matches etc.  It has to be right.
if($kuserid == "") {
        print("This >>$kill_string<< doesn't seem to be a valid user name.\n");
        exit();
}

# Get our list of processes

@processes = `ps -fu $kill_string`;

while ( ($item=&Menu) > ($#processes + 1) ) { };
$prstring = $processes[$item];
chomp $prstring;

# split the ps output.  We really just need the first 3 fields
($user,$pid,$ppid) = split(" ",$prstring) ;

# Check for the -s (summary) flag
if($opt_s ne "") {
        print( "Right here we would kill \n>>$prstring<<\n");
        print( "But you have the -s (summary) option turned on.  Boohoo.\n");
        exit();
}

print "\n\nLast chance.  When you hit 'y<return>'" ;
print "\n******They will be gone!!******" ;
print "\nLocked, loaded and ready to kill\n>>$prstring<<\n\tOkay? : ";
$confirm = <STDIN>;
chomp $confirm;
$myowner = getpwuid $<;
if( ($confirm eq "y") || ($confirm eq "Y")) {
        print "Killing $user, $pid\n";
        $cnt = kill 'KILL', $pid;
        if($cnt == 0){
                print "No processes killed.  Do you have permission to do this?\n";
                $mstring = "Failed killp attempt on";
                syslog( 'info', $mstring);
                syslog( 'info', $prstring);
                exit;
        }
        else {
                print "$cnt processes killed.  Hope you're happy!\n";

                # log the event
                syslog( 'info', $prstring);
                exit;
        }
}

# User bailed out or mistyped Y/y
else {
        print "Well, you're either smart or chicken :-)\n";
        print "No kill performed\n";
}
print "\n";

####
# This subroutine displays the contents of the @processes list with each
# item numbered.  It then waits for a matching number to be entered.  It
# also presents the user with the option to quit.
sub Menu
{
        my ($rc) = 0;
        system("clear");
        print("\tProcesses belonging to $kill_string\n");
        print("\tEnter the menu number of the process to kill\n\n");

        # The first entry in @processes is the header line from ps
        for ($i = 1; $i<= $#processes; $i++)
        {
                print $i . ". " . $processes[$i] . "\n";
        }
        # 
        # Add a quit option
        print $i . ". " . "Quit\n";
        print "\n\nSelect option: ";
        $s=<STDIN>;
        chop $s;

        # This means they picked the highest number, which is 'Quit'
        # or entered 0 which is invalid and does the same thing.  Other
        # numbers will be kicked back by the while() loop which called
        # us
        if( ($s == $i) || ($s == 0) ){
                print "\nHave it your way. Bye\n";
                exit;
        }
        return ($s);
}