Skip to content


Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
branch: master
Fetching contributors…

Cannot retrieve contributors at this time

executable file 126 lines (102 sloc) 3.578 kb
# Opens a subshell as another user (a la su(1)), after making the SSH_AUTH_SOCK
# and/or .Xauthority files be usable by the target user. This allows ssh/X
# connections from the target user to be made using the source user's
# credentials.
# Restrictions:
# * Target user must be a member of the SSH_AUTH_SOCK owner's default group
# * If using sudo (the default), SSH_AUTH_SOCK must be included in env_keep in
# sudoers
use strict;
use warnings;
use Getopt::Long;
use Sys::Hostname 'hostname';
'xauth!' => \(my $xauth = 1), # share Xauthority
'fatal!' => \(my $fatal), # die instead of warn on errors
'usermod!' => \(my $usermod), # run usermod to add target user to source group
'old-sudo!' => \(my $old_sudo), # for versions without -i
'sudo-cmd=s' => \(my $sudo_cmd = '/usr/bin/sudo'),
'usermod-cmd=s' => \(my $usermod_cmd = '/usr/sbin/usermod'),
'verbose!' => \(my $verbose),
sub error {
unshift @_, shift() . "\n";
$fatal ? die @_ : warn @_;
sub verbose {
return unless $verbose;
unshift @_, shift() . "\n";
warn @_;
my $su = sub {
if ($old_sudo) {
exec $sudo_cmd, qw( su -m - ), shift;
exec $sudo_cmd, qw( -i -u ), shift;
# or 'su', 'su -', etc
unless (@ARGV) {
(my $me = $0) =~ s,.*/,,;
die "usage: $me <username>\n";
my $user = shift;
verbose sprintf "Preparing to switch from %s to %s", (getpwuid $<)[0], $user;
share_xauthority() if $xauth;
sub share_auth_sock {
verbose "Attempting to share authsock";
my $auth_sock = $ENV{SSH_AUTH_SOCK};
unless ($auth_sock && -w $auth_sock) {
error "No writable \$SSH_AUTH_SOCK in environment";
verbose "\$SSH_AUTH_SOCK found: $auth_sock";
my @stat = stat $auth_sock;
# check that target user is in the group of the authsock
my $gid = $stat[5];
my @group = getgrgid $gid;
verbose "$auth_sock has group $group[0] ($gid)";
my @members = split / /, (getgrgid $gid)[3];
verbose "Group $group[0] contains [@members]";
unless (grep({$_ eq $user} @members)) {
if ($usermod) {
verbose "Adding $user to group $group[0]";
system $sudo_cmd, $usermod_cmd, '-G', $group[0], $user;
else {
error "$user is not a member of ${auth_sock}'s group ($group[0]); it will not be accessible";
verbose "$user is in ${auth_sock}'s group";
(my $auth_dir = $auth_sock) =~ s,/[^/]+$,,;
my $group_perms = 070;
for my $file ($auth_sock, $auth_dir) {
my $old_perms = (stat $file)[2];
verbose sprintf "$file has permissions 0%o (need 0%o)", $old_perms, $group_perms;
next if ($old_perms & $group_perms) == $group_perms;
unless (chmod $old_perms | $group_perms, $file) {
error "Failed to add group RWX permissions on $auth_dir/$auth_sock: $!";
verbose sprintf "Added group permissions on $auth_dir & $auth_sock";
sub share_xauthority {
verbose "Attempting to share Xauthority";
my $display = $ENV{DISPLAY};
unless (defined $display) {
error "\$DISPLAY not available";
$display =~ s{localhost:}{hostname() . '/unix:'}e; # convert to xauth-compatible string
if (system "xauth extract - $display | $sudo_cmd -u $user -H xauth merge -") {
error "xauth failed";
verbose "Shared Xauthority";
Jump to Line
Something went wrong with that request. Please try again.