#!/usr/bin/perl
# (c) Copyright 2001-2010. CodeWeavers, Inc.
use warnings;
use strict;


# Portable which(1) implementation
sub cxwhich($$;$)
{
    my ($dirs, $app, $noexec)=@_;
    if ($app =~ /^\//)
    {
        return $app if ((-x $app or $noexec) and -f $app);
    }
    elsif ($app =~ /\//)
    {
        require Cwd;
        my $path=Cwd::cwd() . "/$app";
        return $path if ((-x $path or $noexec) and -f $path);
    }
    else
    {
        foreach my $dir (split /:/, $dirs)
        {
            return "$dir/$app" if ($dir ne "" and (-x "$dir/$app" or $noexec) and -f "$dir/$app");
        }
    }
    return undef;
}

# Fast dirname() implementation
sub _cxdirname($)
{
    my ($path)=@_;
    return undef if (!defined $path);
    return "." if ($path !~ s!/+[^/]+/*$!!s);
    return "/" if ($path eq "");
    return $path;
}

# Locate where CrossOver is installed by looking for the directory
# where the cxmenu script is located, unwinding symlinks on the way
sub locate_cx_root(;$)
{
    my ($fallback)=@_;
    my $argv0=cxwhich($ENV{PATH},$0);
    $argv0=$0 if (!defined $argv0);
    if ($argv0 !~ m+^/+)
    {
        require Cwd;
        $argv0=Cwd::cwd() . "/$argv0";
    }
    my $dir=_cxdirname($argv0);
    my $bindir=$dir;
    $bindir =~ s%/lib$%/bin%;
    while (!-x "$bindir/cxmenu" or !-f "$bindir/cxmenu")
    {
        last if (!-l $argv0);
        $argv0=readlink($argv0);
        $argv0="$dir/$argv0" if ($argv0 !~ m+^/+);
        $dir=_cxdirname($argv0);
        $bindir=$dir;
        $bindir =~ s%/lib$%/bin%;
    }
    $bindir =~ s%/(?:\./)+%/%g;
    $bindir =~ s%/\.$%%;
    $ENV{CX_ROOT}=_cxdirname($bindir);
    if ((!-x "$ENV{CX_ROOT}/bin/cxmenu" or !-f "$ENV{CX_ROOT}/bin/cxmenu") and
        $fallback)
    {
        $ENV{CX_ROOT}=$fallback;
    }
    if (!-x "$ENV{CX_ROOT}/bin/cxmenu" or !-f "$ENV{CX_ROOT}/bin/cxmenu")
    {
        my $name0=$0;
        $name0 =~ s+^.*/++;
        print STDERR "$name0:error: could not find CrossOver in '$ENV{CX_ROOT}'\n";
        exit 1;
    }
    return $ENV{CX_ROOT};
}

BEGIN {
    unshift @INC, locate_cx_root() . "/lib/perl";
}
use CXLog;
use CXUtils;


# Process command-line options
my $opt_console;
my $opt_wait;
my $opt_ignore_home;
my $opt_verbose;
my $opt_help;
require CXOpts;
my $cxopts=CXOpts->new(["stop_on_non_option"]);
$cxopts->add_options(["console"    => \$opt_console,
                      "wait"       => \$opt_wait,
                      "ignore-home"=> \$opt_ignore_home,
                      "verbose!"   => \$opt_verbose,
                      "?|h|help"   => \$opt_help
                     ]);
my $err=$cxopts->parse();
CXLog::fdopen(2) if ($opt_verbose);

# Validate the command line options
my $usage;
if ($err)
{
    cxerr("$err\n");
    $usage=2;
}
elsif ($opt_help)
{
    $usage=0;
}
elsif (!@ARGV)
{
    cxerr("no command specified\n");
    $usage=2;
}

# Print usage
if (defined $usage)
{
    my $name0=cxname0();
    if ($usage)
    {
        cxerr("try '$name0 --help' for more information\n");
        exit $usage;
    }
    print "Usage: $name0 [--console] [--wait] [--verbose] [--help] COMMAND\n";

    print "\n";
    print "Starts a graphical interface to perform an su and run the specified\n";
    print "application as root.\n";

    print "\n";
    print "Options:\n";
    print "  COMMAND     The command to run as root\n";
    print "  --console   Use the current stdin and stdout for the password prompt\n";
    print "  --wait      Wait after the command has completed\n";
    print "              and command rather than launching a GUI prompt\n";
    print "  --verbose   Output more information about what is going on\n";
    print "  --help, -h  Shows this help message\n";
    exit 0;
}

if ($> == 0)
{
    # We are already root, so just run the command
    cxexec(@ARGV);
    cxerr("unable to run ", CXUtils::argv2shcmd(@ARGV), ": $!\n");
    exit 1;
}

my @args;
if (!$opt_console and $ENV{DISPLAY})
{
    my @su_list=("xdg-su -c");
    # Try to pick the appropriate default for the current desktop environment
    my $de=CXUtils::get_desktop_environment();
    cxlog("desktop environment: $de\n");
    push @su_list, "gksu" if ($de eq "gnome");
    push @su_list, "kdesudo -c", "kdesu -c" if ($de eq "kde");
    # And push all known tools as fallbacks
    push @su_list, "gksu", "kdesudo -c", "kdesu -c";

    foreach my $sutool_cmd (@su_list)
    {
        my @sutool=CXUtils::cmdline2argv($sutool_cmd);
        my $sutoolname=shift @sutool;
        cxlog("trying '$sutoolname'\n");
        my $path=cxwhich("$ENV{PATH}:/usr/lib64/kde4/libexec:/usr/lib/kde4/libexec", $sutoolname);
        if (defined $path)
        {
            # Some su tools (gksu for instance) are incapable of dealing with
            # a space in the executable name but don't care about spaces in
            # later arguments. So use 'sh -c'.
            # gksu is even more broken: if not given the '--' option it cannot
            # deal with spaces in any argument but older versions don't
            # support '--'! This means we should not use '--' and 'sh -c'
            # unless necessary.
            if ($sutoolname eq "gksu" and !grep / /, @ARGV)
            {
                # ARGV contains no bad character so there is no need for
                # 'sh -c'. Also since the command won't go through a shell it
                # does not need extensive escaping.
                @args=($path, @sutool, join(' ', @ARGV))
            }
            else
            {
                push @sutool, "--" if ($sutoolname eq "gksu");
                @args=($path, @sutool, "/bin/sh", "-c", CXUtils::argv2shcmd(@ARGV));
            }
            last;
        }
    }
    if (!@args)
    {
        require POSIX;
        if (!POSIX::isatty(0))
        {
            @args=CXUtils::get_terminal_emulator(cxgettext("Enter the root password"));
            if (!@args)
            {
                cxerr("unable to find a suitable terminal emulator\n");
                exit 1;
            }
            # Use 'sh -c' as recommended by get_terminal_emulator()
            push @args, "/bin/sh", "-c", CXUtils::argv2shcmd($0, "--wait", @ARGV);
            cxexec(@args);
            cxerr("unable to run '@args': $!\n");
            exit 1;
        }
    }
}

if (!@args)
{
    my $command=CXUtils::argv2shcmd(@ARGV);
    print cxgettext("This operation must be run as root:\n");
    print "\n   $command\n\n";

    # Use sudo if available and allowed
    my $sudo_path=cxwhich("$ENV{PATH}","sudo");
    if (defined $sudo_path)
    {
        my $cmd=shquote_string($sudo_path) . " -l |";
        print "Trying with sudo...\n";
        cxlog("Running: $cmd\n");
        if (open(my $fh, $cmd))
        {
            if (grep /^\s*\((?:root|ALL)\)\s+(?:NOPASSWD:\s+)?ALL\s*$/, <$fh>)
            {
                @args=($sudo_path);
                push @args, "-H" if (!$opt_ignore_home);
                push @args, @ARGV;
            }
            close($fh);
        }
    }
    if (!@args)
    {
        # Try sux first as it will preserve access to X
        my $sux_path=cxwhich("$ENV{PATH}","sux");
        if (defined $sux_path)
        {
            print "Trying with sux...\n";
            @args=("sux");
            push @args, "-" if (!$opt_ignore_home);
            push @args, "root", @ARGV;
        }
    }
    if (!@args)
    {
        # Otherwise, use the regular su
        # Note that unlike sudo and sux, su expects
        # the whole command in just one argument
        print "Trying with su...\n";
        @args=("su");
        push @args, "-" if (!$opt_ignore_home);
        push @args, "root", "-c", $command;
    }
}

my $rc = cxsystem(@args);
if ($opt_wait)
{
    print cxgettext("Finished. Press Return to continue.");
    <STDIN>;
}

# Sanitize $rc so it is suitable for exit
exit($rc >> 8) if ($rc >> 8);
exit($rc);
