--- /dev/null
+/**
+ * security.c - Handling security/ACLs in NTFS. Originated from the Linux-NTFS project.
+ *
+ * Copyright (c) 2004 Anton Altaparmakov
+ * Copyright (c) 2005-2006 Szabolcs Szakacsits
+ * Copyright (c) 2006 Yura Pakhuchiy
+ * Copyright (c) 2007-2010 Jean-Pierre Andre
+ *
+ * This program/include file 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/include file 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 (in the main directory of the NTFS-3G
+ * distribution in the file COPYING); 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
+
+#include <stdio.h>
+#ifdef HAVE_STDLIB_H
+#include <stdlib.h>
+#endif
+#ifdef HAVE_STRING_H
+#include <string.h>
+#endif
+#include <errno.h>
+#include <fcntl.h>
+#ifdef HAVE_SETXATTR
+#include <sys/xattr.h>
+#endif
+#ifdef HAVE_SYS_STAT_H
+#include <sys/stat.h>
+#endif
+
+#include <stdarg.h>
+
+#include <unistd.h>
+#include <pwd.h>
+#include <grp.h>
+
+#include <ntfs-3g/param.h>
+#include <ntfs-3g/types.h>
+#include <ntfs-3g/layout.h>
+#include <ntfs-3g/attrib.h>
+#include <ntfs-3g/index.h>
+#include <ntfs-3g/dir.h>
+#include <ntfs-3g/bitmap.h>
+#include <ntfs-3g/security.h>
+#include <ntfs-3g/acls.h>
+#include <ntfs-3g/cache.h>
+#include <ntfs-3g/misc.h>
+
+/*
+ * JPA NTFS constants or structs
+ * should be moved to layout.h
+ */
+
+#define ALIGN_SDS_BLOCK 0x40000 /* Alignment for a $SDS block */
+#define ALIGN_SDS_ENTRY 16 /* Alignment for a $SDS entry */
+#define STUFFSZ 0x4000 /* unitary stuffing size for $SDS */
+#define FIRST_SECURITY_ID 0x100 /* Lowest security id */
+
+ /* Mask for attributes which can be forced */
+#define FILE_ATTR_SETTABLE ( FILE_ATTR_READONLY \
+ | FILE_ATTR_HIDDEN \
+ | FILE_ATTR_SYSTEM \
+ | FILE_ATTR_ARCHIVE \
+ | FILE_ATTR_TEMPORARY \
+ | FILE_ATTR_OFFLINE \
+ | FILE_ATTR_NOT_CONTENT_INDEXED )
+
+struct SII { /* this is an image of an $SII index entry */
+ le16 offs;
+ le16 size;
+ le32 fill1;
+ le16 indexsz;
+ le16 indexksz;
+ le16 flags;
+ le16 fill2;
+ le32 keysecurid;
+
+ /* did not find official description for the following */
+ le32 hash;
+ le32 securid;
+ le32 dataoffsl; /* documented as badly aligned */
+ le32 dataoffsh;
+ le32 datasize;
+} ;
+
+struct SDH { /* this is an image of an $SDH index entry */
+ le16 offs;
+ le16 size;
+ le32 fill1;
+ le16 indexsz;
+ le16 indexksz;
+ le16 flags;
+ le16 fill2;
+ le32 keyhash;
+ le32 keysecurid;
+
+ /* did not find official description for the following */
+ le32 hash;
+ le32 securid;
+ le32 dataoffsl;
+ le32 dataoffsh;
+ le32 datasize;
+ le32 fill3;
+ } ;
+
+/*
+ * A few useful constants
+ */
+
+static ntfschar sii_stream[] = { const_cpu_to_le16('$'),
+ const_cpu_to_le16('S'),
+ const_cpu_to_le16('I'),
+ const_cpu_to_le16('I'),
+ const_cpu_to_le16(0) };
+static ntfschar sdh_stream[] = { const_cpu_to_le16('$'),
+ const_cpu_to_le16('S'),
+ const_cpu_to_le16('D'),
+ const_cpu_to_le16('H'),
+ const_cpu_to_le16(0) };
+
+/*
+ * null SID (S-1-0-0)
+ */
+
+extern const SID *nullsid;
+
+/*
+ * The zero GUID.
+ */
+
+static const GUID __zero_guid = { const_cpu_to_le32(0), const_cpu_to_le16(0),
+ const_cpu_to_le16(0), { 0, 0, 0, 0, 0, 0, 0, 0 } };
+static const GUID *const zero_guid = &__zero_guid;
+
+/**
+ * ntfs_guid_is_zero - check if a GUID is zero
+ * @guid: [IN] guid to check
+ *
+ * Return TRUE if @guid is a valid pointer to a GUID and it is the zero GUID
+ * and FALSE otherwise.
+ */
+BOOL ntfs_guid_is_zero(const GUID *guid)
+{
+ return (memcmp(guid, zero_guid, sizeof(*zero_guid)));
+}
+
+/**
+ * ntfs_guid_to_mbs - convert a GUID to a multi byte string
+ * @guid: [IN] guid to convert
+ * @guid_str: [OUT] string in which to return the GUID (optional)
+ *
+ * Convert the GUID pointed to by @guid to a multi byte string of the form
+ * "XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX". Therefore, @guid_str (if not NULL)
+ * needs to be able to store at least 37 bytes.
+ *
+ * If @guid_str is not NULL it will contain the converted GUID on return. If
+ * it is NULL a string will be allocated and this will be returned. The caller
+ * is responsible for free()ing the string in that case.
+ *
+ * On success return the converted string and on failure return NULL with errno
+ * set to the error code.
+ */
+char *ntfs_guid_to_mbs(const GUID *guid, char *guid_str)
+{
+ char *_guid_str;
+ int res;
+
+ if (!guid) {
+ errno = EINVAL;
+ return NULL;
+ }
+ _guid_str = guid_str;
+ if (!_guid_str) {
+ _guid_str = (char*)ntfs_malloc(37);
+ if (!_guid_str)
+ return _guid_str;
+ }
+ res = snprintf(_guid_str, 37,
+ "%08x-%04x-%04x-%02x%02x-%02x%02x%02x%02x%02x%02x",
+ (unsigned int)le32_to_cpu(guid->data1),
+ le16_to_cpu(guid->data2), le16_to_cpu(guid->data3),
+ guid->data4[0], guid->data4[1],
+ guid->data4[2], guid->data4[3], guid->data4[4],
+ guid->data4[5], guid->data4[6], guid->data4[7]);
+ if (res == 36)
+ return _guid_str;
+ if (!guid_str)
+ free(_guid_str);
+ errno = EINVAL;
+ return NULL;
+}
+
+/**
+ * ntfs_sid_to_mbs_size - determine maximum size for the string of a SID
+ * @sid: [IN] SID for which to determine the maximum string size
+ *
+ * Determine the maximum multi byte string size in bytes which is needed to
+ * store the standard textual representation of the SID pointed to by @sid.
+ * See ntfs_sid_to_mbs(), below.
+ *
+ * On success return the maximum number of bytes needed to store the multi byte
+ * string and on failure return -1 with errno set to the error code.
+ */
+int ntfs_sid_to_mbs_size(const SID *sid)
+{
+ int size, i;
+
+ if (!ntfs_sid_is_valid(sid)) {
+ errno = EINVAL;
+ return -1;
+ }
+ /* Start with "S-". */
+ size = 2;
+ /*
+ * Add the SID_REVISION. Hopefully the compiler will optimize this
+ * away as SID_REVISION is a constant.
+ */
+ for (i = SID_REVISION; i > 0; i /= 10)
+ size++;
+ /* Add the "-". */
+ size++;
+ /*
+ * Add the identifier authority. If it needs to be in decimal, the
+ * maximum is 2^32-1 = 4294967295 = 10 characters. If it needs to be
+ * in hexadecimal, then maximum is 0x665544332211 = 14 characters.
+ */
+ if (!sid->identifier_authority.high_part)
+ size += 10;
+ else
+ size += 14;
+ /*
+ * Finally, add the sub authorities. For each we have a "-" followed
+ * by a decimal which can be up to 2^32-1 = 4294967295 = 10 characters.
+ */
+ size += (1 + 10) * sid->sub_authority_count;
+ /* We need the zero byte at the end, too. */
+ size++;
+ return size * sizeof(char);
+}
+
+/**
+ * ntfs_sid_to_mbs - convert a SID to a multi byte string
+ * @sid: [IN] SID to convert
+ * @sid_str: [OUT] string in which to return the SID (optional)
+ * @sid_str_size: [IN] size in bytes of @sid_str
+ *
+ * Convert the SID pointed to by @sid to its standard textual representation.
+ * @sid_str (if not NULL) needs to be able to store at least
+ * ntfs_sid_to_mbs_size() bytes. @sid_str_size is the size in bytes of
+ * @sid_str if @sid_str is not NULL.
+ *
+ * The standard textual representation of the SID is of the form:
+ * S-R-I-S-S...
+ * Where:
+ * - The first "S" is the literal character 'S' identifying the following
+ * digits as a SID.
+ * - R is the revision level of the SID expressed as a sequence of digits
+ * in decimal.
+ * - I is the 48-bit identifier_authority, expressed as digits in decimal,
+ * if I < 2^32, or hexadecimal prefixed by "0x", if I >= 2^32.
+ * - S... is one or more sub_authority values, expressed as digits in
+ * decimal.
+ *
+ * If @sid_str is not NULL it will contain the converted SUID on return. If it
+ * is NULL a string will be allocated and this will be returned. The caller is
+ * responsible for free()ing the string in that case.
+ *
+ * On success return the converted string and on failure return NULL with errno
+ * set to the error code.
+ */
+char *ntfs_sid_to_mbs(const SID *sid, char *sid_str, size_t sid_str_size)
+{
+ u64 u;
+ le32 leauth;
+ char *s;
+ int i, j, cnt;
+
+ /*
+ * No need to check @sid if !@sid_str since ntfs_sid_to_mbs_size() will
+ * check @sid, too. 8 is the minimum SID string size.
+ */
+ if (sid_str && (sid_str_size < 8 || !ntfs_sid_is_valid(sid))) {
+ errno = EINVAL;
+ return NULL;
+ }
+ /* Allocate string if not provided. */
+ if (!sid_str) {
+ cnt = ntfs_sid_to_mbs_size(sid);
+ if (cnt < 0)
+ return NULL;
+ s = (char*)ntfs_malloc(cnt);
+ if (!s)
+ return s;
+ sid_str = s;
+ /* So we know we allocated it. */
+ sid_str_size = 0;
+ } else {
+ s = sid_str;
+ cnt = sid_str_size;
+ }
+ /* Start with "S-R-". */
+ i = snprintf(s, cnt, "S-%hhu-", (unsigned char)sid->revision);
+ if (i < 0 || i >= cnt)
+ goto err_out;
+ s += i;
+ cnt -= i;
+ /* Add the identifier authority. */
+ for (u = i = 0, j = 40; i < 6; i++, j -= 8)
+ u += (u64)sid->identifier_authority.value[i] << j;
+ if (!sid->identifier_authority.high_part)
+ i = snprintf(s, cnt, "%lu", (unsigned long)u);
+ else
+ i = snprintf(s, cnt, "0x%llx", (unsigned long long)u);
+ if (i < 0 || i >= cnt)
+ goto err_out;
+ s += i;
+ cnt -= i;
+ /* Finally, add the sub authorities. */
+ for (j = 0; j < sid->sub_authority_count; j++) {
+ leauth = sid->sub_authority[j];
+ i = snprintf(s, cnt, "-%u", (unsigned int)
+ le32_to_cpu(leauth));
+ if (i < 0 || i >= cnt)
+ goto err_out;
+ s += i;
+ cnt -= i;
+ }
+ return sid_str;
+err_out:
+ if (i >= cnt)
+ i = EMSGSIZE;
+ else
+ i = errno;
+ if (!sid_str_size)
+ free(sid_str);
+ errno = i;
+ return NULL;
+}
+
+/**
+ * ntfs_generate_guid - generatates a random current guid.
+ * @guid: [OUT] pointer to a GUID struct to hold the generated guid.
+ *
+ * perhaps not a very good random number generator though...
+ */
+void ntfs_generate_guid(GUID *guid)
+{
+ unsigned int i;
+ u8 *p = (u8 *)guid;
+
+ for (i = 0; i < sizeof(GUID); i++) {
+ p[i] = (u8)(random() & 0xFF);
+ if (i == 7)
+ p[7] = (p[7] & 0x0F) | 0x40;
+ if (i == 8)
+ p[8] = (p[8] & 0x3F) | 0x80;
+ }
+}
+
+/**
+ * ntfs_security_hash - calculate the hash of a security descriptor
+ * @sd: self-relative security descriptor whose hash to calculate
+ * @length: size in bytes of the security descritor @sd
+ *
+ * Calculate the hash of the self-relative security descriptor @sd of length
+ * @length bytes.
+ *
+ * This hash is used in the $Secure system file as the primary key for the $SDH
+ * index and is also stored in the header of each security descriptor in the
+ * $SDS data stream as well as in the index data of both the $SII and $SDH
+ * indexes. In all three cases it forms part of the SDS_ENTRY_HEADER
+ * structure.
+ *
+ * Return the calculated security hash in little endian.
+ */
+le32 ntfs_security_hash(const SECURITY_DESCRIPTOR_RELATIVE *sd, const u32 len)
+{
+ const le32 *pos = (const le32*)sd;
+ const le32 *end = pos + (len >> 2);
+ u32 hash = 0;
+
+ while (pos < end) {
+ hash = le32_to_cpup(pos) + ntfs_rol32(hash, 3);
+ pos++;
+ }
+ return cpu_to_le32(hash);
+}
+
+/*
+ * Get the first entry of current index block
+ * cut and pasted form ntfs_ie_get_first() in index.c
+ */
+
+static INDEX_ENTRY *ntfs_ie_get_first(INDEX_HEADER *ih)
+{
+ return (INDEX_ENTRY*)((u8*)ih + le32_to_cpu(ih->entries_offset));
+}
+
+/*
+ * Stuff a 256KB block into $SDS before writing descriptors
+ * into the block.
+ *
+ * This prevents $SDS from being automatically declared as sparse
+ * when the second copy of the first security descriptor is written
+ * 256KB further ahead.
+ *
+ * Having $SDS declared as a sparse file is not wrong by itself
+ * and chkdsk leaves it as a sparse file. It does however complain
+ * and add a sparse flag (0x0200) into field file_attributes of
+ * STANDARD_INFORMATION of $Secure. This probably means that a
+ * sparse attribute (ATTR_IS_SPARSE) is only allowed in sparse
+ * files (FILE_ATTR_SPARSE_FILE).
+ *
+ * Windows normally does not convert to sparse attribute or sparse
+ * file. Stuffing is just a way to get to the same result.
+ */
+
+static int entersecurity_stuff(ntfs_volume *vol, off_t offs)
+{
+ int res;
+ int written;
+ unsigned long total;
+ char *stuff;
+
+ res = 0;
+ total = 0;
+ stuff = (char*)ntfs_malloc(STUFFSZ);
+ if (stuff) {
+ memset(stuff, 0, STUFFSZ);
+ do {
+ written = ntfs_attr_data_write(vol->secure_ni,
+ STREAM_SDS, 4, stuff, STUFFSZ, offs);
+ if (written == STUFFSZ) {
+ total += STUFFSZ;
+ offs += STUFFSZ;
+ } else {
+ errno = ENOSPC;
+ res = -1;
+ }
+ } while (!res && (total < ALIGN_SDS_BLOCK));
+ free(stuff);
+ } else {
+ errno = ENOMEM;
+ res = -1;
+ }
+ return (res);
+}
+
+/*
+ * Enter a new security descriptor into $Secure (data only)
+ * it has to be written twice with an offset of 256KB
+ *
+ * Should only be called by entersecurityattr() to ensure consistency
+ *
+ * Returns zero if sucessful
+ */
+
+static int entersecurity_data(ntfs_volume *vol,
+ const SECURITY_DESCRIPTOR_RELATIVE *attr, s64 attrsz,
+ le32 hash, le32 keyid, off_t offs, int gap)
+{
+ int res;
+ int written1;
+ int written2;
+ char *fullattr;
+ int fullsz;
+ SECURITY_DESCRIPTOR_HEADER *phsds;
+
+ res = -1;
+ fullsz = attrsz + gap + sizeof(SECURITY_DESCRIPTOR_HEADER);
+ fullattr = (char*)ntfs_malloc(fullsz);
+ if (fullattr) {
+ /*
+ * Clear the gap from previous descriptor
+ * this could be useful for appending the second
+ * copy to the end of file. When creating a new
+ * 256K block, the gap is cleared while writing
+ * the first copy
+ */
+ if (gap)
+ memset(fullattr,0,gap);
+ memcpy(&fullattr[gap + sizeof(SECURITY_DESCRIPTOR_HEADER)],
+ attr,attrsz);
+ phsds = (SECURITY_DESCRIPTOR_HEADER*)&fullattr[gap];
+ phsds->hash = hash;
+ phsds->security_id = keyid;
+ phsds->offset = cpu_to_le64(offs);
+ phsds->length = cpu_to_le32(fullsz - gap);
+ written1 = ntfs_attr_data_write(vol->secure_ni,
+ STREAM_SDS, 4, fullattr, fullsz,
+ offs - gap);
+ written2 = ntfs_attr_data_write(vol->secure_ni,
+ STREAM_SDS, 4, fullattr, fullsz,
+ offs - gap + ALIGN_SDS_BLOCK);
+ if ((written1 == fullsz)
+ && (written2 == written1))
+ res = 0;
+ else
+ errno = ENOSPC;
+ free(fullattr);
+ } else
+ errno = ENOMEM;
+ return (res);
+}
+
+/*
+ * Enter a new security descriptor in $Secure (indexes only)
+ *
+ * Should only be called by entersecurityattr() to ensure consistency
+ *
+ * Returns zero if sucessful
+ */
+
+static int entersecurity_indexes(ntfs_volume *vol, s64 attrsz,
+ le32 hash, le32 keyid, off_t offs)
+{
+ union {
+ struct {
+ le32 dataoffsl;
+ le32 dataoffsh;
+ } parts;
+ le64 all;
+ } realign;
+ int res;
+ ntfs_index_context *xsii;
+ ntfs_index_context *xsdh;
+ struct SII newsii;
+ struct SDH newsdh;
+
+ res = -1;
+ /* enter a new $SII record */
+
+ xsii = vol->secure_xsii;
+ ntfs_index_ctx_reinit(xsii);
+ newsii.offs = const_cpu_to_le16(20);
+ newsii.size = const_cpu_to_le16(sizeof(struct SII) - 20);
+ newsii.fill1 = const_cpu_to_le32(0);
+ newsii.indexsz = const_cpu_to_le16(sizeof(struct SII));
+ newsii.indexksz = const_cpu_to_le16(sizeof(SII_INDEX_KEY));
+ newsii.flags = const_cpu_to_le16(0);
+ newsii.fill2 = const_cpu_to_le16(0);
+ newsii.keysecurid = keyid;
+ newsii.hash = hash;
+ newsii.securid = keyid;
+ realign.all = cpu_to_le64(offs);
+ newsii.dataoffsh = realign.parts.dataoffsh;
+ newsii.dataoffsl = realign.parts.dataoffsl;
+ newsii.datasize = cpu_to_le32(attrsz
+ + sizeof(SECURITY_DESCRIPTOR_HEADER));
+ if (!ntfs_ie_add(xsii,(INDEX_ENTRY*)&newsii)) {
+
+ /* enter a new $SDH record */
+
+ xsdh = vol->secure_xsdh;
+ ntfs_index_ctx_reinit(xsdh);
+ newsdh.offs = const_cpu_to_le16(24);
+ newsdh.size = const_cpu_to_le16(
+ sizeof(SECURITY_DESCRIPTOR_HEADER));
+ newsdh.fill1 = const_cpu_to_le32(0);
+ newsdh.indexsz = const_cpu_to_le16(
+ sizeof(struct SDH));
+ newsdh.indexksz = const_cpu_to_le16(
+ sizeof(SDH_INDEX_KEY));
+ newsdh.flags = const_cpu_to_le16(0);
+ newsdh.fill2 = const_cpu_to_le16(0);
+ newsdh.keyhash = hash;
+ newsdh.keysecurid = keyid;
+ newsdh.hash = hash;
+ newsdh.securid = keyid;
+ newsdh.dataoffsh = realign.parts.dataoffsh;
+ newsdh.dataoffsl = realign.parts.dataoffsl;
+ newsdh.datasize = cpu_to_le32(attrsz
+ + sizeof(SECURITY_DESCRIPTOR_HEADER));
+ /* special filler value, Windows generally */
+ /* fills with 0x00490049, sometimes with zero */
+ newsdh.fill3 = const_cpu_to_le32(0x00490049);
+ if (!ntfs_ie_add(xsdh,(INDEX_ENTRY*)&newsdh))
+ res = 0;
+ }
+ return (res);
+}
+
+/*
+ * Enter a new security descriptor in $Secure (data and indexes)
+ * Returns id of entry, or zero if there is a problem.
+ * (should not be called for NTFS version < 3.0)
+ *
+ * important : calls have to be serialized, however no locking is
+ * needed while fuse is not multithreaded
+ */
+
+static le32 entersecurityattr(ntfs_volume *vol,
+ const SECURITY_DESCRIPTOR_RELATIVE *attr, s64 attrsz,
+ le32 hash)
+{
+ union {
+ struct {
+ le32 dataoffsl;
+ le32 dataoffsh;
+ } parts;
+ le64 all;
+ } realign;
+ le32 securid;
+ le32 keyid;
+ u32 newkey;
+ off_t offs;
+ int gap;
+ int size;
+ BOOL found;
+ struct SII *psii;
+ INDEX_ENTRY *entry;
+ INDEX_ENTRY *next;
+ ntfs_index_context *xsii;
+ int retries;
+ ntfs_attr *na;
+ int olderrno;
+
+ /* find the first available securid beyond the last key */
+ /* in $Secure:$SII. This also determines the first */
+ /* available location in $Secure:$SDS, as this stream */
+ /* is always appended to and the id's are allocated */
+ /* in sequence */
+
+ securid = const_cpu_to_le32(0);
+ xsii = vol->secure_xsii;
+ ntfs_index_ctx_reinit(xsii);
+ offs = size = 0;
+ keyid = const_cpu_to_le32(-1);
+ olderrno = errno;
+ found = !ntfs_index_lookup((char*)&keyid,
+ sizeof(SII_INDEX_KEY), xsii);
+ if (!found && (errno != ENOENT)) {
+ ntfs_log_perror("Inconsistency in index $SII");
+ psii = (struct SII*)NULL;
+ } else {
+ /* restore errno to avoid misinterpretation */
+ errno = olderrno;
+ entry = xsii->entry;
+ psii = (struct SII*)xsii->entry;
+ }
+ if (psii) {
+ /*
+ * Get last entry in block, but must get first one
+ * one first, as we should already be beyond the
+ * last one. For some reason the search for the last
+ * entry sometimes does not return the last block...
+ * we assume this can only happen in root block
+ */
+ if (xsii->is_in_root)
+ entry = ntfs_ie_get_first
+ ((INDEX_HEADER*)&xsii->ir->index);
+ else
+ entry = ntfs_ie_get_first
+ ((INDEX_HEADER*)&xsii->ib->index);
+ /*
+ * All index blocks should be at least half full
+ * so there always is a last entry but one,
+ * except when creating the first entry in index root.
+ * This was however found not to be true : chkdsk
+ * sometimes deletes all the (unused) keys in the last
+ * index block without rebalancing the tree.
+ * When this happens, a new search is restarted from
+ * the smallest key.
+ */
+ keyid = const_cpu_to_le32(0);
+ retries = 0;
+ while (entry) {
+ next = ntfs_index_next(entry,xsii);
+ if (next) {
+ psii = (struct SII*)next;
+ /* save last key and */
+ /* available position */
+ keyid = psii->keysecurid;
+ realign.parts.dataoffsh
+ = psii->dataoffsh;
+ realign.parts.dataoffsl
+ = psii->dataoffsl;
+ offs = le64_to_cpu(realign.all);
+ size = le32_to_cpu(psii->datasize);
+ }
+ entry = next;
+ if (!entry && !keyid && !retries) {
+ /* search failed, retry from smallest key */
+ ntfs_index_ctx_reinit(xsii);
+ found = !ntfs_index_lookup((char*)&keyid,
+ sizeof(SII_INDEX_KEY), xsii);
+ if (!found && (errno != ENOENT)) {
+ ntfs_log_perror("Index $SII is broken");
+ } else {
+ /* restore errno */
+ errno = olderrno;
+ entry = xsii->entry;
+ }
+ retries++;
+ }
+ }
+ }
+ if (!keyid) {
+ /*
+ * could not find any entry, before creating the first
+ * entry, make a double check by making sure size of $SII
+ * is less than needed for one entry
+ */
+ securid = const_cpu_to_le32(0);
+ na = ntfs_attr_open(vol->secure_ni,AT_INDEX_ROOT,sii_stream,4);
+ if (na) {
+ if ((size_t)na->data_size < sizeof(struct SII)) {
+ ntfs_log_error("Creating the first security_id\n");
+ securid = const_cpu_to_le32(FIRST_SECURITY_ID);
+ }
+ ntfs_attr_close(na);
+ }
+ if (!securid) {
+ ntfs_log_error("Error creating a security_id\n");
+ errno = EIO;
+ }
+ } else {
+ newkey = le32_to_cpu(keyid) + 1;
+ securid = cpu_to_le32(newkey);
+ }
+ /*
+ * The security attr has to be written twice 256KB
+ * apart. This implies that offsets like
+ * 0x40000*odd_integer must be left available for
+ * the second copy. So align to next block when
+ * the last byte overflows on a wrong block.
+ */
+
+ if (securid) {
+ gap = (-size) & (ALIGN_SDS_ENTRY - 1);
+ offs += gap + size;
+ if ((offs + attrsz + sizeof(SECURITY_DESCRIPTOR_HEADER) - 1)
+ & ALIGN_SDS_BLOCK) {
+ offs = ((offs + attrsz
+ + sizeof(SECURITY_DESCRIPTOR_HEADER) - 1)
+ | (ALIGN_SDS_BLOCK - 1)) + 1;
+ }
+ if (!(offs & (ALIGN_SDS_BLOCK - 1)))
+ entersecurity_stuff(vol, offs);
+ /*
+ * now write the security attr to storage :
+ * first data, then SII, then SDH
+ * If failure occurs while writing SDS, data will never
+ * be accessed through indexes, and will be overwritten
+ * by the next allocated descriptor
+ * If failure occurs while writing SII, the id has not
+ * recorded and will be reallocated later
+ * If failure occurs while writing SDH, the space allocated
+ * in SDS or SII will not be reused, an inconsistency
+ * will persist with no significant consequence
+ */
+ if (entersecurity_data(vol, attr, attrsz, hash, securid, offs, gap)
+ || entersecurity_indexes(vol, attrsz, hash, securid, offs))
+ securid = const_cpu_to_le32(0);
+ }
+ /* inode now is dirty, synchronize it all */
+ ntfs_index_entry_mark_dirty(vol->secure_xsii);
+ ntfs_index_ctx_reinit(vol->secure_xsii);
+ ntfs_index_entry_mark_dirty(vol->secure_xsdh);
+ ntfs_index_ctx_reinit(vol->secure_xsdh);
+ NInoSetDirty(vol->secure_ni);
+ if (ntfs_inode_sync(vol->secure_ni))
+ ntfs_log_perror("Could not sync $Secure\n");
+ return (securid);
+}
+
+/*
+ * Find a matching security descriptor in $Secure,
+ * if none, allocate a new id and write the descriptor to storage
+ * Returns id of entry, or zero if there is a problem.
+ *
+ * important : calls have to be serialized, however no locking is
+ * needed while fuse is not multithreaded
+ */
+
+static le32 setsecurityattr(ntfs_volume *vol,
+ const SECURITY_DESCRIPTOR_RELATIVE *attr, s64 attrsz)
+{
+ struct SDH *psdh; /* this is an image of index (le) */
+ union {
+ struct {
+ le32 dataoffsl;
+ le32 dataoffsh;
+ } parts;
+ le64 all;
+ } realign;
+ BOOL found;
+ BOOL collision;
+ size_t size;
+ size_t rdsize;
+ s64 offs;
+ int res;
+ ntfs_index_context *xsdh;
+ char *oldattr;
+ SDH_INDEX_KEY key;
+ INDEX_ENTRY *entry;
+ le32 securid;
+ le32 hash;
+ int olderrno;
+
+ hash = ntfs_security_hash(attr,attrsz);
+ oldattr = (char*)NULL;
+ securid = const_cpu_to_le32(0);
+ res = 0;
+ xsdh = vol->secure_xsdh;
+ if (vol->secure_ni && xsdh && !vol->secure_reentry++) {
+ ntfs_index_ctx_reinit(xsdh);
+ /*
+ * find the nearest key as (hash,0)
+ * (do not search for partial key : in case of collision,
+ * it could return a key which is not the first one which
+ * collides)
+ */
+ key.hash = hash;
+ key.security_id = const_cpu_to_le32(0);
+ olderrno = errno;
+ found = !ntfs_index_lookup((char*)&key,
+ sizeof(SDH_INDEX_KEY), xsdh);
+ if (!found && (errno != ENOENT))
+ ntfs_log_perror("Inconsistency in index $SDH");
+ else {
+ /* restore errno to avoid misinterpretation */
+ errno = olderrno;
+ entry = xsdh->entry;
+ found = FALSE;
+ /*
+ * lookup() may return a node with no data,
+ * if so get next
+ */
+ if (entry->ie_flags & INDEX_ENTRY_END)
+ entry = ntfs_index_next(entry,xsdh);
+ do {
+ collision = FALSE;
+ psdh = (struct SDH*)entry;
+ if (psdh)
+ size = (size_t) le32_to_cpu(psdh->datasize)
+ - sizeof(SECURITY_DESCRIPTOR_HEADER);
+ else size = 0;
+ /* if hash is not the same, the key is not present */
+ if (psdh && (size > 0)
+ && (psdh->keyhash == hash)) {
+ /* if hash is the same */
+ /* check the whole record */
+ realign.parts.dataoffsh = psdh->dataoffsh;
+ realign.parts.dataoffsl = psdh->dataoffsl;
+ offs = le64_to_cpu(realign.all)
+ + sizeof(SECURITY_DESCRIPTOR_HEADER);
+ oldattr = (char*)ntfs_malloc(size);
+ if (oldattr) {
+ rdsize = ntfs_attr_data_read(
+ vol->secure_ni,
+ STREAM_SDS, 4,
+ oldattr, size, offs);
+ found = (rdsize == size)
+ && !memcmp(oldattr,attr,size);
+ free(oldattr);
+ /* if the records do not compare */
+ /* (hash collision), try next one */
+ if (!found) {
+ entry = ntfs_index_next(
+ entry,xsdh);
+ collision = TRUE;
+ }
+ } else
+ res = ENOMEM;
+ }
+ } while (collision && entry);
+ if (found)
+ securid = psdh->keysecurid;
+ else {
+ if (res) {
+ errno = res;
+ securid = const_cpu_to_le32(0);
+ } else {
+ /*
+ * no matching key :
+ * have to build a new one
+ */
+ securid = entersecurityattr(vol,
+ attr, attrsz, hash);
+ }
+ }
+ }
+ }
+ if (--vol->secure_reentry)
+ ntfs_log_perror("Reentry error, check no multithreading\n");
+ return (securid);
+}
+
+
+/*
+ * Update the security descriptor of a file
+ * Either as an attribute (complying with pre v3.x NTFS version)
+ * or, when possible, as an entry in $Secure (for NTFS v3.x)
+ *
+ * returns 0 if success
+ */
+
+static int update_secur_descr(ntfs_volume *vol,
+ char *newattr, ntfs_inode *ni)
+{
+ int newattrsz;
+ int written;
+ int res;
+ ntfs_attr *na;
+
+ newattrsz = ntfs_attr_size(newattr);
+
+#if !FORCE_FORMAT_v1x
+ if ((vol->major_ver < 3) || !vol->secure_ni) {
+#endif
+
+ /* update for NTFS format v1.x */
+
+ /* update the old security attribute */
+ na = ntfs_attr_open(ni, AT_SECURITY_DESCRIPTOR, AT_UNNAMED, 0);
+ if (na) {
+ /* resize attribute */
+ res = ntfs_attr_truncate(na, (s64) newattrsz);
+ /* overwrite value */
+ if (!res) {
+ written = (int)ntfs_attr_pwrite(na, (s64) 0,
+ (s64) newattrsz, newattr);
+ if (written != newattrsz) {
+ ntfs_log_error("Failed to update "
+ "a v1.x security descriptor\n");
+ errno = EIO;
+ res = -1;
+ }
+ }
+
+ ntfs_attr_close(na);
+ /* if old security attribute was found, also */
+ /* truncate standard information attribute to v1.x */
+ /* this is needed when security data is wanted */
+ /* as v1.x though volume is formatted for v3.x */
+ na = ntfs_attr_open(ni, AT_STANDARD_INFORMATION,
+ AT_UNNAMED, 0);
+ if (na) {
+ clear_nino_flag(ni, v3_Extensions);
+ /*
+ * Truncating the record does not sweep extensions
+ * from copy in memory. Clear security_id to be safe
+ */
+ ni->security_id = const_cpu_to_le32(0);
+ res = ntfs_attr_truncate(na, (s64)48);
+ ntfs_attr_close(na);
+ clear_nino_flag(ni, v3_Extensions);
+ }
+ } else {
+ /*
+ * insert the new security attribute if there
+ * were none
+ */
+ res = ntfs_attr_add(ni, AT_SECURITY_DESCRIPTOR,
+ AT_UNNAMED, 0, (u8*)newattr,
+ (s64) newattrsz);
+ }
+#if !FORCE_FORMAT_v1x
+ } else {
+
+ /* update for NTFS format v3.x */
+
+ le32 securid;
+
+ securid = setsecurityattr(vol,
+ (const SECURITY_DESCRIPTOR_RELATIVE*)newattr,
+ (s64)newattrsz);
+ if (securid) {
+ na = ntfs_attr_open(ni, AT_STANDARD_INFORMATION,
+ AT_UNNAMED, 0);
+ if (na) {
+ res = 0;
+ if (!test_nino_flag(ni, v3_Extensions)) {
+ /* expand standard information attribute to v3.x */
+ res = ntfs_attr_truncate(na,
+ (s64)sizeof(STANDARD_INFORMATION));
+ ni->owner_id = const_cpu_to_le32(0);
+ ni->quota_charged = const_cpu_to_le64(0);
+ ni->usn = const_cpu_to_le64(0);
+ ntfs_attr_remove(ni,
+ AT_SECURITY_DESCRIPTOR,
+ AT_UNNAMED, 0);
+ }
+ set_nino_flag(ni, v3_Extensions);
+ ni->security_id = securid;
+ ntfs_attr_close(na);
+ } else {
+ ntfs_log_error("Failed to update "
+ "standard informations\n");
+ errno = EIO;
+ res = -1;
+ }
+ } else
+ res = -1;
+ }
+#endif
+
+ /* mark node as dirty */
+ NInoSetDirty(ni);
+ return (res);
+}
+
+/*
+ * Upgrade the security descriptor of a file
+ * This is intended to allow graceful upgrades for files which
+ * were created in previous versions, with a security attributes
+ * and no security id.
+ *
+ * It will allocate a security id and replace the individual
+ * security attribute by a reference to the global one
+ *
+ * Special files are not upgraded (currently / and files in
+ * directories /$*)
+ *
+ * Though most code is similar to update_secur_desc() it has
+ * been kept apart to facilitate the further processing of
+ * special cases or even to remove it if found dangerous.
+ *
+ * returns 0 if success,
+ * 1 if not upgradable. This is not an error.
+ * -1 if there is a problem
+ */
+
+static int upgrade_secur_desc(ntfs_volume *vol,
+ const char *attr, ntfs_inode *ni)
+{
+ int attrsz;
+ int res;
+ le32 securid;
+ ntfs_attr *na;
+
+ /*
+ * upgrade requires NTFS format v3.x
+ * also refuse upgrading for special files
+ * whose number is less than FILE_first_user
+ */
+
+ if ((vol->major_ver >= 3)
+ && (ni->mft_no >= FILE_first_user)) {
+ attrsz = ntfs_attr_size(attr);
+ securid = setsecurityattr(vol,
+ (const SECURITY_DESCRIPTOR_RELATIVE*)attr,
+ (s64)attrsz);
+ if (securid) {
+ na = ntfs_attr_open(ni, AT_STANDARD_INFORMATION,
+ AT_UNNAMED, 0);
+ if (na) {
+ /* expand standard information attribute to v3.x */
+ res = ntfs_attr_truncate(na,
+ (s64)sizeof(STANDARD_INFORMATION));
+ ni->owner_id = const_cpu_to_le32(0);
+ ni->quota_charged = const_cpu_to_le64(0);
+ ni->usn = const_cpu_to_le64(0);
+ ntfs_attr_remove(ni, AT_SECURITY_DESCRIPTOR,
+ AT_UNNAMED, 0);
+ set_nino_flag(ni, v3_Extensions);
+ ni->security_id = securid;
+ ntfs_attr_close(na);
+ } else {
+ ntfs_log_error("Failed to upgrade "
+ "standard informations\n");
+ errno = EIO;
+ res = -1;
+ }
+ } else
+ res = -1;
+ /* mark node as dirty */
+ NInoSetDirty(ni);
+ } else
+ res = 1;
+
+ return (res);
+}
+
+/*
+ * Optional simplified checking of group membership
+ *
+ * This only takes into account the groups defined in
+ * /etc/group at initialization time.
+ * It does not take into account the groups dynamically set by
+ * setgroups() nor the changes in /etc/group since initialization
+ *
+ * This optional method could be useful if standard checking
+ * leads to a performance concern.
+ *
+ * Should not be called for user root, however the group may be root
+ *
+ */
+
+static BOOL staticgroupmember(struct SECURITY_CONTEXT *scx, uid_t uid, gid_t gid)
+{
+ BOOL ingroup;
+ int grcnt;
+ gid_t *groups;
+ struct MAPPING *user;
+
+ ingroup = FALSE;
+ if (uid) {
+ user = scx->mapping[MAPUSERS];
+ while (user && ((uid_t)user->xid != uid))
+ user = user->next;
+ if (user) {
+ groups = user->groups;
+ grcnt = user->grcnt;
+ while ((--grcnt >= 0) && (groups[grcnt] != gid)) { }
+ ingroup = (grcnt >= 0);
+ }
+ }
+ return (ingroup);
+}
+
+
+/*
+ * Check whether current thread owner is member of file group
+ *
+ * Should not be called for user root, however the group may be root
+ *
+ * As indicated by Miklos Szeredi :
+ *
+ * The group list is available in
+ *
+ * /proc/$PID/task/$TID/status
+ *
+ * and fuse supplies TID in get_fuse_context()->pid. The only problem is
+ * finding out PID, for which I have no good solution, except to iterate
+ * through all processes. This is rather slow, but may be speeded up
+ * with caching and heuristics (for single threaded programs PID = TID).
+ *
+ * The following implementation gets the group list from
+ * /proc/$TID/task/$TID/status which apparently exists and
+ * contains the same data.
+ */
+
+static BOOL groupmember(struct SECURITY_CONTEXT *scx, uid_t uid, gid_t gid)
+{
+ static char key[] = "\nGroups:";
+ char buf[BUFSZ+1];
+ char filename[64];
+ enum { INKEY, INSEP, INNUM, INEND } state;
+ int fd;
+ char c;
+ int matched;
+ BOOL ismember;
+ int got;
+ char *p;
+ gid_t grp;
+ pid_t tid;
+
+ if (scx->vol->secure_flags & (1 << SECURITY_STATICGRPS))
+ ismember = staticgroupmember(scx, uid, gid);
+ else {
+ ismember = FALSE; /* default return */
+ tid = scx->tid;
+ sprintf(filename,"/proc/%u/task/%u/status",tid,tid);
+ fd = open(filename,O_RDONLY);
+ if (fd >= 0) {
+ got = read(fd, buf, BUFSZ);
+ buf[got] = 0;
+ state = INKEY;
+ matched = 0;
+ p = buf;
+ grp = 0;
+ /*
+ * A simple automaton to process lines like
+ * Groups: 14 500 513
+ */
+ do {
+ c = *p++;
+ if (!c) {
+ /* refill buffer */
+ got = read(fd, buf, BUFSZ);
+ buf[got] = 0;
+ p = buf;
+ c = *p++; /* 0 at end of file */
+ }
+ switch (state) {
+ case INKEY :
+ if (key[matched] == c) {
+ if (!key[++matched])
+ state = INSEP;
+ } else
+ if (key[0] == c)
+ matched = 1;
+ else
+ matched = 0;
+ break;
+ case INSEP :
+ if ((c >= '0') && (c <= '9')) {
+ grp = c - '0';
+ state = INNUM;
+ } else
+ if ((c != ' ') && (c != '\t'))
+ state = INEND;
+ break;
+ case INNUM :
+ if ((c >= '0') && (c <= '9'))
+ grp = grp*10 + c - '0';
+ else {
+ ismember = (grp == gid);
+ if ((c != ' ') && (c != '\t'))
+ state = INEND;
+ else
+ state = INSEP;
+ }
+ default :
+ break;
+ }
+ } while (!ismember && c && (state != INEND));
+ close(fd);
+ if (!c)
+ ntfs_log_error("No group record found in %s\n",filename);
+ } else
+ ntfs_log_error("Could not open %s\n",filename);
+ }
+ return (ismember);
+}
+
+/*
+ * Cacheing is done two-way :
+ * - from uid, gid and perm to securid (CACHED_SECURID)
+ * - from a securid to uid, gid and perm (CACHED_PERMISSIONS)
+ *
+ * CACHED_SECURID data is kept in a most-recent-first list
+ * which should not be too long to be efficient. Its optimal
+ * size is depends on usage and is hard to determine.
+ *
+ * CACHED_PERMISSIONS data is kept in a two-level indexed array. It
+ * is optimal at the expense of storage. Use of a most-recent-first
+ * list would save memory and provide similar performances for
+ * standard usage, but not for file servers with too many file
+ * owners
+ *
+ * CACHED_PERMISSIONS_LEGACY is a special case for CACHED_PERMISSIONS
+ * for legacy directories which were not allocated a security_id
+ * it is organized in a most-recent-first list.
+ *
+ * In main caches, data is never invalidated, as the meaning of
+ * a security_id only changes when user mapping is changed, which
+ * current implies remounting. However returned entries may be
+ * overwritten at next update, so data has to be copied elsewhere
+ * before another cache update is made.
+ * In legacy cache, data has to be invalidated when protection is
+ * changed.
+ *
+ * Though the same data may be found in both list, they
+ * must be kept separately : the interpretation of ACL
+ * in both direction are approximations which could be non
+ * reciprocal for some configuration of the user mapping data
+ *
+ * During the process of recompiling ntfs-3g from a tgz archive,
+ * security processing added 7.6% to the cpu time used by ntfs-3g
+ * and 30% if the cache is disabled.
+ */
+
+static struct PERMISSIONS_CACHE *create_caches(struct SECURITY_CONTEXT *scx,
+ u32 securindex)
+{
+ struct PERMISSIONS_CACHE *cache;
+ unsigned int index1;
+ unsigned int i;
+
+ cache = (struct PERMISSIONS_CACHE*)NULL;
+ /* create the first permissions blocks */
+ index1 = securindex >> CACHE_PERMISSIONS_BITS;
+ cache = (struct PERMISSIONS_CACHE*)
+ ntfs_malloc(sizeof(struct PERMISSIONS_CACHE)
+ + index1*sizeof(struct CACHED_PERMISSIONS*));
+ if (cache) {
+ cache->head.last = index1;
+ cache->head.p_reads = 0;
+ cache->head.p_hits = 0;
+ cache->head.p_writes = 0;
+ *scx->pseccache = cache;
+ for (i=0; i<=index1; i++)
+ cache->cachetable[i]
+ = (struct CACHED_PERMISSIONS*)NULL;
+ }
+ return (cache);
+}
+
+/*
+ * Free memory used by caches
+ * The only purpose is to facilitate the detection of memory leaks
+ */
+
+static void free_caches(struct SECURITY_CONTEXT *scx)
+{
+ unsigned int index1;
+ struct PERMISSIONS_CACHE *pseccache;
+
+ pseccache = *scx->pseccache;
+ if (pseccache) {
+ for (index1=0; index1<=pseccache->head.last; index1++)
+ if (pseccache->cachetable[index1]) {
+#if POSIXACLS
+ struct CACHED_PERMISSIONS *cacheentry;
+ unsigned int index2;
+
+ for (index2=0; index2<(1<< CACHE_PERMISSIONS_BITS); index2++) {
+ cacheentry = &pseccache->cachetable[index1][index2];
+ if (cacheentry->valid
+ && cacheentry->pxdesc)
+ free(cacheentry->pxdesc);
+ }
+#endif
+ free(pseccache->cachetable[index1]);
+ }
+ free(pseccache);
+ }
+}
+
+static int compare(const struct CACHED_SECURID *cached,
+ const struct CACHED_SECURID *item)
+{
+#if POSIXACLS
+ size_t csize;
+ size_t isize;
+
+ /* only compare data and sizes */
+ csize = (cached->variable ?
+ sizeof(struct POSIX_ACL)
+ + (((struct POSIX_SECURITY*)cached->variable)->acccnt
+ + ((struct POSIX_SECURITY*)cached->variable)->defcnt)
+ *sizeof(struct POSIX_ACE) :
+ 0);
+ isize = (item->variable ?
+ sizeof(struct POSIX_ACL)
+ + (((struct POSIX_SECURITY*)item->variable)->acccnt
+ + ((struct POSIX_SECURITY*)item->variable)->defcnt)
+ *sizeof(struct POSIX_ACE) :
+ 0);
+ return ((cached->uid != item->uid)
+ || (cached->gid != item->gid)
+ || (cached->dmode != item->dmode)
+ || (csize != isize)
+ || (csize
+ && isize
+ && memcmp(&((struct POSIX_SECURITY*)cached->variable)->acl,
+ &((struct POSIX_SECURITY*)item->variable)->acl, csize)));
+#else
+ return ((cached->uid != item->uid)
+ || (cached->gid != item->gid)
+ || (cached->dmode != item->dmode));
+#endif
+}
+
+static int leg_compare(const struct CACHED_PERMISSIONS_LEGACY *cached,
+ const struct CACHED_PERMISSIONS_LEGACY *item)
+{
+ return (cached->mft_no != item->mft_no);
+}
+
+/*
+ * Resize permission cache table
+ * do not call unless resizing is needed
+ *
+ * If allocation fails, the cache size is not updated
+ * Lack of memory is not considered as an error, the cache is left
+ * consistent and errno is not set.
+ */
+
+static void resize_cache(struct SECURITY_CONTEXT *scx,
+ u32 securindex)
+{
+ struct PERMISSIONS_CACHE *oldcache;
+ struct PERMISSIONS_CACHE *newcache;
+ int newcnt;
+ int oldcnt;
+ unsigned int index1;
+ unsigned int i;
+
+ oldcache = *scx->pseccache;
+ index1 = securindex >> CACHE_PERMISSIONS_BITS;
+ newcnt = index1 + 1;
+ if (newcnt <= ((CACHE_PERMISSIONS_SIZE
+ + (1 << CACHE_PERMISSIONS_BITS)
+ - 1) >> CACHE_PERMISSIONS_BITS)) {
+ /* expand cache beyond current end, do not use realloc() */
+ /* to avoid losing data when there is no more memory */
+ oldcnt = oldcache->head.last + 1;
+ newcache = (struct PERMISSIONS_CACHE*)
+ ntfs_malloc(
+ sizeof(struct PERMISSIONS_CACHE)
+ + (newcnt - 1)*sizeof(struct CACHED_PERMISSIONS*));
+ if (newcache) {
+ memcpy(newcache,oldcache,
+ sizeof(struct PERMISSIONS_CACHE)
+ + (oldcnt - 1)*sizeof(struct CACHED_PERMISSIONS*));
+ free(oldcache);
+ /* mark new entries as not valid */
+ for (i=newcache->head.last+1; i<=index1; i++)
+ newcache->cachetable[i]
+ = (struct CACHED_PERMISSIONS*)NULL;
+ newcache->head.last = index1;
+ *scx->pseccache = newcache;
+ }
+ }
+}
+
+/*
+ * Enter uid, gid and mode into cache, if possible
+ *
+ * returns the updated or created cache entry,
+ * or NULL if not possible (typically if there is no
+ * security id associated)
+ */
+
+#if POSIXACLS
+static struct CACHED_PERMISSIONS *enter_cache(struct SECURITY_CONTEXT *scx,
+ ntfs_inode *ni, uid_t uid, gid_t gid,
+ struct POSIX_SECURITY *pxdesc)
+#else
+static struct CACHED_PERMISSIONS *enter_cache(struct SECURITY_CONTEXT *scx,
+ ntfs_inode *ni, uid_t uid, gid_t gid, mode_t mode)
+#endif
+{
+ struct CACHED_PERMISSIONS *cacheentry;
+ struct CACHED_PERMISSIONS *cacheblock;
+ struct PERMISSIONS_CACHE *pcache;
+ u32 securindex;
+#if POSIXACLS
+ int pxsize;
+ struct POSIX_SECURITY *pxcached;
+#endif
+ unsigned int index1;
+ unsigned int index2;
+ int i;
+
+ /* cacheing is only possible if a security_id has been defined */
+ if (test_nino_flag(ni, v3_Extensions)
+ && ni->security_id) {
+ /*
+ * Immediately test the most frequent situation
+ * where the entry exists
+ */
+ securindex = le32_to_cpu(ni->security_id);
+ index1 = securindex >> CACHE_PERMISSIONS_BITS;
+ index2 = securindex & ((1 << CACHE_PERMISSIONS_BITS) - 1);
+ pcache = *scx->pseccache;
+ if (pcache
+ && (pcache->head.last >= index1)
+ && pcache->cachetable[index1]) {
+ cacheentry = &pcache->cachetable[index1][index2];
+ cacheentry->uid = uid;
+ cacheentry->gid = gid;
+#if POSIXACLS
+ if (cacheentry->valid && cacheentry->pxdesc)
+ free(cacheentry->pxdesc);
+ if (pxdesc) {
+ pxsize = sizeof(struct POSIX_SECURITY)
+ + (pxdesc->acccnt + pxdesc->defcnt)*sizeof(struct POSIX_ACE);
+ pxcached = (struct POSIX_SECURITY*)malloc(pxsize);
+ if (pxcached) {
+ memcpy(pxcached, pxdesc, pxsize);
+ cacheentry->pxdesc = pxcached;
+ } else {
+ cacheentry->valid = 0;
+ cacheentry = (struct CACHED_PERMISSIONS*)NULL;
+ }
+ cacheentry->mode = pxdesc->mode & 07777;
+ } else
+ cacheentry->pxdesc = (struct POSIX_SECURITY*)NULL;
+#else
+ cacheentry->mode = mode & 07777;
+#endif
+ cacheentry->inh_fileid = const_cpu_to_le32(0);
+ cacheentry->inh_dirid = const_cpu_to_le32(0);
+ cacheentry->valid = 1;
+ pcache->head.p_writes++;
+ } else {
+ if (!pcache) {
+ /* create the first cache block */
+ pcache = create_caches(scx, securindex);
+ } else {
+ if (index1 > pcache->head.last) {
+ resize_cache(scx, securindex);
+ pcache = *scx->pseccache;
+ }
+ }
+ /* allocate block, if cache table was allocated */
+ if (pcache && (index1 <= pcache->head.last)) {
+ cacheblock = (struct CACHED_PERMISSIONS*)
+ malloc(sizeof(struct CACHED_PERMISSIONS)
+ << CACHE_PERMISSIONS_BITS);
+ pcache->cachetable[index1] = cacheblock;
+ for (i=0; i<(1 << CACHE_PERMISSIONS_BITS); i++)
+ cacheblock[i].valid = 0;
+ cacheentry = &cacheblock[index2];
+ if (cacheentry) {
+ cacheentry->uid = uid;
+ cacheentry->gid = gid;
+#if POSIXACLS
+ if (pxdesc) {
+ pxsize = sizeof(struct POSIX_SECURITY)
+ + (pxdesc->acccnt + pxdesc->defcnt)*sizeof(struct POSIX_ACE);
+ pxcached = (struct POSIX_SECURITY*)malloc(pxsize);
+ if (pxcached) {
+ memcpy(pxcached, pxdesc, pxsize);
+ cacheentry->pxdesc = pxcached;
+ } else {
+ cacheentry->valid = 0;
+ cacheentry = (struct CACHED_PERMISSIONS*)NULL;
+ }
+ cacheentry->mode = pxdesc->mode & 07777;
+ } else
+ cacheentry->pxdesc = (struct POSIX_SECURITY*)NULL;
+#else
+ cacheentry->mode = mode & 07777;
+#endif
+ cacheentry->inh_fileid = const_cpu_to_le32(0);
+ cacheentry->inh_dirid = const_cpu_to_le32(0);
+ cacheentry->valid = 1;
+ pcache->head.p_writes++;
+ }
+ } else
+ cacheentry = (struct CACHED_PERMISSIONS*)NULL;
+ }
+ } else {
+ cacheentry = (struct CACHED_PERMISSIONS*)NULL;
+#if CACHE_LEGACY_SIZE
+ if (ni->mrec->flags & MFT_RECORD_IS_DIRECTORY) {
+ struct CACHED_PERMISSIONS_LEGACY wanted;
+ struct CACHED_PERMISSIONS_LEGACY *legacy;
+
+ wanted.perm.uid = uid;
+ wanted.perm.gid = gid;
+#if POSIXACLS
+ wanted.perm.mode = pxdesc->mode & 07777;
+ wanted.perm.inh_fileid = const_cpu_to_le32(0);
+ wanted.perm.inh_dirid = const_cpu_to_le32(0);
+ wanted.mft_no = ni->mft_no;
+ wanted.variable = (void*)pxdesc;
+ wanted.varsize = sizeof(struct POSIX_SECURITY)
+ + (pxdesc->acccnt + pxdesc->defcnt)*sizeof(struct POSIX_ACE);
+#else
+ wanted.perm.mode = mode & 07777;
+ wanted.perm.inh_fileid = const_cpu_to_le32(0);
+ wanted.perm.inh_dirid = const_cpu_to_le32(0);
+ wanted.mft_no = ni->mft_no;
+ wanted.variable = (void*)NULL;
+ wanted.varsize = 0;
+#endif
+ legacy = (struct CACHED_PERMISSIONS_LEGACY*)ntfs_enter_cache(
+ scx->vol->legacy_cache, GENERIC(&wanted),
+ (cache_compare)leg_compare);
+ if (legacy) {
+ cacheentry = &legacy->perm;
+#if POSIXACLS
+ /*
+ * give direct access to the cached pxdesc
+ * in the permissions structure
+ */
+ cacheentry->pxdesc = legacy->variable;
+#endif
+ }
+ }
+#endif
+ }
+ return (cacheentry);
+}
+
+/*
+ * Fetch owner, group and permission of a file, if cached
+ *
+ * Beware : do not use the returned entry after a cache update :
+ * the cache may be relocated making the returned entry meaningless
+ *
+ * returns the cache entry, or NULL if not available
+ */
+
+static struct CACHED_PERMISSIONS *fetch_cache(struct SECURITY_CONTEXT *scx,
+ ntfs_inode *ni)
+{
+ struct CACHED_PERMISSIONS *cacheentry;
+ struct PERMISSIONS_CACHE *pcache;
+ u32 securindex;
+ unsigned int index1;
+ unsigned int index2;
+
+ /* cacheing is only possible if a security_id has been defined */
+ cacheentry = (struct CACHED_PERMISSIONS*)NULL;
+ if (test_nino_flag(ni, v3_Extensions)
+ && (ni->security_id)) {
+ securindex = le32_to_cpu(ni->security_id);
+ index1 = securindex >> CACHE_PERMISSIONS_BITS;
+ index2 = securindex & ((1 << CACHE_PERMISSIONS_BITS) - 1);
+ pcache = *scx->pseccache;
+ if (pcache
+ && (pcache->head.last >= index1)
+ && pcache->cachetable[index1]) {
+ cacheentry = &pcache->cachetable[index1][index2];
+ /* reject if entry is not valid */
+ if (!cacheentry->valid)
+ cacheentry = (struct CACHED_PERMISSIONS*)NULL;
+ else
+ pcache->head.p_hits++;
+ if (pcache)
+ pcache->head.p_reads++;
+ }
+ }
+#if CACHE_LEGACY_SIZE
+ else {
+ cacheentry = (struct CACHED_PERMISSIONS*)NULL;
+ if (ni->mrec->flags & MFT_RECORD_IS_DIRECTORY) {
+ struct CACHED_PERMISSIONS_LEGACY wanted;
+ struct CACHED_PERMISSIONS_LEGACY *legacy;
+
+ wanted.mft_no = ni->mft_no;
+ wanted.variable = (void*)NULL;
+ wanted.varsize = 0;
+ legacy = (struct CACHED_PERMISSIONS_LEGACY*)ntfs_fetch_cache(
+ scx->vol->legacy_cache, GENERIC(&wanted),
+ (cache_compare)leg_compare);
+ if (legacy) cacheentry = &legacy->perm;
+ }
+ }
+#endif
+#if POSIXACLS
+ if (cacheentry && !cacheentry->pxdesc) {
+ ntfs_log_error("No Posix descriptor in cache\n");
+ cacheentry = (struct CACHED_PERMISSIONS*)NULL;
+ }
+#endif
+ return (cacheentry);
+}
+
+/*
+ * Retrieve a security attribute from $Secure
+ */
+
+static char *retrievesecurityattr(ntfs_volume *vol, SII_INDEX_KEY id)
+{
+ struct SII *psii;
+ union {
+ struct {
+ le32 dataoffsl;
+ le32 dataoffsh;
+ } parts;
+ le64 all;
+ } realign;
+ int found;
+ size_t size;
+ size_t rdsize;
+ s64 offs;
+ ntfs_inode *ni;
+ ntfs_index_context *xsii;
+ char *securattr;
+
+ securattr = (char*)NULL;
+ ni = vol->secure_ni;
+ xsii = vol->secure_xsii;
+ if (ni && xsii) {
+ ntfs_index_ctx_reinit(xsii);
+ found =
+ !ntfs_index_lookup((char*)&id,
+ sizeof(SII_INDEX_KEY), xsii);
+ if (found) {
+ psii = (struct SII*)xsii->entry;
+ size =
+ (size_t) le32_to_cpu(psii->datasize)
+ - sizeof(SECURITY_DESCRIPTOR_HEADER);
+ /* work around bad alignment problem */
+ realign.parts.dataoffsh = psii->dataoffsh;
+ realign.parts.dataoffsl = psii->dataoffsl;
+ offs = le64_to_cpu(realign.all)
+ + sizeof(SECURITY_DESCRIPTOR_HEADER);
+
+ securattr = (char*)ntfs_malloc(size);
+ if (securattr) {
+ rdsize = ntfs_attr_data_read(
+ ni, STREAM_SDS, 4,
+ securattr, size, offs);
+ if ((rdsize != size)
+ || !ntfs_valid_descr(securattr,
+ rdsize)) {
+ /* error to be logged by caller */
+ free(securattr);
+ securattr = (char*)NULL;
+ }
+ }
+ } else
+ if (errno != ENOENT)
+ ntfs_log_perror("Inconsistency in index $SII");
+ }
+ if (!securattr) {
+ ntfs_log_error("Failed to retrieve a security descriptor\n");
+ errno = EIO;
+ }
+ return (securattr);
+}
+
+/*
+ * Get the security descriptor associated to a file
+ *
+ * Either :
+ * - read the security descriptor attribute (v1.x format)
+ * - or find the descriptor in $Secure:$SDS (v3.x format)
+ *
+ * in both case, sanity checks are done on the attribute and
+ * the descriptor can be assumed safe
+ *
+ * The returned descriptor is dynamically allocated and has to be freed
+ */
+
+static char *getsecurityattr(ntfs_volume *vol, ntfs_inode *ni)
+{
+ SII_INDEX_KEY securid;
+ char *securattr;
+ s64 readallsz;
+
+ /*
+ * Warning : in some situations, after fixing by chkdsk,
+ * v3_Extensions are marked present (long standard informations)
+ * with a default security descriptor inserted in an
+ * attribute
+ */
+ if (test_nino_flag(ni, v3_Extensions)
+ && vol->secure_ni && ni->security_id) {
+ /* get v3.x descriptor in $Secure */
+ securid.security_id = ni->security_id;
+ securattr = retrievesecurityattr(vol,securid);
+ if (!securattr)
+ ntfs_log_error("Bad security descriptor for 0x%lx\n",
+ (long)le32_to_cpu(ni->security_id));
+ } else {
+ /* get v1.x security attribute */
+ readallsz = 0;
+ securattr = ntfs_attr_readall(ni, AT_SECURITY_DESCRIPTOR,
+ AT_UNNAMED, 0, &readallsz);
+ if (securattr && !ntfs_valid_descr(securattr, readallsz)) {
+ ntfs_log_error("Bad security descriptor for inode %lld\n",
+ (long long)ni->mft_no);
+ free(securattr);
+ securattr = (char*)NULL;
+ }
+ }
+ if (!securattr) {
+ /*
+ * in some situations, there is no security
+ * descriptor, and chkdsk does not detect or fix
+ * anything. This could be a normal situation.
+ * When this happens, simulate a descriptor with
+ * minimum rights, so that a real descriptor can
+ * be created by chown or chmod
+ */
+ ntfs_log_error("No security descriptor found for inode %lld\n",
+ (long long)ni->mft_no);
+ securattr = ntfs_build_descr(0, 0, adminsid, adminsid);
+ }
+ return (securattr);
+}
+
+#if POSIXACLS
+
+/*
+ * Determine which access types to a file are allowed
+ * according to the relation of current process to the file
+ *
+ * Do not call if default_permissions is set
+ */
+
+static int access_check_posix(struct SECURITY_CONTEXT *scx,
+ struct POSIX_SECURITY *pxdesc, mode_t request,
+ uid_t uid, gid_t gid)
+{
+ struct POSIX_ACE *pxace;
+ int userperms;
+ int groupperms;
+ int mask;
+ BOOL somegroup;
+ BOOL needgroups;
+ mode_t perms;
+ int i;
+
+ perms = pxdesc->mode;
+ /* owner and root access */
+ if (!scx->uid || (uid == scx->uid)) {
+ if (!scx->uid) {
+ /* root access if owner or other execution */
+ if (perms & 0101)
+ perms = 07777;
+ else {
+ /* root access if some group execution */
+ groupperms = 0;
+ mask = 7;
+ for (i=pxdesc->acccnt-1; i>=0 ; i--) {
+ pxace = &pxdesc->acl.ace[i];
+ switch (pxace->tag) {
+ case POSIX_ACL_USER_OBJ :
+ case POSIX_ACL_GROUP_OBJ :
+ case POSIX_ACL_GROUP :
+ groupperms |= pxace->perms;
+ break;
+ case POSIX_ACL_MASK :
+ mask = pxace->perms & 7;
+ break;
+ default :
+ break;
+ }
+ }
+ perms = (groupperms & mask & 1) | 6;
+ }
+ } else
+ perms &= 07700;
+ } else {
+ /*
+ * analyze designated users, get mask
+ * and identify whether we need to check
+ * the group memberships. The groups are
+ * not needed when all groups have the
+ * same permissions as other for the
+ * requested modes.
+ */
+ userperms = -1;
+ groupperms = -1;
+ needgroups = FALSE;
+ mask = 7;
+ for (i=pxdesc->acccnt-1; i>=0 ; i--) {
+ pxace = &pxdesc->acl.ace[i];
+ switch (pxace->tag) {
+ case POSIX_ACL_USER :
+ if ((uid_t)pxace->id == scx->uid)
+ userperms = pxace->perms;
+ break;
+ case POSIX_ACL_MASK :
+ mask = pxace->perms & 7;
+ break;
+ case POSIX_ACL_GROUP_OBJ :
+ case POSIX_ACL_GROUP :
+ if (((pxace->perms & mask) ^ perms)
+ & (request >> 6) & 7)
+ needgroups = TRUE;
+ break;
+ default :
+ break;
+ }
+ }
+ /* designated users */
+ if (userperms >= 0)
+ perms = (perms & 07000) + (userperms & mask);
+ else if (!needgroups)
+ perms &= 07007;
+ else {
+ /* owning group */
+ if (!(~(perms >> 3) & request & mask)
+ && ((gid == scx->gid)
+ || groupmember(scx, scx->uid, gid)))
+ perms &= 07070;
+ else {
+ /* other groups */
+ groupperms = -1;
+ somegroup = FALSE;
+ for (i=pxdesc->acccnt-1; i>=0 ; i--) {
+ pxace = &pxdesc->acl.ace[i];
+ if ((pxace->tag == POSIX_ACL_GROUP)
+ && groupmember(scx, uid, pxace->id)) {
+ if (!(~pxace->perms & request & mask))
+ groupperms = pxace->perms;
+ somegroup = TRUE;
+ }
+ }
+ if (groupperms >= 0)
+ perms = (perms & 07000) + (groupperms & mask);
+ else
+ if (somegroup)
+ perms = 0;
+ else
+ perms &= 07007;
+ }
+ }
+ }
+ return (perms);
+}
+
+/*
+ * Get permissions to access a file
+ * Takes into account the relation of user to file (owner, group, ...)
+ * Do no use as mode of the file
+ * Do no call if default_permissions is set
+ *
+ * returns -1 if there is a problem
+ */
+
+static int ntfs_get_perm(struct SECURITY_CONTEXT *scx,
+ ntfs_inode * ni, mode_t request)
+{
+ const SECURITY_DESCRIPTOR_RELATIVE *phead;
+ const struct CACHED_PERMISSIONS *cached;
+ char *securattr;
+ const SID *usid; /* owner of file/directory */
+ const SID *gsid; /* group of file/directory */
+ uid_t uid;
+ gid_t gid;
+ int perm;
+ BOOL isdir;
+ struct POSIX_SECURITY *pxdesc;
+
+ if (!scx->mapping[MAPUSERS])
+ perm = 07777;
+ else {
+ /* check whether available in cache */
+ cached = fetch_cache(scx,ni);
+ if (cached) {
+ uid = cached->uid;
+ gid = cached->gid;
+ perm = access_check_posix(scx,cached->pxdesc,request,uid,gid);
+ } else {
+ perm = 0; /* default to no permission */
+ isdir = (ni->mrec->flags & MFT_RECORD_IS_DIRECTORY)
+ != const_cpu_to_le16(0);
+ securattr = getsecurityattr(scx->vol, ni);
+ if (securattr) {
+ phead = (const SECURITY_DESCRIPTOR_RELATIVE*)
+ securattr;
+ gsid = (const SID*)&
+ securattr[le32_to_cpu(phead->group)];
+ gid = ntfs_find_group(scx->mapping[MAPGROUPS],gsid);
+#if OWNERFROMACL
+ usid = ntfs_acl_owner(securattr);
+ pxdesc = ntfs_build_permissions_posix(scx->mapping,securattr,
+ usid, gsid, isdir);
+ if (pxdesc)
+ perm = pxdesc->mode & 07777;
+ else
+ perm = -1;
+ uid = ntfs_find_user(scx->mapping[MAPUSERS],usid);
+#else
+ usid = (const SID*)&
+ securattr[le32_to_cpu(phead->owner)];
+ pxdesc = ntfs_build_permissions_posix(scx,securattr,
+ usid, gsid, isdir);
+ if (pxdesc)
+ perm = pxdesc->mode & 07777;
+ else
+ perm = -1;
+ if (!perm && ntfs_same_sid(usid, adminsid)) {
+ uid = find_tenant(scx, securattr);
+ if (uid)
+ perm = 0700;
+ } else
+ uid = ntfs_find_user(scx->mapping[MAPUSERS],usid);
+#endif
+ /*
+ * Create a security id if there were none
+ * and upgrade option is selected
+ */
+ if (!test_nino_flag(ni, v3_Extensions)
+ && (perm >= 0)
+ && (scx->vol->secure_flags
+ & (1 << SECURITY_ADDSECURIDS))) {
+ upgrade_secur_desc(scx->vol,
+ securattr, ni);
+ /*
+ * fetch owner and group for cacheing
+ * if there is a securid
+ */
+ }
+ if (test_nino_flag(ni, v3_Extensions)
+ && (perm >= 0)) {
+ enter_cache(scx, ni, uid,
+ gid, pxdesc);
+ }
+ if (pxdesc) {
+ perm = access_check_posix(scx,pxdesc,request,uid,gid);
+ free(pxdesc);
+ }
+ free(securattr);
+ } else {
+ perm = -1;
+ uid = gid = 0;
+ }
+ }
+ }
+ return (perm);
+}
+
+/*
+ * Get a Posix ACL
+ *
+ * returns size or -errno if there is a problem
+ * if size was too small, no copy is done and errno is not set,
+ * the caller is expected to issue a new call
+ */
+
+int ntfs_get_posix_acl(struct SECURITY_CONTEXT *scx, ntfs_inode *ni,
+ const char *name, char *value, size_t size)
+{
+ const SECURITY_DESCRIPTOR_RELATIVE *phead;
+ struct POSIX_SECURITY *pxdesc;
+ const struct CACHED_PERMISSIONS *cached;
+ char *securattr;
+ const SID *usid; /* owner of file/directory */
+ const SID *gsid; /* group of file/directory */
+ uid_t uid;
+ gid_t gid;
+ BOOL isdir;
+ size_t outsize;
+
+ outsize = 0; /* default to error */
+ if (!scx->mapping[MAPUSERS])
+ errno = ENOTSUP;
+ else {
+ /* check whether available in cache */
+ cached = fetch_cache(scx,ni);
+ if (cached)
+ pxdesc = cached->pxdesc;
+ else {
+ securattr = getsecurityattr(scx->vol, ni);
+ isdir = (ni->mrec->flags & MFT_RECORD_IS_DIRECTORY)
+ != const_cpu_to_le16(0);
+ if (securattr) {
+ phead =
+ (const SECURITY_DESCRIPTOR_RELATIVE*)
+ securattr;
+ gsid = (const SID*)&
+ securattr[le32_to_cpu(phead->group)];
+#if OWNERFROMACL
+ usid = ntfs_acl_owner(securattr);
+#else
+ usid = (const SID*)&
+ securattr[le32_to_cpu(phead->owner)];
+#endif
+ pxdesc = ntfs_build_permissions_posix(scx->mapping,securattr,
+ usid, gsid, isdir);
+
+ /*
+ * fetch owner and group for cacheing
+ */
+ if (pxdesc) {
+ /*
+ * Create a security id if there were none
+ * and upgrade option is selected
+ */
+ if (!test_nino_flag(ni, v3_Extensions)
+ && (scx->vol->secure_flags
+ & (1 << SECURITY_ADDSECURIDS))) {
+ upgrade_secur_desc(scx->vol,
+ securattr, ni);
+ }
+#if OWNERFROMACL
+ uid = ntfs_find_user(scx->mapping[MAPUSERS],usid);
+#else
+ if (!(pxdesc->mode & 07777)
+ && ntfs_same_sid(usid, adminsid)) {
+ uid = find_tenant(scx,
+ securattr);
+ } else
+ uid = ntfs_find_user(scx->mapping[MAPUSERS],usid);
+#endif
+ gid = ntfs_find_group(scx->mapping[MAPGROUPS],gsid);
+ if (pxdesc->tagsset & POSIX_ACL_EXTENSIONS)
+ enter_cache(scx, ni, uid,
+ gid, pxdesc);
+ }
+ free(securattr);
+ } else
+ pxdesc = (struct POSIX_SECURITY*)NULL;
+ }
+
+ if (pxdesc) {
+ if (ntfs_valid_posix(pxdesc)) {
+ if (!strcmp(name,"system.posix_acl_default")) {
+ if (ni->mrec->flags
+ & MFT_RECORD_IS_DIRECTORY)
+ outsize = sizeof(struct POSIX_ACL)
+ + pxdesc->defcnt*sizeof(struct POSIX_ACE);
+ else {
+ /*
+ * getting default ACL from plain file :
+ * return EACCES if size > 0 as
+ * indicated in the man, but return ok
+ * if size == 0, so that ls does not
+ * display an error
+ */
+ if (size > 0) {
+ outsize = 0;
+ errno = EACCES;
+ } else
+ outsize = sizeof(struct POSIX_ACL);
+ }
+ if (outsize && (outsize <= size)) {
+ memcpy(value,&pxdesc->acl,sizeof(struct POSIX_ACL));
+ memcpy(&value[sizeof(struct POSIX_ACL)],
+ &pxdesc->acl.ace[pxdesc->firstdef],
+ outsize-sizeof(struct POSIX_ACL));
+ }
+ } else {
+ outsize = sizeof(struct POSIX_ACL)
+ + pxdesc->acccnt*sizeof(struct POSIX_ACE);
+ if (outsize <= size)
+ memcpy(value,&pxdesc->acl,outsize);
+ }
+ } else {
+ outsize = 0;
+ errno = EIO;
+ ntfs_log_error("Invalid Posix ACL built\n");
+ }
+ if (!cached)
+ free(pxdesc);
+ } else
+ outsize = 0;
+ }
+ return (outsize ? (int)outsize : -errno);
+}
+
+#else /* POSIXACLS */
+
+
+/*
+ * Get permissions to access a file
+ * Takes into account the relation of user to file (owner, group, ...)
+ * Do no use as mode of the file
+ *
+ * returns -1 if there is a problem
+ */
+
+static int ntfs_get_perm(struct SECURITY_CONTEXT *scx,
+ ntfs_inode *ni, mode_t request)
+{
+ const SECURITY_DESCRIPTOR_RELATIVE *phead;
+ const struct CACHED_PERMISSIONS *cached;
+ char *securattr;
+ const SID *usid; /* owner of file/directory */
+ const SID *gsid; /* group of file/directory */
+ BOOL isdir;
+ uid_t uid;
+ gid_t gid;
+ int perm;
+
+ if (!scx->mapping[MAPUSERS] || (!scx->uid && !(request & S_IEXEC)))
+ perm = 07777;
+ else {
+ /* check whether available in cache */
+ cached = fetch_cache(scx,ni);
+ if (cached) {
+ perm = cached->mode;
+ uid = cached->uid;
+ gid = cached->gid;
+ } else {
+ perm = 0; /* default to no permission */
+ isdir = (ni->mrec->flags & MFT_RECORD_IS_DIRECTORY)
+ != const_cpu_to_le16(0);
+ securattr = getsecurityattr(scx->vol, ni);
+ if (securattr) {
+ phead = (const SECURITY_DESCRIPTOR_RELATIVE*)
+ securattr;
+ gsid = (const SID*)&
+ securattr[le32_to_cpu(phead->group)];
+ gid = ntfs_find_group(scx->mapping[MAPGROUPS],gsid);
+#if OWNERFROMACL
+ usid = ntfs_acl_owner(securattr);
+ perm = ntfs_build_permissions(securattr,
+ usid, gsid, isdir);
+ uid = ntfs_find_user(scx->mapping[MAPUSERS],usid);
+#else
+ usid = (const SID*)&
+ securattr[le32_to_cpu(phead->owner)];
+ perm = ntfs_build_permissions(securattr,
+ usid, gsid, isdir);
+ if (!perm && ntfs_same_sid(usid, adminsid)) {
+ uid = find_tenant(scx, securattr);
+ if (uid)
+ perm = 0700;
+ } else
+ uid = ntfs_find_user(scx->mapping[MAPUSERS],usid);
+#endif
+ /*
+ * Create a security id if there were none
+ * and upgrade option is selected
+ */
+ if (!test_nino_flag(ni, v3_Extensions)
+ && (perm >= 0)
+ && (scx->vol->secure_flags
+ & (1 << SECURITY_ADDSECURIDS))) {
+ upgrade_secur_desc(scx->vol,
+ securattr, ni);
+ /*
+ * fetch owner and group for cacheing
+ * if there is a securid
+ */
+ }
+ if (test_nino_flag(ni, v3_Extensions)
+ && (perm >= 0)) {
+ enter_cache(scx, ni, uid,
+ gid, perm);
+ }
+ free(securattr);
+ } else {
+ perm = -1;
+ uid = gid = 0;
+ }
+ }
+ if (perm >= 0) {
+ if (!scx->uid) {
+ /* root access and execution */
+ if (perm & 0111)
+ perm = 07777;
+ else
+ perm = 0;
+ } else
+ if (uid == scx->uid)
+ perm &= 07700;
+ else
+ /*
+ * avoid checking group membership
+ * when the requested perms for group
+ * are the same as perms for other
+ */
+ if ((gid == scx->gid)
+ || ((((perm >> 3) ^ perm)
+ & (request >> 6) & 7)
+ && groupmember(scx, scx->uid, gid)))
+ perm &= 07070;
+ else
+ perm &= 07007;
+ }
+ }
+ return (perm);
+}
+
+#endif /* POSIXACLS */
+
+/*
+ * Get an NTFS ACL
+ *
+ * Returns size or -errno if there is a problem
+ * if size was too small, no copy is done and errno is not set,
+ * the caller is expected to issue a new call
+ */
+
+int ntfs_get_ntfs_acl(struct SECURITY_CONTEXT *scx, ntfs_inode *ni,
+ char *value, size_t size)
+{
+ char *securattr;
+ size_t outsize;
+
+ outsize = 0; /* default to no data and no error */
+ securattr = getsecurityattr(scx->vol, ni);
+ if (securattr) {
+ outsize = ntfs_attr_size(securattr);
+ if (outsize <= size) {
+ memcpy(value,securattr,outsize);
+ }
+ free(securattr);
+ }
+ return (outsize ? (int)outsize : -errno);
+}
+
+/*
+ * Get owner, group and permissions in an stat structure
+ * returns permissions, or -1 if there is a problem
+ */
+
+int ntfs_get_owner_mode(struct SECURITY_CONTEXT *scx,
+ ntfs_inode * ni, struct stat *stbuf)
+{
+ const SECURITY_DESCRIPTOR_RELATIVE *phead;
+ char *securattr;
+ const SID *usid; /* owner of file/directory */
+ const SID *gsid; /* group of file/directory */
+ const struct CACHED_PERMISSIONS *cached;
+ int perm;
+ BOOL isdir;
+#if POSIXACLS
+ struct POSIX_SECURITY *pxdesc;
+#endif
+
+ if (!scx->mapping[MAPUSERS])
+ perm = 07777;
+ else {
+ /* check whether available in cache */
+ cached = fetch_cache(scx,ni);
+ if (cached) {
+ perm = cached->mode;
+ stbuf->st_uid = cached->uid;
+ stbuf->st_gid = cached->gid;
+ stbuf->st_mode = (stbuf->st_mode & ~07777) + perm;
+ } else {
+ perm = -1; /* default to error */
+ isdir = (ni->mrec->flags & MFT_RECORD_IS_DIRECTORY)
+ != const_cpu_to_le16(0);
+ securattr = getsecurityattr(scx->vol, ni);
+ if (securattr) {
+ phead =
+ (const SECURITY_DESCRIPTOR_RELATIVE*)
+ securattr;
+ gsid = (const SID*)&
+ securattr[le32_to_cpu(phead->group)];
+#if OWNERFROMACL
+ usid = ntfs_acl_owner(securattr);
+#else
+ usid = (const SID*)&
+ securattr[le32_to_cpu(phead->owner)];
+#endif
+#if POSIXACLS
+ pxdesc = ntfs_build_permissions_posix(scx->mapping, securattr,
+ usid, gsid, isdir);
+ if (pxdesc)
+ perm = pxdesc->mode & 07777;
+ else
+ perm = -1;
+#else
+ perm = ntfs_build_permissions(securattr,
+ usid, gsid, isdir);
+#endif
+ /*
+ * fetch owner and group for cacheing
+ */
+ if (perm >= 0) {
+ /*
+ * Create a security id if there were none
+ * and upgrade option is selected
+ */
+ if (!test_nino_flag(ni, v3_Extensions)
+ && (scx->vol->secure_flags
+ & (1 << SECURITY_ADDSECURIDS))) {
+ upgrade_secur_desc(scx->vol,
+ securattr, ni);
+ }
+#if OWNERFROMACL
+ stbuf->st_uid = ntfs_find_user(scx->mapping[MAPUSERS],usid);
+#else
+ if (!perm && ntfs_same_sid(usid, adminsid)) {
+ stbuf->st_uid =
+ find_tenant(scx,
+ securattr);
+ if (stbuf->st_uid)
+ perm = 0700;
+ } else
+ stbuf->st_uid = ntfs_find_user(scx->mapping[MAPUSERS],usid);
+#endif
+ stbuf->st_gid = ntfs_find_group(scx->mapping[MAPGROUPS],gsid);
+ stbuf->st_mode =
+ (stbuf->st_mode & ~07777) + perm;
+#if POSIXACLS
+ enter_cache(scx, ni, stbuf->st_uid,
+ stbuf->st_gid, pxdesc);
+ free(pxdesc);
+#else
+ enter_cache(scx, ni, stbuf->st_uid,
+ stbuf->st_gid, perm);
+#endif
+ }
+ free(securattr);
+ }
+ }
+ }
+ return (perm);
+}
+
+#if POSIXACLS
+
+/*
+ * Get the base for a Posix inheritance and
+ * build an inherited Posix descriptor
+ */
+
+static struct POSIX_SECURITY *inherit_posix(struct SECURITY_CONTEXT *scx,
+ ntfs_inode *dir_ni, mode_t mode, BOOL isdir)
+{
+ const struct CACHED_PERMISSIONS *cached;
+ const SECURITY_DESCRIPTOR_RELATIVE *phead;
+ struct POSIX_SECURITY *pxdesc;
+ struct POSIX_SECURITY *pydesc;
+ char *securattr;
+ const SID *usid;
+ const SID *gsid;
+ uid_t uid;
+ gid_t gid;
+
+ pydesc = (struct POSIX_SECURITY*)NULL;
+ /* check whether parent directory is available in cache */
+ cached = fetch_cache(scx,dir_ni);
+ if (cached) {
+ uid = cached->uid;
+ gid = cached->gid;
+ pxdesc = cached->pxdesc;
+ if (pxdesc) {
+ pydesc = ntfs_build_inherited_posix(pxdesc,mode,
+ scx->umask,isdir);
+ }
+ } else {
+ securattr = getsecurityattr(scx->vol, dir_ni);
+ if (securattr) {
+ phead = (const SECURITY_DESCRIPTOR_RELATIVE*)
+ securattr;
+ gsid = (const SID*)&
+ securattr[le32_to_cpu(phead->group)];
+ gid = ntfs_find_group(scx->mapping[MAPGROUPS],gsid);
+#if OWNERFROMACL
+ usid = ntfs_acl_owner(securattr);
+ pxdesc = ntfs_build_permissions_posix(scx->mapping,securattr,
+ usid, gsid, TRUE);
+ uid = ntfs_find_user(scx->mapping[MAPUSERS],usid);
+#else
+ usid = (const SID*)&
+ securattr[le32_to_cpu(phead->owner)];
+ pxdesc = ntfs_build_permissions_posix(scx->mapping,securattr,
+ usid, gsid, TRUE);
+ if (pxdesc && ntfs_same_sid(usid, adminsid)) {
+ uid = find_tenant(scx, securattr);
+ } else
+ uid = ntfs_find_user(scx->mapping[MAPUSERS],usid);
+#endif
+ if (pxdesc) {
+ /*
+ * Create a security id if there were none
+ * and upgrade option is selected
+ */
+ if (!test_nino_flag(dir_ni, v3_Extensions)
+ && (scx->vol->secure_flags
+ & (1 << SECURITY_ADDSECURIDS))) {
+ upgrade_secur_desc(scx->vol,
+ securattr, dir_ni);
+ /*
+ * fetch owner and group for cacheing
+ * if there is a securid
+ */
+ }
+ if (test_nino_flag(dir_ni, v3_Extensions)) {
+ enter_cache(scx, dir_ni, uid,
+ gid, pxdesc);
+ }
+ pydesc = ntfs_build_inherited_posix(pxdesc,
+ mode, scx->umask, isdir);
+ free(pxdesc);
+ }
+ free(securattr);
+ }
+ }
+ return (pydesc);
+}
+
+/*
+ * Allocate a security_id for a file being created
+ *
+ * Returns zero if not possible (NTFS v3.x required)
+ */
+
+le32 ntfs_alloc_securid(struct SECURITY_CONTEXT *scx,
+ uid_t uid, gid_t gid, ntfs_inode *dir_ni,
+ mode_t mode, BOOL isdir)
+{
+#if !FORCE_FORMAT_v1x
+ const struct CACHED_SECURID *cached;
+ struct CACHED_SECURID wanted;
+ struct POSIX_SECURITY *pxdesc;
+ char *newattr;
+ int newattrsz;
+ const SID *usid;
+ const SID *gsid;
+ BIGSID defusid;
+ BIGSID defgsid;
+ le32 securid;
+#endif
+
+ securid = const_cpu_to_le32(0);
+
+#if !FORCE_FORMAT_v1x
+
+ pxdesc = inherit_posix(scx, dir_ni, mode, isdir);
+ if (pxdesc) {
+ /* check whether target securid is known in cache */
+
+ wanted.uid = uid;
+ wanted.gid = gid;
+ wanted.dmode = pxdesc->mode & mode & 07777;
+ if (isdir) wanted.dmode |= 0x10000;
+ wanted.variable = (void*)pxdesc;
+ wanted.varsize = sizeof(struct POSIX_SECURITY)
+ + (pxdesc->acccnt + pxdesc->defcnt)*sizeof(struct POSIX_ACE);
+ cached = (const struct CACHED_SECURID*)ntfs_fetch_cache(
+ scx->vol->securid_cache, GENERIC(&wanted),
+ (cache_compare)compare);
+ /* quite simple, if we are lucky */
+ if (cached)
+ securid = cached->securid;
+
+ /* not in cache : make sure we can create ids */
+
+ if (!cached && (scx->vol->major_ver >= 3)) {
+ usid = ntfs_find_usid(scx->mapping[MAPUSERS],uid,(SID*)&defusid);
+ gsid = ntfs_find_gsid(scx->mapping[MAPGROUPS],gid,(SID*)&defgsid);
+ if (!usid || !gsid) {
+ ntfs_log_error("File created by an unmapped user/group %d/%d\n",
+ (int)uid, (int)gid);
+ usid = gsid = adminsid;
+ }
+ newattr = ntfs_build_descr_posix(scx->mapping, pxdesc,
+ isdir, usid, gsid);
+ if (newattr) {
+ newattrsz = ntfs_attr_size(newattr);
+ securid = setsecurityattr(scx->vol,
+ (const SECURITY_DESCRIPTOR_RELATIVE*)newattr,
+ newattrsz);
+ if (securid) {
+ /* update cache, for subsequent use */
+ wanted.securid = securid;
+ ntfs_enter_cache(scx->vol->securid_cache,
+ GENERIC(&wanted),
+ (cache_compare)compare);
+ }
+ free(newattr);
+ } else {
+ /*
+ * could not build new security attribute
+ * errno set by ntfs_build_descr()
+ */
+ }
+ }
+ free(pxdesc);
+ }
+#endif
+ return (securid);
+}
+
+/*
+ * Apply Posix inheritance to a newly created file
+ * (for NTFS 1.x only : no securid)
+ */
+
+int ntfs_set_inherited_posix(struct SECURITY_CONTEXT *scx,
+ ntfs_inode *ni, uid_t uid, gid_t gid,
+ ntfs_inode *dir_ni, mode_t mode)
+{
+ struct POSIX_SECURITY *pxdesc;
+ char *newattr;
+ const SID *usid;
+ const SID *gsid;
+ BIGSID defusid;
+ BIGSID defgsid;
+ BOOL isdir;
+ int res;
+
+ res = -1;
+ isdir = (ni->mrec->flags & MFT_RECORD_IS_DIRECTORY) != const_cpu_to_le16(0);
+ pxdesc = inherit_posix(scx, dir_ni, mode, isdir);
+ if (pxdesc) {
+ usid = ntfs_find_usid(scx->mapping[MAPUSERS],uid,(SID*)&defusid);
+ gsid = ntfs_find_gsid(scx->mapping[MAPGROUPS],gid,(SID*)&defgsid);
+ if (!usid || !gsid) {
+ ntfs_log_error("File created by an unmapped user/group %d/%d\n",
+ (int)uid, (int)gid);
+ usid = gsid = adminsid;
+ }
+ newattr = ntfs_build_descr_posix(scx->mapping, pxdesc,
+ isdir, usid, gsid);
+ if (newattr) {
+ /* Adjust Windows read-only flag */
+ res = update_secur_descr(scx->vol, newattr, ni);
+ if (!res && !isdir) {
+ if (mode & S_IWUSR)
+ ni->flags &= ~FILE_ATTR_READONLY;
+ else
+ ni->flags |= FILE_ATTR_READONLY;
+ }
+#if CACHE_LEGACY_SIZE
+ /* also invalidate legacy cache */
+ if (isdir && !ni->security_id) {
+ struct CACHED_PERMISSIONS_LEGACY legacy;
+
+ legacy.mft_no = ni->mft_no;
+ legacy.variable = pxdesc;
+ legacy.varsize = sizeof(struct POSIX_SECURITY)
+ + (pxdesc->acccnt + pxdesc->defcnt)*sizeof(struct POSIX_ACE);
+ ntfs_invalidate_cache(scx->vol->legacy_cache,
+ GENERIC(&legacy),
+ (cache_compare)leg_compare,0);
+ }
+#endif
+ free(newattr);
+
+ } else {
+ /*
+ * could not build new security attribute
+ * errno set by ntfs_build_descr()
+ */
+ }
+ }
+ return (res);
+}
+
+#else
+
+le32 ntfs_alloc_securid(struct SECURITY_CONTEXT *scx,
+ uid_t uid, gid_t gid, mode_t mode, BOOL isdir)
+{
+#if !FORCE_FORMAT_v1x
+ const struct CACHED_SECURID *cached;
+ struct CACHED_SECURID wanted;
+ char *newattr;
+ int newattrsz;
+ const SID *usid;
+ const SID *gsid;
+ BIGSID defusid;
+ BIGSID defgsid;
+ le32 securid;
+#endif
+
+ securid = const_cpu_to_le32(0);
+
+#if !FORCE_FORMAT_v1x
+ /* check whether target securid is known in cache */
+
+ wanted.uid = uid;
+ wanted.gid = gid;
+ wanted.dmode = mode & 07777;
+ if (isdir) wanted.dmode |= 0x10000;
+ wanted.variable = (void*)NULL;
+ wanted.varsize = 0;
+ cached = (const struct CACHED_SECURID*)ntfs_fetch_cache(
+ scx->vol->securid_cache, GENERIC(&wanted),
+ (cache_compare)compare);
+ /* quite simple, if we are lucky */
+ if (cached)
+ securid = cached->securid;
+
+ /* not in cache : make sure we can create ids */
+
+ if (!cached && (scx->vol->major_ver >= 3)) {
+ usid = ntfs_find_usid(scx->mapping[MAPUSERS],uid,(SID*)&defusid);
+ gsid = ntfs_find_gsid(scx->mapping[MAPGROUPS],gid,(SID*)&defgsid);
+ if (!usid || !gsid) {
+ ntfs_log_error("File created by an unmapped user/group %d/%d\n",
+ (int)uid, (int)gid);
+ usid = gsid = adminsid;
+ }
+ newattr = ntfs_build_descr(mode, isdir, usid, gsid);
+ if (newattr) {
+ newattrsz = ntfs_attr_size(newattr);
+ securid = setsecurityattr(scx->vol,
+ (const SECURITY_DESCRIPTOR_RELATIVE*)newattr,
+ newattrsz);
+ if (securid) {
+ /* update cache, for subsequent use */
+ wanted.securid = securid;
+ ntfs_enter_cache(scx->vol->securid_cache,
+ GENERIC(&wanted),
+ (cache_compare)compare);
+ }
+ free(newattr);
+ } else {
+ /*
+ * could not build new security attribute
+ * errno set by ntfs_build_descr()
+ */
+ }
+ }
+#endif
+ return (securid);
+}
+
+#endif
+
+/*
+ * Update ownership and mode of a file, reusing an existing
+ * security descriptor when possible
+ *
+ * Returns zero if successful
+ */
+
+#if POSIXACLS
+int ntfs_set_owner_mode(struct SECURITY_CONTEXT *scx, ntfs_inode *ni,
+ uid_t uid, gid_t gid, mode_t mode,
+ struct POSIX_SECURITY *pxdesc)
+#else
+int ntfs_set_owner_mode(struct SECURITY_CONTEXT *scx, ntfs_inode *ni,
+ uid_t uid, gid_t gid, mode_t mode)
+#endif
+{
+ int res;
+ const struct CACHED_SECURID *cached;
+ struct CACHED_SECURID wanted;
+ char *newattr;
+ const SID *usid;
+ const SID *gsid;
+ BIGSID defusid;
+ BIGSID defgsid;
+ BOOL isdir;
+
+ res = 0;
+
+ /* check whether target securid is known in cache */
+
+ isdir = (ni->mrec->flags & MFT_RECORD_IS_DIRECTORY) != const_cpu_to_le16(0);
+ wanted.uid = uid;
+ wanted.gid = gid;
+ wanted.dmode = mode & 07777;
+ if (isdir) wanted.dmode |= 0x10000;
+#if POSIXACLS
+ wanted.variable = (void*)pxdesc;
+ if (pxdesc)
+ wanted.varsize = sizeof(struct POSIX_SECURITY)
+ + (pxdesc->acccnt + pxdesc->defcnt)*sizeof(struct POSIX_ACE);
+ else
+ wanted.varsize = 0;
+#else
+ wanted.variable = (void*)NULL;
+ wanted.varsize = 0;
+#endif
+ if (test_nino_flag(ni, v3_Extensions)) {
+ cached = (const struct CACHED_SECURID*)ntfs_fetch_cache(
+ scx->vol->securid_cache, GENERIC(&wanted),
+ (cache_compare)compare);
+ /* quite simple, if we are lucky */
+ if (cached) {
+ ni->security_id = cached->securid;
+ NInoSetDirty(ni);
+ }
+ } else cached = (struct CACHED_SECURID*)NULL;
+
+ if (!cached) {
+ /*
+ * Do not use usid and gsid from former attributes,
+ * but recompute them to get repeatable results
+ * which can be kept in cache.
+ */
+ usid = ntfs_find_usid(scx->mapping[MAPUSERS],uid,(SID*)&defusid);
+ gsid = ntfs_find_gsid(scx->mapping[MAPGROUPS],gid,(SID*)&defgsid);
+ if (!usid || !gsid) {
+ ntfs_log_error("File made owned by an unmapped user/group %d/%d\n",
+ uid, gid);
+ usid = gsid = adminsid;
+ }
+#if POSIXACLS
+ if (pxdesc)
+ newattr = ntfs_build_descr_posix(scx->mapping, pxdesc,
+ isdir, usid, gsid);
+ else
+ newattr = ntfs_build_descr(mode,
+ isdir, usid, gsid);
+#else
+ newattr = ntfs_build_descr(mode,
+ isdir, usid, gsid);
+#endif
+ if (newattr) {
+ res = update_secur_descr(scx->vol, newattr, ni);
+ if (!res) {
+ /* adjust Windows read-only flag */
+ if (!isdir) {
+ if (mode & S_IWUSR)
+ ni->flags &= ~FILE_ATTR_READONLY;
+ else
+ ni->flags |= FILE_ATTR_READONLY;
+ NInoFileNameSetDirty(ni);
+ }
+ /* update cache, for subsequent use */
+ if (test_nino_flag(ni, v3_Extensions)) {
+ wanted.securid = ni->security_id;
+ ntfs_enter_cache(scx->vol->securid_cache,
+ GENERIC(&wanted),
+ (cache_compare)compare);
+ }
+#if CACHE_LEGACY_SIZE
+ /* also invalidate legacy cache */
+ if (isdir && !ni->security_id) {
+ struct CACHED_PERMISSIONS_LEGACY legacy;
+
+ legacy.mft_no = ni->mft_no;
+#if POSIXACLS
+ legacy.variable = wanted.variable;
+ legacy.varsize = wanted.varsize;
+#else
+ legacy.variable = (void*)NULL;
+ legacy.varsize = 0;
+#endif
+ ntfs_invalidate_cache(scx->vol->legacy_cache,
+ GENERIC(&legacy),
+ (cache_compare)leg_compare,0);
+ }
+#endif
+ }
+ free(newattr);
+ } else {
+ /*
+ * could not build new security attribute
+ * errno set by ntfs_build_descr()
+ */
+ res = -1;
+ }
+ }
+ return (res);
+}
+
+/*
+ * Check whether user has ownership rights on a file
+ *
+ * Returns TRUE if allowed
+ * if not, errno tells why
+ */
+
+BOOL ntfs_allowed_as_owner(struct SECURITY_CONTEXT *scx, ntfs_inode *ni)
+{
+ const struct CACHED_PERMISSIONS *cached;
+ char *oldattr;
+ const SID *usid;
+ uid_t processuid;
+ uid_t uid;
+ BOOL gotowner;
+ int allowed;
+
+ processuid = scx->uid;
+/* TODO : use CAP_FOWNER process capability */
+ /*
+ * Always allow for root
+ * Also always allow if no mapping has been defined
+ */
+ if (!scx->mapping[MAPUSERS] || !processuid)
+ allowed = TRUE;
+ else {
+ gotowner = FALSE; /* default */
+ /* get the owner, either from cache or from old attribute */
+ cached = fetch_cache(scx, ni);
+ if (cached) {
+ uid = cached->uid;
+ gotowner = TRUE;
+ } else {
+ oldattr = getsecurityattr(scx->vol, ni);
+ if (oldattr) {
+#if OWNERFROMACL
+ usid = ntfs_acl_owner(oldattr);
+#else
+ const SECURITY_DESCRIPTOR_RELATIVE *phead;
+
+ phead = (const SECURITY_DESCRIPTOR_RELATIVE*)
+ oldattr;
+ usid = (const SID*)&oldattr
+ [le32_to_cpu(phead->owner)];
+#endif
+ uid = ntfs_find_user(scx->mapping[MAPUSERS],
+ usid);
+ gotowner = TRUE;
+ free(oldattr);
+ }
+ }
+ allowed = FALSE;
+ if (gotowner) {
+/* TODO : use CAP_FOWNER process capability */
+ if (!processuid || (processuid == uid))
+ allowed = TRUE;
+ else
+ errno = EPERM;
+ }
+ }
+ return (allowed);
+}
+
+#ifdef HAVE_SETXATTR /* extended attributes interface required */
+
+#if POSIXACLS
+
+/*
+ * Set a new access or default Posix ACL to a file
+ * (or remove ACL if no input data)
+ * Validity of input data is checked after merging
+ *
+ * Returns 0, or -1 if there is a problem which errno describes
+ */
+
+int ntfs_set_posix_acl(struct SECURITY_CONTEXT *scx, ntfs_inode *ni,
+ const char *name, const char *value, size_t size,
+ int flags)
+{
+ const SECURITY_DESCRIPTOR_RELATIVE *phead;
+ const struct CACHED_PERMISSIONS *cached;
+ char *oldattr;
+ uid_t processuid;
+ const SID *usid;
+ const SID *gsid;
+ uid_t uid;
+ uid_t gid;
+ int res;
+ BOOL isdir;
+ BOOL deflt;
+ BOOL exist;
+ int count;
+ struct POSIX_SECURITY *oldpxdesc;
+ struct POSIX_SECURITY *newpxdesc;
+
+ /* get the current pxsec, either from cache or from old attribute */
+ res = -1;
+ deflt = !strcmp(name,"system.posix_acl_default");
+ if (size)
+ count = (size - sizeof(struct POSIX_ACL)) / sizeof(struct POSIX_ACE);
+ else
+ count = 0;
+ isdir = (ni->mrec->flags & MFT_RECORD_IS_DIRECTORY) != const_cpu_to_le16(0);
+ newpxdesc = (struct POSIX_SECURITY*)NULL;
+ if ((!value
+ || (((const struct POSIX_ACL*)value)->version == POSIX_VERSION))
+ && (!deflt || isdir || (!size && !value))) {
+ cached = fetch_cache(scx, ni);
+ if (cached) {
+ uid = cached->uid;
+ gid = cached->gid;
+ oldpxdesc = cached->pxdesc;
+ if (oldpxdesc) {
+ newpxdesc = ntfs_replace_acl(oldpxdesc,
+ (const struct POSIX_ACL*)value,count,deflt);
+ }
+ } else {
+ oldattr = getsecurityattr(scx->vol, ni);
+ if (oldattr) {
+ phead = (const SECURITY_DESCRIPTOR_RELATIVE*)oldattr;
+#if OWNERFROMACL
+ usid = ntfs_acl_owner(oldattr);
+#else
+ usid = (const SID*)&oldattr[le32_to_cpu(phead->owner)];
+#endif
+ gsid = (const SID*)&oldattr[le32_to_cpu(phead->group)];
+ uid = ntfs_find_user(scx->mapping[MAPUSERS],usid);
+ gid = ntfs_find_group(scx->mapping[MAPGROUPS],gsid);
+ oldpxdesc = ntfs_build_permissions_posix(scx->mapping,
+ oldattr, usid, gsid, isdir);
+ if (oldpxdesc) {
+ if (deflt)
+ exist = oldpxdesc->defcnt > 0;
+ else
+ exist = oldpxdesc->acccnt > 3;
+ if ((exist && (flags & XATTR_CREATE))
+ || (!exist && (flags & XATTR_REPLACE))) {
+ errno = (exist ? EEXIST : ENODATA);
+ } else {
+ newpxdesc = ntfs_replace_acl(oldpxdesc,
+ (const struct POSIX_ACL*)value,count,deflt);
+ }
+ free(oldpxdesc);
+ }
+ free(oldattr);
+ }
+ }
+ } else
+ errno = EINVAL;
+
+ if (newpxdesc) {
+ processuid = scx->uid;
+/* TODO : use CAP_FOWNER process capability */
+ if (!processuid || (uid == processuid)) {
+ /*
+ * clear setgid if file group does
+ * not match process group
+ */
+ if (processuid && (gid != scx->gid)
+ && !groupmember(scx, scx->uid, gid)) {
+ newpxdesc->mode &= ~S_ISGID;
+ }
+ res = ntfs_set_owner_mode(scx, ni, uid, gid,
+ newpxdesc->mode, newpxdesc);
+ } else
+ errno = EPERM;
+ free(newpxdesc);
+ }
+ return (res ? -1 : 0);
+}
+
+/*
+ * Remove a default Posix ACL from a file
+ *
+ * Returns 0, or -1 if there is a problem which errno describes
+ */
+
+int ntfs_remove_posix_acl(struct SECURITY_CONTEXT *scx, ntfs_inode *ni,
+ const char *name)
+{
+ return (ntfs_set_posix_acl(scx, ni, name,
+ (const char*)NULL, 0, 0));
+}
+
+#endif
+
+/*
+ * Set a new NTFS ACL to a file
+ *
+ * Returns 0, or -1 if there is a problem
+ */
+
+int ntfs_set_ntfs_acl(struct SECURITY_CONTEXT *scx, ntfs_inode *ni,
+ const char *value, size_t size, int flags)
+{
+ char *attr;
+ int res;
+
+ res = -1;
+ if ((size > 0)
+ && !(flags & XATTR_CREATE)
+ && ntfs_valid_descr(value,size)
+ && (ntfs_attr_size(value) == size)) {
+ /* need copying in order to write */
+ attr = (char*)ntfs_malloc(size);
+ if (attr) {
+ memcpy(attr,value,size);
+ res = update_secur_descr(scx->vol, attr, ni);
+ /*
+ * No need to invalidate standard caches :
+ * the relation between a securid and
+ * the associated protection is unchanged,
+ * only the relation between a file and
+ * its securid and protection is changed.
+ */
+#if CACHE_LEGACY_SIZE
+ /*
+ * we must however invalidate the legacy
+ * cache, which is based on inode numbers.
+ * For safety, invalidate even if updating
+ * failed.
+ */
+ if ((ni->mrec->flags & MFT_RECORD_IS_DIRECTORY)
+ && !ni->security_id) {
+ struct CACHED_PERMISSIONS_LEGACY legacy;
+
+ legacy.mft_no = ni->mft_no;
+ legacy.variable = (char*)NULL;
+ legacy.varsize = 0;
+ ntfs_invalidate_cache(scx->vol->legacy_cache,
+ GENERIC(&legacy),
+ (cache_compare)leg_compare,0);
+ }
+#endif
+ free(attr);
+ } else
+ errno = ENOMEM;
+ } else
+ errno = EINVAL;
+ return (res ? -1 : 0);
+}
+
+#endif /* HAVE_SETXATTR */
+
+/*
+ * Set new permissions to a file
+ * Checks user mapping has been defined before request for setting
+ *
+ * rejected if request is not originated by owner or root
+ *
+ * returns 0 on success
+ * -1 on failure, with errno = EIO
+ */
+
+int ntfs_set_mode(struct SECURITY_CONTEXT *scx, ntfs_inode *ni, mode_t mode)
+{
+ const SECURITY_DESCRIPTOR_RELATIVE *phead;
+ const struct CACHED_PERMISSIONS *cached;
+ char *oldattr;
+ const SID *usid;
+ const SID *gsid;
+ uid_t processuid;
+ uid_t uid;
+ uid_t gid;
+ int res;
+#if POSIXACLS
+ BOOL isdir;
+ int pxsize;
+ const struct POSIX_SECURITY *oldpxdesc;
+ struct POSIX_SECURITY *newpxdesc = (struct POSIX_SECURITY*)NULL;
+#endif
+
+ /* get the current owner, either from cache or from old attribute */
+ res = 0;
+ cached = fetch_cache(scx, ni);
+ if (cached) {
+ uid = cached->uid;
+ gid = cached->gid;
+#if POSIXACLS
+ oldpxdesc = cached->pxdesc;
+ if (oldpxdesc) {
+ /* must copy before merging */
+ pxsize = sizeof(struct POSIX_SECURITY)
+ + (oldpxdesc->acccnt + oldpxdesc->defcnt)*sizeof(struct POSIX_ACE);
+ newpxdesc = (struct POSIX_SECURITY*)malloc(pxsize);
+ if (newpxdesc) {
+ memcpy(newpxdesc, oldpxdesc, pxsize);
+ if (ntfs_merge_mode_posix(newpxdesc, mode))
+ res = -1;
+ } else
+ res = -1;
+ } else
+ newpxdesc = (struct POSIX_SECURITY*)NULL;
+#endif
+ } else {
+ oldattr = getsecurityattr(scx->vol, ni);
+ if (oldattr) {
+ phead = (const SECURITY_DESCRIPTOR_RELATIVE*)oldattr;
+#if OWNERFROMACL
+ usid = ntfs_acl_owner(oldattr);
+#else
+ usid = (const SID*)&oldattr[le32_to_cpu(phead->owner)];
+#endif
+ gsid = (const SID*)&oldattr[le32_to_cpu(phead->group)];
+ uid = ntfs_find_user(scx->mapping[MAPUSERS],usid);
+ gid = ntfs_find_group(scx->mapping[MAPGROUPS],gsid);
+#if POSIXACLS
+ isdir = (ni->mrec->flags & MFT_RECORD_IS_DIRECTORY) != const_cpu_to_le16(0);
+ newpxdesc = ntfs_build_permissions_posix(scx->mapping,
+ oldattr, usid, gsid, isdir);
+ if (!newpxdesc || ntfs_merge_mode_posix(newpxdesc, mode))
+ res = -1;
+#endif
+ free(oldattr);
+ } else
+ res = -1;
+ }
+
+ if (!res) {
+ processuid = scx->uid;
+/* TODO : use CAP_FOWNER process capability */
+ if (!processuid || (uid == processuid)) {
+ /*
+ * clear setgid if file group does
+ * not match process group
+ */
+ if (processuid && (gid != scx->gid)
+ && !groupmember(scx, scx->uid, gid))
+ mode &= ~S_ISGID;
+#if POSIXACLS
+ if (newpxdesc) {
+ newpxdesc->mode = mode;
+ res = ntfs_set_owner_mode(scx, ni, uid, gid,
+ mode, newpxdesc);
+ } else
+ res = ntfs_set_owner_mode(scx, ni, uid, gid,
+ mode, newpxdesc);
+#else
+ res = ntfs_set_owner_mode(scx, ni, uid, gid, mode);
+#endif
+ } else {
+ errno = EPERM;
+ res = -1; /* neither owner nor root */
+ }
+ } else {
+ /*
+ * Should not happen : a default descriptor is generated
+ * by getsecurityattr() when there are none
+ */
+ ntfs_log_error("File has no security descriptor\n");
+ res = -1;
+ errno = EIO;
+ }
+#if POSIXACLS
+ if (newpxdesc) free(newpxdesc);
+#endif
+ return (res ? -1 : 0);
+}
+
+/*
+ * Create a default security descriptor for files whose descriptor
+ * cannot be inherited
+ */
+
+int ntfs_sd_add_everyone(ntfs_inode *ni)
+{
+ /* JPA SECURITY_DESCRIPTOR_ATTR *sd; */
+ SECURITY_DESCRIPTOR_RELATIVE *sd;
+ ACL *acl;
+ ACCESS_ALLOWED_ACE *ace;
+ SID *sid;
+ int ret, sd_len;
+
+ /* Create SECURITY_DESCRIPTOR attribute (everyone has full access). */
+ /*
+ * Calculate security descriptor length. We have 2 sub-authorities in
+ * owner and group SIDs, but structure SID contain only one, so add
+ * 4 bytes to every SID.
+ */
+ sd_len = sizeof(SECURITY_DESCRIPTOR_ATTR) + 2 * (sizeof(SID) + 4) +
+ sizeof(ACL) + sizeof(ACCESS_ALLOWED_ACE);
+ sd = (SECURITY_DESCRIPTOR_RELATIVE*)ntfs_calloc(sd_len);
+ if (!sd)
+ return -1;
+
+ sd->revision = SECURITY_DESCRIPTOR_REVISION;
+ sd->control = SE_DACL_PRESENT | SE_SELF_RELATIVE;
+
+ sid = (SID*)((u8*)sd + sizeof(SECURITY_DESCRIPTOR_ATTR));
+ sid->revision = SID_REVISION;
+ sid->sub_authority_count = 2;
+ sid->sub_authority[0] = const_cpu_to_le32(SECURITY_BUILTIN_DOMAIN_RID);
+ sid->sub_authority[1] = const_cpu_to_le32(DOMAIN_ALIAS_RID_ADMINS);
+ sid->identifier_authority.value[5] = 5;
+ sd->owner = cpu_to_le32((u8*)sid - (u8*)sd);
+
+ sid = (SID*)((u8*)sid + sizeof(SID) + 4);
+ sid->revision = SID_REVISION;
+ sid->sub_authority_count = 2;
+ sid->sub_authority[0] = const_cpu_to_le32(SECURITY_BUILTIN_DOMAIN_RID);
+ sid->sub_authority[1] = const_cpu_to_le32(DOMAIN_ALIAS_RID_ADMINS);
+ sid->identifier_authority.value[5] = 5;
+ sd->group = cpu_to_le32((u8*)sid - (u8*)sd);
+
+ acl = (ACL*)((u8*)sid + sizeof(SID) + 4);
+ acl->revision = ACL_REVISION;
+ acl->size = const_cpu_to_le16(sizeof(ACL) + sizeof(ACCESS_ALLOWED_ACE));
+ acl->ace_count = const_cpu_to_le16(1);
+ sd->dacl = cpu_to_le32((u8*)acl - (u8*)sd);
+
+ ace = (ACCESS_ALLOWED_ACE*)((u8*)acl + sizeof(ACL));
+ ace->type = ACCESS_ALLOWED_ACE_TYPE;
+ ace->flags = OBJECT_INHERIT_ACE | CONTAINER_INHERIT_ACE;
+ ace->size = const_cpu_to_le16(sizeof(ACCESS_ALLOWED_ACE));
+ ace->mask = const_cpu_to_le32(0x1f01ff); /* FIXME */
+ ace->sid.revision = SID_REVISION;
+ ace->sid.sub_authority_count = 1;
+ ace->sid.sub_authority[0] = const_cpu_to_le32(0);
+ ace->sid.identifier_authority.value[5] = 1;
+
+ ret = ntfs_attr_add(ni, AT_SECURITY_DESCRIPTOR, AT_UNNAMED, 0, (u8*)sd,
+ sd_len);
+ if (ret)
+ ntfs_log_perror("Failed to add initial SECURITY_DESCRIPTOR");
+
+ free(sd);
+ return ret;
+}
+
+/*
+ * Check whether user can access a file in a specific way
+ *
+ * Returns 1 if access is allowed, including user is root or no
+ * user mapping defined
+ * 2 if sticky and accesstype is S_IWRITE + S_IEXEC + S_ISVTX
+ * 0 and sets errno if there is a problem or if access
+ * is not allowed
+ *
+ * This is used for Posix ACL and checking creation of DOS file names
+ */
+
+int ntfs_allowed_access(struct SECURITY_CONTEXT *scx,
+ ntfs_inode *ni,
+ int accesstype) /* access type required (S_Ixxx values) */
+{
+ int perm;
+ int res;
+ int allow;
+ struct stat stbuf;
+
+ /*
+ * Always allow for root unless execution is requested.
+ * (was checked by fuse until kernel 2.6.29)
+ * Also always allow if no mapping has been defined
+ */
+ if (!scx->mapping[MAPUSERS]
+ || (!scx->uid
+ && (!(accesstype & S_IEXEC)
+ || (ni->mrec->flags & MFT_RECORD_IS_DIRECTORY))))
+ allow = 1;
+ else {
+ perm = ntfs_get_perm(scx, ni, accesstype);
+ if (perm >= 0) {
+ res = EACCES;
+ switch (accesstype) {
+ case S_IEXEC:
+ allow = (perm & (S_IXUSR | S_IXGRP | S_IXOTH)) != 0;
+ break;
+ case S_IWRITE:
+ allow = (perm & (S_IWUSR | S_IWGRP | S_IWOTH)) != 0;
+ break;
+ case S_IWRITE + S_IEXEC:
+ allow = ((perm & (S_IWUSR | S_IWGRP | S_IWOTH)) != 0)
+ && ((perm & (S_IXUSR | S_IXGRP | S_IXOTH)) != 0);
+ break;
+ case S_IREAD:
+ allow = (perm & (S_IRUSR | S_IRGRP | S_IROTH)) != 0;
+ break;
+ case S_IREAD + S_IEXEC:
+ allow = ((perm & (S_IRUSR | S_IRGRP | S_IROTH)) != 0)
+ && ((perm & (S_IXUSR | S_IXGRP | S_IXOTH)) != 0);
+ break;
+ case S_IREAD + S_IWRITE:
+ allow = ((perm & (S_IRUSR | S_IRGRP | S_IROTH)) != 0)
+ && ((perm & (S_IWUSR | S_IWGRP | S_IWOTH)) != 0);
+ break;
+ case S_IWRITE + S_IEXEC + S_ISVTX:
+ if (perm & S_ISVTX) {
+ if ((ntfs_get_owner_mode(scx,ni,&stbuf) >= 0)
+ && (stbuf.st_uid == scx->uid))
+ allow = 1;
+ else
+ allow = 2;
+ } else
+ allow = ((perm & (S_IWUSR | S_IWGRP | S_IWOTH)) != 0)
+ && ((perm & (S_IXUSR | S_IXGRP | S_IXOTH)) != 0);
+ break;
+ case S_IREAD + S_IWRITE + S_IEXEC:
+ allow = ((perm & (S_IRUSR | S_IRGRP | S_IROTH)) != 0)
+ && ((perm & (S_IWUSR | S_IWGRP | S_IWOTH)) != 0)
+ && ((perm & (S_IXUSR | S_IXGRP | S_IXOTH)) != 0);
+ break;
+ default :
+ res = EINVAL;
+ allow = 0;
+ break;
+ }
+ if (!allow)
+ errno = res;
+ } else
+ allow = 0;
+ }
+ return (allow);
+}
+
+#if 0 /* not needed any more */
+
+/*
+ * Check whether user can access the parent directory
+ * of a file in a specific way
+ *
+ * Returns true if access is allowed, including user is root and
+ * no user mapping defined
+ *
+ * Sets errno if there is a problem or if not allowed
+ *
+ * This is used for Posix ACL and checking creation of DOS file names
+ */
+
+BOOL old_ntfs_allowed_dir_access(struct SECURITY_CONTEXT *scx,
+ const char *path, int accesstype)
+{
+ int allow;
+ char *dirpath;
+ char *name;
+ ntfs_inode *ni;
+ ntfs_inode *dir_ni;
+ struct stat stbuf;
+
+ allow = 0;
+ dirpath = strdup(path);
+ if (dirpath) {
+ /* the root of file system is seen as a parent of itself */
+ /* is that correct ? */
+ name = strrchr(dirpath, '/');
+ *name = 0;
+ dir_ni = ntfs_pathname_to_inode(scx->vol, NULL, dirpath);
+ if (dir_ni) {
+ allow = ntfs_allowed_access(scx,
+ dir_ni, accesstype);
+ ntfs_inode_close(dir_ni);
+ /*
+ * for an not-owned sticky directory, have to
+ * check whether file itself is owned
+ */
+ if ((accesstype == (S_IWRITE + S_IEXEC + S_ISVTX))
+ && (allow == 2)) {
+ ni = ntfs_pathname_to_inode(scx->vol, NULL,
+ path);
+ allow = FALSE;
+ if (ni) {
+ allow = (ntfs_get_owner_mode(scx,ni,&stbuf) >= 0)
+ && (stbuf.st_uid == scx->uid);
+ ntfs_inode_close(ni);
+ }
+ }
+ }
+ free(dirpath);
+ }
+ return (allow); /* errno is set if not allowed */
+}
+
+#endif
+
+/*
+ * Define a new owner/group to a file
+ *
+ * returns zero if successful
+ */
+
+int ntfs_set_owner(struct SECURITY_CONTEXT *scx, ntfs_inode *ni,
+ uid_t uid, gid_t gid)
+{
+ const SECURITY_DESCRIPTOR_RELATIVE *phead;
+ const struct CACHED_PERMISSIONS *cached;
+ char *oldattr;
+ const SID *usid;
+ const SID *gsid;
+ uid_t fileuid;
+ uid_t filegid;
+ mode_t mode;
+ int perm;
+ BOOL isdir;
+ int res;
+#if POSIXACLS
+ struct POSIX_SECURITY *pxdesc;
+ BOOL pxdescbuilt = FALSE;
+#endif
+
+ res = 0;
+ /* get the current owner and mode from cache or security attributes */
+ oldattr = (char*)NULL;
+ cached = fetch_cache(scx,ni);
+ if (cached) {
+ fileuid = cached->uid;
+ filegid = cached->gid;
+ mode = cached->mode;
+#if POSIXACLS
+ pxdesc = cached->pxdesc;
+ if (!pxdesc)
+ res = -1;
+#endif
+ } else {
+ fileuid = 0;
+ filegid = 0;
+ mode = 0;
+ oldattr = getsecurityattr(scx->vol, ni);
+ if (oldattr) {
+ isdir = (ni->mrec->flags & MFT_RECORD_IS_DIRECTORY)
+ != const_cpu_to_le16(0);
+ phead = (const SECURITY_DESCRIPTOR_RELATIVE*)
+ oldattr;
+ gsid = (const SID*)
+ &oldattr[le32_to_cpu(phead->group)];
+#if OWNERFROMACL
+ usid = ntfs_acl_owner(oldattr);
+#else
+ usid = (const SID*)
+ &oldattr[le32_to_cpu(phead->owner)];
+#endif
+#if POSIXACLS
+ pxdesc = ntfs_build_permissions_posix(scx->mapping, oldattr,
+ usid, gsid, isdir);
+ if (pxdesc) {
+ pxdescbuilt = TRUE;
+ fileuid = ntfs_find_user(scx->mapping[MAPUSERS],usid);
+ filegid = ntfs_find_group(scx->mapping[MAPGROUPS],gsid);
+ mode = perm = pxdesc->mode;
+ } else
+ res = -1;
+#else
+ mode = perm = ntfs_build_permissions(oldattr,
+ usid, gsid, isdir);
+ if (perm >= 0) {
+ fileuid = ntfs_find_user(scx->mapping[MAPUSERS],usid);
+ filegid = ntfs_find_group(scx->mapping[MAPGROUPS],gsid);
+ } else
+ res = -1;
+#endif
+ free(oldattr);
+ } else
+ res = -1;
+ }
+ if (!res) {
+ /* check requested by root */
+ /* or chgrp requested by owner to an owned group */
+ if (!scx->uid
+ || ((((int)uid < 0) || (uid == fileuid))
+ && ((gid == scx->gid) || groupmember(scx, scx->uid, gid))
+ && (fileuid == scx->uid))) {
+ /* replace by the new usid and gsid */
+ /* or reuse old gid and sid for cacheing */
+ if ((int)uid < 0)
+ uid = fileuid;
+ if ((int)gid < 0)
+ gid = filegid;
+ /* clear setuid and setgid if owner has changed */
+ /* unless request originated by root */
+ if (uid && (fileuid != uid))
+ mode &= 01777;
+#if POSIXACLS
+ res = ntfs_set_owner_mode(scx, ni, uid, gid,
+ mode, pxdesc);
+#else
+ res = ntfs_set_owner_mode(scx, ni, uid, gid, mode);
+#endif
+ } else {
+ res = -1; /* neither owner nor root */
+ errno = EPERM;
+ }
+#if POSIXACLS
+ if (pxdescbuilt)
+ free(pxdesc);
+#endif
+ } else {
+ /*
+ * Should not happen : a default descriptor is generated
+ * by getsecurityattr() when there are none
+ */
+ ntfs_log_error("File has no security descriptor\n");
+ res = -1;
+ errno = EIO;
+ }
+ return (res ? -1 : 0);
+}
+
+/*
+ * Define new owner/group and mode to a file
+ *
+ * returns zero if successful
+ */
+
+int ntfs_set_ownmod(struct SECURITY_CONTEXT *scx, ntfs_inode *ni,
+ uid_t uid, gid_t gid, const mode_t mode)
+{
+ const struct CACHED_PERMISSIONS *cached;
+ char *oldattr;
+ uid_t fileuid;
+ uid_t filegid;
+ int res;
+#if POSIXACLS
+ const SECURITY_DESCRIPTOR_RELATIVE *phead;
+ const SID *usid;
+ const SID *gsid;
+ BOOL isdir;
+ const struct POSIX_SECURITY *oldpxdesc;
+ struct POSIX_SECURITY *newpxdesc = (struct POSIX_SECURITY*)NULL;
+ int pxsize;
+#endif
+
+ res = 0;
+ /* get the current owner and mode from cache or security attributes */
+ oldattr = (char*)NULL;
+ cached = fetch_cache(scx,ni);
+ if (cached) {
+ fileuid = cached->uid;
+ filegid = cached->gid;
+#if POSIXACLS
+ oldpxdesc = cached->pxdesc;
+ if (oldpxdesc) {
+ /* must copy before merging */
+ pxsize = sizeof(struct POSIX_SECURITY)
+ + (oldpxdesc->acccnt + oldpxdesc->defcnt)*sizeof(struct POSIX_ACE);
+ newpxdesc = (struct POSIX_SECURITY*)malloc(pxsize);
+ if (newpxdesc) {
+ memcpy(newpxdesc, oldpxdesc, pxsize);
+ if (ntfs_merge_mode_posix(newpxdesc, mode))
+ res = -1;
+ } else
+ res = -1;
+ }
+#endif
+ } else {
+ fileuid = 0;
+ filegid = 0;
+ oldattr = getsecurityattr(scx->vol, ni);
+ if (oldattr) {
+#if POSIXACLS
+ isdir = (ni->mrec->flags & MFT_RECORD_IS_DIRECTORY)
+ != const_cpu_to_le16(0);
+ phead = (const SECURITY_DESCRIPTOR_RELATIVE*)
+ oldattr;
+ gsid = (const SID*)
+ &oldattr[le32_to_cpu(phead->group)];
+#if OWNERFROMACL
+ usid = ntfs_acl_owner(oldattr);
+#else
+ usid = (const SID*)
+ &oldattr[le32_to_cpu(phead->owner)];
+#endif
+ newpxdesc = ntfs_build_permissions_posix(scx->mapping, oldattr,
+ usid, gsid, isdir);
+ if (!newpxdesc || ntfs_merge_mode_posix(newpxdesc, mode))
+ res = -1;
+ else {
+ fileuid = ntfs_find_user(scx->mapping[MAPUSERS],usid);
+ filegid = ntfs_find_group(scx->mapping[MAPGROUPS],gsid);
+ }
+#endif
+ free(oldattr);
+ } else
+ res = -1;
+ }
+ if (!res) {
+ /* check requested by root */
+ /* or chgrp requested by owner to an owned group */
+ if (!scx->uid
+ || ((((int)uid < 0) || (uid == fileuid))
+ && ((gid == scx->gid) || groupmember(scx, scx->uid, gid))
+ && (fileuid == scx->uid))) {
+ /* replace by the new usid and gsid */
+ /* or reuse old gid and sid for cacheing */
+ if ((int)uid < 0)
+ uid = fileuid;
+ if ((int)gid < 0)
+ gid = filegid;
+#if POSIXACLS
+ res = ntfs_set_owner_mode(scx, ni, uid, gid,
+ mode, newpxdesc);
+#else
+ res = ntfs_set_owner_mode(scx, ni, uid, gid, mode);
+#endif
+ } else {
+ res = -1; /* neither owner nor root */
+ errno = EPERM;
+ }
+ } else {
+ /*
+ * Should not happen : a default descriptor is generated
+ * by getsecurityattr() when there are none
+ */
+ ntfs_log_error("File has no security descriptor\n");
+ res = -1;
+ errno = EIO;
+ }
+#if POSIXACLS
+ free(newpxdesc);
+#endif
+ return (res ? -1 : 0);
+}
+
+/*
+ * Build a security id for a descriptor inherited from
+ * parent directory the Windows way
+ */
+
+static le32 build_inherited_id(struct SECURITY_CONTEXT *scx,
+ const char *parentattr, BOOL fordir)
+{
+ const SECURITY_DESCRIPTOR_RELATIVE *pphead;
+ const ACL *ppacl;
+ const SID *usid;
+ const SID *gsid;
+ BIGSID defusid;
+ BIGSID defgsid;
+ int offpacl;
+ int offowner;
+ int offgroup;
+ SECURITY_DESCRIPTOR_RELATIVE *pnhead;
+ ACL *pnacl;
+ int parentattrsz;
+ char *newattr;
+ int newattrsz;
+ int aclsz;
+ int usidsz;
+ int gsidsz;
+ int pos;
+ le32 securid;
+
+ parentattrsz = ntfs_attr_size(parentattr);
+ pphead = (const SECURITY_DESCRIPTOR_RELATIVE*)parentattr;
+ if (scx->mapping[MAPUSERS]) {
+ usid = ntfs_find_usid(scx->mapping[MAPUSERS], scx->uid, (SID*)&defusid);
+ gsid = ntfs_find_gsid(scx->mapping[MAPGROUPS], scx->gid, (SID*)&defgsid);
+ if (!usid)
+ usid = adminsid;
+ if (!gsid)
+ gsid = adminsid;
+ } else {
+ /*
+ * If there is no user mapping, we have to copy owner
+ * and group from parent directory.
+ * Windows never has to do that, because it can always
+ * rely on a user mapping
+ */
+ offowner = le32_to_cpu(pphead->owner);
+ usid = (const SID*)&parentattr[offowner];
+ offgroup = le32_to_cpu(pphead->group);
+ gsid = (const SID*)&parentattr[offgroup];
+ }
+ /*
+ * new attribute is smaller than parent's
+ * except for differences in SIDs which appear in
+ * owner, group and possible grants and denials in
+ * generic creator-owner and creator-group ACEs.
+ * For directories, an ACE may be duplicated for
+ * access and inheritance, so we double the count.
+ */
+ usidsz = ntfs_sid_size(usid);
+ gsidsz = ntfs_sid_size(gsid);
+ newattrsz = parentattrsz + 3*usidsz + 3*gsidsz;
+ if (fordir)
+ newattrsz *= 2;
+ newattr = (char*)ntfs_malloc(newattrsz);
+ if (newattr) {
+ pnhead = (SECURITY_DESCRIPTOR_RELATIVE*)newattr;
+ pnhead->revision = SECURITY_DESCRIPTOR_REVISION;
+ pnhead->alignment = 0;
+ pnhead->control = SE_SELF_RELATIVE;
+ pos = sizeof(SECURITY_DESCRIPTOR_RELATIVE);
+ /*
+ * locate and inherit DACL
+ * do not test SE_DACL_PRESENT (wrong for "DR Watson")
+ */
+ pnhead->dacl = const_cpu_to_le32(0);
+ if (pphead->dacl) {
+ offpacl = le32_to_cpu(pphead->dacl);
+ ppacl = (const ACL*)&parentattr[offpacl];
+ pnacl = (ACL*)&newattr[pos];
+ aclsz = ntfs_inherit_acl(ppacl, pnacl, usid, gsid, fordir);
+ if (aclsz) {
+ pnhead->dacl = cpu_to_le32(pos);
+ pos += aclsz;
+ pnhead->control |= SE_DACL_PRESENT;
+ }
+ }
+ /*
+ * locate and inherit SACL
+ */
+ pnhead->sacl = const_cpu_to_le32(0);
+ if (pphead->sacl) {
+ offpacl = le32_to_cpu(pphead->sacl);
+ ppacl = (const ACL*)&parentattr[offpacl];
+ pnacl = (ACL*)&newattr[pos];
+ aclsz = ntfs_inherit_acl(ppacl, pnacl, usid, gsid, fordir);
+ if (aclsz) {
+ pnhead->sacl = cpu_to_le32(pos);
+ pos += aclsz;
+ pnhead->control |= SE_SACL_PRESENT;
+ }
+ }
+ /*
+ * inherit or redefine owner
+ */
+ memcpy(&newattr[pos],usid,usidsz);
+ pnhead->owner = cpu_to_le32(pos);
+ pos += usidsz;
+ /*
+ * inherit or redefine group
+ */
+ memcpy(&newattr[pos],gsid,gsidsz);
+ pnhead->group = cpu_to_le32(pos);
+ pos += usidsz;
+ securid = setsecurityattr(scx->vol,
+ (SECURITY_DESCRIPTOR_RELATIVE*)newattr, pos);
+ free(newattr);
+ } else
+ securid = const_cpu_to_le32(0);
+ return (securid);
+}
+
+/*
+ * Get an inherited security id
+ *
+ * For Windows compatibility, the normal initial permission setting
+ * may be inherited from the parent directory instead of being
+ * defined by the creation arguments.
+ *
+ * The following creates an inherited id for that purpose.
+ *
+ * Note : the owner and group of parent directory are also
+ * inherited (which is not the case on Windows) if no user mapping
+ * is defined.
+ *
+ * Returns the inherited id, or zero if not possible (eg on NTFS 1.x)
+ */
+
+le32 ntfs_inherited_id(struct SECURITY_CONTEXT *scx,
+ ntfs_inode *dir_ni, BOOL fordir)
+{
+ struct CACHED_PERMISSIONS *cached;
+ char *parentattr;
+ le32 securid;
+
+ securid = const_cpu_to_le32(0);
+ cached = (struct CACHED_PERMISSIONS*)NULL;
+ /*
+ * Try to get inherited id from cache
+ */
+ if (test_nino_flag(dir_ni, v3_Extensions)
+ && dir_ni->security_id) {
+ cached = fetch_cache(scx, dir_ni);
+ if (cached)
+ securid = (fordir ? cached->inh_dirid
+ : cached->inh_fileid);
+ }
+ /*
+ * Not cached or not available in cache, compute it all
+ * Note : if parent directory has no id, it is not cacheable
+ */
+ if (!securid) {
+ parentattr = getsecurityattr(scx->vol, dir_ni);
+ if (parentattr) {
+ securid = build_inherited_id(scx,
+ parentattr, fordir);
+ free(parentattr);
+ /*
+ * Store the result into cache for further use
+ */
+ if (securid) {
+ cached = fetch_cache(scx, dir_ni);
+ if (cached) {
+ if (fordir)
+ cached->inh_dirid = securid;
+ else
+ cached->inh_fileid = securid;
+ }
+ }
+ }
+ }
+ return (securid);
+}
+
+/*
+ * Link a group to a member of group
+ *
+ * Returns 0 if OK, -1 (and errno set) if error
+ */
+
+static int link_single_group(struct MAPPING *usermapping, struct passwd *user,
+ gid_t gid)
+{
+ struct group *group;
+ char **grmem;
+ int grcnt;
+ gid_t *groups;
+ int res;
+
+ res = 0;
+ group = getgrgid(gid);
+ if (group && group->gr_mem) {
+ grcnt = usermapping->grcnt;
+ groups = usermapping->groups;
+ grmem = group->gr_mem;
+ while (*grmem && strcmp(user->pw_name, *grmem))
+ grmem++;
+ if (*grmem) {
+ if (!grcnt)
+ groups = (gid_t*)malloc(sizeof(gid_t));
+ else
+ groups = (gid_t*)realloc(groups,
+ (grcnt+1)*sizeof(gid_t));
+ if (groups)
+ groups[grcnt++] = gid;
+ else {
+ res = -1;
+ errno = ENOMEM;
+ }
+ }
+ usermapping->grcnt = grcnt;
+ usermapping->groups = groups;
+ }
+ return (res);
+}
+
+
+/*
+ * Statically link group to users
+ * This is based on groups defined in /etc/group and does not take
+ * the groups dynamically set by setgroups() nor any changes in
+ * /etc/group into account
+ *
+ * Only mapped groups and root group are linked to mapped users
+ *
+ * Returns 0 if OK, -1 (and errno set) if error
+ *
+ */
+
+static int link_group_members(struct SECURITY_CONTEXT *scx)
+{
+ struct MAPPING *usermapping;
+ struct MAPPING *groupmapping;
+ struct passwd *user;
+ int res;
+
+ res = 0;
+ for (usermapping=scx->mapping[MAPUSERS]; usermapping && !res;
+ usermapping=usermapping->next) {
+ usermapping->grcnt = 0;
+ usermapping->groups = (gid_t*)NULL;
+ user = getpwuid(usermapping->xid);
+ if (user && user->pw_name) {
+ for (groupmapping=scx->mapping[MAPGROUPS];
+ groupmapping && !res;
+ groupmapping=groupmapping->next) {
+ if (link_single_group(usermapping, user,
+ groupmapping->xid))
+ res = -1;
+ }
+ if (!res && link_single_group(usermapping,
+ user, (gid_t)0))
+ res = -1;
+ }
+ }
+ return (res);
+}
+
+/*
+ * Apply default single user mapping
+ * returns zero if successful
+ */
+
+static int ntfs_do_default_mapping(struct SECURITY_CONTEXT *scx,
+ uid_t uid, gid_t gid, const SID *usid)
+{
+ struct MAPPING *usermapping;
+ struct MAPPING *groupmapping;
+ SID *sid;
+ int sidsz;
+ int res;
+
+ res = -1;
+ sidsz = ntfs_sid_size(usid);
+ sid = (SID*)ntfs_malloc(sidsz);
+ if (sid) {
+ memcpy(sid,usid,sidsz);
+ usermapping = (struct MAPPING*)ntfs_malloc(sizeof(struct MAPPING));
+ if (usermapping) {
+ groupmapping = (struct MAPPING*)ntfs_malloc(sizeof(struct MAPPING));
+ if (groupmapping) {
+ usermapping->sid = sid;
+ usermapping->xid = uid;
+ usermapping->next = (struct MAPPING*)NULL;
+ groupmapping->sid = sid;
+ groupmapping->xid = gid;
+ groupmapping->next = (struct MAPPING*)NULL;
+ scx->mapping[MAPUSERS] = usermapping;
+ scx->mapping[MAPGROUPS] = groupmapping;
+ res = 0;
+ }
+ }
+ }
+ return (res);
+}
+
+/*
+ * Make sure there are no ambiguous mapping
+ * Ambiguous mapping may lead to undesired configurations and
+ * we had rather be safe until the consequences are understood
+ */
+
+#if 0 /* not activated for now */
+
+static BOOL check_mapping(const struct MAPPING *usermapping,
+ const struct MAPPING *groupmapping)
+{
+ const struct MAPPING *mapping1;
+ const struct MAPPING *mapping2;
+ BOOL ambiguous;
+
+ ambiguous = FALSE;
+ for (mapping1=usermapping; mapping1; mapping1=mapping1->next)
+ for (mapping2=mapping1->next; mapping2; mapping1=mapping2->next)
+ if (ntfs_same_sid(mapping1->sid,mapping2->sid)) {
+ if (mapping1->xid != mapping2->xid)
+ ambiguous = TRUE;
+ } else {
+ if (mapping1->xid == mapping2->xid)
+ ambiguous = TRUE;
+ }
+ for (mapping1=groupmapping; mapping1; mapping1=mapping1->next)
+ for (mapping2=mapping1->next; mapping2; mapping1=mapping2->next)
+ if (ntfs_same_sid(mapping1->sid,mapping2->sid)) {
+ if (mapping1->xid != mapping2->xid)
+ ambiguous = TRUE;
+ } else {
+ if (mapping1->xid == mapping2->xid)
+ ambiguous = TRUE;
+ }
+ return (ambiguous);
+}
+
+#endif
+
+#if 0 /* not used any more */
+
+/*
+ * Try and apply default single user mapping
+ * returns zero if successful
+ */
+
+static int ntfs_default_mapping(struct SECURITY_CONTEXT *scx)
+{
+ const SECURITY_DESCRIPTOR_RELATIVE *phead;
+ ntfs_inode *ni;
+ char *securattr;
+ const SID *usid;
+ int res;
+
+ res = -1;
+ ni = ntfs_pathname_to_inode(scx->vol, NULL, "/.");
+ if (ni) {
+ securattr = getsecurityattr(scx->vol, ni);
+ if (securattr) {
+ phead = (const SECURITY_DESCRIPTOR_RELATIVE*)securattr;
+ usid = (SID*)&securattr[le32_to_cpu(phead->owner)];
+ if (ntfs_is_user_sid(usid))
+ res = ntfs_do_default_mapping(scx,
+ scx->uid, scx->gid, usid);
+ free(securattr);
+ }
+ ntfs_inode_close(ni);
+ }
+ return (res);
+}
+
+#endif
+
+/*
+ * Basic read from a user mapping file on another volume
+ */
+
+static int basicread(void *fileid, char *buf, size_t size, off_t offs __attribute__((unused)))
+{
+ return (read(*(int*)fileid, buf, size));
+}
+
+
+/*
+ * Read from a user mapping file on current NTFS partition
+ */
+
+static int localread(void *fileid, char *buf, size_t size, off_t offs)
+{
+ return (ntfs_attr_data_read((ntfs_inode*)fileid,
+ AT_UNNAMED, 0, buf, size, offs));
+}
+
+/*
+ * Build the user mapping
+ * - according to a mapping file if defined (or default present),
+ * - or try default single user mapping if possible
+ *
+ * The mapping is specific to a mounted device
+ * No locking done, mounting assumed non multithreaded
+ *
+ * returns zero if mapping is successful
+ * (failure should not be interpreted as an error)
+ */
+
+int ntfs_build_mapping(struct SECURITY_CONTEXT *scx, const char *usermap_path,
+ BOOL allowdef)
+{
+ struct MAPLIST *item;
+ struct MAPLIST *firstitem;
+ struct MAPPING *usermapping;
+ struct MAPPING *groupmapping;
+ ntfs_inode *ni;
+ int fd;
+ static struct {
+ u8 revision;
+ u8 levels;
+ be16 highbase;
+ be32 lowbase;
+ le32 level1;
+ le32 level2;
+ le32 level3;
+ le32 level4;
+ le32 level5;
+ } defmap = {
+ 1, 5, const_cpu_to_be16(0), const_cpu_to_be32(5),
+ const_cpu_to_le32(21),
+ const_cpu_to_le32(DEFSECAUTH1), const_cpu_to_le32(DEFSECAUTH2),
+ const_cpu_to_le32(DEFSECAUTH3), const_cpu_to_le32(DEFSECBASE)
+ } ;
+
+ /* be sure not to map anything until done */
+ scx->mapping[MAPUSERS] = (struct MAPPING*)NULL;
+ scx->mapping[MAPGROUPS] = (struct MAPPING*)NULL;
+
+ if (!usermap_path) usermap_path = MAPPINGFILE;
+ if (usermap_path[0] == '/') {
+ fd = open(usermap_path,O_RDONLY);
+ if (fd > 0) {
+ firstitem = ntfs_read_mapping(basicread, (void*)&fd);
+ close(fd);
+ } else
+ firstitem = (struct MAPLIST*)NULL;
+ } else {
+ ni = ntfs_pathname_to_inode(scx->vol, NULL, usermap_path);
+ if (ni) {
+ firstitem = ntfs_read_mapping(localread, ni);
+ ntfs_inode_close(ni);
+ } else
+ firstitem = (struct MAPLIST*)NULL;
+ }
+
+
+ if (firstitem) {
+ usermapping = ntfs_do_user_mapping(firstitem);
+ groupmapping = ntfs_do_group_mapping(firstitem);
+ if (usermapping && groupmapping) {
+ scx->mapping[MAPUSERS] = usermapping;
+ scx->mapping[MAPGROUPS] = groupmapping;
+ } else
+ ntfs_log_error("There were no valid user or no valid group\n");
+ /* now we can free the memory copy of input text */
+ /* and rely on internal representation */
+ while (firstitem) {
+ item = firstitem->next;
+ free(firstitem);
+ firstitem = item;
+ }
+ } else {
+ /* no mapping file, try a default mapping */
+ if (allowdef) {
+ if (!ntfs_do_default_mapping(scx,
+ 0, 0, (const SID*)&defmap))
+ ntfs_log_info("Using default user mapping\n");
+ }
+ }
+ return (!scx->mapping[MAPUSERS] || link_group_members(scx));
+}
+
+#ifdef HAVE_SETXATTR /* extended attributes interface required */
+
+/*
+ * Get the ntfs attribute into an extended attribute
+ * The attribute is returned according to cpu endianness
+ */
+
+int ntfs_get_ntfs_attrib(ntfs_inode *ni, char *value, size_t size)
+{
+ u32 attrib;
+ size_t outsize;
+
+ outsize = 0; /* default to no data and no error */
+ if (ni) {
+ attrib = le32_to_cpu(ni->flags);
+ if (ni->mrec->flags & MFT_RECORD_IS_DIRECTORY)
+ attrib |= const_le32_to_cpu(FILE_ATTR_DIRECTORY);
+ else
+ attrib &= ~const_le32_to_cpu(FILE_ATTR_DIRECTORY);
+ if (!attrib)
+ attrib |= const_le32_to_cpu(FILE_ATTR_NORMAL);
+ outsize = sizeof(FILE_ATTR_FLAGS);
+ if (size >= outsize) {
+ if (value)
+ memcpy(value,&attrib,outsize);
+ else
+ errno = EINVAL;
+ }
+ }
+ return (outsize ? (int)outsize : -errno);
+}
+
+/*
+ * Return the ntfs attribute into an extended attribute
+ * The attribute is expected according to cpu endianness
+ *
+ * Returns 0, or -1 if there is a problem
+ */
+
+int ntfs_set_ntfs_attrib(ntfs_inode *ni,
+ const char *value, size_t size, int flags)
+{
+ u32 attrib;
+ le32 settable;
+ ATTR_FLAGS dirflags;
+ int res;
+
+ res = -1;
+ if (ni && value && (size >= sizeof(FILE_ATTR_FLAGS))) {
+ if (!(flags & XATTR_CREATE)) {
+ /* copy to avoid alignment problems */
+ memcpy(&attrib,value,sizeof(FILE_ATTR_FLAGS));
+ settable = FILE_ATTR_SETTABLE;
+ res = 0;
+ if (ni->mrec->flags & MFT_RECORD_IS_DIRECTORY) {
+ /*
+ * Accept changing compression for a directory
+ * and set index root accordingly
+ */
+ settable |= FILE_ATTR_COMPRESSED;
+ if ((ni->flags ^ cpu_to_le32(attrib))
+ & FILE_ATTR_COMPRESSED) {
+ if (ni->flags & FILE_ATTR_COMPRESSED)
+ dirflags = const_cpu_to_le16(0);
+ else
+ dirflags = ATTR_IS_COMPRESSED;
+ res = ntfs_attr_set_flags(ni,
+ AT_INDEX_ROOT,
+ NTFS_INDEX_I30, 4,
+ dirflags,
+ ATTR_COMPRESSION_MASK);
+ }
+ }
+ if (!res) {
+ ni->flags = (ni->flags & ~settable)
+ | (cpu_to_le32(attrib) & settable);
+ NInoFileNameSetDirty(ni);
+ NInoSetDirty(ni);
+ }
+ } else
+ errno = EEXIST;
+ } else
+ errno = EINVAL;
+ return (res ? -1 : 0);
+}
+
+#endif /* HAVE_SETXATTR */
+
+/*
+ * Open $Secure once for all
+ * returns zero if it succeeds
+ * non-zero if it fails. This is not an error (on NTFS v1.x)
+ */
+
+
+int ntfs_open_secure(ntfs_volume *vol)
+{
+ ntfs_inode *ni;
+ int res;
+
+ res = -1;
+ vol->secure_ni = (ntfs_inode*)NULL;
+ vol->secure_xsii = (ntfs_index_context*)NULL;
+ vol->secure_xsdh = (ntfs_index_context*)NULL;
+ if (vol->major_ver >= 3) {
+ /* make sure this is a genuine $Secure inode 9 */
+ ni = ntfs_pathname_to_inode(vol, NULL, "$Secure");
+ if (ni && (ni->mft_no == 9)) {
+ vol->secure_reentry = 0;
+ vol->secure_xsii = ntfs_index_ctx_get(ni,
+ sii_stream, 4);
+ vol->secure_xsdh = ntfs_index_ctx_get(ni,
+ sdh_stream, 4);
+ if (ni && vol->secure_xsii && vol->secure_xsdh) {
+ vol->secure_ni = ni;
+ res = 0;
+ }
+ }
+ }
+ return (res);
+}
+
+/*
+ * Final cleaning
+ * Allocated memory is freed to facilitate the detection of memory leaks
+ */
+
+void ntfs_close_secure(struct SECURITY_CONTEXT *scx)
+{
+ ntfs_volume *vol;
+
+ vol = scx->vol;
+ if (vol->secure_ni) {
+ ntfs_index_ctx_put(vol->secure_xsii);
+ ntfs_index_ctx_put(vol->secure_xsdh);
+ ntfs_inode_close(vol->secure_ni);
+
+ }
+ ntfs_free_mapping(scx->mapping);
+ free_caches(scx);
+}
+
+/*
+ * API for direct access to security descriptors
+ * based on Win32 API
+ */
+
+
+/*
+ * Selective feeding of a security descriptor into user buffer
+ *
+ * Returns TRUE if successful
+ */
+
+static BOOL feedsecurityattr(const char *attr, u32 selection,
+ char *buf, u32 buflen, u32 *psize)
+{
+ const SECURITY_DESCRIPTOR_RELATIVE *phead;
+ SECURITY_DESCRIPTOR_RELATIVE *pnhead;
+ const ACL *pdacl;
+ const ACL *psacl;
+ const SID *pusid;
+ const SID *pgsid;
+ unsigned int offdacl;
+ unsigned int offsacl;
+ unsigned int offowner;
+ unsigned int offgroup;
+ unsigned int daclsz;
+ unsigned int saclsz;
+ unsigned int usidsz;
+ unsigned int gsidsz;
+ unsigned int size; /* size of requested attributes */
+ BOOL ok;
+ unsigned int pos;
+ unsigned int avail;
+ le16 control;
+
+ avail = 0;
+ control = SE_SELF_RELATIVE;
+ phead = (const SECURITY_DESCRIPTOR_RELATIVE*)attr;
+ size = sizeof(SECURITY_DESCRIPTOR_RELATIVE);
+
+ /* locate DACL if requested and available */
+ if (phead->dacl && (selection & DACL_SECURITY_INFORMATION)) {
+ offdacl = le32_to_cpu(phead->dacl);
+ pdacl = (const ACL*)&attr[offdacl];
+ daclsz = le16_to_cpu(pdacl->size);
+ size += daclsz;
+ avail |= DACL_SECURITY_INFORMATION;
+ } else
+ offdacl = daclsz = 0;
+
+ /* locate owner if requested and available */
+ offowner = le32_to_cpu(phead->owner);
+ if (offowner && (selection & OWNER_SECURITY_INFORMATION)) {
+ /* find end of USID */
+ pusid = (const SID*)&attr[offowner];
+ usidsz = ntfs_sid_size(pusid);
+ size += usidsz;
+ avail |= OWNER_SECURITY_INFORMATION;
+ } else
+ offowner = usidsz = 0;
+
+ /* locate group if requested and available */
+ offgroup = le32_to_cpu(phead->group);
+ if (offgroup && (selection & GROUP_SECURITY_INFORMATION)) {
+ /* find end of GSID */
+ pgsid = (const SID*)&attr[offgroup];
+ gsidsz = ntfs_sid_size(pgsid);
+ size += gsidsz;
+ avail |= GROUP_SECURITY_INFORMATION;
+ } else
+ offgroup = gsidsz = 0;
+
+ /* locate SACL if requested and available */
+ if (phead->sacl && (selection & SACL_SECURITY_INFORMATION)) {
+ /* find end of SACL */
+ offsacl = le32_to_cpu(phead->sacl);
+ psacl = (const ACL*)&attr[offsacl];
+ saclsz = le16_to_cpu(psacl->size);
+ size += saclsz;
+ avail |= SACL_SECURITY_INFORMATION;
+ } else
+ offsacl = saclsz = 0;
+
+ /*
+ * Check having enough size in destination buffer
+ * (required size is returned nevertheless so that
+ * the request can be reissued with adequate size)
+ */
+ if (size > buflen) {
+ *psize = size;
+ errno = EINVAL;
+ ok = FALSE;
+ } else {
+ if (selection & OWNER_SECURITY_INFORMATION)
+ control |= phead->control & SE_OWNER_DEFAULTED;
+ if (selection & GROUP_SECURITY_INFORMATION)
+ control |= phead->control & SE_GROUP_DEFAULTED;
+ if (selection & DACL_SECURITY_INFORMATION)
+ control |= phead->control
+ & (SE_DACL_PRESENT
+ | SE_DACL_DEFAULTED
+ | SE_DACL_AUTO_INHERITED
+ | SE_DACL_PROTECTED);
+ if (selection & SACL_SECURITY_INFORMATION)
+ control |= phead->control
+ & (SE_SACL_PRESENT
+ | SE_SACL_DEFAULTED
+ | SE_SACL_AUTO_INHERITED
+ | SE_SACL_PROTECTED);
+ /*
+ * copy header and feed new flags, even if no detailed data
+ */
+ memcpy(buf,attr,sizeof(SECURITY_DESCRIPTOR_RELATIVE));
+ pnhead = (SECURITY_DESCRIPTOR_RELATIVE*)buf;
+ pnhead->control = control;
+ pos = sizeof(SECURITY_DESCRIPTOR_RELATIVE);
+
+ /* copy DACL if requested and available */
+ if (selection & avail & DACL_SECURITY_INFORMATION) {
+ pnhead->dacl = cpu_to_le32(pos);
+ memcpy(&buf[pos],&attr[offdacl],daclsz);
+ pos += daclsz;
+ } else
+ pnhead->dacl = const_cpu_to_le32(0);
+
+ /* copy SACL if requested and available */
+ if (selection & avail & SACL_SECURITY_INFORMATION) {
+ pnhead->sacl = cpu_to_le32(pos);
+ memcpy(&buf[pos],&attr[offsacl],saclsz);
+ pos += saclsz;
+ } else
+ pnhead->sacl = const_cpu_to_le32(0);
+
+ /* copy owner if requested and available */
+ if (selection & avail & OWNER_SECURITY_INFORMATION) {
+ pnhead->owner = cpu_to_le32(pos);
+ memcpy(&buf[pos],&attr[offowner],usidsz);
+ pos += usidsz;
+ } else
+ pnhead->owner = const_cpu_to_le32(0);
+
+ /* copy group if requested and available */
+ if (selection & avail & GROUP_SECURITY_INFORMATION) {
+ pnhead->group = cpu_to_le32(pos);
+ memcpy(&buf[pos],&attr[offgroup],gsidsz);
+ pos += gsidsz;
+ } else
+ pnhead->group = const_cpu_to_le32(0);
+ if (pos != size)
+ ntfs_log_error("Error in security descriptor size\n");
+ *psize = size;
+ ok = TRUE;
+ }
+
+ return (ok);
+}
+
+/*
+ * Merge a new security descriptor into the old one
+ * and assign to designated file
+ *
+ * Returns TRUE if successful
+ */
+
+static BOOL mergesecurityattr(ntfs_volume *vol, const char *oldattr,
+ const char *newattr, u32 selection, ntfs_inode *ni)
+{
+ const SECURITY_DESCRIPTOR_RELATIVE *oldhead;
+ const SECURITY_DESCRIPTOR_RELATIVE *newhead;
+ SECURITY_DESCRIPTOR_RELATIVE *targhead;
+ const ACL *pdacl;
+ const ACL *psacl;
+ const SID *powner;
+ const SID *pgroup;
+ int offdacl;
+ int offsacl;
+ int offowner;
+ int offgroup;
+ unsigned int size;
+ le16 control;
+ char *target;
+ int pos;
+ int oldattrsz;
+ int newattrsz;
+ BOOL ok;
+
+ ok = FALSE; /* default return */
+ oldhead = (const SECURITY_DESCRIPTOR_RELATIVE*)oldattr;
+ newhead = (const SECURITY_DESCRIPTOR_RELATIVE*)newattr;
+ oldattrsz = ntfs_attr_size(oldattr);
+ newattrsz = ntfs_attr_size(newattr);
+ target = (char*)ntfs_malloc(oldattrsz + newattrsz);
+ if (target) {
+ targhead = (SECURITY_DESCRIPTOR_RELATIVE*)target;
+ pos = sizeof(SECURITY_DESCRIPTOR_RELATIVE);
+ control = SE_SELF_RELATIVE;
+ /*
+ * copy new DACL if selected
+ * or keep old DACL if any
+ */
+ if ((selection & DACL_SECURITY_INFORMATION) ?
+ newhead->dacl : oldhead->dacl) {
+ if (selection & DACL_SECURITY_INFORMATION) {
+ offdacl = le32_to_cpu(newhead->dacl);
+ pdacl = (const ACL*)&newattr[offdacl];
+ } else {
+ offdacl = le32_to_cpu(oldhead->dacl);
+ pdacl = (const ACL*)&oldattr[offdacl];
+ }
+ size = le16_to_cpu(pdacl->size);
+ memcpy(&target[pos], pdacl, size);
+ targhead->dacl = cpu_to_le32(pos);
+ pos += size;
+ } else
+ targhead->dacl = const_cpu_to_le32(0);
+ if (selection & DACL_SECURITY_INFORMATION) {
+ control |= newhead->control
+ & (SE_DACL_PRESENT
+ | SE_DACL_DEFAULTED
+ | SE_DACL_PROTECTED);
+ if (newhead->control & SE_DACL_AUTO_INHERIT_REQ)
+ control |= SE_DACL_AUTO_INHERITED;
+ } else
+ control |= oldhead->control
+ & (SE_DACL_PRESENT
+ | SE_DACL_DEFAULTED
+ | SE_DACL_AUTO_INHERITED
+ | SE_DACL_PROTECTED);
+ /*
+ * copy new SACL if selected
+ * or keep old SACL if any
+ */
+ if ((selection & SACL_SECURITY_INFORMATION) ?
+ newhead->sacl : oldhead->sacl) {
+ if (selection & SACL_SECURITY_INFORMATION) {
+ offsacl = le32_to_cpu(newhead->sacl);
+ psacl = (const ACL*)&newattr[offsacl];
+ } else {
+ offsacl = le32_to_cpu(oldhead->sacl);
+ psacl = (const ACL*)&oldattr[offsacl];
+ }
+ size = le16_to_cpu(psacl->size);
+ memcpy(&target[pos], psacl, size);
+ targhead->sacl = cpu_to_le32(pos);
+ pos += size;
+ } else
+ targhead->sacl = const_cpu_to_le32(0);
+ if (selection & SACL_SECURITY_INFORMATION) {
+ control |= newhead->control
+ & (SE_SACL_PRESENT
+ | SE_SACL_DEFAULTED
+ | SE_SACL_PROTECTED);
+ if (newhead->control & SE_SACL_AUTO_INHERIT_REQ)
+ control |= SE_SACL_AUTO_INHERITED;
+ } else
+ control |= oldhead->control
+ & (SE_SACL_PRESENT
+ | SE_SACL_DEFAULTED
+ | SE_SACL_AUTO_INHERITED
+ | SE_SACL_PROTECTED);
+ /*
+ * copy new OWNER if selected
+ * or keep old OWNER if any
+ */
+ if ((selection & OWNER_SECURITY_INFORMATION) ?
+ newhead->owner : oldhead->owner) {
+ if (selection & OWNER_SECURITY_INFORMATION) {
+ offowner = le32_to_cpu(newhead->owner);
+ powner = (const SID*)&newattr[offowner];
+ } else {
+ offowner = le32_to_cpu(oldhead->owner);
+ powner = (const SID*)&oldattr[offowner];
+ }
+ size = ntfs_sid_size(powner);
+ memcpy(&target[pos], powner, size);
+ targhead->owner = cpu_to_le32(pos);
+ pos += size;
+ } else
+ targhead->owner = const_cpu_to_le32(0);
+ if (selection & OWNER_SECURITY_INFORMATION)
+ control |= newhead->control & SE_OWNER_DEFAULTED;
+ else
+ control |= oldhead->control & SE_OWNER_DEFAULTED;
+ /*
+ * copy new GROUP if selected
+ * or keep old GROUP if any
+ */
+ if ((selection & GROUP_SECURITY_INFORMATION) ?
+ newhead->group : oldhead->group) {
+ if (selection & GROUP_SECURITY_INFORMATION) {
+ offgroup = le32_to_cpu(newhead->group);
+ pgroup = (const SID*)&newattr[offgroup];
+ control |= newhead->control
+ & SE_GROUP_DEFAULTED;
+ } else {
+ offgroup = le32_to_cpu(oldhead->group);
+ pgroup = (const SID*)&oldattr[offgroup];
+ control |= oldhead->control
+ & SE_GROUP_DEFAULTED;
+ }
+ size = ntfs_sid_size(pgroup);
+ memcpy(&target[pos], pgroup, size);
+ targhead->group = cpu_to_le32(pos);
+ pos += size;
+ } else
+ targhead->group = const_cpu_to_le32(0);
+ if (selection & GROUP_SECURITY_INFORMATION)
+ control |= newhead->control & SE_GROUP_DEFAULTED;
+ else
+ control |= oldhead->control & SE_GROUP_DEFAULTED;
+ targhead->revision = SECURITY_DESCRIPTOR_REVISION;
+ targhead->alignment = 0;
+ targhead->control = control;
+ ok = !update_secur_descr(vol, target, ni);
+ free(target);
+ }
+ return (ok);
+}
+
+#if 0
+/*
+ * Return the security descriptor of a file
+ * This is intended to be similar to GetFileSecurity() from Win32
+ * in order to facilitate the development of portable tools
+ *
+ * returns zero if unsuccessful (following Win32 conventions)
+ * -1 if no securid
+ * the securid if any
+ *
+ * The Win32 API is :
+ *
+ * BOOL WINAPI GetFileSecurity(
+ * __in LPCTSTR lpFileName,
+ * __in SECURITY_INFORMATION RequestedInformation,
+ * __out_opt PSECURITY_DESCRIPTOR pSecurityDescriptor,
+ * __in DWORD nLength,
+ * __out LPDWORD lpnLengthNeeded
+ * );
+ *
+ */
+
+int ntfs_get_file_security(struct SECURITY_API *scapi,
+ const char *path, u32 selection,
+ char *buf, u32 buflen, u32 *psize)
+{
+ ntfs_inode *ni;
+ char *attr;
+ int res;
+
+ res = 0; /* default return */
+ if (scapi && (scapi->magic == MAGIC_API)) {
+ ni = ntfs_pathname_to_inode(scapi->security.vol, NULL, path);
+ if (ni) {
+ attr = getsecurityattr(scapi->security.vol, ni);
+ if (attr) {
+ if (feedsecurityattr(attr,selection,
+ buf,buflen,psize)) {
+ if (test_nino_flag(ni, v3_Extensions)
+ && ni->security_id)
+ res = le32_to_cpu(
+ ni->security_id);
+ else
+ res = -1;
+ }
+ free(attr);
+ }
+ ntfs_inode_close(ni);
+ } else
+ errno = ENOENT;
+ if (!res) *psize = 0;
+ } else
+ errno = EINVAL; /* do not clear *psize */
+ return (res);
+}
+
+
+/*
+ * Set the security descriptor of a file or directory
+ * This is intended to be similar to SetFileSecurity() from Win32
+ * in order to facilitate the development of portable tools
+ *
+ * returns zero if unsuccessful (following Win32 conventions)
+ * -1 if no securid
+ * the securid if any
+ *
+ * The Win32 API is :
+ *
+ * BOOL WINAPI SetFileSecurity(
+ * __in LPCTSTR lpFileName,
+ * __in SECURITY_INFORMATION SecurityInformation,
+ * __in PSECURITY_DESCRIPTOR pSecurityDescriptor
+ * );
+ */
+
+int ntfs_set_file_security(struct SECURITY_API *scapi,
+ const char *path, u32 selection, const char *attr)
+{
+ const SECURITY_DESCRIPTOR_RELATIVE *phead;
+ ntfs_inode *ni;
+ int attrsz;
+ BOOL missing;
+ char *oldattr;
+ int res;
+
+ res = 0; /* default return */
+ if (scapi && (scapi->magic == MAGIC_API) && attr) {
+ phead = (const SECURITY_DESCRIPTOR_RELATIVE*)attr;
+ attrsz = ntfs_attr_size(attr);
+ /* if selected, owner and group must be present or defaulted */
+ missing = ((selection & OWNER_SECURITY_INFORMATION)
+ && !phead->owner
+ && !(phead->control & SE_OWNER_DEFAULTED))
+ || ((selection & GROUP_SECURITY_INFORMATION)
+ && !phead->group
+ && !(phead->control & SE_GROUP_DEFAULTED));
+ if (!missing
+ && (phead->control & SE_SELF_RELATIVE)
+ && ntfs_valid_descr(attr, attrsz)) {
+ ni = ntfs_pathname_to_inode(scapi->security.vol,
+ NULL, path);
+ if (ni) {
+ oldattr = getsecurityattr(scapi->security.vol,
+ ni);
+ if (oldattr) {
+ if (mergesecurityattr(
+ scapi->security.vol,
+ oldattr, attr,
+ selection, ni)) {
+ if (test_nino_flag(ni,
+ v3_Extensions))
+ res = le32_to_cpu(
+ ni->security_id);
+ else
+ res = -1;
+ }
+ free(oldattr);
+ }
+ ntfs_inode_close(ni);
+ }
+ } else
+ errno = EINVAL;
+ } else
+ errno = EINVAL;
+ return (res);
+}
+#endif
+
+/*
+ * Set security data on a NTFS file given an inode
+ *
+ * Returns nonzero on success
+ */
+int _ntfs_set_file_security(ntfs_volume *vol, ntfs_inode *ni,
+ u32 selection, const char *attr)
+{
+ const SECURITY_DESCRIPTOR_RELATIVE *phead;
+ int attrsz;
+ BOOL missing;
+ char *oldattr;
+ int res;
+
+ res = 0; /* default return */
+ phead = (const SECURITY_DESCRIPTOR_RELATIVE*)attr;
+ attrsz = ntfs_attr_size(attr);
+ /* if selected, owner and group must be present or defaulted */
+ missing = ((selection & OWNER_SECURITY_INFORMATION)
+ && !phead->owner
+ && !(phead->control & SE_OWNER_DEFAULTED))
+ || ((selection & GROUP_SECURITY_INFORMATION)
+ && !phead->group
+ && !(phead->control & SE_GROUP_DEFAULTED));
+ if (!missing
+ && (phead->control & SE_SELF_RELATIVE)
+ && ntfs_valid_descr(attr, attrsz)) {
+ oldattr = getsecurityattr(vol, ni);
+ if (oldattr) {
+ if (mergesecurityattr(
+ vol,
+ oldattr, attr,
+ selection, ni)) {
+ if (test_nino_flag(ni,
+ v3_Extensions))
+ res = le32_to_cpu(
+ ni->security_id);
+ else
+ res = -1;
+ }
+ free(oldattr);
+ }
+ } else
+ errno = EINVAL;
+ return (res);
+}
+
+
+#if 0
+/*
+ * Return the attributes of a file
+ * This is intended to be similar to GetFileAttributes() from Win32
+ * in order to facilitate the development of portable tools
+ *
+ * returns -1 if unsuccessful (Win32 : INVALID_FILE_ATTRIBUTES)
+ *
+ * The Win32 API is :
+ *
+ * DWORD WINAPI GetFileAttributes(
+ * __in LPCTSTR lpFileName
+ * );
+ */
+
+int ntfs_get_file_attributes(struct SECURITY_API *scapi, const char *path)
+{
+ ntfs_inode *ni;
+ s32 attrib;
+
+ attrib = -1; /* default return */
+ if (scapi && (scapi->magic == MAGIC_API) && path) {
+ ni = ntfs_pathname_to_inode(scapi->security.vol, NULL, path);
+ if (ni) {
+ attrib = le32_to_cpu(ni->flags);
+ if (ni->mrec->flags & MFT_RECORD_IS_DIRECTORY)
+ attrib |= const_le32_to_cpu(FILE_ATTR_DIRECTORY);
+ else
+ attrib &= ~const_le32_to_cpu(FILE_ATTR_DIRECTORY);
+ if (!attrib)
+ attrib |= const_le32_to_cpu(FILE_ATTR_NORMAL);
+
+ ntfs_inode_close(ni);
+ } else
+ errno = ENOENT;
+ } else
+ errno = EINVAL; /* do not clear *psize */
+ return (attrib);
+}
+
+
+/*
+ * Set attributes to a file or directory
+ * This is intended to be similar to SetFileAttributes() from Win32
+ * in order to facilitate the development of portable tools
+ *
+ * Only a few flags can be set (same list as Win32)
+ *
+ * returns zero if unsuccessful (following Win32 conventions)
+ * nonzero if successful
+ *
+ * The Win32 API is :
+ *
+ * BOOL WINAPI SetFileAttributes(
+ * __in LPCTSTR lpFileName,
+ * __in DWORD dwFileAttributes
+ * );
+ */
+
+BOOL ntfs_set_file_attributes(struct SECURITY_API *scapi,
+ const char *path, s32 attrib)
+{
+ ntfs_inode *ni;
+ le32 settable;
+ ATTR_FLAGS dirflags;
+ int res;
+
+ res = 0; /* default return */
+ if (scapi && (scapi->magic == MAGIC_API) && path) {
+ ni = ntfs_pathname_to_inode(scapi->security.vol, NULL, path);
+ if (ni) {
+ settable = FILE_ATTR_SETTABLE;
+ if (ni->mrec->flags & MFT_RECORD_IS_DIRECTORY) {
+ /*
+ * Accept changing compression for a directory
+ * and set index root accordingly
+ */
+ settable |= FILE_ATTR_COMPRESSED;
+ if ((ni->flags ^ cpu_to_le32(attrib))
+ & FILE_ATTR_COMPRESSED) {
+ if (ni->flags & FILE_ATTR_COMPRESSED)
+ dirflags = const_cpu_to_le16(0);
+ else
+ dirflags = ATTR_IS_COMPRESSED;
+ res = ntfs_attr_set_flags(ni,
+ AT_INDEX_ROOT,
+ NTFS_INDEX_I30, 4,
+ dirflags,
+ ATTR_COMPRESSION_MASK);
+ }
+ }
+ if (!res) {
+ ni->flags = (ni->flags & ~settable)
+ | (cpu_to_le32(attrib) & settable);
+ NInoSetDirty(ni);
+ NInoFileNameSetDirty(ni);
+ }
+ if (!ntfs_inode_close(ni))
+ res = -1;
+ } else
+ errno = ENOENT;
+ }
+ return (res);
+}
+#endif
+
+/*
+ * Set attributes of a NTFS file given an inode
+ *
+ * Returns nonzero on success
+ */
+int _ntfs_set_file_attributes(ntfs_inode *ni, s32 attrib)
+{
+ le32 settable = FILE_ATTR_SETTABLE;
+ ATTR_FLAGS dirflags;
+ int ret = 0;
+
+ if (ni->mrec->flags & MFT_RECORD_IS_DIRECTORY) {
+ /*
+ * Accept changing compression for a directory
+ * and set index root accordingly
+ */
+ settable |= FILE_ATTR_COMPRESSED;
+ if ((ni->flags ^ cpu_to_le32(attrib))
+ & FILE_ATTR_COMPRESSED) {
+ if (ni->flags & FILE_ATTR_COMPRESSED)
+ dirflags = const_cpu_to_le16(0);
+ else
+ dirflags = ATTR_IS_COMPRESSED;
+ ret = ntfs_attr_set_flags(ni,
+ AT_INDEX_ROOT,
+ NTFS_INDEX_I30, 4,
+ dirflags,
+ ATTR_COMPRESSION_MASK);
+ }
+ }
+ if (ret == 0) {
+ ni->flags = (ni->flags & ~settable)
+ | (cpu_to_le32(attrib) & settable);
+ NInoSetDirty(ni);
+ NInoFileNameSetDirty(ni);
+ ret = -1;
+ }
+ return ret;
+}
+
+
+#if 0
+BOOL ntfs_read_directory(struct SECURITY_API *scapi,
+ const char *path, ntfs_filldir_t callback, void *context)
+{
+ ntfs_inode *ni;
+ BOOL ok;
+ s64 pos;
+
+ ok = FALSE; /* default return */
+ if (scapi && (scapi->magic == MAGIC_API) && callback) {
+ ni = ntfs_pathname_to_inode(scapi->security.vol, NULL, path);
+ if (ni) {
+ if (ni->mrec->flags & MFT_RECORD_IS_DIRECTORY) {
+ pos = 0;
+ ntfs_readdir(ni,&pos,context,callback);
+ ok = !ntfs_inode_close(ni);
+ } else {
+ ntfs_inode_close(ni);
+ errno = ENOTDIR;
+ }
+ } else
+ errno = ENOENT;
+ } else
+ errno = EINVAL; /* do not clear *psize */
+ return (ok);
+}
+
+/*
+ * read $SDS (for auditing security data)
+ *
+ * Returns the number or read bytes, or -1 if there is an error
+ */
+
+int ntfs_read_sds(struct SECURITY_API *scapi,
+ char *buf, u32 size, u32 offset)
+{
+ int got;
+
+ got = -1; /* default return */
+ if (scapi && (scapi->magic == MAGIC_API)) {
+ if (scapi->security.vol->secure_ni)
+ got = ntfs_attr_data_read(scapi->security.vol->secure_ni,
+ STREAM_SDS, 4, buf, size, offset);
+ else
+ errno = EOPNOTSUPP;
+ } else
+ errno = EINVAL;
+ return (got);
+}
+
+/*
+ * read $SII (for auditing security data)
+ *
+ * Returns next entry, or NULL if there is an error
+ */
+
+INDEX_ENTRY *ntfs_read_sii(struct SECURITY_API *scapi,
+ INDEX_ENTRY *entry)
+{
+ SII_INDEX_KEY key;
+ INDEX_ENTRY *ret;
+ BOOL found;
+ ntfs_index_context *xsii;
+
+ ret = (INDEX_ENTRY*)NULL; /* default return */
+ if (scapi && (scapi->magic == MAGIC_API)) {
+ xsii = scapi->security.vol->secure_xsii;
+ if (xsii) {
+ if (!entry) {
+ key.security_id = const_cpu_to_le32(0);
+ found = !ntfs_index_lookup((char*)&key,
+ sizeof(SII_INDEX_KEY), xsii);
+ /* not supposed to find */
+ if (!found && (errno == ENOENT))
+ ret = xsii->entry;
+ } else
+ ret = ntfs_index_next(entry,xsii);
+ if (!ret)
+ errno = ENODATA;
+ } else
+ errno = EOPNOTSUPP;
+ } else
+ errno = EINVAL;
+ return (ret);
+}
+
+/*
+ * read $SDH (for auditing security data)
+ *
+ * Returns next entry, or NULL if there is an error
+ */
+
+INDEX_ENTRY *ntfs_read_sdh(struct SECURITY_API *scapi,
+ INDEX_ENTRY *entry)
+{
+ SDH_INDEX_KEY key;
+ INDEX_ENTRY *ret;
+ BOOL found;
+ ntfs_index_context *xsdh;
+
+ ret = (INDEX_ENTRY*)NULL; /* default return */
+ if (scapi && (scapi->magic == MAGIC_API)) {
+ xsdh = scapi->security.vol->secure_xsdh;
+ if (xsdh) {
+ if (!entry) {
+ key.hash = const_cpu_to_le32(0);
+ key.security_id = const_cpu_to_le32(0);
+ found = !ntfs_index_lookup((char*)&key,
+ sizeof(SDH_INDEX_KEY), xsdh);
+ /* not supposed to find */
+ if (!found && (errno == ENOENT))
+ ret = xsdh->entry;
+ } else
+ ret = ntfs_index_next(entry,xsdh);
+ if (!ret)
+ errno = ENODATA;
+ } else errno = ENOTSUP;
+ } else
+ errno = EINVAL;
+ return (ret);
+}
+
+/*
+ * Get the mapped user SID
+ * A buffer of 40 bytes has to be supplied
+ *
+ * returns the size of the SID, or zero and errno set if not found
+ */
+
+int ntfs_get_usid(struct SECURITY_API *scapi, uid_t uid, char *buf)
+{
+ const SID *usid;
+ BIGSID defusid;
+ int size;
+
+ size = 0;
+ if (scapi && (scapi->magic == MAGIC_API)) {
+ usid = ntfs_find_usid(scapi->security.mapping[MAPUSERS], uid, (SID*)&defusid);
+ if (usid) {
+ size = ntfs_sid_size(usid);
+ memcpy(buf,usid,size);
+ } else
+ errno = ENODATA;
+ } else
+ errno = EINVAL;
+ return (size);
+}
+
+/*
+ * Get the mapped group SID
+ * A buffer of 40 bytes has to be supplied
+ *
+ * returns the size of the SID, or zero and errno set if not found
+ */
+
+int ntfs_get_gsid(struct SECURITY_API *scapi, gid_t gid, char *buf)
+{
+ const SID *gsid;
+ BIGSID defgsid;
+ int size;
+
+ size = 0;
+ if (scapi && (scapi->magic == MAGIC_API)) {
+ gsid = ntfs_find_gsid(scapi->security.mapping[MAPGROUPS], gid, (SID*)&defgsid);
+ if (gsid) {
+ size = ntfs_sid_size(gsid);
+ memcpy(buf,gsid,size);
+ } else
+ errno = ENODATA;
+ } else
+ errno = EINVAL;
+ return (size);
+}
+
+/*
+ * Get the user mapped to a SID
+ *
+ * returns the uid, or -1 if not found
+ */
+
+int ntfs_get_user(struct SECURITY_API *scapi, const SID *usid)
+{
+ int uid;
+
+ uid = -1;
+ if (scapi && (scapi->magic == MAGIC_API) && ntfs_valid_sid(usid)) {
+ if (ntfs_same_sid(usid,adminsid))
+ uid = 0;
+ else {
+ uid = ntfs_find_user(scapi->security.mapping[MAPUSERS], usid);
+ if (!uid) {
+ uid = -1;
+ errno = ENODATA;
+ }
+ }
+ } else
+ errno = EINVAL;
+ return (uid);
+}
+
+/*
+ * Get the group mapped to a SID
+ *
+ * returns the uid, or -1 if not found
+ */
+
+int ntfs_get_group(struct SECURITY_API *scapi, const SID *gsid)
+{
+ int gid;
+
+ gid = -1;
+ if (scapi && (scapi->magic == MAGIC_API) && ntfs_valid_sid(gsid)) {
+ if (ntfs_same_sid(gsid,adminsid))
+ gid = 0;
+ else {
+ gid = ntfs_find_group(scapi->security.mapping[MAPGROUPS], gsid);
+ if (!gid) {
+ gid = -1;
+ errno = ENODATA;
+ }
+ }
+ } else
+ errno = EINVAL;
+ return (gid);
+}
+
+/*
+ * Initializations before calling ntfs_get_file_security()
+ * ntfs_set_file_security() and ntfs_read_directory()
+ *
+ * Only allowed for root
+ *
+ * Returns an (obscured) struct SECURITY_API* needed for further calls
+ * NULL if not root (EPERM) or device is mounted (EBUSY)
+ */
+
+struct SECURITY_API *ntfs_initialize_file_security(const char *device,
+ unsigned long flags)
+{
+ ntfs_volume *vol;
+ unsigned long mntflag;
+ int mnt;
+ struct SECURITY_API *scapi;
+ struct SECURITY_CONTEXT *scx;
+
+ scapi = (struct SECURITY_API*)NULL;
+ mnt = ntfs_check_if_mounted(device, &mntflag);
+ if (!mnt && !(mntflag & NTFS_MF_MOUNTED) && !getuid()) {
+ vol = ntfs_mount(device, flags);
+ if (vol) {
+ scapi = (struct SECURITY_API*)
+ ntfs_malloc(sizeof(struct SECURITY_API));
+ if (!ntfs_volume_get_free_space(vol)
+ && scapi) {
+ scapi->magic = MAGIC_API;
+ scapi->seccache = (struct PERMISSIONS_CACHE*)NULL;
+ scx = &scapi->security;
+ scx->vol = vol;
+ scx->uid = getuid();
+ scx->gid = getgid();
+ scx->pseccache = &scapi->seccache;
+ scx->vol->secure_flags = 0;
+ /* accept no mapping and no $Secure */
+ ntfs_build_mapping(scx,(const char*)NULL,TRUE);
+ ntfs_open_secure(vol);
+ } else {
+ if (scapi)
+ free(scapi);
+ else
+ errno = ENOMEM;
+ mnt = ntfs_umount(vol,FALSE);
+ scapi = (struct SECURITY_API*)NULL;
+ }
+ }
+ } else
+ if (getuid())
+ errno = EPERM;
+ else
+ errno = EBUSY;
+ return (scapi);
+}
+
+/*
+ * Leaving after ntfs_initialize_file_security()
+ *
+ * Returns FALSE if FAILED
+ */
+
+BOOL ntfs_leave_file_security(struct SECURITY_API *scapi)
+{
+ int ok;
+ ntfs_volume *vol;
+
+ ok = FALSE;
+ if (scapi && (scapi->magic == MAGIC_API) && scapi->security.vol) {
+ vol = scapi->security.vol;
+ ntfs_close_secure(&scapi->security);
+ free(scapi);
+ if (!ntfs_umount(vol, 0))
+ ok = TRUE;
+ }
+ return (ok);
+}
+
+#endif