/*
 * access.c  -  File access routines
 *
 * Copyright (C) 1998-2007 Gero Kuhlmann <gero@gkminix.han.de>
 *
 *  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 of the License, or
 *  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; if not, write to the Free Software
 *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 * $Id: access.c,v 1.14 2007/01/06 18:31:38 gkminix Exp $
 */

#include <common.h>
#include <nblib.h>
#include "privlib.h"



/*
 * Extract path elements from string
 */
struct paths {
	struct paths *next;
	char         *name;
};

static struct paths *extractpath __F((str), const char *str)
{
  struct paths *plist = NULL;
  struct paths *pp;
  char *cp;
  size_t len;

  while (str != NULL) {
	cp = strchr(str, ':');
	len = ((cp == NULL) ? strlen(str) : (size_t)(cp - str));
	if (len > 0) {
		pp = (struct paths *)nbmalloc(sizeof(struct paths));
		pp->name = (char *)nbmalloc(len + 2);
		strncpy(pp->name, str, len);
		pp->name[len] = '\0';
		pp->next = plist;
		plist = pp;
	}
	if (cp != NULL)
		cp++;
	str = cp;
  }
  return(plist);
}



/*
 * Actually normalize a path
 */
static struct paths *donorm __F((pathname, maindir),
					const char *pathname AND
					const char *maindir)
{
  struct paths *pathlist, *dirlist, *pp, *dp, *np, *op;
  struct paths *newlist = NULL;
  size_t len;

  /* Extract path elements */
  pathlist = extractpath(pathname);
  dirlist = extractpath(maindir);

  /* Make all relative paths absolute to the elements in dirlist */
  while (pathlist != NULL) {
	pp = pathlist;
	pathlist = pp->next;
	if (pp->name[0] == '/') {
		pp->next = newlist;
		newlist = pp;
	} else {
		dp = dirlist;
		while (dp != NULL) {
			len = strlen(pp->name) + strlen(dp->name) + 1;
			np = (struct paths *)nbmalloc(sizeof(struct paths));
			np->name = (char *)nbmalloc(len + 2);
			sprintf(np->name, "%s/%s", dp->name, pp->name);
			np->next = newlist;
			newlist = np;
			dp = dp->next;
		}
		free(pp->name);
		free(pp);
	}
  }

  /* Delete directory list, we don't need it anymore */
  while (dirlist != NULL) {
	dp = dirlist;
	dirlist = dirlist->next;
	free(dp->name);
	free(dp);
  }

  /* Normalize all paths which are relative to the netboot directory */
  dp = NULL;
  np = newlist;
  while (np != NULL) {
	if (!strncmp(np->name, "//", 2)) {
		/* Insert netboot non-shared directory into list */
		pp = (struct paths *)nbmalloc(sizeof(struct paths));
		pp->next = np->next;
		if ((len = strlen(np->name)) == 2)
			copystr(&pp->name, nblibdir);
		else {
			len += strlen(nblibdir) - 1;
			pp->name = (char *)nbmalloc(len + 2);
			sprintf(pp->name, "%s%s", nblibdir, &(np->name[1]));
		}

		/* Insert netboot shared directory into list */
		op = pp;
		if (nbdatadir != NULL) {
			op = (struct paths *)nbmalloc(sizeof(struct paths));
			op->next = pp;
			if ((len = strlen(np->name)) == 2)
				copystr(&(op->name), nbdatadir);
			else {
				len += strlen(nbdatadir) - 1;
				op->name = (char *)nbmalloc(len + 2);
				sprintf(op->name, "%s%s", nbdatadir, &(np->name[1]));
			}
		}
		if (dp == NULL)
			newlist = op;
		else
			dp->next = op;

		/* Remove original entry from list */
		free(np->name);
		free(np);
		np = op;
	}
	dp = np;
	np = np->next;
  }
  return(newlist);
}



/*
 * Normalize a path name.
 */
void setpath __F((pathname, maindir), char **pathname AND const char *maindir)
{
  struct paths *pathlist, *pp;
  size_t retlen;
  char *newname, *cp;

  /* Normalize path names */
  if (*pathname == NULL || (pathlist = donorm(*pathname, maindir)) == NULL)
	return;
  free(*pathname);

  /* Determine length of return string */
  retlen = 0;
  pp = pathlist;
  while (pp != NULL) {
	retlen += strlen(pp->name) + 1;
	pp = pp->next;
  }

  /* Generate return string */
  newname = NULL;
  if (pathlist != NULL) {
	cp = newname = (char *)nbmalloc(retlen + 1);
	while (pathlist != NULL) {
		pp = pathlist;
		strcpy(cp, pp->name);
		cp += strlen(pp->name);
		if (pp->next != NULL) {
			*(cp++) = ':';
			*cp     = '\0';
		}
		pathlist = pathlist->next;
		free(pp->name);
		free(pp);
	}
  }
  *pathname = newname;
}



/*
 * Check if a file is really a file and is readable, and determine it's
 * normalized path name
 */
void checkaccess __F((filename, maindir, mode),
				char **filename AND
				const char *maindir AND
				int mode)
{
  struct stat sbuf;
  struct paths *pathlist, *pp;
  char *retname;
  mode_t bitsuser, bitsgroup, bitsother;
  int dirmode;

  /* Actually normalize the file name */
  if (*filename == NULL || (pathlist = donorm(*filename, maindir)) == NULL)
	return;
  free(*filename);

  /* Determine type of checks to be performed */
  switch (mode) {
	case ACCESS_FILE_EXIST:
		bitsuser = 0;
		bitsgroup = 0;
		bitsother = 0;
		dirmode = FALSE;
		break;

	case ACCESS_FILE_READ:
		bitsuser = S_IRUSR;
		bitsgroup = S_IRGRP;
		bitsother = S_IROTH;
		dirmode = FALSE;
		break;

	case ACCESS_FILE_RW:
		bitsuser = S_IRUSR | S_IWUSR;
		bitsgroup = S_IRGRP | S_IWGRP;
		bitsother = S_IROTH | S_IWOTH;
		dirmode = FALSE;
		break;

	case ACCESS_FILE_EXEC:
		bitsuser = S_IXUSR;
		bitsgroup = S_IXGRP;
		bitsother = S_IXOTH;
		dirmode = FALSE;
		break;

	case ACCESS_DIR_EXIST:
		bitsuser = 0;
		bitsgroup = 0;
		bitsother = 0;
		dirmode = TRUE;
		break;

	case ACCESS_DIR_READ:
		bitsuser = S_IRUSR | S_IXUSR;
		bitsgroup = S_IRGRP | S_IXGRP;
		bitsother = S_IROTH | S_IXOTH;
		dirmode = TRUE;
		break;

	case ACCESS_DIR_RW:
		bitsuser = S_IRUSR | S_IWUSR | S_IXUSR;
		bitsgroup = S_IRGRP | S_IWGRP | S_IXGRP;
		bitsother = S_IROTH | S_IWOTH | S_IXOTH;
		dirmode = TRUE;
		break;

	default:
		bitsuser = S_IRUSR;
		bitsgroup = S_IRGRP;
		bitsother = S_IROTH;
		dirmode = FALSE;
		break;
  }

  /* Scan through the normalized file list */
  retname = NULL;
  while (pathlist != NULL) {
	pp = pathlist;
	if (retname == NULL && stat(pp->name, &sbuf) == 0) {
		if (
		    /* Check for file or directory */
		    (
		     (!dirmode && S_ISREG(sbuf.st_mode)) ||
		     (dirmode && S_ISDIR(sbuf.st_mode))
		    ) &&

		    /* Check for access mode */
		    (
		     ((sbuf.st_uid == getuid()) &&
		      (sbuf.st_mode & bitsuser) == bitsuser) ||

		     ((sbuf.st_gid == getgid()) &&
		      (sbuf.st_mode & bitsgroup) == bitsgroup) ||

		     ((sbuf.st_mode & bitsother) == bitsother)
		    )
		   )
			/* Write the file name into the return string pointer */
			copystr(&retname, pp->name);
	}
	pathlist = pathlist->next;
	free(pp->name);
	free(pp);
  }
  *filename = retname;
}

