/*
 * Copyright (c) 2004, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code 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
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */

#include <windows.h>
#include <malloc.h>
#include <string.h>

#include "jni.h"
#include "jni_util.h"
#include "sun_management_FileSystemImpl.h"

/*
 * Access mask to represent any file access
 */
#define ANY_ACCESS (FILE_GENERIC_READ | FILE_GENERIC_WRITE | FILE_GENERIC_EXECUTE)

/*
 * Returns JNI_TRUE if the specified file is on a file system that supports
 * persistent ACLs (On NTFS file systems returns true, on FAT32 file systems
 * returns false).
 */
static jboolean isSecuritySupported(JNIEnv* env, const char* path) {
    char* root;
    char* p;
    BOOL res;
    DWORD dwMaxComponentLength;
    DWORD dwFlags;
    char fsName[128];
    DWORD fsNameLength;

    /*
     * Get root directory. Assume that files are absolute paths. For UNCs
     * the slash after the share name is required.
     */
    root = strdup(path);
    if (*root == '\\') {
        /*
         * \\server\share\file ==> \\server\share\
         */
        int slashskip = 3;
        p = root;
        while ((*p == '\\') && (slashskip > 0)) {
            char* p2;
            p++;
            p2 = strchr(p, '\\');
            if ((p2 == NULL) || (*p2 != '\\')) {
                free(root);
                JNU_ThrowIOException(env, "Malformed UNC");
                return JNI_FALSE;
            }
            p = p2;
            slashskip--;
        }
        if (slashskip != 0) {
            free(root);
            JNU_ThrowIOException(env, "Malformed UNC");
            return JNI_FALSE;
        }
        p++;
        *p = '\0';

    } else {
        p = strchr(root, '\\');
        if (p == NULL) {
            free(root);
            JNU_ThrowIOException(env, "Absolute filename not specified");
            return JNI_FALSE;
        }
        p++;
        *p = '\0';
    }


    /*
     * Get the volume information - this gives us the file system file and
     * also tells us if the file system supports persistent ACLs.
     */
    fsNameLength = sizeof(fsName)-1;
    res = GetVolumeInformation(root,
                               NULL,        // address of name of the volume, can be NULL
                               0,           // length of volume name
                               NULL,        // address of volume serial number, can be NULL
                               &dwMaxComponentLength,
                               &dwFlags,
                               fsName,
                               fsNameLength);
    if (res == 0) {
        free(root);
        JNU_ThrowIOExceptionWithLastError(env, "GetVolumeInformation failed");
        return JNI_FALSE;
    }

    free(root);
    return (dwFlags & FS_PERSISTENT_ACLS) ? JNI_TRUE : JNI_FALSE;
}


/*
 * Returns the security descriptor for a file.
 */
static SECURITY_DESCRIPTOR* getFileSecurityDescriptor(JNIEnv* env, const char* path) {
    SECURITY_DESCRIPTOR* sd;
    DWORD len = 0;
    SECURITY_INFORMATION info =
        OWNER_SECURITY_INFORMATION | DACL_SECURITY_INFORMATION;

    GetFileSecurityA(path, info , 0, 0, &len);
    if (GetLastError() != ERROR_INSUFFICIENT_BUFFER) {
        JNU_ThrowIOExceptionWithLastError(env, "GetFileSecurity failed");
        return NULL;
    }
    sd = (SECURITY_DESCRIPTOR *)malloc(len);
    if (sd == NULL) {
        JNU_ThrowOutOfMemoryError(env, 0);
    } else {
        if (!(*GetFileSecurityA)(path, info, sd, len, &len)) {
            JNU_ThrowIOExceptionWithLastError(env, "GetFileSecurity failed");
            free(sd);
            return NULL;
        }
    }
    return sd;
}

/*
 * Returns pointer to the SID identifying the owner of the specified
 * file.
 */
static SID* getFileOwner(JNIEnv* env, SECURITY_DESCRIPTOR* sd) {
    SID* owner;
    BOOL defaulted;

    if (!GetSecurityDescriptorOwner(sd, &owner, &defaulted)) {
        JNU_ThrowIOExceptionWithLastError(env, "GetSecurityDescriptorOwner failed");
        return NULL;
    }
    return owner;
}

/*
 * Returns pointer discretionary access-control list (ACL) from the security
 * descriptor of the specified file.
 */
static ACL* getFileDACL(JNIEnv* env, SECURITY_DESCRIPTOR* sd) {
    ACL *acl;
    int defaulted, present;

    if (!GetSecurityDescriptorDacl(sd, &present, &acl, &defaulted)) {
        JNU_ThrowIOExceptionWithLastError(env, "GetSecurityDescriptorDacl failed");
        return NULL;
    }
    if (!present) {
        JNU_ThrowInternalError(env, "Security descriptor does not contain a DACL");
        return NULL;
    }
    return acl;
}

/*
 * Returns JNI_TRUE if the specified owner is the only SID will access
 * to the file.
 */
static jboolean isAccessUserOnly(JNIEnv* env, SID* owner, ACL* acl) {
    ACL_SIZE_INFORMATION acl_size_info;
    DWORD i;

    /*
     * If there's no DACL then there's no access to the file
     */
    if (acl == NULL) {
        return JNI_TRUE;
    }

    /*
     * Get the ACE count
     */
    if (!GetAclInformation(acl, (void *) &acl_size_info, sizeof(acl_size_info),
                           AclSizeInformation)) {
        JNU_ThrowIOExceptionWithLastError(env, "GetAclInformation failed");
        return JNI_FALSE;
    }

    /*
     * Iterate over the ACEs. For each "allow" type check that the SID
     * matches the owner, and check that the access is read only.
     */
    for (i = 0; i < acl_size_info.AceCount; i++) {
        void* ace;
        ACCESS_ALLOWED_ACE *access;
        SID* sid;

        if (!GetAce(acl, i, &ace)) {
            JNU_ThrowIOExceptionWithLastError(env, "GetAce failed");
            return -1;
        }
        if (((ACCESS_ALLOWED_ACE *)ace)->Header.AceType != ACCESS_ALLOWED_ACE_TYPE) {
            continue;
        }
        access = (ACCESS_ALLOWED_ACE *)ace;
        sid = (SID *) &access->SidStart;
        if (!EqualSid(owner, sid)) {
            /*
             * If the ACE allows any access then the file is not secure.
             */
            if (access->Mask & ANY_ACCESS) {
                return JNI_FALSE;
            }
        }
    }
    return JNI_TRUE;
}


/*
 * Class:     sun_management_FileSystemImpl
 * Method:    init0
 * Signature: ()V
 */
JNIEXPORT void JNICALL Java_sun_management_FileSystemImpl_init0
  (JNIEnv *env, jclass ignored)
{
        /* nothing to do */
}

/*
 * Class:     sun_management_FileSystemImpl
 * Method:    isSecuritySupported0
 * Signature: (Ljava/lang/String;)Z
 */
JNIEXPORT jboolean JNICALL Java_sun_management_FileSystemImpl_isSecuritySupported0
  (JNIEnv *env, jclass ignored, jstring str)
{
    jboolean res;
    jboolean isCopy;
    const char* path;

    path = JNU_GetStringPlatformChars(env, str, &isCopy);
    if (path != NULL) {
        res = isSecuritySupported(env, path);
        if (isCopy) {
            JNU_ReleaseStringPlatformChars(env, str, path);
        }
        return res;
    } else {
        /* exception thrown - doesn't matter what we return */
        return JNI_TRUE;
    }
}


/*
 * Class:     sun_management_FileSystemImpl
 * Method:    isAccessUserOnly0
 * Signature: (Ljava/lang/String;)Z
 */
JNIEXPORT jboolean JNICALL Java_sun_management_FileSystemImpl_isAccessUserOnly0
  (JNIEnv *env, jclass ignored, jstring str)
{
    jboolean res = JNI_FALSE;
    jboolean isCopy;
    const char* path;

    path = JNU_GetStringPlatformChars(env, str, &isCopy);
    if (path != NULL) {
        /*
         * From the security descriptor get the file owner and
         * DACL. Then check if anybody but the owner has access
         * to the file.
         */
        SECURITY_DESCRIPTOR* sd = getFileSecurityDescriptor(env, path);
        if (sd != NULL) {
            SID *owner = getFileOwner(env, sd);
            if (owner != NULL) {
                ACL* acl = getFileDACL(env, sd);
                if (acl != NULL) {
                    res = isAccessUserOnly(env, owner, acl);
                } else {
                    /*
                     * If acl is NULL it means that an exception was thrown
                     * or there is "all acess" to the file.
                     */
                    res = JNI_FALSE;
                }
            }
            free(sd);
        }
        if (isCopy) {
            JNU_ReleaseStringPlatformChars(env, str, path);
        }
    }
    return res;
}
