#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>     /* <netinet/in.h> needs this */
#include <netinet/in.h>    /* <afs/venus.h> needs this */
#include <afs/param.h>
#include <sys/types.h>
#include <afs/vice.h>
#include <afs/venus.h>
#include "q.h"

/*
 * This routine converts an acl string specification, e.g., rliwdka, rl,
 * write, etc., into a long with the bits set as defined in
 * <afs/prs_fs.h>.  If the string is not recognized, it returns -1
 * and sets error to Q_INVALID_ACL_LETTER.
 */

long
Q_acl_string_to_long (string, error)
     char *string;
     long *error;
{
  char c;
  int i = 0;
  long bits = 0;

  Q_init ();

  if (!strcmp (string, "read"))
    return AFS_ACL_READ;
  if (!strcmp (string, "write"))
    return AFS_ACL_WRITE;
  if (!strcmp (string, "none"))
    return 0;
  if (!strcmp (string, "all"))
    return AFS_ACL_ALL;
  if (!strcmp (string, "mail"))
    return AFS_ACL_MAIL;
  if (!strcmp (string, "fratboy"))
    return PRSFS_USR4 | PRSFS_USR6 | PRSFS_LOCK;

  while ((c = string[i++]) != '\0')
    switch (c)
      {
      case 'r':
	bits |= PRSFS_READ;
	break;
      case 'w':
	bits |= PRSFS_WRITE;
	break;
      case 'i':
	bits |= PRSFS_INSERT;
	break;
      case 'l':
	bits |= PRSFS_LOOKUP;
	break;
      case 'd':
	bits |= PRSFS_DELETE;
	break;
      case 'k':
	bits |= PRSFS_LOCK;
	break;
      case 'a':
	bits |= PRSFS_ADMINISTER;
	break;

	/* right about here, things start getting weird */

      case 'A':
	bits |= PRSFS_USR0;
	break;
      case 'B':
	bits |= PRSFS_USR1;
	break;
      case 'C':
	bits |= PRSFS_USR2;
	break;
      case 'D':
	bits |= PRSFS_USR3;
	break;
      case 'E':
	bits |= PRSFS_USR4;
	break;
      case 'F':
	bits |= PRSFS_USR5;
	break;
      case 'G':
	bits |= PRSFS_USR6;
	break;
      case 'H':
	bits |= PRSFS_USR7;
	break;
      default:
	*error = Q_INVALID_ACL_LETTER;
	return -1;
      }
  return bits;
}

/*
 * This routine converts a long to a `rlidwka' string.
 */

char *
Q_long_to_acl_string (num)
     long num;
{
  char *str;
  int i = 0;

  str = (char *) Q_malloc (AFSMAXACL * sizeof (char));

  if (num & PRSFS_READ)
    str[i++] = 'r';
  if (num & PRSFS_LOOKUP)
    str[i++] = 'l';
  if (num & PRSFS_INSERT)
    str[i++] = 'i';
  if (num & PRSFS_DELETE)
    str[i++] = 'd';
  if (num & PRSFS_WRITE)
    str[i++] = 'w';
  if (num & PRSFS_LOCK)
    str[i++] = 'k';
  if (num & PRSFS_ADMINISTER)
    str[i++] = 'a';
  if (num & PRSFS_USR0)
    str[i++] = 'A';
  if (num & PRSFS_USR1)
    str[i++] = 'B';
  if (num & PRSFS_USR2)
    str[i++] = 'C';
  if (num & PRSFS_USR3)
    str[i++] = 'D';
  if (num & PRSFS_USR4)
    str[i++] = 'E';
  if (num & PRSFS_USR5)
    str[i++] = 'F';
  if (num & PRSFS_USR6)
    str[i++] = 'G';
  if (num & PRSFS_USR7)
    str[i++] = 'H';

  return str;
}

/* This function is intuitively obvious. */

AFS_acl *
Q_parse_afs_acl (s)
     char *s;
{
  char *c;
  char *csave;
  char *ssave;
  AFS_acl *acl;
  int i, j, k, l;

  ssave = s;

  c = (char *) Q_malloc (AFSMAXSIZE * sizeof (char));

  csave = c;

  acl = (AFS_acl *) Q_malloc (sizeof (AFS_acl));

  for (i = 0; (*++c = *s++) != '\n'; i++)
    ;
  acl->num_pos = j = atoi (c - i);
  if (j)
    acl->pos_entries = (AFS_acl_entry *) Q_malloc (j * sizeof (AFS_acl_entry));
  for (i = 0; (*++c = *s++) != '\n'; i++)
    ;
  acl->num_neg = k = atoi (c - i);
  if (k)
    acl->neg_entries = (AFS_acl_entry *) Q_malloc (k * sizeof (AFS_acl_entry));
  for (i = 0; i < j; i++)
    {
      for (l = 0; (*++c = *s++) != '\t'; l++)
	;
      *c = '\0';
      strcpy (acl->pos_entries[i].name, c - l);
      for (l = 0; (*++c = *s++) != '\n'; l++)
	;
      *c = '\0';
      acl->pos_entries[i].bits = atol (c - l);
    }
  for (i = 0; i < k; i++)
    {
      for (l = 0; (*++c = *s++) != '\t'; l++)
	;
      *c = '\0';
      strcpy (acl->neg_entries[i].name, c - l);
      for (l = 0; (*++c = *s++) != '\n'; l++)
	;
      *c = '\0';
      acl->neg_entries[i].bits = atol (c - l);
    }

  Q_free (csave);
  s = ssave;
  return acl;
}

/*
 * Q_get_afs_acl() will return the access control list to an AFS
 * directory, or NULL is there is an error.  It assumes you are giving it
 * a valid directory for an argument.
 */

AFS_acl *
Q_get_afs_acl (dir, error)
     char *dir;
     long *error;
{
  struct ViceIoctl spew;	/* for lack of anything more descriptive */
  static char buf[AFSMAXSIZE];
  int ret_val;
  AFS_acl *acl;
  char *s;

  Q_init ();

  spew.in_size = 0;
  spew.out = buf;
  spew.out_size = sizeof (buf);

  ret_val = pioctl (dir, VIOCGETAL, &spew, 1);

  if (ret_val)
    {
      *error = errno;
      return NULL;
    }

  s = (char *) Q_malloc ((strlen (spew.out) + 1) * sizeof (char));
  strcpy (s, spew.out);
  acl = Q_parse_afs_acl (s);
  Q_free (s);

  return acl;
}

/*
 * Q_set_afs_acl() attempts to set the AFS acl on the directory `dir'.
 * If the third argument `negative' is set, `acl_ent' is interpreted
 * as a negative acl.  Returns -1 on error, 0 on success.
 */

int
Q_set_afs_acl (dir, acl_ent, negative, error)
     char *dir;
     AFS_acl_entry *acl_ent;
     int negative;
     long *error;
{
  struct ViceIoctl spew;
  AFS_acl *current_acl;
  char buf[AFSMAXSIZE];
  char tmpbuf[AFSMAXNAME];
  int i, j;
  int found = 0;
  int nneg;
  int npos;
  int ntot;
  long code;
  long bits;
  int ret_val;

  /* First, we have to get the current acl, because pioctl()
   * only accepts a full acl, and not portions.
   */

  current_acl = Q_get_afs_acl (dir, &code);

  if (current_acl == NULL)
    {
      *error = code;
      return -1;
    }

  npos = current_acl->num_pos;
  nneg = current_acl->num_neg;
  ntot = nneg + npos;
  bits = acl_ent->bits;

  if (!negative)
    {
      for (i = 0; i < npos; i++)
	if (!strcmp (current_acl->pos_entries[i].name, acl_ent->name))
	  {
	    if (bits == 0)
	      {
		for (j = i+1; j < npos; j++)
		  {
		    strcpy(current_acl->pos_entries[j-1].name,
			   current_acl->pos_entries[j].name);
		    current_acl->pos_entries[j-1].bits =
		      current_acl->pos_entries[j].bits;
		  }
		current_acl->num_pos = --npos;
		--ntot;
	      }
	    else
	      {
		current_acl->pos_entries[i].bits = bits;
		found++;
		break;
	      }
	  }
    }
  else
    {
      for (i = 0; i < nneg; i++)
	if (!strcmp (current_acl->neg_entries[i].name, acl_ent->name))
	  {
	    if (bits == 0)
	      {
		for (j = i+1; j < nneg; j++)
		  {
		    strcpy(current_acl->neg_entries[j-1].name,
			   current_acl->neg_entries[j].name);
		    current_acl->neg_entries[j-1].bits =
		      current_acl->neg_entries[j].bits;
		  }
		current_acl->num_neg = --nneg;
		--ntot;
	      }
	    else
	      {
		current_acl->neg_entries[i].bits = bits;
		found++;
		break;
	      }
	  }
    }

  if ((!found)&& bits)           /* we'll have to create a new entry */
    {
      ++ntot;
      if (!negative)
	{
	  current_acl->num_pos = ++npos;
	  current_acl->pos_entries =
	    (AFS_acl_entry *) Q_realloc (current_acl->pos_entries,
					 (npos + 1)
					 * sizeof (AFS_acl_entry));

	  (void) strcpy (current_acl->pos_entries[npos - 1].name, acl_ent->name);
	  current_acl->pos_entries[npos - 1].bits = acl_ent->bits;
	}
      else
	{
	  current_acl->num_neg = ++nneg;
	  current_acl->neg_entries =
	    (AFS_acl_entry *) Q_realloc (current_acl->neg_entries,
					 (nneg + 1)
					 * sizeof (AFS_acl_entry));

	  (void) strcpy (current_acl->neg_entries[nneg - 1].name, acl_ent->name);
	  current_acl->neg_entries[nneg - 1].bits = acl_ent->bits;
	}
    }

  /* convert AFS acl into string */

  sprintf (buf, "%d\n%d\n", current_acl->num_pos, current_acl->num_neg);
  for (i = 0; i < npos; i++)
    {
      sprintf (tmpbuf, "%s\t%ld\n", current_acl->pos_entries[i].name,
	       current_acl->pos_entries[i].bits);
      strcat (buf, tmpbuf);
    }

  for (i = 0; i < nneg; i++)
    {
      sprintf (tmpbuf, "%s\t%ld\n", current_acl->neg_entries[i].name,
	       current_acl->neg_entries[i].bits);
      strcat (buf, tmpbuf);
    }

  spew.out_size = 0;
  spew.in = buf;
  spew.in_size = strlen (buf) + 1;

  ret_val = pioctl (dir, VIOCSETAL, &spew, 1);

  if (ret_val)
    {
      *error = errno;
      return -1;
    }
  else
    return 0;
}

/* Frees acl */

void
Q_free_acl (acl)
     AFS_acl *acl;
{

  Q_free (acl->pos_entries);
  Q_free (acl->neg_entries);

  Q_free (acl);
}
