|
#--
|
|
# CyborgHood, a distributed system management software.
|
|
# Copyright (c) 2009-2010 Marc Dequènes (Duck) <Duck@DuckCorp.org>
|
|
#
|
|
# This program is free software: you can redistribute it and/or modify
|
|
# it under the terms of the GNU General Public License as published by
|
|
# the Free Software Foundation, either version 3 of the License, or
|
|
# (at your option) any later version.
|
|
#
|
|
# This program is distributed in the hope that it will be useful,
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
# GNU General Public License for more details.
|
|
#
|
|
# You should have received a copy of the GNU General Public License
|
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
#++
|
|
|
|
|
|
require 'tempfile'
|
|
require 'shellwords'
|
|
require 'cyborghood/order'
|
|
require 'cyborghood/objects'
|
|
require 'cyborghood/services/dns'
|
|
|
|
|
|
module CyborgHood
|
|
class CommandRunner
|
|
include I18nTranslation
|
|
|
|
def self.run(order)
|
|
result_list = []
|
|
order.commands.each do |cmd|
|
|
result = OpenStruct.new
|
|
result.cmd = cmd.cmdline
|
|
result.ok = false
|
|
|
|
if cmd.valid?
|
|
logger.info "Executing command: #{cmd.cmdline}"
|
|
begin
|
|
execute_cmd(order.user, cmd.cmdsplit, result)
|
|
rescue CyberError => e
|
|
result.message = tm_(e.message.capitalize + ".")
|
|
rescue
|
|
logger.error "Command crashed: " + $!
|
|
logger.error "Crash trace: " + $!.backtrace.join("\n")
|
|
result.message = _("Internal error. Administrator is warned.")
|
|
end
|
|
|
|
tag = result.ok ? "SUCCESS" : "FAILURE"
|
|
logger.debug "Command result: [#{tag}] #{result.message.untranslated}"
|
|
else
|
|
logger.info "Invalid command detected: #{cmd.cmdline}"
|
|
cmd.parsing_errors.collect{|err| logger.debug "Invalid command detected - reason: " + err.untranslated }
|
|
result.message = cmd.parsing_errors.collect{|err| err.to_s }.join("\n")
|
|
end
|
|
result_list << result
|
|
end
|
|
result_list
|
|
end
|
|
|
|
private
|
|
|
|
def self.execute_cmd(user, cmdline, result)
|
|
subsys = cmdline.shift
|
|
case subsys.upcase
|
|
when "DNS"
|
|
return if cmdline.empty?
|
|
case cmdline.shift.upcase
|
|
when "INFO"
|
|
return unless cmdline.empty?
|
|
list = DnsDomain.find_by_manager(user)
|
|
txt_list = list.collect{|z| z.cn }.sort.join(", ")
|
|
result.ok = true
|
|
result.message = _("You are manager of the following zones: %{zone_list}.", :zone_list => txt_list)
|
|
when "GET"
|
|
return if cmdline.empty?
|
|
case cmdline.shift.upcase
|
|
when "ZONE"
|
|
return if cmdline.empty?
|
|
zone = cmdline.shift.downcase
|
|
|
|
dom = DnsDomain.new(zone)
|
|
unless dom.hosted?
|
|
result.message = _("This zone is not hosted here.")
|
|
return result
|
|
end
|
|
unless dom.managed_by? user
|
|
result.message = _("You are not allowed to manage this zone.")
|
|
return result
|
|
end
|
|
|
|
srv_dns = Services::DNS.new(zone)
|
|
zone_content = srv_dns.read_zone
|
|
|
|
result.ok = true
|
|
result.message = _("Requested zone content attached.")
|
|
zone_ref = {:content => zone_content, :filename => "dnszone_#{zone}.txt"}.to_ostruct
|
|
result.refs = [zone_ref]
|
|
end
|
|
when "SET"
|
|
return if cmdline.empty?
|
|
case cmdline.shift.upcase
|
|
when "ZONE"
|
|
return if cmdline.empty?
|
|
zone = cmdline.shift.downcase
|
|
dom = DnsDomain.new(zone)
|
|
unless dom.hosted?
|
|
result.message = _("This zone is not hosted here.")
|
|
return result
|
|
end
|
|
unless dom.managed_by? user
|
|
result.message = _("You are not allowed to manage this zone.")
|
|
return result
|
|
end
|
|
srv_dns = Services::DNS.new(zone)
|
|
|
|
if cmdline.empty?
|
|
result.message = _("No attachment number provided")
|
|
return result
|
|
end
|
|
|
|
zone_data = cmdline.shift
|
|
unless zone_data.content_type == "text/plain"
|
|
result.message = _("Zone data has wrong content-type.")
|
|
return result
|
|
end
|
|
|
|
f = Tempfile.new(zone)
|
|
f.write(zone_data.content)
|
|
f.close
|
|
logger.debug "Created temporary zone file '#{f.path}'"
|
|
|
|
srv_dns = Services::DNS.new(zone)
|
|
current_serial = srv_dns.serial
|
|
logger.debug "Current serial: #{current_serial}"
|
|
|
|
dns_result = srv_dns.check_zone_file(f.path)
|
|
unless dns_result.ok
|
|
result.message = _("Invalid zone data.")
|
|
f.close!
|
|
return result
|
|
end
|
|
logger.debug "New serial: #{dns_result.serial}"
|
|
# allow new serial or missing serial (to allow creating a new zone or replacing a broken zone)
|
|
unless current_serial.nil? or dns_result.serial > current_serial
|
|
result.message = _("Zone serial is not superior to current serial.")
|
|
f.close!
|
|
return result
|
|
end
|
|
|
|
begin
|
|
srv_dns.write_zone_from_file(f.path)
|
|
logger.debug "zone changed"
|
|
if srv_dns.reload_zone
|
|
logger.debug "zone reloaded"
|
|
result.ok = true
|
|
result.message = _("Zone updated.")
|
|
else
|
|
logger.warn "zone reload failed, replacing old content"
|
|
srv_dns.replace_zone_with_backup
|
|
result.message = _("Internal error. Administrator is warned.")
|
|
end
|
|
rescue
|
|
logger.warn "Writing zone file failed"
|
|
raise
|
|
ensure
|
|
f.close!
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
if result.message.nil?
|
|
# here fall lost souls
|
|
result.message = _("Command not recognized.")
|
|
end
|
|
end
|
|
end
|
|
end
|