2012-06-17 11:35:59 +00:00
# (c) 2012, Michael DeHaan <michael.dehaan@gmail.com>
#
# 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 <http://www.gnu.org/licenses/>.
#
import os
import subprocess
import shlex
import pipes
import random
import select
import fcntl
2013-08-02 16:08:02 +00:00
import hmac
2013-03-27 20:41:54 +00:00
import pwd
2013-08-08 15:02:52 +00:00
import gettext
2013-09-16 21:19:39 +00:00
import pty
2013-08-02 16:08:02 +00:00
from hashlib import sha1
2012-08-15 00:13:02 +00:00
import ansible . constants as C
2012-08-09 01:09:14 +00:00
from ansible . callbacks import vvv
2012-06-17 11:35:59 +00:00
from ansible import errors
2013-01-10 05:50:56 +00:00
from ansible import utils
2012-06-17 11:35:59 +00:00
2012-08-18 14:52:24 +00:00
class Connection ( object ) :
2012-06-17 11:35:59 +00:00
''' ssh based connections '''
2013-04-05 18:42:18 +00:00
def __init__ ( self , runner , host , port , user , password , private_key_file , * args , * * kwargs ) :
2012-06-17 11:35:59 +00:00
self . runner = runner
self . host = host
2013-08-18 21:08:35 +00:00
self . ipv6 = ' : ' in self . host
2012-06-17 11:35:59 +00:00
self . port = port
2013-02-10 22:22:18 +00:00
self . user = user
self . password = password
2013-03-19 16:28:43 +00:00
self . private_key_file = private_key_file
2013-08-02 16:08:02 +00:00
self . HASHED_KEY_MAGIC = " |1| "
2012-06-17 11:35:59 +00:00
2013-09-03 15:59:50 +00:00
fcntl . lockf ( self . runner . process_lockfile , fcntl . LOCK_EX )
self . cp_dir = utils . prepare_writeable_dir ( ' $HOME/.ansible/cp ' , mode = 0700 )
fcntl . lockf ( self . runner . process_lockfile , fcntl . LOCK_UN )
2012-06-17 11:35:59 +00:00
def connect ( self ) :
''' connect to the remote host '''
2012-07-15 16:29:53 +00:00
2013-02-10 22:22:18 +00:00
vvv ( " ESTABLISH CONNECTION FOR USER: %s " % self . user , host = self . host )
2012-08-11 21:33:34 +00:00
2012-06-26 18:59:42 +00:00
self . common_args = [ ]
2012-08-15 00:13:02 +00:00
extra_args = C . ANSIBLE_SSH_ARGS
2012-06-17 11:35:59 +00:00
if extra_args is not None :
self . common_args + = shlex . split ( extra_args )
2012-06-19 19:58:24 +00:00
else :
self . common_args + = [ " -o " , " ControlMaster=auto " ,
" -o " , " ControlPersist=60s " ,
2013-09-18 19:03:40 +00:00
" -o " , " ControlPath= %s " % ( C . ANSIBLE_SSH_CONTROL_PATH % dict ( directory = self . cp_dir ) ) ]
2013-08-20 17:03:50 +00:00
cp_in_use = False
cp_path_set = False
for arg in self . common_args :
if arg . find ( " ControlPersist " ) != - 1 :
cp_in_use = True
if arg . find ( " ControlPath " ) != - 1 :
cp_path_set = True
if cp_in_use and not cp_path_set :
2013-09-18 19:03:40 +00:00
self . common_args + = [ " -o " , " ControlPath= %s " % ( C . ANSIBLE_SSH_CONTROL_PATH % dict ( directory = self . cp_dir ) ) ]
2013-07-03 20:47:20 +00:00
if not C . HOST_KEY_CHECKING :
self . common_args + = [ " -o " , " StrictHostKeyChecking=no " ]
2012-06-26 18:59:42 +00:00
if self . port is not None :
self . common_args + = [ " -o " , " Port= %d " % ( self . port ) ]
2013-03-19 16:28:43 +00:00
if self . private_key_file is not None :
self . common_args + = [ " -o " , " IdentityFile= " + os . path . expanduser ( self . private_key_file ) ]
elif self . runner . private_key_file is not None :
2013-01-20 21:16:01 +00:00
self . common_args + = [ " -o " , " IdentityFile= " + os . path . expanduser ( self . runner . private_key_file ) ]
2013-02-10 22:22:18 +00:00
if self . password :
2012-11-27 09:51:35 +00:00
self . common_args + = [ " -o " , " GSSAPIAuthentication=no " ,
" -o " , " PubkeyAuthentication=no " ]
else :
self . common_args + = [ " -o " , " KbdInteractiveAuthentication=no " ,
2013-09-06 08:29:43 +00:00
" -o " , " PreferredAuthentications=gssapi-with-mic,gssapi-keyex,hostbased,publickey " ,
2012-11-27 09:51:35 +00:00
" -o " , " PasswordAuthentication=no " ]
2013-03-27 20:41:54 +00:00
if self . user != pwd . getpwuid ( os . geteuid ( ) ) [ 0 ] :
self . common_args + = [ " -o " , " User= " + self . user ]
2013-02-10 22:22:18 +00:00
self . common_args + = [ " -o " , " ConnectTimeout= %d " % self . runner . timeout ]
2012-06-17 11:35:59 +00:00
return self
2012-11-16 22:42:19 +00:00
def _password_cmd ( self ) :
2013-02-10 22:22:18 +00:00
if self . password :
2012-11-16 22:42:19 +00:00
try :
p = subprocess . Popen ( [ " sshpass " ] , stdin = subprocess . PIPE ,
stdout = subprocess . PIPE , stderr = subprocess . PIPE )
p . communicate ( )
except OSError :
2013-07-19 13:09:04 +00:00
raise errors . AnsibleError ( " to use the ' ssh ' connection type with passwords, you must install the sshpass program " )
2012-11-16 22:42:19 +00:00
( self . rfd , self . wfd ) = os . pipe ( )
return [ " sshpass " , " -d %d " % self . rfd ]
return [ ]
def _send_password ( self ) :
2013-02-10 22:22:18 +00:00
if self . password :
2012-11-16 22:42:19 +00:00
os . close ( self . rfd )
2013-02-10 22:22:18 +00:00
os . write ( self . wfd , " %s \n " % self . password )
2012-11-16 22:42:19 +00:00
os . close ( self . wfd )
2013-07-04 20:03:17 +00:00
def not_in_host_file ( self , host ) :
host_file = os . path . expanduser ( " ~/.ssh/known_hosts " )
if not os . path . exists ( host_file ) :
print " previous known host file not found "
return True
host_fh = open ( host_file )
data = host_fh . read ( )
host_fh . close ( )
for line in data . split ( " \n " ) :
if line is None or line . find ( " " ) == - 1 :
continue
tokens = line . split ( )
2013-08-02 16:08:02 +00:00
if tokens [ 0 ] . find ( self . HASHED_KEY_MAGIC ) == 0 :
# this is a hashed known host entry
try :
( kn_salt , kn_host ) = tokens [ 0 ] [ len ( self . HASHED_KEY_MAGIC ) : ] . split ( " | " , 2 )
hash = hmac . new ( kn_salt . decode ( ' base64 ' ) , digestmod = sha1 )
hash . update ( host )
if hash . digest ( ) == kn_host . decode ( ' base64 ' ) :
return False
except :
# invalid hashed host key, skip it
continue
else :
# standard host file entry
if host in tokens [ 0 ] :
return False
2013-07-04 20:03:17 +00:00
return True
2012-12-23 18:17:07 +00:00
def exec_command ( self , cmd , tmp_path , sudo_user , sudoable = False , executable = ' /bin/sh ' ) :
2012-06-17 11:35:59 +00:00
''' run a command on the remote host '''
2012-11-16 22:42:19 +00:00
ssh_cmd = self . _password_cmd ( )
2013-09-19 10:58:54 +00:00
ssh_cmd + = [ " ssh " , " -tt " ]
if utils . VERBOSITY > 3 :
ssh_cmd + = [ " -vvv " ]
else :
ssh_cmd + = [ " -q " ]
ssh_cmd + = self . common_args
2013-08-18 21:08:35 +00:00
if self . ipv6 :
ssh_cmd + = [ ' -6 ' ]
ssh_cmd + = [ self . host ]
2012-11-16 22:42:19 +00:00
2012-12-23 18:17:07 +00:00
if not self . runner . sudo or not sudoable :
2013-01-08 16:45:37 +00:00
if executable :
ssh_cmd . append ( executable + ' -c ' + pipes . quote ( cmd ) )
else :
ssh_cmd . append ( cmd )
2012-12-23 18:17:07 +00:00
else :
2013-01-10 05:50:56 +00:00
sudocmd , prompt = utils . make_sudo_cmd ( sudo_user , executable , cmd )
ssh_cmd . append ( sudocmd )
2012-11-16 22:42:19 +00:00
vvv ( " EXEC %s " % ssh_cmd , host = self . host )
2013-07-04 20:03:17 +00:00
not_in_host_file = self . not_in_host_file ( self . host )
if C . HOST_KEY_CHECKING and not_in_host_file :
# lock around the initial SSH connectivity so the user prompt about whether to add
# the host to known hosts is not intermingled with multiprocess output.
2013-07-04 22:17:45 +00:00
fcntl . lockf ( self . runner . process_lockfile , fcntl . LOCK_EX )
fcntl . lockf ( self . runner . output_lockfile , fcntl . LOCK_EX )
2013-07-04 20:03:17 +00:00
2012-11-23 16:52:55 +00:00
try :
# Make sure stdin is a proper (pseudo) pty to avoid: tcgetattr errors
master , slave = pty . openpty ( )
p = subprocess . Popen ( ssh_cmd , stdin = slave ,
2012-12-23 00:44:00 +00:00
stdout = subprocess . PIPE , stderr = subprocess . PIPE )
2012-11-23 16:52:55 +00:00
stdin = os . fdopen ( master , ' w ' , 0 )
2013-09-16 21:19:39 +00:00
os . close ( slave )
2012-11-23 16:52:55 +00:00
except :
p = subprocess . Popen ( ssh_cmd , stdin = subprocess . PIPE ,
2012-12-23 00:44:00 +00:00
stdout = subprocess . PIPE , stderr = subprocess . PIPE )
2012-11-23 16:52:55 +00:00
stdin = p . stdin
2012-11-16 22:42:19 +00:00
self . _send_password ( )
if self . runner . sudo and sudoable and self . runner . sudo_pass :
fcntl . fcntl ( p . stdout , fcntl . F_SETFL ,
fcntl . fcntl ( p . stdout , fcntl . F_GETFL ) | os . O_NONBLOCK )
2012-11-22 19:06:30 +00:00
sudo_output = ' '
2012-11-16 22:42:19 +00:00
while not sudo_output . endswith ( prompt ) :
rfd , wfd , efd = select . select ( [ p . stdout ] , [ ] ,
[ p . stdout ] , self . runner . timeout )
if p . stdout in rfd :
chunk = p . stdout . read ( )
if not chunk :
raise errors . AnsibleError ( ' ssh connection closed waiting for sudo password prompt ' )
sudo_output + = chunk
else :
stdout = p . communicate ( )
raise errors . AnsibleError ( ' ssh connection error waiting for sudo password prompt ' )
2012-11-23 16:52:55 +00:00
stdin . write ( self . runner . sudo_pass + ' \n ' )
2012-11-16 22:42:19 +00:00
fcntl . fcntl ( p . stdout , fcntl . F_SETFL , fcntl . fcntl ( p . stdout , fcntl . F_GETFL ) & ~ os . O_NONBLOCK )
2012-06-20 18:22:48 +00:00
# We can't use p.communicate here because the ControlMaster may have stdout open as well
stdout = ' '
2012-12-23 00:44:00 +00:00
stderr = ' '
2012-09-27 19:44:34 +00:00
while True :
2012-12-23 00:44:00 +00:00
rfd , wfd , efd = select . select ( [ p . stdout , p . stderr ] , [ ] , [ p . stdout , p . stderr ] , 1 )
2013-07-17 15:17:13 +00:00
# fail early if the sudo password is wrong
2013-08-08 15:02:52 +00:00
if self . runner . sudo and sudoable and self . runner . sudo_pass :
incorrect_password = gettext . dgettext (
" sudo " , " Sorry, try again. " )
if stdout . endswith ( " %s \r \n %s " % ( incorrect_password , prompt ) ) :
raise errors . AnsibleError ( ' Incorrect sudo password ' )
2013-07-17 15:17:13 +00:00
2012-06-20 18:22:48 +00:00
if p . stdout in rfd :
2012-09-27 19:44:34 +00:00
dat = os . read ( p . stdout . fileno ( ) , 9000 )
stdout + = dat
if dat == ' ' :
p . wait ( )
break
2012-12-23 00:44:00 +00:00
elif p . stderr in rfd :
dat = os . read ( p . stderr . fileno ( ) , 9000 )
stderr + = dat
if dat == ' ' :
p . wait ( )
break
2012-09-27 19:44:34 +00:00
elif p . poll ( ) is not None :
break
2012-11-23 16:52:55 +00:00
stdin . close ( ) # close stdin after we read from stdout (see also issue #848)
2013-07-04 20:03:17 +00:00
if C . HOST_KEY_CHECKING and not_in_host_file :
# lock around the initial SSH connectivity so the user prompt about whether to add
# the host to known hosts is not intermingled with multiprocess output.
2013-07-04 22:17:45 +00:00
fcntl . lockf ( self . runner . output_lockfile , fcntl . LOCK_UN )
fcntl . lockf ( self . runner . process_lockfile , fcntl . LOCK_UN )
2012-06-20 18:22:48 +00:00
2012-12-23 00:44:00 +00:00
if p . returncode != 0 and stderr . find ( ' Bad configuration option: ControlPersist ' ) != - 1 :
2012-10-21 11:11:39 +00:00
raise errors . AnsibleError ( ' using -c ssh on certain older ssh versions may not support ControlPersist, set ANSIBLE_SSH_ARGS= " " (or ansible_ssh_args in the config file) before running again ' )
2012-08-07 00:07:02 +00:00
2012-12-23 00:44:00 +00:00
return ( p . returncode , ' ' , stdout , stderr )
2012-06-17 11:35:59 +00:00
def put_file ( self , in_path , out_path ) :
''' transfer a file from local to remote '''
2012-08-09 01:09:14 +00:00
vvv ( " PUT %s TO %s " % ( in_path , out_path ) , host = self . host )
2012-06-17 11:35:59 +00:00
if not os . path . exists ( in_path ) :
raise errors . AnsibleFileNotFound ( " file or module does not exist: %s " % in_path )
2012-11-16 22:42:19 +00:00
cmd = self . _password_cmd ( )
2013-08-18 21:08:35 +00:00
host = self . host
if self . ipv6 :
host = ' [ %s ] ' % host
2012-11-05 22:25:40 +00:00
if C . DEFAULT_SCP_IF_SSH :
2012-11-16 22:42:19 +00:00
cmd + = [ " scp " ] + self . common_args
2013-08-18 21:08:35 +00:00
cmd + = [ in_path , host + " : " + pipes . quote ( out_path ) ]
2012-11-16 22:42:19 +00:00
indata = None
2012-11-05 22:25:40 +00:00
else :
2013-08-18 21:08:35 +00:00
cmd + = [ " sftp " ] + self . common_args + [ host ]
2013-07-20 22:28:11 +00:00
indata = " put %s %s \n " % ( pipes . quote ( in_path ) , pipes . quote ( out_path ) )
2012-11-16 22:42:19 +00:00
p = subprocess . Popen ( cmd , stdin = subprocess . PIPE ,
stdout = subprocess . PIPE , stderr = subprocess . PIPE )
self . _send_password ( )
stdout , stderr = p . communicate ( indata )
2012-06-17 11:35:59 +00:00
if p . returncode != 0 :
raise errors . AnsibleError ( " failed to transfer file to %s : \n %s \n %s " % ( out_path , stdout , stderr ) )
def fetch_file ( self , in_path , out_path ) :
''' fetch a file from remote to local '''
2012-08-09 01:09:14 +00:00
vvv ( " FETCH %s TO %s " % ( in_path , out_path ) , host = self . host )
2012-11-16 22:42:19 +00:00
cmd = self . _password_cmd ( )
2013-08-18 21:08:35 +00:00
host = self . host
if self . ipv6 :
host = ' [ %s ] ' % host
2012-11-05 22:25:40 +00:00
if C . DEFAULT_SCP_IF_SSH :
2012-11-16 22:42:19 +00:00
cmd + = [ " scp " ] + self . common_args
2013-08-18 21:08:35 +00:00
cmd + = [ host + " : " + in_path , out_path ]
2012-11-16 22:42:19 +00:00
indata = None
2012-11-05 22:25:40 +00:00
else :
2013-08-18 21:08:35 +00:00
cmd + = [ " sftp " ] + self . common_args + [ host ]
2012-11-16 22:42:19 +00:00
indata = " get %s %s \n " % ( in_path , out_path )
p = subprocess . Popen ( cmd , stdin = subprocess . PIPE ,
stdout = subprocess . PIPE , stderr = subprocess . PIPE )
self . _send_password ( )
stdout , stderr = p . communicate ( indata )
2012-06-17 11:35:59 +00:00
if p . returncode != 0 :
raise errors . AnsibleError ( " failed to transfer file from %s : \n %s \n %s " % ( in_path , stdout , stderr ) )
def close ( self ) :
2012-07-15 16:29:53 +00:00
''' not applicable since we ' re executing openssh binaries '''
2012-06-17 11:35:59 +00:00
pass
2012-07-15 16:29:53 +00:00