#!/usr/bin/perl
#
# ktmux_helper: Run krenew in the background for tmux.
-# Notes: Doesn't handle multiple sessions properly.
+# Usage: ktmux_helper [options]
+# Options: -I <path> Specify path to kinit.
+# -L <path> Specify path to klist.
+# -R <path> Specify path to krenew.
+# -T <path> Specify path to tmux.
+# -p <pid> Specify tmux PID.
+# -s <session> Specify tmux session number.
+# Notes: The -p and -s flags are only used to identify instances when
+# viewed in ps output.
#
use FindBin;
+use Getopt::Std;
use POSIX ":sys_wait_h";
my $PROG = $FindBin::Script;
-# Ensure tmux is our parent and find its PID.
-my $tmux_pid = &get_tmux_pid;
+# Remember args for re-exec.
+my @execargs;
+foreach my $arg (@ARGV) {
+ push @execargs, $arg;
+}
+
+my %opts;
+getopts('I:L:R:T:p:s:', \%opts);
+
+# Ensure tmux is our parent and find its PID if not already known.
+our $tmux_pid = &get_tmux_pid($opts{'p'});
unless ($tmux_pid) {
print STDERR "$PROG: Not a child of tmux!\n";
exit 100;
}
+# Find session if possible.
+our $tmux_session = &get_tmux_session($opts{'s'});
+
+# Fix the environment.
+$ENV{TMUX} =~ s/,\d+,-?\d+$/,$tmux_pid,$tmux_session/;
+
# Ensure there isn't already a helper running for this tmux.
my $tmux_helper = &get_tmux_helper;
exit 0 if $tmux_helper;
+$tmux_helper = $$;
+
+# Re-exec ourselves purely so the session and PID are set in the environment.
+my @args;
+push @args, "-p", $tmux_pid unless defined $opts{'p'};
+push @args, "-s", $tmux_session unless defined $opts{'s'};
+exec $0, @args, @execargs if @args;
+
+my $kinit = $opts{'I'} || "kinit";
+my $klist = $opts{'L'} || "klist";
+my $krenew = $opts{'R'} || "krenew";
+my $tmux = $opts{'T'} || "tmux";
+
+my $avoid_race = 0;
my $exitasap = 0;
my $pid = 0;
$SIG{INT} = \&cleanup;
$SIG{QUIT} = \&cleanup;
$SIG{TERM} = \&cleanup;
+$SIG{USR1} = \&want_credentials;
LOOP: while (&ping_tmux) {
$pid = fork;
}
# tmux is dead so kill krenew.
- kill 3, $pid;
+ kill QUIT, $pid;
waitpid $pid, 0;
exit 0;
}
else {
- exec "krenew", "-K", "60";
- print "$PROG: Can't run krenew: $!\n";
+ exit 1 if &check_credentials;
+ exec $krenew, "-K", "60";
+ print STDERR "$PROG: Can't run krenew: $!\n";
exit 111;
}
}
sub get_tmux_pid {
- my $pid = getppid;
+ # Set from command line?
+ my $pid = shift;
+
+ # Set from environment?
+ unless ($pid) {
+ if ($ENV{TMUX} =~ /,(\d+),[^,]+$/) {
+ $pid = $1;
+ }
+ }
+
+ my $ppid = getppid;
+
+ if ($pid) {
+ # Check it really is our parent.
+ if ($pid == $ppid) {
+ # Check that it's still running.
+ return $pid if kill -0, $pid;
+ }
+ }
+
+ return undef;
+
+ # Everything below probably can't happen so should be removed.
my $cmd = `/bin/ps -o args= -p $pid`;
return $pid if $cmd =~ /\btmux\b/;
return undef;
}
+sub get_tmux_session {
+ # Set from command line?
+ my $session = shift;
+
+ # Set from environment?
+ unless ($session) {
+ # The session identifier will be -1 if we were launched from run-shell
+ # because a run-shell command doesn't count as being part of a session.
+ if ($ENV{TMUX} =~ /,(-?\d+)$/) {
+ $session = $1;
+ }
+ }
+
+ return $session;
+}
+
+# Check that a given process was launched from our session.
+sub check_tmux_session {
+ my $pid = shift;
+
+ return undef unless defined $tmux_session;
+
+ my $cmd;
+ if ($^O eq "linux") {
+ $cmd = "xargs -0 -n 1 < /proc/$pid/environ";
+ }
+ elsif ($^O eq "solaris") {
+ $cmd = "pargs -Fe $pid";
+ }
+ else {
+ # Don't know how to check on this OS.
+ return undef;
+ }
+
+ return undef unless (open IN, "$cmd | ");
+
+ while (<IN>) {
+ chomp;
+ next unless /TMUX=.+,(-?\d+)/;
+ # Abort if the running helper doesn't know what session it's for.
+ if ($1 eq $tmux_session || $1 eq "-1") {
+ close IN;
+ return 1;
+ }
+ }
+
+ close IN;
+ return 0;
+}
+
+sub check_kinit_child {
+ foreach my $pid (`/bin/ps -o ppid= -C kinit`) {
+ next unless $pid =~ /^\s*$tmux_pid\s*$/;
+ next if &check_tmux_session($pid);
+ return 1;
+ }
+ return 0;
+}
+
sub get_tmux_helper {
my $pid = undef;
if (open IN, "pgrep -x -P $tmux_pid $PROG | ") {
s/[^\d]//g;
next if $_ == $$;
$pid = $_;
- last;
+ return $pid if &check_tmux_session($pid);
}
close IN;
}
- return $pid;
+ return undef;
}
sub ping_tmux {
return kill 0, $tmux_pid;
}
+# Try to check existing Kerberos credentials.
+sub check_credentials {
+ system $klist, "-s";
+ return 1 if $? < 0;
+ return 0 unless $?;
+ kill USR1, $tmux_helper;
+ return 111;
+}
+
+# We were signalled by our child which noticed that our credentials expired.
+sub want_credentials {
+ return sleep 1 if $avoid_race;
+ $avoid_race = 1;
+ # Do we already know?
+ system $tmux, "new-window", "-n", "Renew Kerberos credentials", "exec $kinit" unless &check_kinit_child;
+ sleep 1;
+ $avoid_race = 0;
+}
+
sub cleanup {
unless ($exitasap) {
$exitasap = 1;