| /** |
| * 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-2015 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 |
| |
| #ifdef HAVE_STDIO_H |
| #include <stdio.h> |
| #endif |
| #ifdef HAVE_STDLIB_H |
| #include <stdlib.h> |
| #endif |
| #ifdef HAVE_STRING_H |
| #include <string.h> |
| #endif |
| #ifdef HAVE_ERRNO_H |
| #include <errno.h> |
| #endif |
| #ifdef HAVE_FCNTL_H |
| #include <fcntl.h> |
| #endif |
| #ifdef HAVE_SETXATTR |
| #include <sys/xattr.h> |
| #endif |
| #ifdef HAVE_SYS_STAT_H |
| #include <sys/stat.h> |
| #endif |
| |
| #include <unistd.h> |
| #include <pwd.h> |
| #include <grp.h> |
| |
| #include "compat.h" |
| #include "param.h" |
| #include "types.h" |
| #include "layout.h" |
| #include "attrib.h" |
| #include "index.h" |
| #include "dir.h" |
| #include "bitmap.h" |
| #include "security.h" |
| #include "acls.h" |
| #include "cache.h" |
| #include "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_valid_sid(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_valid_sid(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; |
| |
| /* this is called at most once from mkntfs */ |
| srandom(time((time_t*)NULL) ^ (getpid() << 16)); |
| 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)) { |
| /* |
| * Make sure the data size for $SDS marks the end |
| * of the last security attribute. Windows uses |
| * this to determine where the next attribute will |
| * be written, which causes issues if chkdsk had |
| * previously deleted the last entries without |
| * adjusting the size. |
| */ |
| res = ntfs_attr_shrink_size(vol->secure_ni,STREAM_SDS, |
| 4, offs - gap + ALIGN_SDS_BLOCK + fullsz); |
| } |
| 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"); |
| psii = (struct SII*)NULL; |
| } else { |
| /* restore errno */ |
| errno = olderrno; |
| entry = xsii->entry; |
| psii = (struct SII*)entry; |
| } |
| if (psii |
| && !(psii->flags & INDEX_ENTRY_END)) { |
| /* save first 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); |
| } |
| 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) |
| + sizeof(INDEX_ENTRY_HEADER))) { |
| 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); |
| } |
| |
| #if defined(__sun) && defined (__SVR4) |
| |
| /* |
| * Check whether current thread owner is member of file group |
| * Solaris/OpenIndiana version |
| * Should not be called for user root, however the group may be root |
| * |
| * The group list is available in "/proc/$PID/cred" |
| * |
| */ |
| |
| static BOOL groupmember(struct SECURITY_CONTEXT *scx, uid_t uid, gid_t gid) |
| { |
| typedef struct prcred { |
| uid_t pr_euid; /* effective user id */ |
| uid_t pr_ruid; /* real user id */ |
| uid_t pr_suid; /* saved user id (from exec) */ |
| gid_t pr_egid; /* effective group id */ |
| gid_t pr_rgid; /* real group id */ |
| gid_t pr_sgid; /* saved group id (from exec) */ |
| int pr_ngroups; /* number of supplementary groups */ |
| gid_t pr_groups[1]; /* array of supplementary groups */ |
| } prcred_t; |
| enum { readset = 16 }; |
| |
| prcred_t basecreds; |
| gid_t groups[readset]; |
| char filename[64]; |
| int fd; |
| int k; |
| int cnt; |
| gid_t *p; |
| BOOL ismember; |
| int got; |
| 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/cred",tid); |
| fd = open(filename,O_RDONLY); |
| if (fd >= 0) { |
| got = read(fd, &basecreds, sizeof(prcred_t)); |
| if (got == sizeof(prcred_t)) { |
| if (basecreds.pr_egid == gid) |
| ismember = TRUE; |
| p = basecreds.pr_groups; |
| cnt = 1; |
| k = 0; |
| while (!ismember |
| && (k < basecreds.pr_ngroups) |
| && (cnt > 0) |
| && (*p != gid)) { |
| k++; |
| cnt--; |
| p++; |
| if (cnt <= 0) { |
| got = read(fd, groups, |
| readset*sizeof(gid_t)); |
| cnt = got/sizeof(gid_t); |
| p = groups; |
| } |
| } |
| if ((cnt > 0) |
| && (k < basecreds.pr_ngroups)) |
| ismember = TRUE; |
| } |
| close(fd); |
| } |
| } |
| return (ismember); |
| } |
| |
| #else /* defined(__sun) && defined (__SVR4) */ |
| |
| /* |
| * Check whether current thread owner is member of file group |
| * Linux version |
| * 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); |
| } |
| |
| #endif /* defined(__sun) && defined (__SVR4) */ |
| |
| #if POSIXACLS |
| |
| /* |
| * Extract the basic permissions from a Posix ACL |
| * |
| * This is only to be used when Posix ACLs are compiled in, |
| * but not enabled in the mount options. |
| * |
| * it replaces the permission mask by the group permissions. |
| * If special groups are mapped, they are also considered as world. |
| */ |
| |
| static int ntfs_basic_perms(const struct SECURITY_CONTEXT *scx, |
| const struct POSIX_SECURITY *pxdesc) |
| { |
| int k; |
| int perms; |
| const struct POSIX_ACE *pace; |
| const struct MAPPING* group; |
| |
| k = 0; |
| perms = pxdesc->mode; |
| for (k=0; k < pxdesc->acccnt; k++) { |
| pace = &pxdesc->acl.ace[k]; |
| if (pace->tag == POSIX_ACL_GROUP_OBJ) |
| perms = (perms & 07707) |
| | ((pace->perms & 7) << 3); |
| else |
| if (pace->tag == POSIX_ACL_GROUP) { |
| group = scx->mapping[MAPGROUPS]; |
| while (group && (group->xid != pace->id)) |
| group = group->next; |
| if (group && group->grcnt |
| && (*(group->groups) == (gid_t)pace->id)) |
| perms |= pace->perms & 7; |
| } |
| } |
| return (perms); |
| } |
| |
| #endif /* POSIXACLS */ |
| |
| /* |
| * 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 |
| * |
| * When Posix ACLs are compiled in but not enabled in the mount |
| * options POSIX_ACL_USER, POSIX_ACL_GROUP and POSIX_ACL_MASK |
| * are ignored. |
| */ |
| |
| 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; |
| BOOL noacl; |
| mode_t perms; |
| int i; |
| |
| noacl = !(scx->vol->secure_flags & (1 << SECURITY_ACL)); |
| if (noacl) |
| perms = ntfs_basic_perms(scx, pxdesc); |
| else |
| 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 |= 01777; |
| 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 : |
| groupperms |= pxace->perms; |
| break; |
| case POSIX_ACL_GROUP : |
| if (!noacl) |
| groupperms |
| |= pxace->perms; |
| break; |
| case POSIX_ACL_MASK : |
| if (!noacl) |
| 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 (!noacl |
| && ((uid_t)pxace->id == scx->uid)) |
| userperms = pxace->perms; |
| break; |
| case POSIX_ACL_MASK : |
| if (!noacl) |
| mask = pxace->perms & 7; |
| break; |
| case POSIX_ACL_GROUP_OBJ : |
| if (((pxace->perms & mask) ^ perms) |
| & (request >> 6) & 7) |
| needgroups = TRUE; |
| break; |
| case POSIX_ACL_GROUP : |
| if (!noacl |
| && (((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 if (!noacl) { |
| /* 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, 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; |
| } 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 |= 01777; |
| 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) { |
| #if POSIXACLS |
| if (!(scx->vol->secure_flags & (1 << SECURITY_ACL)) |
| && cached->pxdesc) |
| perm = ntfs_basic_perms(scx,cached->pxdesc); |
| else |
| #endif |
| 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) { |
| if (!(scx->vol->secure_flags |
| & (1 << SECURITY_ACL))) |
| perm = ntfs_basic_perms(scx, |
| pxdesc); |
| else |
| 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) { |
| if (scx->vol->secure_flags & (1 << SECURITY_ACL)) |
| pydesc = ntfs_build_inherited_posix(pxdesc, |
| mode, scx->umask, isdir); |
| else |
| pydesc = ntfs_build_basic_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); |
| } |
| if (scx->vol->secure_flags |
| & (1 << SECURITY_ACL)) |
| pydesc = ntfs_build_inherited_posix( |
| pxdesc, mode, |
| scx->umask, isdir); |
| else |
| pydesc = ntfs_build_basic_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); |
| /* adjust Windows read-only flag */ |
| if (!isdir) { |
| if (mode & S_IWUSR) |
| ni->flags &= ~FILE_ATTR_READONLY; |
| else |
| ni->flags |= FILE_ATTR_READONLY; |
| NInoFileNameSetDirty(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); |
| } |
| |
| /* |
| * Check whether user can create a file (or directory) |
| * |
| * Returns TRUE if access is allowed, |
| * Also returns the gid and dsetgid applicable to the created file |
| */ |
| |
| int ntfs_allowed_create(struct SECURITY_CONTEXT *scx, |
| ntfs_inode *dir_ni, gid_t *pgid, mode_t *pdsetgid) |
| { |
| int perm; |
| int res; |
| int allow; |
| struct stat stbuf; |
| |
| /* |
| * Always allow for root. |
| * Also always allow if no mapping has been defined |
| */ |
| if (!scx->mapping[MAPUSERS]) |
| perm = 0777; |
| else |
| perm = ntfs_get_perm(scx, dir_ni, S_IWRITE + S_IEXEC); |
| if (!scx->mapping[MAPUSERS] |
| || !scx->uid) { |
| allow = 1; |
| } else { |
| perm = ntfs_get_perm(scx, dir_ni, S_IWRITE + S_IEXEC); |
| if (perm >= 0) { |
| res = EACCES; |
| allow = ((perm & (S_IWUSR | S_IWGRP | S_IWOTH)) != 0) |
| && ((perm & (S_IXUSR | S_IXGRP | S_IXOTH)) != 0); |
| if (!allow) |
| errno = res; |
| } else |
| allow = 0; |
| } |
| *pgid = scx->gid; |
| *pdsetgid = 0; |
| /* return directory group if S_ISGID is set */ |
| if (allow && (perm & S_ISGID)) { |
| if (ntfs_get_owner_mode(scx, dir_ni, &stbuf) >= 0) { |
| *pdsetgid = stbuf.st_mode & S_ISGID; |
| if (perm & S_ISGID) |
| *pgid = stbuf.st_gid; |
| } |
| } |
| 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; |
| #if !defined(__sun) || !defined (__SVR4) |
| /* clear setuid and setgid if owner has changed */ |
| /* unless request originated by root */ |
| if (uid && (fileuid != uid)) |
| mode &= 01777; |
| #endif |
| #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 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 OWNERFROMACL |
| /* Get approximation of parent owner when cannot map */ |
| if (!gsid) |
| gsid = adminsid; |
| if (!usid) { |
| usid = ntfs_acl_owner(parentattr); |
| if (!ntfs_is_user_sid(gsid)) |
| gsid = usid; |
| } |
| #else |
| /* Define owner as root when cannot map */ |
| if (!usid) |
| usid = adminsid; |
| if (!gsid) |
| gsid = adminsid; |
| #endif |
| } else { |
| /* |
| * If there is no user mapping and this is not a root |
| * user, we have to get owner and group from somewhere, |
| * and the parent directory has to contribute. |
| * Windows never has to do that, because it can always |
| * rely on a user mapping |
| */ |
| if (!scx->uid) |
| usid = adminsid; |
| else { |
| #if OWNERFROMACL |
| usid = ntfs_acl_owner(parentattr); |
| #else |
| int offowner; |
| |
| offowner = le32_to_cpu(pphead->owner); |
| usid = (const SID*)&parentattr[offowner]; |
| #endif |
| } |
| if (!scx->gid) |
| gsid = adminsid; |
| else { |
| 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 = (pphead->control |
| & (SE_DACL_AUTO_INHERITED | SE_SACL_AUTO_INHERITED)) |
| | 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, pphead->control |
| & SE_DACL_AUTO_INHERITED); |
| 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, pphead->control |
| & SE_SACL_AUTO_INHERITED); |
| 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 += gsidsz; |
| 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, possible when |
| * the current process owns the parent directory |
| */ |
| if (test_nino_flag(dir_ni, v3_Extensions) |
| && dir_ni->security_id) { |
| cached = fetch_cache(scx, dir_ni); |
| if (cached |
| && (cached->uid == scx->uid) && (cached->gid == scx->gid)) |
| 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 the current process owns the parent directory |
| */ |
| if (securid) { |
| cached = fetch_cache(scx, dir_ni); |
| if (cached |
| && (cached->uid == scx->uid) |
| && (cached->gid == scx->gid)) { |
| 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); |
| } |
| |
| /* |
| * 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); |
| } |
| |
| |
| /* |
| * 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); |
| } |
| |
| |
| 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); |
| } |
| |