nutools/uldap

1554 lines
47 KiB
Bash
Executable File

#!/bin/bash
# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8
source "$(dirname -- "$0")/lib/ulib/auto" || exit 255
urequire ldif prefixes ldap
################################################################################
# Fonctions utilitaire
function split_cmds() {
# splitter la commande $2 sur l'un des caractères $3...$*, et mettre le
# résultat dans le tableau $1. Par défaut, splitter sur //
local __sc_dest="$1" __sc_cmds="$2"; shift 2
[ -n "$*" ] || set -- "//"
local -a __sc_seps=("$@")
local __sc_sep __sc_cmd __sc_arg __sc_foundsep __sc_addtocmd
__sc_foundsep=
for __sc_sep in "${__sc_seps[@]}"; do
if [ "$__sc_cmds" == "$__sc_sep" -o "${__sc_cmds%$__sc_sep}" != "$__sc_cmds" ]; then
__sc_foundsep=1
break
fi
done
[ -n "$__sc_foundsep" ] || __sc_cmds="$__sc_cmds ${__sc_seps[0]}"
eval "set -- $__sc_cmds"
array_new "$__sc_dest"
__sc_cmd= # commande actuelle
for __sc_arg in "$@"; do
__sc_addtocmd=1
__sc_foundsep=
for __sc_sep in "${__sc_seps[@]}"; do
if [ "$__sc_arg" == "$__sc_sep" ]; then
__sc_addtocmd=
__sc_foundsep=1
break
elif [ "${__sc_arg%$__sc_sep}" != "$__sc_arg" ]; then
__sc_arg="${__sc_arg%$__sc_sep}"
__sc_foundsep=1
break
fi
done
if [ -n "$__sc_addtocmd" ]; then
__sc_cmd="${__sc_cmd:+$__sc_cmd }$(qvalm "$__sc_arg")"
__sc_arg=
fi
if [ -n "$__sc_foundsep" ]; then
array_add "$__sc_dest" "$__sc_cmd"
__sc_cmd=
fi
done
}
################################################################################
# Variables
compute_ldap_prefixes
LDAPCONF="$(expand_prefix LDAPCONFDIR)/ldap.conf"
VARIABLES=(WSMODE WSAPPEND
PROFILE LDAPURI SUFFIX BINDDN PASSWORD SEARCHBASE FILTER ATTRS
UNCUT_ON_LOAD CUT_ON_SAVE
DECODE_ON_LOAD ENCODE_ON_SAVE ENCODE_ATTRS
OUTPUT
EXIT_ON_ERROR EXITCODE LINES DEBUG)
SPECIAL_VARIABLES=(PLAIN LDIF APPEND SEARCHBASE_EXACT)
PROTECTED_VARIABLES=()
# valeurs par défaut
WSMODE=ldif
WSAPPEND=
ATTRS=()
UNCUT_ON_LOAD=true
CUT_ON_SAVE=true
DECODE_ON_LOAD=true
ENCODE_ON_SAVE=true
ENCODE_ATTRS=(userPassword)
EXITCODE=0
AUTOPRINT=1
INTERACTIVE=auto
EXIT_ON_ERROR=auto
SKIPCOUNT=
LASTCMD=
EDITLAST=
function eval_cmdline() {
# analyser la ligne $1. ce peut-être une commande ou une suite de commande
# séparés par //, qui forment un seul groupe.
local -a cmds
local origcmd
local cmd="$1"
# La commande en cours va peut-être modifier la valeur de DEBUG. En
# faire une sauvegarde avant
local __DEBUG="$DEBUG"
if [ "${cmd#$}" != "$cmd" ]; then
# avec $, on exécute la commande littérale
is_yes "$__DEBUG" && set -x
if ! eval "${cmd#$}"; then
is_yes "$EXIT_ON_ERROR" && exit 255
fi
is_yes "$__DEBUG" && set +x
LASTCMD="$origcmd"
continue
fi
# sinon, ce sont des commandes de uldap
origcmd="$cmd"
split_cmds cmds "$cmd" "//" # splitter les commandes sur //
for cmd in "${cmds[@]}"; do
if eval "set -- $cmd"; then
cmdalias "$1" cmd; shift
set -- "${cmd[@]}" "$@"
cmd="$1"; shift
if [ "$cmd" == "last" ]; then
EDITLAST=1
else
cmd="uldap_$cmd"
is_yes "$__DEBUG" && set -x
if ! "$cmd" "$@"; then
is_yes "$EXIT_ON_ERROR" && exit 255
fi
is_yes "$__DEBUG" && set +x
LASTCMD="$origcmd"
fi
else
eerror "$cmd: erreur de syntaxe"
is_yes "$EXIT_ON_ERROR" && exit 255
fi
done
}
function set_values() {
local varcmd name uname lname value
local s=0
for varcmd in "$@"; do
if [[ "$varcmd" == *=* ]]; then
name="${varcmd%%=*}"
value="${varcmd#$name=}"
else
name="$varcmd"
value=
fi
uname="$(awk '{print toupper($0)}' <<<"$name")"
lname="$(awk '{print tolower($0)}' <<<"$name")"
if array_contains VARIABLES "$uname" || array_contains SPECIAL_VARIABLES "$uname"; then
"set_$lname" "$value" || s=1
elif ! array_contains PROTECTED_VARIABLES "$name"; then
_setv "$name" "$value"
else
s=1
fi
done
return $s
}
function set_exit_on_error() {
is_yes "$1" && EXIT_ON_ERROR=true || EXIT_ON_ERROR=false
}
function set_wsmode() {
case "$1" in
plain|ldif|mod) WSMODE="$1";;
*) return 1;;
esac
}
function set_plain() {
set_wsmode plain
}
function set_ldif() {
set_wsmode ldif
}
function set_wsappend() {
is_yes "$1" && WSAPPEND=true || WSAPPEND=false
}
function set_append() {
set_wsappend true
}
function set_profile() {
local profile="$1"
if ! array_contains PROFILES "$profile"; then
local profile_alias
for profile_alias in "${PROFILE_ALIASES[@]}"; do
if [ "${profile_alias%%:*}" == "$profile" ]; then
profile="${profile_alias#*:}"
break
fi
done
array_contains PROFILES "$profile" || return 1
fi
PROFILE="$profile"
local -a profile_values
array_copy profile_values "${PROFILE//-/_}_PROFILE"
set_values ldapuri= suffix= binddn= password= searchbase= "${profile_values[@]}"
if [ -z "$SUFFIX" -a -n "$LDAPURI" ]; then
local suffix
suffix="$(get_suffix "$LDAPURI")" || return 1
set_suffix "$suffix"
fi
[ -n "$SEARCHBASE" ] || set_searchbase "$SUFFIX"
return 0
}
function set_ldapuri() {
LDAPURI="$1"
local proto host port
if ! split_ldapuri "$LDAPURI" proto host port; then
LDAPURI=
return 1
fi
return 0
}
function set_suffix() {
[ -n "$BINDDN" ] && BINDDN="$(reldn "$BINDDN")"
SEARCHBASE="$(reldn "$SEARCHBASE")"
SUFFIX="$1"
[ -n "$BINDDN" ] && BINDDN="$(absdn "$BINDDN")"
SEARCHBASE="$(absdn "$SEARCHBASE")"
}
function set_binddn() {
local auth="$1"
if ! array_contains AUTH_PROFILES "$auth"; then
local auth_alias
for auth_alias in "${AUTH_ALIASES[@]}"; do
if [ "${auth_alias%%:*}" == "$auth" ]; then
auth="${auth_alias#*:}"
break
fi
done
fi
if array_contains AUTH_PROFILES "$auth"; then
local -a auth_values
local auth_value
array_copy auth_values "${auth//-/_}_AUTH"
BINDDN=
set_values password=
for auth_value in "${auth_values[@]}"; do
if [[ "$auth_value" == binddn=* ]]; then
auth="${auth_value#binddn=}"
if [ -z "$auth" -o "$auth" == "anonymous" ]; then
BINDDN=
else
BINDDN="$(absdn "$auth")"
fi
else
set_values "$auth_value"
fi
done
elif [ -z "$auth" -o "$auth" == "anonymous" ]; then
BINDDN=
else
BINDDN="$(absdn "$auth")"
fi
}
function set_password() {
PASSWORD="$1"
}
function set_searchbase_exact() {
SEARCHBASE="$1"
}
function set_searchbase() {
set_searchbase_exact "$(absdn "$1")"
}
function set_filter() {
FILTER="$1"
}
function set_attrs() {
array_split ATTRS "$1"
}
function set_uncut_on_load() {
is_yes "$1" && UNCUT_ON_LOAD=true || UNCUT_ON_LOAD=false
}
function set_cut_on_save() {
is_yes "$1" && CUT_ON_SAVE=true || CUT_ON_SAVE=false
}
function set_decode_on_load() {
is_yes "$1" && DECODE_ON_LOAD=true || DECODE_ON_LOAD=false
}
function set_encode_on_save() {
is_yes "$1" && ENCODE_ON_SAVE=true || ENCODE_ON_SAVE=false
}
function set_encode_attrs() {
array_split ENCODE_ATTRS "$1"
}
function set_output() {
OUTPUT="$1"
}
function set_lines() {
LINES="$1"
}
function set_debug() {
is_yes "$1" && DEBUG=true || DEBUG=false
}
ac_set_tmpfile TMPFILE # espace de travail temporaire
ac_set_tmpfile WSFILE # espace de travail courant
# le contenu de l'espace de travail est-il formatté pour ldapmodify? Dans ce
# cas, aucune opération de transformation n'a à lui être appliquée
WSFILES=()
WSSTACK=()
function clear_wsfiles() {
local wsfile
for wsfile in "${WSFILES[@]}"; do
rm "$wsfile"
done
WSFILES=()
WSSTACK=()
WSMODE=ldif
}
function __push() {
local __varcmd __name
for __name in "$@"; do
__varcmd="${__varcmd:+$__varcmd
}$(echo_setv "$__name" "${!__name}")"
done
WSSTACK=("${WSSTACK[@]}" "$__varcmd")
}
function __pop() {
eval "$(last_value WSSTACK)"
array_del_last WSSTACK
}
function push_wsfile() {
WSFILES=("${WSFILES[@]}" "$WSFILE")
__push WSMODE WSAPPEND UNCUT_ON_LOAD CUT_ON_SAVE DECODE_ON_LOAD ENCODE_ON_SAVE ENCODE_ATTRS
ac_set_tmpfile WSFILE
}
function pop_wsfile() {
if [ -n "${WSFILES[*]}" ]; then
rm "$WSFILE"
WSFILE="$(last_value WSFILES)"
array_del_last WSFILES
__pop
fi
}
function peek_wsfile() {
last_value WSFILES
}
function has_wsfiles() {
[ -n "${WSFILES[*]}" ]
}
function count_wsfiles() {
echo "${#WSFILES[*]}"
}
################################################################################
# Commandes
COMMANDS=(help set last profile auth clear load save print alias cd
list ldapsearch transform sed awk grep format sort edit diff
ldapmodify ldapadd ldapdelete
undo quit)
COMMANDS_HELP="\
\$ cmd
Passer directement une commande au shell.
set [options] [var=value...]
Changer des options ou des variables. set sans argument affiche la liste
des variables définies.
[set] plain
Passer en mode 'plain': indiquer que l'espace de travail contient des
données brutes. Les pré-traitements et post-traitements (uncut_on_load,
decode_on_load, encode_on_save, cut_on_save) ne sont pas appliqués sur
cet espace de travail
[set] ldif
Passer en mode 'ldif': indiquer que l'espace de travail contient des
données ldif. Les pré-traitements et post-traitements sont appliqués
normalement sur cet espace de travail
[set] append
Pour certaines opérations, spécifier si le résultat de la *prochaine*
opération remplace le contenu de l'espace de travail courant (par
défaut), ou si le résultat est ajouté à la fin.
last
Afficher en mode édition la dernière commande. Cette commande n'est
fonctionnelle qu'avec une version de bash >=4.x
profile name
Choisir le profil 'name'. Equivalent à 'set profile=name'. Sans
argument, afficher la liste des profils valides.
auth anonymous|binddn [password]
Spécifier le compte à utiliser pour se connecter. Equivalent à
'set binddn=binddn; set password=password'
clear [-k]
Vider l'espace de travail et passer en mode 'plain'.
Avec l'option -k, supprimer aussi tout l'historique d'annulation.
load [-k] input
Charger un fichier dans l'espace de travail. Si l'extension du fichier
est .ldif, passer en mode 'ldif'
En mode append, rajouter le contenu du fichier à l'espace de travail,
puis repasser en mode replace.
Le code de retour est 0 si le fichier a été chargé, 1 sinon.
save [-a] output
Sauvegarder l'espace de travail dans un fichier.
Avec l'option -a, rajouter au fichier au lieu de l'écraser
print
Afficher l'espace de travail
alias a=rdn...
Définir un alias pour la commande cd. 'a' est l'alias, 'rdn' est le dn
correspondant, exprimé par rapport à \$suffix. Sans argument, afficher
la liste des aliases définis.
cd rdn
Changer searchbase. Par défaut, il s'agit d'un rdn relatif à \$searchbase
- Certains aliases sont supportés: .. pour l'objet parent, ~ pour
\$suffix, / pour la racine. 'cd' sans argument équivaut à 'cd ~'
- Si le dn commence par '~/', il s'agit d'un rdn relatif à \$suffix.
- Si le dn commence par /, searchbase reçoit la valeur rdn sans
modifications (sauf bien sûr enlever le '/' de tête si nécessaire). Il
faut alors que ce soit un dn absolu.
ls [-b searchbase] [filter [attrs...]]
search [-b searchbase] [filter [attrs...]]
Utiliser ldapsearch pour faire la recherche, et copier le résultat dans
l'espace de travail. 'ls' est équivalent à 'search -s one'. Si ce n'est
pas déjà le cas, passer en mode 'ldif'.
L'option -b prend une valeur avec la même syntaxe que la commande cd,
sauf que les alias ne sont pas supportés. En particulier, la valeur est
relative au \$searchbase courant. Pour faire une recherche par rapport à
\$suffix, il faut utiliser la syntaxe ~/searchbase.
En mode append, rajouter le résultat de la recherche à l'espace de
travail, puis repasser en mode replace.
Le code de retour est 1 si aucun enregistrement n'a été trouvé, sinon
le code de retour est celui de la commande ldapsearch.
cut Couper les lignes trop longues. Cette action est en principe effectuée
automatiquement lors de la sauvegarde. Il n'est pas conseillé
d'appliquer des méthodes de transformation après avoir utilisé cette
action.
uncut
Fusionner les lignes coupées. Cette action est en principe effectuée
automatiquement lors du chargement ou après la recherche.
encode [attrs...]
Encoder en base64 les valeurs des attributs mentionnés.
decode [attrs...]
Décoder les valeurs des attributs mentionnés si nécessaire (c'est à dire
s'ils sont encodés en base64)
keepattr attrs...
Garder uniquement les lignes des attributs mentionnés. Ensuite,
supprimer les objets ayant uniquement la ligne dn: (en d'autres termes,
keepattr sans argument supprime *tout* l'espace de travail)
keepval attr patterns...
Pour l'attribut attr, garder uniquement les lignes pour lesquelles les
valeurs correspondent aux expressions régulières. Les autres attributs
ne sont pas modifiés. Ensuite, supprimer les objets ayant uniquement la
ligne dn:
exclude attrs...
Supprimer les lignes des attributs mentionnés. Ensuite, supprimer les
objets ayant uniquement la ligne dn:
excludeval attr patterns...
Pour l'attribut attr, supprimer les lignes pour lesquelles les
valeurs correspondent aux expressions régulières. Les autres attributs
ne sont pas modifiés. Ensuite, supprimer les objets ayant uniquement la
ligne dn:
keepvalentry attr patterns...
Pour l'attribut attr, vérifier si *au moins une* valeur correspond à
l'une des expressions régulières. Si c'est le cas, garder l'entrée
entière, sinon supprimer l'entrée.
excludevalentry attr patterns...
Pour l'attribut attr, vérifier si *aucune* des valeurs ne correspond à
l'une des expressions régulières. Si c'est le cas, garder l'entrée
entière, sinon supprimer l'entrée.
setval attr values...
Remplacer toutes les valeurs de l'attribut attr par les valeurs
spécifiées.
addval attr values...
Ajouter un nouvel attribut avec les valeurs spécifiées. Si l'attribut
existe déjà, les nouvelles valeurs sont ajoutées à la fin.
sed args
Modifier l'espace de travail avec le résultat de la commande sed.
note: aucun argument n'est filtré, mais il ne faut pas utiliser les
options de sed qui provoquent la modification en place du fichier,
comme par exemple l'option -i
awk args
Modifier l'espace de travail avec le résultat de la commande awk.
grep args
Modifier l'espace de travail avec le résultat de la commande grep.
format [options] attrs...
Formater l'espace de travail en données tabulaires, et passer en mode
'plain'.
--show-headers
Afficher les en-têtes
-F FSEP
Spécifier le séparateur pour les attributs. Par défaut, il s'agit du
caractère de tabulation.
-R VSEP
Spécifier le séparateur pour les valeurs des attributs. Par défaut, il
s'agit du point-virgule ';'
-e Retourner les valeurs comme des variables shell. Les options -F et -R
sont ignorées. Les attributs multivalués sont écrits sous forme de
tableaux. Par exemple:
attributes=('mail' 'givenName')
index=0
mail='user@domain.fr'
givenName=('peter' 'gabriel')
--bc
Dans le mode -e, spécifier une commande à insérer avant le premier
enregistrement. Quand cette commande est lancée, index==-1
-c Dans le mode -e, spécifier une commande à insérer après chaque
enregistrement
--ec
Dans le mode -e, spécifier une commande à insérer après le dernier
enregistrement
sort [args]
Modifier l'espace de travail avec le résultat de la commande sort.
edit
Lancer un éditeur pour modifier l'espace de travail.
diff [options]
Afficher les différences entre l'espace de travail et la version
précédente
ifok cmd
iferror cmd
Si le dernier code de retour est 0 (resp. !=0), lancer la commande cmd
skip n
Sauter les n prochaines commandes. A utiliser avec ifok et iferror
undo
Annuler la dernière modification effectuée sur l'espace de travail
Les directives suivantes prennent le contenu de l'espace de travail, et le
transforment en une suite de commandes de modifications pour ldapmodify:
A Créer un objet de toutes pièces avec les attributs donnés et leurs
valeurs.
a Ajouter les valeurs spécifiée à l'attribut
r Remplacer les valeurs de l'attribut par celles spécifiées
d Supprimer les valeurs spécifiées de l'attribut
D Supprimer l'attribut
delentry
Supprimer l'objet
ldapmodify
Utiliser ldapmodify pour modifier les objets sur le serveur. Il faut
utiliser au préalable l'une des méthodes de transformation parmi A, a,
r, d, D, delentry.
Le code de retour est celui de la commande ldapmodify.
ldapadd
Utiliser ldapadd pour créer les objets situés dans l'espace de travail.
Le code de retour est celui de la commande ldapadd.
ldapdelete
Utiliser ldapdelete pour supprimer la liste des dns situés dans l'espace
de travail.
Le code de retour est celui de la commande ldapdelete.
Notes:
- les expressions régulières sont celles reconnues par awk.
- pour spécifier plusieurs actions sur une même ligne, les séparer par //
- le code de retour est 0 si ok, 255 si une erreur s'est produite (erreur de
syntaxe, de connexion, de lecture/écriture de fichier, etc.). sinon, les
opérations ldap{search,modify,delete,add} ont leur code de retour respectifs"
function cmdalias() {
# soit le nom de la commande $1, mettre dans le tableau $2(=cmd) la forme
# canonique de la commande, avec ses arguments éventuels
local cmdalias_="$1" cmdname_="${2:-cmd}"
local -a cmdvals_=("$cmdalias_")
case "$cmdalias_" in
h|help) cmdvals_=(help);;
set) cmdvals_=(set);;
plain) cmdvals_=(set wsmode=plain);;
ldif) cmdvals_=(set wsmode=ldif);;
append) cmdvals_=(set wsappend=true);;
l|last) cmdvals_=(last);;
profile) cmdvals_=(profile);;
auth) cmdvals_=(auth);;
clear) cmdvals_=(clear);;
L|load) cmdvals_=(load);;
S|save) cmdvals_=(save);;
p|print) cmdvals_=(print);;
alias) cmdvals_=(alias);;
cd) cmdvals_=(cd);;
ls|list|dir) cmdvals_=(list);;
f|find|s|search|ldapsearch) cmdvals_=(ldapsearch);;
cut) cmdvals_=(transform cut);;
uncut) cmdvals_=(transform uncut);;
enc|encode) cmdvals_=(transform encode);;
dec|decode) cmdvals_=(transform decode);;
av|addval) cmdvals_=(transform addval);;
rv|replval|sv|setval) cmdvals_=(transform replval);;
k|keep|keepattr) cmdvals_=(transform keepattr);;
K|kv|keepval) cmdvals_=(transform keepval);;
x|exclude|excludeattr) cmdvals_=(transform excludeattr);;
kve|keepvalentry) cmdvals_=(transform keepvalentry);;
xve|excludevalentry) cmdvals_=(transform excludevalentry);;
dv|delval|X|xv|excludeval) cmdvals_=(transform excludeval);;
xempty|excludeempty) cmdvals_=(transform excludeempty);;
sed) cmdvals_=(sed);;
awk) cmdvals_=(awk);;
grep) cmdvals_=(grep);;
format) cmdvals_=(format);;
sort) cmdvals_=(sort);;
e|edit) cmdvals_=(edit);;
A|modaddattr) cmdvals_=(transform modaddattr);;
a|ma|modadd|modaddval) cmdvals_=(transform modaddval);;
r|mr|modrepl|modreplval) cmdvals_=(transform modreplval);;
d|md|moddel|moddelval) cmdvals_=(transform moddelval);;
D|moddelattr) cmdvals_=(transform moddelattr);;
delentry|moddelentry) cmdvals_=(transform moddelentry);;
mod|modify|ldapmodify|commit|ci) cmdvals_=(ldapmodify);;
ldapadd) cmdvals_=(ldapadd);;
ldapdelete) cmdvals_=(ldapdelete);;
ifok) cmdvals_=(ifok);;
iferror) cmdvals_=(iferror);;
skip) cmdvals_=(skip);;
u|undo|revert) cmdvals_=(undo);;
q|exit|quit) cmdvals_=(quit);;
esac
set_array "$cmdname_" @ "${cmdvals_[@]}"
}
function uldap_help() {
<<<"$COMMANDS_HELP" sed 's/^ //'
}
function uldap_set() {
local args varcmd name lname uname
local -a varcmds values
if parse_opts \
-C: '$varcmds=("${varcmds[@]}" profile="$value_")' \
-H: '$varcmds=("${varcmds[@]}" ldapuri="$value_")' \
-D: '$varcmds=("${varcmds[@]}" binddn="$value_")' \
-w: '$varcmds=("${varcmds[@]}" password="$value_")' \
-b: '$varcmds=("${varcmds[@]}" searchbase="$value_")' \
--nu '$varcmds=("${varcmds[@]}" uncut_on_load=false)' \
--nc '$varcmds=("${varcmds[@]}" cut_on_save=false)' \
-u '$varcmds=("${varcmds[@]}" uncut_on_load=true cut_on_save=true)' \
--nd '$varcmds=("${varcmds[@]}" decode_on_load= encode_on_save=)' \
-d '$varcmds=("${varcmds[@]}" decode_on_load=true encode_on_save=true encode_attrs="userPassword")' \
-e '$varcmds=("${varcmds[@]}" exit_on_error=true)' \
--ne '$varcmds=("${varcmds[@]}" exit_on_error=false)' \
@ args -- "$@"; then
varcmds=("${varcmds[@]}" "${args[@]}")
else
eerror "$args"
return 1
fi
set -- "${varcmds[@]}"
if [ -n "$*" ]; then
for varcmd in "$@"; do
if [[ "$varcmd" == *=* ]]; then
name="${varcmd%%=*}"
value="${varcmd#$name=}"
else
name="$varcmd"
value=
fi
case "$name" in
profile)
if set_profile "$value"; then
estep "Sélection du profil $(get_color g)$value$(get_color z)${SEARCHBASE:+ avec le dn de base $(get_color g)$SUFFIX$(get_color z)}"
if [ -z "$LDAPURI" ]; then
estep "Chargement des valeurs de $LDAPCONF"
set_ldapuri "$(<"$LDAPCONF" grep '^URI' | sed 's/^URI *//g')"
set_suffix "$(<"$LDAPCONF" grep '^BASE' | sed 's/^BASE *//g')"
fi
if [ -z "$LDAPURI" ]; then
ewarn "Ce profil ne définit pas de serveur ldap, et aucun serveur n'a été défini dans $LDAPCONF"
fi
else
eerror "Profil invalide ou erreur de connexion: $value"
return 1
fi
;;
*)
if ! set_values "$varcmd"; then
eerror "variable inconnue ou valeur invalide: $varcmd"
return 1
fi
;;
esac
done
else
for name in "${VARIABLES[@]}"; do
uname="$(awk '{print toupper($0)}' <<<"$name")"
lname="$(awk '{print tolower($0)}' <<<"$name")"
array_copy values "$uname"
echo "$lname = ${values[*]}"
done
fi
return 0
}
function uldap_profile() {
if [ -n "$*" ]; then
uldap_set profile="$1"
return $?
else
local profile profile_value profile_values
for profile in "${PROFILES[@]}"; do
echo_ "$profile"
array_copy profile_values "${profile//-/_}_PROFILE"
for profile_value in "${profile_values[@]}"; do
if [ "${profile_value#ldapuri=}" != "$profile_value" ]; then
echo_ $'\t'"${profile_value#ldapuri=}"
elif [ "${profile_value#binddn=}" != "$profile_value" ]; then
echo_ $'\t'"[${profile_value#binddn=}]"
fi
done
echo
done
return 0
fi
}
function uldap_auth() {
if [ -n "$*" ]; then
set_values binddn="$1" ${2:+password="$2"}
return 0
else
local auth auth_value auth_values
for auth in "${AUTH_PROFILES[@]}"; do
echo_ "$auth"
array_copy auth_values "${auth//-/_}_AUTH"
for auth_value in "${auth_values[@]}"; do
if [ "${auth_value#binddn=}" != "$auth_value" ]; then
echo_ $'\t'"${auth_value#binddn=}"
fi
done
echo
done
return 0
fi
}
function uldap_clear() {
local args clear
if parse_opts \
-k clear \
@ args -- "$@"; then
set -- "${args[@]}"
else
eerror "$args"
return 1
fi
estep "Vidage de l'espace de travail"
if [ -n "$clear" ]; then
clear_wsfiles
>"$WSFILE"
else
push_wsfile
fi
set_wsmode plain
return 0
}
function __after_load() {
if [ "$WSMODE" == "ldif" ]; then
is_yes "$UNCUT_ON_LOAD" && __transform --nopush uncut_lines
is_yes "$DECODE_ON_LOAD" && __transform --nopush tl_decode "$(def_match_attr)"
fi
}
function uldap_load() {
# par défaut, on reset -u et -d, mais si on spécifie une des options, les honorer
local args clear
local -a options
if parse_opts \
-k clear \
--nu '$array_add options --nu' \
--nc '$array_add options --nc' \
-u '$array_add options -u' \
--nd '$array_add options --nd' \
-d '$array_add options -d' \
@ args -- "$@"; then
set -- "${args[@]}"
else
eerror "$args"
return 1
fi
[ -z "${options[*]}" ] && options=(-u -d)
uldap_set "${options[@]}"
local input="${1:-$OUTPUT}"
if [ -f "$input" ]; then
estep "Chargement depuis $input..."
if [ -n "$clear" ]; then
clear_wsfiles
>"$WSFILE"
elif [ -s "$WSFILE" ]; then
push_wsfile
is_yes "$WSAPPEND" && cat "$(peek_wsfile)" >"$WSFILE"
fi
set_values WSAPPEND=false
cat "$input" >>"$WSFILE" || return
set_values output="$input"
if [ "${input%.ldif}" != "$input" ]; then
set_wsmode ldif
else
set_wsmode plain
fi
__after_load
__show_summary
else
eerror "$input: fichier introuvable"
EXITCODE=1
return 1
fi
EXITCODE=0
return 0
}
function __before_save() {
if [ "$WSMODE" == "ldif" ]; then
is_yes "$ENCODE_ON_SAVE" && __transform --nopush tl_encode "$(def_match_attr "${ENCODE_ATTRS[@]}")"
is_yes "$CUT_ON_SAVE" && __transform --nopush cut_lines
fi
}
function uldap_save() {
local args append
if parse_opts \
-a append \
@ args -- "$@"; then
set -- "${args[@]}"
else
eerror "$args"
return 1
fi
set_values output="${1:-$OUTPUT}"
if [ -n "$OUTPUT" ]; then
estep "Enregistrement de $OUTPUT..."
__before_save
if [ -n "$append" ]; then
cat "$WSFILE" >>"$OUTPUT" || return
else
cat "$WSFILE" >"$OUTPUT" || return
fi
__after_load
else
eerror "Vous devez spécifier un fichier avec la commande save"
return 1
fi
return 0
}
function __head() {
# afficher le fichier $1 en le coupant à $LINES
local file="$1"
local nblines="$(<"$file" wc -l)" maxlines="${LINES:-24}"
let maxlines=$maxlines-7
[ $maxlines -gt 1 ] || maxlines=5
head -n "$maxlines" <"$file"
if [ $nblines -gt $maxlines ]; then
eecho "$(get_color y)... $(($nblines - $maxlines)) lignes restantes$(get_color z)"
fi
}
function __show_summary() {
# Afficher un extrait de $wsfile en mode interactif
[ -n "$INTERACTIVE" ] && __head "$WSFILE"
}
function uldap_print() {
cat "$WSFILE"
if [ -n "$INTERACTIVE" -a "$WSMODE" == "ldif" ]; then
local nbdn="$(<"$WSFILE" grep '^dn:' | wc -l)"
enote "$nbdn objet(s) trouvé(s)"
fi
return 0
}
function uldap_alias() {
local alias name searchbase
local -a tmparray
if [ -n "$*" ]; then
for alias in "$@"; do
if [[ "$alias" == *=* ]]; then
name="${alias%%=*}"
searchbase="${alias#$name=}"
# supprimer l'ancien alias
tmparray=()
for alias in "${SEARCHBASE_ALIASES[@]}"; do
if [ "${alias%%:*}" != "$name" ]; then
array_add tmparray "$alias"
fi
done
array_copy SEARCHBASE_ALIASES tmparray
# ajouter le nouvel alias
if [ -n "$searchbase" ]; then
array_add SEARCHBASE_ALIASES "$name:$searchbase"
fi
else
eerror "$alias: Vous devez utiliser la syntaxe 'alias name=searchbase'"
fi
done
else
for alias in "${SEARCHBASE_ALIASES[@]}"; do
name="${alias%%:*}"
searchbase="${alias#$name:}"
echo "alias $name='$searchbase'"
done
fi
return 0
}
function uldap_cd() {
local args exact
if parse_opts \
-p exact \
@ args -- "$@"; then
set -- "${args[@]}"
else
eerror "$args"
return 1
fi
local dn="$1"
# corriger les expansions éventuelles du shell
if [ "$dn" == "$HOME" ]; then
dn="~"
elif [ "${dn#$HOME}" != "$dn" ]; then
dn="~${dn#$HOME}"
fi
# étendre les aliases
local alias
for alias in "${SEARCHBASE_ALIASES[@]}"; do
name="${alias%%:*}"
if [ "$dn" == "$name" ]; then
# les aliases sont exprimés par rapport à $suffix
dn="/$(absdn "${alias#$name:}")"
break
fi
done
if [ -z "$dn" -o "$dn" == "/" -o "$dn" == "~" -o "$dn" == ".." ]; then
# arguments spéciaux
:
elif ! [[ "$dn" == *=* ]]; then
# sinon, il faut que ce soit un rdn
eerror "Alias ou dn invalide: $dn"
return 1
fi
[ -z "$dn" ] && dn="~"
if [ -n "$exact" ]; then
set_values searchbase_exact="$dn"
elif [ "$dn" == ".." ]; then
if [ "${SEARCHBASE#*,}" != "$SEARCHBASE" ]; then
set_values searchbase_exact="${SEARCHBASE#*,}"
else
set_values searchbase_exact=
fi
else
set_values searchbase_exact="$(rabsdn "$dn")"
fi
return 0
}
function uldap_list() {
if [ -z "$SEARCHBASE" -o \
\( "$SEARCHBASE" != "$SUFFIX" -a "${SEARCHBASE%,$SUFFIX}" == "$SEARCHBASE" \) ]; then
uldap_ldapsearch "$@"
else
uldap_ldapsearch -s one "$@"
fi
}
function uldap_ldapsearch() {
if [ -z "$SEARCHBASE" -a -z "$*" ]; then
set -- '' objectClass supportedLDAPVersion namingContexts
fi
local args nop cont debug deref tls scope
local usearchbase lsearchbase="$SEARCHBASE" lfilter lattrs
if parse_opts \
-n nop \
-c cont \
-d: debug= \
-s: scope= \
-a: deref= \
-Z tls \
-b: usearchbase= \
@ args -- "$@"; then
set -- "${args[@]}"
else
eerror "$args"
return 1
fi
if [ -n "$usearchbase" ]; then
lsearchbase="$(rabsdn "$usearchbase")"
elif [ -z "$lsearchbase" -o \
\( "$lsearchbase" != "$SUFFIX" -a "${lsearchbase%,$SUFFIX}" == "$lsearchbase" \) ]; then
# avec SEARCHBASE="" ou non relatif à $SUFFIX, le scope est par défaut base
[ -n "$scope" ] || scope=base
fi
lfilter="${1:-${FILTER:-objectClass=*}}"; shift
[[ "$lfilter" == \(*\) ]] || lfilter="($lfilter)"
if [ -n "$*" ]; then
lattrs=("$@")
else
lattrs=("${ATTRS[@]}")
fi
local -a lsargs=(ldapsearch -LLL -x ${LDAPURI:+-H "$LDAPURI"} ${tls:+-Z}
${nop:+-n} ${cont:+-c} ${debug:+-d "$debug"} ${scope:+-s "$scope"}
${deref:+-a "$deref"} -b "$lsearchbase")
local msg="${lsargs[*]}"
if [ -n "$BINDDN" ]; then
lsargs=("${lsargs[@]}" -D "$BINDDN" -w "$PASSWORD")
msg="$msg -D $BINDDN -w ****"
fi
estep "$msg $lfilter ${lattrs[*]}"
if [ -s "$WSFILE" ]; then
push_wsfile
is_yes "$WSAPPEND" && cat "$(peek_wsfile)" >"$WSFILE"
fi
set_values WSAPPEND=false
"${lsargs[@]}" "$lfilter" "${lattrs[@]}" >>"$WSFILE"
EXITCODE=$?
if [ $EXITCODE -eq 0 ]; then
# ok seulement s'il y a un résultat
[ -s "$WSFILE" ] && EXITCODE=0 || EXITCODE=1
else
return 1
fi
set_wsmode ldif
__after_load
__show_summary
# affichage du nombre d'objets
local nbdn="$(<"$WSFILE" grep '^dn:' | wc -l)"
[ -n "$INTERACTIVE" ] && enote "$nbdn objet(s) trouvé(s)"
return 0
}
function __transform() {
# appliquer la transformation $1 au fichier $WSFILE en utilisant $TMPFILE
local push=1
if [ "$1" == "--nopush" ]; then
shift
push=
fi
cat "$WSFILE" >"$TMPFILE"
[ -n "$push" ] && push_wsfile
"$@" <"$TMPFILE" >"$WSFILE"
}
function __check_wsmode() {
if [ "$WSMODE" == "mod" ]; then
eerror "L'espace de travail est formatté pour ldapmodify. Vous ne devriez plus y appliquer de tranformation"
return 1
fi
return 0
}
function uldap_transform() {
local action="$1"; shift
case "$action" in
cut)
__transform cut_lines
;;
uncut)
__transform uncut_lines
;;
decode)
__transform tl_decode "$(def_match_attr "$@")"
;;
encode)
__transform tl_encode "$(def_match_attr "$@")"
set_values encode_attrs="$*" encode_on_save=false
;;
addval)
__check_wsmode || return
__transform tl_addval "$@"
__transform --nopush ensure_complete_objects
;;
replval)
__check_wsmode || return
local match_attr="$(def_match_attr "$1")"; shift
__transform tl_replval "$match_attr" "$@"
__transform --nopush ensure_complete_objects
;;
keepattr)
__check_wsmode || return
__transform tl_keepattr "$(def_match_attr dn "$@")"
__transform --nopush ensure_complete_objects
;;
keepval)
__check_wsmode || return
local match_attr="$(def_match_attr "$1")"; shift
__transform tl_keepval "$match_attr" "$(def_match_value "$@")"
__transform --nopush ensure_complete_objects
;;
excludeattr)
__check_wsmode || return
__transform tl_excludeattr "$(def_match_attr "$@")"
__transform --nopush ensure_complete_objects
;;
excludeval)
__check_wsmode || return
local match_attr="$(def_match_attr "$1")"; shift
__transform tl_excludeval "$match_attr" "$(def_match_value "$@")"
__transform --nopush ensure_complete_objects
;;
excludevalentry)
__check_wsmode || return
local match_attr="$(def_match_attr "$1")"; shift
__transform tl_excludevalentry "$match_attr" "$(def_match_value "$@")"
__transform --nopush delete_marked_objects
;;
keepvalentry)
__check_wsmode || return
local match_attr="$(def_match_attr "$1")"; shift
__transform tl_keepvalentry "$match_attr" "$(def_match_value "$@")"
__transform --nopush delete_marked_objects
;;
excludeempty)
__check_wsmode || return
__transform ensure_complete_objects
;;
modaddattr)
__check_wsmode || return
__transform tl_addattr
set_wsmode mod
;;
modaddval)
__check_wsmode || return
__transform tl_modifyattr add
set_wsmode mod
;;
modreplval)
__check_wsmode || return
__transform tl_modifyattr replace
set_wsmode mod
;;
moddelval)
__check_wsmode || return
__transform tl_modifyattr delete
set_wsmode mod
;;
moddelattr)
__check_wsmode || return
__transform tl_deleteattr
set_wsmode mod
;;
moddelentry)
__check_wsmode || return
__transform tl_deleteentry
set_wsmode mod
;;
*)
eerror "$action: type de transformation incorrect"
return 1
;;
esac
__show_summary
return 0
}
function uldap_sed() {
__transform sed "$@"
set_wsmode plain
__show_summary
return 0
}
function uldap_awk() {
__transform awk "$@" || return
set_wsmode plain
__show_summary
return 0
}
function uldap_grep() {
__transform grep "$@"
set_wsmode plain
__show_summary
return 0
}
function uldap_format() {
__transform tl_format "$@"
set_wsmode plain
__show_summary
return 0
}
function uldap_sort() {
__transform sort "$@"
set_wsmode plain
__show_summary
return 0
}
function uldap_edit() {
cat "$WSFILE" >"$TMPFILE"
"${EDITOR:-vi}" "$TMPFILE"
if testdiff "$WSFILE" "$TMPFILE"; then
ac_set_tmpfile diff
diff -U1 "$WSFILE" "$TMPFILE" |
awk 'NR==1 { $2="previous" } NR==2 { $2="current" } {print}' >"$diff"
__head "$diff"
rm -f "$diff"
push_wsfile
cat "$TMPFILE" >"$WSFILE"
else
enote "Aucune modification effectuée"
fi
return 0
}
function uldap_diff() {
if has_wsfiles; then
local orig="$(peek_wsfile)"
if [ -n "$*" ]; then
diff "$@" "$orig" "$WSFILE"
else
diff -U1 "$orig" "$WSFILE" |
awk 'NR==1 { $2="previous" } NR==2 { $2="current" } {print}'
fi
else
enote "Aucune différence détectée"
fi
return 0
}
function uldap_ldapmodify() {
if [ "$WSMODE" != "mod" ]; then
ewarn "L'espace de travail n'a pas été traité avec l'une des méthodes de transformation A, a, r, d, D, delentry"
if [ -n "$INTERACTIVE" ]; then
ask_yesno "Voulez-vous néanmoins utiliser ldapmodify?" N || return
fi
fi
local args nop cont debug verbose tls
if parse_opts \
-n nop \
-c cont \
-d: debug= \
-v verbose \
-Z tls \
@ args -- "$@"; then
set -- "${args[@]}"
else
eerror "$args"
return 1
fi
local -a lsargs=(ldapmodify -x ${LDAPURI:+-H "$LDAPURI"} ${tls:+-Z}
${nop:+-n} ${cont:+-c} ${debug:+-d "$debug"} ${verbose:+-v})
local msg="${lsargs[*]}"
if [ -n "$BINDDN" ]; then
lsargs=("${lsargs[@]}" -D "$BINDDN" -w "$PASSWORD")
msg="$msg -D $BINDDN -w ****"
fi
estep "$msg"
"${lsargs[@]}" -f "$WSFILE"
EXITCODE=$?
return $EXITCODE
}
function uldap_ldapadd() {
local args nop cont debug verbose tls
if parse_opts \
-n nop \
-c cont \
-d: debug= \
-v verbose \
-Z tls \
@ args -- "$@"; then
set -- "${args[@]}"
else
eerror "$args"
return 1
fi
local -a lsargs=(ldapadd -x ${LDAPURI:+-H "$LDAPURI"} ${tls:+-Z}
${nop:+-n} ${cont:+-c} ${debug:+-d "$debug"} ${verbose:+-v})
local msg="${lsargs[*]}"
if [ -n "$BINDDN" ]; then
lsargs=("${lsargs[@]}" -D "$BINDDN" -w "$PASSWORD")
msg="$msg -D $BINDDN -w ****"
fi
estep "$msg"
"${lsargs[@]}" -f "$WSFILE"
EXITCODE=$?
return $EXITCODE
}
function uldap_ldapdelete() {
local args nop cont debug verbose recursive tls
if parse_opts \
-n nop \
-c cont \
-d: debug= \
-v verbose \
-r recursive \
-Z tls \
@ args -- "$@"; then
set -- "${args[@]}"
else
eerror "$args"
return 1
fi
local -a lsargs=(ldapdelete -x ${LDAPURI:+-H "$LDAPURI"} ${tls:+-Z}
${nop:+-n} ${cont:+-c} ${debug:+-d "$debug"} ${verbose:+-v}
${recursive:+-r})
local msg="${lsargs[*]}"
if [ -n "$BINDDN" ]; then
lsargs=("${lsargs[@]}" -D "$BINDDN" -w "$PASSWORD")
msg="$msg -D $BINDDN -w ****"
fi
if [ -n "$*" ]; then
if [ -n "$INTERACTIVE" ]; then
ewarn "Vous êtes sur le point du supprimer ${recursive:+DE FACON RECURSIVE }les objets suivants: $*"
ask_yesno "Etes-vous sûr de vouloir continuer?" N || return
estep "$msg $*"
fi
"${lsargs[@]}" "$@"
EXITCODE=$?
else
if [ -n "$INTERACTIVE" ]; then
__show_summary
ewarn "Vous êtes sur le point de supprimer ${recursive:+DE FACON RECURSIVE }les DNs de l'espace de travail (un par ligne)"
ask_yesno "Etes-vous sûr de vouloir continuer?" N || return
estep "$msg"
fi
"${lsargs[@]}" -f "$WSFILE"
EXITCODE=$?
fi
return $EXITCODE
}
function uldap_undo() {
if has_wsfiles; then
pop_wsfile
__show_summary
fi
return 0
}
function uldap_ifok() {
if [ "$EXITCODE" -eq 0 ]; then
eval_cmdline "$(qvals "$@")"
else
return 0
fi
}
function uldap_iferror() {
if [ "$EXITCODE" -ne 0 ]; then
eval_cmdline "$(qvals "$@")"
else
return 0
fi
}
function uldap_skip() {
let SKIPCOUNT="${1:-1}"
[ $SKIPCOUNT -eq 0 ] && SKIPCOUNT=
return 0
}
function uldap_quit() {
exit "$EXITCODE"
}
################################################################################
# Shell
function display_help() {
uecho "$scriptname: Shell pour accéder à un serveur ldap
USAGE
$scriptname [options]
OPTIONS
-C profile
Sélectionner un profil de connexion. Par défaut, si l'option -H n'est
pas spécifiée, le premier profil est sélectionné.
-x Ne pas tenter de faire une connexion sur le profil par défaut si aucun
profil n'est sélectionné.
-f script
Lire les commandes depuis le script spécifié.
-n Avec un script donné en ligne de commande ou lu depuis un fichier, ne pas
ajouter automatiquement la commande print à la fin
-i Si un script est spécifié, passer en mode interactif après l'exécution
du script.
-e Forcer l'arrêt du script si une erreur se produit. C'est l'option par
défaut pour un script spécifié avec -f.
-l input.ldif
Charger le fichier input.ldif comme espace de travail initial
-H ldapuri
-D binddn
-w password
-b searchbase
-v var=value
COMMANDES
$COMMANDS_HELP"
}
PROFILES=(default)
default_PROFILE=(filter="(objectClass=*)" attrs="")
set_defaults uldap
AUTOPROFILE=1
parse_opts + "${PRETTYOPTS[@]}" \
--help '$exit_with display_help' \
-C: lprofile \
-x AUTOPROFILE= \
-f: lscriptfile \
-n AUTOPRINT= \
-i INTERACTIVE=1 \
-e lexit_on_error \
-l: loutput \
-H: lldapuri \
-D: lbinddn \
-w: lpassword \
-b: lsearchbase \
-v: varcmds \
@ args -- "$@" && set -- "${args[@]}" || die "$args"
[ -n "$lexit_on_error" ] && set_values exit_on_error=true
# choix du profil
if [ -n "$lprofile" ]; then
# un profil spécifique
if ! uldap_set profile="$lprofile"; then
is_yes "$EXIT_ON_ERROR" && exit 255
fi
elif [ -z "$lldapuri" -a -n "$AUTOPROFILE" ]; then
# choisir le profil par défaut
if ! uldap_set profile="${PROFILES[0]}"; then
is_yes "$EXIT_ON_ERROR" && exit 255
fi
fi
if [ -n "$lldapuri" ]; then
# augmenter le profil des paramètres de la ligne de commande
if ! uldap_set ldapuri="$lldapuri" ${lbinddn:+binddn="$lbinddn"} ${lpassword:+password="$lpassword"}; then
is_yes "$EXIT_ON_ERROR" && exit 255
fi
fi
[ -n "$lsearchbase" ] && set_values searchbase="$lsearchbase"
# définitions de variables
for varcmd in "${varcmds[@]}"; do
set_values "$varcmd" || eerror "impossible de spécifier la valeur de ${varcmd%%=*}"
done
# script
lcmds=()
if [ -n "$*" ]; then
[ "$INTERACTIVE" == auto ] && INTERACTIVE= || AUTOPRINT=
INTERACTIVE_AFTER_CMDS="$INTERACTIVE"
INTERACTIVE=
[ "$EXIT_ON_ERROR" == auto ] && set_values exit_on_error=true
# splitter sur les lignes
array_from_lines lcmds "$(qvals "$@")"
[ -n "$AUTOPRINT" ] && array_add lcmds print
elif [ -n "$lscriptfile" ]; then
[ -f "$lscriptfile" ] || die "$lscriptfile: fichier introuvable"
[ "$INTERACTIVE" == auto ] && INTERACTIVE= || AUTOPRINT=
INTERACTIVE_AFTER_CMDS="$INTERACTIVE"
INTERACTIVE=
[ "$EXIT_ON_ERROR" == auto ] && set_values exit_on_error=true
# splitter sur les lignes
array_from_lines lcmds "$(<"$lscriptfile" filter_comment -m)"
[ -n "$AUTOPRINT" ] && array_add lcmds print
elif [ "$INTERACTIVE" == auto ]; then
isatty && INTERACTIVE=1 || INTERACTIVE=
fi
[ "$EXIT_ON_ERROR" == auto ] && set_values exit_on_error=
function run_cmds() {
# lancer toutes les commandes qui sont dans le tableau $1
local -a __rc_cmds
local cmd
array_copy __rc_cmds "${1:-cmds}"
for cmd in "${__rc_cmds[@]}"; do
# sauter éventuellement des commandes
[ "$SKIPCOUNT" == "0" ] && SKIPCOUNT=
if [ -n "$SKIPCOUNT" ]; then
let SKIPCOUNT=$SKIPCOUNT-1
continue
fi
eval_cmdline "$cmd"
done
}
# espace de travail initial
[ -n "$loutput" ] && uldap_load "$loutput"
# commandes non interactives
if [ -n "${lcmds[*]}" ]; then
run_cmds lcmds
INTERACTIVE="$INTERACTIVE_AFTER_CMDS"
fi
# commandes interactives
if [ -n "$INTERACTIVE" ]; then
while true; do
# construire le prompt
prompt=
tmp="$(reldn "$BINDDN")"
prompt="$prompt${tmp:+[$tmp]}"
prompt="$prompt$(get_color y)"
if [ -n "$LDAPURI" ]; then
split_ldapuri "$LDAPURI" proto host port || break
if [ -n "$host" ]; then
if [ "$proto" != "ldap" -a "$proto" != "ldaps" ]; then
prompt="$prompt$proto://"
fi
prompt="$prompt$host"
if [ -n "$port" -a "$port" != "389" ]; then
prompt="$prompt:$port"
fi
else
prompt="$prompt(default)"
fi
else
prompt="${prompt}(default)"
fi
prompt="$prompt$(get_color z)"
tmp="$(pdn "$SEARCHBASE")"
[ -n "$FILTER" -o -n "$ATTRS" ] && tmp="$tmp?$ATTRS??$FILTER"
prompt="$prompt${tmp:+ $tmp}"
eecho "$prompt"
# lire la commande
prompt="$WSMODE"
has_wsfiles && prompt="$prompt$(count_wsfiles)"
read -p "$prompt> " ${EDITLAST:+-i "$LASTCMD"} -e cmds || break
EDITLAST=
[ -n "$cmds" ] || continue
# lancer les commandes
run_cmds cmds
done
echo
fi
exit "$EXITCODE"