#!/bin/bash
set -euo pipefail

#
# Copyright (c) 2016-2020 Samsung Electronics Co., Ltd. All rights reserved.
#
# This file is licensed under the terms of MIT License or the Apache License
# Version 2.0 of your choice. See the LICENSE.MIT file for MIT license details.
# See the LICENSE file or the notice below for Apache License Version 2.0
# details.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#

PATH=/bin:/usr/bin:/sbin:/usr/sbin
POLICY_PATH=/usr/share/security-manager/policy
PRIVILEGE_GROUP_MAPPING=$POLICY_PATH/privilege-group.list
PRIVILEGE_SYSTEMD_LIST=$POLICY_PATH/privilege-managed-by-systemd-for-daemons.list

DB_FILE=`tzplatform-get TZ_SYS_DB | cut -d= -f2`/.security-manager.db
SMACK_ENABLED=$(test "ON" != "" && echo true || echo false)

cynara_policies_to_set=''
# Usage: add_cynara_policy bucket client user privilege type [metadata]
function add_cynara_policy() {
    cynara_policies_to_set+="$1;$2;$3;$4;$5;${6:-}"$'\n'
}

function apply_cynara_policies() {
    cyad --set-policy --bulk=- <<< "${cynara_policies_to_set}"
    cynara_policies_to_set=''
}

# Create default buckets
while read bucket default_policy
do
    # Reuse the primary bucket for PRIVACY_MANAGER bucket
    [ "$bucket" = "PRIVACY_MANAGER" ] && bucket=""
    cyad --set-bucket="$bucket" --type="$default_policy"
done <<END
PRIVACY_MANAGER DENY
ADMIN NONE
APPDEFINED NONE
MAIN DENY
MANIFESTS_GLOBAL DENY
MANIFESTS_LOCAL DENY
END

# Link buckets together
while read bucket_src bucket_dst
do
    # Reuse the main bucket for PRIVACY_MANAGER bucket
    [ "$bucket_src" = "PRIVACY_MANAGER" ] && bucket_src=""
    add_cynara_policy "${bucket_src}" '*' '*' '*' BUCKET "${bucket_dst}"
done <<END
MAIN MANIFESTS_GLOBAL
PRIVACY_MANAGER MAIN
ADMIN APPDEFINED
END

apply_cynara_policies

# Import user-type policies
policy_files="$(find "$POLICY_PATH" -name "usertype-*.profile")"
while read -r file; do
    bucket="$(echo "${file}" | sed -r 's|.*/usertype-(.*).profile$|USER_TYPE_\1|' | tr '[:lower:]' '[:upper:]')"

    # Re-create the bucket with empty contents
    cyad --erase=$bucket --recursive=n --client='#' --user='#' --privilege='#'  >/dev/null 2>&1 || true
    cyad --set-bucket=$bucket --type=DENY

    # Link the bucket to ADMIN bucket
    add_cynara_policy "${bucket}" '*' '*' '*' BUCKET ADMIN

    file_contents="$(grep -v "^'" "${file}")"
    while read -r app privilege; do
        user="*"        # Match any user id
        policy="0xFFFF" # ALLOW (FIXME: cyad should parse policy names, not numeric values)
        add_cynara_policy "${bucket}" "${app}" "${user}" "${privilege}" "${policy}"
    done <<< "${file_contents}"
done <<< "${policy_files}"

# Non-application programs get access to all privileges...
if $SMACK_ENABLED; then
    for client in User System System::Privileged
    do
        add_cynara_policy MANIFESTS_GLOBAL "${client}" '*' '*' ALLOW
    done
else
    for uid in $(cut -d : -f 3 /etc/passwd); do
        # Non-aplication program UIDs are [0,5000), smack-enabled application UIDs are [5000,10000), no-smack app UIDs are >=10000
        if [ "$uid" -lt 10000 ]; then
            add_cynara_policy MANIFESTS_GLOBAL '*' "${uid}" '*' ALLOW
        fi
    done
fi

# ...except these that have their GIDs managed by systemd
privilege_systemd="$(grep -v "^#" "${PRIVILEGE_SYSTEMD_LIST}")"
while read privilege
do
    if $SMACK_ENABLED; then
        for client in User System System::Privileged
        do
            add_cynara_policy MANIFESTS_GLOBAL "${client}" '*' "${privilege}" DENY
        done
    else
        for uid in $(cut -d : -f 3 /etc/passwd); do
            # Non-aplication program UIDs are [0,5000), smack-enabled application UIDs are [5000,10000), no-smack app UIDs are >=10000
            if [ "$uid" -lt 10000 ] && [ "$uid" != 0 ]; then
                add_cynara_policy MANIFESTS_GLOBAL '*' "${uid}" "${privilege}" DENY
            fi
        done
    fi
done <<< "${privilege_systemd}"

# Root shell get access to all privileges
if $SMACK_ENABLED; then
    add_cynara_policy MANIFESTS_GLOBAL 'User::Shell' '0' '*' ALLOW
fi # Already done above in no-smack env

# @(kernel thread) can get access to internet privilege
add_cynara_policy MANIFESTS_GLOBAL '@' '*' 'http://tizen.org/privilege/internet' ALLOW

# Ensure applications can access standard devices
for priv in audio video display; do
    add_cynara_policy MANIFESTS_GLOBAL '*' '*' "http://tizen.org/privilege/internal/device/${priv}" ALLOW
    add_cynara_policy MANIFESTS_LOCAL '*' '*' "http://tizen.org/privilege/internal/device/${priv}" ALLOW
done

apply_cynara_policies

# Load privilege-group mappings
(
echo "BEGIN;"
echo "DELETE FROM privilege_group;"
grep -v '^#' "$PRIVILEGE_GROUP_MAPPING" |
while read privilege group
do
    echo "INSERT INTO privilege_group (privilege_name, group_name) VALUES ('$privilege', '$group');"
done
echo "COMMIT;"
) | sqlite3 "$DB_FILE"

# Migrate pkg_id, app_id and privileges for all apps already installed as if smack was enabled
if ! $SMACK_ENABLED; then
    cynara_buckets="$(cut -d ';' -f 1 /opt/var/cynara/db/buckets)"
    new_puid=10000 # New PUIDs start from this value
    function update_new_puid {
        taken_puids="$(sqlite3 "$DB_FILE" --batch <<< "SELECT pkg_id FROM pkg WHERE pkg_id>=$new_puid UNION SELECT app_id FROM app WHERE app_id>=$new_puid ORDER BY pkg_id")"
        if [[ "$taken_puids" != "" ]]; then
            while read -r taken_puid; do
                if [[ "$new_puid" == "$taken_puid" ]]; then
                    new_puid=$((new_puid + 1))
                    continue
                fi
                break;
            done <<< "$taken_puids"
        fi
    }

    # First remap every pkg_id
    pkg_ids="$(sqlite3 "$DB_FILE" --batch <<< 'SELECT pkg_id FROM pkg ORDER BY pkg_id')"
    if [[ "$pkg_ids" != "" ]]; then
        while read -r pkg_id; do
            if (( pkg_id < 10000 )); then
                update_new_puid
                new_pkg_id="$new_puid"
                echo "remapping pkg_id: $pkg_id -> $new_pkg_id"
                sqlite3 "$DB_FILE" --batch <<< "
                    BEGIN;
                    UPDATE pkg SET pkg_id=$new_pkg_id WHERE pkg_id=$pkg_id;
                    UPDATE app SET pkg_id=$new_pkg_id WHERE pkg_id=$pkg_id;
                    COMMIT;"
            fi
        done <<< "$pkg_ids"
    fi

    # Then remap every author_id
    author_ids="$(sqlite3 "$DB_FILE" --batch <<< 'SELECT author_id FROM author ORDER BY author_id')"
    if [[ "$author_ids" != "" ]]; then
        new_agid=20000 # New AGIDs start from this value
        while read -r author_id; do
            if (( author_id < 20000 )); then
                taken_agids="$(sqlite3 "$DB_FILE" --batch <<< "SELECT author_id FROM author WHERE author_id>=$new_agid ORDER BY author_id")"
                if [[ "$taken_agids" != "" ]]; then
                    while read -r taken_agid; do
                        if [[ "$new_agid" == "$taken_agid" ]]; then
                            new_agid=$((new_agid + 1))
                            continue
                        fi
                        break;
                    done <<< "$taken_agids"
                fi
                new_author_id="$new_agid"
                echo "remapping author_id: $author_id -> $new_author_id"
                sqlite3 "$DB_FILE" --batch <<< "
                    BEGIN;
                    UPDATE author SET author_id=$new_author_id WHERE author_id=$author_id;
                    UPDATE pkg SET author_id=$new_author_id WHERE author_id=$author_id;
                    COMMIT;"
            fi
        done <<< "$author_ids"
    fi

    # Then remap every app_id
    app_ids="$(sqlite3 "$DB_FILE" --batch <<< 'SELECT app.app_id, app.pkg_id, app.name, pkg.name, pkg.is_hybrid FROM app LEFT JOIN pkg USING (pkg_id) ORDER BY app_id')"
    if [[ "$app_ids" != "" ]]; then
        while IFS='|' read -r app_id pkg_id app_name pkg_name is_hybrid; do
            if (( app_id < 10000 )); then
                if [[ "$is_hybrid" == 1 ]]; then
                    echo "Found hybrid app $app_name from package $pkg_name"
                    exit 1
                fi
                echo ">>> migrating app $app_name from package $pkg_name"
                update_new_puid
                new_app_id="$new_puid"
                # Update app_id
                sqlite3 "$DB_FILE" --batch <<< "
                    BEGIN;
                    UPDATE app SET app_id=$new_app_id WHERE app_id=$app_id;
                    UPDATE user_app SET app_id=$new_app_id WHERE app_id=$app_id;
                    UPDATE app_defined_privilege SET app_id=$new_app_id WHERE app_id=$app_id;
                    UPDATE client_license SET app_id=$new_app_id WHERE app_id=$app_id;
                    COMMIT;"
                # Migrate cynara policies
                for bucket in $cynara_buckets; do
                    # TODO: --user='*' matches only '*', to match all values '#' needs to be used
                    # here and in --erase below, but currently UID-sandboxing does not support
                    # multiuser.
                    policies="$(cyad --list-policies="${bucket}" --client="User::Pkg::${pkg_name}" --user='*' --privilege='#')"
                    while IFS=';' read -r _bucket client user privilege type metadata && [[ -n "$_bucket" ]]; do
                        add_cynara_policy "${bucket}" 'User::Pkg::default_app_no_Smack_mode' "${pkg_id}" "${privilege}" "${type}" "${metadata}"
                    done <<< "${policies}"
                    # Erase old policies.
                    cyad --erase="${bucket}" --recursive=no --client="User::Pkg::${pkg_name}" --user="*" --privilege="#"
                done
            fi
        done <<< "$app_ids"
    fi

    apply_cynara_policies
fi
