From c83ce203f5c78d3b4da8d59807fcfbcc23be2a21 Mon Sep 17 00:00:00 2001 From: Oliver Kiddle Date: Sat, 18 Feb 2023 01:10:10 +0100 Subject: [PATCH] 51455, 51461: new completion for the OpenLDAP client tools including a helper function for LDAP search filters --- ChangeLog | 7 + Completion/BSD/Command/_ldap | 4 +- Completion/Unix/Command/_openldap | 222 ++++++++++++++++++++++++++ Completion/Unix/Type/_ldap_attributes | 27 ++++ Completion/Unix/Type/_ldap_filters | 91 +++++++++++ 5 files changed, 349 insertions(+), 2 deletions(-) create mode 100644 Completion/Unix/Command/_openldap create mode 100644 Completion/Unix/Type/_ldap_attributes create mode 100644 Completion/Unix/Type/_ldap_filters diff --git a/ChangeLog b/ChangeLog index 7472298d6..b5bb1da99 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,10 @@ +2023-02-19 Oliver Kiddle + + * 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 * 51447: Src/Zle/zle_keymap.c: silence compiler maybe-uninitialized diff --git a/Completion/BSD/Command/_ldap b/Completion/BSD/Command/_ldap index 8fa17e2f8..181e6b0d0 100644 --- a/Completion/BSD/Command/_ldap +++ b/Completion/BSD/Command/_ldap @@ -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 diff --git a/Completion/Unix/Command/_openldap b/Completion/Unix/Command/_openldap new file mode 100644 index 000000000..233d0950e --- /dev/null +++ b/Completion/Unix/Command/_openldap @@ -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]" ]] diff --git a/Completion/Unix/Type/_ldap_attributes b/Completion/Unix/Type/_ldap_attributes new file mode 100644 index 000000000..0711cfbf1 --- /dev/null +++ b/Completion/Unix/Type/_ldap_attributes @@ -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 diff --git a/Completion/Unix/Type/_ldap_filters b/Completion/Unix/Type/_ldap_filters new file mode 100644 index 000000000..5e0e30f01 --- /dev/null +++ b/Completion/Unix/Type/_ldap_filters @@ -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)^"$(~]##/' '%[=:<>~]%' -'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