relicence under MirOS Licence, granted by private deal with the CTO
[shellsnippets/shellsnippets.git] / mksh / assoldap.ksh
1 # -*- mode: sh -*-
2 #-
3 # Copyright © 2013, 2014, 2015
4 #       mirabilos <m@mirbsd.org>
5 # Copyright © 2014, 2015
6 #       Dominik George <dominik.george@teckids.org>
7 #
8 # Provided that these terms and disclaimer and all copyright notices
9 # are retained or reproduced in an accompanying document, permission
10 # is granted to deal in this work without restriction, including un‐
11 # limited rights to use, publicly perform, distribute, sell, modify,
12 # merge, give away, or sublicence.
13 #
14 # This work is provided “AS IS” and WITHOUT WARRANTY of any kind, to
15 # the utmost extent permitted by applicable law, neither express nor
16 # implied; without malicious intent or gross negligence. In no event
17 # may a licensor, author or contributor be held liable for indirect,
18 # direct, other damage, loss, or other issues arising in any way out
19 # of dealing in the work, even if advised of the possibility of such
20 # damage or existence of a defect, except proven that it results out
21 # of said person’s immediate fault when using the work as intended.
22 #-
23 # Generic LDAP (LDIF) parser into associative arrays.
24
25 # include assockit, unless already done
26 # contract: path to this script is in $PATH
27 [[ -n $ASSO_VAL ]] || . assockit.ksh
28
29 asso_x=${KSH_VERSION#????MIRBSD KSH R}
30 asso_x=${asso_x%% *}
31 if (( asso_x < 51 )); then
32         alias asso_ustore='[[ -o utf8-mode ]]; local u=$?'
33         alias asso_urestore='(( u )) || set -U'
34 else
35         alias asso_ustore=
36         alias asso_urestore=
37 fi
38
39 # not NUL-safe
40 set -A Tb64decode_tbl -- \
41     -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 \
42     -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 \
43     -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 62 -1 -1 -1 63 \
44     52 53 54 55 56 57 58 59 60 61 -1 -1 -1 -1 -1 -1 \
45     -1  0  1  2  3  4  5  6  7  8  9 10 11 12 13 14 \
46     15 16 17 18 19 20 21 22 23 24 25 -1 -1 -1 -1 -1 \
47     -1 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 \
48     41 42 43 44 45 46 47 48 49 50 51 -1 -1 -1 -1 -1 \
49     -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 \
50     -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 \
51     -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 \
52     -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 \
53     -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 \
54     -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 \
55     -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 \
56     -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1
57 function Tb64decode {
58         asso_ustore
59         set +U
60         local s
61         read -raN-1 s <<<"$*"
62         local -i i=0 n=${#s[*]} v x
63         unset s[--n]
64         local -i1 o
65
66         while (( i < n )); do
67                 (( (x = Tb64decode_tbl[s[i++]]) == -1 )) && continue
68                 while (( (v = Tb64decode_tbl[s[i++]]) == -1 )); do
69                         if (( i > n )); then
70                                 asso_urestore
71                                 return 0
72                         fi
73                 done
74                 (( o = ((x = (x << 6) | v) >> 4) & 255 ))
75                 REPLY+=${o#1#}
76                 while (( (v = Tb64decode_tbl[s[i++]]) == -1 )); do
77                         if (( i > n )); then
78                                 asso_urestore
79                                 return 0
80                         fi
81                 done
82                 (( o = ((x = (x << 6) | v) >> 2) & 255 ))
83                 REPLY+=${o#1#}
84                 while (( (v = Tb64decode_tbl[s[i++]]) == -1 )); do
85                         if (( i > n )); then
86                                 asso_urestore
87                                 return 0
88                         fi
89                 done
90                 (( o = ((x << 6) | v) & 255 ))
91                 REPLY+=${o#1#}
92         done
93         asso_urestore
94 }
95 unalias asso_ustore
96 unalias asso_urestore
97
98 # Syntax: asso_setldap arrayname index ... -- ldapsearch-options
99 function asso_setldap_plain {
100         local opts x n=0 found=0
101
102         for x in "$@"; do
103                 opts[n++]=$x
104                 if [[ $x = -[-+] ]]; then
105                         opts[n++]=-x
106                         found=1
107                 fi
108         done
109         if (( !found )); then
110                 opts[n++]=--
111                 opts[n++]=-x
112         fi
113         asso_setldap_internal "${opts[@]}"
114 }
115 function asso_setldap_sasl {
116         local opts x n=0 found=0
117
118         for x in "$@"; do
119                 opts[n++]=$x
120                 if [[ $x = -[-+] ]]; then
121                         opts[n++]=-Q
122                         found=1
123                 fi
124         done
125         if (( !found )); then
126                 opts[n++]=--
127                 opts[n++]=-Q
128         fi
129         asso_setldap_internal "${opts[@]}"
130 }
131 function asso_setldap_internal {
132         local arrpath ldapopts x i=0 T found=0 do_free
133         set -A arrpath
134
135         # parse options
136         while (( $# )); do
137                 [[ $1 = -[-+] ]] && break
138                 arrpath[i++]=$1
139                 shift
140         done
141         [[ $1 = -+ ]]; do_free=$?
142         shift
143         set -A ldapopts -- "$@"
144
145         # Add default host URI if none is given
146         for x in "${ldapopts[@]}"; do
147                 if [[ $x = -H ]]; then
148                         found=1
149                         break
150                 fi
151         done
152         (( found )) || ldapopts+=(-H ldapi://)
153
154         if (( do_free )); then
155                 # just in case, unset the target array and create it as associative
156                 asso__lookup 1 "${arrpath[@]}"
157                 asso__r_free
158                 asso__r_setf $ASSO_AASS
159         fi
160
161         # call ldapsearch with decent output format
162         if ! T=$(mktemp /tmp/assoldap.XXXXXXXXXX); then
163                 print -u2 'assoldap.ksh: could not create temporary file'
164                 return 255
165         fi
166         if ! ldapsearch -LLL "${ldapopts[@]}" >"$T"; then
167                 print -ru2 "assoldap.ksh: error from: ldapsearch -LLL ${ldapopts[*]}"
168                 rm -f "$T"
169                 return $i
170         fi
171         if [[ ! -s $T ]]; then
172                 # empty output
173                 rm -f "$T"
174                 return 0
175         fi
176
177         # parse LDIF
178         asso_setldap_internal_ldif "${arrpath[@]}" <"$T"
179         rm -f "$T"
180         return 0
181 }
182 function asso_setldap_internal_ldif {
183         local line dn value x c newb
184
185         # input is never fully empty (see above)
186         IFS= read -r line
187         while :; do
188                 if [[ -z $line ]]; then
189                         dn=
190                         IFS= read -r line || break
191                         continue
192                 fi
193                 if [[ $line = ' '* ]]; then
194                         value+=${line# }
195                 else
196                         x=${line%%: *}
197                         value=${line: ${#x}+2}
198                 fi
199                 IFS= read -r line || break
200                 [[ $line = ' '* ]] && continue
201                 if [[ $x = *: ]]; then
202                         x=${x%:}
203                         [[ $x = jpegPhoto ]] || value=${|Tb64decode "$value";}
204                 fi
205                 [[ $x = dn ]] && dn=$value
206
207                 # look up and store parent of count/values
208                 asso__lookup 1 "$@" "$dn" "$x"
209                 newb=${asso_b}${asso_k#16#}
210                 # make parent associative
211                 asso__r_setf $ASSO_AASS
212                 # get count member, if any
213                 asso_b=$newb
214                 c=0
215                 if asso__lookup_once count && \
216                     (( (asso_f & ASSO_MASK_TYPE) < ASSO_NULL )); then
217                         nameref _Av=${asso_b}_v
218                         c=${_Av[asso_k]}
219                         [[ $c = +([0-9]) ]] || c=0
220                 fi
221                 # set value
222                 asso_b=$newb
223                 asso__lookup_once $((# c))
224                 asso__r_setk $((# c))
225                 asso__r_setfv $ASSO_STR "$value"
226                 # set count
227                 asso_b=$newb
228                 asso__lookup_once count
229                 asso__r_setk count
230                 asso__r_setfv $ASSO_INT $((# ++c))
231         done
232 }
233
234 :||\
235 {
236         # for testing
237         LDAPTLS_CACERT=/etc/ssl/certs/dc.lan.tarent.de.cer \
238             asso_setldap_plain users -- \
239             -H ldaps://dc.lan.tarent.de -b cn=users,dc=tarent,dc=de -s one \
240             isJabberAccount=1 cn uid mail
241         if (( $? )); then
242                 print -u2 An error occurred: $?
243                 exit 1
244         fi
245         print "uid (dn) = cn"
246         asso_loadk users
247         set -A dns -- "${asso_y[@]}"
248         for user_dn in "${dns[@]}"; do
249                 print -nr -- "$(asso_getv users "$user_dn" uid)" \
250                     "($user_dn) = $(asso_getv users "$user_dn" cn)"
251                 asso_loadk users "$user_dn" mail
252                 for user_mail in "${asso_y[@]}"; do
253                         [[ $user_mail = count ]] && continue
254                         print -nr -- " <$(asso_getv users "$user_dn" \
255                             mail "$user_mail")>"
256                 done
257                 print
258         done | sort
259 }