Project

General

Profile

Download (19.5 KB) Statistics
| Branch: | Tag: | Revision:
#!/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'
require 'ldap_shadows/display_utils'
require 'yaml'
require 'cmdparse2'

include LdapShadows


config_str = IO.read(File.join(LdapShadows::Config::CFG_DIR, "test.conf"))
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!


$program_options = {}

cmdparser = CmdParse::CommandParser.new(true)
cmdparser.banner = PRODUCT
cmdparser.program_name = File.basename(__FILE__)
cmdparser.program_version = VERSION.split(".")

cmdparser.options = CmdParse::OptionParserWrapper.new do |opt|
opt.separator "Global options:"
opt.on("-d", "--debug", "Output debug info without being formated") {|t| $program_options[:debug] = true }
opt.on("-e", "--expert", "Output extra info for expert users") {|t| $program_options[:expert] = true }
opt.on("-H", "--handles", "Output with handles (objects/field/... keys used for manipulations)") {|t| $program_options[:handles] = true }
end

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


$ldapctl = Controller.new
$ldapctl.set_global_config(config[:presentation])
config[:aspects].each_pair do |aspect_name, aspect_data|
$ldapctl.set_aspect(aspect_name, aspect_data)
end
begin
ActiveLdap::Base.setup_connection(config[:ldap])

config[:objects].each_pair do |obj_name, obj_data|
$ldapctl.load_object(obj_name, obj_data)
end
$ldapctl.load_relations
rescue ActiveLdap::Error => e
STDERR.puts "LDAP connection error: " + e.to_s
exit 3
end


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
end
end
end

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

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

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

obj_hdl = args.shift.downcase.singularize
case obj_hdl
when ':object'
list = $ldapctl.objects
puts "=== List of LDAP objects (#{list.size}) ==="
list.each do |obj_name|
puts " - #{obj_name}"
end
else
obj_klass = $ldapctl.find_klass(obj_hdl)
if obj_klass.nil?
STDERR.puts "No such object '#{obj_hdl}'."
exit 2
end

if args.empty?
obj_human_name = Translator.translate_object_name(obj_hdl)
Display.display_item_list("List of #{obj_human_name.pluralize}", obj_klass.find(:all))
else
subobj_hdl = args.shift.downcase.singularize

obj_human_name = Translator.translate_object_name(obj_hdl)

case subobj_hdl
when ':aspect'
list = obj_klass.possible_aspects
puts "=== List of LDAP Aspects of #{obj_human_name.pluralize} (#{list.size}) ==="
list.each do |aspect|
str = Translator.translate_aspect_name(aspect)
str += " [#{aspect}]" if $program_options[:handles]
puts " - #{str}"
end
when ':field'
list = obj_klass.possible_attributes
puts "=== List of LDAP Fields of #{obj_human_name.pluralize} (#{list.size}) ==="
list.each do |field|
str = Translator.translate_field_name(field)
str += " [#{field}]" if $program_options[:handles]
puts " - #{str}"
end
obj_klass.possible_aspects.each do |aspect|
aspect_human_name = Translator.translate_aspect_name(aspect)
list = obj_klass.possible_attributes_for_aspect(aspect)
puts "--- Sublist of LDAP Fields for Aspect #{aspect_human_name} (#{list.size}) ---"
list.each do |field|
str = Translator.translate_field_name(field)
str += " [#{field}]" if $program_options[:handles]
puts " - #{str}"
end
end
else
STDERR.puts "No such subobject '#{subobj_hdl}'."
exit 2
end
end
end
end
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>]"
]
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
begin
item = obj_klass.find(item_hdl, :attributes => ["*", "+"])
rescue ActiveLdap::EntryNotFound
STDERR.puts "No such item '#{obj_hdl}/#{item_hdl}'"
exit 2
end

if args.empty?
$program_options[:skip_binary] = true
Display.display_item(item, $program_options)
else
field_info = args.shift
field_name, file_no = field_info.split("#")

unless item.has_field?(field_name)
STDERR.puts "No such field '#{field_name}' in object '#{obj_hdl}'."
exit 2
end
unless item.attribute_present?(field_name)
STDERR.puts "Field '#{field_name}' in item '#{obj_hdl}/#{item_hdl}' is not present."
exit 2
end

field_data = item.send(field_name, true)

result_is_binary = false
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

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
end

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

result = field_data[file_no]
result_is_binary = attr_info.binary?
end

if args.empty?
if result_is_binary
# output binary data without newline, so it can be pipped or redirected easily
STDOUT.write result
else
puts result
end
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
end
end
end
cmdparser.add_command(ShowCommand.new)

class TreeCommand < Command
def initialize
super('tree', false)

self.short_desc = "show skeleton items tree"
self.usages_params = [
""
]
end

def execute (args)
puts "Tree:"

base_dn = ActiveLdap::DistinguishedName.parse(LdapObject.base)

gconfig = $ldapctl.get_global_config()
if gconfig.has_key?(:tree_objects)
tree = {}

gconfig[:tree_objects].each do |obj_hdl|
obj_klass = $ldapctl.find_klass(obj_hdl)
raise "object '#{obj_hdl}' not defined" unless obj_klass

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
end
end
tree = {base_dn => tree}

Display.display_hash_tree(tree, 0)
end
end
end
cmdparser.add_command(TreeCommand.new)

class ModCommand < Command
def initialize
super('mod', false)

self.short_desc = "Modify attributes of an item"
self.usages_params = [
"<object> <item> (<field>|<relation>|:aspects)(=|+=|-=)<value> [(<field>|<relation>|:aspects)(=|+=|-=)<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
begin
item = obj_klass.find(item_hdl)
rescue ActiveLdap::EntryNotFound
STDERR.puts "No such item '#{obj_hdl}/#{item_hdl}'"
exit 2
end

modification_done = false
args.each do |mod_info|
mod_info =~ /^([a-zA-Z]*(?::[a-zA-Z]+)?)(=|\+=|-=)(.*)$/
key = $1
op = $2
val = $3

if key.nil? or op.nil?
STDERR.puts "Syntax error in modification parameters: invalid field name, or missing/unknown operator, or empty value"
exit 1
end

if key.index(":")
type, field = key.split(":")
else
type = nil
field = key
end

case type
when nil
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

old_val = item.send(field, true)
when 'rel'
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
end

# 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
when ''
case field
when 'aspects'
unless item.class.possible_aspects.include?(val)
STDERR.puts "No such aspect '#{val}' for object '#{obj_hdl}'."
exit 2
end
else
STDERR.puts "Unknown core field '#{field}'"
exit 2
end
else
STDERR.puts "Unknown type '#{type}' for field '#{field}'."
exit 2
end

# 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 '='
if type == '' and field == 'aspects'
STDERR.puts "The equality operator is not possible for aspects."
exit 2
end
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
when ''
case field
when 'aspects'
item.add_aspect(val)
end
end
when '-='
case type
when nil
new_val = old_val - [val]
item.send(field + "=", new_val)
when 'rel'
item.send(field).delete(val)
when ''
case field
when 'aspects'
item.remove_aspect(val)
end
end
else
STDERR.puts "Syntax error in modification parameters: wrong operator"
exit 1
end
modification_done = true
end

if modification_done
missing_fields = item.missing_attributes
unless missing_fields.empty?
STDERR.puts "Cannot save the modifications; the following fields are missing:"
missing_fields.each do |field|
str = Translator.translate_field_name(field)
str += " [#{field}]" if $program_options[:handles]
puts " - #{str}"
end
exit 2
end

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."
else
puts "Nothing to do."
end
end
end
cmdparser.add_command(ModCommand.new)

class DelCommand < Command
def initialize
super('del', false)

self.short_desc = "Delete an item"
self.usages_params = [
"<object> <item>"
]
self.options = CmdParse::OptionParserWrapper.new do |opt|
opt.on( '-r', '--recursive', 'Delete object and all children recursively.' ) { $program_options[:recursive_delete] = true }
end
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
begin
item = obj_klass.find(item_hdl, :attributes => ["*", "+"])
rescue ActiveLdap::EntryNotFound
STDERR.puts "No such item '#{obj_hdl}/#{item_hdl}'"
exit 2
end

if $program_options[:recursive_delete]
item.class.delete_all(nil, :scope => :sub, :base => item.dn)
else
item.delete
end

puts "Deletion done."
end
end
cmdparser.add_command(DelCommand.new)

class CreateCommand < Command
def initialize
super('create', false)

self.short_desc = "Create an item"
self.usages_params = [
"<object> <new_item> <location> [<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
if args.size < 3
STDERR.puts "syntax error: no location 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

location = args.shift
if location =~ /^([a-zA-Z]+)\/(.+)$/
loc_obj_hdl = $1
loc_item_hdl = $2
else
STDERR.puts "syntax error: bad location"
exit 1
end
loc_obj_klass = $ldapctl.find_klass(loc_obj_hdl)
if loc_obj_klass.nil?
STDERR.puts "No such location object '#{loc_obj_hdl}'."
exit 2
end
begin
loc_item = loc_obj_klass.find(loc_item_hdl)
rescue ActiveLdap::EntryNotFound
STDERR.puts "No such item '#{loc_obj_hdl}/#{loc_item_hdl}'"
exit 2
end

item = obj_klass.new(item_hdl)
item.base = loc_item.dn_obj - obj_klass.base_obj

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

val = nil if val == ''

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

missing_fields = item.missing_attributes
unless missing_fields.empty?
STDERR.puts "Cannot save the new item; the following fields are missing:"
missing_fields.each do |field|
str = Translator.translate_field_name(field)
str += " [#{field}]" if $program_options[:handles]
puts " - #{str}"
end
exit 2
end

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


begin
cmdparser.parse
rescue ActiveLdap::EntryInvalid => e
# work around activeldap#26758
STDERR.puts e.to_s.sub(/invalid format: .*: required syntax:/, "invalid format. required syntax:")
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
    (1-1/1)