tp-blather 0.8.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (150) hide show
  1. data/.autotest +13 -0
  2. data/.gemtest +0 -0
  3. data/.gitignore +19 -0
  4. data/.rspec +3 -0
  5. data/.travis.yml +8 -0
  6. data/CHANGELOG.md +249 -0
  7. data/Gemfile +4 -0
  8. data/Guardfile +5 -0
  9. data/LICENSE +22 -0
  10. data/README.md +413 -0
  11. data/Rakefile +20 -0
  12. data/TODO.md +2 -0
  13. data/blather.gemspec +51 -0
  14. data/examples/certs/README +20 -0
  15. data/examples/certs/ca-bundle.crt +3987 -0
  16. data/examples/echo.rb +19 -0
  17. data/examples/execute.rb +17 -0
  18. data/examples/ping_pong.rb +38 -0
  19. data/examples/print_hierarchy.rb +77 -0
  20. data/examples/rosterprint.rb +15 -0
  21. data/examples/stream_only.rb +28 -0
  22. data/examples/trusted_echo.rb +21 -0
  23. data/examples/xmpp4r/echo.rb +36 -0
  24. data/lib/blather.rb +112 -0
  25. data/lib/blather/cert_store.rb +53 -0
  26. data/lib/blather/client.rb +95 -0
  27. data/lib/blather/client/client.rb +345 -0
  28. data/lib/blather/client/dsl.rb +320 -0
  29. data/lib/blather/client/dsl/pubsub.rb +174 -0
  30. data/lib/blather/core_ext/eventmachine.rb +125 -0
  31. data/lib/blather/core_ext/ipaddr.rb +20 -0
  32. data/lib/blather/errors.rb +69 -0
  33. data/lib/blather/errors/sasl_error.rb +44 -0
  34. data/lib/blather/errors/stanza_error.rb +110 -0
  35. data/lib/blather/errors/stream_error.rb +84 -0
  36. data/lib/blather/file_transfer.rb +107 -0
  37. data/lib/blather/file_transfer/ibb.rb +68 -0
  38. data/lib/blather/file_transfer/s5b.rb +114 -0
  39. data/lib/blather/jid.rb +141 -0
  40. data/lib/blather/roster.rb +118 -0
  41. data/lib/blather/roster_item.rb +146 -0
  42. data/lib/blather/stanza.rb +167 -0
  43. data/lib/blather/stanza/disco.rb +32 -0
  44. data/lib/blather/stanza/disco/capabilities.rb +161 -0
  45. data/lib/blather/stanza/disco/disco_info.rb +205 -0
  46. data/lib/blather/stanza/disco/disco_items.rb +134 -0
  47. data/lib/blather/stanza/iq.rb +144 -0
  48. data/lib/blather/stanza/iq/command.rb +339 -0
  49. data/lib/blather/stanza/iq/ibb.rb +86 -0
  50. data/lib/blather/stanza/iq/ping.rb +50 -0
  51. data/lib/blather/stanza/iq/query.rb +53 -0
  52. data/lib/blather/stanza/iq/roster.rb +185 -0
  53. data/lib/blather/stanza/iq/s5b.rb +208 -0
  54. data/lib/blather/stanza/iq/si.rb +415 -0
  55. data/lib/blather/stanza/iq/vcard.rb +149 -0
  56. data/lib/blather/stanza/message.rb +428 -0
  57. data/lib/blather/stanza/message/muc_user.rb +119 -0
  58. data/lib/blather/stanza/muc/muc_user_base.rb +54 -0
  59. data/lib/blather/stanza/presence.rb +172 -0
  60. data/lib/blather/stanza/presence/c.rb +100 -0
  61. data/lib/blather/stanza/presence/muc.rb +35 -0
  62. data/lib/blather/stanza/presence/muc_user.rb +147 -0
  63. data/lib/blather/stanza/presence/status.rb +218 -0
  64. data/lib/blather/stanza/presence/subscription.rb +100 -0
  65. data/lib/blather/stanza/pubsub.rb +119 -0
  66. data/lib/blather/stanza/pubsub/affiliations.rb +79 -0
  67. data/lib/blather/stanza/pubsub/create.rb +65 -0
  68. data/lib/blather/stanza/pubsub/errors.rb +18 -0
  69. data/lib/blather/stanza/pubsub/event.rb +139 -0
  70. data/lib/blather/stanza/pubsub/items.rb +103 -0
  71. data/lib/blather/stanza/pubsub/publish.rb +103 -0
  72. data/lib/blather/stanza/pubsub/retract.rb +92 -0
  73. data/lib/blather/stanza/pubsub/subscribe.rb +68 -0
  74. data/lib/blather/stanza/pubsub/subscription.rb +135 -0
  75. data/lib/blather/stanza/pubsub/subscriptions.rb +83 -0
  76. data/lib/blather/stanza/pubsub/unsubscribe.rb +84 -0
  77. data/lib/blather/stanza/pubsub_owner.rb +51 -0
  78. data/lib/blather/stanza/pubsub_owner/delete.rb +52 -0
  79. data/lib/blather/stanza/pubsub_owner/purge.rb +52 -0
  80. data/lib/blather/stanza/x.rb +416 -0
  81. data/lib/blather/stream.rb +266 -0
  82. data/lib/blather/stream/client.rb +32 -0
  83. data/lib/blather/stream/component.rb +39 -0
  84. data/lib/blather/stream/features.rb +70 -0
  85. data/lib/blather/stream/features/register.rb +38 -0
  86. data/lib/blather/stream/features/resource.rb +63 -0
  87. data/lib/blather/stream/features/sasl.rb +190 -0
  88. data/lib/blather/stream/features/session.rb +45 -0
  89. data/lib/blather/stream/features/tls.rb +29 -0
  90. data/lib/blather/stream/parser.rb +102 -0
  91. data/lib/blather/version.rb +3 -0
  92. data/lib/blather/xmpp_node.rb +94 -0
  93. data/spec/blather/client/client_spec.rb +687 -0
  94. data/spec/blather/client/dsl/pubsub_spec.rb +492 -0
  95. data/spec/blather/client/dsl_spec.rb +266 -0
  96. data/spec/blather/errors/sasl_error_spec.rb +33 -0
  97. data/spec/blather/errors/stanza_error_spec.rb +129 -0
  98. data/spec/blather/errors/stream_error_spec.rb +108 -0
  99. data/spec/blather/errors_spec.rb +33 -0
  100. data/spec/blather/file_transfer_spec.rb +135 -0
  101. data/spec/blather/jid_spec.rb +87 -0
  102. data/spec/blather/roster_item_spec.rb +134 -0
  103. data/spec/blather/roster_spec.rb +107 -0
  104. data/spec/blather/stanza/discos/disco_info_spec.rb +247 -0
  105. data/spec/blather/stanza/discos/disco_items_spec.rb +154 -0
  106. data/spec/blather/stanza/iq/command_spec.rb +206 -0
  107. data/spec/blather/stanza/iq/ibb_spec.rb +124 -0
  108. data/spec/blather/stanza/iq/ping_spec.rb +45 -0
  109. data/spec/blather/stanza/iq/query_spec.rb +64 -0
  110. data/spec/blather/stanza/iq/roster_spec.rb +139 -0
  111. data/spec/blather/stanza/iq/s5b_spec.rb +57 -0
  112. data/spec/blather/stanza/iq/si_spec.rb +98 -0
  113. data/spec/blather/stanza/iq/vcard_spec.rb +93 -0
  114. data/spec/blather/stanza/iq_spec.rb +61 -0
  115. data/spec/blather/stanza/message/muc_user_spec.rb +152 -0
  116. data/spec/blather/stanza/message_spec.rb +282 -0
  117. data/spec/blather/stanza/presence/c_spec.rb +56 -0
  118. data/spec/blather/stanza/presence/muc_spec.rb +37 -0
  119. data/spec/blather/stanza/presence/muc_user_spec.rb +83 -0
  120. data/spec/blather/stanza/presence/status_spec.rb +144 -0
  121. data/spec/blather/stanza/presence/subscription_spec.rb +102 -0
  122. data/spec/blather/stanza/presence_spec.rb +125 -0
  123. data/spec/blather/stanza/pubsub/affiliations_spec.rb +57 -0
  124. data/spec/blather/stanza/pubsub/create_spec.rb +56 -0
  125. data/spec/blather/stanza/pubsub/event_spec.rb +98 -0
  126. data/spec/blather/stanza/pubsub/items_spec.rb +79 -0
  127. data/spec/blather/stanza/pubsub/publish_spec.rb +83 -0
  128. data/spec/blather/stanza/pubsub/retract_spec.rb +75 -0
  129. data/spec/blather/stanza/pubsub/subscribe_spec.rb +61 -0
  130. data/spec/blather/stanza/pubsub/subscription_spec.rb +97 -0
  131. data/spec/blather/stanza/pubsub/subscriptions_spec.rb +59 -0
  132. data/spec/blather/stanza/pubsub/unsubscribe_spec.rb +74 -0
  133. data/spec/blather/stanza/pubsub_owner/delete_spec.rb +50 -0
  134. data/spec/blather/stanza/pubsub_owner/purge_spec.rb +50 -0
  135. data/spec/blather/stanza/pubsub_owner_spec.rb +27 -0
  136. data/spec/blather/stanza/pubsub_spec.rb +68 -0
  137. data/spec/blather/stanza/x_spec.rb +231 -0
  138. data/spec/blather/stanza_spec.rb +134 -0
  139. data/spec/blather/stream/client_spec.rb +1090 -0
  140. data/spec/blather/stream/component_spec.rb +108 -0
  141. data/spec/blather/stream/parser_spec.rb +152 -0
  142. data/spec/blather/stream/ssl_spec.rb +32 -0
  143. data/spec/blather/xmpp_node_spec.rb +47 -0
  144. data/spec/blather_spec.rb +34 -0
  145. data/spec/fixtures/pubsub.rb +311 -0
  146. data/spec/spec_helper.rb +17 -0
  147. data/yard/templates/default/class/html/handlers.erb +18 -0
  148. data/yard/templates/default/class/setup.rb +10 -0
  149. data/yard/templates/default/class/text/handlers.erb +1 -0
  150. metadata +459 -0
@@ -0,0 +1,266 @@
1
+ module Blather
2
+
3
+ # # A pure XMPP stream.
4
+ #
5
+ # Blather::Stream can be used to build your own handler system if Blather's
6
+ # doesn't suit your needs. It will take care of the entire connection
7
+ # process then start sending Stanza objects back to the registered client.
8
+ #
9
+ # The client you register with Blather::Stream needs to implement the following
10
+ # methods:
11
+ # * #post_init(stream, jid = nil)
12
+ # Called after the stream has been initiated.
13
+ # @param [Blather::Stream] stream is the connected stream object
14
+ # @param [Blather::JID, nil] jid is the full JID as recognized by the server
15
+ #
16
+ # * #receive_data(stanza)
17
+ # Called every time the stream receives a new stanza
18
+ # @param [Blather::Stanza] stanza a stanza object from the server
19
+ #
20
+ # * #unbind
21
+ # Called when the stream is shutdown. This will be called regardless of which
22
+ # side shut the stream down.
23
+ #
24
+ # @example Create a new stream and handle it with our own class
25
+ # class MyClient
26
+ # attr :jid
27
+ #
28
+ # def post_init(stream, jid = nil)
29
+ # @stream = stream
30
+ # self.jid = jid
31
+ # p "Stream Started"
32
+ # end
33
+ #
34
+ # # Pretty print the stream
35
+ # def receive_data(stanza)
36
+ # pp stanza
37
+ # end
38
+ #
39
+ # def unbind
40
+ # p "Stream Ended"
41
+ # end
42
+ #
43
+ # def write(what)
44
+ # @stream.write what
45
+ # end
46
+ # end
47
+ #
48
+ # client = Blather::Stream.start MyClient.new, "jid@domain/res", "pass"
49
+ # client.write "[pure xml over the wire]"
50
+ class Stream < EventMachine::Connection
51
+ # Connection not found
52
+ class NoConnection < RuntimeError; end
53
+ class ConnectionFailed < RuntimeError; end
54
+ class ConnectionTimeout < RuntimeError; end
55
+
56
+ # @private
57
+ STREAM_NS = 'http://etherx.jabber.org/streams'
58
+ attr_accessor :password
59
+ attr_reader :jid
60
+ @@store = nil
61
+
62
+ # Start the stream between client and server
63
+ #
64
+ # @param [Object] client an object that will respond to #post_init,
65
+ # #unbind #receive_data
66
+ # @param [Blather::JID, #to_s] jid the jid to authenticate with
67
+ # @param [String] pass the password to authenticate with
68
+ # @param [String, nil] host the hostname or IP to connect to. Default is
69
+ # to use the domain on the JID
70
+ # @param [Fixnum, nil] port the port to connect on. Default is the XMPP
71
+ # default of 5222
72
+ # @param [String, nil] certs the trusted cert store in pem format to verify
73
+ # communication with the server is trusted.
74
+ # @param [Fixnum, nil] connect_timeout the number of seconds for which to wait for a successful connection
75
+ def self.start(client, jid, pass, host = nil, port = nil, certs_directory = nil, connect_timeout = nil)
76
+ jid = JID.new jid
77
+ port ||= 5222
78
+ if certs_directory
79
+ @@store = CertStore.new(certs_directory)
80
+ end
81
+ if host
82
+ connect host, port, self, client, jid, pass, connect_timeout
83
+ else
84
+ require 'resolv'
85
+ srv = []
86
+ Resolv::DNS.open do |dns|
87
+ srv = dns.getresources(
88
+ "_xmpp-client._tcp.#{jid.domain}",
89
+ Resolv::DNS::Resource::IN::SRV
90
+ )
91
+ end
92
+
93
+ if srv.empty?
94
+ connect jid.domain, port, self, client, jid, pass, connect_timeout
95
+ else
96
+ srv.sort! do |a,b|
97
+ (a.priority != b.priority) ? (a.priority <=> b.priority) :
98
+ (b.weight <=> a.weight)
99
+ end
100
+
101
+ srv.detect do |r|
102
+ not connect(r.target.to_s, r.port, self, client, jid, pass, connect_timeout) === false
103
+ end
104
+ end
105
+ end
106
+ end
107
+
108
+ # Attempt a connection
109
+ # Stream will raise +NoConnection+ if it receives #unbind before #post_init
110
+ # this catches that and returns false prompting for another attempt
111
+ # @private
112
+ def self.connect(host, port, conn, client, jid, pass, connect_timeout = nil)
113
+ EM.connect host, port, conn, client, jid, pass, connect_timeout
114
+ rescue NoConnection
115
+ false
116
+ end
117
+
118
+ [:started, :stopped, :ready, :negotiating].each do |state|
119
+ define_method("#{state}?") { @state == state }
120
+ end
121
+
122
+ # Send data over the wire
123
+ #
124
+ # @todo Queue if not ready
125
+ #
126
+ # @param [#to_xml, #to_s] stanza the stanza to send over the wire
127
+ def send(stanza)
128
+ data = stanza.respond_to?(:to_xml) ? stanza.to_xml(:save_with => Nokogiri::XML::Node::SaveOptions::AS_XML) : stanza.to_s
129
+ Blather.log "SENDING: (#{caller[1]}) #{stanza}"
130
+ EM.next_tick { send_data data }
131
+ end
132
+
133
+ # Called by EM.connect to initialize stream variables
134
+ # @private
135
+ def initialize(client, jid, pass, connect_timeout = nil)
136
+ super()
137
+
138
+ @error = nil
139
+ @receiver = @client = client
140
+
141
+ self.jid = jid
142
+ @to = self.jid.domain
143
+ @password = pass
144
+ @connect_timeout = connect_timeout || 180
145
+ end
146
+
147
+ # Called when EM completes the connection to the server
148
+ # this kicks off the starttls/authorize/bind process
149
+ # @private
150
+ def connection_completed
151
+ if @connect_timeout
152
+ @connect_timer = EM::Timer.new @connect_timeout do
153
+ raise ConnectionTimeout, "Stream timed out after #{@connect_timeout} seconds." unless started?
154
+ end
155
+ end
156
+ @connected = true
157
+ # @keepalive = EM::PeriodicTimer.new(60) { send_data ' ' }
158
+ start
159
+ end
160
+
161
+ # Called by EM with data from the wire
162
+ # @private
163
+ def receive_data(data)
164
+ @parser << data
165
+
166
+ rescue ParseError => e
167
+ @error = e
168
+ send "<stream:error><xml-not-well-formed xmlns='#{StreamError::STREAM_ERR_NS}'/></stream:error>"
169
+ stop
170
+ end
171
+
172
+ # Called by EM to verify the peer certificate. If a certificate store directory
173
+ # has not been configured don't worry about peer verification. At least it is encrypted
174
+ # We Log the certificate so that you can add it to the trusted store easily if desired
175
+ # @private
176
+ def ssl_verify_peer(pem)
177
+ # EM is supposed to close the connection when this returns false,
178
+ # but it only does that for inbound connections, not when we
179
+ # make a connection to another server.
180
+ Blather.log "Checking SSL cert: #{pem}"
181
+ return true if !@@store
182
+ @@store.trusted?(pem).tap do |trusted|
183
+ close_connection unless trusted
184
+ end
185
+ end
186
+
187
+ # Called by EM after the connection has started
188
+ # @private
189
+ def post_init
190
+ @inited = true
191
+ end
192
+
193
+ # Called by EM when the connection is closed
194
+ # @private
195
+ def unbind
196
+ raise NoConnection unless @inited
197
+ raise ConnectionFailed unless @connected
198
+
199
+ @connect_timer.cancel if @connect_timer
200
+ # @keepalive.cancel
201
+ @state = :stopped
202
+ @client.receive_data @error if @error
203
+ @client.unbind
204
+ end
205
+
206
+ # Called by the parser with parsed nodes
207
+ # @private
208
+ def receive(node)
209
+ Blather.log "RECEIVING (#{node.element_name}) #{node}"
210
+ @node = node
211
+
212
+ if @node.namespace && @node.namespace.prefix == 'stream'
213
+ case @node.element_name
214
+ when 'stream'
215
+ @state = :ready if @state == :stopped
216
+ return
217
+ when 'error'
218
+ handle_stream_error
219
+ return
220
+ when 'end'
221
+ stop
222
+ return
223
+ when 'features'
224
+ @state = :negotiating
225
+ @receiver = Features.new(
226
+ self,
227
+ proc { ready! },
228
+ proc { |err| @error = err; stop }
229
+ )
230
+ end
231
+ end
232
+ @receiver.receive_data @node.to_stanza
233
+ end
234
+
235
+ # Ensure the JID gets attached to the client
236
+ # @private
237
+ def jid=(new_jid)
238
+ Blather.log "USING JID: #{new_jid}"
239
+ @jid = JID.new new_jid
240
+ end
241
+
242
+ protected
243
+ # Stop the stream
244
+ # @private
245
+ def stop
246
+ unless @state == :stopped
247
+ @state = :stopped
248
+ send '</stream:stream>'
249
+ end
250
+ end
251
+
252
+ # @private
253
+ def handle_stream_error
254
+ @error = StreamError.import(@node)
255
+ stop
256
+ end
257
+
258
+ # @private
259
+ def ready!
260
+ @state = :started
261
+ @receiver = @client
262
+ @client.post_init self, @jid
263
+ end
264
+ end # Stream
265
+
266
+ end # Blather
@@ -0,0 +1,32 @@
1
+ module Blather
2
+ class Stream
3
+
4
+ # @private
5
+ class Client < Stream
6
+ LANG = 'en'
7
+ VERSION = '1.0'
8
+ NAMESPACE = 'jabber:client'
9
+
10
+ def start
11
+ @parser = Parser.new self
12
+ start_stream = <<-STREAM
13
+ <stream:stream
14
+ to='#{@to}'
15
+ xmlns='#{NAMESPACE}'
16
+ xmlns:stream='#{STREAM_NS}'
17
+ version='#{VERSION}'
18
+ xml:lang='#{LANG}'
19
+ >
20
+ STREAM
21
+ send start_stream.gsub(/\s+/, ' ')
22
+ end
23
+
24
+ def send(stanza)
25
+ stanza.from = self.jid if stanza.is_a?(Stanza) && !stanza.from.nil?
26
+ super stanza
27
+ end
28
+
29
+ end #Client
30
+
31
+ end #Stream
32
+ end #Blather
@@ -0,0 +1,39 @@
1
+ module Blather
2
+ class Stream
3
+
4
+ # @private
5
+ class Component < Stream
6
+ NAMESPACE = 'jabber:component:accept'
7
+
8
+ def receive(node) # :nodoc:
9
+ #if node.element_name == 'handshake'
10
+ # ready!
11
+ #else
12
+ super
13
+ #end
14
+ #
15
+ #if node.document.find_first('/stream:stream[not(stream:error)]', :xmlns => NAMESPACE, :stream => STREAM_NS)
16
+ # send("<handshake>#{Digest::SHA1.hexdigest(@node['id']+@password)}</handshake>")
17
+ #end
18
+ end
19
+
20
+ def send(stanza)
21
+ stanza.from ||= self.jid if stanza.respond_to?(:from) && stanza.respond_to?(:from=)
22
+ super stanza
23
+ end
24
+
25
+ def start
26
+ @parser = Parser.new self
27
+ start_stream = <<-STREAM
28
+ <stream:stream
29
+ to='#{@jid}'
30
+ xmlns='#{NAMESPACE}'
31
+ xmlns:stream='#{STREAM_NS}'
32
+ >
33
+ STREAM
34
+ send start_stream.gsub(/\s+/, ' ')
35
+ end
36
+ end #Client
37
+
38
+ end #Stream
39
+ end #Blather
@@ -0,0 +1,70 @@
1
+ module Blather
2
+ class Stream
3
+
4
+ # @private
5
+ class Features
6
+ @@features = {}
7
+ def self.register(ns)
8
+ @@features[ns] = self
9
+ end
10
+
11
+ def self.from_namespace(ns)
12
+ @@features[ns]
13
+ end
14
+
15
+ def initialize(stream, succeed, fail)
16
+ @stream = stream
17
+ @succeed = succeed
18
+ @fail = fail
19
+ end
20
+
21
+ def receive_data(stanza)
22
+ if @feature
23
+ @feature.receive_data stanza
24
+ else
25
+ @features ||= stanza
26
+ next!
27
+ end
28
+ end
29
+
30
+ def next!
31
+ @idx = @idx ? @idx+1 : 0
32
+ if stanza = @features.children[@idx]
33
+ if stanza.namespaces['xmlns'] && (klass = self.class.from_namespace(stanza.namespaces['xmlns']))
34
+ @feature = klass.new(
35
+ @stream,
36
+ proc {
37
+ if (klass == Blather::Stream::Register && stanza = feature?(:mechanisms))
38
+ @idx = @features.children.index(stanza)
39
+ @feature = Blather::Stream::SASL.new @stream, proc { next! }, @fail
40
+ @feature.receive_data stanza
41
+ else
42
+ next!
43
+ end
44
+ },
45
+ (klass == Blather::Stream::SASL && feature?(:register)) ? proc { next! } : @fail
46
+ )
47
+ @feature.receive_data stanza
48
+ else
49
+ next!
50
+ end
51
+ else
52
+ succeed!
53
+ end
54
+ end
55
+
56
+ def succeed!
57
+ @succeed.call
58
+ end
59
+
60
+ def fail!(msg)
61
+ @fail.call msg
62
+ end
63
+
64
+ def feature?(feature)
65
+ @features && @features.children.find { |v| v.element_name == feature.to_s }
66
+ end
67
+ end
68
+
69
+ end #Stream
70
+ end #Blather
@@ -0,0 +1,38 @@
1
+ module Blather
2
+ class Stream
3
+ class Register < Features
4
+ REGISTER_NS = "http://jabber.org/features/iq-register".freeze
5
+
6
+ register REGISTER_NS
7
+
8
+ def initialize(stream, succeed, fail)
9
+ super
10
+ @jid = @stream.jid
11
+ @pass = @stream.password
12
+ end
13
+
14
+ def receive_data(stanza)
15
+ error_node = stanza.xpath("//error").first
16
+
17
+ if error_node
18
+ fail!(BlatherError.new(stanza))
19
+ elsif stanza['type'] == 'result' && (stanza.content.empty? || stanza.children.find { |v| v.element_name == "query" })
20
+ succeed!
21
+ else
22
+ @stream.send register_query
23
+ end
24
+ end
25
+
26
+ def register_query
27
+ node = Blather::Stanza::Iq::Query.new(:set)
28
+ query_node = node.xpath('//query').first
29
+ query_node['xmlns'] = 'jabber:iq:register'
30
+ Nokogiri::XML::Builder.with(query_node) do |xml|
31
+ xml.username @jid.node
32
+ xml.password @pass
33
+ end
34
+ node
35
+ end
36
+ end
37
+ end
38
+ end