Project

General

Profile

Download (9.48 KB) Statistics
| Branch: | Tag: | Revision:
55a68712 Marc Dequenes
#!/usr/bin/ruby -Ku

# http://www.ruby-doc.org/stdlib/libdoc/net/imap/rdoc/index.html

$: << "./lib"

#require 'socket'
157c68c9 Marc Dequenes
require 'tempfile'
55a68712 Marc Dequenes
require 'shellwords'
bc4894ce Marc Dequenes
require 'cyborghood/imap'
c427bfc7 Marc Dequenes
require 'cyborghood/mail'
bc4894ce Marc Dequenes
require 'cyborghood/objects'
4153cce4 Marc Dequenes
require 'cyborghood/services/dns'
fba691ae Marc Dequenes
require 'fileutils'
3f7a1eee Marc Dequenes
55a68712 Marc Dequenes
#Socket.gethostname

class CommandParser
c427bfc7 Marc Dequenes
def self.run(order)
4153cce4 Marc Dequenes
result_list = []
c427bfc7 Marc Dequenes
order.commands.each do |cmd|
logger.info "Executing command: #{cmd}"
3f7a1eee Marc Dequenes
begin
4153cce4 Marc Dequenes
result_list << execute_cmd(order.user, cmd, order.refs)
3f7a1eee Marc Dequenes
rescue
logger.info "Command failed: " + $!
end
55a68712 Marc Dequenes
end
4153cce4 Marc Dequenes
result_list
55a68712 Marc Dequenes
end

private

c427bfc7 Marc Dequenes
def self.execute_cmd(user, cmdstr, refs)
55a68712 Marc Dequenes
cmdline = Shellwords.shellwords(cmdstr)
subsys = cmdline.shift

4153cce4 Marc Dequenes
result = OpenStruct.new
result.cmd = cmdstr
55a68712 Marc Dequenes
ok = true
case subsys.upcase
when "DNS"
case cmdline.shift.upcase
when "INFO"
591ec1a2 Marc Dequenes
if cmdline.empty?
c427bfc7 Marc Dequenes
list = CyborgHood::DnsDomain.find_by_manager(user)
4153cce4 Marc Dequenes
txt_list = list.collect{|z| z.cn }.sort.join(", ")
logger.info "User is manager of the following zones: " + txt_list
result.message = "You are manager of the following zones: " + txt_list
591ec1a2 Marc Dequenes
else
ok = false
end
55a68712 Marc Dequenes
when "GET"
case cmdline.shift.upcase
when "ZONE"
zone = cmdline.shift.downcase
c427bfc7 Marc Dequenes
dom = CyborgHood::DnsDomain.new(zone)
591ec1a2 Marc Dequenes
logger.info "User requesting zone content for '#{zone}'"
if dom.hosted?
if dom.managed_by? user
logger.info "User is manager of the zone"
4153cce4 Marc Dequenes
srv_dns = CyborgHood::Services::DNS.new(zone)
result.message = "Requested zone content attached."
43dd8a57 Marc Dequenes
zone_ref = {:content => srv_dns.read_zone, :filename => "dnszone_#{zone}.txt"}.to_ostruct
result.refs = [zone_ref]
591ec1a2 Marc Dequenes
else
logger.info "User is not allowed to manage the zone"
4153cce4 Marc Dequenes
result.message = "You are not allowed to manage this zone."
591ec1a2 Marc Dequenes
end
else
logger.info "Zone not hosted"
4153cce4 Marc Dequenes
result.message "This zone is not hosted here."
591ec1a2 Marc Dequenes
end
else
ok = false
end
55a68712 Marc Dequenes
when "SET"
157c68c9 Marc Dequenes
case cmdline.shift.upcase
when "ZONE"
zone = cmdline.shift.downcase
dom = CyborgHood::DnsDomain.new(zone)
logger.info "User requesting zone content for '#{zone}'"
if dom.hosted?
if dom.managed_by? user
logger.info "User is manager of the zone"
srv_dns = CyborgHood::Services::DNS.new(zone)

content_ref = cmdline.shift.downcase
if content_ref =~ /^@(\d+)$/
part_ref = $1.to_i
if (1..refs.size).include? part_ref
part = refs[part_ref]
if part.content_type == "text/plain"
f = Tempfile.new(zone)
f.write(part.body)
f.close
logger.debug "Created temporary zone file '#{f.path}'"

srv_dns = CyborgHood::Services::DNS.new(zone)
current_serial = srv_dns.serial
logger.debug "Current serial: #{current_serial}"

result = srv_dns.check_zone_file(f.path)
if result.ok
logger.debug "New serial: #{result.serial}"
# allow new serial or missing serial (to allow creating a new zone or replacing a broken zone)
if current_serial.nil? or result.serial > current_serial
begin
srv_dns.write_zone_from_file(f.path)
fba691ae Marc Dequenes
f.close!
157c68c9 Marc Dequenes
logger.info "zone changed"
fba691ae Marc Dequenes
if srv_dns.reload_zone
logger.info "zone reloaded"
else
logger.info "zone reload failed, replacing old content"
srv_dns.replace_zone_with_backup
result.message = "Internal error."
return result
end
157c68c9 Marc Dequenes
rescue
logger.debug "Writing zone file failed"
raise
ensure
f.close!
end
else
logger.info "zone serial is not superior to current serial"
result.message = "Zone serial is not superior to current serial."
f.close!
return result
end
else
logger.info "new zone file is invalid"
result.message = "invalid zone data"
f.close!
return result
end

f.close!
else
logger.info "attachment for zone is not plain text"
result.message = "Attachment has wrong type."
return result
end
else
logger.info "attachement for zone not found"
result.message = "Attachment number not found."
return result
end
else
ok = false
end
else
logger.info "User is not allowed to manage the zone"
result.message = "You are not allowed to manage this zone."
end
else
logger.info "Zone not hosted"
result.message "This zone is not hosted here."
end
else
ok = false
end
55a68712 Marc Dequenes
else
ok = false
end
else
ok = false
end

if not ok
4153cce4 Marc Dequenes
result.message = "Command not recognized"
result.refs = nil
3f7a1eee Marc Dequenes
logger.info "Command not recognized: #{cmdstr}"
55a68712 Marc Dequenes
end
4153cce4 Marc Dequenes
result
55a68712 Marc Dequenes
end
end

4153cce4 Marc Dequenes
# imap.store(message_id, "+FLAGS", [:Deleted])
# imap.expunge()

55a68712 Marc Dequenes
module CyborgHood
# not yet ready to be a real Cyborg
class Postman #< Cyborg
def initialize
# load config
Config.load(self.human_name.downcase)
@config = Config.instance

0af9cada Marc Dequenes
ldap_config = @config.ldap
ldap_config.logger = logger
ActiveLdap::Base.establish_connection(ldap_config.marshal_dump)

55a68712 Marc Dequenes
# setup logs
unless @config.log.nil?
logger.output_level(@config.log.console_level) unless @config.log.console_level.nil?
logger.log_to_file(@config.log.file) unless @config.log.file.nil?
end

125a6103 Marc Dequenes
@stop_asap = false

55a68712 Marc Dequenes
logger.info "Bot '#{self.human_name}' loaded"
end

def run
945de171 Marc Dequenes
imap = IMAP.new(@config.imap)
imap.check_mail do |msg|
125a6103 Marc Dequenes
if @stop_asap
logger.info "Bot was asked to stop..."
break
end

c427bfc7 Marc Dequenes
mail = Mail.new(msg)
275e20ec Marc Dequenes
logger.info "Received mail with ID '#{mail.message_id}': #{mail.from_addrs} -> #{mail.to_addrs} (#{mail.subject})"
c427bfc7 Marc Dequenes
55a68712 Marc Dequenes
# ignore mails not signed
591ec1a2 Marc Dequenes
unless mail.is_pgp_signed?
c427bfc7 Marc Dequenes
logger.info "Mail not signed or not RFC3156 compliant, ignoring..."
55a68712 Marc Dequenes
next
591ec1a2 Marc Dequenes
end
55a68712 Marc Dequenes
7193ea94 Marc Dequenes
logger.debug "Signed content detected"
c427bfc7 Marc Dequenes
begin
order = mail.parse
rescue CyberError => e
945de171 Marc Dequenes
case e.severity
when :dangerous
logger.fatal " (#{e.message})"
exit 2
when :unrecoverable
logger.error "Internal processing error, skipping mail (#{e.message})"
next
when :ignorable
end
55a68712 Marc Dequenes
end
c427bfc7 Marc Dequenes
if order.nil?
logger.info "Mail is invalid, ignoring..."
next
7193ea94 Marc Dequenes
elsif not order.ok
mail_reply = mail.create_reply
mail_reply.quoted_printable_body = "A message (ID: #{mail.message_id}) apparently from you was rejected for the following reason:\n #{order.msg}"
mail_reply.deliver
next
c427bfc7 Marc Dequenes
end

4153cce4 Marc Dequenes
result_list = CommandParser.run(order)
275e20ec Marc Dequenes
mail_reply = mail.create_reply
4153cce4 Marc Dequenes
reply_txt = "Hello #{order.user.cn},\n\nFollows the transcript of your commands:\n"
reply_attachments = []
result_list.each do |result|
reply_txt << "> #{result.cmd}\n"
reply_txt << "#{result.message}\n"
reply_attachments += result.refs unless result.refs.nil?
end
if reply_attachments.empty?
mail_reply.set_content_type("text", "plain")
mail_reply.set_disposition("inline")
30406d66 Marc Dequenes
mail_reply.quoted_printable_body = reply_txt
4153cce4 Marc Dequenes
else
56793619 Marc Dequenes
mail_reply.set_content_type("multipart", "mixed", {'boundary' => TMail.new_boundary})
4153cce4 Marc Dequenes
parts = []

30406d66 Marc Dequenes
p = CyborgHood::Mail.new
4153cce4 Marc Dequenes
p.set_content_type("text", "plain", {'charset' => "utf-8"})
p.set_disposition("inline")
30406d66 Marc Dequenes
p.quoted_printable_body = reply_txt
4153cce4 Marc Dequenes
mail_reply.parts << p

reply_attachments.each do |attachment|
30406d66 Marc Dequenes
p = CyborgHood::Mail.new
4153cce4 Marc Dequenes
p.set_content_type("text", "plain", {'charset' => "utf-8"})
43dd8a57 Marc Dequenes
p.set_disposition("attachment", {'filename' => attachment.filename})
p.quoted_printable_body = attachment.content
4153cce4 Marc Dequenes
mail_reply.parts << p
end
56793619 Marc Dequenes
end
mail_reply.crypt(order.user.keyFingerPrint)
275e20ec Marc Dequenes
mail_reply.deliver
55a68712 Marc Dequenes
end
end

def ask_to_stop
125a6103 Marc Dequenes
@stop_asap = true
55a68712 Marc Dequenes
end
end
end

bot = CyborgHood::Postman.new

trap('INT') do
bot.ask_to_stop
end
trap('TERM') do
bot.ask_to_stop
end

bot.run