Project

General

Profile

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

# http://www.ruby-doc.org/stdlib/libdoc/net/imap/rdoc/index.html
# http://tmail.rubyforge.org/reference/index.html
# http://tools.ietf.org/html/rfc3156

$: << "./lib"

$KCODE = 'UTF8'
require 'jcode'
require 'log4r'
require 'net/imap'
require 'tmail'
2891e0c2 Marc Dequenes
require 'tmail_extra'
55a68712 Marc Dequenes
#require 'socket'
#require 'fileutils'
#require 'tempfile'
require 'gpgme'
require 'active_ldap'
require 'shellwords'
require 'cyborghood/base'

logger = Log4r::Logger.new('test')
logger.outputters = Log4r::StderrOutputter.new('')
logger.level = Log4r::WARN
#logger.level = Log4r::DEBUG

3f7a1eee Marc Dequenes
class LdapPerson < ActiveLdap::Base
55a68712 Marc Dequenes
ldap_mapping :dn_attribute => 'uid', :prefix => '', :classes => ['person', 'extInetOrgPerson']
end

3f7a1eee Marc Dequenes
class LdapDnsDomain < ActiveLdap::Base
55a68712 Marc Dequenes
ldap_mapping :dn_attribute => 'cn', :prefix => '', :classes => ['genericDomain']

def managers
list = self.manager
return [] if list.nil?
return list.collect{|dn| dn.to_s } if list.is_a? Array
return [list.to_s]
end
end

ldap_config = {
:host => 'localhost',
:port => 389,
:base => 'dc=milkypond,dc=org',
:bind_dn => 'cn=srv-auth,dc=milkypond,dc=org',
:password => 'teckLetsoaj5',
:logger => logger,
:allow_anonymous => false,
:try_sasl => false,
#:method => :tls,
:scope => :sub
}

ActiveLdap::Base.establish_connection(ldap_config)

3f7a1eee Marc Dequenes
class DnsDomain < Delegator
attr_reader :name

def initialize(name)
raise "invalid zone name" unless self.is_valid?(name)

# may not exist (if creating a new one)
begin
@ldap = LdapDnsDomain.find(name)
rescue
@ldap = nil
end
end

def self.is_valid?(name)
name =~ /^[a-z0-9.-]+\.[a-z]{2,4}$/
end

def is_valid?(name)
self.class.is_valid?(name)
end

def managed?
not @ldap.nil?
end

def __getobj__
@ldap
end
end

55a68712 Marc Dequenes
#Socket.gethostname

#
# TODO:
# - should be able to handle encrypted messages for user to send sensitive data (postman would need a GPG key too)
#

class CommandParser
df41472b Marc Dequenes
def self.run(user, txt, refs)
55a68712 Marc Dequenes
txt.each_line do |line|
line.chomp!
sline = line.strip
# skip empty lines and comments
next if sline == "" or sline[0, 1] == "#"
# stop processing when detecting message signature
break if line == "-- "

3f7a1eee Marc Dequenes
logger.info "Executing command: #{sline}"
begin
execute_cmd(user, sline)
rescue
logger.info "Command failed: " + $!
end
55a68712 Marc Dequenes
end
end

private

def self.execute_cmd(user, cmdstr)
cmdline = Shellwords.shellwords(cmdstr)
subsys = cmdline.shift

ok = true
case subsys.upcase
when "DNS"
case cmdline.shift.upcase
when "INFO"
if cmdline.empty?
3f7a1eee Marc Dequenes
list = LdapDnsDomain.find(:all, :attribute => 'manager', :value => user.dn)
logger.info "User is manager of the following zones: " + list.collect{|z| z.cn }.join(", ")
55a68712 Marc Dequenes
else
ok = false
end
when "GET"
case cmdline.shift.upcase
when "ZONE"
zone = cmdline.shift.downcase
3f7a1eee Marc Dequenes
dom = DnsDomain.new(zone)
logger.info "User requesting zone content for '#{zone}'"
if dom.managed?
if dom.managers.include? user.dn
logger.info "User is manager of the zone"
else
logger.info "User is not allowed to manage the zone"
55a68712 Marc Dequenes
end
else
3f7a1eee Marc Dequenes
logger.info "Zone not managed"
55a68712 Marc Dequenes
end
else
ok = false
end
when "SET"
else
ok = false
end
else
ok = false
end

if not ok
3f7a1eee Marc Dequenes
logger.info "Command not recognized: #{cmdstr}"
55a68712 Marc Dequenes
end
end
end

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

# 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

logger.info "Bot '#{self.human_name}' loaded"
end

def run
# using SSL because TLS does not work in the NET::IMAP library
#imap = Net::IMAP.new('imap.duckcorp.org', 993, true, "/etc/ssl/certs/duckcorp.crt", true)
imap = Net::IMAP.new('localhost')
logger.debug "Connected to IMAP server"
2891e0c2 Marc Dequenes
logger.debug "IMAP Capabilities: " + imap.capability.join(", ")
55a68712 Marc Dequenes
imap.authenticate('LOGIN', @config.imap.login, @config.imap.passwd)
logger.debug "Logged into IMAP account"
#p imap.getquotaroot("INBOX")
imap.select('INBOX')
imap.search(["ALL"], "UTF-8").each do |message_id|
msg = imap.fetch(message_id, "RFC822")[0].attr["RFC822"]
# unquote headers and transform into TMail object
mail = TMail::Mail.parse(TMail::Unquoter.unquote_and_convert_to(msg, "UTF-8"))

logger.set_prefix()
logger.debug "######################################"
logger.set_prefix("[#{mail.message_id}] ")
logger.info "#{mail.from_addrs} -> #{mail.to_addrs}: #{mail.subject}"
# ignore mails not signed
2891e0c2 Marc Dequenes
unless mail.is_pgp_signed?
55a68712 Marc Dequenes
logger.info "Mail not signed or not RFC3156 compliant"
next
end

logger.debug "Proper signed content detected"
2891e0c2 Marc Dequenes
sig_check = mail.verify_pgp_signature()
if sig_check.status == 0
logger.info "Mail content was properly signed by key #{sig_check.fingerprint}"
3f7a1eee Marc Dequenes
list = LdapPerson.find(:all, :attribute => 'keyFingerPrint', :value => sig_check.fingerprint)
2891e0c2 Marc Dequenes
case list.size
when 0
3f7a1eee Marc Dequenes
logger.info "Mail is from an unknown person"
2891e0c2 Marc Dequenes
when 1
user = list.first
logger.info "Mail is from user #{user.uid} (#{user.cn})"
df41472b Marc Dequenes
signed_content = mail.pgp_signed_part()
if signed_content.multipart?
if signed_content.parts[0].content_type == "text/plain"
command_txt = signed_content.parts[0].body
command_refs = signed_content.parts.collect{|p| p.dup }
end
else
command_txt = signed_content.body if signed_content.content_type == "text/plain"
command_refs = []
end

if command_txt
CommandParser.run(user, command_txt, command_refs)
else
logger.info "Mail does not contain a proper MIME part for commands"
end
2891e0c2 Marc Dequenes
else
logger.warn "Multiple users match in database, so i guess there is a mistake. It is safer to skip..."
end
else
logger.info "Mail content tampered or badly signed: " + sig_check.to_s
55a68712 Marc Dequenes
end
end
imap.logout
end

def ask_to_stop
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