dump mail too to demonstrate multi-value capability
[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 # not NUL-safe
30 set -A Tb64decode_tbl -- \
31     -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 \
32     -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 \
33     -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 62 -1 -1 -1 63 \
34     52 53 54 55 56 57 58 59 60 61 -1 -1 -1 -1 -1 -1 \
35     -1  0  1  2  3  4  5  6  7  8  9 10 11 12 13 14 \
36     15 16 17 18 19 20 21 22 23 24 25 -1 -1 -1 -1 -1 \
37     -1 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 \
38     41 42 43 44 45 46 47 48 49 50 51 -1 -1 -1 -1 -1 \
39     -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 \
40     -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 \
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 -1 -1 -1 -1 -1 \
44     -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 \
45     -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 \
46     -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1
47 function Tb64decode {
48         [[ -o utf8-mode ]]; local u=$? s
49         set +U
50         read -raN-1 s <<<"$*"
51         local -i i=0 n=${#s[*]} v x
52         unset s[--n]
53         local -i1 o
54
55         while (( i < n )); do
56                 (( (x = Tb64decode_tbl[s[i++]]) == -1 )) && continue
57                 while (( (v = Tb64decode_tbl[s[i++]]) == -1 )); do
58                         if (( i > n )); then
59                                 (( u )) || set -U
60                                 return 0
61                         fi
62                 done
63                 (( o = ((x = (x << 6) | v) >> 4) & 255 ))
64                 REPLY+=${o#1#}
65                 while (( (v = Tb64decode_tbl[s[i++]]) == -1 )); do
66                         if (( i > n )); then
67                                 (( u )) || set -U
68                                 return 0
69                         fi
70                 done
71                 (( o = ((x = (x << 6) | v) >> 2) & 255 ))
72                 REPLY+=${o#1#}
73                 while (( (v = Tb64decode_tbl[s[i++]]) == -1 )); do
74                         if (( i > n )); then
75                                 (( u )) || set -U
76                                 return 0
77                         fi
78                 done
79                 (( o = ((x << 6) | v) & 255 ))
80                 REPLY+=${o#1#}
81         done
82         (( u )) || set -U
83 }
84
85 # Syntax: asso_setldap arrayname index ... -- ldapsearch-options
86 function asso_setldap_plain {
87         local opts x n=0 found=0
88
89         for x in "$@"; do
90                 opts[n++]=$x
91                 if [[ $x = -[-+] ]]; then
92                         opts[n++]=-x
93                         found=1
94                 fi
95         done
96         if (( !found )); then
97                 opts[n++]=--
98                 opts[n++]=-x
99         fi
100         asso_setldap_internal "${opts[@]}"
101 }
102 function asso_setldap_sasl {
103         local opts x n=0 found=0
104
105         for x in "$@"; do
106                 opts[n++]=$x
107                 if [[ $x = -[-+] ]]; then
108                         opts[n++]=-Q
109                         found=1
110                 fi
111         done
112         if (( !found )); then
113                 opts[n++]=--
114                 opts[n++]=-Q
115         fi
116         asso_setldap_internal "${opts[@]}"
117 }
118 function asso_setldap_internal {
119         # parse options
120         local arrpath ldapopts x i=0 T found=0
121         set -A arrpath
122         while (( $# )); do
123                 [[ $1 = -[-+] ]] && break
124                 arrpath[i++]=$1
125                 shift
126         done
127         [[ $1 = -+ ]]; do_free=$?
128         shift
129         set -A ldapopts -- "$@"
130
131         # Add default host URI if none is given
132         for x in "${ldapopts[@]}"; do
133                 if [[ $x = -H ]]; then
134                         found=1
135                         break
136                 fi
137         done
138         (( found )) || ldapopts+=(-H ldapi://)
139
140         if (( do_free )); then
141                 # just in case, unset the target array and create it as associative
142                 asso__lookup 1 "${arrpath[@]}"
143                 asso__r_free
144                 asso__r_setf $ASSO_AASS
145         fi
146
147         # call ldapsearch with decent output format
148         if ! T=$(mktemp /tmp/assoldap.XXXXXXXXXX); then
149                 print -u2 'assoldap.ksh: could not create temporary file'
150                 return 255
151         fi
152         if ! ldapsearch -LLL "${ldapopts[@]}" >"$T"; then
153                 print -ru2 "assoldap.ksh: error from: ldapsearch -LLL ${ldapopts[*]}"
154                 rm -f "$T"
155                 return $i
156         fi
157         if [[ ! -s $T ]]; then
158                 # empty output
159                 rm -f "$T"
160                 return 0
161         fi
162
163         # parse LDIF
164         asso_setldap_internal_ldif "${arrpath[@]}" <"$T"
165         rm -f "$T"
166         return 0
167 }
168 function asso_setldap_internal_ldif {
169         local line dn value x c
170
171         # input is never fully empty (see above)
172         IFS= read -r line
173         while :; do
174                 if [[ -z $line ]]; then
175                         dn=
176                         IFS= read -r line || break
177                         continue
178                 fi
179                 if [[ $line = ' '* ]]; then
180                         value+=${line# }
181                 else
182                         x=${line%%: *}
183                         value=${line: ${#x}+2}
184                 fi
185                 IFS= read -r line || break
186                 [[ $line = ' '* ]] && continue
187                 if [[ $x = *: ]]; then
188                         x=${x%:}
189                         [[ $x = jpegPhoto ]] || value=${|Tb64decode "$value";}
190                 fi
191                 [[ $x = dn ]] && dn=$value
192
193                 c=$(asso_getv "$@" "$dn" "$x" count)
194                 asso_sets "$value" "$@" "$dn" "$x" $((# c))
195                 asso_seti $((# ++c)) "$@" "$dn" "$x" count
196         done
197 }
198
199 :||\
200 {
201         # for testing
202         LDAPTLS_CACERT=/etc/ssl/certs/dc.lan.tarent.de.cer \
203             asso_setldap_plain users -- \
204             -H ldaps://dc.lan.tarent.de -b cn=users,dc=tarent,dc=de -s one \
205             isJabberAccount=1 cn uid mail
206         if (( $? )); then
207                 print -u2 An error occurred: $?
208                 exit 1
209         fi
210         print "uid (dn) = cn"
211         asso_loadk users
212         set -A dns -- "${asso_y[@]}"
213         for user_dn in "${dns[@]}"; do
214                 print -nr -- "$(asso_getv users "$user_dn" uid)" \
215                     "($user_dn) = $(asso_getv users "$user_dn" cn)"
216                 asso_loadk users "$user_dn" mail
217                 for user_mail in "${asso_y[@]}"; do
218                         [[ $user_mail = count ]] && continue
219                         print -nr -- " <$(asso_getv users "$user_dn" \
220                             mail "$user_mail")>"
221                 done
222                 print
223         done | sort
224 }