dyndnsd 1.6.1 → 2.0.0.rc1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/.rubocop.yml +62 -0
- data/.rubocop_todo.yml +7 -0
- data/.travis.yml +5 -9
- data/CHANGELOG.md +78 -0
- data/README.md +82 -6
- data/Rakefile +3 -1
- data/dyndnsd.gemspec +16 -11
- data/lib/dyndnsd/database.rb +10 -8
- data/lib/dyndnsd/generator/bind.rb +9 -9
- data/lib/dyndnsd/helper.rb +41 -0
- data/lib/dyndnsd/responder/dyndns_style.rb +44 -11
- data/lib/dyndnsd/responder/rest_style.rb +44 -11
- data/lib/dyndnsd/updater/command_with_bind_zone.rb +13 -8
- data/lib/dyndnsd/version.rb +1 -1
- data/lib/dyndnsd.rb +178 -115
- data/spec/daemon_spec.rb +42 -8
- data/spec/support/dummy_database.rb +1 -3
- metadata +91 -17
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: aac098254fd64311eda2e12bba4f438f7b7fc6e91a25c24cf3f709b76c1f925e
|
4
|
+
data.tar.gz: ebee973229eb3c4d4f3a001ed21cffd3a6be400f31bfedb9dbc6529281988657
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 4497985876b27f5a8afffa1a9fba699a7837a32488035ba725edf5afd13c307f1c741561e75d2c54a5621936e59555da98f903abd43247aa4769d55b4485e340
|
7
|
+
data.tar.gz: 9f5cf58eb67acee24c0174ec7d65503deef1ccfc44568f118bccb7a0215c84a185e3ba023086ff6ae53a1feb2593793e822c6c16c258e3be588e1164363551d9
|
data/.rubocop.yml
ADDED
@@ -0,0 +1,62 @@
|
|
1
|
+
inherit_from: .rubocop_todo.yml
|
2
|
+
|
3
|
+
AllCops:
|
4
|
+
TargetRubyVersion: '2.3'
|
5
|
+
|
6
|
+
Gemspec/OrderedDependencies:
|
7
|
+
Enabled: false
|
8
|
+
|
9
|
+
# allows nicer usage of private_class_method
|
10
|
+
Layout/EmptyLinesAroundArguments:
|
11
|
+
Enabled: false
|
12
|
+
|
13
|
+
Layout/SpaceInsideHashLiteralBraces:
|
14
|
+
Enabled: false
|
15
|
+
|
16
|
+
Metrics/AbcSize:
|
17
|
+
Enabled: false
|
18
|
+
|
19
|
+
Metrics/BlockLength:
|
20
|
+
Enabled: false
|
21
|
+
|
22
|
+
Metrics/ClassLength:
|
23
|
+
Enabled: false
|
24
|
+
|
25
|
+
Metrics/CyclomaticComplexity:
|
26
|
+
Enabled: false
|
27
|
+
|
28
|
+
Metrics/LineLength:
|
29
|
+
Max: 200
|
30
|
+
|
31
|
+
Metrics/MethodLength:
|
32
|
+
Enabled: false
|
33
|
+
|
34
|
+
Metrics/PerceivedComplexity:
|
35
|
+
Enabled: false
|
36
|
+
|
37
|
+
Style/ConditionalAssignment:
|
38
|
+
Enabled: false
|
39
|
+
|
40
|
+
Style/Documentation:
|
41
|
+
Enabled: false
|
42
|
+
|
43
|
+
Style/FormatStringToken:
|
44
|
+
Enabled: false
|
45
|
+
|
46
|
+
Style/FrozenStringLiteralComment:
|
47
|
+
Enabled: false
|
48
|
+
|
49
|
+
Style/GuardClause:
|
50
|
+
Enabled: false
|
51
|
+
|
52
|
+
Style/IdenticalConditionalBranches:
|
53
|
+
Enabled: false
|
54
|
+
|
55
|
+
Style/InverseMethods:
|
56
|
+
Enabled: false
|
57
|
+
|
58
|
+
Style/NegatedIf:
|
59
|
+
Enabled: false
|
60
|
+
|
61
|
+
Style/SymbolArray:
|
62
|
+
Enabled: false
|
data/.rubocop_todo.yml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
# This configuration was generated by
|
2
|
+
# `rubocop --auto-gen-config`
|
3
|
+
# on 2018-02-23 12:54:10 +0100 using RuboCop version 0.52.1.
|
4
|
+
# The point is for the user to remove these configuration records
|
5
|
+
# one by one as the offenses are removed from the code base.
|
6
|
+
# Note that changes in the inspected code, or installation of new
|
7
|
+
# versions of RuboCop, may require this file to be generated again.
|
data/.travis.yml
CHANGED
data/CHANGELOG.md
ADDED
@@ -0,0 +1,78 @@
|
|
1
|
+
# Changelog
|
2
|
+
|
3
|
+
## 2.0.0
|
4
|
+
|
5
|
+
IMPROVEMENTS:
|
6
|
+
|
7
|
+
- Drop Ruby 2.2 and lower support
|
8
|
+
- Better protocol compliance by returning `badauth` in response body on HTTP 401 errors
|
9
|
+
- Better code maintainability by refactorings
|
10
|
+
- Update dependencies, mainly `rack` to new major version 2
|
11
|
+
- Add Ruby 2.5 support
|
12
|
+
- Add experimental [OpenTracing](http://opentracing.io/) support with [CNCF Jaeger](https://github.com/jaegertracing/jaeger)
|
13
|
+
- Support host offlining by deleting the associated DNS records
|
14
|
+
|
15
|
+
## 1.6.1 (October 31, 2017)
|
16
|
+
|
17
|
+
IMPROVEMENTS:
|
18
|
+
|
19
|
+
- Fix broken password check affecting all previous releases
|
20
|
+
|
21
|
+
## 1.6.0 (December 7, 2016)
|
22
|
+
|
23
|
+
IMPROVEMENTS:
|
24
|
+
|
25
|
+
- Support providing an IPv6 address in addition to a IPv4 for the same hostname
|
26
|
+
|
27
|
+
## 1.5.0 (November 30, 2016)
|
28
|
+
|
29
|
+
IMPROVEMENTS:
|
30
|
+
|
31
|
+
- Drop Ruby 1.8.7 support
|
32
|
+
- Pin `json` gem to allow supporting Ruby 1.9.3
|
33
|
+
- Support determining effective client IP address also from `X-Real-IP` header
|
34
|
+
|
35
|
+
## 1.4.0 (November 27, 2016)
|
36
|
+
|
37
|
+
IMPROVEMENTS:
|
38
|
+
|
39
|
+
- Pin `rack` gem to allow supporting Ruby versions < 2.2.2
|
40
|
+
- Support IPv6 addresses
|
41
|
+
|
42
|
+
## 1.3.0 (October 8, 2013)
|
43
|
+
|
44
|
+
IMPROVEMENTS:
|
45
|
+
|
46
|
+
- Handle `SIGTERM` \*nix signal properly and shutdown the daemon
|
47
|
+
|
48
|
+
## 1.2.2 (June 8, 2013)
|
49
|
+
|
50
|
+
IMPROVEMENTS:
|
51
|
+
|
52
|
+
- Add proper logging to the provided init script for dyndnsd.rb
|
53
|
+
|
54
|
+
## 1.2.1 (June 5, 2013)
|
55
|
+
|
56
|
+
IMPROVEMENTS:
|
57
|
+
|
58
|
+
- Fix bug in previous release related to metrics preventing startup
|
59
|
+
|
60
|
+
## 1.2.0 (May 29, 2013)
|
61
|
+
|
62
|
+
IMPROVEMENTS:
|
63
|
+
|
64
|
+
- Support sending metrics to graphite via undocumented `graphite:` section in configuration file
|
65
|
+
|
66
|
+
## 1.1.0 (April 30, 2013)
|
67
|
+
|
68
|
+
IMPROVEMENTS:
|
69
|
+
|
70
|
+
- Support dropping priviliges on startup, also affects external commands run
|
71
|
+
- Add [metriks](https://github.com/eric/metriks) support for basic metrics in the process title
|
72
|
+
- Detach from child processes running external commands to avoid zombie processes
|
73
|
+
|
74
|
+
## 1.0.0 (April 28, 2013)
|
75
|
+
|
76
|
+
NEW FEATURES:
|
77
|
+
|
78
|
+
- Initial 1.0 release
|
data/README.md
CHANGED
@@ -1,16 +1,18 @@
|
|
1
1
|
# dyndnsd.rb
|
2
2
|
|
3
|
-
[![Build Status](https://travis-ci.org/cmur2/dyndnsd.
|
3
|
+
[![Build Status](https://travis-ci.org/cmur2/dyndnsd.svg?branch=master)](https://travis-ci.org/cmur2/dyndnsd) [![Dependencies](https://badges.depfu.com/badges/4f25da8493f7a29f652ac892fbf9227b/overview.svg)](https://depfu.com/github/cmur2/dyndnsd)
|
4
4
|
|
5
5
|
A small, lightweight and extensible DynDNS server written with Ruby and Rack.
|
6
6
|
|
7
7
|
## Description
|
8
8
|
|
9
|
-
dyndnsd.rb aims to implement a small [DynDNS-compliant](
|
9
|
+
dyndnsd.rb aims to implement a small [DynDNS-compliant](https://help.dyn.com/remote-access-api/) server in Ruby supporting IPv4 and IPv6 addresses. It has an integrated user and hostname database in it's configuration file that is used for authentication and authorization. Besides talking the DynDNS protocol it is able to invoke a so-called *updater*, a small Ruby module that takes care of supplying the current hostname => ip mapping to a DNS server.
|
10
10
|
|
11
|
-
There is currently one updater shipped with dyndnsd.rb `command_with_bind_zone` that writes out a zone file in BIND syntax onto the current system and invokes a user-supplied command afterwards that is assumed to trigger the DNS server (not necessarily BIND since it's zone files are read by other DNS servers too) to reload it's zone configuration.
|
11
|
+
There is currently one updater shipped with dyndnsd.rb `command_with_bind_zone` that writes out a zone file in BIND syntax onto the current system and invokes a user-supplied command afterwards that is assumed to trigger the DNS server (not necessarily BIND since it's zone files are read by other DNS servers, too) to reload it's zone configuration.
|
12
12
|
|
13
|
-
Because of the mechanisms used dyndnsd.rb is known to work only on \*nix systems.
|
13
|
+
Because of the mechanisms used, dyndnsd.rb is known to work only on \*nix systems.
|
14
|
+
|
15
|
+
See the [changelog](CHANGELOG.md) before upgrading. The older version 1.x of dyndnsd.rb is still available on [branch dyndnsd-1.x](https://github.com/cmur2/dyndnsd/tree/dyndnsd-1.x).
|
14
16
|
|
15
17
|
## General Usage
|
16
18
|
|
@@ -101,7 +103,7 @@ Please provide ideas if you are using dyndnsd.rb with other DNS servers :)
|
|
101
103
|
|
102
104
|
The update URL you want to tell your clients (humans or scripts ^^) consists of the following
|
103
105
|
|
104
|
-
http[s]://[USER]:[PASSWORD]@[DOMAIN]:[PORT]/nic/update?hostname=[HOSTNAMES]&myip=[MYIP]
|
106
|
+
http[s]://[USER]:[PASSWORD]@[DOMAIN]:[PORT]/nic/update?hostname=[HOSTNAMES]&myip=[MYIP]&myip6=[MYIP6]
|
105
107
|
|
106
108
|
where:
|
107
109
|
|
@@ -110,7 +112,8 @@ where:
|
|
110
112
|
* DOMAIN should match what you defined in your config.yaml as domain but may be anything else when using a webserver as proxy
|
111
113
|
* PORT depends on your (webserver/proxy) settings
|
112
114
|
* HOSTNAMES is a required list of comma separated FQDNs (they all have to end with your config.yaml domain) the user wants to update
|
113
|
-
* MYIP is optional and the HTTP client's address will be used if missing
|
115
|
+
* MYIP is optional and the HTTP client's IP address will be used if missing
|
116
|
+
* MYIP6 is optional but if present also requires presence of MYIP
|
114
117
|
|
115
118
|
### IP address determination
|
116
119
|
|
@@ -130,6 +133,79 @@ Use a webserver as a proxy to handle SSL and/or multiple listen addresses and po
|
|
130
133
|
|
131
134
|
The [Debian 6 init.d script](init.d/debian-6-dyndnsd) assumes that dyndnsd.rb is installed into the system ruby (no RVM support) and the config.yaml is at /opt/dyndnsd/config.yaml. Modify to your needs.
|
132
135
|
|
136
|
+
### Monitoring
|
137
|
+
|
138
|
+
For monitoring dyndnsd.rb uses the [metriks](https://github.com/eric/metriks) framework and exposes several metrics like the number of unauthenticated requests, requests that did (not) update a hostname, etc. By default the most important metrics are shown in the [proctitle](https://github.com/eric/metriks#proc-title-reporter) but you can also configure a [Graphite](https://graphiteapp.org/) backend for central monitoring.
|
139
|
+
|
140
|
+
```yaml
|
141
|
+
host: "0.0.0.0"
|
142
|
+
port: "8245" # the DynDNS.com alternative HTTP port
|
143
|
+
db: "/opt/dyndnsd/db.json"
|
144
|
+
domain: "dyn.example.org"
|
145
|
+
# configure the Graphite backend to be used instead of proctitle
|
146
|
+
graphite:
|
147
|
+
host: localhost # defaults for host and port of a carbon server
|
148
|
+
port: 2003
|
149
|
+
prefix: "my.graphite.metrics.naming.structure.dyndnsd"
|
150
|
+
# configure the updater, here we use command_with_bind_zone, params are updater-specific
|
151
|
+
updater:
|
152
|
+
name: "command_with_bind_zone"
|
153
|
+
params:
|
154
|
+
zone_file: "dyn.zone"
|
155
|
+
command: "echo 'Hello'"
|
156
|
+
ttl: "5m"
|
157
|
+
dns: "dns.example.org."
|
158
|
+
email_addr: "admin.example.org."
|
159
|
+
# user database with hostnames a user is allowed to update
|
160
|
+
users:
|
161
|
+
# 'foo' is username, 'secret' the password
|
162
|
+
foo:
|
163
|
+
password: "secret"
|
164
|
+
hosts:
|
165
|
+
- foo.example.org
|
166
|
+
- bar.example.org
|
167
|
+
test:
|
168
|
+
password: "ihavenohosts"
|
169
|
+
```
|
170
|
+
|
171
|
+
### Tracing (experimental)
|
172
|
+
|
173
|
+
For tracing dyndnsd.rb is instrumented using the [OpenTracing](http://opentracing.io/) framework and will emit span tracing data for the most important operations happening during the request/response cycle. Using a middleware for Rack allows handling incoming OpenTracing span information properly.
|
174
|
+
Currently only one OpenTracing-compatible tracer implementation named [CNCF Jaeger](https://github.com/jaegertracing/jaeger) can be configured to use with dyndnsd.rb.
|
175
|
+
|
176
|
+
```yaml
|
177
|
+
host: "0.0.0.0"
|
178
|
+
port: "8245" # the DynDNS.com alternative HTTP port
|
179
|
+
db: "/opt/dyndnsd/db.json"
|
180
|
+
domain: "dyn.example.org"
|
181
|
+
# enable and configure tracing using the (currently only) tracer jaeger
|
182
|
+
tracing:
|
183
|
+
trust_incoming_span: false # default value, change to accept incoming OpenTracing spans as parents
|
184
|
+
jaeger:
|
185
|
+
host: 127.0.0.1 # defaults for host and port of local jaeger-agent
|
186
|
+
port: 6831
|
187
|
+
service_name: "my.dyndnsd.identifier"
|
188
|
+
# configure the updater, here we use command_with_bind_zone, params are updater-specific
|
189
|
+
updater:
|
190
|
+
name: "command_with_bind_zone"
|
191
|
+
params:
|
192
|
+
zone_file: "dyn.zone"
|
193
|
+
command: "echo 'Hello'"
|
194
|
+
ttl: "5m"
|
195
|
+
dns: "dns.example.org."
|
196
|
+
email_addr: "admin.example.org."
|
197
|
+
# user database with hostnames a user is allowed to update
|
198
|
+
users:
|
199
|
+
# 'foo' is username, 'secret' the password
|
200
|
+
foo:
|
201
|
+
password: "secret"
|
202
|
+
hosts:
|
203
|
+
- foo.example.org
|
204
|
+
- bar.example.org
|
205
|
+
test:
|
206
|
+
password: "ihavenohosts"
|
207
|
+
```
|
208
|
+
|
133
209
|
## License
|
134
210
|
|
135
211
|
dyndnsd.rb is licensed under the Apache License, Version 2.0. See LICENSE for more information.
|
data/Rakefile
CHANGED
data/dyndnsd.gemspec
CHANGED
@@ -1,31 +1,36 @@
|
|
1
1
|
|
2
|
-
|
2
|
+
$LOAD_PATH.push File.expand_path('../lib', __FILE__)
|
3
3
|
|
4
4
|
require 'dyndnsd/version'
|
5
5
|
|
6
6
|
Gem::Specification.new do |s|
|
7
|
-
s.name
|
7
|
+
s.name = 'dyndnsd'
|
8
8
|
s.version = Dyndnsd::VERSION
|
9
9
|
s.summary = 'dyndnsd.rb'
|
10
10
|
s.description = 'A small, lightweight and extensible DynDNS server written with Ruby and Rack.'
|
11
|
-
s.author
|
11
|
+
s.author = 'Christian Nicolai'
|
12
12
|
s.email = 'chrnicolai@gmail.com'
|
13
|
-
s.
|
14
|
-
s.
|
13
|
+
s.homepage = 'https://github.com/cmur2/dyndnsd'
|
14
|
+
s.license = 'Apache-2.0'
|
15
15
|
|
16
|
-
s.files = `git ls-files`.split(
|
16
|
+
s.files = `git ls-files`.split($INPUT_RECORD_SEPARATOR)
|
17
17
|
s.test_files = s.files.grep(%r{^(test|spec|features)/})
|
18
|
-
|
19
18
|
s.require_paths = ['lib']
|
20
|
-
|
21
19
|
s.executables = ['dyndnsd']
|
22
20
|
|
23
|
-
s.
|
24
|
-
|
21
|
+
s.required_ruby_version = '>= 2.3'
|
22
|
+
|
23
|
+
s.add_runtime_dependency 'rack', '~> 2.0'
|
24
|
+
s.add_runtime_dependency 'json'
|
25
25
|
s.add_runtime_dependency 'metriks'
|
26
|
+
s.add_runtime_dependency 'opentracing', '~> 0.3'
|
27
|
+
s.add_runtime_dependency 'rack-tracer', '~> 0.4'
|
28
|
+
s.add_runtime_dependency 'spanmanager', '~> 0.3'
|
29
|
+
s.add_runtime_dependency 'jaeger-client', '~> 0.4'
|
26
30
|
|
27
|
-
s.add_development_dependency 'bundler'
|
31
|
+
s.add_development_dependency 'bundler'
|
28
32
|
s.add_development_dependency 'rake'
|
29
33
|
s.add_development_dependency 'rspec'
|
30
34
|
s.add_development_dependency 'rack-test'
|
35
|
+
s.add_development_dependency 'rubocop', '~> 0.52.1'
|
31
36
|
end
|
data/lib/dyndnsd/database.rb
CHANGED
@@ -4,27 +4,29 @@ require 'forwardable'
|
|
4
4
|
module Dyndnsd
|
5
5
|
class Database
|
6
6
|
extend Forwardable
|
7
|
-
|
7
|
+
|
8
8
|
def_delegators :@db, :[], :[]=, :each, :has_key?
|
9
|
-
|
9
|
+
|
10
10
|
def initialize(db_file)
|
11
11
|
@db_file = db_file
|
12
12
|
end
|
13
|
-
|
13
|
+
|
14
14
|
def load
|
15
15
|
if File.file?(@db_file)
|
16
|
-
@db = JSON.
|
16
|
+
@db = JSON.parse(File.open(@db_file, 'r', &:read))
|
17
17
|
else
|
18
18
|
@db = {}
|
19
19
|
end
|
20
20
|
@db_hash = @db.hash
|
21
21
|
end
|
22
|
-
|
22
|
+
|
23
23
|
def save
|
24
|
-
|
25
|
-
|
24
|
+
Helper.span('database_save') do |_span|
|
25
|
+
File.open(@db_file, 'w') { |f| JSON.dump(@db, f) }
|
26
|
+
@db_hash = @db.hash
|
27
|
+
end
|
26
28
|
end
|
27
|
-
|
29
|
+
|
28
30
|
def changed?
|
29
31
|
@db_hash != @db.hash
|
30
32
|
end
|
@@ -10,25 +10,25 @@ module Dyndnsd
|
|
10
10
|
@additional_zone_content = config['additional_zone_content']
|
11
11
|
end
|
12
12
|
|
13
|
-
def generate(
|
13
|
+
def generate(db)
|
14
14
|
out = []
|
15
15
|
out << "$TTL #{@ttl}"
|
16
16
|
out << "$ORIGIN #{@domain}."
|
17
|
-
out <<
|
18
|
-
out << "@ IN SOA #{@dns} #{@email_addr} ( #{
|
17
|
+
out << ''
|
18
|
+
out << "@ IN SOA #{@dns} #{@email_addr} ( #{db['serial']} 3h 5m 1w 1h )"
|
19
19
|
out << "@ IN NS #{@dns}"
|
20
|
-
out <<
|
21
|
-
|
22
|
-
|
20
|
+
out << ''
|
21
|
+
db['hosts'].each do |hostname, ips|
|
22
|
+
ips.each do |ip|
|
23
23
|
ip = IPAddr.new(ip).native
|
24
|
-
type = ip.ipv6? ?
|
24
|
+
type = ip.ipv6? ? 'AAAA' : 'A'
|
25
25
|
name = hostname.chomp('.' + @domain)
|
26
26
|
out << "#{name} IN #{type} #{ip}"
|
27
27
|
end
|
28
28
|
end
|
29
|
-
out <<
|
29
|
+
out << ''
|
30
30
|
out << @additional_zone_content
|
31
|
-
out <<
|
31
|
+
out << ''
|
32
32
|
out.join("\n")
|
33
33
|
end
|
34
34
|
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
|
2
|
+
require 'ipaddr'
|
3
|
+
|
4
|
+
module Dyndnsd
|
5
|
+
class Helper
|
6
|
+
def self.fqdn_valid?(hostname, domain)
|
7
|
+
return false if hostname.length < domain.length + 2
|
8
|
+
return false if !hostname.end_with?(domain)
|
9
|
+
name = hostname.chomp(domain)
|
10
|
+
return false if !name.match(/^[a-zA-Z0-9_-]+\.$/)
|
11
|
+
true
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.ip_valid?(ip)
|
15
|
+
IPAddr.new(ip)
|
16
|
+
return true
|
17
|
+
rescue ArgumentError
|
18
|
+
return false
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.user_allowed?(username, password, users)
|
22
|
+
(users.key? username) && (users[username]['password'] == password)
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.changed?(hostname, myips, hosts)
|
26
|
+
# myips order is always deterministic
|
27
|
+
((!hosts.include? hostname) || (hosts[hostname] != myips)) && !myips.empty?
|
28
|
+
end
|
29
|
+
|
30
|
+
def self.span(operation, &block)
|
31
|
+
span = OpenTracing.start_span(operation)
|
32
|
+
span.set_tag('component', 'dyndnsd')
|
33
|
+
span.set_tag('span.kind', 'server')
|
34
|
+
begin
|
35
|
+
block.call(span)
|
36
|
+
ensure
|
37
|
+
span.finish
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -2,19 +2,52 @@
|
|
2
2
|
module Dyndnsd
|
3
3
|
module Responder
|
4
4
|
class DynDNSStyle
|
5
|
-
def
|
6
|
-
|
7
|
-
return [405, {"Content-Type" => "text/plain"}, ["Method Not Allowed"]] if state == :method_forbidden
|
8
|
-
return [404, {"Content-Type" => "text/plain"}, ["Not Found"]] if state == :not_found
|
9
|
-
# specific errors
|
10
|
-
return [200, {"Content-Type" => "text/plain"}, ["notfqdn"]] if state == :hostname_missing
|
11
|
-
return [200, {"Content-Type" => "text/plain"}, ["nohost"]] if state == :host_forbidden
|
12
|
-
return [200, {"Content-Type" => "text/plain"}, ["notfqdn"]] if state == :hostname_malformed
|
5
|
+
def initialize(app)
|
6
|
+
@app = app
|
13
7
|
end
|
14
8
|
|
15
|
-
def
|
16
|
-
|
17
|
-
|
9
|
+
def call(env)
|
10
|
+
@app.call(env).tap do |status_code, headers, body|
|
11
|
+
if headers.key?('X-DynDNS-Response')
|
12
|
+
return decorate_dyndnsd_response(status_code, headers, body)
|
13
|
+
else
|
14
|
+
return decorate_other_response(status_code, headers, body)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def decorate_dyndnsd_response(status_code, headers, body)
|
22
|
+
if status_code == 200
|
23
|
+
[200, {'Content-Type' => 'text/plain'}, [get_success_body(body[0], body[1])]]
|
24
|
+
elsif status_code == 422
|
25
|
+
error_response_map[headers['X-DynDNS-Response']]
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def decorate_other_response(status_code, headers, _body)
|
30
|
+
if status_code == 400
|
31
|
+
[status_code, headers, ['Bad Request']]
|
32
|
+
elsif status_code == 401
|
33
|
+
[status_code, headers, ['badauth']]
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def get_success_body(changes, myips)
|
38
|
+
changes.map { |change| "#{change} #{myips.join(' ')}" }.join("\n")
|
39
|
+
end
|
40
|
+
|
41
|
+
def error_response_map
|
42
|
+
{
|
43
|
+
# general http errors
|
44
|
+
'method_forbidden' => [405, {'Content-Type' => 'text/plain'}, ['Method Not Allowed']],
|
45
|
+
'not_found' => [404, {'Content-Type' => 'text/plain'}, ['Not Found']],
|
46
|
+
# specific errors
|
47
|
+
'hostname_missing' => [200, {'Content-Type' => 'text/plain'}, ['notfqdn']],
|
48
|
+
'hostname_malformed' => [200, {'Content-Type' => 'text/plain'}, ['notfqdn']],
|
49
|
+
'host_forbidden' => [200, {'Content-Type' => 'text/plain'}, ['nohost']]
|
50
|
+
}
|
18
51
|
end
|
19
52
|
end
|
20
53
|
end
|
@@ -2,19 +2,52 @@
|
|
2
2
|
module Dyndnsd
|
3
3
|
module Responder
|
4
4
|
class RestStyle
|
5
|
-
def
|
6
|
-
|
7
|
-
return [405, {"Content-Type" => "text/plain"}, ["Method Not Allowed"]] if state == :method_forbidden
|
8
|
-
return [404, {"Content-Type" => "text/plain"}, ["Not Found"]] if state == :not_found
|
9
|
-
# specific errors
|
10
|
-
return [422, {"Content-Type" => "text/plain"}, ["Hostname missing"]] if state == :hostname_missing
|
11
|
-
return [403, {"Content-Type" => "text/plain"}, ["Forbidden"]] if state == :host_forbidden
|
12
|
-
return [422, {"Content-Type" => "text/plain"}, ["Hostname malformed"]] if state == :hostname_malformed
|
5
|
+
def initialize(app)
|
6
|
+
@app = app
|
13
7
|
end
|
14
8
|
|
15
|
-
def
|
16
|
-
|
17
|
-
|
9
|
+
def call(env)
|
10
|
+
@app.call(env).tap do |status_code, headers, body|
|
11
|
+
if headers.key?('X-DynDNS-Response')
|
12
|
+
return decorate_dyndnsd_response(status_code, headers, body)
|
13
|
+
else
|
14
|
+
return decorate_other_response(status_code, headers, body)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def decorate_dyndnsd_response(status_code, headers, body)
|
22
|
+
if status_code == 200
|
23
|
+
[200, {'Content-Type' => 'text/plain'}, [get_success_body(body[0], body[1])]]
|
24
|
+
elsif status_code == 422
|
25
|
+
error_response_map[headers['X-DynDNS-Response']]
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def decorate_other_response(status_code, headers, _body)
|
30
|
+
if status_code == 400
|
31
|
+
[status_code, headers, ['Bad Request']]
|
32
|
+
elsif status_code == 401
|
33
|
+
[status_code, headers, ['Unauthorized']]
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def get_success_body(changes, myips)
|
38
|
+
changes.map { |change| change == :good ? "Changed to #{myips.join(' ')}" : "No change needed for #{myips.join(' ')}" }.join("\n")
|
39
|
+
end
|
40
|
+
|
41
|
+
def error_response_map
|
42
|
+
{
|
43
|
+
# general http errors
|
44
|
+
'method_forbidden' => [405, {'Content-Type' => 'text/plain'}, ['Method Not Allowed']],
|
45
|
+
'not_found' => [404, {'Content-Type' => 'text/plain'}, ['Not Found']],
|
46
|
+
# specific errors
|
47
|
+
'hostname_missing' => [422, {'Content-Type' => 'text/plain'}, ['Hostname missing']],
|
48
|
+
'hostname_malformed' => [422, {'Content-Type' => 'text/plain'}, ['Hostname malformed']],
|
49
|
+
'host_forbidden' => [403, {'Content-Type' => 'text/plain'}, ['Forbidden']]
|
50
|
+
}
|
18
51
|
end
|
19
52
|
end
|
20
53
|
end
|
@@ -7,16 +7,21 @@ module Dyndnsd
|
|
7
7
|
@command = config['command']
|
8
8
|
@generator = Generator::Bind.new(domain, config)
|
9
9
|
end
|
10
|
-
|
10
|
+
|
11
11
|
def update(zone)
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
12
|
+
Helper.span('updater_update') do |span|
|
13
|
+
span.set_tag('dyndnsd.updater.name', self.class.name.split('::').last)
|
14
|
+
|
15
|
+
# write zone file in bind syntax
|
16
|
+
File.open(@zone_file, 'w') { |f| f.write(@generator.generate(zone)) }
|
17
|
+
# call user-defined command
|
18
|
+
pid = fork do
|
19
|
+
exec @command
|
20
|
+
end
|
21
|
+
|
22
|
+
# detach so children don't become zombies
|
23
|
+
Process.detach(pid)
|
17
24
|
end
|
18
|
-
# detach so children don't become zombies
|
19
|
-
Process.detach(pid)
|
20
25
|
end
|
21
26
|
end
|
22
27
|
end
|
data/lib/dyndnsd/version.rb
CHANGED
data/lib/dyndnsd.rb
CHANGED
@@ -8,12 +8,16 @@ require 'yaml'
|
|
8
8
|
require 'rack'
|
9
9
|
require 'metriks'
|
10
10
|
require 'metriks/reporter/graphite'
|
11
|
+
require 'opentracing'
|
12
|
+
require 'rack/tracer'
|
13
|
+
require 'spanmanager'
|
11
14
|
|
12
15
|
require 'dyndnsd/generator/bind'
|
13
16
|
require 'dyndnsd/updater/command_with_bind_zone'
|
14
17
|
require 'dyndnsd/responder/dyndns_style'
|
15
18
|
require 'dyndnsd/responder/rest_style'
|
16
19
|
require 'dyndnsd/database'
|
20
|
+
require 'dyndnsd/helper'
|
17
21
|
require 'dyndnsd/version'
|
18
22
|
|
19
23
|
module Dyndnsd
|
@@ -26,154 +30,203 @@ module Dyndnsd
|
|
26
30
|
end
|
27
31
|
|
28
32
|
class LogFormatter
|
29
|
-
def call(lvl,
|
30
|
-
"[%s] %-5s %s\n"
|
33
|
+
def call(lvl, _time, _progname, msg)
|
34
|
+
format("[%s] %-5s %s\n", Time.now.strftime('%Y-%m-%d %H:%M:%S'), lvl, msg.to_s)
|
31
35
|
end
|
32
36
|
end
|
33
37
|
|
34
38
|
class Daemon
|
35
|
-
def initialize(config, db, updater
|
39
|
+
def initialize(config, db, updater)
|
36
40
|
@users = config['users']
|
37
41
|
@domain = config['domain']
|
38
42
|
@db = db
|
39
43
|
@updater = updater
|
40
|
-
@responder = responder
|
41
44
|
|
42
45
|
@db.load
|
43
46
|
@db['serial'] ||= 1
|
44
47
|
@db['hosts'] ||= {}
|
45
|
-
|
48
|
+
if @db.changed?
|
49
|
+
@db.save
|
50
|
+
@updater.update(@db)
|
51
|
+
end
|
46
52
|
end
|
47
53
|
|
48
|
-
def
|
49
|
-
|
50
|
-
|
54
|
+
def authorized?(username, password)
|
55
|
+
Helper.span('check_authorized') do |span|
|
56
|
+
span.set_tag('dyndnsd.user', username)
|
51
57
|
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
+
allow = Helper.user_allowed?(username, password, @users)
|
59
|
+
if !allow
|
60
|
+
Dyndnsd.logger.warn "Login failed for #{username}"
|
61
|
+
Metriks.meter('requests.auth_failed').mark
|
62
|
+
end
|
63
|
+
allow
|
64
|
+
end
|
58
65
|
end
|
59
66
|
|
60
67
|
def call(env)
|
61
|
-
return
|
62
|
-
return
|
68
|
+
return [422, {'X-DynDNS-Response' => 'method_forbidden'}, []] if env['REQUEST_METHOD'] != 'GET'
|
69
|
+
return [422, {'X-DynDNS-Response' => 'not_found'}, []] if env['PATH_INFO'] != '/nic/update'
|
63
70
|
|
64
|
-
|
71
|
+
handle_dyndns_request(env)
|
72
|
+
end
|
65
73
|
|
66
|
-
|
74
|
+
def self.run!
|
75
|
+
if ARGV.length != 1
|
76
|
+
puts 'Usage: dyndnsd config_file'
|
77
|
+
exit 1
|
78
|
+
end
|
67
79
|
|
68
|
-
|
80
|
+
config_file = ARGV[0]
|
69
81
|
|
70
|
-
|
71
|
-
|
72
|
-
|
82
|
+
if !File.file?(config_file)
|
83
|
+
puts 'Config file not found!'
|
84
|
+
exit 1
|
73
85
|
end
|
74
86
|
|
75
|
-
|
87
|
+
puts "DynDNSd version #{Dyndnsd::VERSION}"
|
88
|
+
puts "Using config file #{config_file}"
|
76
89
|
|
77
|
-
|
78
|
-
return @responder.response_for_error(:host_forbidden) if not @users[user]['hosts'].include? hostname
|
79
|
-
end
|
90
|
+
config = YAML.safe_load(File.open(config_file, 'r', &:read))
|
80
91
|
|
81
|
-
|
82
|
-
|
83
|
-
if params.has_key?("myip6")
|
84
|
-
# require presence of myip parameter as valid IPAddr (v4) and valid myip6
|
85
|
-
return @responder.response_for_error(:host_forbidden) if not params["myip"]
|
86
|
-
begin
|
87
|
-
IPAddr.new(params["myip"], Socket::AF_INET)
|
88
|
-
IPAddr.new(params["myip6"], Socket::AF_INET6)
|
89
|
-
|
90
|
-
# myip will be an array
|
91
|
-
myip = [params["myip"], params["myip6"]]
|
92
|
-
rescue ArgumentError
|
93
|
-
return @responder.response_for_error(:host_forbidden)
|
94
|
-
end
|
95
|
-
else
|
96
|
-
# fallback value, always present
|
97
|
-
myip = env["REMOTE_ADDR"]
|
98
|
-
|
99
|
-
# check whether X-Real-IP header has valid IPAddr
|
100
|
-
if env.has_key?("HTTP_X_REAL_IP")
|
101
|
-
begin
|
102
|
-
IPAddr.new(env["HTTP_X_REAL_IP"])
|
103
|
-
myip = env["HTTP_X_REAL_IP"]
|
104
|
-
rescue ArgumentError
|
105
|
-
end
|
106
|
-
end
|
92
|
+
setup_logger(config)
|
107
93
|
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
94
|
+
Dyndnsd.logger.info 'Starting...'
|
95
|
+
|
96
|
+
# drop priviliges as soon as possible
|
97
|
+
# NOTE: first change group than user
|
98
|
+
Process::Sys.setgid(Etc.getgrnam(config['group']).gid) if config['group']
|
99
|
+
Process::Sys.setuid(Etc.getpwnam(config['user']).uid) if config['user']
|
100
|
+
|
101
|
+
setup_traps
|
102
|
+
|
103
|
+
setup_monitoring(config)
|
104
|
+
|
105
|
+
setup_tracing(config)
|
106
|
+
|
107
|
+
setup_rack(config)
|
108
|
+
end
|
109
|
+
|
110
|
+
private
|
111
|
+
|
112
|
+
def extract_v4_and_v6_address(params)
|
113
|
+
return [] if !(params['myip'])
|
114
|
+
begin
|
115
|
+
IPAddr.new(params['myip'], Socket::AF_INET)
|
116
|
+
IPAddr.new(params['myip6'], Socket::AF_INET6)
|
117
|
+
[params['myip'], params['myip6']]
|
118
|
+
rescue ArgumentError
|
119
|
+
[]
|
116
120
|
end
|
121
|
+
end
|
117
122
|
|
118
|
-
|
119
|
-
|
123
|
+
def extract_myips(env, params)
|
124
|
+
# require presence of myip parameter as valid IPAddr (v4) and valid myip6
|
125
|
+
return extract_v4_and_v6_address(params) if params.key?('myip6')
|
126
|
+
|
127
|
+
# check whether myip parameter has valid IPAddr
|
128
|
+
return [params['myip']] if params.key?('myip') && Helper.ip_valid?(params['myip'])
|
129
|
+
|
130
|
+
# check whether X-Real-IP header has valid IPAddr
|
131
|
+
return [env['HTTP_X_REAL_IP']] if env.key?('HTTP_X_REAL_IP') && Helper.ip_valid?(env['HTTP_X_REAL_IP'])
|
132
|
+
|
133
|
+
# fallback value, always present
|
134
|
+
[env['REMOTE_ADDR']]
|
135
|
+
end
|
120
136
|
|
137
|
+
def process_changes(hostnames, myips)
|
121
138
|
changes = []
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
139
|
+
Helper.span('process_changes') do |span|
|
140
|
+
span.set_tag('dyndnsd.hostnames', hostnames.join(','))
|
141
|
+
|
142
|
+
hostnames.each do |hostname|
|
143
|
+
# myips order is always deterministic
|
144
|
+
if myips.empty? && @db['hosts'].include?(hostname)
|
145
|
+
@db['hosts'].delete(hostname)
|
146
|
+
changes << :good
|
147
|
+
Metriks.meter('requests.good').mark
|
148
|
+
elsif Helper.changed?(hostname, myips, @db['hosts'])
|
149
|
+
@db['hosts'][hostname] = myips
|
150
|
+
changes << :good
|
151
|
+
Metriks.meter('requests.good').mark
|
152
|
+
else
|
153
|
+
changes << :nochg
|
154
|
+
Metriks.meter('requests.nochg').mark
|
155
|
+
end
|
130
156
|
end
|
131
157
|
end
|
158
|
+
changes
|
159
|
+
end
|
132
160
|
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
end
|
140
|
-
|
141
|
-
@responder.response_for_changes(changes, myip)
|
161
|
+
def update_db
|
162
|
+
@db['serial'] += 1
|
163
|
+
Dyndnsd.logger.info "Committing update ##{@db['serial']}"
|
164
|
+
@db.save
|
165
|
+
@updater.update(@db)
|
166
|
+
Metriks.meter('updates.committed').mark
|
142
167
|
end
|
143
168
|
|
144
|
-
def
|
145
|
-
|
146
|
-
puts "Usage: dyndnsd config_file"
|
147
|
-
exit 1
|
148
|
-
end
|
169
|
+
def handle_dyndns_request(env)
|
170
|
+
params = Rack::Utils.parse_query(env['QUERY_STRING'])
|
149
171
|
|
150
|
-
|
172
|
+
# require hostname parameter
|
173
|
+
return [422, {'X-DynDNS-Response' => 'hostname_missing'}, []] if !(params['hostname'])
|
151
174
|
|
152
|
-
|
153
|
-
|
154
|
-
|
175
|
+
hostnames = params['hostname'].split(',')
|
176
|
+
|
177
|
+
# check for invalid hostnames
|
178
|
+
invalid_hostnames = hostnames.select { |h| !Helper.fqdn_valid?(h, @domain) }
|
179
|
+
return [422, {'X-DynDNS-Response' => 'hostname_malformed'}, []] if invalid_hostnames.any?
|
180
|
+
|
181
|
+
user = env['REMOTE_USER']
|
182
|
+
|
183
|
+
# check for hostnames that the user does not own
|
184
|
+
forbidden_hostnames = hostnames - @users[user]['hosts']
|
185
|
+
return [422, {'X-DynDNS-Response' => 'host_forbidden'}, []] if forbidden_hostnames.any?
|
186
|
+
|
187
|
+
if params['offline'] == 'YES'
|
188
|
+
myips = []
|
189
|
+
else
|
190
|
+
myips = extract_myips(env, params)
|
191
|
+
# require at least one IP to update
|
192
|
+
return [422, {'X-DynDNS-Response' => 'host_forbidden'}, []] if myips.empty?
|
155
193
|
end
|
156
194
|
|
157
|
-
|
158
|
-
|
195
|
+
Metriks.meter('requests.valid').mark
|
196
|
+
Dyndnsd.logger.info "Request to update #{hostnames} to #{myips} for user #{user}"
|
197
|
+
|
198
|
+
changes = process_changes(hostnames, myips)
|
159
199
|
|
160
|
-
|
200
|
+
update_db if @db.changed?
|
161
201
|
|
202
|
+
[200, {'X-DynDNS-Response' => 'success'}, [changes, myips]]
|
203
|
+
end
|
204
|
+
|
205
|
+
# SETUP
|
206
|
+
|
207
|
+
private_class_method def self.setup_logger(config)
|
162
208
|
if config['logfile']
|
163
209
|
Dyndnsd.logger = Logger.new(config['logfile'])
|
164
210
|
else
|
165
211
|
Dyndnsd.logger = Logger.new(STDOUT)
|
166
212
|
end
|
167
213
|
|
168
|
-
Dyndnsd.logger.progname =
|
214
|
+
Dyndnsd.logger.progname = 'dyndnsd'
|
169
215
|
Dyndnsd.logger.formatter = LogFormatter.new
|
216
|
+
end
|
170
217
|
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
218
|
+
private_class_method def self.setup_traps
|
219
|
+
Signal.trap('INT') do
|
220
|
+
Dyndnsd.logger.info 'Quitting...'
|
221
|
+
Rack::Handler::WEBrick.shutdown
|
222
|
+
end
|
223
|
+
Signal.trap('TERM') do
|
224
|
+
Dyndnsd.logger.info 'Quitting...'
|
225
|
+
Rack::Handler::WEBrick.shutdown
|
226
|
+
end
|
227
|
+
end
|
176
228
|
|
229
|
+
private_class_method def self.setup_monitoring(config)
|
177
230
|
# configure metriks
|
178
231
|
if config['graphite']
|
179
232
|
host = config['graphite']['host'] || 'localhost'
|
@@ -192,33 +245,43 @@ module Dyndnsd
|
|
192
245
|
end
|
193
246
|
reporter.start
|
194
247
|
end
|
248
|
+
end
|
249
|
+
|
250
|
+
private_class_method def self.setup_tracing(config)
|
251
|
+
# configure OpenTracing
|
252
|
+
if config.dig('tracing', 'jaeger')
|
253
|
+
require 'jaeger/client'
|
254
|
+
|
255
|
+
host = config['tracing']['jaeger']['host'] || '127.0.0.1'
|
256
|
+
port = config['tracing']['jaeger']['port'] || 6831
|
257
|
+
service_name = config['tracing']['jaeger']['service_name'] || 'dyndnsd'
|
258
|
+
OpenTracing.global_tracer = Jaeger::Client.build(
|
259
|
+
host: host, port: port, service_name: service_name, flush_interval: 1
|
260
|
+
)
|
261
|
+
end
|
262
|
+
# always use SpanManager
|
263
|
+
OpenTracing.global_tracer = SpanManager::Tracer.new(OpenTracing.global_tracer)
|
264
|
+
end
|
195
265
|
|
266
|
+
private_class_method def self.setup_rack(config)
|
196
267
|
# configure daemon
|
197
268
|
db = Database.new(config['db'])
|
198
269
|
updater = Updater::CommandWithBindZone.new(config['domain'], config['updater']['params']) if config['updater']['name'] == 'command_with_bind_zone'
|
199
|
-
|
270
|
+
daemon = Daemon.new(config, db, updater)
|
200
271
|
|
201
272
|
# configure rack
|
202
|
-
app =
|
203
|
-
app = Rack::Auth::Basic.new(app, "DynDNS") do |user,pass|
|
204
|
-
allow = ((config['users'].has_key? user) and (config['users'][user]['password'] == pass))
|
205
|
-
if not allow
|
206
|
-
Dyndnsd.logger.warn "Login failed for #{user}"
|
207
|
-
Metriks.meter('requests.auth_failed').mark
|
208
|
-
end
|
209
|
-
allow
|
210
|
-
end
|
273
|
+
app = Rack::Auth::Basic.new(daemon, 'DynDNS', &daemon.method(:authorized?))
|
211
274
|
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
Signal.trap('TERM') do
|
217
|
-
Dyndnsd.logger.info "Quitting..."
|
218
|
-
Rack::Handler::WEBrick.shutdown
|
275
|
+
if config['responder'] == 'RestStyle'
|
276
|
+
app = Responder::RestStyle.new(app)
|
277
|
+
else
|
278
|
+
app = Responder::DynDNSStyle.new(app)
|
219
279
|
end
|
220
280
|
|
221
|
-
|
281
|
+
trust_incoming_span = config.dig('tracing', 'trust_incoming_span') || false
|
282
|
+
app = Rack::Tracer.new(app, trust_incoming_span: trust_incoming_span)
|
283
|
+
|
284
|
+
Rack::Handler::WEBrick.run app, Host: config['host'], Port: config['port']
|
222
285
|
end
|
223
286
|
end
|
224
287
|
end
|
data/spec/daemon_spec.rb
CHANGED
@@ -18,20 +18,25 @@ describe Dyndnsd::Daemon do
|
|
18
18
|
}
|
19
19
|
db = Dyndnsd::DummyDatabase.new({})
|
20
20
|
updater = Dyndnsd::Updater::Dummy.new
|
21
|
-
|
22
|
-
app = Dyndnsd::Daemon.new(config, db, updater, responder)
|
21
|
+
daemon = Dyndnsd::Daemon.new(config, db, updater)
|
23
22
|
|
24
|
-
Rack::Auth::Basic.new(
|
25
|
-
|
26
|
-
|
23
|
+
app = Rack::Auth::Basic.new(daemon, 'DynDNS', &daemon.method(:authorized?))
|
24
|
+
|
25
|
+
app = Dyndnsd::Responder::DynDNSStyle.new(app)
|
26
|
+
|
27
|
+
Rack::Tracer.new(app, trust_incoming_span: false)
|
27
28
|
end
|
28
29
|
|
29
30
|
it 'requires authentication' do
|
30
31
|
get '/'
|
31
32
|
expect(last_response.status).to eq(401)
|
33
|
+
expect(last_response.body).to eq('badauth')
|
34
|
+
end
|
32
35
|
|
33
|
-
|
34
|
-
|
36
|
+
it 'requires configured correct credentials' do
|
37
|
+
authorize 'test', 'wrongsecret'
|
38
|
+
get '/'
|
39
|
+
expect(last_response.status).to eq(401)
|
35
40
|
expect(last_response.body).to eq('badauth')
|
36
41
|
end
|
37
42
|
|
@@ -96,6 +101,7 @@ describe Dyndnsd::Daemon do
|
|
96
101
|
|
97
102
|
it 'rejects request if user does not own one hostname' do
|
98
103
|
authorize 'test', 'secret'
|
104
|
+
|
99
105
|
get '/nic/update?hostname=notmyhost.example.org'
|
100
106
|
expect(last_response).to be_ok
|
101
107
|
expect(last_response.body).to eq('nohost')
|
@@ -161,6 +167,34 @@ describe Dyndnsd::Daemon do
|
|
161
167
|
expect(last_response.body).to eq("nochg 2001:db8::1\ngood 2001:db8::1")
|
162
168
|
end
|
163
169
|
|
170
|
+
it 'offlines a host' do
|
171
|
+
authorize 'test', 'secret'
|
172
|
+
|
173
|
+
get '/nic/update?hostname=foo.example.org&myip=1.2.3.4'
|
174
|
+
expect(last_response).to be_ok
|
175
|
+
expect(last_response.body).to eq('good 1.2.3.4')
|
176
|
+
|
177
|
+
get '/nic/update?hostname=foo.example.org&offline=YES'
|
178
|
+
expect(last_response).to be_ok
|
179
|
+
expect(last_response.body).to eq('good ')
|
180
|
+
|
181
|
+
get '/nic/update?hostname=foo.example.org&offline=YES'
|
182
|
+
expect(last_response).to be_ok
|
183
|
+
expect(last_response.body).to eq('nochg ')
|
184
|
+
|
185
|
+
get '/nic/update?hostname=foo.example.org&myip=1.2.3.4'
|
186
|
+
expect(last_response).to be_ok
|
187
|
+
expect(last_response.body).to eq('good 1.2.3.4')
|
188
|
+
|
189
|
+
get '/nic/update?hostname=foo.example.org&myip=1.2.3.4&offline=YES'
|
190
|
+
expect(last_response).to be_ok
|
191
|
+
expect(last_response.body).to eq('good ')
|
192
|
+
|
193
|
+
get '/nic/update?hostname=foo.example.org&myip=1.2.3.4&offline=YES'
|
194
|
+
expect(last_response).to be_ok
|
195
|
+
expect(last_response.body).to eq('nochg ')
|
196
|
+
end
|
197
|
+
|
164
198
|
it 'uses clients remote IP address if myip not specified' do
|
165
199
|
authorize 'test', 'secret'
|
166
200
|
get '/nic/update?hostname=foo.example.org'
|
@@ -185,7 +219,7 @@ describe Dyndnsd::Daemon do
|
|
185
219
|
|
186
220
|
get '/nic/update?hostname=foo.example.org&myip=1.2.3.4&myip6=2001:db8::1'
|
187
221
|
expect(last_response).to be_ok
|
188
|
-
expect(last_response.body).to eq(
|
222
|
+
expect(last_response.body).to eq('good 1.2.3.4 2001:db8::1')
|
189
223
|
|
190
224
|
get '/nic/update?hostname=foo.example.org&myip=BROKENIP&myip6=2001:db8::1'
|
191
225
|
expect(last_response).to be_ok
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: dyndnsd
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 2.0.0.rc1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Christian Nicolai
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2018-03-26 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rack
|
@@ -16,28 +16,28 @@ dependencies:
|
|
16
16
|
requirements:
|
17
17
|
- - "~>"
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: '
|
19
|
+
version: '2.0'
|
20
20
|
type: :runtime
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
24
|
- - "~>"
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version: '
|
26
|
+
version: '2.0'
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: json
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
30
30
|
requirements:
|
31
|
-
- - "
|
31
|
+
- - ">="
|
32
32
|
- !ruby/object:Gem::Version
|
33
|
-
version: '
|
33
|
+
version: '0'
|
34
34
|
type: :runtime
|
35
35
|
prerelease: false
|
36
36
|
version_requirements: !ruby/object:Gem::Requirement
|
37
37
|
requirements:
|
38
|
-
- - "
|
38
|
+
- - ">="
|
39
39
|
- !ruby/object:Gem::Version
|
40
|
-
version: '
|
40
|
+
version: '0'
|
41
41
|
- !ruby/object:Gem::Dependency
|
42
42
|
name: metriks
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|
@@ -53,19 +53,75 @@ dependencies:
|
|
53
53
|
- !ruby/object:Gem::Version
|
54
54
|
version: '0'
|
55
55
|
- !ruby/object:Gem::Dependency
|
56
|
-
name:
|
56
|
+
name: opentracing
|
57
57
|
requirement: !ruby/object:Gem::Requirement
|
58
58
|
requirements:
|
59
59
|
- - "~>"
|
60
60
|
- !ruby/object:Gem::Version
|
61
|
-
version: '
|
62
|
-
type: :
|
61
|
+
version: '0.3'
|
62
|
+
type: :runtime
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0.3'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: rack-tracer
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0.4'
|
76
|
+
type: :runtime
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0.4'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: spanmanager
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - "~>"
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0.3'
|
90
|
+
type: :runtime
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - "~>"
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0.3'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: jaeger-client
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - "~>"
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '0.4'
|
104
|
+
type: :runtime
|
63
105
|
prerelease: false
|
64
106
|
version_requirements: !ruby/object:Gem::Requirement
|
65
107
|
requirements:
|
66
108
|
- - "~>"
|
67
109
|
- !ruby/object:Gem::Version
|
68
|
-
version: '
|
110
|
+
version: '0.4'
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: bundler
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - ">="
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '0'
|
118
|
+
type: :development
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - ">="
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: '0'
|
69
125
|
- !ruby/object:Gem::Dependency
|
70
126
|
name: rake
|
71
127
|
requirement: !ruby/object:Gem::Requirement
|
@@ -108,6 +164,20 @@ dependencies:
|
|
108
164
|
- - ">="
|
109
165
|
- !ruby/object:Gem::Version
|
110
166
|
version: '0'
|
167
|
+
- !ruby/object:Gem::Dependency
|
168
|
+
name: rubocop
|
169
|
+
requirement: !ruby/object:Gem::Requirement
|
170
|
+
requirements:
|
171
|
+
- - "~>"
|
172
|
+
- !ruby/object:Gem::Version
|
173
|
+
version: 0.52.1
|
174
|
+
type: :development
|
175
|
+
prerelease: false
|
176
|
+
version_requirements: !ruby/object:Gem::Requirement
|
177
|
+
requirements:
|
178
|
+
- - "~>"
|
179
|
+
- !ruby/object:Gem::Version
|
180
|
+
version: 0.52.1
|
111
181
|
description: A small, lightweight and extensible DynDNS server written with Ruby and
|
112
182
|
Rack.
|
113
183
|
email: chrnicolai@gmail.com
|
@@ -117,7 +187,10 @@ extensions: []
|
|
117
187
|
extra_rdoc_files: []
|
118
188
|
files:
|
119
189
|
- ".gitignore"
|
190
|
+
- ".rubocop.yml"
|
191
|
+
- ".rubocop_todo.yml"
|
120
192
|
- ".travis.yml"
|
193
|
+
- CHANGELOG.md
|
121
194
|
- Gemfile
|
122
195
|
- LICENSE
|
123
196
|
- README.md
|
@@ -128,6 +201,7 @@ files:
|
|
128
201
|
- lib/dyndnsd.rb
|
129
202
|
- lib/dyndnsd/database.rb
|
130
203
|
- lib/dyndnsd/generator/bind.rb
|
204
|
+
- lib/dyndnsd/helper.rb
|
131
205
|
- lib/dyndnsd/responder/dyndns_style.rb
|
132
206
|
- lib/dyndnsd/responder/rest_style.rb
|
133
207
|
- lib/dyndnsd/updater/command_with_bind_zone.rb
|
@@ -138,7 +212,7 @@ files:
|
|
138
212
|
- spec/support/dummy_updater.rb
|
139
213
|
homepage: https://github.com/cmur2/dyndnsd
|
140
214
|
licenses:
|
141
|
-
- Apache
|
215
|
+
- Apache-2.0
|
142
216
|
metadata: {}
|
143
217
|
post_install_message:
|
144
218
|
rdoc_options: []
|
@@ -148,15 +222,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
148
222
|
requirements:
|
149
223
|
- - ">="
|
150
224
|
- !ruby/object:Gem::Version
|
151
|
-
version: '
|
225
|
+
version: '2.3'
|
152
226
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
153
227
|
requirements:
|
154
|
-
- - "
|
228
|
+
- - ">"
|
155
229
|
- !ruby/object:Gem::Version
|
156
|
-
version:
|
230
|
+
version: 1.3.1
|
157
231
|
requirements: []
|
158
232
|
rubyforge_project:
|
159
|
-
rubygems_version: 2.
|
233
|
+
rubygems_version: 2.7.5
|
160
234
|
signing_key:
|
161
235
|
specification_version: 4
|
162
236
|
summary: dyndnsd.rb
|