4

We have two bash scripts to start up an application. The first (Start-App.sh) one sets up the environment and the second (startup.sh) is from a 3rd party that we are trying not to heavily edit. If someone runs the second script before the first the application does not come up correctly.

Is there a way to ensure that the startup.sh can only be called from the Start-App.sh script?

They are both in the same directory and run via bash on Red Hat Linux.

HF Clibron
  • 41
  • 2
  • The right question is, is there a way to prevent it from running automatically (except from Start-App.sh) and make it obvious that it should not be run manually? – chepner Mar 28 '16 at 16:29
  • Scripts that sets up environment are usually sourced by scripts that need those environment. It won't be a heavy modification to `startup.sh` by adding 1 line just below the shebang `source Start-App.sh`. – alvits Mar 28 '16 at 21:01

8 Answers8

3

Is there a way to ensure that the startup.sh can only be called from the Start-App.sh script?

Ensure? No. And even less so without editing startup.sh at all. But you can get fairly close.

Below are three suggestions − you can either use one of them, or any combination of them.


The simplest, and probably the best, way is to add a single line at the top of startup.sh:

[ -z $CALLED_FROM_START_APP ] && { echo "Not called from Start-App.sh"; exit 42; }

And then call it from Start-App.sh like so:

export CALLED_FROM_START_APP=yes
sh startup.sh

of course, you can set this environment variable from the shell yourself, so it won't actually ensure anything, but I hope your engineering staff is mature enough not to do this.


You can also remove the execute permissions from startup.sh:

$ chmod a-x startup.sh

This will not prevent people from using sh startup.sh, so there is a very small guarantee here; but it might prevent auto-completion oopsies, and it will mark the file as "not intended to be executed" − if I see a directory with only one executable .sh file, I'll try and run that one, and not one of the others.


Lastly, you could perhaps rename the startup.sh script; for example, you could rename it to do_not_run, or "hide" it by renaming it to .startup. This probably won't interfere with the operation of this script (although I can't check this).

Martin Tournoij
  • 26,737
  • 24
  • 105
  • 146
  • Need quotes around `"$CALLED_FROM_START_APP"` in the `-z` test or it always passes (even when empty) because `[ -z $var ]` becomes `[ -z ]` which the shells interprets as `[ -n -z ]` which is true (as `-z` has non-zero length). – Etan Reisner Mar 28 '16 at 18:42
  • @EtanReisner Hmm, if I try this in `dash` and `bash` it works fine without quotes? e.g. `[ -z $XXX ] && echo Empty` outputs `Empty` as expected, and `export XXX=yes; [ -z $XXX ] && echo Empty` doesn't... I've also been typing this without quotes for quite a few years, and can't recall ever experiencing the problems you describe ... – Martin Tournoij Mar 28 '16 at 18:55
  • You are correct. I got the problem backwards. `[ -n $XXX ]` will never fail as it degenerates into `[ -n ]` which is `[ -n -n ]` instead of `[ -n '' ]`. That said not using quotes is still wrong because you still end up testing `[ -n -z ]` instead of `[ -z '' ]` *and* if your value contains spaces or globbing characters you get an error. – Etan Reisner Mar 28 '16 at 20:51
3

TL;DR:

[ $(basename "$0") = "Start-App.sh" ] || exit


Explanation

As with all other solutions presented it's not 100% bulletproof but this covers most common instances I've come across for preventing accidentally running a script directly as opposed to calling it from another script.

Unlike other approaches presented, this approach:

  • doesn't rely on manually set file names for each included/sourced script (i.e. is resilient to file name changes)
  • behaves consistently across all major *nix distros that ship with bash
  • introduces no unnecessary environment variables
  • isn't tied to a single parent script
  • prevents running the script through calling bash explicitly (e.g. bash myscript.sh)

The basic idea is having something like this at the top of your script:

[ $(basename "$0") = $(basename "$BASH_SOURCE") ] && exit

$0 returns the name of the script at the beginning of the execution chain

$BASH_SOURCE will always point to the file the currently executing code resides in (or empty if no file e.g. piping text directly to bash)

basename returns only the main file name without any directory information (e.g. basename "/user/foo/example.sh" will return example.sh). This is important so you don't get false negatives from comparing example.sh and ./example.sh for example.

To adapt this to only allow running when sourced from one specific file as in your question and provide a helpful error message to the end user, you could use:

[ $(basename "$0") = "Start-App.sh"  ] || echo "[ERROR] To start MyApplication please run ./Start-App.sh" && exit

As mentioned from the start of the answer, this is not intended as a serious security measure of any kind, but I'm guessing that's not what you're looking for anyway.

nathanchere
  • 8,008
  • 15
  • 65
  • 86
2

You can make startup.sh non-executable by typing chmod -x startup.sh. That way the user would not be able to run it simply by typing ./startup.sh.

Then from Start-App.sh, call your script by explicitly invoking the shell:

sh ./startup.sh arg1 arg2 ...

or

bash ./startup.sh arg1 arg2 ...

You can check which shell it's supposed to run in by inspecting the first line of startup.sh, it should look like:

#!/bin/bash
jeremija
  • 2,338
  • 1
  • 20
  • 28
  • 1
    This wouldn't stop someone running the script with `sh` or `bash`. – Sean Mar 28 '16 at 15:47
  • It wouldn't, but the user would have to explicitly call it with `bash startup.sh`. That way when a user does `ls -l`, only the `Start-App.sh` would be marked as executable and this would be a sign that it should be used rather than the other one. My understanding is that OP doesn't want to edit `startup.sh` so it can easily be updated from upstream. – jeremija Mar 28 '16 at 15:51
1

You can set environment variable in your first script and before running second script check if that environment variable is set properly.

1

Another alternative is checking the parent process and finding the calling script. This also needs adding some code to the second script.

For example, in the called script, you can check the exit status of this and terminate.

ps $PPID | tail -1 | awk '$NF!~/parent/{exit 1}'
karakfa
  • 66,216
  • 7
  • 41
  • 56
1

As others have pointed out, the short answer is "no", although you can play with permissions all day but this is still not bulletproof. Since you said you don't mind editing (just not heavily editing) the second script, the best way to accomplish this would be something along the lines of:

1) in the parent/first script, export an environment variable with its PID. This becomes the parent PID. For example,

# bash store parent pid
export FIRST_SCRIPT_PID = $$

2) then very briefly, in the second script, check to see if the calling PID matches the known acceptable parent PID. For example,

# confirm calling pid
if [ $PPID != $FIRST_SCRIPT_PID ] ; then
    exit 0
fi

Check out these links here and here for reference.

To recap: the most direct way to do this is adding at least a minimal line or two to the second script, which hopefully doesn't count as "heavily editing".

Community
  • 1
  • 1
tniles
  • 329
  • 1
  • 11
1

You can create a script, let's call it check-if-my-env-set containing

#! /bin/bash

source Start-App.sh
exec /bin/bash $@

and replace the shebang (see this) on startup.sh by that script

#! /abs/path/to/check-if-my-env-set
#! /bin/bash
...

then, every time you run startup.sh it will ensure the environment is set correctly.

Diego Torres Milano
  • 65,697
  • 9
  • 111
  • 134
0

To the best of my knowledge, there is no way to do this in a way that it would be impossible to get around it.

However, you could stop most attempts by using permissions.

Change the owner of the startup.sh file:

sudo chown app_specific_user startup.sh

Make startup.sh only executable by the owner:

chmod u+x startup.sh

Run startup.sh as the app_specific_user from Start-App.sh:

sudo -u app_specific_user ./startup.sh
Sean
  • 333
  • 4
  • 9