/*
 * Copyright (c) Huawei Technologies Co., Ltd. 2018-2023. All rights reserved.
 * Licensed under the Mulan PSL v2.
 * You can use this software according to the terms and conditions of the Mulan PSL v2.
 * You may obtain a copy of Mulan PSL v2 at:
 *     http://license.coscl.org.cn/MulanPSL2
 * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR
 * PURPOSE.
 * See the Mulan PSL v2 for more details.
 */

#include "tee_auth_common.h"
#include <stdbool.h>
#include <errno.h>
#include "tee_client_constants.h"
#include "securec.h"
#include "tee_log.h"

#ifdef LOG_TAG
#undef LOG_TAG
#endif
#define LOG_TAG "teecd_auth"

#define DELIM_COUNT 6U
#define NAME_POS    0U
#define UID_POS     1U

#define DECIMAL 10
static int UidCompare(unsigned int caUid, const char *uidString, size_t uidLen)
{
    size_t i = 0;
    size_t uidNum = 0;

    // convert uid string to integer
    for (; i < uidLen; i++) {
        bool is_number = uidString[i] >= '0' && uidString[i] <= '9';
        if (!is_number) {
            tloge("passwd info wrong format: uid missing\n");
            return -1;
        }
        uidNum = DECIMAL * uidNum + (size_t)(uidString[i] - '0');
    }

    if (uidNum == caUid) {
        return 0;
    }

    return -1;
}

static int SplitFields(int delimIndices[], const char *userName, int nameBufLen)
{
    int i = 0;
    unsigned int count = 0;

    for (; i < nameBufLen && userName[i] != '\0'; i++) {
        if (count > DELIM_COUNT) {
            tloge("passwd info wrong format: extra field\n");
            return -1;
        }
        if (userName[i] == ':') {
            delimIndices[count] = i;
            count++;
        }
    }

    return 0;
}

static int ParseUserName(unsigned int caUid, const char *userName, int nameBufLen)
{
    int delimIndices[DELIM_COUNT + 1] = { 0 };

    int ret = SplitFields(delimIndices, userName, nameBufLen);
    if (ret != 0) {
        return ret;
    }

    const char *uidString = userName + delimIndices[UID_POS] + 1;
    int uidLen = delimIndices[UID_POS + 1] - delimIndices[UID_POS] - 1;

    if (UidCompare(caUid, uidString, uidLen) == 0) {
        return delimIndices[NAME_POS];
    }

    return -1;
}

/* get username by uid,
 * on linux, user info is stored in system file "/proc/<pid>/root/etc/passwd",
 * each line represents a user, fields are separated by ':',
 * formatted as such: "username:[encrypted password]:uid:gid:[comments]:home directory:login shell"
 */
int TeeGetUserName(int caPid, unsigned int caUid, char *userName, size_t nameBufLen)
{
    int i;
    char path[MAX_PATH_LENGTH] = { 0 };

    int ret = snprintf_s(path, sizeof(path), sizeof(path) - 1, "/proc/%d/root/etc/passwd", caPid);
    if (ret == -1) {
        tloge("get passwd filename failed\n");
        return -1;
    }

    FILE *fd = fopen(path, "r");
    if (fd == NULL) {
        tloge("open passwd file failed\n");
        return -1;
    }

    // fgets will append a '\0' to userName, no need to memset it every time
    while (fgets(userName, nameBufLen - 1, fd) != NULL) {
        int userNameLen = ParseUserName(caUid, userName, (int)nameBufLen);
        if (userNameLen != -1) {
            // erase the buffer after username
            for (i = userNameLen; i < (int)nameBufLen; i++) {
                userName[i] = '\0';
            }
            fclose(fd);
            return 0;
        }
    }

    (void)fclose(fd);
    return -1;
}

// locate pkgname start index
static int LocatePkgName(const char *cmdLine, size_t cmdLen)
{
    int rIndex = (int)cmdLen - 1;
    int nextArgStart = -1;
    char *target_str = "-jar";
    int target_len = strlen(target_str);

    while (rIndex >= 0) {
        while (rIndex >= 0 && cmdLine[rIndex] == '\0') {
            rIndex--;
        }
        if (rIndex < 0)
            break;

        int argEnd = rIndex;
        while (rIndex >= 0 && cmdLine[rIndex] != '\0') {
            rIndex--;
        }
        int argStart = rIndex + 1;
        int argLen = argEnd - argStart + 1;

        if (argLen == target_len && memcmp(cmdLine + argStart, target_str, target_len) == 0) {
            return nextArgStart;
        }

        nextArgStart = argStart;
    }

    return -1;
}

/* fd is valid, cmdLine won't be NULL, already checked in TeeGetCaName
 * cmdline format looks like this:
 * "java option1 option2 ... optionn -jar com.company.module.app"
 * we need to extract the package name "com.company.module.app" from it
 */
#define JAVA_CMD_MIN_INDEX 3
#define JAVA_OFFSET_3      3
#define JAVA_OFFSET_2      2
#define JAVA_OFFSET_1      1
static int ParsePkgName(const char *cmdLine, size_t cmdLen, char *caName, size_t nameLen)
{
    // there will be a '\0' at the end of this string
    int rIndex = (int)strnlen(caName, nameLen) - 1;
    bool is_java_cmd = rIndex >= JAVA_CMD_MIN_INDEX &&
                       caName[rIndex - JAVA_OFFSET_3] == 'j' &&
                       caName[rIndex - JAVA_OFFSET_2] == 'a' &&
                       caName[rIndex - JAVA_OFFSET_1] == 'v' &&
                       caName[rIndex]                 == 'a';
    if (!is_java_cmd) {
        return 0;
    }

    rIndex = LocatePkgName(cmdLine, cmdLen);
    if (rIndex < 0) {
        tlogw("locate pkg name failed,use default caname:%s.\n", caName);
        return 0;
    }
    int pkgLen = strnlen(cmdLine + rIndex, cmdLen - rIndex);

    errno_t ret = strncpy_s(caName, nameLen - 1, cmdLine + rIndex, pkgLen);
    if (ret != EOK) {
        tloge("copy caName failed\n");
        return -1;
    }

    // erase the buffer after pkgname
    for (; pkgLen < (int)nameLen; pkgLen++) {
        caName[pkgLen] = '\0';
    }

    return 0;
}

static int ReadCmdLine(const char *path, char *buffer, size_t bufferLen, char *caName, size_t nameLen)
{
    FILE *fd = fopen(path, "rb");
    if (fd == NULL) {
        tloge("fopen is error: %d\n", errno);
        return -1;
    }
    int bytesRead = (int)fread(buffer, sizeof(char), bufferLen - 1, fd);
    bool readError = (bytesRead <= 0 || (ferror(fd) != 0));
    if (readError) {
        tloge("cannot read from cmdline\n");
        fclose(fd);
        return -1;
    }
    (void)fclose(fd);

    if (caName == NULL || nameLen == 0)
        return bytesRead;

    size_t firstStringLen = strnlen(buffer, bufferLen - 1);
    errno_t res = strncpy_s(caName, nameLen - 1, buffer, firstStringLen);
    if (res != EOK) {
        tloge("copy caName failed\n");
        return -1;
    }

    return bytesRead;
}

/*
 * this file "/proc/pid/cmdline" can be modified by any user,
 * so the package name we get from it is not to be trusted,
 * the CA authentication strategy does not rely much on the pkgname,
 * this is mainly to make it compatible with POHNE_PLATFORM
 */
static int TeeGetCaName(int caPid, char *caName, size_t nameLen)
{
    char path[MAX_PATH_LENGTH] = { 0 };
    char temp[CMD_MAX_SIZE] = { 0 };

    if (caName == NULL || nameLen == 0) {
        tloge("input :caName invalid\n");
        return -1;
    }

    int ret = snprintf_s(path, sizeof(path), sizeof(path) - 1, "/proc/%d/cmdline", caPid);
    if (ret == -1) {
        tloge("tee get ca name snprintf_s err\n");
        return ret;
    }

    int bytesRead = ReadCmdLine(path, temp, CMD_MAX_SIZE, caName, nameLen);

    bool stat = bytesRead <= 0 || ParsePkgName(temp, bytesRead, caName, nameLen) != 0;
    if (stat) {
        tloge("parse package name from cmdline failed\n");
        return -1;
    }

    return bytesRead;
}

int TeeGetPkgName(int caPid, char *path, size_t pathLen)
{
    if (path == NULL || pathLen > MAX_PATH_LENGTH) {
        tloge("path is null or path len overflows\n");
        return -1;
    }

    if (TeeGetCaName(caPid, path, pathLen) < 0) {
        tloge("get ca name failed\n");
        return -1;
    }

    if (strncmp(path, MEDIA_CODEC_PATH, strlen(MEDIA_CODEC_PATH) + 1) == 0) {
        int ret = snprintf_s(path, pathLen, strlen(OMX_PATH), OMX_PATH);
        if (ret < 0) {
            tloge("copy omx path failed");
            return ret;
        }
    }

    return 0;
}

#ifdef CONFIG_TA_GET_CA_CMDLINE
static void parse_cmdline(char *str, int len)
{
    for (int i = 0; i < len - 1; ++i) {
        if (str[i] == 0) {
            str[i] = ' ';
        }
    }
}

int TeeGetCaCmdline(int caPid, uint8_t *buffer, uint32_t *len)
{
    char path[MAX_PATH_LENGTH] = { 0 };
    char cmdline[MAX_CMDLINE_LENGTH] = { 0 };
    uint32_t freeLen = 0;

    if (buffer == NULL || len == NULL || *len < MAX_CMDLINE_LENGTH + sizeof(uint32_t)) {
        tloge("buffer is null or len invalid\n");
        return -1;
    }
    freeLen = *len;

    int ret = snprintf_s(path, sizeof(path), sizeof(path) - 1, "/proc/%d/cmdline", caPid);
    if (ret == -1) {
        tloge("tee get ca cmdline snprintf_s err %d\n", ret);
        return ret;
    }

    int bytesRead = ReadCmdLine(path, cmdline, MAX_CMDLINE_LENGTH, NULL, 0);
    if (bytesRead <= 0) {
        tloge("tee get ca cmdline err\n");
        return -1;
    }
    parse_cmdline(cmdline, bytesRead);

    ret = memcpy_s(buffer, freeLen, &bytesRead, sizeof(uint32_t));
    if (ret != EOK) {
        tloge("copy cmdline length failed, err %d\n", ret);
        return ret;
    }

    buffer  += sizeof(uint32_t);
    freeLen -= (uint32_t)sizeof(bytesRead);

    ret = memcpy_s(buffer, freeLen, &cmdline, bytesRead);
    if (ret != EOK) {
        tloge("copy cmdline failed, err %d\n", ret);
        return ret;
    }
    *len = (uint32_t)bytesRead + (uint32_t)sizeof(bytesRead);

    tlogd("get ca cmdline len %d, str: %s\n", bytesRead, (char *)cmdline);
    return 0;
}
#endif

static int TeeCheckCaPath(unsigned int uid, int pid, const char *auth_ctx)
{
    char path[MAX_PATH_LENGTH] = { 0 };
    char str_path_uid[BUF_MAX_SIZE] = { 0 };

    if (auth_ctx == NULL) {
        tloge("bad params\n");
        return TEEC_ERROR_ACCESS_DENIED;
    }

    size_t auth_ctx_len = strnlen(auth_ctx, MAX_PATH_LENGTH);
    if (auth_ctx_len == 0 || auth_ctx_len >= MAX_PATH_LENGTH) {
        tloge("invalid path context\n");
        return TEEC_ERROR_ACCESS_DENIED;
    }

    if (TeeGetCaName(pid, path, sizeof(path)) < 0) {
        tloge("get ca name failed\n");
        return TEEC_ERROR_ACCESS_DENIED;
    }

    if (snprintf_s((char *)str_path_uid, sizeof(str_path_uid), sizeof(str_path_uid) - 1, "%s:%u", path, uid) == -1) {
        tloge("snprintf_s failed!\n");
        return TEEC_ERROR_ACCESS_DENIED;
    }

    if (strnlen(str_path_uid, BUF_MAX_SIZE) != auth_ctx_len || strncmp(str_path_uid, auth_ctx, auth_ctx_len) != 0) {
        tloge("check path failed\n");
        return TEEC_ERROR_ACCESS_DENIED;
    }

    return 0;
}

int TeeCheckHidlAuth(unsigned int uid, int pid)
{
    int ret = TeeCheckCaPath(uid, pid, CA_HIDL_PATH_UID_AUTH_CTX);
    if (ret != 0) {
        tloge("check hidl path failed, ret %d\n", ret);
        return ret;
    }

    return 0;
}