Blame modules/pam_console/modechange.c

Packit 7e982e
/* This file is derived from modechange.c included in the GNU fileutils
Packit 7e982e
   distribution.  It has been changed to be a library specifically
Packit 7e982e
   for use within pam_console
Packit 7e982e
   Changes Copyright 1999 Red Hat Software, Inc.
Packit 7e982e
 */
Packit 7e982e
Packit 7e982e
/* modechange.c -- file mode manipulation
Packit 7e982e
   Copyright (C) 1989, 1990 Free Software Foundation, Inc.
Packit 7e982e
Packit 7e982e
   This program is free software; you can redistribute it and/or modify
Packit 7e982e
   it under the terms of the GNU General Public License as published by
Packit 7e982e
   the Free Software Foundation; either version 2, or (at your option)
Packit 7e982e
   any later version.
Packit 7e982e
Packit 7e982e
   This program is distributed in the hope that it will be useful,
Packit 7e982e
   but WITHOUT ANY WARRANTY; without even the implied warranty of
Packit 7e982e
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
Packit 7e982e
   GNU General Public License for more details.
Packit 7e982e
Packit 7e982e
   You should have received a copy of the GNU General Public License
Packit 7e982e
   along with this program; if not, write to the Free Software Foundation,
Packit 7e982e
   Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.  */
Packit 7e982e
Packit 7e982e
/* Written by David MacKenzie <djm@ai.mit.edu> */
Packit 7e982e
Packit 7e982e
/* The ASCII mode string is compiled into a linked list of `struct
Packit 7e982e
   modechange', which can then be applied to each file to be changed.
Packit 7e982e
   We do this instead of re-parsing the ASCII string for each file
Packit 7e982e
   because the compiled form requires less computation to use; when
Packit 7e982e
   changing the mode of many files, this probably results in a
Packit 7e982e
   performance gain. */
Packit 7e982e
Packit 7e982e
#include "config.h"
Packit 7e982e
#include <sys/types.h>
Packit 7e982e
#include <sys/stat.h>
Packit 7e982e
#include "modechange.h"
Packit 7e982e
Packit 7e982e
#include <stdlib.h>
Packit 7e982e
Packit 7e982e
/* Return newly allocated memory to hold one element of type TYPE. */
Packit 7e982e
#define talloc(type) ((type *) malloc (sizeof (type)))
Packit 7e982e
Packit 7e982e
#define isodigit(c) ((c) >= '0' && (c) <= '7')
Packit 7e982e
Packit 7e982e
/* Return a positive integer containing the value of the ASCII
Packit 7e982e
   octal number S.  If S is not an octal number, return -1.  */
Packit 7e982e
Packit 7e982e
static int
Packit 7e982e
oatoi (const char *s)
Packit 7e982e
{
Packit 7e982e
  register int i;
Packit 7e982e
Packit 7e982e
  if (*s == 0)
Packit 7e982e
    return -1;
Packit 7e982e
  for (i = 0; isodigit (*s); ++s)
Packit 7e982e
    i = i * 8 + *s - '0';
Packit 7e982e
  if (*s)
Packit 7e982e
    return -1;
Packit 7e982e
  return i;
Packit 7e982e
}
Packit 7e982e
Packit 7e982e
/* Return a linked list of file mode change operations created from
Packit 7e982e
   MODE_STRING, an ASCII string that contains either an octal number
Packit 7e982e
   specifying an absolute mode, or symbolic mode change operations with
Packit 7e982e
   the form:
Packit 7e982e
   [ugoa...][[+-=][rwxXstugo...]...][,...]
Packit 7e982e
   MASKED_OPS is a bitmask indicating which symbolic mode operators (=+-)
Packit 7e982e
   should not affect bits set in the umask when no users are given.
Packit 7e982e
   Operators not selected in MASKED_OPS ignore the umask.
Packit 7e982e
Packit 7e982e
   Return MODE_INVALID if `mode_string' does not contain a valid
Packit 7e982e
   representation of file mode change operations;
Packit 7e982e
   return MODE_MEMORY_EXHAUSTED if there is insufficient memory. */
Packit 7e982e
Packit 7e982e
STATIC struct mode_change *
Packit 7e982e
mode_compile (mode_string, masked_ops)
Packit 7e982e
     const char *mode_string;
Packit 7e982e
     unsigned masked_ops;
Packit 7e982e
{
Packit 7e982e
  struct mode_change *head;	/* First element of the linked list. */
Packit 7e982e
  struct mode_change *change;	/* An element of the linked list. */
Packit 7e982e
  int i;			/* General purpose temporary. */
Packit 7e982e
  int umask_value;		/* The umask value (surprise). */
Packit 7e982e
  unsigned short affected_bits;	/* Which bits in the mode are operated on. */
Packit 7e982e
  unsigned short affected_masked; /* `affected_bits' modified by umask. */
Packit 7e982e
  unsigned ops_to_mask;		/* Operators to actually use umask on. */
Packit 7e982e
Packit 7e982e
  i = oatoi (mode_string);
Packit 7e982e
  if (i >= 0)
Packit 7e982e
    {
Packit 7e982e
      if (i > 07777)
Packit 7e982e
	return MODE_INVALID;
Packit 7e982e
      head = talloc (struct mode_change);
Packit 7e982e
      if (head == NULL)
Packit 7e982e
	return MODE_MEMORY_EXHAUSTED;
Packit 7e982e
      head->next = NULL;
Packit 7e982e
      head->op = '=';
Packit 7e982e
      head->flags = 0;
Packit 7e982e
      head->value = i;
Packit 7e982e
      head->affected = 07777;	/* Affect all permissions. */
Packit 7e982e
      return head;
Packit 7e982e
    }
Packit 7e982e
Packit 7e982e
  umask_value = umask (0);
Packit 7e982e
  umask (umask_value);		/* Restore the old value. */
Packit 7e982e
Packit 7e982e
  head = NULL;
Packit 7e982e
  change = NULL;
Packit 7e982e
  --mode_string;
Packit 7e982e
Packit 7e982e
  /* One loop iteration for each "ugoa...=+-rwxXstugo...[=+-rwxXstugo...]". */
Packit 7e982e
  do
Packit 7e982e
    {
Packit 7e982e
      affected_bits = 0;
Packit 7e982e
      ops_to_mask = 0;
Packit 7e982e
      /* Turn on all the bits in `affected_bits' for each group given. */
Packit 7e982e
      for (++mode_string;; ++mode_string)
Packit 7e982e
	switch (*mode_string)
Packit 7e982e
	  {
Packit 7e982e
	  case 'u':
Packit 7e982e
	    affected_bits |= 04700;
Packit 7e982e
	    break;
Packit 7e982e
	  case 'g':
Packit 7e982e
	    affected_bits |= 02070;
Packit 7e982e
	    break;
Packit 7e982e
	  case 'o':
Packit 7e982e
	    affected_bits |= 01007;
Packit 7e982e
	    break;
Packit 7e982e
	  case 'a':
Packit 7e982e
	    affected_bits |= 07777;
Packit 7e982e
	    break;
Packit 7e982e
	  default:
Packit 7e982e
	    goto no_more_affected;
Packit 7e982e
	  }
Packit 7e982e
Packit 7e982e
    no_more_affected:
Packit 7e982e
      /* If none specified, affect all bits, except perhaps those
Packit 7e982e
	 set in the umask. */
Packit 7e982e
      if (affected_bits == 0)
Packit 7e982e
	{
Packit 7e982e
	  affected_bits = 07777;
Packit 7e982e
	  ops_to_mask = masked_ops;
Packit 7e982e
	}
Packit 7e982e
Packit 7e982e
      while (*mode_string == '=' || *mode_string == '+' || *mode_string == '-')
Packit 7e982e
	{
Packit 7e982e
	  /* Add the element to the tail of the list, so the operations
Packit 7e982e
	     are performed in the correct order. */
Packit 7e982e
	  if (head == NULL)
Packit 7e982e
	    {
Packit 7e982e
	      head = talloc (struct mode_change);
Packit 7e982e
	      if (head == NULL)
Packit 7e982e
		return MODE_MEMORY_EXHAUSTED;
Packit 7e982e
	      change = head;
Packit 7e982e
	    }
Packit 7e982e
	  else
Packit 7e982e
	    {
Packit 7e982e
	      change->next = talloc (struct mode_change);
Packit 7e982e
	      if (change->next == NULL)
Packit 7e982e
		{
Packit 7e982e
		  mode_free (change);
Packit 7e982e
		  return MODE_MEMORY_EXHAUSTED;
Packit 7e982e
		}
Packit 7e982e
	      change = change->next;
Packit 7e982e
	    }
Packit 7e982e
Packit 7e982e
	  change->next = NULL;
Packit 7e982e
	  change->op = *mode_string;	/* One of "=+-". */
Packit 7e982e
	  affected_masked = affected_bits;
Packit 7e982e
	  if (ops_to_mask & (*mode_string == '=' ? MODE_MASK_EQUALS
Packit 7e982e
			     : *mode_string == '+' ? MODE_MASK_PLUS
Packit 7e982e
			     : MODE_MASK_MINUS))
Packit 7e982e
	    affected_masked &= ~umask_value;
Packit 7e982e
	  change->affected = affected_masked;
Packit 7e982e
	  change->value = 0;
Packit 7e982e
	  change->flags = 0;
Packit 7e982e
Packit 7e982e
	  /* Set `value' according to the bits set in `affected_masked'. */
Packit 7e982e
	  for (++mode_string;; ++mode_string)
Packit 7e982e
	    switch (*mode_string)
Packit 7e982e
	      {
Packit 7e982e
	      case 'r':
Packit 7e982e
		change->value |= 00444 & affected_masked;
Packit 7e982e
		break;
Packit 7e982e
	      case 'w':
Packit 7e982e
		change->value |= 00222 & affected_masked;
Packit 7e982e
		break;
Packit 7e982e
	      case 'X':
Packit 7e982e
		change->flags |= MODE_X_IF_ANY_X;
Packit 7e982e
		/* Fall through. */
Packit 7e982e
	      case 'x':
Packit 7e982e
		change->value |= 00111 & affected_masked;
Packit 7e982e
		break;
Packit 7e982e
	      case 's':
Packit 7e982e
		/* Set the setuid/gid bits if `u' or `g' is selected. */
Packit 7e982e
		change->value |= 06000 & affected_masked;
Packit 7e982e
		break;
Packit 7e982e
	      case 't':
Packit 7e982e
		/* Set the "save text image" bit if `o' is selected. */
Packit 7e982e
		change->value |= 01000 & affected_masked;
Packit 7e982e
		break;
Packit 7e982e
	      case 'u':
Packit 7e982e
		/* Set the affected bits to the value of the `u' bits
Packit 7e982e
		   on the same file.  */
Packit 7e982e
		if (change->value)
Packit 7e982e
		  goto invalid;
Packit 7e982e
		change->value = 00700;
Packit 7e982e
		change->flags |= MODE_COPY_EXISTING;
Packit 7e982e
		break;
Packit 7e982e
	      case 'g':
Packit 7e982e
		/* Set the affected bits to the value of the `g' bits
Packit 7e982e
		   on the same file.  */
Packit 7e982e
		if (change->value)
Packit 7e982e
		  goto invalid;
Packit 7e982e
		change->value = 00070;
Packit 7e982e
		change->flags |= MODE_COPY_EXISTING;
Packit 7e982e
		break;
Packit 7e982e
	      case 'o':
Packit 7e982e
		/* Set the affected bits to the value of the `o' bits
Packit 7e982e
		   on the same file.  */
Packit 7e982e
		if (change->value)
Packit 7e982e
		  goto invalid;
Packit 7e982e
		change->value = 00007;
Packit 7e982e
		change->flags |= MODE_COPY_EXISTING;
Packit 7e982e
		break;
Packit 7e982e
	      default:
Packit 7e982e
		goto no_more_values;
Packit 7e982e
	      }
Packit 7e982e
	no_more_values:;
Packit 7e982e
	}
Packit 7e982e
  } while (*mode_string == ',');
Packit 7e982e
  if (*mode_string == 0)
Packit 7e982e
    return head;
Packit 7e982e
invalid:
Packit 7e982e
  mode_free (head);
Packit 7e982e
  return MODE_INVALID;
Packit 7e982e
}
Packit 7e982e
Packit 7e982e
/* Return file mode OLDMODE, adjusted as indicated by the list of change
Packit 7e982e
   operations CHANGES.  If OLDMODE is a directory, the type `X'
Packit 7e982e
   change affects it even if no execute bits were set in OLDMODE.
Packit 7e982e
   The returned value has the S_IFMT bits cleared. */
Packit 7e982e
Packit 7e982e
STATIC unsigned short
Packit 7e982e
mode_adjust (oldmode, changes)
Packit 7e982e
     unsigned oldmode;
Packit 7e982e
     const struct mode_change *changes;
Packit 7e982e
{
Packit 7e982e
  unsigned short newmode;	/* The adjusted mode and one operand. */
Packit 7e982e
  unsigned short value;		/* The other operand. */
Packit 7e982e
Packit 7e982e
  newmode = oldmode & 07777;
Packit 7e982e
Packit 7e982e
  for (; changes; changes = changes->next)
Packit 7e982e
    {
Packit 7e982e
      if (changes->flags & MODE_COPY_EXISTING)
Packit 7e982e
	{
Packit 7e982e
	  /* Isolate in `value' the bits in `newmode' to copy, given in
Packit 7e982e
	     the mask `changes->value'. */
Packit 7e982e
	  value = newmode & changes->value;
Packit 7e982e
Packit 7e982e
	  if (changes->value & 00700)
Packit 7e982e
	    /* Copy `u' permissions onto `g' and `o'. */
Packit 7e982e
	    value |= (value >> 3) | (value >> 6);
Packit 7e982e
	  else if (changes->value & 00070)
Packit 7e982e
	    /* Copy `g' permissions onto `u' and `o'. */
Packit 7e982e
	    value |= (value << 3) | (value >> 3);
Packit 7e982e
	  else
Packit 7e982e
	    /* Copy `o' permissions onto `u' and `g'. */
Packit 7e982e
	    value |= (value << 3) | (value << 6);
Packit 7e982e
Packit 7e982e
	  /* In order to change only `u', `g', or `o' permissions,
Packit 7e982e
	     or some combination thereof, clear unselected bits.
Packit 7e982e
	     This can not be done in mode_compile because the value
Packit 7e982e
	     to which the `changes->affected' mask is applied depends
Packit 7e982e
	     on the old mode of each file. */
Packit 7e982e
	  value &= changes->affected;
Packit 7e982e
	}
Packit 7e982e
      else
Packit 7e982e
	{
Packit 7e982e
	  value = changes->value;
Packit 7e982e
	  /* If `X', do not affect the execute bits if the file is not a
Packit 7e982e
	     directory and no execute bits are already set. */
Packit 7e982e
	  if ((changes->flags & MODE_X_IF_ANY_X)
Packit 7e982e
	      && !S_ISDIR (oldmode)
Packit 7e982e
	      && (newmode & 00111) == 0)
Packit 7e982e
	    value &= ~00111;	/* Clear the execute bits. */
Packit 7e982e
	}
Packit 7e982e
Packit 7e982e
      switch (changes->op)
Packit 7e982e
	{
Packit 7e982e
	case '=':
Packit 7e982e
	  /* Preserve the previous values in `newmode' of bits that are
Packit 7e982e
	     not affected by this change operation. */
Packit 7e982e
	  newmode = (newmode & ~changes->affected) | value;
Packit 7e982e
	  break;
Packit 7e982e
	case '+':
Packit 7e982e
	  newmode |= value;
Packit 7e982e
	  break;
Packit 7e982e
	case '-':
Packit 7e982e
	  newmode &= ~value;
Packit 7e982e
	  break;
Packit 7e982e
	}
Packit 7e982e
    }
Packit 7e982e
  return newmode;
Packit 7e982e
}
Packit 7e982e
Packit 7e982e
/* Free the memory used by the list of file mode change operations
Packit 7e982e
   CHANGES. */
Packit 7e982e
Packit 7e982e
STATIC void
Packit 7e982e
mode_free (changes)
Packit 7e982e
     register struct mode_change *changes;
Packit 7e982e
{
Packit 7e982e
  register struct mode_change *next;
Packit 7e982e
Packit 7e982e
  while (changes)
Packit 7e982e
    {
Packit 7e982e
      next = changes->next;
Packit 7e982e
      free (changes);
Packit 7e982e
      changes = next;
Packit 7e982e
    }
Packit 7e982e
}
Packit 7e982e