Using the passwd command from within a shell script - linux

I'm writing a shell script to automatically add a new user and update their password. I don't know how to get passwd to read from the shell script instead of interactively prompting me for the new password. My code is below.
adduser $1
passwd $1
$2
$2

from "man 1 passwd":
--stdin
This option is used to indicate that passwd should read the new
password from standard input, which can be a pipe.
So in your case
adduser "$1"
echo "$2" | passwd "$1" --stdin
[Update] a few issues were brought up in the comments:
Your passwd command may not have a --stdin option: use the chpasswd
utility instead, as suggested by ashawley.
If you use a shell other than bash, "echo" might not be a builtin command,
and the shell will call /bin/echo. This is insecure because the password
will show up in the process table and can be seen with tools like ps.
In this case, you should use another scripting language. Here is an example in Perl:
#!/usr/bin/perl -w
open my $pipe, '|chpasswd' or die "can't open pipe: $!";
print {$pipe} "$username:$password";
close $pipe

The only solution works on Ubuntu 12.04:
echo -e "new_password\nnew_password" | (passwd user)
But the second option only works when I change from:
echo "password:name" | chpasswd
To:
echo "user:password" | chpasswd
See explanations in original post: Changing password via a script

Nowadays, you can use this command:
echo "user:pass" | chpasswd

Read the wise words from:
http://mywiki.wooledge.org/BashFAQ/078
I quote:
Nothing you can do in bash can possibly work. passwd(1) does not read from standard input. This is intentional. It is for your protection. Passwords were never intended to be put into programs, or generated by programs. They were intended to be entered only by the fingers of an actual human being, with a functional brain, and never, ever written down anywhere.
Nonetheless, we get hordes of users asking how they can circumvent 35 years of Unix security.
It goes on to explain how you can set your shadow(5) password properly, and shows you the GNU-I-only-care-about-security-if-it-doesn't-make-me-think-too-much-way of abusing passwd(1).
Lastly, if you ARE going to use the silly GNU passwd(1) extension --stdin, do not pass the password putting it on the command line.
echo $mypassword | passwd --stdin # Eternal Sin.
echo "$mypassword" | passwd --stdin # Eternal Sin, but at least you remembered to quote your PE.
passwd --stdin <<< "$mypassword" # A little less insecure, still pretty insecure, though.
passwd --stdin < "passwordfile" # With a password file that was created with a secure `umask(1)`, a little bit secure.
The last is the best you can do with GNU passwd. Though I still wouldn't recommend it.
Putting the password on the command line means anyone with even the remotest hint of access to the box can be monitoring ps or such and steal the password. Even if you think your box is safe; it's something you should really get in the habit of avoiding at all cost (yes, even the cost of doing a bit more trouble getting the job done).

Here-document works if your passwd doesn't support --stdin and you don't want to (or can't) use chpasswd for some reason.
Example:
#!/usr/bin/env bash
username="user"
password="pass"
passwd ${username} << EOD
${password}
${password}
EOD
Tested under Arch Linux. This passwd is an element of shadow-utils and installed from the core/filesystem package, which you usually have by default since the package is required by core/base.

You could use chpasswd
echo $1:$2 | chpasswd

For those who need to 'run as root' remotely through a script logging into a user account in the sudoers file, I found an evil horrible hack, that is no doubt very insecure:
sshpass -p 'userpass' ssh -T -p port user#server << EOSSH
sudo -S su - << RROOT
userpass
echo ""
echo "*** Got Root ***"
echo ""
#[root commands go here]
useradd -m newuser
echo "newuser:newpass" | chpasswd
RROOT
EOSSH

I stumbled upon the same problem and for some reason the --stdin option was not available on the version of passwd I was using (shipped in Ubuntu 14.04).
If any of you happen to experience the same issue, you can work it around as I did, by using the chpasswd command like this:
echo "<user>:<password>" | chpasswd

Tested this on a CentOS VMWare image that I keep around for this sort of thing. Note that you probably want to avoid putting passwords as command-line arguments, because anybody on the entire machine can read them out of 'ps -ef'.
That said, this will work:
user="$1"
password="$2"
adduser $user
echo $password | passwd --stdin $user

This is the definitive answer for a teradata node admin.
Go to your /etc/hosts file and create a list of IP's or node names in a text file.
SMP007-1
SMP007-2
SMP007-3
Put the following script in a file.
#set a password across all nodes
printf "User ID: "
read MYUSERID
printf "New Password: "
read MYPASS
while read -r i; do
echo changing password on "$i"
ssh root#"$i" sudo echo "$MYUSERID":"$MYPASS" | chpasswd
echo password changed on "$i"
done< /usr/bin/setpwd.srvrs
Okay I know I've broken a cardinal security rule with ssh and root
but I'll let you security folks deal with it.
Now put this in your /usr/bin subdir along with your setpwd.srvrs config file.
When you run the command it prompts you one time for the User ID
then one time for the password. Then the script traverses all nodes
in the setpwd.srvrs file and does a passwordless ssh to each node,
then sets the password without any user interaction or secondary
password validation.

For me on Raspbian it works only this way (old password added):
#!/usr/bin/env bash
username="pi"
password="Szevasz123"
new_ps="Szevasz1234"
passwd ${username} << EOD
${password}
${new_ps}
${new_ps}
EOD

Have you looked at the -p option of adduser (which AFAIK is just another name for useradd)? You may also want to look at the -P option of luseradd which takes a plaintext password, but I don't know if luseradd is a standard command (it may be part of SE Linux or perhaps just an oddity of Fedora).

Sometimes it is useful to set a password which nobody knows. This seems to work:
tr -dc A-Za-z0-9 < /dev/urandom | head -c44 | passwd --stdin $user

echo 'yourPassword' | sudo -S yourCommand
if -S doesnt work try with -kS

You can use the expect utility to drive all programs that read from a tty (as opposed to stdin, which is what passwd does). Expect comes with ready to run examples for all sorts of interactive problems, like passwd entry.

Related

How can I script a remote connection that is elevated via su?

I have scripts and programs which run commands on a remote computer, and I need some of the commands to be elevated, I.e. su. Since this is scripted, I cannot rely on a user to enter the password; it needs to be passed to su by the script.
I have tried a bunch of things, including echoing the password to su like so:
ssh user#host "echo password | su -c myCommand"
and
echo password | ssh user#host su -c myCommand
and in programming languages like C# creating a new process and reading from its stdout and writing the password to its stdin.
I've tried some alternate things others have mentioned online, but they generally involve commands that I do not have on some of the machines. Some of them have no expect or sudo or other alternatives others have mentioned.
How can I do this without installing any other tools?
The su tag says general su support is off topic, but this is not about su support or usage, it's specifically about a programming problem which requires elevated su use, so hopefully it's not taken as off topic. "Linux & Unix" and "Super User" users may be interested in this question too, but it really applies more to programming.
TL;DR: Use echo pass | ssh -t -t user#host 'su -c "command"'
This one gave me headaches and took a while to figure out. There are hints about the answer elsewhere, but I have not seen the answer stated explicitly anywhere, so it took a bit of playing around with to get right.
Using ssh
echo <password> | ssh -t -t <user>#<host> 'su -c "<command>"'
The quotes seem to be necessary. If I omitted one or the other pair of quotes I got incorrect results for some commands. For example, <…> ssh -t -t me#mypc su -c "ls -l /root/something_secure" would give me the ls contents of me's home directory, ignoring the -l and the /root/something_secure. So I had to have the quotes set up like that.
The -t is where the magic is at for our need, and yes you should put 2 of them. Doing echo pass | su -c command can fail because su doesn't take a password from standard input. Doing echo pass | ssh <…> su -c command seems like a good idea at first, because su needs to get input from ssh, and ssh gets it from standard input, but really su gets it from a "tty", not from standard input. The -t tells ssh to fake it by using a pseudo-tty which sends standard input to this fake tty. Sometimes ssh will complain and not want to allocate the pseudo-tty for you, so doing the -t twice tells ssh to shut up and do it anyway.
-t' Force pseudo-tty allocation. This can be used to execute arbitrary screen-based programs on a remote machine, which can be very useful,
e.g. when implementing menu services. Multiple -t options force tty
allocation, even if ssh has no local tty.
From man ssh
Example:
echo MySecretPass | ssh -t -t me#mydevice 'su -c "chown me /some/file"'
Ssh does not have a password parameter. If you have not set up a password-less secure login, you should do so. Read up here. Otherwise, this answer will supply the password to su for you, but you would still have to supply the password to ssh for the initial ssh login.
Using plink (Putty command line ssh-like tool for Windows)
echo <password> | plink -t -t -pw <password> <user>#<host> "su -c '<command>'"
Again, the quotes seemed to be necessary. If I omitted them, the results were incorrect, similar as with using ssh.
Example:
echo RootPass | plink -t -t -pw MePass me#mydevice "su -c 'echo Rooty!> /root/rootiness'"
Reasoning is similar to the ssh section above.
Your favorite programming language
If your favorite programming language supports the ability to execute a shell command and control the standard input/output for it, then instead of piping an echo you can instead run the ssh or plink command above (minus the piped echo), then put the password into standard input.
So for C#
Process process = new Process();
// If I remember correctly, I think UseShellExecute needs to be false to redirect std in/out
process.StartInfo.UseShellExecute = false;
process.StartInfo.RedirectStandardOutput = true;
process.StartInfo.RedirectStandardError = true;
process.StartInfo.RedirectStandardInput = true;
// You can use either ssh or plink; I'll use plink here
process.StartInfo.FileName = "<path to plink>\\plink.exe";
process.StartInfo.Arguments = "-t -t -l " + username + " -pw " + password + " " + hostname + " 'su -c \"MyCommand\"'";
process.StartInfo.CreateNoWindow = true;
process.OutputDataReceived += new DataReceivedEventHandler(FunctionCallbackYouMake);
process.ErrorDataReceived += new DataReceivedEventHandler(AnotherFunctionYouMake);
// I'm not actually sure if this next line is necessary, but I've been using it because of an example I used
process.SynchronizingObject = null;
process.Start();
And then read the data coming in through the ssh (plink) connection by using the function callback you make and provide to OutputDataReceived. It has the form void f(object sending_process, DataReceivedEventArgs event)
You can write data to process.StandardInput, including the su password, such as
process.StandardInput.Write(password);
process.StandardInput.Flush();
I've done something similar in Java as well, so I know you can do pretty much the same thing there, but it's been so long I don't recall all the details, but it also involves a process object and setting up std in/out handlers. If I have time later I might include some of those details.
No matter what language or method you use, some programs may block if they get too much stuff in their output streams or error streams, so make sure that you register for both standard output and standard error even if you don't care about them, even if all you do is read their data and discard it.
But don't hard-code the password!
Since this is for programming, there is generally no need to hard-code passwords into scripts. You could if you absolutely had to, but preferably use variables place of the username, password, and host name so that you are more secure and your tool is more flexible.
For example, I have a line in Windows cmd which does the following
echo %password% | plink -t -t -pw %password% %username%#%ip% "su -c '<what I want to do goes here>'"
And the username, password, and ip are supplied as arguments to the cmd file. Also note the C# example above, they are variables.

Script to change root password authenticating with password enabled sudo account

I want to change root password of multiple server's. I used shell with for loop and chpasswd utility to do this. Since the sudo account is password enabled, it is prompting sudo password all the time I exit script.
Below is bash Script is written in bash. But always prompting for password.
#!/bin/bash
pass="PASSWORD"
for i in $(cat serverlist)
do
ssh -t sudouser#$i "sudo chpasswd <<EOF
root:"$pass"
EOF" ;
done
Completely automated bash to change root password.
I also think you should use expect. The script I've written isn't fully tested, since I don't have a server which I'm conformable on to change passwords :-)
#!/bin/bash
read -p "Server username? " USERNAME
read -sp "Server password for ${USERNAME}? " PASSWORD
echo
read -p "Name of file containing server list? " S_FILE
read -p "User to change on servers? " S_USERNAME
read -sp "New password for user ${S_USERNAME}?" S_PASSWORD
echo
while IFS= read -r SERVER; do
[ ! -z "${SERVER}" ] || continue
expect <<-EOF
spawn ssh ${USERNAME}#${SERVER}
expect "*: " { send "${PASSWORD}\r" }
expect "*$ " { send "echo '${S_USERNAME}:${S_PASSWORD}' | sudo chpasswd\r" }
expect "*: " { send "${PASSWORD}\r" }
expect "*$ " { send "exit\r" }
EOF
echo
done < ${S_FILE}
exit $?
Writing a script to do unattended root things is dangerous. All you need is one machine to somehow behave differently than you expect and your automated approach wouldn't work. Worse, you could end up in some bad state, possibly without even realizing that anything went wrong.
This sounds like a great fit for csshx (or something similar). Use it to manually apply whatever changes you want in parallel across multiple hosts. For example, you could connect to 16 hosts at once like this:
csshx host[1-16]
then type commands and watch output for each host.
If this seems infeasible due to the number of machines you have, I would counter that it's much safer than scripting, and - even if "slow" - the overall time spent might very well be less than that of trying to create an automated solution. ;)

'su' by using 'script' in Docker returns different results compared to the standard environment

I need to request certain commands via su including password in one line.
I found a solution and it is working in a standard environment (Ubuntu) (more about solution here):
{ sleep 1; echo password; } | script -qc 'su -l user -c id' /dev/null | tail -n +2
But I am faced with the problem that this solution is not suitable in a Docker container environment
Script terminates the command without waiting for echo and as a result i get:
su: Authentication failure
Any help is much appreciated.
Passing the password for su via stdin is problematic for various reasons: the biggest one is probably that your password will end up in the history.
You could instead:
Call the entire script as the specific user and thus enter the password manually
Use sudo with the appropriate NOPASSWD sudoers configuration
In your case you are using docker, so you could just set the USER in your Dockerfile

Having an issue passing variables to subshell

So here is my problem, I have this script I wrote where I'm exporting two variables however they're not making it into the subshell.
The point of this script is to change a users password and clear out their pam_tally for CentOS and Ubuntu hosts.
A little background is that this environment's users are managed by puppet but the passwords are all local, ssh keys are not allowed either (this is set in stone and can't be changed so I have to work with what I got) and the reason is that every log in has to be manual (even number of sessions are limited to two so you can't even user csshX effectively).
Here is my script
#!/bin/bash
echo "Please enter user whose password you want to change"
read NEWUSER
echo "Please enter new password for user"
read -s -p "Temp Password:" TEMPPASSWORD
PASSWORD=$TEMPPASSWORD
export PASSWORD
NEWUSER2=$NEWUSER
export NEWUSER2
for i in HOST{cluster1,cluster2,cluster3}0{1..9}
do
ping -c 2 $i && (echo $i ; ssh -t $i '
sudo pam_tally2 --user=$NEWUSER2 --reset
echo -e "$PASSWORD\n$PASSWORD" | sudo passwd $NEWUSER2
sudo chage -d 0 $NEWUSER2
')
done
You are using ssh to connect to a remote host and run a script on that host. ssh does not export the local environment to the remote session but instead performs a login on the remote host which sets the environment according to the remote user's configuration on the remote host.
I suggest you pass all needed values via the command you want to execute. This could be done like this:
ssh -t $i '
sudo pam_tally2 --user='"$NEWUSER2"' --reset
echo -e "'"$PASSWORD"'\n'"$PASSWORD"'" | sudo passwd '"$NEWUSER2"'
sudo chage -d 0 '"$NEWUSER2"
Watch closely how this uses quotes. At each occasion where you used a variable, I terminate the single-quoted string (using '), then add a double-quoted use of the variable (e. g. "$PASSWORD") and then start the single-quoted string again (using ' again). This way, the shell executing the ssh command will expand the variables already, so you have no need to pass them into the remote shell.
But be aware that special characters in the password (like " or ' or or maybe a bunch of other characters) can mean trouble using this simple mechanism. To be safe against this as well, you would need to use the %q format specifier of the printf command to quote your values before passing them:
ssh -t "$i" "$(printf '
sudo pam_tally2 --user=%q --reset
{ echo %q; echo %q; } | sudo passwd %q
sudo chage -d 0 %q' \
"$NEWUSER2" "$PASSWORD" "$PASSWORD" "$NEWUSER2" "$NEWUSER2")"

Most reliable way to identify the current user through a sudo

I have an application that may or may not be run while users are sudo'ed to a shared user account. I would like to reliably identify who the real user is for a sort of "honor-system" ACL. I think there's some way by tracing parent/group/session process ids the way that the pstree command does, but I'm not sure how to do that best or if there are better alternatives.
I tried getlogin() originally. That works if ./myapp is used, but it fails with 'cat input | ./myapp` (because the "controlling terminal" is a pipe owned by the shared account).
I'd rather not trust environment variables, as I don't want my "honor system" to be completely thwarted by a simply unset, when the information is still available elsewhere.
I'd also like to avoid forcing a lookup in the password database, as that is a remote RPC (NIS or LDAP) and I'm pretty sure wtmp already contains the information I need.
For a shell script, you might use this to get the sudo'ing user:
WHO=$(who am i | sed -e 's/ .*//'`)
and extract the id from the login using:
ID_WHO=$(id -u $WHO)
I'll ferret out the C library equivalent later.
sudo sets the environment variables SUDO_USER, SUDO_UID, and SUDO_GID.
You can test this with:
$ sudo env
[sudo] password for shteef:
TERM=xterm
# [...snip...]
SHELL=/bin/bash
LOGNAME=root
USER=root
USERNAME=root
SUDO_COMMAND=/usr/bin/env
SUDO_USER=shteef
SUDO_UID=1000
SUDO_GID=1000
But if your users have shell access on the shared account, then I suppose you cannot blindly trust this either.
How about:
#!/usr/bin/ksh
username=`id | cut -d"=" -f2 | cut -d" " -f1`
if [ $username == "0(root)" ]
then
print "Yes, the user is root"
else
print "Sorry! the user $username, is not a root"
fi

Resources