diff -Nru shelldap-1.4.0/CHANGELOG shelldap-1.5.0/CHANGELOG --- shelldap-1.4.0/CHANGELOG 2017-06-07 22:11:05.000000000 +0000 +++ shelldap-1.5.0/CHANGELOG 1970-01-01 00:00:00.000000000 +0000 @@ -1,164 +0,0 @@ -2017-06-07 v1.4.0 - - * Optionally support server side pagination controls. - - * Documentation spelling corrections. - - -2016-01-12 v1.3.2 - - * Don't test for optional perl modules by string eval(). - - * Migration to BitBucket for issue tracking and downloads. - Primary repository is still (and will continue to be) martini.nu. - - * Fix anonymous binds when SASL is not used. - - * Allow the period character when moving an entry by DN. - - * Allow editor arguments in your .shelldap.rc or EDITOR environment - variable when editing files externally. - - -2015-03-04 v1.3.1 - - * Use the system definied tempdir instead of hardcoding /tmp. - - -2014-12-04 v1.3.0 - - * Add a 'less' command, that does the same as cat, but uses a pager. - - -2014-08-11 v.1.2.0 - - * Disable LDIF line wrapping when writing -to- temporary files. - - * Add 'rm' for a fully qualified DN, instead of only working with RDN. - - -2014-06-23 v1.1.1 - - * Fix pod documentation so it can build without complaint under perl 5.20. - - -2014-06-21 v.1.1.0 - - * Add CONTRIBUTORS file. - - * Add simple (optional) SASL support. - - * Fix the DN regexp to include dashes. - - -2013-05-15 v1.0.2 - - * Automatically use ldif syntax highlighting for editors that understand LDIF. - - * Catch a case where the LDAP object is defined, but in a state that - schema/root_dse are not obtainable. Add the connected server to - 'id/whoami' output. - - * Fix another LCS edge case. - - -2013-04-18 v1.0.1 - - * Fix for edge case Diff::LCS traversals. - * Ensure re-edit state is cleared in between attempts. - - -2013-03-19 v1.0.0 - - * Add the "inspect" command, which provides some quick reference for - server schema objectClasses and attributes. - - * Add a command line option (-f) to specify an alternate configuration file. - - * Allow setting the $editor from the config file. - - * Offer to re-enter the editor if there is an error during create or - edit, so changes aren't lost. - - * Change the version number to reflect semantic versioning (http://semver.org). - - * Alter the default wrap width for LDIF to expand to the terminal - size, with an optional rc file override. - - * More robust path for connection retries. Show optional, unused - attributes as comments in the editor. - - -2013-01-08 v0.7 - - * Attempt to retry the operation on failure. - - * Add a flag to force a password prompt, so you can override - credentials from your cached shelldap.rc. - - * Make the behaviour of cd, edit and mkdir similar to cat and - delete/rm and fail if some RDN's in the argument contain spaces - without beeing quoted. - - -2011-09-06 v0.5 - - * Backout the additional objectClasses patch for mkdir: same behavior - can be acheived with 'touch', less complex to leave it as is. - - * Add a --version flag. - - * Add better verbosity when saving connection cache data. - - * Make sure the hasSubordinates attribute is defined before checking its value. - - * Fix 'ls -R' output. - - * Repair broken path behavior, remove unneeded #path_to_dn 'relative' flag. - - * Add method path_to_dn() to convert a given "path" to a DN - - * mkdir: support more objectclasses - - * make_filter: cope with filters that are already parenthesized - - * cd: flexible treatment of repeated '..', even as prefix - - * base(): make more secure, allow '' as DN - - * Added documentation for the additional short flags. - - * Accept short option names for some options - - * use symbolic LDAP error codes instead of numbers - - * Exit with a nicer error message if IO::Socket::SSL isn't installed. - - * Allow '-' on RDN name when copying - - -2011-02-17 v0.4 - - * Follow regular man page conventions. - - * Improve performance for cd/ls for containers with a large number of entries. - - -2011-02-17 v0.3 - - * Update documentation, now that multiline edits work. Minor other cleanups. - - * Combine multiple lines into a single one before displaying LDIF. - - * Append a trailing slash to entries that contain other entries. - - * Add options to support ssl key verification when connecting with TLS. - - * Display correct configuration file in error message, if a YAML parse error occurrs. - - -2008-12-04 v0.2 - - * Start using a repository. :-) - - diff -Nru shelldap-1.4.0/CONTRIBUTORS shelldap-1.5.0/CONTRIBUTORS --- shelldap-1.4.0/CONTRIBUTORS 2017-06-07 22:11:05.000000000 +0000 +++ shelldap-1.5.0/CONTRIBUTORS 1970-01-01 00:00:00.000000000 +0000 @@ -1,20 +0,0 @@ -Adam Spiers -Andr Goens -Alexander Perlis -Dennis Kaarsemaker -Emile "iMil" Heitor -Gertjan Halkes -Giacomo Tenaglia -Jonathan Rozes -Josef Wells -Landry Breuil -Lars Täuber -Michael Granger -Michael Raitza -Mike Hix -Peter Marschall -Rick H. -Rong-En Fan -Salvatore Bonaccorso -Yann Cezard - diff -Nru shelldap-1.4.0/debian/changelog shelldap-1.5.0/debian/changelog --- shelldap-1.4.0/debian/changelog 2018-12-28 22:26:25.000000000 +0000 +++ shelldap-1.5.0/debian/changelog 2019-07-06 21:16:10.000000000 +0000 @@ -1,3 +1,21 @@ +shelldap (1.5.0-2) unstable; urgency=medium + + * Upload to unstable + + -- Salvatore Bonaccorso Sat, 06 Jul 2019 23:16:10 +0200 + +shelldap (1.5.0-1) experimental; urgency=medium + + * New upstream version 1.5.0 + * Update copyright years for upstream files + * Update copyright files for debian/* packaging files + * Refresh add-editor-fallback.patch patch + * Refresh add-pager-fallback.patch patch + * Update (simplify) salsa CI pipeline includes + * Add Depends on libtie-ixhash-perl + + -- Salvatore Bonaccorso Sun, 30 Jun 2019 09:30:05 +0200 + shelldap (1.4.0-4) unstable; urgency=medium * Replace home-made GitLab CI with the standard Salsa pipeline diff -Nru shelldap-1.4.0/debian/control shelldap-1.5.0/debian/control --- shelldap-1.4.0/debian/control 2018-12-28 22:26:25.000000000 +0000 +++ shelldap-1.5.0/debian/control 2019-07-06 21:16:10.000000000 +0000 @@ -18,6 +18,7 @@ libterm-readkey-perl, libterm-readline-gnu-perl, libterm-shell-perl, + libtie-ixhash-perl, libyaml-syck-perl, ${misc:Depends}, ${perl:Depends} diff -Nru shelldap-1.4.0/debian/copyright shelldap-1.5.0/debian/copyright --- shelldap-1.4.0/debian/copyright 2018-12-28 22:26:25.000000000 +0000 +++ shelldap-1.5.0/debian/copyright 2019-07-06 21:16:10.000000000 +0000 @@ -4,11 +4,11 @@ Source: http://projects.martini.nu/shelldap Files: * -Copyright: 2006-2015, Mahlon E. Smith +Copyright: 2006-2019, Mahlon E. Smith License: BSD-3-clause Files: debian/* -Copyright: 2009-2018, Salvatore Bonaccorso +Copyright: 2009-2019, Salvatore Bonaccorso License: BSD-3-clause License: BSD-3-clause diff -Nru shelldap-1.4.0/debian/.gitlab-ci.yml shelldap-1.5.0/debian/.gitlab-ci.yml --- shelldap-1.4.0/debian/.gitlab-ci.yml 2018-12-28 22:26:25.000000000 +0000 +++ shelldap-1.5.0/debian/.gitlab-ci.yml 2019-07-06 21:16:10.000000000 +0000 @@ -1,16 +1,3 @@ -include: https://salsa.debian.org/salsa-ci-team/pipeline/raw/master/salsa-ci.yml - -build: - extends: .build-unstable - -reprotest: - extends: .test-reprotest - -lintian: - extends: .test-lintian - -autopkgtest: - extends: .test-autopkgtest - -piuparts: - extends: .test-piuparts +include: + - https://salsa.debian.org/salsa-ci-team/pipeline/raw/master/salsa-ci.yml + - https://salsa.debian.org/salsa-ci-team/pipeline/raw/master/pipeline-jobs.yml diff -Nru shelldap-1.4.0/debian/patches/add-editor-fallback.patch shelldap-1.5.0/debian/patches/add-editor-fallback.patch --- shelldap-1.4.0/debian/patches/add-editor-fallback.patch 2018-12-28 22:26:25.000000000 +0000 +++ shelldap-1.5.0/debian/patches/add-editor-fallback.patch 2019-07-06 21:16:10.000000000 +0000 @@ -7,12 +7,12 @@ --- a/shelldap +++ b/shelldap -@@ -492,7 +492,7 @@ sub init +@@ -629,7 +629,7 @@ sub init my $self = shift; $self->{'API'}->{'match_uniq'} = 0; - $self->{'editor'} = $conf->{'editor'} || $ENV{'EDITOR'} || 'vi'; + $self->{'editor'} = $conf->{'editor'} || $ENV{'EDITOR'} || '/usr/bin/editor' || 'vi'; $self->{'pager'} = $conf->{'pager'} || $ENV{'PAGER'} || 'less'; - $self->{'env'} = [ qw/ debug cacheage timeout / ]; + $self->{'env'} = [ qw/ debug cacheage timeout attributes configfile / ]; diff -Nru shelldap-1.4.0/debian/patches/add-pager-fallback.patch shelldap-1.5.0/debian/patches/add-pager-fallback.patch --- shelldap-1.4.0/debian/patches/add-pager-fallback.patch 2018-12-28 22:26:25.000000000 +0000 +++ shelldap-1.5.0/debian/patches/add-pager-fallback.patch 2019-07-06 21:16:10.000000000 +0000 @@ -7,12 +7,12 @@ --- a/shelldap +++ b/shelldap -@@ -493,7 +493,7 @@ sub init +@@ -630,7 +630,7 @@ sub init $self->{'API'}->{'match_uniq'} = 0; $self->{'editor'} = $conf->{'editor'} || $ENV{'EDITOR'} || '/usr/bin/editor' || 'vi'; - $self->{'pager'} = $conf->{'pager'} || $ENV{'PAGER'} || 'less'; + $self->{'pager'} = $conf->{'pager'} || $ENV{'PAGER'} || '/usr/bin/pager' || 'less'; - $self->{'env'} = [ qw/ debug cacheage timeout / ]; + $self->{'env'} = [ qw/ debug cacheage timeout attributes configfile / ]; # let autocomplete work with the '=' character diff -Nru shelldap-1.4.0/README.md shelldap-1.5.0/README.md --- shelldap-1.4.0/README.md 2017-06-07 22:11:05.000000000 +0000 +++ shelldap-1.5.0/README.md 1970-01-01 00:00:00.000000000 +0000 @@ -1,100 +0,0 @@ -Shelldap -======== - -A handy shell-like interface for browsing LDAP servers and editing their -content. It keeps command history, has sane autocompletes, credential caching, -site-wide and individual configs, and it's fun to say. -Shelldap! Shelldap! Shelldap! - -Witness! - - -``` -% shelldap -~ > [ tab ] -~ > -cat clear cp delete env grep id ls move passwd read search touch whoami -cd copy create edit exit help list mkdir mv pwd rm setenv vi -~ > cd ou=People -ou=People,~ > cat uid=ma[ tab ] -ou=People,~ > cat uid=mahlon - -dn: uid=mahlon,ou=People,dc=laika,dc=com -objectClass: inetOrgPerson -objectClass: organizationalPerson -objectClass: person -objectClass: top -objectClass: posixAccount -objectClass: shadowAccount -objectClass: apple-user -cn: Mahlon E. Smith -departmentNumber: Technology - IT -displayName: Mahlon -gecos: Mahlon E. Smith -gidNumber: 200 -givenName: Mahlon -homeDirectory: /home/m/mahlon -l: Ghetto -loginShell: /bin/tcsh -mail: mahlon@martini.nu -mobile: 1+8829999005747 -sn: Smith -title: Manager, Information Systems -uid: mahlon -uidNumber: 20933 -userPassword: * - -ou=People,~ > -``` - - -Downloads ---------- - -You can download the latest script [here](http://code.martini.nu/shelldap/archive/tip.zip). - -It is also available via the -[FreeBSD ports](http://www.freebsd.org/cgi/cvsweb.cgi/ports/net/shelldap/) system, OS X via -[macports](https://trac.macports.org/browser/trunk/dports/net/shelldap/Portfile), Debian/Ubuntu via [apt](https://packages.debian.org/shelldap), -NetBSD's [pkgsrc](ftp://ftp.netbsd.org/pub/pkgsrc/current/pkgsrc/sysutils/shelldap/README.html), OpenBSD [ports](http://openports.se/sysutils/shelldap), and openSUSE's [build service](http://software.opensuse.org/search?q=shelldap&baseproject=openSUSE%3AFactory&lang=en). - -Many thanks to the maintainers of these packages. - - -Installation -------------- - -Shelldap is simply a perl script. Put it into a convenient place of your choosing. - -Shelldap requires the following perl modules to function properly: - -* YAML::Syck -* Term::Shell -* Digest::MD5 -* Net::LDAP -* Algorithm::Diff - -Additional functionality is supported with the presence of: - -* IO::Socket::SSL -* Authen::SASL -* Term::ReadLine::Gnu - - -Development ------------ - - -You can also check out the source via [Mercurial](http://mercurial.selenic.com/wiki/) from the following uri: - - % hg clone http://code.martini.nu/shelldap/ - -or via [BitBucket](http://bitbucket.org/) at: - - % hg clone https://bitbucket.org/mahlon/shelldap - - -Documentation -------------- - -Once installed, Shelldap is fully documented via perldoc, and has a built-in `help` command as well. \ No newline at end of file diff -Nru shelldap-1.4.0/shelldap shelldap-1.5.0/shelldap --- shelldap-1.4.0/shelldap 2017-06-07 22:10:54.000000000 +0000 +++ shelldap-1.5.0/shelldap 2019-06-25 16:53:42.000000000 +0000 @@ -1,7 +1,7 @@ #!/usr/bin/env perl # vim: set nosta noet ts=4 sw=4: # -# Copyright (c) 2006-2015, Mahlon E. Smith +# Copyright (c) 2006-2019, Mahlon E. Smith # All rights reserved. # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: @@ -109,6 +109,7 @@ --server ldaps://ldap.example.net -H ldaps://ldap.example.net + -h hostname_or_IP =back @@ -145,7 +146,7 @@ sizelimit errors, you'll likely need server configuration to raise the limits for paginated results. - --paginate 100 + --paginate 100 =back @@ -165,7 +166,7 @@ A space separated list of SASL mechanisms. Requires the Authen::SASL module. - --sasl "PLAIN CRAM-MD5 GSSAPI" + --sasl 'PLAIN CRAM-MD5 GSSAPI' =back @@ -238,63 +239,112 @@ =head1 SHELL COMMANDS -=over 4 +=head2 alias + +Define or display aliases. -=item B< cat> +Without arguments, `alias' prints the list of aliases in the reusable +form `alias NAME=VALUE' on standard output. -Display an LDIF dump of an entry. Globbing is supported. Specify -either the full dn, or an rdn. For most commands, rdns are local to the -current search base. ('cwd', as translated to shell speak.) You may additionally -add a list of attributes to display. Use '+' for server side attributes. +Otherwise, an alias is defined for each NAME whose VALUE is given. +A trailing space in VALUE causes the next word to be checked for +alias substitution when the alias is expanded. + + alias + alias ll=ls -al + alias ll + alias show=cat + alias cmd1=command 'arg with spaces' + alias cmd2='command ' + alias cmd2=command 'with_arg ' + +=head2 cat + +Print contents of LDAP entry to STDOUT in LDIF format. + +Globbing is supported. Specify either full DN, or a RDN. +RDNs are local to the current search base ('cwd' in shell terms). If RDN is '.' or missing, +it defaults to the current search base. +You may additionally add a list of attributes to display (e.g. use '+' for operational +attributes or provide a specific space-separated list). Default list of attributes is ['*'] +and this default list can be changed using 'attributes' config key or --attributes cmdline +option. cat uid=mahlon cat ou=* cat uid=mahlon,ou=People,dc=example,o=company cat uid=mahlon + userPassword -=item B< less> +=head2 configfile + +Load or save config file. + +If no config file is specified as argument to 'load', the default search list is: + + $HOME/.shelldap.rc + /usr/local/etc/shelldap.conf + /etc/shelldap.conf + +If no config file is specified as argument to 'save', the default path is +$HOME/.shelldap.rc. -Like cat, but uses the configured pager to display output. + configfile load + configfile load /path/to/config + configfile save + configfile save /path/to/config -=item B< cd> +=head2 less -Change directory. Translated to LDAP, this changes the current basedn. +Like 'cat', but use configured pager to paginate output. + +=head2 cd + +Change the working directory (LDAP search base). + +Translated to LDAP, this changes the current basedn. All commands after a 'cd' operate within the new basedn. - cd change to 'home' basedn - cd ~ change to the binddn, or basedn if anonymously bound + cd change to 'home' (binddn if any, or basedn) + cd ~ change to 'home' (binddn if any, or basedn) cd - change to previous node cd ou=People change to explicit path below current node cd .. change to parent node cd ../../ou=Groups change to node ou=Groups, which is a sibling to the current node's grandparent -Since LDAP doesn't actually limit what can be a container object, you -can actually cd into any entry. Many commands then work on '.', meaning +Since LDAP doesn't limit what can be a container object, you can 'cd' into +any entry. Many commands then work on '.' or default to '.', meaning "wherever I currently am." cd uid=mahlon cat . + cat + +=head2 clear + +Clear the terminal screen. -=item B +Clears screen similar to 'clear' or Ctrl+l on the shell command line. -Clear the screen. +Ctrl+l alias is also supported. -=item B +=head2 copy -Copy an entry to a different dn path. All copies are relative to the -current basedn, unless a full dn is specified. All attributes are -copied, then an LDAP moddn() is performed. +Copy an entry. + +All copies are relative to the +current basedn unless a full DN is specified. All attributes are +copied and then an LDAP moddn() is performed. copy uid=mahlon uid=bob copy uid=mahlon ou=Others,dc=example,o=company copy uid=mahlon,ou=People,dc=example,o=company uid=mahlon,ou=Others,dc=example,o=company -aliased to: cp +=head2 create -=item B +Create an entry. -Create an entry from scratch. Arguments are space separated objectClass +Arguments are space separated objectClass names. Possible objectClasses are derived automatically from the server, and will tab-complete. @@ -305,11 +355,11 @@ create top person organizationalPerson inetOrgPerson posixAccount -aliased to: touch +=head2 delete -=item B +Remove an entry. -Remove an entry from the directory. Globbing is supported. +Globbing is supported. All deletes are sanity-prompted. The -v flag prints the entries out for review before delete. @@ -317,37 +367,38 @@ delete uid=ma* rm -v uid=mahlon,ou=People,dc=example,o=company l=office -aliased to: rm +=head2 edit -=item B +Edit an entry in an external editor. -Edit an entry in an external editor. After the editor exits, the +After the editor exits, the resulting LDIF is sanity checked, and changes are written to the LDAP directory. edit uid=mahlon -aliased to: vi +=head2 env + +Print values of configurable shelldap variables. -=item B +This is a subset of all variables configurable via shelldap config +file and/or its command line options. - Show values for various runtime variables. +=head2 grep -=item B +Search using LDAP filters and return matching DN results. -Search for arbitrary LDAP filters, and return matching dn results. The search string must be a valid LDAP filter. grep uid=mahlon grep uid=mahlon ou=People grep -r (&(uid=mahlon)(objectClass=*)) - aliased to: search +=head2 inspect -=item B +View schema and flags for an entry or objectClass. -View schema information about a given entry, or a list of arbitrary -objectClasses, along with the most common flags for the objectClass +It also includes the most common flags for the objectClass attributes. inspect uid=mahlon @@ -363,11 +414,11 @@ If you ask for the special "_schema" object, the raw server schema is dumped to screen. -=item B +=head2 list -List entries for the current basedn. Globbing is supported. +List directory contents. -aliased to: ls +Globbing is supported. ls -l ls -lR uid=mahlon @@ -385,45 +436,64 @@ posixGroup: gidNumber ipHost: ipHostNumber -=item B +=head2 mkdir + +Create a new 'organizationalUnit' LDAP entry. -Creates a new 'organizationalUnit' entry. + mkdir containername + mkdir ou=whatever - mkdir containername - mkdir ou=whatever +=head2 move -=item B +Move (rename) entry. -Move an entry to a different dn path. Usage is identical to B. +Usage is identical to B. -aliased to: mv +=head2 passwd -=item B +Change user password. If supported server side, change the password for a specified entry. The entry must have a 'userPassword' attribute. passwd uid=mahlon -=item B< pwd> +=head2 pwd -Print the 'working directory' - aka, the current ldap basedn. +Print name of current/working LDAP search base. -=item B +=head2 setenv -Modify various runtime variables normally set from the command line. +Change or define shelldap variable. setenv debug 1 export debug=1 -=item B +=head2 unalias + +Remove each NAME from the list of defined aliases. + + alias ll=ls -al + alias + unalias ll + unalias ll ls + alias + +=head2 unsetenv + +Remove each NAME from the list of defined shelldap variables. + + unset debug + unset configfile + unset myvar1 myvar2 myvar3 + +=head2 whoami + +Print current bind DN. Show current auth credentials. Unless you specified a binddn, this will just show an anonymous bind. -aliased to: id - -=back =head1 TODO @@ -434,6 +504,15 @@ For now, it only makes sense to connect to a master if you plan on doing any writes. +Add ability for command definitions in cmd_map to contain default +arguments passed to functions. + +Then add ability to define custom commands/aliases in config file. + +Split 'inspect' into separate commands, one working on files/entries, +and one working on objectclasses. This way, autocompleter for both commands +will be reasonable, unlike now. + =head1 BUGS / LIMITATIONS There is no support for editing binary data. If you need to edit base64 @@ -448,9 +527,9 @@ package LDAP::Shell; use strict; use warnings; -use Term::ReadKey; -use Term::Shell; -use Digest::MD5; +use Term::ReadKey qw//; +use Term::Shell qw//; +use Digest::MD5 qw//; use Net::LDAP qw/ LDAP_SUCCESS LDAP_SERVER_DOWN @@ -465,22 +544,80 @@ LDAP_CONNECT_ERROR LDAP_CONTROL_PAGED /; use Net::LDAP::Util qw/ canonical_dn ldap_explode_dn /; -use Net::LDAP::LDIF; -use Net::LDAP::Extension::SetPassword; -use Net::LDAP::Control::Paged; -use Data::Dumper; -use File::Temp; -use Algorithm::Diff; +use Net::LDAP::LDIF qw//; +use Net::LDAP::Extension::SetPassword qw//; +use Net::LDAP::Control::Paged qw//; +use Data::Dumper qw//; +use File::Temp qw//; +use Algorithm::Diff qw//; use Carp 'confess'; +use POSIX qw//; +use Tie::IxHash qw//; +use Fatal qw/open/; use base 'Term::Shell'; - my $conf = $main::conf; # make 'die' backtrace in debug mode $SIG{'__DIE__'} = \&Carp::confess if $conf->{'debug'}; + +######################################################################## +### Term::Shell Fixes +######################################################################## + +# Term::Shell function add_handlers() is implemented in an incorrect way. +# We reimplement the method here to fix its problems. + +# In add_handlers, we split searching for aliases in a separate loop, +# because otherwise not all aliases are registered before we look them +# up. +sub add_handlers +{ + my $o = shift; + for my $hnd (@_) + { + next unless $hnd =~ /^(run|help|smry|comp|catch|alias)_/o; + my $t = $1; + my $a = substr( $hnd, length($t) + 1 ); + + # Add on the prefix and suffix if the command is defined + if ( length $a ) + { + substr( $a, 0, 0 ) = $o->cmd_prefix; + $a .= $o->cmd_suffix; + } + $o->{handlers}{$a}{$t} = $hnd; + } + for my $hnd (@_) + { + next unless $hnd =~ /^(run|help|smry|comp|catch|alias)_/o; + my $t = $1; + my $a = substr( $hnd, length($t) + 1 ); + + # Add on the prefix and suffix if the command is defined + if ( length $a ) + { + substr( $a, 0, 0 ) = $o->cmd_prefix; + $a .= $o->cmd_suffix; + } + + if ( $o->has_aliases($a) ) + { + my @a = $o->get_aliases($a); + for my $alias (@a) + { + substr( $alias, 0, 0 ) = $o->cmd_prefix; + $alias .= $o->cmd_suffix; + $o->{handlers}{$alias}{$t} = $hnd; + } + } + } +} + + + ######################################################################## ### U T I L I T Y F U N C T I O N S ######################################################################## @@ -494,7 +631,7 @@ $self->{'editor'} = $conf->{'editor'} || $ENV{'EDITOR'} || 'vi'; $self->{'pager'} = $conf->{'pager'} || $ENV{'PAGER'} || 'less'; - $self->{'env'} = [ qw/ debug cacheage timeout / ]; + $self->{'env'} = [ qw/ debug cacheage timeout attributes configfile / ]; # let autocomplete work with the '=' character my $term = $self->term(); @@ -522,7 +659,7 @@ my @versions = $self->{'root_dse'}->get_value('supportedLDAPVersion'); print "Connected to $conf->{'server'}\n"; print "Supported LDAP version: ", ( join ', ', @versions ), "\n"; - print "Cipher in use: ", $self->ldap()->cipher(), "\n"; + print "Cipher in use: ", $self->ldap()->cipher() || '', "\n"; } # check for the pagination extension on the server early, and bail @@ -572,19 +709,25 @@ You may try connecting insecurely, or install the module and try again.\n} if $@; } - # Prompt for a password after disabling local echo. - # - if ( ($conf->{'binddn'} && ! $conf->{'bindpass'}) || $conf->{'promptpass'} ) { - print "Bind password: "; - Term::ReadKey::ReadMode 2; - chomp( $conf->{'bindpass'} = ); - Term::ReadKey::ReadMode 0; - print "\n"; + if ($conf->{'binddn'}) { + if($conf->{'promptpass'}) { + # Prompt for a password after disabling local echo. + # + print "Bind password: "; + Term::ReadKey::ReadMode 2; + chomp( $conf->{'bindpass'} = ); + Term::ReadKey::ReadMode 0; + print "\n"; + } elsif($conf->{'pass'}) { + $conf->{'bindpass'} = $conf->{'pass'} + } elsif($conf->{'passfile'}) { + chomp( $conf->{'bindpass'} = slurp($conf->{'passfile'})); + } } # make the connection - my $ldap = Net::LDAP->new( $conf->{'server'} ) - or die "Unable to connect to LDAP server '$conf->{'server'}': $!\n"; + my $ldap = Net::LDAP->new( $conf->{'server'}, $conf->{port} ? ('port' => $conf->{port}) : ()) + or die "Unable to connect to LDAP server '$conf->{'server'}' port ${\( $conf->{port} || 'default' )}: $!\n"; # secure connection options # @@ -667,8 +810,7 @@ print "Would you like to cache your connection information? [Yn]: "; chomp( my $response = ); unless ( $response =~ /^n/i ) { - YAML::Syck::DumpFile( $conf->{'configfile'}, $conf ); - chmod 0600, $conf->{'configfile'}; + main::save_config($conf->{configfile}); print "Connection info cached to $conf->{'configfile'}.\n"; } } @@ -686,19 +828,21 @@ my $self = shift; my $use_temp = shift; + my $raw = qr/(^jpegPhoto|;binary)/; + # create tmpfile and link ldif object with it # if ( $use_temp ) { my ( undef, $fname ) = File::Temp::tempfile( 'shelldap_XXXXXXXX', SUFFIX => '.ldif', TMPDIR => 1, UNLINK => 1 ); - $self->{'ldif'} = Net::LDAP::LDIF->new( $fname, 'w', sort => 1, wrap => 0 ); + $self->{'ldif'} = Net::LDAP::LDIF->new( $fname, 'w', sort => 1, wrap => 0, raw => $raw ); $self->{'ldif_fname'} = $fname; } # ldif -> stdout # else { - $self->{'ldif'} = Net::LDAP::LDIF->new( \*STDOUT, 'w', sort => 1, wrap => $self->wrapsize ); + $self->{'ldif'} = Net::LDAP::LDIF->new( \*STDOUT, 'w', sort => 1, wrap => $self->wrapsize, raw => $raw ); } return $self->{'ldif'}; @@ -730,7 +874,7 @@ my $file = shift or return; my $md5 = Digest::MD5->new(); - open F, $file or die "Unable to read file: $!\n"; + open F, $file; my $hash = $md5->addfile( *F )->hexdigest(); close F; @@ -843,8 +987,7 @@ my $use_pager = shift; unless ( $dn ) { - print "No dn provided.\n"; - return; + $dn = '.' } # support '.' @@ -1194,85 +1337,43 @@ ### Autocomplete values: Returns cached children entries. ### -sub autocomplete_cwd +sub autocomplete_from_cwd { my $self = shift; - return @{ $self->{'cwd_entries'} }; + my $word = quotemeta shift; + return grep {/^$word/} @{ $self->{'cwd_entries'} }; } ### Autocomplete values: Returns previously set shelldap environment values. ### -sub comp_setenv +sub autocomplete_from_env { my $self = shift; - return @{ $self->{'env'} }; + my $word = quotemeta shift; + return grep {/^$word/} @{ $self->{'env'} }; } ### Autocomplete values: Returns all objectClasses as defined ### by the LDAP server. ### -sub comp_create +sub autocomplete_from_objectclasses { my $self = shift; - return @{ $self->{'objectclasses'} }; + my $word = quotemeta shift; + return grep {/^$word/} @{ $self->{'objectclasses'} }; } ### Autocomplete values: Returns all objectClasses as defined ### by the LDAP server, along with current children DNs. ### -sub comp_inspect +sub autocomplete_from_objectclasses_and_cwd { my $self = shift; - return ('_schema', @{ $self->{'objectclasses'} }, @{ $self->{'cwd_entries'} }); -} - - -### Inject various autocomplete and alias routines into the symbol table. -### -{ - no warnings; - no strict 'refs'; - - # command, alias - my %cmd_map = ( - whoami => 'id', - list => 'ls', - grep => 'search', - edit => 'vi', - delete => 'rm', - copy => 'cp', - cat => 'read', - move => 'mv', - less => undef, - cd => undef, - passwd => undef - ); - - # setup autocompletes - foreach ( %cmd_map ) { - next unless $_; - my $sub = "comp_$_"; - *$sub = \&autocomplete_cwd; - } - *comp_touch = \&comp_create; - *comp_export = \&comp_setenv; - - # setup alias subs - # - # Term::Shell has an alias_* feature, but - # it seems to work about 90% of the time. - # that last 10% is something of a mystery. - # - $cmd_map{'create'} = 'touch'; - foreach my $cmd ( keys %cmd_map ) { - next unless defined $cmd_map{$cmd}; - my $alias_sub = 'run_' . $cmd_map{$cmd}; - my $real_sub = 'run_' . $cmd; - *$alias_sub = \&$real_sub; - } + my $word = quotemeta shift; + return grep {/^$word/} ('_schema', @{ $self->{'objectclasses'} }, @{ $self->{'cwd_entries'} }); } @@ -1408,9 +1509,51 @@ ### S H E L L M E T H O D S ######################################################################## -### Don't die on a newline, just no-op. -### -sub run_ { return; } +# alias_or_command => [ real_command_name, completion_function ] +# +tie my %cmd_map, 'Tie::IxHash'; +%cmd_map = ( + # Real commands: + 'alias' => [ undef ], + 'configfile'=> [ undef ], + 'whoami' => [ undef ], + 'pwd' => [ undef ], + 'list' => [ undef, 'autocomplete_from_cwd' ], + 'grep' => [ undef, 'autocomplete_from_cwd' ], + 'edit' => [ undef, 'autocomplete_from_cwd' ], + 'delete' => [ undef, 'autocomplete_from_cwd' ], + 'copy' => [ undef, 'autocomplete_from_cwd' ], + 'cat' => [ undef, 'autocomplete_from_cwd' ], + 'move' => [ undef, 'autocomplete_from_cwd' ], + 'less' => [ undef, 'autocomplete_from_cwd' ], + 'cd' => [ undef, 'autocomplete_from_cwd' ], + 'create' => [ undef, 'autocomplete_from_objectclasses' ], + 'setenv' => [ undef, 'autocomplete_from_env' ], + 'passwd' => [ undef ], + 'clear' => [ undef ], + 'env' => [ undef, 'autocomplete_from_env' ], + #'help' => [ undef ], + 'mkdir' => [ undef ], + 'inspect' => [ undef, 'autocomplete_from_objectclasses_and_cwd' ], + 'unalias' => [ undef ], + 'unsetenv'=> [ undef ], + + # Aliases: + 'id' => [ 'whoami' ], + 'ls' => [ 'list' ], + 'search' => [ 'grep' ], + 'vi' => [ 'edit' ], + 'rm' => [ 'delete' ], + 'cp' => [ 'copy' ], + 'read' => [ 'read' ], + 'mv' => [ 'move' ], + 'touch' => [ 'create' ], + 'export' => [ 'setenv' ], + 'set' => [ 'setenv' ], + '?' => [ 'help' ], + 'man' => [ 'help' ], + 'unset' => [ 'unsetenv'], +); ### Term::Shell hook. @@ -1429,18 +1572,137 @@ } +### Display or define aliases. +### +sub run_alias +{ + my $self = shift; + my $cmd_alias = shift; + + # If $cmd_alias is empty, user requested printing of known aliases + unless($cmd_alias) { + while(my($alias,$cmd_args) = each %{$conf->{alias}}) { + print "alias $alias=${\( join ' ', map { $_=~ /\s/ ? \"'$_'\" : $_} @{$cmd_args})}\n"; + } + return + + # If there is argument but without = or space, user wanted to print specific alias + } elsif($cmd_alias !~ /[=\s]/ and !@_) { + my $alias = $cmd_alias; + my $cmd_args = $conf->{alias}{$alias}; + unless( $cmd_args) { + print "alias: $alias: not found\n"; + } else { + print "alias $alias=${\( join ' ', map { $_=~ /\s/ ? \"'$_'\" : $_} @{$cmd_args})}\n"; + } + return + + # There is argument with =, so the line is a new alias definition + } else { + my($alias, $alias2, $command) = ($cmd_alias =~ m/^([a-zA-Z0-9_-]+)$|^(\S+?)[\=\s]+(.+)$/); + $alias = $alias2 if $alias2; + unless( $alias) { + print "Invalid syntax.\n"; + return + } + $command = $cmd_map{$command}[0] if $command and $cmd_map{$command} and $cmd_map{$command}[0]; + $conf->{alias}{$alias} = [ $command ? $command : (), @_ ]; + } +} + +# Remove alias +sub run_unalias +{ + my $self = shift; + for my $alias(@_) { + unless( $conf->{alias}{$alias}) { + print "alias: $alias: not found\n"; + } else { + delete $conf->{alias}{$alias}; + } + } + return +} + +# Run aliased command when alias is entered +sub catch_run { + my $self = shift; + my @cmdline; + + unless( $conf->{alias}{$_[0]}) { + print $self->msg_unknown_cmd($_[0]); + return + } + + my $done = 0; + while(my $arg = $_[0]) { + my @alias = @{$conf->{alias}{$arg} or last}; + + if($alias[-1] !~ s/\s+$//) { + $done++ + } + push @cmdline, @alias; + shift; + last if $done; + } + + push @cmdline, @_; + + $self->run(@cmdline); +} + + ### Display an entry as LDIF to the terminal. ### sub run_cat { my $self = shift; my $dn = shift; - my @attrs = (@_) ? @_ : ('*'); + my @attrs = (@_) ? @_ : @{$conf->{'attributes'}}; $self->display( $dn, \@attrs, 0 ); } +### Load or save config +### +sub run_configfile +{ + my $self = shift; + my $action = shift; + my $filepath = shift; + + unless ( $action) { + if( $conf->{configfile} ) { + print qq{Current config file is '$conf->{configfile}'.\n} + } else { + print qq{Current config file is unset.\nDefault search locations:\n ${\( join "\n ", main::default_configfiles() )}\n} + } + return + } + + unless( $action =~ /^(?:load|save)$/) { + print "Invalid action specified; use 'load' or 'save'.\n"; + return; + } + + # This too can result in $filepath being undef. In that case the defaults + # from load_config() / save_config() will apply. + $filepath ||= $conf->{configfile}; + + if( $action eq 'load') { + my($filepath, $more_conf) = main::load_config($filepath); + if( $more_conf) { + while ( my ($k, $v) = each %{$more_conf} ) { $conf->{ $k } = $v } + } + print "Config file '$filepath' loaded.\n"; + } elsif( $action eq 'save') { + $filepath = main::save_config($filepath); + print "Config file '$filepath' saved.\n"; + } +} + + ### Display an entry as LDIF to the terminal with external pagination. ### sub run_less @@ -1773,7 +2035,7 @@ } # load it into an array for potential comparison - open LDIF, "$self->{'ldif_fname'}" or return; + open LDIF, "$self->{'ldif_fname'}"; my @orig_ldif = ; close LDIF; @@ -1823,7 +2085,7 @@ # load changes into a new array for comparison # - open LDIF, "$self->{'ldif_fname'}" or return; + open LDIF, "$self->{'ldif_fname'}"; my @new_ldif = ; close LDIF; @@ -1850,11 +2112,8 @@ { my $self = shift; - foreach ( sort @{ $self->{'env'} } ) { - print "$_: "; - print $conf->{$_} ? $conf->{$_} : 0; - print "\n" - } + print YAML::Syck::Dump( { map { $conf->{$_} ? ($_, $conf->{$_}) : ()} sort @{ $self->{'env'}} } ); + print "\n" } @@ -1874,6 +2133,19 @@ } +### Alter settings. +### +sub run_unsetenv +{ + my $self = shift; + + for(@_) { + delete $conf->{$_} + } + return; +} + + ### Search across the directory and display matching entries. ### sub run_grep @@ -1921,18 +2193,6 @@ } -### Override internal help function with pod2usage output. -### -sub run_help -{ - return Pod::Usage::pod2usage( - -exitval => 'NOEXIT', - -verbose => 99, - -sections => 'SHELL COMMANDS' - ); -} - - ### Generate and display a list of LDAP entries, relative to the current ### location the command was run from. ### @@ -1944,7 +2204,7 @@ my $filter; # flag booleans - my ( $recurse, $long ); + my ( $recurse, $long, $all ); # parse arguments: [