#include <unistd.h>		/* readlink */
#include <limits.h>		/* PATH_MAX */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>		/* strcmp, memset */
#include <dirent.h>		/* opendir, etc. */
#include <sys/types.h>		/* lstat */
#include <sys/stat.h>		/* lstat */
#include <fcntl.h>		/* open */
#include <errno.h>

#define STORE_LINKS 1
#define RETRIEVE_LINKS 2

char *progname;

typedef struct _filename {
  struct _filename *next;
  char *name;
} filename;

typedef struct {
  filename *first, *current, *last;
} stringlist;

void resetstringlist(list)
     stringlist *list;
{
  if (list)
    list->current = list->first;

  return;
}

void makestringlist(list)
     stringlist *list;
{
  list->first = NULL;
  list->current = NULL;
  list->last = NULL;

  return;
}

void freestringlist(list)
     stringlist *list;
{
  filename *i, *j = NULL;

  i = list->first;
  while (i != NULL)
    {
      j = i;
      i = i->next;
      free(j->name);
      free(j);
    }
}

void savestring(list, name)
     stringlist *list;
     char *name;
{
  filename *new;

  new = malloc(sizeof(filename));
  if (new == NULL)
    {
      fprintf(stderr, "%s: error in savestring during malloc()\n", progname);
      exit(1);
    }

  new->name = malloc(strlen(name) + 1);
  if (new->name == NULL)
    {
      fprintf(stderr, "%s: error in savestring during malloc()\n", progname);
      exit(1);
    }

  strcpy(new->name, name);

  new->next = NULL;
  if (list->first == NULL)
    list->first = new;
  if (list->current == NULL)
    list->current = new;
  if (list->last != NULL)
    list->last->next = new;
  list->last = new;
}

char *getstring(list)
     stringlist *list;
{
  char *ptr;

  if (list && list->current)
    {
      ptr = list->current->name;
      list->current = list->current->next;
      return ptr;
    }
  else
    return NULL;
}

stringlist *readfile(linkname)
     char *linkname;
{
  char link[10240];
  stringlist *strings;
  char *ptr, *end;
  int fd, len;

  fd = open(linkname, O_RDONLY);
  if (fd == -1)
    {
      if (errno == ENOENT)
	return NULL;

      fprintf(stderr, "%s: open on %s failed with errno %d\n",
	      progname, linkname, errno);
      exit(1);
    }

  len = read(fd, link, sizeof(link));
  close(fd);
  if (len == -1)
    {
      fprintf(stderr, "%s: read of %s failed with errno %d\n",
	      progname, linkname, errno);
      exit(1);
    }

  link[len] = '\0';

  strings = (stringlist *)malloc(sizeof(stringlist));
  if (strings == NULL)
    {
      fprintf(stderr, "%s: unable to malloc %d bytes\n", sizeof(stringlist));
      exit(1);
    }

  makestringlist(strings);

  ptr = link;

  while (ptr - link < len)
    {
      end = strchr(ptr, '\n');
      if (end != NULL)
	*end = '\0';

      /* Remove leading whitespace, comments, and blank lines. */
      while (isspace(*ptr))
	ptr++;
      if (*ptr != '#' && ptr < end)
	savestring(strings, ptr);

      ptr = end + 1;
    }

  return strings;
}

char *printacl(groups, entry)
     stringlist *groups;
     char *entry;
{
  char *ptr, *end, *lookup;
  char *access = NULL;
  static char output[4096];
  int didoutput = 0;

  output[0] = '\0';
  ptr = entry;

  if (!strncmp(entry, "read", 4))
    {
      access = "read";
      ptr += 4;
    }
  else
    if (!strncmp(entry, "write", 5))
      {
	access = "write";
	ptr += 5;
      }
    else
      if (!strncmp(entry, "adm", 3))
	{
	  access = "la";
	  ptr += 3;
	}

  if (access == NULL || !isspace(*ptr))
    {
      fprintf(stderr, "%s: unrecognized acl type in: %s\n", progname, entry);
      exit(1);
    }

  do {
    while (isspace(*ptr))
      ptr++;

    end = ptr;
    while (!isspace(*end) && *end != '\0')
      end++;

    if (end == ptr)
      break;

    resetstringlist(groups);
    while (lookup = getstring(groups))
      if (!strncmp(lookup, ptr, end - ptr))
	{
	  while (!isspace(*lookup) && *lookup != '\0')
	    lookup++;
	  while (isspace(*lookup))
	    lookup++;
	  break;
	}

    if (lookup == NULL)
      {
	fprintf(stderr, "%s: no such group %s\n", progname, ptr);
	exit(1);
      }

    if (didoutput)
      strcat(output, " ");
    strcat(output, lookup);
    strcat(output, " ");
    strcat(output, access);
    didoutput++;

    ptr = end;
  } while (*ptr != '\0');

  return output;
}

void process(absolute, relative, recursive, groups, global, localin, fangs)
     char *absolute, *relative;
     stringlist *groups, *global, *localin;
     int recursive, fangs;
{
  FILE *linkfile = NULL;
  DIR *dir;
  char abs[PATH_MAX], rel[PATH_MAX];
  char *directoryname, *entry;
  struct dirent *curent;
  struct stat info;
  stringlist dirlist, *local;
  int length;
  char output[10240];
  int ourlocal = 1;

  fprintf(stdout, "Processing %s\n", relative);

  if (!fangs)
    fprintf(stdout, "cd %s\n", absolute);

  if (chdir(absolute))
    {
      fprintf(stderr, "%s: error %d from chdir()\n", progname, errno);
      exit(1);
    }

  makestringlist(&dirlist);

  dir = opendir(".");
  if (dir == NULL)
    {
      fprintf(stderr, "%s: could not open directory %s\n",
	      progname, relative);
      exit(1);
    }

  while (curent = readdir(dir))
    {
      if (strcmp(curent->d_name, ".") && strcmp(curent->d_name, ".."))
	{
	  if (lstat(curent->d_name, &info))
	    {
	      fprintf(stderr, "%s: error %d from lstat(%s)\n", progname,
		      errno, curent->d_name);
	      exit(1);
	    }

	  if ((info.st_mode & S_IFMT) == S_IFDIR)
	    savestring(&dirlist, curent->d_name);
	}
    }

  closedir(dir);

  local = readfile(".protections.local");
  if (local == NULL)
    {
      local = localin;
      ourlocal = 0;
    }

  resetstringlist(global);
  resetstringlist(local);

  strcpy(output, "fs sa .");

  while (entry = getstring(local))
    {
      strcat(output, " ");
      strcat(output, printacl(groups, entry));
    }
  while (entry = getstring(global))
    {
      strcat(output, " ");
      strcat(output, printacl(groups, entry));
    }

  strcat(output, " -clear");

  if (fangs)
    {
      if (system(output))
	{
	  fprintf(stderr, "%s: system() returned %d\n", progname, errno);
	  exit(1);
	}
    }
  else
    fprintf(stdout, "%s\n", output);

  if (recursive)
    while (directoryname = getstring(&dirlist))
      {
	sprintf(abs, "%s/%s", absolute, directoryname);
	sprintf(rel, "%s/%s", relative, directoryname);
	process(abs, rel, recursive, groups, global, local, fangs);
      }

  if (ourlocal)
    {
      freestringlist(local);
      free(local);
    }

  freestringlist(&dirlist);
}

void usage()
{
  fprintf(stderr,
	  "usage: %s rootdir [-r] [-n]\n",
	  progname);
  exit(1);
}

int main(argc, argv)
     int argc;
     char **argv;
{
  char curdir[PATH_MAX];
  char *partial = ".";
  int recursive = 0, fangs = 1;
  stringlist *groups, *global;
  char *root;
  char path[PATH_MAX];

  progname = argv[0];

  if (getcwd(curdir, sizeof(curdir)) == NULL)
    {
      fprintf(stderr, "%s: getcwd() failed with error %d\n", progname, errno);
      exit(1);
    }

  root = argv[1];
  if (root == NULL || root[0] == '-')
    usage();

  sprintf(path, "%s/%s", root, ".protections.groups.afs");
  groups = readfile(path);
  sprintf(path, "%s/%s", root, ".protections.global");
  global = readfile(path);

  argv += 2;
  while (*argv)
    {
      if (!strcmp("-r", *argv))
	recursive = 1;
      else
	if (!strcmp("-n", *argv))
	  fangs = 0;
	else
	  usage();
      argv++;
    }

  process(curdir, partial, recursive, groups, global, NULL, fangs);

  exit(0);
}
