/*  Authentication functions, to keep everything secure

    ©1998, 1999 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.
*/

/*
   AmigaOS has no intrinsic concept of permissions, other than the single
    user ones. Therefore it's up to us to reimplement Unix-style
    protection throughout. There are many gotchas we have to take
    care of, and protection probably needs to be more integrated.
*/

/*
    24-Aug-1999 - Some changes to the semantics of root's permissions.
    30-Nov-1999 - Changes to support new RPC code.
*/

#include "auth.h"
#include "rpc.h"
#include "nfs.h"
#include "config.h"
#include "handle_list.h"

#include <dos/dos.h>

#include <proto/socket.h>
#include <stdio.h>

#include <dos/dos.h>
#include <exec/lists.h>

BOOL allow_root = FALSE; /* This may be changed by nfsd.c */
BOOL allow_nonpriv_port = FALSE;

/* This is the One True definition of being root - a UID of zero */
#define ROOT_UID (0)

extern struct List cfg_list; /* In config.c */
extern struct FileInfoBlock fib; /* In nfs_functions.c */

BOOL is_group_member(UWORD gid, struct authunix *auth);

/* A simple front-end that does the most common checkings
    It assumes the file has been Examine()'d */
nfsstat check_permission(struct Call *call, struct LocalFile *lf,
    ULONG permission)
{
    nfsstat status;

    /* First, is this allowed by the exports? */
    status = does_mount_permit(call, lf->lf_Name, permission);

    /* Does the file itself allow this operation? */
    if (status == NFS_OK) {
        status = does_file_allow(call,
            (lf->lf_ForcedUID != -1) ? lf->lf_ForcedUID : fib.fib_OwnerUID,
            (lf->lf_ForcedGID != -1) ? lf->lf_ForcedGID : fib.fib_OwnerGID,
            ((lf->lf_ForcedProtection != -1)
                ? lf->lf_ForcedProtection
                : fib.fib_Protection),
            permission);
    }else
        puts("Export file does not allow that operation");

    return status;
}

/* Is this operation allowed according to the user-configured exports? */
nfsstat does_mount_permit(struct Call *call,
    STRPTR path, ULONG permission)
{
    nfsstat result = NFSERR_ACCES;

    struct ConfigEntry *ce;

    struct sockaddr_in *client_addr = &(call->c_from);

    /* First of all, is this from a non-privileged port? */
    if ((ntohs(client_addr->sin_port) >= IPPORT_RESERVED)
        && !allow_nonpriv_port)
    {
        return result;
    }

    ce = cfg_list.lh_Head;

    while (ce->ce_Node.mln_Succ) {
        /* Does this entry concern this path? */

        /* Is ce->ce_Path a prefix of path? */
        if ((STRPTR)strstr(path, ce->ce_Path) == path) {
            /* Does this line export to this IP address? */
            if (0 == ((ntohl(client_addr->sin_addr.s_addr) ^ ce->ce_ExportIP) & ce->ce_IPMask)) {
                /* Is this export as writeable as needs be? */
                switch (permission) {
                    case ALLOW_READ:
                    case ALLOW_GETATTRS:
                        return NFS_OK;

                    case ALLOW_WRITE:
                    case ALLOW_SETATTRS:
                       if (ce->ce_ReadWrite)
                            return NFS_OK;
                       /* Otherwise, keep trying, but if we fail then this is why */
                       result = NFSERR_ROFS;
                       break;

                    default:
                        result = NFSERR_ACCES; /* WHAT are they trying? */
                }
            }
        }

        ce = (struct ConfigEntry *)ce->ce_Node.mln_Succ;
    }

    /* We've failed, but this error code should explain why */
    return result;
}

#define ID_MATCH(a,b) (((a) == (b)) && (allow_root || ((a) != 0)))

/* Is this operation allowed according to file ownership and
    permissions? */
nfsstat does_file_allow(struct Call *call, UWORD uid, UWORD gid,
    LONG prot, ULONG permission)
{
    struct authunix *auth = &(call->c_auth);

    /* We need UNIX authentication for this - otherwise we reject */
    if (!auth->isUnix)
        return NFSERR_ACCES;

    #define RV(x) ((x)?NFS_OK:NFSERR_ACCES)

    /* First of all, are they root? (And are we accepting root access?) */
    if (allow_root && (uid == ROOT_UID)) {
        switch (permission) {
            case ALLOW_READ:
            case ALLOW_WRITE:
            case ALLOW_GETATTRS:
            case ALLOW_SETATTRS:
                return RV(TRUE); /* root can do anything */

            default:
                return RV(FALSE);
       }
    }

    /* Now, do they own this file? */
    if (ID_MATCH(uid, auth->uid)) {
        switch (permission) {
            case ALLOW_READ:
                return RV((prot & FIBF_READ) == 0);

            case ALLOW_WRITE:
                return RV((prot & FIBF_WRITE) == 0);

            case ALLOW_GETATTRS:
            case ALLOW_SETATTRS:
                return RV(TRUE);

            default:
                return RV(FALSE);
        }
    }

    /* Soo... are they a member of the group? */
    if (is_group_member(gid, auth)) {
        switch (permission) {
            case ALLOW_READ:
            case ALLOW_GETATTRS:
                return RV(prot & FIBF_GRP_READ);

            case ALLOW_WRITE:
                return RV(prot & FIBF_GRP_WRITE);

            case ALLOW_SETATTRS: /* I'm not sure about this one... */
                return RV(FALSE);

            default:
                return RV(FALSE);
        }
    }

    /* Then they're "other" */
    switch (permission) {
        case ALLOW_READ:
        case ALLOW_GETATTRS:
            return RV(prot & FIBF_OTR_READ);

        case ALLOW_WRITE:
            return RV(prot & FIBF_OTR_WRITE);

        case ALLOW_SETATTRS: /* Nor this one... */
            return RV(FALSE);

        default:
            return RV(FALSE);
    }

    #undef RV
}

BOOL is_group_member(UWORD gid, struct authunix *auth)
{
    int i;

    /* First, is it their primary group? */

    if (ID_MATCH(gid, auth->gid))
        return TRUE;

    /* Is it one of their supplementary groups? */
    for (i = 0 ; i < auth->numGids ; i++) {
        if (ID_MATCH(gid, auth->gids[i]))
            return TRUE;
    }

    /* Nope - not a member */
    return FALSE;
}

#undef ID_MATCH

/* This gets the owner we should attribute this request to */
LONG owner_of(struct Call *call)
{
    if (call && call->c_auth.isUnix) {
        return ((call->c_auth.uid << 16) | call->c_auth.gid);
    }

    /* Fall through, and give it to root */
    return ROOT_UID;
}

/* This summarises the Unix authentication being offered */
void print_auth(struct Call *call)
{
    struct sockaddr_in *addr = &(call->c_from);

    /* This could probably be done in a more elegant fashion */
    printf("%s:%d ", Inet_NtoA(addr->sin_addr.s_addr), ntohs(addr->sin_port));

    /* Hey - let's summarise the authentication! */
    struct authunix *auth = &(call->c_auth);
    if (auth->isUnix) {
        printf("[UID %ld, GID %ld, %ld other groups]\n",
            auth->uid, auth->gid, auth->numGids);

/* This bit is a little too verbose... */
/*        for (int i = 0 ; i < auth->numGids ; i++) {
              printf("Member of %d\n", auth->gids[i]);
          } */
    } else
        puts("[Was not AUTH_UNIX]");

    /* If this is set some routines may wish to avoid printing it again */
    call->c_hasBeenPrinted = TRUE;
}

/* Print authorisation, unless it has already been printed */
void print_auth_once(struct Call *call)
{
    if (!call->c_hasBeenPrinted) {
        print_auth(call);
    }
}

/* Impose any constraints dictated by the configuration on this LocalFile */
void impose_configuration(struct LocalFile *lf)
{
    struct ConfigEntry *ce;
    STRPTR path;

    if (!lf)
        return;

    path = lf->lf_Name;

    ce = cfg_list.lh_Head;

    while (ce->ce_Node.mln_Succ) {
        /* Is ce->ce_Path a prefix of path? */
        if ((STRPTR)strstr(path, ce->ce_Path) == path) {
            /* Is this configuration entry ForceAll'd? */
            if (ce->ce_ForceAll) {
                lf->lf_ForcedUID = ce->ce_UID;
                lf->lf_ForcedGID = ce->ce_GID;
                lf->lf_ForcedProtection = ce->ce_Protection;
            }
        }
        ce = (struct ConfigEntry *)ce->ce_Node.mln_Succ;
    }
}

