/*
 * Copyright (C) 2020 Uniontech Technology Co., Ltd.
 *
 * Author:     xinbo wang <wangxinbo@uniontech.com>
 *
 * Maintainer: xinbo wang <wangxinbo@uniontech.com>
 *
 * This program 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 3 of the License, or
 * any later version.
 *
 * This program 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.  If not, see <http://www.gnu.org/licenses/>.
 */

#include <stdio.h>
#include <string.h>
#include <stdbool.h>
#include <fcntl.h>

#include "uace.h"

#ifdef HAVE_DIX_CONFIG_H
#include <dix-config.h>
#endif

#include "misc.h"
#include "os.h"
#include "dixstruct.h"
#include "extnsionst.h"
#include "scrnintstr.h"

#include "xorg-version.h"
#include <xorg/xacestr.h>
#include <xorg/window.h>

#include "pixmap.h"
#include "pixmapstr.h"

#include <X11/Xatom.h>

#include "./ext/uaceExt.h"

#define FIFO_READ "/tmp/dtkdisplay_ace_read"
#define FIFO_WRITE "/tmp/dtkdisplay_ace_write"

static int ProcUACEDispatch(ClientPtr client);
static int SProcUACEDispatch(ClientPtr client);
static void uaceSelection(CallbackListPtr *pcbl, void *unused, void *calldata);

ClientPtr m_client = 0;
int m_windowId = 0;

// the client spying selection
typedef struct _spyclient{
    ClientPtr client;
    int read_fd;
    int write_fd;
}SpyClientInfo;

typedef struct _CheckPermission{
    int type;
    int sourcePID;
    int targetPID;    
}SelectionPermissionCheck;

SpyClientInfo spyClientInfo = {0};

//Display *m_pDisplay = 0;

static int uaceEventBase = 0;
#define SelectionNotify		    1

static bool uaceInit(void)
{
    bool bRet = false;
    bRet = AddCallback(&ClientStateCallback, uaceClientStateChange, 0);
#ifdef XACE
    bRet &= XaceRegisterCallback(XACE_RESOURCE_ACCESS, uaceResourcecallback, NULL);
    bRet &= XaceRegisterCallback(XACE_SELECTION_ACCESS, uaceSelection, NULL);

#endif
    return bRet;
}

void uaceExtensionInit(void)
{
    LogMessage(X_INFO, "uace init extension.\n");

    ExtensionEntry *extEntry;

    if (uaceInit() != TRUE) {
      LogMessage(X_INFO, "Failed to register one or more callbacks on Xace.\n");
    }

    extEntry = AddExtension(UACE_EXTENSION_NAME,  // 插件的名称
                            UACE_NUMBER_EVENTS,   // 为扩展保留的事件数
                            UACE_NUMBER_ERRORS,   // 为扩展保留的错误码数
                            ProcUACEDispatch,     // 扩展的处理函数
                            SProcUACEDispatch,    // 扩展的处理函数,在处理前先交换字节顺序
                            UACEResetProc,        // 扩展的析构函数
                            StandardMinorOpcode); // 用来得到子处理号,一般没有什么用处,在出错时,设置到错误信息里

    uaceEventBase = extEntry->eventBase;


    if (!extEntry) {
        LogMessage(X_INFO, "uaceAddExtension: AddExtension failed.\n");
    } else {
        LogMessage(X_INFO, "uaceAddExtension: AddExtension succeed.\n");
    }
}

static int ProcUaceExtSetProtectedWindow(ClientPtr client)
{
    xUaceExtSetProtectedWindowReply rep;

    REQUEST(xUaceExtSetProtectedWindowReq);
    REQUEST_SIZE_MATCH(xUaceExtSetProtectedWindowReq);

    rep.type = X_Reply;
    rep.length = 0;
    rep.success = 1;
    rep.sequenceNumber = client->sequence;

    SetProtectedWindow(stuff->window);
    WriteToClient(client, sizeof(xUaceExtSetProtectedWindowReply), (char *)&rep);

    return Success;
}

static int SProcUaceExtSetProtectedWindow(ClientPtr client)
{
    REQUEST(xUaceExtSetProtectedWindowReq);
#if XORG < 112
    register char n;
    swaps(&stuff->length, n);
#else
    swaps(&stuff->length);
#endif
    REQUEST_SIZE_MATCH(xUaceExtSetProtectedWindowReq);
    return ProcUaceExtSetProtectedWindow(client);
}

static int ProcUaceExtRemoveProtectedWindow(ClientPtr client)
{
    xUaceExtRemoveProtectedWindowReply rep;

    REQUEST(xUaceExtRemoveProtectedWindowReq);
    REQUEST_SIZE_MATCH(xUaceExtRemoveProtectedWindowReq);

    rep.type = X_Reply;
    rep.length = 0;
    rep.success = 1;
    rep.sequenceNumber = client->sequence;

    RemoveProtectedWindow(stuff->window);
    WriteToClient(client, sizeof(xUaceExtRemoveProtectedWindowReply), (char *)&rep);
    return Success;
}

static int SProcUaceExtRemoveProtectedWindow(ClientPtr client)
{
    REQUEST(xUaceExtRemoveProtectedWindowReq);
#if XORG < 112
    register char n;
    swaps(&stuff->length, n);
#else
    swaps(&stuff->length);
#endif
    REQUEST_SIZE_MATCH(xUaceExtRemoveProtectedWindowReq);
    return ProcUaceExtRemoveProtectedWindow(client);
}

static int ProcUaceExtGetWindowPID(ClientPtr client)
{
    xUaceExtGetWindowPIDReply rep;

    REQUEST(xUaceExtGetWindowPIDReq);
    REQUEST_SIZE_MATCH(xUaceExtGetWindowPIDReq);

    rep.type = X_Reply;
    rep.length = 0;
    rep.success = 1;
    rep.sequenceNumber = client->sequence;

    int clientId = CLIENT_ID(stuff->window);
    ClientPtr targetClient = clients[clientId];

    if (clientId < 0 || clientId >= currentMaxClients || !targetClient
        || targetClient->clientState != ClientStateRunning || !ClientIsLocal(targetClient)) {
        rep.PID = 0;
        WriteToClient(client, sizeof(xUaceExtGetWindowPIDReply), (char *)&rep);
        return Success;
    }

    rep.PID = targetClient->clientIds->pid;
    WriteToClient(client, sizeof(xUaceExtGetWindowPIDReply), (char *)&rep);
    return Success;
}

static int ProcUaceExtSetScreenShotTools(ClientPtr client)
{
    char *param;
    xUaceExtSetScreenShotToolsReply rep;

    REQUEST(xUaceExtSetScreenShotToolsReq);
    REQUEST_FIXED_SIZE(xUaceExtSetScreenShotToolsReq, stuff->paramLen);

    param = malloc(stuff->paramLen+1);
    strncpy(param, (char*)&stuff[1], stuff->paramLen);
    param[stuff->paramLen] = '\0';

    rep.type = X_Reply;
    rep.length = 0;
    rep.success = 1;
    rep.sequenceNumber = client->sequence;

    SetScreenShotToolsBlackList(param);
    WriteToClient(client, sizeof(xUaceExtSetScreenShotToolsReply), (char *)&rep);

    free(param);
    return Success;
}

static int SProcUaceExtSetScreenShotTools(ClientPtr client)
{
    REQUEST(xUaceExtSetScreenShotToolsReq);
#if XORG < 112
    register char n;
    swaps(&stuff->length, n);
#else
    swaps(&stuff->length);
#endif
    REQUEST_SIZE_MATCH(xUaceExtSetScreenShotToolsReq);
    return ProcUaceExtSetScreenShotTools(client);
}

static int ProcUaceExtRemoveScreenShotTools(ClientPtr client)
{
    xUaceExtRemoveScreenShotToolsReply rep;

    REQUEST(xUaceExtRemoveScreenShotToolsReq);
    REQUEST_SIZE_MATCH(xUaceExtRemoveScreenShotToolsReq);

    rep.type = X_Reply;
    rep.length = 0;
    rep.success = 1;
    rep.sequenceNumber = client->sequence;

    RemoveScreenShotToolsBlackList(stuff->name);
    WriteToClient(client, sizeof(xUaceExtRemoveScreenShotToolsReply), (char *)&rep);
    return Success;
}

static int SProcUaceExtRemoveScreenShotTools(ClientPtr client)
{
    REQUEST(xUaceExtRemoveScreenShotToolsReq);
#if XORG < 112
    register char n;
    swaps(&stuff->length, n);
#else
    swaps(&stuff->length);
#endif
    REQUEST_SIZE_MATCH(xUaceExtRemoveScreenShotToolsReq);
    return ProcUaceExtRemoveScreenShotTools(client);
}

static int ProcUaceExtFreeScreenShotTools(ClientPtr client)
{
    xUaceExtFreeScreenShotToolsReply rep;

    REQUEST(xUaceExtFreeScreenShotToolsReq);
    REQUEST_SIZE_MATCH(xUaceExtFreeScreenShotToolsReq);

    rep.type = X_Reply;
    rep.length = 0;
    rep.success = 1;
    rep.sequenceNumber = client->sequence;

    FreeScreenShotToolsBlackList();
    WriteToClient(client, sizeof(xUaceExtFreeScreenShotToolsReply), (char *)&rep);
    return Success;
}

static int ProcUaceExtRegisterSelectionSpy(ClientPtr client)
{
    xUaceExtRegisterSelectionReply rep;

    REQUEST(xUaceExtRegisterSelectionSpyReq);
    REQUEST_SIZE_MATCH(xUaceExtRegisterSelectionSpyReq);

    rep.type = X_Reply;
    rep.success = 1;
    rep.sequenceNumber = client->sequence;

    spyClientInfo.client = client;
    spyClientInfo.read_fd = open(FIFO_READ, O_RDONLY);
    spyClientInfo.write_fd = open(FIFO_WRITE, O_WRONLY);

    WriteToClient(client, sizeof(xUaceExtRegisterSelectionReply), (char *)&rep);
    return 0;
}

static int SProcUaceExtRegisterSelectionSpy(ClientPtr client)
{
    REQUEST(xUaceExtRegisterSelectionSpyReq);
#if XORG < 112
    register char n;
    swaps(&stuff->length, n);
#else
    swaps(&stuff->length);
#endif
    REQUEST_SIZE_MATCH(xUaceExtRegisterSelectionSpyReq);
    return ProcUaceExtRegisterSelectionSpy(client);
}


static int ProcUACEDispatch(ClientPtr client)
{
    REQUEST(xReq);
    switch (stuff->data) {
    case X_UaceExtSetProtectedWindow:
        return ProcUaceExtSetProtectedWindow(client);
    case X_UaceExtRemoveProtectedWindow:
        return ProcUaceExtRemoveProtectedWindow(client);
    case X_UaceExtGetWindowPID:
        return ProcUaceExtGetWindowPID(client);
    case X_UaceExtSetScreenShotTools:
        return ProcUaceExtSetScreenShotTools(client);
    case X_UaceExtRemoveScreenShotTools:
        return ProcUaceExtRemoveScreenShotTools(client);
    case X_UaceExtFreeScreenShotTools:
        return ProcUaceExtFreeScreenShotTools(client);
    case X_UaceExtRegisterSelectionSpy:
        return ProcUaceExtRegisterSelectionSpy(client);
    default:
        return BadRequest;
  }
}

static int SProcUACEDispatch(ClientPtr client)
{
    REQUEST(xReq);
    switch (stuff->data) {
    case X_UaceExtSetProtectedWindow:
        return SProcUaceExtSetProtectedWindow(client);
    case X_UaceExtRemoveProtectedWindow:
        return SProcUaceExtRemoveProtectedWindow(client);
    case X_UaceExtSetScreenShotTools:
        return SProcUaceExtSetScreenShotTools(client);
    case X_UaceExtRemoveScreenShotTools:
        return SProcUaceExtRemoveScreenShotTools(client);
    case X_UaceExtRegisterSelectionSpy:
        return SProcUaceExtRegisterSelectionSpy(client);
    default:
        return BadRequest;
    }
}

void UACEResetProc(ExtensionEntry *extEntry)
{
#ifdef XACE
    XaceDeleteCallback(XACE_RESOURCE_ACCESS, uaceResourcecallback, NULL);
    XaceDeleteCallback(XACE_SELECTION_ACCESS, uaceSelection, NULL);
#endif
    DeleteCallback(&ClientStateCallback, uaceClientStateChange, 0);
}

static void
uaceSelection(CallbackListPtr *pcbl, void *unused, void *calldata)
{
    XaceSelectionAccessRec *rec = calldata;
    Selection *pSel = *rec->ppSel;
    Atom name = pSel->selection;
    Mask access_mode = rec->access_mode;
    int res;

    if (spyClientInfo.client == NULL)
        return;

    if (name == XA_PRIMARY)
        return;

    if (access_mode & DixSetAttrAccess)
        return;

    UpdateCurrentTime();

    int sourcePID = 0;
    int targetPID = 0;
    if (pSel) {
        *rec->ppSel = pSel;
        if (pSel->client) {
            sourcePID = pSel->client->clientIds->pid;
        }
    } else {
        rec->status = BadMatch;
        return;
    }

    targetPID = rec->client->clientIds->pid;
    if (sourcePID == 0 || targetPID == 0)
        return;

    // request security client permission check
    SelectionPermissionCheck spc = {access_mode, sourcePID, targetPID};
    write(spyClientInfo.write_fd, &spc, sizeof(SelectionPermissionCheck));
    read(spyClientInfo.read_fd, &res, sizeof(res));

    if (res != 1) {
        LogMessage(X_INFO, "uaceSelection client: %s request access mode: %d, no Selection permission\n", rec->client->clientIds->cmdname, access_mode);
    }

    rec->status = (res == 1 ? Success : BadMatch);
}

void uaceClientStateChange(CallbackListPtr *l, void *d, void *p)
{
    NewClientInfoRec *rec = p;
    ClientPtr clientptr = ((NewClientInfoRec *)p)->client;

    if (!rec) {
        LogMessage(X_INFO, "error cbk data rec. func:%s", __func__);
        return;
    }

    if (!rec->client) {
        LogMessage(X_INFO, "error cbk data rec->client. func:%s", __func__);
        return;
    }

    if (clientptr == spyClientInfo.client) {
        LogMessage(X_INFO, "uace security client %s quit\n", clientptr->clientIds->cmdname);
        spyClientInfo.client = NULL;
        close(spyClientInfo.read_fd);
        close(spyClientInfo.write_fd);
    }
}

void uaceResourcecallback(CallbackListPtr *l, void *d, void *p)
{
  XaceResourceAccessRec *rec = p;
  if (!rec) {
      LogMessage(X_INFO, "error cbk data rec. func:%s", __func__);
      return;
  }

  if (!rec->client) {
      LogMessage(X_INFO, "error cbk data rec->client. func:%s", __func__);
      return;
  }
}
