#!/bin/bash
# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8
source "$(dirname "$0")/lib/ulib/ulib" || exit 255
urequire DEFAULTS 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 }$(quoted_arg "$__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
            set_var "$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
}$(set_var_cmd "$__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 "$(quoted_args "$@")"
    else
        return 0
    fi
}

function uldap_iferror() {
    if [ "$EXITCODE" -ne 0 ]; then
        eval_cmdline "$(quoted_args "$@")"
    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 "$(quoted_args "$@")"
    [ -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"