shelldap
changeset 50 21ba5eb5c2fc
parent 49 57df728cdb77
child 51 27bbe75233a3
equal deleted inserted replaced
49:57df728cdb77 50:21ba5eb5c2fc
    82     tls_cert:   ~/.ssl/client.cert.pem 
    82     tls_cert:   ~/.ssl/client.cert.pem 
    83     tls_key:    ~/.ssl/private/client.key.pem
    83     tls_key:    ~/.ssl/private/client.key.pem
    84 
    84 
    85 =over 4
    85 =over 4
    86 
    86 
       
    87 =item B<configfile>
       
    88 
       
    89 Optional.  Use an alternate configuration file, instead of the
       
    90 default ~/.shelldap.rc.
       
    91 
       
    92     --configfile /tmp/alternate-config.yml
       
    93     -f /tmp/alternate-config.yml
       
    94 
       
    95 =back
       
    96 
       
    97 =over 4
       
    98 
    87 =item B<server>
    99 =item B<server>
    88 
   100 
    89 Required. The LDAP server to connect to.  This can be a hostname, IP
   101 Required. The LDAP server to connect to.  This can be a hostname, IP
    90 address, or a URI.
   102 address, or a URI.
    91 
   103 
   405 my $conf = $main::conf;
   417 my $conf = $main::conf;
   406 
   418 
   407 # make 'die' backtrace in debug mode
   419 # make 'die' backtrace in debug mode
   408 $SIG{'__DIE__'} = \&Carp::confess if $conf->{'debug'};
   420 $SIG{'__DIE__'} = \&Carp::confess if $conf->{'debug'};
   409 
   421 
   410 ###############################################################
   422 
   411 #
   423 ########################################################################
   412 # UTILITY FUNCTIONS
   424 ### U T I L I T Y   F U N C T I O N S
   413 #
   425 ########################################################################
   414 ###############################################################
   426 
   415 
   427 ### Initial shell behaviors.
   416 # initial shell behaviors
   428 ### 
   417 # 
       
   418 sub init
   429 sub init
   419 {
   430 {
   420 	my $self = shift;
   431 	my $self = shift;
   421 	$self->{'API'}->{'match_uniq'} = 0;
   432 	$self->{'API'}->{'match_uniq'} = 0;
   422 
   433 
   423 	$self->{'editor'} = $ENV{'EDITOR'} || 'vi';
   434 	$self->{'editor'} = $conf->{'editor'} || $ENV{'EDITOR'} || 'vi';
   424 	$self->{'env'}	= [ qw/ debug cacheage timeout / ];
   435 	$self->{'env'}	= [ qw/ debug cacheage timeout / ];
   425 
   436 
   426 	# let autocomplete work with the '=' character
   437 	# let autocomplete work with the '=' character
   427 	my $term = $self->term();
   438 	my $term = $self->term();
   428 	$term->Attribs->{'basic_word_break_characters'}	 =~ s/=//m;
   439 	$term->Attribs->{'basic_word_break_characters'}	 =~ s/=//m;
   432 	eval {
   443 	eval {
   433 		$term->history_truncate_file("$ENV{'HOME'}/.shelldap_history", 50);
   444 		$term->history_truncate_file("$ENV{'HOME'}/.shelldap_history", 50);
   434 		$term->ReadHistory("$ENV{'HOME'}/.shelldap_history");
   445 		$term->ReadHistory("$ENV{'HOME'}/.shelldap_history");
   435 	};
   446 	};
   436 
   447 
       
   448 	# gather metadata from the LDAP server
   437 	$self->{'root_dse'} = $self->ldap->root_dse();
   449 	$self->{'root_dse'} = $self->ldap->root_dse();
       
   450 	$self->{'schema'} = $self->ldap->schema();
       
   451 
       
   452 	# get an initial list of all objectClasses
       
   453 	$self->{'objectclasses'} = [];
       
   454 	foreach my $o ( $self->{'schema'}->all_objectclasses() ) {
       
   455 		push @{ $self->{'objectclasses'} }, $o->{'name'};
       
   456 	}
       
   457 
   438 	if ( $conf->{'debug'} ) {
   458 	if ( $conf->{'debug'} ) {
   439 		$self->{'schema'}   = $self->ldap->schema();
       
   440 		my @versions = $self->{'root_dse'}->get_value('supportedLDAPVersion');
   459 		my @versions = $self->{'root_dse'}->get_value('supportedLDAPVersion');
   441 		print "Connected to $conf->{'server'}\n";
   460 		print "Connected to $conf->{'server'}\n";
   442 		print "Supported LDAP version: ", ( join ', ', @versions ), "\n";
   461 		print "Supported LDAP version: ", ( join ', ', @versions ), "\n";
   443 		print "Cipher in use: ", $self->ldap()->cipher(), "\n";
   462 		print "Cipher in use: ", $self->ldap()->cipher(), "\n";
   444 	}
   463 	}
   445 
   464 
   446 	# try an initial search and die if it doesn't work
   465 	# try an initial search and bail early if it doesn't work. (bad baseDN?)
   447 	# (bad baseDN)
       
   448 	my $s = $self->search();
   466 	my $s = $self->search();
   449 	die "LDAP baseDN error: ", $s->{'message'}, "\n" if $s->{'code'};
   467 	die "LDAP baseDN error: ", $s->{'message'}, "\n" if $s->{'code'};
   450 
   468 
   451 	$self->{'schema'} = $self->ldap->schema();
   469 	# okay, now do an initial population of 'cwd' for autocomplete.
   452 
       
   453 	# okay, now do an initial population of 'cwd'
       
   454 	# for autocomplete.
       
   455 	$self->update_entries();
   470 	$self->update_entries();
   456 
   471 
   457 	# whew, okay.  Update prompt, wait for input!
   472 	# whew, okay.  Update prompt, wait for input!
   458 	$self->update_prompt();
   473 	$self->update_prompt();
   459 
   474 
   460 	return;
   475 	return;
   461 }
   476 }
   462 
   477 
   463 
   478 
   464 # get an ldap connection handle
   479 ### Return an LDAP connection handle, creating it if necessary.
   465 #
   480 ###
   466 sub ldap
   481 sub ldap
   467 {
   482 {
   468 	my $self = shift;
   483 	my $self = shift;
       
   484 	my $rv;
   469 
   485 
   470 	# use cached connection object if it exists
   486 	# use cached connection object if it exists
   471 	return $self->{'ldap'} if $self->{'ldap'};
   487 	return $self->{'ldap'} if $self->{'ldap'};
   472 	
   488 	
   473 	# fill in potentially missing info
   489 	# fill in potentially missing info
   480 		eval 'use IO::Socket::SSL';
   496 		eval 'use IO::Socket::SSL';
   481 		die qq{IO::Socket::SSL not installed, but is required for SSL or TLS connections.
   497 		die qq{IO::Socket::SSL not installed, but is required for SSL or TLS connections.
   482 You may try connecting insecurely, or install the module and try again.\n} if $@;
   498 You may try connecting insecurely, or install the module and try again.\n} if $@;
   483 	}
   499 	}
   484 
   500 
       
   501 	# Prompt for a password after disabling local echo.
       
   502 	#
   485 	if ( ($conf->{'binddn'} && ! $conf->{'bindpass'}) || $conf->{'promptpass'} ) {
   503 	if ( ($conf->{'binddn'} && ! $conf->{'bindpass'}) || $conf->{'promptpass'} ) {
   486 		print "Bind password: ";
   504 		print "Bind password: ";
   487 		Term::ReadKey::ReadMode 2;
   505 		Term::ReadKey::ReadMode 2;
   488 		chomp($conf->{'bindpass'} = <STDIN>);
   506 		chomp( $conf->{'bindpass'} = <STDIN> );
   489 		Term::ReadKey::ReadMode 0;
   507 		Term::ReadKey::ReadMode 0;
   490 		print "\n";
   508 		print "\n";
   491 	}
   509 	}
   492 
   510 
   493 	# make connection
   511 	# make the connection
   494 	my $ldap = Net::LDAP->new( $conf->{'server'} )
   512 	my $ldap = Net::LDAP->new( $conf->{'server'} )
   495 		or die "Unable to connect to LDAP server '$conf->{'server'}': $!\n";
   513 		or die "Unable to connect to LDAP server '$conf->{'server'}': $!\n";
   496 
   514 
   497 	# secure connection options
   515 	# secure connection options
   498 	#
   516 	#
   504 				clientcert => $conf->{'tls_cert'},
   522 				clientcert => $conf->{'tls_cert'},
   505 				clientkey  => $conf->{'tls_key'},
   523 				clientkey  => $conf->{'tls_key'},
   506 				keydecrypt => sub {
   524 				keydecrypt => sub {
   507 					print "Key Passphrase: "; 
   525 					print "Key Passphrase: "; 
   508 					Term::ReadKey::ReadMode 2;
   526 					Term::ReadKey::ReadMode 2;
   509 					chomp(my $secret = <STDIN>);
   527 					chomp( my $secret = <STDIN> );
   510 					Term::ReadKey::ReadMode 0;
   528 					Term::ReadKey::ReadMode 0;
   511 					print "\n";
   529 					print "\n";
   512 					return $secret;
   530 					return $secret;
   513 				});
   531 				});
   514 		}
   532 		}
   515 		else {
   533 		else {
   516 			$ldap->start_tls( verify => 'none' );
   534 			$ldap->start_tls( verify => 'none' );
   517 		}
   535 		}
   518 	}
   536 	}
   519 
   537 
   520 	# bind
   538 	# bind as an authenicated dn
   521 	my $rv;
       
   522 	if ( $conf->{'binddn'} ) {
   539 	if ( $conf->{'binddn'} ) {
   523 		# authed
       
   524 		$rv = $ldap->bind(
   540 		$rv = $ldap->bind(
   525 			$conf->{'binddn'},
   541 			$conf->{'binddn'},
   526 			password => $conf->{'bindpass'}
   542 			password => $conf->{'bindpass'}
   527 		);
   543 		);
   528 	}
   544 	}
       
   545 
       
   546 	# bind anonymously
   529 	else {
   547 	else {
   530 		# anon
       
   531 		$rv = $ldap->bind();
   548 		$rv = $ldap->bind();
   532 	}
   549 	}
   533 
   550 
   534 	my $err = $rv->error();
   551 	my $err = $rv->error();
   535 	if ( $rv->code() ) {
   552 	if ( $rv->code() ) {
   536 		$err .= " (try the --tls flag?)"
   553 		$err .= " (try the --tls flag?)" if $err =~ /confidentiality required/i;
   537 			if $err =~ /confidentiality required/i;
       
   538 		die "LDAP bind error: $err\n";
   554 		die "LDAP bind error: $err\n";
   539 	}
   555 	}
   540 
   556 
   541 	# offer to cache authentication info
   557 	# offer to cache authentication info
   542 	# if we enter this conditional, we have successfully 
   558 	# if we enter this conditional, we have successfully 
   543 	# authed with the server (non anonymous), and 
   559 	# authed with the server (non anonymous), and 
   544 	# we haven't cached anything in the past.
   560 	# we haven't cached anything in the past.
   545 	#
   561 	#
   546 	if ( $conf->{'binddn'} && ! -e $conf->{'confpath'} ) {
   562 	if ( $conf->{'binddn'} && ! -e $conf->{'configfile'} ) {
   547 		print "Would you like to cache your connection information? [Y/n]: ";
   563 		print "Would you like to cache your connection information? [Yn]: ";
   548 		chomp( my $response = <STDIN> );
   564 		chomp( my $response = <STDIN> );
   549 		unless ( $response =~ /^n/i ) {
   565 		unless ( $response =~ /^n/i ) {
   550 			YAML::Syck::DumpFile( $conf->{'confpath'}, $conf );
   566 			YAML::Syck::DumpFile( $conf->{'configfile'}, $conf );
   551 			chmod 0600, $conf->{'confpath'};
   567 			chmod 0600, $conf->{'configfile'};
   552 			print "Connection info cached to $conf->{'confpath'}.\n";
   568 			print "Connection info cached to $conf->{'configfile'}.\n";
   553 		}
   569 		}
   554 	}
   570 	}
   555 
   571 
   556 	$self->{'ldap'} = $ldap;
   572 	$self->{'ldap'} = $ldap;
   557 	return $ldap;
   573 	return $ldap;
   558 }
   574 }
   559 
   575 
   560 # just return an LDIF object
   576 
   561 #
   577 ### Return a new LDIF object, suitable for populating with
       
   578 ### a Net::LDAP::Entry.
       
   579 ###
   562 sub ldif 
   580 sub ldif 
   563 {
   581 {
   564 	my $self	 = shift;
   582 	my $self	 = shift;
   565 	my $use_temp = shift;
   583 	my $use_temp = shift;
   566 
   584 
   589 	}
   607 	}
   590 
   608 
   591 	return $self->{'ldif'};
   609 	return $self->{'ldif'};
   592 }
   610 }
   593 
   611 
   594 # load and return an Entry object from LDIF
   612 
   595 #
   613 ### Return an Entry object from an LDIF filename, or undef if there was an error.
       
   614 ###
   596 sub load_ldif
   615 sub load_ldif
   597 {
   616 {
   598 	my $self = shift;
   617 	my $self = shift;
   599 
   618 	my $ldif = Net::LDAP::LDIF->new( shift(), 'r' );
   600 	my $ldif =  Net::LDAP::LDIF->new( shift(), 'r' );
   619 
   601 	return unless $ldif;
   620 	return unless $ldif;
   602 
   621 
   603 	my $e;
   622 	my $e;
   604 	eval { $e = $ldif->read_entry(); };
   623 	eval { $e = $ldif->read_entry(); };
   605 
   624 
   606 	return if $@;
   625 	return if $@;
   607 	return $e;
   626 	return $e;
   608 }
   627 }
   609 
   628 
   610 # given a filename, return an md5 checksum
   629 
   611 #
   630 ### Given a filename, return an md5 checksum.
       
   631 ###
   612 sub chksum 
   632 sub chksum 
   613 {
   633 {
   614 	my $self = shift;
   634 	my $self = shift;
   615 	my $file = shift or return;
   635 	my $file = shift or return;
   616 
   636 
   617 	my $md5 = Digest::MD5->new();
   637 	my $md5 = Digest::MD5->new();
   618 	open F, $file or die "Unable to read temporary ldif: $!\n";
   638 	open F, $file or die "Unable to read file: $!\n";
   619 	my $hash = $md5->addfile( *F )->hexdigest();
   639 	my $hash = $md5->addfile( *F )->hexdigest();
   620 	close F;
   640 	close F;
   621 
   641 
   622 	return $hash;
   642 	return $hash;
   623 }
   643 }
   624 
   644 
   625 # prompt functions
   645 
   626 #
   646 ### Used by Term::Shell to generate the prompt.
       
   647 ###
   627 sub prompt_str
   648 sub prompt_str
   628 {
   649 {
   629 	my $self = shift;
   650 	my $self = shift;
   630 	return $self->{'prompt'};
   651 	return $self->{'prompt'};
   631 }
   652 }
       
   653 
       
   654 
       
   655 ### Display the current working entry as the prompt,
       
   656 ### truncating if necessary.
       
   657 ###
   632 sub update_prompt 
   658 sub update_prompt 
   633 {
   659 {
   634 	my $self = shift;
   660 	my $self = shift;
   635 	my $base = $self->base();
   661 	my $base = $self->base();
   636 
   662 
   644 		$self->{'prompt'} = "$prompt > ";
   670 		$self->{'prompt'} = "$prompt > ";
   645 	}
   671 	}
   646 	return;
   672 	return;
   647 }
   673 }
   648 
   674 
   649 # search base accessor
   675 
   650 #
   676 ### Prompt the user to re-edit their LDIF on error.
       
   677 ### Returns true if the user wants to do so.
       
   678 ###
       
   679 sub prompt_edit_again
       
   680 {
       
   681 	my $self = shift;
       
   682 	print "Edit again? [Yn]: ";
       
   683 	chomp( my $ans = <STDIN> );
       
   684 	return $ans !~ /^n/i;
       
   685 }
       
   686 
       
   687 
       
   688 ### Return the basedn of the LDAP connection, being either explicitly
       
   689 ### configured or determined automatically from server metadata.
       
   690 ###
   651 sub base 
   691 sub base 
   652 {
   692 {
   653 	my $self = shift;
   693 	my $self = shift;
   654 	$self->{'base'} ||= $conf->{'basedn'};
   694 	$self->{'base'} ||= $conf->{'basedn'};
   655 
   695 
   665 		$self->{'base'} = $base if $base;
   705 		$self->{'base'} = $base if $base;
   666 	}
   706 	}
   667 	return $self->{'base'};
   707 	return $self->{'base'};
   668 }
   708 }
   669 
   709 
   670 # do a search on a dn to determine if it is valid.
   710 
   671 # returns a bool.
   711 ### Returns true if the specified dn is valid on this LDAP server.
   672 #
   712 ###
   673 sub is_valid_dn 
   713 sub is_valid_dn 
   674 {
   714 {
   675 	my $self = shift;
   715 	my $self = shift;
   676 	my $dn   = shift or return 0;
   716 	my $dn   = shift or return 0;
   677 
   717 
   678 	my $r = $self->search({ base => $dn });
   718 	my $r = $self->search({ base => $dn });
   679 
       
   680 	return $r->{'code'} == LDAP_SUCCESS ? 1 : 0;
   719 	return $r->{'code'} == LDAP_SUCCESS ? 1 : 0;
   681 }
   720 }
   682 
   721 
   683 # perform an ldap search
   722 
   684 # return an hashref containing return code and
   723 ### Perform an LDAP search.
   685 # arrayref of Net::LDAP::Entry objects
   724 ###
   686 #
   725 ### Returns a hashref containing the return code and
       
   726 ### an arrayref of Net::LDAP::Entry objects.
       
   727 ###
   687 sub search 
   728 sub search 
   688 {
   729 {
   689 	my $self = shift;
   730 	my $self = shift;
   690 	my $opts = shift || {};
   731 	my $opts = shift || {};
   691 
   732 
   704 		);
   745 		);
   705 	};
   746 	};
   706 
   747 
   707 	my $s = $self->with_retry( $search );
   748 	my $s = $self->with_retry( $search );
   708 	my $rv = {
   749 	my $rv = {
   709 		code	  => $s->code(),
   750 		code	=> $s->code(),
   710 		message   => $s->error(),
   751 		message => $s->error(),
   711 		entries   => []
   752 		entries => []
   712 	};
   753 	};
   713 
   754 
   714 	$rv->{'entries'} =
   755 	$rv->{'entries'} =
   715 	  $opts->{'scope'} eq 'base' ? [ $s->shift_entry() ] : [ $s->entries() ];
   756 	  $opts->{'scope'} eq 'base' ? [ $s->shift_entry() ] : [ $s->entries() ];
   716 
   757 
   717 	return $rv;
   758 	return $rv;
   718 }
   759 }
   719 
   760 
   720 # update the autocomplete for entries
   761 
   721 # in the current base tree, respecting or creating cache.
   762 ### Maintain the cache of possible autocomplete values for
   722 #
   763 ### the current DN.
       
   764 ###
   723 sub update_entries 
   765 sub update_entries 
   724 {
   766 {
   725 	my $self = shift;
   767 	my $self = shift;
   726 	my %opts = @_;
   768 	my %opts = @_;
   727 	my $base = lc( $self->base() );
   769 	my $base = lc( $self->base() );
   728 	
   770 	
   729 	my $s = $opts{'search'} || $self->search({ scope => 'one' });
   771 	my $s = $opts{'search'} || $self->search({ scope => 'one', base => $base });
   730 
   772 
   731 	$self->{'cwd_entries'} = [];
   773 	$self->{'cwd_entries'} = [];
   732 	return if $s->{'code'};
   774 	return if $s->{'code'};
   733 
   775 
   734 	# setup cache object
   776 	# setup cache object
   757 
   799 
   758 	$self->{'cwd_entries'} = $cache->{'entries'};
   800 	$self->{'cwd_entries'} = $cache->{'entries'};
   759 	return;
   801 	return;
   760 }
   802 }
   761 
   803 
   762 # convert a given path to a DN: deal with '..', '.'
   804 
   763 # Synopsis: $dn = $self->path_to_dn( $path );
   805 ### Roughly convert a given path to a DN.
       
   806 ###
       
   807 ### Additionally support:
       
   808 ###    parent  '..'
       
   809 ###    current '.'
       
   810 ###    last    '-'
       
   811 ###    home    '~'
       
   812 ###
       
   813 ### Synopsis: $dn = $self->path_to_dn( $path );
       
   814 ###
   764 sub path_to_dn
   815 sub path_to_dn
   765 {
   816 {
   766 	my $self    = shift;
   817 	my $self    = shift;
   767 	my $path    = shift;
   818 	my $path    = shift;
   768 	my %flags   = @_;
   819 	my %flags   = @_;
   782 	if ( $path =~ /^\.\./o ) {
   833 	if ( $path =~ /^\.\./o ) {
   783 		# support '..' (possibly iterated and as prefix to a DN)
   834 		# support '..' (possibly iterated and as prefix to a DN)
   784 		my @base = @{ ldap_explode_dn($curbase, casefold => 'none') };
   835 		my @base = @{ ldap_explode_dn($curbase, casefold => 'none') };
   785 
   836 
   786 		# deal with leading ..,
   837 		# deal with leading ..,
       
   838 		#
   787 		while ( $path =~ /^\.\./ ) {
   839 		while ( $path =~ /^\.\./ ) {
   788 			shift( @base ) if @base;
   840 			shift( @base ) if @base;
   789 			$path =~ s/^\.\.//;
   841 			$path =~ s/^\.\.//;
   790 			last if $path !~ /[,\/]\s*/;
   842 			last if $path !~ /[,\/]\s*/;
   791 			$path =~ s/[,\/]\s*//;
   843 			$path =~ s/[,\/]\s*//;
   807 	}
   859 	}
   808 
   860 
   809 	return $path;
   861 	return $path;
   810 }
   862 }
   811 
   863 
   812 # given an array ref of shell-like globs, 
   864 
   813 # make and return an LDAP filter object.
   865 ### Given an array ref of shell-like globs, 
   814 #
   866 ### create and return a Net::LDAP::Filter object.
       
   867 ###
   815 sub make_filter 
   868 sub make_filter 
   816 {
   869 {
   817 	my $self  = shift;
   870 	my $self  = shift;
   818 	my $globs = shift or return;
   871 	my $globs = shift or return;
   819 
   872 
   821 	return unless scalar @$globs;
   874 	return unless scalar @$globs;
   822 
   875 
   823 	my $filter;
   876 	my $filter;
   824 	$filter = join('', map { (/^\(.*\)$/o) ? $_ : "($_)" } @$globs);
   877 	$filter = join('', map { (/^\(.*\)$/o) ? $_ : "($_)" } @$globs);
   825 	$filter = '(|' . $filter . ')'  if (scalar(@$globs) > 1);
   878 	$filter = '(|' . $filter . ')'  if (scalar(@$globs) > 1);
   826 	$filter = Net::LDAP::Filter->new($filter);
   879 	$filter = Net::LDAP::Filter->new( $filter );
   827 
   880 
   828 	if ( $filter ) {
   881 	if ( $filter ) {
   829 		$self->debug('Filter parsed as: ' . $filter->as_string() . "\n");
   882 		$self->debug( 'Filter parsed as: ' . $filter->as_string() . "\n" );
   830 	}
   883 	}
   831 	else {
   884 	else {
   832 		print "Error parsing filter.\n";
   885 		print "Error parsing filter.\n";
   833 		return;
   886 		return;
   834 	}
   887 	}
   835 
   888 
   836 	return $filter;
   889 	return $filter;
   837 }
   890 }
   838 
   891 
   839 
   892 
   840 # check whether a given string may be a filter
   893 ### Given an arrayref of objectClasses, pull a complete list of 
   841 # Synopsis: $yesNo = $self->is_valid_filter($string);
   894 ### required and optional attrbutes.  Returns two arrayrefs.
   842 #
   895 ###
       
   896 sub fetch_attributes
       
   897 {
       
   898 	my $self = shift;
       
   899 	my $ocs  = shift or return [], [];
       
   900 
       
   901 	my ( %seen, @must_attr, @may_attr );
       
   902 	foreach my $oc ( sort @{$ocs} ) {
       
   903 
       
   904 		# required
       
   905 		my @must = $self->{'schema'}->must( $oc );
       
   906 		foreach my $attr ( sort { $a->{'name'} cmp $b->{'name'} } @must ) {
       
   907 			next if $attr->{'name'} =~ /^objectclass$/i;
       
   908 			next if $seen{ $attr->{'name'} };
       
   909 			push @must_attr, $attr->{'name'};
       
   910 			$seen{ $attr->{'name'} }++;
       
   911 		}
       
   912 
       
   913 		# optional
       
   914 		my @may  = $self->{'schema'}->may( $oc );
       
   915 		foreach my $attr ( sort { $a->{'name'} cmp $b->{'name'} } @may ) {
       
   916 			next if $attr->{'name'} =~ /^objectclass$/i;
       
   917 			next if $seen{ $attr->{'name'} };
       
   918 			push @may_attr, $attr->{'name'};
       
   919 			$seen{ $attr->{'name'} }++;
       
   920 		}
       
   921 	}
       
   922 
       
   923 	return \@must_attr, \@may_attr;
       
   924 }
       
   925 
       
   926 
       
   927 ### Check whether a given string can be used directly as
       
   928 ### an LDAP search filter.
       
   929 ###
       
   930 ### Synopsis: $yesNo = $self->is_valid_filter($string);
       
   931 ###
   843 sub is_valid_filter
   932 sub is_valid_filter
   844 {
   933 {
   845 	my $self   = shift;
   934 	my $self   = shift;
   846 	my $filter = shift or return;
   935 	my $filter = shift or return;
   847 
   936 
   848 	return Net::LDAP::Filter->new( $filter ) ? 1 : 0;
   937 	return Net::LDAP::Filter->new( $filter ) ? 1 : 0;
   849 }
   938 }
   850 
   939 
   851 
   940 
   852 # Call code in subref $action, if there's any connection related errors,
   941 ### Call code in subref $action, if there's any connection related errors,
   853 # try it one additional time before giving up.  This should take care of
   942 ### try it one additional time before giving up.  This should take care of
   854 # most server disconnects due to timeout and other generic connection
   943 ### most server disconnects due to timeout and other generic connection
   855 # errors, and will attempt to re-establish a connection.
   944 ### errors, and will attempt to transparently re-establish a connection.
   856 #
   945 ###
   857 sub with_retry
   946 sub with_retry
   858 {
   947 {
   859 	my $self = shift;
   948 	my $self = shift;
   860 	my $action = shift;
   949 	my $action = shift;
   861 
   950 
   877 
   966 
   878 	return $rv;
   967 	return $rv;
   879 }
   968 }
   880 
   969 
   881 
   970 
   882 # little. yellow. different. better.
   971 ### little. yellow. different. better.
   883 #
   972 ###
   884 sub debug 
   973 sub debug 
   885 {
   974 {
   886 	my $self = shift;
   975 	my $self = shift;
   887 	return unless $conf->{'debug'};
   976 	return unless $conf->{'debug'};
   888 	print "\e[33m";
   977 	print "\e[33m";
   889 	print shift();
   978 	print shift();
   890 	print "\e[0m";
   979 	print "\e[0m";
   891 	return;
   980 	return;
   892 }
   981 }
   893 
   982 
   894 # setup command autocompletes for
   983 
   895 # all commands that have the same possible values
   984 ### Autocomplete values: Returns cached children entries.
   896 #
   985 ###
   897 sub autocomplete_cwd
   986 sub autocomplete_cwd
   898 {
   987 {
   899 	my $self = shift;
   988 	my $self = shift;
   900 	my $word = $_[0];
   989 	my $word = $_[0];
   901 
   990 
   902 	return sort @{ $self->{'cwd_entries'} };
   991 	return sort @{ $self->{'cwd_entries'} };
   903 }
   992 }
   904 
   993 
       
   994 
       
   995 ### Autocomplete values: Returns previously set shelldap environment values.
       
   996 ###
   905 sub comp_setenv
   997 sub comp_setenv
   906 { 
   998 { 
   907 	my $self = shift;
   999 	my $self = shift;
   908 	return @{ $self->{'env'} };
  1000 	return @{ $self->{'env'} };
   909 }
  1001 }
   910 
  1002 
       
  1003 
       
  1004 ### Autocomplete values: Returns all objectClasses as defined
       
  1005 ### by the LDAP server.
       
  1006 ###
   911 sub comp_create
  1007 sub comp_create
   912 {
  1008 {
   913 	my $self = shift;
  1009 	my $self = shift;
   914 	return @{ $self->{'objectclasses'} } if $self->{'objectclasses'};
  1010 	return @{ $self->{'objectclasses'} };
   915 
  1011 }
   916 	my @oc_data = $self->{'schema'}->all_objectclasses();
  1012 
   917 	my @oc;
  1013 
   918 	foreach my $o ( @oc_data ) {
  1014 ### Inject various autocomplete and alias routines into the symbol table.
   919 		push @oc, $o->{'name'};
  1015 ###
   920 	}
       
   921 	@oc = sort @oc;
       
   922 	$self->{'objectclasses'} = \@oc;
       
   923 
       
   924 	return @oc;
       
   925 }
       
   926 
       
   927 {
  1016 {
   928 	no warnings;
  1017 	no warnings;
   929 	no strict 'refs';
  1018 	no strict 'refs';
   930 
  1019 
   931 	# command, alias
  1020 	# command, alias
   965 		*$alias_sub = \&$real_sub;
  1054 		*$alias_sub = \&$real_sub;
   966 	}
  1055 	}
   967 }
  1056 }
   968 
  1057 
   969 
  1058 
   970 # Given an $arrayref, remove LDIF continuation wrapping,
  1059 ### Given an $arrayref, remove LDIF continuation wrapping in place,
   971 # effectively making each entry a single line.
  1060 ### effectively making each entry a single line for LCS comparisons.
   972 # 
  1061 ### 
   973 sub unwrap {
  1062 sub unwrap_line {
       
  1063 	my $self  = shift;
   974 	my $array = shift;
  1064 	my $array = shift;
   975 
  1065 
   976 	my $i = 1;
  1066 	my $i = 1;
   977 	while ( $i < scalar(@$array) ) {
  1067 	while ( $i < scalar(@$array) ) {
   978 		if ( $array->[$i] =~ /^\s/ ) {
  1068 		if ( $array->[$i] =~ /^\s/ ) {
   985 		}
  1075 		}
   986 	}
  1076 	}
   987 }
  1077 }
   988 
  1078 
   989 
  1079 
   990 ###############################################################
  1080 ########################################################################
   991 #
  1081 ### S H E L L   M E T H O D S
   992 # SHELL METHODS
  1082 ########################################################################
   993 #
  1083 
   994 ###############################################################
  1084 ### Don't die on a newline, just no-op.
   995 
  1085 ###
   996 # don't die on a newline
       
   997 #
       
   998 sub run_ { return; }
  1086 sub run_ { return; }
   999 
  1087 
  1000 # print shell debug actions
  1088 
  1001 # 
  1089 ### Term::Shell hook.
       
  1090 ### Write history for each command, print shell debug actions.
       
  1091 ###
  1002 sub precmd
  1092 sub precmd
  1003 {
  1093 {
  1004 	my $self = shift;
  1094 	my $self = shift;
  1005 	my ( $handler, $cmd, $args ) = @_;
  1095 	my ( $handler, $cmd, $args ) = @_;
  1006 
  1096 
  1007 	my $term = $self->term();
  1097 	my $term = $self->term();
  1008 	eval { $term->WriteHistory("$ENV{'HOME'}/.shelldap_history"); };
  1098 	eval { $term->WriteHistory("$ENV{'HOME'}/.shelldap_history"); };
  1009 
  1099 
  1010 	return unless $conf->{'debug'};
       
  1011 	$self->debug( "$$cmd (" . ( join ' ', @$args ) . "), calling '$$handler'\n" );
  1100 	$self->debug( "$$cmd (" . ( join ' ', @$args ) . "), calling '$$handler'\n" );
  1012 	return;
  1101 	return;
  1013 } 
  1102 } 
  1014 
  1103 
       
  1104 
       
  1105 ### Display an entry as LDIF to the terminal.
       
  1106 ###
  1015 sub run_cat 
  1107 sub run_cat 
  1016 {
  1108 {
  1017 	my $self  = shift;
  1109 	my $self  = shift;
  1018 	my $dn    = shift;
  1110 	my $dn    = shift;
  1019 	my @attrs = (@_) ? @_ : ('*');
  1111 	my @attrs = (@_) ? @_ : ('*');
  1025 
  1117 
  1026 	# support '.'
  1118 	# support '.'
  1027 	$dn = $self->base() if $dn eq '.';
  1119 	$dn = $self->base() if $dn eq '.';
  1028 
  1120 
  1029 	# support globbing
  1121 	# support globbing
       
  1122 	#
  1030 	my $s;
  1123 	my $s;
  1031 	if ( $dn eq '*' ) {
  1124 	if ( $dn eq '*' ) {
  1032 		$s = $self->search({
  1125 		$s = $self->search({
  1033 			scope  => 'one',
  1126 			scope  => 'one',
  1034 			vals   => 1,
  1127 			vals   => 1,
  1041 			vals   => 1,
  1134 			vals   => 1,
  1042 			filter => $dn,
  1135 			filter => $dn,
  1043 			attrs  => \@attrs
  1136 			attrs  => \@attrs
  1044 		});
  1137 		});
  1045 	}
  1138 	}
       
  1139 
       
  1140 	# absolute/relative dn
       
  1141 	#
  1046 	else {
  1142 	else {
  1047 		# convert given path to DN
       
  1048 		$dn = $self->path_to_dn( $dn );
  1143 		$dn = $self->path_to_dn( $dn );
  1049 		$s = $self->search({
  1144 		$s = $self->search({
  1050 			base   => $dn,
  1145 			base   => $dn,
  1051 			vals   => 1,
  1146 			vals   => 1,
  1052 			attrs  => \@attrs
  1147 			attrs  => \@attrs
  1053 		});
  1148 		});
  1054 	}
  1149 	}
  1055 
  1150 
       
  1151 	# emit error, if any
       
  1152 	#
  1056 	if ( $s->{'code'} ) {
  1153 	if ( $s->{'code'} ) {
  1057 		print $s->{'message'} . "\n";
  1154 		print $s->{'message'} . "\n";
  1058 		return;
  1155 		return;
  1059 	}
  1156 	}
  1060 
  1157 
       
  1158 	# display to stdout
       
  1159 	#
  1061 	foreach my $e ( @{ $s->{'entries'} } ) {
  1160 	foreach my $e ( @{ $s->{'entries'} } ) {
  1062 		$self->ldif->write_entry( $e );
  1161 		$self->ldif->write_entry( $e );
  1063 		print "\n";
  1162 		print "\n";
  1064 	}
  1163 	}
       
  1164 
  1065 	return;
  1165 	return;
  1066 }
  1166 }
  1067 
  1167 
       
  1168 
       
  1169 ### Change shelldap's idea of a current working 'directory',
       
  1170 ### by adjusting the current default basedn for all searches.
       
  1171 ###
  1068 sub run_cd 
  1172 sub run_cd 
  1069 {
  1173 {
  1070 	my $self	= shift;
  1174 	my $self	= shift;
  1071 	my $newbase = shift;
  1175 	my $newbase = shift;
  1072 
  1176 
  1095 	# reflect cwd change in prompt
  1199 	# reflect cwd change in prompt
  1096 	$self->update_prompt();
  1200 	$self->update_prompt();
  1097 	return;
  1201 	return;
  1098 }
  1202 }
  1099 
  1203 
       
  1204 
       
  1205 ### Simply clear the screen.
       
  1206 ###
  1100 sub run_clear
  1207 sub run_clear
  1101 {
  1208 {
  1102 	my $self = shift;
  1209 	my $self = shift;
  1103 	system('clear');
  1210 	system( 'clear' );
  1104 	return;
  1211 	return;
  1105 }
  1212 }
  1106 
  1213 
       
  1214 
       
  1215 ### Fetch the source DN entry, modify it's DN data
       
  1216 ### and write it back to the directory.
       
  1217 ###
  1107 sub run_copy
  1218 sub run_copy
  1108 {
  1219 {
  1109 	my $self = shift;
  1220 	my $self = shift;
  1110 	my ( $s_dn, $d_dn ) = @_;
  1221 	my ( $s_dn, $d_dn ) = @_;
  1111 
  1222 
  1112 	unless ( $s_dn ) {
  1223 	unless ( $s_dn ) {
  1113 		print "No source dn provided.\n";
  1224 		print "No source DN provided.\n";
  1114 		return;
  1225 		return;
  1115 	}
  1226 	}
  1116 	unless ( $d_dn ) {
  1227 	unless ( $d_dn ) {
  1117 		print "No destination dn provided.\n";
  1228 		print "No destination DN provided.\n";
  1118 		return;
  1229 		return;
  1119 	}
  1230 	}
  1120 
  1231 
  1121 	# convert given source path to DN
  1232 	# convert given source path to DN
  1122 	$s_dn = $self->path_to_dn( $s_dn );
  1233 	$s_dn = $self->path_to_dn( $s_dn );
  1123 
  1234 
       
  1235 	# sanity check source
       
  1236 	#
  1124 	my $s = $self->search({ base => $s_dn, vals => 1 });
  1237 	my $s = $self->search({ base => $s_dn, vals => 1 });
  1125 	unless ( $s->{'code'} == LDAP_SUCCESS ) {
  1238 	unless ( $s->{'code'} == LDAP_SUCCESS ) {
  1126 		print "No such object\n";
  1239 		print "No such object\n";
  1127 		return;
  1240 		return;
  1128 	}
  1241 	}
  1129 
  1242 
  1130 	# see if we're copying the entry to a totally new path
  1243 	# see if we're copying the entry to a nonexistent path
       
  1244 	#
  1131 	my ( $new_dn, $old_dn );
  1245 	my ( $new_dn, $old_dn );
  1132 	( $d_dn, $new_dn ) = ( $1, $2 ) if $d_dn =~ /^([\w=]+),(.*)$/;
  1246 	( $d_dn, $new_dn ) = ( $1, $2 ) if $d_dn =~ /^([\w=]+),(.*)$/;
  1133 	if ( $new_dn ) {
  1247 	if ( $new_dn ) { # absolute
  1134 		unless ( $self->is_valid_dn( $new_dn ) ) {
  1248 		unless ( $self->is_valid_dn( $new_dn ) ) {
  1135 			print "Invalid destination.\n";
  1249 			print "Invalid destination.\n";
  1136 			return;
  1250 			return;
  1137 		}
  1251 		}
  1138 	}
  1252 	}
  1139 	else {
  1253 	else { # relative
  1140 		$new_dn = $self->base();
  1254 		$new_dn = $self->base();
  1141 	}
  1255 	}
  1142 	$old_dn = $1 if $s_dn =~ /^[\w=]+,(.*)$/;
  1256 	$old_dn = $1 if $s_dn =~ /^[\w=]+,(.*)$/;
  1143 
  1257 
  1144 	# get the source object
  1258 	# get the source entry object
  1145 	my $e = ${ $s->{'entries'} }[0];
  1259 	my $e = ${ $s->{'entries'} }[0];
  1146 	$e->dn( $s_dn );
  1260 	$e->dn( $s_dn );
  1147 
  1261 
  1148 	# add changes in new entry instead of modifying existing
  1262 	# add changes in new entry instead of modifying existing
  1149 	$e->changetype('add'); 
  1263 	$e->changetype( 'add' ); 
  1150 	$e->dn( "$d_dn,$new_dn" );
  1264 	$e->dn( "$d_dn,$new_dn" );
  1151 
  1265 
  1152 	# get the unique attribute from the dn for modification
  1266 	# get the unique attribute from the dn for modification
  1153 	# perhaps there is a better way to do this...?
  1267 	# perhaps there is a better way to do this...?
  1154 	#
  1268 	#
  1155 	my ( $uniqkey, $uniqval ) = ( $1, $2 )
  1269 	my ( $uniqkey, $uniqval ) = ( $1, $2 )
  1156 	  if $d_dn =~ /^([\.\w\-]+)(?:\s+)?=(?:\s+)?([\.\-\s\w]+),?/;
  1270 	  if $d_dn =~ /^([\.\w\-]+)(?:\s+)?=(?:\s+)?([\.\-\s\w]+),?/;
  1157 	unless ( $uniqkey && $uniqval ) {
  1271 	unless ( $uniqkey && $uniqval ) {
  1158 		print "Unable to parse unique values from rdn.\n";
  1272 		print "Unable to parse unique values from RDN.\n";
  1159 		return;
  1273 		return;
  1160 	}
  1274 	}
  1161 	$e->replace( $uniqkey => $uniqval );
  1275 	$e->replace( $uniqkey => $uniqval );
  1162 
  1276 
  1163 	# update
  1277 	# update (which will actually create the new entry)
  1164 	my $rv = $e->update( $self->ldap() );
  1278 	#
  1165 	print $rv->error , "\n";
  1279 	my $update = sub { return $e->update($self->ldap()) };
       
  1280 	my $rv = $self->with_retry( $update );
       
  1281 	print $rv->error(), "\n";
  1166 
  1282 
  1167 	# clear caches
  1283 	# clear caches
       
  1284 	#
  1168 	$self->{'cache'}->{ $new_dn } = {} if $new_dn;
  1285 	$self->{'cache'}->{ $new_dn } = {} if $new_dn;
  1169 	$self->{'cache'}->{ $old_dn } = {} if $old_dn;
  1286 	$self->{'cache'}->{ $old_dn } = {} if $old_dn;
  1170 	$self->update_entries( clearcache => 1 );
  1287 	$self->update_entries( clearcache => 1 );
  1171 	return;
  1288 	return;
  1172 }
  1289 }
  1173 
  1290 
       
  1291 
       
  1292 ### Create a new entry from scratch, using attributes from
       
  1293 ### what the server's schema says is available from the specified
       
  1294 ### (optional) objectClass list.  Populate a new LDIF file and
       
  1295 ### present an editor to the user.
       
  1296 ###
  1174 sub run_create
  1297 sub run_create
  1175 {
  1298 {
  1176 	my $self = shift;
  1299 	my $self = shift;
  1177 	my @ocs  = @_;
  1300 	my @ocs  = @_;
  1178 
  1301 
  1179 	my ( $fh, $fname ) =
  1302 	# manually generate some boilerplate LDIF.
  1180 		File::Temp::tempfile( 'shelldap_XXXXXXXX', DIR => '/tmp', UNLINK => 1 );
  1303 	#
  1181 
  1304 	unless ( $self->{'create_file'} ) {
  1182 	# first print out the dn and object classes.
  1305 		my $fh;
  1183 	print $fh 'dn: ???,', $self->base(), "\n";
  1306 
  1184 	foreach my $oc ( sort @ocs ) {
  1307 		( $fh, $self->{'create_file'} ) =
  1185 		print $fh "objectClass: $oc\n";
  1308 			File::Temp::tempfile( 'shelldap_XXXXXXXX', DIR => '/tmp', UNLINK => 1 );
  1186 	}
  1309 
  1187 
  1310 		# first print out the dn and object classes.
  1188 	# now gather attributes for requested objectClasses
  1311 		#
  1189 	#
  1312 		print $fh 'dn: ???,', $self->base(), "\n";
  1190 	my ( %seen, @must_attr, @may_attr );
  1313 		foreach my $oc ( sort @ocs ) {
  1191 	foreach my $oc ( sort @ocs ) {
  1314 			print $fh "objectClass: $oc\n";
  1192 
  1315 		}
  1193 		# required
  1316 
  1194 		my @must = $self->{'schema'}->must( $oc );
  1317 		# gather and print attributes for requested objectClasses
  1195 		foreach my $attr ( sort { $a->{'name'} cmp $b->{'name'} } @must ) {
  1318 		#
  1196 			next if $attr->{'name'} =~ /^objectclass$/i;
  1319 		my ( $must_attr, $may_attr ) = $self->fetch_attributes( \@ocs );
  1197 			next if $seen{ $attr->{'name'} };
  1320 		print $fh "$_: \n"   foreach @{ $must_attr };
  1198 			push @must_attr, $attr->{'name'};
  1321 		print $fh "# $_: \n" foreach @{ $may_attr };
  1199 			$seen{ $attr->{'name'} }++;
  1322 		close $fh;
  1200 		}
  1323 	}
  1201 
  1324 
  1202 		# optional
  1325 	# checksum the file.
  1203 		my @may  = $self->{'schema'}->may( $oc );
  1326 	#
  1204 		foreach my $attr ( sort { $a->{'name'} cmp $b->{'name'} } @may ) {
  1327 	my $hash_orig = $self->chksum( $self->{'create_file'} );
  1205 			next if $attr->{'name'} =~ /^objectclass$/i;
  1328 	system( $self->{'editor'}, $self->{'create_file'} ) && die "Unable to launch editor: $!\n";
  1206 			next if $seen{ $attr->{'name'} };
  1329 
  1207 			push @may_attr, $attr->{'name'};
  1330 	# detect a total lack of change
  1208 			$seen{ $attr->{'name'} }++;
  1331 	#
  1209 		}
  1332 	if ( $hash_orig eq $self->chksum($self->{'create_file'}) ) {
  1210 	}
       
  1211 
       
  1212 	# print attributes
       
  1213 	print $fh "$_: \n"   foreach @must_attr;
       
  1214 	print $fh "# $_: \n" foreach @may_attr;
       
  1215 	close $fh;
       
  1216 	my $hash_a = $self->chksum( $fname );
       
  1217 	system( $self->{'editor'}, $fname ) && die "Unable to launch editor: $!\n";
       
  1218 
       
  1219 	# hash compare
       
  1220 	my $hash_b = $self->chksum( $fname );
       
  1221 	if ( $hash_a eq $hash_b ) {
       
  1222 		print "Entry not modified.\n";
  1333 		print "Entry not modified.\n";
  1223 		unlink $fname;
  1334 		unlink $self->{'create_file'};
       
  1335 		$self->{'create_file'} = undef;
  1224 		return;
  1336 		return;
  1225 	}
  1337 	}
  1226 
  1338 
  1227 	# load in LDIF
  1339 	# load in LDIF
  1228 	my $ldif = Net::LDAP::LDIF->new( $fname, 'r', onerror => 'warn' );
  1340 	#
  1229 	my $e	= $ldif->read_entry();
  1341 	my $ldif = Net::LDAP::LDIF->new( $self->{'create_file'}, 'r', onerror => 'warn' );
       
  1342 	my $e	 = $ldif->read_entry();
  1230 	unless ( $e ) {
  1343 	unless ( $e ) {
  1231 		print "Unable to parse LDIF.\n";
  1344 		print "Unable to parse LDIF.\n";
  1232 		unlink $fname;
  1345 		unlink $self->{'create_file'};
  1233 		return;
  1346 		$self->{'create_file'} = undef;
  1234 	}
  1347 		return;
       
  1348 	}
       
  1349 
       
  1350 	# create the new entry.
       
  1351 	#
  1235 	$e->changetype('add');
  1352 	$e->changetype('add');
  1236 	my $create = sub { return $e->update($self->ldap()) };
  1353 	my $create = sub { return $e->update($self->ldap()) };
  1237 	my $rv = $self->with_retry( $create );
  1354 	my $rv = $self->with_retry( $create );
  1238 	print $rv->error(), "\n";
  1355 	print $rv->error(), "\n";
  1239 
  1356 
  1240 	$self->update_entries( clearcache => 1 ) unless $rv->code();
  1357 	if ( $rv->code() != LDAP_SUCCESS && $self->prompt_edit_again() ) {
  1241 
  1358 		return $self->run_create();
  1242 	unlink $fname;
  1359 	}
       
  1360 
       
  1361 	$self->update_entries( clearcache => 1 );
       
  1362 	unlink $self->{'create_file'};
       
  1363 	$self->{'create_file'} = undef;
  1243 	return;
  1364 	return;
  1244 }
  1365 }
  1245 
  1366 
       
  1367 
       
  1368 ### Remove an entry (or entries) from the LDAP directory.
       
  1369 ###
  1246 sub run_delete
  1370 sub run_delete
  1247 {
  1371 {
  1248 	my $self = shift;
  1372 	my $self = shift;
  1249 	my @DNs  = @_;
  1373 	my @DNs  = @_;
  1250 
  1374 
  1257 	unless ( $DNs[0] eq '*' ) {
  1381 	unless ( $DNs[0] eq '*' ) {
  1258 		$filter = $self->make_filter( \@DNs ) or return;
  1382 		$filter = $self->make_filter( \@DNs ) or return;
  1259 	}
  1383 	}
  1260 
  1384 
  1261 	my $s = $self->search({ scope => 'one', filter => $filter });
  1385 	my $s = $self->search({ scope => 'one', filter => $filter });
  1262 	if ( $s->{'code'} ) {
  1386 	unless ( $s->{'code'} == LDAP_SUCCESS ) {
  1263 		print "$s->{'message'}\n";
  1387 		print "$s->{'message'}\n";
  1264 		return;
  1388 		return;
  1265 	}
  1389 	}
  1266 
  1390 
  1267 	print "Are you sure? [N/y]: ";
  1391 	print "Are you sure? [Ny]: ";
  1268 	chomp( my $resp = <STDIN> );
  1392 	chomp( my $resp = <STDIN> );
  1269 	return unless $resp =~ /^y/i;
  1393 	return unless $resp =~ /^y/i;
  1270    
  1394    
  1271 	foreach my $e ( @{ $s->{'entries'} } ) {
  1395 	foreach my $e ( @{ $s->{'entries'} } ) {
  1272 		my $dn = $e->dn();
  1396 		my $dn = $e->dn();
  1276    
  1400    
  1277 	$self->update_entries( clearcache => 1 );
  1401 	$self->update_entries( clearcache => 1 );
  1278 	return;
  1402 	return;
  1279 }
  1403 }
  1280 
  1404 
       
  1405 
       
  1406 ### Fetch an entry from the directory, write it out to disk
       
  1407 ### as LDIF, launch an editor, then compare changes and write
       
  1408 ### it back to the directory.
       
  1409 ###
  1281 sub run_edit
  1410 sub run_edit
  1282 {
  1411 {
  1283 	my $self = shift;
  1412 	my $self = shift;
  1284 	my $dn   = shift;
  1413 	my $dn   = shift;
  1285 
  1414 
  1289 	}
  1418 	}
  1290 
  1419 
  1291 	# convert given path to DN
  1420 	# convert given path to DN
  1292 	$dn = $self->path_to_dn( $dn );
  1421 	$dn = $self->path_to_dn( $dn );
  1293 
  1422 
       
  1423 	# sanity check
       
  1424 	#
  1294 	my $s = $self->search({ base => $dn, vals => 1 });
  1425 	my $s = $self->search({ base => $dn, vals => 1 });
  1295 
  1426 	unless ( $s->{'code'} == LDAP_SUCCESS ) {
  1296 	if ( $s->{'code'} ) {
       
  1297 		print $s->{'message'} . "\n";
  1427 		print $s->{'message'} . "\n";
  1298 		return;
  1428 		return;
  1299 	}
  1429 	}
  1300 
  1430 
  1301 	# fetch entry and write it out to disk
  1431 	# fetch entry.
  1302 	my $e = ${ $s->{'entries'} }[0];
  1432 	my $e = ${ $s->{'entries'} }[0];
  1303 	my $ldif = $self->ldif(1);
  1433 	$e->changetype( 'modify' );
  1304 	$ldif->write_entry( $e );
  1434 
  1305 	$ldif->done();  # force sync
  1435 	# write it out to disk.
       
  1436 	#
       
  1437 	unless( $self->{'edit_again'} )  {
       
  1438 		my $ldif = $self->ldif(1);
       
  1439 		$ldif->write_entry( $e );
       
  1440 		$ldif->done();  # force sync
       
  1441 	}
  1306 
  1442 
  1307 	# load it into an array for potential comparison
  1443 	# load it into an array for potential comparison
  1308 	open LDIF, "$self->{'ldif_fname'}" or return;
  1444 	open LDIF, "$self->{'ldif_fname'}" or return;
  1309 	my @orig_ldif = <LDIF>;
  1445 	my @orig_ldif = <LDIF>;
  1310 	close LDIF;
  1446 	close LDIF;
  1311 
  1447 
  1312 	# append optional, unused attributes as comments
  1448 	# append optional, unused attributes as comments for fast reference.
  1313 	# for fast reference.
  1449 	#
  1314 	#
  1450 	unless ( $self->{'edit_again'} )  {
  1315 	open LDIF, ">> $self->{'ldif_fname'}";
  1451 		my %current_attrs = map { $_ => 1 } $e->attributes();
  1316 	my %current_attrs = map { $_ => 1 } $e->attributes();
  1452 		my ( $must_attr, $may_attr ) = $self->fetch_attributes( $e->get_value('objectClass', asref => 1) );
  1317 	foreach my $oc ( $e->get_value('objectClass') ) {
  1453 
  1318 		my @may = $self->{'schema'}->may( $oc );
  1454 		open LDIF, ">> $self->{'ldif_fname'}";
  1319 		foreach my $opt_attr ( sort { $a->{'name'} cmp $b->{'name'} } @may ) {
  1455 		foreach my $opt_attr ( sort { $a cmp $b } @{$may_attr} ) {
  1320 			next if $current_attrs{ $opt_attr->{'name'} };
  1456 			next if $current_attrs{ $opt_attr };
  1321 			print LDIF "# " . $opt_attr->{'name'} . ":\n";
  1457 			print LDIF "# " . $opt_attr . ":\n";
  1322 		}
  1458 		}
  1323 	}
  1459 		close LDIF;
  1324 	close LDIF;
  1460 	}
  1325 
  1461 
  1326 	# checksum it, then open it in an editor
  1462 	# checksum it, then open it in an editor
  1327 	my $hash_a = $self->chksum( $self->{'ldif_fname'} );
  1463 	#
  1328 	system( "$self->{'editor'} $self->{'ldif_fname'}" ) &&
  1464 	my $hash_orig = $self->chksum( $self->{'ldif_fname'} );
       
  1465 	system( $self->{'editor'}, $self->{'ldif_fname'} ) &&
  1329 		die "Unable to launch editor: $!\n";
  1466 		die "Unable to launch editor: $!\n";
  1330 
  1467 
  1331 	# detect a total lack of change
  1468 	# detect a total lack of change
  1332 	my $hash_b = $self->chksum( $self->{'ldif_fname'} );
  1469 	#
  1333 	if ( $hash_a eq $hash_b ) {
  1470 	if ( $hash_orig eq $self->chksum($self->{'ldif_fname'}) ) {
  1334 		print "Entry not modified.\n";
  1471 		print "Entry not modified.\n";
  1335 		unlink $self->{'ldif_fname'};
  1472 		unlink $self->{'ldif_fname'};
  1336 		return;
  1473 		return;
  1337 	}
  1474 	}
  1338 
  1475 
  1339 	# check changes for basic LDIF validity
  1476 	# check changes for basic LDIF validity
  1340 	my $new_e = $self->load_ldif( $self->{'ldif_fname'} );
  1477 	#
  1341 	unless ( $new_e ) {
  1478 	while( ! $self->load_ldif($self->{'ldif_fname'}) ) {
  1342 		print "Unable to parse LDIF.\n";
  1479 		print "Unable to parse LDIF.\n";
  1343 		unlink $self->{'ldif_fname'};
  1480 		if ( $self->prompt_edit_again() ) {
  1344 		return;
  1481 			system( $self->{'editor'}, $self->{'ldif_fname'} );
       
  1482 		}
       
  1483 		else {
       
  1484 			unlink $self->{'ldif_fname'};
       
  1485 			return;
       
  1486 		}
  1345 	}
  1487 	}
  1346 
  1488 
  1347 	# load changes into a new array for comparison
  1489 	# load changes into a new array for comparison
       
  1490 	#
  1348 	open LDIF, "$self->{'ldif_fname'}" or return;
  1491 	open LDIF, "$self->{'ldif_fname'}" or return;
  1349 	my @new_ldif = <LDIF>;
  1492 	my @new_ldif = <LDIF>;
  1350 	close LDIF;
  1493 	close LDIF;
  1351 
  1494 
  1352 	$e->changetype('modify');
  1495 	# parser subref
  1353 
  1496 	#
  1354 	my $parse = sub {
  1497 	my $parse = sub {
  1355 		my $line = shift || $_;
  1498 		my $line = shift || $_;
  1356 		return if $line	 =~ /^\#/; # ignore comments
  1499 		return if $line	 =~ /^\#/; # ignore comments
  1357 		my ( $attr, $val ) = ( $1, $2 ) if $line =~ /^(.+?): (.*)$/;
  1500 		my ( $attr, $val ) = ( $1, $2 ) if $line =~ /^(.+?): (.*)$/;
  1358 		return unless $attr;
  1501 		return unless $attr;
  1359 		return if index($attr, ':') != -1;  # ignore base64
  1502 		return if index($attr, ':') != -1;  # ignore base64
  1360 		return ( $attr, $val );
  1503 		return ( $attr, $val );
  1361 	};
  1504 	};
  1362 
  1505 
  1363 	unwrap( \@orig_ldif );
  1506 	$self->unwrap_line( \@orig_ldif );
  1364 	unwrap( \@new_ldif );
  1507 	$self->unwrap_line( \@new_ldif );
  1365 
  1508 
  1366 	my $diff = Algorithm::Diff->new( \@orig_ldif, \@new_ldif );
  1509 	my $diff = Algorithm::Diff->new( \@orig_ldif, \@new_ldif );
  1367 	HUNK:
  1510 	HUNK:
  1368 	while ( $diff->Next() ) {
  1511 	while ( $diff->Next() ) {
  1369 		next if $diff->Same();
  1512 		next if $diff->Same();
  1370 		my $diff_bit = $diff->Diff();
  1513 		my $diff_bit = $diff->Diff();
  1371 		my %seen_attr;
  1514 		my %seen_attr;
  1372 
  1515 
  1373 		# total deletions
  1516 		# attr removals
       
  1517 		#
  1374 		if ( $diff_bit == 1 ) {
  1518 		if ( $diff_bit == 1 ) {
  1375 			foreach ( $diff->Items(1) ) {
  1519 			foreach ( $diff->Items(1) ) {
  1376 				my ( $attr, $val ) = $parse->( $_ ) or next;
  1520 				my ( $attr, $val ) = $parse->( $_ ) or next;
  1377 				$self->debug("DELETE: $_");
  1521 				$self->debug("DELETE: $_");
  1378 				$e->delete( $attr => [ $val ] );
  1522 				$e->delete( $attr => [ $val ] );
  1379 			}
  1523 			}
  1380 		}
  1524 		}
  1381 
  1525 
  1382 		# new insertions
  1526 		# attr insertions
       
  1527 		#
  1383 		if ( $diff_bit == 2 ) {
  1528 		if ( $diff_bit == 2 ) {
  1384 			foreach ( $diff->Items(2) ) {
  1529 			foreach ( $diff->Items(2) ) {
  1385 				my ( $attr, $val ) = $parse->( $_ ) or next;
  1530 				my ( $attr, $val ) = $parse->( $_ ) or next;
  1386 				$self->debug("INSERT: $_");
  1531 				$self->debug("INSERT: $_");
  1387 				$e->add( $attr => $val );
  1532 				$e->add( $attr => $val );
  1388 			}
  1533 			}
  1389 		}
  1534 		}
  1390 
  1535 
  1391 		# replacements
  1536 		# attr change
       
  1537 		#
  1392 		if ( $diff_bit == 3 ) {
  1538 		if ( $diff_bit == 3 ) {
  1393 			foreach ( $diff->Items(2) ) {
  1539 			foreach ( $diff->Items(2) ) {
  1394 				my ( $attr, $val ) = $parse->( $_ ) or next;
  1540 				my ( $attr, $val ) = $parse->( $_ ) or next;
  1395 				$self->debug("MODIFY: $_");
  1541 				$self->debug("MODIFY: $_");
  1396 
  1542 
  1403 				if ( $cur_valcount == 1 ) {
  1549 				if ( $cur_valcount == 1 ) {
  1404 					$e->replace( $attr => $val );
  1550 					$e->replace( $attr => $val );
  1405 				}
  1551 				}
  1406 				else {
  1552 				else {
  1407 
  1553 
  1408 					# make sure the replace doesn't squash
  1554 					# retain attributes that allow multiples, so updating
  1409 					# other attributes listed with the same name
  1555 					# one attribute doesn't inadvertently remove others with
       
  1556 					# the same name.
  1410 					#
  1557 					#
  1411 					next if $seen_attr{ $attr };
  1558 					next if $seen_attr{ $attr };
  1412 					my @new_vals;
  1559 					my @new_vals;
  1413 					foreach my $line ( @new_ldif ) {
  1560 					foreach my $line ( @new_ldif ) {
  1414 						my ( $new_attr, $new_val ) = $parse->( $line ) or next;
  1561 						my ( $new_attr, $new_val ) = $parse->( $line ) or next;
  1415 						next unless $new_attr eq $attr;
  1562 						next unless $new_attr eq $attr;
  1416 						$seen_attr{ $attr }++;
  1563 						$seen_attr{ $attr }++;
  1417 						push @new_vals, $new_val;
  1564 						push @new_vals, $new_val;
  1418 					}
  1565 					}
       
  1566 
  1419 					$e->replace( $attr => \@new_vals );
  1567 					$e->replace( $attr => \@new_vals );
  1420 				}
  1568 				}
  1421 			}
  1569 			}
  1422 		}
  1570 		}
  1423 
  1571 	}
  1424 	}
  1572 
  1425 
       
  1426 	unlink $self->{'ldif_fname'};
       
  1427 	my $update = sub { return $e->update( $self->ldap ); };
  1573 	my $update = sub { return $e->update( $self->ldap ); };
  1428 	my $rv = $self->with_retry( $update );
  1574 	my $rv = $self->with_retry( $update );
  1429 	print $rv->error(), "\n";
  1575 	print $rv->error(), "\n";
  1430 
  1576 
       
  1577 	if ( $rv->code() != LDAP_SUCCESS && $self->prompt_edit_again() ) {
       
  1578 		$self->{'edit_again'} = 1;
       
  1579 		return $self->run_edit( $dn );
       
  1580 	}
       
  1581 
       
  1582 	unlink $self->{'ldif_fname'};
       
  1583 	$self->{'edit_again'} = undef;
  1431 	return;
  1584 	return;
  1432 }
  1585 }
  1433 
  1586 
       
  1587 
       
  1588 ### Display current tunable runtime settings.
       
  1589 ###
  1434 sub run_env
  1590 sub run_env
  1435 {
  1591 {
  1436 	my $self = shift;
  1592 	my $self = shift;
  1437 
  1593 
  1438 	foreach ( sort @{ $self->{'env'} } ) {
  1594 	foreach ( sort @{ $self->{'env'} } ) {
  1440 		print $conf->{$_} ? $conf->{$_} : 0; 
  1596 		print $conf->{$_} ? $conf->{$_} : 0; 
  1441 		print "\n"
  1597 		print "\n"
  1442 	}
  1598 	}
  1443 }
  1599 }
  1444 
  1600 
       
  1601 
       
  1602 ### Alter settings.
       
  1603 ###
       
  1604 sub run_setenv
       
  1605 {
       
  1606 	my $self = shift;
       
  1607 	my ( $key, $val ) = @_;
       
  1608 
       
  1609 	( $key, $val ) = split /=/, $key if $key && ! defined $val;
       
  1610 	return unless $key && defined $val;
       
  1611 	$key = lc $key;
       
  1612 
       
  1613 	$conf->{$key} = $val;
       
  1614 	return;
       
  1615 }
       
  1616 
       
  1617 
       
  1618 ### Search across the directory and display matching entries.
       
  1619 ###
  1445 sub run_grep
  1620 sub run_grep
  1446 {
  1621 {
  1447 	my $self = shift;
  1622 	my $self = shift;
  1448 	my ( $recurse, $filter, $base ) = @_;
  1623 	my ( $recurse, $filter, $base ) = @_;
  1449 
  1624 
  1470 	# convert base path to DN
  1645 	# convert base path to DN
  1471 	$base = $self->path_to_dn( $base );
  1646 	$base = $self->path_to_dn( $base );
  1472 
  1647 
  1473 	$self->debug("Filter parsed as: " . $filter->as_string() . "\n");
  1648 	$self->debug("Filter parsed as: " . $filter->as_string() . "\n");
  1474 
  1649 
  1475 	my $s = $self->search(
  1650 	my $s = $self->search({
  1476 		{
  1651 		scope  => $recurse ? 'sub' : 'one',
  1477 			scope  => $recurse ? 'sub' : 'one',
  1652 		base   => $base,
  1478 			base   => $base,
  1653 		filter => $filter
  1479 			filter => $filter
  1654 	});
  1480 		}
       
  1481 	);
       
  1482 
  1655 
  1483 	foreach my $e ( @{ $s->{'entries'} } ) {
  1656 	foreach my $e ( @{ $s->{'entries'} } ) {
  1484 		my $dn = $e->dn();
  1657 		my $dn = $e->dn();
  1485 		print "$dn\n";
  1658 		print "$dn\n";
  1486 	}
  1659 	}
  1487 
  1660 
  1488 	return;
  1661 	return;
  1489 }
  1662 }
  1490 
  1663 
  1491 # override internal help functions
  1664 
  1492 # with pod2usage
  1665 ### Override internal help function with pod2usage output.
  1493 #
  1666 ###
  1494 sub run_help 
  1667 sub run_help 
  1495 {
  1668 {
  1496 	return Pod::Usage::pod2usage(
  1669 	return Pod::Usage::pod2usage(
  1497 		-exitval  => 'NOEXIT',
  1670 		-exitval  => 'NOEXIT',
  1498 		-verbose  => 99,
  1671 		-verbose  => 99,
  1499 		-sections => 'SHELL COMMANDS'
  1672 		-sections => 'SHELL COMMANDS'
  1500 	);
  1673 	);
  1501 }
  1674 }
  1502 
  1675 
       
  1676 
       
  1677 ### Generate and display a list of LDAP entries, relative to the current
       
  1678 ### location the command was run from.
       
  1679 ###
  1503 sub run_list
  1680 sub run_list
  1504 {
  1681 {
  1505 	my $self  = shift;
  1682 	my $self  = shift;
  1506 	my @args  = @_;
  1683 	my @args  = @_;
  1507 	my @attrs = ();
  1684 	my @attrs = ();
  1509 
  1686 
  1510 	# flag booleans
  1687 	# flag booleans
  1511 	my ( $recurse, $long );
  1688 	my ( $recurse, $long );
  1512 
  1689 
  1513 	# parse arguments: [ <option> ...] [<filter> ...] [<attribute> ...]
  1690 	# parse arguments: [ <option> ...] [<filter> ...] [<attribute> ...]
       
  1691 	#
  1514 	if ( scalar @args ) {
  1692 	if ( scalar @args ) {
  1515 		# options: support '-l' or '-R' listings
  1693 		# options: support '-l' or '-R' listings
  1516 		if ( $args[0] =~ /^\-(\w+)/o ) {
  1694 		if ( $args[0] =~ /^\-(\w+)/o ) {
  1517 			my $flags = $1;
  1695 			my $flags = $1;
  1518 			$recurse  = $flags =~ /R/;
  1696 			$recurse  = $flags =~ /R/;
  1521 		}
  1699 		}
  1522 
  1700 
  1523 		my @filters;
  1701 		my @filters;
  1524 
  1702 
  1525 		# get filter elements from argument list
  1703 		# get filter elements from argument list
       
  1704 		#
  1526 		while ( @args && $self->is_valid_filter($args[0]) ) {
  1705 		while ( @args && $self->is_valid_filter($args[0]) ) {
  1527 			push( @filters, shift(@args) );
  1706 			push( @filters, shift(@args) );
  1528 		}
  1707 		}
  1529 
  1708 
       
  1709 		# No filter for display?  Default to all entries.
  1530 		push( @filters, '(objectClass=*)' ) unless scalar @filters;
  1710 		push( @filters, '(objectClass=*)' ) unless scalar @filters;
  1531 		
  1711 		
  1532 		# construct OR'ed filter from filter elements
  1712 		# construct OR'ed filter from filter elements
  1533 		$filter = $self->make_filter( \@filters );
  1713 		$filter = $self->make_filter( \@filters );
  1534 
  1714 
  1599 				push( @elements, join(',', @vals) );
  1779 				push( @elements, join(',', @vals) );
  1600 			}
  1780 			}
  1601 
  1781 
  1602 			print join( "\t", @elements )."\n";
  1782 			print join( "\t", @elements )."\n";
  1603 		}
  1783 		}
       
  1784 
       
  1785 		# show descriptions
       
  1786 		#
  1604 		else {
  1787 		else {
  1605 			# show descriptions
       
  1606 			my $desc = $e->get_value( 'description' );
  1788 			my $desc = $e->get_value( 'description' );
  1607 			if ( $desc ) {
  1789 			if ( $desc ) {
  1608 				$desc =~ s/\n.*//s; # 1st line only
  1790 				$desc =~ s/\n.*//s; # 1st line only
  1609 				$dn .= " ($desc)";
  1791 				$dn .= " ($desc)";
  1610 			}
  1792 			}
  1611 
  1793 
  1612 			# no desc?  Try and infer something useful
  1794 			# no desc?  Try and infer something useful
  1613 			# to display.
  1795 			# to display.
       
  1796 			#
  1614 			else {
  1797 			else {
  1615 
  1798 
  1616 				# pull objectClasses, hash for lookup speed
  1799 				# pull objectClasses, hash for lookup speed
  1617 				my @oc = $e->get_value( 'objectClass' );
  1800 				my @oc = $e->get_value( 'objectClass' );
  1618 				my %ochash;
  1801 				my %ochash;
  1635 		( $dn_count == 1 ? 'object.' : 'objects.') .
  1818 		( $dn_count == 1 ? 'object.' : 'objects.') .
  1636 		"\n" if $long;
  1819 		"\n" if $long;
  1637 	return;
  1820 	return;
  1638 }
  1821 }
  1639 
  1822 
       
  1823 
       
  1824 ### Create a new organizationalUnit entry.
       
  1825 ###
  1640 sub run_mkdir
  1826 sub run_mkdir
  1641 {
  1827 {
  1642 	my $self = shift;
  1828 	my $self = shift;
  1643 	my $dir  = shift;
  1829 	my $dir  = shift;
  1644 
  1830 
  1668 	print $rv->error(), "\n";
  1854 	print $rv->error(), "\n";
  1669 	$self->update_entries( clearcache => 1 );
  1855 	$self->update_entries( clearcache => 1 );
  1670 	return;
  1856 	return;
  1671 }
  1857 }
  1672 
  1858 
       
  1859 
       
  1860 ### Alter an entry's DN.
       
  1861 ###
  1673 sub run_move
  1862 sub run_move
  1674 {
  1863 {
  1675 	my $self = shift;
  1864 	my $self = shift;
  1676 	my ( $s_dn, $d_dn ) = @_;
  1865 	my ( $s_dn, $d_dn ) = @_;
  1677 
  1866 
  1713 	$self->{'cache'}->{ $old_dn } = {} if $old_dn;
  1902 	$self->{'cache'}->{ $old_dn } = {} if $old_dn;
  1714 	$self->update_entries( clearcache => 1 );
  1903 	$self->update_entries( clearcache => 1 );
  1715 	return;
  1904 	return;
  1716 }
  1905 }
  1717 
  1906 
       
  1907 
       
  1908 ### Change the 'userPassword' attribute of an entry, if
       
  1909 ### supported by the LDAP server.
       
  1910 ###
  1718 sub run_passwd 
  1911 sub run_passwd 
  1719 {
  1912 {
  1720 	my $self = shift;
  1913 	my $self = shift;
  1721 	my $dn   = shift || $self->base();
  1914 	my $dn   = shift || $self->base();
  1722 
  1915 
  1755 	if ( $pw ne $pw2 ) {
  1948 	if ( $pw ne $pw2 ) {
  1756 		print "Sorry, passwords do not match.\n";
  1949 		print "Sorry, passwords do not match.\n";
  1757 		return;
  1950 		return;
  1758 	}
  1951 	}
  1759 
  1952 
  1760 	my $rv = $self->ldap->set_password(
  1953 	my $setpw = sub { return $self->ldap->set_password( user => $dn, newpasswd => $pw ); };
  1761 		user	  => $dn,
  1954 	my $rv = $self->with_retry( $setpw );
  1762 		newpasswd => $pw
       
  1763 	);
       
  1764 
  1955 
  1765 	if ( $rv->code() == LDAP_SUCCESS ) {
  1956 	if ( $rv->code() == LDAP_SUCCESS ) {
  1766 		print "Password updated successfully.\n";
  1957 		print "Password updated successfully.\n";
  1767 	}
  1958 	}
  1768 	else {
  1959 	else {
  1770 	}
  1961 	}
  1771 
  1962 
  1772 	return;
  1963 	return;
  1773 }
  1964 }
  1774 
  1965 
       
  1966 
       
  1967 ### Display the current working "directory".
       
  1968 ###
  1775 sub run_pwd 
  1969 sub run_pwd 
  1776 {
  1970 {
  1777 	my $self = shift;
  1971 	my $self = shift;
  1778 	print $self->base() . "\n";
  1972 	print $self->base() . "\n";
  1779 	return;   
  1973 	return;   
  1780 }
  1974 }
  1781 
  1975 
  1782 sub run_setenv
  1976 
  1783 {
  1977 ### Display the currently bound user.
  1784 	my $self = shift;
  1978 ###
  1785 	my ( $key, $val ) = @_;
       
  1786 
       
  1787 	( $key, $val ) = split /=/, $key if $key && ! defined $val;
       
  1788 	return unless $key && defined $val;
       
  1789 	$key = lc $key;
       
  1790 
       
  1791 	$conf->{$key} = $val;
       
  1792 	return;
       
  1793 }
       
  1794 
       
  1795 sub run_whoami
  1979 sub run_whoami
  1796 {
  1980 {
  1797 	my $self = shift;
  1981 	my $self = shift;
  1798 	print $conf->{'binddn'} || 'anonymous bind';
  1982 	print $conf->{'binddn'} || 'anonymous bind';
  1799 	print "\n";
  1983 	print "\n";
  1800 	return;
  1984 	return;
  1801 }
  1985 }
  1802 
  1986 
  1803 ###############################################################
  1987 
  1804 #
  1988 ########################################################################
  1805 # MAIN
  1989 ### M A I N
  1806 #
  1990 ########################################################################
  1807 ###############################################################
       
  1808 
  1991 
  1809 package main;
  1992 package main;
  1810 use strict;
  1993 use strict;
  1811 use warnings;
  1994 use warnings;
  1812 
  1995 
  1813 $0 = 'shelldap';
  1996 $0 = 'shelldap';
  1814 my $VERSION = '0.8';
  1997 my $VERSION = '0.9.0';
  1815 
  1998 
  1816 use Getopt::Long;
  1999 use Getopt::Long;
  1817 use YAML::Syck;
  2000 use YAML::Syck;
  1818 use Pod::Usage;
  2001 use Pod::Usage;
  1819 eval 'use Term::ReadLine::Gnu';
  2002 eval 'use Term::ReadLine::Gnu';
  1824 use vars '$conf';
  2007 use vars '$conf';
  1825 $conf = load_config() || {};
  2008 $conf = load_config() || {};
  1826 Getopt::Long::GetOptions(
  2009 Getopt::Long::GetOptions(
  1827 	$conf, 
  2010 	$conf, 
  1828 	'server|H=s',
  2011 	'server|H=s',
       
  2012 	'configfile|f=s',
  1829 	'binddn|D=s',
  2013 	'binddn|D=s',
  1830 	'basedn|b=s',
  2014 	'basedn|b=s',
  1831 	'cacheage=i',
  2015 	'cacheage=i',
  1832 	'promptpass|W',
  2016 	'promptpass|W',
  1833 	'timeout=i',
  2017 	'timeout=i',
  1843 	}
  2027 	}
  1844 );
  2028 );
  1845 
  2029 
  1846 # show version
  2030 # show version
  1847 if ( $conf->{'version'} ) {
  2031 if ( $conf->{'version'} ) {
  1848 	print "$VERSION\n";
  2032 	print "$0 $VERSION\n";
  1849 	exit( 0 );
  2033 	exit( 0 );
  1850 }
  2034 }
  1851 
  2035 
       
  2036 # additional/different config file?
       
  2037 #
       
  2038 if ( $conf->{'configfile'} ) {
       
  2039 	my $more_conf = load_config( $conf->{'configfile'} );
       
  2040 	while ( my ($k, $v) = each %{$conf} ) { $conf->{ $k } = $v }
       
  2041 }
       
  2042 
       
  2043 
  1852 # defaults
  2044 # defaults
  1853 $conf->{'confpath'} = "$ENV{'HOME'}/.shelldap.rc";
  2045 $conf->{'configfile'} ||= "$ENV{'HOME'}/.shelldap.rc";
  1854 $conf->{'cacheage'} ||= 300;
  2046 $conf->{'cacheage'} ||= 300;
  1855 $conf->{'timeout'}  ||= 10;
  2047 $conf->{'timeout'}  ||= 10;
  1856 
  2048 
  1857 # create and enter shell loop
  2049 # create and enter shell loop
  1858 my $shell = LDAP::Shell->new();
  2050 my $shell = LDAP::Shell->new();
  1859 $shell->cmdloop();
  2051 $shell->cmdloop();
  1860 
  2052 
  1861 # load YAML config into global conf.
  2053 ### load YAML config into global conf.
  1862 #
  2054 ###
  1863 sub load_config
  2055 sub load_config
  1864 {
  2056 {
       
  2057 	my $confpath = shift;
  1865 	my ( $d, $data );
  2058 	my ( $d, $data );
  1866 
  2059 
  1867 	my $confpath;
  2060 	unless ( $confpath ) {
  1868 	my @confs = (
  2061 		my @confs = (
  1869 		"$ENV{'HOME'}/.shelldap.rc",
  2062 			"$ENV{'HOME'}/.shelldap.rc",
  1870 		'/usr/local/etc/shelldap.conf',
  2063 			'/usr/local/etc/shelldap.conf',
  1871 		'/etc/shelldap.conf',
  2064 			'/etc/shelldap.conf',
  1872 	);
  2065 		);
  1873 	foreach ( @confs ) {
  2066 		foreach ( @confs ) {
  1874 		if ( -e $_ ) {
  2067 			if ( -e $_ ) {
  1875 			$confpath = $_;
  2068 				$confpath = $_;
  1876 			last;
  2069 				last;
       
  2070 			}
  1877 		}
  2071 		}
  1878 	}
  2072 	}
  1879 	$confpath or return undef;
  2073 	$confpath or return undef;
  1880 
  2074 
  1881 	open YAML, $confpath or return undef;
  2075 	open YAML, $confpath or return undef;
  1889 	die "Invalid YAML in $confpath\n" if $@;
  2083 	die "Invalid YAML in $confpath\n" if $@;
  1890 
  2084 
  1891 	return $conf;
  2085 	return $conf;
  1892 }
  2086 }
  1893 
  2087 
  1894 ## EOF
  2088 ### EOF
  1895 
  2089