#!/usr/bin/env bash
#
# Copyright (C) 2024-2026 Bacula Systems SA
# License: BSD 2-Clause; see file LICENSE-FOSS
#
#
TestName="scram-test"
JobName=backup
. scripts/functions

scripts/cleanup
scripts/copy-confs

# Switch the trace files between each steps
current=0
switchtrace() {
    for i in $working/*.trace
    do
        cp $i $i.$current
        cp /dev/null $i
    done
    current=`expr $current + 1`
    out=$tmp/out.$current
}

# Revert to the original configuration file
# Can also restart the daemons with the "yes" parameter
revert() {
    restart="$1"
    for i in $tmp/*.conf
    do
        cp -f $i $conf/
    done
    if [ "$restart" = yes ]; then
        $bin/bacula-ctl-dir stop &
        $bin/bacula-ctl-sd stop &
        $bin/bacula-ctl-fd stop &
        wait
        wait
        wait
        $bin/bacula-ctl-dir start -T -d200,network
        $bin/bacula-ctl-sd start -T -d200,network
        $bin/bacula-ctl-fd start -T -d200,network
    fi
}

# Small report at the end
nbok=0
nberr=0
nbtotal=0
report() {
    print_debug "  ==============>  Got OK $nbok/$nbtotal  <==============="
}


# Look for a given string in a file. The current lookup is in the xxx-xx.trace
# file While the message printed will be about xxx-xx.trace.yy for futher
# analysis
findstr() {
    file="$1"
    str="$2"
    if echo  $file | grep .trace > /dev/null; then
        f=$file.$current
    else
        f=$file
    fi
    grep "$str" "$file" > /dev/null
    if [ $? -ne 0 ]; then
        print_debug "ERROR: should find '$str' in $f"
        estat=1
        nberr=`expr $nberr + 1`
    else
        print_debug "OK: '$str' in $f"
        nbok=`expr $nbok + 1`
    fi
    nbtotal=`expr $nbtotal + 1`
}

# Same as findstr, but we are looking that a string doesn't exist in the file
nfindstr() {
    file="$1"
    str="$2"
    if echo  $file | grep .trace > /dev/null; then
        f=$file.$current
    else
        f=$file
    fi
    grep "$str" "$file" > /dev/null
    if [ $? -eq 0 ]; then
        print_debug "ERROR: should NOT find '$str' in $f"
        estat=1
        nberr=`expr $nberr + 1`
    else
        print_debug "OK: '$str' not in $f"
        nbok=`expr $nbok + 1`
    fi
    nbtotal=`expr $nbtotal + 1`
}

# debug message
log() {
    print_debug ""
    print_debug "$*"
    print_debug "----------------------------------------------------------------"
}

#
# Zap out any schedule in default conf file so that
#  it doesn't start during our test
#
outf="$tmp/sed_tmp"
echo "s%  Schedule =%# Schedule =%g" >${outf}
cp $scripts/bacula-dir.conf $tmp/1
sed -f ${outf} $tmp/1 >$scripts/bacula-dir.conf

c=`$bperl -e 'get_client_name()'`
$bperl -e 'add_virtual_changer("Ach", 10)'
$bperl -e 'add_attribute("$conf/bacula-dir.conf", "NextPool", "Default", "Pool", "File")'
$bperl -e 'add_attribute("$conf/bacula-dir.conf", "Storage", "Ach", "Pool", "Default")'
$bperl -e 'add_attribute("$conf/bacula-dir.conf", "LabelFormat", "Ach", "Pool", "Default")'

cat <<EOF >> $conf/bacula-dir.conf
Job {
 Name = Mig
 Type = Migrate
 Client = $c
 FileSet = "Full Set"
 Pool = File
 Messages = Standard
 Maximum Concurrent Jobs = 2
 Selection Type = Job
 Selection Pattern = ".*"
 Storage = Ach
}
EOF
 
# backup all config files
cp $conf/*.conf $tmp/

change_jobname BackupClient1 $JobName
start_test

$bin/bacula-ctl-dir start -T -d200,network &
$bin/bacula-ctl-sd start -T -d200,network &
$bin/bacula-ctl-fd start -T -d200,network &
wait
wait
wait

# If we don't have GSASL, we cannot do so much tests
if grep "Have GSASL" build/config.out | grep yes > /dev/null
then
    have_gsasl=1
else
    have_gsasl=0
    log "WARNING libgsasl is not available in this build"
fi

log "Check if the bconsole can connect"
switchtrace
echo quit | $bin/bconsole -d200,network &> $out
findstr $out "1000 OK:"

if [ $have_gsasl = 1 ]; then
    findstr $out "SCRAM-SHA-256 respond for "

    switchtrace
    log "Configure CRAM-MD5 on bconsole"
    $bperl -e "add_attribute('$conf/bconsole.conf', 'AuthenticationMethods', 'CRAM-MD5', 'Director')"
    echo -e ".api 2\n.status dir header" | $bin/bconsole -d200,network &> $out
    findstr $out "1000 OK:"
    findstr $out "auth_algo=CRAM-MD5"
    findstr $out "auth cram-md5"
    findstr $out "scram-sha-256=true"

    switchtrace
    log "Configure SCRAM-SHA-256 on bconsole"
    $bperl -e "add_attribute('$conf/bconsole.conf', 'AuthenticationMethods', 'SCRAM-SHA-256', 'Director')"
    echo quit | $bin/bconsole -d200,network &> $out
    findstr $out "1000 OK:"
    findstr $out "SCRAM-SHA-256 respond for "
    findstr $out "auth_algo=SCRAM-SHA-256"

    log "Revert the bconsole back"
    cp $tmp/bconsole.conf $conf/bconsole.conf

    switchtrace
    log "Configure wrong password on bconsole (auth is not used, only TLS)"
    $bperl -e "add_attribute('$conf/bconsole.conf', 'Password', 'xxx', 'Director')"
    echo quit | $bin/bconsole -d200,network &> $out
    nfindstr $out "1000 OK:"
    nfindstr $out "SCRAM-SHA-256 respond for "
    findstr $out "Director authorization problem"

    switchtrace
    log "Configure wrong password on bconsole (without TLS)"
    $bperl -e "add_attribute('$conf/bconsole.conf', 'TLS PSK Enable', 'no', 'Director')"
    echo quit | $bin/bconsole -d200,network &> $out
    nfindstr $out "1000 OK:"
    findstr $out "SCRAM-SHA-256 respond for "
    findstr $out "You are not authorized"

    log "Revert the bconsole back"
    cp $tmp/bconsole.conf $conf/bconsole.conf

    switchtrace
    log "Test the FileDaemon"
    echo -e ".api 2\n.status client header" | $bin/bconsole &> $out
    findstr $out "started="
    findstr $working/*-fd.trace "Authentication negotiation OK"
    findstr $working/*-fd.trace "SCRAM-SHA-256 challenge"
    findstr $working/*-fd.trace "OK Authenticate"
    findstr $out "scram-sha-256=true"

    switchtrace
    log "Test the FileDaemon with only SCRAM-SHA-256"
    $bperl -e "add_attribute('$conf/bacula-dir.conf', 'AuthenticationMethods', 'SCRAM-SHA-256', 'Client')"
    echo reload | $bin/bconsole > /dev/null
    echo status client | $bin/bconsole  &> $out
    findstr $out "1000 OK:"
    findstr $working/*-dir.trace "SCRAM-SHA-256 respond for "
    findstr $working/*-dir.trace "auth_algo=SCRAM-SHA-256"
    nfindstr $out "CRAM-MD5 respond for "
    findstr $out "Daemon started"

    switchtrace
    log "Test the FileDaemon with CRAM-MD5 set on the FileDaemon"
    $bperl -e "add_attribute('$conf/bacula-fd.conf', 'AuthenticationMethods', 'CRAM-MD5', 'Director')"
    kill `cat $working/bacula-fd.*.pid`
    $bin/bacula-ctl-fd start -T -d200,network
    echo status client | $bin/bconsole  &> $out
    findstr $out "1000 OK:"
    findstr $working/*-dir.trace "Authentication negotiation failed"
    findstr $working/*-fd.trace "Authentication negotiation failed"
    nfindstr $out "Daemon started"

    sleep 6 # Wait to avoid orphan buffer
    revert

    switchtrace
    log "Test the FileDaemon without Director TLS"
    $bperl -e "add_attribute('$conf/bacula-dir.conf', 'TLS PSK Enable', 'no', 'Client')"
    echo reload | $bin/bconsole &> $out
    echo status client | $bin/bconsole  &>> $out
    findstr $out "1000 OK:"
    findstr $working/*-dir.trace "Authentication negotiation OK"
    findstr $working/*-dir.trace "TLSPSK Local need 0"
    findstr $working/*-dir.trace "TLSPSK Start CLEAR"
    findstr $out "Daemon started"

    switchtrace
    log "Test the FileDaemon without FD TLS"
    $bperl -e "add_attribute('$conf/bacula-fd.conf', 'TLS PSK Enable', 'no', 'Director')"
    kill `cat $working/bacula-fd.*.pid` &> $out
    $bin/bacula-ctl-fd start -T -d200,network &>> $out
    echo status client | $bin/bconsole  &>> $out
    findstr $out "1000 OK:"
    findstr $working/*-dir.trace "Authentication negotiation OK"
    findstr $working/*-dir.trace "TLSPSK Local need 0"
    findstr $working/*-dir.trace "TLSPSK Start CLEAR"
    findstr $out "Daemon started"

    switchtrace
    revert
    echo reload | $bin/bconsole > /dev/null
    
    switchtrace
    log "Test the FileDaemon with an incorrect password"
    $bperl -e "add_attribute('$conf/bacula-fd.conf', 'AuthenticationMethods', 'SCRAM-SHA-256,CRAM-MD5', 'Director')"
    $bperl -e "add_attribute('$conf/bacula-fd.conf', 'Password', 'xxx', 'Director')"
    kill `cat $working/bacula-fd.*.pid`
    $bin/bacula-ctl-fd start -T -d200,network
    echo status client | $bin/bconsole  &> $out
    findstr $out "1000 OK:"
    findstr $working/*-dir.trace "Authentication negotiation OK"
    nfindstr $out "Daemon started"

    switchtrace
    log "Test the Storage Daemon"
    echo -e ".api 2\n.status storage header" | $bin/bconsole &> $out
    findstr $out "started="
    findstr $out "scram-sha-256=true"

    switchtrace
    log "Test with CRAM-MD5"
    $bperl -e "add_attribute('$conf/bacula-dir.conf', 'AuthenticationMethods', 'CRAM-MD5', 'Autochanger')"
    echo reload | $bin/bconsole &> $out
    echo status storage | $bin/bconsole &>> $out
    findstr $out "Daemon started"
    findstr $working/*-dir.trace "Authentication negotiation OK, selected CRAM-MD5"
    findstr $working/*-sd.trace "auth cram-md5 challenge"
    findstr $working/*-dir.trace "stored: 1000 OK auth"

    switchtrace
    log "Test without SD/DIR TLS"
    $bperl -e "add_attribute('$conf/bacula-dir.conf', 'TLS PSK Enable', 'no', 'Autochanger')"
    echo reload | $bin/bconsole &> $out
    echo status storage | $bin/bconsole &>> $out
    findstr $out "Daemon started"
    findstr $working/*-dir.trace "Authentication negotiation OK, selected CRAM-MD5"
    findstr $working/*-sd.trace "auth cram-md5 challenge"
    findstr $working/*-dir.trace "stored: 1000 OK auth"
    findstr $working/*-dir.trace "TLSPSK Start CLEAR"

    switchtrace
    log "Test with incompatibility DIR/SD"
    $bperl -e "add_attribute('$conf/bacula-sd.conf', 'AuthenticationMethods', 'SCRAM-SHA-256', 'Director')"
    kill -9 `cat $working/bacula-sd.*.pid`
    $bin/bacula-ctl-sd start -T -d200,network
    echo status storage | $bin/bconsole &>> $out
    nfindstr $out "Daemon started"
    findstr $working/*-dir.trace "Authentication negotiation failed"
    nfindstr $working/*-dir.trace "stored: 1000 OK auth"

    switchtrace
    log "Test with incorrect password SD/DIR"
    $bperl -e "add_attribute('$conf/bacula-dir.conf', 'AuthenticationMethods', 'SCRAM-SHA-256,CRAM-MD5', 'Autochanger')"
    $bperl -e "add_attribute('$conf/bacula-dir.conf', 'Password', 'xxx', 'Autochanger')"
    echo reload | $bin/bconsole &> $out
    echo status storage | $bin/bconsole &>> $out
    nfindstr $out "Daemon started"
    findstr $out "Director unable to authenticate"

    switchtrace
    sleep 5
    revert yes
    log "Test FD/SD connection"
    echo "reload" | $bin/bconsole &> $out
    echo "status network" | $bin/bconsole &>> $out
    findstr $out "Running network test between"
    findstr $out "2000 OK"
    findstr $working/*fd.trace "SCRAM-SHA-256 respond for .SD"
    findstr $working/*sd.trace "SCRAM-SHA-256 challenge for .SD"
    findstr $working/*fd.trace "Authenticated with SD"

    switchtrace
    log "Test FD/SD connection with CRAM-MD5 on one side"
    $bperl -e "add_attribute('$conf/bacula-fd.conf', 'AuthenticationMethods', 'CRAM-MD5', 'Director')"
    kill `cat $working/bacula-fd.*.pid`
    $bin/bacula-ctl-fd start -T -d200,network
    echo "status network" | $bin/bconsole &>> $out
    findstr $out "Running network test between"
    findstr $out "2000 OK"

    export BACULA_REGRESS_DISABLE_SCRAM=1
    log "Test with SCRAM disabled on FD side"
    switchtrace
    revert
    echo "reload" | $bin/bconsole &> $out
    kill `cat $working/bacula-fd.*.pid`
    $bin/bacula-ctl-fd start -T -d200,network
    echo "status client" | $bin/bconsole &>> $out
    echo "status network" | $bin/bconsole &>> $out
    findstr $out "Running network test between"
    findstr $out "2000 OK"
    findstr $out "Daemon started"
    findstr $working/*-dir.trace "Authentication negotiation OK, selected CRAM-MD5"
    findstr $working/*-fd.trace "auth cram-md5 challenge"

    switchtrace
    log "Test with Copy/Migration"
    cat <<EOF > $tmp/bconcmds
run job=BackupClient1 yes
wait
run job=Mig yes
wait
messages
EOF
    run_bconsole
    findstr $working/*-sd.trace "Authentication negotiation OK"
    findstr $working/*-sd.trace "auth cram-md5 challenge"
fi

switchtrace
log "Stopping all daemons"
sleep 6 # Wait a bit to let all daemons cleanup
$bin/bacula-ctl-dir stop
$bin/bacula-ctl-sd stop
$bin/bacula-ctl-fd stop
report
end_test
