Alternative to su for scripts that need to change users?

I tried to search the forums for this topic, but unfortunately "su" isn't a valid search term.

I have a set of scripts that need to run as root, and at times they su to a normal user to execute other commands, e.g., su -m ta0kira -c 'find .' or the like. This is fine in most cases, but I realized that su unconditionally steals terminal control if I run my script from a terminal (su.c:453). The effect can be replicated by doing the following as @root: su -m nobody -c 'find /var' | less. less will stop in the background, but su will be in the foreground waiting for the child process to exit. So, I guess I have three options here:
  • Always follow su with 2> /dev/null in scripts so it doesn't take terminal control.
  • Modify su so that it doesn't take terminal control when arguments are passed. Note that if PAM needs a password, the respective module should steal terminal control as needed.
  • Use something other than su to change users.
Note that using a port (e.g., sudo) is not an acceptable solution. On Linux, su doesn't steal terminal control and things work just fine, so I'm tempted to take my second option above.

Thanks!

Kevin Barry
 
Last edited by a moderator:
ta0kira said:
I have a set of scripts that need to run as root, and at times they su to a normal user to execute other commands, e.g., su -m ta0kira -c 'find .' or the like. This is fine in most cases, but I realized that su unconditionally steals terminal control if I run my script from a terminal. The effect can be replicated by doing the following as @root: su -m nobody -c 'find /var' | less. less will stop in the background, but su will be in the foreground waiting for the child process to exit. So, I guess I have three options here:
  • Always follow su with 2> /dev/null in scripts so it doesn't take terminal control.
  • Modify su so that it doesn't take terminal control when arguments are passed. Note that if PAM needs a password, the respective module should steal terminal control as needed.
  • Use something other than su to change users.
Note that using a port (e.g., sudo) is not an acceptable solution. On Linux, su doesn't steal terminal control and things work just fine, so I'm tempted to take my second option above.
There's another option. su -m nobody -c 'find /var | less' (Note the placement of the quote)
 
Last edited by a moderator:
That was just to demonstrate the effect; the output of the su command is used for other things within the script, and the script has separate output. I can't include less, etc. in the su calls. It's more like this:
Code:
#!/usr/bin/env bash

find /home | su -m ta0kira -c 'script-that-does-something.sh' | \
while read something; do
  echo "this is something: $something"
done
Code:
root@host$ ./the-script-above.sh | less
Again, that's not the actual code; it's just to show the type of context where I use su.

Thanks!

Kevin
 
If I eliminate tcsetpgrp altogether, I can't kill the process with [Ctrl]+C when su isn't part of a pipe. I therefore modified su so that it calls tcsetpgrp on STDOUT_FILENO rather than STDERR_FILENO, which gives me the expected behavior. But, I like to be able to do things without modifying the base system, so I'm still open to alternatives.

Kevin
 
I think the actual cause of the problem is the fork(2) and wait(2), rather than just doing an execv(3). The fork(2) code really just sets the process group and terminal control, then wait(2)s, and there's really no need to do that unless you want the original @root process to stick around. The GNU version of su(1) doesn't fork(2), so I'm guessing it isn't a security risk to not fork(2), so I just deleted the fork(2) code in su.c. (Bug Report)

Kevin
 
Last edited by a moderator:
Back
Top