// See the file "COPYING" in the main distribution directory for copyright.

#include "zeek/analyzer/protocol/rpc/MOUNT.h"

#include "zeek/zeek-config.h"

#include <algorithm>
#include <vector>

#include "zeek/Event.h"
#include "zeek/NetVar.h"
#include "zeek/ZeekString.h"
#include "zeek/analyzer/protocol/rpc/XDR.h"
#include "zeek/analyzer/protocol/rpc/events.bif.h"

namespace zeek::analyzer::rpc {
namespace detail {

bool MOUNT_Interp::RPC_BuildCall(RPC_CallInfo* c, const u_char*& buf, int& n) {
    if ( c->Program() != 100005 )
        Weird("bad_RPC_program", util::fmt("%d", c->Program()));

    uint32_t proc = c->Proc();
    // The call arguments, depends on the call type obviously ...
    RecordValPtr callarg;

    switch ( proc ) {
        case BifEnum::MOUNT3::PROC_NULL: break;

        case BifEnum::MOUNT3::PROC_MNT: callarg = mount3_dirmntargs(buf, n); break;

        case BifEnum::MOUNT3::PROC_UMNT: callarg = mount3_dirmntargs(buf, n); break;

        case BifEnum::MOUNT3::PROC_UMNT_ALL: callarg = mount3_dirmntargs(buf, n); break;

        default:
            if ( proc < BifEnum::MOUNT3::PROC_END_OF_PROCS ) {
                // We know the procedure but haven't implemented it.
                // Otherwise DeliverRPC would complain about
                // excess_RPC.
                n = 0;
            }
            else
                Weird("unknown_MOUNT_request", util::fmt("%u", proc));

            // Return 1 so that replies to unprocessed calls will still
            // be processed, and the return status extracted.
            return true;
    }

    if ( ! buf )
        // There was a parse error while trying to extract the call arguments.
        return false;

    c->AddVal(callarg);
    return true;
}

bool MOUNT_Interp::RPC_BuildReply(RPC_CallInfo* c, BifEnum::rpc_status rpc_status, const u_char*& buf, int& n,
                                  double start_time, double last_time, int reply_len) {
    EventHandlerPtr event = nullptr;
    ValPtr reply;
    BifEnum::MOUNT3::status_t mount_status = BifEnum::MOUNT3::MNT3_OK;
    bool rpc_success = (rpc_status == BifEnum::RPC_SUCCESS);

    // Reply always starts with the MOUNT status.
    if ( rpc_success ) {
        if ( n >= 4 )
            mount_status = (BifEnum::MOUNT3::status_t)extract_XDR_uint32(buf, n);
        else
            mount_status = BifEnum::MOUNT3::MOUNT3ERR_UNKNOWN;
    }

    if ( mount_reply_status ) {
        auto vl = event_common_vl(c, rpc_status, mount_status, start_time, last_time, reply_len, 0);
        analyzer->EnqueueConnEvent(mount_reply_status, std::move(vl));
    }

    if ( ! rpc_success ) {
        // We set the buffer to NULL, the function that extract the
        // reply from the data stream will then return empty records.
        //
        buf = nullptr;
        n = 0;
    }

    switch ( c->Proc() ) {
        case BifEnum::MOUNT3::PROC_NULL: event = mount_proc_null; break;

        case BifEnum::MOUNT3::PROC_MNT:
            reply = mount3_mnt_reply(buf, n, mount_status);
            event = mount_proc_mnt;
            break;

        case BifEnum::MOUNT3::PROC_UMNT:
            n = 0;
            mount_status = BifEnum::MOUNT3::MNT3_OK;
            event = mount_proc_umnt;
            break;

        case BifEnum::MOUNT3::PROC_UMNT_ALL:
            n = 0;
            mount_status = BifEnum::MOUNT3::MNT3_OK;
            event = mount_proc_umnt;
            break;

        default:
            if ( c->Proc() < BifEnum::MOUNT3::PROC_END_OF_PROCS ) {
                // We know the procedure but haven't implemented it.
                // Otherwise DeliverRPC would complain about
                // excess_RPC.
                n = 0;
                reply = BifType::Enum::MOUNT3::proc_t->GetEnumVal(c->Proc());
                event = mount_proc_not_implemented;
            }
            else
                return false;
    }

    if ( rpc_success && ! buf ) {
        // There was a parse error.
        reply = nullptr;
        return false;
    }

    // Note: if reply == 0, it won't be added to the val_list for the
    // event. While we can check for that on the policy layer it's kinda
    // ugly, because it's contrary to the event prototype. But having
    // this optional argument to the event is really helpful. Otherwise I
    // have to let reply point to a RecordVal where all fields are
    // optional and all are set to 0 ...
    if ( event ) {
        auto request = c->TakeRequestVal();

        auto vl =
            event_common_vl(c, rpc_status, mount_status, start_time, last_time, reply_len, (bool)request + (bool)reply);

        if ( request )
            vl.emplace_back(std::move(request));

        if ( reply )
            vl.emplace_back(reply);

        analyzer->EnqueueConnEvent(event, std::move(vl));
    }

    return true;
}

Args MOUNT_Interp::event_common_vl(RPC_CallInfo* c, BifEnum::rpc_status rpc_status,
                                   BifEnum::MOUNT3::status_t mount_status, double rep_start_time, double rep_last_time,
                                   int reply_len, int extra_elements) {
    // Returns a new val_list that already has a conn_val, and mount3_info.
    // These are the first parameters for each mount_* event ...
    Args vl;
    vl.reserve(2 + extra_elements);
    vl.emplace_back(analyzer->ConnVal());
    auto auxgids = make_intrusive<VectorVal>(id::index_vec);

    for ( size_t i = 0; i < c->AuxGIDs().size(); ++i ) {
        auxgids->Assign(i, val_mgr->Count(c->AuxGIDs()[i]));
    }

    auto info = make_intrusive<RecordVal>(BifType::Record::MOUNT3::info_t);
    info->Assign(0, BifType::Enum::rpc_status->GetEnumVal(rpc_status));
    info->Assign(1, BifType::Enum::MOUNT3::status_t->GetEnumVal(mount_status));
    info->AssignTime(2, c->StartTime());
    info->AssignInterval(3, c->LastTime() - c->StartTime());
    info->Assign(4, c->RPCLen());
    info->AssignTime(5, rep_start_time);
    info->AssignInterval(6, rep_last_time - rep_start_time);
    info->Assign(7, reply_len);
    info->Assign(8, c->Uid());
    info->Assign(9, c->Gid());
    info->Assign(10, c->Stamp());
    info->Assign(11, c->MachineName());
    info->Assign(12, std::move(auxgids));

    vl.emplace_back(std::move(info));
    return vl;
}

EnumValPtr MOUNT_Interp::mount3_auth_flavor(const u_char*& buf, int& n) {
    BifEnum::MOUNT3::auth_flavor_t t = (BifEnum::MOUNT3::auth_flavor_t)extract_XDR_uint32(buf, n);
    auto rval = BifType::Enum::MOUNT3::auth_flavor_t->GetEnumVal(t);
    return rval;
}

StringValPtr MOUNT_Interp::mount3_fh(const u_char*& buf, int& n) {
    int fh_n;
    const u_char* fh = extract_XDR_opaque(buf, n, fh_n, 64);

    if ( ! fh )
        return nullptr;

    return make_intrusive<StringVal>(new String(fh, fh_n, false));
}

StringValPtr MOUNT_Interp::mount3_filename(const u_char*& buf, int& n) {
    int name_len;
    const u_char* name = extract_XDR_opaque(buf, n, name_len);

    if ( ! name )
        return nullptr;

    return make_intrusive<StringVal>(new String(name, name_len, false));
}

RecordValPtr MOUNT_Interp::mount3_dirmntargs(const u_char*& buf, int& n) {
    auto dirmntargs = make_intrusive<RecordVal>(BifType::Record::MOUNT3::dirmntargs_t);
    dirmntargs->Assign(0, mount3_filename(buf, n));
    return dirmntargs;
}

RecordValPtr MOUNT_Interp::mount3_mnt_reply(const u_char*& buf, int& n, BifEnum::MOUNT3::status_t status) {
    auto rep = make_intrusive<RecordVal>(BifType::Record::MOUNT3::mnt_reply_t);

    if ( status == BifEnum::MOUNT3::MNT3_OK ) {
        rep->Assign(0, mount3_fh(buf, n));

        auto auth_flavors_count_in_reply = extract_XDR_uint32(buf, n);
        auto auth_flavors_count = auth_flavors_count_in_reply;
        const auto max_auth_flavors = 32u;

        if ( auth_flavors_count_in_reply > max_auth_flavors ) {
            Weird("excessive_MNT_auth_flavors");
            auth_flavors_count = max_auth_flavors;
        }

        auto enum_vector = make_intrusive<VectorType>(BifType::Enum::MOUNT3::auth_flavor_t);
        auto auth_flavors = make_intrusive<VectorVal>(std::move(enum_vector));

        for ( auto i = 0u; i < auth_flavors_count; ++i )
            auth_flavors->Assign(auth_flavors->Size(), mount3_auth_flavor(buf, n));

        if ( auth_flavors_count_in_reply > max_auth_flavors )
            // Prevent further "excess RPC" weirds
            n = 0;

        rep->Assign(1, std::move(auth_flavors));
    }

    return rep;
}

} // namespace detail

MOUNT_Analyzer::MOUNT_Analyzer(Connection* conn) : RPC_Analyzer("MOUNT", conn, new detail::MOUNT_Interp(this)) {
    orig_rpc = resp_rpc = nullptr;
}

void MOUNT_Analyzer::Init() {
    RPC_Analyzer::Init();

    if ( Conn()->ConnTransport() == TRANSPORT_TCP ) {
        orig_rpc = new Contents_RPC(Conn(), true, interp);
        resp_rpc = new Contents_RPC(Conn(), false, interp);
        AddSupportAnalyzer(orig_rpc);
        AddSupportAnalyzer(resp_rpc);
    }
}

} // namespace zeek::analyzer::rpc
