oxidized 0.21.0 → 0.22.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (170) hide show
  1. checksums.yaml +5 -5
  2. data/.github/no-response.yml +13 -0
  3. data/.gitignore +3 -0
  4. data/.rubocop.yml +37 -0
  5. data/.rubocop_todo.yml +714 -0
  6. data/.travis.yml +7 -1
  7. data/CHANGELOG.md +341 -243
  8. data/Dockerfile +44 -16
  9. data/LICENSE +201 -0
  10. data/README.md +114 -82
  11. data/Rakefile +19 -0
  12. data/TODO.md +29 -23
  13. data/bin/oxidized +1 -2
  14. data/docs/Configuration.md +71 -31
  15. data/docs/Creating-Models.md +78 -0
  16. data/docs/Hooks.md +145 -41
  17. data/docs/Model-Notes/AireOS.md +12 -0
  18. data/docs/Model-Notes/ArbOS.md +12 -0
  19. data/docs/Model-Notes/Comware.md +14 -0
  20. data/docs/Model-Notes/EOS.md +9 -0
  21. data/docs/Model-Notes/JunOS.md +34 -0
  22. data/docs/Model-Notes/Netgear.md +68 -0
  23. data/docs/Model-Notes/README.md +19 -0
  24. data/docs/{VRP-Huawei.md → Model-Notes/VRP-Huawei.md} +10 -2
  25. data/docs/Model-Notes/XGS4600-Zyxel.md +39 -0
  26. data/docs/Outputs.md +27 -28
  27. data/docs/Ruby-API.md +38 -18
  28. data/docs/Sources.md +78 -16
  29. data/docs/Supported-OS-Types.md +171 -148
  30. data/extra/oxidized.logrotate +7 -0
  31. data/extra/oxidized.service +1 -1
  32. data/extra/rest_client.rb +4 -5
  33. data/extra/syslog.rb +16 -16
  34. data/lib/oxidized/cli.rb +3 -3
  35. data/lib/oxidized/config.rb +7 -4
  36. data/lib/oxidized/core.rb +3 -3
  37. data/lib/oxidized/hook.rb +64 -65
  38. data/lib/oxidized/hook/awssns.rb +2 -3
  39. data/lib/oxidized/hook/ciscosparkdiff.rb +49 -0
  40. data/lib/oxidized/hook/exec.rb +5 -5
  41. data/lib/oxidized/hook/githubrepo.rb +20 -14
  42. data/lib/oxidized/hook/slackdiff.rb +38 -19
  43. data/lib/oxidized/hook/xmppdiff.rb +58 -0
  44. data/lib/oxidized/input/cli.rb +5 -6
  45. data/lib/oxidized/input/ftp.rb +8 -7
  46. data/lib/oxidized/input/http.rb +39 -0
  47. data/lib/oxidized/input/ssh.rb +24 -22
  48. data/lib/oxidized/input/telnet.rb +38 -32
  49. data/lib/oxidized/jobs.rb +3 -4
  50. data/lib/oxidized/manager.rb +9 -4
  51. data/lib/oxidized/model/acos.rb +15 -16
  52. data/lib/oxidized/model/acsw.rb +3 -8
  53. data/lib/oxidized/model/aen.rb +1 -2
  54. data/lib/oxidized/model/aireos.rb +3 -5
  55. data/lib/oxidized/model/alteonos.rb +16 -18
  56. data/lib/oxidized/model/alvarion.rb +0 -4
  57. data/lib/oxidized/model/aos.rb +2 -4
  58. data/lib/oxidized/model/aos7.rb +2 -3
  59. data/lib/oxidized/model/aosw.rb +13 -15
  60. data/lib/oxidized/model/apc_aos.rb +0 -3
  61. data/lib/oxidized/model/arbos.rb +26 -0
  62. data/lib/oxidized/model/aricentiss.rb +51 -0
  63. data/lib/oxidized/model/asa.rb +33 -35
  64. data/lib/oxidized/model/asyncos.rb +41 -44
  65. data/lib/oxidized/model/audiocodes.rb +4 -8
  66. data/lib/oxidized/model/awplus.rb +84 -0
  67. data/lib/oxidized/model/boss.rb +6 -5
  68. data/lib/oxidized/model/br6910.rb +43 -45
  69. data/lib/oxidized/model/c4cmts.rb +3 -5
  70. data/lib/oxidized/model/cambium.rb +22 -0
  71. data/lib/oxidized/model/catos.rb +0 -2
  72. data/lib/oxidized/model/cisconga.rb +1 -3
  73. data/lib/oxidized/model/ciscosma.rb +37 -40
  74. data/lib/oxidized/model/ciscosmb.rb +7 -4
  75. data/lib/oxidized/model/comnetms.rb +43 -0
  76. data/lib/oxidized/model/comware.rb +9 -9
  77. data/lib/oxidized/model/coriant8600.rb +3 -5
  78. data/lib/oxidized/model/coriantgroove.rb +3 -5
  79. data/lib/oxidized/model/corianttmos.rb +1 -3
  80. data/lib/oxidized/model/cumulus.rb +26 -32
  81. data/lib/oxidized/model/datacom.rb +0 -2
  82. data/lib/oxidized/model/dcnos.rb +46 -0
  83. data/lib/oxidized/model/dlink.rb +1 -1
  84. data/lib/oxidized/model/dnos.rb +9 -5
  85. data/lib/oxidized/model/edgecos.rb +45 -0
  86. data/lib/oxidized/model/edgeos.rb +5 -3
  87. data/lib/oxidized/model/edgeswitch.rb +1 -3
  88. data/lib/oxidized/model/enterasys.rb +1 -3
  89. data/lib/oxidized/model/eos.rb +6 -8
  90. data/lib/oxidized/model/fabricos.rb +3 -5
  91. data/lib/oxidized/model/firewareos.rb +2 -5
  92. data/lib/oxidized/model/fortios.rb +21 -17
  93. data/lib/oxidized/model/ftos.rb +2 -4
  94. data/lib/oxidized/model/fujitsupy.rb +2 -4
  95. data/lib/oxidized/model/gaiaos.rb +6 -10
  96. data/lib/oxidized/model/gcombnps.rb +82 -0
  97. data/lib/oxidized/model/hatteras.rb +8 -5
  98. data/lib/oxidized/model/hirschmann.rb +8 -10
  99. data/lib/oxidized/model/hpebladesystem.rb +19 -17
  100. data/lib/oxidized/model/hpemsa.rb +0 -3
  101. data/lib/oxidized/model/ios.rb +54 -55
  102. data/lib/oxidized/model/iosxe.rb +5 -0
  103. data/lib/oxidized/model/iosxr.rb +1 -3
  104. data/lib/oxidized/model/ipos.rb +1 -3
  105. data/lib/oxidized/model/ironware.rb +12 -15
  106. data/lib/oxidized/model/isam.rb +4 -5
  107. data/lib/oxidized/model/junos.rb +8 -7
  108. data/lib/oxidized/model/masteros.rb +1 -3
  109. data/lib/oxidized/model/mlnxos.rb +3 -4
  110. data/lib/oxidized/model/model.rb +15 -7
  111. data/lib/oxidized/model/mtrlrfs.rb +1 -4
  112. data/lib/oxidized/model/ndms.rb +24 -0
  113. data/lib/oxidized/model/netgear.rb +3 -4
  114. data/lib/oxidized/model/netscaler.rb +0 -2
  115. data/lib/oxidized/model/nos.rb +1 -3
  116. data/lib/oxidized/model/nxos.rb +13 -3
  117. data/lib/oxidized/model/oneos.rb +6 -8
  118. data/lib/oxidized/model/openbsd.rb +76 -0
  119. data/lib/oxidized/model/opengear.rb +3 -5
  120. data/lib/oxidized/model/openwrt.rb +77 -0
  121. data/lib/oxidized/model/opnsense.rb +19 -0
  122. data/lib/oxidized/model/outputs.rb +1 -3
  123. data/lib/oxidized/model/panos.rb +1 -2
  124. data/lib/oxidized/model/pfsense.rb +9 -5
  125. data/lib/oxidized/model/planet.rb +8 -12
  126. data/lib/oxidized/model/powerconnect.rb +6 -9
  127. data/lib/oxidized/model/procurve.rb +18 -4
  128. data/lib/oxidized/model/quantaos.rb +3 -5
  129. data/lib/oxidized/model/routeros.rb +3 -2
  130. data/lib/oxidized/model/saos.rb +0 -1
  131. data/lib/oxidized/model/screenos.rb +3 -5
  132. data/lib/oxidized/model/sgos.rb +2 -3
  133. data/lib/oxidized/model/siklu.rb +0 -2
  134. data/lib/oxidized/model/slxos.rb +59 -0
  135. data/lib/oxidized/model/sros.rb +117 -0
  136. data/lib/oxidized/model/stoneos.rb +32 -0
  137. data/lib/oxidized/model/supermicro.rb +6 -41
  138. data/lib/oxidized/model/timos.rb +6 -114
  139. data/lib/oxidized/model/tmos.rb +1 -3
  140. data/lib/oxidized/model/tplink.rb +7 -11
  141. data/lib/oxidized/model/trango.rb +6 -7
  142. data/lib/oxidized/model/ucs.rb +0 -1
  143. data/lib/oxidized/model/voltaire.rb +3 -6
  144. data/lib/oxidized/model/voss.rb +1 -2
  145. data/lib/oxidized/model/vrp.rb +4 -5
  146. data/lib/oxidized/model/vyatta.rb +6 -4
  147. data/lib/oxidized/model/weos.rb +1 -3
  148. data/lib/oxidized/model/xos.rb +6 -5
  149. data/lib/oxidized/model/zhoneolt.rb +2 -2
  150. data/lib/oxidized/model/zynos.rb +1 -3
  151. data/lib/oxidized/model/zynoscli.rb +36 -0
  152. data/lib/oxidized/node.rb +11 -11
  153. data/lib/oxidized/node/stats.rb +15 -2
  154. data/lib/oxidized/nodes.rb +8 -8
  155. data/lib/oxidized/output/file.rb +41 -42
  156. data/lib/oxidized/output/git.rb +113 -115
  157. data/lib/oxidized/output/gitcrypt.rb +241 -242
  158. data/lib/oxidized/output/http.rb +23 -27
  159. data/lib/oxidized/output/output.rb +1 -2
  160. data/lib/oxidized/source/csv.rb +44 -45
  161. data/lib/oxidized/source/http.rb +52 -49
  162. data/lib/oxidized/source/source.rb +6 -7
  163. data/lib/oxidized/source/sql.rb +55 -51
  164. data/lib/oxidized/string.rb +3 -4
  165. data/lib/oxidized/version.rb +17 -1
  166. data/lib/oxidized/worker.rb +12 -3
  167. data/oxidized.gemspec +19 -13
  168. metadata +139 -51
  169. data/.ruby-version +0 -1
  170. data/Gemfile.lock +0 -44
@@ -35,26 +35,32 @@ class GithubRepo < Oxidized::Hook
35
35
  end
36
36
 
37
37
  Rugged::Commit.create(repo, {
38
- parents: [repo.head.target, their_branch.target],
39
- tree: merge_index.write_tree(repo),
40
- message: "Merge remote-tracking branch '#{their_branch.name}'",
41
- update_ref: "HEAD"
42
- })
38
+ parents: [repo.head.target, their_branch.target],
39
+ tree: merge_index.write_tree(repo),
40
+ message: "Merge remote-tracking branch '#{their_branch.name}'",
41
+ update_ref: "HEAD"
42
+ })
43
43
  end
44
44
 
45
45
  private
46
46
 
47
47
  def credentials
48
- @credentials ||= if cfg.has_key?('username') && cfg.has_key?('password')
49
- log "Using https auth", :debug
50
- Rugged::Credentials::UserPassword.new(username: cfg.username, password: cfg.password)
51
- else
52
- if cfg.has_key?('publickey') && cfg.has_key?('privatekey')
53
- log "Using ssh auth with key", :debug
54
- Rugged::Credentials::SshKey.new(username: 'git', publickey: File.expand_path(cfg.publickey), privatekey: File.expand_path(cfg.privatekey))
48
+ Proc.new do |url, username_from_url, allowed_types|
49
+ if cfg.has_key?('username')
50
+ git_user = cfg.username
51
+ else
52
+ git_user = username_from_url ? username_from_url : 'git'
53
+ end
54
+
55
+ if cfg.has_key?('password')
56
+ log "Authenticating using username and password as '#{git_user}'", :debug
57
+ Rugged::Credentials::UserPassword.new(username: git_user, password: cfg.password)
58
+ elsif cfg.has_key?('publickey') && cfg.has_key?('privatekey')
59
+ log "Authenticating using ssh keys as '#{git_user}'", :debug
60
+ Rugged::Credentials::SshKey.new(username: git_user, publickey: File.expand_path(cfg.publickey), privatekey: File.expand_path(cfg.privatekey), passphrase: ENV["OXIDIZED_SSH_PASSPHRASE"])
55
61
  else
56
- log "Using ssh auth with agentforwarding", :debug
57
- Rugged::Credentials::SshKeyFromAgent.new(username: 'git')
62
+ log "Authenticating using ssh agent as '#{git_user}'", :debug
63
+ Rugged::Credentials::SshKeyFromAgent.new(username: git_user)
58
64
  end
59
65
  end
60
66
  end
@@ -1,5 +1,8 @@
1
1
  require 'slack'
2
2
 
3
+ # defaults to posting a diff, if messageformat is supplied them a message will be posted too
4
+ # diffenable defaults to true
5
+
3
6
  class SlackDiff < Oxidized::Hook
4
7
  def validate_cfg!
5
8
  raise KeyError, 'hook.token is required' unless cfg.has_key?('token')
@@ -7,28 +10,44 @@ class SlackDiff < Oxidized::Hook
7
10
  end
8
11
 
9
12
  def run_hook(ctx)
10
- if ctx.node
11
- if ctx.event.to_s == "post_store"
12
- log "Connecting to slack"
13
- Slack.configure do |config|
14
- config.token = cfg.token
15
- config.proxy = cfg.proxy if cfg.has_key?('proxy')
16
- end
17
- client = Slack::Client.new
18
- client.auth_test
19
- log "Connected"
20
- gitoutput = ctx.node.output.new
21
- diff = gitoutput.get_diff ctx.node, ctx.node.group, ctx.commitref, nil
22
- title = "#{ctx.node.name.to_s} #{ctx.node.group.to_s} #{ctx.node.model.class.name.to_s.downcase}"
13
+ return unless ctx.node
14
+ return unless ctx.event.to_s == "post_store"
15
+ log "Connecting to slack"
16
+ Slack.configure do |config|
17
+ config.token = cfg.token
18
+ config.proxy = cfg.proxy if cfg.has_key?('proxy')
19
+ end
20
+ client = Slack::Client.new
21
+ client.auth_test
22
+ log "Connected"
23
+ # diff snippet - default
24
+ diffenable = true
25
+ if cfg.has_key?('diff') == true
26
+ if cfg.diff == false
27
+ diffenable = false
28
+ end
29
+ end
30
+ if diffenable == true
31
+ gitoutput = ctx.node.output.new
32
+ diff = gitoutput.get_diff ctx.node, ctx.node.group, ctx.commitref, nil
33
+ unless diff == "no diffs"
34
+ title = "#{ctx.node.name} #{ctx.node.group} #{ctx.node.model.class.name.to_s.downcase}"
23
35
  log "Posting diff as snippet to #{cfg.channel}"
24
36
  client.files_upload(channels: cfg.channel, as_user: true,
25
- content: diff[:patch].lines.to_a[4..-1].join,
26
- filetype: "diff",
27
- title: title,
28
- filename: "change"
29
- )
30
- log "Finished"
37
+ content: diff[:patch].lines.to_a[4..-1].join,
38
+ filetype: "diff",
39
+ title: title,
40
+ filename: "change")
31
41
  end
32
42
  end
43
+ # message custom formatted - optional
44
+ if cfg.has_key?('message') == true
45
+ log cfg.message
46
+ msg = cfg.message % { :node => ctx.node.name.to_s, :group => ctx.node.group.to_s, :commitref => ctx.commitref, :model => ctx.node.model.class.name.to_s.downcase }
47
+ log msg
48
+ log "Posting message to #{cfg.channel}"
49
+ client.chat_postMessage(channel: cfg.channel, text: msg, as_user: true)
50
+ end
51
+ log "Finished"
33
52
  end
34
53
  end
@@ -0,0 +1,58 @@
1
+ require 'xmpp4r'
2
+ require 'xmpp4r/muc/helper/simplemucclient'
3
+
4
+ class XMPPDiff < Oxidized::Hook
5
+ def validate_cfg!
6
+ raise KeyError, 'hook.jid is required' unless cfg.has_key?('jid')
7
+ raise KeyError, 'hook.password is required' unless cfg.has_key?('password')
8
+ raise KeyError, 'hook.channel is required' unless cfg.has_key?('channel')
9
+ raise KeyError, 'hook.nick is required' unless cfg.has_key?('nick')
10
+ end
11
+
12
+ def run_hook(ctx)
13
+ return unless ctx.node
14
+ return unless ctx.event.to_s == "post_store"
15
+ begin
16
+ Timeout.timeout(15) do
17
+ gitoutput = ctx.node.output.new
18
+ diff = gitoutput.get_diff ctx.node, ctx.node.group, ctx.commitref, nil
19
+
20
+ interesting = diff[:patch].lines.to_a[4..-1].any? do |line|
21
+ ["+", "-"].include?(line[0]) and not ["#", "!"].include?(line[1])
22
+ end
23
+ interesting &&= diff[:patch].lines.to_a[5..-1].any? { |line| line[0] == '-' }
24
+ interesting &&= diff[:patch].lines.to_a[5..-1].any? { |line| line[0] == '+' }
25
+
26
+ if interesting
27
+ log "Connecting to XMPP"
28
+ client = Jabber::Client.new(Jabber::JID.new(cfg.jid))
29
+ client.connect
30
+ sleep 1
31
+ client.auth(cfg.password)
32
+ sleep 1
33
+
34
+ log "Connected"
35
+
36
+ m = Jabber::MUC::SimpleMUCClient.new(client)
37
+ m.join(cfg.channel + "/" + cfg.nick)
38
+
39
+ log "Joined"
40
+
41
+ title = "#{ctx.node.name} #{ctx.node.group} #{ctx.node.model.class.name.to_s.downcase}"
42
+ log "Posting diff as snippet to #{cfg.channel}"
43
+
44
+ m.say(title + "\n\n" + diff[:patch].lines.to_a[4..-1].join)
45
+
46
+ sleep 1
47
+
48
+ client.close
49
+
50
+ log "Finished"
51
+
52
+ end
53
+ end
54
+ rescue Timeout::Error
55
+ log "timed out"
56
+ end
57
+ end
58
+ end
@@ -32,26 +32,25 @@ module Oxidized
32
32
  @pre_logout.each { |command, block| block ? block.call : (cmd command, nil) }
33
33
  end
34
34
 
35
- def post_login _post_login=nil, &block
35
+ def post_login _post_login = nil, &block
36
36
  unless @exec
37
37
  @post_login << [_post_login, block]
38
38
  end
39
39
  end
40
40
 
41
- def pre_logout _pre_logout=nil, &block
41
+ def pre_logout _pre_logout = nil, &block
42
42
  unless @exec
43
- @pre_logout << [_pre_logout, block]
43
+ @pre_logout << [_pre_logout, block]
44
44
  end
45
45
  end
46
46
 
47
- def username re=/^(Username|login)/
47
+ def username re = /^(Username|login)/
48
48
  @username or @username = re
49
49
  end
50
50
 
51
- def password re=/^Password/
51
+ def password re = /^Password/
52
52
  @password or @password = re
53
53
  end
54
-
55
54
  end
56
55
  end
57
56
  end
@@ -6,20 +6,22 @@ module Oxidized
6
6
  class FTP < Input
7
7
  RescueFail = {
8
8
  :debug => [
9
- #Net::SSH::Disconnect,
9
+ # Net::SSH::Disconnect,
10
10
  ],
11
11
  :warn => [
12
- #RuntimeError,
13
- #Net::SSH::AuthenticationFailed,
12
+ # RuntimeError,
13
+ # Net::SSH::AuthenticationFailed,
14
14
  ],
15
15
  }
16
16
  include Input::CLI
17
17
 
18
18
  def connect node
19
- @node = node
19
+ @node = node
20
20
  @node.model.cfg['ftp'].each { |cb| instance_exec(&cb) }
21
21
  @log = File.open(Oxidized::Config::Log + "/#{@node.ip}-ftp", 'w') if Oxidized.config.input.debug?
22
- @ftp = Net::FTP.new @node.ip, @node.auth[:username], @node.auth[:password]
22
+ @ftp = Net::FTP.new(@node.ip)
23
+ @ftp.passive = Oxidized.config.input.ftp.passive
24
+ @ftp.login @node.auth[:username], @node.auth[:password]
23
25
  connected?
24
26
  end
25
27
 
@@ -45,10 +47,9 @@ module Oxidized
45
47
 
46
48
  def disconnect
47
49
  @ftp.close
48
- #rescue Errno::ECONNRESET, IOError
50
+ # rescue Errno::ECONNRESET, IOError
49
51
  ensure
50
52
  @log.close if Oxidized.config.input.debug?
51
53
  end
52
-
53
54
  end
54
55
  end
@@ -0,0 +1,39 @@
1
+ module Oxidized
2
+ require_relative "cli"
3
+
4
+ begin
5
+ require "mechanize"
6
+ rescue LoadError
7
+ raise OxidizedError, "mechanize not found: sudo gem install mechanize"
8
+ end
9
+
10
+ class HTTP < Input
11
+ include Input::CLI
12
+
13
+ def connect node
14
+ @node = node
15
+ @m = Mechanize.new
16
+ @log = File.open(Oxidized::Config::Log + "/#{@node.ip}-http", "w") if Oxidized.config.input.debug?
17
+
18
+ @node.model.cfg["http"].each { |cb| instance_exec(&cb) }
19
+
20
+ url = URI::HTTP.build host: @node.ip, path: @main_page
21
+ @m_page = @m.get(url.to_s)
22
+ login
23
+ end
24
+
25
+ def cmd callback
26
+ instance_exec(&callback)
27
+ end
28
+
29
+ private
30
+
31
+ def log str
32
+ @log.write(str) if @log
33
+ end
34
+
35
+ def disconnect
36
+ @log.close if Oxidized.config.input.debug?
37
+ end
38
+ end
39
+ end
@@ -24,20 +24,24 @@ module Oxidized
24
24
  secure = Oxidized.config.input.ssh.secure
25
25
  @log = File.open(Oxidized::Config::Log + "/#{@node.ip}-ssh", 'w') if Oxidized.config.input.debug?
26
26
  port = vars(:ssh_port) || 22
27
-
27
+
28
28
  ssh_opts = {
29
- :port => port.to_i,
30
- :password => @node.auth[:password], :timeout => Oxidized.config.timeout,
31
- :paranoid => secure,
32
- :auth_methods => %w(none publickey password keyboard-interactive),
33
- :number_of_password_prompts => 0,
34
- }
29
+ port: port.to_i,
30
+ paranoid: secure,
31
+ keepalive: true,
32
+ password: @node.auth[:password], :timeout => Oxidized.config.timeout,
33
+ number_of_password_prompts: 0,
34
+ }
35
+
36
+ auth_methods = vars(:auth_methods) || %w(none publickey password)
37
+ ssh_opts[:auth_methods] = auth_methods
38
+ Oxidized.logger.debug "AUTH METHODS::#{auth_methods}"
35
39
 
36
40
  if proxy_host = vars(:ssh_proxy)
37
41
  proxy_command = "ssh "
38
42
  proxy_command += "-o StrictHostKeyChecking=no " unless secure
39
43
  proxy_command += "#{proxy_host} -W %h:%p"
40
- proxy = Net::SSH::Proxy::Command.new(proxy_command)
44
+ proxy = Net::SSH::Proxy::Command.new(proxy_command)
41
45
  ssh_opts[:proxy] = proxy
42
46
  end
43
47
 
@@ -52,7 +56,7 @@ module Oxidized
52
56
  begin
53
57
  login
54
58
  rescue Timeout::Error
55
- raise PromptUndetect, [ @output, 'not matching configured prompt', @node.prompt ].join(' ')
59
+ raise PromptUndetect, [@output, 'not matching configured prompt', @node.prompt].join(' ')
56
60
  end
57
61
  end
58
62
  connected?
@@ -62,7 +66,7 @@ module Oxidized
62
66
  @ssh and not @ssh.closed?
63
67
  end
64
68
 
65
- def cmd cmd, expect=node.prompt
69
+ def cmd cmd, expect = node.prompt
66
70
  Oxidized.logger.debug "lib/oxidized/input/ssh.rb #{cmd} @ #{node.name} with expect: #{expect.inspect}"
67
71
  if @exec
68
72
  @ssh.exec! cmd
@@ -100,7 +104,7 @@ module Oxidized
100
104
  ch.on_data do |_ch, data|
101
105
  if Oxidized.config.input.debug?
102
106
  @log.print data
103
- @log.fsync
107
+ @log.flush
104
108
  end
105
109
  @output << data
106
110
  @output = @node.model.expects @output
@@ -117,19 +121,18 @@ module Oxidized
117
121
  # some models have SSH auth or terminal auth based on version of code
118
122
  # if SSH is configured for terminal auth, we'll still try to detect prompt
119
123
  def login
120
- if @username
121
- match = expect username, @node.prompt
122
- if match == username
123
- cmd @node.auth[:username], password
124
- cmd @node.auth[:password]
125
- end
126
- else
127
- expect @node.prompt
124
+ match_re = [@node.prompt]
125
+ match_re << @username if @username
126
+ match_re << @password if @password
127
+ until (match = expect(match_re)) == @node.prompt
128
+ cmd(@node.auth[:username], nil) if match == @username
129
+ cmd(@node.auth[:password], nil) if match == @password
130
+ match_re.delete match
128
131
  end
129
132
  end
130
133
 
131
- def exec state=nil
132
- state == nil ? @exec : (@exec=state) unless vars :ssh_no_exec
134
+ def exec state = nil
135
+ state == nil ? @exec : (@exec = state) unless vars :ssh_no_exec
133
136
  end
134
137
 
135
138
  def cmd_shell(cmd, expect_re)
@@ -152,6 +155,5 @@ module Oxidized
152
155
  end
153
156
  end
154
157
  end
155
-
156
158
  end
157
159
  end
@@ -10,15 +10,16 @@ module Oxidized
10
10
  @node = node
11
11
  @timeout = Oxidized.config.timeout
12
12
  @node.model.cfg['telnet'].each { |cb| instance_exec(&cb) }
13
+ @log = File.open(Oxidized::Config::Log + "/#{@node.ip}-telnet", 'w') if Oxidized.config.input.debug?
13
14
  port = vars(:telnet_port) || 23
14
15
 
15
- opt = { 'Host' => @node.ip,
16
- 'Port' => port.to_i,
17
- 'Timeout' => @timeout,
18
- 'Model' => @node.model }
19
- opt['Output_log'] = Oxidized::Config::Log + "/#{@node.ip}-telnet" if Oxidized.config.input.debug?
16
+ telnet_opts = { 'Host' => @node.ip,
17
+ 'Port' => port.to_i,
18
+ 'Timeout' => @timeout,
19
+ 'Model' => @node.model,
20
+ 'Log' => @log }
20
21
 
21
- @telnet = Net::Telnet.new opt
22
+ @telnet = Net::Telnet.new telnet_opts
22
23
  if @node.auth[:username] and @node.auth[:username].length > 0
23
24
  expect username
24
25
  @telnet.puts @node.auth[:username]
@@ -28,7 +29,7 @@ module Oxidized
28
29
  begin
29
30
  expect @node.prompt
30
31
  rescue Timeout::Error
31
- raise PromptUndetect, [ 'unable to detect prompt:', @node.prompt ].join(' ')
32
+ raise PromptUndetect, ['unable to detect prompt:', @node.prompt].join(' ')
32
33
  end
33
34
  end
34
35
 
@@ -36,7 +37,7 @@ module Oxidized
36
37
  @telnet and not @telnet.sock.closed?
37
38
  end
38
39
 
39
- def cmd cmd, expect=@node.prompt
40
+ def cmd cmd, expect = @node.prompt
40
41
  Oxidized.logger.debug "Telnet: #{cmd} @#{@node.name}"
41
42
  args = { 'String' => cmd }
42
43
  args.merge!({ 'Match' => expect, 'Timeout' => @timeout }) if expect
@@ -62,13 +63,14 @@ module Oxidized
62
63
  disconnect_cli
63
64
  @telnet.close
64
65
  rescue Errno::ECONNRESET
66
+ ensure
67
+ @log.close if Oxidized.config.input.debug?
68
+ (@telnet.close rescue true) unless @telnet.sock.closed?
65
69
  end
66
70
  end
67
-
68
71
  end
69
72
  end
70
73
 
71
-
72
74
  class Net::Telnet
73
75
  ## FIXME: we just need 'line = model.expects line' to handle pager
74
76
  ## how to do this, without redefining the whole damn thing
@@ -79,6 +81,7 @@ class Net::Telnet
79
81
  waittime = @options["Waittime"]
80
82
  fail_eof = @options["FailEOF"]
81
83
  model = @options["Model"]
84
+ @log = @options["Log"]
82
85
 
83
86
  if options.kind_of?(Hash)
84
87
  prompt = if options.has_key?("Match")
@@ -86,7 +89,7 @@ class Net::Telnet
86
89
  elsif options.has_key?("Prompt")
87
90
  options["Prompt"]
88
91
  elsif options.has_key?("String")
89
- Regexp.new( Regexp.quote(options["String"]) )
92
+ Regexp.new(Regexp.quote(options["String"]))
90
93
  end
91
94
  time_out = options["Timeout"] if options.has_key?("Timeout")
92
95
  waittime = options["Waittime"] if options.has_key?("Waittime")
@@ -102,9 +105,9 @@ class Net::Telnet
102
105
  line = ''
103
106
  buf = ''
104
107
  rest = ''
105
- until(prompt === line and not IO::select([@sock], nil, nil, waittime))
108
+ until prompt === line and not IO::select([@sock], nil, nil, waittime)
106
109
  unless IO::select([@sock], nil, nil, time_out)
107
- raise TimeoutError, "timed out while waiting for more data"
110
+ raise Timeout::Error, "timed out while waiting for more data"
108
111
  end
109
112
  begin
110
113
  c = @sock.readpartial(1024 * 1024)
@@ -114,32 +117,35 @@ class Net::Telnet
114
117
  c = rest + c
115
118
  if Integer(c.rindex(/#{IAC}#{SE}/no) || 0) <
116
119
  Integer(c.rindex(/#{IAC}#{SB}/no) || 0)
117
- buf = preprocess(c[0 ... c.rindex(/#{IAC}#{SB}/no)])
118
- rest = c[c.rindex(/#{IAC}#{SB}/no) .. -1]
120
+ buf = preprocess(c[0...c.rindex(/#{IAC}#{SB}/no)])
121
+ rest = c[c.rindex(/#{IAC}#{SB}/no)..-1]
119
122
  elsif pt = c.rindex(/#{IAC}[^#{IAC}#{AO}#{AYT}#{DM}#{IP}#{NOP}]?\z/no) ||
120
123
  c.rindex(/\r\z/no)
121
- buf = preprocess(c[0 ... pt])
122
- rest = c[pt .. -1]
124
+ buf = preprocess(c[0...pt])
125
+ rest = c[pt..-1]
123
126
  else
124
127
  buf = preprocess(c)
125
128
  rest = ''
126
129
  end
127
- else
128
- # Not Telnetmode.
129
- #
130
- # We cannot use preprocess() on this data, because that
131
- # method makes some Telnetmode-specific assumptions.
132
- buf = rest + c
133
- rest = ''
134
- unless @options["Binmode"]
135
- if pt = buf.rindex(/\r\z/no)
136
- buf = buf[0 ... pt]
137
- rest = buf[pt .. -1]
138
- end
139
- buf.gsub!(/#{EOL}/no, "\n")
140
- end
130
+ else
131
+ # Not Telnetmode.
132
+ #
133
+ # We cannot use preprocess() on this data, because that
134
+ # method makes some Telnetmode-specific assumptions.
135
+ buf = rest + c
136
+ rest = ''
137
+ unless @options["Binmode"]
138
+ if pt = buf.rindex(/\r\z/no)
139
+ buf = buf[0...pt]
140
+ rest = buf[pt..-1]
141
+ end
142
+ buf.gsub!(/#{EOL}/no, "\n")
143
+ end
144
+ end
145
+ if Oxidized.config.input.debug?
146
+ @log.print buf
147
+ @log.flush
141
148
  end
142
- @log.print(buf) if @options.has_key?("Output_log")
143
149
  line += buf
144
150
  line = model.expects line
145
151
  line = yield line if block_given?