# Alternative to su for scripts that need to change users?



## ta0kira (Aug 19, 2014)

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 `wait`ing 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


----------



## SirDice (Aug 19, 2014)

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 `wait`ing 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.
> ...


There's another option. `su -m nobody -c 'find /var | less'` (Note the placement of the quote)


----------



## ta0kira (Aug 19, 2014)

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:
	
	



```
#!/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
```


```
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


----------



## ta0kira (Aug 19, 2014)

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


----------



## ta0kira (Aug 19, 2014)

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


----------

