56c29a3c87
(groupname): Ditto. (usage): Ditto. Add more user friendly help text. (main): Add -n and --help option. Print names instead of IDs unless -n option is given. * setfacl.c (getperm): Tolerate whitespace and comment chars in input. (getaclentry): Fix several input string misdetections. (getaclentries): Allow - as input file name to read from stdin. Tolerate whitespace and comment chars in input. (usage): Add more user friendly help text. (main): Add --help option.
496 lines
13 KiB
C
496 lines
13 KiB
C
/* setfacl.c
|
|
|
|
Copyright 2000, 2001 Red Hat Inc.
|
|
|
|
Written by Corinna Vinschen <vinschen@redhat.com>
|
|
|
|
This file is part of Cygwin.
|
|
|
|
This software is a copyrighted work licensed under the terms of the
|
|
Cygwin license. Please consult the file "CYGWIN_LICENSE" for
|
|
details. */
|
|
|
|
#include <stdio.h>
|
|
#include <ctype.h>
|
|
#include <string.h>
|
|
#include <stdlib.h>
|
|
#include <unistd.h>
|
|
#include <getopt.h>
|
|
#include <pwd.h>
|
|
#include <grp.h>
|
|
#include <sys/types.h>
|
|
#include <sys/acl.h>
|
|
|
|
#ifndef BOOL
|
|
#define BOOL int
|
|
#endif
|
|
|
|
#ifndef TRUE
|
|
#define TRUE (1)
|
|
#endif
|
|
|
|
#ifndef FALSE
|
|
#define FALSE (0)
|
|
#endif
|
|
|
|
#ifndef ILLEGAL_MODE
|
|
#define ILLEGAL_MODE ((mode_t)0xffffffff)
|
|
#endif
|
|
|
|
typedef enum {
|
|
NoAction,
|
|
Set,
|
|
Modify,
|
|
Delete,
|
|
ModNDel,
|
|
SetFromFile
|
|
} action_t;
|
|
|
|
char *myname;
|
|
|
|
mode_t getperm (char *in)
|
|
{
|
|
if (isdigit (*in) && !in[1])
|
|
{
|
|
int i = atoi (in);
|
|
if (i < 0 || i > 7)
|
|
return ILLEGAL_MODE;
|
|
return i << 6 | i << 3 | i;
|
|
}
|
|
if (strlen (in) > 3 && strchr (" \t\n\r#", in[3]))
|
|
in[3] = '\0';
|
|
if (strlen (in) != 3)
|
|
return ILLEGAL_MODE;
|
|
if (!strchr ("r-", in[0])
|
|
|| !strchr ("w-", in[1])
|
|
|| !strchr ("x-", in[2]))
|
|
return ILLEGAL_MODE;
|
|
return (in[0] == 'r' ? S_IRUSR | S_IRGRP | S_IROTH : 0)
|
|
| (in[1] == 'w' ? S_IWUSR | S_IWGRP | S_IWOTH : 0)
|
|
| (in[2] == 'x' ? S_IXUSR | S_IXGRP | S_IXOTH : 0);
|
|
}
|
|
|
|
BOOL
|
|
getaclentry (action_t action, char *c, aclent_t *ace)
|
|
{
|
|
char *c2;
|
|
|
|
ace->a_type = 0;
|
|
ace->a_id = 0;
|
|
ace->a_perm = 0;
|
|
|
|
if (!strncmp (c, "default:", 8)
|
|
|| !strncmp (c, "d:", 2))
|
|
{
|
|
ace->a_type = ACL_DEFAULT;
|
|
c = strchr (c, ':') + 1;
|
|
}
|
|
if (!strncmp (c, "user:", 5)
|
|
|| !strncmp (c, "u:", 2))
|
|
{
|
|
ace->a_type |= USER_OBJ;
|
|
c = strchr (c, ':') + 1;
|
|
}
|
|
else if (!strncmp (c, "group:", 6)
|
|
|| !strncmp (c, "g:", 2))
|
|
{
|
|
ace->a_type |= GROUP_OBJ;
|
|
c = strchr (c, ':') + 1;
|
|
}
|
|
else if (!strncmp (c, "mask:", 5)
|
|
|| !strncmp (c, "m:", 2))
|
|
{
|
|
ace->a_type |= CLASS_OBJ;
|
|
c = strchr (c, ':') + 1;
|
|
}
|
|
else if (!strncmp (c, "other:", 6)
|
|
|| !strncmp (c, "o:", 2))
|
|
{
|
|
ace->a_type |= OTHER_OBJ;
|
|
c = strchr (c, ':') + 1;
|
|
}
|
|
else
|
|
return FALSE;
|
|
if (ace->a_type & (USER_OBJ | GROUP_OBJ))
|
|
{
|
|
if ((c2 = strchr (c, ':')))
|
|
{
|
|
if (action == Delete)
|
|
return FALSE;
|
|
*c2 = '\0';
|
|
}
|
|
else if (action != Delete)
|
|
return FALSE;
|
|
if (c2 == c)
|
|
{
|
|
if (action == Delete)
|
|
return FALSE;
|
|
}
|
|
else if (isdigit (*c))
|
|
{
|
|
char *c3;
|
|
|
|
ace->a_id = strtol (c, &c3, 10);
|
|
if (*c3)
|
|
return FALSE;
|
|
}
|
|
else if (ace->a_type & USER_OBJ)
|
|
{
|
|
struct passwd *pw = getpwnam (c);
|
|
if (!pw)
|
|
return FALSE;
|
|
ace->a_id = pw->pw_uid;
|
|
}
|
|
else
|
|
{
|
|
struct group *gr = getgrnam (c);
|
|
if (!gr)
|
|
return FALSE;
|
|
ace->a_id = gr->gr_gid;
|
|
}
|
|
if (c2 != c)
|
|
{
|
|
if (ace->a_type & USER_OBJ)
|
|
{
|
|
ace->a_type &= ~USER_OBJ;
|
|
ace->a_type |= USER;
|
|
}
|
|
else
|
|
{
|
|
ace->a_type &= ~GROUP_OBJ;
|
|
ace->a_type |= GROUP;
|
|
}
|
|
}
|
|
if (c2)
|
|
c = c2 + 1;
|
|
}
|
|
else if (*c++ != ':')
|
|
return FALSE;
|
|
if (action == Delete)
|
|
{
|
|
if ((ace->a_type & (CLASS_OBJ | OTHER_OBJ))
|
|
&& *c)
|
|
return FALSE;
|
|
ace->a_perm = ILLEGAL_MODE;
|
|
return TRUE;
|
|
}
|
|
if ((ace->a_perm = getperm (c)) == ILLEGAL_MODE)
|
|
return FALSE;
|
|
return TRUE;
|
|
}
|
|
|
|
BOOL
|
|
getaclentries (action_t action, char *buf, aclent_t *acls, int *idx)
|
|
{
|
|
char *c;
|
|
|
|
if (action == SetFromFile)
|
|
{
|
|
FILE *fp;
|
|
char fbuf[256], *fb;
|
|
|
|
if (!strcmp (buf, "-"))
|
|
fp = stdin;
|
|
else if (! (fp = fopen (buf, "r")))
|
|
return FALSE;
|
|
while ((fb = fgets (fbuf, 256, fp)))
|
|
{
|
|
while (strchr (" \t", *fb))
|
|
++fb;
|
|
if (strchr ("\n\r#", *fb))
|
|
continue;
|
|
if (!getaclentry (action, fb, acls + (*idx)++))
|
|
{
|
|
fclose (fp);
|
|
return FALSE;
|
|
}
|
|
}
|
|
if (fp != stdin)
|
|
fclose (fp);
|
|
}
|
|
else
|
|
for (c = strtok (buf, ","); c; c = strtok (NULL, ","))
|
|
if (!getaclentry (action, c, acls + (*idx)++))
|
|
return FALSE;
|
|
return TRUE;
|
|
}
|
|
|
|
int
|
|
searchace (aclent_t *aclp, int nentries, int type, int id)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < nentries; ++i)
|
|
if ((aclp[i].a_type == type && (id < 0 || aclp[i].a_id == id))
|
|
|| !aclp[i].a_type)
|
|
return i;
|
|
return -1;
|
|
}
|
|
|
|
int
|
|
modacl (aclent_t *tgt, int tcnt, aclent_t *src, int scnt)
|
|
{
|
|
int t, s, i;
|
|
|
|
for (s = 0; s < scnt; ++s)
|
|
{
|
|
t = searchace (tgt, MAX_ACL_ENTRIES, src[s].a_type,
|
|
(src[s].a_type & (USER | GROUP)) ? src[s].a_id : -1);
|
|
if (t < 0)
|
|
return -1;
|
|
if (src[s].a_perm == ILLEGAL_MODE)
|
|
{
|
|
if (t < tcnt)
|
|
{
|
|
for (i = t + 1; i < tcnt; ++i)
|
|
tgt[i - 1] = tgt[i];
|
|
--tcnt;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
tgt[t] = src[s];
|
|
if (t >= tcnt)
|
|
++tcnt;
|
|
}
|
|
}
|
|
return tcnt;
|
|
}
|
|
|
|
void
|
|
setfacl (action_t action, char *path, aclent_t *acls, int cnt)
|
|
{
|
|
aclent_t lacl[MAX_ACL_ENTRIES];
|
|
int lcnt;
|
|
|
|
memset (lacl, 0, sizeof lacl);
|
|
if (action == Set)
|
|
{
|
|
if (acl (path, SETACL, cnt, acls))
|
|
perror (myname);
|
|
return;
|
|
}
|
|
if ((lcnt = acl (path, GETACL, MAX_ACL_ENTRIES, lacl)) < 0
|
|
|| (lcnt = modacl (lacl, lcnt, acls, cnt)) < 0
|
|
|| (lcnt = acl (path, SETACL, lcnt, lacl)) < 0)
|
|
perror (myname);
|
|
}
|
|
|
|
#define pn(txt) fprintf (fp, txt "\n", myname)
|
|
#define p(txt) fprintf (fp, txt "\n")
|
|
|
|
int
|
|
usage (int help)
|
|
{
|
|
FILE *fp = help ? stdout : stderr;
|
|
|
|
pn ("usage: %s [-r] -s acl_entries file...");
|
|
pn (" %s [-r] -md acl_entries file...");
|
|
pn (" %s [-r] -f acl_file file...");
|
|
if (!help)
|
|
pn ("Try `%s --help' for more information.");
|
|
else
|
|
{
|
|
p ("");
|
|
p ("Modify file and directory access control lists (ACLs)");
|
|
p ("");
|
|
pn ("For each file given as parameter, %s will either replace its");
|
|
p ("complete ACL (-s, -f)), or it will add, modify, or delete ACL");
|
|
p ("entries.");
|
|
p ("");
|
|
p ("The following options are supported:");
|
|
p ("");
|
|
p ("-s Substitute the ACL of the file by the entries specified on");
|
|
p (" the command line. Required entries are");
|
|
p (" - One user entry for the owner of the file.");
|
|
p (" - One group entry for the group of the file.");
|
|
p (" - One other entry.");
|
|
p (" If additional user and group entries are given:");
|
|
p (" - A mask entry for the file group class of the file.");
|
|
p (" - No duplicate user or group entries with the same uid/gid.");
|
|
p (" If it is a directory:");
|
|
p (" - One default user entry for the owner of the file.");
|
|
p (" - One default group entry for the group of the file.");
|
|
p (" - One default mask entry for the file group class of the file.");
|
|
p (" - One default other entry.");
|
|
p ("");
|
|
p (" Acl_entries are one or more comma-separated ACL entries from");
|
|
p (" the following list:");
|
|
p ("");
|
|
p (" u[ser]::perm");
|
|
p (" u[ser]:uid:perm");
|
|
p (" g[roup]::perm");
|
|
p (" g[roup]:gid:perm");
|
|
p (" m[ask]::perm");
|
|
p (" o[ther]::perm");
|
|
p ("");
|
|
p (" Default entries are like the above with the trailing default");
|
|
p (" identifier. E.g.");
|
|
p ("");
|
|
p (" d[efault]:u[ser]:uid:perm");
|
|
p ("");
|
|
p (" `perm' is either a 3-char permissions string in the form");
|
|
p (" \"rwx\" with the character - for not setting a permission");
|
|
p (" or it is the octal representation of the permissions, a");
|
|
p (" value from 0 (equivalent to \"---\") to 7 (\"rwx\").");
|
|
p (" `uid' is a user name or a numerical uid.");
|
|
p (" `gid' is a group name or a numerical gid.");
|
|
p ("");
|
|
p ("-f Like -s but take the ACL entries from `acl_file'. Acl_entries");
|
|
p (" are given one per line. Whitespace characters are ignored,");
|
|
p (" the character \"#\" may be used to start a comment. The");
|
|
p (" special filename \"-\" indicates reading from stdin.");
|
|
p ("");
|
|
p ("-m Add or modify one or more specified ACL entries. Acl_entries");
|
|
p (" is a comma-separated list of entries from the same list as");
|
|
p (" above.");
|
|
p ("");
|
|
p ("-d Delete one or more specified entries from the file's ACL.");
|
|
p (" The owner, group and others entries must not be deleted");
|
|
p (" Acl_entries are one or more comma-separated ACL entries");
|
|
p (" without permissions, taken from the following list:");
|
|
p ("");
|
|
p (" u[ser]:uid");
|
|
p (" g[roup]:gid");
|
|
p (" d[efault]:u[ser]:uid");
|
|
p (" d[efault]:g[roup]:gid");
|
|
p (" d[efault]:m[ask]:");
|
|
p (" d[efault]:o[ther]:");
|
|
p ("");
|
|
p ("-r Causes the permissions specified in the mask entry to be");
|
|
p (" ignored and replaced by the maximum permissions needed for");
|
|
p (" the file group class.");
|
|
p ("");
|
|
p ("While the -m and -d options may be used in the same command, the");
|
|
p ("-s and -f options may be used only exclusively.");
|
|
p ("");
|
|
p ("Directories may contain default ACL entries. Files created");
|
|
p ("in a directory that contains default ACL entries will have");
|
|
p ("permissions according to the combination of the current umask,");
|
|
p ("the explicit permissions requested and the default ACL entries");
|
|
p ("Note: Under Cygwin, the default ACL entries are not taken into");
|
|
p ("account currently.");
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
struct option longopts[] = {
|
|
{"help", no_argument, NULL, 'h'},
|
|
{0, no_argument, NULL, 0}
|
|
};
|
|
|
|
int
|
|
main (int argc, char **argv)
|
|
{
|
|
extern char *optarg;
|
|
extern int optind;
|
|
int c;
|
|
action_t action = NoAction;
|
|
int ropt = 0;
|
|
aclent_t acls[MAX_ACL_ENTRIES];
|
|
int aclidx = 0;
|
|
|
|
myname = argv[0];
|
|
memset (acls, 0, sizeof acls);
|
|
while ((c = getopt_long (argc, argv, "d:f:m:rs:", longopts, NULL)) != EOF)
|
|
switch (c)
|
|
{
|
|
case 'd':
|
|
if (action == NoAction)
|
|
action = Delete;
|
|
else if (action == Modify)
|
|
action = ModNDel;
|
|
else
|
|
return usage (0);
|
|
if (! getaclentries (Delete, optarg, acls, &aclidx))
|
|
{
|
|
fprintf (stderr, "%s: illegal acl entries\n", myname);
|
|
return 2;
|
|
}
|
|
break;
|
|
case 'f':
|
|
if (action == NoAction)
|
|
action = Set;
|
|
else
|
|
return usage (0);
|
|
if (! getaclentries (SetFromFile, optarg, acls, &aclidx))
|
|
{
|
|
fprintf (stderr, "%s: illegal acl entries\n", myname);
|
|
return 2;
|
|
}
|
|
break;
|
|
case 'm':
|
|
if (action == NoAction)
|
|
action = Modify;
|
|
else if (action == Delete)
|
|
action = ModNDel;
|
|
else
|
|
return usage (0);
|
|
if (! getaclentries (Modify, optarg, acls, &aclidx))
|
|
{
|
|
fprintf (stderr, "%s: illegal acl entries\n", myname);
|
|
return 2;
|
|
}
|
|
break;
|
|
case 'r':
|
|
if (!ropt)
|
|
ropt = 1;
|
|
else
|
|
return usage (0);
|
|
break;
|
|
case 's':
|
|
if (action == NoAction)
|
|
action = Set;
|
|
else
|
|
return usage (0);
|
|
break;
|
|
if (! getaclentries (Set, optarg, acls, &aclidx))
|
|
{
|
|
fprintf (stderr, "%s: illegal acl entries\n", myname);
|
|
return 2;
|
|
}
|
|
break;
|
|
case 'h':
|
|
return usage (1);
|
|
default:
|
|
return usage (0);
|
|
}
|
|
if (action == NoAction)
|
|
return usage (0);
|
|
if (optind > argc - 1)
|
|
return usage (0);
|
|
if (action == Set)
|
|
switch (aclcheck (acls, aclidx, NULL))
|
|
{
|
|
case GRP_ERROR:
|
|
fprintf (stderr, "%s: more than one group entry.\n", myname);
|
|
return 2;
|
|
case USER_ERROR:
|
|
fprintf (stderr, "%s: more than one user entry.\n", myname);
|
|
return 2;
|
|
case CLASS_ERROR:
|
|
fprintf (stderr, "%s: more than one mask entry.\n", myname);
|
|
return 2;
|
|
case OTHER_ERROR:
|
|
fprintf (stderr, "%s: more than one other entry.\n", myname);
|
|
return 2;
|
|
case DUPLICATE_ERROR:
|
|
fprintf (stderr, "%s: duplicate additional user or group.\n", myname);
|
|
return 2;
|
|
case ENTRY_ERROR:
|
|
fprintf (stderr, "%s: invalid entry type.\n", myname);
|
|
return 2;
|
|
case MISS_ERROR:
|
|
fprintf (stderr, "%s: missing entries.\n", myname);
|
|
return 2;
|
|
case MEM_ERROR:
|
|
fprintf (stderr, "%s: out of memory.\n", myname);
|
|
return 2;
|
|
default:
|
|
break;
|
|
}
|
|
for (c = optind; c < argc; ++c)
|
|
setfacl (action, argv[c], acls, aclidx);
|
|
return 0;
|
|
}
|