Class: Dyndnsd::Daemon

Inherits:
Object
  • Object
show all
Defined in:
lib/dyndnsd.rb

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(config, db, updater, responder) ⇒ Daemon

Returns a new instance of Daemon.



35
36
37
38
39
40
41
42
43
44
45
46
# File 'lib/dyndnsd.rb', line 35

def initialize(config, db, updater, responder)
  @users = config['users']
  @domain = config['domain']
  @db = db
  @updater = updater
  @responder = responder

  @db.load
  @db['serial'] ||= 1
  @db['hosts'] ||= {}
  (@db.save; update) if @db.changed?
end

Class Method Details

.run!Object



121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
# File 'lib/dyndnsd.rb', line 121

def self.run!
  if ARGV.length != 1
    puts "Usage: dyndnsd config_file"
    exit 1
  end

  config_file = ARGV[0]

  if not File.file?(config_file)
    puts "Config file not found!"
    exit 1
  end
  
  puts "DynDNSd version #{Dyndnsd::VERSION}"
  puts "Using config file #{config_file}"

  config = YAML::load(File.open(config_file, 'r') { |f| f.read })
  
  if config['logfile']
    Dyndnsd.logger = Logger.new(config['logfile'])
  else
    Dyndnsd.logger = Logger.new(STDOUT)
  end
  
  Dyndnsd.logger.progname = "dyndnsd"
  Dyndnsd.logger.formatter = LogFormatter.new

  Dyndnsd.logger.info "Starting..."
  
  # drop privs (first change group than user)
  Process::Sys.setgid(Etc.getgrnam(config['group']).gid) if config['group']
  Process::Sys.setuid(Etc.getpwnam(config['user']).uid) if config['user']

  # configure metriks
  if config['graphite']
    host = config['graphite']['host'] || 'localhost'
    port = config['graphite']['port'] || 2003
    options = {}
    options[:prefix] = config['graphite']['prefix'] if config['graphite']['prefix']
    reporter = Metriks::Reporter::Graphite.new(host, port, options)
    reporter.start
  else
    reporter = Metriks::Reporter::ProcTitle.new
    reporter.add 'good', 'sec' do
      Metriks.meter('requests.good').mean_rate
    end
    reporter.add 'nochg', 'sec' do
      Metriks.meter('requests.nochg').mean_rate
    end
    reporter.start
  end

  # configure daemon
  db = Database.new(config['db'])
  updater = Updater::CommandWithBindZone.new(config['domain'], config['updater']['params']) if config['updater']['name'] == 'command_with_bind_zone'
  responder = Responder::DynDNSStyle.new
  
  # configure rack
  app = Daemon.new(config, db, updater, responder)
  app = Rack::Auth::Basic.new(app, "DynDNS") do |user,pass|
    allow = (config['users'].has_key? user) and (config['users'][user]['password'] == pass)
    if not allow
      Dyndnsd.logger.warn "Login failed for #{user}"
      Metriks.meter('requests.auth_failed').mark
    end
    allow
  end

  Signal.trap('INT') do
    Dyndnsd.logger.info "Quitting..."
    Rack::Handler::WEBrick.shutdown
  end

  Rack::Handler::WEBrick.run app, :Host => config['host'], :Port => config['port']
end

Instance Method Details

#call(env) ⇒ Object



60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
# File 'lib/dyndnsd.rb', line 60

def call(env)
  return @responder.response_for_error(:method_forbidden) if env["REQUEST_METHOD"] != "GET"
  return @responder.response_for_error(:not_found) if env["PATH_INFO"] != "/nic/update"
  
  params = Rack::Utils.parse_query(env["QUERY_STRING"])
  
  return @responder.response_for_error(:hostname_missing) if not params["hostname"]
  
  hostnames = params["hostname"].split(',')
  
  # Check if hostname match rules
  hostnames.each do |hostname|
    return @responder.response_for_error(:hostname_malformed) if not is_fqdn_valid?(hostname)
  end
  
  user = env["REMOTE_USER"]
  
  hostnames.each do |hostname|
    return @responder.response_for_error(:host_forbidden) if not @users[user]['hosts'].include? hostname
  end

  # no myip?
  if not params["myip"]
    params["myip"] = env["REMOTE_ADDR"]
  end
  
  # malformed myip?
  begin
    IPAddr.new(params["myip"], Socket::AF_INET)
  rescue ArgumentError
    params["myip"] = env["REMOTE_ADDR"]
  end
  
  myip = params["myip"]
  
  Metriks.meter('requests.valid').mark
  Dyndnsd.logger.info "Request to update #{hostnames} to #{myip} for user #{user}"
  
  changes = []
  hostnames.each do |hostname|
    if (not @db['hosts'].include? hostname) or (@db['hosts'][hostname] != myip)
      changes << :good
      @db['hosts'][hostname] = myip
      Metriks.meter('requests.good').mark
    else
      changes << :nochg
      Metriks.meter('requests.nochg').mark
    end
  end
  
  if @db.changed?
    @db['serial'] += 1
    Dyndnsd.logger.info "Committing update ##{@db['serial']}"
    @db.save
    update
    Metriks.meter('updates.committed').mark
  end
  
  @responder.response_for_changes(changes, myip)
end

#is_fqdn_valid?(hostname) ⇒ Boolean

Returns:

  • (Boolean)


52
53
54
55
56
57
58
# File 'lib/dyndnsd.rb', line 52

def is_fqdn_valid?(hostname)
  return false if hostname.length < @domain.length + 2
  return false if not hostname.end_with?(@domain)
  name = hostname.chomp(@domain)
  return false if not name.match(/^[a-zA-Z0-9_-]+\.$/)
  true
end

#updateObject



48
49
50
# File 'lib/dyndnsd.rb', line 48

def update
  @updater.update(@db)
end