RCS_ID_C="$Id: usergroup.c,v 4.1 1994/05/16 14:09:07 ppessi Exp $";
/*
 * Author: ppessi <Pekka.Pessi@hut.fi>
 *
 * This file is part of the AmiTCP/IP UserGroup Library.
 *
 * Copyright © 1993 AmiTCP/IP Group, <AmiTCP-Group@hut.fi>
 *                  Helsinki University of Technology, Finland.
 *
 * Created      : Sun Nov 28 17:45:55 1993 ppessi
 * Last modified: Mon May 16 14:33:00 1994 ppessi
 *
 * $Log: usergroup.c,v $
 * Revision 4.1  1994/05/16  14:09:07  ppessi
 * Release 4
 *
 * Revision 3.1  1994/02/24  16:49:34  ppessi
 * Release 3.
 *
 * Revision 2.2  1994/02/17  02:21:58  ppessi
 * *** empty log message ***
 *
 * Revision 2.1  1994/01/23  03:23:23  ppessi
 * Cleaned documentation. Added licence.
 * Modified the errno pointer tags; there is now a separate tag
 * for each legal errno size.
 * Activated the UGT_OWNER code.
 *
 * Revision 1.3  1994/01/21  08:12:22  ppessi
 * Added _ProgramName as first argument of ug_SetupContext()
 *
 * Revision 1.2  1994/01/20  08:28:13  ppessi
 * Changed the ug_StrError slightly, an device error code is now also
 * accepted.
 *
 * Revision 1.1  94/01/19  10:05:08  ppessi
 * Initial revision
 *
 */

/****** usergroup.library/--background-- ***********************************

    WARNING
        Unfortunately, this experimental release of usergroup.library is not
        compatible with multiuser.library.  There are some problems with
        multiuser.library, eg. the multiuser.library does not support the
        real ids.  Also the password format is different, multiuser.library
        uses the AS225r2 password format, which is very simple encoding.
        The usergroup.library uses the standard Unix password encryption.

        The current implementation of this model is very simple.  All tasks
        belong to one session and they share common credentials.  The
        setsid() function call does nothing.  You are supposed to log in
        using "login -f login-name" when the machine is booted.

    PURPOSE
        When the AmiTCP/IP was originally released, a little attention was
        paid to the security aspects.  Since the AmigaOS is basically a
        single user operating system with little or no provisions for
        multiple users, there was no standard how accounts, password
        checking and access control should be implemented.

    USERGROUP.LIBRARAY SEMANTICS
        The usergroup.library provides a BSD-stylish interface to the user
        and group identification, the account database, the group databases,
        password checking and login information.  Since it is a shared
        library instead of link library, the underlying security mechanisms
        can be changed according future standards and needs.  The
        usergroup.library provides quite clean basic model. Each process has
        credentials, which consist of real used ID, real group ID, effective
        user ID and up to 32 effective group IDs. The process credentials
        can be changed with setuid()/setgid()/setgroups() functions.

        Each process belongs also to an session.  A new session will created
        with setsid() function call, which is typically executed before you
        call command or when you create a new connection.  A session
        contains the login name of the user and possibly some other
        information.

	The information about users logging in and out is typically stored
        into a file in Unix systems.  These files (in BSD Net2 release they
        are /var/run/utmp and /var/log/wtmp) are usually very long and
        contain holes.  Since the AmigaDOS files cannot contain holes, this
        approach is not practical.  The usergroup.library provides an
        loosely HP-UX-stylish interface to the utmp and lastlogin databases.
        The utmp database contains an entry for each session, it is searched
        in linear manner qith getutent().  The lastlogin database contains
        an entry for each user and getlastlogin() returns an entry for given
        UID.

        The usergroup.library does not directly depend on AmiTCP/IP.  It can
        be used with any program needing user identification, account and
        group databases.
	
    USING USERGROUP.LIBRARY
        Each time the usergroup.library is opened, it creates an new
        instance of the library base.  The library base contains the static
        data buffers used by many library functions.  The usergroup.library
        functions behave exactly like they were in link library. The
        functions allocate all resources for you, the library also frees the
        resources when they are no more needed.

	Since each library contains static data and resources allocated in
        the context of calling task (ie. signals), only the task which
        opened the library is allowed to call most library functions.
        However, any task whatsoever can call following functions:

            getuid() geteuid() getgid() getegid() getsid()

        These functions return the credentials of calling task.

        It is also possible to call following functions from any task.
        However, note that a non-owning tasks cannot recover error codes:

            getgroups() setreuid() setuid() setregid() setgid() setgroups() 
	    setsid() setlogin() 

        It is possible to give the library instance to another task.  Only
        the current owner can close the library.

        The user and group information is provided by netinfo.device.
        It is more convenient interface to user and group databases
        for multitasking applications.

    EXAMPLE PROGRAMS
        There are a few utilities provided as examples.  The finger programs
        deals with user (password), utmp and lastlog database, the id and
        whoami with user and group identification, login and passwd with
        password checking and password changing.

    SEE ALSO
        netinfo.device/--background--, ug_SetupContextTags(),
        SAS C Manual, libinit.c and libinitr.o

    COPYRIGHT
        Copyright © 1980--1991 The Regents of the University of California.
        Copyright © 1993, 1994 AmiTCP/IP-Group, <AmiTCP-Group@hut.fi>,
        Helsinki University of Technology, Finland.

****************************************************************************
*/

/****** usergroup.library/--Licence-- **************************************

   USERGROUP.LIBRARY LICENCE
        The usergroup.library is Copyright © 1993, 1994 AmiTCP/IP-Group,
        <AmiTCP-Group@hut.fi>, Helsinki University of Technology, Finland.

        The usergroup.library contains source code from 4.3BSD Net2 release.
        The 4.3BSD Net2 release is copyright © 1980 --- 1991 The Regents of
        the University of California.  The following licence apply to the
        usergroup.library and its documentation:

        Redistribution and use in source and binary forms, with or without
        modification, are permitted provided that the following conditions
        are met:

        1. Redistributions of source code must retain the above copyright
           notice, this list of conditions and the following disclaimer.
        2. Redistributions in binary form must reproduce the above copyright
           notice, this list of conditions and the following disclaimer in
           the documentation and/or other materials provided with the
           distribution.
        3. All advertising materials mentioning features or use of this
           software must display the following acknowledgement: This product
           includes software developed by the University of California,
           Berkeley and its contributors.
        4. Neither the name of the University nor the names of its
           contributors may be used to endorse or promote products derived
           from this software without specific prior written permission.

        THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS''
        AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
        TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
        PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR
        CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
        SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
        LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
        USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
        ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
        OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
        OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
        SUCH DAMAGE.

****************************************************************************
*/


#include "base.h"
#include "libfunc.h"

#include <exec/memory.h>

#include <assert.h>

#include <sys/errno.h>
#include <exec/errors.h>
#include "credential.h"

struct ExecBase *SysBase;
struct DosLibrary *DOSBase;
struct Library *UtilityBase;

#ifndef UTILITY_TAGITEM_H
#include <utility/tagitem.h>
#endif
#include <proto/utility.h>

extern struct Process*owner;
static void CleanupNIO(void);
int TimeInit(void);
void TimeCleanup(void);

ASM void __UserLibCleanup(void);

/*
 * Library initialization
 */
SAVEDS ASM int __UserLibInit(void)
{
  SysBase = *(struct ExecBase **)4;

  InitSemaphore(ni_lock);

  if ((DOSBase = (void *)OpenLibrary("dos.library", 37L)) &&
      (UtilityBase = OpenLibrary("utility.library", 37L)) &&
      TimeInit() == 0 && LRandomInit() == 0) {
    owner = (struct Process *)FindTask(NULL);
    Forbid();
    if (!(CredentialBase = OpenResource(CREDENTIALNAME))) {
      CredentialBase = CredentialInit("root");
    }
    Permit();
    if (CredentialBase)
      return 0;
  }

  __UserLibCleanup();

  return -1;
}

SAVEDS ASM void __UserLibCleanup(void)
{
  CleanupUTMP();
  CleanupNIO();
  TimeCleanup();
  if (DOSBase)
    CloseLibrary((void *)DOSBase);
  if (UtilityBase)
    CloseLibrary(UtilityBase);
}

/*
 * Errno handling
 */
ULONG break_mask = 0L;
int internal_errno;
void *errnop = NULL;
errnop_t errnosize;

/*
 * Set errno
 */
void SetErrno(int errno)
{
  internal_errno = errno;

  if (errnop)
    switch (errnosize) {
    case es_byte:
      *(UBYTE *)errnop = errno;
      break;
    case es_word:
      *(UWORD *)errnop = errno;
      break;
    case es_long:
      *(ULONG *)errnop = errno;
      break;
    }
}

/****** usergroup.library/ug_GetErr ****************************************

    NAME
        ug_GetErr - get current error code

    SYNOPSIS
        error = ug_GetErr(void)
         D0

        int ug_GetErr(void)

    FUNCTION
        Most usergroup.library functions return -1 to indicate an error.
        When this happens (or whatever the defined error return for the
        routine) this routine may be called to determine more information.
        The default startup function will redirect the error codes also into
        the global variable `errno'.

        Note: there is no guarantee as to the value returned from ug_GetErr()
        after a successful operation.

    RESULTS
        error - error code

    SEE ALSO
        ug_StrError(), ug_SetupContextTags(), dos.library/IoErr()

****************************************************************************
*/

/*
 * Get errno
 */
SAVEDS ASM int R_ug_GetErr(void)
{
  return internal_errno;
}

/*
 * Handle the netinfo device
 */
struct SignalSemaphore ni_lock[1];
struct Process *owner;
static struct MsgPort *niport;
static struct NetInfoReq *nireq;

static struct Device *nidevice[2];
static APTR niunit[2];
static APTR nibuffer[2];

struct NetInfoReq *OpenNIUnit(ULONG unit)
{
  /* Check ownership */
  if (owner != (struct Process *)FindTask(NULL)) {
    SetErrno(EDEADLK);
    return NULL;
  }

  /* Allocate port */
  if (niport == NULL) {
    niport = CreateMsgPort();
    if (niport == NULL)
      return NULL;
  }
  if (nireq == NULL) {
    nireq = CreateIORequest(niport, sizeof(*nireq));

    if (nireq == NULL)
      return NULL;
  }

  if (nidevice[unit]) {
    /* Already opened */
    nireq->io_Device = nidevice[unit];
    nireq->io_Unit = niunit[unit];
  } else {
    if (OpenDevice(NETINFONAME, unit, (struct IORequest *)nireq, 0L)) {
      return NULL;
    }

    nidevice[unit] = nireq->io_Device;
    niunit[unit] = nireq->io_Unit;
  }

  if (nibuffer[unit] == NULL) {
    nibuffer[unit] = AllocVec(MAXLINELEN, MEMF_PUBLIC);
    if (nibuffer[unit] == NULL)
      return NULL;
  }

  nireq->io_Length = MAXLINELEN;
  nireq->io_Data = nibuffer[unit];

  return nireq;
}

void CloseNIUnit(ULONG unit)
{
  ObtainSemaphore(ni_lock);

  if (nidevice[unit]) {
    assert(nireq != NULL);

    nireq->io_Device = nidevice[unit];
    nireq->io_Unit = niunit[unit];

    CloseDevice(nireq);

    nidevice[unit] = NULL;
    niunit[unit] = NULL;
  }

  ReleaseSemaphore(ni_lock);
}

static void CleanupNIO(void)
{
  R_endpwent();
  R_endgrent();

  if (nibuffer[NETINFO_PASSWD_UNIT] != NULL)
    FreeVec(nibuffer[NETINFO_PASSWD_UNIT]);
  nibuffer[NETINFO_PASSWD_UNIT] = NULL;

  if (nibuffer[NETINFO_GROUP_UNIT] != NULL)
    FreeVec(nibuffer[NETINFO_GROUP_UNIT]);
  nibuffer[NETINFO_GROUP_UNIT] = NULL;

  if (nireq)
    DeleteIORequest(nireq), nireq = NULL;

  if (niport)
    DeleteMsgPort(niport), niport = NULL;
}

BYTE myDoIO(struct NetInfoReq *req)
{
  DoIO(req);
  return req->io_Error;
}

static const BYTE ioerr2errno[-IOERR_SELFTEST] = {
  /* IOERR_OPENFAIL */
  ENOENT,
  /* IOERR_ABORTED */
  EINTR,
  /* IOERR_NOCMD */
  ENODEV,
  /* IOERR_BADLENGTH */
  EBUSY,
  /* IOERR_BADADDRESS */
  EFAULT,
  /* IOERR_UNITBUSY */
  EBUSY,
  /* IOERR_SELFTEST */
  ENXIO,
};

void SetDeviceErr(void)
{
  short err;

  if (nireq)
    err = nireq->io_Error;
  else
    err = ENOMEM;

  if (err < 0) {
    if (err >= IOERR_SELFTEST)
      err = ioerr2errno[-1 - err];
    else
      err = EIO;
  }

  SetErrno(err);
}

/*
 * Get error string
 */
#define EUNKNOWN "Unknown error"

static const char * const __ug_errlist[] = {
  "Undefined error: 0",			  /*  0 - ENOERROR */
  "Operation not permitted",		  /*  1 - EPERM */
  "No such entry",			  /*  2 - ENOENT */
  "No such process",		          /*  3 - ESRCH */
  "Interrupted library call",		  /*  4 - EINTR */
  "Input/output error",			  /*  5 - EIO */
  "Device not configured",		  /*  6 - ENXIO */
  "Argument list too long",		  /*  7 - E2BIG */
  "Exec format error",			  /*  8 - ENOEXEC */
  "Bad file descriptor",		  /*  9 - EBADF */
  "No child processes",			  /* 10 - ECHILD */
  "Resource deadlock avoided",	          /* 11 - EDEADLK */
  "Cannot allocate memory",		  /* 12 - ENOMEM */
  "Permission denied",			  /* 13 - EACCES */
  "Bad address",			  /* 14 - EFAULT */
  "Block device required",		  /* 15 - ENOTBLK */
  "Device busy",			  /* 16 - EBUSY */
  "File exists",			  /* 17 - EEXIST */
  "Cross-device link",			  /* 18 - EXDEV */
  "Operation not supported by device",	  /* 19 - ENODEV */
  "Not a directory",			  /* 20 - ENOTDIR */
  "Is a directory",			  /* 21 - EISDIR */
  "Invalid argument",			  /* 22 - EINVAL */
  "Too many open files in system",	  /* 23 - ENFILE */
  "Too many open files",		  /* 24 - EMFILE */
  "Inappropriate operation for device",   /* 25 - ENOTTY */
  "Text file busy",			  /* 26 - ETXTBSY */
  "File too large",			  /* 27 - EFBIG */
  "No space left on device",		  /* 28 - ENOSPC */
  "Illegal seek",			  /* 29 - ESPIPE */
  "Read-only file system",		  /* 30 - EROFS */
  "Too many links",			  /* 31 - EMLINK */
  "Broken pipe",			  /* 32 - EPIPE */

  /* math software */
  "Numerical argument out of domain",	  /* 33 - EDOM */
  "Result too large",			  /* 34 - ERANGE */
/* non-blocking and interrupt i/o */
  "Resource temporarily unavailable",	  /* 35 - EAGAIN */
					  /* 35 - EWOULDBLOCK */
  "Operation now in progress",		  /* 36 - EINPROGRESS */
  "Operation already in progress",	  /* 37 - EALREADY */

/* ipc/network software -- argument errors */
  "Socket operation on non-socket",	  /* 38 - ENOTSOCK */
  "Destination address required",	  /* 39 - EDESTADDRREQ */
  "Message too long",			  /* 40 - EMSGSIZE */
  "Protocol wrong type for socket",	  /* 41 - EPROTOTYPE */
  "Protocol not available",		  /* 42 - ENOPROTOOPT */
  "Protocol not supported",		  /* 43 - EPROTONOSUPPORT */
  "Socket type not supported",		  /* 44 - ESOCKTNOSUPPORT */
  "Operation not supported",		  /* 45 - EOPNOTSUPP */
  "Protocol family not supported",	  /* 46 - EPFNOSUPPORT */
					  /* 47 - EAFNOSUPPORT */
  "Address family not supported by protocol family",
  "Address already in use",		  /* 48 - EADDRINUSE */
  "Can't assign requested address",	  /* 49 - EADDRNOTAVAIL */

/* ipc/network software -- operational errors */
  "Network is down",			  /* 50 - ENETDOWN */
  "Network is unreachable",		  /* 51 - ENETUNREACH */
  "Network dropped connection on reset",  /* 52 - ENETRESET */
  "Software caused connection abort",	  /* 53 - ECONNABORTED */
  "Connection reset by peer",		  /* 54 - ECONNRESET */
  "No buffer space available",		  /* 55 - ENOBUFS */
  "Socket is already connected",	  /* 56 - EISCONN */
  "Socket is not connected",		  /* 57 - ENOTCONN */
  "Can't send after socket shutdown",	  /* 58 - ESHUTDOWN */
  "Too many references: can't splice",	  /* 59 - ETOOMANYREFS */
  "Connection timed out",		  /* 60 - ETIMEDOUT */
  "Connection refused",			  /* 61 - ECONNREFUSED */

  "Too many levels of symbolic links",	  /* 62 - ELOOP */
  "File name too long",			  /* 63 - ENAMETOOLONG */

/* should be rearranged */
  "Host is down",			  /* 64 - EHOSTDOWN */
  "No route to host",			  /* 65 - EHOSTUNREACH */
  "Directory not empty",		  /* 66 - ENOTEMPTY */
};

static const int __ug_nerr = ENOTEMPTY + 1;

/****** usergroup.library/ug_StrError **************************************

    NAME
        ug_StrError - Return the text associated with error code

    SYNOPSIS
        text = ug_StrError(code)
         D0                   D1

        const char *ug_StrError(LONG);

    FUNCTION
        The strerror() function maps the error number specified by the
        errnum parameter to a language-dependent error message string, and
        returns a pointer to the string.  The string pointed to by the
        return value should not be modified by the program, but may be
        overwritten by a subsequent call to this function.

    INPUTS
        code - error code returned by ug_GetErr() function.

    RESULT
        text - text associated with the error code.

    NOTES
        The current implementation will understands also the negative IO
        error codes.

    BUGS
        Currently only language available is English.

    SEE ALSO
        ug_GetErr()

******************************************************************************
*/

SAVEDS ASM const char *R_ug_StrError(REG(d1) LONG code)
{
  if (code < 0) {
    if (code >= IOERR_SELFTEST)
      code = ioerr2errno[-1 - code];
    else
      code = EIO;
  }

  if (code >= __ug_nerr)
    return EUNKNOWN;
  else
    return __ug_errlist[code];
}

/****** usergroup.library/ug_SetupContextTags ********************************

    NAME
        ug_SetupContextTagList - Set up the caller context
        ug_SetupContextTags    -  varargs stub for ug_SetupContextTagList

    SYNOPSIS
        success = ug_SetupContextTagList(taglist)
          D0                               A1

        ULONG ug_SetupContextTagList(struct TagItem *);

        success = ug_SetupContextTags(...)

        ULONG ug_SetupContextTags(LONG tag, ...);


    FUNCTION
        The function ug_SetupContextTags() will prepare the library caller
        context.

    INPUTS
        taglist - pointer to taglist

        Currently, there are defined tags as follows:

        UGT_ERRNOPTR  - gives the pointer to the errno variable.  The error
                        variable is redirected to the scope of the task.  If
                        the pointer is NULL, no redirection is done anymore.

        UGT_ERRNOSIZE - specifies the size of the errno variable. Legal
                        values are 1, 2 and 4.  The UGT_ERRNOSIZE must be
                        given with same call if the UGT_ERRNOPTR is given a
                        non-NULL value.

        UGT_INTRMASK  - specifies the interrupt signal mask. All blocking
                        library calls will be interrrupted when a signal in
                        the break mask is received. The signals in the
                        `mask' are not cleared when a library call is
                        interrupted.  The signals in INTRMASK should be
                        allocated in the context of the owning task.

        UGT_OWNER     - changes the owner of this library instance.  The
                        UGT_OWNET tagData must be a valid task pointer or
                        NULL. If the pointer is NULL, the library will have
                        no owner and any task can become owner by calling
                        ug_SetupContextTagList(UGT_OWNER, FindTask(NULL),
                        TAG_END) ; 

			Most of the library calls are allowed only for the
                        owner of library.  Only the owner can CloseLibrary()
                        this library.

    RESULT
        If the call is successfull, value of 0 is returned. Otherwise the
        value -1 is returned. Old context is cleared, if an error occurs.
        The error code can be retrieved with function ug_GetErr().

    ERRORS
        [EINVAL]    An illegal input value was specified.

    BUGS
        Strange and unusual things will happen if the signal allocated for
        the use of the library is included in the mask.

    SEE ALSO
        ug_GetErr(), --background--

******************************************************************************
*/

const UBYTE *_ProgramName = USERGROUPNAME;

SAVEDS ASM int R_ug_SetupContextTagList(REG(a0) const UBYTE *name,
					REG(a1) struct TagItem *tagargs)
{
  struct TagItem *tag;
  struct Process *caller = (struct Process *)FindTask(NULL);

  if (owner != NULL && owner != caller) {
    /* We should */
    InMsg("ug_SetupContextTags: not an owner (%lx)", caller);
    return -1;
  }

  if (tagargs == NULL || name == NULL) {
    SetErrno(EINVAL);
    return -1;
  }

  _ProgramName = name;

  if (tag = FindTagItem(UGT_OWNER, tagargs)) {
    short error;

    ObtainSemaphore(ni_lock);

    if (owner != NULL && owner != caller) {
      if (tag->ti_Data != NULL) {
	owner = (void *)tag->ti_Data;
	error = 0;
      } else {
	error = EINVAL;
      }
    } else {
      error = EPERM;
    }

    ReleaseSemaphore(ni_lock);

    if (error) {
      SetErrno(error);
      return -1;
    }
  }

  if (tag = FindTagItem(UGT_ERRNOLPTR, tagargs)) {
    errnop = (void *)tag->ti_Data;
    errnosize = es_long;
  }
  if (tag = FindTagItem(UGT_ERRNOWPTR, tagargs)) {
    errnop = (void *)tag->ti_Data;
    errnosize = es_word;
  }
  if (tag = FindTagItem(UGT_ERRNOBPTR, tagargs)) {
    errnop = (void *)tag->ti_Data;
    errnosize = es_byte;
  }
  if (tag = FindTagItem(UGT_INTRMASK, tagargs)) {
    break_mask = tag->ti_Data;
  }

  return 0;
}

