/*
 * Amiga support routines for chroot() emulation
 * © 1994 Blaz Zupan, <blaz.zupan@uni-mb.si>
 * All Rights Reserved
 *
 * Contains:
 *
 *   chroot()
 *   CheckAccess()
 *   amiga_seteuid()
 *   amiga_getuid()
 *   amiga_getpwnam()
 *   amiga_getgrnam()
 *   amiga_umask()
 *
 */

#include <libraries/multiuser.h>
#include <proto/multiuser.h>
#include <proto/dos.h>
#include <proto/usergroup.h>
#include <string.h>
#include <stdio.h>
#include "extensions.h"
#include "config.h"

extern char *amigausername;
extern struct muBase *muBase;

/*
 * This routine is an attempt to emulate the Unix chroot()
 * function. It only saves the name supplied in the global
 * AmigaRootDir. Everything else must be done by CheckAccess().
 */
extern int anonymous, guest;
char AmigaRootDir[MAXPATHLEN + 2] = "";

int
chroot (char *dir)
{
  BPTR lock;

  if (lock = Lock (dir, SHARED_LOCK))
  {
    NameFromLock (lock, AmigaRootDir, MAXPATHLEN);
    UnLock (lock);
    return 0;
  }
  return -1;
}

/*
 * Checks if pathname "name1" is above
 * "name" in directory structure.
 */
static BOOL
IsParent (char *name1, char *name2)
{
  BOOL ret = FALSE;
  BPTR l1;

  if (l1 = Lock (name1, SHARED_LOCK))
  {
    BPTR l2;

    if (l2 = Lock (name2, SHARED_LOCK))
    {
      switch (SameLock (l1, l2))
      {
      case LOCK_DIFFERENT:
	break;
      case LOCK_SAME:
	ret = TRUE;
	break;
      case LOCK_SAME_VOLUME:
	{
	  BPTR l;

	  while (l2)
	  {
	    l = l2;
	    l2 = ParentDir (l);
	    UnLock (l);
	    if (SameLock (l1, l2) == LOCK_SAME)
	    {
	      ret = TRUE;
	      break;
	    }
	  }
	  break;
	}
      }
      UnLock (l2);
    }
    UnLock (l1);
  }
  return ret;
}

/* Compare two strings with pattern matching */
static BOOL
mymatch (char *pattern, char *string)
{
  BOOL ret = FALSE;
  char *parsedpattern;
  long len = strlen (pattern) * 2 + 10;
  long oldflags = DOSBase->dl_Root->rn_Flags;

  DOSBase->dl_Root->rn_Flags |= RNF_WILDSTAR;	/* turn on recognitions of star as wildcard */
  if (parsedpattern = malloc (len))
  {
    if (ParsePattern (pattern, parsedpattern, len) != -1)
      ret = MatchPattern (parsedpattern, string);
    free (parsedpattern);
  }
  DOSBase->dl_Root->rn_Flags = oldflags;	/* restore original flags */
  return ret;
}

/* Checks if user is allowed to access file/directory.
 * If create == TRUE check for parent of specified name
 * (this is used if we want to check if user is allowed
 * to access the directory into which a file should be
 * created - so now "put <something> AmiTCP:db/passwd"
 * hacks are possible).
 */
BOOL
CheckAccess (char *dir, BOOL create)
{
  BPTR lock;
  char name[MAXPATHLEN];
  struct aclmember *entry = NULL;

  strcpy (name, dir);
  lock = Lock (name, SHARED_LOCK);
  if (!lock && create)
  {
    *(FilePart (name)) = 0;
    lock = Lock (name, SHARED_LOCK);
  }

  if (lock)
    UnLock (lock);
  else
    /* If we could not access file/directory then return "OK"
     * for now because access will be denied later by the
     * command that called us.
     */
    return TRUE;


  /* first check if directory is on "deny" list */
  while (getaclentry ("denydir", &entry) && ARG0 && ARG1)
  {
    if (mymatch (ARG1, amigausername) && IsParent (ARG0, name))
      return FALSE;
  }
  /* now see if it is under root dir */
  if (IsParent (AmigaRootDir, name))
    return TRUE;
  /* check if access is allowed even if dir is not under root */
  else
  {
    entry = NULL;
    while (getaclentry ("allowdir", &entry) && ARG0 && ARG1)
    {
      if (mymatch (ARG1, amigausername) && IsParent (ARG0, name))
	return TRUE;
    }
  }
  /* If directory is not on deny or allow list and not
   * under root directory then we allow access for
   * real users and don't allow access for guests.
   */
  if (!anonymous && !guest)
    return TRUE;
  else
    return FALSE;
}

/*
 * Simulate Unix geteuid() call with MultiUser.
 */
int
amiga_geteuid (void)
{
  if (muBase)
    return ((muGetTaskOwner (FindTask (NULL)) & muMASK_UID) >> 16);
  else
#undef geteuid
    return (geteuid ());
}

/*
 * Simulate Unix seteuid() call with MultiUser.
 * You have to be logged in as root initially for this
 * routine to work (after that it correctly keeps track
 * of the user).
 */
int
amiga_seteuid (int u)
{
  if (muBase)
  {
    ULONG tags[5];
    int res = -1;

    if (u == 0)
    {
      int uid;

      uid = amiga_geteuid ();

      /* Are we already root? */
      if (uid == muROOT_UID)
	return 0;

      /* We can't set effective user ID to root
       * if we are currently logged in as nobody
       */
      if (uid == muNOBODY_UID)
	return -1;

      /* Now logout until we become either root or
       * nobody. If we become nobody we fail.
       */
      do
      {
	tags[0] = muT_Quiet;
	tags[1] = TRUE;
	tags[2] = TAG_END;
	muLogoutA (tags);
	uid = amiga_geteuid ();
      }
      while (uid != muROOT_UID && uid != muNOBODY_UID);

      return ((uid == muROOT_UID) ? 0 : -1);
    }

    /* Check if we are maybe already logged in as that user. */
    if (u == amiga_geteuid ())
      return 0;

    /* Ok, so we are trying to login as someone else.
     * first make sure that we are root. */
    if (amiga_seteuid (0) != -1)
    {
      struct muUserInfo *ui;

      if (ui = muAllocUserInfo ())
      {
	ui->uid = u;
	if (muGetUserInfo (ui, muKeyType_uid))
	{
	  tags[0] = muT_UserID;
	  tags[1] = (ULONG) ui->UserID;
	  tags[2] = muT_NoLog;
	  tags[3] = TRUE;
	  tags[4] = TAG_END;
	  res = (muLoginA (tags) ? 0 : -1);
	}
	muFreeUserInfo (ui);
      }
    }
    return res;
  }
  /* MultiUser is not installed so use dummy
   * usergroup.library call.
   */
#undef seteuid
  return (seteuid (u));
}

/*
 * Simulate Unix seteuid() call with MultiUser.
 * You have to be logged in as root initially for this
 * routine to work (after that it correctly keeps track
 * of the user).
 */
int
amiga_setegid (int g)
{
  /* We can't set another group under
   * MultiUser so we fail.
   */
  if (muBase)
    return -1;
  /* MultiUser is not installed so use dummy
   * usergroup.library call.
   */
#undef setegid
  return (setegid (g));
}


/*
 * Simulate Unix getpwnam() call with MultiUser.
 */

static struct passwd p;

struct passwd *
amiga_getpwnam (char *name)
{
  if (muBase)
  {
    struct muUserInfo *ui;
    BOOL ok = FALSE;

    if (ui = muAllocUserInfo ())
    {
      strcpy (ui->UserID, name);
      if (muGetUserInfo (ui, muKeyType_UserID))
      {
	p.pw_uid = ui->uid;
	p.pw_gid = ui->gid;
	if (p.pw_name)
	  free (p.pw_name);
	p.pw_name = malloc (strlen (ui->UserID) + 1);
	if (p.pw_name)
	  strcpy (p.pw_name, ui->UserID);
	p.pw_passwd = NULL;
	if (p.pw_gecos)
	  free (p.pw_gecos);
	p.pw_gecos = malloc (strlen (ui->UserName) + 1);
	if (p.pw_gecos)
	  strcpy (p.pw_gecos, ui->UserName);
	if (p.pw_dir)
	  free (p.pw_dir);
	p.pw_dir = malloc (strlen (ui->HomeDir) + 1);
	if (p.pw_dir)
	  strcpy (p.pw_dir, ui->HomeDir);
	if (p.pw_shell)
	  free (p.pw_shell);
	p.pw_shell = malloc (strlen (ui->Shell) + 1);
	if (p.pw_shell)
	  strcpy (p.pw_shell, ui->Shell);
	ok = TRUE;
      }
      muFreeUserInfo (ui);
    }
    if (!ok)
      return (NULL);
    return (&p);
  }
#undef getpwnam
  return (getpwnam (name));
}

/*
 * Simulate Unix getgrnam() call with MultiUser.
 */

static struct group g;

struct group *
amiga_getgrnam (char *name)
{
  if (muBase)
  {
    struct muGroupInfo *gi;
    BOOL ok = FALSE;

    if (gi = muAllocGroupInfo ())
    {
      strcpy (gi->GroupID, name);
      if (muGetGroupInfo (gi, muKeyType_GroupID))
      {
	static char *dummy_memb = NULL;

	g.gr_gid = gi->gid;
	if (g.gr_name)
	  free (g.gr_name);
	g.gr_name = malloc (strlen (gi->GroupID) + 1);
	if (g.gr_name)
	  strcpy (g.gr_name, gi->GroupID);
	g.gr_passwd = NULL;
	g.gr_mem = &dummy_memb;;
	ok = TRUE;
      }
      muFreeGroupInfo (gi);
    }
    if (!ok)
      return (NULL);
    return (&g);
  }
#undef getgrnam
  return (getgrnam (name));
}

/*
 * Simulate Unix umask() call with MultiUser.
 */

const static BYTE abits[8] =
{
  0, 0x2, 0x5, 0x7, 0x8, 0xA, 0xD, 0xF,
};

const static BYTE ubits[16] =
{
  0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7
};

ULONG
U2A_prot (int mode)
{
  return ((abits[mode & 7] << FIBB_OTR_DELETE) |
	  (abits[(mode >> 3) & 7] << FIBB_GRP_DELETE) |
	  (abits[(mode >> 6) & 7] ^ 0xf));
}

int
A2U_prot (ULONG mode)
{
  return ((ubits[(mode >> 12) & 15] << 6) |
	  (ubits[(mode >> 8) & 15] << 3) |
	  (ubits[mode & 15]));
}

int
amiga_umask (int m)
{
  if (muBase)
  {
    ULONG tags[3];
    ULONG oldmask;

    tags[0] = muT_DefProtection;
    tags[1] = U2A_prot (m);
    tags[2] = TAG_END;
    oldmask = muGetDefProtection (NULL);
    muSetDefProtection (tags);
    return (A2U_prot (oldmask));
  }
#undef umask
  return (umask (m));
}

/* dummy stub function for compatibility with original source */
FILE *
ftpd_popen (char *program, char *type, int closestderr)
{
  return (popen (program, type));
}

/* dummy stub function for compatibility with original source */
void
ftpd_pclose (FILE * iop)
{
  pclose (iop);
}
