devdnsd 3.1.2 → 4.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (53) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +2 -2
  3. data/.rubocop.yml +82 -0
  4. data/.travis-gemfile +4 -10
  5. data/.travis.yml +11 -5
  6. data/CHANGELOG.md +11 -0
  7. data/Gemfile +9 -8
  8. data/README.md +4 -5
  9. data/Rakefile +22 -6
  10. data/bin/devdnsd +42 -36
  11. data/config/devdnsd_config.sample +11 -11
  12. data/devdnsd.gemspec +7 -6
  13. data/doc/DevDNSd.html +6 -6
  14. data/doc/DevDNSd/{ApplicationMethods/Aliases.html → Aliases.html} +96 -100
  15. data/doc/DevDNSd/Application.html +2170 -1084
  16. data/doc/DevDNSd/Configuration.html +63 -33
  17. data/doc/DevDNSd/Errors.html +3 -3
  18. data/doc/DevDNSd/Errors/InvalidRule.html +3 -3
  19. data/doc/DevDNSd/{ApplicationMethods/System.html → OSX.html} +116 -489
  20. data/doc/DevDNSd/Rule.html +448 -749
  21. data/doc/DevDNSd/{ApplicationMethods/Server.html → Server.html} +77 -73
  22. data/doc/DevDNSd/System.html +895 -0
  23. data/doc/DevDNSd/Version.html +6 -6
  24. data/doc/_index.html +28 -27
  25. data/doc/class_list.html +6 -2
  26. data/doc/file.README.html +8 -9
  27. data/doc/file_list.html +5 -1
  28. data/doc/frames.html +1 -1
  29. data/doc/index.html +8 -9
  30. data/doc/js/full_list.js +4 -1
  31. data/doc/method_list.html +106 -84
  32. data/doc/top-level-namespace.html +3 -3
  33. data/lib/devdnsd.rb +10 -8
  34. data/lib/devdnsd/aliases.rb +171 -0
  35. data/lib/devdnsd/application.rb +93 -704
  36. data/lib/devdnsd/configuration.rb +20 -11
  37. data/lib/devdnsd/errors.rb +1 -1
  38. data/lib/devdnsd/osx.rb +217 -0
  39. data/lib/devdnsd/rule.rb +65 -94
  40. data/lib/devdnsd/server.rb +118 -0
  41. data/lib/devdnsd/system.rb +102 -0
  42. data/lib/devdnsd/version.rb +3 -3
  43. data/locales/en.yml +17 -16
  44. data/locales/it.yml +17 -16
  45. data/spec/devdnsd/application_spec.rb +188 -184
  46. data/spec/devdnsd/configuration_spec.rb +2 -2
  47. data/spec/devdnsd/rule_spec.rb +33 -34
  48. data/spec/resolver_helper.rb +10 -27
  49. data/spec/spec_helper.rb +21 -7
  50. metadata +36 -21
  51. data/doc/DevDNSd/ApplicationMethods.html +0 -125
  52. data/doc/DevDNSd/ApplicationMethods/System/ClassMethods.html +0 -538
  53. data/spec/coverage_helper.rb +0 -20
@@ -6,7 +6,7 @@
6
6
  <title>
7
7
  Top Level Namespace
8
8
 
9
- &mdash; Documentation by YARD 0.8.7.4
9
+ &mdash; Documentation by YARD 0.8.7.6
10
10
 
11
11
  </title>
12
12
 
@@ -103,9 +103,9 @@
103
103
  </div>
104
104
 
105
105
  <div id="footer">
106
- Generated on Sat Mar 29 11:53:13 2014 by
106
+ Generated on Wed Mar 30 19:23:43 2016 by
107
107
  <a href="http://yardoc.org" title="Yay! A Ruby Documentation Tool" target="_parent">yard</a>
108
- 0.8.7.4 (ruby-2.1.0).
108
+ 0.8.7.6 (ruby-2.3.0).
109
109
  </div>
110
110
 
111
111
  </body>
@@ -4,23 +4,25 @@
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
- require "rubygems"
8
- require "bovem"
9
7
  require "rubydns"
10
- require "rexec/daemon"
8
+ require "process/daemon"
11
9
  require "mustache"
12
10
  require "ipaddr"
13
- require "fiber"
14
11
  require "plist"
15
12
  require "tempfile"
13
+ require "bovem"
16
14
 
17
- Lazier.load!(:object)
18
-
15
+ require "devdnsd/version" unless defined?(DevDNSd::Version)
16
+ require "devdnsd/aliases"
17
+ require "devdnsd/server"
18
+ require "devdnsd/system"
19
+ require "devdnsd/osx"
19
20
  require "devdnsd/application"
20
21
  require "devdnsd/configuration"
21
22
  require "devdnsd/errors"
22
23
  require "devdnsd/rule"
23
- require "devdnsd/version" if !defined?(DevDNSd::Version)
24
+
25
+ Celluloid.logger = nil
24
26
 
25
27
  # DevDNSd is not supported on JRuby
26
- DevDNSd::Application.check_ruby_implementation
28
+ DevDNSd::Application.check_ruby_implementation
@@ -0,0 +1,171 @@
1
+ # encoding: utf-8
2
+ #
3
+ # This file is part of the devdnsd gem. Copyright (C) 2013 and above Shogun <shogun@cowtech.it>.
4
+ # Licensed under the MIT license, which can be found at http://www.opensource.org/licenses/mit-license.php.
5
+ #
6
+
7
+ # A small DNS server to enable local .dev domain resolution.
8
+ module DevDNSd
9
+ # Methods to handle interfaces aliases.
10
+ module Aliases
11
+ extend ActiveSupport::Concern
12
+
13
+ # Manages aliases.
14
+ #
15
+ # @param operation [Symbol] The type of operation. Can be `:add` or `:remove`.
16
+ # @param message [String] The message to show if no addresses are found.
17
+ # @param options [Hash] The options provided by the user.
18
+ # @return [Boolean] `true` if operation succeeded, `false` otherwise.
19
+ def manage_aliases(operation, message, options)
20
+ config = self.config
21
+ options.each { |k, v| config.send("#{k}=", v) if config.respond_to?("#{k}=") }
22
+
23
+ addresses = compute_addresses
24
+
25
+ if addresses.present?
26
+ # Now, for every address, call the command
27
+ addresses.all? { |address| manage_address(operation, address, options[:dry_run]) }
28
+ else
29
+ @logger.error(message)
30
+ false
31
+ end
32
+ end
33
+
34
+ # Adds or removes an alias from the interface.
35
+ #
36
+ # @param type [Symbol] The operation to execute. Can be `:add` or `:remove`.
37
+ # @param address [String] The address to manage.
38
+ # @param dry_run [Boolean] If only show which modifications will be done.
39
+ # @return [Boolean] `true` if operation succeeded, `false` otherwise.
40
+ def manage_address(type, address, dry_run = false)
41
+ rv, command, prefix = setup_management(type, address)
42
+
43
+ # Now execute
44
+ if rv
45
+ if !dry_run
46
+ execute_manage(command, prefix, type, address, config)
47
+ else
48
+ log_management(:dry_run, prefix, type, i18n.remove, i18n.add, address, config)
49
+ end
50
+ end
51
+
52
+ rv
53
+ end
54
+
55
+ # Computes the list of address to manage.
56
+ #
57
+ # @param type [Symbol] The type of addresses to consider. Valid values are `:ipv4`, `:ipv6`, otherwise all addresses are considered.
58
+ # @return [Array] The list of addresses to add or remove from the interface.
59
+ def compute_addresses(type = :all)
60
+ config = self.config
61
+ config.addresses.present? ? filter_addresses(config, type) : generate_addresses(config, type)
62
+ end
63
+
64
+ # Checks if an address is a valid IPv4 address.
65
+ #
66
+ # @param address [String] The address to check.
67
+ # @return [Boolean] `true` if the address is a valid IPv4 address, `false` otherwise.
68
+ def ipv4?(address)
69
+ address = address.ensure_string
70
+
71
+ mo = /\A(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})\Z/.match(address)
72
+ (mo && mo.captures.all? { |i| i.to_i < 256 }) ? true : false
73
+ end
74
+ alias_method :is_ipv4?, :ipv4?
75
+
76
+ # Checks if an address is a valid IPv6 address.
77
+ #
78
+ # @param address [String] The address to check.
79
+ # @return [Boolean] `true` if the address is a valid IPv6 address, `false` otherwise.
80
+ def ipv6?(address)
81
+ address = address.ensure_string
82
+
83
+ catch(:valid) do
84
+ # IPv6 (normal)
85
+ check_normal_ipv6(address)
86
+ # IPv6 (IPv4 compat)
87
+ check_compat_ipv6(address)
88
+
89
+ false
90
+ end
91
+ end
92
+
93
+ private
94
+
95
+ # :nodoc:
96
+ def setup_management(type, address)
97
+ @addresses ||= compute_addresses
98
+ length = @addresses.length
99
+ length_s = length.to_s.length
100
+ progress = ((@addresses.index(address) || 0) + 1).indexize(length: length_s)
101
+ message = "{mark=blue}[{mark=bright white}#{progress}{mark=reset blue}/{/mark}#{length}{/mark}]{/mark}"
102
+
103
+ [true, build_command(type, address), message]
104
+ rescue ArgumentError
105
+ [false]
106
+ end
107
+
108
+ # :nodoc:
109
+ def filter_addresses(config, type)
110
+ filters = [:ipv4, :ipv6].select { |i| type == i || type == :all }.compact
111
+ config.addresses.select { |address| filters.any? { |filter| send("#{filter}?", address) } }.compact.uniq
112
+ end
113
+
114
+ # :nodoc:
115
+ def generate_addresses(config, type)
116
+ ip = IPAddr.new(config.start_address.ensure_string)
117
+ raise ArgumentError if type != :all && !ip.send("#{type}?")
118
+ Array.new([config.aliases, 1].max) do |_|
119
+ current = ip
120
+ ip = ip.succ
121
+ current
122
+ end
123
+ rescue ArgumentError
124
+ []
125
+ end
126
+
127
+ # :nodoc:
128
+ def build_command(type, address)
129
+ template = config.send((type == :remove) ? :remove_command : :add_command)
130
+ Mustache.render(template, {interface: config.interface, address: address.to_s}) + " > /dev/null 2>&1"
131
+ end
132
+
133
+ # :nodoc:
134
+ def execute_manage(command, prefix, type, address, config)
135
+ rv = execute_command(command)
136
+ log_management(:run, prefix, type, i18n.removing, i18n.adding, address, config)
137
+ log_management_error(config, address, manage_labels(type)) unless rv
138
+ rv
139
+ end
140
+
141
+ # :nodoc:
142
+ def manage_labels(type)
143
+ type == :remove ? [i18n.remove, i18n.from] : [i18n.add, i18n.to]
144
+ end
145
+
146
+ # :nodoc:
147
+ def log_management_error(config, address, labels)
148
+ @logger.error(replace_markers(i18n.general_error(labels[0], address, labels[1], config.interface)))
149
+ end
150
+
151
+ # :nodoc:
152
+ def log_management(message, prefix, type, remove_label, add_label, address, config)
153
+ labels = (type == :remove ? [remove_label, i18n.from] : [add_label, i18n.to])
154
+ @logger.info(replace_markers(i18n.send(message, prefix, labels[0], address, labels[1], config.interface)))
155
+ end
156
+
157
+ # :nodoc:
158
+ def check_compat_ipv6(address)
159
+ throw(:valid, true) if /\A[\dA-Fa-f]{1,4}(:[\dA-Fa-f]{1,4})*:/ =~ address && ipv4?($')
160
+ throw(:valid, true) if /\A[\dA-Fa-f]{1,4}(:[\dA-Fa-f]{1,4})*::([\dA-Fa-f]{1,4}(:[\dA-Fa-f]{1,4})*:)?/ =~ address && ipv4?($')
161
+ throw(:valid, true) if /\A::([\dA-Fa-f]{1,4}(:[\dA-Fa-f]{1,4})*:)?/ =~ address && ipv4?($')
162
+ end
163
+
164
+ # :nodoc:
165
+ def check_normal_ipv6(address)
166
+ throw(:valid, true) if /\A[\dA-Fa-f]{1,4}(:[\dA-Fa-f]{1,4})*\Z/ =~ address
167
+ throw(:valid, true) if /\A[\dA-Fa-f]{1,4}(:[\dA-Fa-f]{1,4})*::([\dA-Fa-f]{1,4}(:[\dA-Fa-f]{1,4})*)?\Z/ =~ address
168
+ throw(:valid, true) if /\A::([\dA-Fa-f]{1,4}(:[\dA-Fa-f]{1,4})*)?\Z/ =~ address
169
+ end
170
+ end
171
+ end
@@ -6,676 +6,52 @@
6
6
 
7
7
  # A small DNS server to enable local .dev domain resolution.
8
8
  module DevDNSd
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(instance.config.pid_file, ".pid")
22
- end
23
-
24
- # Returns the standard location of the PID file.
25
- #
26
- # @return [String] The standard location of the PID file.
27
- def working_directory
28
- File.dirname(instance.config.pid_file)
29
- end
30
- alias_method :runtime_directory, :working_directory
31
-
32
- # Returns the complete path of the PID file.
33
- #
34
- # @return [String] The complete path of the PID file.
35
- def process_file_path
36
- instance.config.pid_file
37
- end
38
-
39
- # Returns the complete path of the log file.
40
- #
41
- # @return [String] The complete path of the log file.
42
- def log_file_path
43
- instance.config.log_file
44
- end
45
-
46
- # Returns the standard location of the log file.
47
- #
48
- # @return [String] The standard location of the log file.
49
- def log_directory
50
- File.dirname(instance.config.log_file)
51
- end
52
- end
53
-
54
- # Gets the path for the resolver file.
55
- #
56
- # @param tld [String] The TLD to manage.
57
- # @return [String] The path for the resolver file.
58
- def resolver_path(tld = nil)
59
- tld ||= @config.tld
60
- "/etc/resolver/#{tld}"
61
- end
62
-
63
- # Gets the path for the launch agent file.
64
- #
65
- # @param name [String] The base name for the agent.
66
- # @return [String] The path for the launch agent file.
67
- def launch_agent_path(name = "it.cowtech.devdnsd")
68
- ENV["HOME"] + "/Library/LaunchAgents/#{name}.plist"
69
- end
70
-
71
- # Executes a shell command.
72
- #
73
- # @param command [String] The command to execute.
74
- # @return [Boolean] `true` if command succeeded, `false` otherwise.
75
- def execute_command(command)
76
- system("#{command} 2>&1 > /dev/null")
77
- end
78
-
79
- # Updates DNS cache.
80
- #
81
- # @return [Boolean] `true` if command succeeded, `false` otherwise.
82
- def dns_update
83
- @logger.info(i18n.dns_update)
84
-
85
- script = Tempfile.new("devdnsd-dns-cache-script")
86
- script.write("dscacheutil -flushcache 2>&1 > /dev/null\n")
87
- script.write("killall -9 mDNSResponder 2>&1 > /dev/null\n")
88
- script.write("killall -9 mDNSResponderHelper 2>&1 > /dev/null\n")
89
- script.close
90
-
91
- Kernel.system("/usr/bin/osascript -e 'do shell script \"sh #{script.path}\" with administrator privileges' 2>&1 > /dev/null")
92
- script.unlink
93
- end
94
-
95
- # Checks if we are running on MacOS X.
96
- #
97
- # System services are only available on that platform.
98
- #
99
- # @return [Boolean] `true` if the current platform is MacOS X, `false` otherwise.
100
- def is_osx?
101
- ::RbConfig::CONFIG['host_os'] =~ /^darwin/
102
- end
103
-
104
- # Starts the server in background.
105
- #
106
- # @return [Boolean] `true` if action succeeded, `false` otherwise.
107
- def action_start
108
- get_logger.info(i18n.starting)
109
-
110
- if !Process.respond_to?(:fork) then
111
- logger.warn(i18n.no_fork)
112
- @config.foreground = true
113
- elsif @command.options[:foreground].value then
114
- @config.foreground = true
115
- end
116
-
117
- @config.foreground ? perform_server : RExec::Daemon::Controller.start(self.class)
118
- true
119
- end
120
-
121
- # Stops the server in background.
122
- #
123
- # @return [Boolean] `true` if action succeeded, `false` otherwise.
124
- def action_stop
125
- RExec::Daemon::Controller.stop(self.class)
126
- true
127
- end
128
-
129
- # Restarts the server in background.
130
- #
131
- # @return [Boolean] `true` if action succeeded, `false` otherwise.
132
- def action_restart
133
- action_stop
134
- action_start
135
- true
136
- end
137
-
138
- # Shows the status of the server
139
- #
140
- # @return [Boolean] `true` if action succeeded, `false` otherwise.
141
- def action_status
142
- daemon = self.class
143
- status = RExec::Daemon::ProcessFile.status(daemon)
144
- pid = RExec::Daemon::ProcessFile.recall(daemon).to_s
145
- status = :crashed if status == :unknown && daemon.crashed?
146
-
147
- if status == :running then
148
- logger.info(replace_markers(i18n.status_running(pid)))
149
- elsif
150
- logger.info(replace_markers(i18n.send("status_#{status}")))
151
- end
152
- end
153
-
154
- # Adds aliases to an interface.
155
- #
156
- # @param options [Hash] The options provided by the user.
157
- # @return [Boolean] `true` if action succeeded, `false` otherwise.
158
- def action_add(options)
159
- manage_aliases(:add, i18n.add_empty, options)
160
- end
161
-
162
- # Removes aliases from an interface.
163
- #
164
- # @param options [Hash] The options provided by the user.
165
- # @return [Boolean] `true` if action succeeded, `false` otherwise.
166
- def action_remove(options)
167
- manage_aliases(:remove, i18n.remove_empty, options)
168
- end
169
-
170
-
171
- # Installs the application into the autolaunch.
172
- #
173
- # @return [Boolean] `true` if action succeeded, `false` otherwise.
174
- def action_install
175
- manage_installation(launch_agent_path, resolver_path, :create_resolver, :create_agent, :load_agent)
176
- end
177
-
178
- # Uninstalls the application from the autolaunch.
179
- #
180
- # @return [Boolean] `true` if action succeeded, `false` otherwise.
181
- def action_uninstall
182
- manage_installation(launch_agent_path, resolver_path, :delete_resolver, :unload_agent, :delete_agent)
183
- end
184
-
185
- private
186
- # Manages a OSX agent.
187
- #
188
- # @param launch_agent [String] The agent path.
189
- # @param resolver_path [String] The resolver path.
190
- # @param first_operation [Symbol] The first operation to execute.
191
- # @param second_operation [Symbol] The second operation to execute.
192
- # @param third_operation [Symbol] The third operation to execute.
193
- # @return [Boolean] `true` if operation succeeded, `false` otherwise.
194
- def manage_installation(launch_agent, resolver_path, first_operation, second_operation, third_operation)
195
- rv = check_agent_available
196
-
197
- logger.warn(replace_markers(i18n.admin_privileges_warning))
198
- rv = send(first_operation, launch_agent, resolver_path) if rv
199
- rv = send(second_operation, launch_agent, resolver_path) if rv
200
- rv = send(third_operation, launch_agent, resolver_path) if rv
201
- dns_update
202
- rv
203
- end
204
-
205
- # Deletes a file
206
- #
207
- # @param file [String] The file to delete.
208
- # @param before_message [Symbol] The message to show before deleting.
209
- # @param error_message [Symbol] The message to show in case of errors.
210
- # @return [Boolean] `true` if the file have been deleted, `false` otherwise.
211
- def delete_file(file, before_message, error_message)
212
- begin
213
- logger.info(i18n.send(before_message, file))
214
- ::File.delete(file)
215
- true
216
- rescue
217
- logger.warn(i18n.send(error_message))
218
- false
219
- end
220
- end
221
-
222
- # Checks if agent is enabled (that is, we are on OSX).
223
- #
224
- # @return [Boolean] `true` if the agent is enabled, `false` otherwise.
225
- def check_agent_available
226
- rv = true
227
- if !is_osx? then
228
- logger.fatal(i18n.no_agent)
229
- rv = false
230
- end
231
-
232
- rv
233
- end
234
-
235
- # Creates a OSX resolver.
236
- #
237
- # @param resolver_path [String] The resolver path.
238
- # @return [Boolean] `true` if operation succeeded, `false` otherwise.
239
- def create_resolver(_, resolver_path)
240
- begin
241
- logger.info(replace_markers(i18n.resolver_creating(resolver_path)))
242
-
243
- script = Tempfile.new("devdnsd-install-script")
244
- script.write("mkdir -p '#{File.dirname(resolver_path)}'\nrm -rf '#{resolver_path}'\n")
245
- script.write("echo 'nameserver 127.0.0.1\\nport #{@config.port}' >> '#{resolver_path}'")
246
- script.close
247
-
248
- Kernel.system("/usr/bin/osascript -e 'do shell script \"sh #{script.path}\" with administrator privileges' 2>&1 > /dev/null")
249
- script.unlink
250
- true
251
- rescue Exception
252
- logger.error(i18n.resolver_creating_error)
253
- false
254
- end
255
- end
256
-
257
- # Deletes a OSX resolver.
258
- #
259
- # @param resolver_path [String] The resolver path.
260
- # @return [Boolean] `true` if operation succeeded, `false` otherwise.
261
- def delete_resolver(_, resolver_path)
262
- begin
263
- logger.info(i18n.resolver_deleting(resolver_path))
264
- Kernel.system("/usr/bin/osascript -e 'do shell script \"rm #{resolver_path}\" with administrator privileges' 2>&1 > /dev/null")
265
- true
266
- rescue Exception
267
- logger.warn(i18n.resolver_deleting_error)
268
- false
269
- end
270
- end
271
-
272
- # Creates a OSX system agent.
273
- #
274
- # @param launch_agent [String] The agent path.
275
- # @return [Boolean] `true` if operation succeeded, `false` otherwise.
276
- def create_agent(launch_agent, _)
277
- begin
278
- logger.info(replace_markers(i18n.agent_creating(launch_agent)))
279
- program, args = prepare_agent
280
-
281
- ::File.open(launch_agent, "w") {|f|
282
- f.write({"KeepAlive" => true, "Label" => "it.cowtech.devdnsd", "Program" => program, "ProgramArguments" => args, "RunAtLoad" => true}.to_plist)
283
- f.flush
284
- }
285
-
286
- true
287
- rescue
288
- logger.error(i18n.agent_creating_error)
289
- false
290
- end
291
- end
292
-
293
- # Prepares arguments for an agent.
294
- #
295
- # @return [Array] The arguments for an agent.
296
- def prepare_agent
297
- [
298
- (::Pathname.new(Dir.pwd) + $0).to_s,
299
- (ARGV ? ARGV[0, ARGV.length - 1] : [])
300
- ]
301
- end
302
-
303
- # Deletes a OSX system agent.
304
- #
305
- # @param launch_agent [String] The agent path.
306
- # @return [Boolean] `true` if operation succeeded, `false` otherwise.
307
- def delete_agent(launch_agent, _)
308
- delete_file(launch_agent, :agent_deleting, :agent_deleting_error)
309
- end
310
-
311
- # Loads a OSX system agent.
312
- #
313
- # @param launch_agent [String] The agent path.
314
- # @return [Boolean] `true` if operation succeeded, `false` otherwise.
315
- def load_agent(launch_agent, _)
316
- toggle_agent(launch_agent, "load", :agent_loading, :agent_loading_error, :error)
317
- end
318
-
319
- # Unloads a OSX system agent.
320
- #
321
- # @param launch_agent [String] The agent path.
322
- # @return [Boolean] `true` if operation succeeded, `false` otherwise.
323
- def unload_agent(launch_agent, _)
324
- toggle_agent(launch_agent, "unload", :agent_unloading, :agent_unloading_error, :warn)
325
- end
326
-
327
- # Loads or unloads a OSX system agent.
328
- #
329
- # @param launch_agent [String] The agent path.
330
- # @param operation [String] The operation to perform. Can be `load` or `unload`.
331
- # @param info_message [Symbol] The message to show in case of errors.
332
- # @param error_message [Symbol] The message to show in case of errors.
333
- # @param error_level [Symbol] The error level to show. Can be `:warn` or `:error`.
334
- # @return [Boolean] `true` if operation succeeded, `false` otherwise.
335
- def toggle_agent(launch_agent, operation, info_message, error_message, error_level)
336
- begin
337
- logger.info(i18n.send(info_message, launch_agent))
338
- execute_command("launchctl #{operation} -w \"#{launch_agent}\" > /dev/null 2>&1")
339
- true
340
- rescue
341
- logger.send(error_level, i18n.send(error_message))
342
- false
343
- end
344
- end
345
-
346
- # Replaces markers in a log message.
347
- #
348
- # @param message [String] The message to process.
349
- # @return [String] The processed message.
350
- def replace_markers(message)
351
- @command.application.console.replace_markers(message)
352
- end
353
- end
354
-
355
- # Methods to handle interfaces aliases.
356
- module Aliases
357
- extend ActiveSupport::Concern
358
-
359
- # Manages aliases.
360
- #
361
- # @param operation [Symbol] The type of operation. Can be `:add` or `:remove`.
362
- # @param message [String] The message to show if no addresses are found.
363
- # @param options [Hash] The options provided by the user.
364
- # @return [Boolean] `true` if operation succeeded, `false` otherwise.
365
- def manage_aliases(operation, message, options)
366
- config = self.config
367
- options.each { |k, v| config.send("#{k}=", v) if config.respond_to?("#{k}=") }
368
-
369
- addresses = compute_addresses
370
-
371
- if addresses.present? then
372
- # Now, for every address, call the command
373
- addresses.all? {|address| manage_address(operation, address, options[:dry_run]) }
374
- else
375
- @logger.error(message)
376
- false
377
- end
378
- end
379
-
380
- # Adds or removes an alias from the interface.
381
- #
382
- # @param type [Symbol] The operation to execute. Can be `:add` or `:remove`.
383
- # @param address [String] The address to manage.
384
- # @param dry_run [Boolean] If only show which modifications will be done.
385
- # @return [Boolean] `true` if operation succeeded, `false` otherwise.
386
- def manage_address(type, address, dry_run = false)
387
- locale = i18n
388
- rv, command, prefix = setup_management(type, address)
389
-
390
- # Now execute
391
- if rv then
392
- if !dry_run then
393
- execute_manage(command, prefix, type, address, self.config)
394
- else
395
- log_management(:dry_run, prefix, type, locale.remove, locale.add, address, config)
396
- end
397
- end
398
-
399
- rv
400
- end
401
-
402
- # Computes the list of address to manage.
403
- #
404
- # @param type [Symbol] The type of addresses to consider. Valid values are `:ipv4`, `:ipv6`, otherwise all addresses are considered.
405
- # @return [Array] The list of addresses to add or remove from the interface.
406
- def compute_addresses(type = :all)
407
- config = self.config
408
- config.addresses.present? ? filter_addresses(config, type) : generate_addresses(config, type)
409
- end
410
-
411
- # Checks if an address is a valid IPv4 address.
412
- #
413
- # @param address [String] The address to check.
414
- # @return [Boolean] `true` if the address is a valid IPv4 address, `false` otherwise.
415
- def is_ipv4?(address)
416
- address = address.ensure_string
417
-
418
- mo = /\A(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})\Z/.match(address)
419
- (mo && mo.captures.all? {|i| i.to_i < 256}) ? true : false
420
- end
421
-
422
- # Checks if an address is a valid IPv6 address.
423
- #
424
- # @param address [String] The address to check.
425
- # @return [Boolean] `true` if the address is a valid IPv6 address, `false` otherwise.
426
- def is_ipv6?(address)
427
- address = address.ensure_string
428
-
429
- catch(:valid) do
430
- # IPv6 (normal)
431
- throw(:valid, true) if /\A[\dA-Fa-f]{1,4}(:[\dA-Fa-f]{1,4})*\Z/ =~ address
432
- throw(:valid, true) if /\A[\dA-Fa-f]{1,4}(:[\dA-Fa-f]{1,4})*::([\dA-Fa-f]{1,4}(:[\dA-Fa-f]{1,4})*)?\Z/ =~ address
433
- throw(:valid, true) if /\A::([\dA-Fa-f]{1,4}(:[\dA-Fa-f]{1,4})*)?\Z/ =~ address
434
- # IPv6 (IPv4 compat)
435
- throw(:valid, true) if /\A[\dA-Fa-f]{1,4}(:[\dA-Fa-f]{1,4})*:/ =~ address && is_ipv4?($')
436
- throw(:valid, true) if /\A[\dA-Fa-f]{1,4}(:[\dA-Fa-f]{1,4})*::([\dA-Fa-f]{1,4}(:[\dA-Fa-f]{1,4})*:)?/ =~ address && is_ipv4?($')
437
- throw(:valid, true) if /\A::([\dA-Fa-f]{1,4}(:[\dA-Fa-f]{1,4})*:)?/ =~ address && is_ipv4?($')
438
-
439
- false
440
- end
441
- end
442
-
443
- private
444
- # Setups management.
445
- #
446
- # @param type [Symbol] The type of operation. Can be `:add` or `:remove`.
447
- # @param address [String] The address to manage.
448
- # @return [Array] A list of parameters for the management.
449
- def setup_management(type, address)
450
- begin
451
- @addresses ||= compute_addresses
452
- length = @addresses.length
453
- length_s = length.to_s.length
454
- [true, build_command(type, address), "{mark=blue}[{mark=bright white}#{((@addresses.index(address) || 0) + 1).indexize(length_s)}{mark=reset blue}/{/mark}#{length}{/mark}]{/mark}"]
455
- rescue ArgumentError
456
- [false]
457
- end
458
- end
459
-
460
- # Filters a list of addresses to return just certain type(s).
461
- #
462
- # @param config [Configuration] The current configuration.
463
- # @param type [Symbol] The type of addresses to return.
464
- # @return [Array] A list of IPs.
465
- def filter_addresses(config, type)
466
- filters = [:ipv4, :ipv6].select {|i| type == i || type == :all }.compact
467
- config.addresses.select { |address| filters.any? {|filter| send("is_#{filter}?", address) } }.compact.uniq
468
- end
469
-
470
- # Generates a list of addresses which are immediate successors of a start address.
471
- #
472
- # @param config [Configuration] The current configuration.
473
- # @param type [Symbol] The type of addresses to return.
474
- # @return [Array] A list of IPs.
475
- def generate_addresses(config, type)
476
- begin
477
- ip = IPAddr.new(config.start_address.ensure_string)
478
- raise ArgumentError if type != :all && !ip.send("#{type}?")
479
-
480
- [config.aliases, 1].max.times.map {|_|
481
- current = ip
482
- ip = ip.succ
483
- current
484
- }
485
- rescue ArgumentError
486
- []
487
- end
488
- end
489
-
490
- # Builds the command to execute.
491
- #
492
- # @param type [Symbol] The type of operation. Can be `:add` or `:remove`.
493
- # @param address [String] The address to manage.
494
- # @return [String] The command to execute.
495
- def build_command(type, address)
496
- Mustache.render(config.send((type == :remove) ? :remove_command : :add_command), {interface: config.interface, address: address.to_s}) + " > /dev/null 2>&1"
497
- end
498
-
499
- # Executes management.
500
- #
501
- # @param command [String] The command to execute.
502
- # @param prefix [String] The prefix to apply to the message.
503
- # @param type [Symbol] The type of operation. Can be `:add` or `:remove`.
504
- # @param address [String] The address that will be managed.
505
- # @param config [Configuration] The current configuration.
506
- # @return [Boolean] `true` if operation succeeded, `false` otherwise.
507
- def execute_manage(command, prefix, type, address, config)
508
- locale = i18n
509
- log_management(:run, prefix, type, locale.removing, locale.adding, address, config)
510
- rv = execute_command(command)
511
- labels = (type == :remove ? [locale.remove, locale.from] : [locale.add, locale.to])
512
- @logger.error(replace_markers(locale.general_error(labels[0], address, labels[1], config.interface))) if !rv
513
- rv
514
- end
515
-
516
- # Logs an operation.
517
- #
518
- # @param message [Symbol] The message to print.
519
- # @param prefix [String] The prefix to apply to the message.
520
- # @param type [Symbol] The type of operation. Can be `:add` or `:remove`.
521
- # @param remove_label [String] The label to use for removing.
522
- # @param add_label [String] The label to use for adding.
523
- # @param address [String] The address that will be managed.
524
- # @param config [Configuration] The current configuration.
525
- def log_management(message, prefix, type, remove_label, add_label, address, config)
526
- locale = i18n
527
- labels = (type == :remove ? [remove_label, locale.from] : [add_label, locale.to])
528
- @logger.info(replace_markers(i18n.send(message, prefix, labels[0], address, labels[1], config.interface)))
529
- end
530
- end
531
-
532
- # Methods to process requests.
533
- module Server
534
- # Starts the DNS server.
535
- #
536
- # @return [Object] The result of stop callbacks.
537
- def perform_server
538
- application = self
539
-
540
- RubyDNS::run_server(listen: build_listen_interfaces) do
541
- self.logger = application.logger
542
-
543
- match(/.+/, DevDNSd::Application::ANY_CLASSES) do |transaction, match_data|
544
- 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.
545
- end
546
-
547
- # Default DNS handler and event handlers
548
- otherwise { |transaction| transaction.failure!(:NXDomain) }
549
- on(:start) { application.on_start }
550
- on(:stop) { application.on_stop }
551
- end
552
- end
553
-
554
- # Processes a DNS rule.
555
- #
556
- # @param rule [Rule] The rule to process.
557
- # @param type [Symbol] The type of the query.
558
- # @param match_data [MatchData|nil] If the rule pattern was a Regexp, then this holds the match data, otherwise `nil` is passed.
559
- # @param transaction [RubyDNS::Transaction] The current DNS transaction (http://rubydoc.info/gems/rubydns/RubyDNS/Transaction).
560
- def process_rule(rule, type, match_data, transaction)
561
- reply, type = perform_process_rule(rule, type, match_data, transaction)
562
- logger.debug(reply ? i18n.reply(reply, type) : i18n.no_reply)
563
-
564
- if reply then
565
- transaction.respond!(*finalize_reply(reply, rule, type))
566
- elsif reply.is_a?(FalseClass) then
567
- false
568
- else
569
- nil
570
- end
571
- end
572
-
573
- # Processes a rule against a set of DNS resource classes.
574
- #
575
- # @param rule [Rule] The rule to process.
576
- # @param match_data [MatchData|nil] If the rule pattern was a Regexp, then this holds the match data, otherwise `nil` is passed.
577
- # @param transaction [RubyDNS::Transaction] The current DNS transaction (http://rubydoc.info/gems/rubydns/RubyDNS/Transaction).
578
- def process_rule_in_classes(rule, match_data, transaction)
579
- # Get the subset of handled class that is valid for the rule
580
- resource_classes = DevDNSd::Application::ANY_CLASSES & rule.resource_class.ensure_array
581
- resource_classes = resource_classes & [transaction.resource_class] if transaction.resource_class != DevDNSd::Application::ANY_REQUEST
582
-
583
- if resource_classes.present? then
584
- resource_classes.each do |resource_class| # Now for every class
585
- matches = rule.match_host(match_data[0])
586
- process_rule(rule, resource_class, rule.is_regexp? ? matches : nil, transaction) if matches
587
- end
588
- end
589
- end
590
-
591
- private
592
- # Builds the list of listening interfaces.
593
- #
594
- # @return [Array] Array of addresses.
595
- def build_listen_interfaces
596
- port = @config.port.to_integer
597
- @config.bind_addresses.ensure_array {|address| [:udp, address, port] }
598
- end
599
-
600
- # Performs the processing of a rule.
601
- #
602
- # @param rule [Rule] The rule to process.
603
- # @param type [Symbol] The type of query.
604
- # @param match_data [MatchData|nil] If the rule pattern was a Regexp, then this holds the match data, otherwise `nil` is passed.
605
- # @param transaction [RubyDNS::Transaction] The current DNS transaction (http://rubydoc.info/gems/rubydns/RubyDNS/Transaction).
606
- # @return [Array] The type and reply to the query.
607
- def perform_process_rule(rule, type, match_data, transaction)
608
- type = DevDNSd::Rule.resource_class_to_symbol(type)
609
- reply = !rule.block.nil? ? rule.block.call(match_data, type, transaction) : rule.reply
610
- reply = match_data[0].gsub(rule.match, reply.gsub("$", "\\")) if rule.match.is_a?(::Regexp) && reply && match_data && match_data[0]
611
-
612
- logger.debug(i18n.match(rule.match, type))
613
- [reply, type]
614
- end
615
-
616
- # Finalizes a query to return to the client.
617
- #
618
- # @param reply [String] The reply to send to the client.
619
- # @param rule [Rule] The rule to process.
620
- # @param type [Symbol] The type of query.
621
- def finalize_reply(reply, rule, type)
622
- rv = []
623
- rv << rule.options.delete(:priority).to_integer(10) if type == :MX
624
- rv << ([:A, :AAAA].include?(type) ? reply : Resolv::DNS::Name.create(reply))
625
- rv << rule.options.merge({resource_class: DevDNSd::Rule.symbol_to_resource_class(type, @locale), ttl: validate_ttl(rule.options.delete(:ttl))})
626
- rv
627
- end
628
-
629
- # Validates a TTL.
630
- #
631
- # @param current [Fixnum] The current value.
632
- # @param default [Fixnum] The value to return if current is not valid.
633
- # @return [Fixnum] The validated TTL.
634
- def validate_ttl(current, default = 300)
635
- current = current.to_integer
636
- current > 0 ? current : default
637
- end
638
- end
639
- end
640
-
641
9
  # The main DevDNSd application.
642
10
  #
643
11
  # @attribute [r] config
644
12
  # @return [Configuration] The {Configuration Configuration} of this application.
13
+ # @attribute [r] server
14
+ # @return [RubyDNS::RuleBasedServer] The server of this application.
645
15
  # @attribute [r] command
646
16
  # @return [Bovem::Command] The Bovem command.
647
17
  # @attribute logger
648
18
  # @return [Bovem::Logger] The logger for this application.
649
19
  # @attribute [r] locale
650
20
  # @return [Symbol|nil] The current application locale.
651
- class Application < RExec::Daemon::Base
21
+ # @attribute [r] :i18n
22
+ # @return [Bovem::I18n] A localizer object.
23
+ class Application < Process::Daemon
652
24
  # Class for ANY DNS request.
653
25
  ANY_REQUEST = Resolv::DNS::Resource::IN::ANY
654
26
 
655
27
  # List of classes handled in case of DNS request with resource class ANY.
656
- 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]
28
+ ANY_CLASSES = [
29
+ Resolv::DNS::Resource::IN::A, Resolv::DNS::Resource::IN::AAAA, Resolv::DNS::Resource::IN::ANY, Resolv::DNS::Resource::IN::CNAME,
30
+ Resolv::DNS::Resource::IN::HINFO, Resolv::DNS::Resource::IN::MINFO, Resolv::DNS::Resource::IN::MX, Resolv::DNS::Resource::IN::NS,
31
+ Resolv::DNS::Resource::IN::PTR, Resolv::DNS::Resource::IN::SOA, Resolv::DNS::Resource::IN::TXT
32
+ ].freeze
657
33
 
658
- include Lazier::I18n
659
- include DevDNSd::ApplicationMethods::System
660
- include DevDNSd::ApplicationMethods::Aliases
661
- include DevDNSd::ApplicationMethods::Server
34
+ include DevDNSd::Aliases
35
+ include DevDNSd::Server
36
+ include DevDNSd::System
37
+ include DevDNSd::OSX
662
38
 
663
39
  attr_reader :config
664
40
  attr_reader :command
665
41
  attr_accessor :logger
666
42
  attr_reader :locale
43
+ attr_reader :i18n
44
+ attr_reader :server
667
45
 
668
46
  # Creates a new application.
669
47
  #
670
48
  # @param command [Bovem::Command] The current Bovem command.
671
49
  # @param locale [Symbol] The locale to use for the application.
672
50
  def initialize(command, locale)
673
- i18n_setup(:devdnsd, ::File.absolute_path(::Pathname.new(::File.dirname(__FILE__)).to_s + "/../../locales/"))
674
- self.i18n = locale
675
-
51
+ @i18n = Bovem::I18n.new(locale, root: "devdnsd", path: ::Pathname.new(::File.dirname(__FILE__)).to_s + "/../../locales/")
676
52
  @locale = locale
677
53
  @command = command
678
- options = @command.application.get_options.reject {|_, v| v.nil? }
54
+ options = load_options
679
55
 
680
56
  # Setup logger
681
57
  create_logger(options)
@@ -683,14 +59,22 @@ module DevDNSd
683
59
  # Open configuration
684
60
  read_configuration(options)
685
61
 
62
+ super(working_directory)
686
63
  self
687
64
  end
688
65
 
66
+ # Stops the server.
67
+ def shutdown
68
+ server.actors.first.links.each(&:terminate) if server
69
+ end
70
+
689
71
  # Gets the current logger of the application.
690
72
  #
73
+ # @param force [Boolean] If to force recreation of the logger.
691
74
  # @return [Logger] The current logger of the application.
692
- def get_logger
693
- @logger ||= Bovem::Logger.create(@config.foreground ? $stdout : @config.log_file, @config.log_level, @log_formatter)
75
+ def logger(force = false)
76
+ @logger = nil if force
77
+ @logger ||= Bovem::Logger.create(@config.foreground ? $stdout : @config.log_file, level: @config.log_level, formatter: @log_formatter)
694
78
  end
695
79
 
696
80
  # This method is called when the server starts. By default is a no-op.
@@ -726,85 +110,90 @@ module DevDNSd
726
110
 
727
111
  # Stops the application.
728
112
  def self.quit
729
- begin
730
- EM.add_timer(0.1) { ::EM.stop }
731
- rescue
732
- end
113
+ instance.shutdown
114
+ rescue
115
+ Kernel.exit(1)
733
116
  end
734
117
 
735
118
  # Check if the current implementation supports DevDNSd.
736
119
  def self.check_ruby_implementation
737
- if defined?(JRuby) then
738
- Kernel.puts(Lazier::Localizer.new(:devdnsd, ::File.absolute_path(::Pathname.new(::File.dirname(__FILE__)).to_s + "/../../locales/")).i18n.no_jruby)
120
+ if defined?(JRuby)
121
+ Kernel.puts(Bovem::I18n.new(root: "devdnsd", path: ::Pathname.new(::File.dirname(__FILE__)).to_s + "/../../locales/").no_jruby)
739
122
  Kernel.exit(0)
740
123
  end
741
124
  end
742
125
 
743
126
  private
744
- # Creates a logger.
745
- #
746
- # @param options [Hash] The configuration to use.
747
- def create_logger(options)
748
- warn_failure = false
749
- orig_file = file = Bovem::Logger.get_real_file(options["log_file"]) || Bovem::Logger.default_file
750
127
 
751
- if file.is_a?(String) then
752
- file = File.absolute_path(File.expand_path(file))
128
+ # :nodoc:
129
+ def load_options
130
+ @command.application.get_options.reject { |_, v| v.nil? }.merge(@command.get_options.reject { |_, v| v.nil? })
131
+ end
753
132
 
754
- begin
755
- FileUtils.mkdir_p(File.dirname(file))
756
- @logger = Bovem::Logger.create(file, Logger::INFO)
757
- rescue
758
- options["log_file"] = "STDOUT"
759
- file = $stdout
760
- warn_failure = true
761
- end
762
- end
133
+ # :nodoc:
134
+ def create_logger(options)
135
+ warn_failure = false
136
+ orig_file = file = Bovem::Logger.get_real_file(options["log_file"]) || Bovem::Logger.default_file
763
137
 
764
- @logger = Bovem::Logger.create(file, Logger::INFO)
765
- @logger.warn(replace_markers(i18n.logging_failed(orig_file))) if @logger && warn_failure
766
- @logger
138
+ if file.is_a?(String)
139
+ file, warn_failure = load_logger(File.absolute_path(File.expand_path(file)), options)
767
140
  end
768
141
 
769
- # Reads configuration.
770
- #
771
- # @param options [Hash] The configuration to read.
772
- def read_configuration(options)
773
- path = ::File.absolute_path(File.expand_path(options["configuration"]))
142
+ finalize_create_logger(file, orig_file, warn_failure)
143
+ end
774
144
 
775
- begin
776
- @config = DevDNSd::Configuration.new(path, options, @logger)
777
- ensure_directory_for(@config.log_file) if @config.log_file.is_a?(String)
778
- ensure_directory_for(@config.pid_file)
145
+ # :nodoc:
146
+ def load_logger(file, options)
147
+ warn_failure = false
779
148
 
780
- @logger = nil
781
- @logger = get_logger
782
- rescue Bovem::Errors::InvalidConfiguration => e
783
- log_failed_configuration(path, e)
784
- raise ::SystemExit
785
- end
149
+ begin
150
+ FileUtils.mkdir_p(File.dirname(file))
151
+ @logger = Bovem::Logger.create(file, level: ::Logger::INFO)
152
+ rescue
153
+ options["log_file"] = "STDOUT"
154
+ file = $stdout
155
+ warn_failure = true
786
156
  end
787
157
 
788
- # Creates a folder for a file.
789
- #
790
- # @param path [String] The path of the file.
791
- def ensure_directory_for(path)
792
- begin
793
- FileUtils.mkdir_p(File.dirname(path))
794
- rescue
795
- @logger.warn(replace_markers(i18n.invalid_directory(File.dirname(path))))
796
- raise ::SystemExit
797
- end
798
- end
158
+ [file, warn_failure]
159
+ end
160
+
161
+ # :nodoc:
162
+ def finalize_create_logger(file, orig_file, warn_failure)
163
+ @logger = Bovem::Logger.create(file, level: ::Logger::INFO)
164
+ @logger.warn(replace_markers(i18n.logging_failed(orig_file))) if @logger && warn_failure
165
+ @logger
166
+ end
799
167
 
800
- # Logs a failed configuration
801
- #
802
- # @param path [String] The path of the invalid file.
803
- # @param exception [Exception] The occurred exception.
804
- def log_failed_configuration(path, exception)
805
- logger = Bovem::Logger.create($stderr)
806
- logger.fatal(exception.message)
807
- logger.warn(replace_markers(i18n.application_create_config(path)))
168
+ # :nodoc:
169
+ def read_configuration(options)
170
+ path = ::File.absolute_path(File.expand_path(options["configuration"]))
171
+
172
+ begin
173
+ @config = DevDNSd::Configuration.new(path, options, @logger)
174
+ ensure_directory_for(@config.log_file) if @config.log_file.is_a?(String)
175
+ ensure_directory_for(@config.pid_file)
176
+ @logger = logger(true)
177
+ rescue Bovem::Errors::InvalidConfiguration => e
178
+ log_failed_configuration(path, e)
179
+ shutdown
808
180
  end
181
+ end
182
+
183
+ # :nodoc:
184
+ def ensure_directory_for(path)
185
+ FileUtils.mkdir_p(File.dirname(path))
186
+ rescue
187
+ @logger.warn(replace_markers(i18n.invalid_directory(File.dirname(path))))
188
+ shutdown
189
+ Kernel.exit(1)
190
+ end
191
+
192
+ # :nodoc:
193
+ def log_failed_configuration(path, exception)
194
+ logger = Bovem::Logger.create($stderr)
195
+ logger.fatal(exception.message)
196
+ logger.warn(replace_markers(i18n.application_create_config(path)))
197
+ end
809
198
  end
810
199
  end