Project

General

Profile

Download (16 KB) Statistics
| Branch: | Tag: | Revision:
89d8bebc Marc Dequènes (Duck)
#!/usr/bin/ruby -Ku

#--
# LdapShadows, a Medium-level LDAP Access Library and Tool.
# Copyright (c) 2009 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/>.
#++

# to allow in-place run for test
$: << File.join(File.dirname(__FILE__), "..", "lib")

require 'ldap_shadows'
bbb82941 Marc Dequènes (Duck)
require 'ldap_shadows/display_utils'
89d8bebc Marc Dequènes (Duck)
require 'yaml'
require 'cmdparse2'

bbb82941 Marc Dequènes (Duck)
include LdapShadows

b0304d30 Marc Dequènes (Duck)
89d8bebc Marc Dequènes (Duck)
config_str = IO.read(File.join(LdapShadows::Config::CFG_DIR, "test.conf"))
1744c478 Marc Dequènes (Duck)
config = YAML.load(config_str)
config_str_prv_filelist = [
File.join(ENV['HOME'], ".shadowwalker"),
File.join(LdapShadows::Config::CFG_DIR, "private.conf")
]
config_str_prv_filelist.each do |file|
if File.exists?(file)
config_str_prv = IO.read(file)
config.merge!(YAML.load(config_str_prv))
break
end
end
config.recursive_symbolize_keys!
89d8bebc Marc Dequènes (Duck)
304a503d Marc Dequènes (Duck)
bbb82941 Marc Dequènes (Duck)
$program_options = {}

89d8bebc Marc Dequènes (Duck)
cmdparser = CmdParse::CommandParser.new(true)
bbb82941 Marc Dequènes (Duck)
cmdparser.banner = PRODUCT
5ca3fa56 Marc Dequènes (Duck)
cmdparser.program_name = File.basename(__FILE__)
bbb82941 Marc Dequènes (Duck)
cmdparser.program_version = VERSION.split(".")
89d8bebc Marc Dequènes (Duck)
cmdparser.options = CmdParse::OptionParserWrapper.new do |opt|
opt.separator "Global options:"
bbb82941 Marc Dequènes (Duck)
opt.on("--debug", "Output debug info without being formated") {|t| $program_options[:debug] = true }
opt.on("--expert", "Output extra info for expert users") {|t| $program_options[:expert] = true }
opt.on("--handles", "Output with handles (objects/field/... keys used for manipulations)") {|t| $program_options[:handles] = true }
89d8bebc Marc Dequènes (Duck)
end

cmdparser.add_command(CmdParse::HelpCommand.new)
cmdparser.add_command(CmdParse::VersionCommand.new)

304a503d Marc Dequènes (Duck)
$ldapctl = Controller.new
$ldapctl.set_global_config(config[:presentation])
89d8bebc Marc Dequènes (Duck)
config[:aspects].each_pair do |aspect_name, aspect_data|
304a503d Marc Dequènes (Duck)
$ldapctl.set_aspect(aspect_name, aspect_data)
89d8bebc Marc Dequènes (Duck)
end
127edd07 Marc Dequènes (Duck)
begin
ActiveLdap::Base.setup_connection(config[:ldap])

config[:objects].each_pair do |obj_name, obj_data|
304a503d Marc Dequènes (Duck)
$ldapctl.load_object(obj_name, obj_data)
127edd07 Marc Dequènes (Duck)
end
304a503d Marc Dequènes (Duck)
$ldapctl.load_relations
127edd07 Marc Dequènes (Duck)
rescue ActiveLdap::Error => e
STDERR.puts "LDAP connection error: " + e.to_s
exit 3
89d8bebc Marc Dequènes (Duck)
end


304a503d Marc Dequènes (Duck)
class Command < CmdParse::Command
attr_accessor :usages_params

def usage
if @usages_params
base_usage_str = super.sub("[ARGS]", "")
usages = @usages_params.collect {|params| base_usage_str + params }
usages.join("\n")
else
super
6089b33a Marc Dequènes (Duck)
end
304a503d Marc Dequènes (Duck)
end
end

class ListCommand < Command
def initialize
super('list', false)

self.short_desc = "list items of an object"
self.usages_params = [
"<object>",
":objects",
":aspects <object>"
]
end

def execute (args)
if args.size < 1
STDERR.puts "syntax error: no object name given"
6089b33a Marc Dequènes (Duck)
exit 1
end

304a503d Marc Dequènes (Duck)
obj_hdl = args.shift.singularize
case obj_hdl
when ':object'
26a3ca45 Marc Dequènes (Duck)
list = $ldapctl.objects
puts "=== List of LDAP objects (#{list.size}) ==="
list.each do |obj_name|
304a503d Marc Dequènes (Duck)
puts " - #{obj_name}"
end
when ':aspect'
if args.empty?
STDERR.puts "syntax error: no sub-object name given"
exit 1
end
6089b33a Marc Dequènes (Duck)
304a503d Marc Dequènes (Duck)
subobj_hdl = args.shift.singularize
obj_klass = $ldapctl.find_klass(subobj_hdl)
if obj_klass.nil?
STDERR.puts "No such object '#{obj_hdl}'."
exit 2
end
6089b33a Marc Dequènes (Duck)
304a503d Marc Dequènes (Duck)
subobj_human_name = Translator.translate_object_name(subobj_hdl)
26a3ca45 Marc Dequènes (Duck)
list = obj_klass.possible_aspects
puts "=== List of LDAP aspects of #{subobj_human_name.pluralize} (#{list.size}) ==="
list.each do |subobj_name|
304a503d Marc Dequènes (Duck)
puts " - #{subobj_name}"
end
else
obj_klass = $ldapctl.find_klass(obj_hdl)
if obj_klass.nil?
STDERR.puts "No such object '#{obj_hdl}'."
exit 2
end
89d8bebc Marc Dequènes (Duck)
304a503d Marc Dequènes (Duck)
obj_human_name = Translator.translate_object_name(obj_hdl)
Display.display_item_list("List of #{obj_human_name.pluralize}", obj_klass.find(:all))
end
89d8bebc Marc Dequènes (Duck)
end
304a503d Marc Dequènes (Duck)
end
cmdparser.add_command(ListCommand.new)

class ShowCommand < Command
def initialize
super('show', false)

self.short_desc = "show item information"
self.usages_params = [
"<object> <item>",
"<object> <item> <field>#?",
"<object> <item> <field>[#<index>]"
]
89d8bebc Marc Dequènes (Duck)
end

304a503d Marc Dequènes (Duck)
def execute (args)
if args.size < 1
STDERR.puts "syntax error: no object name given"
exit 1
end
if args.size < 2
STDERR.puts "syntax error: no item name given"
exit 1
end
ca51664c Marc Dequènes (Duck)
304a503d Marc Dequènes (Duck)
obj_hdl = args.shift
obj_klass = $ldapctl.find_klass(obj_hdl)
if obj_klass.nil?
STDERR.puts "No such object '#{obj_hdl}'."
ca51664c Marc Dequènes (Duck)
exit 2
end
304a503d Marc Dequènes (Duck)
item_hdl = args.shift
begin
item = obj_klass.find(item_hdl, :attributes => ["*", "+"])
rescue ActiveLdap::EntryNotFound
STDERR.puts "No such item '#{obj_hdl}/#{item_hdl}'"
ca51664c Marc Dequènes (Duck)
exit 2
end

304a503d Marc Dequènes (Duck)
if args.empty?
$program_options[:skip_binary] = true
Display.display_item(item, $program_options)
ca51664c Marc Dequènes (Duck)
else
304a503d Marc Dequènes (Duck)
field_info = args.shift
field_name, file_no = field_info.split("#")
ca51664c Marc Dequènes (Duck)
5d28ad6e Marc Dequènes (Duck)
unless item.has_field?(field_name)
304a503d Marc Dequènes (Duck)
STDERR.puts "No such field '#{field_name}' in object '#{obj_hdl}'."
ca51664c Marc Dequènes (Duck)
exit 2
end
304a503d Marc Dequènes (Duck)
unless item.attribute_present?(field_name)
STDERR.puts "Field '#{field_name}' in item '#{obj_hdl}/#{item_hdl}' is not present."
ca51664c Marc Dequènes (Duck)
exit 2
end

5d28ad6e Marc Dequènes (Duck)
field_data = item.send(field_name, true)
ca51664c Marc Dequènes (Duck)
11c9a6c9 Marc Dequènes (Duck)
result_is_binary = false
304a503d Marc Dequènes (Duck)
if file_no == "?"
result = field_data.size
else
file_no_i = file_no.to_i
file_no = (file_no_i.to_s == file_no) ? file_no_i : nil
ca51664c Marc Dequènes (Duck)
304a503d Marc Dequènes (Duck)
attr_info = ActiveLdap::Base.schema.attribute(field_name)

field_single = attr_info.single_value?
if file_no.nil? and not field_single
STDERR.puts "Field '#{field_name}' in object '#{obj_hdl}' has multiple values, but you didn't select one."
exit 2
ca51664c Marc Dequènes (Duck)
end
304a503d Marc Dequènes (Duck)
file_no = 0 if file_no.nil?
if file_no > 0 and field_single
STDERR.puts "Field '#{field_name}' in object '#{obj_hdl}' has only a single value"
exit 2
end
if file_no < 0 or file_no >= field_data.size
STDERR.puts "Field '#{field_name}' in object '#{obj_hdl}' doesn't have such file number '#{file_no}' (select in range [0, #{field_data.size - 1}])"
exit 2
end

11c9a6c9 Marc Dequènes (Duck)
result = field_data[file_no]
result_is_binary = attr_info.binary?
ca51664c Marc Dequènes (Duck)
end

304a503d Marc Dequènes (Duck)
if args.empty?
11c9a6c9 Marc Dequènes (Duck)
if result_is_binary
# output binary data without newline, so it can be pipped or redirected easily
STDOUT.write result
else
puts result
end
304a503d Marc Dequènes (Duck)
else
file_name = args.shift
if File.exists?(file_name)
STDERR.puts "File '#{file_name}' already exists, and i won't overwrite it."
exit 2
end

begin
open(file_name, "w") do |fp|
fp.write result
end
rescue
STDERR.puts "Cannot save file: " + $!
exit 3
end

puts "File saved."
end
ca51664c Marc Dequènes (Duck)
end
end
89d8bebc Marc Dequènes (Duck)
end
304a503d Marc Dequènes (Duck)
cmdparser.add_command(ShowCommand.new)
89d8bebc Marc Dequènes (Duck)
304a503d Marc Dequènes (Duck)
class TreeCommand < Command
def initialize
super('tree', false)
0d75a0b6 Marc Dequènes (Duck)
304a503d Marc Dequènes (Duck)
self.short_desc = "show skeleton items tree"
self.usages_params = [
""
]
end

def execute (args)
puts "Tree:"

base_dn = ActiveLdap::DistinguishedName.parse(LdapObject.base)
0d75a0b6 Marc Dequènes (Duck)
304a503d Marc Dequènes (Duck)
gconfig = $ldapctl.get_global_config()
if gconfig.has_key?(:tree_objects)
tree = {}
0d75a0b6 Marc Dequènes (Duck)
304a503d Marc Dequènes (Duck)
gconfig[:tree_objects].each do |obj_hdl|
obj_klass = $ldapctl.find_klass(obj_hdl)
raise "object '#{obj_hdl}' not defined" unless obj_klass
0d75a0b6 Marc Dequènes (Duck)
304a503d Marc Dequènes (Duck)
obj_klass.find(:all).each do |obj|
ptree = tree
pdn = nil
(obj.dn_obj - base_dn).to_s.split(',').reverse.each do |pdn|
ptree[pdn] = {} unless ptree.has_key?(pdn)
ptree = ptree[pdn]
end
ptree = nil
0d75a0b6 Marc Dequènes (Duck)
end
end
304a503d Marc Dequènes (Duck)
tree = {base_dn => tree}
0d75a0b6 Marc Dequènes (Duck)
304a503d Marc Dequènes (Duck)
Display.display_hash_tree(tree, 0)
end
0d75a0b6 Marc Dequènes (Duck)
end
end
304a503d Marc Dequènes (Duck)
cmdparser.add_command(TreeCommand.new)
b0304d30 Marc Dequènes (Duck)
304a503d Marc Dequènes (Duck)
class ModCommand < Command
def initialize
super('mod', false)
b0304d30 Marc Dequènes (Duck)
304a503d Marc Dequènes (Duck)
self.short_desc = "Modify attributes of an item"
self.usages_params = [
"<object> <item> (<field>|<relation>)(=|+=|-=)<value> [(<field>|<relation>)(=|+=|-=)<value>] ..."
]
b0304d30 Marc Dequènes (Duck)
end

304a503d Marc Dequènes (Duck)
def execute (args)
if args.size < 1
STDERR.puts "syntax error: no object name given"
0814bfc3 Marc Dequènes (Duck)
exit 1
end
304a503d Marc Dequènes (Duck)
if args.size < 2
STDERR.puts "syntax error: no item name given"
exit 1
0814bfc3 Marc Dequènes (Duck)
end

304a503d Marc Dequènes (Duck)
obj_hdl = args.shift
obj_klass = $ldapctl.find_klass(obj_hdl)
if obj_klass.nil?
STDERR.puts "No such object '#{obj_hdl}'."
exit 2
end
43bd8057 Marc Dequènes (Duck)
304a503d Marc Dequènes (Duck)
item_hdl = args.shift
begin
item = obj_klass.find(item_hdl)
rescue ActiveLdap::EntryNotFound
STDERR.puts "No such item '#{obj_hdl}/#{item_hdl}'"
exit 2
end
43bd8057 Marc Dequènes (Duck)
304a503d Marc Dequènes (Duck)
modification_done = false
args.each do |mod_info|
mod_info =~ /^([a-zA-Z]+(?::[a-zA-Z]+)?)(=|\+=|-=)(.*)$/
key = $1
op = $2
val = $3
43bd8057 Marc Dequènes (Duck)
304a503d Marc Dequènes (Duck)
if key.nil?
STDERR.puts "Syntax error in modification parameters: invalid field name"
exit 1
34cb4054 Marc Dequènes (Duck)
end
43bd8057 Marc Dequènes (Duck)
304a503d Marc Dequènes (Duck)
if key.index(":")
type, field = key.split(":")
else
type = nil
field = key
43bd8057 Marc Dequènes (Duck)
end
0814bfc3 Marc Dequènes (Duck)
43bd8057 Marc Dequènes (Duck)
case type
when nil
5d28ad6e Marc Dequènes (Duck)
unless item.has_field?(field)
304a503d Marc Dequènes (Duck)
STDERR.puts "No such field '#{field}' in object '#{obj_hdl}'."
exit 2
43bd8057 Marc Dequènes (Duck)
end

304a503d Marc Dequènes (Duck)
attr_info = ActiveLdap::Base.schema.attribute(field)
if attr_info.read_only?
STDERR.puts "The field '#{field}' cannot be modified (read only), skipping."
next
end

11c9a6c9 Marc Dequènes (Duck)
if attr_info.binary?
unless File.exists?(val)
STDERR.puts "The field '#{field}' contains binary data, you must provide a filename instead of a direct value."
exit 2
end

begin
val = File.read(val)
rescue
STDERR.puts "The file for the binary field '#{field}' cannot be read: " + $!
exit 2
end
end

5d28ad6e Marc Dequènes (Duck)
old_val = item.send(field, true)
43bd8057 Marc Dequènes (Duck)
when 'rel'
304a503d Marc Dequènes (Duck)
unless item.relations.include?(field)
STDERR.puts "No such relation '#{field}' for object '#{obj_hdl}'."
exit 2
end

rel_info = item.info_for_relation(field)
if rel_info[:read_only]
STDERR.puts "The relation '#{field}' cannot be modified (read only), skipping."
next
43bd8057 Marc Dequènes (Duck)
end

304a503d Marc Dequènes (Duck)
# fetch remote object in relation, which will be the real 'val'
foreign_item_list = rel_info[:foreign_klass].find(:all, val)
if foreign_item_list.empty?
STDERR.puts "Foreign item '#{val}' for relation '#{field}' not found."
exit 2
end
if foreign_item_list.size > 1
STDERR.puts "Ambiguous item '#{val}' for relation '#{field}' (#{foreign_item_list.size} possible items)"
exit 4
end
val = foreign_item_list.first
else
STDERR.puts "Unknown type '#{type}' for field '#{field}'."
0814bfc3 Marc Dequènes (Duck)
end
304a503d Marc Dequènes (Duck)
# if val is nil or the latest value is removed, then the atribute is removed from the object,
# or the association's attribute is removed from one side
case op
when '='
item.send(field + "=", [val])
when '+='
case type
when nil
if attr_info.single_value?
STDERR.puts "The field '#{field}' cannot hold more than one value."
exit 3
end

new_val = old_val << val
item.send(field + "=", new_val)
when 'rel'
if rel_info[:single_value]
STDERR.puts "The relation '#{field}' cannot hold more than one foreign item."
exit 3
end

item.send(field) << val
end
when '-='
case type
when nil
new_val = old_val - [val]
11c9a6c9 Marc Dequènes (Duck)
item.send(field + "=", new_val)
304a503d Marc Dequènes (Duck)
when 'rel'
item.send(field).delete(val)
end
else
STDERR.puts "Syntax error in modification parameters: wrong operator"
exit 1
43bd8057 Marc Dequènes (Duck)
end
304a503d Marc Dequènes (Duck)
modification_done = true
end

if modification_done
begin
item.save!
rescue ActiveLdap::OperationNotPermitted => e
STDERR.puts "You don't have enough rights to modify--at least--one of these attributes."
exit 3
end

puts "Modification done."
0814bfc3 Marc Dequènes (Duck)
else
304a503d Marc Dequènes (Duck)
puts "Nothing to do."
0814bfc3 Marc Dequènes (Duck)
end
b0304d30 Marc Dequènes (Duck)
end
304a503d Marc Dequènes (Duck)
end
cmdparser.add_command(ModCommand.new)
0814bfc3 Marc Dequènes (Duck)
304a503d Marc Dequènes (Duck)
class DelCommand < Command
def initialize
super('del', false)

self.short_desc = "Delete an item"
self.usages_params = [
5d28ad6e Marc Dequènes (Duck)
"<object> <item>"
304a503d Marc Dequènes (Duck)
]
end

def execute (args)
if args.size < 1
STDERR.puts "syntax error: no object name given"
exit 1
end
if args.size < 2
STDERR.puts "syntax error: no item name given"
exit 1
end

obj_hdl = args.shift
obj_klass = $ldapctl.find_klass(obj_hdl)
if obj_klass.nil?
STDERR.puts "No such object '#{obj_hdl}'."
exit 2
end

item_hdl = args.shift
34cb4054 Marc Dequènes (Duck)
begin
304a503d Marc Dequènes (Duck)
item = obj_klass.find(item_hdl, :attributes => ["*", "+"])
rescue ActiveLdap::EntryNotFound
STDERR.puts "No such item '#{obj_hdl}/#{item_hdl}'"
exit 2
34cb4054 Marc Dequènes (Duck)
end
b0304d30 Marc Dequènes (Duck)
304a503d Marc Dequènes (Duck)
item.delete

puts "Deletion done."
34cb4054 Marc Dequènes (Duck)
end
b0304d30 Marc Dequènes (Duck)
end
304a503d Marc Dequènes (Duck)
cmdparser.add_command(DelCommand.new)

5d28ad6e Marc Dequènes (Duck)
class CreateCommand < Command
def initialize
super('create', false)

self.short_desc = "Create an item"
self.usages_params = [
"<object> <new_item> [<field>=<value>] ..."
]
end

def execute (args)
if args.size < 1
STDERR.puts "syntax error: no object name given"
exit 1
end
if args.size < 2
STDERR.puts "syntax error: no item name given"
exit 1
end

obj_hdl = args.shift
obj_klass = $ldapctl.find_klass(obj_hdl)
if obj_klass.nil?
STDERR.puts "No such object '#{obj_hdl}'."
exit 2
end

item_hdl = args.shift
item = obj_klass.new(item_hdl)

args.each do |param|
param =~ /^([a-zA-Z]+)=(.*)$/
key = $1
val = $2

if key.nil?
STDERR.puts "Syntax error in creation parameters: invalid field name"
exit 1
end

field = key

unless item.has_field?(field)
STDERR.puts "No such field '#{field}' in object '#{obj_hdl}'."
exit 2
end

attr_info = ActiveLdap::Base.schema.attribute(field)
if attr_info.read_only?
STDERR.puts "The field '#{field}' cannot be modified (read only), skipping."
next
end

if attr_info.binary?
unless File.exists?(val)
STDERR.puts "The field '#{field}' contains binary data, you must provide a filename instead of a direct value."
exit 2
end

begin
val = File.read(val)
rescue
STDERR.puts "The file for the binary field '#{field}' cannot be read: " + $!
exit 2
end
end

item.send(field + "=", val)
end

item.save!
end
end
cmdparser.add_command(CreateCommand.new)

b0304d30 Marc Dequènes (Duck)
127edd07 Marc Dequènes (Duck)
begin
cmdparser.parse
rescue ActiveLdap::EntryInvalid => e
11c9a6c9 Marc Dequènes (Duck)
# work around #26758
STDERR.puts e.to_s.sub(/invalid format: .*: required syntax:/, "invalid format. required syntax:")
127edd07 Marc Dequènes (Duck)
exit 3
rescue ActiveLdap::OperationNotPermitted => e
STDERR.puts "You don't have enough rights for this operation."
exit 3
rescue ActiveLdap::Error => e
STDERR.puts "LDAP error: " + e.to_s
exit 3
end