root/src/add-ons/kernel/file_systems/ntfs/libntfs/efs.c
/**
 * efs.c - Limited processing of encrypted files
 *
 *      This module is part of ntfs-3g library
 *
 * Copyright (c)      2009 Martin Bene
 * Copyright (c)      2009-2010 Jean-Pierre Andre
 *
 * This program/include file is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as published
 * by the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program/include file is distributed in the hope that it will be
 * useful, but WITHOUT ANY WARRANTY; without even the implied warranty
 * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program (in the main directory of the NTFS-3G
 * distribution in the file COPYING); if not, write to the Free Software
 * Foundation,Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#ifdef HAVE_STDLIB_H
#include <stdlib.h>
#endif
#ifdef HAVE_ERRNO_H
#include <errno.h>
#endif
#ifdef HAVE_STRING_H
#include <string.h>
#endif
#ifdef HAVE_SYS_STAT_H
#include <sys/stat.h>
#endif

#ifdef HAVE_SYS_SYSMACROS_H
#include <sys/sysmacros.h>
#endif

#include "types.h"
#include "debug.h"
#include "attrib.h"
#include "inode.h"
#include "dir.h"
#include "efs.h"
#include "index.h"
#include "logging.h"
#include "misc.h"
#include "efs.h"
#include "xattrs.h"

static ntfschar logged_utility_stream_name[] = {
        const_cpu_to_le16('$'),
        const_cpu_to_le16('E'),
        const_cpu_to_le16('F'),
        const_cpu_to_le16('S'),
        const_cpu_to_le16(0)
} ;


/*
 *              Get the ntfs EFS info into an extended attribute
 */

int ntfs_get_efs_info(ntfs_inode *ni, char *value, size_t size)
{
        EFS_ATTR_HEADER *efs_info;
        s64 attr_size = 0;

        if (ni) {
                if (ni->flags & FILE_ATTR_ENCRYPTED) {
                        efs_info = (EFS_ATTR_HEADER*)ntfs_attr_readall(ni,
                                AT_LOGGED_UTILITY_STREAM,(ntfschar*)NULL, 0,
                                &attr_size);
                        if (efs_info
                            && (le32_to_cpu(efs_info->length) == attr_size)) {
                                if (attr_size <= (s64)size) {
                                        if (value)
                                                memcpy(value,efs_info,attr_size);
                                        else {
                                                errno = EFAULT;
                                                attr_size = 0;
                                        }
                                } else
                                        if (size) {
                                                errno = ERANGE;
                                                attr_size = 0;
                                        }
                                free (efs_info);
                        } else {
                                if (efs_info) {
                                        free(efs_info);
                                        ntfs_log_error("Bad efs_info for inode %lld\n",
                                                (long long)ni->mft_no);
                                } else {
                                        ntfs_log_error("Could not get efsinfo"
                                                " for inode %lld\n",
                                                (long long)ni->mft_no);
                                }
                                errno = EIO;
                                attr_size = 0;
                        }
                } else {
                        errno = ENODATA;
                        ntfs_log_trace("Inode %lld is not encrypted\n",
                                (long long)ni->mft_no); 
                }
        }
        return (attr_size ? (int)attr_size : -errno);
}

/*
 *              Fix all encrypted AT_DATA attributes of an inode
 *
 *      The fix may require making an attribute non resident, which
 *      requires more space in the MFT record, and may cause some
 *      attribute to be expelled and the full record to be reorganized.
 *      When this happens, the search for data attributes has to be
 *      reinitialized.
 *
 *      Returns zero if successful.
 *              -1 if there is a problem.
 */

static int fixup_loop(ntfs_inode *ni)
{
        ntfs_attr_search_ctx *ctx;
        ntfs_attr *na;
        ATTR_RECORD *a;
        BOOL restart;
        int cnt;
        int maxcnt;
        int res = 0;

        maxcnt = 0;
        do {
                restart = FALSE;
                ctx = ntfs_attr_get_search_ctx(ni, NULL);
                if (!ctx) {
                        ntfs_log_error("Failed to get ctx for efs\n");
                        res = -1;
                }
                cnt = 0;
                while (!restart && !res
                        && !ntfs_attr_lookup(AT_DATA, NULL, 0, 
                                   CASE_SENSITIVE, 0, NULL, 0, ctx)) {
                        cnt++;
                        a = ctx->attr;
                        na = ntfs_attr_open(ctx->ntfs_ino, AT_DATA,
                                (ntfschar*)((u8*)a + le16_to_cpu(a->name_offset)),
                                a->name_length);
                        if (!na) {
                                ntfs_log_error("can't open DATA Attribute\n");
                                res = -1;
                        }
                        if (na && !(ctx->attr->flags & ATTR_IS_ENCRYPTED)) {
                                if (!NAttrNonResident(na)
                                   && ntfs_attr_make_non_resident(na, ctx)) {
                                /*
                                 * ntfs_attr_make_non_resident fails if there
                                 * is not enough space in the MFT record.
                                 * When this happens, force making non-resident
                                 * so that some other attribute is expelled.
                                 */
                                        if (ntfs_attr_force_non_resident(na)) {
                                                res = -1;
                                        } else {
                                        /* make sure there is some progress */
                                                if (cnt <= maxcnt) {
                                                        errno = EIO;
                                                        ntfs_log_error("Multiple failure"
                                                                " making non resident\n");
                                                        res = -1;
                                                } else {
                                                        ntfs_attr_put_search_ctx(ctx);
                                                        ctx = (ntfs_attr_search_ctx*)NULL;
                                                        restart = TRUE;
                                                        maxcnt = cnt;
                                                }
                                        }
                                }
                                if (!restart && !res
                                    && ntfs_efs_fixup_attribute(ctx, na)) {
                                        ntfs_log_error("Error in efs fixup of AT_DATA Attribute\n");
                                        res = -1;
                                }
                        }
                if (na)
                        ntfs_attr_close(na);
                }
        } while (restart && !res);
        if (ctx)
                ntfs_attr_put_search_ctx(ctx);
        return (res);
}

/*
 *              Set the efs data from an extended attribute
 *      Warning : the new data is not checked
 *      Returns 0, or -1 if there is a problem
 */

int ntfs_set_efs_info(ntfs_inode *ni, const char *value, size_t size,
                        int flags)
                        
{
        int res;
        int written;
        ntfs_attr *na;
        const EFS_ATTR_HEADER *info_header;

        res = 0;
        if (ni && value && size) {
                if (ni->flags & (FILE_ATTR_ENCRYPTED | FILE_ATTR_COMPRESSED)) {
                        if (ni->flags & FILE_ATTR_ENCRYPTED) {
                                ntfs_log_trace("Inode %lld already encrypted\n",
                                                (long long)ni->mft_no);
                                errno = EEXIST;
                        } else {
                                /*
                                 * Possible problem : if encrypted file was
                                 * restored in a compressed directory, it was
                                 * restored as compressed.
                                 * TODO : decompress first.
                                 */
                                ntfs_log_error("Inode %lld cannot be encrypted and compressed\n",
                                        (long long)ni->mft_no);
                                errno = EIO;
                        }
                        return -1;
                }
                info_header = (const EFS_ATTR_HEADER*)value;
                        /* make sure we get a likely efsinfo */
                if (le32_to_cpu(info_header->length) != size) {
                        errno = EINVAL;
                        return (-1);
                }
                if (!ntfs_attr_exist(ni,AT_LOGGED_UTILITY_STREAM,
                                (ntfschar*)NULL,0)) {
                        if (!(flags & XATTR_REPLACE)) {
                        /*
                         * no logged_utility_stream attribute : add one,
                         * apparently, this does not feed the new value in
                         */
                                res = ntfs_attr_add(ni,AT_LOGGED_UTILITY_STREAM,
                                        logged_utility_stream_name,4,
                                        (u8*)NULL,(s64)size);
                        } else {
                                errno = ENODATA;
                                res = -1;
                        }
                } else {
                        errno = EEXIST;
                        res = -1;
                }
                if (!res) {
                        /*
                         * open and update the existing efs data
                         */
                        na = ntfs_attr_open(ni, AT_LOGGED_UTILITY_STREAM,
                                logged_utility_stream_name, 4);
                        if (na) {
                                /* resize attribute */
                                res = ntfs_attr_truncate(na, (s64)size);
                                /* overwrite value if any */
                                if (!res && value) {
                                        written = (int)ntfs_attr_pwrite(na,
                                                 (s64)0, (s64)size, value);
                                        if (written != (s64)size) {
                                                ntfs_log_error("Failed to "
                                                        "update efs data\n");
                                                errno = EIO;
                                                res = -1;
                                        }
                                }
                                ntfs_attr_close(na);
                        } else
                                res = -1;
                }
                if (!res) {
                        /* Don't handle AT_DATA Attribute(s) if inode is a directory */
                        if (!(ni->mrec->flags & MFT_RECORD_IS_DIRECTORY)) {
                                /* iterate over AT_DATA attributes */
                                /* set encrypted flag, truncate attribute to match padding bytes */
                        
                        if (fixup_loop(ni))
                                return -1;
                        }
                        ni->flags |= FILE_ATTR_ENCRYPTED;
                        NInoSetDirty(ni);
                        NInoFileNameSetDirty(ni);
                }
        } else {
                errno = EINVAL;
                res = -1;
        }
        return (res ? -1 : 0);
}

/*
 *              Fixup raw encrypted AT_DATA Attribute
 *     read padding length from last two bytes
 *     truncate attribute, make non-resident,
 *     set data size to match padding length
 *     set ATTR_IS_ENCRYPTED flag on attribute 
 *
 *      Return 0 if successful
 *              -1 if failed (errno tells why)
 */

int ntfs_efs_fixup_attribute(ntfs_attr_search_ctx *ctx, ntfs_attr *na) 
{
        s64 newsize;
        s64 oldsize;
        le16 appended_bytes;
        u16 padding_length;
        ntfs_inode *ni;
        BOOL close_ctx = FALSE;

        if (!na) {
                ntfs_log_error("no na specified for efs_fixup_attribute\n");
                goto err_out;
        }
        if (!ctx) {
                ctx = ntfs_attr_get_search_ctx(na->ni, NULL);
                if (!ctx) {
                        ntfs_log_error("Failed to get ctx for efs\n");
                        goto err_out;
                }
                close_ctx = TRUE;
                if (ntfs_attr_lookup(AT_DATA, na->name, na->name_len, 
                                CASE_SENSITIVE, 0, NULL, 0, ctx)) {
                        ntfs_log_error("attr lookup for AT_DATA attribute failed in efs fixup\n");
                        goto err_out;
                }
        } else {
                if (!NAttrNonResident(na)) {
                        ntfs_log_error("Cannot make non resident"
                                " when a context has been allocated\n");
                        goto err_out;
                }
        }

                /* no extra bytes are added to void attributes */
        oldsize = na->data_size;
        if (oldsize) {
                /* make sure size is valid for a raw encrypted stream */
                if ((oldsize & 511) != 2) {
                        ntfs_log_error("Bad raw encrypted stream\n");
                        goto err_out;
                }
                /* read padding length from last two bytes of attribute */
                if (ntfs_attr_pread(na, oldsize - 2, 2, &appended_bytes) != 2) {
                        ntfs_log_error("Error reading padding length\n");
                        goto err_out;
                }
                padding_length = le16_to_cpu(appended_bytes);
                if (padding_length > 511 || padding_length > na->data_size-2) {
                        errno = EINVAL;
                        ntfs_log_error("invalid padding length %d for data_size %lld\n",
                                 padding_length, (long long)oldsize);
                        goto err_out;
                }
                newsize = oldsize - padding_length - 2;
                /*
                 * truncate attribute to possibly free clusters allocated 
                 * for the last two bytes, but do not truncate to new size
                 * to avoid losing useful data
                 */
                if (ntfs_attr_truncate(na, oldsize - 2)) {
                        ntfs_log_error("Error truncating attribute\n");
                        goto err_out;
                }
        } else
                newsize = 0;

        /*
         * Encrypted AT_DATA Attributes MUST be non-resident
         * This has to be done after the attribute is resized, as
         * resizing down to zero may cause the attribute to be made
         * resident.
         */
        if (!NAttrNonResident(na)
            && ntfs_attr_make_non_resident(na, ctx)) {
                if (!close_ctx
                    || ntfs_attr_force_non_resident(na)) {
                        ntfs_log_error("Error making DATA attribute non-resident\n");
                        goto err_out;
                } else {
                        /*
                         * must reinitialize context after forcing
                         * non-resident. We need a context for updating
                         * the state, and at this point, we are sure
                         * the context is not used elsewhere.
                         */
                        ntfs_attr_reinit_search_ctx(ctx);
                        if (ntfs_attr_lookup(AT_DATA, na->name, na->name_len, 
                                        CASE_SENSITIVE, 0, NULL, 0, ctx)) {
                                ntfs_log_error("attr lookup for AT_DATA attribute failed in efs fixup\n");
                                goto err_out;
                        }
                }
        }
        ni = na->ni;
        if (!na->name_len) {
                ni->data_size = newsize;
                ni->allocated_size = na->allocated_size;
        }
        NInoSetDirty(ni);
        NInoFileNameSetDirty(ni);

        ctx->attr->data_size = cpu_to_sle64(newsize);
        if (sle64_to_cpu(ctx->attr->initialized_size) > newsize)
                ctx->attr->initialized_size = ctx->attr->data_size;
        ctx->attr->flags |= ATTR_IS_ENCRYPTED;
        if (close_ctx)
                ntfs_attr_put_search_ctx(ctx);
                
        return (0);
err_out:
        if (close_ctx && ctx)
                ntfs_attr_put_search_ctx(ctx);
        return (-1);
}