diff --git a/lib/ansible/constants.py b/lib/ansible/constants.py index 80d81f5b0d..b11ade51ba 100644 --- a/lib/ansible/constants.py +++ b/lib/ansible/constants.py @@ -132,6 +132,7 @@ ANSIBLE_NOCOWS = get_config(p, DEFAULTS, 'nocows', 'ANSIBLE_NOCO ANSIBLE_SSH_ARGS = get_config(p, 'ssh_connection', 'ssh_args', 'ANSIBLE_SSH_ARGS', None) PARAMIKO_RECORD_HOST_KEYS = get_config(p, 'paramiko_connection', 'record_host_keys', 'ANSIBLE_PARAMIKO_RECORD_HOST_KEYS', True, boolean=True) ZEROMQ_PORT = int(get_config(p, 'fireball_connection', 'zeromq_port', 'ANSIBLE_ZEROMQ_PORT', 5099)) +FIREBALL2_PORT = int(get_config(p, 'fireball_connection', 'fireball2_port', 'ANSIBLE_FIREBALL2_PORT', 5099)) DEFAULT_UNDEFINED_VAR_BEHAVIOR = get_config(p, DEFAULTS, 'error_on_undefined_vars', 'ANSIBLE_ERROR_ON_UNDEFINED_VARS', True, boolean=True) HOST_KEY_CHECKING = get_config(p, DEFAULTS, 'host_key_checking', 'ANSIBLE_HOST_KEY_CHECKING', True, boolean=True) diff --git a/lib/ansible/runner/connection_plugins/fireball2.py b/lib/ansible/runner/connection_plugins/fireball2.py new file mode 100644 index 0000000000..dbc881e4e6 --- /dev/null +++ b/lib/ansible/runner/connection_plugins/fireball2.py @@ -0,0 +1,129 @@ +# (c) 2012, Michael DeHaan +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . + +import json +import os +import base64 +import socket +from ansible.callbacks import vvv +from ansible import utils +from ansible import errors +from ansible import constants + +class Connection(object): + ''' raw socket accelerated connection ''' + + def __init__(self, runner, host, port, *args, **kwargs): + + self.runner = runner + + # attempt to work around shared-memory funness + if getattr(self.runner, 'aes_keys', None): + utils.AES_KEYS = self.runner.aes_keys + + self.host = host + self.context = None + self.conn = None + self.cipher = AES256Cipher() + + if port is None: + self.port = constants.FIREBALL2_PORT + else: + self.port = port + + def connect(self): + ''' activates the connection object ''' + + self.conn = socket.socket() + self.conn.connect((self.host,self.port)) + + return self + + def exec_command(self, cmd, tmp_path, sudo_user, sudoable=False, executable='/bin/sh'): + ''' run a command on the remote host ''' + + vvv("EXEC COMMAND %s" % cmd) + + data = dict( + mode='command', + cmd=cmd, + tmp_path=tmp_path, + executable=executable, + ) + data = utils.jsonify(data) + data = self.cipher.encrypt(data) + if self.conn.sendall(data): + raise errors.AnisbleError("Failed to send command to %s:%s" % (self.host,self.port)) + + response = self.conn.recv(2048) + response = self.cipher.decrypt(response) + response = utils.parse_json(response) + + return (response.get('rc',None), '', response.get('stdout',''), response.get('stderr','')) + + def put_file(self, in_path, out_path): + + ''' transfer a file from local to remote ''' + vvv("PUT %s TO %s" % (in_path, out_path), host=self.host) + + if not os.path.exists(in_path): + raise errors.AnsibleFileNotFound("file or module does not exist: %s" % in_path) + + data = base64.file(in_path).read() + data = base64.b64encode(data) + data = dict(mode='put', data=data, out_path=out_path) + + # TODO: support chunked file transfer + data = utils.jsonify(data) + data = self.cipher.encrypt(data) + if self.conn.sendall(data): + raise errors.AnsibleError("failed to send the file to %s:%s" % (self.host,self.port)) + + response = self.conn.recv(2048) + response = self.cipher.decrypt(response) + response = utils.parse_json(response) + + # no meaningful response needed for this + + def fetch_file(self, in_path, out_path): + ''' save a remote file to the specified path ''' + vvv("FETCH %s TO %s" % (in_path, out_path), host=self.host) + + data = dict(mode='fetch', in_path=in_path) + data = utils.jsonify(data) + data = self.cipher.encrypt(data) + if self.conn.sendall(data): + raise errors.AnsibleError("failed to initiate the file fetch with %s:%s" % (self.host,self.port)) + + response = self.socket.recv(2048) + response = self.cipher.decrypt(response) + response = utils.parse_json(response) + response = response['data'] + response = base64.b64decode(response) + + fh = open(out_path, "w") + fh.write(response) + fh.close() + + def close(self): + ''' terminate the connection ''' + # Be a good citizen + try: + self.conn.close() + except: + pass + diff --git a/lib/ansible/utils/__init__.py b/lib/ansible/utils/__init__.py index 7a28d215be..f431b2a73c 100644 --- a/lib/ansible/utils/__init__.py +++ b/lib/ansible/utils/__init__.py @@ -31,6 +31,7 @@ import ansible.constants as C import time import StringIO import stat +import string import termios import tty import pipes @@ -40,6 +41,7 @@ import warnings import traceback import getpass +import hmac from Crypto.Cipher import from Crypto import Random from Crypto.Random.random import StrongRandom @@ -55,8 +57,10 @@ except ImportError: try: from hashlib import md5 as _md5 + from hashlib import sha1 as _sha1 except ImportError: from md5 import md5 as _md5 + from sha1 import sha1 as _sha1 PASSLIB_AVAILABLE = False try: @@ -109,7 +113,7 @@ class AES256Cipher(object): Returns true if the lifetime of the current key has exceeded the set lifetime. """ - if ((time.time() - self.init_time) > self.lifetime): + if (time.time() - self.init_time) > self.lifetime: return True else: return False @@ -134,7 +138,7 @@ class AES256Cipher(object): """ Generates an HMAC-SHA1 signature for the message """ - return hmac.new(self.key, msg, hashlib.sha1).digest() + return hmac.new(self.key, msg, _sha1).digest() def validate_sig(self, msg, sig): """