I need a class which gives me a list of avahi services of a certain type which a currently available. Therefor I run a gobject.MainLoop() (line 23-25) in a separate thread an add a browser for each service I am interested in (line 27,28). This works in principle.
My problem is, that I do not get always all services. Sometimes all services that are available are listed, sometimes none of them, sometimes just a few. My guess is, that the browser starts iterating the services (line 36) before the appropriate signals are connected (line 41-44), but I have no clue how to fix this. Below a minimal example which shows the failure.
Most examples I have seen on the net (e.g.: Stopping the Avahi service and return a list of elements) run the MainLoop after the browser is set up and the signals are connected. The loop is then quit when the "AllForNow" signal is received. This is not an option for me as the Browser should keep running and listen for new or removed services (which works reliable by the way, just the initial query is problematic).
#!/usr/bin/python
import dbus
from dbus.mainloop.glib import DBusGMainLoop
import avahi
import gobject
import threading
gobject.threads_init()
dbus.mainloop.glib.threads_init()
class ZeroconfBrowser:
def __init__(self):
self.service_browsers = set()
self.services = {}
self.lock = threading.Lock()
loop = DBusGMainLoop(set_as_default=True)
self._bus = dbus.SystemBus(mainloop=loop)
self.server = dbus.Interface(
self._bus.get_object(avahi.DBUS_NAME, avahi.DBUS_PATH_SERVER),
avahi.DBUS_INTERFACE_SERVER)
thread = threading.Thread(target=gobject.MainLoop().run)
thread.daemon = True
thread.start()
self.browse("_ssh._tcp")
self.browse("_http._tcp")
def browse(self, service):
if service in self.service_browsers:
return
self.service_browsers.add(service)
with self.lock:
browser = dbus.Interface(self._bus.get_object(avahi.DBUS_NAME,
self.server.ServiceBrowserNew(avahi.IF_UNSPEC,
avahi.PROTO_UNSPEC, service, 'local', dbus.UInt32(0))),
avahi.DBUS_INTERFACE_SERVICE_BROWSER)
browser.connect_to_signal("ItemNew", self.item_new)
browser.connect_to_signal("ItemRemove", self.item_remove)
browser.connect_to_signal("AllForNow", self.all_for_now)
browser.connect_to_signal("Failure", self.failure)
def resolved(self, interface, protocol, name, service, domain, host,
aprotocol, address, port, txt, flags):
print "resolved", interface, protocol, name, service, domain, flags
def failure(self, exception):
print "Browse error:", exception
def item_new(self, interface, protocol, name, stype, domain, flags):
with self.lock:
self.server.ResolveService(interface, protocol, name, stype,
domain, avahi.PROTO_UNSPEC, dbus.UInt32(0),
reply_handler=self.resolved, error_handler=self.resolve_error)
def item_remove(self, interface, protocol, name, service, domain, flags):
print "removed", interface, protocol, name, service, domain, flags
def all_for_now(self):
print "all for now"
def resolve_error(self, *args, **kwargs):
with self.lock:
print "Resolve error:", args, kwargs
import time
def main():
browser = ZeroconfBrowser()
while True:
time.sleep(3)
for key, value in browser.services.items():
print key, str(value)
if __name__ == '__main__':
main()
I couldn't find an installable python-dbus package. However I did find an example Avahi browser which works great -- avahi.py
pip install python-tdbus
source
#!/usr/bin/env python
#
# This file is part of python-tdbus. Python-tdbus is free software
# available under the terms of the MIT license. See the file "LICENSE" that
# was provided together with this source file for the licensing terms.
#
# Copyright (c) 2012 the python-tdbus authors. See the file "AUTHORS" for a
# complete list.
# This example shows how to access Avahi on the D-BUS.
from tdbus import SimpleDBusConnection, DBUS_BUS_SYSTEM, DBusHandler, signal_handler, DBusError
import logging
logging.basicConfig(level=logging.DEBUG)
CONN_AVAHI = 'org.freedesktop.Avahi'
PATH_SERVER = '/'
IFACE_SERVER = 'org.freedesktop.Avahi.Server'
conn = SimpleDBusConnection(DBUS_BUS_SYSTEM)
try:
result = conn.call_method(PATH_SERVER, 'GetVersionString',
interface=IFACE_SERVER, destination=CONN_AVAHI)
except DBusError:
print 'Avahi NOT available.'
raise
print 'Avahi is available at %s' % CONN_AVAHI
print 'Avahi version: %s' % result.get_args()[0]
print
print 'Browsing service types on domain: local'
print 'Press CTRL-c to exit'
print
result = conn.call_method('/', 'ServiceTypeBrowserNew', interface=IFACE_SERVER,
destination=CONN_AVAHI, format='iisu', args=(-1, 0, 'local', 0))
browser = result.get_args()[0]
print browser
class AvahiHandler(DBusHandler):
#signal_handler()
def ItemNew(self, message):
args = message.get_args()
print 'service %s exists on domain %s' % (args[2], args[3])
conn.add_handler(AvahiHandler())
conn.dispatch()
output
Avahi is available at org.freedesktop.Avahi
Avahi version: avahi 0.6.31
Browsing service types on domain: local
Press CTRL-c to exit
/Client1/ServiceTypeBrowser1
service _udisks-ssh._tcp exists on domain local
service _workstation._tcp exists on domain local
service _workstation._tcp exists on domain local
service _udisks-ssh._tcp exists on domain local
Related
I implemented a Twisted SSH server to test a component that uses fabric to run commands on a remote machine via SSH. I have found this example but I don't understand how I have to implement the execCommand method to be compatible with fabric. Here is my implementation of the SSH server:
from pathlib import Path
from twisted.conch import avatar, recvline
from twisted.conch.insults import insults
from twisted.conch.interfaces import ISession
from twisted.conch.ssh import factory, keys, session
from twisted.cred import checkers, portal
from twisted.internet import reactor
from zope.interface import implementer
SSH_KEYS_FOLDER = Path(__file__).parent.parent / "resources" / "ssh_keys"
#implementer(ISession)
class SSHDemoAvatar(avatar.ConchUser):
def __init__(self, username: str):
avatar.ConchUser.__init__(self)
self.username = username
self.channelLookup.update({b"session": session.SSHSession})
def openShell(self, protocol):
pass
def getPty(self, terminal, windowSize, attrs):
return None
def execCommand(self, protocol: session.SSHSessionProcessProtocol, cmd: bytes):
protocol.write("Some text to return")
protocol.session.conn.sendEOF(protocol.session)
def eofReceived(self):
pass
def closed(self):
pass
#implementer(portal.IRealm)
class SSHDemoRealm(object):
def requestAvatar(self, avatarId, _, *interfaces):
return interfaces[0], SSHDemoAvatar(avatarId), lambda: None
def getRSAKeys():
with open(SSH_KEYS_FOLDER / "ssh_key") as private_key_file:
private_key = keys.Key.fromString(data=private_key_file.read())
with open(SSH_KEYS_FOLDER / "ssh_key.pub") as public_key_file:
public_key = keys.Key.fromString(data=public_key_file.read())
return public_key, private_key
if __name__ == "__main__":
sshFactory = factory.SSHFactory()
sshFactory.portal = portal.Portal(SSHDemoRealm())
users = {
"admin": b"aaa",
"guest": b"bbb",
}
sshFactory.portal.registerChecker(checkers.InMemoryUsernamePasswordDatabaseDontUse(**users))
pubKey, privKey = getRSAKeys()
sshFactory.publicKeys = {b"ssh-rsa": pubKey}
sshFactory.privateKeys = {b"ssh-rsa": privKey}
reactor.listenTCP(22222, sshFactory)
reactor.run()
Trying to execute a command via fabric yields the following output:
[paramiko.transport ][INFO ] Connected (version 2.0, client Twisted_22.4.0)
[paramiko.transport ][INFO ] Authentication (password) successful!
Some text to return
This looks promising but the program execution hangs after this line. Do I have to close the connection from the server side after executing the command? How do I implement that properly?
In a traditional UNIX server, it's still the server's responsibility to close the connection if it was told to execute a command. It's up to the server's discretion where and how to do this.
I believe you just want to change protocol.session.conn.sendEOF(protocol.session) to protocol.loseConnection(). I apologize for not testing this myself to be sure, setting up an environment to properly test a full-size conch setup like this is a bit tedious (and the example isn't self-contained, requiring key generation and moduli, etc)
I'm trying to write a Twisted-based mock DNS server to do some tests.
Taking inspiration from this guide I wrote a very simple server that just resolves everything to 127.0.0.1:
from twisted.internet import defer, reactor
from twisted.names import dns, error, server
class MockDNSResolver:
def _doDynamicResponse(self, query):
name = query.name.name
record = dns.Record_A(address=b"127.0.0.1")
answer = dns.RRHeader(name=name, payload=record)
authority = []
additional = []
return [answer], authority, additional
def query(self, query, timeout=None):
print("Incoming query for:", query.name)
if query.type == dns.A:
return defer.succeed(self._doDynamicResponse(query))
else:
return defer.fail(error.DomainError())
if __name__ == "__main__":
clients = [MockDNSResolver()]
factory = server.DNSServerFactory(clients=clients)
protocol = dns.DNSDatagramProtocol(controller=factory)
reactor.listenUDP(10053, protocol)
reactor.listenTCP(10053, factory)
reactor.run()
The above is working just fine with dig and nslookup (from a different terminal):
$ dig -p 10053 #localhost something.example.org A +short
127.0.0.1
$ nslookup something.else.example.org 127.0.0.1 -port=10053
Server: 127.0.0.1
Address: 127.0.0.1#10053
Non-authoritative answer:
Name: something.else.example.org
Address: 127.0.0.1
I'm also getting the corresponding hits on the terminal that's running the server:
Incoming query for: something.example.org
Incoming query for: something.else.example.org
Then, I wrote the following piece of code, based on this section about making requests and this section about installing a custom resolver:
from twisted.internet import reactor
from twisted.names.client import createResolver
from twisted.web.client import Agent
d = Agent(reactor).request(b'GET', b'http://does.not.exist')
reactor.installResolver(createResolver(servers=[('127.0.0.1', 10053)]))
def callback(result):
print('Result:', result)
d.addBoth(callback)
d.addBoth(lambda _: reactor.stop())
reactor.run()
But this fails (and I get no lines in the server terminal). It appears as if the queries are not going to the mock server, but to the system-defined server:
Result: [Failure instance: Traceback: <class 'twisted.internet.error.DNSLookupError'>: DNS lookup failed: no results for hostname lookup: does.not.exist.
/.../venv/lib/python3.6/site-packages/twisted/internet/_resolver.py:137:deliverResults
/.../venv/lib/python3.6/site-packages/twisted/internet/endpoints.py:921:resolutionComplete
/.../venv/lib/python3.6/site-packages/twisted/internet/defer.py:460:callback
/.../venv/lib/python3.6/site-packages/twisted/internet/defer.py:568:_startRunCallbacks
--- <exception caught here> ---
/.../venv/lib/python3.6/site-packages/twisted/internet/defer.py:654:_runCallbacks
/.../venv/lib/python3.6/site-packages/twisted/internet/endpoints.py:975:startConnectionAttempts
]
I'm using:
macOS 10.14.6 Python 3.6.6, Twisted 18.9.0
Linux Mint 19.1, Python 3.6.9, Twisted 19.7.0
I appreciate any help, please let me know if additional information is required.
Thanks!
The solution was:
(Client) swap the order of the lines that install the resolver and create the deferred for the request, as suggested by #Hadus. I thought this didn't matter, since the reactor was not running yet.
(Server) implement lookupAllRecords, reusing the existing _doDynamicResponse method.
# server
from twisted.internet import defer, reactor
from twisted.names import dns, error, server
class MockDNSResolver:
"""
Implements twisted.internet.interfaces.IResolver partially
"""
def _doDynamicResponse(self, name):
print("Resolving name:", name)
record = dns.Record_A(address=b"127.0.0.1")
answer = dns.RRHeader(name=name, payload=record)
return [answer], [], []
def query(self, query, timeout=None):
if query.type == dns.A:
return defer.succeed(self._doDynamicResponse(query.name.name))
return defer.fail(error.DomainError())
def lookupAllRecords(self, name, timeout=None):
return defer.succeed(self._doDynamicResponse(name))
if __name__ == "__main__":
clients = [MockDNSResolver()]
factory = server.DNSServerFactory(clients=clients)
protocol = dns.DNSDatagramProtocol(controller=factory)
reactor.listenUDP(10053, protocol)
reactor.listenTCP(10053, factory)
reactor.run()
# client
from twisted.internet import reactor
from twisted.names.client import createResolver
from twisted.web.client import Agent
reactor.installResolver(createResolver(servers=[('127.0.0.1', 10053)]))
d = Agent(reactor).request(b'GET', b'http://does.not.exist:8000')
def callback(result):
print('Result:', result)
d.addBoth(callback)
d.addBoth(lambda _: reactor.stop())
reactor.run()
$ python client.py
Result: <twisted.web._newclient.Response object at 0x101077f98>
(I'm running a simple web server with python3 -m http.server in another terminal, otherwise I get a reasonable twisted.internet.error.ConnectionRefusedError exception).
This is how you should make a request to it:
from twisted.internet import reactor
from twisted.names import client
resolver = client.createResolver(servers=[('127.0.0.1', 10053)])
d = resolver.getHostByName('does.not.exist')
def callback(result):
print('Result:', result)
d.addBoth(callback)
reactor.callLater(4, reactor.stop)
reactor.run()
And the MockDNSResolvershould be a subclass of twisted.names.common.ResolverBase and then you only have to implement _lookup.
class MockDNSResolver(common.ResolverBase):
def _lookup(self, name, cls, type, timeout):
print("Incoming query for:", name)
record = dns.Record_A(address=b"127.0.0.5")
answer = dns.RRHeader(name=name, payload=record)
return defer.succeed(([answer], [], []))
Full code: https://pastebin.com/MfGahtAV
I’m building an Python application that has to connect and disconnect from Wifi on linux box. I’m using NetworkManager layer, through the nice networkmanager lib found in cnetworkmanager (a python CLI for NetworkManager http://vidner.net/martin/software/cnetworkmanager/ thanx to Martin Vidner), in a daemon (named stationd).
This daemon runs a gobject.MainLoop. Once a timeout_add_seconds awake (triggers by a action of user in GUI), I have to disconnect current running Wifi and connect to a new one:
from dbus.mainloop.glib import DBusGMainLoop
DBusGMainLoop(set_as_default=True)
from networkmanager import NetworkManager
import networkmanager.applet.settings as settings
from networkmanager.applet import USER_SERVICE
from networkmanager.applet.service import NetworkManagerUserSettings, NetworkManagerSettings
import time
import memcache
import gobject
loop = gobject.MainLoop()
nm = NetworkManager()
dummy_handler = lambda *args: None
cache = memcache.Client(['127.0.0.1:11211',] )
def get_device(dev_spec, hint):
candidates = []
devs = NetworkManager().GetDevices()
for dev in devs:
if dev._settings_type() == hint:
candidates.append(dev)
if len(candidates) == 1:
return candidates[0]
for dev in devs:
if dev["Interface"] == dev_spec:
return dev
print "Device '%s' not found" % dev_spec
return None
def kill_allconnections():
connections=nm['ActiveConnections']
for c in connections:
print c.object_path
nm.DeactivateConnection(c.object_path)
class Wifi(object):
def connect(self, ssid, security="open", password=None):
"Connects to given Wifi network"
c=None # connection settings
us = NetworkManagerUserSettings([])
if security=="open":
c = settings.WiFi(ssid)
elif security=="wep":
c = settings.Wep(ssid, password)
elif security=="wpa":
c = settings.WpaPsk(ssid, password)
else:
raise AttributeError("invalid security model '%s'"%security)
svc = USER_SERVICE
svc_conn = us.addCon(c.conmap)
hint = svc_conn.settings["connection"]["type"]
dev = get_device("", hint)
appath = "/"
nm.ActivateConnection(svc, svc_conn, dev, appath, reply_handler=dummy_handler, error_handler=dummy_handler)
def change_network_settings():
key="station:network:change"
change=cache.get(key)
if change is not None:
print "DISCONNECT"
kill_allconnections()
print "CHANGE SETTINGS"
wifi=cache.get(key+':wifi')
if wifi is not None:
ssid=cache.get(key+':wifi:ssid')
security=cache.get(key+':wifi:security')
password=cache.get(key+':wifi:password')
print "SWITCHING TO %s"%ssid
Wifi().connect(ssid, security, password)
cache.delete(key)
return True
def mainloop():
gobject.timeout_add_seconds(1, change_network_settings)
try:
loop.run()
except KeyboardInterrupt:
loop.quit()
if __name__=="__main__":
mainloop()
This runs perfectly for a first connection (read : the box is not connected, daemon is ran and box connects flawlessly to Wifi). Issue is when I try to connect to another Wifi : kill_allconnections() is ran silently, and connect method raises an exception on nm.ActivateConnection:
Traceback (most recent call last):
File "stationd.py", line 40, in change_network_settings
Wifi().connect(ssid, security, password)
File "/home/biopredictive/station/lib/network.py", line 88, in connect
us = NetworkManagerUserSettings([])
File "/home/biopredictive/station/lib/networkmanager/applet/service/__init__.py", line 71, in __init__
super(NetworkManagerUserSettings, self).__init__(conmaps, USER_SERVICE)
File "/home/biopredictive/station/lib/networkmanager/applet/service/__init__.py", line 33, in __init__
dbus.service.Object.__init__(self, bus, opath, bus_name)
File "/usr/lib/pymodules/python2.6/dbus/service.py", line 480, in __init__
self.add_to_connection(conn, object_path)
File "/usr/lib/pymodules/python2.6/dbus/service.py", line 571, in add_to_connection
self._fallback)
KeyError: "Can't register the object-path handler for '/org/freedesktop/NetworkManagerSettings': there is already a handler"
It looks like my former connection didn’t release all its resources ?
I’m very new to gobject/dbus programming. Would you please help ?
I have answered on the D-Bus mailing list. Quoting here for the archive:
class Wifi(...):
def connect(...):
...
us = NetworkManagerUserSettings([])
NetworkManagerUserSettings is a server, not a client. (It's a design
quirk of NM which was eliminated in NM 0.9)
You should create only one for the daemon, not for each conection
attempt.
Can anyone recommend something for making a ssh connection in python?
I need it to be compatible with any OS.
I've already tried pyssh only to get an error with SIGCHLD, which I've read is because Windows lacks this.
I've tried getting paramiko to work, but I've had errors between paramiko and Crypto to the point where the latest versions of each won't work together.
Python 2.6.1 currently on a Windows machine.
Notice that this doesn't work in Windows.
The module pxssh does exactly what you want:
For example, to run 'ls -l' and to print the output, you need to do something like that :
from pexpect import pxssh
s = pxssh.pxssh()
if not s.login ('localhost', 'myusername', 'mypassword'):
print "SSH session failed on login."
print str(s)
else:
print "SSH session login successful"
s.sendline ('ls -l')
s.prompt() # match the prompt
print s.before # print everything before the prompt.
s.logout()
Some links :
Pxssh docs : http://dsnra.jpl.nasa.gov/software/Python/site-packages/Contrib/pxssh.html
Pexpect (pxssh is based on pexpect) : http://pexpect.readthedocs.io/en/stable/
Twisted has SSH support : http://www.devshed.com/c/a/Python/SSH-with-Twisted/
The twisted.conch package adds SSH support to Twisted. This chapter shows how you can use the modules in twisted.conch to build SSH servers and clients.
Setting Up a Custom SSH Server
The command line is an incredibly efficient interface for certain tasks. System administrators love the ability to manage applications by typing commands without having to click through a graphical user interface. An SSH shell is even better, as it’s accessible from anywhere on the Internet.
You can use twisted.conch to create an SSH server that provides access to a custom shell with commands you define. This shell will even support some extra features like command history, so that you can scroll through the commands you’ve already typed.
How Do I Do That?
Write a subclass of twisted.conch.recvline.HistoricRecvLine that implements your shell protocol. HistoricRecvLine is similar to twisted.protocols.basic.LineReceiver , but with higher-level features for controlling the terminal.
Write a subclass of twisted.conch.recvline.HistoricRecvLine that implements your shell protocol. HistoricRecvLine is similar to twisted.protocols.basic.LineReceiver, but with higher-level features for controlling the terminal.
To make your shell available through SSH, you need to implement a few different classes that twisted.conch needs to build an SSH server. First, you need the twisted.cred authentication classes: a portal, credentials checkers, and a realm that returns avatars. Use twisted.conch.avatar.ConchUser as the base class for your avatar. Your avatar class should also implement twisted.conch.interfaces.ISession , which includes an openShell method in which you create a Protocol to manage the user’s interactive session. Finally, create a twisted.conch.ssh.factory.SSHFactory object and set its portal attribute to an instance of your portal.
Example 10-1 demonstrates a custom SSH server that authenticates users by their username and password. It gives each user a shell that provides several commands.
Example 10-1. sshserver.py
from twisted.cred import portal, checkers, credentials
from twisted.conch import error, avatar, recvline, interfaces as conchinterfaces
from twisted.conch.ssh import factory, userauth, connection, keys, session, common from twisted.conch.insults import insults from twisted.application import service, internet
from zope.interface import implements
import os
class SSHDemoProtocol(recvline.HistoricRecvLine):
def __init__(self, user):
self.user = user
def connectionMade(self) :
recvline.HistoricRecvLine.connectionMade(self)
self.terminal.write("Welcome to my test SSH server.")
self.terminal.nextLine()
self.do_help()
self.showPrompt()
def showPrompt(self):
self.terminal.write("$ ")
def getCommandFunc(self, cmd):
return getattr(self, ‘do_’ + cmd, None)
def lineReceived(self, line):
line = line.strip()
if line:
cmdAndArgs = line.split()
cmd = cmdAndArgs[0]
args = cmdAndArgs[1:]
func = self.getCommandFunc(cmd)
if func:
try:
func(*args)
except Exception, e:
self.terminal.write("Error: %s" % e)
self.terminal.nextLine()
else:
self.terminal.write("No such command.")
self.terminal.nextLine()
self.showPrompt()
def do_help(self, cmd=”):
"Get help on a command. Usage: help command"
if cmd:
func = self.getCommandFunc(cmd)
if func:
self.terminal.write(func.__doc__)
self.terminal.nextLine()
return
publicMethods = filter(
lambda funcname: funcname.startswith(‘do_’), dir(self))
commands = [cmd.replace(‘do_’, ”, 1) for cmd in publicMethods]
self.terminal.write("Commands: " + " ".join(commands))
self.terminal.nextLine()
def do_echo(self, *args):
"Echo a string. Usage: echo my line of text"
self.terminal.write(" ".join(args))
self.terminal.nextLine()
def do_whoami(self):
"Prints your user name. Usage: whoami"
self.terminal.write(self.user.username)
self.terminal.nextLine()
def do_quit(self):
"Ends your session. Usage: quit"
self.terminal.write("Thanks for playing!")
self.terminal.nextLine()
self.terminal.loseConnection()
def do_clear(self):
"Clears the screen. Usage: clear"
self.terminal.reset()
class SSHDemoAvatar(avatar.ConchUser):
implements(conchinterfaces.ISession)
def __init__(self, username):
avatar.ConchUser.__init__(self)
self.username = username
self.channelLookup.update({‘session’:session.SSHSession})
def openShell(self, protocol):
serverProtocol = insults.ServerProtocol(SSHDemoProtocol, self)
serverProtocol.makeConnection(protocol)
protocol.makeConnection(session.wrapProtocol(serverProtocol))
def getPty(self, terminal, windowSize, attrs):
return None
def execCommand(self, protocol, cmd):
raise NotImplementedError
def closed(self):
pass
class SSHDemoRealm:
implements(portal.IRealm)
def requestAvatar(self, avatarId, mind, *interfaces):
if conchinterfaces.IConchUser in interfaces:
return interfaces[0], SSHDemoAvatar(avatarId), lambda: None
else:
raise Exception, "No supported interfaces found."
def getRSAKeys():
if not (os.path.exists(‘public.key’) and os.path.exists(‘private.key’)):
# generate a RSA keypair
print "Generating RSA keypair…"
from Crypto.PublicKey import RSA
KEY_LENGTH = 1024
rsaKey = RSA.generate(KEY_LENGTH, common.entropy.get_bytes)
publicKeyString = keys.makePublicKeyString(rsaKey)
privateKeyString = keys.makePrivateKeyString(rsaKey)
# save keys for next time
file(‘public.key’, ‘w+b’).write(publicKeyString)
file(‘private.key’, ‘w+b’).write(privateKeyString)
print "done."
else:
publicKeyString = file(‘public.key’).read()
privateKeyString = file(‘private.key’).read()
return publicKeyString, privateKeyString
if __name__ == "__main__":
sshFactory = factory.SSHFactory()
sshFactory.portal = portal.Portal(SSHDemoRealm())
users = {‘admin’: ‘aaa’, ‘guest’: ‘bbb’}
sshFactory.portal.registerChecker(
checkers.InMemoryUsernamePasswordDatabaseDontUse(**users))
pubKeyString, privKeyString =
getRSAKeys()
sshFactory.publicKeys = {
‘ssh-rsa’: keys.getPublicKeyString(data=pubKeyString)}
sshFactory.privateKeys = {
‘ssh-rsa’: keys.getPrivateKeyObject(data=privKeyString)}
from twisted.internet import reactor
reactor.listenTCP(2222, sshFactory)
reactor.run()
{mospagebreak title=Setting Up a Custom SSH Server continued}
sshserver.py will run an SSH server on port 2222. Connect to this server with an SSH client using the username admin and password aaa, and try typing some commands:
$ ssh admin#localhost -p 2222
admin#localhost’s password: aaa
>>> Welcome to my test SSH server.
Commands: clear echo help quit whoami
$ whoami
admin
$ help echo
Echo a string. Usage: echo my line of text
$ echo hello SSH world!
hello SSH world!
$ quit
Connection to localhost closed.
adding here to driquet's answer as the edit waitlist is full
for python3 :
from pexpect import pxssh
s = pxssh.pxssh()
if not s.login ('10.6.192.18', 'root', 'paloalto'):
print ("SSH session failed on login.")
print (str(s))
else:
print ("SSH session login successful")
s.sendline ('ls -l')
s.prompt() # match the prompt
print (s.before.decode('UTF-8')) # decode to string and print everything before the prompt.
s.logout()
I have a twisted application which now needs to monitor processes running on several boxes. The way I manually do is 'ssh and ps', now I'd like my twisted application to do. I have 2 options.
Use paramiko or leverage the power of twisted.conch
I really want to use twisted.conch but my research led me to believe that its primarily intended to create SSHServers and SSHClients. However my requirement is a simple remoteExecute(some_cmd)
I was able to figure out how to do this using paramiko but I didnt want to stickparamiko in my twisted app before looking at how to do this using twisted.conch
Code snippets using twisted on how to run remote_cmds using ssh would be highly appreciated. Thanks.
Followup - Happily, the ticket I referenced below is now resolved. The simpler API will be included in the next release of Twisted. The original answer is still a valid way to use Conch and may reveal some interesting details about what's going on, but from Twisted 13.1 and on, if you just want to run a command and handle it's I/O, this simpler interface will work.
It takes an unfortunately large amount of code to execute a command on an SSH using the Conch client APIs. Conch makes you deal with a lot of different layers, even if you just want sensible boring default behavior. However, it's certainly possible. Here's some code which I've been meaning to finish and add to Twisted to simplify this case:
import sys, os
from zope.interface import implements
from twisted.python.failure import Failure
from twisted.python.log import err
from twisted.internet.error import ConnectionDone
from twisted.internet.defer import Deferred, succeed, setDebugging
from twisted.internet.interfaces import IStreamClientEndpoint
from twisted.internet.protocol import Factory, Protocol
from twisted.conch.ssh.common import NS
from twisted.conch.ssh.channel import SSHChannel
from twisted.conch.ssh.transport import SSHClientTransport
from twisted.conch.ssh.connection import SSHConnection
from twisted.conch.client.default import SSHUserAuthClient
from twisted.conch.client.options import ConchOptions
# setDebugging(True)
class _CommandTransport(SSHClientTransport):
_secured = False
def verifyHostKey(self, hostKey, fingerprint):
return succeed(True)
def connectionSecure(self):
self._secured = True
command = _CommandConnection(
self.factory.command,
self.factory.commandProtocolFactory,
self.factory.commandConnected)
userauth = SSHUserAuthClient(
os.environ['USER'], ConchOptions(), command)
self.requestService(userauth)
def connectionLost(self, reason):
if not self._secured:
self.factory.commandConnected.errback(reason)
class _CommandConnection(SSHConnection):
def __init__(self, command, protocolFactory, commandConnected):
SSHConnection.__init__(self)
self._command = command
self._protocolFactory = protocolFactory
self._commandConnected = commandConnected
def serviceStarted(self):
channel = _CommandChannel(
self._command, self._protocolFactory, self._commandConnected)
self.openChannel(channel)
class _CommandChannel(SSHChannel):
name = 'session'
def __init__(self, command, protocolFactory, commandConnected):
SSHChannel.__init__(self)
self._command = command
self._protocolFactory = protocolFactory
self._commandConnected = commandConnected
def openFailed(self, reason):
self._commandConnected.errback(reason)
def channelOpen(self, ignored):
self.conn.sendRequest(self, 'exec', NS(self._command))
self._protocol = self._protocolFactory.buildProtocol(None)
self._protocol.makeConnection(self)
def dataReceived(self, bytes):
self._protocol.dataReceived(bytes)
def closed(self):
self._protocol.connectionLost(
Failure(ConnectionDone("ssh channel closed")))
class SSHCommandClientEndpoint(object):
implements(IStreamClientEndpoint)
def __init__(self, command, sshServer):
self._command = command
self._sshServer = sshServer
def connect(self, protocolFactory):
factory = Factory()
factory.protocol = _CommandTransport
factory.command = self._command
factory.commandProtocolFactory = protocolFactory
factory.commandConnected = Deferred()
d = self._sshServer.connect(factory)
d.addErrback(factory.commandConnected.errback)
return factory.commandConnected
class StdoutEcho(Protocol):
def dataReceived(self, bytes):
sys.stdout.write(bytes)
sys.stdout.flush()
def connectionLost(self, reason):
self.factory.finished.callback(None)
def copyToStdout(endpoint):
echoFactory = Factory()
echoFactory.protocol = StdoutEcho
echoFactory.finished = Deferred()
d = endpoint.connect(echoFactory)
d.addErrback(echoFactory.finished.errback)
return echoFactory.finished
def main():
from twisted.python.log import startLogging
from twisted.internet import reactor
from twisted.internet.endpoints import TCP4ClientEndpoint
# startLogging(sys.stdout)
sshServer = TCP4ClientEndpoint(reactor, "localhost", 22)
commandEndpoint = SSHCommandClientEndpoint("/bin/ls", sshServer)
d = copyToStdout(commandEndpoint)
d.addErrback(err, "ssh command / copy to stdout failed")
d.addCallback(lambda ignored: reactor.stop())
reactor.run()
if __name__ == '__main__':
main()
Some things to note about it:
It uses the new endpoint APIs introduced in Twisted 10.1. It's possible to do this directly on reactor.connectTCP, but I did it as an endpoint to make it more useful; endpoints can be swapped easily without the code that actually asks for a connection knowing.
It does no host key verification at all! _CommandTransport.verifyHostKey is where you would implement that. Take a look at twisted/conch/client/default.py for some hints about what kinds of things you might want to do.
It takes $USER to be the remote username, which you may want to be a parameter.
It probably only works with key authentication. If you want to enable password authentication, you probably need to subclass SSHUserAuthClient and override getPassword to do something.
Almost all of the layers of SSH and Conch are visible here:
_CommandTransport is at the bottom, a plain old protocol that implements the SSH transport protocol. It creates a...
_CommandConnection which implements the SSH connection negotiation parts of the protocol. Once that completes, a...
_CommandChannel is used to talk to a newly opened SSH channel. _CommandChannel does the actual exec to launch your command. Once the channel is opened it creates an instance of...
StdoutEcho, or whatever other protocol you supply. This protocol will get the output from the command you execute, and can write to the command's stdin.
See http://twistedmatrix.com/trac/ticket/4698 for progress in Twisted on supporting this with less code.