devdnsd 3.1.2 → 4.0.0

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 (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