devdnsd 1.7.0 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,352 +1,420 @@
1
1
  # encoding: utf-8
2
2
  #
3
- # This file is part of the devdnsd gem. Copyright (C) 2012 and above Shogun <shogun_panda@me.com>.
3
+ # This file is part of the devdnsd gem. Copyright (C) 2013 and above Shogun <shogun_panda@me.com>.
4
4
  # Licensed under the MIT license, which can be found at http://www.opensource.org/licenses/mit-license.php.
5
5
  #
6
6
 
7
7
  # A small DNS server to enable local .dev domain resolution.
8
8
  module DevDNSd
9
- # The main DevDNSd application.
10
- class Application < RExec::Daemon::Base
11
- # Class for ANY DNS request.
12
- ANY_REQUEST = Resolv::DNS::Resource::IN::ANY
9
+ # Methods for the {Application Application} class.
10
+ module ApplicationMethods
11
+ # System management methods.
12
+ module System
13
+ extend ActiveSupport::Concern
14
+
15
+ # Class methods.
16
+ module ClassMethods
17
+ # Returns the name of the daemon.
18
+ #
19
+ # @return [String] The name of the daemon.
20
+ def daemon_name
21
+ File.basename(self.instance.config.pid_file, ".pid")
22
+ end
13
23
 
14
- # List of classes handled in case of DNS request with resource class ANY.
15
- ANY_CLASSES = [Resolv::DNS::Resource::IN::A, Resolv::DNS::Resource::IN::AAAA, Resolv::DNS::Resource::IN::ANY, Resolv::DNS::Resource::IN::CNAME, Resolv::DNS::Resource::IN::HINFO, Resolv::DNS::Resource::IN::MINFO, Resolv::DNS::Resource::IN::MX, Resolv::DNS::Resource::IN::NS, Resolv::DNS::Resource::IN::PTR, Resolv::DNS::Resource::IN::SOA, Resolv::DNS::Resource::IN::TXT]
24
+ # Returns the standard location of the PID file.
25
+ #
26
+ # @return [String] The standard location of the PID file.
27
+ def pid_directory
28
+ File.dirname(self.instance.config.pid_file)
29
+ end
16
30
 
17
- # The {Configuration Configuration} of this application.
18
- attr_reader :config
31
+ # Returns the complete path of the PID file.
32
+ #
33
+ # @return [String] The complete path of the PID file.
34
+ def pid_fn
35
+ self.instance.config.pid_file
36
+ end
37
+ end
19
38
 
20
- # The Mamertes command.
21
- attr_reader :command
39
+ # Gets the path for the resolver file.
40
+ #
41
+ # @param tld [String] The TLD to manage.
42
+ # @return [String] The path for the resolver file.
43
+ def resolver_path(tld = nil)
44
+ tld ||= @config.tld
45
+ "/etc/resolver/#{tld}"
46
+ end
22
47
 
23
- # The logger for this application.
24
- attr_accessor :logger
48
+ # Gets the path for the launch agent file.
49
+ #
50
+ # @param name [String] The base name for the agent.
51
+ # @return [String] The path for the launch agent file.
52
+ def launch_agent_path(name = "it.cowtech.devdnsd")
53
+ ENV["HOME"] + "/Library/LaunchAgents/#{name}.plist"
54
+ end
25
55
 
26
- # Creates a new application.
27
- #
28
- # @param command [Mamertes::Command] The current Mamertes command.
29
- def initialize(command)
30
- @command = command
31
- application = @command.application
56
+ # Executes a shell command.
57
+ #
58
+ # @param command [String] The command to execute.
59
+ # @return [Boolean] `true` if command succeeded, `false` otherwise.
60
+ def execute_command(command)
61
+ system(command)
62
+ end
32
63
 
33
- # Setup logger
34
- Bovem::Logger.start_time = Time.now
35
- @logger = Bovem::Logger.create(Bovem::Logger.get_real_file(application.options["log-file"].value) || Bovem::Logger.default_file, Logger::INFO)
64
+ # Updates DNS cache.
65
+ #
66
+ # @return [Boolean] `true` if command succeeded, `false` otherwise.
67
+ def dns_update
68
+ @logger.info(self.i18n.dns_update)
69
+ self.execute_command("dscacheutil -flushcache")
70
+ end
36
71
 
37
- # Open configuration
38
- begin
39
- overrides = {
40
- :foreground => command.name == "start" ? command.options["foreground"].value : false,
41
- :tld => application.options["tld"].value,
42
- :port => application.options["port"].value,
43
- :pid_file => application.options["pid-file"].value,
44
- :log_file => application.options["log-file"].value,
45
- :log_level => application.options["log-level"].value
46
- }.reject {|k,v| v.nil? }
47
-
48
- @config = DevDNSd::Configuration.new(application.options["configuration"].value, overrides, @logger)
49
-
50
- @logger = nil
51
- @logger = self.get_logger
52
- rescue Bovem::Errors::InvalidConfiguration, DevDNSd::Errors::InvalidRule => e
53
- @logger ? @logger.fatal(e.message) : Bovem::Logger.create("STDERR").fatal("Cannot log to #{config.log_file}. Exiting...")
54
- raise ::SystemExit
72
+ # Checks if we are running on MacOS X.
73
+ #
74
+ # System services are only available on that platform.
75
+ #
76
+ # @return [Boolean] `true` if the current platform is MacOS X, `false` otherwise.
77
+ def is_osx?
78
+ ::RbConfig::CONFIG['host_os'] =~ /^darwin/
55
79
  end
56
80
 
57
- self
58
- end
81
+ # Starts the server in background.
82
+ #
83
+ # @return [Boolean] `true` if action succedeed, `false` otherwise.
84
+ def action_start
85
+ self.get_logger.info(self.i18n.starting)
59
86
 
60
- # Returns the name of the daemon.
61
- #
62
- # @return [String] The name of the daemon.
63
- def self.daemon_name
64
- File.basename(self.instance.config.pid_file, ".pid")
65
- end
87
+ if !Process.respond_to?(:fork) then
88
+ self.logger.warn(self.i18n.no_fork)
89
+ @config.foreground = true
90
+ end
66
91
 
67
- # Returns the standard location of the PID file.
68
- #
69
- # @return [String] The standard location of the PID file.
70
- def self.pid_directory
71
- File.dirname(self.instance.config.pid_file)
72
- end
92
+ @config.foreground ? self.perform_server : RExec::Daemon::Controller.start(self.class)
93
+ true
94
+ end
73
95
 
74
- # Returns the complete path of the PID file.
75
- #
76
- # @return [String] The complete path of the PID file.
77
- def self.pid_fn
78
- self.instance.config.pid_file
79
- end
96
+ # Stops the server in background.
97
+ #
98
+ # @return [Boolean] `true` if action succedeed, `false` otherwise.
99
+ def action_stop
100
+ RExec::Daemon::Controller.stop(self.class)
101
+ true
102
+ end
80
103
 
81
- # Check if we are running on MacOS X.
82
- # System services are only available on that platform.
83
- #
84
- # @return [Boolean] `true` if the current platform is MacOS X, `false` otherwise.
85
- def is_osx?
86
- ::Config::CONFIG['host_os'] =~ /^darwin/
87
- end
104
+ # Installs the application into the autolaunch.
105
+ #
106
+ # @return [Boolean] `true` if action succedeed, `false` otherwise.
107
+ def action_install
108
+ manage_installation(self.launch_agent_path, self.resolver_path, :create_resolver, :create_agent, :load_agent)
109
+ end
88
110
 
89
- # Gets the current logger of the application.
90
- #
91
- # @return [Logger] The current logger of the application.
92
- def get_logger
93
- @logger ||= Bovem::Logger.create(@config.foreground ? Bovem::Logger.default_file : @config.log_file, @config.log_level, @log_formatter)
94
- end
111
+ # Uninstalls the application from the autolaunch.
112
+ #
113
+ # @return [Boolean] `true` if action succedeed, `false` otherwise.
114
+ def action_uninstall
115
+ manage_installation(self.launch_agent_path, self.resolver_path, :delete_resolver, :unload_agent, :delete_agent)
116
+ end
95
117
 
96
- # Gets the path for the resolver file.
97
- #
98
- # @param tld [String] The TLD to manage.
99
- # @return [String] The path for the resolver file.
100
- def resolver_path(tld = nil)
101
- tld ||= @config.tld
102
- "/etc/resolver/#{tld}"
103
- end
118
+ private
119
+ # Manages a OSX agent.
120
+ #
121
+ # @param launch_agent [String] The agent path.
122
+ # @param resolver_path [String] The resolver path.
123
+ # @param first_operation [Symbol] The first operation to execute.
124
+ # @param second_operation [Symbol] The second operation to execute.
125
+ # @param third_operation [Symbol] The third operation to execute.
126
+ # @return [Boolean] `true` if operation succedeed, `false` otherwise.
127
+ def manage_installation(launch_agent, resolver_path, first_operation, second_operation, third_operation)
128
+ rv = true
129
+
130
+ rv = check_agent_available
131
+ rv = send(first_operation, launch_agent, resolver_path) if rv
132
+ rv = send(second_operation, launch_agent, resolver_path) if rv
133
+ rv = send(third_operation, launch_agent, resolver_path) if rv
134
+ self.dns_update
135
+ rv
136
+ end
104
137
 
105
- # Gets the path for the launch agent file.
106
- #
107
- # @param name [String] The base name for the agent.
108
- # @return [String] The path for the launch agent file.
109
- def launch_agent_path(name = "it.cowtech.devdnsd")
110
- ENV["HOME"] + "/Library/LaunchAgents/#{name}.plist"
111
- end
138
+ # Deletes a file
139
+ #
140
+ # @param file [String] The file to delete.
141
+ # @param before_message [Symbol] The message to show before deleting.
142
+ # @param error_message [Symbol] The message to show in case of errors.
143
+ # @return [Boolean] `true` if the file have been deleted, `false` otherwise.
144
+ def delete_file(file, before_message, error_message)
145
+ begin
146
+ self.logger.info(self.i18n.send(before_message, file))
147
+ ::File.delete(file)
148
+ true
149
+ rescue => e
150
+ self.logger.warn(self.i18n.send(error_message))
151
+ false
152
+ end
153
+ end
112
154
 
113
- # Executes a shell command.
114
- #
115
- # @param command [String] The command to execute.
116
- # @return [Boolean] `true` if command succeeded, `false` otherwise.
117
- def execute_command(command)
118
- system(command)
119
- end
155
+ # Checks if agent is enabled (that is, we are on OSX).
156
+ #
157
+ # @return [Boolean] `true` if the agent is enabled, `false` otherwise.
158
+ def check_agent_available
159
+ rv = true
160
+ if !self.is_osx? then
161
+ logger.fatal(self.i18n.no_agent)
162
+ rv = false
163
+ end
120
164
 
121
- # Updates DNS cache.
122
- #
123
- # @return [Boolean] `true` if command succeeded, `false` otherwise.
124
- def dns_update
125
- @logger.info("Flushing DNS cache and resolvers ...")
126
- self.execute_command("dscacheutil -flushcache")
127
- end
165
+ rv
166
+ end
128
167
 
129
- # Starts the DNS server.
130
- #
131
- # @return [Object] The result of stop callbacks.
132
- def perform_server
133
- RubyDNS::run_server(:listen => [[:udp, @config.address, @config.port.to_integer]]) do
134
- self.logger = DevDNSd::Application.instance.logger
135
-
136
- match(/.+/, DevDNSd::Application::ANY_CLASSES) do |match_data, transaction|
137
- transaction.append_question!
138
-
139
- DevDNSd::Application.instance.config.rules.each do |rule|
140
- begin
141
- # Get the subset of handled class that is valid for the rule
142
- resource_classes = DevDNSd::Application::ANY_CLASSES & rule.resource_class.ensure_array
143
- resource_classes = resource_classes & [transaction.resource_class] if transaction.resource_class != DevDNSd::Application::ANY_REQUEST
144
-
145
- if resource_classes.present? then
146
- resource_classes.each do |resource_class| # Now for every class
147
- matches = rule.match_host(match_data[0])
148
- DevDNSd::Application.instance.process_rule(rule, resource_class, rule.is_regexp? ? matches : nil, transaction) if matches
149
- end
150
- end
151
- rescue ::Exception => e
152
- raise e
153
- end
168
+ # Creates a OSX resolver.
169
+ #
170
+ # @param launch_agent [String] The agent path.
171
+ # @param resolver_path [String] The resolver path.
172
+ # @return [Boolean] `true` if operation succedeed, `false` otherwise.
173
+ def create_resolver(launch_agent, resolver_path)
174
+ begin
175
+ self.logger.info(self.i18n.resolver_creating(resolver_path))
176
+ write_resolver(resolver_path)
177
+ true
178
+ rescue
179
+ self.logger.error(self.i18n.resolver_creating_error)
180
+ false
154
181
  end
155
182
  end
156
183
 
157
- # Default DNS handler
158
- otherwise do |transaction|
159
- transaction.failure!(:NXDomain)
184
+ # Writes a OSX resolver.
185
+ #
186
+ # @param resolver_path [String] The resolver path.
187
+ def write_resolver(resolver_path)
188
+ ::File.open(resolver_path, "w") {|f|
189
+ f.write("nameserver 127.0.0.1\n")
190
+ f.write("port #{@config.port}")
191
+ f.flush
192
+ }
160
193
  end
161
194
 
162
- # Attach event handlers
163
- self.on(:start) do
164
- DevDNSd::Application.instance.on_start
195
+ # Deletes a OSX resolver.
196
+ #
197
+ # @param launch_agent [String] The agent path.
198
+ # @param resolver_path [String] The resolver path.
199
+ # @return [Boolean] `true` if operation succedeed, `false` otherwise.
200
+ def delete_resolver(launch_agent, resolver_path)
201
+ delete_file(resolver_path, :resolver_deleting, :resolver_deleting_error)
165
202
  end
166
203
 
167
- self.on(:stop) do
168
- DevDNSd::Application.instance.on_stop
204
+ # Creates a OSX system agent.
205
+ #
206
+ # @param launch_agent [String] The agent path.
207
+ # @param resolver_path [String] The resolver path.
208
+ # @return [Boolean] `true` if operation succedeed, `false` otherwise.
209
+ def create_agent(launch_agent, resolver_path)
210
+ begin
211
+ self.logger.info(self.i18n.agent_creating(launch_agent))
212
+ write_agent(launch_agent)
213
+ self.execute_command("plutil -convert binary1 \"#{launch_agent}\"")
214
+ true
215
+ rescue
216
+ self.logger.error(self.i18n.agent_creating_error)
217
+ false
218
+ end
169
219
  end
170
- end
171
- end
172
220
 
173
- # Processes a DNS rule.
174
- #
175
- # @param rule [Rule] The rule to process.
176
- # @param type [Class] The type of request.
177
- # @param match_data [MatchData|nil] If the rule pattern was a Regexp, then this holds the match data, otherwise `nil` is passed.
178
- # @param transaction [Transaction] The current DNS transaction (http://rubydoc.info/gems/rubydns/RubyDNS/Transaction).
179
- # @return A reply for the request if matched, otherwise `false` or `nil`.
180
- def process_rule(rule, type, match_data, transaction)
181
- is_regex = rule.match.is_a?(::Regexp)
182
- type = DevDNSd::Rule.resource_class_to_symbol(type)
183
-
184
- DevDNSd::Application.instance.logger.debug("Found match on #{rule.match} with type #{type}.")
185
-
186
- if !rule.block.nil? then
187
- reply = rule.block.call(match_data, type, transaction)
188
- else
189
- reply = rule.reply
190
- end
191
-
192
- if is_regex && reply && match_data[0] then
193
- reply = match_data[0].gsub(rule.match, reply.gsub("$", "\\"))
194
- end
195
221
 
196
- DevDNSd::Application.instance.logger.debug(reply ? "Reply is #{reply} with type #{type}." : "No reply found.")
197
-
198
- if reply then
199
- options = rule.options
200
-
201
- final_reply = []
222
+ # Writes a OSX system agent.
223
+ #
224
+ # @param launch_agent [String] The agent path.
225
+ def write_agent(launch_agent)
226
+ ::File.open(launch_agent, "w") {|f|
227
+ f.write({"KeepAlive" => true, "Label" => "it.cowtech.devdnsd", "Program" => (::Pathname.new(Dir.pwd) + $0).to_s, "ProgramArguments" => ($ARGV ? $ARGV[0, $ARGV.length - 1] : []), "RunAtLoad" => true}.to_json)
228
+ f.flush
229
+ }
230
+ end
202
231
 
203
- case type
204
- when :MX
205
- preference = options.delete(:preference)
206
- preference = preference.nil? ? 10 : preference.to_integer(10)
207
- final_reply << preference
232
+ # Deletes a OSX system agent.
233
+ #
234
+ # @param launch_agent [String] The agent path.
235
+ # @param resolver_path [String] The resolver path.
236
+ # @return [Boolean] `true` if operation succedeed, `false` otherwise.
237
+ def delete_agent(launch_agent, resolver_path)
238
+ delete_file(launch_agent, :agent_deleting, :agent_deleting_error)
208
239
  end
209
240
 
210
- if [:A, :AAAA].include?(type) then
211
- final_reply << reply
212
- else
213
- final_reply << Resolv::DNS::Name.create(reply)
241
+ # Loads a OSX system agent.
242
+ #
243
+ # @param launch_agent [String] The agent path.
244
+ # @param resolver_path [String] The resolver path.
245
+ # @return [Boolean] `true` if operation succedeed, `false` otherwise.
246
+ def load_agent(launch_agent, resolver_path)
247
+ begin
248
+ self.logger.info(self.i18n.agent_loading(launch_agent))
249
+ self.execute_command("launchctl load -w \"#{launch_agent}\" > /dev/null 2>&1")
250
+ true
251
+ rescue
252
+ self.logger.error(self.i18n.agent_loading_error)
253
+ false
254
+ end
214
255
  end
215
256
 
216
- final_reply << options.merge({:resource_class => DevDNSd::Rule.symbol_to_resource_class(type)})
217
- transaction.respond!(*final_reply)
218
- elsif reply == false then
219
- false
220
- else
221
- reply
222
- end
257
+ # Unoads a OSX system agent.
258
+ #
259
+ # @param launch_agent [String] The agent path.
260
+ # @param resolver_path [String] The resolver path.
261
+ # @return [Boolean] `true` if operation succedeed, `false` otherwise.
262
+ def unload_agent(launch_agent, resolver_path)
263
+ begin
264
+ self.logger.info(self.i18n.agent_unloading(launch_agent))
265
+ self.execute_command("launchctl unload -w \"#{launch_agent}\" > /dev/null 2>&1")
266
+ true
267
+ rescue => e
268
+ self.logger.warn(self.i18n.agent_unloading_error)
269
+ false
270
+ end
271
+ end
223
272
  end
224
273
 
225
- # Starts the server in background.
226
- #
227
- # @return [Boolean] `true` if action succedeed, `false` otherwise.
228
- def action_start
229
- logger = self.get_logger
230
-
231
- logger.info("Starting DevDNSd ...")
274
+ # Methods to process requests.
275
+ module Server
276
+ # Starts the DNS server.
277
+ #
278
+ # @return [Object] The result of stop callbacks.
279
+ def perform_server
280
+ application = self
281
+ RubyDNS::run_server(listen: [[:udp, @config.address, @config.port.to_integer]]) do
282
+ self.logger = application.logger
283
+
284
+ match(/.+/, DevDNSd::Application::ANY_CLASSES) do |match_data, transaction|
285
+ transaction.append_question!
286
+ application.config.rules.each { |rule| application.process_rule_in_classes(rule, match_data, transaction) } # During debugging, wrap the inside of the block with a begin rescue and PRINT the exception because RubyDNS hides it.
287
+ end
232
288
 
233
- if @config.foreground then
234
- self.perform_server
235
- else
236
- RExec::Daemon::Controller.start(self.class)
289
+ # Default DNS handler and event handlers
290
+ otherwise { |transaction| transaction.failure!(:NXDomain) }
291
+ self.on(:start) { application.on_start }
292
+ self.on(:stop) { application.on_stop }
293
+ end
237
294
  end
238
295
 
239
- true
240
- end
241
-
242
- # Stops the server in background.
243
- #
244
- # @return [Boolean] `true` if action succedeed, `false` otherwise.
245
- def action_stop
246
- RExec::Daemon::Controller.stop(self.class)
247
-
248
- true
249
- end
250
-
251
- # Installs the server into the system.
252
- #
253
- # @return [Boolean] `true` if action succedeed, `false` otherwise.
254
- def action_install
255
- logger = get_logger
256
-
257
- if !self.is_osx? then
258
- logger.fatal("Install DevDNSd as a local resolver is only available on MacOSX.")
259
- return false
296
+ # Processes a DNS rule.
297
+ #
298
+ # @param rule [Rule] The rule to process.
299
+ # @param type [Symbol] The type of the query.
300
+ # @param match_data [MatchData|nil] If the rule pattern was a Regexp, then this holds the match data, otherwise `nil` is passed.
301
+ # @param transaction [RubyDNS::Transaction] The current DNS transaction (http://rubydoc.info/gems/rubydns/RubyDNS/Transaction).
302
+ def process_rule(rule, type, match_data, transaction)
303
+ reply, type = perform_process_rule(rule, type, match_data, transaction)
304
+ self.logger.debug(reply ? self.i18n.reply(reply, type) : self.i18n.no_reply)
305
+
306
+ if reply then
307
+ transaction.respond!(*finalize_reply(reply, rule, type))
308
+ elsif reply == false then
309
+ false
310
+ else
311
+ nil
312
+ end
260
313
  end
261
314
 
262
- resolver_file = self.resolver_path
263
- launch_agent = self.launch_agent_path
264
-
265
- # Installs the resolver
266
- begin
267
- logger.info("Installing the resolver in #{resolver_file} ...")
268
-
269
- open(resolver_file, "w") {|f|
270
- f.write("nameserver 127.0.0.1\n")
271
- f.write("port #{@config.port}")
272
- f.flush
273
- }
274
- rescue => e
275
- logger.error("Cannot create the resolver file.")
276
- return false
315
+ # Processes a rule against a set of DNS resource classes.
316
+ #
317
+ # @param rule [Rule] The rule to process.
318
+ # @param match_data [MatchData|nil] If the rule pattern was a Regexp, then this holds the match data, otherwise `nil` is passed.
319
+ # @param transaction [RubyDNS::Transaction] The current DNS transaction (http://rubydoc.info/gems/rubydns/RubyDNS/Transaction).
320
+ def process_rule_in_classes(rule, match_data, transaction)
321
+ # Get the subset of handled class that is valid for the rule
322
+ resource_classes = DevDNSd::Application::ANY_CLASSES & rule.resource_class.ensure_array
323
+ resource_classes = resource_classes & [transaction.resource_class] if transaction.resource_class != DevDNSd::Application::ANY_REQUEST
324
+
325
+ if resource_classes.present? then
326
+ resource_classes.each do |resource_class| # Now for every class
327
+ matches = rule.match_host(match_data[0])
328
+ self.process_rule(rule, resource_class, rule.is_regexp? ? matches : nil, transaction) if matches
329
+ end
330
+ end
277
331
  end
278
332
 
279
- begin
280
- logger.info("Creating the launch agent in #{launch_agent} ...")
333
+ private
334
+ # Performs the processing of a rule.
335
+ #
336
+ # @param rule [Rule] The rule to process.
337
+ # @param type [Symbol] The type of query.
338
+ # @param match_data [MatchData|nil] If the rule pattern was a Regexp, then this holds the match data, otherwise `nil` is passed.
339
+ # @param transaction [RubyDNS::Transaction] The current DNS transaction (http://rubydoc.info/gems/rubydns/RubyDNS/Transaction).
340
+ # @return [Array] The type and reply to the query.
341
+ def perform_process_rule(rule, type, match_data, transaction)
342
+ type = DevDNSd::Rule.resource_class_to_symbol(type)
343
+ reply = !rule.block.nil? ? rule.block.call(match_data, type, transaction) : rule.reply
344
+ reply = match_data[0].gsub(rule.match, reply.gsub("$", "\\")) if rule.match.is_a?(::Regexp) && reply && match_data[0]
345
+
346
+ self.logger.debug(self.i18n.match(rule.match, type))
347
+ [reply, type]
348
+ end
281
349
 
282
- args = $ARGV ? $ARGV[0, $ARGV.length - 1] : []
350
+ # Finalizes a query to return to the client.
351
+ #
352
+ # @param reply [String] The reply to send to the client.
353
+ # @param rule [Rule] The rule to process.
354
+ # @param type [Symbol] The type of query.
355
+ def finalize_reply(reply, rule, type)
356
+ rv = []
357
+ rv << rule.options.delete(:preference).to_integer(10) if type == :MX
358
+ rv << ([:A, :AAAA].include?(type) ? reply : Resolv::DNS::Name.create(reply))
359
+ rv << rule.options.merge({resource_class: DevDNSd::Rule.symbol_to_resource_class(type, @locale)})
360
+ rv
361
+ end
362
+ end
363
+ end
283
364
 
284
- plist = {"KeepAlive" => true, "Label" => "it.cowtech.devdnsd", "Program" => (::Pathname.new(Dir.pwd) + $0).to_s, "ProgramArguments" => args, "RunAtLoad" => true}
285
- ::File.open(launch_agent, "w") {|f|
286
- f.write(plist.to_json)
287
- f.flush
288
- }
289
- self.execute_command("plutil -convert binary1 \"#{launch_agent}\"")
290
- rescue => e
291
- logger.error("Cannot create the launch agent.")
292
- return false
293
- end
365
+ # The main DevDNSd application.
366
+ #
367
+ # @attribute [r] config
368
+ # @return [Configuration] The {Configuration Configuration} of this application.
369
+ # @attribute [r] command
370
+ # @return [Mamertes::Command] The Mamertes command.
371
+ # @attribute logger
372
+ # @return [Bovem::Logger] The logger for this application.
373
+ # @attribute [r] locale
374
+ # @return [Symbol|nil] The current application locale.
375
+ class Application < RExec::Daemon::Base
376
+ # Class for ANY DNS request.
377
+ ANY_REQUEST = Resolv::DNS::Resource::IN::ANY
294
378
 
295
- begin
296
- logger.info("Loading the launch agent ...")
297
- self.execute_command("launchctl load -w \"#{launch_agent}\" > /dev/null 2>&1")
298
- rescue => e
299
- logger.error("Cannot load the launch agent.")
300
- return false
301
- end
379
+ # List of classes handled in case of DNS request with resource class ANY.
380
+ ANY_CLASSES = [Resolv::DNS::Resource::IN::A, Resolv::DNS::Resource::IN::AAAA, Resolv::DNS::Resource::IN::ANY, Resolv::DNS::Resource::IN::CNAME, Resolv::DNS::Resource::IN::HINFO, Resolv::DNS::Resource::IN::MINFO, Resolv::DNS::Resource::IN::MX, Resolv::DNS::Resource::IN::NS, Resolv::DNS::Resource::IN::PTR, Resolv::DNS::Resource::IN::SOA, Resolv::DNS::Resource::IN::TXT]
302
381
 
303
- self.dns_update
382
+ include Lazier::I18n
383
+ include DevDNSd::ApplicationMethods::System
384
+ include DevDNSd::ApplicationMethods::Server
304
385
 
305
- true
306
- end
386
+ attr_reader :config
387
+ attr_reader :command
388
+ attr_accessor :logger
389
+ attr_reader :locale
307
390
 
308
- # Uninstalls the server from the system.
391
+ # Creates a new application.
309
392
  #
310
- # @return [Boolean] `true` if action succedeed, `false` otherwise.
311
- def action_uninstall
312
- logger = self.get_logger
313
-
314
- if !self.is_osx? then
315
- logger.fatal("Install DevDNSd as a local resolver is only available on MacOSX.")
316
- return false
317
- end
318
-
319
- resolver_file = self.resolver_path
320
- launch_agent = self.launch_agent_path
393
+ # @param command [Mamertes::Command] The current Mamertes command.
394
+ # @param locale [Symbol] The locale to use for the application.
395
+ def initialize(command, locale)
396
+ self.i18n_setup(:devdnsd, ::File.absolute_path(::Pathname.new(::File.dirname(__FILE__)).to_s + "/../../locales/"))
397
+ self.i18n = locale
321
398
 
322
- # Remove the resolver
323
- begin
324
- logger.info("Deleting the resolver #{resolver_file} ...")
325
- ::File.delete(resolver_file)
326
- rescue => e
327
- logger.warn("Cannot delete the resolver file.")
328
- return false
329
- end
399
+ @locale = locale
400
+ @command = command
401
+ options = @command.application.get_options.reject {|k,v| v.nil? }
330
402
 
331
- # Unload the launch agent.
332
- begin
333
- self.execute_command("launchctl unload -w \"#{launch_agent}\" > /dev/null 2>&1")
334
- rescue => e
335
- logger.warn("Cannot unload the launch agent.")
336
- end
403
+ # Setup logger
404
+ Bovem::Logger.start_time = Time.now
405
+ @logger = Bovem::Logger.create(Bovem::Logger.get_real_file(options["log_file"]) || Bovem::Logger.default_file, Logger::INFO)
337
406
 
338
- # Delete the launch agent.
339
- begin
340
- logger.info("Deleting the launch agent #{launch_agent} ...")
341
- ::File.delete(launch_agent)
342
- rescue => e
343
- logger.warn("Cannot delete the launch agent.")
344
- return false
345
- end
407
+ # Open configuration
408
+ read_configuration(options)
346
409
 
347
- self.dns_update
410
+ self
411
+ end
348
412
 
349
- true
413
+ # Gets the current logger of the application.
414
+ #
415
+ # @return [Logger] The current logger of the application.
416
+ def get_logger
417
+ @logger ||= Bovem::Logger.create(@config.foreground ? Bovem::Logger.default_file : @config.log_file, @config.log_level, @log_formatter)
350
418
  end
351
419
 
352
420
  # This method is called when the server starts. By default is a no-op.
@@ -364,11 +432,12 @@ module DevDNSd
364
432
  # Returns a unique (singleton) instance of the application.
365
433
  #
366
434
  # @param command [Mamertes::Command] The current Mamertes command.
435
+ # @param locale [Symbol] The locale to use for the application.
367
436
  # @param force [Boolean] If to force recreation of the instance.
368
437
  # @return [Application] The unique (singleton) instance of the application.
369
- def self.instance(command = nil, force = false)
438
+ def self.instance(command = nil, locale = nil, force = false)
370
439
  @instance = nil if force
371
- @instance ||= DevDNSd::Application.new(command) if command
440
+ @instance ||= DevDNSd::Application.new(command, locale) if command
372
441
  @instance
373
442
  end
374
443
 
@@ -381,7 +450,30 @@ module DevDNSd
381
450
 
382
451
  # Stops the application.
383
452
  def self.quit
384
- ::EventMachine.stop
453
+ ::EventMachine.stop rescue nil
454
+ end
455
+
456
+ # Check if the current implementation supports DevDNSd.
457
+ def self.check_ruby_implementation
458
+ if defined?(Rubinius) || defined?(JRuby) then
459
+ Kernel.puts(Lazier::Localizer.new(:devdnsd, ::File.absolute_path(::Pathname.new(::File.dirname(__FILE__)).to_s + "/../../locales/")).i18n.no_jruby_rubinius)
460
+ Kernel.exit(0)
461
+ end
385
462
  end
463
+
464
+ private
465
+ # Reads configuration.
466
+ #
467
+ # @param options [Hash] The configuration to read.
468
+ def read_configuration(options)
469
+ begin
470
+ @config = DevDNSd::Configuration.new(options["configuration"], options, @logger)
471
+ @logger = nil
472
+ @logger = self.get_logger
473
+ rescue Bovem::Errors::InvalidConfiguration => e
474
+ @logger ? @logger.fatal(e.message) : Bovem::Logger.create("STDERR").fatal(self.i18n.logging_failed(log_file))
475
+ raise ::SystemExit
476
+ end
477
+ end
386
478
  end
387
479
  end