From 86f94559c206680dd18d6d1fd805292091b7d943 Mon Sep 17 00:00:00 2001 From: jpandre Date: Fri, 12 Oct 2007 12:56:28 +0000 Subject: [PATCH] Implemented an option for upgrading gracefully individual security attributes Implemented a security API for tools Fixed a case for potential index/data inconsistency Improved hash collision detection through a generic get_next_key() --- libntfs-3g/security.c | 635 ++++++++++++++++++++++++++++++++---------- 1 file changed, 482 insertions(+), 153 deletions(-) diff --git a/libntfs-3g/security.c b/libntfs-3g/security.c index 890bd681..67a5a65f 100644 --- a/libntfs-3g/security.c +++ b/libntfs-3g/security.c @@ -526,7 +526,7 @@ static BOOL same_sid(const SID *first, const SID *second) static unsigned int attr_size(const char *attr) { - const SECURITY_DESCRIPTOR_RELATIVE *pnhead; + const SECURITY_DESCRIPTOR_RELATIVE *phead; const ACL *pdacl; const ACL *psacl; const SID *psid; @@ -535,38 +535,41 @@ static unsigned int attr_size(const char *attr) unsigned int offowner; unsigned int offgroup; unsigned int endsid; + unsigned int endsacl; unsigned int attrsz; /* * First check DACL, which is the last field in all descriptors * we build, and in most descriptors built by Windows */ - pnhead = (const SECURITY_DESCRIPTOR_RELATIVE*)attr; + phead = (const SECURITY_DESCRIPTOR_RELATIVE*)attr; /* find end of DACL */ - offdacl = le32_to_cpu(pnhead->dacl); + offdacl = le32_to_cpu(phead->dacl); pdacl = (const ACL*)&attr[offdacl]; attrsz = offdacl + le16_to_cpu(pdacl->size); - offowner = le32_to_cpu(pnhead->owner); + offowner = le32_to_cpu(phead->owner); if (offowner >= attrsz) { /* find end of USID */ psid = (const SID*)&attr[offowner]; endsid = offowner + sid_size(psid); attrsz = endsid; } - offgroup = le32_to_cpu(pnhead->group); + offgroup = le32_to_cpu(phead->group); if (offgroup >= attrsz) { /* find end of GSID */ psid = (const SID*)&attr[offgroup]; endsid = offgroup + sid_size(psid); if (endsid > attrsz) attrsz = endsid; } - offsacl = le32_to_cpu(pnhead->sacl); + offsacl = le32_to_cpu(phead->sacl); if (offsacl >= attrsz) { - /* find end of DACL */ - offsacl = le32_to_cpu(pnhead->sacl); + /* find end of SACL */ + offsacl = le32_to_cpu(phead->sacl); psacl = (const ACL*)&attr[offsacl]; - attrsz = offsacl + le16_to_cpu(psacl->size); + endsacl = offsacl + le16_to_cpu(psacl->size); + if (endsacl > attrsz) + attrsz = endsacl; } return (attrsz); @@ -715,21 +718,24 @@ static int ntfs_local_read(ntfs_inode *ni, res = -errno; goto exit; } - if (offset + size > (size_t)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; + if ((size_t)offset < (size_t)na->data_size) { + if (offset + size > (size_t)na->data_size) + size = na->data_size - offset; + while (size) { + res = ntfs_attr_pread(na, offset, size, buf); + if ((off_t)res < (off_t)size) + ntfs_log_perror("ntfs_attr_pread partial read " + "(%lld : %lld <> %d)", + (long long)offset, + (long long)size, res); + if (res <= 0) { + res = -errno; + goto exit; + } + size -= res; + offset += res; + total += res; } - size -= res; - offset += res; - total += res; } res = total; exit: @@ -929,8 +935,10 @@ static struct SECURITY_CACHE *create_caches(struct SECURITY_CONTEXT *scx, static void free_caches(struct SECURITY_CONTEXT *scx) { - free((*scx->pseccache)->head.first_securid); - free(*scx->pseccache); + if (*scx->pseccache) { + free((*scx->pseccache)->head.first_securid); + free(*scx->pseccache); + } } /* @@ -1661,7 +1669,8 @@ static char *build_secur_descr(mode_t mode, * - 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 + * 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 */ @@ -2050,6 +2059,9 @@ static int build_permissions(const char *securattr, ntfs_inode *ni) * returns -1 if there is a problem */ +static int upgrade_secur_desc(ntfs_volume *vol, const char *path, + const char *attr, ntfs_inode *ni); + static int ntfs_get_perm(struct SECURITY_CONTEXT *scx, const char *path, ntfs_inode * ni) { @@ -2073,11 +2085,26 @@ static int ntfs_get_perm(struct SECURITY_CONTEXT *scx, gid = cached->gid; } else { perm = 0; /* default to no permission */ - securattr = getsecurityattr(scx,path, ni); + securattr = getsecurityattr(scx, path, ni); if (securattr) { perm = build_permissions(securattr, ni); - /* 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) + && (perm >= 0) + && (scx->vol->flags + & (1 << SECURITY_ADDSECURIDS))) { + upgrade_secur_desc(scx->vol, path, + securattr, ni); + /* + * fetch owner and group for cacheing + * if there is a securid + */ + } + if (test_nino_flag(ni, v3_Extensions) + && (perm >= 0)) { phead = (const SECURITY_DESCRIPTOR_RELATIVE*) securattr; @@ -2095,15 +2122,15 @@ static int ntfs_get_perm(struct SECURITY_CONTEXT *scx, perm = -1; uid = gid = 0; } - } - if (perm >= 0) { - if (uid == scx->uid) - perm &= 0700; - else - if (gid == scx->gid) - perm &= 070; + if (perm >= 0) { + if (uid == scx->uid) + perm &= 0700; else - perm &= 007; + if (gid == scx->gid) + perm &= 070; + else + perm &= 007; + } } return (perm); } @@ -2136,10 +2163,23 @@ int ntfs_get_owner_mode(struct SECURITY_CONTEXT *scx, stbuf->st_mode = (stbuf->st_mode & ~0777) + perm; } else { perm = -1; /* default to error */ - securattr = getsecurityattr(scx,path, ni); + securattr = getsecurityattr(scx, path, ni); if (securattr) { perm = build_permissions(securattr, ni); + /* + * 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->flags + & (1 << SECURITY_ADDSECURIDS))) { + upgrade_secur_desc(scx->vol, + path, securattr, ni); + } phead = (const SECURITY_DESCRIPTOR_RELATIVE*) securattr; @@ -2171,119 +2211,13 @@ 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 - * (currently limited to SII and SDH) - * - * returns NULL at end of last block - * - * Linking to next block should be improved and made generic - * then function should then be made public in index.c - * currently walk up is generic but limited to a two-level tree - * and walk down used a lookup and is not generic - */ - -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; - int flags; - - /* get down if have a subnode */ - - if (ie->ie_flags & INDEX_ENTRY_NODE) { - ntfs_index_ctx_reinit(xc); - 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 = psdh->keyhash; - sdhkey.security_id = - cpu_to_le32( - le32_to_cpu(psdh->keysecurid) + 1); - ntfs_index_lookup((char*)&sdhkey, - sizeof(SDH_INDEX_KEY), xc); - } - next = xc->entry; - - } else { - - next = (INDEX_ENTRY*)((char*)ie + - le16_to_cpu(ie->length)); - flags = next->ie_flags; - if ((flags & INDEX_ENTRY_END) && (flags & INDEX_ENTRY_NODE)) { - - /* - * Move down in the tree if end of block and subnode present - */ - - ntfs_index_ctx_reinit(xc); - 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 = psdh->keyhash; - sdhkey.security_id = - cpu_to_le32( - le32_to_cpu(psdh->keysecurid) + 1); - ntfs_index_lookup((char*)&sdhkey, - sizeof(SDH_INDEX_KEY), xc); - } - next = xc->entry; - } - /* - * If end of block and no subnode, move up in the tree - * until current node is not the last child of parent - */ - - if ((flags & INDEX_ENTRY_END) && !(flags & INDEX_ENTRY_NODE)) { - if (xc->pindex > 0) { - do { - xc->pindex--; - if (!xc->pindex) { - free(xc->ib); - xc->ib = (INDEX_BLOCK*)NULL; - xc->ir = ntfs_ir_lookup(xc->ni, - xc->name, xc->name_len, - &xc->actx); - xc->is_in_root = TRUE; - xc->entry = ntfs_ie_get_by_pos( - &xc->ir->index, - xc->parent_pos[xc->pindex]); - } else { -/* ! TODO ! up into another non-root block */ - xc->entry = (INDEX_ENTRY*)NULL; - } - } while (xc->entry && (xc->pindex > 0) - && !(xc->entry->ie_flags & INDEX_ENTRY_END)); - } - /* done if stuck at end of block */ - if (xc->entry && (xc->entry->ie_flags & INDEX_ENTRY_END)) { - xc->entry = (INDEX_ENTRY*)NULL; - } - next = xc->entry; - } - } - /* return NULL if stuck at end of block */ - if (next && (next->ie_flags & INDEX_ENTRY_END)) - next = (INDEX_ENTRY*)NULL; - return (next); -} - /* - * Enter a new security descriptor to $Secure (data only) + * 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 */ @@ -2327,6 +2261,9 @@ static int entersecurity_data(ntfs_volume *vol, /* * Enter a new security descriptor in $Secure (indexes only) + * + * Should only be called by entersecurityattr() to ensure consistency + * * Returns zero if sucessful */ @@ -2463,7 +2400,7 @@ static le32 entersecurityattr(ntfs_volume *vol, */ keyid = cpu_to_le32(0); while (entry) { - next = ntfs_index_next(entry,xsii,TRUE); + next = ntfs_index_next(entry,xsii); if (next) { psii = (struct SII*)next; /* save last key and */ @@ -2501,8 +2438,11 @@ static le32 entersecurityattr(ntfs_volume *vol, if (entersecurity_data(vol, attr, attrsz, hash, securid, offs) || entersecurity_indexes(vol, attrsz, hash, securid, offs)) securid = cpu_to_le32(0); - /* inode now is dirty */ + /* inode now is dirty, synchronize it all */ + ntfs_index_ctx_reinit(vol->secure_xsii); + ntfs_index_ctx_reinit(vol->secure_xsdh); NInoSetDirty(vol->secure_ni); + ntfs_inode_sync(vol->secure_ni); return (securid); } @@ -2579,7 +2519,7 @@ static le32 setsecurityattr(ntfs_volume *vol, /* (hash collision), try next one */ if (!found) { entry = ntfs_index_next( - entry,xsdh,FALSE); + entry,xsdh); collision = TRUE; } } else @@ -2611,7 +2551,7 @@ static le32 setsecurityattr(ntfs_volume *vol, */ static int update_secur_descr(ntfs_volume *vol, - char *newattr, ntfs_inode *ni) + const char *newattr, ntfs_inode *ni) { int newattrsz; int written; @@ -2695,7 +2635,7 @@ static int update_secur_descr(ntfs_volume *vol, ntfs_attr_remove(ni, AT_SECURITY_DESCRIPTOR, AT_UNNAMED, 0); - } + } set_nino_flag(ni, v3_Extensions); ni->security_id = securid; ntfs_attr_close(na); @@ -2716,6 +2656,77 @@ static int update_secur_descr(ntfs_volume *vol, 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 create a duplicate (attribute and entry in $Secure) + * and allow for cacheing and inheritance. + * The attribute is kept for use by utilities which would not + * accept entries in $Secure + * At next update of permissions, the duplicate will be removed + * + * 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 *path, + 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 + */ + + if ((vol->major_ver >= 3) + && (path[0] == '/') + && (path[1] != '$') && (path[1] != '\0')) { + attrsz = 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) { + res = 0; + /* expand standard information attribute to v3.x */ + res = ntfs_attr_truncate(na, + (s64)sizeof(STANDARD_INFORMATION)); + ni->owner_id = cpu_to_le32(0); + ni->quota_charged = cpu_to_le32(0); + ni->usn = cpu_to_le32(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); + ntfs_inode_sync(ni); /* useful ? */ + } else + res = 1; + + return (res); +} + + /* * Update ownership and mode of a file, reusing an existing * security descriptor when possible @@ -3526,5 +3537,323 @@ void ntfs_close_secure(struct SECURITY_CONTEXT *scx) } free_mapping(scx); free_caches(scx); +showlist(99); +} + +/* + * 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; + + /* + * First check DACL, which is the last field in all descriptors + * we build, and in most descriptors built by Windows + */ + + avail = 0; + phead = (const SECURITY_DESCRIPTOR_RELATIVE*)attr; + size = 0; + + /* locate DACL if requested and available */ + if (selection & phead->control & DACL_SECURITY_INFORMATION) { + offdacl = le32_to_cpu(phead->dacl); + pdacl = (const ACL*)&attr[offdacl]; + daclsz = le16_to_cpu(pdacl->size); + size = offdacl + 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 = sid_size(pusid); + if ((offowner + usidsz) > size) + size = offowner + 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 = sid_size(pgsid); + if ((offgroup + gsidsz) > size) + size = offgroup + gsidsz; + avail |= GROUP_SECURITY_INFORMATION; + } else + offgroup = gsidsz = 0; + + /* locate SACL if requested and available */ + if (selection & phead->control & SACL_SECURITY_INFORMATION) { + /* find end of SACL */ + offsacl = le32_to_cpu(phead->sacl); + psacl = (const ACL*)&attr[offsacl]; + saclsz = le16_to_cpu(psacl->size); + if ((offsacl + saclsz) > size) + size = offsacl + saclsz; + avail |= SACL_SECURITY_INFORMATION; + } else + offsacl = saclsz = 0; + + /* + * Check whether not requesting unavailable information + * and having enough size in destination buffer + */ + if ((selection & ~avail) + || (size > buflen)) { + errno = EINVAL; + ok = FALSE; + } else { + /* copy header and feed new flags */ + memcpy(buf,attr,sizeof(SECURITY_DESCRIPTOR_RELATIVE)); + pnhead = (SECURITY_DESCRIPTOR_RELATIVE*)buf; + pnhead->control = avail; + pos = sizeof(SECURITY_DESCRIPTOR_RELATIVE); + + /* copy DACL if requested */ + if (selection & DACL_SECURITY_INFORMATION) { + pnhead->dacl = pos; + memcpy(&buf[pos],&attr[offdacl],daclsz); + pos += daclsz; + } else + pnhead->dacl = 0; + + /* copy SACL if requested */ + if (selection & SACL_SECURITY_INFORMATION) { + pnhead->sacl = pos; + memcpy(&buf[pos],&attr[offsacl],saclsz); + pos += saclsz; + } else + pnhead->sacl = 0; + + /* copy owner if requested */ + if (selection & OWNER_SECURITY_INFORMATION) { + pnhead->owner = pos; + memcpy(&buf[pos],&attr[offowner],usidsz); + pos += usidsz; + } else + pnhead->owner = 0; + + /* copy group if requested */ + if (selection & GROUP_SECURITY_INFORMATION) { + pnhead->group = pos; + memcpy(&buf[pos],&attr[offgroup],gsidsz); + pos += gsidsz; + } else + pnhead->group = 0; + if (pos != size) + ntfs_log_error("Error in security descriptor size\n"); + *psize = size; + ok = TRUE; + } + + 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 NON zero if successful (following Win32 conventions) + * + * BOOL WINAPI GetFileSecurity( + * __in LPCTSTR lpFileName, + * __in SECURITY_INFORMATION RequestedInformation, + * __out_opt PSECURITY_DESCRIPTOR pSecurityDescriptor, + * __in DWORD nLength, + * __out LPDWORD lpnLengthNeeded + * ); + * + */ + +uid_t getuid(void); +gid_t getgid(void); + +BOOL ntfs_get_file_security(struct SECURITY_API *scapi, + const char *path, u32 selection, + char *buf, u32 buflen, u32 *psize) +{ + ntfs_inode *ni; + char *attr; + BOOL ok; + + ok = FALSE; /* default return */ + if (scapi && (scapi->magic == MAGIC_API)) { + ni = ntfs_pathname_to_inode(scapi->security.vol, NULL, path); + if (ni) { + attr = getsecurityattr(&scapi->security, path, ni); + if (attr) { + ok = feedsecurityattr(attr,selection, + buf,buflen,psize); + free(attr); + } + ntfs_inode_close(ni); + } else + errno = ENOENT; + if (!ok) *psize = 0; + } else + errno = EINVAL; /* do not clear *psize */ + return (ok); +} + + +/* + * 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 NON zero if successful (following Win32 conventions) + * + * BOOL WINAPI SetFileSecurity( + * __in LPCTSTR lpFileName, + * __in SECURITY_INFORMATION SecurityInformation, + * __in PSECURITY_DESCRIPTOR pSecurityDescriptor + * ); + */ + +BOOL 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 ok; + + ok = FALSE; /* default return */ + if (scapi && (scapi->magic == MAGIC_API) && attr) { + phead = (const SECURITY_DESCRIPTOR_RELATIVE*)attr; + attrsz = attr_size(attr); +/* TODO should probably selectively merge into existing attr */ +/* for now we only enter new attributes */ + if (valid_securattr(attr, attrsz) + && (phead->control == selection)) { + ni = ntfs_pathname_to_inode(scapi->security.vol, + NULL, path); + if (ni) { + ok = !update_secur_descr(scapi->security.vol, + attr, ni); + ntfs_inode_close(ni); + } + } + } + return (ok); +} + + +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)) { + ni = ntfs_pathname_to_inode(scapi->security.vol, NULL, path); + if (ni) { + pos = 0; + ntfs_readdir(ni,&pos,context,callback); + ntfs_inode_close(ni); +ok = TRUE; /* clarification needed */ + } else + errno = ENOENT; + } else + errno = EINVAL; /* do not clear *psize */ + return (ok); +} + +/* + * Initializations before calling ntfs_get_file_security() + * ntfs_set_file_security() and ntfs_read_directory() + * + * Returns an (obscured) struct SECURITY_API* needed for further calls + */ + +struct SECURITY_API *ntfs_initialize_file_security(const char *device, + int flags) +{ + ntfs_volume *vol; + struct SECURITY_API *scapi; + struct SECURITY_CONTEXT *scx; + + scapi = (struct SECURITY_API*)NULL; + vol = ntfs_mount(device, flags); + if (vol) { + scapi = (struct SECURITY_API*) + ntfs_malloc(sizeof(struct SECURITY_API)); + if (scapi) { + scapi->magic = MAGIC_API; + scx = &scapi->security; + scx->vol = vol; + scx->uid = getuid(); + scx->gid = getgid(); + scx->pseccache = &scapi->seccache; + scx->vol->secure_flags = 0; + if (ntfs_build_mapping(scx) + || ntfs_open_secure(vol)) { + free(scapi); + scapi = (struct SECURITY_API*)NULL; + } + } else + errno = ENOMEM; + } + 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); }