/* * setcifsacl utility * * Copyright (C) Shirish Pargaonkar (shirishp@us.ibm.com) 2011 * * Used to alter entries of an ACL or replace an entire ACL in a * security descriptor of a file system object that belongs to a * share mounted using option cifsacl. * * 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 * (at your option) 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #ifdef HAVE_CONFIG_H #include "config.h" #endif /* HAVE_CONFIG_H */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include "cifsacl.h" static const char *prog = "setcifsacl"; static void copy_sec_desc(const struct cifs_ntsd *pntsd, struct cifs_ntsd *pnntsd, int numaces, int acessize) { int i; int osidsoffset, gsidsoffset, dacloffset; struct cifs_sid *owner_sid_ptr, *group_sid_ptr; struct cifs_sid *nowner_sid_ptr, *ngroup_sid_ptr; struct cifs_ctrl_acl *dacl_ptr, *ndacl_ptr; /* copy security descriptor control portion */ osidsoffset = htole32(pntsd->osidoffset); gsidsoffset = htole32(pntsd->gsidoffset); dacloffset = htole32(pntsd->dacloffset); pnntsd->revision = pntsd->revision; pnntsd->type = pntsd->type; pnntsd->osidoffset = pntsd->osidoffset; pnntsd->gsidoffset = pntsd->gsidoffset; pnntsd->dacloffset = pntsd->dacloffset; dacl_ptr = (struct cifs_ctrl_acl *)((char *)pntsd + dacloffset); ndacl_ptr = (struct cifs_ctrl_acl *)((char *)pnntsd + dacloffset); ndacl_ptr->revision = dacl_ptr->revision; ndacl_ptr->size = htole16(acessize + sizeof(struct cifs_ctrl_acl)); ndacl_ptr->num_aces = htole32(numaces); /* copy owner sid */ owner_sid_ptr = (struct cifs_sid *)((char *)pntsd + osidsoffset); nowner_sid_ptr = (struct cifs_sid *)((char *)pnntsd + osidsoffset); nowner_sid_ptr->revision = owner_sid_ptr->revision; nowner_sid_ptr->num_subauth = owner_sid_ptr->num_subauth; for (i = 0; i < 6; i++) nowner_sid_ptr->authority[i] = owner_sid_ptr->authority[i]; for (i = 0; i < 5; i++) nowner_sid_ptr->sub_auth[i] = owner_sid_ptr->sub_auth[i]; /* copy group sid */ group_sid_ptr = (struct cifs_sid *)((char *)pntsd + gsidsoffset); ngroup_sid_ptr = (struct cifs_sid *)((char *)pnntsd + gsidsoffset); ngroup_sid_ptr->revision = group_sid_ptr->revision; ngroup_sid_ptr->num_subauth = group_sid_ptr->num_subauth; for (i = 0; i < 6; i++) ngroup_sid_ptr->authority[i] = group_sid_ptr->authority[i]; for (i = 0; i < 5; i++) ngroup_sid_ptr->sub_auth[i] = group_sid_ptr->sub_auth[i]; return; } static int copy_ace(struct cifs_ace *dace, struct cifs_ace *sace) { int i; dace->type = sace->type; dace->flags = sace->flags; dace->access_req = htole32(sace->access_req); dace->sid.revision = sace->sid.revision; dace->sid.num_subauth = sace->sid.num_subauth; for (i = 0; i < 6; i++) dace->sid.authority[i] = sace->sid.authority[i]; for (i = 0; i < sace->sid.num_subauth; i++) dace->sid.sub_auth[i] = sace->sid.sub_auth[i]; dace->size = htole16(sace->size); return dace->size; } static int compare_aces(struct cifs_ace *sace, struct cifs_ace *dace, int compflags) { int i; if (compflags & COMPSID) { if (dace->sid.revision != sace->sid.revision) return 0; if (dace->sid.num_subauth != sace->sid.num_subauth) return 0; for (i = 0; i < 6; i++) { if (dace->sid.authority[i] != sace->sid.authority[i]) return 0; } for (i = 0; i < sace->sid.num_subauth; i++) { if (dace->sid.sub_auth[i] != sace->sid.sub_auth[i]) return 0; } } if (compflags & COMPTYPE) { if (dace->type != sace->type) return 0; } if (compflags & COMPFLAG) { if (dace->flags != sace->flags) return 0; } if (compflags & COMPMASK) { if (dace->access_req != htole32(sace->access_req)) return 0; } return 1; } static int get_sec_desc_size(struct cifs_ntsd *pntsd, struct cifs_ntsd **npntsd, int aces, ssize_t *bufsize, size_t *acesoffset) { unsigned int size, acessize, dacloffset; size = sizeof(struct cifs_ntsd) + 2 * sizeof(struct cifs_sid) + sizeof(struct cifs_ctrl_acl); dacloffset = le32toh(pntsd->dacloffset); *acesoffset = dacloffset + sizeof(struct cifs_ctrl_acl); acessize = aces * sizeof(struct cifs_ace); *bufsize = size + acessize; *npntsd = malloc(*bufsize); if (!*npntsd) { printf("%s: Memory allocation failure", __func__); return errno; } return 0; } static int ace_set(struct cifs_ntsd *pntsd, struct cifs_ntsd **npntsd, ssize_t *bufsize, struct cifs_ace **cacesptr, int numcaces) { int i, rc, acessize = 0; size_t acesoffset; char *acesptr; rc = get_sec_desc_size(pntsd, npntsd, numcaces, bufsize, &acesoffset); if (rc) return rc; acesptr = (char *)*npntsd + acesoffset; for (i = 0; i < numcaces; ++i) { acessize += copy_ace((struct cifs_ace *)acesptr, cacesptr[i]); acesptr += sizeof(struct cifs_ace); } copy_sec_desc(pntsd, *npntsd, numcaces, acessize); acesptr = (char *)*npntsd + acesoffset; return 0; } static int ace_add(struct cifs_ntsd *pntsd, struct cifs_ntsd **npntsd, ssize_t *bufsize, struct cifs_ace **facesptr, int numfaces, struct cifs_ace **cacesptr, int numcaces) { int i, rc, numaces, size, acessize = 0; size_t acesoffset; char *acesptr; numaces = numfaces + numcaces; rc = get_sec_desc_size(pntsd, npntsd, numaces, bufsize, &acesoffset); if (rc) return rc; acesptr = (char *)*npntsd + acesoffset; for (i = 0; i < numfaces; ++i) { size = copy_ace((struct cifs_ace *)acesptr, facesptr[i]); acesptr += size; acessize += size; } for (i = 0; i < numcaces; ++i) { size = copy_ace((struct cifs_ace *)acesptr, cacesptr[i]); acesptr += size; acessize += size; } copy_sec_desc(pntsd, *npntsd, numaces, acessize); return 0; } static int ace_modify(struct cifs_ntsd *pntsd, struct cifs_ntsd **npntsd, ssize_t *bufsize, struct cifs_ace **facesptr, int numfaces, struct cifs_ace **cacesptr, int numcaces) { int i, j, rc, size, acessize = 0; size_t acesoffset; char *acesptr; if (numfaces == 0) { printf("%s: No entries to modify", __func__); return -1; } rc = get_sec_desc_size(pntsd, npntsd, numfaces, bufsize, &acesoffset); if (rc) return rc; for (j = 0; j < numcaces; ++j) { for (i = 0; i < numfaces; ++i) { if (compare_aces(facesptr[i], cacesptr[j], COMPSID | COMPTYPE)) { copy_ace(facesptr[i], cacesptr[j]); break; } } } acesptr = (char *)*npntsd + acesoffset; for (i = 0; i < numfaces; ++i) { size = copy_ace((struct cifs_ace *)acesptr, facesptr[i]); acesptr += size; acessize += size; } copy_sec_desc(pntsd, *npntsd, numfaces, acessize); return 0; } static int ace_delete(struct cifs_ntsd *pntsd, struct cifs_ntsd **npntsd, ssize_t *bufsize, struct cifs_ace **facesptr, int numfaces, struct cifs_ace **cacesptr, int numcaces) { int i, j, numaces = 0, rc, size, acessize = 0; size_t acesoffset; char *acesptr; if (numfaces == 0) { printf("%s: No entries to delete\n", __func__); return -1; } if (numfaces < numcaces) { printf("%s: Invalid entries to delete\n", __func__); return -1; } rc = get_sec_desc_size(pntsd, npntsd, numfaces, bufsize, &acesoffset); if (rc) return rc; acesptr = (char *)*npntsd + acesoffset; for (i = 0; i < numfaces; ++i) { for (j = 0; j < numcaces; ++j) { if (compare_aces(facesptr[i], cacesptr[j], COMPALL)) break; } if (j == numcaces) { size = copy_ace((struct cifs_ace *)acesptr, facesptr[i]); acessize += size; acesptr += size; ++numaces; } } if (numaces == numfaces) { printf("%s: Nothing to delete\n", __func__); return 1; } copy_sec_desc(pntsd, *npntsd, numaces, acessize); return 0; } static int get_numfaces(struct cifs_ntsd *pntsd, ssize_t acl_len, struct cifs_ctrl_acl **daclptr) { int numfaces = 0; uint32_t dacloffset; struct cifs_ctrl_acl *ldaclptr; char *end_of_acl = ((char *)pntsd) + acl_len; if (pntsd == NULL) return 0; dacloffset = le32toh(pntsd->dacloffset); if (!dacloffset) return 0; else { ldaclptr = (struct cifs_ctrl_acl *)((char *)pntsd + dacloffset); /* validate that we do not go past end of acl */ if (end_of_acl >= (char *)ldaclptr + le16toh(ldaclptr->size)) { numfaces = le32toh(ldaclptr->num_aces); *daclptr = ldaclptr; } } return numfaces; } static struct cifs_ace ** build_fetched_aces(char *daclptr, int numfaces) { int i, j, rc = 0, acl_size; char *acl_base; struct cifs_ace *pace, **facesptr; facesptr = (struct cifs_ace **)malloc(numfaces * sizeof(struct cifs_aces *)); if (!facesptr) { printf("%s: Error %d allocating ACE array", __func__, errno); rc = errno; } acl_base = daclptr; acl_size = sizeof(struct cifs_ctrl_acl); for (i = 0; i < numfaces; ++i) { facesptr[i] = malloc(sizeof(struct cifs_ace)); if (!facesptr[i]) { rc = errno; goto build_fetched_aces_ret; } pace = (struct cifs_ace *) (acl_base + acl_size); memcpy(facesptr[i], pace, sizeof(struct cifs_ace)); acl_base = (char *)pace; acl_size = le16toh(pace->size); } build_fetched_aces_ret: if (rc) { printf("%s: Invalid fetched ace\n", __func__); if (i) { for (j = i; j >= 0; --j) free(facesptr[j]); } free(facesptr); } return facesptr; } static int verify_ace_sid(char *sidstr, struct cifs_sid *sid) { int rc; char *lstr; struct passwd *winpswdptr; lstr = strstr(sidstr, "\\"); /* everything before | */ if (lstr) ++lstr; else lstr = sidstr; /* Check if it is a (raw) SID (string) */ rc = wbcStringToSid(lstr, (struct wbcDomainSid *)sid); if (!rc) return rc; /* Check if it a name (string) which can be resolved to a SID*/ rc = wbcGetpwnam(lstr, &winpswdptr); if (rc) { printf("%s: Invalid user name: %s\n", __func__, sidstr); return rc; } rc = wbcUidToSid(winpswdptr->pw_uid, (struct wbcDomainSid *)sid); if (rc) { printf("%s: Invalid user: %s\n", __func__, sidstr); return rc; } return 0; } static int verify_ace_type(char *typestr, uint8_t *typeval) { int i, len; char *invaltype; if (strstr(typestr, "0x")) { /* hex type value */ *typeval = strtol(typestr, &invaltype, 16); if (!strlen(invaltype)) { if (*typeval != ACCESS_ALLOWED && *typeval != ACCESS_DENIED && *typeval != ACCESS_ALLOWED_OBJECT && *typeval != ACCESS_DENIED_OBJECT) { printf("%s: Invalid type: %s\n", __func__, typestr); return 1; } return 0; } } len = strlen(typestr); for (i = 0; i < len; ++i) *(typestr + i) = toupper(*(typestr + i)); if (!strcmp(typestr, "ALLOWED")) *typeval = 0x0; else if (!strcmp(typestr, "DENIED")) *typeval = 0x1; else if (!strcmp(typestr, "ALLOWED_OBJECT")) *typeval = 0x5; else if (!strcmp(typestr, "DENIED_OBJECT")) *typeval = 0x6; else { printf("%s: Invalid type: %s\n", __func__, typestr); return 1; } return 0; } static uint8_t ace_flag_value(char *flagstr) { uint8_t flagval = 0x0; char *iflag; iflag = strtok(flagstr, "|"); /* everything before | */ while (iflag) { if (!strcmp(iflag, "OI")) flagval += 0x1; else if (!strcmp(iflag, "CI")) flagval += 0x2; else if (!strcmp(iflag, "NP")) flagval += 0x4; else if (!strcmp(iflag, "IO")) flagval += 0x8; else if (!strcmp(iflag, "I")) flagval += 0x10; else return 0x0; /* Invalid flag */ iflag = strtok(NULL, "|"); /* everything before | */ } return flagval; } static int verify_ace_flags(char *flagstr, uint8_t *flagval) { char *invalflag; if (!strcmp(flagstr, "0") || !strcmp(flagstr, "0x0")) return 0; if (strstr(flagstr, "0x")) { /* hex flag value */ *flagval = strtol(flagstr, &invalflag, 16); if (strlen(invalflag)) { printf("%s: Invalid flags: %s\n", __func__, flagstr); return 1; } } else *flagval = ace_flag_value(flagstr); if (!*flagval || (*flagval & ~VFLAGS)) { printf("%s: Invalid flag %s and value: 0x%x\n", __func__, flagstr, *flagval); return 1; } return 0; } static uint32_t ace_mask_value(char *maskstr) { int i, len; uint32_t maskval = 0x0; char *lmask; if (!strcmp(maskstr, "FULL")) return FULL_CONTROL; else if (!strcmp(maskstr, "CHANGE")) return CHANGE; else if (!strcmp(maskstr, "D")) return DELETE; else if (!strcmp(maskstr, "READ")) return EREAD; else { len = strlen(maskstr); lmask = maskstr; for (i = 0; i < len; ++i, ++lmask) { if (*lmask == 'R') maskval |= EREAD; else if (*lmask == 'W') maskval |= EWRITE; else if (*lmask == 'X') maskval |= EXEC; else if (*lmask == 'D') maskval |= DELETE; else if (*lmask == 'P') maskval |= WRITE_DAC; else if (*lmask == 'O') maskval |= WRITE_OWNER; else return 0; } return maskval; } return 0; } static int verify_ace_mask(char *maskstr, uint32_t *maskval) { char *invalflag; if (strstr(maskstr, "0x") || !strcmp(maskstr, "DELDHLD")) { *maskval = strtol(maskstr, &invalflag, 16); if (!invalflag) { printf("%s: Invalid mask: %s\n", __func__, maskstr); return 1; } } else *maskval = ace_mask_value(maskstr); if (!*maskval) { printf("%s: Invalid mask %s and value: 0x%x\n", __func__, maskstr, *maskval); return 1; } return 0; } static struct cifs_ace ** build_cmdline_aces(char **arrptr, int numcaces) { int i; char *acesid, *acetype, *aceflag, *acemask; struct cifs_ace **cacesptr; cacesptr = (struct cifs_ace **)malloc(numcaces * sizeof(struct cifs_aces *)); if (!cacesptr) { printf("%s: Error %d allocating ACE array", __func__, errno); return NULL; } for (i = 0; i < numcaces; ++i) { acesid = strtok(arrptr[i], ":"); acetype = strtok(NULL, "/"); aceflag = strtok(NULL, "/"); acemask = strtok(NULL, "/"); if (!acesid || !acetype || !aceflag || !acemask) { printf("%s: Incomplete ACE: %s\n", __func__, arrptr[i]); goto build_cmdline_aces_ret; } cacesptr[i] = malloc(sizeof(struct cifs_ace)); if (!cacesptr[i]) { printf("%s: ACE alloc error %d\n", __func__, errno); goto build_cmdline_aces_ret; } if (verify_ace_sid(acesid, &cacesptr[i]->sid)) { printf("%s: Invalid SID: %s\n", __func__, arrptr[i]); goto build_cmdline_aces_ret; } if (verify_ace_type(acetype, &cacesptr[i]->type)) { printf("%s: Invalid ACE type: %s\n", __func__, arrptr[i]); goto build_cmdline_aces_ret; } if (verify_ace_flags(aceflag, &cacesptr[i]->flags)) { printf("%s: Invalid ACE flag: %s\n", __func__, arrptr[i]); goto build_cmdline_aces_ret; } if (verify_ace_mask(acemask, &cacesptr[i]->access_req)) { printf("%s: Invalid ACE mask: %s\n", __func__, arrptr[i]); goto build_cmdline_aces_ret; } cacesptr[i]->size = 1 + 1 + 2 + 4 + 1 + 1 + 6 + (cacesptr[i]->sid.num_subauth * 4); } return cacesptr; build_cmdline_aces_ret: for (; i >= 0; --i) free(cacesptr[i]); free(cacesptr); return NULL; } static char ** parse_cmdline_aces(char *optarg, int numcaces) { int i = 0, len; char *acestr, *vacestr, **arrptr = NULL; errno = EINVAL; arrptr = (char **)malloc(numcaces * sizeof(char *)); if (!arrptr) { printf("%s: Error %d allocating char array\n", __func__, errno); return NULL; } while (i < numcaces) { acestr = strtok(optarg, ","); /* everything before , */ if (acestr) { vacestr = strstr(acestr, "ACL:"); /* ace as ACL:*" */ if (vacestr) { vacestr = strchr(vacestr, ':'); if (vacestr) ++vacestr; /* go past : */ if (vacestr) { len = strlen(vacestr); arrptr[i] = malloc(len + 1); if (!arrptr[i]) goto parse_cmdline_aces_ret; strcpy(arrptr[i], vacestr); ++i; } else goto parse_cmdline_aces_ret; } else goto parse_cmdline_aces_ret; } else goto parse_cmdline_aces_ret; optarg = NULL; } errno = 0; return arrptr; parse_cmdline_aces_ret: printf("%s: Error %d parsing ACEs\n", __func__, errno); for (; i >= 0; --i) free(arrptr[i]); free(arrptr); return NULL; } static unsigned int get_numcaces(const char *optarg) { int i, len; unsigned int numcaces = 1; if (!optarg) return 0; len = strlen(optarg); for (i = 0; i < len; ++i) { if (*(optarg + i) == ',') ++numcaces; } return numcaces; } static int setacl_action(struct cifs_ntsd *pntsd, struct cifs_ntsd **npntsd, ssize_t *bufsize, struct cifs_ace **facesptr, int numfaces, struct cifs_ace **cacesptr, int numcaces, int maction) { int rc = 1; switch (maction) { case 0: rc = ace_delete(pntsd, npntsd, bufsize, facesptr, numfaces, cacesptr, numcaces); break; case 1: rc = ace_modify(pntsd, npntsd, bufsize, facesptr, numfaces, cacesptr, numcaces); break; case 2: rc = ace_add(pntsd, npntsd, bufsize, facesptr, numfaces, cacesptr, numcaces); break; case 3: rc = ace_set(pntsd, npntsd, bufsize, cacesptr, numcaces); break; default: printf("%s: Invalid action: %d\n", __func__, maction); break; } return rc; } static void setcifsacl_usage(void) { fprintf(stderr, "%s: Alter CIFS/NTFS ACL in a security descriptor of a file object\n", prog); fprintf(stderr, "Usage: %s option \n", prog); fprintf(stderr, "Valid options:\n"); fprintf(stderr, "\t-v Version of the program\n"); fprintf(stderr, "\n\t-a Add ACE(s), separated by a comma, to an ACL\n"); fprintf(stderr, "\tsetcifsacl -a \"ACL:Administrator:ALLOWED/0x0/FULL\" \n"); fprintf(stderr, "\n"); fprintf(stderr, "\t-D Delete ACE(s), separated by a comma, from an ACL\n"); fprintf(stderr, "\tsetcifsacl -D \"ACL:Administrator:DENIED/0x0/D\" \n"); fprintf(stderr, "\n"); fprintf(stderr, "\t-M Modify ACE(s), separated by a comma, in an ACL\n"); fprintf(stderr, "\tsetcifsacl -M \"ACL:user1:ALLOWED/0x0/0x1e01ff\" \n"); fprintf(stderr, "\n\t-S Replace existing ACL with ACE(s), separated by a comma\n"); fprintf(stderr, "\tsetcifsacl -S \"ACL:Administrator:ALLOWED/0x0/D\" \n"); fprintf(stderr, "\nRefer to setcifsacl(1) manpage for details\n"); } int main(const int argc, char *const argv[]) { int i, rc, c, numcaces, numfaces, maction = -1; ssize_t attrlen, bufsize = BUFSIZE; char *filename, *attrval, **arrptr = NULL; struct cifs_ctrl_acl *daclptr = NULL; struct cifs_ace **cacesptr = NULL, **facesptr = NULL; struct cifs_ntsd *ntsdptr = NULL; openlog(prog, 0, LOG_DAEMON); c = getopt(argc, argv, "v:D:M:a:S:?"); switch (c) { case 'v': printf("Version: %s\n", VERSION); goto out; case 'D': maction = 0; break; case 'M': maction = 1; break; case 'a': maction = 2; break; case 'S': maction = 3; break; case '?': setcifsacl_usage(); return 0; default: break; } if (argc != 4) { setcifsacl_usage(); return -1; } filename = argv[3]; numcaces = get_numcaces(optarg); if (!numcaces) { printf("%s: No valid ACEs specified\n", __func__); return -1; } arrptr = parse_cmdline_aces(optarg, numcaces); if (!arrptr) goto setcifsacl_numcaces_ret; cacesptr = build_cmdline_aces(arrptr, numcaces); if (!cacesptr) goto setcifsacl_cmdlineparse_ret; cifsacl: if (bufsize >= XATTR_SIZE_MAX) { printf("%s: Buffer size %ld exceeds max size of %d\n", __func__, bufsize, XATTR_SIZE_MAX); goto setcifsacl_cmdlineverify_ret; } attrval = malloc(bufsize * sizeof(char)); if (!attrval) { printf("error allocating memory for attribute value buffer\n"); goto setcifsacl_cmdlineverify_ret; } attrlen = getxattr(filename, ATTRNAME, attrval, bufsize); if (attrlen == -1) { if (errno == ERANGE) { free(attrval); bufsize += BUFSIZE; goto cifsacl; } else { printf("getxattr error: %d\n", errno); goto setcifsacl_getx_ret; } } numfaces = get_numfaces((struct cifs_ntsd *)attrval, attrlen, &daclptr); if (!numfaces && maction != 2) { /* if we are not adding aces */ printf("%s: Empty DACL\n", __func__); goto setcifsacl_facenum_ret; } facesptr = build_fetched_aces((char *)daclptr, numfaces); if (!facesptr) goto setcifsacl_facenum_ret; bufsize = 0; rc = setacl_action((struct cifs_ntsd *)attrval, &ntsdptr, &bufsize, facesptr, numfaces, cacesptr, numcaces, maction); if (rc) goto setcifsacl_action_ret; attrlen = setxattr(filename, ATTRNAME, ntsdptr, bufsize, 0); if (attrlen == -1) printf("%s: setxattr error: %s\n", __func__, strerror(errno)); goto setcifsacl_facenum_ret; out: return 0; setcifsacl_action_ret: free(ntsdptr); setcifsacl_facenum_ret: for (i = 0; i < numfaces; ++i) free(facesptr[i]); free(facesptr); setcifsacl_getx_ret: free(attrval); setcifsacl_cmdlineverify_ret: for (i = 0; i < numcaces; ++i) free(cacesptr[i]); free(cacesptr); setcifsacl_cmdlineparse_ret: for (i = 0; i < numcaces; ++i) free(arrptr[i]); free(arrptr); setcifsacl_numcaces_ret: return -1; }