[Python-modules-commits] [txwinrm] 01/03: New upstream version 1.1.28
Christopher Hoskin
christopher.hoskin at gmail.com
Sun Oct 9 07:49:34 UTC 2016
This is an automated email from the git hooks/post-receive script.
grinorcole-guest pushed a commit to branch master
in repository txwinrm.
commit 6e679d0ea3d9e9250c3535d60d472ff2ff17bc19
Author: Christopher Hoskin <christopher.hoskin at gmail.com>
Date: Sun Oct 9 08:36:41 2016 +0100
New upstream version 1.1.28
---
README.rst | 7 +
setup.py | 2 +-
txwinrm/SessionManager.py | 205 +++++++++++++++++++
txwinrm/WinRMClient.py | 496 ++++++++++++++++++++++++++++++++++++++++++++++
txwinrm/app.py | 7 +-
txwinrm/shell.py | 5 -
txwinrm/util.py | 42 ++--
txwinrm/winrm.py | 4 +-
txwinrm/winrs.py | 13 +-
9 files changed, 752 insertions(+), 29 deletions(-)
diff --git a/README.rst b/README.rst
index aecf117..8bac9f0 100644
--- a/README.rst
+++ b/README.rst
@@ -417,3 +417,10 @@ Run txwinrm/test/precommit before merging to master. This requires that you...
easy_install flake8
easy_install coverage
git clone https://github.com/dgladkov/cyclic_complexity
+
+
+Changes
+-------
+
+1.1.27
+* Add support for running commands/enumerations in a single session
diff --git a/setup.py b/setup.py
old mode 100644
new mode 100755
index 1221d15..c261752
--- a/setup.py
+++ b/setup.py
@@ -11,7 +11,7 @@
setup_kwargs = dict(
name='txwinrm',
- version='1.1.26',
+ version='1.1.28',
description='Asynchronous Python WinRM client',
long_description=open('README.rst').read(),
license='See LICENSE file',
diff --git a/txwinrm/SessionManager.py b/txwinrm/SessionManager.py
new file mode 100644
index 0000000..e202dcc
--- /dev/null
+++ b/txwinrm/SessionManager.py
@@ -0,0 +1,205 @@
+##############################################################################
+#
+# Copyright (C) Zenoss, Inc. 2016, all rights reserved.
+#
+# This content is made available according to terms specified in
+# License.zenoss under the directory where your Zenoss product is installed.
+#
+##############################################################################
+
+"""txsessionmgr - Python module for a single persistent connection to a device
+for multiple clients.
+
+Useful for situations when multiple connections to a device can be handled
+with one connection through a single login, e.g. txciscoapic, txwinrm
+
+The global SESSION_MANAGER is instantiated one time and is used to manage
+all sessions
+
+Session should be subclassed and implemented to login/logout, send requests,
+and handle responses
+
+A Client should always have a key property. This will be unique to the types
+of transactions/requests being made through a single Session
+
+"""
+
+from twisted.internet.defer import inlineCallbacks, returnValue, succeed
+
+
+class Session(object):
+ """
+ Session handler for connection to a device.
+
+ deferred_init will kick off a deferred login to a device from the first
+ client that needs the connection. Subsequent clients will use the data
+ returned from the first login.
+
+ The Session class is responsible for implementing the login/logout methods
+ """
+ def __init__(self):
+ # Used to keep track of clients using session
+ self._clients = set()
+
+ # The currently valid token. This can be anything that the client
+ # needs to know the connection is alive
+ self._token = None
+
+ # Deferred waiting for login result.
+ self._login_d = None
+
+ # Error from last login if applicable.
+ self._login_error = None
+
+ # Deferred sending a refresh/keep-alive signal/request
+ self._refresh_d = None
+
+ @inlineCallbacks
+ def deferred_login(self, client):
+ """Return Deferred token
+
+ :param client: Client initiating a connection
+ :client: ZenPack specific client
+ :rtype: Deferred
+ :return: Returns ZenPack unique token to be used for a session.
+ """
+ self._clients.add(client)
+ if self._token:
+ returnValue(self._token)
+
+ # No one already waiting for a token. Login to get a new one.
+ if not self._login_d or self._login_d.called:
+ self._login_d = self._deferred_login(client)
+
+ try:
+ self._token = yield self._login_d
+ except Exception as e:
+ self._login_error = e
+ raise
+
+ # At least one other client is already waiting for a token, and
+ # the login to get it is already in progress. Wait for that
+ # login to finish, then return its token.
+ else:
+ yield self._login_d
+ if self._login_error:
+ raise self._login_error
+
+ returnValue(self._token)
+
+ @inlineCallbacks
+ def deferred_logout(self, client):
+ """Return Deferred None.
+
+ Calls session._deferred_logout() only if all other clients using the same
+ session have also called deferred_logout.
+
+ """
+ if len(self._clients) <= 1:
+ if self._token:
+ try:
+ yield self._deferred_logout()
+ except Exception:
+ pass
+
+ self._token = None
+
+ if client in self._clients:
+ self._clients.remove(client)
+ returnValue(None)
+
+ @inlineCallbacks
+ def _deferred_login(self, client):
+ '''login method
+
+ Performs the ZenPack specific login to a device. This will only be called
+ from the first client to fire off the deferred. All other clients will
+ use the _token returned from this method
+
+ :param client: Client initiating a connection
+ :type client: ZenPack specific client
+ :rtype: Deferred
+ :return: Returns a Deferred which is logs into the device.
+ '''
+ returnValue(None)
+
+ @inlineCallbacks
+ def _deferred_logout(self):
+ '''logout method
+
+ Performs the ZenPack specific logout from a device. This will only be called
+ by the last client to logout of the session.
+
+ :rtype: Deferred
+ :return: Returns a Deferred which logs out of the device
+ '''
+ returnValue(None)
+
+
+class SessionManager(object):
+ '''
+ Class to manage open sessions to devices.
+ '''
+ def __init__(self):
+ # Used to keep track of sessions
+ self._sessions = {}
+
+ def get_connection(self, key):
+ '''Return the session for a given key
+ '''
+ if key is None:
+ raise Exception('Client key cannot be empty')
+ return self._sessions.get(key, None)
+
+ def remove_connection(self, key):
+ session = self.get_connection(key)
+ if session:
+ self._sessions.pop(session)
+
+ @inlineCallbacks
+ def init_connection(self, client, session_class=Session):
+ '''Initialize connection to device.
+ If a session is already started return it.
+ Else kick off deferred to initiate session
+
+ The client must contain a key for session storage
+
+ :param client: Client initiating connection
+ :client: ZenPack defined client.
+ '''
+ if not hasattr(client, 'key'):
+ raise Exception('Client must contain a key field')
+
+ session = self.get_connection(client.key)
+ if session:
+ if session._token:
+ if client not in session._clients:
+ session._clients.add(client)
+ returnValue(session._token)
+
+ if session is None:
+ session = session_class()
+ self._sessions[client.key] = session
+
+ token = yield session.deferred_login(client)
+ returnValue(token)
+
+ @inlineCallbacks
+ def close_connection(self, client):
+ '''Kick off a session's logout
+ If there are no more clients using a session, remove it
+
+ :param client: Client closing connection
+ :client: ZenPack defined class
+ '''
+ session = self.get_connection(client.key)
+ if not session:
+ returnValue(None)
+ yield session.deferred_logout(client)
+ if not session._clients:
+ # no more clients so we don't need to keep the session
+ self._sessions.pop(client.key)
+ returnValue(None)
+
+
+SESSION_MANAGER = SessionManager()
diff --git a/txwinrm/WinRMClient.py b/txwinrm/WinRMClient.py
new file mode 100755
index 0000000..797008a
--- /dev/null
+++ b/txwinrm/WinRMClient.py
@@ -0,0 +1,496 @@
+##############################################################################
+#
+# Copyright (C) Zenoss, Inc. 2013, all rights reserved.
+#
+# This content is made available according to terms specified in the LICENSE
+# file at the top-level directory of this package.
+#
+##############################################################################
+
+import logging
+from httplib import BAD_REQUEST, UNAUTHORIZED, FORBIDDEN, OK
+
+from twisted.internet.defer import (
+ inlineCallbacks,
+ returnValue,
+ DeferredSemaphore,
+ succeed
+)
+from twisted.internet.error import TimeoutError
+
+try:
+ from twisted.web.client import ResponseFailed
+ ResponseFailed
+except ImportError:
+ class ResponseFailed(Exception):
+ pass
+
+from . import constants as c
+from kerberos import GSSError
+from .util import (
+ _authenticate_with_kerberos,
+ _get_agent,
+ verify_conn_info,
+ _CONTENT_TYPE,
+ _ENCRYPTED_CONTENT_TYPE,
+ Headers,
+ _get_basic_auth_header,
+ _get_request_template,
+ _StringProducer,
+ get_auth_details,
+ UnauthorizedError,
+ ForbiddenError,
+ RequestError,
+ _ErrorReader,
+ _StringProtocol,
+ ET
+)
+from .shell import (
+ _find_shell_id,
+ _build_command_line_elem,
+ _find_command_id,
+ _MAX_REQUESTS_PER_COMMAND,
+ _find_stream,
+ _find_exit_code,
+ CommandResponse,
+ _stripped_lines
+)
+from .enumerate import (
+ DEFAULT_RESOURCE_URI,
+ SaxResponseHandler,
+ _MAX_REQUESTS_PER_ENUMERATION
+)
+from .SessionManager import SESSION_MANAGER, Session
+LOG = logging.getLogger('winrm')
+
+
+class WinRMSession(Session):
+ '''
+ Session class to keep track of single winrm connection
+
+
+ '''
+ def __init__(self):
+ super(WinRMSession, self).__init__()
+
+ # twisted agent to send http/https requests
+ self._agent = _get_agent()
+
+ # our kerberos context for encryption/decryption
+ self._gssclient = None
+
+ # url for session
+ self._url = None
+
+ # headers to use for requests
+ self._headers = None
+
+ # connection info. see util.ConnectionInfo
+ self._conn_info = None
+
+ # DeferredSemaphore so that we complete one transaction/conversation
+ # at a time. Windows cannot handle mixed transaction types on one
+ # connection.
+ self.sem = DeferredSemaphore(1)
+
+ def is_kerberos(self):
+ return self._conn_info.auth_type == 'kerberos'
+
+ def decrypt_body(self, body):
+ return self._gssclient.decrypt_body(body)
+
+ def _set_headers(self):
+ if self._headers:
+ return self._headers
+ if self._conn_info.auth_type == 'basic':
+ self._headers = Headers(_CONTENT_TYPE)
+ self._headers.addRawHeader('Connection', self._conn_info.connectiontype)
+ self._headers.addRawHeader(
+ 'Authorization', _get_basic_auth_header(self._conn_info))
+ elif self.is_kerberos():
+ self._headers = Headers(_ENCRYPTED_CONTENT_TYPE)
+ self._headers.addRawHeader('Connection', self._conn_info.connectiontype)
+ return self._headers
+
+ @inlineCallbacks
+ def _deferred_login(self, client=None):
+ if client:
+ self._conn_info = client._conn_info
+ self._url = "{c.scheme}://{c.ipaddress}:{c.port}/wsman".format(c=self._conn_info)
+ if self.is_kerberos():
+ self._gssclient = yield _authenticate_with_kerberos(self._conn_info, self._url, self._agent)
+ returnValue(self._gssclient)
+ else:
+ returnValue('basic_auth_token')
+
+ @inlineCallbacks
+ def _deferred_logout(self):
+ # close connections so we do not end up with orphans
+ # return a Deferred()
+ self.loggedout = True
+ if self._agent and hasattr(self._agent, 'closeCachedConnections'):
+ # twisted 11 has no return and is part of the Agent
+ return succeed(self._agent.closeCachedConnections())
+ elif self._agent:
+ # twisted 12 returns a Deferred
+ return self._agent._pool.closeCachedConnections()
+ else:
+ # no agent
+ return succeed(None)
+
+ @inlineCallbacks
+ def handle_response(self, request, response, client):
+ if response.code == UNAUTHORIZED or response.code == BAD_REQUEST:
+ # check to see if we need to re-authorize due to lost connection or bad request error
+ if self._gssclient is not None:
+ self._gssclient.cleanup()
+ self._gssclient = None
+ self._token = None
+ self._agent = _get_agent()
+ self._login_d = None
+ yield SESSION_MANAGER.init_connection(client, WinRMSession)
+ try:
+ yield self._set_headers()
+ encrypted_request = self._gssclient.encrypt_body(request)
+ if not encrypted_request.startswith("--Encrypted Boundary"):
+ self._headers.setRawHeaders('Content-Type', _CONTENT_TYPE['Content-Type'])
+ body_producer = _StringProducer(encrypted_request)
+ response = yield self._agent.request(
+ 'POST', self._url, self._headers, body_producer)
+ except Exception as e:
+ raise e
+ if response.code == UNAUTHORIZED:
+ if self.is_kerberos():
+ auth_header = response.headers.getRawHeaders('WWW-Authenticate')[0]
+ auth_details = get_auth_details(auth_header)
+ try:
+ if auth_details:
+ self._gssclient._step(auth_details)
+ except GSSError as e:
+ msg = "HTTP Unauthorized received. "
+ "Kerberos error code {0}: {1}.".format(e.args[1][1], e.args[1][0])
+ raise Exception(msg)
+ raise UnauthorizedError(
+ "HTTP Unauthorized received: Check username and password")
+ if response.code == FORBIDDEN:
+ raise ForbiddenError(
+ "Forbidden: Check WinRM port and version")
+ elif response.code != OK:
+ if self.is_kerberos():
+ reader = _ErrorReader(self._gssclient)
+ else:
+ reader = _ErrorReader()
+ response.deliverBody(reader)
+ message = yield reader.d
+ raise RequestError("HTTP status: {}. {}".format(
+ response.code, message))
+ returnValue(response)
+
+ @inlineCallbacks
+ def send_request(self, request_template_name, client, **kwargs):
+ response = yield self._send_request(
+ request_template_name, client, **kwargs)
+ proto = _StringProtocol()
+ response.deliverBody(proto)
+ body = yield proto.d
+ if self.is_kerberos():
+ xml_str = self._gssclient.decrypt_body(body)
+ else:
+ xml_str = yield body
+ if LOG.isEnabledFor(logging.DEBUG):
+ try:
+ import xml.dom.minidom
+ xml = xml.dom.minidom.parseString(xml_str)
+ LOG.debug(xml.toprettyxml())
+ except:
+ LOG.debug('Could not prettify response XML: "{0}"'.format(xml_str))
+ returnValue(ET.fromstring(xml_str))
+
+ @inlineCallbacks
+ def _send_request(self, request_template_name, client, **kwargs):
+ if self._login_d and not self._login_d.called:
+ # check for a reconnection attempt so we do not send any requests
+ # to a dead connection
+ yield self._login_d
+ LOG.debug('sending request: {0} {1}'.format(
+ request_template_name, kwargs))
+ request = _get_request_template(request_template_name).format(**kwargs)
+ self._headers = self._set_headers()
+ if self.is_kerberos():
+ encrypted_request = self._gssclient.encrypt_body(request)
+ if not encrypted_request.startswith("--Encrypted Boundary"):
+ self._headers.setRawHeaders('Content-Type', _CONTENT_TYPE['Content-Type'])
+ body_producer = _StringProducer(encrypted_request)
+ else:
+ body_producer = _StringProducer(request)
+ try:
+ response = yield self._agent.request(
+ 'POST', self._url, self._headers, body_producer)
+ except Exception as e:
+ raise e
+ LOG.debug('received response {0} {1}'.format(
+ response.code, request_template_name))
+ response = yield self.handle_response(request, response, client)
+ returnValue(response)
+
+
+class WinRMClient(object):
+ def __init__(self, conn_info):
+ verify_conn_info(conn_info)
+ self.key = None
+ self._conn_info = conn_info
+ self.session_manager = SESSION_MANAGER
+ self._session = None
+
+ @inlineCallbacks
+ def init_connection(self):
+ '''Initialize a connection through the session_manager'''
+ yield self.session_manager.init_connection(self, WinRMSession)
+ self._session = self.session_manager.get_connection(self.key)
+ returnValue(None)
+
+ def is_kerberos(self):
+ return self._conn_info.auth_type == 'kerberos'
+
+ def decrypt_body(self, body):
+ return self._session.decrypt_body(body)
+
+ @inlineCallbacks
+ def _create_shell(self):
+ elem = yield self._session.send_request('create', self)
+ returnValue(_find_shell_id(elem))
+
+ @inlineCallbacks
+ def _delete_shell(self, shell_id):
+ yield self._session.send_request('delete', self, shell_id=shell_id)
+ returnValue(None)
+
+ @inlineCallbacks
+ def _signal_terminate(self, shell_id, command_id):
+ yield self._session.send_request('signal',
+ self,
+ shell_id=shell_id,
+ command_id=command_id,
+ signal_code=c.SHELL_SIGNAL_TERMINATE)
+ returnValue(None)
+
+ @inlineCallbacks
+ def _signal_ctrl_c(self, shell_id, command_id):
+ yield self._session.send_request('signal',
+ self,
+ shell_id=shell_id,
+ command_id=command_id,
+ signal_code=c.SHELL_SIGNAL_CTRL_C)
+ returnValue(None)
+
+ @inlineCallbacks
+ def _send_command(self, shell_id, command_line):
+ command_line_elem = _build_command_line_elem(command_line)
+ command_elem = yield self._session.send_request(
+ 'command', self, shell_id=shell_id, command_line_elem=command_line_elem,
+ timeout=self._conn_info.timeout)
+ returnValue(command_elem)
+
+ @inlineCallbacks
+ def _send_receive(self, shell_id, command_id):
+ receive_elem = yield self._session.send_request(
+ 'receive', self, shell_id=shell_id, command_id=command_id)
+ returnValue(receive_elem)
+
+ @inlineCallbacks
+ def close_connection(self):
+ yield self.session_manager.close_connection(self)
+ returnValue(None)
+
+
+class SingleCommandClient(WinRMClient):
+
+ def __init__(self, conn_info):
+ super(SingleCommandClient, self).__init__(conn_info)
+ self.key = (self._conn_info.ipaddress, 'short')
+
+ @inlineCallbacks
+ def run_command(self, command_line):
+ '''
+ Run a single command in the session's semaphore. Windows must finish
+ a command conversation before a new command or enumeration can start
+ '''
+ yield self.init_connection()
+ try:
+ cmd_response = yield self._session.sem.run(self.run_single_command,
+ command_line)
+ except Exception:
+ yield self.close_connection()
+ returnValue(cmd_response)
+
+ @inlineCallbacks
+ def run_single_command(self, command_line):
+ """
+ Run a single command line in a remote shell like the winrs application
+ on Windows. Returns a dictionary with the following
+ structure:
+ CommandResponse
+ .stdout = [<non-empty, stripped line>, ...]
+ .stderr = [<non-empty, stripped line>, ...]
+ .exit_code = <int>
+ """
+ shell_id = yield self._create_shell()
+
+ try:
+ cmd_response = yield self._run_command(shell_id, command_line)
+ except TimeoutError:
+ yield self.close_connection()
+ yield self._delete_shell(shell_id)
+ yield self.close_connection()
+ returnValue(cmd_response)
+
+ @inlineCallbacks
+ def _run_command(self, shell_id, command_line):
+ command_elem = yield self._send_command(shell_id, command_line)
+ command_id = _find_command_id(command_elem)
+ stdout_parts = []
+ stderr_parts = []
+ for i in xrange(_MAX_REQUESTS_PER_COMMAND):
+ receive_elem = yield self._send_receive(shell_id, command_id)
+ stdout_parts.extend(
+ _find_stream(receive_elem, command_id, 'stdout'))
+ stderr_parts.extend(
+ _find_stream(receive_elem, command_id, 'stderr'))
+ exit_code = _find_exit_code(receive_elem, command_id)
+ if exit_code is not None:
+ break
+ else:
+ raise Exception("Reached max requests per command.")
+ yield self._signal_terminate(shell_id, command_id)
+ stdout = _stripped_lines(stdout_parts)
+ stderr = _stripped_lines(stderr_parts)
+ returnValue(CommandResponse(stdout, stderr, exit_code))
+
+
+class LongCommandClient(WinRMClient):
+ def __init__(self, conn_info):
+ super(LongCommandClient, self).__init__(conn_info)
+ self._shell_id = None
+ self._command_id = None
+ self._exit_code = None
+
+ @inlineCallbacks
+ def start(self, command_line):
+ LOG.debug("LongRunningCommand run_command: {0}".format(command_line))
+ self.key = (self._conn_info.ipaddress, command_line)
+ yield self.init_connection()
+ self._shell_id = yield self._create_shell()
+ command_line_elem = _build_command_line_elem(command_line)
+ LOG.debug('LongRunningCommand run_command: sending command request '
+ '(shell_id={0}, command_line_elem={1})'.format(
+ self._shell_id, command_line_elem))
+ try:
+ command_elem = yield self._send_command(self._shell_id,
+ command_line)
+ except TimeoutError:
+ yield self.close_connection()
+ raise
+ self._command_id = _find_command_id(command_elem)
+ returnValue(None)
+
+ @inlineCallbacks
+ def receive(self):
+ try:
+ receive_elem = yield self._send_receive(self._shell_id, self._command_id)
+ except TimeoutError:
+ yield self.close_connection()
+ raise
+ stdout_parts = _find_stream(receive_elem, self._command_id, 'stdout')
+ stderr_parts = _find_stream(receive_elem, self._command_id, 'stderr')
+ self._exit_code = _find_exit_code(receive_elem, self._command_id)
+ stdout = _stripped_lines(stdout_parts)
+ stderr = _stripped_lines(stderr_parts)
+ returnValue((stdout, stderr))
+
+ @inlineCallbacks
+ def stop(self, close=False):
+ yield self._signal_ctrl_c(self._shell_id, self._command_id)
+ try:
+ stdout, stderr = yield self.receive()
+ except TimeoutError:
+ pass
+ yield self._signal_terminate(self._shell_id, self._command_id)
+ yield self._delete_shell(self._shell_id)
+ if close:
+ yield self.close_connection()
+ returnValue(CommandResponse(stdout, stderr, self._exit_code))
+
+
+class EnumerateClient(WinRMClient):
+ """
+ Sends enumerate requests to a host running the WinRM service and returns
+ a list of items.
+ """
+
+ def __init__(self, conn_info):
+ super(EnumerateClient, self).__init__(conn_info)
+ self._handler = SaxResponseHandler(self)
+ self._hostname = conn_info.ipaddress
+ self.key = (conn_info.ipaddress, 'short')
+
+ @inlineCallbacks
+ def enumerate(self, wql, resource_uri=DEFAULT_RESOURCE_URI):
+ """
+ Runs a remote WQL query.
+ """
+ self.init_connection()
+ request_template_name = 'enumerate'
+ enumeration_context = None
+ items = []
+ try:
+ for i in xrange(_MAX_REQUESTS_PER_ENUMERATION):
+ LOG.info('{0} "{1}" {2}'.format(
+ self._hostname, wql, request_template_name))
+ response = yield self._session._send_request(
+ request_template_name,
+ self,
+ resource_uri=resource_uri,
+ wql=wql,
+ enumeration_context=enumeration_context)
+ LOG.info("{0} {1} HTTP status: {2}".format(
+ self._hostname, wql, response.code))
+ enumeration_context, new_items = \
+ yield self._handler.handle_response(response)
+ items.extend(new_items)
+ if not enumeration_context:
+ break
+ request_template_name = 'pull'
+ else:
+ raise Exception("Reached max requests per enumeration.")
+ except (ResponseFailed, RequestError, Exception) as e:
+ if isinstance(e, ResponseFailed):
+ for reason in e.reasons:
+ LOG.error('{0} {1}'.format(self._hostname, reason.value))
+ else:
+ LOG.info('{0} {1}'.format(self._hostname, e))
+ raise
+ returnValue(items)
+
+ @inlineCallbacks
+ def do_collect(self, enum_infos):
+ '''
+ Run enumerations in the session's semaphore. Windows must finish
+ an enumeration before a new command or enumeration can start
+ '''
+ items = {}
+ yield self.init_connection()
+ self._session = self.session_manager.get_connection(self.key)
+ for enum_info in enum_infos:
+ try:
+ items[enum_info] = yield self._session.sem.run(self.enumerate,
+ enum_info.wql,
+ enum_info.resource_uri)
+ except (UnauthorizedError, ForbiddenError):
+ # Fail the collection for general errors.
+ raise
+ except RequestError:
+ # Store empty results for other query-specific errors.
+ continue
+
+ yield self.close_connection()
+ returnValue(items)
diff --git a/txwinrm/app.py b/txwinrm/app.py
index 1c7f426..4f92834 100644
--- a/txwinrm/app.py
+++ b/txwinrm/app.py
@@ -219,6 +219,7 @@ def _parse_args(utility):
parser.add_argument("--keytab", "-k")
parser.add_argument("--password", "-p")
parser.add_argument("--ipaddress", "-s")
+ parser.add_argument("--service", "-e", help='http/https/wsman', default='http')
utility.add_args(parser)
args = parser.parse_args()
if not args.config:
@@ -235,8 +236,10 @@ def _parse_args(utility):
password = getpass()
connectiontype = 'Keep-Alive'
args.conn_info = ConnectionInfo(
- hostname, args.authentication, args.username, password, scheme,
- port, connectiontype, args.keytab, args.dcip, ipaddress=args.ipaddress)
+ hostname, args.authentication,
+ args.username, password, scheme,
+ port, connectiontype, args.keytab,
+ args.dcip, ipaddress=args.ipaddress, service=args.service)
try:
verify_conn_info(args.conn_info)
except Exception as e:
diff --git a/txwinrm/shell.py b/txwinrm/shell.py
index 3d5e2ed..18086f5 100644
--- a/txwinrm/shell.py
+++ b/txwinrm/shell.py
@@ -181,10 +181,6 @@ class LongRunningCommand(object):
self._command_id = None
self._exit_code = None
- def __del__(self):
- # in case of network errors
- yield self._sender.close_connections()
-
@defer.inlineCallbacks
def start(self, command_line):
log.debug("LongRunningCommand run_command: {0}".format(command_line))
@@ -239,7 +235,6 @@ class LongRunningCommand(object):
command_id=self._command_id,
signal_code=c.SHELL_SIGNAL_TERMINATE)
yield self._sender.send_request('delete', shell_id=self._shell_id)
- yield self._sender.close_connections()
defer.returnValue(CommandResponse(stdout, stderr, self._exit_code))
diff --git a/txwinrm/util.py b/txwinrm/util.py
index 295a727..4e337fb 100644
--- a/txwinrm/util.py
+++ b/txwinrm/util.py
@@ -21,6 +21,7 @@ from twisted.internet.protocol import Protocol
from twisted.web.client import Agent
from twisted.internet.ssl import ClientContextFactory
from twisted.web.http_headers import Headers
+from twisted.internet.threads import deferToThread
from . import constants as c
from .krb5 import kinit, ccname, add_trusted_realm
@@ -237,7 +238,7 @@ class AuthGSSClient(object):
@return: a result code
"""
log.debug('GSSAPI step challenge="{0}"'.format(challenge))
- return kerberos.authGSSClientStep(self._context, challenge)
+ return deferToThread(kerberos.authGSSClientStep, self._context, challenge)
@defer.inlineCallbacks
def get_base64_client_data(self, challenge=''):
@@ -248,7 +249,7 @@ class AuthGSSClient(object):
result_code = None
for i in xrange(_MAX_KERBEROS_RETRIES):
try:
- result_code = self._step(challenge)
+ result_code = yield self._step(challenge)
break
except kerberos.GSSError as e:
msg = e.args[1][0]
@@ -270,6 +271,7 @@ class AuthGSSClient(object):
base64_client_data = kerberos.authGSSClientResponse(self._context)
defer.returnValue(base64_client_data)
+ @defer.inlineCallbacks
def get_username(self, challenge):
"""
Get the user name of the principal authenticated via the now complete
@@ -278,12 +280,12 @@ class AuthGSSClient(object):
@param challenge: a string containing the base64-encoded server data
@return: a string containing the user name.
"""
- result_code = self._step(challenge)
+ result_code = yield self._step(challenge)
if result_code != kerberos.AUTH_GSS_COMPLETE:
raise Exception('kerberos authGSSClientStep failed ({0}). '
'challenge={1}'
.format(result_code, challenge))
- return kerberos.authGSSClientUserName(self._context)
+ defer.returnValue(kerberos.authGSSClientUserName(self._context))
def encrypt_body(self, body):
# get original length of body. wrap will encrypt in place
@@ -309,13 +311,13 @@ class AuthGSSClient(object):
payload = bytes(base64.b64decode(ewrap))
# add carriage returns to body
body = _BODY.replace('\n', '\r\n')
- body = bytes(body.format(original_length=orig_len+pad_len, emsg=payload))
+ body = bytes(body.format(original_length=orig_len + pad_len, emsg=payload))
return body
def decrypt_body(self, body):
try:
b_start = body.index("Content-Type: application/octet-stream") + \
- len("Content-Type: application/octet-stream\r\n")
+ len("Content-Type: application/octet-stream\r\n")
except ValueError:
# Unencrypted data, return body
return body
@@ -354,7 +356,7 @@ def get_auth_details(auth_header=''):
@defer.inlineCallbacks
def _authenticate_with_kerberos(conn_info, url, agent, gss_client=None):
- service = '{0}@{1}'.format(conn_info.scheme.upper(), conn_info.hostname)
+ service = '{0}@{1}'.format(conn_info.service.upper(), conn_info.hostname)
if gss_client is None:
gss_client = AuthGSSClient(
service,
@@ -372,7 +374,7 @@ def _authenticate_with_kerberos(conn_info, url, agent, gss_client=None):
if response.code == httplib.UNAUTHORIZED:
try:
if auth_details:
- gss_client._step(auth_details)
+ yield gss_client._step(auth_details)
except kerberos.GSSError as e:
msg = "HTTP Unauthorized received on kerberos initialization. "\
"Kerberos error code {0}: {1}.".format(e.args[1][1], e.args[1][0])
@@ -394,7 +396,7 @@ def _authenticate_with_kerberos(conn_info, url, agent, gss_client=None):
raise Exception(
'negotiate not found in WWW-Authenticate header: {0}'
.format(auth_header))
- k_username = gss_client.get_username(auth_details)
+ k_username = yield gss_client.get_username(auth_details)
log.debug('kerberos auth successful for user: {0} / {1} '
.format(conn_info.username, k_username))
defer.returnValue(gss_client)
@@ -414,16 +416,19 @@ class ConnectionInfo(namedtuple(
'timeout',
'trusted_realm',
'trusted_kdc',
- 'ipaddress'])):
+ 'ipaddress',
+ 'service'])):
def __new__(cls, hostname, auth_type, username, password, scheme, port,
- connectiontype, keytab, dcip, timeout=60, trusted_realm='', trusted_kdc='', ipaddress=''):
+ connectiontype, keytab, dcip, timeout=60, trusted_realm='', trusted_kdc='', ipaddress='', service=''):
if not ipaddress:
ipaddress = hostname
+ if not service:
+ service = scheme
return super(ConnectionInfo, cls).__new__(cls, hostname, auth_type,
username, password, scheme,
port, connectiontype, keytab,
dcip, timeout,
- trusted_realm, trusted_kdc, ipaddress)
+ trusted_realm, trusted_kdc, ipaddress, service)
def verify_hostname(conn_info):
@@ -465,6 +470,17 @@ def verify_scheme(conn_info):
.format(scheme))
+def verify_service(conn_info):
+ has_service, service = _has_get_attr(conn_info, 'service')
+ if not has_service:
+ # if not supplied, default to scheme
+ has_service, service = _has_get_attr(conn_info, 'scheme')
+ if not has_service or service not in ['http', 'https', 'wsman']:
+ raise Exception(
+ "service must be http, https, or wsman: {0}"
+ .format(service))
+
+
def verify_port(conn_info):
has_port, port = _has_get_attr(conn_info, 'port')
if not has_port or not port or not isinstance(port, int):
@@ -585,7 +601,7 @@ class RequestSender(object):
auth_details = get_auth_details(auth_header)
try:
if auth_details:
- self.gssclient._step(auth_details)
+ yield self.gssclient._step(auth_details)
except kerberos.GSSError as e:
msg ="HTTP Unauthorized received. "\
"Kerberos error code {0}: {1}.".format(e.args[1][1],e.args[1][0])
diff --git a/txwinrm/winrm.py b/txwinrm/winrm.py
index e7a4335..ca80b98 100644
--- a/txwinrm/winrm.py
+++ b/txwinrm/winrm.py
@@ -14,7 +14,7 @@ Use twisted web client to enumerate/pull WQL query.
import sys
from twisted.internet import defer
from . import app
-from .enumerate import create_winrm_client
+from .WinRMClient import EnumerateClient
class WinrmStrategy(object):
@@ -49,7 +49,7 @@ class WinrmStrategy(object):
include_header = len(config.conn_infos) > 1
ds = []
for conn_info in good_conn_infos:
- client = create_winrm_client(conn_info)
+ client = EnumerateClient(conn_info)
for wql in config.wqls:
d = client.enumerate(wql)
d.addCallback(
diff --git a/txwinrm/winrs.py b/txwinrm/winrs.py
index d2d0bdb..18276ae 100644
--- a/txwinrm/winrs.py
+++ b/txwinrm/winrs.py
@@ -12,8 +12,8 @@ import cmd
from pprint import pprint
from twisted.internet import reactor, defer, task, threads
from . import app
-from .shell import create_long_running_command, create_single_shot_command, \
- create_remote_shell
+from .shell import create_remote_shell
+from .WinRMClient import SingleCommandClient, LongCommandClient
def print_output(stdout, stderr):
@@ -57,7 +57,7 @@ class WinrsCmd(cmd.Cmd):
@defer.inlineCallbacks
def long_running_main(args):
try:
- client = create_long_running_command(args.conn_info)
+ client = LongCommandClient(args.conn_info)
yield client.start(args.command)
for i in xrange(5):
stdout, stderr = yield task.deferLater(
... 30 lines suppressed ...
--
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/python-modules/packages/txwinrm.git
More information about the Python-modules-commits
mailing list