#!/usr/bin/perl # # ktmux_helper: Run krenew in the background for tmux. # Usage: ktmux_helper [options] # Options: -I Specify path to kinit. # -L Specify path to klist. # -R Specify path to krenew. # -T Specify path to tmux. # -p Specify tmux PID. # -s 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; # 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; die "$PROG: Can't fork: $!\n" unless defined $pid; if ($pid) { while (&ping_tmux) { my $exited = waitpid $pid, WNOHANG; goto LOOP if $exited == $pid || $exited < 0; sleep 1; } # tmux is dead so kill krenew. kill QUIT, $pid; waitpid $pid, 0; exit 0; } else { exit 1 if &check_credentials; exec $krenew, "-K", "60"; print STDERR "$PROG: Can't run krenew: $!\n"; exit 111; } } sub get_tmux_pid { # 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 () { 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 | ") { while () { chomp; s/[^\d]//g; next if $_ == $$; $pid = $_; return $pid if &check_tmux_session($pid); } close IN; } 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; kill $pid; waitpid $pid, WNOHANG; exit 0; } }