Project

General

Profile

Download (5.21 KB) Statistics
| Branch: | Tag: | Revision:
ecdabe95 Marc Dequènes (Duck)
#--
# 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 'eventmachine'
d1e614b5 Marc Dequènes (Duck)
require 'cyborghood/cyborg/interface'
ce98dac2 Marc Dequènes (Duck)
require 'shellwords'
ecdabe95 Marc Dequènes (Duck)

module CyborgHood
class BotServer
private_class_method :new

050eca43 Marc Dequènes (Duck)
def self.build(interface)
ecdabe95 Marc Dequènes (Duck)
case Config.instance.botnet.connection_type
when 'unix_socket'
050eca43 Marc Dequènes (Duck)
return BotServerUNIXSocket.new(interface)
ecdabe95 Marc Dequènes (Duck)
else
raise CyberError.new(:unrecoverable, "config", "Unknown botnet connection type")
end
end

050eca43 Marc Dequènes (Duck)
def initialize(interface)
@interface = interface

ecdabe95 Marc Dequènes (Duck)
@config = Config.instance
end

def run
EventMachine.run do
yield
end
end

def stop
EventMachine.stop
end
end

class BotServerUNIXSocket < BotServer
public_class_method :new

050eca43 Marc Dequènes (Duck)
def initialize(interface)
super(interface)
ecdabe95 Marc Dequènes (Duck)
@socket = File.join(Config::RUN_DIR, @config.bot_name.downcase + ".sock")
at_exit { remove_socket_file }
end

def run
super do
050eca43 Marc Dequènes (Duck)
EventMachine.start_unix_domain_server(@socket, ConversationUNIXSocket, @interface)
ecdabe95 Marc Dequènes (Duck)
end
end

private

def remove_socket_file
File.delete(@socket) if @socket && File.exist?(@socket)
end
end

class Conversation < EventMachine::Protocols::LineAndTextProtocol
private_class_method :new

ce98dac2 Marc Dequènes (Duck)
# don't rely on EventMachine's default, it may change one day
MaxLineLength = 16*1024

EOD = "\033[0J"
f94d64b1 Marc Dequènes (Duck)
NODE_PATTERN = /^([a-zA-Z0-9_]+(?:\.[a-zA-Z0-9_]+)*[?=]?)(?: ([+?]+))?$/
ce98dac2 Marc Dequènes (Duck)
050eca43 Marc Dequènes (Duck)
def initialize(interface)
@interface = interface

ecdabe95 Marc Dequènes (Duck)
super

@config = Config.instance
ce98dac2 Marc Dequènes (Duck)
@split_data_mode = false
@split_data_cmd = nil
@split_data = []
ecdabe95 Marc Dequènes (Duck)
end

def send_line(msg)
ce98dac2 Marc Dequènes (Duck)
send_data "#{msg}\n"
logger.debug "Sent data [#{identifier}]: #{msg}"
ecdabe95 Marc Dequènes (Duck)
end

def post_init
logger.debug "New conversation with #{identifier}"
send_line "220 #{PRODUCT} #{VERSION} - #{@config.bot_name}"
end

def receive_line(data)
ce98dac2 Marc Dequènes (Duck)
return if data.empty?

if data == EOD
logger.debug "Received EOD [#{identifier}]"
exit_split_mode
else
if @split_data_mode
logger.debug "Received data (split mode) [#{identifier}]: #{data}"

@split_data << data
else
logger.debug "Received data [#{identifier}]: #{data}"

f94d64b1 Marc Dequènes (Duck)
unless data =~ NODE_PATTERN
ce98dac2 Marc Dequènes (Duck)
logger.error "Error [#{identifier}]: syntax error"
f94d64b1 Marc Dequènes (Duck)
send_line "552 syntax error in command"
ce98dac2 Marc Dequènes (Duck)
return
end
f94d64b1 Marc Dequènes (Duck)
cmd = $1
flags = $2 || ""
ce98dac2 Marc Dequènes (Duck)
f94d64b1 Marc Dequènes (Duck)
if flags.index '?'
send_line "250+ ok"
1c89625f Marc Dequènes (Duck)
send_line({'exists?' => @interface.find_node_action(cmd)}.to_yaml)
f94d64b1 Marc Dequènes (Duck)
return
end

if flags.index '+'
enter_split_mode(cmd)
ce98dac2 Marc Dequènes (Duck)
else
f94d64b1 Marc Dequènes (Duck)
receive_command(cmd)
ce98dac2 Marc Dequènes (Duck)
end
end
end
end

f94d64b1 Marc Dequènes (Duck)
def receive_command(cmd, data = nil)
ce98dac2 Marc Dequènes (Duck)
logger.debug "Executing command '#{cmd}' [#{identifier}]"
1c89625f Marc Dequènes (Duck)
send_line @interface.call(cmd, data)
ecdabe95 Marc Dequènes (Duck)
end

def receive_error(msg)
logger.error "Error [#{identifier}]: #{msg}"
end

def unbind
logger.debug "Conversation finished with #{identifier}"
end
ce98dac2 Marc Dequènes (Duck)
protected

f94d64b1 Marc Dequènes (Duck)
def enter_split_mode(cmd)
ce98dac2 Marc Dequènes (Duck)
if @split_data_mode
logger.error "Error [#{identifier}]: already in split mode"
send_line "551 protocol error"
@split_data_mode = false
@split_data_cmd = nil
else
logger.debug "Entered split mode for command '#{cmd}' [#{identifier}]"
@split_data_mode = true
@split_data_cmd = cmd
end
@split_data = []
end

def exit_split_mode
if @split_data_mode
logger.debug "Quit split mode for command '#{@split_data_cmd}' [#{identifier}]"
f94d64b1 Marc Dequènes (Duck)
receive_command(@split_data_cmd, @split_data.join("\n"))
ce98dac2 Marc Dequènes (Duck)
else
logger.error "Error [#{identifier}]: not in split mode"
send_line "551 protocol error"
end
@split_data_mode = false
@split_data_cmd = nil
@split_data = []
end
ecdabe95 Marc Dequènes (Duck)
end

class ConversationUNIXSocket < Conversation
public_class_method :new

def identifier
"unix_socket/#{@signature}"
end
end
d1e614b5 Marc Dequènes (Duck)
bc473567 Marc Dequènes (Duck)
class EmptyInterface
include CyborgServerInterface
include CyborgServerDefaultInterface
end

d1e614b5 Marc Dequènes (Duck)
module SimpleServer
def setup
super
@server = BotServer.build(self.interface)
end

def run
super
@server.run
end

def ask_to_stop
@server.stop
super
end

def interface
1c89625f Marc Dequènes (Duck)
EmptyInterface.instance
d1e614b5 Marc Dequènes (Duck)
end
end
ecdabe95 Marc Dequènes (Duck)
end