kill4

Mike Gogulski (syadasti@seminole.iag.net)
Fri, 10 Nov 1995 21:41:37 -0500 (EST)

Here's a program for yas... I posted a few months ago about trouble I
experienced with it trying to reconnect immediately to the same portmaster
it just disconnected from with pm protocol. I finally got around to altering
the structure enough to work around this bug (*if* you have multiple port-
masters, single pm shops may need to add a delay, or strip the disconnect
and reconnect code out.

Bug reports, fixes, expansions, and wants are welcome to <syadasti@iag.net>

Peace,
Mike

#!/usr/local/bin/perl5
#
# kill4 - knock abusive users off Portmaster terminal servers
# according to specified criteria
#
# by Mike Gogulski <syadasti@iag.net>
#
# Requires perl5 and bpmtools.
#
# I developed this code in response to our need to limit dialup
# users continuous usage during peak hours.
#
# Currently being run as a cron job. Output consists of successful
# kill reports and error messages from the pm.pl library.
#
# Logs successful kills to syslog. Commented-out debugging log
# statements are included.
#
# Originally a cron job, a shell script, an expect script, and
# perl script which constructed an expect script.
#
# Neither I nor IAG will be held responsible for anything this
# program does. Use it, modify it, just don't redistribute it
# without permission.
#

require 'syslog.pl';
require 'pm.pl';

# bpmtools provides for only a single password for all your Portmasters. I
# don't live in a one-password world, however, so I've got this file
# "pmpass.pl" which looks like this:
#
# %hosts = ( # list of pms to monitor
# "firsthost", "firstpassword",
# "secondhost, "secondpassword"
# );
#
# 1;

require '/opt/lib/pmpass.pl';

$prog_name = "kill4"; # call it what you will, it's just a tag for logging
$min_idle_ports = 60; # if there are fewer than $min_idle_ports, then kill
# this '60' says kill anyone exceeding max_time since
# we've got 2 PM2e30s
$max_time = 4*60; # max time a user can be on before killable (minutes)
#$maxmax_time = VALUE; # if user's on longer than this, kill them anyway
# UNIMPLEMENTED
#
# Exemptions files. These files must exist currently, so just touch(1)
# them if you don't want to exempt anyone.
#
# File format is one entry per line, no spaces or extraneous characters.
#
# Each entry in the users file is one username per line.
#
# Each entry in the ports file is of the form
#
# domain-S#[#]
#
# where domain is the name of the portmaster, and the ##s represent the
# port number. Don't add leading zeros to port numbers.
#

$exempt_users_file = "/opt/lib/k4users";
$exempt_ports_file = "/opt/lib/k4ports";

#$kill4_log = "/var/log/killed";

##############################################################################
# End of configuration section
##############################################################################

$| = 1;
$date = `date`; chop($date);

sub killable {
local($user, $port) = @_;
! (grep(/$user/, @exempt_users) || grep(/$port/, @exempt_ports));
}

sub kill4_log {
local(@entry) = @_;
#
# file-based logging
#
# if (! open(LOG, ">>$kill4_log")) {
# print STDERR "$prog_name: Can't open logfile $kill4_log: $!\n";
# 0;
# }
# print LOG "$date: $prog_name: @entry\n";
# close(LOG);
#
# log to stdout
#
# print "$date: $prog_name: @entry\n";
#
# syslog
#
&openlog($prog_name, 'pid', 'daemon');
&syslog('daemon|notice', "@entry");
&closelog();
}

#
# read the user exemptions file and the port exemptions file
#
if (! open(EXU, $exempt_users_file)) {
&kill4_log("Can't open user exemptions file: $!");
die();
}
@exempt_users = <EXU>;
close (EXU);

if (! open(EXP, $exempt_ports_file)) {
&kill4_log("Can't open port exemptions file: $!");
die();
}
@exempt_ports = <EXP>;
close (EXP);

$idle_count = 0;

while (($host, $pass) = each(%hosts)) {

&portmaster::Connect($host, $pass);
# &kill4_log("Connected to $host");

for ($Cnt = 0; $Cnt <= 29 && $idle_count < $min_idle_ports; $Cnt++) {
&portmaster::Who("$Cnt");
if ($PortValue{$Cnt, STATUS} eq "IDLE") {
++$idle_count;
} else {
# we assume a non-idle port with no username is in the process of connecting
if ($user = $PortValue{$Cnt, USERNAME}) {
$Sport = "S" . "$Cnt";
$port=$host . "-". $Sport;
if (&killable($user, $port)) {
$time = $PortValue{$Cnt, STARTTIME};
if ($time > $max_time) {
# line 'em up!
# push(@candidates, $time, $user,
# $Sport, $host);
push(@candidates, $host, $Sport, $user, $time);
}
}
}
}
}
# &kill4_log("Disconnecting from $host");
close(portmaster'DS);
# &kill4_log("Disconnected from $host");
}

# We've got a list of candidates, now let's kill 'em (if necessary)

# Well, the portmasters apparently have a problem accepting a new pm
# protocol connection immediately after one is closed on it. We used to
# use pop() and push() to deal with the @candidates queue, but that gave
# us login failures any time the last portmaster queried had a killable
# user logged in.
#
# These should shift() off in the reverse of the order they were push()ed,
# so we should have to do no checking to be efficient with the number of
# new connections to portmasters.

$host = shift(@candidates);

if ($idle_count < $min_idle_ports && $host) {
# we gotta check here to see if Connect returns failure

# &kill4_log("Got candidate list");
# &kill4_log("First host $host");
# &kill4_log("Connecting to $host");

&portmaster::Connect("$host", $hosts{$host});

# &kill4_log("Connected to $host");

$current_host = $host;
#
# We could decrement $idle_count in this loop and exit when <= $min_idle_ports
#
while ($host) {
$Sport = shift(@candidates);
$victim = shift(@candidates);
$time = shift(@candidates);
# &portmaster::Connect($host, $hosts{$host});
print "$host: ";
&portmaster::Command("reset $Sport"); # shoot 'em!
# close(portmaster'DS);
&kill4_log("Killed $victim on $host-$Sport ($time)");
$host = shift(@candidates);
if ($current_host ne $host) {
# &kill4_log("Disconnecting from $current_host");
close(portmaster'DS);
# &kill4_log("Disconnected from $current_host");
if ($host) {
# we gotta check here to see if Connect returns failure
# &kill4_log("Connecting to $host");
&portmaster::Connect("$host", $hosts{$host});
# &kill4_log("Connected to $host");
$current_host = $host;
}
}
}
}

-- 
Mike Gogulski                   syadasti@iag.net
Network Administrator  		syadasti@cat.net
Internet Access Group           Altamonte Springs, Florida, USA
+1-407-786-1145 Work            +1-407-672-2340 Other