/*
 * Amiga support routines for chroot() emulation,
 * some MultiUser support routines, alarm() and
 * setproctitle().
 *
 * © 1994 Blaz Zupan, <blaz.zupan@uni-mb.si>
 * All Rights Reserved
 *
 */

#include <exec/memory.h>
#include <devices/timer.h>
#include <libraries/multiuser.h>
#include <proto/multiuser.h>
#include <proto/dos.h>
#include <proto/exec.h>
#include <proto/usergroup.h>
#include <string.h>
#include <stdio.h>
#include <stdarg.h>
#include <signal.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 = 0;
  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 = 2;
	break;
      case LOCK_SAME_VOLUME:
	{
	  BPTR l;

	  while (l2)
	  {
	    l = l2;
	    l2 = ParentDir (l);
	    UnLock (l);
	    if (SameLock (l1, l2) == LOCK_SAME)
	    {
	      ret = 1;
	      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;
char dummypas[] = "";

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))
      {
	ok = TRUE;
	p.pw_uid = ui->uid;
	p.pw_gid = ui->gid;
	if (p.pw_name)
	  free (p.pw_name);
	if (p.pw_name = malloc (strlen (ui->UserID) + 1))
	  strcpy (p.pw_name, ui->UserID);
	else
	  ok = FALSE;
	p.pw_passwd = "";
	if (p.pw_gecos)
	  free (p.pw_gecos);
	if (p.pw_gecos = malloc (strlen (ui->UserName) + 1))
	  strcpy (p.pw_gecos, ui->UserName);
	else
	  ok = FALSE;
	if (p.pw_dir)
	  free (p.pw_dir);
	if (p.pw_dir = malloc (strlen (ui->HomeDir) + 1))
	  strcpy (p.pw_dir, ui->HomeDir);
	else
	  ok = FALSE;
	if (p.pw_shell)
	  free (p.pw_shell);
	if (p.pw_shell = malloc (strlen (ui->Shell) + 1))
	  strcpy (p.pw_shell, ui->Shell);
	else
	  ok = FALSE;
      }
      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;

	ok = TRUE;
	g.gr_gid = gi->gid;
	if (g.gr_name)
	  free (g.gr_name);
	if (g.gr_name = malloc (strlen (gi->GroupID) + 1))
	  strcpy (g.gr_name, gi->GroupID);
	else
	  ok = FALSE;
	g.gr_passwd = "";
	g.gr_mem = &dummy_memb;
      }
      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 UBYTE ubits[16] =
{
  0, 2, 1, 3, 2, 2, 3, 3,
  4, 6, 5, 7, 6, 6, 7, 7,
};

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

mode_t
A2U_prot (ULONG mode)
{
  return ((ubits[(mode ^ 0xf) & 0xf] << 6) |
	  (ubits[((mode ^ 0xf) >> FIBB_GRP_DELETE) & 0xf] << 3) |
	  (ubits[((mode ^ 0xf) >> FIBB_OTR_DELETE) & 0xf]));
}

mode_t
amiga_umask (mode_t m)
{
  if (muBase)
  {
    ULONG oldmask;

    oldmask = muGetDefProtection (NULL);
    muSetDefProtection (muT_DefProtection, U2A_prot (m), TAG_END);
    return (A2U_prot (oldmask));
  }
#undef umask
  return (umask (m));
}

mode_t
amiga_getumask (void)
{
  if (muBase)
    return (A2U_prot (muGetDefProtection (NULL)));
#undef getumask
  return (getumask ());
}

/* 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);
}

/*
 * This is a stub function which is called when
 * the SIGINT signal arrives. It checks if maybe
 * this SIGINT is a SIGALRM and executes the
 * appropriate function.
 */
void (*intfunc) (int) = NULL;
void (*alrmfunc) (int) = NULL;
BOOL isalrm = FALSE;

void
amiga_sigalrm (int signum)
{
#undef signal
  signal (SIGINT, amiga_sigalrm);
  if (isalrm)			/* Did we really get a SIGALRM? */
  {
    isalrm = FALSE;
    (*alrmfunc) (SIGALRM);	/* Yes, execute SIGALRM function */
  }
  else
    (*intfunc) (SIGINT);	/* No, it was a standard ctrl-c */
}

/*
 * This is my implementation of the Unix alarm() function.
 * Is kind of a hack, because when the time expires it sends
 * a break signal (control-c) to itself, the break routine
 * then notices that it was called from an alarm() and
 * executes the proper code. A hack, but it works :-)
 */
struct Task *ThisTask;
struct MsgPort *TimerPort = NULL;
struct timerequest *TimerIO = NULL;
struct Interrupt *TimerInterrupt = NULL;
BOOL deviceopen = FALSE, alarmon = FALSE;

void __saveds __interrupt
IntFunc (void)
{
  struct Message *msg;

  if ((msg = GetMsg (TimerPort)) && alarmon)
  {
    isalrm = TRUE;
    Signal (ThisTask, SIGBREAKF_CTRL_C);
  }
}

void
cleanup_alarm (void)
{
  if (TimerIO)
  {
    alarmon = FALSE;

    if (!CheckIO (TimerIO))
    {
      AbortIO (TimerIO);
      WaitIO (TimerIO);
    }
  }
  if (deviceopen)
  {
    CloseDevice (TimerIO);
    deviceopen = FALSE;
  }
  if (TimerIO)
  {
    DeleteExtIO (TimerIO);
    TimerIO = NULL;
  }
  if (TimerPort)
  {
    FreeVec (TimerPort);
    TimerPort = NULL;
  }
  if (TimerInterrupt)
  {
    FreeVec (TimerInterrupt);
    TimerInterrupt = NULL;
  }
}

int
alarm (int t)
{
  if (t == 0)
  {
    alarmon = FALSE;

    /* delete previous timer request */
    if (TimerIO)
    {
      if (!CheckIO (TimerIO))
      {
	AbortIO (TimerIO);
	WaitIO (TimerIO);
      }
    }
  }
  else
  {
    ThisTask = FindTask (NULL);
    if (!TimerInterrupt)
    {
      if (TimerInterrupt = AllocVec (sizeof (struct Interrupt), MEMF_CLEAR))
      {
	TimerInterrupt->is_Node.ln_Type = NT_INTERRUPT;
	TimerInterrupt->is_Code = &IntFunc;
      }
    }

    if (TimerInterrupt && !TimerPort)
    {
      if (TimerPort = AllocVec (sizeof (struct MsgPort), MEMF_CLEAR))
      {
	TimerPort->mp_Node.ln_Type = NT_MSGPORT;
	TimerPort->mp_Flags = PA_SOFTINT;	/* cause a software interrupt when signal arrives */
	TimerPort->mp_SoftInt = TimerInterrupt;
	NewList (&(TimerPort->mp_MsgList));
      }
    }

    if (TimerPort && !TimerIO)
      TimerIO = (struct timerequest *) CreateExtIO (TimerPort, sizeof (struct timerequest));

    if (TimerPort && TimerIO && !deviceopen)
    {
      deviceopen = (!OpenDevice (TIMERNAME, UNIT_VBLANK, (struct IORequest *) TimerIO, 0));
      if (deviceopen)
	atexit (cleanup_alarm);
    }

    if (deviceopen)
    {
      alarmon = FALSE;
      if (!CheckIO (TimerIO))
      {
	AbortIO (TimerIO);
	WaitIO (TimerIO);
      }
      TimerIO->tr_node.io_Command = TR_ADDREQUEST;
      TimerIO->tr_time.tv_secs = t;
      TimerIO->tr_time.tv_micro = 0;
      alarmon = TRUE;
      BeginIO ((struct IORequest *) TimerIO);
    }
    else
      cleanup_alarm ();
  }
}

/*
 * This is a new signal() function that also knows
 * about SIGALRM (it installs SIGALRM as SIGINT with
 * a special handler that knows about both SIGALRM and
 * SIGINT).
 */
void
  (*amiga_signal (int signum, void (*sigfunc) (int))) (int)
{
  void (*oldfunc) (int) = NULL;
  if (signum == SIGALRM || signum == SIGINT)
  {
    if (!intfunc)
      intfunc = signal (SIGINT, amiga_sigalrm);

    if (signum == SIGINT)
    {
      oldfunc = intfunc;
      intfunc = sigfunc;
    }
    else
    {
      oldfunc = alrmfunc;
      alrmfunc = sigfunc;
    }
  }
  else
    return (signal (signum, sigfunc));
  return oldfunc;
}

#ifdef SETPROCTITLE
/* Sets name of currently running program. */
void
setproctitle (const char *fmt,...)
{
  va_list args;
  char *text;

  if (text = malloc (BUFSIZ))
  {
    char *newfmt;

    if (newfmt = malloc (strlen (fmt) + sizeof ("ftpd: ")))
    {
      sprintf (newfmt, "ftpd: %s", fmt);
      va_start (args, fmt);
      vsprintf (text, newfmt, args);
      SetProgramName (text);
      va_end (args);
      free (newfmt);
    }
    else
      SetProgramName ("ftpd");
    free (text);
  }
  else
    SetProgramName ("ftpd");
}
#endif
