Project

General

Profile

Download (8.56 KB) Statistics
| Branch: | Tag: | Revision:
a587ca10 Marc Dequènes (Duck)
#!/usr/bin/ruby1.9.3
55a68712 Marc Dequenes
d32ee48a Marc Dequenes
#--
# CyborgHood, a distributed system management software.
e7315259 Marc Dequènes (Duck)
# Copyright (c) 2009-2010 Marc Dequènes (Duck) <Duck@DuckCorp.org>
d32ee48a Marc Dequenes
#
# 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/>.
#++

d4b5798a Marc Dequènes (Duck)
# to allow in-place run for test
$: << File.join(File.dirname(__FILE__), "..", "lib")
55a68712 Marc Dequenes
#require 'socket'
157c68c9 Marc Dequenes
require 'tempfile'
55a68712 Marc Dequenes
require 'shellwords'
44648f83 Marc Dequènes (Duck)
require 'cyborghood'
bc4894ce Marc Dequenes
require 'cyborghood/imap'
c427bfc7 Marc Dequenes
require 'cyborghood/mail'
cb0d68fd Marc Dequènes (Duck)
require 'cyborghood/mail_order'
7d9d434c Marc Dequènes (Duck)
require 'cyborghood/command_runner'
3f7a1eee Marc Dequenes
55a68712 Marc Dequenes
#Socket.gethostname

5f013d0a Marc Dequènes (Duck)
dfc48308 Marc Dequenes
module CyborgHood
2f1c6b5b Marc Dequènes (Duck)
# example for specific validation rules of the bot-specific config file
#class Config
# protected
# class PostmanValidator < CyborgHoodValidator
# def validate_hook_in(value, rule, path, msg_list)
# msg_list << "Youhou !!!"
# end
# end
#end

ccab26de Marc Dequènes (Duck)
module PostmanHome
ed09e1e5 Marc Dequènes (Duck)
include I18nTranslation
d4b5798a Marc Dequènes (Duck)
bindtextdomain("cyborghood_postman", {:path => Config::L10N_DIR, :charset => "UTF-8"})
5f013d0a Marc Dequènes (Duck)
ccab26de Marc Dequènes (Duck)
# not yet ready to be a real Cyborg
class Postman #< Cyborg
ed09e1e5 Marc Dequènes (Duck)
include I18nTranslation
5f013d0a Marc Dequènes (Duck)
ccab26de Marc Dequènes (Duck)
def initialize
# load config
Config.load(self.human_name.downcase)
@config = Config.instance
5f013d0a Marc Dequènes (Duck)
ccab26de Marc Dequènes (Duck)
# setup logs
unless @config.log.nil?
logger.output_level(@config.log.console_level) unless @config.log.console_level.nil?
a3732b39 Marc Dequènes (Duck)
unless @config.log.path.nil?
if File.directory? @config.log.path
logger.log_to_file(File.join(@config.log.path, "ch_#{self.class.human_name}.log"))
else
logger.fatal "Log path does not exist or is not a directory, exiting"
exit 1
end
end
5f013d0a Marc Dequènes (Duck)
end
ccab26de Marc Dequènes (Duck)
d88d3472 Marc Dequènes (Duck)
# setup LDAP
ldap_config = @config.ldap.marshal_dump
ldap_config[:logger] = logger
begin
ActiveLdap::Base.setup_connection(ldap_config)
# force testing a connection NOW (by default ActiveLdap is doing lazy connections)
# (the loaded schema will be useful soon anyway)
# it also tests search parameters (provided to setup_connection)
ActiveLdap::Base.find(:first)
rescue
824a3af6 Marc Dequènes (Duck)
logger.fatal "LDAP failure: %s" % $!
d88d3472 Marc Dequènes (Duck)
exit 1
end

2fd36b40 Marc Dequènes (Duck)
@imap = IMAP.new(@config.imap, @config.imap.min_check_interval)
ccab26de Marc Dequènes (Duck)
logger.info "Bot '#{self.human_name}' loaded"
5f013d0a Marc Dequènes (Duck)
end

ccab26de Marc Dequènes (Duck)
def run
6bc51624 Marc Dequènes (Duck)
logger.info "Bot starting"
2e179358 Marc Dequènes (Duck)
begin
@imap.check_mails do |msg|
begin
process_message(msg)
rescue CyberError => e
raise
rescue
85fb699c Marc Dequènes (Duck)
logger.error "Mail processing crashed unexpectedly: %s" % $!.message
824a3af6 Marc Dequènes (Duck)
logger.error "Crash class: %s" % $!.class.to_s
85fb699c Marc Dequènes (Duck)
if $!.class == GPGME::Error::General
logger.error "GPG error code: %s" % $!.code
logger.error "GPG error source: %s" % $!.source
end
824a3af6 Marc Dequènes (Duck)
logger.error "Crash trace: %s" % $!.backtrace.join("\n")
f3075838 Marc Dequènes (Duck)
false
2e179358 Marc Dequènes (Duck)
end
177a5b4f Marc Dequènes (Duck)
end
2e179358 Marc Dequènes (Duck)
rescue
85fb699c Marc Dequènes (Duck)
logger.error "IMAP processing crashed unexpectedly: %s" % $!.message
824a3af6 Marc Dequènes (Duck)
logger.error "Crash class: %s" % $!.class.to_s
logger.error "Crash trace: %s" % $!.backtrace.join("\n")
5f013d0a Marc Dequènes (Duck)
end
ccab26de Marc Dequènes (Duck)
logger.info "Bot terminating"
end
5f013d0a Marc Dequènes (Duck)
c75f6985 Marc Dequènes (Duck)
def ask_to_stop
2fd36b40 Marc Dequènes (Duck)
logger.info "Bot was asked to stop..."
@imap.stop_mail_check
c75f6985 Marc Dequènes (Duck)
end

private

6bc51624 Marc Dequènes (Duck)
def process_message(msg)
mail = Mail.new(msg.content)
1879aa76 Marc Dequènes (Duck)
logger.info "Received mail with ID '#{mail.message_id}': #{mail[:from].decoded} -> #{mail[:to].decoded} (#{mail.subject})"
5f013d0a Marc Dequènes (Duck)
6bc51624 Marc Dequènes (Duck)
# ignore mails not signed or encrypted
unless mail.is_pgp_signed? or mail.is_pgp_encrypted?
logger.info "Mail not signed/encrypted or not RFC3156 compliant, ignoring..."
msg.delete
return true
end
ccab26de Marc Dequènes (Duck)
6bc51624 Marc Dequènes (Duck)
logger.debug "RFC3156 content detected"
begin
report = mail.process
rescue CyberError => e
case e.severity
when :grave
logger.fatal "Fatal processing error, exiting (#{e.message})"
exit 2
when :unrecoverable
logger.error "Internal processing error, skipping mail (#{e.message})"
return true
when :processable
logger.error "Untreated processing problem, skipping mail (#{e.message})"
return true
when :ignorable
logger.warn "Internal processing warning, continuing (#{e.message})"
ccab26de Marc Dequènes (Duck)
end
6bc51624 Marc Dequènes (Duck)
end
result_tag = report.ok? ? "SUCCESS" : "FAILURE"
result_msg = "Processing result: #{result_tag}"
result_msg += " (#{report.error.untranslated})" unless report.ok?
logger.info result_msg

i18n = I18nController.instance
i18n.set_language_for_user(report.user)

unless report.ok?
if report.warn_sender
logger.info "Sending reply for rejected message"
reply_intro = report.user ? _("Hello %{cn},", :cn =>report.user.cn) : _("Hello,")
mail_reply = mail.create_simple_reject_reply(reply_intro.to_s + "\n\n" +
_("A message (ID: %{id}), apparently from you, was rejected for the following reason:",
6ed10f49 Marc Dequènes (Duck)
:id => mail.message_id).to_s + "\n " + report.error.to_s + "\n")
6bc51624 Marc Dequènes (Duck)
mail_reply.deliver
5f013d0a Marc Dequènes (Duck)
end
6bc51624 Marc Dequènes (Duck)
msg.delete
return true
end
5f013d0a Marc Dequènes (Duck)
3d7bbe55 Marc Dequènes (Duck)
mail_parser = MailOrderParser.new(report.user)
order = mail_parser.parse(report.message)
6bc51624 Marc Dequènes (Duck)
result_tag = order.valid? ? "SUCCESS" : "FAILURE"
result_msg = "Processing result: #{result_tag}"
result_msg += " (#{order.error.untranslated})" unless order.valid?
logger.info result_msg
eb6e0359 Marc Dequènes (Duck)
6bc51624 Marc Dequènes (Duck)
reply_intro = _("Hello %{cn},", :cn => order.user.cn)
cb0d68fd Marc Dequènes (Duck)
6bc51624 Marc Dequènes (Duck)
unless order.valid?
logger.info "Sending reply for rejected order"
mail_reply = mail.create_simple_reject_reply(reply_intro.to_s + "\n\n" +
_("An order, in a message (ID: %{id}) from you, was rejected for the following reason:",
6ed10f49 Marc Dequènes (Duck)
:id => mail.message_id).to_s + "\n " + order.error.to_s + "\n")
6bc51624 Marc Dequènes (Duck)
mail_reply.deliver
msg.delete
return true
end
cb0d68fd Marc Dequènes (Duck)
6bc51624 Marc Dequènes (Duck)
logger.debug "Message accepted, processing orders..."
result_list = CommandRunner.run(order)

45cd6107 Marc Dequènes (Duck)
# create transcript and attachments
logger.debug "Preparing reply"
reply_txt = reply_intro.to_s + "\n\n"
reply_txt += _("Follows the transcript of your commands:").to_s + "\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
reply_txt += "\n"

6bc51624 Marc Dequènes (Duck)
# create mail
logger.debug "Preparing mail"
c8c1e72c Marc Dequènes (Duck)
mail_reply = mail.reply
ab03f821 Marc Dequènes (Duck)
transcript_part = nil
6bc51624 Marc Dequènes (Duck)
if reply_attachments.empty?
transcript_part = mail_reply
else
cdab9091 Marc Dequènes (Duck)
transcript_part = ::Mail::Part.new
mail_reply.add_part(transcript_part)
5f013d0a Marc Dequènes (Duck)
6bc51624 Marc Dequènes (Duck)
reply_attachments.each do |attachment|
cdab9091 Marc Dequènes (Duck)
mail_reply.attachments[attachment.filename] = {
:mime_type => 'text/plain',
:content => attachment.content
}
5f013d0a Marc Dequènes (Duck)
end
6bc51624 Marc Dequènes (Duck)
end
6ed10f49 Marc Dequènes (Duck)
6bc51624 Marc Dequènes (Duck)
# insert transcript
cdab9091 Marc Dequènes (Duck)
transcript_part.content_type = 'text/plain; charset="UTF-8"'
transcript_part.content_disposition = 'inline; format="flowed"'
transcript_part.body = reply_txt + mail_reply.default_body_signature
ccab26de Marc Dequènes (Duck)
6bc51624 Marc Dequènes (Duck)
# send reply
logger.debug "Sending mail"
958e72a0 Marc Dequènes (Duck)
mail_reply.sign_and_crypt
6bc51624 Marc Dequènes (Duck)
mail_reply.deliver
ccab26de Marc Dequènes (Duck)
6bc51624 Marc Dequènes (Duck)
logger.info "Message processed completely, deleting"
msg.delete

true
5f013d0a Marc Dequènes (Duck)
end
end
55a68712 Marc Dequenes
end
end

ccab26de Marc Dequènes (Duck)
bot = CyborgHood::PostmanHome::Postman.new
55a68712 Marc Dequenes
trap('INT') do
bot.ask_to_stop
end
trap('TERM') do
bot.ask_to_stop
end

bot.run