getconf: don't include xpg4 bits, gcc7 includes xpg6 bits for us
[unleashed.git] / usr / src / lib / libsec / common / aclutils.c
blobf104687b4461e1ebad66ecf8ec4e7fce53ca5b53
1 /*
2 * CDDL HEADER START
4 * The contents of this file are subject to the terms of the
5 * Common Development and Distribution License (the "License").
6 * You may not use this file except in compliance with the License.
8 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9 * or http://www.opensolaris.org/os/licensing.
10 * See the License for the specific language governing permissions
11 * and limitations under the License.
13 * When distributing Covered Code, include this CDDL HEADER in each
14 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15 * If applicable, add the following below this CDDL HEADER, with the
16 * fields enclosed by brackets "[]" replaced with your own identifying
17 * information: Portions Copyright [yyyy] [name of copyright owner]
19 * CDDL HEADER END
22 * Copyright (c) 2005, 2010, Oracle and/or its affiliates. All rights reserved.
23 * Copyright 2011 Nexenta Systems, Inc. All rights reserved.
27 #include <stdlib.h>
28 #include <string.h>
29 #include <unistd.h>
30 #include <limits.h>
31 #include <grp.h>
32 #include <pwd.h>
33 #include <strings.h>
34 #include <sys/types.h>
35 #include <errno.h>
36 #include <sys/stat.h>
37 #include <sys/varargs.h>
38 #include <locale.h>
39 #include <aclutils.h>
40 #include <sys/avl.h>
41 #include <acl_common.h>
42 #include <idmap.h>
44 #define ACL_PATH 0
45 #define ACL_FD 1
48 typedef union {
49 const char *file;
50 int fd;
51 } acl_inp;
55 * Determine whether a file has a trivial ACL
56 * returns: 0 = trivial
57 * 1 = nontrivial
58 * <0 some other system failure, such as ENOENT or EPERM
60 int
61 acl_trivial(const char *filename)
63 int acl_flavor;
64 int aclcnt;
65 int cntcmd;
66 int val = 0;
67 ace_t *acep;
69 acl_flavor = pathconf(filename, _PC_ACL_ENABLED);
71 if (acl_flavor == _ACL_ACE_ENABLED)
72 cntcmd = ACE_GETACLCNT;
73 else
74 cntcmd = GETACLCNT;
76 aclcnt = acl(filename, cntcmd, 0, NULL);
77 if (aclcnt > 0) {
78 if (acl_flavor == _ACL_ACE_ENABLED) {
79 acep = malloc(sizeof (ace_t) * aclcnt);
80 if (acep == NULL)
81 return (-1);
82 if (acl(filename, ACE_GETACL,
83 aclcnt, acep) < 0) {
84 free(acep);
85 return (-1);
88 val = ace_trivial(acep, aclcnt);
89 free(acep);
91 } else if (aclcnt > MIN_ACL_ENTRIES)
92 val = 1;
94 return (val);
98 static int
99 cacl_get(acl_inp inp, int get_flag, int type, acl_t **aclp)
101 const char *fname;
102 int fd;
103 int ace_acl = 0;
104 int error;
105 int getcmd, cntcmd;
106 acl_t *acl_info;
107 int save_errno;
108 int stat_error;
109 struct stat64 statbuf;
111 *aclp = NULL;
112 if (type == ACL_PATH) {
113 fname = inp.file;
114 ace_acl = pathconf(fname, _PC_ACL_ENABLED);
115 } else {
116 fd = inp.fd;
117 ace_acl = fpathconf(fd, _PC_ACL_ENABLED);
121 * if acl's aren't supported then
122 * send it through the old GETACL interface
124 if (ace_acl == 0 || ace_acl == -1) {
125 ace_acl = _ACL_ACLENT_ENABLED;
128 if (ace_acl & _ACL_ACE_ENABLED) {
129 cntcmd = ACE_GETACLCNT;
130 getcmd = ACE_GETACL;
131 acl_info = acl_alloc(ACE_T);
132 } else {
133 cntcmd = GETACLCNT;
134 getcmd = GETACL;
135 acl_info = acl_alloc(ACLENT_T);
138 if (acl_info == NULL)
139 return (-1);
141 if (type == ACL_PATH) {
142 acl_info->acl_cnt = acl(fname, cntcmd, 0, NULL);
143 } else {
144 acl_info->acl_cnt = facl(fd, cntcmd, 0, NULL);
147 save_errno = errno;
148 if (acl_info->acl_cnt < 0) {
149 acl_free(acl_info);
150 errno = save_errno;
151 return (-1);
154 if (acl_info->acl_cnt == 0) {
155 acl_free(acl_info);
156 errno = save_errno;
157 return (0);
160 acl_info->acl_aclp =
161 malloc(acl_info->acl_cnt * acl_info->acl_entry_size);
162 save_errno = errno;
164 if (acl_info->acl_aclp == NULL) {
165 acl_free(acl_info);
166 errno = save_errno;
167 return (-1);
170 if (type == ACL_PATH) {
171 stat_error = stat64(fname, &statbuf);
172 error = acl(fname, getcmd, acl_info->acl_cnt,
173 acl_info->acl_aclp);
174 } else {
175 stat_error = fstat64(fd, &statbuf);
176 error = facl(fd, getcmd, acl_info->acl_cnt,
177 acl_info->acl_aclp);
180 save_errno = errno;
181 if (error == -1) {
182 acl_free(acl_info);
183 errno = save_errno;
184 return (-1);
188 if (stat_error == 0) {
189 acl_info->acl_flags =
190 (S_ISDIR(statbuf.st_mode) ? ACL_IS_DIR : 0);
191 } else
192 acl_info->acl_flags = 0;
194 switch (acl_info->acl_type) {
195 case ACLENT_T:
196 if (acl_info->acl_cnt <= MIN_ACL_ENTRIES)
197 acl_info->acl_flags |= ACL_IS_TRIVIAL;
198 break;
199 case ACE_T:
200 if (ace_trivial(acl_info->acl_aclp, acl_info->acl_cnt) == 0)
201 acl_info->acl_flags |= ACL_IS_TRIVIAL;
202 break;
203 default:
204 errno = EINVAL;
205 acl_free(acl_info);
206 return (-1);
209 if ((acl_info->acl_flags & ACL_IS_TRIVIAL) &&
210 (get_flag & ACL_NO_TRIVIAL)) {
211 acl_free(acl_info);
212 errno = 0;
213 return (0);
216 *aclp = acl_info;
217 return (0);
221 * return -1 on failure, otherwise the number of acl
222 * entries is returned
225 acl_get(const char *path, int get_flag, acl_t **aclp)
227 acl_inp acl_inp;
228 acl_inp.file = path;
230 return (cacl_get(acl_inp, get_flag, ACL_PATH, aclp));
234 facl_get(int fd, int get_flag, acl_t **aclp)
237 acl_inp acl_inp;
238 acl_inp.fd = fd;
240 return (cacl_get(acl_inp, get_flag, ACL_FD, aclp));
244 * Set an ACL, translates acl to ace_t when appropriate.
246 static int
247 cacl_set(acl_inp *acl_inp, acl_t *aclp, int type)
249 int error = 0;
250 int acl_flavor_target;
251 struct stat64 statbuf;
252 int stat_error;
253 int isdir;
256 if (type == ACL_PATH) {
257 stat_error = stat64(acl_inp->file, &statbuf);
258 if (stat_error)
259 return (-1);
260 acl_flavor_target = pathconf(acl_inp->file, _PC_ACL_ENABLED);
261 } else {
262 stat_error = fstat64(acl_inp->fd, &statbuf);
263 if (stat_error)
264 return (-1);
265 acl_flavor_target = fpathconf(acl_inp->fd, _PC_ACL_ENABLED);
269 * If target returns an error or 0 from pathconf call then
270 * fall back to UFS/POSIX Draft interface.
271 * In the case of 0 we will then fail in either acl(2) or
272 * acl_translate(). We could erroneously get 0 back from
273 * a file system that is using fs_pathconf() and not answering
274 * the _PC_ACL_ENABLED question itself.
276 if (acl_flavor_target == 0 || acl_flavor_target == -1)
277 acl_flavor_target = _ACL_ACLENT_ENABLED;
279 isdir = S_ISDIR(statbuf.st_mode);
281 if ((error = acl_translate(aclp, acl_flavor_target, isdir,
282 statbuf.st_uid, statbuf.st_gid)) != 0) {
283 return (error);
286 if (type == ACL_PATH) {
287 error = acl(acl_inp->file,
288 (aclp->acl_type == ACE_T) ? ACE_SETACL : SETACL,
289 aclp->acl_cnt, aclp->acl_aclp);
290 } else {
291 error = facl(acl_inp->fd,
292 (aclp->acl_type == ACE_T) ? ACE_SETACL : SETACL,
293 aclp->acl_cnt, aclp->acl_aclp);
296 return (error);
300 acl_set(const char *path, acl_t *aclp)
302 acl_inp acl_inp;
304 acl_inp.file = path;
306 return (cacl_set(&acl_inp, aclp, ACL_PATH));
310 facl_set(int fd, acl_t *aclp)
312 acl_inp acl_inp;
314 acl_inp.fd = fd;
316 return (cacl_set(&acl_inp, aclp, ACL_FD));
320 acl_cnt(acl_t *aclp)
322 return (aclp->acl_cnt);
326 acl_type(acl_t *aclp)
328 return (aclp->acl_type);
331 acl_t *
332 acl_dup(acl_t *aclp)
334 acl_t *newaclp;
336 newaclp = acl_alloc(aclp->acl_type);
337 if (newaclp == NULL)
338 return (NULL);
340 newaclp->acl_aclp = malloc(aclp->acl_entry_size * aclp->acl_cnt);
341 if (newaclp->acl_aclp == NULL) {
342 acl_free(newaclp);
343 return (NULL);
346 (void) memcpy(newaclp->acl_aclp,
347 aclp->acl_aclp, aclp->acl_entry_size * aclp->acl_cnt);
348 newaclp->acl_cnt = aclp->acl_cnt;
350 return (newaclp);
354 acl_flags(acl_t *aclp)
356 return (aclp->acl_flags);
359 void *
360 acl_data(acl_t *aclp)
362 return (aclp->acl_aclp);
366 * Take an acl array and build an acl_t.
368 acl_t *
369 acl_to_aclp(enum acl_type type, void *acl, int count)
371 acl_t *aclp;
374 aclp = acl_alloc(type);
375 if (aclp == NULL)
376 return (aclp);
378 aclp->acl_aclp = acl;
379 aclp->acl_cnt = count;
381 return (aclp);
385 * Remove an ACL from a file and create a trivial ACL based
386 * off of the mode argument. After acl has been set owner/group
387 * are updated to match owner,group arguments
390 acl_strip(const char *file, uid_t owner, gid_t group, mode_t mode)
392 int error = 0;
393 aclent_t min_acl[MIN_ACL_ENTRIES];
394 ace_t *min_ace_acl;
395 int acl_flavor;
396 int aclcnt;
397 struct stat64 statbuf;
399 acl_flavor = pathconf(file, _PC_ACL_ENABLED);
401 if (stat64(file, &statbuf) != 0) {
402 error = 1;
403 return (error);
407 * force it through aclent flavor when file system doesn't
408 * understand question
410 if (acl_flavor == 0 || acl_flavor == -1)
411 acl_flavor = _ACL_ACLENT_ENABLED;
413 if (acl_flavor & _ACL_ACLENT_ENABLED) {
414 min_acl[0].a_type = USER_OBJ;
415 min_acl[0].a_id = owner;
416 min_acl[0].a_perm = ((mode & 0700) >> 6);
417 min_acl[1].a_type = GROUP_OBJ;
418 min_acl[1].a_id = group;
419 min_acl[1].a_perm = ((mode & 0070) >> 3);
420 min_acl[2].a_type = CLASS_OBJ;
421 min_acl[2].a_id = (uid_t)-1;
422 min_acl[2].a_perm = ((mode & 0070) >> 3);
423 min_acl[3].a_type = OTHER_OBJ;
424 min_acl[3].a_id = (uid_t)-1;
425 min_acl[3].a_perm = (mode & 0007);
426 aclcnt = 4;
427 error = acl(file, SETACL, aclcnt, min_acl);
428 } else if (acl_flavor & _ACL_ACE_ENABLED) {
429 if ((error = acl_trivial_create(mode, S_ISDIR(statbuf.st_mode),
430 &min_ace_acl, &aclcnt)) != 0)
431 return (error);
432 error = acl(file, ACE_SETACL, aclcnt, min_ace_acl);
433 free(min_ace_acl);
434 } else {
435 errno = EINVAL;
436 error = 1;
439 if (error == 0)
440 error = chown(file, owner, group);
441 return (error);
444 static int
445 ace_match(void *entry1, void *entry2)
447 ace_t *p1 = (ace_t *)entry1;
448 ace_t *p2 = (ace_t *)entry2;
449 ace_t ace1, ace2;
451 ace1 = *p1;
452 ace2 = *p2;
455 * Need to fixup who field for abstrations for
456 * accurate comparison, since field is undefined.
458 if (ace1.a_flags & (ACE_OWNER|ACE_GROUP|ACE_EVERYONE))
459 ace1.a_who = (uid_t)-1;
460 if (ace2.a_flags & (ACE_OWNER|ACE_GROUP|ACE_EVERYONE))
461 ace2.a_who = (uid_t)-1;
462 return (memcmp(&ace1, &ace2, sizeof (ace_t)));
465 static int
466 aclent_match(void *entry1, void *entry2)
468 aclent_t *aclent1 = (aclent_t *)entry1;
469 aclent_t *aclent2 = (aclent_t *)entry2;
471 return (memcmp(aclent1, aclent2, sizeof (aclent_t)));
475 * Find acl entries in acl that correspond to removeacl. Search
476 * is started from slot. The flag argument indicates whether to
477 * remove all matches or just the first match.
480 acl_removeentries(acl_t *acl, acl_t *removeacl, int start_slot, int flag)
482 int i, j;
483 int match;
484 int (*acl_match)(void *acl1, void *acl2);
485 void *acl_entry, *remove_entry;
486 void *start;
487 int found = 0;
489 if (flag != ACL_REMOVE_ALL && flag != ACL_REMOVE_FIRST)
490 flag = ACL_REMOVE_FIRST;
492 if (acl == NULL || removeacl == NULL)
493 return (EACL_NO_ACL_ENTRY);
495 if (acl->acl_type != removeacl->acl_type)
496 return (EACL_DIFF_TYPE);
498 if (acl->acl_type == ACLENT_T)
499 acl_match = aclent_match;
500 else
501 acl_match = ace_match;
503 for (i = 0, remove_entry = removeacl->acl_aclp;
504 i != removeacl->acl_cnt; i++) {
506 j = 0;
507 acl_entry = (char *)acl->acl_aclp +
508 (acl->acl_entry_size * start_slot);
509 for (;;) {
510 match = acl_match(acl_entry, remove_entry);
511 if (match == 0) {
512 found++;
514 /* avoid memmove if last entry */
515 if (acl->acl_cnt == (j + 1)) {
516 acl->acl_cnt--;
517 break;
520 start = (char *)acl_entry +
521 acl->acl_entry_size;
522 (void) memmove(acl_entry, start,
523 acl->acl_entry_size *
524 (acl->acl_cnt-- - (j + 1)));
526 if (flag == ACL_REMOVE_FIRST)
527 break;
529 * List has changed, just continue so this
530 * slot gets checked with it's new contents.
532 continue;
534 acl_entry = ((char *)acl_entry + acl->acl_entry_size);
535 if (++j >= acl->acl_cnt) {
536 break;
539 remove_entry = (char *)remove_entry + removeacl->acl_entry_size;
542 return ((found == 0) ? EACL_NO_ACL_ENTRY : 0);
546 * Replace entires entries in acl1 with the corresponding entries
547 * in newentries. The where argument specifies where to begin
548 * the replacement. If the where argument is 1 greater than the
549 * number of acl entries in acl1 then they are appended. If the
550 * where argument is 2+ greater than the number of acl entries then
551 * EACL_INVALID_SLOT is returned.
554 acl_modifyentries(acl_t *acl1, acl_t *newentries, int where)
557 int slot;
558 int slots_needed;
559 int slots_left;
560 int newsize;
562 if (acl1 == NULL || newentries == NULL)
563 return (EACL_NO_ACL_ENTRY);
565 if (where < 0 || where >= acl1->acl_cnt)
566 return (EACL_INVALID_SLOT);
568 if (acl1->acl_type != newentries->acl_type)
569 return (EACL_DIFF_TYPE);
571 slot = where;
573 slots_left = acl1->acl_cnt - slot + 1;
574 if (slots_left < newentries->acl_cnt) {
575 slots_needed = newentries->acl_cnt - slots_left;
576 newsize = (acl1->acl_entry_size * acl1->acl_cnt) +
577 (acl1->acl_entry_size * slots_needed);
578 acl1->acl_aclp = realloc(acl1->acl_aclp, newsize);
579 if (acl1->acl_aclp == NULL)
580 return (-1);
582 (void) memcpy((char *)acl1->acl_aclp + (acl1->acl_entry_size * slot),
583 newentries->acl_aclp,
584 newentries->acl_entry_size * newentries->acl_cnt);
587 * Did ACL grow?
590 if ((slot + newentries->acl_cnt) > acl1->acl_cnt) {
591 acl1->acl_cnt = slot + newentries->acl_cnt;
594 return (0);
598 * Add acl2 entries into acl1. The where argument specifies where
599 * to add the entries.
602 acl_addentries(acl_t *acl1, acl_t *acl2, int where)
605 int newsize;
606 int len;
607 void *start;
608 void *to;
610 if (acl1 == NULL || acl2 == NULL)
611 return (EACL_NO_ACL_ENTRY);
613 if (acl1->acl_type != acl2->acl_type)
614 return (EACL_DIFF_TYPE);
617 * allow where to specify 1 past last slot for an append operation
618 * but anything greater is an error.
620 if (where < 0 || where > acl1->acl_cnt)
621 return (EACL_INVALID_SLOT);
623 newsize = (acl2->acl_entry_size * acl2->acl_cnt) +
624 (acl1->acl_entry_size * acl1->acl_cnt);
625 acl1->acl_aclp = realloc(acl1->acl_aclp, newsize);
626 if (acl1->acl_aclp == NULL)
627 return (-1);
630 * first push down entries where new ones will be inserted
633 to = (void *)((char *)acl1->acl_aclp +
634 ((where + acl2->acl_cnt) * acl1->acl_entry_size));
636 start = (void *)((char *)acl1->acl_aclp +
637 where * acl1->acl_entry_size);
639 if (where < acl1->acl_cnt) {
640 len = (acl1->acl_cnt - where) * acl1->acl_entry_size;
641 (void) memmove(to, start, len);
645 * now stick in new entries.
648 (void) memmove(start, acl2->acl_aclp,
649 acl2->acl_cnt * acl2->acl_entry_size);
651 acl1->acl_cnt += acl2->acl_cnt;
652 return (0);
656 * return text for an ACL error.
658 char *
659 acl_strerror(int errnum)
661 switch (errnum) {
662 case EACL_GRP_ERROR:
663 return (dgettext(TEXT_DOMAIN,
664 "There is more than one group or default group entry"));
665 case EACL_USER_ERROR:
666 return (dgettext(TEXT_DOMAIN,
667 "There is more than one user or default user entry"));
668 case EACL_OTHER_ERROR:
669 return (dgettext(TEXT_DOMAIN,
670 "There is more than one other entry"));
671 case EACL_CLASS_ERROR:
672 return (dgettext(TEXT_DOMAIN,
673 "There is more than one mask entry"));
674 case EACL_DUPLICATE_ERROR:
675 return (dgettext(TEXT_DOMAIN,
676 "Duplicate user or group entries"));
677 case EACL_MISS_ERROR:
678 return (dgettext(TEXT_DOMAIN,
679 "Missing user/group owner, other, mask entry"));
680 case EACL_MEM_ERROR:
681 return (dgettext(TEXT_DOMAIN,
682 "Memory error"));
683 case EACL_ENTRY_ERROR:
684 return (dgettext(TEXT_DOMAIN,
685 "Unrecognized entry type"));
686 case EACL_INHERIT_ERROR:
687 return (dgettext(TEXT_DOMAIN,
688 "Invalid inheritance flags"));
689 case EACL_FLAGS_ERROR:
690 return (dgettext(TEXT_DOMAIN,
691 "Unrecognized entry flags"));
692 case EACL_PERM_MASK_ERROR:
693 return (dgettext(TEXT_DOMAIN,
694 "Invalid ACL permissions"));
695 case EACL_COUNT_ERROR:
696 return (dgettext(TEXT_DOMAIN,
697 "Invalid ACL count"));
698 case EACL_INVALID_SLOT:
699 return (dgettext(TEXT_DOMAIN,
700 "Invalid ACL entry number specified"));
701 case EACL_NO_ACL_ENTRY:
702 return (dgettext(TEXT_DOMAIN,
703 "ACL entry doesn't exist"));
704 case EACL_DIFF_TYPE:
705 return (dgettext(TEXT_DOMAIN,
706 "Different file system ACL types cannot be merged"));
707 case EACL_INVALID_USER_GROUP:
708 return (dgettext(TEXT_DOMAIN, "Invalid user or group"));
709 case EACL_INVALID_STR:
710 return (dgettext(TEXT_DOMAIN, "ACL string is invalid"));
711 case EACL_FIELD_NOT_BLANK:
712 return (dgettext(TEXT_DOMAIN, "Field expected to be blank"));
713 case EACL_INVALID_ACCESS_TYPE:
714 return (dgettext(TEXT_DOMAIN, "Invalid access type"));
715 case EACL_UNKNOWN_DATA:
716 return (dgettext(TEXT_DOMAIN, "Unrecognized entry"));
717 case EACL_MISSING_FIELDS:
718 return (dgettext(TEXT_DOMAIN,
719 "ACL specification missing required fields"));
720 case EACL_INHERIT_NOTDIR:
721 return (dgettext(TEXT_DOMAIN,
722 "Inheritance flags are only allowed on directories"));
723 case -1:
724 return (strerror(errno));
725 default:
726 errno = EINVAL;
727 return (dgettext(TEXT_DOMAIN, "Unknown error"));
731 extern int yyinteractive;
733 /* PRINTFLIKE1 */
734 void
735 acl_error(const char *fmt, ...)
737 va_list va;
739 if (yyinteractive == 0)
740 return;
742 va_start(va, fmt);
743 (void) vfprintf(stderr, fmt, va);
744 va_end(va);
748 sid_to_id(char *sid, boolean_t user, uid_t *id)
750 idmap_get_handle_t *get_hdl = NULL;
751 char *rid_start = NULL;
752 idmap_stat status;
753 char *end;
754 int error = 1;
755 char *domain_start;
757 if ((domain_start = strchr(sid, '@')) == NULL) {
758 idmap_rid_t rid;
760 if ((rid_start = strrchr(sid, '-')) == NULL)
761 return (1);
762 *rid_start++ = '\0';
763 errno = 0;
764 rid = strtoul(rid_start--, &end, 10);
765 if (errno == 0 && *end == '\0') {
766 if (idmap_get_create(&get_hdl) ==
767 IDMAP_SUCCESS) {
768 if (user)
769 error = idmap_get_uidbysid(get_hdl,
770 sid, rid, IDMAP_REQ_FLG_USE_CACHE,
771 id, &status);
772 else
773 error = idmap_get_gidbysid(get_hdl,
774 sid, rid, IDMAP_REQ_FLG_USE_CACHE,
775 id, &status);
776 if (error == IDMAP_SUCCESS) {
777 error = idmap_get_mappings(get_hdl);
778 if (error == IDMAP_SUCCESS &&
779 status != IDMAP_SUCCESS)
780 error = 1;
781 else
782 error = 0;
784 } else {
785 error = 1;
787 if (get_hdl)
788 idmap_get_destroy(get_hdl);
789 } else {
790 error = 1;
792 *rid_start = '-'; /* putback character removed earlier */
793 } else {
794 char *name = sid;
795 *domain_start++ = '\0';
797 if (user)
798 error = idmap_getuidbywinname(name, domain_start,
799 IDMAP_REQ_FLG_USE_CACHE, id);
800 else
801 error = idmap_getgidbywinname(name, domain_start,
802 IDMAP_REQ_FLG_USE_CACHE, id);
803 *--domain_start = '@';
804 error = (error == IDMAP_SUCCESS) ? 0 : 1;
807 return (error);