--=-XN87+79wxRM1KhQCmcRq
Content-Type: text/plain
Content-Transfer-Encoding: 7bit
Besides which: Ruby/DL is often touted as a great way to write extension
libraries. Are there any examples of libraries that have been written
using Ruby/DL instead of C? I've never seen any, much to my own
frustration when I would like a good example of Ruby/DL usage.
I wouldn't dare to brag about 'good example', but it at least is a
(mostly) working one. I say mostly as a good number of the settings for
a user are undocumented and I had to guess the return value type.
Anyway, the following code is a binding to the Merak mail server
library. It allowed may to automate the transition from our old mail
server to Merak. It is now a nice way to get the password in clear that
the stupid GUI shows as stars. The creation and basic settings of a
Domain and a User should work. The more exotic settings are untested.
Here is an example of a session using the created merak shell:
$ ssh Administrator@fakedomain irb -r merak_shell
Administrator@fakedomain's password:
Warning: Remote host denied X11 forwarding.
Merak 1:0> toto = Domain["toto.net"]
toto = Domain["toto.net"]
=> #<Domain name=toto.net; index=15>
Merak 2:0> user = toto["toto"]
user = toto["toto"]
=> #<User domain=toto.net index=0>
Merak 3:0> user.password
user.password
=> "coucou"
Merak 4:0> user.password = "hello"
user.password = "hello"
=> "hello"
Merak 5:0> user.save
user.save
=> #<User domain=toto.net index=0>
In short, I would never have written it if I had to drop down to C. One
reason being that I don't have a compiler installed on any Windows
machine yet.
Guillaume.
--=-XN87+79wxRM1KhQCmcRq
Content-Disposition: attachment; filename=merak.rb
Content-Transfer-Encoding: quoted-printable
Content-Type: text/plain; charset=ISO-8859-1
require 'Win32API'
module Merak
class Error < ScriptError; end
class APIError < Error; end
class Failure < APIError; CODE =3D -1; end
class License < APIError; CODE =3D -2; end
class Params < APIError; CODE =3D -3; end
class Paths < APIError; CODE =3D -4; end
class Config < APIError; CODE =3D -5; end
ERRORS =3D [nil, Failure, License, Params, Paths, Config]
# Call function in the DLL. Return an error object if an error occures.
class APIClass
attr_reader :winapis
DLL =3D 'API.dll'
ARGS =3D {
:Init =3D> ["P"],=20
:GetDomainCount =3D> [],
:GetDomainList =3D> ["P", "L"],
:AddDomain =3D> ["P", "P", "L"],
eleteDomain =3D> ["L"],
:GetDomainIndex =3D> ["P"],
:GetDomainName =3D> ["L", "P", "L"],
:LoadDomain =3D> ["L", "P", "L"],
:SaveDomain =3D> ["L", "P", "L"],
:SetDomainDefaults =3D> ["P", "L"],
:GetDomainIP =3D> ["L", "P", "L"],
:SetDomainIP =3D> ["L", "P", "L"],
:GetDomainSetting =3D> ["P", "L", "L", "P", "L"],
:SetDomainSetting =3D> ["P", "L", "L", "P", "L"],
:GetUserCount =3D> ["P"],
:GetUserList =3D> ["P", "P", "L"],
:GetUserIndex =3D> ["P", "P"],
:AddUser =3D> ["P", "P", "L"],
eleteUser =3D> ["P", "L"],
:SetUserDefaults =3D> ["P", "L"],
:LoadUser =3D> ["P", "L", "P", "L"],
:SaveUser =3D> ["P", "L", "P", "L"],
:GetUserSetting =3D> ["P", "L", "L", "P", "L"],
:SetUserSetting =3D> ["P", "L", "L", "P", "L"],
}
def initialize; @winapis =3D {}; end
def method_missing(call, *args)
winapi =3D @winapis[call]
if winapi.nil?
api_args =3D ARGS[call]
raise Error, "Unknown API call '#{call}'" if api_args.nil?
winapi =3D @winapis[call] =3D Win32API.new(DLL, call.to_s, api_args=
, "L")
end
param =3D args.last.kind_of?(Hash) ? args.pop : nil
r =3D winapi.call(*args)
if r < 0
msg =3D "Error calling #{call}(#{args.join(", ")})"
pmsg =3D param ? param[("err_%d" % (-r)).intern] : nil
msg <<=3D "\n#{pmsg}" if pmsg
raise ERRORS[-r], msg
end
r
end
end # API
=20
# Different types to store in Merak fields
# Should implement pack, unpack, size and base_size
# The virtual type must be instanciate with a base_size argument
class BoolT
def self.pack(b); [b ? 1 : 0].pack("C"); end
def self.unpack(s); s.unpack("C")[0] !=3D 0; end
def self.size(s); 1; end
def self.base_size; 1; end
end
=20
class NumT
def self.pack(n); [n].pack("L"); end
def self.unpack(s); s.unpack("L")[0]; end
def self.size(s); 4; end
def self.base_size; 4; end
end
=20
class TimeT
def self.pack(n); nil; end
def self.unpack(s); nil; end
def self.size(s); 8; end
def self.base_size; 8; end
end
=20
class StringTVirtual
attr_accessor :base_size
def initialize(base_size); @base_size =3D base_size; end
def pack(s); s; end
def unpack(s); s; end
def size(s); s.size; end
end
=20
class ListTVirtual
attr_accessor :base_size
def initialize(base_size); @base_size =3D base_size; end
def pack(l); l.kind_of?(Array) ? l.join('; ') : l; end
def unpack(s); s.split(/[,;]/).collect { |x| x.strip }; end
def size(s); s.size; end
end
class TypeTVirtual
attr_reader :category
def initialize(category); @category =3D category; end
def pack(t); [@category.index(t)].pack("L"); end
def unpack(s); @category[s.unpack("L")[0]]; s.unpack("L")[0]; end
def size(s); 4; end
def base_size; 4; end
end
# Actual type of virtual definition
String32T =3D StringTVirtual.new(32)
String128T =3D StringTVirtual.new(128)
String1024T =3D StringTVirtual.new(1024)
List128T =3D ListTVirtual.new(128)
List1024T =3D ListTVirtual.new(1024)
=20
# Get/Set settings for object
module Settings
class << self
alias :__append_features :append_features
def append_features(mod)
__append_features(mod)
mod.const_get
SETTINGS_DESC).each_key do |k|
mod.module_eval("def #{k}; get_setting
#{k}); end")
mod.module_eval("def #{k}=3D(val); set_setting
#{k}, val); end")
end
end
end
def get_setting(setting, b_size =3D nil)
s_desc =3D settings_desc[setting]
raise Error, "Unknown setting '#{setting}'" if s_desc.nil?
get_setting =3D settings_calls[:get_setting]
=20
args =3D [@buffer, @buffer.size, s_desc[:val], :buffer, :size]
r, buffer, args =3D call_retry(get_setting, s_desc[:type].base_size, =
args)
return s_desc[:type].unpack(buffer[0, r])
end
private :get_setting
def set_setting(setting, value)
s_desc =3D settings_desc[setting]
raise Error, "Unknown setting '#{setting}'" if s_desc.nil?
set_setting =3D settings_calls[:set_setting]
s_buf =3D s_desc[:type].pack(value)
r =3D API.send(set_setting, @buffer, @buffer.size, s_desc[:val],
s_buf, s_buf.size)
return value
end
private :set_setting
end # module Settings
module Utils
# Call an API methods, max attempts time, increasing the size of a buff=
er
# The buffer is located at :buffer.
# Return: [result, buffer, args]
def call_retry(call, size_base, args, attempts =3D 5)
b_index =3D args.index
buffer)
s_index =3D args.index
size)
return nil if b_index.nil?
=20
i =3D 1
begin
args[b_index] =3D buffer =3D " " * (i * size_base)
args[s_index] =3D buffer.size if s_index
r =3D API.__send__(call, *args)
rescue Params =3D> e
i +=3D 1
raise e if i > attempts
retry
end
=20
return [r, buffer, args]
end
module_function :call_retry
end
class Domain
include Utils
class << self
private :new
=20
def names
count =3D API.GetDomainCount
base =3D count * 50
r, buf, args =3D Utils.call_retry
GetDomainList, base, [:buffer, :=
size])
buf[0, r].split("\0")
end
# Get by index or name
def get(ident)
case ident
when Integer
begin
r, name, a =3D Utils.call_retry
GetDomainName, 50,=20
[ident, :buffer, :size])
name =3D name[0, r]
rescue Params
raise Error, "No such domain with index '#{ident}'"
end
index =3D ident
when String
index =3D API.GetDomainIndex(ident,=20
:err_1 =3D> "No such domain '#{ident}'=
")
name =3D ident
end
=20
res =3D new(index, nil, name)
res.reload
res
end=0D
alias :[] :get
def create(name)
r, buffer, args =3D Utils.call_retry
SetDomainDefaults, 4192,=20
[:buffer, :size])
API.AddDomain(name, buffer, buffer.size)
index =3D API.GetDomainIndex(name)
new(index, buffer, name)
end
def delete(ident); get(ident).delete; end
end # class << Domain
=20
attr_reader :index
def initialize(index, buffer =3D nil, name =3D nil)
@index, @buffer, @name =3D index, buffer, name
end
=20
# Returns Virtual Binding IP, or nil if none
def ip
ip =3D " " * 16
r =3D API.GetDomainIP(@index, ip, ip.size)
return (r =3D=3D 0) ? nil : ip[0, r]
end
def ip=3D(ip)
ip ||=3D ""
API.SetDomainIP(@index, ip, ip.size,
:err_1 =3D> "Bad IP '#{ip}'")
ip
end
def save
return true unless @buffer
API.SaveDomain(@index, @buffer, @buffer.size)
self
end
def delete; API.DeleteDomain(@index); self; end
def name
return @name if @name
r, name, args =3D call_retry
GetDomainName, 50, [@index, :buffer, :s=
ize])
@name =3D name[0, r]
@name
end
def inspect; "#<Domain name=3D#{name}; index=3D#{@index}>"; end
SETTINGS_DESC =3D {
:description =3D> { :val =3D> 0, :type =3D> String1024T },
:type =3D> { :val =3D> 1, :type =3D> NumT },
:domain_value =3D> { :val =3D> 3, :type =3D> String1024T },
ostmaster =3D> { :val =3D> 4, :type =3D> List1024T },
:admin_forward =3D> { :val =3D> 5, :type =3D> String1024T },
:unknown_users_forward =3D> { :val =3D> 6, :type =3D> BoolT },
:unknown_forward_to =3D> { :val =3D> 7, :type =3D> List1024T },
:info_to_admin =3D> { :val =3D> 8, :type =3D> BoolT },
}
def settings_desc; SETTINGS_DESC; end
private :settings_desc
SETTINGS_CALLS =3D {=20
:get_setting =3D> :GetDomainSetting, :set_setting =3D> :SetDomainSett=
ing=20
}
def settings_calls; SETTINGS_CALLS; end
private :settings_calls
include Settings
=20
def reload
args =3D [@index, :buffer, :size]
r, buffer, args =3D call_retry
LoadDomain, 4192, args)
@buffer =3D buffer
r
end
# User management
def users
count =3D API.GetUserCount(@name)
base =3D count * 50
r, buf, args =3D call_retry
GetUserList, base, [@name, :buffer, :siz=
e])
buf[0, r].split("\0")
end
def get_user(ident)
case ident
when Integer
index =3D ident
when String
index =3D API.GetUserIndex(@name, ident)
end
r, buf, args =3D call_retry
LoadUser, 4192,=20
[@name, index, :buffer, :size])
User.new(self, index, buf)
end=0D
alias :[] :get_user
def create_user(*aliases)
r, buffer, args =3D Utils.call_retry
SetUserDefaults, 4192,=20
[:buffer, :size])
u =3D User.new(self, nil, buffer)
u.alias =3D aliases unless aliases.empty?
u
end
alias :add_user :create_user
end # Domain
class User
include Utils
=0D
attr_reader :index, :buffer, :domain
def initialize(domain, index, buf)
@index, @buffer, @domain =3D index, buf, domain
end
def inspect
"#<User domain=3D#{@domain ? @domain.name : @domain_index} index=3D#{=
@index}>"
end
# class POP3; end
# class IMAP; end
# class IMAPandPOP3; end
# UserTypeT =3D TypeTVirtual.new([POP3, IMAP, IMAPandPOP3])
SETTINGS_DESC =3D {
:type =3D> { :val =3D> 0, :type =3D> NumT },
:anti_spam_index =3D> { :val =3D> 1, :type =3D> NumT },
:name =3D> { :val =3D> 2, :type =3D> String32T },
:alias =3D> { :val =3D> 3, :type =3D> List128T },
:mailbox =3D> { :val =3D> 16, :type =3D> String32T },
:account_disabled =3D> { :val =3D> 17, :type =3D> BoolT },
:account_valid =3D> { :val =3D> 18, :type =3D> BoolT },
:account_valid_till =3D> { :val =3D> 19, :type =3D> TimeT },
:check_virus =3D> { :val =3D> 20, :type =3D> BoolT },
:allow_remote =3D> { :val =3D> 21, :type =3D> BoolT },
:validity_report =3D> { :val =3D> 22, :type =3D> BoolT },
:validity_report_days =3D> { :val =3D> 23, :type =3D> NumT },
:nt_Password =3D> { :val =3D> 24, :type =3D> BoolT },
:imap =3D> { :val =3D> 25, :type =3D> BoolT },
:imap_mailbox =3D> { :val =3D> 26, :type =3D> String32T },
:max_message_size =3D> { :val =3D> 27, :type =3D> NumT },
:dont_show_messages =3D> { :val =3D> 28, :type =3D> BoolT },
:any_password =3D> { :val =3D> 29, :type =3D> BoolT },
:etrn =3D> { :val =3D> 30, :type =3D> BoolT },
:delete_expire =3D> { :val =3D> 31, :type =3D> BoolT },
:null =3D> { :val =3D> 32, :type =3D> BoolT },
assword =3D> { :val =3D> 33, :type =3D> String32T },
:nt_password_value =3D> { :val =3D> 34, :type =3D> String32T },
:domain_admin_index =3D> { :val =3D> 35, :type =3D> NumT },
:domain_admin =3D> { :val =3D> 36, :type =3D> BoolT },
:mail_box_path =3D> { :val =3D> 37, :type =3D> String128T },
:admin =3D> { :val =3D> 38, :type =3D> BoolT },
:max_box =3D> { :val =3D> 39, :type =3D> NumT },
:max_box_size =3D> { :val =3D> 40, :type =3D> NumT },
:force_from =3D> { :val =3D> 41, :type =3D> String32T },
:respond =3D> { :val =3D> 42, :type =3D> NumT },
nly_local_domain =3D> { :val =3D> 43, :type =3D> BoolT },
:use_remote_address =3D> { :val =3D> 44, :type =3D> String32T },
:forward_to =3D> { :val =3D> 45, :type =3D> String32T },
:respond_with =3D> { :val =3D> 46, :type =3D> String1024T },
:mail_in =3D> { :val =3D> 47, :type =3D> String32T },
:mail_out =3D> { :val =3D> 48, :type =3D> String32T },
:valid_report =3D> { :val =3D> 49, :type =3D> BoolT },
:delete_older =3D> { :val =3D> 50, :type =3D> BoolT },
:delete_older_days =3D> { :val =3D> 51, :type =3D> NumT },
:forward_older =3D> { :val =3D> 52, :type =3D> BoolT },
:forward_older_days =3D> { :val =3D> 53, :type =3D> NumT },
:forward_older_to =3D> { :val =3D> 54, :type =3D> String32T },
:remote_address =3D> { :val =3D> 55, :type =3D> String32T },
:force_from_address =3D> { :val =3D> 56, :type =3D> BoolT },
:megabyte_send_limit =3D> { :val =3D> 57, :type =3D> NumT },
:number_send_limit =3D> { :val =3D> 58, :type =3D> NumT },
}
def settings_desc; SETTINGS_DESC; end
private :settings_desc
SETTINGS_CALLS =3D {
:get_setting =3D> :GetUserSetting, :set_setting =3D> :SetUserSetting=20
}
def settings_calls; SETTINGS_CALLS; end
private :settings_calls
include Settings
def save
return true unless @buffer
if @index
API.SaveUser(@domain.name, @index, @buffer, @buffer.size)
else
r =3D API.AddUser(@domain.name, @buffer, @buffer.size)
@index =3D r
end
self
end
=20
def delete; API.DeleteUser(@domain.name, @index); end
end # User
=20
def self.init(path)
const_set("API", APIClass.new)
API.Init(path)
end
end
--=-XN87+79wxRM1KhQCmcRq
Content-Disposition: attachment; filename=merak_shell.rb
Content-Transfer-Encoding: quoted-printable
Content-Type: text/plain; charset=ISO-8859-1
prompt =3D "Merak %3n:%i"
IRB.conf[
ROMPT][:Merak_Prompt] =3D {
ROMPT_I =3D> "#{prompt}> ",
ROMPT_C =3D> "#{prompt}* ",
ROMPT_S =3D> "#{prompt}* ",
:RETURN =3D> "=3D> %s\n",
}
IRB.conf[
ROMPT_MODE] =3D :Merak_Prompt
require 'merak'
include Merak
Merak.init('C:\Program Files\Merak')
--=-XN87+79wxRM1KhQCmcRq
Content-Disposition: attachment; filename="Merak Shell.bat"
Content-Transfer-Encoding: quoted-printable
Content-Type: text/plain; charset=ISO-8859-1
@echo OFF=0D
cd \cygwin\home\ADMINI~1=0D
irb -r merak_shell=0D
--=-XN87+79wxRM1KhQCmcRq--