2

I have an sh script which runs on our server. It is started with nohup from another script so that i can easily close session and it still runs

Main script (which will start this one) is also an sh script. And it can be executed with two parameters:

--start (will start application)
--stop (will stop it using kill PID)

But since application is started by me, none else (except root) can stop already running instance or restart it.

So i was thinking more about changing owner to nobody. Would that help it? If yes, then how can it be done?

Alexey Kamenskiy
  • 2,560
  • 3
  • 31
  • 51
  • 1
    Start a tcp socket server under your user & send some shutdown type command over the socket (from other user), then that server application will kill your process. – anishsane Nov 23 '12 at 09:10
  • @anishsane it does not sound like `simple` solution. On (already) pretty much loaded server that will create one more socket process. Thats definitely not something i am going to do. – Alexey Kamenskiy Nov 23 '12 at 09:22
  • ^^ Yes, I just gave you a `daemon` style solution. :-) – anishsane Nov 23 '12 at 09:45
  • 1
    Running as a different user just pushes the problem sideways. If a socket is a problem then use some other RPC mechanism, but your program needs to implement some sort of interface. Maybe it could be as simple as checking for the presence of a particular file in `/tmp`, or you could get fancy and use shared memory or something. Or just offer a `suid root` tool for restarting ... – tripleee Nov 23 '12 at 11:50
  • @tripleee My program us just a bash script which does somethings and never stops until you stop it manually. There are better solutions for that rather than use socket. – Alexey Kamenskiy Nov 23 '12 at 11:52

5 Answers5

4

Set up the script to shut itself down on some event, instead of running until killed. Then make the main script trigger that event at --stop (the event can be something simple like creating a specific file, or something more complex like writing a command to a fifo that the script periodically reads). This way, other users should be able to shut the script down no matter who started it.

How to do this depends on the details of your script. If it doesn't have a loop in which it can check the shutdown condition itself, you may be able to do something like have it spawn a background process that periodically checks the condition and kills its parent pid when met.

Anders Johansson
  • 3,706
  • 16
  • 18
  • That is an interesting solution. I already have PID file for that script that helps avoid duplicates. And also it has a loop. So if someone triggers this script with `-stop` key then i could manipulate with that file, lets say remove PID from it, where as original script will be also checking existence of PID in .pid file and if it is not there will gracefully shut itself and child processes down. – Alexey Kamenskiy Nov 29 '12 at 03:48
  • Thanks for your answer. You absolutely deserve this bounty for good, simple and easy to implement solution. – Alexey Kamenskiy Nov 29 '12 at 16:37
2

Setup a user especially for running this script.

And run the script using:

su -l <user> -c "<command>"

Update:

Proposing this kind of approach assumed that modifing the script in question is not an option.

alk
  • 66,653
  • 10
  • 83
  • 219
  • using `su -l` is a bad idea IMO, because then all users (who are allowed to stop/start this script) will be able to execute any command under that user, which does not let me track who did what. Maybe i am being paranoid, but i don't feel that will be secure. If there is no other way then I more tend to implement the way as in the answer from @AndersJohansson, simple, dummy, but easy to control. – Alexey Kamenskiy Nov 29 '12 at 04:10
  • I do agree, that adding some kind of IPC facility (for example as proposed in some other answers) to the scipt would be a more flexible (and thought better) solution to the OP's question. My answer was based on the assumption that modifing the script is not an option. @AlexKey – alk Nov 29 '12 at 13:10
2

Changing owner to nobody would of course help in your objective. The below describes one way of doing this by using a dedicated helper program which is invisible to the user.

Solution: You can use setuid program to make sure the script is always run as the same user. Because of security reasons, you probably are not allowed to make your shell script setuid, but you can write a simple launcher program, maybe in C:

#include <stdio.h>
#include <unistd.h>

#define PROG "./daemon.sh"

int main(int argc, char** argv)
{
    char action[32];

    if (argc < 2) {
            fprintf(stderr, "Usage: %s { --start | --stop }n", argv[0]);
            return 0;
    }

    snprintf(action, sizeof(action), "%s-the-real-thing", argv[1]);
    return execl(PROG, PROG, action, (char *)0);
}

Here we are supposing your script is named daemon.sh and is in the same directory where you run the script. You can use full path or change in other obvious ways according to your need.

Compile and make setuid as follows (assuming here name launcher.c and we decide to run as nobody):

$ gcc -Wall launcher.c -o launcher
$ sudo chown nobody launcher
$ sudo chmod u+s launcher

You need to modify your shell script daemon program slightly:

#!/bin/sh

prog=$(basename $0)

start()
{
    i=0
    while :; do
        i=$(expr $i + 1)
        echo "Daemon working like a mad horse... ($i)"
        sleep 1
    done
}

stop()
{
    pids=$(ps axu | grep "$prog.*--start" | grep -v grep | awk '{print $2}')
    echo "Killing [$pids]"
    [ -n "$pids" ] && exec /bin/kill -KILL $pids
}

case "$1" in
    --start|--stop)         exec ./launcher $1;;
    --start-the-real-thing) start;;
    --stop-the-real-thing)  stop;;
    *)                      echo "Bad argument \"$1\"";;
esac

Here start function is where all the interesting funk is done until your kill the program by calling the stop function. There are some obvious changes required to run this from anywhere, and I am hoping your existing kill logic is more elegant.

Usage is like before:

./daemon.sh --start
./daemon.sh --stop

Explanation: the above will execute ./launcher --start or ./launcher --stop, repectively. This will change the effective user to nobody (or whatever you chose to setuid the program), and execute either ./daemon.sh --start-the-real-thing or ./daemon.sh --stop-the-real-thing which will perform the desired original action.

Because the simple launcher program only allows running the named script, the security is easy to control (just to make sure, you can check in the launcher program that argv[1] really is --start or --stop, then exactly two command line combinations can be run as ).

FooF
  • 3,297
  • 2
  • 26
  • 44
  • While this is definitely +1 answer, absolutely geeky and thought-through. I do really love it for how beautiful solution is. But I sadly should say that business and real life vise it is too complex. I hope you would agree with me. – Alexey Kamenskiy Nov 29 '12 at 16:32
  • Maybe you could create a github for this tool where anyone in the future could use it ;) – Alexey Kamenskiy Nov 29 '12 at 16:34
  • If you have the script and the launcher program in the same directory, then the complexity is very manageable IMO. However, as `perl` can be run as setuid (unlike shell scripts), more elegant and straightforward solution could be to just have a single `perl` script to do the business if that is suitable. I agree it could be easy to extend the `launcher` program, but having single purpose program that only knows to run one specified script serves the security here. Making general purpose tool would quickly start to resemble `sudo` in its scope... – FooF Nov 29 '12 at 17:01
-1

You can try below options:

  1. create a TCP socket in your process, to which a client process started by other user can communicate.
  2. From your user, start a separate TCP server, which can accept some limited set of commands over TCP. That server can accept the daemon-shutdown command from any user. That process can then issue a kill command for your main process.
  3. Create a named fifo, which can be made as writable by any user. Create a child thread, which would try to read from that fifo. Since by nature, the fifo read calls are blocking calls, that thread will keep sleeping (& will not consume your CPU). After some other user writes something to that fifo, the thread will wake up & cleanly terminate the parent thread.
  4. Same as above, but the thread waiting is part of another process, started by the same user. That process, when wakes up, will kill main process.

In my opinion, 3rd approach is the cleanest one, among the three.

EDIT: Since the 'main process' is actually a shell script, the 'thread' would change to sub-shell. & the child thread to be waiting would be simple 'cat fifo'.

anishsane
  • 17,770
  • 5
  • 33
  • 64
  • dont_read_comments@post_answer_immediately. 1) It is a shell script, opening TCP socket from shell? That is not even funny. And as stated in comments that i ain't going to deal with sockets to solve this. 2) 3rd and 4th approach will created additional processes. Why to do that if i can use just pid file for that as it is offered in another answer. – Alexey Kamenskiy Nov 29 '12 at 09:18
  • pid file has inherent issue, that the process may have already ended & the pid has been assigned to another one; unless off course, the risk is acceptable. – anishsane Nov 29 '12 at 09:33
  • If you would read carefully, the process doesn't stop until someone stops it. Sure there will be some check that it is not trying to launch twice. – Alexey Kamenskiy Nov 29 '12 at 09:49
  • "opening TCP socket from shell? That is not even funny." Yes, indeed. this is serious... You can use `netcat -l PORTNUMBER` to create a socket. (netcat may be renamed to nc on some distros.) & bash has native capability to listen to sockets. `exec FILE_DESCRIPTOR<>/dev/tcp/localhost/PORTNUMBER` – anishsane Nov 29 '12 at 10:00
  • Oh, sorry. I did not mean to say it is impossible. I meant that for this issue TCP sockets will be too much for solving it. – Alexey Kamenskiy Nov 29 '12 at 10:16
  • But thanks a LOT! Your comment led me to search for bash handling of tcp sockets & netcat. NOW I how to create a relay between 2 system, via a 3rd system :-) – anishsane Nov 29 '12 at 10:37
-2

If you have root access, you could use sudo and configure it to allow running the script with the --stop argument as root.

Jester
  • 52,795
  • 4
  • 67
  • 108