1
0
Fork 0
mirror of git://git.code.sf.net/p/zsh/code synced 2025-01-19 11:31:26 +01:00

51455, 51461: new completion for the OpenLDAP client tools including a helper function for LDAP search filters

This commit is contained in:
Oliver Kiddle 2023-02-18 01:10:10 +01:00
parent 6f4aa1d949
commit c83ce203f5
5 changed files with 349 additions and 2 deletions

View file

@ -1,3 +1,10 @@
2023-02-19 Oliver Kiddle <opk@zsh.org>
* 51455, 51461: Completion/Unix/Type/_ldap_attributes
Completion/BSD/Command/_ldap, Completion/Unix/Command/_openldap,
Completion/Unix/Type/_ldap_filters: new completion for the OpenLDAP
client tools including a helper function for LDAP search filters
2023-02-17 Oliver Kiddle <opk@zsh.org>
* 51447: Src/Zle/zle_keymap.c: silence compiler maybe-uninitialized

View file

@ -80,8 +80,8 @@ else
'-x[use simple authentication]' \
'-Z[use StartTLS]' \
'-z+[specify maximum number of results or 0 for no limit]:size limit [0]:' \
'::filter:' \
'*:attribute:'
'1: :_ldap_filters' \
'*: :_ldap_attributes'
;;
esac
fi

View file

@ -0,0 +1,222 @@
#compdef ldapadd ldapcompare ldapdelete ldapexop ldapmodify ldapmodrdn ldappasswd ldapsearch ldapurl ldapwhoami
local curcontext="$curcontext" nm="$compstate[nmatches]"
local -a args auth state line expl
args=( '*-e[general extensions]:extension:->general-extensions' )
case $service in
ldapadd|ldapcompare|ldapdelete|ldapexop|ldapmodify|ldapmodrdn|ldappasswd|ldapsearch|ldapwhoami)
if (( $words[(I)-[^Z]#Z[^Z]#] )); then
args+=( '*-Z[require success for start TLS request]' )
elif (( ! $words[(I)-[^Z]#Z] )); then
args+=( '-Z[start TLS request]' )
fi
args+=(
'!(-)-VV' '-V[display version information]'
'*-d+[set LDAP debugging level]:level:((1\:trace 2\:packets 4\:args 8\:conns 10\:ber 2048\:parse -1\:all))'
"-n[show what would be done but don't actually do it]"
'-v[verbose output]'
"-N[don't use reverse DNS to canonicalize SASL host name]"
'*-o+[specify any ldap.conf options]: : _values option
"ldif_wrap[specify width]\:width"
"nettimeout[specify timeout]\:timeout (seconds)"'
)
auth=(
'-D[specify bind DN]:binddn'
'-H[specify LDAP URIs]:uri'
'-P[specify protocol version]:version [3]:(2 3)'
+ simple
'(sasl)-x[use simple authentication]'
'(sasl -W -y)-w+[specify bind password]:bind password'
'(sasl -w -y)-W[prompt for bind password]'
'(sasl -w -W)-y+[read password from file]:file:_files'
+ sasl
'(simple)-O+[specify SASL security properties]: : _values -s , property
none noplain noactive nodict noanonymous forwardsec passcred
minssf\:factor maxssf\:factor maxbufsize\:factor'
'(simple)-X+[specify SASL authorization identity]:authzid:->authzids'
'(simple)-Y+[specify SASL mechanism]:mechanism:compadd -M "m:{a-zA-Z}={A-Za-z}" EXTERNAL GSSAPI' # iana has a full list but cyrus support seems limited
'(simple)-R+[specify SASL realm]:realm'
'(simple)-U+[specify SASL authentication identity]:authcid'
'(simple)-I[use SASL Interactive mode]'
'(simple)-Q[use SASL Quiet mode]'
)
;|
ldapadd|ldapcompare|ldapdelete|ldapmodify|ldapmodrdn|ldapsearch)
if (( $words[(I)-[^M]#M[^M]#] )); then
args+=( '*-M[enable Manage DSA IT control critical]' )
elif (( ! $words[(I)-[^M]#M] )); then
args+=( '-M[enable Manage DSA IT control]' )
fi
;|
ldapadd|ldapdelete|ldapmodify|ldapmodrdn|ldapsearch)
# ldapexop documents but doesn't implement this
args+=( '(1 2 *)-f+[read operations from file]:file:_files' )
;|
ldapadd|ldapdelete|ldapmodify|ldapmodrdn|ldapsearch)
args+=( "-c[continuous operation mode (don't stop on errors)]" )
;|
ldapdelete|ldapsearch)
args+=( '-z+[specify size limit]:size limit (entries)' )
;|
ldapadd|ldapmodify)
args+=(
'-S+[write records that are skipped due to an error to file]:file:_files'
'*-E+[modify extensions]:extension:->modify-extensions'
)
;|
ldapurl|ldapsearch)
args+=(
'(decompose)-s+[specify search scope]:search scope [sub]:(base one sub children)'
)
;|
ldapdelete|ldapmodrdn|ldapurl|ldapwhoami) args+=( '!*-E+:extension' ) ;|
ldapadd) args+=( '!-a' ) ;;
ldapmodify) args+=( '-a[add new entries]' ) ;;
ldapcompare)
args+=(
'-z[quiet mode - no output aside return status]'
'*-E+[compare extensions]:extension:->compare-extensions'
)
;;
ldapdelete)
args+=(
'-r[do a recursive delete]'
'*: :_guard "^-*" "distinguished name"'
)
;;
ldapexop) args+=( '*:: :->extended-operations' ) ;;
ldapmodrdn)
args+=(
'-r[remove old RDN values from the entry]'
'-s[specify new superior entry to move target to]:entry'
'1:distinguished name'
'2:relative distinguished name'
)
;;
ldappasswd)
args+=(
'(-a -t)-A[prompt for old password]'
'(-A -t)-a+[specify old password]:password'
'(-A -a)-t+[read old password from file]:file:_files'
'(-s -T)-S[prompt for new password]'
'(-S -T)-s+[specify new password]:password'
'(-S -s)-T+[read new password from file]:file:_files'
)
;;
ldapsearch)
if (( $words[(I)-[^L]#L[^L]#L[^L]#] )); then
args+=( '*-L[LDIF format without comments and version]' )
elif (( $words[(I)-[^L]#L[^L]#] )); then
args+=( '*-L[LDIF format without comments]' )
elif ! (( $words[(I)-[^L]#L[^L]#L[^L]#L] )); then
args+=( '-L[LDIFv1 format]' )
else
args+=( '!*-L' )
fi
if (( $words[(I)-[^t]#t[^t]#] )); then
args+=( '*-t[write all retrieved values to files in temporary directory]' )
elif (( ! $words[(I)-[^t]#t] )); then
args+=( '-t[write binary values to files in temporary directory]' )
fi
args+=(
'-a+[specify how aliases dereferencing is done]:deref [never]:(never always search find)'
'-A[retrieve attributes only (no values)]'
'-b+[specify base dn for search]:basedn'
'*-E+[search extensions]:extension:->search-extensions'
'-F+[specify URL prefix for temporary files]:prefix [file:///tmp//]'
'-l+[specify time limit for search]:time limit (seconds)'
'-S+[sort results by specified attribute]:attribute:_ldap_attributes'
'-T[write files to specified directory]:path [/tmp]:_directories'
'-u[include User Friendly entry names in the output]'
'1: :_ldap_filters'
'2: : _alternative
"attributes:attribute:_ldap_attributes"
"attributes:attribute:((1.1\:no\ attributes \*\:all\ user\ attributes \+\:all\ operational\ attributes))"'
'*:attribute:_ldap_attributes -F line'
)
;;
ldapurl)
args+=(
- compose
'-a+[set a list of attribute selectors]:attribute selectors (comma separated)'
'-b+[set the searchbase]:search base'
'-f+[set the URL filter]:filter:_ldap_filters'
'-h+[set the host]:host:_hosts'
'-p+[set the tcp port]:port:(389 636)'
'-S+[set the URL scheme]:scheme:(ldap ldaps)'
- decompose
'(-s)-H+[specify URI to be exploded]:uri'
)
;;
esac
_arguments -C -S -s $args $auth
case $state in
extended-operations)
case $CURRENT:$words[1] in
1:*)
if compset -P '*::'; then
_message -e data 'base64 data'
elif compset -P '*:'; then
_message -e data data
else
_alternative \
'oids::_guard "(<->(|.))#" oid' \
'operations:operation:(whoami cancel refresh)'
fi
;;
2:cancel) _message -e ids 'cancel id' ;;
2:refresh) _message -e names 'distinguished name' ;;
3:refresh) _message -e times 'ttl' ;;
*) _message 'no more arguments' ;;
esac
;;
*-extensions)
if ! compset -P \!; then
_description criticality expl critical
compadd -S "" "$expl[@]" \!
fi
;|
modify-extensions) _values extension 'txn:txn:(abort commit)' ;;
compare-extensions) _values extension dontUseCopy ;;
search-extensions)
_values extension \
'mv[matched values filter]:filter:_ldap_filters' \
'pr[paged results/prompt]:size[/prompt|noprompt]' \
'sss[server side sorting]: :_sequence -s / _ldap_attributes' \
'subentries: :(true false)' \
'sync:sync[/cookie][/slimit]:((ro\:refreshOnly rp\:refreshAndPersist))' \
'vlv[virtual list view]:before/after(/offset/count|\:value' \
'deref:derefAttr:_sequence _ldap_attributes' \
dontUseCopy domainScope
;;
general-extensions)
_values extension \
'assert:filter:_ldap_filters' \
'authzid:authzid:->authzids' \
{post,pre}'read: :_sequence _ldap_attributes' \
'sessiontracking:username:_users' \
'chaining:behavior:(chainingPreferred chainingRequired referralsPreferred referralsRequired)' \
bauthzid manageDSAit noop ppolicy relax abandon cancel ignore
;&
authzids)
if [[ $state != authzids ]]; then
: # fall-through from above without the authzids state
elif compset -P 'u:'; then
_description users expl authzid
_users "$expl[@]"
elif compset -P 'dn:'; then
_message -e ids 'distinguished name'
else
_description prefixes expl prefix
compadd -S: "$expl[@]" u dn
fi
;;
esac
[[ nm -ne "$compstate[nmatches]" ]]

View file

@ -0,0 +1,27 @@
#autoload
local -a expl attrs
# These come from dumping attributes from basic installations of both openldap
# and FreeIPA and combining results. It is possible to have custom additions so
# a definitive list is not possible Hence the use of -x with compadd.
#
attrs=(
associatedDomain authenticationMethod automountInformation automountKey
automountMapName bindTimeLimit cACertificate;binary cn dc defaultSearchBase
defaultServerList description displayName dn followReferrals gecos gidNumber
givenName homeDirectory info initials ipaCertIssuerSerial ipaCertSubject
ipaConfigString ipaKeyExtUsage ipaKeyTrust ipaNTSecurityIdentifier
ipaPublicKey ipaUniqueID ipHostNumber loginShell mail member memberUid
mepManagedBy nisDomain nisNetgroupTriple o objectClass objectClassMap ou
pwdAllowUserChange pwdAttribute pwdCheckQuality pwdExpireWarning
pwdFailureCountInterval pwdGraceAuthNLimit pwdInHistory pwdLockout
pwdLockoutDuration pwdMaxAge pwdMaxFailure pwdMinAge pwdMinLength
pwdMustChange pwdSafeModify searchTimeLimit serviceSearchDescriptor sn
telephoneNumber uid uidNumber userCertificate;binary userPKCS12
userSMIMECertificate
)
_description ldap-attributes expl "ldap attribute"
compadd "${@:/-X/-x}" "${expl[@]:/-X/-x}" \
-M 'm:{a-zA-Z}={A-Za-z} r:[^A-Z]||[A-Z]=* r:|=*' -a attrs

View file

@ -0,0 +1,91 @@
#autoload
# LDAP search filters conforming to RFC4515
local -a expl excl optype disp end pre
local -i nest=0
local open='(' close=')' andop='&' orop='|'
[[ -prefix - ]] && return 1
local -a matchingrules=( # From RFC4517
bitStringMatch booleanMatch caseExactIA5Match
caseExactMatch caseExactOrderingMatch caseExactSubstringsMatch
caseIgnoreIA5Match caseIgnoreIA5SubstringsMatch caseIgnoreListMatch
caseIgnoreListSubstringsMatch caseIgnoreMatch caseIgnoreOrderingMatch
caseIgnoreSubstringsMatch directoryStringFirstComponentMatch
distinguishedNameMatch generalizedTimeMatch generalizedTimeOrderingMatch
integerFirstComponentMatch integerMatch integerOrderingMatch keywordMatch
numericStringMatch numericStringOrderingMatch numericStringSubstringsMatch
objectIdentifierFirstComponentMatch objectIdentifierMatch octetStringMatch
octetStringOrderingMatch telephoneNumberMatch telephoneNumberSubstringsMatch
uniqueMemberMatch wordMatch
)
local -a classes=( # Sampled from real servers, arbitrary other values allowed
automount automountMap cosTemplate dcObject device dnaSharedConfig domain
domainRelatedObject DUAConfigProfile extensibleObject groupOfNames
groupOfPrincipals ieee802device inetOrgPerson inetuser ipaassociation ipaca
ipacaacl ipaCertificate ipaCertMapConfigObject ipacertprofile ipaConfigObject
ipaDomainIDRange ipaDomainLevelConfig ipaGuiConfig ipahbacrule ipahbacservice
ipahbacservicegroup ipahost ipahostgroup ipaIDrange ipaKeyPolicy
ipakrbprincipal ipaNameResolutionData ipaNTDomainAttrs ipaNTGroupAttrs
ipaNTUserAttrs ipaobject ipaPublicKeyObject ipaReplTopoManagedServer
ipaservice ipaSshGroupOfPubKeys ipasshhost ipasshuser ipasudorule
ipaSupportedDomainLevelConfig ipaTrustedADDomainRange ipaUserAuthTypeClass
ipausergroup ipHost krbContainer krbprincipal krbprincipalaux
krbrealmcontainer krbTicketPolicyAux mepManagedEntry mepOriginEntry
nestedGroup nisDomainObject nisNetgroup nsContainer nsDS5Replica nshost
organization organizationalPerson organizationalRole organizationalUnit
person pilotObject pkiCA pkiuser posixAccount posixGroup pwdPolicy
shadowAccount simpleSecurityObject top
)
compquote open close andop orop
open=${(q)open} close=${(q)close}
# default to double rather than backslash quoting
[[ -z $compstate[quote] && -z $PREFIX ]] && pre='"('
zstyle -s ":completion:${curcontext}:operators" list-separator sep || sep=--
print -v disp -f "%s $sep %s" \| or \& and \! not
end=( ") $sep end" )
excl=( ! \\\| \& ) # compadd -F uses globs: only | needs quoting
local -a query=(
\( /$'*\0[ \t\n]#'/ \) # strip off any preceding arguments
\(
\( "/${open}!/" -'optype[++nest]=1;pre=""'
\| "/${open}${(q)orop}/" -'optype[++nest]=2;pre=""'
\| "/${open}${andop}/" -'optype[++nest]=3;pre=""'
\| '/[]/' ':operators:operator:compadd -F "( ${(q)excl[optype[nest]]} )" -d disp -P ${pre:-${(Q)open}} -S ${(Q)open} \| \& \!' \)
\|
\( "/${open}[^\\)]##/" "%$close%" # pass over whole var=value, needed due to lack of backtracking after the following
\| "/${open}(#i)homeDirectory=/" '/[]/' ':directories:directory:_directories -P / -W / -r ") \t\n\-"'
\| "/${open}(#i)loginShell=/" '/[]/' ':shells:shell:compadd -S ${(Q)close} ${(f)^"$(</etc/shells)"}(N)'
\| "/${open}(#i)mail=/" '/[]/' ':email-addresses:mail:_email_addresses -S ${(Q)close}'
\| "/${open}(#i)objectClass=/" '/[]/' ':object-classes:class:compadd -S ${(Q)close} -M "m:{a-zA-Z}={A-Za-z} r:[^A-Z]||[A-Z]=* r:|=*" -a classes'
\| "/${open}(#i)(automountKey|(member|)uid)=/" '/[]/' ':users:username:_users -S ${(Q)close}'
\| "/${open}(#i)cn=/" '/[]/' ':cn:cn: _alternative "users:user:_users -S ${close}" "groups:group:_groups -S ${close}" "hosts:host:_hosts -S ${close}"'
\|
'/[^:=<>~]##/' '%[=:<>~]%' -'pre=""'
':object-types:object type:_ldap_attributes -P ${pre:-${(Q)open}} -S = -r ":=~<> \t\n\-"'
\(
'/:/'
'/[^:]##:=/' ':matching-rules:matching rule:compadd -S ":=" -a matchingrules'
\|
'/([~<>]|)=/' ':operators:operator:compadd -S "" "<=" \>= \~='
\)
'/[^\\)]##/' "%$close%" ': _message -e object-values "object value (* for presence check)"'
\)
"/$close/" -'(( nest ))' ':brackets:bracket:compadd ${=query[nest]:+-S ""} \)'
\(
# This use of -P/-d and an empty match works around a limitation/bug where
# mixed use of -P removes any quoting
"/$close/" ':operators:operator:compadd ${=query[nest-1]:+-S ""} -d end -P ${(Q)close} ""'
\( // -'(( --nest ))' \| '//' -'((!nest))' '/[]/' ': compadd ""' \)
\) \#
// -'(( nest && optype[nest] > 1 ))'
\) \#
)
_regex_arguments _ldap_search_filters "$query[@]"
_ldap_search_filters