devdnsd 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.DS_Store +0 -0
- data/.gitignore +21 -0
- data/.yardopts +1 -0
- data/Gemfile +9 -0
- data/Gemfile.lock +93 -0
- data/README.md +79 -0
- data/Rakefile +18 -0
- data/bin/devdnsd +77 -0
- data/config/devdnsd_config.sample +24 -0
- data/devdnsd.gemspec +40 -0
- data/lib/devdnsd.rb +23 -0
- data/lib/devdnsd/application.rb +366 -0
- data/lib/devdnsd/configuration.rb +94 -0
- data/lib/devdnsd/errors.rb +18 -0
- data/lib/devdnsd/logger.rb +84 -0
- data/lib/devdnsd/rule.rb +130 -0
- data/lib/devdnsd/version.rb +23 -0
- data/spec/coverage_helper.rb +19 -0
- data/spec/devdnsd/application_spec.rb +436 -0
- data/spec/devdnsd/configuration_spec.rb +77 -0
- data/spec/devdnsd/logger_spec.rb +86 -0
- data/spec/devdnsd/rule_spec.rb +111 -0
- data/spec/spec_helper.rb +13 -0
- data/utils/tester.rb +127 -0
- metadata +269 -0
data/.DS_Store
ADDED
Binary file
|
data/.gitignore
ADDED
data/.yardopts
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--exclude lib/devdnsd/patches/.+ --exclude utils/ -m markdown
|
data/Gemfile
ADDED
@@ -0,0 +1,9 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
#
|
3
|
+
# This file is part of the devdnsd gem. Copyright (C) 2012 and above Shogun <shogun_panda@me.com>.
|
4
|
+
# Licensed under the MIT license, which can be found at http://www.opensource.org/licenses/mit-license.php.
|
5
|
+
#
|
6
|
+
|
7
|
+
source "http://rubygems.org"
|
8
|
+
|
9
|
+
gemspec
|
data/Gemfile.lock
ADDED
@@ -0,0 +1,93 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
devdnsd (1.0.0)
|
5
|
+
cowtech-extensions (~> 2.1.0)
|
6
|
+
gli (~> 1.6.0)
|
7
|
+
rainbow (~> 1.1.0)
|
8
|
+
rexec (~> 1.4.1)
|
9
|
+
rubydns (~> 0.4.0)
|
10
|
+
|
11
|
+
GEM
|
12
|
+
remote: http://rubygems.org/
|
13
|
+
specs:
|
14
|
+
actionpack (3.2.6)
|
15
|
+
activemodel (= 3.2.6)
|
16
|
+
activesupport (= 3.2.6)
|
17
|
+
builder (~> 3.0.0)
|
18
|
+
erubis (~> 2.7.0)
|
19
|
+
journey (~> 1.0.1)
|
20
|
+
rack (~> 1.4.0)
|
21
|
+
rack-cache (~> 1.2)
|
22
|
+
rack-test (~> 0.6.1)
|
23
|
+
sprockets (~> 2.1.3)
|
24
|
+
activemodel (3.2.6)
|
25
|
+
activesupport (= 3.2.6)
|
26
|
+
builder (~> 3.0.0)
|
27
|
+
activesupport (3.2.6)
|
28
|
+
i18n (~> 0.6)
|
29
|
+
multi_json (~> 1.0)
|
30
|
+
builder (3.0.0)
|
31
|
+
coderay (1.0.7)
|
32
|
+
cowtech-extensions (2.1.3)
|
33
|
+
actionpack (~> 3.0)
|
34
|
+
tzinfo (~> 0.3.33)
|
35
|
+
diff-lcs (1.1.3)
|
36
|
+
erubis (2.7.0)
|
37
|
+
eventmachine (0.12.10)
|
38
|
+
github-markup (0.7.2)
|
39
|
+
gli (1.6.0)
|
40
|
+
hike (1.2.1)
|
41
|
+
i18n (0.6.0)
|
42
|
+
journey (1.0.4)
|
43
|
+
method_source (0.8)
|
44
|
+
multi_json (1.3.6)
|
45
|
+
net-dns (0.7.1)
|
46
|
+
pry (0.9.10)
|
47
|
+
coderay (~> 1.0.5)
|
48
|
+
method_source (~> 0.8)
|
49
|
+
slop (~> 3.3.1)
|
50
|
+
rack (1.4.1)
|
51
|
+
rack-cache (1.2)
|
52
|
+
rack (>= 0.4)
|
53
|
+
rack-test (0.6.1)
|
54
|
+
rack (>= 1.0)
|
55
|
+
rainbow (1.1.4)
|
56
|
+
redcarpet (2.1.1)
|
57
|
+
rexec (1.4.1)
|
58
|
+
rspec (2.11.0)
|
59
|
+
rspec-core (~> 2.11.0)
|
60
|
+
rspec-expectations (~> 2.11.0)
|
61
|
+
rspec-mocks (~> 2.11.0)
|
62
|
+
rspec-core (2.11.0)
|
63
|
+
rspec-expectations (2.11.1)
|
64
|
+
diff-lcs (~> 1.1.3)
|
65
|
+
rspec-mocks (2.11.1)
|
66
|
+
rubydns (0.4.1)
|
67
|
+
eventmachine
|
68
|
+
rexec
|
69
|
+
simplecov (0.6.4)
|
70
|
+
multi_json (~> 1.0)
|
71
|
+
simplecov-html (~> 0.5.3)
|
72
|
+
simplecov-html (0.5.3)
|
73
|
+
slop (3.3.2)
|
74
|
+
sprockets (2.1.3)
|
75
|
+
hike (~> 1.2)
|
76
|
+
rack (~> 1.0)
|
77
|
+
tilt (~> 1.1, != 1.3.0)
|
78
|
+
tilt (1.3.3)
|
79
|
+
tzinfo (0.3.33)
|
80
|
+
yard (0.8.2.1)
|
81
|
+
|
82
|
+
PLATFORMS
|
83
|
+
ruby
|
84
|
+
|
85
|
+
DEPENDENCIES
|
86
|
+
devdnsd!
|
87
|
+
github-markup (~> 0.7.0)
|
88
|
+
net-dns (~> 0.7.0)
|
89
|
+
pry (~> 0.9.9)
|
90
|
+
redcarpet (~> 2.1.0)
|
91
|
+
rspec (~> 2.11.0)
|
92
|
+
simplecov (~> 0.6.0)
|
93
|
+
yard (~> 0.8.0)
|
data/README.md
ADDED
@@ -0,0 +1,79 @@
|
|
1
|
+
# devdnsd
|
2
|
+
|
3
|
+
A small DNS server to enable local .dev domain resolution.
|
4
|
+
http://github.com/ShogunPanda/devdnsd
|
5
|
+
|
6
|
+
## Description
|
7
|
+
|
8
|
+
DevDNSd is a small DNS server which add a resolver to the system only for single TLD (by default, **.dev**). This way you can access your local application by typing every kind of url, i.e. *locallapp.dev*.
|
9
|
+
|
10
|
+
Of course, DevDNSd is inspired by [pow](https://github.com/37signals/pow), but it only provides DNS functionalities, delegating the setup of a web-server to the user.
|
11
|
+
|
12
|
+
## Basic usage
|
13
|
+
|
14
|
+
1. Install the gem:
|
15
|
+
|
16
|
+
`gem install devdnsd`
|
17
|
+
|
18
|
+
2. Install the service:
|
19
|
+
|
20
|
+
`devdnsd install`
|
21
|
+
|
22
|
+
**You're done!**
|
23
|
+
|
24
|
+
## Advanced usage
|
25
|
+
|
26
|
+
Just type `devdns help` and you'll see all available options.
|
27
|
+
|
28
|
+
## Configuration
|
29
|
+
|
30
|
+
By defaults, DevDNSd uses a configuration file in `~/.devdnsd_config`, but you can change the path using the `--config` switch.
|
31
|
+
|
32
|
+
The file is a plain Ruby file with a single `config` object that supports the following directives.
|
33
|
+
|
34
|
+
* `foreground`: If run the application in foreground.
|
35
|
+
* `address`: The IP to bind, 0.0.0.0 by default.
|
36
|
+
* `port`: The port to bind, 7771 by default.
|
37
|
+
* `tld`: The TLD to handle.
|
38
|
+
* `log_file`: The default log file. Not used if run in foreground.
|
39
|
+
* `log_level`: The default log level. Valid values are from 0 to 5 where 0 means "all messages".
|
40
|
+
* `add_rule`: Add a rule to the server. See section *Rules* below.
|
41
|
+
|
42
|
+
## Rules
|
43
|
+
|
44
|
+
DevDNSd has a nice rules system for matching name.
|
45
|
+
You can add rules by calling `config.add_rule(...)` into the configuration file.
|
46
|
+
|
47
|
+
The first argument of the function is the name you want to match. You can use regular expressions and this is highly recommended if you want to pass a block to the method (see below).
|
48
|
+
|
49
|
+
The second argument should be the IP associated to the name. If the first argument is a regexp with groups, you can use the standard Ruby `String#gsub` substitution syntax for the reply.
|
50
|
+
You can also use `false` to reject matching for this nameserver. In this case the next nameserver for the system will be used. You can skip this argument if you pass a block to the function.
|
51
|
+
If you return `nil` from the block, then you'll be responsible to set the response via the [`transaction`](http://rubydoc.info/gems/rubydns/RubyDNS/Transaction) variable.
|
52
|
+
|
53
|
+
The third argument can optionally be the record type for the resolv. By default is `:A`.
|
54
|
+
This argument is ignored if you pass the block, as it assumes that the second argument is the record type.
|
55
|
+
|
56
|
+
If you pass a block to the method, its return code will be taken as the result of the resolving. So if you return `false` then the resolving will be rejected. The block takes three arguments, `match`, `type` and `transaction`. If you used regular expression for the first argument, then `match` will contain all the match information for the name.
|
57
|
+
|
58
|
+
For some examples of rules, see the `config/devdnsd_config.sample` file into the repository.
|
59
|
+
|
60
|
+
## Remarks
|
61
|
+
|
62
|
+
DevDNSd as a local resolver is tightly coupled with the OSX name resolution system, so it is only available for MacOSX.
|
63
|
+
|
64
|
+
You can, anyway, run the software as DNS server.
|
65
|
+
|
66
|
+
## Contributing to devdns
|
67
|
+
|
68
|
+
* Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet
|
69
|
+
* Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it
|
70
|
+
* Fork the project
|
71
|
+
* Start a feature/bugfix branch
|
72
|
+
* Commit and push until you are happy with your contribution
|
73
|
+
* Make sure to add tests for it. This is important so I don't break it in a future version unintentionally.
|
74
|
+
* Please try not to mess with the Rakefile, version, or history. If you want to have your own version, or is otherwise necessary, that is fine, but please isolate to its own commit so I can cherry-pick around it.
|
75
|
+
|
76
|
+
## Copyright
|
77
|
+
|
78
|
+
Copyright (C) 2012 and above Shogun <[shogun_panda@me.com](mailto:shogun_panda@me.com)>.
|
79
|
+
Licensed under the MIT license, which can be found at [http://www.opensource.org/licenses/mit-license.php](http://www.opensource.org/licenses/mit-license.php).
|
data/Rakefile
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
#
|
3
|
+
# This file is part of the cowtech-extensions gem. Copyright (C) 2011 and above Shogun <shogun_panda@me.com>.
|
4
|
+
# Licensed under the MIT license, which can be found at http://www.opensource.org/licenses/mit-license.php.
|
5
|
+
#
|
6
|
+
|
7
|
+
require "bundler/gem_tasks"
|
8
|
+
require "rspec/core/rake_task"
|
9
|
+
|
10
|
+
RSpec::Core::RakeTask.new("spec")
|
11
|
+
|
12
|
+
namespace :spec do
|
13
|
+
desc "Run all specs with coverage"
|
14
|
+
task :coverage do
|
15
|
+
ENV["DEVDNSD_COVERAGE"] = "TRUE"
|
16
|
+
Rake::Task["spec"].invoke
|
17
|
+
end
|
18
|
+
end
|
data/bin/devdnsd
ADDED
@@ -0,0 +1,77 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# encoding: utf-8
|
3
|
+
#
|
4
|
+
# This file is part of the devdnsd gem. Copyright (C) 2012 and above Shogun <shogun_panda@me.com>.
|
5
|
+
# Licensed under the MIT license, which can be found at http://www.opensource.org/licenses/mit-license.php.
|
6
|
+
#
|
7
|
+
|
8
|
+
require "rubygems"
|
9
|
+
require "devdnsd"
|
10
|
+
|
11
|
+
# TODO: Replace GLI with another library.
|
12
|
+
|
13
|
+
include GLI
|
14
|
+
program_desc("A small DNS server to enable local domain resolution.")
|
15
|
+
version(DevDNSd::Version::STRING)
|
16
|
+
|
17
|
+
desc("Do not ask any confirmation.")
|
18
|
+
switch([:f, :force])
|
19
|
+
|
20
|
+
desc("The TLD to handle.")
|
21
|
+
default_value("dev")
|
22
|
+
arg_name("TLD")
|
23
|
+
flag([:t,:tld])
|
24
|
+
|
25
|
+
desc("The port to bind.")
|
26
|
+
default_value(7771)
|
27
|
+
arg_name("PORT")
|
28
|
+
flag([:p,:port])
|
29
|
+
|
30
|
+
desc("The default configuration file.")
|
31
|
+
default_value("~/.devdnsd_config")
|
32
|
+
arg_name("FILE")
|
33
|
+
flag([:c, :config])
|
34
|
+
|
35
|
+
desc("The default log file. Not used if run in foreground.")
|
36
|
+
default_value("/var/log/devdnsd.log")
|
37
|
+
arg_name("LOG")
|
38
|
+
flag([:l, :log])
|
39
|
+
|
40
|
+
desc("The default log level. Valid values are from 0 to 5 where 0 means \"all messages\".")
|
41
|
+
default_value(1)
|
42
|
+
arg_name("LEVEL")
|
43
|
+
flag([:L, :log_level])
|
44
|
+
|
45
|
+
desc("Starts the server.")
|
46
|
+
command [:s, :start] do |c|
|
47
|
+
c.desc("Do not daemonize.")
|
48
|
+
c.switch [:n, :foreground]
|
49
|
+
|
50
|
+
c.action do |globals, locals, args|
|
51
|
+
DevDNSd::Application.instance(globals, locals, args).action_start()
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
desc("Stops the server.")
|
56
|
+
command [:t, :stop] do |c|
|
57
|
+
c.action do |globals, locals, args|
|
58
|
+
DevDNSd::Application.instance(globals, locals, args).action_stop()
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
desc("Install the server.")
|
63
|
+
command [:i, :install] do |c|
|
64
|
+
c.action do |globals, locals, args|
|
65
|
+
DevDNSd::Application.instance(globals, locals, args).action_install()
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
desc("Uninstall the server.")
|
70
|
+
command [:u, :uninstall] do |c|
|
71
|
+
c.action do |globals, locals, args|
|
72
|
+
DevDNSd::Application.instance(globals, locals, args).action_uninstall()
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
ARGV << "help" if ARGV.length == 0
|
77
|
+
exit(GLI.run(ARGV))
|
@@ -0,0 +1,24 @@
|
|
1
|
+
config.add_rule("match_1.dev", "10.0.1.1")
|
2
|
+
|
3
|
+
config.add_rule("match_2.dev", "10.0.2.1", :MX)
|
4
|
+
|
5
|
+
config.add_rule("match_3.dev") do "10.0.3.1" end
|
6
|
+
|
7
|
+
config.add_rule("match_4.dev", :CNAME) do "10.0.4.1" end
|
8
|
+
|
9
|
+
|
10
|
+
config.add_rule(/match_5_(\d+)\.dev/, "10.0.5.$1")
|
11
|
+
|
12
|
+
config.add_rule(/match_6_(\d+).dev/, "10.0.6.$1", :PTR)
|
13
|
+
|
14
|
+
config.add_rule(/match_7_(\d+).dev/) do "10.0.7.$1" end
|
15
|
+
|
16
|
+
config.add_rule(/match_8_(\d+).dev/, :PTR) do "10.0.8.$1" end
|
17
|
+
|
18
|
+
|
19
|
+
config.add_rule("match_9.dev") do false end
|
20
|
+
|
21
|
+
config.add_rule("match_10.dev", [:A, :MX]) do |match, type, transaction| type == :A ? "10.0.10.1" : "10.0.10.2" end
|
22
|
+
|
23
|
+
|
24
|
+
config.add_rule("quit.dev") do DevDNSd::Application.quit end
|
data/devdnsd.gemspec
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
#
|
3
|
+
# This file is part of the devdnsd gem. Copyright (C) 2012 and above Shogun <shogun_panda@me.com>.
|
4
|
+
# Licensed under the MIT license, which can be found at http://www.opensource.org/licenses/mit-license.php.
|
5
|
+
#
|
6
|
+
|
7
|
+
require "./lib/devdnsd/version"
|
8
|
+
|
9
|
+
Gem::Specification.new do |gem|
|
10
|
+
gem.name = "devdnsd"
|
11
|
+
gem.version = DevDNSd::Version::STRING
|
12
|
+
gem.authors = ["Shogun"]
|
13
|
+
gem.email = ["shogun_panda@me.com"]
|
14
|
+
gem.homepage = "http://github.com/ShogunPanda/devdnsd"
|
15
|
+
gem.summary = %q{A small DNS server to enable local domain resolution.}
|
16
|
+
gem.description = %q{A small DNS server to enable local domain resolution.}
|
17
|
+
|
18
|
+
gem.rubyforge_project = "devdnsd"
|
19
|
+
gem.files = `git ls-files`.split("\n")
|
20
|
+
gem.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
21
|
+
gem.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
22
|
+
gem.require_paths = ["lib"]
|
23
|
+
#gem.platform = Gem::Platform::CURRENT
|
24
|
+
|
25
|
+
gem.add_dependency("rubydns", "~> 0.4.0")
|
26
|
+
gem.add_dependency("cowtech-extensions", "~> 2.1.0")
|
27
|
+
gem.add_dependency("gli", "~> 1.6.0")
|
28
|
+
gem.add_dependency("rexec", "~> 1.4.1")
|
29
|
+
gem.add_dependency("rainbow", "~> 1.1.0")
|
30
|
+
|
31
|
+
gem.add_development_dependency("rspec", "~> 2.11.0")
|
32
|
+
gem.add_development_dependency("simplecov", "~> 0.6.0")
|
33
|
+
gem.add_development_dependency("pry", "~> 0.9.9")
|
34
|
+
gem.add_development_dependency("net-dns", "~> 0.7.0")
|
35
|
+
gem.add_development_dependency("yard", "~> 0.8.0")
|
36
|
+
gem.add_development_dependency("redcarpet", "~> 2.1.0")
|
37
|
+
gem.add_development_dependency("github-markup", "~> 0.7.0")
|
38
|
+
end
|
39
|
+
|
40
|
+
|
data/lib/devdnsd.rb
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
#
|
3
|
+
# This file is part of the devdns gem. Copyright (C) 2012 and above Shogun <shogun_panda@me.com>.
|
4
|
+
# Licensed under the MIT license, which can be found at http://www.opensource.org/licenses/mit-license.php.
|
5
|
+
#
|
6
|
+
|
7
|
+
require "rubygems"
|
8
|
+
require "rubydns"
|
9
|
+
require "gli"
|
10
|
+
require "rexec/daemon"
|
11
|
+
require "pathname"
|
12
|
+
require "rainbow"
|
13
|
+
require "cowtech-extensions"
|
14
|
+
require "rbconfig"
|
15
|
+
|
16
|
+
Cowtech::Extensions.load!
|
17
|
+
|
18
|
+
require "devdnsd/application"
|
19
|
+
require "devdnsd/configuration"
|
20
|
+
require "devdnsd/errors"
|
21
|
+
require "devdnsd/logger"
|
22
|
+
require "devdnsd/rule"
|
23
|
+
require "devdnsd/version" if !defined?(DevDNSd::Version)
|
@@ -0,0 +1,366 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
#
|
3
|
+
# This file is part of the devdns gem. Copyright (C) 2012 and above Shogun <shogun_panda@me.com>.
|
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
|
+
# The main DevDNSd application.
|
10
|
+
class Application < RExec::Daemon::Base
|
11
|
+
# Class for ANY DNS request.
|
12
|
+
ANY_REQUEST = Resolv::DNS::Resource::IN::ANY
|
13
|
+
|
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]
|
16
|
+
|
17
|
+
# The {Configuration Configuration} of this application.
|
18
|
+
attr_reader :config
|
19
|
+
|
20
|
+
# The arguments passed via command-line.
|
21
|
+
attr_reader :args
|
22
|
+
|
23
|
+
# The {Logger Logger} for this application.
|
24
|
+
attr_accessor :logger
|
25
|
+
|
26
|
+
# Creates a new application.
|
27
|
+
#
|
28
|
+
# @param globals [Hash] Global options.
|
29
|
+
# @param locals [Hash] Local command options.
|
30
|
+
# @param args [Array] Extra arguments.
|
31
|
+
def initialize(globals = {}, locals = {}, args = [])
|
32
|
+
@args = {
|
33
|
+
:global => globals,
|
34
|
+
:local => locals,
|
35
|
+
:args => args
|
36
|
+
}
|
37
|
+
|
38
|
+
# Setup logger
|
39
|
+
DevDNSd::Logger.start_time = Time.now
|
40
|
+
@logger = DevDNSd::Logger.create(DevDNSd::Logger.get_real_file(@args[:global][:log_file]) || DevDNSd::Logger.default_file, Logger::INFO)
|
41
|
+
|
42
|
+
# Open configuration
|
43
|
+
begin
|
44
|
+
@config = DevDNSd::Configuration.new(@args[:global][:config], self, {
|
45
|
+
:foreground => @args[:local][:foreground],
|
46
|
+
:log_file => @args[:global][:log_file],
|
47
|
+
:log_level => @args[:global][:log_level],
|
48
|
+
:tld => @args[:global][:tld],
|
49
|
+
:port => @args[:global][:port]
|
50
|
+
})
|
51
|
+
@logger = nil
|
52
|
+
@logger = self.get_logger
|
53
|
+
|
54
|
+
rescue DevDNSd::Errors::InvalidConfiguration, DevDNSd::Errors::InvalidRule => e
|
55
|
+
@logger.fatal(e.message)
|
56
|
+
raise SystemExit
|
57
|
+
end
|
58
|
+
|
59
|
+
self
|
60
|
+
end
|
61
|
+
|
62
|
+
# Check if we are running on MacOS X.
|
63
|
+
# System services are only available on that platform.
|
64
|
+
#
|
65
|
+
# @return [Boolean] `true` if the current platform is MacOS X, `false` otherwise.
|
66
|
+
def is_osx?
|
67
|
+
Config::CONFIG['host_os'] =~ /^darwin/
|
68
|
+
end
|
69
|
+
|
70
|
+
# Gets the current logger of the application.
|
71
|
+
#
|
72
|
+
# @return [Logger] The current logger of the application.
|
73
|
+
def get_logger
|
74
|
+
@logger ||= DevDNSd::Logger.create(@config.foreground ? DevDNSd::Logger.default_file : @config.log_file, @config.log_level, @log_formatter)
|
75
|
+
end
|
76
|
+
|
77
|
+
# Gets the path for the resolver file.
|
78
|
+
#
|
79
|
+
# @param tld [String] The TLD to manage.
|
80
|
+
# @return [String] The path for the resolver file.
|
81
|
+
def resolver_path(tld = nil)
|
82
|
+
tld ||= @config.tld
|
83
|
+
"/etc/resolver/#{tld}"
|
84
|
+
end
|
85
|
+
|
86
|
+
# Gets the path for the launch agent file.
|
87
|
+
#
|
88
|
+
# @param name [String] The base name for the agent.
|
89
|
+
# @return [String] The path for the launch agent file.
|
90
|
+
def launch_agent_path(name = "it.cowtech.devdnsd")
|
91
|
+
ENV["HOME"] + "/Library/LaunchAgents/#{name}.plist"
|
92
|
+
end
|
93
|
+
|
94
|
+
# Executes a shell command.
|
95
|
+
#
|
96
|
+
# @param command [String] The command to execute.
|
97
|
+
# @return [Boolean] `true` if command succeeded, `false` otherwise.
|
98
|
+
def execute_command(command)
|
99
|
+
system(command)
|
100
|
+
end
|
101
|
+
|
102
|
+
# Updates DNS cache.
|
103
|
+
#
|
104
|
+
# @return [Boolean] `true` if command succeeded, `false` otherwise.
|
105
|
+
def dns_update
|
106
|
+
@logger.info("Flushing DNS cache and resolvers ...")
|
107
|
+
self.execute_command("dscacheutil -flushcache")
|
108
|
+
end
|
109
|
+
|
110
|
+
# Starts the DNS server.
|
111
|
+
#
|
112
|
+
# @return [Object] The result of stop callbacks.
|
113
|
+
def perform_server
|
114
|
+
RubyDNS::run_server(:listen => [[:udp, @config.address, @config.port.to_integer]]) do
|
115
|
+
self.logger = Application.instance.logger
|
116
|
+
|
117
|
+
match(/.+/, Application::ANY_REQUEST) do |match_data, transaction|
|
118
|
+
transaction.append_question!
|
119
|
+
|
120
|
+
Application.instance.config.rules.each do |rule|
|
121
|
+
begin
|
122
|
+
# Get the subset of handled class that is valid for the rule
|
123
|
+
resource_classes = Application::ANY_CLASSES & rule.resource_class.ensure_array
|
124
|
+
|
125
|
+
if resource_classes.present? then
|
126
|
+
resource_classes.each do |resource_class| # Now for every class
|
127
|
+
matches = rule.match_host(match_data[0])
|
128
|
+
Application.instance.process_rule(rule, resource_class, rule.is_regexp? ? matches : nil, transaction) if matches
|
129
|
+
end
|
130
|
+
end
|
131
|
+
rescue Exception => e
|
132
|
+
raise e
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
# Default DNS handler
|
138
|
+
otherwise do |transaction|
|
139
|
+
transaction.failure!(:NXDomain)
|
140
|
+
end
|
141
|
+
|
142
|
+
# Attach event handlers
|
143
|
+
self.on(:start) do
|
144
|
+
Application.instance.on_start
|
145
|
+
end
|
146
|
+
|
147
|
+
self.on(:stop) do
|
148
|
+
Application.instance.on_stop
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
# Processes a DNS rule.
|
154
|
+
#
|
155
|
+
# @param rule [Rule] The rule to process.
|
156
|
+
# @param type [Class] The type of request.
|
157
|
+
# @param match_or_transaction [MatchData|nil] If the rule pattern was a Regexp, then this holds the match data, otherwise `nil` is passed.
|
158
|
+
# @param transaction [Transaction] The current DNS transaction (http://rubydoc.info/gems/rubydns/RubyDNS/Transaction).
|
159
|
+
# @return A reply for the request if matched, otherwise `false` or `nil`.
|
160
|
+
def process_rule(rule, type, match_data, transaction)
|
161
|
+
is_regex = rule.match.is_a?(Regexp)
|
162
|
+
type = DevDNSd::Rule.resource_class_to_symbol(type)
|
163
|
+
|
164
|
+
Application.instance.logger.debug("Found match on #{rule.match} with type #{type}.")
|
165
|
+
|
166
|
+
if !rule.block.nil? then
|
167
|
+
reply = rule.block.call(match_data, type, transaction)
|
168
|
+
else
|
169
|
+
reply = rule.reply
|
170
|
+
end
|
171
|
+
|
172
|
+
if is_regex && reply && match_data[0] then
|
173
|
+
reply = match_data[0].gsub(rule.match, reply.gsub("$", "\\"))
|
174
|
+
end
|
175
|
+
|
176
|
+
Application.instance.logger.debug(reply ? "Reply is #{reply} with type #{type}." : "No reply found.")
|
177
|
+
|
178
|
+
if reply then
|
179
|
+
options = rule.options
|
180
|
+
|
181
|
+
final_reply = []
|
182
|
+
|
183
|
+
case type
|
184
|
+
when :MX
|
185
|
+
preference = options.delete(:preference)
|
186
|
+
preference = 10 if !preference.is_integer?
|
187
|
+
final_reply << preference
|
188
|
+
end
|
189
|
+
|
190
|
+
if [:A, :AAAA].include?(type) then
|
191
|
+
final_reply << reply
|
192
|
+
else
|
193
|
+
final_reply << Resolv::DNS::Name.create(reply)
|
194
|
+
end
|
195
|
+
|
196
|
+
final_reply << options.merge({:resource_class => DevDNSd::Rule.symbol_to_resource_class(type)})
|
197
|
+
transaction.respond!(*final_reply)
|
198
|
+
elsif reply == false then
|
199
|
+
false
|
200
|
+
else
|
201
|
+
reply
|
202
|
+
end
|
203
|
+
end
|
204
|
+
|
205
|
+
# Starts the server in background.
|
206
|
+
#
|
207
|
+
# @return [Boolean] `true` if action succedeed, `false` otherwise.
|
208
|
+
def action_start
|
209
|
+
logger = self.get_logger
|
210
|
+
|
211
|
+
logger.info("Starting DevDNSd ...")
|
212
|
+
|
213
|
+
if @config.foreground then
|
214
|
+
self.perform_server
|
215
|
+
else
|
216
|
+
RExec::Daemon::Controller.start(self.class)
|
217
|
+
end
|
218
|
+
|
219
|
+
true
|
220
|
+
end
|
221
|
+
|
222
|
+
# Stops the server in background.
|
223
|
+
#
|
224
|
+
# @return [Boolean] `true` if action succedeed, `false` otherwise.
|
225
|
+
def action_stop
|
226
|
+
RExec::Daemon::Controller.stop(self.class)
|
227
|
+
|
228
|
+
true
|
229
|
+
end
|
230
|
+
|
231
|
+
# Installs the server into the system.
|
232
|
+
#
|
233
|
+
# @return [Boolean] `true` if action succedeed, `false` otherwise.
|
234
|
+
def action_install
|
235
|
+
logger = get_logger
|
236
|
+
|
237
|
+
if !self.is_osx? then
|
238
|
+
logger.fatal("Install DevDNSd as a local resolver is only available on MacOSX.")
|
239
|
+
return false
|
240
|
+
end
|
241
|
+
|
242
|
+
resolver_file = self.resolver_path
|
243
|
+
launch_agent = self.launch_agent_path
|
244
|
+
|
245
|
+
# Installs the resolver
|
246
|
+
begin
|
247
|
+
logger.info("Installing the resolver in #{resolver_file} ...")
|
248
|
+
|
249
|
+
open(resolver_file, "w") {|f|
|
250
|
+
f.write("nameserver 127.0.0.1\n")
|
251
|
+
f.write("port #{@config.port}")
|
252
|
+
f.flush
|
253
|
+
}
|
254
|
+
rescue => e
|
255
|
+
logger.error("Cannot create the resolver file.")
|
256
|
+
return false
|
257
|
+
end
|
258
|
+
|
259
|
+
begin
|
260
|
+
logger.info("Creating the launch agent in #{launch_agent} ...")
|
261
|
+
|
262
|
+
args = $ARGV ? $ARGV[0, $ARGV.length - 1] : ["A"]
|
263
|
+
|
264
|
+
plist = {"KeepAlive" => true, "Label" => "it.cowtech.devdnsd", "Program" => (Pathname.new(Dir.pwd) + $0).to_s, "ProgramArguments" => args, "RunAtLoad" => true}
|
265
|
+
File.open(launch_agent, "w") {|f|
|
266
|
+
f.write(plist.to_json)
|
267
|
+
f.flush
|
268
|
+
}
|
269
|
+
self.execute_command("plutil -convert binary1 \"#{launch_agent}\"")
|
270
|
+
rescue => e
|
271
|
+
logger.error("Cannot create the launch agent.")
|
272
|
+
return false
|
273
|
+
end
|
274
|
+
|
275
|
+
begin
|
276
|
+
logger.info("Loading the launch agent ...")
|
277
|
+
self.execute_command("launchctl load -w \"#{launch_agent}\" > /dev/null 2>&1")
|
278
|
+
rescue => e
|
279
|
+
logger.error("Cannot load the launch agent.")
|
280
|
+
return false
|
281
|
+
end
|
282
|
+
|
283
|
+
self.dns_update
|
284
|
+
|
285
|
+
true
|
286
|
+
end
|
287
|
+
|
288
|
+
# Uninstalls the server from the system.
|
289
|
+
#
|
290
|
+
# @return [Boolean] `true` if action succedeed, `false` otherwise.
|
291
|
+
def action_uninstall
|
292
|
+
logger = self.get_logger
|
293
|
+
|
294
|
+
if !self.is_osx? then
|
295
|
+
logger.fatal("Install DevDNSd as a local resolver is only available on MacOSX.")
|
296
|
+
return false
|
297
|
+
end
|
298
|
+
|
299
|
+
resolver_file = self.resolver_path
|
300
|
+
launch_agent = self.launch_agent_path
|
301
|
+
|
302
|
+
# Remove the resolver
|
303
|
+
begin
|
304
|
+
logger.info("Deleting the resolver #{resolver_file} ...")
|
305
|
+
File.delete(resolver_file)
|
306
|
+
rescue => e
|
307
|
+
logger.warn("Cannot delete the resolver file.")
|
308
|
+
return false
|
309
|
+
end
|
310
|
+
|
311
|
+
# Unload the launch agent.
|
312
|
+
begin
|
313
|
+
self.execute_command("launchctl unload -w \"#{launch_agent}\" > /dev/null 2>&1")
|
314
|
+
rescue => e
|
315
|
+
logger.warn("Cannot unload the launch agent.")
|
316
|
+
end
|
317
|
+
|
318
|
+
# Delete the launch agent.
|
319
|
+
begin
|
320
|
+
logger.info("Deleting the launch agent #{launch_agent} ...")
|
321
|
+
File.delete(launch_agent)
|
322
|
+
rescue => e
|
323
|
+
logger.warn("Cannot delete the launch agent.")
|
324
|
+
return false
|
325
|
+
end
|
326
|
+
|
327
|
+
self.dns_update
|
328
|
+
|
329
|
+
true
|
330
|
+
end
|
331
|
+
|
332
|
+
# This method is called when the server starts. By default is a no-op.
|
333
|
+
#
|
334
|
+
# @return [NilClass] `nil`.
|
335
|
+
def on_start
|
336
|
+
end
|
337
|
+
|
338
|
+
# This method is called when the server stop.
|
339
|
+
#
|
340
|
+
# @return [NilClass] `nil`.
|
341
|
+
def on_stop
|
342
|
+
end
|
343
|
+
|
344
|
+
# Returns a unique (singleton) instance of the application.
|
345
|
+
# @param globals [Hash] Global options.
|
346
|
+
# @param locals [Hash] Local command options.
|
347
|
+
# @param args [Array] Extra arguments.
|
348
|
+
# @return [Application] The unique (singleton) instance of the application.
|
349
|
+
def self.instance(globals = {}, locals = {}, args = [], force = false)
|
350
|
+
@@instance = nil if force
|
351
|
+
@@instance ||= Application.new(globals, locals, args)
|
352
|
+
end
|
353
|
+
|
354
|
+
# Runs the application in foreground.
|
355
|
+
#
|
356
|
+
# @see #perform_server
|
357
|
+
def self.run
|
358
|
+
self.instance.perform_server
|
359
|
+
end
|
360
|
+
|
361
|
+
# Stops the application.
|
362
|
+
def self.quit
|
363
|
+
EventMachine.stop
|
364
|
+
end
|
365
|
+
end
|
366
|
+
end
|