// lowrun2.exe <mode> <command line...>
//   mode = low | untrusted | appcontainer | appcontainer-net
// low/untrusted   : duplicate our own token, drop TokenIntegrityLevel, CreateProcessAsUser.
// appcontainer*   : create an AppContainer profile and launch into it via
//                   PROC_THREAD_ATTRIBUTE_SECURITY_CAPABILITIES (what a browser sandbox uses).
//                   "-net" adds internetClient + privateNetworkClientServer capabilities.
// Exit code of this program = exit code of the child, so callers can probe without needing
// the child to be able to write anywhere.
#include <windows.h>
#include <sddl.h>
#include <stdio.h>
#include <wchar.h>

#ifndef PROC_THREAD_ATTRIBUTE_SECURITY_CAPABILITIES
#define PROC_THREAD_ATTRIBUTE_SECURITY_CAPABILITIES 0x00020009
#endif

typedef HRESULT (WINAPI *pfnCreateAppContainerProfile)(PCWSTR,PCWSTR,PCWSTR,PSID_AND_ATTRIBUTES,DWORD,PSID*);
typedef HRESULT (WINAPI *pfnDeriveAppContainerSid)(PCWSTR,PSID*);

static void banner(const wchar_t *m, const wchar_t *cmd) {
    wprintf(L"mode=%ls\n", m);
    wprintf(L"cmd =%ls\n", cmd);
}

static int run_lowered(DWORD rid, wchar_t *cmd) {
    HANDLE hTok=NULL, hDup=NULL;
    if (!OpenProcessToken(GetCurrentProcess(),
            TOKEN_DUPLICATE|TOKEN_ADJUST_DEFAULT|TOKEN_QUERY|TOKEN_ASSIGN_PRIMARY,&hTok)) {
        wprintf(L"OpenProcessToken %lu\n",GetLastError()); return 250; }
    if (!DuplicateTokenEx(hTok,0,NULL,SecurityImpersonation,TokenPrimary,&hDup)) {
        wprintf(L"DuplicateTokenEx %lu\n",GetLastError()); return 250; }
    SID_IDENTIFIER_AUTHORITY mla=SECURITY_MANDATORY_LABEL_AUTHORITY;
    PSID pSid=NULL;
    if (!AllocateAndInitializeSid(&mla,1,rid,0,0,0,0,0,0,0,&pSid)) {
        wprintf(L"AllocateAndInitializeSid %lu\n",GetLastError()); return 250; }
    TOKEN_MANDATORY_LABEL tml; ZeroMemory(&tml,sizeof(tml));
    tml.Label.Attributes=SE_GROUP_INTEGRITY; tml.Label.Sid=pSid;
    if (!SetTokenInformation(hDup,TokenIntegrityLevel,&tml,
                             sizeof(TOKEN_MANDATORY_LABEL)+GetLengthSid(pSid))) {
        wprintf(L"SetTokenInformation %lu\n",GetLastError()); return 250; }
    LPWSTR s=NULL; ConvertSidToStringSidW(pSid,&s);
    wprintf(L"token integrity set to %ls\n", s?s:L"?");
    STARTUPINFOW si; PROCESS_INFORMATION pi;
    ZeroMemory(&si,sizeof(si)); si.cb=sizeof(si); ZeroMemory(&pi,sizeof(pi));
    if (!CreateProcessAsUserW(hDup,NULL,cmd,NULL,NULL,FALSE,DETACHED_PROCESS,NULL,L"C:\\Windows\\System32",&si,&pi)) {
        wprintf(L"CreateProcessAsUser FAILED %lu\n",GetLastError()); return 251; }
    WaitForSingleObject(pi.hProcess,180000);
    DWORD ec=0; GetExitCodeProcess(pi.hProcess,&ec);
    wprintf(L"child pid %lu exit %lu\n",pi.dwProcessId,ec);
    return (int)ec;
}

static int run_appcontainer(int withNet, wchar_t *cmd) {
    HMODULE ue = LoadLibraryW(L"userenv.dll");
    if (!ue) { wprintf(L"userenv.dll load failed %lu\n",GetLastError()); return 250; }
    pfnCreateAppContainerProfile pCreate =
        (pfnCreateAppContainerProfile)GetProcAddress(ue,"CreateAppContainerProfile");
    pfnDeriveAppContainerSid pDerive =
        (pfnDeriveAppContainerSid)GetProcAddress(ue,"DeriveAppContainerSidFromAppContainerName");
    if (!pCreate || !pDerive) { wprintf(L"AppContainer APIs unavailable\n"); return 250; }

    const wchar_t *name = L"hgfs.ac.probe";
    PSID acSid=NULL;
    HRESULT hr = pCreate(name,name,L"hgfs appcontainer probe",NULL,0,&acSid);
    if (FAILED(hr)) {
        hr = pDerive(name,&acSid);
        if (FAILED(hr)) { wprintf(L"AppContainer SID failed hr=0x%08lx\n",(unsigned long)hr); return 250; }
        wprintf(L"reusing existing AppContainer profile\n");
    }
    LPWSTR s=NULL; ConvertSidToStringSidW(acSid,&s);
    wprintf(L"appcontainer SID %ls\n", s?s:L"?");

    SID_AND_ATTRIBUTES caps[2]; DWORD capCount=0;
    if (withNet) {
        PSID c1=NULL,c2=NULL;
        ConvertStringSidToSidW(L"S-1-15-3-1",&c1);   // internetClient
        ConvertStringSidToSidW(L"S-1-15-3-3",&c2);   // privateNetworkClientServer
        caps[0].Sid=c1; caps[0].Attributes=SE_GROUP_ENABLED;
        caps[1].Sid=c2; caps[1].Attributes=SE_GROUP_ENABLED;
        capCount=2;
        wprintf(L"capabilities: internetClient + privateNetworkClientServer\n");
    } else {
        wprintf(L"capabilities: none\n");
    }

    SECURITY_CAPABILITIES sc; ZeroMemory(&sc,sizeof(sc));
    sc.AppContainerSid=acSid; sc.Capabilities=capCount?caps:NULL; sc.CapabilityCount=capCount;

    SIZE_T sz=0;
    InitializeProcThreadAttributeList(NULL,1,0,&sz);
    LPPROC_THREAD_ATTRIBUTE_LIST al=(LPPROC_THREAD_ATTRIBUTE_LIST)HeapAlloc(GetProcessHeap(),0,sz);
    if (!InitializeProcThreadAttributeList(al,1,0,&sz)) {
        wprintf(L"InitializeProcThreadAttributeList %lu\n",GetLastError()); return 250; }
    if (!UpdateProcThreadAttribute(al,0,PROC_THREAD_ATTRIBUTE_SECURITY_CAPABILITIES,
                                   &sc,sizeof(sc),NULL,NULL)) {
        wprintf(L"UpdateProcThreadAttribute %lu\n",GetLastError()); return 250; }

    STARTUPINFOEXW six; ZeroMemory(&six,sizeof(six));
    six.StartupInfo.cb=sizeof(STARTUPINFOEXW);
    six.lpAttributeList=al;
    PROCESS_INFORMATION pi; ZeroMemory(&pi,sizeof(pi));
    if (!CreateProcessW(NULL,cmd,NULL,NULL,FALSE,EXTENDED_STARTUPINFO_PRESENT|DETACHED_PROCESS,
                        NULL,L"C:\\Windows\\System32",&six.StartupInfo,&pi)) {
        wprintf(L"CreateProcess(appcontainer) FAILED %lu\n",GetLastError()); return 251; }
    WaitForSingleObject(pi.hProcess,180000);
    DWORD ec=0; GetExitCodeProcess(pi.hProcess,&ec);
    wprintf(L"child pid %lu exit %lu\n",pi.dwProcessId,ec);
    return (int)ec;
}

int wmain(int argc, wchar_t **argv) {
    if (argc<3) { wprintf(L"usage: lowrun2 <low|untrusted|appcontainer|appcontainer-net> <cmdline>\n"); return 2; }
    static wchar_t cmd[8192]; cmd[0]=0;
    for (int i=2;i<argc;i++){ wcscat(cmd,argv[i]); if(i+1<argc) wcscat(cmd,L" "); }
    banner(argv[1],cmd);
    if (!_wcsicmp(argv[1],L"low"))              return run_lowered(SECURITY_MANDATORY_LOW_RID,cmd);
    if (!_wcsicmp(argv[1],L"untrusted"))        return run_lowered(SECURITY_MANDATORY_UNTRUSTED_RID,cmd);
    if (!_wcsicmp(argv[1],L"appcontainer"))     return run_appcontainer(0,cmd);
    if (!_wcsicmp(argv[1],L"appcontainer-net")) return run_appcontainer(1,cmd);
    wprintf(L"unknown mode\n"); return 2;
}
