changeset 50 | 21ba5eb5c2fc |
parent 49 | 57df728cdb77 |
child 51 | 27bbe75233a3 |
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 |