forked from ManageIQ/manageiq-gems-pending
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathMiqSshUtil.rb
269 lines (232 loc) · 9.13 KB
/
MiqSshUtil.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
require 'net/ssh'
class MiqSshUtil
attr_reader :status, :host
def initialize(host, user, password, options = {})
@host = host
@user = user
@password = password
@status = 0
@shell = nil
@options = {:password => @password, :remember_host => false, :verbose => :warn}.merge(options)
# Seems like in 2.9.2, there needs to be blank :keys, when we are passing private key as string
@options[:keys] = [] if options[:key_data]
# Pull the 'remember_host' key out of the hash because the SSH initializer will complain
@remember_host = @options.delete(:remember_host)
@su_user = @options.delete(:su_user)
@su_password = @options.delete(:su_password)
@passwordless_sudo = @options.delete(:passwordless_sudo)
# Obsolete, delete if passed in
@options.delete(:authentication_prompt_delay)
# don't use the ssh-agent
@options[:use_agent] = false
# Set logging to use our default handle if it exists and one was not passed in
unless @options.key?(:logger)
# @options[:logger] = $log if $log
end
end # def initialize
def get_file(from, to)
Net::SFTP.start(@host, @user, @options) do |sftp|
$log.debug "MiqSshUtil::get_file - Copying file #{@host}:#{from} to #{to}." if $log
data = sftp.download!(from, to)
$log.debug "MiqSshUtil::get_file - Copying of #{@host}:#{from} to #{to}, complete." if $log
return data
end
end
def exec(cmd, doneStr = nil)
errBuf = ""
outBuf = ""
status = nil
signal = nil
# If passwordless sudo is true, we will just prepend every command by sudo
cmd = 'sudo ' + cmd if @passwordless_sudo
run_session do |ssh|
ssh.open_channel do |channel|
channel.on_data do |_channel, data|
$log.debug "MiqSshUtil::exec - STDOUT: #{data}" if $log
outBuf << data
data.each_line { |l| return outBuf if doneStr == l.chomp } unless doneStr.nil?
end
channel.on_extended_data do |_channel, data|
$log.debug "MiqSshUtil::exec - STDERR: #{data}" if $log
errBuf << data
end
channel.on_request('exit-status') do |_channel, data|
status = data.read_long
$log.debug "MiqSshUtil::exec - STATUS: #{status}" if $log
end
channel.on_request('exit-signal') do |_channel, data|
signal = data.read_string
$log.debug "MiqSshUtil::exec - SIGNAL: #{signal}" if $log
end
channel.on_eof do |_channel|
$log.debug "MiqSshUtil::exec - EOF RECEIVED" if $log
end
channel.on_close do |_channel|
$log.debug "MiqSshUtil::exec - Command: #{cmd}, exit status: #{status}" if $log
unless signal.nil? || status.zero?
raise "MiqSshUtil::exec - Command #{cmd}, exited with signal #{signal}" unless signal.nil?
raise "MiqSshUtil::exec - Command #{cmd}, exited with status #{status}" if errBuf.empty?
raise "MiqSshUtil::exec - Command #{cmd} failed: #{errBuf}, status: #{status}"
end
return outBuf
end
$log.debug "MiqSshUtil::exec - Command: #{cmd} started." if $log
channel.exec(cmd) { |_channel, success| raise "MiqSshUtil::exec - Could not execute command #{cmd}" unless success }
end
ssh.loop
end
end # def exec
def suexec(cmd_str, doneStr = nil)
errBuf = ""
outBuf = ""
prompt = ""
cmdRX = ""
status = nil
signal = nil
state = :initial
run_session do |ssh|
temp_cmd_file(cmd_str) do |cmd|
ssh.open_channel do |channel|
# now we request a "pty" (i.e. interactive) session so we can send data back and forth if needed.
# it WILL NOT WORK without this, and it has to be done before any call to exec.
channel.request_pty(:chars_wide => 256) do |_channel, success|
raise "Could not obtain pty (i.e. an interactive ssh session)" unless success
end
channel.on_data do |channel, data|
$log.debug "MiqSshUtil::suexec - state: [#{state.inspect}] STDOUT: [#{data.hex_dump.chomp}]" if $log
if state == :prompt
# Detect the common prompts
# someuser@somehost ... $ rootuser@somehost ... # [someuser@somehost ...] $ [rootuser@somehost ...] #
prompt = data if data =~ /^\[*[\w\-\.]+@[\w\-\.]+.+\]*[\#\$]\s*$/
outBuf << data
unless doneStr.nil?
data.each_line { |l| return outBuf if doneStr == l.chomp }
end
if outBuf[-prompt.length, prompt.length] == prompt
return outBuf[0..(outBuf.length - prompt.length)]
end
end
if state == :command_sent
cmdRX << data
state = :prompt if cmdRX == "#{cmd}\r\n"
end
if (state == :password_sent)
prompt << data.lstrip
if data.strip =~ /\#/
$log.debug "MiqSshUtil::suexec - Superuser Prompt detected: sending command #{cmd}" if $log
channel.send_data("#{cmd}\n")
state = :command_sent
end
end
if (state == :initial)
prompt << data.lstrip
if data.strip =~ /[Pp]assword:/
prompt = ""
$log.debug "MiqSshUtil::suexec - Password Prompt detected: sending su password" if $log
channel.send_data("#{@su_password}\n")
state = :password_sent
end
end
end
channel.on_extended_data do |_channel, data|
$log.debug "MiqSshUtil::suexec - STDERR: #{data}" if $log
errBuf << data
end
channel.on_request('exit-status') do |_channel, data|
status = data.read_long
$log.debug "MiqSshUtil::suexec - STATUS: #{status}" if $log
end
channel.on_request('exit-signal') do |_channel, data|
signal = data.read_string
$log.debug "MiqSshUtil::suexec - SIGNAL: #{signal}" if $log
end
channel.on_eof do |_channel|
$log.debug "MiqSshUtil::suexec - EOF RECEIVED" if $log
end
channel.on_close do |_channel|
errBuf << prompt if [:initial, :password_sent].include?(state)
$log.debug "MiqSshUtil::suexec - Command: #{cmd}, exit status: #{status}" if $log
raise "MiqSshUtil::suexec - Command #{cmd}, exited with signal #{signal}" unless signal.nil?
unless status.zero?
raise "MiqSshUtil::suexec - Command #{cmd}, exited with status #{status}" if errBuf.empty?
raise "MiqSshUtil::suexec - Command #{cmd} failed: #{errBuf}, status: #{status}"
end
return outBuf
end
$log.debug "MiqSshUtil::suexec - Command: [#{cmd_str}] started." if $log
su_command = @su_user == 'root' ? "su -l\n" : "su -l #{@su_user}\n"
channel.exec(su_command) { |_channel, success| raise "MiqSshUtil::suexec - Could not execute command #{cmd}" unless success }
end
end
ssh.loop
end
end # suexec
def temp_cmd_file(cmd)
temp_remote_script = Tempfile.new(["miq-", ".sh"], "/var/tmp")
temp_file = temp_remote_script.path
begin
temp_remote_script.write(cmd)
temp_remote_script.close
remote_cmd = "chmod 700 #{temp_file}; #{temp_file}; rm -f #{temp_file}"
yield(remote_cmd)
ensure
temp_remote_script.close!
end
end
def self.shell_with_su(host, remote_user, remote_password, su_user, su_password, options = {})
options[:su_user], options[:su_password] = su_user, su_password
ssu = MiqSshUtil.new(host, remote_user, remote_password, options)
yield(ssu, nil)
rescue Net::SSH::AuthenticationFailed
raise MiqException::MiqInvalidCredentialsError
rescue Net::SSH::HostKeyMismatch
raise MiqException::MiqSshUtilHostKeyMismatch
end
def shell_exec(cmd, doneStr = nil, _shell = nil)
return exec(cmd, doneStr) if @su_user.nil?
ret = suexec(cmd, doneStr)
# Remove escape character from the end of the line
ret.sub!(/\e$/, '')
ret
end
def fileOpen(file_path, perm = 'r')
require 'tempfile'
if block_given?
Tempfile.open('miqscvmm') do |tf|
tf.close
get_file(file_path, tf.path)
File.open(tf.path, perm) { |f| yield(f) }
end
else
tf = Tempfile.open('miqscvmm')
tf.close
get_file(file_path, tf.path)
f = File.open(tf.path, perm)
return f
end
end
def fileExists?(filename)
shell_exec("test -f #{filename}") rescue return false
true
end
# This method runs the ssh session and can handle reseting the ssh fingerprint
# if it does not match and raises an error.
def run_session
first_try = true
begin
Net::SSH.start(@host, @user, @options) do |ssh|
yield(ssh)
end
rescue Net::SSH::HostKeyMismatch => e
if @remember_host == true && first_try
# Save fingerprint and try again
first_try = false
e.remember_host!
retry
else
# Re-raise error
raise e
end
end
end
end