├── VERSION.yml
├── lib
├── router
│ ├── common.rb
│ ├── cisco
│ │ ├── common
│ │ │ ├── show.rb
│ │ │ ├── common.rb
│ │ │ └── ping.rb
│ │ ├── ios
│ │ │ ├── termserver.rb
│ │ │ ├── ios.rb
│ │ │ └── modes.rb
│ │ └── iox
│ │ │ ├── iox.rb
│ │ │ └── modes.rb
│ ├── juniper
│ │ └── junos
│ │ │ ├── show.rb
│ │ │ ├── junos.rb
│ │ │ ├── modes.rb
│ │ │ └── ping.rb
│ ├── vyatta
│ │ ├── show.rb
│ │ ├── vyatta.rb
│ │ ├── modes.rb
│ │ └── ping.rb
│ ├── modes.rb
│ └── error.rb
├── misc
│ ├── passwd.rb
│ ├── shell.rb
│ └── base.rb
├── expect4r.rb
└── expect
│ └── io.rb
├── test
├── expect4r_test.rb
├── misc
│ ├── passwd_test.rb
│ └── base_test.rb
└── router
│ └── cisco
│ └── iox
│ └── iox_test.rb
├── LICENSE
├── Rakefile
├── CHANGELOG
├── README.rdoc
└── COPYING
/VERSION.yml:
--------------------------------------------------------------------------------
1 | ---
2 | :major: 0
3 | :minor: 0
4 | :patch: 10
5 | :build:
6 |
--------------------------------------------------------------------------------
/lib/router/common.rb:
--------------------------------------------------------------------------------
1 |
2 | require 'rubygems'
3 | require 'highline/import'
4 | require 'misc/passwd'
5 |
6 | module Expect4r::Router
7 | module Common
8 |
9 | def console?
10 | @port.to_i > 0
11 | end
12 |
13 | def io_escape_char_cb
14 | putc "\r" if console?
15 | end
16 |
17 | end
18 | end
19 |
--------------------------------------------------------------------------------
/lib/router/cisco/common/show.rb:
--------------------------------------------------------------------------------
1 | module Expect4r::Router::CiscoCommon
2 | module Show
3 |
4 | def show(s, arg={})
5 | output = []
6 | nlines = 0
7 | s.each_line { |l|
8 | next unless l.strip.size>0
9 | output << exec("show #{l}", arg) if l.strip.size>0
10 | nlines +=1
11 | }
12 | nlines > 1 ? output : output[0]
13 | end
14 |
15 | end
16 | end
17 |
--------------------------------------------------------------------------------
/test/expect4r_test.rb:
--------------------------------------------------------------------------------
1 | require 'expect4r'
2 | require "test/unit"
3 | class TestAutoload < Test::Unit::TestCase
4 | include Expect4r
5 | def test_autoload
6 | assert Expect4r::Iox.new_ssh 'hostname', 'username'
7 | assert Expect4r::Iox.new_telnet 'hostname', 'username'
8 | assert Expect4r::Ios.new_ssh 'hostname', 'username'
9 | assert Expect4r::J.new_ssh 'hostname', 'username'
10 | assert Expect4r::Shell.new
11 | end
12 | end
13 |
--------------------------------------------------------------------------------
/test/misc/passwd_test.rb:
--------------------------------------------------------------------------------
1 | require "test/unit"
2 | require "misc/passwd"
3 |
4 | class TestMiscPasswd < Test::Unit::TestCase
5 | def test_cipher
6 | assert_not_equal 'my password', Expect4r.cipher('my password')
7 | assert_equal 'my password', Expect4r.decipher(Expect4r.cipher('my password'))
8 | assert_not_equal 'my password', Expect4r.cipher('my password', 'abcdef')
9 | assert_equal 'my password', Expect4r.decipher(Expect4r.cipher('my password'))
10 | end
11 | end
--------------------------------------------------------------------------------
/lib/router/juniper/junos/show.rb:
--------------------------------------------------------------------------------
1 |
2 | module Expect4r::Router::Junos
3 | module Show
4 |
5 | def show(s, arg={})
6 | output = []
7 | s.each_line { |l|
8 | output << exec("show #{l}", arg) if l.strip.size>0
9 | }
10 | output
11 | end
12 |
13 | def method_missing(name, *args, &block)
14 | if name.to_s =~ /^show_/
15 | cmd = name.to_s.split('_').join(' ') + args.join(' ')
16 | output = __send__ :exec, cmd, *args
17 | else
18 | super
19 | end
20 | end
21 |
22 | end
23 | end
24 |
--------------------------------------------------------------------------------
/lib/router/cisco/ios/termserver.rb:
--------------------------------------------------------------------------------
1 | module Expect4r::Router::Ios
2 | module TermServer
3 |
4 | def powercycle(lineno)
5 | config "line #{lineno}\nmodem dtr\nmodem dtr-active"
6 | sleep 2
7 | config "line #{lineno}\nno modem dtr\nno modem dtr-active"
8 | end
9 | def clear_line(lineno)
10 | confirm = [/\[confirm\]/, ""]
11 | @matches << confirm
12 | putline "clear line #{lineno}"
13 | rescue Expect4r::Router::Error::SyntaxError => e
14 | puts e.err_msg
15 | ensure
16 | @matches.delete confirm
17 | end
18 |
19 | end
20 | end
21 |
--------------------------------------------------------------------------------
/lib/router/vyatta/show.rb:
--------------------------------------------------------------------------------
1 |
2 | module Expect4r::Router::Vyatta
3 | module Show
4 |
5 | def show(s, arg={})
6 | output = []
7 | s.each_line { |l|
8 | output << exec("show #{l}", arg) if l.strip.size>0
9 | }
10 | output
11 | end
12 |
13 | def method_missing(name, *args, &block)
14 | if name.to_s =~ /^show_/
15 | cmd = name.to_s.split('_').join(' ') + args.join(' ')
16 | output = __send__ :exec, cmd, *args
17 | else
18 | super
19 | end
20 | end
21 |
22 | # count | match | no-match | no-more | more
23 | def method_missing(name, *args, &block)
24 | @invalid_inputs ||=[]
25 | super if @invalid_inputs.include?(name.to_s)
26 | cmd = name.to_s.split('_').join(' ')
27 | cmd += ""
28 | end
29 |
30 | end
31 | end
32 |
--------------------------------------------------------------------------------
/lib/misc/passwd.rb:
--------------------------------------------------------------------------------
1 | require 'rubygems'
2 |
3 | begin
4 |
5 | require 'openssl'
6 | require 'digest/sha1'
7 |
8 | module Expect4r
9 | def self.cipher(this, pwd='expect4r')
10 | c = OpenSSL::Cipher.new("aes-256-cbc")
11 | c.encrypt
12 | c.key = key = Digest::SHA1.hexdigest(pwd)[0..31]
13 | e = c.update(this)
14 | e << c.final
15 | end
16 | def self.decipher(cipher,pwd='expect4r')
17 | c = OpenSSL::Cipher.new("aes-256-cbc")
18 | c.decrypt
19 | c.key = key = Digest::SHA1.hexdigest(pwd)[0..31]
20 | d = c.update(cipher)
21 | d << c.final
22 | end
23 | end
24 |
25 | rescue LoadError => e
26 |
27 | # module Expect4r
28 | # def self.cipher(arg, pwd='')
29 | # arg
30 | # end
31 | # def self.decipher(arg,pwd='')
32 | # arg
33 | # end
34 | # end
35 |
36 | raise
37 |
38 | end
39 |
--------------------------------------------------------------------------------
/lib/router/modes.rb:
--------------------------------------------------------------------------------
1 | module Expect4r
2 | module Router
3 | module Common
4 | module Modes
5 |
6 | # Adds a in? mixin.
7 | #
8 | # Examples:
9 | #
10 | # >> iox.in?
11 | # => *:exec*
12 | # >> iox.config
13 | # => *:config*
14 | # >> iox.in?
15 | # => *:config*
16 | # >> iox.shell
17 | # => *:shell*
18 | # >> iox.in?
19 | # => *:shell*
20 | #
21 | def in?(mode=:none)
22 | login unless connected?
23 | case mode
24 | when :exec ; exec?
25 | when :config ; config?
26 | when :shell ; shell?
27 | else
28 | _mode_?
29 | end
30 | end
31 | def change_mode_to(mode)
32 | login unless connected?
33 | case mode
34 | when :exec ; to_exec
35 | when :config ; to_config
36 | when :shell ; to_shell
37 | end
38 | end
39 | def _mode_?
40 | putline ' ', :no_trim=>true, :no_echo=>true unless @lp
41 | if exec?
42 | :exec
43 | elsif config?
44 | :config
45 | elsif shell?
46 | :shell
47 | else
48 | end
49 | end
50 | end
51 | end
52 | end
53 | end
--------------------------------------------------------------------------------
/lib/misc/shell.rb:
--------------------------------------------------------------------------------
1 | module Expect4r
2 | class Shell < Expect4r::Base
3 | def initialize()
4 | super
5 | ENV['PROMPT_COMMAND']="date +%k:%m:%S"
6 | ENV['PS1']="shell>"
7 | @ps1 = /shell>$/
8 | @shell = ENV['SHELL'] || 'bash'
9 | login
10 | end
11 | def login
12 | spawn @shell
13 | end
14 | end
15 | class RShell < ::Expect4r::BaseLoginObject
16 | def initialize(*args)
17 | super
18 | default_ps1
19 | end
20 | def logout
21 | default_ps1
22 | super
23 | end
24 | def login(arg={})
25 | super(spawnee, arg)
26 | cmd %{export COLUMNS=1024}
27 | end
28 | # FIXME: 1.9.2 bug:
29 | # It calls LoginBaseOject#login() instead of calling J#login()
30 | # modified login_by_proxy to call _login_ seems to work.
31 | alias :_login_ :login
32 |
33 | def ps1=(val)
34 | # Assumes bash
35 | @ps1_bis = /#{val}$/
36 | cmd %? export PS1='#{val}' ?
37 | @ps1 = @ps1_bis
38 | end
39 | def cmd(*args)
40 | exp_send *args
41 | end
42 |
43 | private
44 |
45 | def default_ps1
46 | @ps1 = /.+[^\#](\#|\$)\s+$/
47 | end
48 | end
49 | end
50 |
51 | __END__
52 |
53 |
54 | cmd %? PROMPT_COMMAND="echo -n [$(date +%k:%m:%S)]: && pwd" && export PS1='#{val}' ?
55 |
--------------------------------------------------------------------------------
/lib/expect4r.rb:
--------------------------------------------------------------------------------
1 | require 'expect/io'
2 | require 'misc/base.rb'
3 | require 'misc/passwd'
4 |
5 | module Expect4r
6 |
7 | autoload :Iox, 'router/cisco/iox/iox'
8 | autoload :Ios, 'router/cisco/ios/ios'
9 | autoload :J, 'router/juniper/junos/junos'
10 | autoload :V, 'router/vyatta/vyatta'
11 | autoload :Shell, 'misc/shell'
12 | autoload :RShell, 'misc/shell'
13 |
14 | module Router
15 | autoload :Common, 'router/common'
16 | module Common
17 | autoload :Modes, 'router/modes'
18 | end
19 | module Error
20 | autoload :RouterError, 'router/error'
21 | autoload :SyntaxError, 'router/error'
22 | autoload :SemanticError, 'router/error'
23 | autoload :PingError, 'router/error'
24 | end
25 | module CiscoCommon
26 | autoload :Show, 'router/cisco/common/show'
27 | autoload :Ping, 'router/cisco/common/ping'
28 | end
29 | module Ios
30 | autoload :TermServer, 'router/cisco/ios/termserver'
31 | autoload :Modes, 'router/cisco/ios/modes'
32 | end
33 | module Iox
34 | autoload :Modes, 'router/cisco/iox/modes'
35 | end
36 | module Vyatta
37 | autoload :Modes, 'router/vyatta/modes'
38 | autoload :Ping, 'router/vyatta/ping'
39 | autoload :Show, 'router/vyatta/show'
40 | end
41 | module Junos
42 | autoload :Modes, 'router/juniper/junos/modes'
43 | autoload :Show, 'router/juniper/junos/show'
44 | autoload :Ping, 'router/juniper/junos/ping'
45 | end
46 | end
47 |
48 | end
49 |
--------------------------------------------------------------------------------
/lib/router/juniper/junos/junos.rb:
--------------------------------------------------------------------------------
1 | class Expect4r::J < ::Expect4r::BaseLoginObject
2 | include Expect4r
3 | include Expect4r::Router::Error
4 | include Expect4r::Router::Common
5 | include Expect4r::Router::Common::Modes
6 | include Expect4r::Router::Junos::Modes
7 | include Expect4r::Router::Junos::Show
8 | include Expect4r::Router::Junos::Ping
9 |
10 | def initialize(*args)
11 | super
12 | @ps1 = /(^|\r\r)([-a-zA-z@_~=\.\(\)\d]+(>|\#|%)|%|\$) $/
13 | @more = /---\(more(| \d+\%)\)---/
14 | end
15 |
16 | def login(arg={})
17 | super(spawnee, arg)
18 | exec 'set cli screen-length 0'
19 | self
20 | end
21 | # FIXME: 1.9.2 bug:
22 | # It calls LoginBaseOject#login() instead of calling J#login()
23 | # modified login_by_proxy to call _login_ seems to work.
24 | alias :_login_ :login
25 |
26 | def console?
27 | @port>0
28 | end
29 |
30 | def putline(line,arg={})
31 | o, rc = super
32 | raise SyntaxError.new(self.class.to_s, line) if o.join =~ /(unknown command|syntax error)/
33 | o
34 | end
35 |
36 | def rollback
37 | return unless config?
38 | top
39 | putline 'rollback'
40 | end
41 |
42 | def top
43 | putline 'top'
44 | end
45 |
46 | def exit
47 | putline 'exit'
48 | end
49 |
50 | def cli
51 | putline 'cli'
52 | end
53 |
54 | def commit(arg={})
55 | return unless config?
56 | @matches << [/Exit with uncommitted changes.+\(yes\)/, 'yes']
57 | output = putline "commit", arg
58 | if /error: configuration check-out failed/.match(output.join)
59 | rollback
60 | raise SemanticError.new(self.class.to_s, output)
61 | end
62 | output
63 | end
64 |
65 | private
66 |
67 | end
68 |
--------------------------------------------------------------------------------
/lib/router/cisco/iox/iox.rb:
--------------------------------------------------------------------------------
1 |
2 | require 'router/cisco/common/common'
3 |
4 | class Expect4r::Iox < ::Expect4r::BaseLoginObject
5 |
6 | include Expect4r
7 | include Expect4r::Router
8 | include Expect4r::Router::Common
9 | include Expect4r::Router::Common::Modes
10 | include Expect4r::Router::CiscoCommon
11 | include Expect4r::Router::Iox::Modes
12 | include Expect4r::Router::CiscoCommon::Show
13 | include Expect4r::Router::CiscoCommon::Ping
14 |
15 | def initialize(*args)
16 | super
17 | @ps1 = /(.*)(>|#|\$)\s*$/
18 | @more = / --More-- /
19 | end
20 |
21 | def login(arg={})
22 | super(spawnee, arg)
23 | config 'no logging console' if port>0
24 | exec "terminal len 0\nterminal width 0"
25 | self
26 | end
27 | # FIXME: 1.9.2 bug:
28 | # It calls LoginBaseOject#login() instead of calling J#login()
29 | # modified login_by_proxy to call _login_ seems to work.
30 | alias :_login_ :login
31 |
32 | def commit(arg={})
33 | return unless config?
34 | output = putline "commit", arg
35 | if /\% Failed to commit/.match(output.join)
36 | err = show_configuration_failed
37 | abort_config
38 | raise Iox::SemanticError.new(self.class.to_s, show_configuration_failed)
39 | end
40 | output
41 | end
42 |
43 | def putline(line,*args)
44 | output, rc = super
45 | raise SyntaxError.new(self.class.to_s, line) if output.join =~ /\% Invalid input detected at/
46 | output
47 | end
48 |
49 | private
50 |
51 | def abort_config
52 | return unless config?
53 | putline 'abort' # make sure buffer config is cleaned up.
54 | nil
55 | end
56 |
57 | def show_configuration_failed
58 | putline 'show configuration failed'
59 | end
60 |
61 | end
62 |
--------------------------------------------------------------------------------
/lib/router/vyatta/vyatta.rb:
--------------------------------------------------------------------------------
1 | class Expect4r::V < ::Expect4r::BaseLoginObject
2 | include Expect4r
3 | include Expect4r::Router::Error
4 | include Expect4r::Router::Common
5 | include Expect4r::Router::Common::Modes
6 | include Expect4r::Router::Vyatta::Modes
7 | include Expect4r::Router::Vyatta::Ping
8 | include Expect4r::Router::Vyatta::Show
9 |
10 | class << self
11 | # v = V.new_telnet 'hostname'
12 | def new_telnet(*args)
13 | if args.size==1 and args[0].is_a?(String)
14 | super :host=> args[0], :user=>'vyatta', :pwd=>'vyatta'
15 | else
16 | super
17 | end
18 | end
19 |
20 | # v = V.new_ssh 'hostname'
21 | def new_ssh(*args)
22 | if args.size==1 and args[0].is_a?(String)
23 | super :host=> args[0], :user=>'vyatta', :pwd=>'vyatta'
24 | else
25 | super
26 | end
27 | end
28 | end
29 |
30 | def initialize(*args)
31 | super
32 | @ps1 = /([A-z\d]+)@([A-z\d]+)(:[^\$]+|)(#|\$) $/
33 | end
34 |
35 | def login(arg={})
36 | super(spawnee, arg)
37 | putline "terminal length 0"
38 | putline "terminal width 0"
39 | self
40 | end
41 | alias :_login_ :login
42 |
43 | def putline(line,arg={})
44 | o, rc = super
45 | raise SyntaxError.new(self.class.to_s, line) if o.join =~ /(% unknown|Invalid command)/
46 | o
47 | end
48 |
49 | def top
50 | putline 'top'
51 | end
52 |
53 | def exit
54 | putline 'exit'
55 | end
56 |
57 | def exit_discard
58 | putline 'exit discard'
59 | end
60 |
61 | def commit(arg={})
62 | return unless config?
63 | @matches << [/Exit with uncommitted changes.+\(yes\)/, 'yes']
64 | output = putline "commit", arg
65 | if /error: configuration check-out failed/.match(output.join)
66 | rollack
67 | raise SemanticError.new(self.class.to_s, output)
68 | end
69 | output
70 | end
71 |
72 | private
73 |
74 | end
75 |
--------------------------------------------------------------------------------
/lib/router/cisco/common/common.rb:
--------------------------------------------------------------------------------
1 |
2 | module Expect4r::Router
3 | module CiscoCommon
4 |
5 | #
6 | # Returns the config level depths:
7 | #
8 | # Example:
9 | #
10 | # iox(config-ospf-ar)#
11 | #
12 | # irb> iox.config_lvl?
13 | # => 3
14 | #
15 | def config_lvl?
16 | return -1 unless config?
17 | @lp =~ /\(config(|.+)\)/
18 | lvl = Regexp.last_match(1).split('-').size
19 | lvl == 0 ? 1 : lvl
20 | end
21 |
22 | def top
23 | return unless config?
24 | 1.upto(config_lvl? - 1) { putline 'exit'}
25 | end
26 |
27 | def top?
28 | return false unless config?
29 | @lp =~ /\(config(|.+)\)/
30 | Regexp.last_match(1).size == 0
31 | end
32 |
33 | def method_missing(name, *args, &block)
34 | if name.to_s =~ /^show_/
35 | cmd = name.to_s.split('_').join(' ') + args.join(' ')
36 | cmd.gsub!(/running config/, 'running-config')
37 | output = __send__ :exec, cmd, *args
38 | elsif name.to_s =~ /^shell_/
39 | cmd = name.to_s.split('_')[1..-1].join(' ') + args.join(' ')
40 | output = __send__ :shell, cmd, *args
41 | else
42 | super
43 | end
44 | end
45 |
46 | def enable
47 | proc = Proc.new {
48 | puts "FOUND WE HAVE A BAD PASSWORD SITUATION"
49 | }
50 | @pre_matches ||= []
51 | @pre_matches << [/Bad Password/i, proc]
52 |
53 | enable_pwd = [/^Password: $/, spawnee_enable_password]
54 | @matches << enable_pwd
55 | exp_send 'enable'
56 | @matches =[]
57 | @pre_matches=[]
58 | exec "term len 0\nterm width 0"
59 | rescue
60 | raise
61 | ensure
62 | @matches.delete enable_pwd
63 | end
64 |
65 | def login(arg={})
66 | # Skip the default banner.
67 | proc = Proc.new {
68 | read_until /QUICK START GUIDE/, 2
69 | }
70 | @pre_matches = []
71 | @pre_matches << [/Cisco Configuration Professional/, proc]
72 |
73 | super(spawnee,arg)
74 | enable unless arg[:no_enable]
75 |
76 | self
77 | end
78 | # FIXME: 1.9.2 bug:
79 | # It calls LoginBaseOject#login() instead of calling J#login()
80 | # modified login_by_proxy to call _login_ seems to work.
81 | alias :_login_ :login
82 |
83 | end
84 | end
85 |
86 |
--------------------------------------------------------------------------------
/lib/router/cisco/ios/ios.rb:
--------------------------------------------------------------------------------
1 | require 'router/cisco/common/common'
2 |
3 | class Expect4r::Ios < ::Expect4r::BaseLoginObject
4 |
5 | include Expect4r
6 | include Expect4r::Router::Common
7 | include Expect4r::Router::Common::Modes
8 | include Expect4r::Router::Ios::Modes
9 | include Expect4r::Router::CiscoCommon
10 | include Expect4r::Router::CiscoCommon::Show
11 | include Expect4r::Router::CiscoCommon::Ping
12 | include Expect4r::Router::Ios::TermServer
13 |
14 | def initialize(*args)
15 | super
16 | @ps1 = /(.*)(>|#|\$)\s*$/
17 | @more = / --More-- /
18 | end
19 |
20 | def putline(line,*args)
21 | output, rc = super
22 | return output unless error?(output)
23 | raise BadPasswordError.new(self.class.to_s,line) if output =~ /Bad password/
24 | raise SyntaxError.new(self.class.to_s,line)
25 | end
26 |
27 | private
28 |
29 | if "a"[0]==97
30 | def string_start_with_pct_char?(s)
31 | return unless s
32 | s[0].chr == '%' if s[0]
33 | end
34 | else
35 | def string_start_with_pct_char?(s)
36 | return unless s
37 | s[0] == '%'
38 | end
39 | end
40 |
41 | def error?(output)
42 | string_start_with_pct_char?(output[-2]) ||
43 | string_start_with_pct_char?(output[-3])
44 | end
45 |
46 | def method_missing(name, *args, &block)
47 | if name.to_s =~ /^show_/
48 | filters=[]
49 | if args.last.is_a?(Hash)
50 | _begin = args.last.delete(:begin)
51 | _section = args.last.delete(:section)
52 | _count = args.last.delete(:count)
53 | _exclude = args.last.delete(:exclude)
54 | _include = args.last.delete(:include)
55 | filters << "| begin #{_begin}" if _begin
56 | filters << "| count #{_count}" if _count
57 | filters << "| section #{_section}" if _section
58 | filters << "| exclude #{_exclude}" if _exclude
59 | filters << "| include #{_include}" if _include
60 | end
61 | cmd = name.to_s.split('_').join(' ')
62 | cmd += " " + filters.join(' ') if filters
63 | cmd.gsub!(/running config/, 'running-config')
64 | output = __send__ :exec, cmd, *args
65 | elsif name.to_s =~ /^shell_/
66 | cmd = name.to_s.split('_')[1..-1].join(' ') + args.join(' ')
67 | output = __send__ :shell, cmd, *args
68 | else
69 | super
70 | end
71 | end
72 |
73 | end
74 |
--------------------------------------------------------------------------------
/test/router/cisco/iox/iox_test.rb:
--------------------------------------------------------------------------------
1 | require 'expect4r'
2 | require "test/unit"
3 |
4 | class TestRouterCiscoIoxIox < Test::Unit::TestCase
5 | include Expect4r
6 | def test_new_hash_terse
7 | x = Iox.new :ssh,
8 | :host=> '1.1.1.1',
9 | :user=> 'username',
10 | :pwd=> 'lab',
11 | :method=> :ssh
12 | assert_equal '1.1.1.1', x.host
13 | assert_equal '1.1.1.1', x.hostname
14 | assert_equal 0, x.port
15 | assert_equal 'username', x.user
16 | assert_equal 'username', x.username
17 | assert_raise(NoMethodError) { x.pwd }
18 | assert_not_equal "lab", x.instance_eval { @pwd }
19 | end
20 | def test_new_hash
21 | x = Iox.new :ssh,
22 | :hostname=> '1.1.1.1',
23 | :username=> 'username',
24 | :password=> 'lab'
25 | assert_equal '1.1.1.1', x.host
26 | assert_equal '1.1.1.1', x.hostname
27 | assert_equal 0, x.port
28 | assert_equal 'username', x.user
29 | assert_equal 'username', x.username
30 | assert_raise(NoMethodError) { x.pwd }
31 | assert_not_equal "lab", x.instance_eval { @pwd }
32 | assert_equal :ssh, x.instance_eval { @method }
33 | end
34 | def test_new
35 | x = Iox.new :telnet, '1.1.1.1 4002', 'username', 'lab'
36 | assert_equal '1.1.1.1', x.host
37 | assert_equal 4002, x.port
38 | assert_equal '1.1.1.1', x.hostname
39 | assert_equal 'username', x.user
40 | assert_equal 'username', x.username
41 | assert_raise(NoMethodError) { x.pwd }
42 | assert_not_equal "lab", x.instance_eval { @pwd }
43 | assert_equal :telnet, x.instance_eval { @method }
44 | end
45 | def test_new_ssh
46 | x = Iox.new_ssh :hostname=> '1.1.1.1'
47 | assert_equal '1.1.1.1', x.host
48 | assert_equal '1.1.1.1', x.hostname
49 | assert_equal 0, x.port
50 | assert_nil x.user
51 | assert_nil x.username
52 | assert_raise(NoMethodError) { x.pwd }
53 | assert_not_equal "lab", x.instance_eval { @pwd }
54 | assert_equal :ssh, x.instance_eval { @method }
55 | end
56 | def test_new_telnet
57 | x = Iox.new_telnet :hostname=> '1.1.1.1'
58 | assert_equal '1.1.1.1', x.host
59 | assert_equal '1.1.1.1', x.hostname
60 | assert_equal 0, x.port
61 | assert_nil x.user
62 | assert_nil x.username
63 | assert_raise(NoMethodError) { x.pwd }
64 | assert_not_equal "lab", x.instance_eval { @pwd }
65 | assert_equal :telnet, x.instance_eval { @method }
66 | end
67 | end
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Expect4r is copyrighted free software by Jean-Michel Esnault.
2 |
3 | You can redistribute it and/or modify it under either the terms of the GPL
4 | (see the COPYING file), or the conditions below:
5 |
6 | 1. You may make and give away verbatim copies of the source form of the
7 | software without restriction, provided that you duplicate all of the
8 | original copyright notices and associated disclaimers.
9 |
10 | 2. You may modify your copy of the software in any way, provided that
11 | you do at least ONE of the following:
12 |
13 | a) place your modifications in the Public Domain or otherwise
14 | make them Freely Available, such as by posting said
15 | modifications to Usenet or an equivalent medium, or by allowing
16 | the author to include your modifications in the software.
17 |
18 | b) use the modified software only within your corporation or
19 | organization.
20 |
21 | c) rename any non-standard executables so the names do not conflict
22 | with standard executables, which must also be provided.
23 |
24 | d) make other distribution arrangements with the author.
25 |
26 | 3. You may distribute the software in object code or executable
27 | form, provided that you do at least ONE of the following:
28 |
29 | a) distribute the executables and library files of the software,
30 | together with instructions (in the manual page or equivalent)
31 | on where to get the original distribution.
32 |
33 | b) accompany the distribution with the machine-readable source of
34 | the software.
35 |
36 | c) give non-standard executables non-standard names, with
37 | instructions on where to get the original software distribution.
38 |
39 | d) make other distribution arrangements with the author.
40 |
41 | 4. You may modify and include the part of the software into any other
42 | software (possibly commercial).
43 |
44 | 5. The scripts and library files supplied as input to or produced as
45 | output from the software do not automatically fall under the
46 | copyright of the software, but belong to whomever generated them,
47 | and may be sold commercially, and may be aggregated with this
48 | software.
49 |
50 | 6. THIS SOFTWARE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR
51 | IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
52 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
53 | PURPOSE.
54 |
--------------------------------------------------------------------------------
/lib/router/cisco/ios/modes.rb:
--------------------------------------------------------------------------------
1 | require 'router/modes'
2 |
3 | module Expect4r::Router::Ios
4 | module Modes
5 |
6 | # Adds a in? mixin.
7 | #
8 | # Returns the mode the router is in: :user, :exec, :config
9 | #
10 | def in?(mode=:none)
11 | login unless connected?
12 | case mode
13 | when :exec ; exec?
14 | when :user ; user?
15 | when :config ; config?
16 | else
17 | _mode_?
18 | end
19 | end
20 |
21 |
22 | def config(config=nil, arg={})
23 | login unless connected?
24 | if config
25 | mode = in?
26 | change_mode_to :config
27 | output = exp_send(config, arg)
28 | change_mode_to mode
29 | output
30 | else
31 | change_mode_to :config
32 | end
33 | end
34 |
35 | def exec(cmd=nil, arg={})
36 | login unless connected?
37 | if cmd.nil?
38 | change_mode_to :exec
39 | else
40 | if exec?
41 | output = exp_send(cmd, arg)
42 | elsif config?
43 | output = exp_send("do #{cmd}", arg)
44 | else
45 | mode = in?
46 | change_mode_to :exec
47 | output = exp_send(cmd, arg)
48 | change_mode_to mode
49 | output
50 | end
51 | end
52 | end
53 |
54 | def _mode_?
55 | if user?
56 | :user
57 | elsif config?
58 | :config
59 | elsif exec?
60 | :exec
61 | end
62 | end
63 |
64 | def config?
65 | @lp =~ /\(config(|.+)\)/
66 | end
67 |
68 | #
69 | # returns *true* if router is in :exec (enabled) mode, *false* otherwise.
70 | #
71 | def exec?
72 | ! user? and ! config?
73 | end
74 |
75 | #
76 | # returns *true* if router is in :user mode, *false* otherwise.
77 | #
78 | def user?
79 | @lp.split.last =~ /.+>\s*$/
80 | end
81 |
82 | def to_config
83 | return :config if config?
84 | to_exec
85 | putline 'configure terminal' if exec?
86 | raise RuntimeError, "unable to get to config mode" unless config?
87 | :config
88 | end
89 |
90 | def to_exec
91 | return :exec if exec?
92 | if config?
93 | 1.upto(config_lvl?) { putline 'exit'}
94 | end
95 | raise RuntimeError, "unable to get to exec mode" unless exec?
96 | :exec
97 | end
98 |
99 | def to_user
100 | return if user?
101 | putline "exit"
102 | raise RuntimeError, "unable to get to user mode" unless exec?
103 | :user
104 | end
105 |
106 | end
107 | end
108 |
--------------------------------------------------------------------------------
/lib/router/error.rb:
--------------------------------------------------------------------------------
1 | module Expect4r::Router
2 | module Error
3 | class RouterError < RuntimeError
4 | def initialize(rname, msg)
5 | @rname = rname
6 | @msg = msg
7 | end
8 | def err_msg
9 | "#{@rname} [Error] : #{@msg}"
10 | end
11 | end
12 |
13 | class SyntaxError < RouterError
14 | def err_msg
15 | "#{@rname} [SyntaxError] : #{@msg}"
16 | end
17 | end
18 |
19 | class BadPasswordError < RouterError
20 | def err_msg
21 | "#{@rname} [BadPassword] : #{@msg}"
22 | end
23 | end
24 |
25 | class SemanticError < RouterError
26 | def err_msg
27 | "#{@rname} [SemanticError] : #{@msg}"
28 | end
29 | end
30 |
31 | class PingError < RuntimeError
32 | attr_reader :rname, :dest, :exp_pct, :act_pct, :sent, :recv, :ping_output
33 | def initialize(rname, dest, exp_pct, act_pct, sent, recv, output)
34 | @rname, @dest, @exp_pct, @act_pct, @sent, @recv, @ping_output = \
35 | rname, dest, exp_pct, act_pct, sent, recv, output
36 | end
37 | def err_msg
38 | "#{@rname} [PingError] : failed to ping #{@dest}, expected/actual pct: #{@exp_pct}/#{@act_pct}"
39 | end
40 | end
41 | end
42 | end
43 |
44 |
45 | if __FILE__ == $0
46 |
47 | require "test/unit"
48 |
49 | # require "router/error"
50 |
51 | class TestRouterError < Test::Unit::TestCase
52 | include Expect4r::Router::Error
53 | def test_raise
54 | assert_raise(RouterError) {raise RouterError.new('paris','show bogus command')}
55 | assert_err_msg 'paris [Error] : show bogus command', lambda {raise RouterError.new('paris','show bogus command')}
56 | assert_err_msg 'paris [SyntaxError] : show bogus command', lambda {raise SyntaxError.new('paris','show bogus command')}
57 | assert_err_msg 'paris [SemanticError] : show bogus command', lambda {raise SemanticError.new('paris','show bogus command')}
58 | assert_err_msg 'paris [PingError] : failed to ping 1.1.1.1, expected/actual pct: 100/90', lambda {raise PingError.new('paris','1.1.1.1', 100, 90, 10, 9, '')}
59 | assert_equal 100, exception(lambda {raise PingError.new('paris','1.1.1.1', 100, 90, 10, 9,'')}).exp_pct
60 | assert_equal 'paris', exception(lambda {raise PingError.new('paris','1.1.1.1', 100, 90, 10, 9,'')}).rname
61 | end
62 |
63 | private
64 |
65 | def assert_err_msg(err_msg, block)
66 | begin
67 | block.call
68 | rescue RouterError, PingError => re
69 | assert_equal err_msg, re.err_msg
70 | end
71 | end
72 |
73 | def exception(block)
74 | begin
75 | block.call
76 | rescue RouterError, PingError => re
77 | re
78 | end
79 | end
80 |
81 | end
82 |
83 | end
84 |
--------------------------------------------------------------------------------
/lib/router/vyatta/modes.rb:
--------------------------------------------------------------------------------
1 | require 'router/modes'
2 |
3 | module Expect4r::Router::Vyatta
4 | module Modes
5 |
6 | # Adds a Vyatta config mixin.
7 | #
8 | # Example:
9 | #
10 | # c.config %{
11 | # edit protocols bgp 100
12 | # set neighbor 192.168.129.1
13 | # set neighbor 192.168.129.1 capability orf prefix-list receive
14 | # set neighbor 192.168.129.1 ebgp-multihop 10
15 | # set neighbor 192.168.129.1 remote-as 200
16 | # }
17 | #
18 | def config(stmts=nil, arg={})
19 | login unless connected?
20 | if stmts
21 | mode = in?
22 | to_config
23 | output = exp_send(stmts, arg)
24 | output << commit
25 | change_mode_to(mode)
26 | output.flatten
27 | else
28 | to_config
29 | end
30 | end
31 |
32 | # Adds a Vyatta exec mixin.
33 | #
34 | # Example:
35 | #
36 | # v.exec 'set cli screen-length 0'
37 | #
38 | def exec(cmd=nil, *args)
39 | login unless connected?
40 | if cmd.nil?
41 | to_exec
42 | else
43 | if config?
44 | exp_send("run #{cmd}", *args)
45 | elsif exec?
46 | exp_send cmd, *args
47 | else
48 | mode = _mode_?
49 | to_exec
50 | output = exp_send(cmd, *args)
51 | change_mode_to mode
52 | output
53 | end
54 | end
55 | end
56 |
57 | def exec?
58 | if logged_as_root?
59 | ! config?
60 | else
61 | @lp =~ /\$ $/ ? true : false
62 | end
63 | end
64 |
65 | def config?
66 | if logged_as_root?
67 | @_lp_1 =~ /\[edit\]/ ? true : false
68 | else
69 | @lp =~ /^.+# $/ ? true : false
70 | end
71 | end
72 |
73 | def in?(mode=:none)
74 | login unless connected?
75 | case mode
76 | when :exec ; exec?
77 | when :config ; config?
78 | else
79 | _mode_?
80 | end
81 | end
82 |
83 | def to_config
84 | return :config if config?
85 | to_exec
86 | putline 'configure', :debug=>1
87 | raise RuntimeError, "unable to get to config mode" unless config?
88 | :config
89 | end
90 |
91 | def to_exec
92 | return :exec if exec?
93 | top if config?
94 | exit
95 | raise RuntimeError, "unable to get to exec mode" unless exec?
96 | :exec
97 | end
98 |
99 | def change_mode_to(mode)
100 | login unless connected?
101 | case mode
102 | when :exec ; to_exec
103 | when :config ; to_config
104 | end
105 | end
106 |
107 | private
108 |
109 | def logged_as_root?
110 | ! (@_is_root_ ||= (@user=='root' and @lp =~ /root@/)).nil?
111 | end
112 |
113 | def _mode_?
114 | putline ' ', :no_trim=>true, :no_echo=>true unless @lp
115 | if exec?
116 | :exec
117 | elsif config?
118 | :config
119 | else
120 | end
121 | end
122 |
123 | end
124 | end
125 |
--------------------------------------------------------------------------------
/lib/router/cisco/iox/modes.rb:
--------------------------------------------------------------------------------
1 | require 'router/modes'
2 |
3 | module Expect4r::Router::Iox
4 | module Modes
5 |
6 | # Adds an Iox config mixin.
7 | #
8 | # Options are:
9 | #
10 | # Option examples:
11 | #
12 | # iox.config %{
13 | # interface GigabitEthernet0/2/0/0
14 | # desc to switch port 13'
15 | # ipv4 address 190.0.0.9 255.255.255.252'
16 | # no shut'
17 | # }
18 | #
19 | def config(config=nil, arg={})
20 | login unless connected?
21 | if config
22 | mode = in?
23 | change_mode_to :config
24 | output = exp_send(config, arg)
25 | output << commit
26 | change_mode_to mode
27 | output
28 | else
29 | change_mode_to :config
30 | end
31 | end
32 |
33 | def exec(cmd=nil, arg={})
34 | login unless connected?
35 | if cmd.nil?
36 | change_mode_to :exec
37 | else
38 | if exec?
39 | output = exp_send(cmd, arg)
40 | elsif config?
41 | output = exp_send("do #{cmd}", arg)
42 | else
43 | mode = in?
44 | change_mode_to :exec
45 | output = exp_send(cmd, arg)
46 | change_mode_to mode
47 | output
48 | end
49 | end
50 | end
51 |
52 | def shell(cmd=nil, arg={})
53 | connected = connected?
54 | login unless connected?
55 | if cmd.nil?
56 | to_shell
57 | else
58 | if shell?
59 | output = exp_send(cmd, arg)
60 | elsif config?
61 | output = exp_send("do run #{cmd}", arg)
62 | elsif exec?
63 | output = exp_send("run #{cmd}", arg)
64 | else
65 | raise RuntimeError # TODO
66 | end
67 | output
68 | end
69 | end
70 |
71 | def config?
72 | @lp =~ /\(config(|.+)\)/
73 | end
74 |
75 | def submode?
76 | return '' unless config?
77 | @lp =~ /\(config(|.+)\)/
78 | Regexp.last_match(1).split('-')[1..-1].join('-')
79 | end
80 |
81 | def exec?
82 | ! shell? and ! config?
83 | end
84 |
85 | def shell?
86 | @lp == '# '
87 | end
88 |
89 | def to_config
90 | return :config if config?
91 | to_exec
92 | putline 'configure'
93 | raise RuntimeError, "unable to get to config mode" unless config?
94 | :config
95 | end
96 |
97 | def to_shell
98 | return :shell if shell?
99 | to_exec
100 | putline 'run'
101 | raise RuntimeError, "unable to get to shell mode" unless shell?
102 | :shell
103 | end
104 |
105 | def to_exec
106 | return :exec if exec?
107 | putline "exit" if shell?
108 | putline "abort" if config?
109 | raise RuntimeError, "unable to get to exec mode" unless exec?
110 | :exec
111 | end
112 |
113 | end
114 | end
115 |
--------------------------------------------------------------------------------
/Rakefile:
--------------------------------------------------------------------------------
1 | require 'rake'
2 | require 'rdoc/task'
3 | require 'rake/testtask'
4 | require "rake/clean"
5 |
6 | task "default" => 'test'
7 |
8 | CLEAN.include ["*.gem", "pkg", "rdoc"]
9 |
10 | begin
11 | require 'jeweler'
12 | Jeweler::Tasks.new do |s|
13 | s.name = 'expect4r'
14 | s.authors = ['Jean-Michel Esnault']
15 | s.email = "jesnault@gmail.com"
16 | s.summary = "Expect4r"
17 | s.description = "A Ruby Library for interacting with IOS, IOS-XR, and JUNOS CLI."
18 | s.platform = Gem::Platform::RUBY
19 | s.executables = []
20 | s.files = %w( LICENSE COPYING README.rdoc ) + Dir["lib/**/*"] + ["examples/**/*"]
21 | s.test_files = Dir["test/**/*"]
22 | s.has_rdoc = false
23 | s.rdoc_options = ["--quiet", "--title", "Expect4r", "--line-numbers"]
24 | s.require_path = 'lib'
25 | s.required_ruby_version = ">= 1.8.6"
26 | s.add_dependency('highline', '>= 1.5.0')
27 | s.homepage = "http://github.com/jesnault/expect4r"
28 | # s.rubyforge_project = 'expect4r'
29 | end
30 | rescue LoadError
31 | puts "Jeweler not available. Install it with: sudo gem install jeweler"
32 | end
33 |
34 | # These are new tasks
35 | begin
36 | require 'rake/contrib/sshpublisher'
37 | namespace :rubyforge do
38 |
39 | desc "Release gem and RDoc documentation to RubyForge"
40 | task :release => ["rubyforge:release:gem", "rubyforge:release:docs"]
41 |
42 | namespace :release do
43 | desc "Publish RDoc to RubyForge."
44 | task :docs => [:rdoc] do
45 | config = YAML.load(
46 | File.read(File.expand_path('~/.rubyforge/user-config.yml'))
47 | )
48 | host = "#{config['username']}@rubyforge.org"
49 | remote_dir = "/var/www/gforge-projects/the-perfect-gem/"
50 | local_dir = 'rdoc'
51 |
52 | Rake::SshDirPublisher.new(host, remote_dir, local_dir).upload
53 | end
54 | end
55 | end
56 | rescue LoadError
57 | puts "Rake SshDirPublisher is unavailable or your rubyforge environment is not configured."
58 | end
59 |
60 |
61 | Rake::TestTask.new do |t|
62 | t.libs = ['.']
63 | t.pattern = "test/**/*test.rb"
64 | t.warning = true
65 | end
66 |
67 | Rake::RDocTask.new do |t|
68 | t.rdoc_dir = 'rdoc'
69 | t.rdoc_files.include("lib/**/*.rb")
70 | t.options = ["--quiet", "--title", "Expect4r", "--line-numbers"]
71 | t.options << '--fileboxes'
72 | end
73 |
74 | require 'rubygems/package_task'
75 |
76 | namespace :gem do
77 |
78 | desc "Run :package and install the .gem locally"
79 | task :install => [:gem, :package] do
80 | sh %{sudo gem install --local pkg/interact-#{PKG_VERSION}.gem}
81 | end
82 |
83 | desc "Like gem:install but without ri or rdocs"
84 | task :install_fast => [:gem, :package] do
85 | sh %{sudo gem install --local pkg/interact-#{PKG_VERSION}.gem --no-rdoc --no-ri}
86 | end
87 |
88 | desc "Run :clean and uninstall the .gem"
89 | task :uninstall => :clean do
90 | sh %{sudo gem uninstall interact}
91 | end
92 |
93 | end
94 |
--------------------------------------------------------------------------------
/test/misc/base_test.rb:
--------------------------------------------------------------------------------
1 | require "test/unit"
2 | require "misc/base"
3 |
4 |
5 | require "test/unit"
6 |
7 | class Base < Test::Unit::TestCase
8 | include Expect4r
9 |
10 | def test_add
11 | assert_equal( [], Base.all)
12 | Base.new
13 | assert_equal( 1, Base.all.size)
14 | Base.new
15 | assert_equal( 2, Base.all.size)
16 | end
17 |
18 | end
19 |
20 | require 'misc/passwd'
21 | require 'set'
22 |
23 | class TestBaseLoginObject < Test::Unit::TestCase
24 | include Expect4r
25 |
26 | def setup
27 | @pwd = "MySecretPassword"
28 | @cpwd = Expect4r.cipher(@pwd)
29 | end
30 |
31 | def test_we_are_not_caching_password_in_the_clear
32 | o = BaseLoginObject.new :telnet, :host=> '1.1.1.1', :user=> 'lab', :password=>'lab'
33 | assert_not_equal "lab", o.instance_eval { @pwd }
34 | o = BaseLoginObject.new :telnet, :host=> '1.1.1.1', :user=> 'lab', :password=>'lab'
35 | assert_not_equal "lab", o.instance_eval { @pwd }
36 | assert ! o.respond_to?(:spawnee_enable_password), "certain things are better kept private!"
37 | assert ! o.respond_to?(:spawnee_password), "certain things are better kept private!"
38 | end
39 |
40 | def test_that_default_enable_password_is_set_to_password
41 | o = BaseLoginObject.new :telnet, :host=> '1.1.1.1', :user=> 'lab', :password=>'lab'
42 | assert_equal(o.password, o.enable_password)
43 | end
44 |
45 | def test_dedicated_enable_password
46 | o = BaseLoginObject.new :telnet, :host=> '1.1.1.1', :user=> 'lab', :password=>'lab', :enable_password=>'LAB'
47 | assert_not_equal(o.password, o.enable_password)
48 | end
49 |
50 | def test_new_4_args
51 | o1 = BaseLoginObject.new :telnet, '1.1.1.1', 'lab', 'lab'
52 | o2 = BaseLoginObject.new :telnet, :host=> '1.1.1.1', :user=> 'lab', :password=>'lab'
53 | assert o1.cmp(o2)
54 | end
55 |
56 | def test_new_5th_arg_is_port_number
57 | o1 = BaseLoginObject.new :telnet, '1.1.1.1', 'lab', 'lab', 2001
58 | o2 = BaseLoginObject.new :telnet, :host=> '1.1.1.1', :user=> 'lab', :password=>'lab'
59 | assert ! o1.cmp(o2)
60 | o2.port=2001
61 | assert o1.cmp(o2)
62 | end
63 |
64 | def test_new_5th_arg_is_enable_password
65 | o1 = BaseLoginObject.new :telnet, '1.1.1.1', 'lab', 'lab', 'secret'
66 | o2 = BaseLoginObject.new :telnet, :host=> '1.1.1.1', :user=> 'lab', :password=>'lab', :enable_password=>'secret'
67 | assert o1.cmp(o2)
68 | end
69 |
70 | def test_new_6_args
71 | o1 = BaseLoginObject.new :telnet, '1.1.1.1', 'user', 'pwd', 'enable', 2001
72 | o2 = BaseLoginObject.new :telnet, :host=> '1.1.1.1', :user=> 'user', :password=>'pwd', :enable_password=>'enable', :port=>2001
73 | assert o1.cmp(o2)
74 | end
75 |
76 | def test_verify_private_spawnee_passord
77 | o1 = BaseLoginObject.new :telnet, '1.1.1.1', 'user', 'pwd', 'enable', 2001
78 | assert_equal("pwd", o1.instance_eval { spawnee_password})
79 | end
80 |
81 | def test_verify_private_spawnee_enable_passord
82 | o1 = BaseLoginObject.new :telnet, '1.1.1.1', 'user', 'pwd', 'enable', 2001
83 | assert_equal("enable", o1.instance_eval { spawnee_enable_password})
84 | end
85 |
86 | end
87 |
88 |
--------------------------------------------------------------------------------
/lib/router/juniper/junos/modes.rb:
--------------------------------------------------------------------------------
1 | require 'router/modes'
2 |
3 | module Expect4r::Router::Junos
4 | module Modes
5 |
6 | # Adds a Junos config mixin.
7 | #
8 | # Example:
9 | #
10 | # j.config %{
11 | # edit logical-router Orleans protocols bgp
12 | # edit group session-to-200
13 | # set type external
14 | # set peer-as 200
15 | # set neighbor 40.0.2.1 peer-as 200
16 | # }
17 | #
18 | def config(stmts=nil, arg={})
19 | login unless connected?
20 | if stmts
21 | mode = in?
22 | to_config
23 | output = exp_send(stmts, arg)
24 | output << commit
25 | change_mode_to(mode)
26 | output.flatten
27 | else
28 | to_config
29 | end
30 | end
31 |
32 | # Adds a Junos exec mixin.
33 | #
34 | # Example:
35 | #
36 | # j.exec 'set cli screen-length 0'
37 | #
38 | def exec(cmd=nil, *args)
39 | login unless connected?
40 | if cmd.nil?
41 | to_exec
42 | else
43 | if config?
44 | exp_send("run #{cmd}", *args)
45 | elsif exec?
46 | exp_send cmd, *args
47 | else
48 | mode = _mode_?
49 | to_exec
50 | output = exp_send(cmd, *args)
51 | change_mode_to mode
52 | output
53 | end
54 | end
55 | end
56 |
57 | def shell(cmd=nil, *args)
58 | connected = connected?
59 | login unless connected?
60 | if cmd.nil?
61 | to_shell
62 | else
63 | mode = _mode_?
64 | to_shell
65 | output = exp_send(cmd, *args)
66 | change_mode_to mode
67 | output
68 | end
69 | end
70 |
71 | #
72 | # returns *true* if router is in :exec mode, *false* otherwise.
73 | #
74 | def exec?
75 | @lp =~ /> $/ ? true : false
76 | end
77 |
78 | #
79 | # returns *true* if router is in :config mode, *false* otherwise.
80 | #
81 | def config?
82 | @lp =~ /^.+# $/ ? true : false
83 | end
84 |
85 | #
86 | # returns *true* if router is in :shell mode, *false* otherwise.
87 | #
88 | def shell?
89 | if @lp == '% '
90 | true
91 | elsif logged_as_root? and @lp =~ /root@.+% $/
92 | true
93 | else
94 | false
95 | end
96 | end
97 |
98 | def set_cli_logical_router(logical_router)
99 | return if @ps1_bis
100 | to_exec
101 | arr= @lp.split(">")
102 | @ps1_bis = /#{arr[0]}:#{logical_router}(\#|>)\s*$/
103 | p @ps1_bis
104 | putline "set cli logical-router #{logical_router}"
105 | end
106 |
107 | def clear_cli_logical_router()
108 | return unless @ps1_bis
109 | to_exec
110 | @ps1_bis=nil
111 | putline "clear cli logical-router"
112 | end
113 |
114 | private
115 |
116 | def to_config
117 | return :config if config?
118 | to_exec
119 | putline 'edit', :debug=>1
120 | raise RuntimeError, "unable to get to config mode" unless config?
121 | :config
122 | end
123 |
124 | def to_shell
125 | return in? if @ps1_bis
126 | return :shell if shell?
127 | to_exec
128 | putline 'start shell'
129 | raise RuntimeError, "unable to get to shell mode" unless shell?
130 | :shell
131 | end
132 |
133 | def logged_as_root?
134 | ! (@_is_root_ ||= @lp =~ /root@/).nil?
135 | end
136 |
137 | def to_exec
138 | return :exec if exec?
139 | top if config?
140 | logged_as_root? ? cli : exit
141 | raise RuntimeError, "unable to get to exec mode" unless exec?
142 | :exec
143 | end
144 |
145 | end
146 | end
147 |
--------------------------------------------------------------------------------
/lib/router/vyatta/ping.rb:
--------------------------------------------------------------------------------
1 | require 'router/error'
2 |
3 | module Expect4r::Router::Vyatta
4 | module Ping
5 |
6 | # Adds a ping method to V class:
7 | #
8 | # Options are:
9 | # * :count or :repeat_count
10 | # * :size or :datagram_size
11 | # * :datagram_size or :size
12 | # * :timeout
13 | # * :tos
14 | # * :ttl
15 | # * :pattern
16 | # * :pct_success - default is 99.
17 | #
18 | # Option examples:
19 | # :count => 10
20 | # :timeout => 1
21 | # :size=> 512
22 | # :protocol=> 'ipv4', :count=> 20, :size=> 1500, :timeout=>5, :ttl=>16
23 | # Example:
24 | # v.ping('192.168.129.1', :count=>10, :size=>256) { |r| r.interact }
25 | #--
26 | # PING 192.168.129.1 (192.168.129.1) 56(84) bytes of data.
27 | # 64 bytes from 192.168.129.1: icmp_req=1 ttl=64 time=0.173 ms
28 | # 64 bytes from 192.168.129.1: icmp_req=2 ttl=64 time=0.132 ms
29 | # 64 bytes from 192.168.129.1: icmp_req=3 ttl=64 time=0.144 ms
30 | # 64 bytes from 192.168.129.1: icmp_req=4 ttl=64 time=0.128 ms
31 | # 64 bytes from 192.168.129.1: icmp_req=5 ttl=64 time=0.225 ms
32 | #
33 | # --- 192.168.129.1 ping statistics ---
34 | # 5 packets transmitted, 5 received, 0% packet loss, time 3996ms
35 | # rtt min/avg/max/mdev = 0.128/0.160/0.225/0.037 ms
36 | # vyatta@vyatta2:~$
37 | #++
38 | def ping(host, arg={}, &on_error)
39 |
40 | pct_success = arg.delete(:pct_success) || 99
41 | raise_on_error = arg.delete(:on_error_raise) || true
42 |
43 | output = exp_send(ping_cmd(host, arg), arg)
44 |
45 | r = output[0].find { |x| x =~/(\d+) packets transmitted, (\d+) received, (\d+)\% packet loss/}
46 |
47 | if r &&
48 | Regexp.last_match(1) &&
49 | Regexp.last_match(2) &&
50 | Regexp.last_match(3)
51 |
52 | success = 100 - $3.to_i
53 | tx = $2.to_i
54 | rx = $3.to_i
55 |
56 | if (100 - $3.to_i) < pct_success
57 | raise ::Expect4r::Router::Error::PingError.new(@host, host, pct_success, tx, rx, output)
58 | else
59 | [$1.to_i,[$2.to_i,$3.to_i],output]
60 | end
61 |
62 | else
63 |
64 | if on_error
65 | on_error.call(self)
66 | else
67 | if raise_on_error
68 | raise ::Expect4r::Router::Error::PingError.new(@host, host, pct_success, $1.to_i, tx, rx, output)
69 | end
70 | end
71 |
72 | end
73 |
74 | end
75 |
76 | private
77 |
78 | # vyatta@vyatta2:~$ /bin/ping
79 | # Usage: ping [-LRUbdfnqrvVaAD] [-c count] [-i interval] [-w deadline]
80 | # [-p pattern] [-s packetsize] [-t ttl] [-I interface]
81 | # [-M pmtudisc-hint] [-m mark] [-S sndbuf]
82 | # [-T tstamp-options] [-Q tos] [hop1 ...] destination
83 | def ping_cmd(host, arg={})
84 | arg = {:count=>5}.merge(arg)
85 | cmd = "/bin/ping"
86 | cmd += " -c #{arg[:count] || arg[:repeat_count]}" if arg[:count] || arg[:repeat_count]
87 | cmd += " -s #{arg[:size] || arg[:datagram_size]}" if arg[:size] || arg[:datagram_size]
88 | cmd += " -p #{arg[:pattern]}" if arg[:pattern]
89 | cmd += " -Q #{arg[:tos]}" if arg[:tos]
90 | cmd += " -t #{arg[:ttl]}" if arg[:ttl]
91 | cmd += " -S #{arg[:sndbuf]}" if arg[:sndbuf]
92 | cmd += " -c #{arg[:intf] || arg[:interface]}" if arg[:intf] || arg[:interface]
93 | cmd += " -w #{arg[:deadline]}" if arg[:deadline]
94 | cmd += " #{host}"
95 | cmd
96 | end
97 |
98 | end
99 | end
100 |
--------------------------------------------------------------------------------
/CHANGELOG:
--------------------------------------------------------------------------------
1 | === 0.0.11 Thu Sep 18, 2014
2 |
3 | * Adding connection retry mechanism.
4 | When enabled, instead of getting an IO error
5 | when attempting to read or write from a disconnected session,
6 | the connection will be re-established and then read or write command retried.
7 |
8 | s = Expect4r::Ios.new_ssh ...
9 | s.connect_retry
10 | s.connect_retry=
11 | s.login
12 |
13 | Connection retry is disabled by default:
14 | irb > s.connect_retry
15 | => 0
16 | to enable it, set then number of connection retry attemtpts:
17 | irb > s.connect_retry= 10
18 | => 10
19 |
20 | === 0.0.10 Tue Aug 6, 2013
21 |
22 | * Adding support ios enable password
23 |
24 | * Fix an issue reported by Gael Leonet where the ios default login banner
25 | is getting in the way of matching the username.
26 |
27 | === 0.0.9 Sat Feb 9, 2013
28 |
29 | * Fix few issues when telnetting to ios
30 | send \r instead of \n to get to enabled mode
31 | don't try and derive enable_password from password if no password
32 |
33 | * Vyatta:
34 | add on_error block for ping
35 |
36 | * moved Base class out of io.rb to misc/base.rb
37 |
38 | * Update gem file
39 | add misc/base.rb
40 |
41 |
42 | === 0.0.8 Mon Feb 20, 2012
43 |
44 | * add V (vyatta) class
45 |
46 | V.new :ssh, :user=>'vyatta', :pwd=>'vyatta', :host=>'hostname'
47 | V.new_ssh 'hostname' (default user to vyatta and pwd to vyatta)
48 | V.new_ssh :host=> 'hostname', :user=>'root', :pwd=>'123'
49 |
50 | V.new :telnet, :user=>'vyatta', :pwd=>'vyatta', :host=>'hostname'
51 | V.new_telnet 'hostname' (default user to vyatta and pwd to vyatta)
52 | V.new_telnet :host=> 'hostname', :user=>'root', :pwd=>'123'
53 |
54 | v.ping '10.0.0.1'
55 | v.ping('10.0.0.1', :count=>5) { |r| r.interact }
56 | v.ping('10.0.0.1', :count=>5, :size=>512, ...) { |r| r.interact }
57 |
58 | * modify ping to accept a block to be exececuted on error
59 | eg,
60 | r.ping('10.0.0.1', :count=> 100, :size=>512) { |r| r.interact }
61 |
62 | === 0.0.7.dev Thu Dec 16, 2010
63 |
64 | * fix _io_save bug when no_echo
65 | * add matches to expect()
66 | * readline return empty string when timeout
67 |
68 | === 0.0.6.dev Sun Nov 28, 2010
69 |
70 | * bug fix in Base class
71 | * matches to accept String or Proc
72 |
73 | === 0.0.5.dev Sat Nov 13, 2010
74 |
75 | * new Expect4r::Base class
76 | * Fixes
77 |
78 | === 0.0.4.dev Sat Oct 30, 2010
79 |
80 | * Fixes
81 |
82 | === 0.0.3 dev Wed Oct 27, 2010
83 |
84 | * Added a RShell class
85 |
86 | * Added login_via method so as to login to a box from another.
87 |
88 | iox = Iox.new_telnet 'router', 'user'
89 | iox.login_via RShell.new_ssh('hostname', 'user')
90 |
91 | === 0.0.2.dev Tue Oct 26, 2010
92 |
93 | * Added Junos#ping
94 | * Numerous fixes and improvements to CisoCommon#ping
95 |
96 | === 0.0.1.dev Mon Oct 25, 2010
97 |
98 | * Initial release.
99 |
100 | While this is still work in progress and buggy, it already allows to:
101 |
102 | + login to a Cisco router running IOS or IOS-XR or a Juniper router running Junos.
103 | + push configuration using #config() method
104 | + pull states using #show() method
105 | + check connectivity state using #ping() (not yet implemented for J class)
106 |
107 | Provides following:
108 |
109 | Classes:
110 | Expect4r::Ios
111 | Expect4r::Iox
112 | Expect4r::J
113 |
114 | Class methods:
115 | new
116 | new_ssh
117 | new_telnet
118 |
119 | Instance methods:
120 | login
121 | logout
122 | config
123 | show
124 | shell
125 | exec
126 | in?
127 | ping
128 |
--------------------------------------------------------------------------------
/lib/router/cisco/common/ping.rb:
--------------------------------------------------------------------------------
1 | require 'router/error'
2 |
3 | module Expect4r::Router::CiscoCommon
4 | module Ping
5 | include ::Expect4r::Router::Error
6 |
7 | # Adds a ping method to CiscoCommon::Ping mixin:
8 | #
9 | # Options are:
10 | # * :protocol
11 | # * :repeat_count or :count
12 | # * :datagram_size or :size
13 | # * :timeout
14 | # * :source_address
15 | # * :tos
16 | # * :df
17 | # * :pattern
18 | # * :sweep_min_size
19 | # * :sweep_max_size
20 | # * :sweep_interval
21 | # * :pct_success - default is 99.
22 | #
23 | # Option examples:
24 | # :protocol => 'ipv4'
25 | # :count => 10
26 | # :timeout => 1
27 | # :size=> 512
28 | # :protocol=> 'ipv4', :count=> 20, :size=> 1500, :timeout=>5
29 | #
30 | def ping(target_ip_address, arg={}, &on_error)
31 |
32 | pct_success = arg.delete(:pct_success) || 99
33 |
34 | if arg.empty?
35 |
36 | output = exec "ping #{target_ip_address}", arg
37 |
38 | else
39 |
40 | @matches = Set.new
41 |
42 | set_ping_base_matches target_ip_address, arg
43 | set_ping_extended_matches arg
44 | set_ping_sweep_matches arg
45 |
46 | output = exec "ping", arg
47 |
48 | end
49 |
50 | # r = output[0].find { |x| x =~/Success.*[^\d](\d+) percent \((\d+)\/(\d+)\)/}
51 | r = output.join =~/Success.*[^\d](\d+) percent \((\d+)\/(\d+)\)/
52 |
53 | if r &&
54 | Regexp.last_match(1) &&
55 | Regexp.last_match(2) &&
56 | Regexp.last_match(3)
57 |
58 | pct = $1.to_i
59 | tx = $3.to_i
60 | rx = $2.to_i
61 |
62 | if $1.to_i < pct_success
63 | raise ::Expect4r::Router::Error::PingError.new(@host, target_ip_address, pct_success, pct, tx, rx, output)
64 | else
65 | [$1.to_i,[$2.to_i,$3.to_i],output]
66 | end
67 |
68 | else
69 |
70 | if on_error
71 | on_error.call(self)
72 | else
73 | raise ::Expect4r::Router::Error::PingError.new(@host, target_ip_address, pct_success, pct, tx, rx, output)
74 | end
75 |
76 | end
77 |
78 | end
79 |
80 | private
81 |
82 | def set_ping_base_matches(target_ip_address, arg={})
83 | protocol = arg[:protocol] || ''
84 | repeat_count = arg[:repeat_count] || arg[:count] || ''
85 | datagram_size = arg[:datagram_size] || arg[:size] || ''
86 | timeout = arg[:timeout] || ''
87 | @matches << [/Protocol.+\: $/, protocol]
88 | @matches << [/Target IP address\: $/, target_ip_address]
89 | @matches << [/Repeat count.+\: $/, repeat_count]
90 | @matches << [/Datagram size.+\: $/, datagram_size]
91 | @matches << [/Timeout.+\: $/, timeout]
92 | end
93 |
94 | def set_ping_extended_matches(arg={})
95 | extended_keys = [:source_address, :tos, :df, :pattern]
96 | if (extended_keys & arg.keys).empty? and ! arg[:source_address]
97 | @matches << [/Extended.+\: $/, '']
98 | else
99 | src_adr = arg[:source_address]
100 | tos = arg[:tos] || ''
101 | df = arg[:df] || ''
102 | pattern = arg[:pattern] || ''
103 | @matches << [/Extended.+\: $/, 'yes']
104 | @matches << [/Source address or interface\: $/, src_adr]
105 | @matches << [/Type of service.+\: $/, tos]
106 | @matches << [/Set DF bit.+\: $/, df]
107 | @matches << [/Data pattern.+\: $/, pattern]
108 | @matches << [/Validate reply.+\: $/, ''] #TODO
109 | @matches << [/Loose, Strict,.+\: $/, ''] #TODO
110 | end
111 | end
112 |
113 | def set_ping_sweep_matches(arg={})
114 | sweep_keys = [:sweep_min_size, :sweep_max_size, :sweep_interval]
115 | if (sweep_keys & arg.keys).empty?
116 | @matches << [/Sweep range of sizes.+: $/, 'no']
117 | else
118 | min_size = arg[:sweep_min_size] || ''
119 | max_size = arg[:sweep_max_size] || ''
120 | interval = arg[:sweep_interval] || ''
121 | @matches << [/Sweep range of sizes.+: $/, 'yes']
122 | @matches << [/Sweep min size.+\: $/, min_size]
123 | @matches << [/Sweep max size.+\: $/, max_size]
124 | @matches << [/Sweep interval.+\: $/, interval]
125 | end
126 | end
127 |
128 | end
129 | end
130 |
131 |
--------------------------------------------------------------------------------
/lib/router/juniper/junos/ping.rb:
--------------------------------------------------------------------------------
1 | require 'router/error'
2 |
3 | module Expect4r::Router::Junos
4 | module Ping
5 |
6 | # Adds a ping method to J class:
7 | #
8 | # Options are:
9 | # * :count or :repeat_count
10 | # * :size or :datagram_size
11 | # * :datagram_size or :size
12 | # * :timeout
13 | # * :tos
14 | # * :ttl
15 | # * :pattern
16 | # * :pct_success - default is 99.
17 | #
18 | # Option examples:
19 | # :count => 10
20 | # :timeout => 1
21 | # :size=> 512
22 | # :protocol=> 'ipv4', :count=> 20, :size=> 1500, :timeout=>5, :ttl=>16
23 | #
24 | def ping(host, arg={}, &on_error)
25 |
26 | pct_success = arg.delete(:pct_success) || 99
27 |
28 | output = exec(ping_cmd(host, arg), arg)
29 |
30 | r = output[0].find { |x| x =~/(\d+) packets transmitted, (\d+) packets received, (\d+)\% packet loss/}
31 |
32 | if r &&
33 | Regexp.last_match(1) &&
34 | Regexp.last_match(2) &&
35 | Regexp.last_match(3)
36 |
37 | success = 100 - $3.to_i
38 | tx = $2.to_i
39 | rx = $3.to_i
40 |
41 | if (100 - $3.to_i) < pct_success
42 | raise ::Expect4r::Router::Error::PingError.new(@host, host, pct_success, tx, rx, output)
43 | else
44 | [$1.to_i,[$2.to_i,$3.to_i],output]
45 | end
46 |
47 | else
48 | if on_error
49 | on_error.call(self)
50 | else
51 | raise ::Expect4r::Router::Error::PingError.new(@host, host, pct_success, $1.to_i, tx, rx, output)
52 | end
53 | end
54 |
55 | end
56 |
57 | private
58 |
59 | def ping_cmd(host, arg={})
60 | cmd = "ping #{host}"
61 | cmd += " rapid"
62 | cmd += " logical-router #{arg[:logical_router]}" if arg[:logical_router]
63 | cmd += " count #{arg[:count] || arg[:repeat_count]}" if arg[:count] || arg[:repeat_count]
64 | cmd += " size #{arg[:size] || arg[:datagram_size]}" if arg[:size] || arg[:datagram_size]
65 | cmd += " pattern #{arg[:pattern]}" if arg[:pattern]
66 | cmd += " tos #{arg[:tos]}" if arg[:tos]
67 | cmd += " ttl #{arg[:ttl]}" if arg[:ttl]
68 | cmd
69 | end
70 |
71 | end
72 | end
73 |
74 | __END__
75 |
76 |
77 | if __FILE__ == $0
78 |
79 | module Expect4r
80 | module Router
81 | module Junos
82 | end
83 | end
84 | end
85 |
86 | end
87 |
88 | if __FILE__ == $0
89 |
90 | require "test/unit"
91 |
92 | class TestLibraryFileName < Test::Unit::TestCase
93 | include Expect4r::Router::Junos::Ping
94 | def test_case_name
95 |
96 | assert_equal 'ping 1.1.1.1 rapid', ping_cmd( '1.1.1.1')
97 | assert_equal 'ping 1.1.1.1 rapid size 256', ping_cmd( '1.1.1.1', :size=>256)
98 | assert_equal 'ping 1.1.1.1 rapid size 256', ping_cmd( '1.1.1.1', :datagram_size=>256)
99 | assert_equal 'ping 1.1.1.1 rapid pattern 0xdead', ping_cmd( '1.1.1.1', :pattern=>'0xdead')
100 | assert_equal 'ping 1.1.1.1 rapid tos 2', ping_cmd( '1.1.1.1', :tos=>2)
101 | assert_equal 'ping 1.1.1.1 rapid ttl 2', ping_cmd( '1.1.1.1', :ttl=>2)
102 |
103 | end
104 | end
105 |
106 | end
107 |
108 |
109 | __END__
110 |
111 | 5 packets transmitted, 5 packets received, 0% packet loss
112 | round-trip min/avg/max/stddev = 1.483/17.024/78.904/30.940 ms
113 |
114 |
115 |
116 | Possible completions:
117 | Hostname or IP address of remote host
118 | atm Ping remote Asynchronous Transfer Mode node
119 | bypass-routing Bypass routing table, use specified interface
120 | count Number of ping requests to send (1..2000000000 packets)
121 | detail Display incoming interface of received packet
122 | do-not-fragment Don't fragment echo request packets (IPv4)
123 | inet Force ping to IPv4 destination
124 | inet6 Force ping to IPv6 destination
125 | interface Source interface (multicast, all-ones, unrouted packets)
126 | interval Delay between ping requests (seconds)
127 | logical-router Name of logical router
128 | + loose-source Intermediate loose source route entry (IPv4)
129 | mpls Ping label-switched path
130 | no-resolve Don't attempt to print addresses symbolically
131 | pattern Hexadecimal fill pattern
132 | rapid Send requests rapidly (default count of 5)
133 | record-route Record and report packet's path (IPv4)
134 | routing-instance Routing instance for ping attempt
135 | size Size of request packets (0..65468 bytes)
136 | source Source address of echo request
137 | strict Use strict source route option (IPv4)
138 | + strict-source Intermediate strict source route entry (IPv4)
139 | tos IP type-of-service value (0..255)
140 | ttl IP time-to-live value (IPv6 hop-limit value) (hops)
141 | verbose Display detailed output
142 | vpls Ping VPLS MAC address
143 | wait Delay after sending last packet (seconds)
144 |
--------------------------------------------------------------------------------
/lib/misc/base.rb:
--------------------------------------------------------------------------------
1 |
2 | module Expect4r
3 | class Base
4 | include Expect4r
5 | class << self
6 | attr_reader :routers
7 | def all
8 | @arr || []
9 | end
10 | def add(r)
11 | @arr ||=[]
12 | @arr << r
13 | end
14 | end
15 | def initialize(*args)
16 | @matches = Set.new
17 | Base.add(self)
18 | self
19 | end
20 | end
21 |
22 | class BaseLoginObject < Base
23 | class << self
24 | # Examples:
25 | # my_mac = RShell.new_telnet '1.1.1.1', 'me', 'secret'
26 | # ios = Ios.new_telnet '1.1.1.1', 'lab', 'lab'
27 | # iox = Iox.new_telnet '1.1.1.1', 'username', 'pwd'
28 | #
29 | def new_telnet(*args)
30 | new :telnet, *args
31 | end
32 | # Examples:
33 | # my_mac = RShell.new_ssh '1.1.1.1', 'me', 'secret'
34 | # iox = Ios.new_ssh '1.1.1.1', 'lab', 'lab'
35 | # ios = Iosx.new_ssh '1.1.1.1', 'username', 'pwd'
36 | #
37 | def new_ssh(*args)
38 | new :ssh, *args
39 | end
40 | attr_reader :routers
41 | def add(r)
42 | @routers ||=[]
43 | @routers << r
44 | end
45 | end
46 | attr_reader :host, :user, :method, :port, :proxy
47 | alias :username :user
48 | alias :hostname :host
49 | # Adds a login to a Expect4r::BaseLoginObject"
50 | #
51 | # Constructor:
52 | # * new , , , ,
53 | # * new , , ,
54 | # * new , , ,
55 | # * new , ,
56 | #
57 | # * new , options={}
58 | #
59 | # Options are:
60 | # * :host or :hostname
61 | # * :user or :username
62 | # * :pwd or :password
63 | # * :enable_password
64 | # * :port
65 | #
66 | # Examples:
67 | #
68 | # new :telnet, :host=> '1.1.1.1',
69 | # :user=> 'lab',
70 | # :password=>'lab',
71 | # :enable_password=>'secret',
72 | # :port=>2001
73 | #
74 | # new :ssh, :host=> '1.1.1.1', :user=> 'jme'
75 | # new :telnet, '1.1.1.1', 'jme', 'myPwd', 'myEnablePwd', 2001
76 | #
77 | #
78 |
79 | =begin rdoc
80 |
81 | @method, host, @user, pwd, enable_pwd, port = args
82 | @method, host, @user, pwd, enable_pwd = args
83 | @method, host, @user, pwd, port = args
84 | @method, host, @user, pwd = args
85 |
86 | @method, host+port, @user, pwd, enable_pwd = args
87 | @method, host+port, @user, pwd = args
88 |
89 | =end
90 |
91 | def initialize(*args)
92 | host, _pwd, _enable_pwd, ciphered_password=[nil]*5
93 | if args.size>2 and args[1].is_a?(String)
94 | case args.size
95 | when 6
96 | @method, host, @user, _pwd, _enable_pwd, port = args
97 | when 5
98 | if args.last.is_a?(Integer)
99 | @method, host, @user, _pwd, port = args
100 | else
101 | @method, host, @user, _pwd, _enable_pwd = args
102 | end
103 | else
104 | @method, host, @user, _pwd, port = args
105 | end
106 |
107 | raise ArgumentError if host.split.size>1 and port
108 |
109 | @host, _port = host.split
110 | @port = port || (_port || 0).to_i
111 |
112 | elsif args.size == 2 and args[1].is_a?(Hash) and args[0].is_a?(Symbol)
113 | @method = args[0]
114 | @host = args[1][:host] || args[1][:hostname]
115 | port = args[1][:port]
116 | @port = (port || 0).to_i
117 | @user = args[1][:user]|| args[1][:username]
118 |
119 | _pwd = args[1][:pwd] || args[1][:password]
120 | ciphered_password = args[1][:ciphered_password]
121 |
122 | _enable_pwd = args[1][:enable_password]
123 | ciphered_enable_password = args[1][:ciphered_enable_password]
124 |
125 | else
126 | raise
127 | end
128 |
129 | @pwd = if ciphered_password
130 | ciphered_password
131 | else
132 | Expect4r.cipher(_pwd) if _pwd
133 | end
134 |
135 | @enable_pwd = if ciphered_enable_password
136 | ciphered_enable_password
137 | else
138 | Expect4r.cipher(_enable_pwd || _pwd) if _enable_pwd || _pwd
139 | end
140 |
141 | @ps1 = /(.*)(>|#|\$)\s*$/
142 | @more = / --More-- /
143 | @matches=Set.new
144 | Base.add(self)
145 | self
146 | end
147 |
148 | attr_writer :host, :user, :port
149 | alias :hostname= :host=
150 | alias :username= :user=
151 |
152 | attr_accessor :ssh_options
153 |
154 | def method=(arg)
155 | case arg
156 | when :telnet, :ssh
157 | @method= arg
158 | else
159 | raise ArgumentError
160 | end
161 | end
162 |
163 | def spawnee
164 | case method
165 | when :telnet ; "telnet #{host} #{port if port>0}"
166 | when :ssh
167 | cmd ="ssh #{host}"
168 | cmd +=" -oPort=#{port}" if port>0
169 | cmd +=" -oUser=#{spawnee_username}" if spawnee_username
170 | cmd += [" ", [ssh_options]].flatten.join(" ") if ssh_options
171 | cmd
172 | else
173 | raise RuntimeError
174 | end
175 | end
176 |
177 | def spawnee_username
178 | @user
179 | end
180 |
181 | def spawnee_prompt
182 | @ps1
183 | end
184 |
185 | def connect_retry
186 | @connect_retry ||= 0
187 | end
188 |
189 | def connect_retry=(arg)
190 | raise ArgumentError unless (0 .. 100_000) === arg
191 | @connect_retry = arg
192 | end
193 |
194 | def dup
195 | if @pwd
196 | self.class.new @method, @host, @user, Expect4r.decipher(@pwd)
197 | else
198 | self.class.new @method, @host, @user
199 | end
200 | end
201 |
202 | def password
203 | @pwd
204 | end
205 |
206 | def enable_password
207 | @enable_pwd
208 | end
209 |
210 | def cmp(o)
211 | o.spawnee == spawnee and o.password == password and o.enable_password == enable_password
212 | end
213 |
214 | private
215 |
216 | def spawnee_password
217 | @pwd ||= Expect4r.cipher( ask("(#{self}) Enter your password: ") { |q| q.echo = "X" } )
218 | Expect4r.decipher(password)
219 | end
220 |
221 | def spawnee_enable_password
222 | @enable_pwd ||= Expect4r.cipher( ask("(#{self}) Enter your enable password: ") { |q| q.echo = "X" } )
223 | Expect4r.decipher(enable_password)
224 | end
225 |
226 | def spawnee_reset
227 | @pwd=nil
228 | @enable_pwd=nil
229 | end
230 |
231 | end
232 | end
233 |
234 | at_exit {
235 | if Expect4r::Base.all
236 | Expect4r::Base.all.each { |o| o.logout if o.respond_to? :logout }
237 | end
238 | }
239 |
240 |
--------------------------------------------------------------------------------
/README.rdoc:
--------------------------------------------------------------------------------
1 | = Expect4r -- Interact with Cisco IOS, IOS-XR, and Juniper JUNOS CLI.
2 |
3 | Expect4r is a library that enables a ruby program to 'talk' to routers following a written ruby script.
4 | You talk to routers by connecting to them using ssh or telnet and send exec or config command and collect router output than can be parsed using ruby.
5 |
6 | * Connect to routers
7 |
8 | ios = Ios.new_telnet :host=> "hostname", :user=> "username", :pwd => "password"
9 | ios.login
10 |
11 |
12 | iox = Iox.new_telnet 'host', 'username', 'password'
13 | iox.login
14 |
15 | j = J.new_telnet :host=> '', :user=> 'lab', :pwd => 'lab'
16 | j.login
17 |
18 | * Connect to a box via another
19 |
20 | my_mac = RShell.new_ssh 'mac', 'me'
21 | router = Iox.new_telnet 'hostname', 'username'
22 | router.login_via my_mac
23 |
24 | * Re-establish connection
25 |
26 | irb> localhost = Expect4r::BaseLoginObject.new_ssh :hostname=>'localhost'
27 | irb> localhost.connect_retry=10
28 | irb> localhost.login
29 | irb> localhost.interact
30 | irb> s.interact
31 |
32 | #
33 | # ^T to terminate.
34 | #
35 |
36 | [jme@localhost ~]$ ps -ax | grep localhost
37 | 5497 pts/8 Ss+ 0:00 ssh localhost
38 | 5592 pts/9 S+ 0:00 grep --color=auto localhost
39 | [jme@localhost ~]$ kill 5497
40 | Killed by signal 15.
41 |
42 | [jme@localhost ~]$
43 | [jme@localhost ~]$ ls
44 | Desktop Downloads Music Public Videos
45 | Documents expect4r-0.0.11.gem Pictures Templates
46 | [jme@localhost ~]$ ps -ax | grep localhost
47 | 5648 pts/9 Ss+ 0:00 ssh localhost
48 | 5777 pts/10 S+ 0:00 grep --color=auto localhost
49 | [jme@localhost ~]$
50 | [jme@localhost ~]$ ^T
51 | irb>
52 |
53 |
54 |
55 | * Push configurations to routers
56 |
57 | ios.config 'no logging console'
58 |
59 | ios.config %{
60 | interface loopback0
61 | shutdown
62 | }
63 |
64 | iox.config
65 | iox.exp_send 'interface GigabitEthernet0/2/0/0'
66 | iox.exp_send 'desc to switch port 13'
67 | iox.exp_send 'ipv4 address 190.0.0.9 255.255.255.252'
68 | iox.exp_send 'no shut'
69 | iox.commit
70 |
71 | iox.config %|
72 | interface GigabitEthernet0/2/0/0
73 | desc to switch port 13'
74 | ipv4 address 190.0.0.9 255.255.255.252'
75 | no shut
76 | |
77 |
78 | j.config %{
79 | edit logical-router Orleans protocols bgp
80 | edit group session-to-200
81 | set type external
82 | set peer-as 200
83 | set neighbor 40.0.2.1 peer-as 200
84 | }
85 |
86 | * exec commands
87 |
88 | j.exec 'set cli screen-length 0'
89 |
90 | iox.exec "terminal len 0\nterminal width 0"
91 |
92 |
93 | * exec shell commands
94 |
95 | iox.shell 'pidin'
96 |
97 |
98 | * interact with CLI.
99 |
100 | irb> r.interact
101 |
102 | #
103 | # ^T to terminate.
104 | #
105 |
106 | router#clear line 2
107 | [confirm]
108 | [OK]
109 | router# ^T
110 | => nil
111 | irb>
112 |
113 |
114 | irb(main):210:0* j.interact
115 |
116 | #
117 | # ^T to terminate.
118 | #
119 |
120 | jme@router> configure
121 | Entering configuration mode
122 | The configuration has been changed but not committed
123 |
124 | [edit]
125 | jme@router# rollback
126 | load complete
127 |
128 | [edit]
129 | jme@router# show | compare
130 |
131 | [edit]
132 | jme@router# ^T
133 | => nil
134 | irb(main):211:0>
135 |
136 | * Parsing output
137 |
138 | The output is returned as an array of arrays of responses to commands.
139 | Each response is returned as an array of lines.
140 | Parsing a response becomes a matter of iterating an array using the
141 | Arrays's enumerator methods such as find, find_all ...
142 |
143 | irb> output = ts.exec %{
144 | irb" show terminal | include History
145 | irb" show line | include AUX
146 | irb" }
147 | => [
148 | ["show terminal | include History", "History is enabled, history size is 10.", "ios#"],
149 | ["show line | include AUX", " 17 AUX 9600/9600 - 0/0 -", "ios#"]
150 | ]
151 |
152 | output[0] contains output for 1st command 'show terminal'.
153 | output[1] contains output for 1st command 'show line'.
154 | ...
155 | output[n-1] contains output for nth command.
156 |
157 |
158 | * Ios Ping
159 |
160 | irb(main):016:0> ios.ping '192.168.1.23', :count=> 100, :pct_success=>90, :sweep_max_size=> 1500
161 | => [ 99, [146148, 146500],
162 | [["ping",
163 | "Protocol [ip]: ", "",
164 | "Target IP address: ", "192.168.1.23",
165 | "Repeat count [5]: ", "100",
166 | "Datagram size [100]: ", "",
167 | "Timeout in seconds [2]: ", "",
168 | "Extended commands [n]: ", "",
169 | "Sweep range of sizes [n]: ", "yes",
170 | "Sweep min size [36]: ", "",
171 | "Sweep max size [18024]: ", "1500",
172 | "Sweep interval [1]: ", "",
173 | "Type escape sequence to abort.",
174 | "Sending 146500, [36..1500]-byte ICMP Echos to 192.168.1.23, timeout is 2 seconds:",
175 | "!!!!!!!!!!!!!!!!!!!!!!!!!!!!.!!!!!!!!!!!!.!!!!!!!!.!!!!!!!!!!!!!!!!!!!",
176 | "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!",
177 | ...
178 | "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!",
179 | "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!",
180 | "Success rate is 99 percent (146148/146500), round-trip min/avg/max = 1/7/924 ms",
181 | "ios#"]]]
182 |
183 |
184 | * Iox ping
185 |
186 | irb> iox.ping '192.168.1.100'
187 | => [100, [5, 5],
188 | [["ping 192.168.1.100",
189 | "\rTue Oct 26 08:42:58.235 UTC",
190 | "Type escape sequence to abort.",
191 | "Sending 5, 100-byte ICMP Echos to 192.168.1.100, timeout is 2 seconds:",
192 | "!!!!!",
193 | "Success rate is 100 percent (5/5), round-trip min/avg/max = 1/1/2 ms",
194 | "RP/0/0/CPU0:Paris-rp0#"]]]
195 |
196 |
197 | irb> iox.ping '192.168.1.100', :sweep_min_size => 1000, :sweep_max_size => 1300
198 | => [100, [1505, 1505],
199 | [["ping",
200 | "\rTue Oct 26 08:44:33.024 UTC",
201 | "Protocol [ipv4]: ", "",
202 | "\rTarget IP address: ", "192.168.1.100",
203 | "\rRepeat count [5]: ", "", "\rDatagram size [100]: ", "",
204 | "\rTimeout in seconds [2]: ", "",
205 | "\rExtended commands? [no]: ", "",
206 | "\rSweep range of sizes? [no]: ", "yes",
207 | "\rSweep min size [36]: ", "1000",
208 | "\rSweep max size [18024]: ", "1300",
209 | "\rSweep interval [1]: ", "",
210 | "\rType escape sequence to abort.",
211 | "Sending 1505, [1000..1300]-byte ICMP Echos to 192.168.1.100, timeout is 2 seconds:",
212 | "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!",
213 | "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!",
214 | ...
215 | "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!",
216 | "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!",
217 | "Success rate is 100 percent (1505/1505), round-trip min/avg/max = 1/1/3 ms",
218 | "RP/0/0/CPU0:Paris-rp0#"]]]
219 |
220 |
221 | * Junos ping
222 |
223 | irb> j.ping '192.168.1.123'
224 | => [5, [5, 0],
225 | [["ping 192.168.1.123 rapid ",
226 | "PING 192.168.1.123 (192.168.1.123): 56 data bytes",
227 | "!!!!!",
228 | "--- 192.168.1.123 ping statistics ---",
229 | "5 packets transmitted, 5 packets received, 0% packet loss",
230 | "round-trip min/avg/max/stddev = 2.310/2.943/4.390/0.753 ms",
231 | "",
232 | junos> "]]]
233 |
--------------------------------------------------------------------------------
/lib/expect/io.rb:
--------------------------------------------------------------------------------
1 | require 'thread'
2 | require 'pty'
3 | require 'set'
4 |
5 | Thread.abort_on_exception=true
6 |
7 | class IO
8 | def _io_save(no_echo=false, match_string=nil, ch=nil)
9 | s = _io_string!
10 | exp_internal match_string
11 | exp_internal s
12 | s = s.chomp(ch) if ch
13 | _io_buf1 << s
14 | _io_buf1
15 | end
16 | def _io_more? ; ! IO.select([self],nil,nil,0.20).nil? ; end
17 | def _io_exit? ; _io_buf0.last.nil? ; end
18 | def _io_buf0 ; @_buf0_ ; end
19 | def _io_buf1 ; @_buf1_ ; end
20 | def _io_string ; @_buf0_.join ; end
21 | def _io_string!
22 | b = _io_string
23 | @_buf0_=[]
24 | b
25 | end
26 | def _io_sync
27 | @_buf0_ ||=[]
28 | while _io_more?
29 | break if _io_read_char.nil?
30 | end
31 | end
32 | def _io_read_char
33 | c = getc
34 | @_buf0_ << c.chr unless c.nil?
35 | exp_internal "buf0: [#{_io_string.gsub(/\n/,'\n').gsub(/\r/,'\r')}]"
36 | c
37 | end
38 | def readbuf(ti=10)
39 | @_buf0_, @_buf1_=[],[]
40 | loop do
41 | if IO.select([self],nil,nil,ti).nil?
42 | exp_internal "IO.select is NIL (TIMEOUT=#{ti})"
43 | @_buf0_ << nil
44 | else
45 | _io_read_char
46 | end
47 | yield(self)
48 | end
49 | end
50 | end
51 |
52 | module Expect4r
53 |
54 | class ConnectionError < RuntimeError
55 | def initialize(*args)
56 | @output = args[0]
57 | end
58 | def err_msg
59 | "Connection Error: #{@output}"
60 | end
61 | end
62 |
63 | class ExpTimeoutError < RuntimeError
64 | def initialize(*args)
65 | @output, @timeout = args
66 | end
67 | def err_msg
68 | "Timeout Error: timeout= #{@timeout}: #{@output}"
69 | end
70 | end
71 |
72 | class SpawnError < RuntimeError
73 | def initialize(*args)
74 | @cmd = args[0]
75 | end
76 | def err_msg
77 | "Spawn Error: #{@cmd}"
78 | end
79 | end
80 | class Expect4rIO_Error < RuntimeError
81 | end
82 |
83 | def child_exit
84 | if @proxy
85 | @proxy.logout
86 | else
87 | Process.kill(:KILL, @pid) if @pid
88 | @thread.kill if @thread
89 | end
90 | rescue Errno::ESRCH, Errno::ECHILD => e
91 | ensure
92 | @lp, @r, @w, @pid, @proxy = [nil]*5
93 | end
94 |
95 | def spawn(cmd)
96 | begin
97 | child_exited = false
98 | @thread = Thread.new do
99 | PTY.spawn(cmd) do |pipe_read, pipe_write, pid|
100 | @r, @w, @pid = pipe_read, pipe_write, pid
101 | begin
102 | Process.wait(@pid,0)
103 | rescue
104 | ensure
105 | child_exited = true
106 | end
107 | end
108 | end
109 | @thread.priority = -2
110 | unless child_exited
111 | while @r.nil?
112 | sleep(0.05)
113 | end
114 | end
115 | rescue => e
116 | raise SpawnError.new(cmd)
117 | end
118 | end
119 |
120 | def logout
121 | child_exit
122 | @lp, @_lp_1 = nil,nil
123 | @pid
124 | end
125 |
126 | def putc(c)
127 | return unless @w || c.nil?
128 | exp_internal "[#{c}]"
129 | @w.putc(c) and flush
130 | rescue Errno::EIO
131 | child_exit
132 | raise
133 | end
134 |
135 | def exp_puts(s)
136 | exp_print "#{s}\r"
137 | end
138 |
139 | def exp_print(s)
140 | _retry=self.connect_retry
141 | begin
142 | exp_internal "print: #{s.inspect}, io_writer: #{@w}"
143 | return unless @w
144 | @w.print(s) and flush
145 | rescue Errno::EIO, Errno::ECHILD
146 | child_exit
147 | exp_internal "session disconnected: retrying..."
148 | raise unless _retry>0
149 | _retry -=1
150 | sleep(1)
151 | self.login
152 | retry
153 | end
154 | end
155 |
156 | def getc
157 | _retry=self.connect_retry
158 | begin
159 | @r.getc if @r
160 | rescue Errno::EIO, Errno::ECHILD
161 | child_exit
162 | exp_internal "session disconnected: retrying..."
163 | raise unless _retry>0
164 | _retry -=1
165 | sleep(1)
166 | self.login
167 | retry
168 | end
169 | end
170 |
171 | def interact(k="C-t")
172 | raise unless k =~ /C\-[A-Z]/i
173 | unless STDOUT.tty? and STDIN.tty?
174 | $stderr.puts "Cannot interact: not running from terminal!"
175 | return
176 | end
177 | login unless connected?
178 | @@interact_mutex ||= Mutex.new
179 |
180 | @@interact_mutex.synchronize {
181 | k.upcase!
182 | STDOUT.puts "\n\#\n\# #{k.gsub(/C\-/,'^')} to terminate.\n\#\n"
183 | reader :start
184 | writer(eval "?\\#{k}")
185 | }
186 | rescue
187 | ensure
188 | begin
189 | reader :terminate
190 | putline ' ', :no_trim=>true, :no_echo=>true
191 | rescue => e
192 | exp_internal e.to_s
193 | end
194 | end
195 |
196 | def putcr
197 | putline '', :no_trim=>true, :no_echo=>true
198 | nil
199 | end
200 |
201 | def exp_send(lines, arg={})
202 | r=[]
203 | lines.each_line do |l|
204 | l = l.chomp("\n").chomp("\r").strip
205 | r << putline(l, arg) if l.size>0
206 | end
207 | lines.size==1 ? r[0] : r
208 | end
209 |
210 | def expect(match, ti=5, matches=[])
211 | t0 = Time.now
212 | rc, buf = catch(:done) do
213 | @r.readbuf(ti) do |r|
214 | if r._io_exit?
215 | throw :done, [:abort, r._io_string!]
216 | end
217 | case r._io_string
218 | when match
219 | r._io_save false, "matching PROMPT"
220 | # puts "debug IO BUF 1 #{r._io_buf1.inspect}"
221 | throw(:done, [:ok, r._io_buf1])
222 | # FIXME
223 | # Watch out for the ping command !!!!
224 | # throw :done, [:ok, r._io_string!.chomp("\r\n")]
225 | when /(.+)\r\n/, "\r\n"
226 | r._io_save false, "matching EOL"
227 | else
228 | matches.each do |match, arg|
229 | if r._io_string =~ match
230 | r._io_save false, "match #{match}"
231 | if arg.is_a?(Proc)
232 | arg.call(self)
233 | else
234 | exp_puts arg
235 | end
236 | end
237 | end
238 | end
239 | end
240 | end
241 | case rc
242 | when :abort
243 | elapsed = Time.now - t0
244 | if elapsed < ti
245 | child_exit
246 | raise ConnectionError.new(buf)
247 | else
248 | raise ExpTimeoutError.new(buf, elapsed)
249 | end
250 | else
251 | @lp = buf.last
252 | end
253 | [buf, rc]
254 | end
255 |
256 | def get_prompt
257 | putline '', :no_trim=>true, :no_echo=>true
258 | end
259 |
260 | def readline(ti=0.2, matches=[])
261 | ret = expect(/(.+)\r\n/, ti, matches)
262 | ret[0][0].chomp
263 | rescue ExpTimeoutError => ex
264 | ''
265 | end
266 |
267 | def read_until(match,ti=0.2)
268 | ret = expect(match, ti, [])
269 | ret[0][0].chomp
270 | rescue ExpTimeoutError => ex
271 | ''
272 | end
273 |
274 | def connected?
275 | @r && (not child_exited?)
276 | end
277 |
278 | def login_by_proxy(proxy)
279 | raise ArgumentError, "Don't know how to login to #{proxy}" unless proxy.respond_to? :_login_
280 | _login_ :proxy=> proxy
281 | end
282 | alias :login_via :login_by_proxy
283 |
284 | def _login(cmd=self.spawnee, arg={})
285 |
286 | return if connected?
287 |
288 | if arg[:proxy]
289 | login_proxy cmd, arg
290 | else
291 | spawn cmd
292 | end
293 |
294 | arg={:timeout=>13, :no_echo=>false}.merge(arg)
295 | timeout = arg[:timeout]
296 | no_echo = arg[:no_echo]
297 |
298 | output=[]
299 | t0 = Time.now
300 | ev, buf = catch(:done) do
301 | @r.readbuf(timeout) do |read_pipe|
302 | if read_pipe._io_exit?
303 | exp_internal "readbuf: _io_exit?"
304 | throw :done, [ :cnx_error, read_pipe._io_buf1]
305 | end
306 |
307 | @pre_matches ||= []
308 | @pre_matches.each do |match, _send|
309 | if read_pipe._io_string =~ match
310 | read_pipe._io_save no_echo, "match #{match}"
311 | if _send.is_a?(Proc)
312 | _send.call
313 | else
314 | exp_puts _send.to_s
315 | end
316 | end
317 | end
318 |
319 | case read_pipe._io_string
320 | when spawnee_prompt
321 | read_pipe._io_save no_echo, "match PROMPT"
322 | throw(:done, [:ok, read_pipe._io_buf1])
323 | when /Last (L|l)ogin:/
324 | read_pipe._io_save no_echo # consumes
325 | when /(user\s*name\s*|login):\s*$/i
326 | read_pipe._io_save no_echo, "match USERNAME"
327 | exp_puts spawnee_username
328 | when /password:\s*$/i
329 | read_pipe._io_save no_echo, "match PASSWORD"
330 | @w.print(spawnee_password+"\r") and flush
331 | when /Escape character is/
332 | read_pipe._io_save no_echo, "match Escape char"
333 | io_escape_char_cb
334 | when /.*\r\n/
335 | exp_internal "match EOL"
336 | read_pipe._io_save no_echo, "match EOL"
337 | when /Are you sure you want to continue connecting \(yes\/no.*\)\?/
338 | read_pipe._io_save no_echo, "match continue connecting"
339 | exp_puts 'yes'
340 | when /(Login incorrect|denied, please try again)/
341 | spawnee_reset
342 | else
343 | # For objects that include Expect4r but do not subclass base Login class.
344 | @matches ||= []
345 | @matches.each { |match, _send|
346 | if read_pipe._io_string =~ match
347 | read_pipe._io_save no_echo, "match #{match}"
348 | if _send.is_a?(Proc)
349 | exp_puts _send.call
350 | else
351 | exp_puts _send
352 | end
353 | end
354 | }
355 | end
356 | end
357 | end
358 | case ev
359 | when :cnx_error
360 | child_exit
361 | err_msg = "Could not connect to #{@host}:\n"
362 | err_msg += " >> #{cmd}\n "
363 | err_msg += buf.join("\n ")
364 | raise ConnectionError.new(err_msg)
365 | else
366 | @_lp_1 = buf[-2]
367 | @lp = buf.last
368 | end
369 | [buf, ev]
370 | end
371 |
372 | alias :login :_login
373 |
374 |
375 | private
376 |
377 | def login_proxy(cmd, arg)
378 | @proxy = arg[:proxy].dup
379 | @proxy.login
380 | @proxy.exp_puts cmd
381 | @r, @w, @pid = @proxy.instance_eval { [@r, @w, @pid] }
382 | end
383 |
384 | #FIXME ? putline to send_cmd ?
385 | # hide putline and expose cmd
386 | def _putline_(line, arg={})
387 | raise ConnectionError.new(line) if child_exited?
388 |
389 | arg = {:ti=>13, :no_echo=>false, :debug=>0, :sync=> false, :no_trim=>false}.merge(arg)
390 | no_echo = arg[:no_echo]
391 | ti = arg[:ti]
392 | unless arg[:no_trim]
393 | line = line.gsub(/\s+/,' ').gsub(/^\s+/,'') unless arg[:no_trim]
394 | return [[], :empty_line] unless line.size>0
395 | end
396 | sync if arg[:sync]
397 | t0 = Time.now
398 | exp_puts line
399 | output=[]
400 | rc, buf = catch(:done) do
401 | @r.readbuf(arg[:ti]) do |r|
402 | if r._io_exit?
403 | r._io_save(no_echo)
404 | throw :done, [ :abort, r._io_buf1]
405 | end
406 | case r._io_string
407 | when @ps1, @ps1_bis
408 | unless r._io_more?
409 | r._io_save no_echo, "matching PROMPT"
410 | throw(:done, [:ok, r._io_buf1])
411 | end
412 | exp_internal "more..."
413 | when /(.+)\r\n/, "\r\n"
414 | r._io_save no_echo, "matching EOL"
415 | when @more
416 | r._io_save no_echo, "matching MORE"
417 | putc ' '
418 | else
419 | # For objects that include Expect4r but do not subclass base Login class.
420 | @matches ||= []
421 | @matches.each { |match, _send|
422 | if r._io_string =~ match
423 | r._io_save no_echo, "match #{match}"
424 | if _send.is_a?(Proc)
425 | exp_puts _send.call
426 | else
427 | exp_puts _send
428 | end
429 | end
430 | }
431 | end
432 | end
433 | end
434 | case rc
435 | when :abort
436 | elapsed = Time.now - t0
437 | if elapsed < ti
438 | child_exit
439 | raise ConnectionError.new(line)
440 | else
441 | raise ExpTimeoutError.new(line, elapsed)
442 | end
443 | else
444 | @_lp_1 = buf[-2]
445 | @lp = buf.last
446 | end
447 | [buf, rc]
448 | end
449 |
450 | def putline(*args)
451 | _retry=self.connect_retry
452 | begin
453 | _putline_(*args)
454 | rescue Errno::EIO, Errno::ECHILD
455 | child_exit
456 | exp_internal "session disconnected: retrying..."
457 | raise unless _retry>0
458 | _retry -=1
459 | sleep(1)
460 | self.login
461 | retry
462 | end
463 | end
464 |
465 | def writer(k)
466 | stty_raw
467 | begin
468 | loop do
469 | break if (c = STDIN.getc) == k
470 | putc(c)
471 | end
472 | rescue PTY::ChildExited => e
473 | child_exit
474 | ensure
475 | stty_cooked
476 | end
477 | end
478 |
479 | def reader(arg=:start)
480 | case arg
481 | when :start
482 | stty_raw
483 | @reader = Thread.new do
484 | begin
485 | loop do
486 | c = getc
487 | break if c.nil?
488 | STDOUT.putc c
489 | end
490 | rescue Errno::EIO
491 | rescue => e
492 | p e
493 | ensure
494 | stty_cooked
495 | end
496 | end
497 | when :terminate
498 | @reader.terminate if @reader
499 | stty_cooked
500 | end
501 | end
502 |
503 | def sync
504 | @r._io_sync
505 | end
506 |
507 | def child_exited?
508 | @pid == nil
509 | end
510 |
511 | def stty_cooked
512 | system "stty echo -raw"
513 | end
514 |
515 | def stty_raw
516 | system "stty -echo raw"
517 | end
518 |
519 | def flush
520 | @w.flush
521 | end
522 |
523 | end
524 |
525 | module Kernel
526 | def exp_debug(state=:enable)
527 | case state
528 | when :disable, :off
529 | Kernel.instance_eval {
530 | define_method("exp_internal") do |s|
531 | end
532 | }
533 | :disable
534 | when :enable, :on
535 | Kernel.instance_eval {
536 | define_method("exp_internal") do |s|
537 | STDOUT.print "\ndebug: #{s}"
538 | end
539 | }
540 | :enable
541 | else
542 | nil
543 | end
544 | end
545 | exp_debug :disable
546 | end
547 |
--------------------------------------------------------------------------------
/COPYING:
--------------------------------------------------------------------------------
1 | GNU GENERAL PUBLIC LICENSE
2 | Version 3, 29 June 2007
3 |
4 | Copyright (C) 2007 Free Software Foundation, Inc.
5 | Everyone is permitted to copy and distribute verbatim copies
6 | of this license document, but changing it is not allowed.
7 |
8 | Preamble
9 |
10 | The GNU General Public License is a free, copyleft license for
11 | software and other kinds of works.
12 |
13 | The licenses for most software and other practical works are designed
14 | to take away your freedom to share and change the works. By contrast,
15 | the GNU General Public License is intended to guarantee your freedom to
16 | share and change all versions of a program--to make sure it remains free
17 | software for all its users. We, the Free Software Foundation, use the
18 | GNU General Public License for most of our software; it applies also to
19 | any other work released this way by its authors. You can apply it to
20 | your programs, too.
21 |
22 | When we speak of free software, we are referring to freedom, not
23 | price. Our General Public Licenses are designed to make sure that you
24 | have the freedom to distribute copies of free software (and charge for
25 | them if you wish), that you receive source code or can get it if you
26 | want it, that you can change the software or use pieces of it in new
27 | free programs, and that you know you can do these things.
28 |
29 | To protect your rights, we need to prevent others from denying you
30 | these rights or asking you to surrender the rights. Therefore, you have
31 | certain responsibilities if you distribute copies of the software, or if
32 | you modify it: responsibilities to respect the freedom of others.
33 |
34 | For example, if you distribute copies of such a program, whether
35 | gratis or for a fee, you must pass on to the recipients the same
36 | freedoms that you received. You must make sure that they, too, receive
37 | or can get the source code. And you must show them these terms so they
38 | know their rights.
39 |
40 | Developers that use the GNU GPL protect your rights with two steps:
41 | (1) assert copyright on the software, and (2) offer you this License
42 | giving you legal permission to copy, distribute and/or modify it.
43 |
44 | For the developers' and authors' protection, the GPL clearly explains
45 | that there is no warranty for this free software. For both users' and
46 | authors' sake, the GPL requires that modified versions be marked as
47 | changed, so that their problems will not be attributed erroneously to
48 | authors of previous versions.
49 |
50 | Some devices are designed to deny users access to install or run
51 | modified versions of the software inside them, although the manufacturer
52 | can do so. This is fundamentally incompatible with the aim of
53 | protecting users' freedom to change the software. The systematic
54 | pattern of such abuse occurs in the area of products for individuals to
55 | use, which is precisely where it is most unacceptable. Therefore, we
56 | have designed this version of the GPL to prohibit the practice for those
57 | products. If such problems arise substantially in other domains, we
58 | stand ready to extend this provision to those domains in future versions
59 | of the GPL, as needed to protect the freedom of users.
60 |
61 | Finally, every program is threatened constantly by software patents.
62 | States should not allow patents to restrict development and use of
63 | software on general-purpose computers, but in those that do, we wish to
64 | avoid the special danger that patents applied to a free program could
65 | make it effectively proprietary. To prevent this, the GPL assures that
66 | patents cannot be used to render the program non-free.
67 |
68 | The precise terms and conditions for copying, distribution and
69 | modification follow.
70 |
71 | TERMS AND CONDITIONS
72 |
73 | 0. Definitions.
74 |
75 | "This License" refers to version 3 of the GNU General Public License.
76 |
77 | "Copyright" also means copyright-like laws that apply to other kinds of
78 | works, such as semiconductor masks.
79 |
80 | "The Program" refers to any copyrightable work licensed under this
81 | License. Each licensee is addressed as "you". "Licensees" and
82 | "recipients" may be individuals or organizations.
83 |
84 | To "modify" a work means to copy from or adapt all or part of the work
85 | in a fashion requiring copyright permission, other than the making of an
86 | exact copy. The resulting work is called a "modified version" of the
87 | earlier work or a work "based on" the earlier work.
88 |
89 | A "covered work" means either the unmodified Program or a work based
90 | on the Program.
91 |
92 | To "propagate" a work means to do anything with it that, without
93 | permission, would make you directly or secondarily liable for
94 | infringement under applicable copyright law, except executing it on a
95 | computer or modifying a private copy. Propagation includes copying,
96 | distribution (with or without modification), making available to the
97 | public, and in some countries other activities as well.
98 |
99 | To "convey" a work means any kind of propagation that enables other
100 | parties to make or receive copies. Mere interaction with a user through
101 | a computer network, with no transfer of a copy, is not conveying.
102 |
103 | An interactive user interface displays "Appropriate Legal Notices"
104 | to the extent that it includes a convenient and prominently visible
105 | feature that (1) displays an appropriate copyright notice, and (2)
106 | tells the user that there is no warranty for the work (except to the
107 | extent that warranties are provided), that licensees may convey the
108 | work under this License, and how to view a copy of this License. If
109 | the interface presents a list of user commands or options, such as a
110 | menu, a prominent item in the list meets this criterion.
111 |
112 | 1. Source Code.
113 |
114 | The "source code" for a work means the preferred form of the work
115 | for making modifications to it. "Object code" means any non-source
116 | form of a work.
117 |
118 | A "Standard Interface" means an interface that either is an official
119 | standard defined by a recognized standards body, or, in the case of
120 | interfaces specified for a particular programming language, one that
121 | is widely used among developers working in that language.
122 |
123 | The "System Libraries" of an executable work include anything, other
124 | than the work as a whole, that (a) is included in the normal form of
125 | packaging a Major Component, but which is not part of that Major
126 | Component, and (b) serves only to enable use of the work with that
127 | Major Component, or to implement a Standard Interface for which an
128 | implementation is available to the public in source code form. A
129 | "Major Component", in this context, means a major essential component
130 | (kernel, window system, and so on) of the specific operating system
131 | (if any) on which the executable work runs, or a compiler used to
132 | produce the work, or an object code interpreter used to run it.
133 |
134 | The "Corresponding Source" for a work in object code form means all
135 | the source code needed to generate, install, and (for an executable
136 | work) run the object code and to modify the work, including scripts to
137 | control those activities. However, it does not include the work's
138 | System Libraries, or general-purpose tools or generally available free
139 | programs which are used unmodified in performing those activities but
140 | which are not part of the work. For example, Corresponding Source
141 | includes interface definition files associated with source files for
142 | the work, and the source code for shared libraries and dynamically
143 | linked subprograms that the work is specifically designed to require,
144 | such as by intimate data communication or control flow between those
145 | subprograms and other parts of the work.
146 |
147 | The Corresponding Source need not include anything that users
148 | can regenerate automatically from other parts of the Corresponding
149 | Source.
150 |
151 | The Corresponding Source for a work in source code form is that
152 | same work.
153 |
154 | 2. Basic Permissions.
155 |
156 | All rights granted under this License are granted for the term of
157 | copyright on the Program, and are irrevocable provided the stated
158 | conditions are met. This License explicitly affirms your unlimited
159 | permission to run the unmodified Program. The output from running a
160 | covered work is covered by this License only if the output, given its
161 | content, constitutes a covered work. This License acknowledges your
162 | rights of fair use or other equivalent, as provided by copyright law.
163 |
164 | You may make, run and propagate covered works that you do not
165 | convey, without conditions so long as your license otherwise remains
166 | in force. You may convey covered works to others for the sole purpose
167 | of having them make modifications exclusively for you, or provide you
168 | with facilities for running those works, provided that you comply with
169 | the terms of this License in conveying all material for which you do
170 | not control copyright. Those thus making or running the covered works
171 | for you must do so exclusively on your behalf, under your direction
172 | and control, on terms that prohibit them from making any copies of
173 | your copyrighted material outside their relationship with you.
174 |
175 | Conveying under any other circumstances is permitted solely under
176 | the conditions stated below. Sublicensing is not allowed; section 10
177 | makes it unnecessary.
178 |
179 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
180 |
181 | No covered work shall be deemed part of an effective technological
182 | measure under any applicable law fulfilling obligations under article
183 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or
184 | similar laws prohibiting or restricting circumvention of such
185 | measures.
186 |
187 | When you convey a covered work, you waive any legal power to forbid
188 | circumvention of technological measures to the extent such circumvention
189 | is effected by exercising rights under this License with respect to
190 | the covered work, and you disclaim any intention to limit operation or
191 | modification of the work as a means of enforcing, against the work's
192 | users, your or third parties' legal rights to forbid circumvention of
193 | technological measures.
194 |
195 | 4. Conveying Verbatim Copies.
196 |
197 | You may convey verbatim copies of the Program's source code as you
198 | receive it, in any medium, provided that you conspicuously and
199 | appropriately publish on each copy an appropriate copyright notice;
200 | keep intact all notices stating that this License and any
201 | non-permissive terms added in accord with section 7 apply to the code;
202 | keep intact all notices of the absence of any warranty; and give all
203 | recipients a copy of this License along with the Program.
204 |
205 | You may charge any price or no price for each copy that you convey,
206 | and you may offer support or warranty protection for a fee.
207 |
208 | 5. Conveying Modified Source Versions.
209 |
210 | You may convey a work based on the Program, or the modifications to
211 | produce it from the Program, in the form of source code under the
212 | terms of section 4, provided that you also meet all of these conditions:
213 |
214 | a) The work must carry prominent notices stating that you modified
215 | it, and giving a relevant date.
216 |
217 | b) The work must carry prominent notices stating that it is
218 | released under this License and any conditions added under section
219 | 7. This requirement modifies the requirement in section 4 to
220 | "keep intact all notices".
221 |
222 | c) You must license the entire work, as a whole, under this
223 | License to anyone who comes into possession of a copy. This
224 | License will therefore apply, along with any applicable section 7
225 | additional terms, to the whole of the work, and all its parts,
226 | regardless of how they are packaged. This License gives no
227 | permission to license the work in any other way, but it does not
228 | invalidate such permission if you have separately received it.
229 |
230 | d) If the work has interactive user interfaces, each must display
231 | Appropriate Legal Notices; however, if the Program has interactive
232 | interfaces that do not display Appropriate Legal Notices, your
233 | work need not make them do so.
234 |
235 | A compilation of a covered work with other separate and independent
236 | works, which are not by their nature extensions of the covered work,
237 | and which are not combined with it such as to form a larger program,
238 | in or on a volume of a storage or distribution medium, is called an
239 | "aggregate" if the compilation and its resulting copyright are not
240 | used to limit the access or legal rights of the compilation's users
241 | beyond what the individual works permit. Inclusion of a covered work
242 | in an aggregate does not cause this License to apply to the other
243 | parts of the aggregate.
244 |
245 | 6. Conveying Non-Source Forms.
246 |
247 | You may convey a covered work in object code form under the terms
248 | of sections 4 and 5, provided that you also convey the
249 | machine-readable Corresponding Source under the terms of this License,
250 | in one of these ways:
251 |
252 | a) Convey the object code in, or embodied in, a physical product
253 | (including a physical distribution medium), accompanied by the
254 | Corresponding Source fixed on a durable physical medium
255 | customarily used for software interchange.
256 |
257 | b) Convey the object code in, or embodied in, a physical product
258 | (including a physical distribution medium), accompanied by a
259 | written offer, valid for at least three years and valid for as
260 | long as you offer spare parts or customer support for that product
261 | model, to give anyone who possesses the object code either (1) a
262 | copy of the Corresponding Source for all the software in the
263 | product that is covered by this License, on a durable physical
264 | medium customarily used for software interchange, for a price no
265 | more than your reasonable cost of physically performing this
266 | conveying of source, or (2) access to copy the
267 | Corresponding Source from a network server at no charge.
268 |
269 | c) Convey individual copies of the object code with a copy of the
270 | written offer to provide the Corresponding Source. This
271 | alternative is allowed only occasionally and noncommercially, and
272 | only if you received the object code with such an offer, in accord
273 | with subsection 6b.
274 |
275 | d) Convey the object code by offering access from a designated
276 | place (gratis or for a charge), and offer equivalent access to the
277 | Corresponding Source in the same way through the same place at no
278 | further charge. You need not require recipients to copy the
279 | Corresponding Source along with the object code. If the place to
280 | copy the object code is a network server, the Corresponding Source
281 | may be on a different server (operated by you or a third party)
282 | that supports equivalent copying facilities, provided you maintain
283 | clear directions next to the object code saying where to find the
284 | Corresponding Source. Regardless of what server hosts the
285 | Corresponding Source, you remain obligated to ensure that it is
286 | available for as long as needed to satisfy these requirements.
287 |
288 | e) Convey the object code using peer-to-peer transmission, provided
289 | you inform other peers where the object code and Corresponding
290 | Source of the work are being offered to the general public at no
291 | charge under subsection 6d.
292 |
293 | A separable portion of the object code, whose source code is excluded
294 | from the Corresponding Source as a System Library, need not be
295 | included in conveying the object code work.
296 |
297 | A "User Product" is either (1) a "consumer product", which means any
298 | tangible personal property which is normally used for personal, family,
299 | or household purposes, or (2) anything designed or sold for incorporation
300 | into a dwelling. In determining whether a product is a consumer product,
301 | doubtful cases shall be resolved in favor of coverage. For a particular
302 | product received by a particular user, "normally used" refers to a
303 | typical or common use of that class of product, regardless of the status
304 | of the particular user or of the way in which the particular user
305 | actually uses, or expects or is expected to use, the product. A product
306 | is a consumer product regardless of whether the product has substantial
307 | commercial, industrial or non-consumer uses, unless such uses represent
308 | the only significant mode of use of the product.
309 |
310 | "Installation Information" for a User Product means any methods,
311 | procedures, authorization keys, or other information required to install
312 | and execute modified versions of a covered work in that User Product from
313 | a modified version of its Corresponding Source. The information must
314 | suffice to ensure that the continued functioning of the modified object
315 | code is in no case prevented or interfered with solely because
316 | modification has been made.
317 |
318 | If you convey an object code work under this section in, or with, or
319 | specifically for use in, a User Product, and the conveying occurs as
320 | part of a transaction in which the right of possession and use of the
321 | User Product is transferred to the recipient in perpetuity or for a
322 | fixed term (regardless of how the transaction is characterized), the
323 | Corresponding Source conveyed under this section must be accompanied
324 | by the Installation Information. But this requirement does not apply
325 | if neither you nor any third party retains the ability to install
326 | modified object code on the User Product (for example, the work has
327 | been installed in ROM).
328 |
329 | The requirement to provide Installation Information does not include a
330 | requirement to continue to provide support service, warranty, or updates
331 | for a work that has been modified or installed by the recipient, or for
332 | the User Product in which it has been modified or installed. Access to a
333 | network may be denied when the modification itself materially and
334 | adversely affects the operation of the network or violates the rules and
335 | protocols for communication across the network.
336 |
337 | Corresponding Source conveyed, and Installation Information provided,
338 | in accord with this section must be in a format that is publicly
339 | documented (and with an implementation available to the public in
340 | source code form), and must require no special password or key for
341 | unpacking, reading or copying.
342 |
343 | 7. Additional Terms.
344 |
345 | "Additional permissions" are terms that supplement the terms of this
346 | License by making exceptions from one or more of its conditions.
347 | Additional permissions that are applicable to the entire Program shall
348 | be treated as though they were included in this License, to the extent
349 | that they are valid under applicable law. If additional permissions
350 | apply only to part of the Program, that part may be used separately
351 | under those permissions, but the entire Program remains governed by
352 | this License without regard to the additional permissions.
353 |
354 | When you convey a copy of a covered work, you may at your option
355 | remove any additional permissions from that copy, or from any part of
356 | it. (Additional permissions may be written to require their own
357 | removal in certain cases when you modify the work.) You may place
358 | additional permissions on material, added by you to a covered work,
359 | for which you have or can give appropriate copyright permission.
360 |
361 | Notwithstanding any other provision of this License, for material you
362 | add to a covered work, you may (if authorized by the copyright holders of
363 | that material) supplement the terms of this License with terms:
364 |
365 | a) Disclaiming warranty or limiting liability differently from the
366 | terms of sections 15 and 16 of this License; or
367 |
368 | b) Requiring preservation of specified reasonable legal notices or
369 | author attributions in that material or in the Appropriate Legal
370 | Notices displayed by works containing it; or
371 |
372 | c) Prohibiting misrepresentation of the origin of that material, or
373 | requiring that modified versions of such material be marked in
374 | reasonable ways as different from the original version; or
375 |
376 | d) Limiting the use for publicity purposes of names of licensors or
377 | authors of the material; or
378 |
379 | e) Declining to grant rights under trademark law for use of some
380 | trade names, trademarks, or service marks; or
381 |
382 | f) Requiring indemnification of licensors and authors of that
383 | material by anyone who conveys the material (or modified versions of
384 | it) with contractual assumptions of liability to the recipient, for
385 | any liability that these contractual assumptions directly impose on
386 | those licensors and authors.
387 |
388 | All other non-permissive additional terms are considered "further
389 | restrictions" within the meaning of section 10. If the Program as you
390 | received it, or any part of it, contains a notice stating that it is
391 | governed by this License along with a term that is a further
392 | restriction, you may remove that term. If a license document contains
393 | a further restriction but permits relicensing or conveying under this
394 | License, you may add to a covered work material governed by the terms
395 | of that license document, provided that the further restriction does
396 | not survive such relicensing or conveying.
397 |
398 | If you add terms to a covered work in accord with this section, you
399 | must place, in the relevant source files, a statement of the
400 | additional terms that apply to those files, or a notice indicating
401 | where to find the applicable terms.
402 |
403 | Additional terms, permissive or non-permissive, may be stated in the
404 | form of a separately written license, or stated as exceptions;
405 | the above requirements apply either way.
406 |
407 | 8. Termination.
408 |
409 | You may not propagate or modify a covered work except as expressly
410 | provided under this License. Any attempt otherwise to propagate or
411 | modify it is void, and will automatically terminate your rights under
412 | this License (including any patent licenses granted under the third
413 | paragraph of section 11).
414 |
415 | However, if you cease all violation of this License, then your
416 | license from a particular copyright holder is reinstated (a)
417 | provisionally, unless and until the copyright holder explicitly and
418 | finally terminates your license, and (b) permanently, if the copyright
419 | holder fails to notify you of the violation by some reasonable means
420 | prior to 60 days after the cessation.
421 |
422 | Moreover, your license from a particular copyright holder is
423 | reinstated permanently if the copyright holder notifies you of the
424 | violation by some reasonable means, this is the first time you have
425 | received notice of violation of this License (for any work) from that
426 | copyright holder, and you cure the violation prior to 30 days after
427 | your receipt of the notice.
428 |
429 | Termination of your rights under this section does not terminate the
430 | licenses of parties who have received copies or rights from you under
431 | this License. If your rights have been terminated and not permanently
432 | reinstated, you do not qualify to receive new licenses for the same
433 | material under section 10.
434 |
435 | 9. Acceptance Not Required for Having Copies.
436 |
437 | You are not required to accept this License in order to receive or
438 | run a copy of the Program. Ancillary propagation of a covered work
439 | occurring solely as a consequence of using peer-to-peer transmission
440 | to receive a copy likewise does not require acceptance. However,
441 | nothing other than this License grants you permission to propagate or
442 | modify any covered work. These actions infringe copyright if you do
443 | not accept this License. Therefore, by modifying or propagating a
444 | covered work, you indicate your acceptance of this License to do so.
445 |
446 | 10. Automatic Licensing of Downstream Recipients.
447 |
448 | Each time you convey a covered work, the recipient automatically
449 | receives a license from the original licensors, to run, modify and
450 | propagate that work, subject to this License. You are not responsible
451 | for enforcing compliance by third parties with this License.
452 |
453 | An "entity transaction" is a transaction transferring control of an
454 | organization, or substantially all assets of one, or subdividing an
455 | organization, or merging organizations. If propagation of a covered
456 | work results from an entity transaction, each party to that
457 | transaction who receives a copy of the work also receives whatever
458 | licenses to the work the party's predecessor in interest had or could
459 | give under the previous paragraph, plus a right to possession of the
460 | Corresponding Source of the work from the predecessor in interest, if
461 | the predecessor has it or can get it with reasonable efforts.
462 |
463 | You may not impose any further restrictions on the exercise of the
464 | rights granted or affirmed under this License. For example, you may
465 | not impose a license fee, royalty, or other charge for exercise of
466 | rights granted under this License, and you may not initiate litigation
467 | (including a cross-claim or counterclaim in a lawsuit) alleging that
468 | any patent claim is infringed by making, using, selling, offering for
469 | sale, or importing the Program or any portion of it.
470 |
471 | 11. Patents.
472 |
473 | A "contributor" is a copyright holder who authorizes use under this
474 | License of the Program or a work on which the Program is based. The
475 | work thus licensed is called the contributor's "contributor version".
476 |
477 | A contributor's "essential patent claims" are all patent claims
478 | owned or controlled by the contributor, whether already acquired or
479 | hereafter acquired, that would be infringed by some manner, permitted
480 | by this License, of making, using, or selling its contributor version,
481 | but do not include claims that would be infringed only as a
482 | consequence of further modification of the contributor version. For
483 | purposes of this definition, "control" includes the right to grant
484 | patent sublicenses in a manner consistent with the requirements of
485 | this License.
486 |
487 | Each contributor grants you a non-exclusive, worldwide, royalty-free
488 | patent license under the contributor's essential patent claims, to
489 | make, use, sell, offer for sale, import and otherwise run, modify and
490 | propagate the contents of its contributor version.
491 |
492 | In the following three paragraphs, a "patent license" is any express
493 | agreement or commitment, however denominated, not to enforce a patent
494 | (such as an express permission to practice a patent or covenant not to
495 | sue for patent infringement). To "grant" such a patent license to a
496 | party means to make such an agreement or commitment not to enforce a
497 | patent against the party.
498 |
499 | If you convey a covered work, knowingly relying on a patent license,
500 | and the Corresponding Source of the work is not available for anyone
501 | to copy, free of charge and under the terms of this License, through a
502 | publicly available network server or other readily accessible means,
503 | then you must either (1) cause the Corresponding Source to be so
504 | available, or (2) arrange to deprive yourself of the benefit of the
505 | patent license for this particular work, or (3) arrange, in a manner
506 | consistent with the requirements of this License, to extend the patent
507 | license to downstream recipients. "Knowingly relying" means you have
508 | actual knowledge that, but for the patent license, your conveying the
509 | covered work in a country, or your recipient's use of the covered work
510 | in a country, would infringe one or more identifiable patents in that
511 | country that you have reason to believe are valid.
512 |
513 | If, pursuant to or in connection with a single transaction or
514 | arrangement, you convey, or propagate by procuring conveyance of, a
515 | covered work, and grant a patent license to some of the parties
516 | receiving the covered work authorizing them to use, propagate, modify
517 | or convey a specific copy of the covered work, then the patent license
518 | you grant is automatically extended to all recipients of the covered
519 | work and works based on it.
520 |
521 | A patent license is "discriminatory" if it does not include within
522 | the scope of its coverage, prohibits the exercise of, or is
523 | conditioned on the non-exercise of one or more of the rights that are
524 | specifically granted under this License. You may not convey a covered
525 | work if you are a party to an arrangement with a third party that is
526 | in the business of distributing software, under which you make payment
527 | to the third party based on the extent of your activity of conveying
528 | the work, and under which the third party grants, to any of the
529 | parties who would receive the covered work from you, a discriminatory
530 | patent license (a) in connection with copies of the covered work
531 | conveyed by you (or copies made from those copies), or (b) primarily
532 | for and in connection with specific products or compilations that
533 | contain the covered work, unless you entered into that arrangement,
534 | or that patent license was granted, prior to 28 March 2007.
535 |
536 | Nothing in this License shall be construed as excluding or limiting
537 | any implied license or other defenses to infringement that may
538 | otherwise be available to you under applicable patent law.
539 |
540 | 12. No Surrender of Others' Freedom.
541 |
542 | If conditions are imposed on you (whether by court order, agreement or
543 | otherwise) that contradict the conditions of this License, they do not
544 | excuse you from the conditions of this License. If you cannot convey a
545 | covered work so as to satisfy simultaneously your obligations under this
546 | License and any other pertinent obligations, then as a consequence you may
547 | not convey it at all. For example, if you agree to terms that obligate you
548 | to collect a royalty for further conveying from those to whom you convey
549 | the Program, the only way you could satisfy both those terms and this
550 | License would be to refrain entirely from conveying the Program.
551 |
552 | 13. Use with the GNU Affero General Public License.
553 |
554 | Notwithstanding any other provision of this License, you have
555 | permission to link or combine any covered work with a work licensed
556 | under version 3 of the GNU Affero General Public License into a single
557 | combined work, and to convey the resulting work. The terms of this
558 | License will continue to apply to the part which is the covered work,
559 | but the special requirements of the GNU Affero General Public License,
560 | section 13, concerning interaction through a network will apply to the
561 | combination as such.
562 |
563 | 14. Revised Versions of this License.
564 |
565 | The Free Software Foundation may publish revised and/or new versions of
566 | the GNU General Public License from time to time. Such new versions will
567 | be similar in spirit to the present version, but may differ in detail to
568 | address new problems or concerns.
569 |
570 | Each version is given a distinguishing version number. If the
571 | Program specifies that a certain numbered version of the GNU General
572 | Public License "or any later version" applies to it, you have the
573 | option of following the terms and conditions either of that numbered
574 | version or of any later version published by the Free Software
575 | Foundation. If the Program does not specify a version number of the
576 | GNU General Public License, you may choose any version ever published
577 | by the Free Software Foundation.
578 |
579 | If the Program specifies that a proxy can decide which future
580 | versions of the GNU General Public License can be used, that proxy's
581 | public statement of acceptance of a version permanently authorizes you
582 | to choose that version for the Program.
583 |
584 | Later license versions may give you additional or different
585 | permissions. However, no additional obligations are imposed on any
586 | author or copyright holder as a result of your choosing to follow a
587 | later version.
588 |
589 | 15. Disclaimer of Warranty.
590 |
591 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
592 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
593 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
594 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
595 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
596 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
597 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
598 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
599 |
600 | 16. Limitation of Liability.
601 |
602 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
603 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
604 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
605 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
606 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
607 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
608 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
609 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
610 | SUCH DAMAGES.
611 |
612 | 17. Interpretation of Sections 15 and 16.
613 |
614 | If the disclaimer of warranty and limitation of liability provided
615 | above cannot be given local legal effect according to their terms,
616 | reviewing courts shall apply local law that most closely approximates
617 | an absolute waiver of all civil liability in connection with the
618 | Program, unless a warranty or assumption of liability accompanies a
619 | copy of the Program in return for a fee.
620 |
621 | END OF TERMS AND CONDITIONS
622 |
623 | How to Apply These Terms to Your New Programs
624 |
625 | If you develop a new program, and you want it to be of the greatest
626 | possible use to the public, the best way to achieve this is to make it
627 | free software which everyone can redistribute and change under these terms.
628 |
629 | To do so, attach the following notices to the program. It is safest
630 | to attach them to the start of each source file to most effectively
631 | state the exclusion of warranty; and each file should have at least
632 | the "copyright" line and a pointer to where the full notice is found.
633 |
634 |
635 | Copyright (C)
636 |
637 | This program is free software: you can redistribute it and/or modify
638 | it under the terms of the GNU General Public License as published by
639 | the Free Software Foundation, either version 3 of the License, or
640 | (at your option) any later version.
641 |
642 | This program is distributed in the hope that it will be useful,
643 | but WITHOUT ANY WARRANTY; without even the implied warranty of
644 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
645 | GNU General Public License for more details.
646 |
647 | You should have received a copy of the GNU General Public License
648 | along with this program. If not, see .
649 |
650 | Also add information on how to contact you by electronic and paper mail.
651 |
652 | If the program does terminal interaction, make it output a short
653 | notice like this when it starts in an interactive mode:
654 |
655 | Copyright (C)
656 | This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
657 | This is free software, and you are welcome to redistribute it
658 | under certain conditions; type `show c' for details.
659 |
660 | The hypothetical commands `show w' and `show c' should show the appropriate
661 | parts of the General Public License. Of course, your program's commands
662 | might be different; for a GUI interface, you would use an "about box".
663 |
664 | You should also get your employer (if you work as a programmer) or school,
665 | if any, to sign a "copyright disclaimer" for the program, if necessary.
666 | For more information on this, and how to apply and follow the GNU GPL, see
667 | .
668 |
669 | The GNU General Public License does not permit incorporating your program
670 | into proprietary programs. If your program is a subroutine library, you
671 | may consider it more useful to permit linking proprietary applications with
672 | the library. If this is what you want to do, use the GNU Lesser General
673 | Public License instead of this License. But first, please read
674 | .
--------------------------------------------------------------------------------