/*  Receives, decodes the headers and delegates incoming RPC packets

    ©1999,2000 Joseph Walton

    This software is distributed under the terms of the GNU General Public
    License; either version 2 of the License, or (at your option) any
    later version.
*/

/*
    30-Jan-2000 - Now listens to signals, rather than treating them
                    as any other failure.
    21-Feb-2000 - Actually reloads configuration now.
*/

#include "nfsd.h"

#include "rpc.h"
#include "nfs.h"
#include "mount.h"
#include "pcnfsd.h"

#include "memory.h"
#include "handle_list.h"
#include "util.h"
#include "file_cache.h"

#include <stdio.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <dos/dos.h> /* SIGBREAKF_#? */

#include <proto/exec.h>
#include <proto/dos.h>
#include <proto/socket.h>
#include <proto/timer.h>

/* How frequently do we check for unused handles and files? */
#define ATTEMPT_FLUSH_AFTER 60

static int handlePacket(int32 *ptr, int32 *limit, struct sockaddr_in *from);

void service(int s)
{
    BOOL check_handles_pending = TRUE;
    struct timeval last_flushed;

    GetSysTime(&last_flushed);

    while (TRUE) {
        ULONG signal_mask = SIGBREAKF_CTRL_C | SIGBREAKF_CTRL_E | SIGBREAKF_CTRL_F;
        int l;
        fd_set fdset;
        FD_ZERO(&fdset);
        FD_SET(s, &fdset);

        if (check_handles_pending) {
            struct timeval timeout = {ATTEMPT_FLUSH_AFTER, 0};
            DEBUG(puts("Waiting for a request with a timeout"));
            l = WaitSelect(FD_SETSIZE, &fdset, NULL, NULL, &timeout, &signal_mask);
        } else {
            DEBUG(puts("Waiting for a request without timeout"));
            l = WaitSelect(FD_SETSIZE, &fdset, NULL, NULL, NULL, &signal_mask);
        }

        if (l < 0) {
            printSocketError("WaitSelect() failed");
            break;
        }

        if (l > 0) {
            struct sockaddr_in remote_sa;

            int addr_len = sizeof(remote_sa);
            l = recvfrom(s, (char *)global.recv_buffer, RECV_BUFFER_BYTES, 0, &remote_sa, &addr_len);

            if (l < 0) {
                printSocketError("recvfrom() failed");
                break;
            }

            DEBUG(printf("Packet received: %d bytes\n", l));

            /* Scale it down */
            l /= SINT32;

            if (addr_len == sizeof(remote_sa)) {
                int response_len =
                    handlePacket((int32 *)global.recv_buffer, (int32 *)global.recv_buffer + l, (struct sockaddr *)&remote_sa);
                if (response_len >= 0) {
                    int res = sendto(s, (char *)global.send_buffer, response_len * SINT32, 0, (struct sockaddr *)&remote_sa, sizeof(remote_sa));
                    DEBUG(printf("Response sent: %d bytes\n", res));
                    if (res < 0) {
                        printSocketError("Could not send");
                    }
                }

                /* That may have had some effect on the handle list */
                check_handles_pending = TRUE;
            } else {
                printf("Unexpected address length - %d\n", addr_len);
            }
        } else { /* WaitSelect() returned zero - a timeout or a signal */

            /* Have we been explicitly killed? */
            if (signal_mask & SIGBREAKF_CTRL_C) {
                puts("SIGBREAKF_CTRL_C");
                break; /* Quit out */
            }

            /* Was a configuration reload requested? */
            if (signal_mask & SIGBREAKF_CTRL_F) {
                puts("Reloading configuration...");
                if (!configuration_load()) {
                    puts("Failed to load - configuration is now empty");
                } else {
                    puts("...done");
                }
            }

            /* Did they ask to close all open files? */
            if (signal_mask & SIGBREAKF_CTRL_E) {
                if (global.verbose) {
                    puts("Closing all open file...");
                }
                cfCloseAll();
                puts("Closed all open files");
            }
        }

        /* Keep a periodic check on resources */
        {
            struct timeval time_now;
            GetSysTime(&time_now);
            SubTime(&time_now, &last_flushed);
            if (time_now.tv_secs > ATTEMPT_FLUSH_AFTER) {
                if (global.verbose) {
                    puts("Flushing unused NFS handles...");
                }
                check_handles_pending = flush_handles();

                if (global.verbose) {
                    puts("Flushing cached open files");
                }
                check_handles_pending |= cfFlush();

                GetSysTime(&last_flushed);
            }
        }
    }
}

static int handlePacket(int32 *ptr, int32 *limit, struct sockaddr_in *from)
{
    int32 id;
    int l;

    struct Call call;

    int32 *rptr = global.send_buffer;
    int32 *rlimit = rptr + (SEND_BUFFER_BYTES / SINT32);

    if ((limit - ptr) < 6)
        return -1;

    call.c_hasBeenPrinted = FALSE;

    // Assignment of structures
    call.c_from = *from;

    id = G_UINT(*ptr++);

    /* One could, in theory, keep track of IDs and the corresponding
        responses for swift retransmissions of repeated requests.
       This is not done here, as it would require a fairly complex
        cache and a fair bit of memory management, and would only benefit
        us if packets were regularly getting lost on the way to the client.
    */

    if (G_ENUM(*ptr++) != CALL) { // This is a call?
        // We should simply ignore this silently
        return -1;
    }

    if (G_UINT(*ptr++) != RPC_VERSION) {
        /* Requires 6 int32s */
        *rptr++ = P_UINT(id);
        *rptr++ = P_ENUM(REPLY);
        *rptr++ = P_ENUM(MSG_DENIED);
        *rptr++ = P_ENUM(RPC_MISMATCH);
        *rptr++ = P_UINT(RPC_VERSION);
        *rptr   = P_UINT(RPC_VERSION);

        return 6;
    }

    call.c_program = G_UINT(*ptr++);
    call.c_version = G_UINT(*ptr++);
    call.c_procedure = G_UINT(*ptr++);

//    printf("Program: %d, Version: %d, Procedure: %d\n", call.c_program,
//        call.c_version, call.c_procedure);

    /* Authorisation */
    ptr = readAuth(ptr, limit, &call.c_auth);
    if (!ptr)
        return -1;

    if ((limit - ptr) < 2)
        return -1;

    /* We're not bothered about the verifier */
    *ptr++;
    l = G_UINT(*ptr++);
    ptr += INT32S(l);
    if (ptr >= limit) /* Check we've not overcommitted */
        return -1;

/* This block of code rejects the request if it doesn't have Unix
    authentication. In fact there's no need to do this yet -
    we only fail if they then try to perform a privileged action.
    As such it's just here for demonstration purposes.
*/
#if 0
    /* Check what authentication we got */
    if (call.c_auth.isUnix) {
        printf(" Name: %s, UID: %u GID: %u (%u secondary)\n",
            call.c_auth.machineName, call.c_auth.uid,
            call.c_auth.gid, call.c_auth.numGids);
    } else {
        puts("Non-UNIX authentication!");

        /* Ironically, we'd fail DES as 'too weak' */
        *rptr++ = P_UINT(id);
        *rptr++ = P_ENUM(REPLY);
        *rptr++ = P_ENUM(MSG_DENIED);
        *rptr++ = P_ENUM(AUTH_ERROR);
        *rptr = P_ENUM(AUTH_TOOWEAK);
        return 5;
    }
#endif

    /* We've accepted the request */
    rptr = PrepareAcceptedHeader(id, rptr);
    // 5 int32s

    switch (call.c_program) {
        case NFS_PROGRAM:
            return nfs_procedure(&call, ptr, limit, rptr, rlimit);

        case MOUNT_PROGRAM:
            return mount_procedure(&call, ptr, limit, rptr, rlimit);

        case PCNFSD_PROGRAM:
            return pcnfsd_procedure(&call, ptr, limit, rptr, rlimit);

        default:
            *rptr = P_ENUM(PROG_UNAVAIL);
            return 6;
    }
}

