/* Copyright (c) 1993
 *      Juergen Weigert (jnweiger@immd4.informatik.uni-erlangen.de)
 *      Michael Schroeder (mlschroe@immd4.informatik.uni-erlangen.de)
 * Copyright (c) 1987 Oliver Laumann
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2, or (at your option)
 * any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program (see the file COPYING); if not, write to the
 * Free Software Foundation, Inc.,
 * 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA
 *
 ****************************************************************
 */
#include "rcs.h"
RCS_ID("$Id: acls.c,v 1.12 1994/05/31 12:31:21 mlschroe Exp $ FAU")

#include <sys/types.h>

#include "config.h"
#include "screen.h"	/* includes acls.h */
#include "extern.h"


/************************************************************************
 * user managing code, this does not really belong into the acl stuff   *
 ************************************************************************/

extern struct comm comms[];
extern struct win *windows, *wtab[];
extern struct display *display, *displays;
struct user *users;

#ifdef MULTIUSER
/* record given user ids here */
static AclBits userbits;

/* rights a new unknown user will have on windows and cmds */
static char default_w_bit[ACL_BITS_PER_WIN] = 
{ 
  1,	/* EXEC */
  1, 	/* WRITE */
  1 	/* READ */
};

static char default_c_bit[ACL_BITS_PER_CMD] = 
{ 
  1	/* EXEC */
};

/* rights of all users per newly created window */
static AclBits default_w_userbits[ACL_BITS_PER_WIN];

/*
static AclBits default_c_userbits[ACL_BITS_PER_CMD];
*/

static int maxusercount = 0;

static int GrowBitfield __P((AclBits *, int, int, int));



static int
GrowBitfield(bfp, len, delta, defaultbit)
AclBits *bfp;
int len, delta, defaultbit;
{
  AclBits n, o = *bfp;
  int i;

  if (!(n = (AclBits)calloc(1, (unsigned long)(&ACLBYTE((char *)0, len + delta + 1)))))
    return -1;
  for (i = 0; i < (len + delta); i++)
    {
      if (((i <  len) && (ACLBIT(i) & ACLBYTE(o, i))) ||
          ((i >= len) && (defaultbit)))
	ACLBYTE(n, i) |= ACLBIT(i);
    }
  if (len)
    free((char *)o);
  *bfp = n;
  return 0;
}

#endif /* MULTIUSER */

/* 
 * Returns an nonzero Address. Its contents is either a User-ptr, 
 * or NULL which may be replaced by a User-ptr to create the entry.
 */
struct user **
FindUserPtr(name)
char *name;
{
  struct user **u;

  for (u = &users; *u; u = &(*u)->u_next)
    if (!strcmp((*u)->u_name, name))
      break;
#ifdef MULTIUSER
  debug3("FindUserPtr %s %sfound, id %d\n", name, (*u)?"":"not ", 
         (*u)?(*u)->id:-1);
#else
  debug2("FindUserPtr %s %sfound\n", name, (*u)?"":"not ");
#endif
  return u;
}

int DefaultEsc = Ctrl('a');
int DefaultMetaEsc = 'a';

/*
 * Add a new user. His password may be NULL or "" if none. 
 * He has no rights.
 */
int
UserAdd(name, pass, up)
char *name, *pass;
struct user **up;
{
  if (!up)
    up = FindUserPtr(name);
  if (*up)
    return 1;		/* he is already there */
  *up = (struct user *)calloc(1, sizeof(struct user));
  if (!*up)
    return -1;		/* he still does not exist */
#ifdef COPY_PASTE
  (*up)->u_copybuffer = NULL;
  (*up)->u_copylen = 0;
#endif
  (*up)->u_Esc = DefaultEsc;
  (*up)->u_MetaEsc = DefaultMetaEsc;
  strncpy((*up)->u_name, name, 20);
  if (pass)
    strncpy((*up)->u_password, pass, 20);

#ifdef MULTIUSER
  /* now find an unused index */
  for ((*up)->id = 0; (*up)->id < maxusercount; (*up)->id++)
    if (!(ACLBIT((*up)->id) & ACLBYTE(userbits, (*up)->id)))
      break;
  debug2("UserAdd %s id %d\n", name, (*up)->id);
  if ((*up)->id == maxusercount)
    {
      int i, j;
      struct win *w;

      debug2("growing all bitfields %d += %d\n", maxusercount, USER_CHUNK);
      /* the bitfields are full, grow a chunk */
      /* first, the used_uid_indicator: */
      if (GrowBitfield(&userbits, maxusercount, USER_CHUNK, 0))
        {
	  free((char *)*up); *up = NULL; return -1;
	}
      /* second, default command bits  */
      /* (only if we generate commands dynamically) */
/*
      for (j = 0; j < ACL_BITS_PER_CMD; j++)
	if (GrowBitfield(&default_c_userbits[j], maxusercount, USER_CHUNK, 
	    default_c_bit[j]))
	  {
	    free((char *)*up); *up = NULL; return -1;
	  }
*/
      /* third, the bits for each commands */
      for (i = 0; i <= RC_LAST; i++)
        for (j = 0; j < ACL_BITS_PER_CMD; j++)
	  if (GrowBitfield(&comms[i].userbits[j], maxusercount, USER_CHUNK,
	      default_c_bit[j]))
	    {
	      free((char *)*up); *up = NULL; return -1;
	    }
      /* fourth, default window and bits */
      for (j = 0; j < ACL_BITS_PER_WIN; j++)
	if (GrowBitfield(&default_w_userbits[j], maxusercount, USER_CHUNK,
	    default_w_bit[j]))
	  {
	    free((char *)*up); *up = NULL; return -1;
	  }
      /* fifth, the bits for each window */
      for (w = windows; w; w = w->w_next)
        for (j = 0; j < ACL_BITS_PER_WIN; j++)
	  if (GrowBitfield(&w->w_userbits[j], maxusercount, USER_CHUNK,
	      default_w_bit[j]))
	    {
	      free((char *)*up); *up = NULL; return -1;
	    }
      maxusercount += USER_CHUNK;
    }
  ACLBYTE(userbits, (*up)->id) |= ACLBIT((*up)->id);    
  /* user id 0 is the session creator, he has all rights */
  if ((*up)->id == 0)
    AclSetPerm(*up, "+rwx", "#?");
#else /* MULTIUSER */
  debug1("UserAdd %s\n", name);
#endif /* MULTIUSER */
  return 0;
}

/* change user's password */
int 
UserSetPass(name, pass, up)
char *name, *pass;
struct user **up;
{
  if (!up)
    up = FindUserPtr(name);
  if (!*up)
    return UserAdd(name, pass, up);
  strncpy((*up)->u_password, pass ? pass : "", 20);
  (*up)->u_password[20] = '\0';
  return 0;
}

/* 
 * Remove a user from the list. 
 * Destroy all his permissions and completely detach him from the session.
 */
int 
UserDel(name, up)
char *name;
struct user **up;
{
  struct user *u;
  struct display *old, *next;

  if (!up)
    up = FindUserPtr(name);
  if ((u = *up) == 0)
    return -1;			/* he who does not exist cannot be removed */
  old = display;
  for (display = displays; display; display = next)
    {
      next = display->d_next;
      if (D_user != u)
	continue;
      if (display == old)
	old = 0;
      Detach(D_REMOTE);
    }
  display = old;
  *up = u->u_next;
#ifdef MULTIUSER
  ACLBYTE(userbits, u->id) &= ~ACLBIT(u->id);
  /* restore the bits in his slot to default: */
  AclSetPerm(u, default_w_bit[ACL_READ] ? "+r" : "-r", "#");
  AclSetPerm(u, default_w_bit[ACL_WRITE]? "+w" : "-w", "#");
  AclSetPerm(u, default_w_bit[ACL_EXEC] ? "+x" : "-x", "#");
  AclSetPerm(u, default_c_bit[ACL_EXEC] ? "+x" : "-x", "?");
#endif
  debug1("FREEING user structure for %s\n", u->u_name);
#ifdef COPY_PASTE
  UserFreeCopyBuffer(u);
#endif
  free((char *)u);
  return 0;
}


#ifdef COPY_PASTE

/*
 * returns 0 if the copy buffer was really deleted.
 * Also removes any references into the users copybuffer
 */
int
UserFreeCopyBuffer(u)
struct user *u;
{
  struct win *w;

  if (!u->u_copybuffer)
    return 1;
  for (w = windows; w; w = w->w_next)
    {
      if (w->w_pasteptr >= u->u_copybuffer &&
          w->w_pasteptr - u->u_copybuffer < u->u_copylen)
	{
	  if (w->w_pastebuf)
            free(w->w_pastebuf);
          w->w_pastebuf = 0;
	  w->w_pasteptr = 0;
	  w->w_pastelen = 0;
	}
    }
  free(u->u_copybuffer);
  D_user->u_copylen = 0;
  u->u_copybuffer = NULL;
  return 0;
}

#endif	/* COPY_PASTE */

/************************************************************************
 *                     end of user managing code                        *
 ************************************************************************/


#ifdef MULTIUSER

extern char *multi;	/* username */

/* This gives the users default rights to the new window */
int
NewWindowAcl(w)
struct win *w;
{
  int i, j;

  debug1("NewWindowAcl default_w_userbits for window %d\n", w->w_number);
  for (j = 0; j < ACL_BITS_PER_WIN; j++)
    {
      /* we start with len 0 for the new bitfield size and add maxusercount */
      if (GrowBitfield(&w->w_userbits[j], 0, maxusercount, 0))
	{
	  while (--j >= 0)
	    free((char *)w->w_userbits[j]);
	  return -1;
	}
      for (i = 0; i < maxusercount; i++)
        if (ACLBIT(i) & ACLBYTE(default_w_userbits[j], i))
	  ACLBYTE(w->w_userbits[j], i) |= ACLBIT(i);
    }
  return 0;
}

/* if mode starts with '-' we remove the users exec bit for cmd */
int
AclSetPermCmd(u, mode, cmd)
struct user *u;
char *mode;
struct comm *cmd;
{
  int neg = 0;

  if (!multi)
    return 0;
  debug3("AclSetPermCmd %s %s %s\n", u->u_name, mode, cmd->name);
  while (*mode)
    {
      switch (*mode++)
        {
	case '-': 
	  neg = 1;
	  continue;
        case '+':
	  neg = 0;
	  continue;
        case 'e': 
        case 'x': 
	  if (neg)
	    ACLBYTE(cmd->userbits[ACL_EXEC], u->id) &= ~ACLBIT(u->id);
	  else
	    ACLBYTE(cmd->userbits[ACL_EXEC], u->id) |= ACLBIT(u->id);
	  break;
        case 'r':
	case 'w':
	  break;
        default:
	  return -1;
	}
    }
  return 0;
}

/* mode strings of the form +rwx -w+rx r -wx are parsed and evaluated */
/*
 * aclchg nerd -w+w 2
 * releases a writelock on window 2 held by user nerd.
 */
int
AclSetPermWin(u, mode, win)
struct user *u;
char *mode;
struct win *win;
{
  int neg = 0;
  int bit;

  if (!multi)
    return 0;
  debug3("AclSetPermWin %s %s %d\n", u->u_name, mode, win->w_number);
  while (*mode)
    {
      switch (*mode++)
        {
	case '-': 
	  neg = 1;
	  continue;
        case '+':
	  neg = 0;
	  continue;
        case 'r': 
	  bit = ACL_READ;
	  break;
	case 'w':
	  bit = ACL_WRITE;
	  break;
        case 'x':
	  bit = ACL_EXEC;
	  break;
	default:
	  return -1;
        }
      if (neg)
	ACLBYTE(win->w_userbits[bit], u->id) &= ~ACLBIT(u->id);
      else
	ACLBYTE(win->w_userbits[bit], u->id) |= ACLBIT(u->id);
      if ((win->w_wlockuser == u) && neg && (bit == ACL_WRITE))
        {
	  debug2("%s lost writelock on win %d\n", u->u_name, win->w_number);
          win->w_wlockuser = NULL;
	  if (win->w_wlock == WLOCK_ON)
	    win->w_wlock = WLOCK_AUTO;
        }
    }
  return 0;
}

/* string is broken down into comand and window names, mode applies */
int
AclSetPerm(u, mode, s)
struct user *u;
char *mode, *s;
{
  struct win *w;
  int i;
  char *p, ch;

  while (*s)
    {
      switch (*s)
	{
	case '*':	/* all windows and all commands */
	  return AclSetPerm(u, mode, "#?");
	case '#':	/* all windows */
	  for (w = windows; w; w = w->w_next)
	    AclSetPermWin(u, mode, w);
	  s++;
	  break;
	case '?':	/* all commands */
	  for (i = 0; i <= RC_LAST; i++)
	    AclSetPermCmd(u, mode, &comms[i]);
	  s++;
	  break;
	default:
	  for (p = s; *p && *p != ' ' && *p != '\t' && *p != ','; p++)
	    ;
	  if ((ch = *p) != 0)
	    *p++ = '\0';
	  if ((i = FindCommnr(s)) != RC_ILLEGAL)
	    AclSetPermCmd(u, mode, &comms[i]);
	  else if (((i = WindowByNoN(s)) >= 0) && wtab[i])
	    AclSetPermWin(u, mode, wtab[i]);
	  else
	    /* checking group name */
	    return -1;
	  if (ch)
	    p[-1] = ch;
	  s = p; 
	}    
    }
  return 0;
}

#if 0
void
AclWinSwap(a, b)
int a, b;
{
  int a_bit = 0, b_bit = 0;
  AclGroupList **g;
  AclBits p;

  debug2("acl lists swapping windows %d and %d\n", a, b);
  
  for (g = &aclgrouproot; *g; g = &(*g)->next) 
    {
      p = (*g)->group->winbits;
      /* see if there was a bit for window a. zap it */
      if (a >= 0)
	if ((a_bit = ACLBIT(a) & ACLBYTE(p, a)))
	  ACLBYTE(p, a) &= ~ACLBIT(a);
      /* see if there was a bit for window b. zap it */
      if (b >= 0)
	if ((b_bit = ACLBIT(b) & ACLBYTE(p, b)))
	  ACLBYTE(p, b) &= ~ACLBIT(b);
      /* b may cause a set */
      if (b_bit && a >= 0)
        ACLBYTE(p, a) |= ACLBIT(a);
      /* a may cause b set */
      if (a_bit && b >= 0)
        ACLBYTE(p, b) |= ACLBIT(b);
    }
}
#else
void
AclWinSwap(a, b)
int a, b;
{
  debug2("AclWinSwap(%d, %d) DUMMY\n", a, b);
}
#endif

int 
AclCheckPermWin(u, mode, w)
struct user *u;
int mode;
struct win *w;
{
  if (!multi)
    return 0;
  if (mode < 0 || mode >= ACL_BITS_PER_WIN)
    return -1;
  debug3("AclCheckPermWin(%s, %d, %d) = ", u->u_name, mode, w->w_number);
  debug1("%d\n", !(ACLBYTE(w->w_userbits[mode], u->id) & ACLBIT(u->id)));
  return         !(ACLBYTE(w->w_userbits[mode], u->id) & ACLBIT(u->id));
}

int 
AclCheckPermCmd(u, mode, c)
struct user *u;
int mode;
struct comm *c;
{
  if (!multi)
    return 0;
  if (mode < 0 || mode >= ACL_BITS_PER_CMD)
    return -1;
  debug3("AclCheckPermCmd(%s %d %s) = ", u->u_name, mode, c->name); 
  debug1("%d\n", !(ACLBYTE(c->userbits[mode], u->id) & ACLBIT(u->id)));
  return         !(ACLBYTE(c->userbits[mode], u->id) & ACLBIT(u->id));
}

#endif /* MULTIUSER */
