shelldap
changeset 51 27bbe75233a3
parent 50 21ba5eb5c2fc
child 54 0cc20d93ff50
equal deleted inserted replaced
50:21ba5eb5c2fc 51:27bbe75233a3
    56  - History and autocomplete via readline, if installed.
    56  - History and autocomplete via readline, if installed.
    57 
    57 
    58  - Automatic reconnection attempts if the connection is lost with the
    58  - Automatic reconnection attempts if the connection is lost with the
    59    LDAP server.
    59    LDAP server.
    60 
    60 
       
    61  - Basic schema introspection for quick reference.
       
    62 
    61  - It feels like a semi-crippled shell, making LDAP browsing and editing
    63  - It feels like a semi-crippled shell, making LDAP browsing and editing
    62    at least halfway pleasurable.
    64    at least halfway pleasurable.
    63 
    65 
    64 =head1 OPTIONS
    66 =head1 OPTIONS
    65 
    67 
    90 default ~/.shelldap.rc.
    92 default ~/.shelldap.rc.
    91 
    93 
    92     --configfile /tmp/alternate-config.yml
    94     --configfile /tmp/alternate-config.yml
    93     -f /tmp/alternate-config.yml
    95     -f /tmp/alternate-config.yml
    94 
    96 
       
    97 This config file overrides values found in the default config, so
       
    98 you can easily have separate config files for connecting to your
       
    99 cn=monitor or cn=log overlays (for example.)
       
   100 
    95 =back
   101 =back
    96 
   102 
    97 =over 4
   103 =over 4
    98 
   104 
    99 =item B<server>
   105 =item B<server>
   291 
   297 
   292     edit uid=mahlon
   298     edit uid=mahlon
   293 
   299 
   294 aliased to: vi
   300 aliased to: vi
   295 
   301 
   296 =item B< env>
   302 =item B<env>
   297 
   303 
   298  Show values for various runtime variables.
   304  Show values for various runtime variables.
   299 
   305 
   300 =item B<grep>
   306 =item B<grep>
   301 
   307 
   305     grep uid=mahlon
   311     grep uid=mahlon
   306     grep uid=mahlon ou=People
   312     grep uid=mahlon ou=People
   307     grep -r (&(uid=mahlon)(objectClass=*))
   313     grep -r (&(uid=mahlon)(objectClass=*))
   308 
   314 
   309  aliased to: search
   315  aliased to: search
       
   316 
       
   317 =item B<inspect>
       
   318 
       
   319 View schema information about a given entry, or a list of arbitrary
       
   320 objectClasses, along with the most common flags for the objectClass
       
   321 attributes.
       
   322 
       
   323     inspect uid=mahlon
       
   324     inspect posixAccount organizationalUnit
       
   325     inspect _schema
       
   326 
       
   327 The output is a list of found objectClasses, their schema heirarchy
       
   328 (up to 'top'), whether or not they are a structural class, and then
       
   329 a merged list of all valid attributes for the given objectClasses.
       
   330 Attributes are marked as either required or optional, and whether
       
   331 they allow multiple values or not.
       
   332 
       
   333 If you ask for the special "_schema" object, the raw server schema
       
   334 is dumped to screen.
   310 
   335 
   311 =item B<list>
   336 =item B<list>
   312 
   337 
   313 List entries for the current basedn.  Globbing is supported.
   338 List entries for the current basedn.  Globbing is supported.
   314 
   339 
   580 sub ldif 
   605 sub ldif 
   581 {
   606 {
   582 	my $self	 = shift;
   607 	my $self	 = shift;
   583 	my $use_temp = shift;
   608 	my $use_temp = shift;
   584 
   609 
   585 	# find the terminal width
   610 	# create tmpfile and link ldif object with it
   586 	#
   611 	#
       
   612 	if ( $use_temp ) {
       
   613 		my ( undef, $fname ) =
       
   614 		  File::Temp::tempfile( 'shelldap_XXXXXXXX', DIR => '/tmp', UNLINK => 1 );
       
   615 		$self->{'ldif'}	      = Net::LDAP::LDIF->new( $fname, 'w', sort => 1, wrap => $self->wrapsize );
       
   616 		$self->{'ldif_fname'} = $fname;
       
   617 	}
       
   618 
       
   619 	# ldif -> stdout
       
   620 	else {
       
   621 		$self->{'ldif'} = Net::LDAP::LDIF->new( \*STDOUT, 'w', sort => 1, wrap => $self->wrapsize );
       
   622 	}
       
   623 
       
   624 	return $self->{'ldif'};
       
   625 }
       
   626 
       
   627 
       
   628 ### Return an Entry object from an LDIF filename, or undef if there was an error.
       
   629 ###
       
   630 sub load_ldif
       
   631 {
       
   632 	my $self = shift;
       
   633 	my $ldif = Net::LDAP::LDIF->new( shift(), 'r' );
       
   634 
       
   635 	return unless $ldif;
       
   636 
       
   637 	my $e;
       
   638 	eval { $e = $ldif->read_entry(); };
       
   639 
       
   640 	return if $@;
       
   641 	return $e;
       
   642 }
       
   643 
       
   644 
       
   645 ### Given a filename, return an md5 checksum.
       
   646 ###
       
   647 sub chksum 
       
   648 {
       
   649 	my $self = shift;
       
   650 	my $file = shift or return;
       
   651 
       
   652 	my $md5 = Digest::MD5->new();
       
   653 	open F, $file or die "Unable to read file: $!\n";
       
   654 	my $hash = $md5->addfile( *F )->hexdigest();
       
   655 	close F;
       
   656 
       
   657 	return $hash;
       
   658 }
       
   659 
       
   660 
       
   661 ### Find and return the current terminal width.
       
   662 ###
       
   663 sub wrapsize
       
   664 {
       
   665 	my $self = shift;
       
   666 
   587 	my $wrap = $conf->{'wrap'};
   667 	my $wrap = $conf->{'wrap'};
   588 	eval {
   668 	eval {
   589 		my $rows;
   669 		my $rows;
   590 		my $term = Term::ReadLine->new( 1 );
   670 		my $term = Term::ReadLine->new( 1 );
   591 		( $rows, $wrap ) = $term->get_screen_size() unless $wrap;
   671 		( $rows, $wrap ) = $term->get_screen_size() unless $wrap;
   592 	};
   672 	};
       
   673 
   593 	$wrap ||= 78;
   674 	$wrap ||= 78;
   594 
   675 	return $wrap;
   595 	# create tmpfile and link ldif object with it
       
   596 	#
       
   597 	if ( $use_temp ) {
       
   598 		my ( undef, $fname ) =
       
   599 		  File::Temp::tempfile( 'shelldap_XXXXXXXX', DIR => '/tmp', UNLINK => 1 );
       
   600 		$self->{'ldif'}	      = Net::LDAP::LDIF->new( $fname, 'w', sort => 1, wrap => $wrap );
       
   601 		$self->{'ldif_fname'} = $fname;
       
   602 	}
       
   603 
       
   604 	# ldif -> stdout
       
   605 	else {
       
   606 		$self->{'ldif'} = Net::LDAP::LDIF->new( \*STDOUT, 'w', sort => 1, wrap => $wrap );
       
   607 	}
       
   608 
       
   609 	return $self->{'ldif'};
       
   610 }
       
   611 
       
   612 
       
   613 ### Return an Entry object from an LDIF filename, or undef if there was an error.
       
   614 ###
       
   615 sub load_ldif
       
   616 {
       
   617 	my $self = shift;
       
   618 	my $ldif = Net::LDAP::LDIF->new( shift(), 'r' );
       
   619 
       
   620 	return unless $ldif;
       
   621 
       
   622 	my $e;
       
   623 	eval { $e = $ldif->read_entry(); };
       
   624 
       
   625 	return if $@;
       
   626 	return $e;
       
   627 }
       
   628 
       
   629 
       
   630 ### Given a filename, return an md5 checksum.
       
   631 ###
       
   632 sub chksum 
       
   633 {
       
   634 	my $self = shift;
       
   635 	my $file = shift or return;
       
   636 
       
   637 	my $md5 = Digest::MD5->new();
       
   638 	open F, $file or die "Unable to read file: $!\n";
       
   639 	my $hash = $md5->addfile( *F )->hexdigest();
       
   640 	close F;
       
   641 
       
   642 	return $hash;
       
   643 }
   676 }
   644 
   677 
   645 
   678 
   646 ### Used by Term::Shell to generate the prompt.
   679 ### Used by Term::Shell to generate the prompt.
   647 ###
   680 ###
   984 ### Autocomplete values: Returns cached children entries.
  1017 ### Autocomplete values: Returns cached children entries.
   985 ###
  1018 ###
   986 sub autocomplete_cwd
  1019 sub autocomplete_cwd
   987 {
  1020 {
   988 	my $self = shift;
  1021 	my $self = shift;
   989 	my $word = $_[0];
  1022 	return @{ $self->{'cwd_entries'} };
   990 
       
   991 	return sort @{ $self->{'cwd_entries'} };
       
   992 }
  1023 }
   993 
  1024 
   994 
  1025 
   995 ### Autocomplete values: Returns previously set shelldap environment values.
  1026 ### Autocomplete values: Returns previously set shelldap environment values.
   996 ###
  1027 ###
  1006 ###
  1037 ###
  1007 sub comp_create
  1038 sub comp_create
  1008 {
  1039 {
  1009 	my $self = shift;
  1040 	my $self = shift;
  1010 	return @{ $self->{'objectclasses'} };
  1041 	return @{ $self->{'objectclasses'} };
       
  1042 }
       
  1043 
       
  1044 
       
  1045 ### Autocomplete values: Returns all objectClasses as defined
       
  1046 ### by the LDAP server, along with current children DNs.
       
  1047 ###
       
  1048 sub comp_inspect
       
  1049 {
       
  1050 	my $self = shift;
       
  1051 	return ('_schema', @{ $self->{'objectclasses'} }, @{ $self->{'cwd_entries'} });
  1011 }
  1052 }
  1012 
  1053 
  1013 
  1054 
  1014 ### Inject various autocomplete and alias routines into the symbol table.
  1055 ### Inject various autocomplete and alias routines into the symbol table.
  1015 ###
  1056 ###
  1035 	foreach ( %cmd_map ) {
  1076 	foreach ( %cmd_map ) {
  1036 		next unless $_;
  1077 		next unless $_;
  1037 		my $sub = "comp_$_";
  1078 		my $sub = "comp_$_";
  1038 		*$sub   = \&autocomplete_cwd;
  1079 		*$sub   = \&autocomplete_cwd;
  1039 	}
  1080 	}
  1040 	*comp_touch  = \&comp_create;
  1081 	*comp_touch   = \&comp_create;
  1041 	*comp_export = \&comp_setenv;
  1082 	*comp_export  = \&comp_setenv;
  1042 
  1083 
  1043 	# setup alias subs
  1084 	# setup alias subs
  1044 	#
  1085 	#
  1045 	# Term::Shell has an alias_* feature, but
  1086 	# Term::Shell has an alias_* feature, but
  1046 	# it seems to work about 90% of the time.
  1087 	# it seems to work about 90% of the time.
  1983 	print "\n";
  2024 	print "\n";
  1984 	return;
  2025 	return;
  1985 }
  2026 }
  1986 
  2027 
  1987 
  2028 
       
  2029 ### Show basic information for an entry (DN) or list of objectClasses.
       
  2030 ###
       
  2031 ###   structural/auxillary classes
       
  2032 ###   required attributes
       
  2033 ###   optional attributes
       
  2034 ###
       
  2035 sub run_inspect
       
  2036 {
       
  2037 	my $self = shift;
       
  2038 	my @ocs  = @_;
       
  2039 	my $dn   = $ocs[0];
       
  2040 	my ( $must_attr, $may_attr );
       
  2041 
       
  2042 	unless ( $dn ) {
       
  2043 		print "No DN or objectClass(es) provided.\n";
       
  2044 		return;
       
  2045 	}
       
  2046 
       
  2047 	# "Magic" argument that dumps all raw schema information.
       
  2048 	#
       
  2049 	if ( $dn eq '_schema' ) {
       
  2050 		$self->{'schema'}->dump();
       
  2051 		return;
       
  2052 	}
       
  2053 
       
  2054 	# one argument -- if it successfully resolves to a valid DN, fetch
       
  2055 	# the objectClass list from it.
       
  2056 	#
       
  2057 	if ( scalar @ocs == 1 ) {
       
  2058 		$dn = $self->base() if $dn eq '.';
       
  2059 		$dn = $self->path_to_dn( $dn );
       
  2060 
       
  2061 		my $s = $self->search({ base => $dn, vals => 1, attrs => ['objectClass'] });
       
  2062 		if ( $s->{'code'} == LDAP_SUCCESS ) {
       
  2063 			my $e = ${ $s->{'entries'} }[0];
       
  2064 			@ocs = $e->get_value('objectClass');
       
  2065 		}
       
  2066 	}
       
  2067 
       
  2068 	# get the complete attributes list.
       
  2069 	#
       
  2070 	( $must_attr, $may_attr ) = $self->fetch_attributes( \@ocs );
       
  2071 	my %must = map { $_ => 1 } @{$must_attr};
       
  2072 
       
  2073 	# Output objectClass chains and flags.
       
  2074 	#
       
  2075 	print "ObjectClasses:\n";
       
  2076 	foreach my $oc ( sort @ocs ) {
       
  2077 		my @sups = $self->findall_supers( $oc );
       
  2078 
       
  2079 		my @oc_chain = ( $oc, @sups );
       
  2080 		my @oc_out;
       
  2081 
       
  2082 		foreach my $oc ( @oc_chain ) {
       
  2083 			my $oc_obj = $self->{'schema'}->objectclass( $oc );
       
  2084 			next unless $oc_obj;
       
  2085 
       
  2086 			$oc = $oc . ' (' . 'structural' . ')' if $oc_obj->{'structural'};
       
  2087 			push( @oc_out, $oc );
       
  2088 		}
       
  2089 
       
  2090 		print "    " . join( ' --> ', @oc_out ) . "\n" if scalar @oc_out;
       
  2091 	}
       
  2092 
       
  2093 	# Output attributes and flags.
       
  2094 	#
       
  2095 	print "\nAttributes:\n";
       
  2096 	foreach my $attr ( sort (@{$must_attr}, @{$may_attr}) ) {
       
  2097 		my @flaglist;
       
  2098 		if ( $self->{'schema'}->attribute( $attr )->{'single-value'} ) {
       
  2099 			push ( @flaglist, 'single-value' );
       
  2100 		}
       
  2101 		else {
       
  2102 			push ( @flaglist, 'multivalue' );
       
  2103 		}
       
  2104 
       
  2105 		push ( @flaglist, $must{$attr} ? 'required' : 'optional' );
       
  2106 
       
  2107 		my $flags = '';
       
  2108 		$flags = (' (' . join( ', ', sort @flaglist ) . ')') if scalar @flaglist > 0;
       
  2109 
       
  2110 		printf( "    %s%s\n", $attr, $flags );
       
  2111 	}
       
  2112 
       
  2113 	print "\n";
       
  2114 	return;
       
  2115 }
       
  2116 
       
  2117 
       
  2118 ### Recursively walk an objectClass heirarchy, returning an array
       
  2119 ### of inheritence.
       
  2120 ###
       
  2121 sub findall_supers
       
  2122 {
       
  2123 	my $self = shift;
       
  2124 	my $oc   = shift or return;
       
  2125 	my @found;
       
  2126 
       
  2127 	foreach my $sup ( $self->{'schema'}->superclass($oc) ) {
       
  2128 		push( @found, $sup );
       
  2129 		push( @found, $self->findall_supers( $sup ) );
       
  2130 	}
       
  2131 
       
  2132 	return @found;
       
  2133 }
       
  2134 
       
  2135 
       
  2136 
  1988 ########################################################################
  2137 ########################################################################
  1989 ### M A I N
  2138 ### M A I N
  1990 ########################################################################
  2139 ########################################################################
  1991 
  2140 
  1992 package main;
  2141 package main;
  1993 use strict;
  2142 use strict;
  1994 use warnings;
  2143 use warnings;
  1995 
  2144 
  1996 $0 = 'shelldap';
  2145 $0 = 'shelldap';
  1997 my $VERSION = '0.9.0';
  2146 my $VERSION = '1.0.0';
  1998 
  2147 
  1999 use Getopt::Long;
  2148 use Getopt::Long;
  2000 use YAML::Syck;
  2149 use YAML::Syck;
  2001 use Pod::Usage;
  2150 use Pod::Usage;
  2002 eval 'use Term::ReadLine::Gnu';
  2151 eval 'use Term::ReadLine::Gnu';