Switch user as well as environment variable in Unix Shell Script - unix

I'm currently working on a script that logs into another user account using
su username -c "comand1; comand2"
There's no sudo command in the shell.
However the environment variables do not change.
For example, the work directories are different with different users. So when the user is switched in the script, I expect that the $HOME variable should contain different work directories.
Does anyone know how to solve this problem?

You are probably looking for the -l (or just -) option to make it a login shell. See the manual for su(1).

One hack-ish kind of way would be put all env variables of the second user in a file (like .bashrc) and source it first in the list of the commands:
su username -c "source file_name; comand1; comand2"

There are two issues:
First: If you user " to delimit the argument for -c then everything in this argument is expanded by the calling shell and not by the executed shell. You can use ' instead of " to prevent this.
Second: By default su does not perform the same steps as during a real user login. Use the -l or - options. This may be required in your case or not - just try it.
Together:
> echo $HOME
/root
> su -c "echo $HOME" user -l
/root
> su -c 'echo $HOME' user -l
/home/user
> su -c 'echo $HOME' user
/home/user

Related

How to copy files to remote server with a user without privileges?

When I need to copy a file from local server (server A) to remote server(server B) via SSH, using a user with enough privileges, I do this successfuly like below
localpath='/this/is/local/path/file1.txt'
remotepath='/this/is/remote/path/'
mypass='MyPassword123'
sshpass -p $mypass scp username#hostname:$localpath $remotepath
Now, I have to transfer a file from server A to server C with a user that doesn't have enough privileges to copy. Then once
I connected to Server C, I need to send su in order to be able to send commands like cd, ls, etc.
Manually, I access the server C via SSH like this:
[root#ServerA ~]# ssh username#hostname
You are trying to access a restricted zone. Only Authorized Users allowed.
Password:
Last login: Sat Jun 13 10:17:40 2020 from XXX.XXX.XXX.XXX
ServerC ~ $
ServerC ~ $ su
Password:
ServerC /home/myuser #
ServerC /home/myuser # cd /documents/backups/
ServerC /documents/backups #
At this moment myuser has superuser privileges and I can send commands.
Then, how can I automate the task to copy files from server A to server C with the need to send su once I'm connected to Server C?
I've tried so far doing like this:
sshpass -p $mypass ssh -t username#hostname "su -c \"cd /documents/backups/ && ls\""
it requests password for su and I'm able to send cd and ls but with this command, I'm not copying files from Server A to Server C, only semi-automating the access to Server C and sending the su in Server C.
Thanks in advance for any help.
UPDATE
# $TAR | ssh $username#$hostname "$COMMAND"
+ tar -cv -C /this/is/local/path/file1.txt .
+ ssh username#X.X.X.X 'set -x; rm -f /tmp/copy && mknod /tmp/copy p; su - <<< "su_password
set -x; tar -xv -C /this/is/remote/path/ . < /tmp/copy" & cat > /tmp/copy'
tar: /this/is/local/path/file1.txt: Cannot chdir: Not a directory
tar: Error is not recoverable: exiting now
You are trying to access a restricted zone. Only Authorized Users allowed.
Password:
+ rm -f /tmp/copy
+ mknod /tmp/copy p
+ su -
+ cat
Password:
Editorial note: the previous version of this answer used sudo, the current version uses su as requested in the question.
You could use tar and pipes, like so:
TAR="tar -cv -C $localpath ."
UNTAR="tar -xv -C $remotepath ."
PREPARE_PIPE="rm -f /tmp/copy && mknod /tmp/copy p"
NEWLINE=$'\n' # that's the easiest way to get a literal newline
ROOT_PASSWORD=rootpasswordverydangerous
COMMAND="set -x; $PREPARE_PIPE; su - <<< \"${ROOT_PASSWORD}${NEWLINE} set -x; $UNTAR < /tmp/copy\" & cat > /tmp/copy"
$TAR | ssh username#hostname "$COMMAND"
Explanation:
tar -c . archives the current directory into a single file. We aren't passing -f to tar, so that single file is standard output.
tar -x . extracts the content of a single tar archive file to the current directory. We aren't passing -f to tar, so that single file is standard input.
-C <path> tells tar to cd into <path> so that it will be the current directory in which files are copied from/to.
-v just tells tar to list the files tar archives/extracts, for debugging purposes.
Likewise, set -x is just to have bash to emit trace information, for debugging purposes.
So we're archiving $localpath into stdout, and piping it to ssh, which will pipe it to $COMMAND.
If there was a way to give su the password in the command line, we would have used something like:
$TAR | ssh ... su --password ${ROOT_PASSWORD} -c "$UNTAR"
and things would have been simple.
But su doesn't have that. su runs like a shell, reading from stdin. So it will first read the password, and once the password is read and su has established a root session, it reads commands from stdin. That's why we have su - <<< \"${ROOT_PASSWORD}${NEWLINE}${UNTAR}.
But now stdin is used by the password and command, so we can't use it as the archive. We could use another file descriptor, but I prefer not to, because then the solution can be more easily ported to work with sudo instead of su. sudo closes all file descriptors, and sudo -C 200 (only close file descriptors above 200) may not work (didn't work on my test machine).
if we went that direction, we would have used something like
$TAR | ssh ... 'exec 9<&2 && sudo -S <<< $mypass bash -c "$UNTAR <&9"'
Our next option is to do something like cat > /tmp/archive.tar in order to write the entire archive into a file, and then have something like $UNTAR < /tmp/archive.tar. But the archive may be huge and we may run out of disk space.
So the idea is to create a dedicated pipe - that's PREPARE_PIPE. Pipes don't save anything to disk, and don't store the entire stream in memory, so the reader and the writer have to work concurrently (you know, like with a real pipe).
So having redirected su's stdin from $ROOT_PASSWORD, we pull ssh's stdin into our pipe with cat > /tmp/copy, and in parallel (&) having $UNTAR read from the pipe (< /tmp/copy).
Notes:
You could also pass -z to both tar commands to pass it compressed, if your network is too slow.
tar will preserve the source's metadata, e.g. timestamps and ownership.
Passing $ROOT_PASSWORD to commands is not good practice, anyone who runs ps -ef can see the password. There are ways to pass the password to server C in a more secure way, I didn't include it in order to not further complicate this answer.
I would suggest asking the server's owner to install sudo, so that if the password is compromised via ps -ef, at least it's not the root password.

How to always have the same current directory in VIm and in Terminal?

I would like to my terminal current directory follows my VIM one.
Example:
In TERMINAL:
> pwd
=> /Users/rege
> vim
Then in VIM
:cd /Users/rege/project
<Ctrl-z>(for suspend)
In terminal
> pwd
=> /Users/rege/project
I`m using MacOS, zsh, tmux.
I need this because when Im trying to use tags in VIM, tags are check in project from my terminal directory not vim one.
So I need to change terminal current directory always when I change VIM current directory.
What kind of command do you issue in your shell after you suspend Vim? Isn't Vim's :!command enough?
With set autochdir, Vim's current directory follows you as you jump from file to file. With this setting, a simple :!ctags -R . will always create a tags file in the directory of the current file.
Another useful setting is set tags=./tags,tags;$HOME which tells Vim to look for a tags file in the directory of the current file, then in the "current directory" and up and up until it reaches your ~/. You might modify the endpoint to suit your needs. This allows you to use a tags at the root of your project while editing any file belonging to the project.
So, basically, you can go a long way without leaving Vim at all.
If you really need to go back to the shell to issue your commands, :shell (or :sh) launchs a new shell with Vim's current directory. When you are done, you only have to $ exit to go back to Vim:
$ pwd
/home/romainl
$ vim
:cd Projects
:sh
$ pwd
/home/romainl/Projects
$ exit
In bash or zsh and on Unix you can do this: current working directory of the process is represented in /proc/{PID}/cwd as a symlink to a real directory. Speaking about zsh the following code will do the job:
function precmd()
{
emulate -L zsh
(( $#jobstates == 1 )) || return
local -i PID=${${${(s.:.)${(v)jobstates[1]}}[3]}%\=*}
cd $(readlink /proc/$PID/cwd)
}
. Note: with this code you won’t be able to pernamently switch directories in terminal anymore, only in vim or for duration of one command (using cd other-dir && some command).
Note 2: I have no idea how to express this in bash. The straightforward way is to get PIDs of all children of the shell (using ps --ppid $$ -o CMD), filter out the ps process (it will be shown as a child as well), check that there is only one other child and use its PID like in the last line above. But I am pretty sure there is a better way using some shell builtins like I did with zsh’s $jobstates associative array. I also don’t remember what is the analogue of precmd in bash.
Another idea would be making vim save its current directory into some file when you do <C-z> and make shell read this in precmd:
" In .vimrc:
function s:CtrlZ()
call writefile([fnamemodify('.', ':p')], $CWDFILE, 'b')
return "\<C-z>"
endfunction
nnoremap <expr> <C-z> <SID>CtrlZ()
# In .zshrc
function vim()
{
local -x CWDFILE=~/.workdirs/$$
test -d $CWDFILE:h || mkdir $CWDFILE:h
vim $#
}
function precmd()
{
local CWDFILE=~/.workdirs/$$
test -e $CWDFILE && cd "$(cat $CWDFILE)"
}
. It should be easier to port above code to bash.
you can open a new terminal like this
:!xterm -e bash -c "cd %:p:h;bash" &
actually I write this in my .vimrc
nmap <F3> :!xterm -e bash -c "cd %:p:h;bash" &<CR> | :redraw!
For bash users coming by:
Vim: Save pwd at <c-z> (with map and getpwd()).
Bash: Before prompt command, goto directory indicated by vim with PROMPT_COMMAND.
.bashrc
PROMPT_COMMAND='read -r line 2>/dev/null </tmp/cd_vim'\
'&& > /tmp/cd_vim && cd ${line##\r};'$PROMPT_COMMAND
vimrc
function! s:CtrlZ() call writefile([getcwd(),''], '/tmp/cd_vim', 'b')
return "\<C-z>"
endfunction
nnoremap <expr> <C-z> <SID>CtrlZ()
This is ZyX answer edited for bash https://stackoverflow.com/a/12241861/2544873

running command with sudo and go into root (sudo -i) and run the command

Question would be
what exactly is the difference between running these two commands.
As a root, I have made a custom env. variable
export A="abcdef"
then in root shell
sudo -i
echo $A
returns
abcdef (as expected)
However, when I go back to normal user and run
sudo -i echo $A
it returns blank line.
So when you run command sudo echo $A, does it use environment variables and shell from the normal user?
and is there a way to get abcdef even if I run sudo echo $A ?
Thanks
EDIT 1
When you say you have made a variable A as root, I assume you mean you did this in root's .profile or something like that. --> (yes!)
EDIT 2
This makes perfect sense
but having some trouble.
When I do
sudo -i 'echo $A'
I get
-bash: echo $A: command not found.
However when I do
su -c 'echo $A'
it gives back
abcdef
What is wrong with the
sudo -i 'echo $A'
command?
If you want to pass your environment to sudo, use sudo -E:
-E The -E (preserve environment) option indicates to the
security policy that the user wishes to preserve their
existing environment variables. The security policy may
return an error if the -E option is specified and the user
does not have permission to preserve the environment.
The environment is preserved both interactively and through whatever you run from the command line.
When you say you have made a variable A as root, I assume you mean you did this in root's .profile or something like that. And I assume you mean that the normal user does not have A set. In that case the following applies:
When you run your command sudo -i echo $A this is first interpreted by the local shell and $A is substituted. That results in sudo -i echo, which is what is actually executed.
What you mean is this:
sudo -i 'echo $A'
That passes echo $A to the sudo shell.
~ rnapier$ sudo -i echo $USER
rnapier
~ rnapier$ sudo -i 'echo $USER'
root
Try this syntax:
sudo -i echo '$USER'
Although I couldn't replicate the results on my machine, the man page for sudo, specifies the -i option will unset/remove a handful of variables.
man sudo
-i [command]
The -i (simulate initial login) option runs the shell specified in the
passwd(5) entry of the target user as a login shell. This means that
login-specific resource files such as .profile or .login will be read
by the shell. If a command is specified, it is passed to the shell for
execution. Otherwise, an interactive shell is executed. sudo attempts
to change to that user's home directory before running the shell. It
also initializes the environment, leaving DISPLAY and TERM unchanged,
setting HOME , MAIL , SHELL , USER , LOGNAME , and PATH , as well as
the contents of /etc/environment on Linux and AIX systems. All other
environment variables are removed.
So I would try without the -i option.

How to write a shell script that runs some commands as superuser and some commands not as superuser, without having to babysit it?

I want to write a shell script to automate a series of commands. The problem is some commands MUST be run as superuser and some commands MUST NOT be run as superuser. What I have done so far is something like this:
#!/bin/bash
command1
sudo command2
command3
sudo command4
The problem is, this means somebody has to wait until command1 finishes before they are prompted for a password, then, if command3 takes long enough, they will then have to wait for command3 to finish. It would be nice if the person could get up and walk away, then come back an hour later and be done. For example, the following script has this problem:
#!/bin/bash
sleep 310
sudo echo "Hi, I'm root"
sleep 310
sudo echo "I'm still root?"
How can I make it so that the user can just enter their password once, at the very start, and then walk away?
Update:
Thanks for the responses. I'm running on Mac OS X Lion and ran Stephen P's script and got different results: (I also added $HOME)
pair#abbey scratch$ ./test2.sh
uid is 501
user is pair
username is
home directory is /Users/pair
pair#abbey scratch$ sudo ./test2.sh
Password:
uid is 0
user is root
username is root
home directory is /Users/pair
File sutest
#!/bin/bash
echo "uid is ${UID}"
echo "user is ${USER}"
echo "username is ${USERNAME}"
run it: `./sutest' gives me
uid is 500
user is stephenp
username is stephenp
but using sudo: sudo ./sutest gives
uid is 0
user is root
username is stephenp
So you retain the original user name in $USERNAME when running as sudo. This leads to a solution similar to what others posted:
#!/bin/bash
sudo -u ${USERNAME} normal_command_1
root_command_1
root_command_2
sudo -u ${USERNAME} normal_command_2
# etc.
Just sudo to invoke your script in the first place, it will prompt for the password once.
I originally wrote this answer on Linux, which does have some differences with OS X
OS X (I'm testing this on Mountain Lion 10.8.3) has an environment variable SUDO_USER when you're running sudo, which can be used in place of USERNAME above, or to be more cross-platform the script could check to see if SUDO_USER is set and use it if so, or use USERNAME if that's set.
Changing the original script for OS X, it becomes...
#!/bin/bash
sudo -u ${SUDO_USER} normal_command_1
root_command_1
root_command_2
sudo -u ${SUDO_USER} normal_command_2
# etc.
A first stab at making it cross-platform could be...
#!/bin/bash
#
# set "THE_USER" to SUDO_USER if that's set,
# else set it to USERNAME if THAT is set,
# else set it to the string "unknown"
# should probably then test to see if it's "unknown"
#
THE_USER=${SUDO_USER:-${USERNAME:-unknown}}
sudo -u ${THE_USER} normal_command_1
root_command_1
root_command_2
sudo -u ${THE_USER} normal_command_2
# etc.
You should run your entire script as superuser. If you want to run some command as non-superuser, use "-u" option of sudo:
#!/bin/bash
sudo -u username command1
command2
sudo -u username command3
command4
When running as root, sudo doesn't ask for a password.
If you use this, check man sudo too:
#!/bin/bash
sudo echo "Hi, I'm root"
sudo -u nobody echo "I'm nobody"
sudo -u 1000 touch /test_user
Well, you have some options.
You could configure sudo to not prompt for a password. This is not recommended, due to the security risks.
You could write an expect script to read the password and supply it to sudo when required, but that's clunky and fragile.
I would recommend designing the script to run as root and drop its privileges whenever they're not needed. Simply have it sudo -u someotheruser command for the commands that don't require root.
(If they have to run specifically as the user invoking the script, then you could have the script save the uid and invoke a second script via sudo with the id as an argument, so it knows who to su to..)

How to sudo as a different user inside a script?

I have a wrapper script which calls two scripts aaa.sh and bbb.sh. These two scripts should be executed as different users as
sudo -H -u user1
. /user/bin/scripts/aaa.sh
sudo -H -u user1
. /user/bin/scripts/bbb.sh
but the sudo command can't be executed inside a script. Need help...
If you just want to switch users, you should use 'su' not sudo, right?
su user1 -c ./user/bin/scripts/aaa.sh
(that is unless you actually do need elevated privileges)
sudo can be used inside a script, but is the user that executes this script actually allowed to use sudo? Check your /etc/sudoers file.
sudo can be used only if the user name is mapped in /etc/sudoers file as mentioned above. But he may not have the complete priveleges as compared to su user.

Resources