/*  Authentication functions, to keep everything secure

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

/*
   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.
    21-Feb-2000 - Another rejig of the authentication code.
*/


#include "nfsd.h"
#include "handle_list.h"
#include "util.h"
#include "auth.h"

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

#include <proto/socket.h>

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

static 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 : global.fib.fib_OwnerUID,
            (lf->lf_ForcedGID != -1) ? lf->lf_ForcedGID : global.fib.fib_OwnerGID,
            ((lf->lf_ForcedProtection != -1)
                ? lf->lf_ForcedProtection
                : global.fib.fib_Protection),
            permission);
    } else {
        /* It's probably somebody trying to write to a RO mount */
        print_auth_once(call);
        printf("Export file does not allow that operation - %s\n", reason(status));
    }

    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 sockaddr_in *client_addr = &(call->c_from);
    struct ConfigEntry *ce;

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

    ce = global.config.lh_Head;

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

        /* XXX Is ce->ce_Path a prefix of path? */
        if (0 == strnicmp(path, ce->ce_Path, strlen(ce->ce_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;
}

/* 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? */
    if (uid == ROOT_UID) {
        /* And are we accepting root access? */
        if (global.allow_root) {
            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);
           }
        }
        /* Fall through, subject them to `world' permissions */
    } else {
    /* They're not root - do they own this file? */
        if (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
}

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

    /* Is gid 0 special? It's hardly innocuous: the request is
        never considered to belong to group 0 if we're not allowing
        root
    */
    if ((gid == ROOT_UID) && !global.allow_root) {
        return FALSE;
    }

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

    if (gid == auth->gid)
        return TRUE;

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

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

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

    /* Fall through, and attribute 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);
    struct authunix *auth = &(call->c_auth);

    /* 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! */
    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;

    /* Is it already set properly? */
    if (lf->lf_CfgSerial == global.cfgSerial)
        return;

    path = lf->lf_Name;

    ce = global.config.lh_Head;

    while (ce->ce_Node.mln_Succ) {
        /* XXX Is ce->ce_Path a prefix of path? */
        if (0 == strnicmp(path, ce->ce_Path, strlen(ce->ce_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;
    }

    /* Note that it's now current */
    lf->lf_CfgSerial = global.cfgSerial;
}

