2013-06-15 21:06:14 +02:00
|
|
|
|
#!/usr/bin/ruby
|
|
|
|
|
|
|
|
|
|
require 'rubygems'
|
|
|
|
|
require 'sinatra'
|
|
|
|
|
require 'data_mapper'
|
|
|
|
|
require 'json'
|
2013-07-07 12:46:39 +02:00
|
|
|
|
require 'base64'
|
2017-07-24 01:55:54 +02:00
|
|
|
|
require 'bcrypt'
|
2013-06-15 21:06:14 +02:00
|
|
|
|
|
2016-04-26 12:18:16 +02:00
|
|
|
|
######################
|
|
|
|
|
### Configuration ###
|
|
|
|
|
######################
|
|
|
|
|
|
2013-08-07 12:40:49 +02:00
|
|
|
|
DataMapper.setup(:default, ENV['DATABASE_URL'] || "postgres://dynette:myPassword@localhost/dynette")
|
2013-06-19 09:52:35 +02:00
|
|
|
|
DOMAINS = ["nohost.me", "noho.st"]
|
2013-08-07 12:40:49 +02:00
|
|
|
|
ALLOWED_IP = ["127.0.0.1"]
|
2013-06-15 21:06:14 +02:00
|
|
|
|
|
2016-04-26 12:18:16 +02:00
|
|
|
|
|
|
|
|
|
###############
|
|
|
|
|
### Classes ###
|
|
|
|
|
###############
|
|
|
|
|
|
|
|
|
|
# Dynette Entry class
|
2013-06-15 21:06:14 +02:00
|
|
|
|
class Entry
|
|
|
|
|
include DataMapper::Resource
|
2017-07-24 01:55:54 +02:00
|
|
|
|
include BCrypt
|
2013-06-15 21:06:14 +02:00
|
|
|
|
|
|
|
|
|
property :id, Serial
|
|
|
|
|
property :public_key, String
|
2017-09-06 05:29:31 +02:00
|
|
|
|
|
|
|
|
|
# for historical reasons, dnssec algo was md5, so we assume that every
|
|
|
|
|
# entry is using md5 while we provide automatic upgrade code inside
|
2017-09-18 19:47:27 +02:00
|
|
|
|
# yunohost to move to sha512 instead (and register new domains using sh512)
|
2017-09-06 05:29:31 +02:00
|
|
|
|
# it would be good to depreciate md5 in the futur but that migh be complicated
|
|
|
|
|
property :key_algo, String, :default => "hmac-md5"
|
|
|
|
|
|
2013-06-15 21:06:14 +02:00
|
|
|
|
property :subdomain, String
|
|
|
|
|
property :current_ip, String
|
2013-06-16 10:21:06 +02:00
|
|
|
|
property :created_at, DateTime
|
2017-07-24 01:55:54 +02:00
|
|
|
|
property :recovery_password, Text
|
2013-06-15 21:06:14 +02:00
|
|
|
|
|
|
|
|
|
has n, :ips
|
|
|
|
|
end
|
|
|
|
|
|
2016-04-26 12:18:16 +02:00
|
|
|
|
# IP class
|
2013-06-15 21:06:14 +02:00
|
|
|
|
class Ip
|
|
|
|
|
include DataMapper::Resource
|
|
|
|
|
|
|
|
|
|
property :id, Serial
|
|
|
|
|
property :ip_addr, String
|
|
|
|
|
|
|
|
|
|
belongs_to :entry
|
|
|
|
|
end
|
|
|
|
|
|
2016-04-26 12:18:16 +02:00
|
|
|
|
# IP Log class
|
2013-06-16 00:13:29 +02:00
|
|
|
|
class Iplog
|
|
|
|
|
include DataMapper::Resource
|
|
|
|
|
|
|
|
|
|
property :ip_addr, String, :key => true
|
|
|
|
|
property :visited_at, DateTime
|
|
|
|
|
end
|
|
|
|
|
|
2016-04-26 12:18:16 +02:00
|
|
|
|
# IP ban class
|
2013-06-16 00:13:29 +02:00
|
|
|
|
class Ipban
|
|
|
|
|
include DataMapper::Resource
|
|
|
|
|
|
|
|
|
|
property :ip_addr, String, :key => true
|
|
|
|
|
end
|
|
|
|
|
|
2016-04-26 12:18:16 +02:00
|
|
|
|
|
|
|
|
|
################
|
|
|
|
|
### Handlers ###
|
|
|
|
|
################
|
|
|
|
|
|
|
|
|
|
# 404 Error handler
|
2013-06-16 10:21:06 +02:00
|
|
|
|
not_found do
|
|
|
|
|
content_type :json
|
|
|
|
|
halt 404, { :error => "Not found" }.to_json
|
|
|
|
|
end
|
|
|
|
|
|
2016-04-26 12:18:16 +02:00
|
|
|
|
|
|
|
|
|
##############
|
|
|
|
|
### Routes ###
|
|
|
|
|
##############
|
|
|
|
|
|
2016-04-26 09:24:33 +02:00
|
|
|
|
# Common tasks and settings for every route
|
2013-06-16 00:13:29 +02:00
|
|
|
|
before do
|
2016-04-26 09:56:20 +02:00
|
|
|
|
# Always return json
|
|
|
|
|
content_type :json
|
|
|
|
|
|
|
|
|
|
# Allow CORS
|
|
|
|
|
headers['Access-Control-Allow-Origin'] = '*'
|
|
|
|
|
|
2016-04-26 09:24:33 +02:00
|
|
|
|
# Ban IP on flood
|
2013-06-16 00:13:29 +02:00
|
|
|
|
if Ipban.first(:ip_addr => request.ip)
|
2016-04-26 09:56:20 +02:00
|
|
|
|
halt 410, { :error => "Your ip is banned from the service" }.to_json
|
2013-06-16 00:13:29 +02:00
|
|
|
|
end
|
2013-06-16 10:21:06 +02:00
|
|
|
|
unless %w[domains test all ban unban].include? request.path_info.split('/')[1]
|
|
|
|
|
if iplog = Iplog.last(:ip_addr => request.ip)
|
|
|
|
|
if iplog.visited_at.to_time > Time.now - 30
|
2016-04-26 09:56:20 +02:00
|
|
|
|
halt 410, { :error => "Please wait 30sec" }.to_json
|
2013-06-16 10:21:06 +02:00
|
|
|
|
else
|
|
|
|
|
iplog.update(:visited_at => Time.now)
|
|
|
|
|
end
|
2013-06-16 00:13:29 +02:00
|
|
|
|
else
|
2013-06-16 10:21:06 +02:00
|
|
|
|
Iplog.create(:ip_addr => request.ip, :visited_at => Time.now)
|
2013-06-16 00:13:29 +02:00
|
|
|
|
end
|
|
|
|
|
end
|
2016-04-26 09:24:33 +02:00
|
|
|
|
|
2013-06-16 10:21:06 +02:00
|
|
|
|
end
|
2013-06-16 09:45:02 +02:00
|
|
|
|
|
2013-06-16 10:21:06 +02:00
|
|
|
|
# Check params
|
|
|
|
|
['/test/:subdomain', '/key/:public_key', '/ips/:public_key', '/ban/:ip', '/unban/:ip' ].each do |path|
|
|
|
|
|
before path do
|
|
|
|
|
if params.has_key?("public_key")
|
2013-07-07 13:20:39 +02:00
|
|
|
|
public_key = Base64.decode64(params[:public_key].encode('ascii-8bit'))
|
2013-07-07 12:52:37 +02:00
|
|
|
|
unless public_key.length == 24
|
2013-07-07 13:00:21 +02:00
|
|
|
|
halt 400, { :error => "Key is invalid: #{public_key.to_s.encode('UTF-8', {:invalid => :replace, :undef => :replace, :replace => '?'})}" }.to_json
|
2013-06-16 10:21:06 +02:00
|
|
|
|
end
|
2013-06-16 09:45:02 +02:00
|
|
|
|
end
|
2013-06-16 10:21:06 +02:00
|
|
|
|
if params.has_key?("subdomain")
|
2014-05-09 16:33:50 +02:00
|
|
|
|
unless params[:subdomain].match /^([a-z0-9]{1}([a-z0-9\-]*[a-z0-9])*)(\.[a-z0-9]{1}([a-z0-9\-]*[a-z0-9])*)*(\.[a-z]{1}([a-z0-9\-]*[a-z0-9])*)$/
|
2013-06-16 10:21:06 +02:00
|
|
|
|
halt 400, { :error => "Subdomain is invalid: #{params[:subdomain]}" }.to_json
|
|
|
|
|
end
|
|
|
|
|
unless DOMAINS.include? params[:subdomain].gsub(params[:subdomain].split('.')[0]+'.', '')
|
|
|
|
|
halt 400, { :error => "Subdomain #{params[:subdomain]} is not part of available domains: #{DOMAINS.join(', ')}" }.to_json
|
|
|
|
|
end
|
2013-06-16 09:45:02 +02:00
|
|
|
|
end
|
2013-06-16 10:21:06 +02:00
|
|
|
|
if params.has_key?("ip")
|
|
|
|
|
unless params[:ip].match /^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/
|
|
|
|
|
halt 400, { :error => "IP is invalid: #{params[:ip]}" }.to_json
|
|
|
|
|
end
|
2013-06-16 09:45:02 +02:00
|
|
|
|
end
|
|
|
|
|
end
|
2013-06-16 00:13:29 +02:00
|
|
|
|
end
|
|
|
|
|
|
2016-04-26 12:18:16 +02:00
|
|
|
|
# Main page, return some basic text
|
2013-06-15 21:57:12 +02:00
|
|
|
|
get '/' do
|
2016-04-26 09:56:20 +02:00
|
|
|
|
content_type 'text/html'
|
2013-06-16 00:17:33 +02:00
|
|
|
|
"Wanna play the dynette ?"
|
2013-06-15 21:57:12 +02:00
|
|
|
|
end
|
2013-06-15 21:06:14 +02:00
|
|
|
|
|
2017-07-24 01:55:54 +02:00
|
|
|
|
# Delete interface for user with recovery password
|
|
|
|
|
get '/delete' do
|
|
|
|
|
f = File.open("delete.html", "r")
|
|
|
|
|
|
|
|
|
|
content_type 'text/html'
|
|
|
|
|
f.read
|
|
|
|
|
end
|
|
|
|
|
|
2016-04-26 12:18:16 +02:00
|
|
|
|
# Get availables DynDNS domains
|
2013-06-16 09:45:02 +02:00
|
|
|
|
get '/domains' do
|
|
|
|
|
DOMAINS.to_json
|
|
|
|
|
end
|
|
|
|
|
|
2016-04-26 12:18:16 +02:00
|
|
|
|
# Check for sub-domain vailability
|
2013-06-16 01:06:25 +02:00
|
|
|
|
get '/test/:subdomain' do
|
|
|
|
|
if entry = Entry.first(:subdomain => params[:subdomain])
|
2013-06-16 10:21:06 +02:00
|
|
|
|
halt 409, { :error => "Subdomain already taken: #{entry.subdomain}" }.to_json
|
2013-06-16 01:06:25 +02:00
|
|
|
|
else
|
2013-06-16 10:21:06 +02:00
|
|
|
|
halt 200, "Domain #{params[:subdomain]} is available".to_json
|
2013-06-16 01:06:25 +02:00
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
2016-04-26 12:18:16 +02:00
|
|
|
|
# Register a sub-domain
|
2013-06-16 10:21:06 +02:00
|
|
|
|
post '/key/:public_key' do
|
2013-07-07 13:28:56 +02:00
|
|
|
|
params[:public_key] = Base64.decode64(params[:public_key].encode('ascii-8bit'))
|
2013-06-15 21:57:12 +02:00
|
|
|
|
# Check params
|
2013-06-16 09:45:02 +02:00
|
|
|
|
halt 400, { :error => "Please indicate a subdomain" }.to_json unless params.has_key?("subdomain")
|
2013-06-15 21:57:12 +02:00
|
|
|
|
|
|
|
|
|
# If already exists
|
2013-06-15 22:19:33 +02:00
|
|
|
|
if entry = Entry.first(:subdomain => params[:subdomain])
|
2013-06-16 10:21:06 +02:00
|
|
|
|
halt 409, { :error => "Subdomain already taken: #{entry.subdomain}" }.to_json
|
2013-06-15 22:19:33 +02:00
|
|
|
|
end
|
|
|
|
|
if entry = Entry.first(:public_key => params[:public_key])
|
2013-06-16 10:21:06 +02:00
|
|
|
|
halt 409, { :error => "Key already exists for domain #{entry.subdomain}" }.to_json
|
2013-06-15 22:19:33 +02:00
|
|
|
|
end
|
2013-06-15 22:13:57 +02:00
|
|
|
|
|
2017-07-24 01:55:54 +02:00
|
|
|
|
# If user provided a recovery password, hash and salt it before storing it
|
|
|
|
|
if params.has_key?("recovery_password")
|
|
|
|
|
recovery_password = BCrypt::Password.create(params[:recovery_password])
|
|
|
|
|
else
|
|
|
|
|
recovery_password = ""
|
|
|
|
|
end
|
|
|
|
|
|
2013-06-15 22:13:57 +02:00
|
|
|
|
# Process
|
2017-07-24 01:55:54 +02:00
|
|
|
|
entry = Entry.new(:public_key => params[:public_key], :subdomain => params[:subdomain], :current_ip => request.ip, :created_at => Time.now, :recovery_password => recovery_password)
|
2013-06-15 21:57:12 +02:00
|
|
|
|
entry.ips << Ip.create(:ip_addr => request.ip)
|
2017-07-24 01:55:54 +02:00
|
|
|
|
|
2013-06-15 21:06:14 +02:00
|
|
|
|
if entry.save
|
2013-06-16 09:45:02 +02:00
|
|
|
|
halt 201, { :public_key => entry.public_key, :subdomain => entry.subdomain, :current_ip => entry.current_ip }.to_json
|
2013-06-15 21:06:14 +02:00
|
|
|
|
else
|
2013-06-16 09:45:02 +02:00
|
|
|
|
halt 412, { :error => "A problem occured during DNS registration" }.to_json
|
2013-06-15 21:06:14 +02:00
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
2016-04-26 12:18:16 +02:00
|
|
|
|
# Update a sub-domain
|
2013-06-16 10:21:06 +02:00
|
|
|
|
put '/key/:public_key' do
|
2013-07-07 13:28:56 +02:00
|
|
|
|
params[:public_key] = Base64.decode64(params[:public_key].encode('ascii-8bit'))
|
2013-06-15 22:13:57 +02:00
|
|
|
|
entry = Entry.first(:public_key => params[:public_key])
|
|
|
|
|
unless request.ip == entry.current_ip
|
|
|
|
|
entry.ips << Ip.create(:ip_addr => request.ip)
|
|
|
|
|
end
|
|
|
|
|
entry.current_ip = request.ip
|
|
|
|
|
if entry.save
|
2013-06-16 09:45:02 +02:00
|
|
|
|
halt 201, { :public_key => entry.public_key, :subdomain => entry.subdomain, :current_ip => entry.current_ip }.to_json
|
2013-06-15 22:13:57 +02:00
|
|
|
|
else
|
2013-06-16 09:45:02 +02:00
|
|
|
|
halt 412, { :error => "A problem occured during DNS update" }.to_json
|
2013-06-15 22:13:57 +02:00
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
2016-04-26 12:18:16 +02:00
|
|
|
|
# Delete a sub-domain from key
|
2013-06-16 10:21:06 +02:00
|
|
|
|
delete '/key/:public_key' do
|
2014-05-09 16:33:50 +02:00
|
|
|
|
unless ALLOWED_IP.include? request.ip
|
2016-04-26 09:56:20 +02:00
|
|
|
|
halt 403, { :error => "Access denied"}.to_json
|
2014-05-09 16:33:50 +02:00
|
|
|
|
end
|
2013-07-07 13:28:56 +02:00
|
|
|
|
params[:public_key] = Base64.decode64(params[:public_key].encode('ascii-8bit'))
|
2013-06-16 00:13:29 +02:00
|
|
|
|
if entry = Entry.first(:public_key => params[:public_key])
|
2014-05-09 16:33:50 +02:00
|
|
|
|
Ip.first(:entry_id => entry.id).destroy
|
|
|
|
|
if entry.destroy
|
|
|
|
|
halt 200, "OK".to_json
|
|
|
|
|
else
|
|
|
|
|
halt 412, { :error => "A problem occured during DNS deletion" }.to_json
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
2016-04-26 12:18:16 +02:00
|
|
|
|
# Delete a sub-domain
|
2014-05-09 16:33:50 +02:00
|
|
|
|
delete '/domains/:subdomain' do
|
2017-07-24 01:55:54 +02:00
|
|
|
|
unless (ALLOWED_IP.include? request.ip) || (params.has_key?("recovery_password"))
|
2016-04-26 09:56:20 +02:00
|
|
|
|
halt 403, { :error => "Access denied"}.to_json
|
2014-05-09 16:33:50 +02:00
|
|
|
|
end
|
|
|
|
|
if entry = Entry.first(:subdomain => params[:subdomain])
|
2017-07-24 01:55:54 +02:00
|
|
|
|
|
|
|
|
|
# For non-admin
|
|
|
|
|
unless (ALLOWED_IP.include? request.ip)
|
|
|
|
|
# If no recovery password was provided when registering domain,
|
|
|
|
|
# or if wrong password is provided, deny access
|
|
|
|
|
if (entry.recovery_password == "") || (BCrypt::Password.new(entry.recovery_password) != params[:recovery_password])
|
|
|
|
|
halt 403, { :error => "Access denied" }.to_json
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
|
2014-05-09 16:33:50 +02:00
|
|
|
|
Ip.first(:entry_id => entry.id).destroy
|
2013-06-16 09:45:02 +02:00
|
|
|
|
if entry.destroy
|
|
|
|
|
halt 200, "OK".to_json
|
|
|
|
|
else
|
|
|
|
|
halt 412, { :error => "A problem occured during DNS deletion" }.to_json
|
|
|
|
|
end
|
2013-06-16 00:13:29 +02:00
|
|
|
|
end
|
2017-07-24 01:55:54 +02:00
|
|
|
|
halt 404
|
2013-06-16 00:13:29 +02:00
|
|
|
|
end
|
|
|
|
|
|
2016-04-26 12:18:16 +02:00
|
|
|
|
# Get all registered sub-domains
|
2013-06-15 21:06:14 +02:00
|
|
|
|
get '/all' do
|
2013-07-07 10:03:15 +02:00
|
|
|
|
unless ALLOWED_IP.include? request.ip
|
2016-04-26 09:56:20 +02:00
|
|
|
|
halt 403, { :error => "Access denied"}.to_json
|
2013-06-15 21:06:14 +02:00
|
|
|
|
end
|
|
|
|
|
Entry.all.to_json
|
|
|
|
|
end
|
|
|
|
|
|
2016-04-26 12:18:16 +02:00
|
|
|
|
# Get all registered sub-domains for a specific DynDNS domain
|
2013-06-16 10:39:41 +02:00
|
|
|
|
get '/all/:domain' do
|
2013-07-07 10:03:15 +02:00
|
|
|
|
unless ALLOWED_IP.include? request.ip
|
2016-04-26 09:56:20 +02:00
|
|
|
|
halt 403, { :error => "Access denied"}.to_json
|
2013-06-16 10:39:41 +02:00
|
|
|
|
end
|
|
|
|
|
result = []
|
|
|
|
|
Entry.all.each do |entry|
|
|
|
|
|
result.push(entry) if params[:domain] == entry.subdomain.gsub(entry.subdomain.split('.')[0]+'.', '')
|
|
|
|
|
end
|
|
|
|
|
halt 200, result.to_json
|
|
|
|
|
end
|
|
|
|
|
|
2016-04-26 12:18:16 +02:00
|
|
|
|
# ?
|
2013-06-16 10:21:06 +02:00
|
|
|
|
get '/ips/:public_key' do
|
2013-07-07 13:28:56 +02:00
|
|
|
|
params[:public_key] = Base64.decode64(params[:public_key].encode('ascii-8bit'))
|
2013-07-07 10:03:15 +02:00
|
|
|
|
unless ALLOWED_IP.include? request.ip
|
2016-04-26 09:56:20 +02:00
|
|
|
|
halt 403, { :error => "Access denied"}.to_json
|
2013-06-15 22:33:45 +02:00
|
|
|
|
end
|
2013-06-16 00:13:29 +02:00
|
|
|
|
ips = []
|
|
|
|
|
Entry.first(:public_key => params[:public_key]).ips.all.each do |ip|
|
|
|
|
|
ips.push(ip.ip_addr)
|
|
|
|
|
end
|
|
|
|
|
ips.to_json
|
|
|
|
|
end
|
|
|
|
|
|
2016-04-26 12:18:16 +02:00
|
|
|
|
# Ban an IP address for 30 seconds
|
2013-06-16 09:45:02 +02:00
|
|
|
|
get '/ban/:ip' do
|
2013-07-07 10:03:15 +02:00
|
|
|
|
unless ALLOWED_IP.include? request.ip
|
2016-04-26 09:56:20 +02:00
|
|
|
|
halt 403, { :error => "Access denied"}.to_json
|
2013-06-16 00:13:29 +02:00
|
|
|
|
end
|
2013-06-16 09:45:02 +02:00
|
|
|
|
Ipban.create(:ip_addr => params[:ip])
|
2013-06-16 00:13:29 +02:00
|
|
|
|
Ipban.all.to_json
|
|
|
|
|
end
|
|
|
|
|
|
2016-04-26 12:18:16 +02:00
|
|
|
|
# Unban an IP address
|
2013-06-16 09:45:02 +02:00
|
|
|
|
get '/unban/:ip' do
|
2013-07-07 10:03:15 +02:00
|
|
|
|
unless ALLOWED_IP.include? request.ip
|
2016-04-26 09:56:20 +02:00
|
|
|
|
halt 403, { :error => "Access denied"}.to_json
|
2013-06-16 00:13:29 +02:00
|
|
|
|
end
|
2013-06-16 09:45:02 +02:00
|
|
|
|
Ipban.first(:ip_addr => params[:ip]).destroy
|
2013-06-16 00:13:29 +02:00
|
|
|
|
Ipban.all.to_json
|
2013-06-15 22:33:45 +02:00
|
|
|
|
end
|
|
|
|
|
|
2016-04-26 12:18:16 +02:00
|
|
|
|
|
2014-05-09 16:33:50 +02:00
|
|
|
|
#DataMapper.auto_migrate! # Destroy db content
|
|
|
|
|
DataMapper.auto_upgrade!
|