LinuxQuestions.org

LinuxQuestions.org (/questions/)
-   Linux - General (https://www.linuxquestions.org/questions/linux-general-1/)
-   -   interactive bash script to ask for superuser password (https://www.linuxquestions.org/questions/linux-general-1/interactive-bash-script-to-ask-for-superuser-password-855358/)

kostya 01-10-2011 09:11 AM

interactive bash script to ask for superuser password
 
Hi everyone.
This is an old question asked many times, which, however, is NEVER answered directly in any manual I've checked. So...

I'm writing a bash install script (instead of a rpm or .deb package) that must be run by a normal user.
Then at a certain point the script needs to change to superuser (asking for password and receiving it) and the rest of the script to be executed in the superuser mode in order to install what I mean to install.

I know how `sudo ...` or `su `root -c "..."` or `gnome-terminal -e ...` can achieve this purpose by creating certain batch files and then give them as argument to these commands. That's NOT what I'm asking, however.

I want to know how I can make the script interactively switch to superuser mode and go on running the rest of the script (can be a lot of code) in that mode. I don't mind if it opens a separate terminal window to do that; just how can that be achieved?

Thanks in advance, hope I'm expressing myself clear enough :D.

Snark1994 01-10-2011 10:35 AM

If you include one 'sudo' command in the script, then any other commands prefixed by sudo will run without asking for passwords :)

Code:

#!/bin/bash
echo `iwconfig` 1> normal
sudo echo "Thanks for the password"
echo `sudo iwconfig` &> su
diff normal su -q

That's a rather clumsy way of showing that the sudo command remembered the password (root's output of iwconfig contains the encryption key of wlan0, and accordingly diff says the files are different) :)

kostya 01-11-2011 03:10 AM

OK, great thanks. I'll try this one out; however, let me describe just WHY I'm thinking of this "abnormal" thing I'm asking about.
I often have to help beginners to install linux, and quite often they're people who're not going to be really deep into it. Say, after finishing installation I just noticed that I forgot to install a iptables script which I usually use. But going back to my friend's place is taking time...

So my dirty fix is: I email him a file, bash script executable, which will install the needed files into the system once the user clicks on it. Such a dirty fix when small changes are needed to have it up and running ASAP.
Then, as the system in question may not have the "/etc/sudoers" file setup properly (unless it is Ubuntu), using `sudo $command` may not work. Therefore, starting a root subshell will be more universal. But in the Advanced Bash Scripting Guide in the chapter on "subshells" there is nothing about starting a different user subshell inside a script.
So I wonder if it is possible, or I'll always have to define a huge piece of code as $COMMAND and then use
Code:

`su root -c "$COMMAND"`
or better still
Code:

`gnome-terminal -e $COMMAND`
move.
At least, in the install scripts I studied folks use this approach.

Snark1994 01-12-2011 04:39 PM

I had a play around and looked in the man pages, but everything I tried just took me to a root prompt, then executed the rest of the script once I quit that... Sorry :)

kostya 01-13-2011 12:16 AM

Quote:

Originally Posted by Snark1994 (Post 4222222)
I had a play around and looked in the man pages, but everything I tried just took me to a root prompt, then executed the rest of the script once I quit that... Sorry :)

Yea, seems to be a tricky thing, right :D?
OK, so far I'm using this way:
Code:

#!/bin/bash
cat <<-'EOF' >/tmp/myFile
...............................
....my script text goes here...
...............................
EOF
chmod +x /tmp/myFile
xterm -e su root -c "cp /tmp/myFile /usr/bin"

Avoiding variables like $myfile makes things easier when passing arguments to `su`. XTERM creates a window for the user to type in his password.
But it seems one cannot supply more than ONE command to `su` in a non-interactive way.
...Well ACTUALLY, all the stuff can be done without root privs and then only the crucial operation like copying it into SYSTEM location be done with `su root`.

But hey, I wanna know in general, if it is possible in a bash script to start a non-interactive subshell from another UID. Or should one use other languages for that purpose?
OR, alternatively, how can one supply several commands to `su`? I'll try several things and report my results here, too.

Thanks for your answers anyway.
Kostya

kostya 01-13-2011 02:44 AM

Quote:

Originally Posted by kostya (Post 4222537)
...
But it seems one cannot supply more than ONE command to `su` in a non-interactive way.
...how can one supply several commands to `su`?

Ehm, sorry that I didn't read enough documentation before asking these, he-he. In fact, the `su` command seems to be executing just another instance of bash (a subshell), only doing so as superuser. Therefore, a succession of commands can be passed on to `su -c` just as we do in normal bash shell, when we pass on a whole script of commands, each command ending with ";", as in:
Code:

su -c 'cd /root; mkdir mydir; touch mydir/file'
The above simple command succession worked perfectly fine once I tried it. It works the same in:
Code:

xterm -e su -c 'cd /root; mkdir mydir; touch mydir/file'
I'll try later on some more complicated stuff with interactive tricks and report the results.
If it works OK, then this will be the answer to my question.

Bye!

Nominal Animal 01-13-2011 03:44 AM

Code:

#!/bin/bash
if [ -z "$BASH_LINENO" ] || [ $BASH_LINENO -gt 0 ]; then
    [ -n "$BASH_ARGV" ] && cmd="$BASH_ARGV" || cmd="$BASH_SOURCE"
    case "$cmd" in
        ""|/*|./*|../*) ;;
        *) cmd="./$cmd" ;;
    esac
    if [ -f "$cmd" ]; then
        chmod a+rx "$cmd" &>/dev/null
        echo -n "root "
        su -c "env TRIEDWITHSU=1 \"${cmd//\"/\\\"}\"" root
        return $?
    fi
    echo "You have a typo there." >&2
    return 1
fi
if [ `id -u` -ne 0 ]; then
    if [ -n "$TRIEDWITHSU" ]; then
        echo "Failed to become root." >&2
        exit 1
    fi
    chmod a+rx "$0" &>/dev/null
    echo -n "root "
    su -c "env TRIEDWITHSU=1 \"${0//\"/\\\"}\"" root
    exit $?
fi
echo "Now running as `id -un`:`id -gn`."

Feel free to omit the last echo; its there just for debugging purposes.

It is more complex than is really needed, but this way the user does not need to set any permission bits.

You can tell the user to save the script as say fixer.txt in their home directory.
Then, they need to open a terminal and type
Code:

. fixer.txt
to run the script.

The scriptlet at the beginning checks whether it's being sourced or executed, and works accordingly. Before su-ing, it adds read and execute bits to itself. It is completely automagic: it uses various bash variables to find out its own name and path.
Nominal Animal

kostya 01-13-2011 07:36 AM

Why thank you for this interesting example, must take a closer study later on :).
You know, I learn better from examples, than from "dry theory"...

Nominal Animal 01-13-2011 10:49 PM

I'm happy to help.

Here's a breakdown on the scriptlet (from my above post #7):
  • The first if clause is executed if the script was sourced (via source or .).
    Bash provides a variable BASH_LINENO, which is nonzero for sourced scripts.
  • If the script was sourced, it may have been specified with just the file name (in the current directory).
    Since the current directory is probably not in PATH, we may need to prepend the current directory to it, to be able to execute it.
    This is what the case ... esac block does.
  • [ -f file ] is true, if and only if file exists and is a regular file.
  • Variables must be put in double quotes to avoid filename expansion. If, for example, you test [ -f $name ],
    and name has value *, you actually test if there is a single file in the current directory. (If there are more, the shell will error out, because [ -f name1 name2 ] does not work.)
    So always use [ -f "$name" ] instead.
  • The above is not just for tests, it applies everywhere in Bash scripts. Quote your variables, unless you need filename expansion.
  • The file name may contain double quotes and spaces, so we cannot just supply it directly to su in a string,
    because su would just use the first word as the file name and fail.
    Within a doublequoted string \"${cmd//\"/\\\"}\" produces $cmd, but in double quotes, and with all double quotes in (the value of) cmd escaped.
  • `id -u` resolves to the UID number of the current process. It will be zero for superuser.
  • There may be some configuration where su succeeds but does not get elevated rights.
    If we just tried again, we might end up in an endless loop. Without configured limits, this will eventually use all resources, kick in the dreaded OOM killer (which kills some processes trying to free up resources). Nasty. And if the user automatically saves their session at exit, they are screwed: they'll get the half dead session at next login, too.
    Both su calls in this script set environment variable TRIEDWITHSU to 1, so they can detect that they've already tried su and stop, avoiding the possible endless loop.
  • If the script was executed (and not sourced), $0 contains the script name and path.
    Since su takes the command as a string, we need it doublequoted in a doublequoted string, with all double quotes escaped.
    Thus, \"${0//\"/\\\"}\" is used. (The pattern is same as for cmd variable above, of course.)
As I said, this is a bit more complex than is really necessary, but this works even if the user has a localized non-English desktop, and the script name has to be something as complex as Frï'X - Garble garble garble for the user to follow the directions you give.

I think it's still sensitive to backslashes (\), variable expansion ($), and history expansion (!), so avoid those three characters in the file names. They are escapable, but I'm striving for maximally useful, not perfect ;)

If the patterns interest you, you can test them via e.g.
Code:

x="It's a Chëëzbrgr!" ; echo "x becomes \"${x//\"/\\\"}\" in the output"
Nominal Animal


All times are GMT -5. The time now is 08:05 AM.