Check if a command exists in Bash (including superusers)

I want to check if the program is installed on a UNIX system.

I could use commands like:

  • command -v
  • hash
  • type ,
  • which ...

... and all of them are already mentioned in this answer .

However, none of them work if I want to check, as a regular user , whether I or any superuser can execute a given command.

Here is an example of what I mean:

 dummy:~$ command -v poweroff; echo $? 1 dummy:~$ su root:~# command -v poweroff; echo $? /sbin/poweroff 0 

As you can see, the average user did not recognize the existence of the poweroff command. Note that dummy users are free to see that in /sbin anyway.

+8
bash shell
source share
2 answers

Source of problem

The reason your commands do not work is because they only look for executables in the $PATH variable. First, let's test our hypothesis.

 dummy:~$ mkdir test dummy:~$ cd test dummy:~/test$ echo '#!/bin/sh' >test.sh dummy:~/test$ chmod +x test.sh dummy:~/test$ cd dummy:~$ command -v test.sh dummy:~$ PATH+=:/home/dummy/test/ dummy:~$ command -v test.sh /home/dummy/test/test.sh 

This confirms my presentation above.
Now let's see what $PATH looks like for different users:

 dummy:~$ echo $PATH /usr/local/bin:/usr/bin:/bin:/usr/local/games:/usr/games dummy:~$ su root:~# echo $PATH /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin 

So, to check if this command is available to this user (in your question, namely: root), you need to know its $PATH environment variable.

Decision

The values ​​of such environment variables on Debian can usually be found in the /etc/profile and /etc/environment/ files. There is no easy way to get these values ​​by catching them from files.

The most basic solution is to temporarily add known directories to your $PATH variable, and then use command -v :

 dummy~$ OLDPATH=$PATH dummy~$ PATH=$OLDPATH:/sbin:/usr/sbin/:/usr/local/sbin/ dummy~$ command -v poweroff /sbin/poweroff dummy~$ PATH=$OLDPATH 

There is one problem with this solution: if you want to be portable, you really don't know which folders you should concatenate. In most cases, this approach should be sufficient.

Alternative solution

Instead, you can write a script program that uses setuid bit . The Setuid bit is a somewhat hidden feature of Linux operating systems that allows you to run programs with the rights of their owners. Thus, you write a program that executes certain commands, such as the superuser, except that ordinary users can run it. So you can see the output of command -v poweroff , as the root would do.

Unfortunately, the material that shebang uses cannot have the setuid bit , so you cannot create a shell script for this, and you need a program in C. Here is an example of a program that will do the job:

 #include <stdio.h> #include <string.h> #include <stdlib.h> #include <unistd.h> int main(int argc, char** argv) { if (argc <= 1) { fprintf(stderr, "No arguments.\n"); return 1; } //validate the argv char* prog = argv[1]; int i; for (i = 0; i < strlen(prog); i ++) { if (prog[i] < 'a' || prog[i] > 'z') { fprintf(stderr, "%s contains invalid characters (%c), exiting.", prog, prog[i]); return 1; } } //here the `which` command. We start it in new interactive shell, //since this program inherits environment variables from its //parent shell. We need to start *new* shell that will initialize //and overwrite existing PATH environment variable. char* command = (char*) malloc(strlen(prog) + 30); if (!command) { fprintf(stderr, "No memory!\n"); return 1; } sprintf(command, "bash -cli 'command -v %s'", prog); int exists = 0; //first we try to execute the command as a dummy user. exists |= system(command) == 0; if (!exists) { //then we try to execute the command as a root user. setuid(0); exists |= system(command) == 0; } return exists ? 0 : 1; } 

Safety Notice . The above version has a very simple argument check (it only skips lines matching ^[az]*$ ). The real program should probably include a better check.

Testing

Suppose we saved a file in test.c We compile it and add the setuid bit:

 root:~# gcc ./test.c -o ./test root:~# chown root:root ./test root:~# chmod 4755 ./test 

Note that chown comes before chmod . 4 before the regular pattern 755 is the setuid bit.
Now we can test the program as a regular user.

 dummy:~$ ./test ls; echo $? alias ls='ls -vhF1 --color=auto --group-directories-first' 0 dummy:~$ ./test blah; echo $? 1 dummy:~$ ./test poweroff; echo $? /sbin/poweroff 0 

And best of all - it is portable enough to run on cygwin without any problems. :)

+13
source share

The real answer is that you cannot satisfy the “any superuser” aspect if you mean: “Does this command appear in the search path of any user with sudo access?” The reason is that you will need to run each user startup script to find out where its search path ends: many users will include their own files ~ / bin or ~ / pod / abi / x86_64-ubu-1204 / bin or what anything and God knows what else (/ afs // bin, someone?) - and many startup scripts have side effects that can turn all this into a real mess, including creating logs, launching various kinds of daemons, etc. You really will have problems if one of the startup scripts for these users tries to launch your new team, as they then recurs and launch a denial of service attack on your own system.

What you can check more safely:

  • can anyone execute the command given the full path name? (skips running script madness)
  • can the current user execute a simple (not fully fixed) command?
  • can the current user execute a simple command using sudo? (this makes some assumptions about root startup scripts)
  • Can any user running the default environment run the command? (use a dummy user with a known setting).

On a larger system with thousands of users, the “any” option is impractical. Mature sites DO NOT want a root-friendly team to run arbitrary user scripts, even those that have been approved with sudo, which are generally trustworthy admins.

+3
source share

All Articles