Total noob here. I'm trying to create a python object and execute methods in an instance therein and it seems the code block I want to execute just won't run. The code block in question is run_job which when called just seems to do nothing. What am I doing wrong?
import datetime
import uuid
import paramiko
class scan_job(object):
def __init__(self, protocol, target, user_name, password, command):
self.guid = uuid.uuid1()
self.start_time = datetime.datetime.now()
self.target = target
self.command = command
self.user_name = user_name
self.password = password
self.protocol = protocol
self.result = ""
def run_job(self):
if self.protocol == 'SSH':
ssh = paramiko.SSHClient()
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
try:
print "creating connection"
ssh.connect(self.target, self.user_name, self.password)
print "connected"
stdin, stdout, stderr = ssh.exec_command(self.command)
for line in stdout:
print '... ' + line.strip('\n')
self.result += line.strip('\n')
yield ssh
finally:
print "closing connection"
ssh.close()
print "closed"
else:
print "Unknown protocol"
def show_command(self):
print self.command
test = scan_job('SSH', '192.168.14.10', 'myuser', 'mypassword', 'uname -n')
test.show_command()
test.run_job()
Your method contains a yield statement, which makes it a generator. Generators are evaluated lazily. Consider:
>>> def gen():
... yield 10
... yield 3
... yield 42
...
>>> result = gen()
>>> next(result)
10
>>> next(result)
3
>>> next(result)
42
>>>
This is likely not what you intended to do.
Yield is a keyword that is used like return, except the function will
return a generator.
To read more about generators:
1) Understanding Generators in Python
2) What does the "yield" keyword do in Python?
3) Understanding the Yield Keyword in Python
All you need to do is, change:
yield ssh
To:
return ssh
So that, run_job will execute like a normal function, until it reaches its end, exception or return statement. However, if you want to run it without changing the yield statement. Here is how you can do it:
x = test.run_job()
x.next()
Related
I want to answer a input() from another thread of the same process on python from within the code.
This is the code:
import sys
import threading
def threaded(fn):
def wrapper(*args, **kwargs):
thread = threading.Thread(target=fn, args=args, kwargs=kwargs, daemon=True)
thread.start()
return thread
return wrapper
#threaded
def answer():
time.sleep(2)
sys.stdin.write('to be inputed')
answer()
x = input('insert a value: ')
print(f'value inserted: {x}') # excpeted print: 'value inserted: to be inputed'
But I think its not possbile because I receive this error:
Exception in thread Thread-1:
Traceback (most recent call last):
File "teste.py", line 80, in answer
sys.stdin.write('to be inputed')
io.UnsupportedOperation: not writable
It's hard to explain why I want that, but sometimes the user will input the value and sometimes it will come from another input source (telegram). So this second thread should be able to input the value and release the code execution.
I also can't change the input() part of the code because its from inside a library, so it need to be this way: input('insert a value: ')
Is there a way to achive that?
The simple answer is that if you replace sys.stdin with your own variable, then input uses that instead.
However, then you've lost your original stdin, so you need start a new process to listen for user input, since you said:
but sometimes the user will input the value
This needs to be another process rather than a thread since it needs to be killed when you want to restore the original stdin, and killing the process interrupts it mid-readline.
Here is a working version of the code with the mock object implemented. The region inside the with block is where stdin has been replaced.
import sys
import time
import multiprocessing
import threading
class MockStdin:
def __init__(self):
self.queue = None
self.real_stdin = sys.stdin
self.relay_process = None
def readline(self):
# when input() is called, it calls this function
return self.queue.get()
def writeline(self, s):
# for input from elsewhere in the program
self.queue.put(s)
def relay_stdin(self):
# for input from the user
my_stdin = open(0) # this is a new process so it needs its own stdin
try:
while True:
inp = my_stdin.readline()
self.queue.put(inp)
except KeyboardInterrupt:
# when killed, exit silently
pass
def __enter__(self):
# when entering the `with` block, start replace stdin with self and relay real stdin
self.queue = multiprocessing.Queue()
self.relay_process = multiprocessing.Process(target=self.relay_stdin)
self.relay_process.start()
sys.stdin = self
def __exit__(self, exc_type=None, exc_val=None, exc_tb=None):
# when exiting the `with` block, put stdin back and stop relaying
sys.stdin = self.real_stdin
self.relay_process.terminate()
self.relay_process.join()
def __getstate__(self):
# this is needed for Windows - credit to Leonardo Rick for this fix
self_dict = self.__dict__.copy()
del self_dict['real_stdin']
return self_dict
def threaded(fn):
def wrapper(*args, **kwargs):
thread = threading.Thread(target=fn, args=args, kwargs=kwargs, daemon=True)
thread.start()
return thread
return wrapper
if __name__ == '__main__':
mock = MockStdin()
#threaded
def answer():
time.sleep(2)
# use mock to write to stdin
mock.writeline('to be inputed')
answer()
with mock:
# inside `with` block, stdin is replaced
x = input('insert a value: ')
print(f'\nvalue inserted: {x}')
answer()
# __enter__ and __exit__ can also be used
mock.__enter__()
x = input('insert a value: ')
print(f'\nvalue inserted: {x}')
mock.__exit__()
# now outside the `with` block, stdin is back to normal
x = input('insert another (stdin should be back to normal now): ')
print(f'value inserted: {x}')
I am maintaining an operator terminal based on cmd. The customer asked for an alerting behavior. e.g. a message shown onscreen when some asynchronous event occurs. I made a thread that periodically checks for alerts, and when it finds some, it just prints them to stdout.
This seems to work OK, but it doesn't seem very elegant, and it has a problem:
Because cmd doesn't know an alert happened, the message is followed onscreen by blank. The command prompt is not reprinted, and any user input is left pending.
Is there a better way to do asynchronous alerts during Python cmd? With the method as-is, can I interrupt cmd and get it to redraw its prompt?
I tried from my thread to poke a newline in stdin using StringIO, but this is not ideal, and I haven't gotten it work right.
Example code:
import cmd, sys
import threading, time
import io
import sys
class MyShell(cmd.Cmd):
intro = '*** Terminal ***\nType help or ? to list commands.\n'
prompt = '> '
file = None
def alert(self):
time.sleep(5)
print ('\n\n*** ALERT!\n')
sys.stdin = io.StringIO("\n")
def do_bye(self, arg):
'Stop recording, close the terminal, and exit: BYE'
print('Exiting.')
sys.exit(0)
return True
def do_async(self, arg):
'Set a five second timer to pop an alert.'
threading.Thread(target=self.alert).start()
def emptyline(self):
pass
def parse(arg):
'Convert a series of zero or more numbers to an argument tuple'
return tuple(map(int, arg.split()))
if __name__ == '__main__':
MyShell().cmdloop()
I ended up overriding Cmd.cmdloop with my own version, replacing the readlines() with my own readlines that use non-blocking terminal IO.
Non-Blocking terminal IO info here:
Non-Blocking terminal IO
Unfortunately, this opens another can trouble in that it is messy and breaks auto-completion and command history. Fortunately, the customer was OK with having to push Enter to redo the prompt, so I don't need to worry about it anymore.
Incomplete example code showing the non-blocking terminal input approach:
import cmd, sys
import threading, time
import io
import os
if os.name=='nt':
import msvcrt
def getAnyKey():
if msvcrt.kbhit():
return msvcrt.getch()
return None
else:
import sys
import select
import tty
import termios
import atexit
def isData():
return select.select([sys.stdin], [], [], 0) == ([sys.stdin], [], [])
old_settings = termios.tcgetattr(sys.stdin)
def restoreSettings():
global old_settings
termios.tcsetattr(sys.stdin, termios.TCSADRAIN, old_settings)
atexit.register(restoreSettings)
def getAnyKey():
try:
if isData():
return sys.stdin.read(1)
return None
except:
pass
return None
class MyShell(cmd.Cmd):
prompt = '> '
file = None
realstdin = sys.stdin
mocking=False
breakReadLine=False
def alert(self):
time.sleep(5)
print ('\n\n*** ALERT!\n')
self.breakReadLine=True
# ----- basic commands -----
def do_bye(self, arg):
'Stop recording, close the terminal, and exit: BYE'
print('Exiting.')
sys.exit(0)
return True
def do_async(self, arg):
'Set a five second timer to pop an alert.'
threading.Thread(target=self.alert).start()
def emptyline(self):
pass
def myReadLine(self):
sys.stdout.flush()
self.breakReadLine=False
line=''
while not self.breakReadLine:
c=getAnyKey()
if not c is None:
c=c.decode("utf-8")
if c=='\x08' and len(line):
line=line[0:-1]
elif c in ['\r','\n']:
print('\n')
return line
else:
line+=c
print(c,end='')
sys.stdout.flush()
def mycmdloop(self, intro=None):
"""Repeatedly issue a prompt, accept input, parse an initial prefix
off the received input, and dispatch to action methods, passing them
the remainder of the line as argument.
"""
self.preloop()
if self.use_rawinput and self.completekey:
try:
import readline
self.old_completer = readline.get_completer()
readline.set_completer(self.complete)
readline.parse_and_bind(self.completekey+": complete")
except ImportError:
pass
try:
if intro is not None:
self.intro = intro
if self.intro:
self.stdout.write(str(self.intro)+"\n")
stop = None
while not stop:
if self.cmdqueue:
line = self.cmdqueue.pop(0)
else:
if self.use_rawinput:
try:
print(self.prompt,end='')
line = self.myReadLine()#input(self.prompt)
except EOFError:
line = 'EOF'
else:
self.stdout.write(self.prompt)
self.stdout.flush()
line = self.myReadLine()#self.stdin.readline()
if not line is None:
line = line.rstrip('\r\n')
line = self.precmd(line)
stop = self.onecmd(line)
stop = self.postcmd(stop, line)
self.postloop()
finally:
if self.use_rawinput and self.completekey:
try:
import readline
readline.set_completer(self.old_completer)
except ImportError:
pass
def cmdloop_with_keyboard_interrupt(self, intro):
doQuit = False
while doQuit != True:
try:
if intro!='':
cintro=intro
intro=''
self.mycmdloop(cintro)
else:
self.intro=''
self.mycmdloop()
doQuit = True
except KeyboardInterrupt:
sys.stdout.write('\n')
def parse(arg):
'Convert a series of zero or more numbers to an argument tuple'
return tuple(map(int, arg.split()))
if __name__ == '__main__':
#MyShell().cmdloop()
MyShell().cmdloop_with_keyboard_interrupt('*** Terminal ***\nType help or ? to list commands.\n')
I have written a code to write parallel in a csv file in python.
When my program gets over, what I see is that few lines are merged instead of in seperate lines. Each line should only contain 3 columns. But instead it shows as below
EG
myname myage myvalue
myname myage myvaluemyname
myname myage myvalue
myage
What I understood by reading few other questions, is that I need to lock my file if I want to avoid such scenarios. So I added fcntl module. But it seems my file is still not being locked as it produces similar output
My code
def getdata(x):
try:
# get data from API
c.writefile(x,x1,x2)
except Exception,err:
print err
class credits:
def __init__(self):
self.d = dict()
self.details = dict()
self.filename = "abc.csv"
self.fileopen = open(self.filename,"w")
def acquire(self):
fcntl.flock (self.fileopen, fcntl.LOCK_EX)
def release(self):
fcntl.flock(self.fileopen, fcntl.LOCK_UN)
def __del__(self):
self.fileopen.close()
def writefile(self,x,x1,x2,x3):
try:
self.acquire()
self.fileopen.write(str(x)+","+str(x1)+","+str(x2)+"\n")
except Exception, e:
raise e
finally:
self.release()
if __name__ == '__main__':
conn = psycopg2.connect()
curr = conn.cursor(cursor_factory=psycopg2.extras.DictCursor)
curr.execute("select * from emp")
rows = curr.fetchall()
listdata = []
for each in rows:
listdata.append(each[0])
c = credits()
p = Pool(processes = 5)
results = p.map(getdata,listdata)
conn.close()
I had to declare getdata as TOP level function otherwise it gave me "Cant pickle function"
Why don't you write to multiple files in each separate process and then merge them? It might be more computationally expensive but it will ensure thread safety.
I wrote the following python module to handle ssh connections in my program:
#!/usr/bin/env python
from vxpty import VX_PTY
class SSHError(Exception):
def __init__(self, msg):
self.msg = msg
def __str__(self):
return repr(self.msg)
class SSHShell:
def __init__(self, host, port, user, password):
self.host = host
self.port = port
self.user = user
self.password = password
self.authenticated = False
def authenticate(self):
self.tty = VX_PTY(['/usr/bin/ssh', 'ssh', '-p'+str(self.port), self.user+'#'+self.host])
resp = self.tty.read()
if "authenticity of host" in resp:
self.tty.println('yes')
while 1:
resp = self.tty.read()
if "added" in resp:
break
resp = self.tty.read()
if "assword:" in resp:
self.tty.println(self.password)
tmp_resp = self.tty.read()
tmp_resp += self.tty.read()
if "denied" in tmp_resp or "assword:" in tmp_resp:
raise(SSHError("Authentication failed"))
else:
self.authenticated = True
self.tty.println("PS1=''")
return self.authenticated
def execute(self, os_cmd):
self.tty.println(os_cmd)
resp_buf = self.tty.read().replace(os_cmd+'\r\n', '')
return resp_buf
Which uses a pty module I wrote earlier:
#!/usr/bin/env python
import os,pty
class PTYError(Exception):
def __init__(self, msg):
self.msg = msg
def __str__(self):
return repr(self.msg)
class VX_PTY:
def __init__(self, execlp_args):
self.execlp_args = execlp_args
self.pty_execlp(execlp_args)
def pty_execlp(self, execlp_args):
(self.pid, self.f) = pty.fork()
if self.pid==0:
os.execlp(*execlp_args)
elif self.pid<0:
raise(PTYError("Failed to fork pty"))
def read(self):
data = None
try:
data = os.read(self.f, 1024)
except Exception:
raise(PTYError("Read failed"))
return data
def write(self, data):
try:
os.write(self.f, data)
except Exception:
raise(PTYError("Write failed"))
def fsync(self):
os.fsync(self.f)
def seek_end(self):
os.lseek(self.f, os.SEEK_END, os.SEEK_CUR)
def println(self, ln):
self.write(ln+'\n')
However, whenever I call the execute() method, I end up reading the output from the first line:
>>> import SSH;shell=SSH.SSHShell('localhost',22,'735tesla','notmypassword');shell.authenticate()
True
>>> shell.execute('whoami')
"\x1b[?1034hLaptop:~ 735Tesla$ PS1=''\r\n"
>>>
Then the second time I call read() I get the output:
>>> shell.tty.read()
'whoami\r\n735Tesla\r\n'
>>>
Removing whoami\r\n from the output is not problem but is there any way to clear the output so I don't have to call read twice with the first command?
I think your problem is deeper than you realize. Luckily, it's also easier to solve than you realize.
What you seem to want is for os.read to return the entirety of what the shell has to send to you in one call. That's not something you can ask for. Depending on several factors, including, but not limited to, the shell's implementation, network bandwidth and latency, and the behavior of the PTYs (yours and the remote host's), the amount of data you'll get back in each call to read can be as much as, well, everything, and as little as a single character.
If you want to receive just the output of your command, you should bracket it with unique markers, and don't worry about messing with PS1. What I mean is that you need to make the shell output a unique string before your command executes and another one after your command executes. Your tty.read method should then return all the text it finds in between these two marker strings. The easiest way to make the shell output these unique strings is just to use the echo command.
For multiline commands, you have to wrap the command in a shell function, and echo the markers before and after executing the function.
A simple implementation is as follows:
def execute(self, cmd):
if '\n' in cmd:
self.pty.println(
'__cmd_func__(){\n%s\n' % cmd +
'}; echo __"cmd_start"__; __cmd_func__; echo __"cmd_end"__; unset -f __cmd_func__'
)
else:
self.pty.println('echo __"cmd_start"__; %s; echo __"cmd_end"__' % cmd)
resp = ''
while not '__cmd_start__\r\n' in resp:
resp += self.pty.read()
resp = resp[resp.find('__cmd_start__\r\n') + 15:] # 15 == len('__cmd_start__\r\n')
while not '_cmd_end__' in resp:
resp += self.pty.read()
return resp[:resp.find('__cmd_end__')]
So, I wanted to extend the Python smtpd SMTPServer class so that it could handle SMTP AUTH connections. Seemed simple enough...
So, it looked like I could just start like this:
def smtp_EHLO(self, arg):
print 'got in arg: ', arg
# do stuff here...
But for some reason, that never gets called. The Python smtpd library calls other similar methods like this:
method = None
i = line.find(' ')
if i < 0:
command = line.upper()
arg = None
else:
command = line[:i].upper()
arg = line[i+1:].strip()
method = getattr(self, 'smtp_' + command, None)
Why won't it call my method?
After that, I thought that I could probably just override the entire found_terminator(self): method, but that doesn't seem to work either.
def found_terminator(self):
# I add this to my child class and it never gets called...
Am I doing something stupid or...? Maybe I just haven't woken up fully yet today...
import smtpd
import asyncore
class CustomSMTPServer(smtpd.SMTPServer):
def smtp_EHLO(self, arg):
print 'got in arg: ', arg
def process_message(self, peer, mailfrom, rcpttos, data):
print 'Receiving message from:', peer
print 'Message addressed from:', mailfrom
print 'Message addressed to :', rcpttos
print 'Message length :', len(data)
print 'HERE WE ARE MAN!'
return
# Implementation of base class abstract method
def found_terminator(self):
print 'THIS GOT CALLED RIGHT HERE!'
line = EMPTYSTRING.join(self.__line)
print >> DEBUGSTREAM, 'Data:', repr(line)
self.__line = []
if self.__state == self.COMMAND:
if not line:
self.push('500 Error: bad syntax')
return
method = None
i = line.find(' ')
if i < 0:
command = line.upper()
arg = None
else:
command = line[:i].upper()
arg = line[i+1:].strip()
method = getattr(self, 'smtp_' + command, None)
print 'looking for: ', command
print 'method is: ', method
if not method:
self.push('502 Error: command "%s" not implemented' % command)
return
method(arg)
return
else:
if self.__state != self.DATA:
self.push('451 Internal confusion')
return
# Remove extraneous carriage returns and de-transparency according
# to RFC 821, Section 4.5.2.
data = []
for text in line.split('\r\n'):
if text and text[0] == '.':
data.append(text[1:])
else:
data.append(text)
self.__data = NEWLINE.join(data)
status = self.__server.process_message(self.__peer,
self.__mailfrom,
self.__rcpttos,
self.__data)
self.__rcpttos = []
self.__mailfrom = None
self.__state = self.COMMAND
self.set_terminator('\r\n')
if not status:
self.push('250 Ok')
else:
self.push(status)
server = CustomSMTPServer(('127.0.0.1', 1025), None)
asyncore.loop()
You need to extend SMTPChannel -- that's where the smtp_verb methods are implemented; your extension of SMTPServer just needs to return your own subclass of the channel.
TL&DR: To add additional functionality to SMTPChannel you just need to declare a function, and then add it directly to smtpd.SMTPChannel
Explanation:
The SMTPChannel class is designed to respond to the commands that are entered by the user on the open port (typically port 25). The way it searches for which commands it can answer is based off 'Introspection' where it examines all the available attributes of the function.
Take note that the functions within SMTPChannel need to start with the "smtp_". For Example, if you wanted to respond to HELP you would create smtpd.SMTPChannel.smtp_HELP.
The Function below is from the source-code that details the introspection
class SMTPChannel(asynchat.async_chat):
method = getattr(self, 'smtp_' + command, None)
CodeThatWorks
Step 1: Declare a FUNCTION that will be called
def smtp_HELP(self,arg):
self.push("[8675309] GPT Answers to HELP")
Step 2: Add the following function to smtpd.SMTPChannel
class FakeSMTPServer(smtpd.SMTPServer):
"""A Fake smtp server"""
smtpd.SMTPChannel.smtp_HELP = smtp_HELP
Step 3: Telnet to localhost 25 and test out
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
220 vics-imac.fios-router.home ESMTP Sendmail 6.7.4 Sunday 17 March 2019
HELP
[8675309] GPT Answers to HELP