wamupd 1.1.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,210 @@
1
+ #!/usr/bin/ruby
2
+ # Copyright (C) 2009-2010 James Brown <roguelazer@roguelazer.com>.
3
+ #
4
+ # This file is part of wamupd.
5
+ #
6
+ # wamupd is free software: you can redistribute it and/or modify
7
+ # it under the terms of the GNU General Public License as published by
8
+ # the Free Software Foundation, either version 3 of the License, or
9
+ # (at your option) any later version.
10
+ #
11
+ # wamupd is distributed in the hope that it will be useful,
12
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
+ # GNU General Public License for more details.
15
+ #
16
+ # You should have received a copy of the GNU General Public License
17
+ # along with wamupd. If not, see <http://www.gnu.org/licenses/>.
18
+
19
+ require "wamupd/avahi_service"
20
+ require "wamupd/signals"
21
+
22
+ require "dbus"
23
+ require "set"
24
+
25
+ # Wamupd is a module that is used to namespace all of the wamupd code.
26
+ module Wamupd
27
+ # Model for Avahi's registered services. Uses D-BUS to talk to the
28
+ # system Avahi daemon and pull down service information. It's kind of a
29
+ # hack because Avahi has a really (really!) horrible D-BUS interface.
30
+ #
31
+ # ==Signals
32
+ #
33
+ # [:added]
34
+ # A service was added from the system Avahi. Includes the service as
35
+ # a parameter.
36
+ #
37
+ # [:removed]
38
+ # A service was removed from the system Avahi. Includes the service
39
+ # as a paramter.
40
+ #
41
+ # [:quit]
42
+ # The model is quitting.
43
+ class AvahiModel
44
+ include Signals
45
+
46
+ # Constructor. Boring.
47
+ def initialize
48
+ @bus = DBus::SystemBus.instance
49
+ @service = @bus.service("org.freedesktop.Avahi")
50
+ @service.introspect
51
+ @server = @service.object("/")
52
+ @server.introspect
53
+ @server = @server["org.freedesktop.Avahi.Server"]
54
+
55
+ @service_types = Set.new
56
+ @count = 0
57
+
58
+ @known_services = Hash.new
59
+ @handlers = Hash.new
60
+ end
61
+
62
+ # Actually starts listening.
63
+ def start_listen
64
+ stb = @server.ServiceTypeBrowserNew(-1,-1,"",0)
65
+ mr = DBus::MatchRule.new
66
+ mr.type = "signal"
67
+ mr.interface = "org.freedesktop.Avahi.ServiceTypeBrowser"
68
+ mr.path = stb.first
69
+ mr.member = "ItemNew"
70
+ @bus.add_match(mr) do |msg, first_param|
71
+ type = msg.params[2]
72
+ if (not @service_types.member?(type))
73
+ @service_types.add(msg.params[2])
74
+ add_type_listener(msg.params[2])
75
+ end
76
+ end
77
+ end
78
+
79
+ # Add a D-BUS listener for an individual service type
80
+ def add_type_listener(service_type)
81
+ @server.ServiceBrowserNew(-1,-1,service_type,"",0) { |sb, first_param|
82
+ sb=sb.params.first
83
+ mr = DBus::MatchRule.new
84
+ mr.type = "signal"
85
+ mr.interface = "org.freedesktop.Avahi.ServiceBrowser"
86
+ mr.path = sb
87
+ @bus.add_match(mr) do |item_msg, first_param|
88
+ if (item_msg.member == "ItemNew")
89
+ # From avahi-common/defs.h:
90
+ #
91
+ # typedef enum {
92
+ # AVAHI_LOOKUP_RESULT_CACHED = 1, This response originates from the cache
93
+ # AVAHI_LOOKUP_RESULT_WIDE_AREA = 2, This response originates from wide area DNS
94
+ # AVAHI_LOOKUP_RESULT_MULTICAST = 4, This response originates from multicast DNS
95
+ # AVAHI_LOOKUP_RESULT_LOCAL = 8, This record/service resides on and was announced by the local host. Only available in service and record browsers and only on AVAHI_BROWSER_NEW.
96
+ # AVAHI_LOOKUP_RESULT_OUR_OWN = 16, This service belongs to the same local client as the browser object. Only available in avahi-client, and only for service browsers and only on AVAHI_BROWSER_NEW.
97
+ # AVAHI_LOOKUP_RESULT_STATIC = 32 The returned data has been defined statically by some configuration option *
98
+ # } AvahiLookupResultFlags;
99
+ #
100
+ # AND the flags result (param 5) to limit ourselves to
101
+ # local services
102
+ if ((item_msg.params[5] & 8) != 0)
103
+ # Then use an async serviceresolver to look up the
104
+ # service. Has to be async, because otherwise
105
+ # there's a recursive call that causes us to run out
106
+ # of stack space. Woo broken libraries.
107
+ @server.ServiceResolverNew(-1,-1,item_msg.params[2],
108
+ item_msg.params[3],
109
+ item_msg.params[4],
110
+ -1,0) do |srb,fp|
111
+ srb = srb.params.first
112
+ mrs = DBus::MatchRule.new
113
+ mrs.type = "signal"
114
+ mrs.interface = "org.freedesktop.Avahi.ServiceResolver"
115
+ mrs.path = srb
116
+ @bus.add_match(mrs) do |msg,fp|
117
+ if (msg.member == "Found")
118
+ name = msg.params[2]
119
+ type = msg.params[3]
120
+ host = msg.params[5]
121
+ address = msg.params[7]
122
+ port = msg.params[8]
123
+ txt = Wamupd::AvahiModel.pack_txt_param(msg.params[9])
124
+ add_service_record(name, type, host, port, txt)
125
+ end
126
+ end
127
+ end
128
+ end
129
+ elsif (item_msg.member == "ItemRemove")
130
+ if ((item_msg.params[5] & 8) != 0)
131
+ name = msg.params[2]
132
+ type = msg.params[3]
133
+ host = msg.params[5]
134
+ address = msg.params[7]
135
+ port = msg.params[8]
136
+ txt = Wamupd::AvahiModel.pack_txt_param(msg.params[9])
137
+ remove_service_record(name, type, host, port, txt)
138
+ end
139
+ end
140
+ end
141
+ }
142
+ end
143
+
144
+ # Pack an array of TXT parameters into a single TXT record
145
+ # appropriately
146
+ #
147
+ # Note: The actual TXT record should consist of name-value pairs,
148
+ # with each pair delimited by its length.
149
+ #
150
+ # As of 2010-04-21, Dnsruby internally converts double-quoted,
151
+ # space-separted strings into the appropriate format. Most
152
+ # annoyingly, if you provide it input already in the right format,
153
+ # it sticks a completely unnecessary byte on the front and screws it
154
+ # up.
155
+ #
156
+ # Aren't you happy you know that now?
157
+ def self.pack_txt_param(strs)
158
+ val = ""
159
+ strs.each { |c|
160
+ # Dnsruby uses multi-byte lengths if any of your records are
161
+ # over 255 characters, though. This makes mDNSResponder get
162
+ # amusingly confused.
163
+ if (c.length > 255)
164
+ next
165
+ end
166
+ val += "\"#{c.pack("c*")}\" "
167
+ }
168
+ return val.chop
169
+ end
170
+
171
+ # Construct an AvahiService from the given parameters
172
+ def add_service_record(name, type, host, port, txt)
173
+ # Replace the .local that Avahi sticks at the end of the host (why
174
+ # doesn't it just use the domain field? who knows?)
175
+ host.sub!(/\.local$/, "")
176
+ a = AvahiService.new(name, {:type=>type, :hostname=>host, :port=>port, :txt=>txt})
177
+ @known_services[a.identifier] = a
178
+ signal(:added, a)
179
+ end
180
+
181
+ # Remove an AvahiService using the given parameters.
182
+ def remove_service_record(name, type, host, port, txt)
183
+ a = AvahiService.new(name, {:type=>type, :hostname=>host, :port=>port, :txt=>txt})
184
+ if (@known_services.has_key?(a.identifier))
185
+ @known_services.delete(a.identifier)
186
+ end
187
+ signal(:removed, a)
188
+ end
189
+
190
+ # Exit the listener
191
+ def exit
192
+ signal(:quit)
193
+ end
194
+
195
+ # Run the listener. This function doesn't return until
196
+ # exit is called.
197
+ def run
198
+ start_listen
199
+
200
+ @main_loop = DBus::Main.new
201
+ self.on(:quit) {
202
+ @main_loop.quit
203
+ }
204
+ @main_loop << @bus
205
+ @main_loop.run
206
+ end
207
+
208
+ private :add_type_listener
209
+ end
210
+ end
@@ -0,0 +1,112 @@
1
+ # Copyright (C) 2009-2010 James Brown <roguelazer@roguelazer.com>.
2
+ #
3
+ # This file is part of wamupd.
4
+ #
5
+ # wamupd is free software: you can redistribute it and/or modify
6
+ # it under the terms of the GNU General Public License as published by
7
+ # the Free Software Foundation, either version 3 of the License, or
8
+ # (at your option) any later version.
9
+ #
10
+ # wamupd is distributed in the hope that it will be useful,
11
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
+ # GNU General Public License for more details.
14
+ #
15
+ # You should have received a copy of the GNU General Public License
16
+ # along with wamupd. If not, see <http://www.gnu.org/licenses/>.
17
+
18
+ # Wamupd is a module that is used to namespace all of the wamupd code.
19
+ module Wamupd
20
+ # A single <service> entry from a service record. A given service file
21
+ # (representated by an AvahiServiceFile) may contain many AvahiServices.
22
+ class AvahiService
23
+ attr_reader :type
24
+ attr_reader :subtype
25
+ attr_reader :hostname
26
+ attr_reader :port
27
+ attr_reader :domainname
28
+ attr_reader :name
29
+
30
+ # Get the subtype as Apple displays it
31
+ def subtype_display
32
+ "#{@type},#{@subtype}"
33
+ end
34
+
35
+ # The type and name in this zone. Name of the SRV and TXT records
36
+ def type_in_zone_with_name
37
+ sa = MainSettings.instance
38
+ return sa.hostname + "." + @type + "."+ sa.zone
39
+ end
40
+
41
+ # The full type in this zone. Goes in the PTR
42
+ def type_in_zone
43
+ sa = MainSettings.instance
44
+ return @type + "." + sa.zone
45
+ end
46
+
47
+ # A key that can be used to identify this service.
48
+ # Is the subtype-type followed by a - followed by the port
49
+ def identifier
50
+ retval = ""
51
+ if (@subtype)
52
+ retval += @subtype
53
+ retval += "."
54
+ end
55
+ if (@type)
56
+ retval += @type
57
+ retval += "-"
58
+ end
59
+ retval += @port.to_s
60
+ return retval
61
+ end
62
+
63
+ # The target of this service
64
+ def target
65
+ t = ""
66
+ sa = MainSettings.instance
67
+ if (@hostname.nil?)
68
+ t += sa.hostname
69
+ else
70
+ t += @hostname
71
+ end
72
+ t += "."
73
+ if (@domainname.nil?)
74
+ t += sa.zone
75
+ else
76
+ t += @domainname
77
+ end
78
+ end
79
+
80
+ # TXT record
81
+ def txt
82
+ return (@txt.nil? || @txt == "") ? false : @txt
83
+ end
84
+
85
+ # Initialize
86
+ #
87
+ # Argument:
88
+ # Either an XML node or a hash with some useful subset of the
89
+ # parameters :type, :subtype, :hostname, :port, :txt, and
90
+ # :domainname
91
+ def initialize(name, param)
92
+ @name = name
93
+ mapping = {:type=>:@type,
94
+ :subtype=>:@subtype,
95
+ :hostname=>:@hostname,
96
+ :port=>:@port,
97
+ :txt=>:@txt,
98
+ :domainname=>:@domainname
99
+ }
100
+ mapping.each {|k,v|
101
+ if (param.has_key?(k))
102
+ self.instance_variable_set(v, param[k])
103
+ end
104
+ }
105
+ end
106
+
107
+ # String coercer
108
+ def to_s
109
+ "<AvahiService name='#{@name}' type=#{@type} txt=#{self.txt}>"
110
+ end
111
+ end
112
+ end
@@ -0,0 +1,160 @@
1
+ # Copyright (C) 2009-2010 James Brown <roguelazer@roguelazer.com>.
2
+ #
3
+ # This file is part of wamupd.
4
+ #
5
+ # wamupd is free software: you can redistribute it and/or modify
6
+ # it under the terms of the GNU General Public License as published by
7
+ # the Free Software Foundation, either version 3 of the License, or
8
+ # (at your option) any later version.
9
+ #
10
+ # wamupd is distributed in the hope that it will be useful,
11
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
+ # GNU General Public License for more details.
14
+ #
15
+ # You should have received a copy of the GNU General Public License
16
+ # along with wamupd. If not, see <http://www.gnu.org/licenses/>.
17
+ # Copyright (C) 2009-2010 James Brown <roguelazer@roguelazer.com>.
18
+ #
19
+ # This file is part of wamupd.
20
+ #
21
+ # wamupd is free software: you can redistribute it and/or modify
22
+ # it under the terms of the GNU General Public License as published by
23
+ # the Free Software Foundation, either version 3 of the License, or
24
+ # (at your option) any later version.
25
+ #
26
+ # wamupd is distributed in the hope that it will be useful,
27
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
28
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
29
+ # GNU General Public License for more details.
30
+ #
31
+ # You should have received a copy of the GNU General Public License
32
+ # along with wamupd. If not, see <http://www.gnu.org/licenses/>.
33
+
34
+ require "xml"
35
+
36
+ require "wamupd/avahi_service"
37
+
38
+ # Wamupd is a module that is used to namespace all of the wamupd code.
39
+ module Wamupd
40
+ # AvahiServiceFile is a class to abstract Avahi's .service files. It is
41
+ # capable of loading, parsing, and validating these files into a group
42
+ # of AvahiService objects.
43
+ class AvahiServiceFile
44
+ include Enumerable
45
+
46
+ SERVICE_DTD_PATH = "/usr/local/share/avahi/avahi-service.dtd";
47
+
48
+ # Get the name of this service
49
+ def name
50
+ if (@replace_wildcards)
51
+ return AvahiServiceFile.replace_wildcards(@name)
52
+ end
53
+ return @name
54
+ end
55
+
56
+ # Access each service in turn
57
+ def each(&block)
58
+ @services.each { |c|
59
+ yield c
60
+ }
61
+ end
62
+
63
+ # The number of services in this definition
64
+ def size
65
+ return @services.count
66
+ end
67
+
68
+ # The first service defined
69
+ def first
70
+ if (self.size > 0)
71
+ return @services[0]
72
+ else
73
+ return nil
74
+ end
75
+ end
76
+
77
+ # Replace the wildcards in a string using the Avahi
78
+ # rules
79
+ def self.replace_wildcards(string)
80
+ return string.sub("%h", MainSettings.instance.hostname)
81
+ end
82
+
83
+ # Has this service file entry been validated against the DTD?
84
+ def valid?
85
+ return @valid
86
+ end
87
+
88
+ # Initialize the service from a service file
89
+ def initialize(filename)
90
+ @valid = false
91
+ @services = []
92
+ @replace_wildcards = false
93
+ load_from_file(filename)
94
+ end
95
+
96
+ # Load this AvahiService entry from a .service defintion file
97
+ #
98
+ # Arguments:
99
+ # filename:: The file to load
100
+ def load_from_file(filename)
101
+ d = XML::Document.file(filename)
102
+ if (not File.exists?(SERVICE_DTD_PATH))
103
+ $stderr.puts "Could not find service DTD at #{SERVICE_DTD_PATH}"
104
+ exit(1)
105
+ end
106
+ dtd = File.open(SERVICE_DTD_PATH, "r") { |f|
107
+ lines = f.readlines().join("")
108
+ }
109
+ validator = XML::Dtd.new(dtd)
110
+ @valid = d.validate(validator)
111
+ if (not @valid)
112
+ $stderr.puts "Service file #{filename} failed validation"
113
+ exit(1)
114
+ end
115
+ sg = d.root
116
+ sg.children.each { |c|
117
+ case c.name
118
+ when "name"
119
+ @name = c.content
120
+ if ((not c["replace-wildcards"].nil?) and (c["replace-wildcards"] == "yes"))
121
+ @replace_wildcards = true
122
+ end
123
+ when "service"
124
+ node = c
125
+ params = {}
126
+ node.children.each { |c|
127
+ case c.name
128
+ when "type"
129
+ params[:type] = c.content
130
+ when "subtype"
131
+ params[:subtype] = c.content
132
+ when "host-name"
133
+ params[:hostname] = c.content
134
+ when "port"
135
+ params[:port] = c.content.to_i
136
+ when "txt-record"
137
+ params[:txt] = c.content
138
+ when "domain-name"
139
+ params[:domainname] = c.content
140
+ end
141
+ }
142
+ @services.push(AvahiService.new(self.name, params))
143
+ end
144
+ }
145
+ end
146
+
147
+ # Load all of the service definitions in a directory
148
+ #
149
+ # Returns:
150
+ # an array of AvahiService objects
151
+ def self.load_from_directory(dir)
152
+ retval = []
153
+ Dir.glob(File.join(dir, "*.service")).each { |f|
154
+ retval.push(AvahiServiceFile.new(f))
155
+ }
156
+ return retval
157
+ end
158
+ end
159
+ end
160
+