I want to remotely excute a program tcp_sender with root priviledge
,the following function is for making a ssh connection
def connect(hostname):
ssh = paramiko.SSHClient()
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
ssh.connect(hostname, username='usr', pkey=paramiko.RSAKey.from_private_key(open('id_rsa'), 'psw'), timeout = 240.0)
return ssh
then I have 3 solutions:
solution A)
ssh = connect(hostname)
chan = ssh.invoke_shell()
chan.send('sudo ./tcp_sender\n')
with this solution, the remote tcp_sender is not executed, I checked using ps -ef|grep "tcp_sender", there is no process
I tried chan.send('sudo ./tcp_sender > log 2>&1\n')
and in the log, it says:
sudo: no tty present and no askpass program specified
solution B)
ssh = connect(hostname)
(stdin, stdout, stderr) = ssh.exec_command("[ -f tcp_sender ] && echo 1 || echo 0")
res = stdout.readlines()
print hostname,res[0]
if res[0] == '0\n':
UnusedHostFile.write(hostname+'no tcp_sender exists\n')
else:
chan = ssh.invoke_shell()
chan.send("sudo chmod 777 tcp_sender\n")
# if a tcp_sender is runnning, kill it
chan.send('x=`ps -ef|grep "tcp_sender"|grep -v "grep"|awk \'{print $2}\'`; [ -n "${x}" ] && sudo kill -9 $x\n')
time.sleep(4)
while not chan.recv_ready():
time.sleep(1)
buf = ''
buf +=chan.recv(9999)
print buf
chan.send('sudo ./tcp_sender\n')
with this solution, I just add some un-relevant lines, then the remote tcp_sender is running, something like:
bash-4.0# ps -ef|grep "sender"
root 9348 9325 0 Apr07 ? 00:00:00 sudo ./tcp_sender
root 9349 9348 0 Apr07 ? 00:00:00 ./tcp_sender
however, it can't run normally(as expected). In the tcp_sender, there is a fork(), maybe it is due to this?
I tried chan.send('sudo ./tcp_sender > log 2>&1\n')
and in the log, it is empty. Because I have many error-checking related printf in my tcp_sender program, I think there should be printf results in the log, but it is empty.
In addition, I noticed a phenomenon, if I kill -9 9348, all these two processes are ended.
But for the next solution C, the process 9349 will be handed over to system init process 1.
Solution C):
with this solution, I can run the remote tcp_sender correctly. But the python script will be blocked by the remote program until it exits. I don't want my script to wait that the remote exits.
log = open('log','a+')
ssh = connect(hostname)
(stdin, stdout, stderr) = ssh.exec_command("[ -f tcp_sender ] && echo 1 || echo 0")
res = stdout.readlines()
print hostname,res[0]
if res[0] == '0\n':
UnusedHostFile.write(hostname+"tcp_sender doesn't exists\n")
else:
chan = ssh.invoke_shell()
chan.send("sudo chmod 777 tcp_sender\n")
chan.send('x=`ps -ef|grep "tcp_sender"|grep -v "grep"|awk \'{print $2}\'`; [ -n "${x}" ] && sudo kill -9 $x\n')
time.sleep(4)
while not chan.recv_ready():
time.sleep(1)
buf = ''
buf +=chan.recv(9999)
print buf
chan.send('sudo ./tcp_sender\n')
#chan.send('sudo whoami\n')
time.sleep(2)
(stdin, stdout, stderr) = ssh.exec_command("ps -ef|grep 'tcp_sender'|grep -v 'grep'|wc -l")
res = stdout.readlines()
while res[0].strip() != '0':
time.sleep(3)
(stdin, stdout, stderr) = ssh.exec_command("ps -ef|grep 'tcp_sender'|grep -v 'grep'|wc -l")
res = stdout.readlines()
print res[0].strip()
while not chan.recv_ready():
time.slepp(1)
buf = ''
buf += chan.recv(9999)
log.write(hostname+': '+''.join(str(elem) for elem in buf)+'\n\n')
log.close()
so what are potential reasons for this phenomenon?
can anyone give some advice? thanks!
You're mixing things that you should probably keep separate.
First, write a script on the remote side that usr (= username that you give paramiko) can execute and which can correctly start tcp_sender using sudo without asking for a password, etc.
In the script, start sudo as background process using nohup:
nohup sudo ./tcp_sender
nohup makes sure that the new child process is properly detached so it stays alive when the connection is lost/cut.
When this script works, start the new script using ssh.exec_command('script')
Reasoning: It's probably possible to do what you want using a shell and clever Python code that drives the shell as if you were typing the commands. But it will always be brittle, hard to test - it's a variant of the God object.
Instead, split your problem into small, distinct problems that you can develop and test independently. You have three problems to solve:
tcp_sender itself.
Starting tcp_sender
Starting it remotely
so use three distinct tools to solve them.
Agreed with other comment - you are mixing issues which can be solved separately. Many people (including myself) have made this error.
Paramiko is powerful, and smart: it can navigate and respond to SSH username and password prompts. But this is a special case. Most of the time when you need to respond to a PROMPT in Paramiko, you basically wait then shoot text at the assumed prompt. This is messy.
This also has absolutely nothing to do with your real problem.
What you want to do is edit /etc/sudoers file so that your automation test user or group is able to run the precise command you want using NOPASSWD.
Let's say I want to remotely grep /var/log/auth.log on host "ServerB". While grep can be run by any user, it's known that auth.log on this system is only readable by user root. SO:
1) My test user is "scott", and a member of group "adm. See /etc/groups and /etc/passwd. Basic stuff.
2) /etc/sudoers:
%adm ALL=(ALL)NOPASSWD:/bin/grep
3) From a remote system, I run:
$ ssh scott#ServerB "sudo grep Accepted /var/log/auth.log"
2013-10-14T21:17:54+00:00 proc4-01-us1 sshd[28873]: Accepted publickey for scott from x.x.x.x port 56799 ssh2
2013-10-14T21:19:16+00:00 proc4-01-us1 sshd[29367]: Accepted publickey for scott from x.x.x.x port 56804 ssh2
2013-10-14T21:19:21+00:00 proc4-01-us1 sshd[29519]: Accepted publickey for scott from x.x.x.x port 56805 ssh2
Bang, you're done.
NOTES
DO use an absolute filesystem path to the script you specify in sudoers.
DO use SSH keys. You can use keys +passphrase if you like. (Remember, Paramiko can answer login prompts) But this also means storing your passphrase in the script...
DO consider security. You're not really lessoning security here if you attach this permission to a special user. Certainly the method I describe is more secure than hardcoding a sudo password into the script.
DON'T use NOPASSWD:ALL except in testing. Specify what is allowed explicitly.
DO consider adding restrictions to what these users can run. For example, if I'm always running this test from an EC2 box, I'd only allow that user to connect from that EC2 IP. Conversationally, I could restrict what commands can be run by a user by adding prefixes in "authorized"keys" (ie, restricting that user to only being able to run rsync command, if I wanted to avoid running an rsync server full-time for example).
Related
I have to implement a scenario where I need to login as root and then perform some ssh and scp commands through python to automate certain workflows.
Example: Suppose MACHINE12 is my host and current user is test, i.e test#MACHINE12
I want a interactive code to be written in such a way that , if I put the following commands it should login as root user and the session should be maintained to perform other operations in python.
{test#MACHINE12} su
password:
once password is entered, logged in as root.
{test#MACHINE12 test}
Perform other commands here
{test#MACHINE12 test} ssh user1#abc
I tried this using the pexpect command in python, but the root session is not maintained or staying to perform other operations.
The commands can be only executed if the logged in session is root because abc host need root permission.
Find my tried snippet here:
def loginAsroot():
childTab = pexpect.spawn("su") //command su to enter to root
childTab.expect(":")
childTab.sendline("asdgfgh") // entering password
childTab.expect("#")
def runCommands():
command = "ssh user1#abc 'cat /dfg/hello.txt'>> /doc/g/auth.txt"
process = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True)
output, error = process.communicate(timeout=10000)
return output
//function call
loginAsroot()
runCommands()
I'am trying to do linux command execution it is giving permission denied error, because the root session is not staying, its getting logged out. Hence Permission denied
How can I achieve this kind of executions?
You're trying to use both pexpect and subprocess. These are possibly spawning different sessions. Does the following work?
import pexpect
child = pexpect.spawn('su')
child.expect(':')
child.sendline('asdgfgh')
child.expect('#')
command = "ssh user1#abc 'cat /dfg/hello.txt'>> /doc/g/auth.txt"
child.sendline(command)
child.expect('#')
I think using either of the modules throughout would solve your issue.
For SSH, I like Paramiko, because it takes care of overhead. To demonstrate, I wrote two versions of the code: one using Pexpect all the way, and the other using Paramiko. Just remember:
Make sure the IP address of the remote machine is not in the /root/.ssh/known_hosts; otherwise you get the WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED! error.
I prefer expect_exact because expect uses regex patterns. With expect, you can get an error if you look for a prompts that use regex characters, such as $.
I also prefer using su - over su if I have to use root, since it clears the environment variables.
Thanks for the question, by the way. Sometimes I forget what I just told you to remember :facepalm: !
NOTE - Tested using two Debian VM's and Python 3.9. I changed the prompts and obscured the passwords and IP addresses.
Using Pexpect (with everything echoed to STDOUT):
import sys
import pexpect
child = pexpect.spawn("su -", logfile=sys.stdout.buffer)
child.expect_exact("Password:")
child.sendline("**********")
child.expect_exact("#")
child.sendline("ssh user#192.168.X.X")
while True:
# Must use expect_exact to avoid expect regex conflict with prompt ($)!
index = child.expect_exact(
["Are you sure you want to continue connecting", "password:", "$", ])
if index == 0:
child.sendline("yes")
elif index == 1:
child.sendline("**********")
else:
break
child.sendline("cat hello.txt >> auth.txt")
child.expect_exact("$")
child.sendline("cat auth.txt")
child.expect_exact("$")
# If you do not use the logfile, you can make sure the file got copied using:
# print(child.before)
child.sendline("exit")
child.expect_exact("#")
child.sendline("exit")
# The child closes after the exit command (EOF); this is just to make sure
child.expect_exact(["$", pexpect.EOF, ])
child.close()
print("Script complete. Have a nice day.")
Output:
Password: **********
test#MACHINE12:~# ssh user1#192.168.X.X
ssh user1#192.168.X.X
The authenticity of host '192.168.X.X (192.168.X.X)' can't be established.
RSA key fingerprint is SHA256:********************************************
This key is not known by any other names
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
yes
Warning: Permanently added '192.168.X.X' (RSA) to the list of known hosts.
**********#192.168.X.X's password: **********
Last login: Sat Dec 4 20:40:12 2021 from 192.168.X.X
user1#192.168.X.X:~$ cat hello.txt >> auth.txt
cat hello.txt >> auth.txt
user1#192.168.X.X:~$ cat auth.txt
cat auth.txt
Hello, world!
user1#192.168.X.X:~$ b' cat auth.txt\r\nHello, world!\r\nuser1#192.168.X.X:~'
exit
exit
logout
Connection to 192.168.X.X closed.
test#MACHINE12:~# exit
exit
Script complete. Have a nice day.
Process finished with exit code 0
Using Paramiko (without echoing):
import paramiko
import pexpect
child = pexpect.spawn("su -")
child.expect_exact("Password:")
child.sendline("**********")
child.expect_exact("#")
ssh = paramiko.SSHClient()
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
ssh.connect("192.168.X.X", username="user1", password="**********")
ssh.exec_command("cat hello.txt >> auth.txt")
_, chan_out, _ = ssh.exec_command("cat auth.txt")
print("\nauth contents:", chan_out.read().decode())
ssh.close()
child.sendline("exit")
child.expect_exact(["$", pexpect.EOF, ])
child.close()
print("Script complete. Have a nice day.")
Output:
auth contents: Hello, world!
Good luck with your code!
I am trying to run shell comment through PHP scripts.
I want to first su into a user then run a sudo command. I tried:
echo mypassword | (su -c "sudo reboot" user2)
It doesn't work because it requires two password and I only passed one password. I checked many other posts, the solution below doesn't work for me because I don't have the sudo password for the current user. I need to change the user a first then do a sudo. Can I get some help?
echo mypassword | sudo -s ... Not work...
I know this is a bad practice. I just need it to restart server as the port 22 is closed accidentally. I can't ssh into the server to do any operations..
This is ONE time use to reboot the server from PHP side as I am not able to reach the admin to reboot the server right now. I fully understand the disadvantages... Please DO NOT suggest the disadvantages.
Since it's an one time thing, try to use the following. It spawns a sudo environment (for the current user) in which sudo reboot is called.
#! /bin/bash
read -sp "pass? " pass
expect 2>&1 <<-EOF
spawn sudo reboot
expect "*: " { send "${pass}\r" }
expect eof
catch wait result
exit [lindex \$result 3]
EOF
exit $?
You can call it as follows to automate stuff.
$ ./test.sh <<-EOF
notreallymypassword
EOF
Note: Although I think it works, I haven't tested it yet.
I am having some issues trying to connect through telnet to a mail server.The main problem that I am having is that I need to create a script that logs me to the destination and I can't find a way to echo the password.
What I tried:
telnet host -l username ; echo 'password'
And still it asks for my password.Is there any way to fix this or I am doing something wrong?
First of all, you can use eval:
eval "{ echo user_name; sleep 1; echo pass; sleep 1; echo '?'; sleep 5; }" | telnet host_address
Make sure to replace user_name, pass, ? which is the command you want to run and host_address where your telnet host is listening; for me it is a local IP.
It’s surprisingly easy to script a set of command and pipe them into the telnet application. All you need to do is something like this:
(echo commandname;echo anothercommand) | telnet host_address
The only problem is the nagging login that you have to get through… it doesn’t show up right away. So if you pipe in an “echo admin” and then “echo password,” it will happen too quickly and won’t be sent to the server. The solution? Use the sleep command!
Adding in a couple of sleep 3 commands, to wait three seconds, solves the problem. First we’ll echo the username and password, and then we’ll echo the reboot command, and each time we’ll wait three seconds between. The final command will reboot the server immediately:
(sleep 3;echo admin;sleep 3;echo mypassword;sleep 3;echo system reboot;sleep 3;) | telnet host_address
You can put this into a shell script and run it whenever you want. Or you can add it to your cron like this (on OS X or Linux):
crontab -e
Add this line somewhere:
1 7 * * * (sleep 3;echo admin;sleep 3;echo mypassword;sleep 3;echo system reboot;sleep 3;) | telnet host_address
This will reboot your router at 7:01 AM each morning.
AFAIK, you won't be able to automate telnet that way. But it is still possible - even if it is a very bad idea (I'll elaborate on that later).
First why does your try fail :
you launched a telnet command reading from stdin (I suppose terminal) and writing to stdout and stderr (I suppose also a terminal)
if your telnet is reasonably recent, it tries to protect your authentication and asks your password from /dev/tty (for security reasons)
when that command has ended you write password on your own terminal
What should you do instead :
launch telnet with automatic authentication disable (on mine it is telnet -X SRA)
feed its input with the commands you want to pass
wait some delay before entering input, at least for login and password, because if you don't telnet clear input before reading and discards your inputs
Here is an example that allowed me to telnet to my own machine :
sh << EOF | telnet -X SRA localhost
sleep 2
echo my_user_name
sleep 1
echo my_password
# sleep 1 # looks like it can be removed
echo echo foo and bar
sleep 1
EOF
It correctly logs me into my box, executes echo foo and bar (essential command :-) ) and disconnects
Now why you should never do that :
you write a password in clear text in a script file which is poor security practice
you use telnet to do batch processing when it is not intended to be used that way : the script may not be portable to another telnet version
If you really want to pass command in a batch way to a remote server, you should instead try to use ssh which :
has options to process authentication securely (no password in script, nothing in clear text)
is intended to be used in batch mode as well as interactively
If you cannot use ssh (some sysadmin do not like to have uncontrolled input ssh connections) you could also try to use rsh. It is older, far less secure, but at least was designed for batch usage.
Thanks to Harvix answer, I got knew that there is also expect alternative native for shell, called sexpect. Get it from here. Then create this script (I call it telnetpass):
#!/bin/bash
# This script is used for automatically pass authentication by username and password in telnet prompt
# Its goal is similar as sshpass, but for telnet, so I call it telnetpass
. ~/.private/cisco_pw # should contain PASSWORD variable
export SEXPECT_SOCKFILE=/tmp/sexpect-telnetpass-$$.sock
sexpect spawn telnet $1
sexpect expect -cstring 'Username:'
sexpect send -enter $USER
sexpect expect -cstring 'Password:'
sexpect send -enter $PASSWORD
sexpect interact
Then you can run: telnetpass Host125 and got pass the authentication automatically
Trying 198.51.100.78 ...
Connected to Host125.
Escape character is '^]'.
User Access Verification
Username: ashark
Password:
host-125>
I like this solution more than using sleep commands as suggested in another answers, because sleep solutions sometimes fail.
Have you tried using the expect command ?? You will have to create a script where you identify the 'expected' response from the server e.g. 'Password:' and then supply the password in the script. The following will explain: https://en.wikipedia.org/wiki/Expect - A good example is also shown here: http://en.kioskea.net/faq/4736-shell-script-for-telnet-and-run-commands
Try eval:
eval "{ echo;
sleep 3;
echo $user;
sleep 1;
echo $pass;
sleep 1;
echo '?';
sleep 1; }" | telnet your_host
In this example, my remote command is '?' (help).
The sleeps (maybe not all of them nor these times; trial-error...) are needed to avoid telnet misses some inputs.
The user and password are passed as variables ($user and $pass). Take into account security recommendations to store the password if you are scripting.
I am trying to pipe commands to an opened SSH session. The commands will be generated by a script, analyzing the results, and sending the next commands in accordance.
I do not want to put all the commands in a script on the remote host, and just run that script, because I am interested also in the status of the SSH process: sending locally the commands allow to "test" whether the SSH connection is alive or not, and get the appropriate return code from the SSH process.
I tried using something along these lines:
$ mkfifo /tpm/commands
$ ssh -t remote </tmp/commands
And from another term:
$ echo "command" >> /tmp/commands
Problem: SSH tells me that no pseudo-tty will be opened for stdin, and closes the connection as soon as "command" terminates.
I tried another approach:
$ ssh -t remote <<EOF
$(echo "command"; while true; do sleep 10; echo "command"; done)
EOF
But then, nothing is flushed to ssh until EOF is reached (in my case, never).
Do any of you have a solution ?
Stop closing /tmp/commands before you're done with it. When you close the pipe, ssh stops reading from it.
exec 7> /tmp/commands. # open once
echo foo >&7 # write multiple times
echo bar >&7
exec 7>&- # close once
You can additionally use ssh -tt to force ssh to open a tty on the remote.
I am running the following simple telnet script which just logs into a machine and exits.
The same script works fine (goes through 1000 iterations) from one Linux server but fails (consistently) from another Linux server (fails after say 200 attempts).
In failure case, the number of iterations it takes to fail varies but failure is persistent.
#!/usr/bin/perl
use Net::Telnet;
my $loop = 0;
my $dumpfile = "dump.log";
my $inputfile = "input.log";
for ($loop =1; $loop <=1000; $loop++) {
print "===============Loop: $loop =====================\n";
$telnet = new Net::Telnet ( Timeout=>20, Errmode=>'die');
print "$telnet\n";
$telnet->open('sys-007');
$telnet->dump_log($dumpfile);
$telnet->input_log($inputfile);
$telnet->waitfor('/login: $/i');
$telnet->print('root');
$telnet->waitfor('/Password:$/i');
$telnet->print('007');
sleep 2;
$telnet->print('exit');
print "=================================================\n";
}
Script exits with:
pattern match read eof at ./telnettest.pl line 15 (i.e, waitfor('/login: $/i'); Line)
I tried the following to see what is going wrong:
In the client machine: (sys-007)
tcpdump -nvvv -w test.txt host <Server IP>
strings test.txt has:
Successful attempt Log:
sys-007 (ttyp0)
^Fl$4
^!^Fl$
login:
^Fl$4
^Fl$4
root
^(^Fl*
root
bP"u
^Fl*4
bP5u
^.^Fl*
Password:
^Fl*4
^Fl*4
007
^7^Fl6
^Fl64
^9^Fl6
Terminal type? [xterm]
^Fl64
^Fl64
exit
^Fl<4
^Fl<
exit
^Fl<
Failed Attempt:
sys-007 (ttyp0)
*^hn
+^hn
No login: prompt!
In server machine: (Linux Server)
[telnet#server]~% netstat --inet -a | grep telnet | grep sys-007
There are no TIME_WAIT or CLOSE_WAIT sockets.
Please tell me what should I look for to find out what is going wrong.
Are you able to login normally after this? I'm guessing this other server is cutting you off because you've made too many connections.
What I suspect is that the telnet program is waiting for the prompt. I usually use the below format to define the prompt, so that the script usually is able to find the prompt from the below given options.
test = new Net::Telnet (Timeout => 3000 , Prompt => '/[%#\$>?:] $/' );
$test->open($IPAddress1);
$test->login($Login,$Password1);
my #input=$test->cmd("uname -a\n");
print "Connected to : #input";
#input=$cmd->cmd("pwd\n");
Please let us know if you still face the problem.