aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rwxr-xr-xslack_last_update.sh15
-rw-r--r--slacktopic.pl224
2 files changed, 187 insertions, 52 deletions
diff --git a/slack_last_update.sh b/slack_last_update.sh
index 696bed5..1f04cb6 100755
--- a/slack_last_update.sh
+++ b/slack_last_update.sh
@@ -15,21 +15,26 @@
# it depends on what the error actually was.
# This script can be tested from the command line, but in production it
-# will be executed from an irssi script, which will use the exit status
-# to decide whether the channel /topic should be updated.
+# will be executed from an irssi script, so it's got no need for options
+# or verbose output. Actually, the irssi script currently doesn't even
+# look at the exit status (other than to check for an error), so even
+# that could be removed to simplify things.
# For the morbidly curious: I intend this to be executed by bash,
# but it also works with Slackware 14.2's ash and ksh, and dash from
-# SBo. But *not* zsh: somehow $RANGE gets expanded to a quoted string,
+# SBo. But *not* zsh: $RANGE gets expanded to a quoted string,
# so curl gets executed as:
# curl --silent '--range 0-28' http://...
# and quite properly complains:
# curl: option --range 0-28: is unknown
-# I'm not a zsh guy, if you are & have a fix, let me know.
+# I'm not a zsh guy, but I did some research... either have to use the
+# syntax $=range (zsh-specific) or check for ZSH_* in the environment
+# and do 'setopt shwordsplit'. Pretty sure this means I shall never
+# again care whether one of my shell scripts fails on zsh.
### Config stuff.
-# Where the date get stored
+# Where the date gets stored.
DATEFILE="$HOME/.slack_last_update"
# Use the primary site, not a mirror.
diff --git a/slacktopic.pl b/slacktopic.pl
index 12fe27b..ea450f1 100644
--- a/slacktopic.pl
+++ b/slacktopic.pl
@@ -1,23 +1,35 @@
#!/usr/bin/perl
# irssi script. updates the ##slackware topic any time there's a security
-# update in Pat's ChangeLog. Depends on an external shell script,
-# slack_last_update.sh.
+# update in the Slackware ChangeLog.
-# Plan of action:
-# - When this script is loaded, it uses an irssi timer to check
-# for updates periodically.
-# - When there's an update, it asks for ops in the channel if not already
-# opped, changes the topic (replacing only the [YYYY-MM-DD] part), then
-# if we weren't already opped, it deops. If we *were* already opped,
-# it doesn't deop (that would be *annoying*).
+# At script startup, and again every $update_frequency seconds, we
+# check for updates like so:
+# - Exec a curl process to get the first part of the ChangeLog
+# and extract the date from it.
+# - Extract the date from the current /topic.
+# - If the /topic has a date, and if it's different, update the
+# /topic. Only the date (inside []) is changed, all the other
+# stuff is left as-is.
# Assumptions made:
-# - By the time the first check happens ($update_frequency sec after
-# script is loaded, or whenever it's run manually), we will be logged
-# into services.
+# - Every new ChangeLog entry is a security fix. This is almost
+# 100% true for stable releases (which is what we track), and
+# the only other updates will be fixes for major regressions...
+# - Client is set to autojoin ##slackware, or else the user will
+# always manually join it. Script doesn't do anything until
+# this happens.
+# - At some point we'll successfully log in to services. If the
+# script tries to check for updates before that happens, it
+# won't hurt anything, but the topic won't get updated either.
# - We have enough ChanServ access on the channel to set the topic via
-# ChanServ's topic command (flag +t in access list).
+# ChanServ's topic command (flag +t in access list). Again, no
+# harm done, but no topic updates either.
+
+# TODO: send debug/error/status messages to window #1, including
+# successful topic updates. Otherwise, I'll never know when this script
+# is working (the topic gets changed by ChanServ, I can't tell if that
+# was my script doing it or another op doing it manually...)
# References:
# https://raw.githubusercontent.com/irssi/irssi/master/docs/perl.txt
@@ -34,8 +46,11 @@ use Irssi qw/
window_find_name
window_find_item
windows
+ window_find_refnum
channel_find
command_bind
+ pidwait_add
+ signal_add_last
/;
our $VERSION = "0.1";
@@ -49,26 +64,68 @@ our %IRSSI = (
url => 'none',
);
-# External script. Assume it's in $PATH. Could hardcode the full
-# path instead.
-our $update_script = "slack_last_update.sh";
+# TODO: make some or all of these config variables into irssi
+# settings. Probably overkill, this script is niche-market (probably
+# nobody but the author will ever run it...)
+
+# Print verbose debugging messages in local irssi window?
+our $DEBUG = 1;
+
+# For testing, fake the date. 0 = use the real ChangeLog date. If you
+# set this, it *must* match /\d\d\d\d-\d\d-\d\d/.
+#our $FAKE = '9999-99-99';
+our $FAKE = 0;
+
+# Slackware ChangeLog URL. Version number is hardcoded here.
+our $changelog_url =
+ "http://ftp.slackware.com/pub/slackware/slackware64-14.2/ChangeLog.txt";
+
+# Max time curl will spend trying to do its thing. It'll give up after
+# this many seconds, if it can't download the ChangeLog.
+our $cmd_timeout = 60;
-# Where the script stores the last update date.
-our $update_file = "$ENV{HOME}/.slack_last_update";
+# $update_cmd will write its output here. It'll be in YYYY-MM-DD form,
+# which is just what's needed for the /topic.
+our $cmd_outfile = "$ENV{HOME}/.irssi/slack_update.txt";
+
+# We /exec this to get the first line of the ChangeLog. So long as Pat
+# follows his standard conventions, bytes 0-28 are the first line of
+# the ChangeLog.
+our $curl_args = "--silent --range 0-28 --max-time $cmd_timeout";
+our $update_cmd = "rm -f $cmd_outfile ; " .
+ "curl $curl_args $changelog_url |" .
+ "date -u -f- '+%F' > $cmd_outfile.new ; " .
+ "mv $cmd_outfile.new $cmd_outfile";
+
+if($FAKE) { $update_cmd = "echo '$FAKE' > $cmd_outfile"; }
# What channel's /topic are we updating?
our $update_channel = "##slackware";
+# What server is $update_channel supposed to be on? This is paranoid
+# maybe, AFAIK no other network uses the ## like freenode does, so
+# the channel name ##slackware should be enough to identify it. But,
+# ehhh, a little paranoia goes a long way...
+our $server_regex = qr/\.freenode\.(org|net)$/;
+
# Seconds between update checks. Every check executes $update_script, which
# talks to ftp.slackware.com, so be polite here.
-# TODO: make this an irssi setting?
our $update_frequency = 600;
-# Print debugging message in local irssi window?
-our $DEBUG = 1;
+# Bookkeeping stuffs.
+our $timeout_tag;
+our $child_proc;
+our $log_window;
+# Print a message to the status window, if there is one. Otherwise print
+# it to whatever the active window happens to be. Use this or one of
+# (err|debug|log)msg for all output, don't use regular print or warn.
sub echo {
- command("/echo $_") for @_;
+ if($log_window) {
+ $log_window->print($_) for @_;
+ } else {
+ command("/echo $_") for @_;
+ }
}
sub errmsg {
@@ -82,44 +139,102 @@ sub debugmsg {
echo("$file:$line: $_") for @_;
}
+sub logmsg {
+ echo("$IRSSI{name}: $_") for @_;
+}
+
+# Called once at script load.
sub init {
+ $log_window = window_find_name("(msgs)"); # should be status window
+ if($log_window) {
+ logmsg("Logging to status window");
+ } else {
+ logmsg("Logging to active window");
+ }
+
debugmsg("init() called");
+ # This gets called any time an /exec finished.
+ signal_add_last("exec remove", "finish_update");
+
# Command for manual updates.
- command_bind("slacktopic", "update_check");
+ command_bind("slacktopic", "start_update");
# Check once at script load.
- update_check();
+ start_update();
# Also, automatically run it on a timer. 3rd argument unused here.
- timeout_add($update_frequency * 1000, "update_check", 0);
+ timeout_add($update_frequency * 1000, "start_update", 0);
}
-sub update_check {
- debugmsg("update_check() called");
+# Start the update process.
+sub start_update {
+ debugmsg("start_update() called");
# Don't do anything if we're not joined to the channel already.
my $chan = channel_find($update_channel);
- debugmsg("not joined to $update_channel") unless $chan;;
- return unless $chan;
+ if(!$chan) {
+ errmsg("not joined to $update_channel");
+ return;
+ }
- # Get the date of the last update.
- my $new_date = exec_update();
- if(not defined $new_date) {
- errmsg("couldn't get new date, not updating topic");
+ my $server = $chan->{server}->{address};
+ if($server !~ $server_regex) {
+ errmsg("channel $update_channel server is wrong: " .
+ $chan->{server}->{address});
+ return;
+ }
+
+ exec_update();
+}
+
+# Called when an /exec finishes.
+sub finish_update {
+ debugmsg("finish_update() called");
+
+ my ($proc, $status) = @_;
+
+ # We get called for *every* /exec. Make sure we only respond to
+ # the right one.
+ ## debugmsg("$proc->{name}: $status");
+ return unless $proc->{name} eq 'slacktopic_update';
+
+ if(defined($timeout_tag)) {
+ timeout_remove($timeout_tag);
+ undef $timeout_tag;
+ undef $child_proc;
+ }
+
+ my $chan = channel_find($update_channel);
+ if(!$chan) {
+ debugmsg("not joined to $update_channel");
return;
}
+ # Get the date of the last update.
+ my $new_date;
+ open my $fh, "<$cmd_outfile" or do {
+ errmsg("$cmd_outfile not found, update command failed");
+ return;
+ };
+
+ chomp($new_date = <$fh>);
+ close $fh;
+ $new_date ||= "";
+
# This should never happen, but...
- if($new_date !~ /^\d\d\d\d-\d\d-\d\d/) {
- errmsg("$update_script result isn't a valid date: $new_date");
+ if($new_date !~ /^\d\d\d\d-\d\d-\d\d$/) {
+ errmsg("$cmd_outfile content isn't a valid date: '$new_date'");
return;
}
# Get old topic, replace the date with the new one.
debugmsg("\$new_date is: $new_date");
my $t = $chan->{topic};
- $t =~ s,\[\d\d\d\d-\d\d-\d\d\],[$new_date],;
+ unless($t =~ s,\[\d\d\d\d-\d\d-\d\d\],[$new_date],) {
+ errmsg("topic doesn't contain [yyyy-mm-dd] date, fix it manually");
+ return;
+ }
# Don't do anything if the topic's already correct.
if($t eq $chan->{topic}) {
@@ -129,23 +244,38 @@ sub update_check {
# Ask ChanServ to change the topic for us. We don't need +o in
# the channel, so long as we're logged in to services and have +t.
- debugmsg("asking ChanServ to update the topic");
- $chan->{server}->send_raw("ChanServ topic $update_channel :$t");
+ logmsg("ChangeLog updated [$new_date], asking ChanServ to update topic");
+ $chan->{server}->send_raw("ChanServ topic $update_channel $t");
+}
+
+# Called if the child process times out ($cmd_timeout + 2 sec).
+sub update_timed_out {
+ errmsg("child process timed out, killing it");
+ undef $timeout_tag;
+ if(defined($child_proc) && defined($child_proc->{pid})) {
+ kill 'KILL', $child_proc->{pid};
+ }
+ undef $child_proc;
}
+# Spawn $update_cmd. It'll either complete (in which case finish_update()
+# gets called) or time out (in which case, update_timed_out()).
sub exec_update {
debugmsg("exec_update() called");
- open my $fh, "$update_script|" or do {
- errmsg("couldn't execute '$update_script': $!");
- return undef;
- };
- chomp(my $result = <$fh>);
- close $fh;
- my $status = $? >> 8;
+ if($timeout_tag) {
+ errmsg("Timeout still active, not spawning new process");
+ return;
+ }
+
+ $child_proc = command("/exec - -name slacktopic_update $update_cmd");
+ $timeout_tag = timeout_add_once(
+ 1000 * ($cmd_timeout + 2),
+ 'update_timed_out',
+ 0); # last arg is unused
- debugmsg("$update_script exit status: $status, result '$result'");
- return $result;
+ # is this really necessary? seems it isn't.
+ #pidwait_add($child_proc->{pid});
}
# main()