diff --git a/include/ntfs-3g/dir.h b/include/ntfs-3g/dir.h index 33e1b2e1..a8e0ba36 100644 --- a/include/ntfs-3g/dir.h +++ b/include/ntfs-3g/dir.h @@ -68,11 +68,11 @@ extern u64 ntfs_inode_lookup_by_name(ntfs_inode *dir_ni, extern ntfs_inode *ntfs_pathname_to_inode(ntfs_volume *vol, ntfs_inode *parent, const char *pathname); -extern ntfs_inode *ntfs_create(ntfs_inode *dir_ni, ntfschar *name, u8 name_len, - dev_t type); -extern ntfs_inode *ntfs_create_device(ntfs_inode *dir_ni, +extern ntfs_inode *ntfs_create(ntfs_inode *dir_ni, le32 securid, + ntfschar *name, u8 name_len, dev_t type); +extern ntfs_inode *ntfs_create_device(ntfs_inode *dir_ni, le32 securid, ntfschar *name, u8 name_len, dev_t type, dev_t dev); -extern ntfs_inode *ntfs_create_symlink(ntfs_inode *dir_ni, +extern ntfs_inode *ntfs_create_symlink(ntfs_inode *dir_ni, le32 securid, ntfschar *name, u8 name_len, ntfschar *target, u8 target_len); extern int ntfs_check_empty_dir(ntfs_inode *ni); extern int ntfs_delete(ntfs_inode *ni, ntfs_inode *dir_ni, ntfschar *name, diff --git a/include/ntfs-3g/index.h b/include/ntfs-3g/index.h index 9d7fd88b..b181256f 100644 --- a/include/ntfs-3g/index.h +++ b/include/ntfs-3g/index.h @@ -127,5 +127,7 @@ extern char *ntfs_ie_filename_get(INDEX_ENTRY *ie); extern void ntfs_ie_filename_dump(INDEX_ENTRY *ie); extern void ntfs_ih_filename_dump(INDEX_HEADER *ih); +extern int ntfs_ie_add(ntfs_index_context *icx, INDEX_ENTRY *ie); + #endif /* _NTFS_INDEX_H */ diff --git a/include/ntfs-3g/inode.h b/include/ntfs-3g/inode.h index 94c79f8e..b9467463 100644 --- a/include/ntfs-3g/inode.h +++ b/include/ntfs-3g/inode.h @@ -50,6 +50,7 @@ typedef enum { in the index. */ NI_NoMtimeUpdate, /* 1: Don't update modifiction time. */ NI_NoParentMtimeUpdate, /* 1: Don't update parent dir's mtime. */ + NI_v3_Extensions, /* 1: JPA v3.x extensions present. */ } ntfs_inode_state_bits; #define test_nino_flag(ni, flag) test_bit(NI_##flag, (ni)->state) @@ -159,6 +160,12 @@ struct _ntfs_inode { time_t last_data_change_time; time_t last_mft_change_time; time_t last_access_time; + /* NTFS 3.x extensions added by JPA */ + /* only if NI_v3_Extensions is set in state */ + le32 owner_id; + le32 security_id; + le32 quota_charged; + le32 usn; }; extern ntfs_inode *ntfs_inode_base(ntfs_inode *ni); diff --git a/include/ntfs-3g/security.h b/include/ntfs-3g/security.h index 04cc30a9..4a90ee14 100644 --- a/include/ntfs-3g/security.h +++ b/include/ntfs-3g/security.h @@ -28,6 +28,50 @@ #include "layout.h" #include "inode.h" +/* + * item in the mapping list + */ + +struct MAPPING { + struct MAPPING *next; + int xid; /* linux id : uid or gid */ + SID *sid; /* Windows id : usid or gsid */ +}; + +/* + * Security context, needed by most security functions + */ + +struct SECURITY_ENTRY { + uid_t uid; + gid_t gid; + unsigned int mode:9; + unsigned int valid:1; +} ; + +struct SECURITY_HEAD { + int first; + int last; + /* statistics */ + unsigned long attempts; + unsigned long reads; + unsigned long writes; +} ; + +struct SECURITY_CACHE { + struct SECURITY_HEAD head; + struct SECURITY_ENTRY cachetable[1]; +} ; + +struct SECURITY_CONTEXT { + ntfs_volume *vol; + struct MAPPING *usermapping; + struct MAPPING *groupmapping; + struct SECURITY_CACHE **pseccache; + uid_t uid; /* uid of user requesting (not the mounter) */ + gid_t gid; /* gid of user requesting (not the mounter) */ + } ; + extern const GUID *const zero_guid; extern BOOL ntfs_guid_is_zero(const GUID *guid); @@ -58,4 +102,21 @@ extern int ntfs_sd_add_everyone(ntfs_inode *ni); extern le32 ntfs_security_hash(const SECURITY_DESCRIPTOR_RELATIVE *sd, const u32 len); +int ntfs_build_mapping(struct SECURITY_CONTEXT *scx); +int ntfs_get_owner_mode(struct SECURITY_CONTEXT *scx, + const char *path, ntfs_inode *ni, struct stat*); +int ntfs_set_mode(struct SECURITY_CONTEXT *scx, + const char *path, ntfs_inode *ni, mode_t mode); +BOOL ntfs_allowed_access(struct SECURITY_CONTEXT *scx, const char *path, + ntfs_inode *ni, int accesstype); +BOOL ntfs_allowed_dir_access(struct SECURITY_CONTEXT *scx, + const char *path, int accesstype); + +int ntfs_set_owner(struct SECURITY_CONTEXT *scx, + const char *path, ntfs_inode *ni, uid_t uid, gid_t gid); +int ntfs_set_owner_mode(struct SECURITY_CONTEXT *scx, + const char *path, ntfs_inode *ni, + uid_t uid, gid_t gid, mode_t mode); + + #endif /* defined _NTFS_SECURITY_H */ diff --git a/libntfs-3g/collate.c b/libntfs-3g/collate.c index 2352423f..2288274f 100644 --- a/libntfs-3g/collate.c +++ b/libntfs-3g/collate.c @@ -41,16 +41,21 @@ BOOL ntfs_is_collation_rule_supported(COLLATION_RULES cr) * FIXME: At the moment we only support COLLATION_BINARY, * COLLATION_NTOFS_ULONG and COLLATION_FILE_NAME so we return false * for everything else. + * JPA added COLLATION_NTOFS_SECURITY_HASH */ - if (cr != COLLATION_BINARY && cr != COLLATION_NTOFS_ULONG && - cr != COLLATION_FILE_NAME) + if (cr != COLLATION_BINARY && cr != COLLATION_NTOFS_ULONG + && cr != COLLATION_FILE_NAME + && cr != COLLATION_NTOFS_SECURITY_HASH) return FALSE; +/* JPA remove double checking i = le32_to_cpu(cr); if (((i >= 0) && (i <= 0x02)) || ((i >= 0x10) && (i <= 0x13))) return TRUE; return FALSE; +*/ + return TRUE; } /** @@ -121,6 +126,58 @@ static int ntfs_collate_ntofs_ulong(ntfs_volume *vol __attribute__((unused)), return rc; } +/** + * ntfs_collate_ntofs_security_hash - Which of two security descriptors + * should be listed first + * @vol: unused + * @data1: + * @data1_len: + * @data2: + * @data2_len: + * + * JPA compare two security hash keys made of two unsigned le32 + * + * Returns: -1, 0 or 1 depending of how the keys compare + */ +static int ntfs_collate_ntofs_security_hash(ntfs_volume *vol __attribute__((unused)), + const void *data1, const int data1_len, + const void *data2, const int data2_len) +{ + int rc; + u32 d1, d2; + u32 *p1, *p2; + + ntfs_log_trace("Entering.\n"); + if (data1_len != data2_len || data1_len != 8) { + ntfs_log_error("data1_len or/and data2_len not equal to 8.\n"); + return NTFS_COLLATION_ERROR; + } + p1 = (u32*)data1; + p2 = (u32*)data2; + d1 = le32_to_cpup(p1); + d2 = le32_to_cpup(p2); + if (d1 < d2) + rc = -1; + else { + if (d1 > d2) + rc = 1; + else { + d1 = le32_to_cpup(++p1); + d2 = le32_to_cpup(++p2); + if (d1 < d2) + rc = -1; + else { + if (d1 > d2) + rc = 1; + else + rc = 0; + } + } + } + ntfs_log_trace("Done, returning %i.\n", rc); + return rc; +} + /** * ntfs_collate_file_name - Which of two filenames should be listed first * @vol: @@ -162,7 +219,7 @@ static ntfs_collate_func_t ntfs_do_collate0x0[3] = { static ntfs_collate_func_t ntfs_do_collate0x1[4] = { ntfs_collate_ntofs_ulong, NULL/*ntfs_collate_ntofs_sid*/, - NULL/*ntfs_collate_ntofs_security_hash*/, + ntfs_collate_ntofs_security_hash, NULL/*ntfs_collate_ntofs_ulongs*/, }; @@ -199,9 +256,11 @@ int ntfs_collate(ntfs_volume *vol, COLLATION_RULES cr, * FIXME: At the moment we only support COLLATION_BINARY, * COLLATION_NTOFS_ULONG and COLLATION_FILE_NAME so we return error * for everything else. + * JPA added COLLATION_NTOFS_SECURITY_HASH */ - if (cr != COLLATION_BINARY && cr != COLLATION_NTOFS_ULONG && - cr != COLLATION_FILE_NAME) + if (cr != COLLATION_BINARY && cr != COLLATION_NTOFS_ULONG + && cr != COLLATION_FILE_NAME + && cr != COLLATION_NTOFS_SECURITY_HASH) goto err; i = le32_to_cpu(cr); if (i < 0) diff --git a/libntfs-3g/dir.c b/libntfs-3g/dir.c index 52fcb66b..6a463122 100644 --- a/libntfs-3g/dir.c +++ b/libntfs-3g/dir.c @@ -1001,6 +1001,7 @@ err_out: /** * __ntfs_create - create object on ntfs volume * @dir_ni: ntfs inode for directory in which create new object + * @securid: id of inheritable security descriptor, 0 if none * @name: unicode name of new object * @name_len: length of the name in unicode characters * @type: type of the object to create @@ -1029,7 +1030,7 @@ err_out: * Return opened ntfs inode that describes created object on success or NULL * on error with errno set to the error code. */ -static ntfs_inode *__ntfs_create(ntfs_inode *dir_ni, +static ntfs_inode *__ntfs_create(ntfs_inode *dir_ni, le32 securid, ntfschar *name, u8 name_len, dev_t type, dev_t dev, ntfschar *target, u8 target_len) { @@ -1057,10 +1058,14 @@ static ntfs_inode *__ntfs_create(ntfs_inode *dir_ni, if (!ni) return NULL; /* - * Create STANDARD_INFORMATION attribute. Write STANDARD_INFORMATION - * version 1.2, windows will upgrade it to version 3 if needed. + * Create STANDARD_INFORMATION attribute. + * JPA Depending on available inherited security descriptor, + * Write STANDARD_INFORMATION v1.2 (no inheritance) or v3 */ - si_len = offsetof(STANDARD_INFORMATION, v1_end); + if (securid) + si_len = sizeof(STANDARD_INFORMATION); + else + si_len = offsetof(STANDARD_INFORMATION, v1_end); si = ntfs_calloc(si_len); if (!si) { err = errno; @@ -1070,6 +1075,14 @@ static ntfs_inode *__ntfs_create(ntfs_inode *dir_ni, si->last_data_change_time = utc2ntfs(ni->last_data_change_time); si->last_mft_change_time = utc2ntfs(ni->last_mft_change_time); si->last_access_time = utc2ntfs(ni->last_access_time); + if (securid) { + set_nino_flag(ni, v3_Extensions); + si->owner_id = 0; + si->security_id = securid; + si->quota_charged = 0; + si->usn = 0; + } else + clear_nino_flag(ni, v3_Extensions); if (!S_ISREG(type) && !S_ISDIR(type)) { si->file_attributes = FILE_ATTR_SYSTEM; ni->flags = FILE_ATTR_SYSTEM; @@ -1082,10 +1095,12 @@ static ntfs_inode *__ntfs_create(ntfs_inode *dir_ni, "attribute.\n"); goto err_out; } - - if (ntfs_sd_add_everyone(ni)) { - err = errno; - goto err_out; + + if (securid) { + if (ntfs_sd_add_everyone(ni)) { + err = errno; + goto err_out; + } } rollback_sd = 1; @@ -1256,35 +1271,35 @@ err_out: * Some wrappers around __ntfs_create() ... */ -ntfs_inode *ntfs_create(ntfs_inode *dir_ni, ntfschar *name, u8 name_len, - dev_t type) +ntfs_inode *ntfs_create(ntfs_inode *dir_ni, le32 securid, ntfschar *name, + u8 name_len, dev_t type) { if (type != S_IFREG && type != S_IFDIR && type != S_IFIFO && type != S_IFSOCK) { ntfs_log_error("Invalid arguments.\n"); return NULL; } - return __ntfs_create(dir_ni, name, name_len, type, 0, NULL, 0); + return __ntfs_create(dir_ni, securid, name, name_len, type, 0, NULL, 0); } -ntfs_inode *ntfs_create_device(ntfs_inode *dir_ni, ntfschar *name, u8 name_len, - dev_t type, dev_t dev) +ntfs_inode *ntfs_create_device(ntfs_inode *dir_ni, le32 securid, + ntfschar *name, u8 name_len, dev_t type, dev_t dev) { if (type != S_IFCHR && type != S_IFBLK) { ntfs_log_error("Invalid arguments.\n"); return NULL; } - return __ntfs_create(dir_ni, name, name_len, type, dev, NULL, 0); + return __ntfs_create(dir_ni, securid, name, name_len, type, dev, NULL, 0); } -ntfs_inode *ntfs_create_symlink(ntfs_inode *dir_ni, ntfschar *name, u8 name_len, - ntfschar *target, u8 target_len) +ntfs_inode *ntfs_create_symlink(ntfs_inode *dir_ni, le32 securid, + ntfschar *name, u8 name_len, ntfschar *target, u8 target_len) { if (!target || !target_len) { ntfs_log_error("Invalid arguments.\n"); return NULL; } - return __ntfs_create(dir_ni, name, name_len, S_IFLNK, 0, + return __ntfs_create(dir_ni, securid, name, name_len, S_IFLNK, 0, target, target_len); } diff --git a/libntfs-3g/index.c b/libntfs-3g/index.c index 227ce53a..6c8d9e3a 100644 --- a/libntfs-3g/index.c +++ b/libntfs-3g/index.c @@ -1429,16 +1429,20 @@ static int ntfs_ib_split(ntfs_index_context *icx, INDEX_BLOCK *ib) } -static int ntfs_ie_add(ntfs_index_context *icx, INDEX_ENTRY *ie) +/* JPA static */ +int ntfs_ie_add(ntfs_index_context *icx, INDEX_ENTRY *ie) { - char *fn; INDEX_HEADER *ih; int allocated_size, new_size; int ret = STATUS_ERROR; + +/* removed by JPA to make function usable for security indexes + char *fn; fn = ntfs_ie_filename_get(ie); ntfs_log_trace("file: '%s'\n", fn); ntfs_attr_name_free(&fn); +*/ while (1) { diff --git a/libntfs-3g/inode.c b/libntfs-3g/inode.c index d00789d9..14a2075f 100644 --- a/libntfs-3g/inode.c +++ b/libntfs-3g/inode.c @@ -190,6 +190,19 @@ ntfs_inode *ntfs_inode_open(ntfs_volume *vol, const MFT_REF mref) ni->last_data_change_time = ntfs2utc(std_info->last_data_change_time); ni->last_mft_change_time = ntfs2utc(std_info->last_mft_change_time); ni->last_access_time = ntfs2utc(std_info->last_access_time); + /* JPA insert v3 extensions if present */ + /* length may be seen as 72 (v1.x) or 96 (v3.x) */ + if (ctx->attr->length > sizeof(STANDARD_INFORMATION)) { + set_nino_flag(ni, v3_Extensions); + ni->owner_id = std_info->owner_id; + ni->security_id = std_info->security_id; + ni->quota_charged = std_info->quota_charged; + ni->usn = std_info->usn; + } else { + clear_nino_flag(ni, v3_Extensions); + ni->owner_id = 0; + ni->security_id = 0; + } /* Set attribute list information. */ if (ntfs_attr_lookup(AT_ATTRIBUTE_LIST, AT_UNNAMED, 0, 0, 0, NULL, 0, ctx)) { @@ -531,6 +544,13 @@ static int ntfs_inode_sync_standard_information(ntfs_inode *ni) std_info->last_data_change_time = utc2ntfs(ni->last_data_change_time); std_info->last_mft_change_time = utc2ntfs(ni->last_mft_change_time); std_info->last_access_time = utc2ntfs(ni->last_access_time); + /* JPA update v3.x extensions */ + if (test_nino_flag(ni, v3_Extensions)) { + std_info->owner_id = ni->owner_id; + std_info->security_id = ni->security_id; + std_info->quota_charged = ni->quota_charged; + std_info->usn = ni->usn; + } ntfs_inode_mark_dirty(ctx->ntfs_ino); ntfs_attr_put_search_ctx(ctx); return 0; diff --git a/libntfs-3g/security.c b/libntfs-3g/security.c index 2729ae08..a4de9c96 100644 --- a/libntfs-3g/security.c +++ b/libntfs-3g/security.c @@ -4,6 +4,7 @@ * Copyright (c) 2004 Anton Altaparmakov * Copyright (c) 2005-2006 Szabolcs Szakacsits * Copyright (c) 2006 Yura Pakhuchiy + * Copyright (c) 2007 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 @@ -37,14 +38,80 @@ #ifdef HAVE_ERRNO_H #include #endif +#ifdef HAVE_FCNTL_H +#include +#endif #include "types.h" #include "layout.h" #include "attrib.h" +#include "index.h" +#include "dir.h" #include "security.h" #include "misc.h" #include "bitmap.h" +/* + * JPA configuration modes for this module + * should move to some config file + */ + +#define FORCE_FORMAT_v1x 0 /* Insert security data as in NTFS v1.x */ +#define BUFSZ 1024 /* buffer size to read mapping file */ +#define MAPPINGFILE "/$Mapping" /* name of mapping file */ +#define LINESZ 120 /* maximum useful size of a mapping line */ + +/* + * JPA NTFS constants or structs + * should move to layout.h + */ + +#define ALIGN_SDS_BLOCK 0x40000 /* Alignment for a $SDS block */ +#define ALIGN_SDS_ENTRY 16 /* Alignment for a $SDS entry */ +#define FIRST_SECURITY_ID 0x100 /* Lowest security id */ + +struct SII { /* this is an image of index (le) */ + le16 offs; + le16 size; + le32 fill1; + le16 indexsz; + le16 indexksz; + le16 flags; + le16 fill2; + le32 keysecurid; + + /* did not found 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 index (le) */ + le16 offs; + le16 size; + le32 fill1; + le16 indexsz; + le16 indexksz; + le16 flags; + le16 fill2; + le32 keyhash; + le32 keysecurid; + + /* did not found official description for the following */ + le32 hash; + le32 securid; + le32 dataoffsl; + le32 dataoffsh; + le32 datasize; + le32 fill3; + } ; + +static ntfschar sii_name[] = { '$', 'S', 'I', 'I', 0 }; +static ntfschar sdh_name[] = { '$', 'S', 'D', 'H', 0 }; +static ntfschar sds_name[] = { '$', 'S', 'D', 'S', 0 }; + /* * The zero GUID. */ @@ -275,6 +342,2107 @@ void ntfs_generate_guid(GUID *guid) } } +/** + * 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 = (le32*)sd; + const le32 *end = pos + (len >> 2); + u32 hash = 0; + + while (pos < end) + hash = le32_to_cpup(pos++) + ntfs_rol32(hash, 3); + return cpu_to_le32(hash); +} + + +/* + * Matching of ntfs permissions to Linux permissions + * these constants are adapted to endianness + * when setting, set them all + * when checking, check one is present + * (checks needed) + */ + /* flags which are set to mean exec, write or read */ +#define FILE_READ (FILE_READ_DATA | FILE_READ_EA) +#define FILE_WRITE (FILE_WRITE_DATA | FILE_APPEND_DATA | FILE_WRITE_EA) +#define FILE_EXEC (FILE_EXECUTE) +#define DIR_READ (FILE_LIST_DIRECTORY | FILE_READ_EA) +#define DIR_WRITE (FILE_ADD_FILE | FILE_ADD_SUBDIRECTORY \ + | FILE_WRITE_EA | FILE_DELETE_CHILD) +#define DIR_EXEC (FILE_TRAVERSE) + + /* flags interpreted as meaning exec, write or read */ +#define FILE_GREAD (FILE_READ | GENERIC_READ) +#define FILE_GWRITE (FILE_WRITE | GENERIC_WRITE) +#define FILE_GEXEC (FILE_EXEC | GENERIC_EXECUTE) +#define DIR_GREAD (DIR_READ | GENERIC_READ) +#define DIR_GWRITE (DIR_WRITE | GENERIC_WRITE) +#define DIR_GEXEC (DIR_EXEC | GENERIC_EXECUTE) + + /* standard owner (and administrator) rights */ +#define OWNER_RIGHTS (DELETE | READ_CONTROL | WRITE_DAC | WRITE_OWNER | SYNCHRONIZE \ + | FILE_READ_ATTRIBUTES | FILE_WRITE_ATTRIBUTES) + /* standard world rights */ +#define WORLD_RIGHTS (READ_CONTROL | FILE_READ_ATTRIBUTES); + /* inheritance flags for files and directories */ +#define FILE_INHERITANCE 0 +#define DIR_INHERITANCE (OBJECT_INHERIT_ACE | CONTAINER_INHERIT_ACE) + +struct MAPLIST { + struct MAPLIST *next; + char *uidstr; /* uid text from the same record */ + char *gidstr; /* gid text from the same record */ + char *sidstr; /* sid text from the same record */ + char maptext[LINESZ + 1]; +}; + +/* + * The following must be in some library... + */ + +unsigned int cpu_to_be32(unsigned int x) +{ + return (((x >> 24) & 255) + + ((x >> 8) & 0xff00) + + ((x & 0xff00) << 8) + + ((x & 255) << 24)); +} + +/* + * The following must be in some library... + */ + +unsigned long atoul(const char *p) +{ /* must be somewhere ! */ + unsigned long v; + + v = 0; + while ((*p >= '0') && (*p <= '9')) + v = v * 10 + (*p++) - '0'; + return (v); +} + +static int sid_size(const SID * sid) +{ + return (sid->sub_authority_count * 4 + 8); +} + +/* + * Determine the size of a security attribute + * whatever the order of fields + * we however assume USID, GSID and DACL are present + */ + +static int attr_size(const char *attr) +{ + const SECURITY_DESCRIPTOR_RELATIVE *pnhead; + const ACL *pdacl; + const SID *psid; + int offdacl; + int offsid; + int endsid; + int attrsz; + + pnhead = (const SECURITY_DESCRIPTOR_RELATIVE*)attr; + /* find end of DACL */ + offdacl = le32_to_cpu(pnhead->dacl); + pdacl = (const ACL*)&attr[offdacl]; + attrsz = offdacl + le16_to_cpu(pdacl->size); + /* find end of USID */ + offsid = le32_to_cpu(pnhead->owner); + psid = (const SID*)&attr[offsid]; + endsid = offsid + sid_size(psid); + if (endsid > attrsz) attrsz = endsid; + /* find end of GSID */ + offsid = le32_to_cpu(pnhead->group); + psid = (const SID*)&attr[offsid]; + endsid = offsid + sid_size(psid); + if (endsid > attrsz) attrsz = endsid; + + return (attrsz); +} + +/* + * Build an internal representation of a SID + * Returns a copy in allocated memory if it succeeds + * Currently it does only safety checks + */ + +static SID *encodesid(const char *sidstr) +{ + SID *sid; + int cnt; + union { + SID sid; + char bytes[8 * 4 + 8]; /* maximum size for 8 authorities */ + } bigsid; + SID *bsid; + long auth; + const char *p; + + sid = (SID*) NULL; + if (!strncmp(sidstr, "S-1-", 4)) { + bsid = &bigsid.sid; + bsid->revision = 1; + p = &sidstr[4]; + auth = atoul(p); + bsid->identifier_authority.high_part = 0; + bsid->identifier_authority.low_part = cpu_to_be32(auth); + cnt = 0; + p = strchr(p, '-'); + while (p && (cnt < 8)) { + p++; + bsid->sub_authority[cnt] = cpu_to_le32(atoul(p)); + p = strchr(p, '-'); + cnt++; + } + bsid->sub_authority_count = cnt; + if (cnt > 0) { + sid = (SID*) malloc(4 * cnt + 8); + if (sid) + memcpy(sid, bsid, 4 * cnt + 8); + } + } + return (sid); +} + + + +/* + * Internal read + * copied and pasted from ntfs_fuse_read() and made independent + * of fuse context + */ + +static int ntfs_local_read(ntfs_volume *vol, ntfs_inode *ni, + const char *path, ntfschar *stream_name, int stream_name_len, + char *buf, size_t size, off_t offset) +{ + ntfs_attr *na = NULL; + int res, total = 0; + + na = ntfs_attr_open(ni, AT_DATA, stream_name, stream_name_len); + if (!na) { + res = -errno; + goto exit; + } + if (offset + size > na->data_size) + size = na->data_size - offset; + while (size) { + res = ntfs_attr_pread(na, offset, size, buf); + if (res < (s64)size) + ntfs_log_perror("ntfs_attr_pread partial write (%lld: " + "%lld <> %d)", (long long)offset, + (long long)size, res); + if (res <= 0) { + res = -errno; + goto exit; + } + size -= res; + offset += res; + total += res; + } + res = total; +exit: + if (na) + ntfs_attr_close(na); + return res; +} + + +/* + * Internal write + * copied and pasted from ntfs_fuse_write() and made independent + * of fuse context + */ + +static int ntfs_local_write(ntfs_volume *vol, ntfs_inode *ni, + const char *path, ntfschar *stream_name, int stream_name_len, + char *buf, size_t size, off_t offset) +{ + ntfs_attr *na = NULL; + int res, total = 0; + + na = ntfs_attr_open(ni, AT_DATA, stream_name, stream_name_len); + if (!na) { + res = -errno; + goto exit; + } + while (size) { + res = ntfs_attr_pwrite(na, offset, size, buf); + if (res < (s64)size) + ntfs_log_perror("ntfs_attr_pwrite partial write (%lld: " + "%lld <> %d)", (long long)offset, + (long long)size, res); + if (res <= 0) { + res = -errno; + goto exit; + } + size -= res; + offset += res; + total += res; + } + res = total; +exit: +// ! ntfs_fuse_mark_free_space_outdated(); + if (na) + ntfs_attr_close(na); + return res; +} + +/* + * Build a sid for world user + * (a constant in static data, do not free) + */ + +static const SID *worldsid(void) +{ + static char wsid[] = { + 1, /* revision */ + 1, /* auth count */ + 0, 0, 0, 0, 0, 1, /* base */ + 0, 0, 0, 0 /* 1st level */ + }; + + return ((SID*) wsid); +} + +/* + * Build a sid for administrator + * (a constant in static data, do not free) + */ + +static const SID *adminsid(void) +{ + static char asid[] = { + 1, /* revision */ + 2, /* auth count */ + 0, 0, 0, 0, 0, 5, /* base */ + 32, 0, 0, 0, /* 1st level */ + 32, 2, 0, 0 /* 2nd level */ + }; + + return ((const SID*)asid); +} + +/* + * Build a sid for system + * (a constant in static data, do not free) + */ + +static const SID *systemsid(void) +{ + static char ssid[] = { + 1, /* revision */ + 1, /* auth count */ + 0, 0, 0, 0, 0, 5, /* base */ + 18, 0, 0, 0 /* 1st level */ + }; + + return ((const SID*)ssid); +} + +/* + * Find Linux owner mapped to a usid + * Returns 0 (root) if not found + */ + +static int findowner(struct SECURITY_CONTEXT *scx, const SID * usid) +{ + struct MAPPING *p; + int usidsz; + + usidsz = sid_size(usid); + p = scx->usermapping; + while (p && memcmp(usid, p->sid, usidsz)) + p = p->next; + return (p ? p->xid : 0); +} + +/* + * Find Linux group mapped to a gsid + * Returns 0 (root) if not found + */ + +static int findgroup(struct SECURITY_CONTEXT *scx, const SID * gsid) +{ + struct MAPPING *p; + int gsidsz; + + gsidsz = sid_size(gsid); + p = scx->groupmapping; + while (p && memcmp(gsid, p->sid, gsidsz)) + p = p->next; + return (p ? p->xid : 0); +} + +/* + * Find usid mapped to a Linux user + * Returns NULL if not found + */ + +static const SID *find_usid(struct SECURITY_CONTEXT *scx, uid_t uid) +{ + struct MAPPING *p; + const SID *sid; + + if (!uid) + sid = adminsid(); + else { + p = scx->usermapping; + while (p && (p->xid != uid)) + p = p->next; + sid = (p ? p->sid : (const SID*)NULL); + } + return (sid); +} + +/* + * Find Linux group mapped to a gsid + * Returns 0 (root) if not found + */ + +static const SID *find_gsid(struct SECURITY_CONTEXT *scx, gid_t gid) +{ + struct MAPPING *p; + const SID *sid; + + if (!gid) + sid = adminsid(); + else { + p = scx->groupmapping; + while (p && (p->xid != gid)) + p = p->next; + sid = (p ? p->sid : (const SID*)NULL); + } + return (sid); +} + +/* + * Resize security cache in either direction + * do not call unless resizing is needed + * + * returns pointer to required entry or NULL if not possible + */ + + +static struct SECURITY_ENTRY *resize_cache(struct SECURITY_CONTEXT *scx, + le32 securid) +{ + struct SECURITY_ENTRY *cacheentry; + struct SECURITY_CACHE *oldcache; + struct SECURITY_CACHE *newcache; + int oldcnt; + int newcnt; + int i; + + cacheentry = (struct SECURITY_ENTRY*)NULL; + oldcache = *scx->pseccache; + if (oldcache->head.last < securid) { + /* expand cache beyond current end */ + newcnt = securid - oldcache->head.first + 1; + newcache = (struct SECURITY_CACHE*) + realloc(oldcache, + sizeof(struct SECURITY_HEAD) + + newcnt*sizeof(struct SECURITY_ENTRY)); + if (newcache) { + /* mark new entries as not valid */ + for (i=newcache->head.last+1; i<=securid; i++) + newcache->cachetable[ + i - newcache->head.first].valid = 0; + newcache->head.last = securid; + *scx->pseccache = newcache; + cacheentry = &newcache-> + cachetable[securid - newcache->head.first]; + } + } else { + /* expand cache before current beginning */ + newcnt = oldcache->head.last - securid + 1; + newcache = (struct SECURITY_CACHE*) + malloc(sizeof(struct SECURITY_HEAD) + + newcnt*sizeof(struct SECURITY_ENTRY)); + if (newcache) { + /* mark new entries as not valid */ + for (i=securid; ihead.first; i++) + newcache->cachetable[i - securid].valid = 0; + newcache->head.first = securid; + newcache->head.last = oldcache->head.last; + newcache->head.attempts = oldcache->head.attempts; + newcache->head.reads = oldcache->head.reads; + newcache->head.writes = oldcache->head.writes; + oldcnt = oldcache->head.last - oldcache->head.first + 1; + memcpy(&newcache->cachetable[oldcache->head.first + - newcache->head.first], + oldcache->cachetable, + oldcnt*sizeof(struct SECURITY_ENTRY)); + *scx->pseccache = newcache; + free(oldcache); + cacheentry = &newcache->cachetable[0]; + } + } + return (cacheentry); +} + + +/* + * 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) + */ + +static struct SECURITY_ENTRY *enter_cache(struct SECURITY_CONTEXT *scx, + ntfs_inode *ni, uid_t uid, gid_t gid, mode_t mode) +{ + struct SECURITY_ENTRY *cacheentry; + struct SECURITY_CACHE *pcache; + + /* 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 + */ + pcache = *scx->pseccache; + if (pcache + && (pcache->head.first <= ni->security_id) + && (pcache->head.last >= ni->security_id)) { + cacheentry = &pcache->cachetable[ni->security_id + - pcache->head.first]; + cacheentry->uid = uid; + cacheentry->gid = gid; + cacheentry->mode = mode; + cacheentry->valid = 1; + pcache->head.writes++; + } else { + if (!pcache) { + /* create the first cache entry */ + pcache = (struct SECURITY_CACHE*) + malloc(sizeof(struct SECURITY_HEAD) + + sizeof(struct SECURITY_ENTRY)); + pcache->head.first = ni->security_id; + pcache->head.last = ni->security_id; + pcache->head.attempts = 0; + pcache->head.reads = 0; + pcache->head.writes = 0; + *scx->pseccache = pcache; + cacheentry = &pcache->cachetable[0]; + } else { + cacheentry = resize_cache(scx, ni->security_id); + } + if (cacheentry) { + cacheentry->uid = uid; + cacheentry->gid = gid; + cacheentry->mode = mode; + cacheentry->valid = 1; + pcache->head.writes++; + } + } + } else + cacheentry = (struct SECURITY_ENTRY*)NULL; + return (cacheentry); +} + +/* + * Fetch a cache entry, if available + * + * 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 SECURITY_ENTRY *fetch_cache(struct SECURITY_CONTEXT *scx, + ntfs_inode *ni) +{ + struct SECURITY_ENTRY *cacheentry; + struct SECURITY_CACHE *pcache; + + /* cacheing is only possible if a security_id has been defined */ + cacheentry = (struct SECURITY_ENTRY*)NULL; + if (test_nino_flag(ni, v3_Extensions) + && (ni->security_id)) { + pcache = *scx->pseccache; + if (pcache + && (pcache->head.first <= ni->security_id) + && (pcache->head.last >= ni->security_id)) { + cacheentry = &pcache->cachetable[ni->security_id + - pcache->head.first]; + /* reject if entry is not valid */ + if (!cacheentry->valid) + cacheentry = (struct SECURITY_ENTRY*)NULL; + else + pcache->head.reads++; + if (pcache) + pcache->head.attempts++; + } + } + return (cacheentry); +} + +static char *indexsearch(struct SECURITY_CONTEXT *scx, SII_INDEX_KEY id) +{ + struct SII *psii; /* this is an image of index (le) */ + union { + struct { + u32 dataoffsl; + u32 dataoffsh; + } parts; + u64 all; + } realign; + int found; + size_t size; + size_t rdsize; + s64 offs; + ntfs_inode *ni; + ntfs_index_context *xc; + char *securattr; + + securattr = (char*)NULL; + ni = ntfs_pathname_to_inode(scx->vol, NULL, "$Secure"); + if (ni) { + xc = ntfs_index_ctx_get(ni, sii_name, 4); + if (xc) { + found = + !ntfs_index_lookup((char*)&id, + sizeof(SII_INDEX_KEY), xc); + if (found) { + psii = (struct SII*)xc->entry; + size = + (size_t) le32_to_cpu(psii->datasize) - 20; + /* work around bad alignment problem */ + realign.parts.dataoffsh = psii->dataoffsh; + realign.parts.dataoffsl = psii->dataoffsl; + offs = le64_to_cpu(realign.all) + 20; + + securattr = (char*)malloc(size); + if (securattr) { + rdsize = ntfs_local_read( + scx->vol,ni, "/$Secure", + sds_name,4, + securattr, size, offs); + if (rdsize != size) { + free(securattr); + securattr = (char*)NULL; + } + } + } + ntfs_index_ctx_put(xc); + } + ntfs_inode_close(ni); + } + if (!securattr) + 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) + * + * The returned descriptor is dynamically allocated and has to be freed + */ + +static char *build_secur_descr(const char *path, mode_t mode, + int isdir, const SID * usid, const SID * gsid); + +static char *getsecurityattr(struct SECURITY_CONTEXT *scx, + const char *path, ntfs_inode *ni) +{ + SII_INDEX_KEY securid; + const SID *asid; + char *securattr; + ntfschar nullchar = 0; + 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) && ni->security_id) { + /* get v3.x descriptor in $Secure */ + securid.security_id = ni->security_id; + securattr = indexsearch(scx,securid); + } else { + /* get v1.x security attribute */ + readallsz = 0; + securattr = ntfs_attr_readall(ni, AT_SECURITY_DESCRIPTOR, + &nullchar, 0,&readallsz); + } + 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 %s\n",path); + asid = adminsid(); + securattr = build_secur_descr(path, 0, 0, asid, asid); + } + return (securattr); +} + +/* + * test whether a SID means "world user" + */ + +static int is_world_sid(const SID * usid) +{ + int any; + + /* check whether S-1-1-0 */ + any = (usid->sub_authority_count == 1) + && (usid->identifier_authority.high_part == cpu_to_be32(0)) + && (usid->identifier_authority.low_part == cpu_to_be32(1)) + && (usid->sub_authority[0] == cpu_to_le32(0)); + return (any); +} + +/* + * test whether a SID means "some user" + */ + +static int is_user_sid(const SID * usid) +{ + int user; + + /* check whether S-1-5-21... */ + user = (usid->sub_authority_count == 5) + && (usid->identifier_authority.high_part == cpu_to_be32(0)) + && (usid->identifier_authority.low_part == cpu_to_be32(5)) + && (usid->sub_authority[0] == cpu_to_le32(21)); + return (user); +} + +static int merge_permissions(ntfs_inode *ni, + le32 owner, le32 group, le32 world) + +{ + int perm; + + perm = 0; + /* build owner permission */ + if (owner) { + if (ni->mrec->flags & MFT_RECORD_IS_DIRECTORY) { + /* exec if any of list, traverse */ + if (owner & DIR_GEXEC) + perm |= S_IXUSR; + /* write if any of addfile, adddir, delchild */ + if (owner & DIR_GWRITE) + perm |= S_IWUSR; + /* read if any of list */ + if (owner & DIR_GREAD) + perm |= S_IRUSR; + } else { + /* exec if execute or generic execute */ + if (owner & FILE_GEXEC) + perm |= S_IXUSR; + /* write if any of writedata or generic write */ + if (owner & FILE_GWRITE) + perm |= S_IWUSR; + /* read if any of readdata or generic read */ + if (owner & FILE_GREAD) + perm |= S_IRUSR; + } +//TRACE(fprintf(stderr,"owner allow 0x%x deny 0x%x perm 0%03o\n",owner,denyown,perm)); + } + /* build group permission */ + if (group) { + if (ni->mrec->flags & MFT_RECORD_IS_DIRECTORY) { + /* exec if any of list, traverse */ + if (group & DIR_GEXEC) + perm |= S_IXGRP; + /* write if any of addfile, adddir, delchild */ + if (group & DIR_GWRITE) + perm |= S_IWGRP; + /* read if any of list */ + if (group & DIR_GREAD) + perm |= S_IRGRP; + } else { + /* exec if execute */ + if (group & FILE_GEXEC) + perm |= S_IXGRP; + /* write if any of writedata, appenddata */ + if (group & FILE_GWRITE) + perm |= S_IWGRP; + /* read if any of readdata */ + if (group & FILE_GREAD) + perm |= S_IRGRP; + } +//TRACE(fprintf(stderr,"group allow 0x%x deny 0x%x perm 0%03o\n",group,denygrp,perm)); + } + /* build world permission */ + if (world) { + if (ni->mrec->flags & MFT_RECORD_IS_DIRECTORY) { + /* exec if any of list, traverse */ + if (world & DIR_GEXEC) + perm |= S_IXOTH; + /* write if any of addfile, adddir, delchild */ + if (world & DIR_GWRITE) + perm |= S_IWOTH; + /* read if any of list */ + if (world & DIR_GREAD) + perm |= S_IROTH; + } else { + /* exec if execute */ + if (world & FILE_GEXEC) + perm |= S_IXOTH; + /* write if any of writedata, appenddata */ + if (world & FILE_GWRITE) + perm |= S_IWOTH; + /* read if any of readdata */ + if (world & FILE_GREAD) + perm |= S_IROTH; + } + } + return (perm); +} + + +static int build_std_permissions(struct SECURITY_CONTEXT *scx, + const char *securattr, ntfs_inode *ni) +{ + const SECURITY_DESCRIPTOR_RELATIVE *phead; + const ACL *pacl; + const ACCESS_ALLOWED_ACE *pace; + const SID *usid; /* owner of file/directory */ + const SID *gsid; /* group of file/directory */ + int usidsz; + int gsidsz; + int offdacl; + int offace; + int acecnt; + int nace; + le32 allowown, allowgrp, allowall; + le32 denyown, denygrp, denyall; + + phead = (const SECURITY_DESCRIPTOR_RELATIVE*)securattr; + usid = (SID*)&securattr[le32_to_cpu(phead->owner)]; + usidsz = sid_size(usid); + gsid = (SID*)&securattr[le32_to_cpu(phead->group)]; + gsidsz = sid_size(gsid); + offdacl = le32_to_cpu(phead->dacl); + pacl = (ACL*)&securattr[offdacl]; + allowown = allowgrp = allowall = cpu_to_le32(0); + denyown = denygrp = denyall = cpu_to_le32(0); + acecnt = le16_to_cpu(pacl->ace_count); + offace = offdacl + sizeof(ACL); + for (nace = 0; nace < acecnt; nace++) { + pace = (const ACCESS_ALLOWED_ACE*)&securattr[offace]; + if (!memcmp(usid, &pace->sid, usidsz)) { + if (pace->type == ACCESS_ALLOWED_ACE_TYPE) + allowown |= pace->mask; + else if (pace->type == ACCESS_DENIED_ACE_TYPE) + denyown |= pace->mask; +//TRACE(fprintf(stderr,"owner allow 0x%x deny 0x%x\n",allowown,denyown)); + } else + if (!memcmp(gsid, &pace->sid, gsidsz)) { + if (pace->type == ACCESS_ALLOWED_ACE_TYPE) + allowgrp |= pace->mask; + else if (pace->type == ACCESS_DENIED_ACE_TYPE) + denygrp |= pace->mask; +//TRACE(fprintf(stderr,"group allow 0x%x deny 0x%x\n",allowgrp,denygrp)); + } else + if (is_world_sid((const SID*)&pace->sid)) { + if (pace->type == ACCESS_ALLOWED_ACE_TYPE) + allowall |= pace->mask; + else + if (pace->type == ACCESS_DENIED_ACE_TYPE) + denyall |= pace->mask; +//TRACE(fprintf(stderr,"world allow 0x%x deny 0x%x\n",allowall,denyall)); + } + offace += le16_to_cpu(pace->size); + } + /* + * Add to owner rights granted to group or world + * unless denied personaly, and add to group rights + * granted to world unless denied specifically + */ + allowown |= allowgrp | allowall; + allowgrp |= allowall; + return (merge_permissions(ni, + allowown & ~denyown, + allowgrp & ~denygrp, + allowall & ~denyall)); +} + + +static int build_owngrp_permissions(struct SECURITY_CONTEXT *scx, + const char *securattr, ntfs_inode *ni) +{ + const SECURITY_DESCRIPTOR_RELATIVE *phead; + const ACL *pacl; + const ACCESS_ALLOWED_ACE *pace; + const SID *usid; /* owner and group of file/directory */ + int usidsz; + int offdacl; + int offace; + int acecnt; + int nace; + le32 allowown, allowgrp, allowall; + le32 denyown, denygrp, denyall; + + phead = (const SECURITY_DESCRIPTOR_RELATIVE*)securattr; + usid = (SID*)&securattr[le32_to_cpu(phead->owner)]; + usidsz = sid_size(usid); + offdacl = le32_to_cpu(phead->dacl); + pacl = (ACL*)&securattr[offdacl]; + allowown = allowgrp = allowall = cpu_to_le32(0); + denyown = denygrp = denyall = cpu_to_le32(0); + acecnt = le16_to_cpu(pacl->ace_count); + offace = offdacl + sizeof(ACL); + for (nace = 0; nace < acecnt; nace++) { + pace = (const ACCESS_ALLOWED_ACE*)&securattr[offace]; + if (!memcmp(usid, &pace->sid, usidsz) + && (pace->mask & FILE_WRITE_ATTRIBUTES)) { + if (pace->type == ACCESS_ALLOWED_ACE_TYPE) + allowown |= pace->mask; +//TRACE(fprintf(stderr,"owner allow 0x%x deny 0x%x\n",allowown,denyown)); + } else + if (!memcmp(usid, &pace->sid, usidsz) + && (!(pace->mask & FILE_WRITE_ATTRIBUTES))) { + if (pace->type == ACCESS_ALLOWED_ACE_TYPE) + allowgrp |= pace->mask; +//TRACE(fprintf(stderr,"group allow 0x%x deny 0x%x\n",allowgrp,denygrp)); + } else + if (is_world_sid((const SID*)&pace->sid)) { + if (pace->type == ACCESS_ALLOWED_ACE_TYPE) + allowall |= pace->mask; + else + if (pace->type == ACCESS_DENIED_ACE_TYPE) + denyall |= pace->mask; +//TRACE(fprintf(stderr,"world allow 0x%x deny 0x%x\n",allowall,denyall)); + } + offace += le16_to_cpu(pace->size); + } + return (merge_permissions(ni, + allowown & ~denyown, + allowgrp & ~denygrp, + allowall & ~denyall)); +} + + +static int build_ownadmin_permissions(struct SECURITY_CONTEXT *scx, + const char *securattr, ntfs_inode *ni) +{ + const SECURITY_DESCRIPTOR_RELATIVE *phead; + const ACL *pacl; + const ACCESS_ALLOWED_ACE *pace; + const SID *usid; /* owner of file/directory */ + const SID *gsid; /* group of file/directory */ + int usidsz; + int gsidsz; + int offdacl; + int offace; + int acecnt; + int nace; + le32 allowown, allowgrp, allowall; + le32 denyown, denygrp, denyall; + + phead = (const SECURITY_DESCRIPTOR_RELATIVE*)securattr; + usid = (SID*)&securattr[le32_to_cpu(phead->owner)]; + usidsz = sid_size(usid); + gsid = (SID*)&securattr[le32_to_cpu(phead->group)]; + gsidsz = sid_size(gsid); + offdacl = le32_to_cpu(phead->dacl); + pacl = (ACL*)&securattr[offdacl]; + allowown = allowgrp = allowall = cpu_to_le32(0); + denyown = denygrp = denyall = cpu_to_le32(0); + acecnt = le16_to_cpu(pacl->ace_count); +//TRACE(fprintf(ntfslog,"adminowns %d acecnt %d\n",adminowns,acecnt)); + offace = offdacl + sizeof(ACL); + for (nace = 0; nace < acecnt; nace++) { + pace = (const ACCESS_ALLOWED_ACE*)&securattr[offace]; + if (!memcmp(usid, &pace->sid, usidsz) + && (((pace->mask & FILE_WRITE_ATTRIBUTES) && !nace))) { + if (pace->type == ACCESS_ALLOWED_ACE_TYPE) + allowown |= pace->mask; + else + if (pace->type == ACCESS_DENIED_ACE_TYPE) + denyown |= pace->mask; +//TRACE(fprintf(stderr,"owner allow 0x%x deny 0x%x\n",allowown,denyown)); + } else + if (!memcmp(gsid, &pace->sid, gsidsz) + && (!(pace->mask & FILE_WRITE_ATTRIBUTES))) { + if (pace->type == ACCESS_ALLOWED_ACE_TYPE) + allowgrp |= pace->mask; + else + if (pace->type == ACCESS_DENIED_ACE_TYPE) + denygrp |= pace->mask; +//TRACE(fprintf(stderr,"group allow 0x%x deny 0x%x\n",allowgrp,denygrp)); + } else if (is_world_sid((const SID*)&pace->sid)) { + if (pace->type == ACCESS_ALLOWED_ACE_TYPE) + allowall |= pace->mask; + else + if (pace->type == ACCESS_DENIED_ACE_TYPE) + denyall |= pace->mask; +//TRACE(fprintf(stderr,"world allow 0x%x deny 0x%x\n",allowall,denyall)); + } + offace += le16_to_cpu(pace->size); + } + return (merge_permissions(ni, + allowown & ~denyown, + allowgrp & ~denygrp, + allowall & ~denyall)); +} + +/* + * Build unix-style (mode_t) permissions + * optionally filtered by the relation of user to file (owner, group or other) + * returns a negative result and sets errno if there is a problem + */ + +static int build_permissions(struct SECURITY_CONTEXT *scx, + const char *securattr, ntfs_inode *ni) +{ + const SECURITY_DESCRIPTOR_RELATIVE *phead; + const SID *usid; /* owner of file/directory */ + const SID *gsid; /* group of file/directory */ + int usidsz; + int gsidsz; + int perm; + BOOL adminowns; + BOOL groupowns; + + phead = (const SECURITY_DESCRIPTOR_RELATIVE*)securattr; + if (phead->control & SE_DACL_PRESENT) { /* no DACL, reject */ + usid = (SID*)&securattr[le32_to_cpu(phead->owner)]; + usidsz = sid_size(usid); + gsid = (SID*)&securattr[le32_to_cpu(phead->group)]; + gsidsz = sid_size(gsid); + adminowns = !memcmp(usid,adminsid(),usidsz) + || !memcmp(gsid,adminsid(),gsidsz); + groupowns = !adminowns && !memcmp(gsid,usid,gsidsz); + if (adminowns) + perm = build_ownadmin_permissions(scx,securattr, + ni); + else + if (groupowns) + perm = build_owngrp_permissions(scx,securattr, + ni); + else + perm = build_std_permissions(scx,securattr, + ni); + } else { + perm = -1; + errno = EIO; + } + return (perm); +} + +/* + * 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, + const char *path, ntfs_inode * ni) +{ + const SECURITY_DESCRIPTOR_RELATIVE *phead; + const struct SECURITY_ENTRY *cached; + const char *securattr; + const SID *usid; /* owner of file/directory */ + const SID *gsid; /* group of file/directory */ + uid_t uid; + gid_t gid; + int perm; + + if (!scx->usermapping || !scx->uid) + perm = 0777; + 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 */ + securattr = getsecurityattr(scx,path, ni); + if (securattr) { + perm = build_permissions(scx,securattr, ni); + /* fetch owner and group for cacheing */ + if (perm >= 0) { + phead = + (const SECURITY_DESCRIPTOR_RELATIVE*) + securattr; + usid = (SID*)& + securattr[le32_to_cpu(phead->owner)]; + gsid = (SID*)& + securattr[le32_to_cpu(phead->group)]; + uid = findowner(scx,usid); + gid = findgroup(scx,gsid); + enter_cache(scx, ni, uid, + gid, perm); + } + free((void*)securattr); + } else + perm = -1; + uid = gid = 0; + } + } + if (perm >= 0) { + if (uid == scx->uid) + perm &= 0700; + else + if (gid == scx->gid) + perm &= 070; + else + perm &= 007; + } + return (perm); +} + +/* + * 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, + const char *path, ntfs_inode * ni, + struct stat *stbuf) +{ + const SECURITY_DESCRIPTOR_RELATIVE *phead; + const char *securattr; + const SID *usid; /* owner of file/directory */ + const SID *gsid; /* group of file/directory */ + const struct SECURITY_ENTRY *cached; + int perm; + + if (!scx->usermapping) + perm = 0777; + else { + /* check whether available in cache */ + cached = fetch_cache(scx,ni); + if (cached) { + perm = cached->mode; + stbuf->st_uid = cached->uid; + stbuf->st_gid = cached->gid; + stbuf->st_mode = (stbuf->st_mode & ~0777) + perm; + } else { + perm = -1; /* default to error */ + securattr = getsecurityattr(scx,path, ni); + if (securattr) { + perm = build_permissions(scx,securattr, ni); + if (perm >= 0) { + phead = + (const SECURITY_DESCRIPTOR_RELATIVE*) + securattr; + usid = (SID*)& + securattr[le32_to_cpu(phead->owner)]; + gsid = (SID*)& + securattr[le32_to_cpu(phead->group)]; + stbuf->st_uid = findowner(scx,usid); + stbuf->st_gid = findgroup(scx,gsid); + stbuf->st_mode = + (stbuf->st_mode & ~0777) + perm; + enter_cache(scx, ni, stbuf->st_uid, + stbuf->st_gid, perm); + } + free((void*)securattr); + } + } + } + return (perm); +} + +/* + * Build an ACL composed of several ACE's + * (not expected to fail) + * + * Three schemes are defined : + * + * 1) if root is neither owner nor group up to 7 ACE's are set up : + * - grants to owner (always present) + * - denials to owner (preventing grants to world or group to apply) + * - grants to group (unless groups has same rights as world) + * - denials to group (preventing grants to world to apply) + * - grants to world (unless none) + * - full privileges to administrator, always present + * - full privileges to system, always present + * + * 2) if root is either owner or group, two problems arise : + * - granting full rights to administrator (as needed to transpose + * to Windows rights bypassing granting to root) would imply + * Linux permissions to always be seen as rwx, no matter the chmod + * - there is no different SID to separate an administrator owner + * from an administrator group. Hence Linux permissions for owner + * would always be similar to permissions to group. + * + * as a work-around, up to 5 ACE's are set up if owner or group : + * - grants to owner, always present at first position + * - grants to group, always present + * - grants to world, unless none + * - full privileges to administrator, always present + * - full privileges to system, always present + * + * On Windows, these ACE's are processed normally, though they + * are redundant (owner, group and administrator are the same, + * as a consequence any denials would damage administrator rights) + * but on Linux, privileges to administrator are ignored (they + * are not needed as root has always full privileges), and + * neither grants to group are applied to owner, nor grants to + * world are applied to owner or group. + * + * 3) finally a similar situation arises when group is owner (they + * have the same SID), but is not root. + * In this situation up to 6 ACE's are set up : + * + * - grants to owner (always present) + * - denials to owner (preventing grants to world to apply) + * - grants to group (unless groups has same rights as world) + * - grants to world (unless none) + * - full privileges to administrator, always present + * - full privileges to system, always present + * + * On Windows, these ACE's are processed normally, though they + * are redundant (as owner group are the same, but this has + * no impact on administrator rights) + */ + +static int buildacls(char *secattr, int offs, mode_t mode, int isdir, + const SID * usid, const SID * gsid) +{ + ACL *pacl; + ACCESS_ALLOWED_ACE *pgace; + ACCESS_ALLOWED_ACE *pdace; + const SID *wsid; + const SID *asid; + const SID *ssid; + BOOL adminowns; + BOOL groupowns; + int pos; + int acecnt; + int usidsz; + int gsidsz; + int wsidsz; + int asidsz; + int ssidsz; + long grants; + long denials; + + usidsz = sid_size(usid); + gsidsz = sid_size(gsid); + asid = adminsid(); + asidsz = sid_size(asid); + ssid = systemsid(); + ssidsz = sid_size(ssid); + adminowns = !memcmp(usid, asid, usidsz) + || !memcmp(gsid, asid, gsidsz); + groupowns = !adminowns && !memcmp(usid, gsid, usidsz); + + /* ACL header */ + pacl = (ACL*)&secattr[offs]; + pacl->revision = ACL_REVISION; + pacl->alignment1 = 0; + pacl->size = sizeof(ACL) + usidsz + 8; + pacl->ace_count = cpu_to_le16(1); + pacl->alignment2 = 0; + pos = sizeof(ACL); + acecnt = 0; + + /* a grant ACE for owner */ + + pgace = (ACCESS_ALLOWED_ACE*) &secattr[offs + pos]; + pgace->type = ACCESS_ALLOWED_ACE_TYPE; + pgace->size = usidsz + 8; + grants = OWNER_RIGHTS; + if (isdir) { + pgace->flags = DIR_INHERITANCE; + if (mode & S_IXUSR) + grants |= DIR_EXEC; + if (mode & S_IWUSR) + grants |= DIR_WRITE; + if (mode & S_IRUSR) + grants |= DIR_READ; + } else { + pgace->flags = FILE_INHERITANCE; + if (mode & S_IXUSR) + grants |= FILE_EXEC; + if (mode & S_IWUSR) + grants |= FILE_WRITE; + if (mode & S_IRUSR) + grants |= FILE_READ; + } + pgace->mask = cpu_to_le32(grants); + memcpy((char*)&pgace->sid, usid, usidsz); + pos += pgace->size; + acecnt++; + + /* a possible ACE to deny owner what he/she would */ + /* induely get from administrator, group or world */ + /* unless owner is administrator or group */ + + denials = 0; + pdace = (ACCESS_DENIED_ACE*) &secattr[offs + pos]; + if (!adminowns) { + if (!groupowns) { + if (isdir) { + pdace->flags = DIR_INHERITANCE; + if (mode & (S_IXGRP | S_IXOTH)) + denials |= DIR_EXEC; + if (mode & (S_IWGRP | S_IWOTH)) + denials |= DIR_WRITE; + if (mode & (S_IRGRP | S_IROTH)) + denials |= DIR_READ; + } else { + pdace->flags = FILE_INHERITANCE; + if (mode & (S_IXGRP | S_IXOTH)) + denials |= FILE_EXEC; + if (mode & (S_IWGRP | S_IWOTH)) + denials |= FILE_WRITE; + if (mode & (S_IRGRP | S_IROTH)) + denials |= FILE_READ; + } + } else { + if (isdir) { + pdace->flags = DIR_INHERITANCE; + if ((mode & S_IXOTH) && !(mode & S_IXGRP)) + denials |= DIR_EXEC; + if ((mode & S_IWOTH) && !(mode & S_IWGRP)) + denials |= DIR_WRITE; + if ((mode & S_IROTH) && !(mode & S_IRGRP)) + denials |= DIR_READ; + } else { + pdace->flags = FILE_INHERITANCE; + if ((mode & S_IXOTH) && !(mode & S_IXGRP)) + denials |= FILE_EXEC; + if ((mode & S_IWOTH) && !(mode & S_IWGRP)) + denials |= FILE_WRITE; + if ((mode & S_IROTH) && !(mode & S_IRGRP)) + denials |= FILE_READ; + } + } + denials &= ~grants; + if (denials) { + pdace->type = ACCESS_DENIED_ACE_TYPE; + pdace->size = usidsz + 8; + pdace->mask = cpu_to_le32(denials); + memcpy((char*)&pdace->sid, usid, usidsz); + pos += pdace->size; + acecnt++; + } + } + + /* a grant ACE for group */ + /* unless group has the same rights as world */ + /* but present if owner is administrator */ + + if (adminowns + || (((mode >> 3) ^ mode) & 7)) { + pgace = (ACCESS_ALLOWED_ACE*)&secattr[offs + pos]; + pgace->type = ACCESS_ALLOWED_ACE_TYPE; + pgace->size = gsidsz + 8; + grants = WORLD_RIGHTS; + if (isdir) { + pgace->flags = DIR_INHERITANCE; + if (mode & S_IXGRP) + grants |= DIR_EXEC; + if (mode & S_IWGRP) + grants |= DIR_WRITE; + if (mode & S_IRGRP) + grants |= DIR_READ; + } else { + pgace->flags = FILE_INHERITANCE; + if (mode & S_IXGRP) + grants |= FILE_EXEC; + if (mode & S_IWGRP) + grants |= FILE_WRITE; + if (mode & S_IRGRP) + grants |= FILE_READ; + } + pgace->mask = cpu_to_le32(grants); + memcpy((char*)&pgace->sid, gsid, gsidsz); + pos += pgace->size; + acecnt++; + + /* a possible ACE to deny group what it would get from world */ + /* or administrator, unless owner is administrator or group */ + + denials = 0; + pdace = (ACCESS_ALLOWED_ACE*)&secattr[offs + pos]; + if (!adminowns && !groupowns) { + if (isdir) { + pdace->flags = DIR_INHERITANCE; + if (mode & S_IXOTH) + denials |= DIR_EXEC; + if (mode & S_IWOTH) + denials |= DIR_WRITE; + if (mode & S_IROTH) + denials |= DIR_READ; + } else { + pdace->flags = FILE_INHERITANCE; + if (mode & S_IXOTH) + denials |= FILE_EXEC; + if (mode & S_IWOTH) + denials |= FILE_WRITE; + if (mode & S_IROTH) + denials |= FILE_READ; + } + denials &= ~grants; + if (denials) { + pdace->type = ACCESS_DENIED_ACE_TYPE; + pdace->size = gsidsz + 8; + pdace->mask = cpu_to_le32(denials); + memcpy((char*)&pdace->sid, gsid, gsidsz); + pos += pdace->size; + acecnt++; + } + } + } + + /* an ACE for world users */ + + wsid = worldsid(); + wsidsz = sid_size(wsid); + pgace = (ACCESS_ALLOWED_ACE*)&secattr[offs + pos]; + pgace->type = ACCESS_ALLOWED_ACE_TYPE; + pgace->size = wsidsz + 8; + grants = WORLD_RIGHTS; + if (isdir) { + pgace->flags = DIR_INHERITANCE; + if (mode & S_IXOTH) + grants |= DIR_EXEC; + if (mode & S_IWOTH) + grants |= DIR_WRITE; + if (mode & S_IROTH) + grants |= DIR_READ; + } else { + pgace->flags = FILE_INHERITANCE; + if (mode & S_IXOTH) + grants |= FILE_EXEC; + if (mode & S_IWOTH) + grants |= FILE_WRITE; + if (mode & S_IROTH) + grants |= FILE_READ; + } + pgace->mask = cpu_to_le32(grants); + memcpy((char*)&pgace->sid, wsid, wsidsz); + pos += pgace->size; + acecnt++; + + /* an ACE for administrators */ + /* always full access */ + + pgace = (ACCESS_ALLOWED_ACE*)&secattr[offs + pos]; + pgace->type = ACCESS_ALLOWED_ACE_TYPE; + if (isdir) + pgace->flags = DIR_INHERITANCE; + else + pgace->flags = FILE_INHERITANCE; + pgace->size = asidsz + 8; + grants = OWNER_RIGHTS | FILE_READ | FILE_WRITE | FILE_EXEC; + pgace->mask = cpu_to_le32(grants); + memcpy((char*)&pgace->sid, asid, asidsz); + pos += pgace->size; + acecnt++; + + /* an ACE for system (needed ?) */ + /* always full access */ + + pgace = (ACCESS_ALLOWED_ACE*)&secattr[offs + pos]; + pgace->type = ACCESS_ALLOWED_ACE_TYPE; + if (isdir) + pgace->flags = DIR_INHERITANCE; + else + pgace->flags = FILE_INHERITANCE; + pgace->size = ssidsz + 8; + grants = OWNER_RIGHTS | FILE_READ | FILE_WRITE | FILE_EXEC; + pgace->mask = cpu_to_le32(grants); + memcpy((char*)&pgace->sid, ssid, ssidsz); + pos += pgace->size; + acecnt++; + + /* fix ACL header */ + pacl->size = cpu_to_le16(pos); + pacl->ace_count = cpu_to_le16(acecnt); + return (pos); +} + +/* + * Build a full security descriptor + * (in allocated memory, must free() after use) + */ + +static char *build_secur_descr(const char *path, mode_t mode, + int isdir, const SID * usid, const SID * gsid) +{ + int newattrsz; + SECURITY_DESCRIPTOR_RELATIVE *pnhead; + const SID *wsid; + char *newattr; + int aclsz; + int usidsz; + int gsidsz; + int wsidsz; + int asidsz; + int ssidsz; + + wsid = worldsid(); + usidsz = sid_size(usid); + gsidsz = sid_size(gsid); + wsidsz = sid_size(wsid); + asidsz = sid_size(adminsid()); + ssidsz = sid_size(systemsid()); + + /* allocate enough space for the new security attribute */ + newattrsz = sizeof(SECURITY_DESCRIPTOR_RELATIVE) /* header */ + + usidsz + gsidsz /* usid and gsid */ + + sizeof(ACL) /* acl header */ + + 2*(8 + usidsz) /* two possible ACE for user */ + + 2*(8 + gsidsz) /* two possible ACE for group */ + + 8 + wsidsz /* one ACE for world */ + + 8 + asidsz /* one ACE for admin */ + + 8 + ssidsz; /* one ACE for system */ + newattr = (char*)malloc(newattrsz); + if (newattr) { + /* build the main header part */ + pnhead = (SECURITY_DESCRIPTOR_RELATIVE*) newattr; + pnhead->revision = 1; + pnhead->alignment = 0; + pnhead->control = SE_DACL_PRESENT | SE_SELF_RELATIVE; + /* + * Windows prefers ACL first, do the same to + * get the same hash value and avoid duplication + */ + /* build permissions */ + aclsz = buildacls(newattr, + sizeof(SECURITY_DESCRIPTOR_RELATIVE), + mode, isdir, usid, gsid); + if ((aclsz + usidsz + gsidsz) <= newattrsz) { + /* append usid and gsid */ + memcpy(&newattr[sizeof(SECURITY_DESCRIPTOR_RELATIVE) + + aclsz], usid, usidsz); + memcpy(&newattr[sizeof(SECURITY_DESCRIPTOR_RELATIVE) + + aclsz + usidsz], gsid, gsidsz); + /* positions of ACL, USID and GSID into header */ + pnhead->owner = + cpu_to_le32(sizeof(SECURITY_DESCRIPTOR_RELATIVE) + + aclsz); + pnhead->group = + cpu_to_le32(sizeof(SECURITY_DESCRIPTOR_RELATIVE) + + aclsz + usidsz); + pnhead->sacl = cpu_to_le32(0); + pnhead->dacl = + cpu_to_le32(sizeof(SECURITY_DESCRIPTOR_RELATIVE)); + } else { + /* hope error was detected before overflowing */ + free(newattr); + newattr = (char*)NULL; + ntfs_log_error("Security descriptor is longer than expected\n"); + errno = EIO; + } + } else + errno = ENOMEM; + return (newattr); +} + +/* + * 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)); +} + +/* + * get next index entry in current block or next block + * (limited to SII and SDH) + * + * returns NULL at end of last block + * + * ! make sure getting next key value always returns an entry ! + * ! in the correct index level (a block with no child) ! + * + * linking to next block should be improved and made generic + * (walk into tree instead of lookup) + * then function should then be made public in index.c + */ + +static INDEX_ENTRY *ntfs_index_next(INDEX_ENTRY *ie, ntfs_index_context *xc, + BOOL forsii) +{ + INDEX_ENTRY *next; + struct SII *psii; + struct SDH *psdh; + SDH_INDEX_KEY sdhkey; + le32 siikey; + + if (!(ie->ie_flags & INDEX_ENTRY_END)) + next = (INDEX_ENTRY*)((char*)ie + le16_to_cpu(ie->length)); + else { + if (forsii) { + psii = (struct SII*)ie; + siikey = cpu_to_le32(le32_to_cpu(psii->keysecurid) + 1); + ntfs_index_lookup((char*)&siikey,sizeof(SII_INDEX_KEY), xc); + } else { + psdh = (struct SDH*)ie; + sdhkey.hash = cpu_to_le32(le32_to_cpu(psdh->keyhash) + 1); + if (sdhkey.hash) + sdhkey.security_id = psdh->keysecurid; + else + sdhkey.security_id = + cpu_to_le32(le32_to_cpu(psdh->keysecurid) + 1); + ntfs_index_lookup((char*)&sdhkey,sizeof(SDH_INDEX_KEY), xc); + } + /* + * ! lookup does not assume next key exists, but we ! + * ! do assume we get to the start of correct block anyway ! + */ + next = xc->entry; + /* for safety, return NULL at end of last block */ + if ((ie->ie_flags & INDEX_ENTRY_END) + && !(ie->ie_flags & INDEX_ENTRY_NODE)) + next = (INDEX_ENTRY*)NULL; + } + return (next); +} + + +/* + * Enter a new security descriptor to $Secure (data only) + * if has to be written twice with an offset of 256KB + * Returns zero if sucessful + */ + +static int entersecurity_data(ntfs_volume *vol, ntfs_inode *ni, + const SECURITY_DESCRIPTOR_RELATIVE *attr, s64 attrsz, + le32 hash, le32 keyid, off_t offs) +{ + int res; + int written1; + int written2; + char *fullattr; + SECURITY_DESCRIPTOR_HEADER *phsds; + + res = -1; + fullattr = malloc(attrsz + 20); + if (fullattr) { + memcpy(&fullattr[20],attr,attrsz); + phsds = (SECURITY_DESCRIPTOR_HEADER*)fullattr; + phsds->hash = hash; + phsds->security_id = keyid; + phsds->offset = cpu_to_le64(offs); + phsds->length = cpu_to_le64(attrsz + 20); + written1 = ntfs_local_write(vol, ni, "$Secure", + sds_name, 4, fullattr, attrsz + 20, + offs); + written2 = ntfs_local_write(vol, ni, "$Secure", + sds_name, 4, fullattr, attrsz + 20, + offs + ALIGN_SDS_BLOCK); + if ((written1 == (attrsz + 20)) + && (written2 == written1)) + res = 0; + else + errno = ENOMEM; + free(fullattr); + } + return (res); +} + +/* + * Enter a new security descriptor in $Secure (indexes only) + * Returns zero if sucessful + */ + +static int entersecurity_indexes(ntfs_inode *ni, + const SECURITY_DESCRIPTOR_RELATIVE *attr, s64 attrsz, + le32 hash, le32 keyid, off_t offs) +{ + union { + struct { + u32 dataoffsl; + u32 dataoffsh; + } parts; + u64 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 = ntfs_index_ctx_get(ni, sii_name, 4); + if (xsii) { + newsii.offs = 20; + newsii.size = sizeof(struct SII) - 20; + newsii.fill1 = 0; + newsii.indexsz = sizeof(struct SII); + newsii.indexksz = 4; + newsii.flags = 0; + newsii.fill2 = 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 = attrsz + 20; + if (!ntfs_ie_add(xsii,(INDEX_ENTRY*)&newsii)) { + xsdh = ntfs_index_ctx_get(ni, sdh_name, 4); + if (xsdh) { + /* enter a new $SDH record */ + newsdh.offs = 24; + newsdh.size = 20; + newsdh.fill1 = 0; + newsdh.indexsz = sizeof(struct SDH); + newsdh.indexksz = 8; + newsdh.flags = 0; + newsdh.fill2 = 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 = attrsz + 20; + newsdh.fill3 = 0; + if (!ntfs_ie_add(xsdh,(INDEX_ENTRY*)&newsdh)) + res = 0; + ntfs_index_ctx_put(xsdh); + } + } + ntfs_index_ctx_put(xsii); + } + return (res); +} + +/* + * Enter a new security descriptor in $Secure (data and indexes) + * 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 u32 entersecurityattr(ntfs_volume *vol, + ntfs_inode *ni, + const SECURITY_DESCRIPTOR_RELATIVE *attr, s64 attrsz, + le32 hash) +{ + union { + struct { + u32 dataoffsl; + u32 dataoffsh; + } parts; + u64 all; + } realign; + u32 securid; + le32 keyid; + off_t offs; + int size; + struct SII *psii; + INDEX_ENTRY *entry; + INDEX_ENTRY *next; + ntfs_index_context *xsii; + + /* 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 = 0; + xsii = ntfs_index_ctx_get(ni, sii_name, 4); + if (xsii) { + offs = size = 0; + keyid = cpu_to_le32(-1); + ntfs_index_lookup((char*)&keyid, + sizeof(SII_INDEX_KEY), xsii); + 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(&xsii->ir->index); + else + entry = ntfs_ie_get_first(&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 + */ + keyid = 0; + while (!(entry->ie_flags & INDEX_ENTRY_END)) { + next = ntfs_index_next(entry,xsii,TRUE); + if (next->ie_flags & INDEX_ENTRY_END) { + psii = (struct SII*)entry; + /* save last key and */ + /* available position */ + keyid = psii->keysecurid; + securid = le32_to_cpu(keyid) + 1; + 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 (!securid) { + /* assume we could have to insert the first entry */ + /* (after upgrading from an old version ?) */ + ntfs_log_error("Creating the first security_id\n"); + securid = FIRST_SECURITY_ID; + } + if (securid) { + /* + * 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. + */ + offs += ((size - 1) | (ALIGN_SDS_ENTRY - 1)) + 1; + if ((offs + attrsz - 1) & ALIGN_SDS_BLOCK) + offs = ((offs + attrsz - 1) + | (ALIGN_SDS_BLOCK - 1)) + 1; + /* now write the security attr to storage */ + keyid = cpu_to_le32(securid); + if (entersecurity_data(vol,ni,attr,attrsz, + hash,keyid,offs) + || entersecurity_indexes(ni,attr,attrsz, + hash,keyid,offs)) + securid = 0; + } else { + ntfs_log_error("Could not find an available security_id\n"); + errno = EIO; + } + ntfs_index_ctx_put(xsii); + } + 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 u32 setsecurityattr(ntfs_volume *vol, + const SECURITY_DESCRIPTOR_RELATIVE *attr, s64 attrsz) +{ + struct SDH *psdh; /* this is an image of index (le) */ + union { + struct { + u32 dataoffsl; + u32 dataoffsh; + } parts; + u64 all; + } realign; + BOOL found; + BOOL collision; + size_t size; + size_t rdsize; + s64 offs; + int res; + ntfs_inode *ni; + ntfs_index_context *xc; + char *oldattr; + SDH_INDEX_KEY key; + INDEX_ENTRY *entry; + u32 securid; + le32 hash; + + hash = ntfs_security_hash(attr,attrsz); + oldattr = (char*)NULL; + securid = 0; + res = 0; + ni = ntfs_pathname_to_inode(vol, NULL, "$Secure"); + if (ni) { + xc = ntfs_index_ctx_get(ni, sdh_name, 4); + if (xc) { + /* find the nearest key */ + key.hash = hash; + key.security_id = 0; + ntfs_index_lookup((char*)&key, + sizeof(SDH_INDEX_KEY), xc); + entry = xc->entry; + found = FALSE; + do { + collision = FALSE; + psdh = (struct SDH*)entry; + size = (size_t) le32_to_cpu(psdh->datasize) - 20; + /* 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) + 20; + oldattr = (char*)malloc(size); + if (oldattr) { + rdsize = ntfs_local_read( + vol, ni, "/$Secure", + sds_name, 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,xc,FALSE); + collision = TRUE; + } + } else + res = ENOMEM; + } + } while (collision && entry); + if (found) + securid = le32_to_cpu(psdh->keysecurid); + else { + if (res) { + errno = res; + securid = 0; + } else { + /* no matching key : have to build a new one */ + securid = entersecurityattr(vol,ni, + attr,attrsz,hash); + } + } + ntfs_index_ctx_put(xc); + } + ntfs_inode_close(ni); + } + return (securid); +} + + +/* + * Update a security descriptor + * + * returns 0 if success + */ + +static int update_secur_descr(ntfs_volume *vol, + const char *newattr, ntfs_inode *ni) +{ + int newattrsz; + int written; + int res; + ntfs_attr *na; + ntfschar nullchar = 0; + + newattrsz = attr_size(newattr); + +#if !FORCE_FORMAT_v1x + if (vol->major_ver < 3) { +#endif + + /* update for NTFS format v1.x */ + + /* update the old security attribute */ + na = ntfs_attr_open(ni, AT_SECURITY_DESCRIPTOR, &nullchar, 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("File 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, + &nullchar, 0); + if (na) { + clear_nino_flag(ni, v3_Extensions); + /* Truncating the record does not sweep extensions */ + /* from copy in memory. Clear security_id for precaution */ + ni->security_id = 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, + &nullchar, 0, (u8*)newattr, + (s64) newattrsz); + } +#if !FORCE_FORMAT_v1x + } else { + + /* update for NTFS format v3.x */ + + u32 securid; + + securid = setsecurityattr(vol, + (const SECURITY_DESCRIPTOR_RELATIVE*)newattr, + (s64)newattrsz); + if (securid) { + na = ntfs_attr_open(ni, AT_STANDARD_INFORMATION, + &nullchar, 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)72); + ni->owner_id = 0; + ni->quota_charged = 0; + ni->usn = 0; + ntfs_attr_remove(ni, + AT_SECURITY_DESCRIPTOR, + &nullchar, 0); + } + set_nino_flag(ni, v3_Extensions); + ni->security_id = cpu_to_le32(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); +} + +/* + * Set new permissions to a file + * Checks user mapping has been defined before request for setting + * + * rejected is 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, + const char *path, ntfs_inode * ni, mode_t mode) +{ + const SECURITY_DESCRIPTOR_RELATIVE *phead; + const struct SECURITY_ENTRY *cached; + const char *oldattr; + const char *newattr; + const SID *usid; + const SID *gsid; + uid_t uid; + uid_t fileuid; + uid_t filegid; + int isdir; + int res; + + /* get the current owner, either from cache or from old attribute */ + res = 0; + usid = (const SID*)NULL; + oldattr = (char*)NULL; + cached = fetch_cache(scx,ni); + if (cached) { + fileuid = cached->uid; + filegid = cached->gid; + usid = find_usid(scx,fileuid); + gsid = find_usid(scx,filegid); + } else { + oldattr = getsecurityattr(scx,path, ni); + if (oldattr) { + phead = (const SECURITY_DESCRIPTOR_RELATIVE*)oldattr; + usid = (SID*)&oldattr[le32_to_cpu(phead->owner)]; + gsid = (SID*)&oldattr[le32_to_cpu(phead->group)]; + fileuid = findowner(scx,usid); + filegid = findowner(scx,gsid); + /* do not free oldattr until usid and gsid are not needed */ + } + } + if (usid) { + isdir = (ni->mrec->flags & MFT_RECORD_IS_DIRECTORY) != 0; + uid = scx->uid; + if (!uid || (fileuid == uid)) { + newattr = build_secur_descr(path, mode, isdir, + usid, gsid); + if (newattr) { + res = update_secur_descr(scx->vol, newattr, ni); + if (!res) { + enter_cache(scx, ni, + fileuid, filegid, mode); + if (mode & S_IWUSR) + ni->flags &= ~FILE_ATTR_READONLY; + else + ni->flags |= FILE_ATTR_READONLY; + } + free((void*)newattr); + } else { + /* could not build new security attribute */ + errno = EIO; + res = -1; + } + } else { + errno = EPERM; + res = -1; /* neither owner nor root */ + } + if (oldattr) + free((void*)oldattr); + } else { + res = -1; /* could not get old security attribute */ + errno = EIO; + } + return (res ? -1 : 0); +} + +/* + * Create a default security descriptor for files whose descriptor + * cannot be inherited + */ + int ntfs_sd_add_everyone(ntfs_inode *ni) { SECURITY_DESCRIPTOR_ATTR *sd; @@ -298,29 +2466,29 @@ int ntfs_sd_add_everyone(ntfs_inode *ni) sd->revision = 1; sd->control = SE_DACL_PRESENT | SE_SELF_RELATIVE; - sid = (SID *)((u8 *)sd + sizeof(SECURITY_DESCRIPTOR_ATTR)); + sid = (SID*)((u8*)sd + sizeof(SECURITY_DESCRIPTOR_ATTR)); sid->revision = 1; sid->sub_authority_count = 2; sid->sub_authority[0] = cpu_to_le32(SECURITY_BUILTIN_DOMAIN_RID); sid->sub_authority[1] = cpu_to_le32(DOMAIN_ALIAS_RID_ADMINS); sid->identifier_authority.value[5] = 5; - sd->owner = cpu_to_le32((u8 *)sid - (u8 *)sd); + sd->owner = cpu_to_le32((u8*)sid - (u8*)sd); - sid = (SID *)((u8 *)sid + sizeof(SID) + 4); + sid = (SID*)((u8*)sid + sizeof(SID) + 4); sid->revision = 1; sid->sub_authority_count = 2; sid->sub_authority[0] = cpu_to_le32(SECURITY_BUILTIN_DOMAIN_RID); sid->sub_authority[1] = cpu_to_le32(DOMAIN_ALIAS_RID_ADMINS); sid->identifier_authority.value[5] = 5; - sd->group = cpu_to_le32((u8 *)sid - (u8 *)sd); + sd->group = cpu_to_le32((u8*)sid - (u8*)sd); - acl = (ACL *)((u8 *)sid + sizeof(SID) + 4); + acl = (ACL*)((u8*)sid + sizeof(SID) + 4); acl->revision = 2; acl->size = cpu_to_le16(sizeof(ACL) + sizeof(ACCESS_ALLOWED_ACE)); acl->ace_count = cpu_to_le16(1); - sd->dacl = cpu_to_le32((u8 *)acl - (u8 *)sd); + sd->dacl = cpu_to_le32((u8*)acl - (u8*)sd); - ace = (ACCESS_ALLOWED_ACE *)((u8 *)acl + sizeof(ACL)); + ace = (ACCESS_ALLOWED_ACE*)((u8*)acl + sizeof(ACL)); ace->type = ACCESS_ALLOWED_ACE_TYPE; ace->flags = OBJECT_INHERIT_ACE | CONTAINER_INHERIT_ACE; ace->size = cpu_to_le16(sizeof(ACCESS_ALLOWED_ACE)); @@ -329,40 +2497,557 @@ int ntfs_sd_add_everyone(ntfs_inode *ni) ace->sid.sub_authority_count = 1; ace->sid.sub_authority[0] = 0; ace->sid.identifier_authority.value[5] = 1; - - ret = ntfs_attr_add(ni, AT_SECURITY_DESCRIPTOR, AT_UNNAMED, 0, (u8 *)sd, + + ret = ntfs_attr_add(ni, AT_SECURITY_DESCRIPTOR, AT_UNNAMED, 0, (u8*)sd, sd_len); if (ret) - ntfs_log_perror("Failed to add SECURITY_DESCRIPTOR\n"); + ntfs_log_perror("Failed to add initial SECURITY_DESCRIPTOR\n"); free(sd); return ret; } -/** - * 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 +/* + * Check whether user can access a file in a specific way * - * 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. + * Always returns true is user is root or if no user mapping + * has been defined + * Sets errno if there is a problem or if not allowed */ -le32 ntfs_security_hash(const SECURITY_DESCRIPTOR_RELATIVE *sd, const u32 len) -{ - const le32 *pos = (le32 *)sd; - const le32 *end = pos + (len >> 2); - u32 hash = 0; - while (pos < end) - hash = le32_to_cpup(pos++) + ntfs_rol32(hash, 3); - return cpu_to_le32(hash); +BOOL ntfs_allowed_access(struct SECURITY_CONTEXT *scx, + const char *path, ntfs_inode *ni, + int accesstype) /* access type required (1..6) */ +{ + mode_t perm; + int allow; + + /* always allow for root (also root group ?) */ + /* also always allow if no mapping has been defined */ + if (!scx->usermapping || !scx->uid) + allow = 1; + else { + perm = ntfs_get_perm(scx, path, ni); + 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; + default: /* BUG ! */ + allow = 0; + break; + } + if (!allow) + errno = EPERM; + } + return (allow); } +/* + * Check whether user can access the parent directory + * of a file in a specific way + * + * Always returns true is user is root or if no user mapping + * has been defined + * Sets errno if there is a problem or if not allowed + */ + +BOOL ntfs_allowed_dir_access(struct SECURITY_CONTEXT *scx, + const char *path, int accesstype) +{ + BOOL allow; + char *dirpath; + char *name; + ntfs_inode *dir_ni; + + 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,path, + dir_ni, accesstype); + free(dirpath); + } + return (allow); /* errno is set if not allowed */ +} + +int ntfs_set_owner(struct SECURITY_CONTEXT *scx, + const char *path, ntfs_inode *ni, uid_t uid, gid_t gid) +{ + const SECURITY_DESCRIPTOR_RELATIVE *phead; + const struct SECURITY_ENTRY *cached; + const char *oldattr; + const char *newattr; + const SID *usid; + const SID *gsid; + uid_t fileuid; + uid_t filegid; + mode_t mode; + int isdir; + int res; + + res = 0; + /* get the current owner and mode from cache or security attributes */ + usid = (const SID*)NULL; + oldattr = (char*)NULL; + cached = fetch_cache(scx,ni); + if (cached) { + fileuid = cached->uid; + filegid = cached->gid; + mode = cached->mode; + usid = find_usid(scx,fileuid); + gsid = find_usid(scx,filegid); + } else { + oldattr = getsecurityattr(scx,path, ni); + if (oldattr) { + mode = build_permissions(scx, oldattr, ni); + if (mode >= 0) { + phead = (const SECURITY_DESCRIPTOR_RELATIVE*)oldattr; + usid = (SID*)&oldattr[le32_to_cpu(phead->owner)]; + gsid = (SID*)&oldattr[le32_to_cpu(phead->group)]; + fileuid = findowner(scx,usid); + filegid = findowner(scx,gsid); + } + /* do not free oldattr until usid and gsid are not needed */ + } + } + if (usid) { + isdir = (ni->mrec->flags & MFT_RECORD_IS_DIRECTORY) != 0; + /* check requested by owner or root */ + /* for chgrp, group must match owner's */ + if (!scx->uid + || ((fileuid == scx->uid) + && (((int)gid < 0) + || (filegid == scx->gid)))) { + /* replace by the new usid and gsid */ + /* or reuse old gid and sid for cacheing */ + if ((int)uid >= 0) + usid = find_usid(scx,uid); + else + uid = fileuid; + if ((int)gid >= 0) + gsid = find_gsid(scx,gid); + else + gid = filegid; + if (usid && gsid) { + newattr = build_secur_descr(path, mode, + isdir, usid, gsid); + if (newattr) { + res = + update_secur_descr(scx->vol, + newattr, ni); + if (!res) { + enter_cache(scx, ni, + uid, gid, mode); + } + free((void*)newattr); + } else { + errno = EIO; + res = -1; /* could not build new security attribute */ + } + } else { + res = -1; /* user mapping not defined */ + errno = EOPNOTSUPP; + } + } else { + res = -1; /* neither owner nor root */ + errno = EPERM; + } + if (oldattr) + free((void*)oldattr); + } else { + res = -1; /* could not get old security attribute */ + errno = EIO; + } + return (res ? -1 : 0); +} + + +int ntfs_set_owner_mode(struct SECURITY_CONTEXT *scx, + const char *path, ntfs_inode * ni, + uid_t uid, gid_t gid, mode_t mode) +{ + const char *newattr; + const SID *usid; + const SID *gsid; + int isdir; + int res; + + res = 0; + isdir = (ni->mrec->flags & MFT_RECORD_IS_DIRECTORY) != 0; + usid = find_usid(scx,uid); + gsid = find_gsid(scx,gid); + if (usid && gsid) { + newattr = build_secur_descr(path, mode, + isdir, usid, gsid); + if (newattr) { + res = update_secur_descr(scx->vol, newattr, ni); + if (!res) { + enter_cache(scx, ni, uid, gid, mode); + if (mode & S_IWUSR) + ni->flags &= ~FILE_ATTR_READONLY; + else + ni->flags |= FILE_ATTR_READONLY; + } + free((void*)newattr); + } else { + errno = EIO; + res = -1; /* could not build new security attribute */ + } + } else { + res = -1; /* user mapping not defined */ + errno = EOPNOTSUPP; + } + return (res ? -1 : 0); +} + +/* + * Get a single mapping item from buffer + * + * Always reads a full line, truncating long lines + * Refills buffer when exhausted + * Returns pointer to item, or NULL when no more + */ + +static struct MAPLIST *getmappingitem(struct SECURITY_CONTEXT *scx, + ntfs_inode *ni, off_t *poffs, char *buf, + int *psrc, s64 *psize) +{ + int src; + int dst; + char *p; + char *q; + int gotend; + ntfschar nullchar = 0; + struct MAPLIST *item; + + src = *psrc; + dst = 0; + /* allocate and get a full line */ + item = (struct MAPLIST*)malloc(sizeof(struct MAPLIST)); + if (item) { + do { + gotend = 0; + while ((src < *psize) + && (buf[src] != '\n')) { + if (dst < LINESZ) + item->maptext[dst] = buf[src]; + dst++; + src++; + } + if (buf[src] != '\n') { + *poffs += *psize; + *psize = ntfs_local_read(scx->vol,ni, + MAPPINGFILE, &nullchar, 0, + buf, (size_t)BUFSZ, *poffs); + src = 0; + } else { + gotend = 1; + src++; + item->maptext[dst] = '\0'; + dst = 0; + } + } while (*psize && ((item->maptext[0] == '#') || !gotend)); + if (gotend) { + /* decompose into uid, gid and sid */ + p = item->maptext; + item->uidstr = item->maptext; + item->gidstr = strchr(item->uidstr, ':'); + if (item->gidstr) { + *item->gidstr++ = '\0'; + item->sidstr = strchr(item->gidstr, ':'); + if (item->sidstr) { + *item->sidstr++ = 0; + q = strchr(item->sidstr, ':'); + if (q) *q = 0; + } else + p = (char*)NULL; + } else + p = (char*)NULL; /* bad line, stop */ + if (!p) { + free(item); + item = (struct MAPLIST*)NULL; + } + } else { + free(item); /* free unused item */ + item = (struct MAPLIST*)NULL; + } + } + *psrc = src; + return (item); +} + +/* + * Read user mapping file and split into their attribute + * parameters are kept as text in a chained list until logins + * are converted to uid. + * Returns the head of list, if any + * + * Basic IO routines are called since we are still mounting + * and we have not entered the fuse loop yet. + */ + +static struct MAPLIST *readmapping(struct SECURITY_CONTEXT *scx) +{ + char buf[BUFSZ]; + struct MAPLIST *item; + struct MAPLIST *firstitem; + struct MAPLIST *lastitem; + ntfs_inode *ni; + int src; + ntfschar nullchar = 0; + off_t offs; + s64 size; + + firstitem = (struct MAPLIST*)NULL; + lastitem = (struct MAPLIST*)NULL; + offs = 0; + ni = ntfs_pathname_to_inode(scx->vol, NULL, MAPPINGFILE); + if (ni) { + size = ntfs_local_read(scx->vol,ni,MAPPINGFILE, + &nullchar, 0, + buf, (size_t)BUFSZ, offs); + if (size > 0) { + src = 0; + do { + item = getmappingitem(scx,ni,&offs, + buf,&src,&size); + if (item) { + item->next = (struct MAPLIST*)NULL; + if (lastitem) + lastitem->next = item; + else + firstitem = item; + lastitem = item; + } + } while (item); + } + ntfs_inode_close(ni); + } + return (firstitem); +} + +/* + * Build the user mapping list + * decimal uid are currently expected + */ + +static struct MAPPING *ntfs_do_user_mapping(struct MAPLIST *firstitem) +{ + struct MAPLIST *item; + struct MAPPING *firstmapping; + struct MAPPING *lastmapping; + struct MAPPING *mapping; + SID *sid; + + firstmapping = (struct MAPPING*)NULL; + lastmapping = (struct MAPPING*)NULL; + for (item = firstitem; item; item = item->next) { + if (item->uidstr[0]) { + sid = encodesid(item->sidstr); + if (sid) { + mapping = + (struct MAPPING*) + malloc(sizeof(struct MAPPING)); + if (mapping) { + mapping->sid = sid; + mapping->xid = atoi(item->uidstr); + mapping->next = (struct MAPPING*)NULL; + if (lastmapping) + lastmapping->next = mapping; + else + firstmapping = mapping; + lastmapping = mapping; + } + } + } + } + return (firstmapping); +} + +/* + * Build the group mapping list + * decimal gid are currently expected + * gid not associated to a uid are processed first in order + * to favour real groups + */ + +static struct MAPPING *ntfs_do_group_mapping(struct MAPLIST *firstitem) +{ + struct MAPLIST *item; + struct MAPPING *firstmapping; + struct MAPPING *lastmapping; + struct MAPPING *mapping; + BOOL uidpresent; + BOOL ok; + int step; + SID *sid; + + firstmapping = (struct MAPPING*)NULL; + lastmapping = (struct MAPPING*)NULL; + for (step=1; step<=2; step++) { + for (item = firstitem; item; item = item->next) { + uidpresent = (item->uidstr[0] >= '1') + && (item->uidstr[0] <= '9'); + ok = (step == 1 ? !uidpresent : uidpresent); + if (item->gidstr[0] && ok) { + sid = encodesid(item->sidstr); + if (sid) { + mapping = (struct MAPPING*) + malloc(sizeof(struct MAPPING)); + if (mapping) { + mapping->sid = sid; + mapping->xid = atoi(item->gidstr); + mapping->next = (struct MAPPING*)NULL; + if (lastmapping) + lastmapping->next = mapping; + else + firstmapping = mapping; + lastmapping = mapping; + } + } + } + } + } + return (firstmapping); +} + +/* + * Apply default mapping + * returns zero if successful + */ + +int ntfs_do_default_mapping(struct SECURITY_CONTEXT *scx, const SID *usid) +{ + struct MAPPING *usermapping; + struct MAPPING *groupmapping; + SID *sid; + int sidsz; + int res; + + res = -1; + sidsz = sid_size(usid); + sid = (SID*)malloc(sidsz); + if (sid) { + memcpy(sid,usid,sidsz); + usermapping = (struct MAPPING*)malloc(sizeof(struct MAPPING)); + if (usermapping) { + groupmapping = (struct MAPPING*)malloc(sizeof(struct MAPPING)); + if (groupmapping) { + usermapping->sid = sid; + usermapping->xid = scx->uid; + usermapping->next = (struct MAPPING*)NULL; + groupmapping->sid = sid; + groupmapping->xid = scx->uid; + groupmapping->next = (struct MAPPING*)NULL; + scx->usermapping = usermapping; + scx->groupmapping = groupmapping; + res = 0; + } + } + } + return (res); + +} + +/* + * Try and apply default mapping + * returns zero if successful + */ + +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,"/.",ni); + if (securattr) { + phead = (const SECURITY_DESCRIPTOR_RELATIVE*)securattr; + usid = (SID*)&securattr[le32_to_cpu(phead->owner)]; + if (is_user_sid(usid)) + res = ntfs_do_default_mapping(scx,usid); + free(securattr); + } + ntfs_inode_close(ni); + } + return (res); +} + + +/* + * Build the user mapping + * - according to $Mapping file if present, + * - or try default 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) +{ + struct MAPLIST *item; + struct MAPLIST *firstitem; + struct MAPPING *usermapping; + struct MAPPING *groupmapping; + + /* be sure not to map anything until done */ + scx->usermapping = (struct MAPPING*)NULL; + scx->groupmapping = (struct MAPPING*)NULL; + firstitem = readmapping(scx); + if (firstitem) { + usermapping = ntfs_do_user_mapping(firstitem); + groupmapping = ntfs_do_group_mapping(firstitem); + if (usermapping && groupmapping) { + scx->usermapping = usermapping; + scx->groupmapping = 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 default mapping */ + if (scx->uid && scx->gid) { + if (!ntfs_default_mapping(scx)) + ntfs_log_info("Using default user mapping\n"); + } + } + return (!scx->usermapping); +} + +