nextcloud-desktop/admin/win/nsi/nsis_uac/uac.cpp
2012-04-02 13:44:24 +02:00

1518 lines
46 KiB
C++
Executable file
Raw Blame History

//Copyright (C) 2007 Anders Kjersem. Licensed under the zlib/libpng license, see License.txt for details.
/*
UAC plugin for NSIS
===================
Compiled with VC6+PlatformSDK (StdCall & MinimizeSize)
Todo:
-----
二etCurrentDir in elevated parent and pass along to outer process for Exec* (or somekind of ipc to request it if workingdir param is empty)
X七heck if secondary logon service is running in SysElevationPresent() on NT5
下se IsUserAnAdmin? MAKEINTRESOURCE(680) export on 2k (it even exists on NT4?) //http://forums.winamp.com/showthread.php?s=&threadid=195020
乙llowSetForegroundWindow
下se RPC instead of WM_COPYDATA for IPC
乙utodetect "best" default admin user in MyRunAs
下se ChangeWindowMessageFilter so we can get >WM_USER msg success feedback and possibly fill the log with detailprints
人ide IPC window inside inner instance window? (Could this add unload problems?)
七REATE_PRESERVE_CODE_AUTHZ_LEVEL? http://msdn2.microsoft.com/en-us/library/ms684863.aspx
下pdateProcThreadAttribute?
七onsent UI on XP ?
乙ll langs in single file; [MyRunAsStrings]>LangSections != 0 then load strings from [langid] sections
丁roadcastSystemMessage to help with SetForeground
下AC::StackPop
Notes:
------
Primary integrity levels:
Name SID RID
Low Mandatory Level S-1-16-4096 0x1000
Medium Mandatory Level S-1-16-8192 0x2000
High Mandatory Level S-1-16-12288 0x3000
System Mandatory Level S-1-16-16384 0x4000
*/
#define UAC_HACK_Clammerz //ugly messagebox focus hack for .onInit
#define UAC_HACK_FGWND1 //super ugly fullscreen invisible window for focus tricks
#define UAC_INITIMPORTS
#include "UAC.h"
#include <objbase.h>//CoInitialize
#include "NSISUtil.h"
using namespace NSIS;
NSISUTIL_INIT();
#define ERRAPP_BADIPCSRV (0x20000000|1)
#define SW_INVALID ((WORD)-1)
#define IPCTOUT_DEF (1000*3) //default timeout for IPC messages
#define IPCTOUT_SHORT 1500
#define IPCTOUT_LONG (IPCTOUT_DEF*2)
enum _IPCSRVWNDMSG
{
IPC_GETEXITCODE=WM_USER, //Get exit-code of the process spawned by the last call to ExecWait/ShellExecWait
IPC_ELEVATEAGAIN,
IPC_GETSRVPID, //Get PID of outer process
IPC_GETSRVHWND, //Get $HWNDParent of outer process
IPC_EXECCODESEGMENT, //wp:pos | lp:hwnd | returns ErrorCode+1
IPC_GETOUTERPROCESSTOKEN,
#ifdef UAC_HACK_FGWND1
IPC_HACKFINDRUNAS,
#endif
};
enum _COPYDATAID
{
CDI_SHEXEC=666, //returns WindowsErrorCode+1
CDI_SYNCVAR,
CDI_STACKPUSH,
};
typedef struct
{
UINT VarId;
NSISCH buf[ANYSIZE_ARRAY];
} IPC_SYNCVAR;
typedef struct
{
HWND hwnd;
bool Wait;
bool UseCreateProcess;
WORD ShowMode;
NSISCH*strExec;
NSISCH*strParams;
NSISCH*strWorkDir;
NSISCH*strVerb;
NSISCH buf[ANYSIZE_ARRAY];
} IPC_SHEXEC;
typedef struct
{
HINSTANCE hInstance;
HWND hSrvIPC;
BYTE DllRef;
bool UseIPC;
bool CheckedIPCParam;
UINT NSISStrLen;
//IPC Server stuff:
HANDLE hElevatedProcess;
HANDLE threadIPC;
DWORD LastWaitExitCode;//Exit code of process started from last call to ExecWait/ShellExecWait
NSIS::extra_parameters*pXParams;
bool ElevateAgain;
//DelayLoadedModules:
HMODULE hModAdvAPI;
} GLOBALS;
GLOBALS g = {0};
void StopIPCSrv();
DWORD _GetElevationType(TOKEN_ELEVATION_TYPE* pTokenElevType);
bool GetIPCSrvWndFromParams(HWND&hwndOut);
#if _MSC_VER >= 1400 //MSVC 2005 wants to pull in the CRT, let's try to help it out
void* __cdecl memset(void*mem,int c,size_t len)
{
char *p=(char*)mem;
while (len-- > 0){*p++=c;}
return mem;
}
#endif
FORCEINLINE NSISCH* GetIPCWndClass() { return _T("NSISUACIPC"); }
FORCEINLINE bool StrCmpI(LPCTSTR s1,LPCTSTR s2) {return 0==lstrcmpi(s1,s2);}
LPTSTR StrNextChar(LPCTSTR Str) { return CharNext(Str); }
bool StrContainsWhiteSpace(LPCTSTR s) { if (s) {while(*s && *s>_T(' '))s=StrNextChar(s);if (*s)return true;}return false; }
DWORD GetSysVer(bool Major=true)
{
OSVERSIONINFO ovi = { sizeof(ovi) };
if ( !GetVersionEx(&ovi) ) return 0;
return Major ? ovi.dwMajorVersion : ovi.dwMinorVersion;
}
#define GetOSVerMaj() (GetSysVer(true))
#define GetOSVerMin() (GetSysVer(false))
UINT_PTR StrToUInt(LPTSTR s,bool ForceHEX=false,BOOL*pFoundBadChar=0)
{
UINT_PTR v=0;
BYTE base=ForceHEX?16:10;
if (pFoundBadChar)*pFoundBadChar=false;
if ( !ForceHEX && *s=='0' && ((*(s=StrNextChar(s)))&~0x20)=='X' && (s=StrNextChar(s)) )base=16;
for (TCHAR c=*s; c; c=*(s=StrNextChar(s)) )
{
if (c >= _T('0') && c <= _T('9')) c-='0';
else if (base==16 && (c & ~0x20) >= 'A' && (c & ~0x20) <= 'F') c=(c & 7) +9;
else
{
if (pFoundBadChar /*&& c!=' '*/)*pFoundBadChar=true;
break;
}
v*=base;v+=c;
}
return v;
}
LPTSTR FindExePathEnd(LPTSTR p)
{
if ( *p=='"' && *(++p) )
{
while( *p && *p!='"' )p=StrNextChar(p);
if (*p)
p=StrNextChar(p);
else
return 0;
}
else
if ( *p!='/' )while( *p && *p!=' ' )p=StrNextChar(p);
return p;
}
#ifdef FEAT_MSRUNASDLGMODHACK
HHOOK g_MSRunAsHook;
void MSRunAsDlgMod_Unload(void*hook)
{
if (hook)
{
//ASSERT(g_MSRunAsHook==hook);
UnhookWindowsHookEx((HHOOK)hook);
//g_MSRunAsHook=0;
}
}
LRESULT CALLBACK MSRunAsDlgMod_ShellProc(int nCode,WPARAM wp,LPARAM lp)
{
CWPRETSTRUCT*pCWPS;
if (nCode >= 0 && (pCWPS=(CWPRETSTRUCT*)lp) && WM_INITDIALOG==pCWPS->message)
{
TCHAR buf[30];
GetClassName(pCWPS->hwnd,buf,COUNTOF(buf));
if (!lstrcmpi(buf,_T("#32770")))
{
const UINT IDC_USRSAFER=0x106,IDC_OTHERUSER=0x104,IDC_SYSCRED=0x105;
GetClassName(GetDlgItem(pCWPS->hwnd,IDC_SYSCRED),buf,COUNTOF(buf));
if (!lstrcmpi(buf,_T("SysCredential"))) //make sure this is the run as dialog
{
MySndDlgItemMsg(pCWPS->hwnd,IDC_USRSAFER,BM_SETCHECK,BST_UNCHECKED);
MySndDlgItemMsg(pCWPS->hwnd,IDC_OTHERUSER,BM_CLICK);
}
}
}
return CallNextHookEx(g_MSRunAsHook,nCode,wp,lp);
}
void* MSRunAsDlgMod_Init()
{
if(GetOSVerMaj()!=5 || GetOSVerMin()<1)return NULL;//only XP/2003
return g_MSRunAsHook=SetWindowsHookEx(WH_CALLWNDPROCRET,MSRunAsDlgMod_ShellProc,0,GetCurrentThreadId());
}
#endif
DWORD DllSelfAddRef()
{
NSISCH buf[MAX_PATH*5];//Lets hope $pluginsdir is shorter than this, only special builds could break this
DWORD len=GetModuleFileName(g.hInstance,buf,MAX_PATH*5);
if ( len && len<MAX_PATH*5 && LoadLibrary(buf) )
{
if (!g.DllRef)g.DllRef++;
return NO_ERROR;
}
ASSERT(!"DllSelfAddRef failed!");
return ERROR_BUFFER_OVERFLOW;
}
FORCEINLINE DWORD MaintainDllSelfRef() //Call this from every exported function to prevent NSIS from unloading our plugin
{
if(!g.CheckedIPCParam && !g.DllRef)
{
HWND hSrv;
g.CheckedIPCParam=true;
g.UseIPC=GetIPCSrvWndFromParams(hSrv);
if(g.UseIPC)
{
g.DllRef++;
g.hSrvIPC=hSrv;
}
}
return (g.DllRef)?DllSelfAddRef():NO_ERROR;
}
DWORD SendIPCMsg(UINT Msg,WPARAM wp,LPARAM lp,DWORD_PTR&MsgRet,DWORD tout=IPCTOUT_DEF,const HWND hIPCSrv=g.hSrvIPC)
{
if (tout==INFINITE) //BUGFIX: SendMessageTimeout(...,INFINITE,...) seems to be broken, SendMessageTimeout(...,SMTO_NORMAL,0,..) seems to work but why take the chance
{
MsgRet=SendMessage(hIPCSrv,Msg,wp,lp);
return NO_ERROR;
}
if ( SendMessageTimeout(hIPCSrv,Msg,wp,lp,SMTO_NORMAL,tout,&MsgRet) )return NO_ERROR;
return (tout=GetLastError()) ? tout : ERROR_TIMEOUT;
}
void _Unload()
{
StopIPCSrv();
if (g.DllRef)
{
g.DllRef=0;
FreeLibrary(g.hInstance);
//Why bother?> FreeLibrary(g.hModAdvAPI);
}
}
DWORD DelayLoadGetProcAddr(void**ppProc,HMODULE hLib,LPCSTR Export)
{
ASSERT(ppProc && hLib && Export);
if (!*ppProc)
{
*ppProc=GetProcAddress(hLib,Export);
if (!*ppProc)return GetLastError();
}
return NO_ERROR;
}
DWORD DelayLoadDlls()
{
#ifdef UNICODE
# define __DLD_FUNCSUFFIX "W"
# else
# define __DLD_FUNCSUFFIX "A"
# endif
if (!g.hModAdvAPI) //using g.hModAdvAPI to test if this is the first time we have been called
{
struct
{
HMODULE*pMod;
LPCSTR DllName;//NOTE: Always using ANSI strings to save a couple of bytes
}
dld[]=
{
{&g.hModAdvAPI,"AdvAPI32"},
{0}
};
DWORD ec;
UINT o;
for (o=0; dld[o].pMod; ++o)
if ( !(*dld[o].pMod=LoadLibraryA(dld[o].DllName)) )
return GetLastError();
struct
{
HMODULE hMod;
void**ppProc;
LPCSTR Export;
bool Optional;
}
dldprocs[]=
{
{GetModuleHandle(_T("USER32")),(void**)&_AllowSetForegroundWindow,"AllowSetForegroundWindow",true},
{g.hModAdvAPI,(void**)&_OpenProcessToken, "OpenProcessToken"},
{g.hModAdvAPI,(void**)&_OpenThreadToken, "OpenThreadToken"},
{g.hModAdvAPI,(void**)&_GetTokenInformation, "GetTokenInformation"},
{g.hModAdvAPI,(void**)&_AllocateAndInitializeSid, "AllocateAndInitializeSid"},
{g.hModAdvAPI,(void**)&_FreeSid, "FreeSid"},
{g.hModAdvAPI,(void**)&_EqualSid, "EqualSid"},
{g.hModAdvAPI,(void**)&_CheckTokenMembership, "CheckTokenMembership",true},
#ifdef FEAT_CUSTOMRUNASDLG
{g.hModAdvAPI,(void**)&_GetUserName, "GetUserName" __DLD_FUNCSUFFIX},
{g.hModAdvAPI,(void**)&_CreateProcessWithLogonW,"CreateProcessWithLogonW",true},
{LoadLibraryA("SECUR32"),(void**)&_GetUserNameEx,"GetUserNameEx" __DLD_FUNCSUFFIX,true},//We never free this library
#endif
{0}
};
//#undef __DLD_FUNCSUFFIX
for (o=0; dldprocs[o].hMod; ++o)
if (ec=DelayLoadGetProcAddr(dldprocs[o].ppProc,dldprocs[o].hMod,dldprocs[o].Export) && !dldprocs[o].Optional)
{
TRACEF("DelayLoadDlls failed to find %s in %X\n",dldprocs[o].Export,dldprocs[o].hMod);
return ec;
}
}
return NO_ERROR;
}
void AllowOuterInstanceWindowFocus()
{
if (g.UseIPC)
{
DWORD_PTR MsgRet;
if (!SendIPCMsg(IPC_GETSRVPID,0,0,MsgRet,IPCTOUT_SHORT) && MsgRet && _AllowSetForegroundWindow)_AllowSetForegroundWindow(MsgRet);
}
}
DWORD SyncVars(HWND hwndNSIS)
{
DWORD i,ec=NO_ERROR;
IPC_SYNCVAR*pSV=0;
if (!g.UseIPC)return NO_ERROR;
g.NSISStrLen=NSIS::StrSize;
TRACEF("SyncVars: g.NSISStrLen=%d\n",g.NSISStrLen);ASSERT(g.NSISStrLen>10);
DWORD cbStruct=FIELD_OFFSET(IPC_SYNCVAR,buf[g.NSISStrLen+1]);
pSV=(IPC_SYNCVAR*)MemAlloc(cbStruct);
if (!pSV)
goto die_GLE;
else
{
COPYDATASTRUCT cds={CDI_SYNCVAR,cbStruct,pSV};
for (i=0;i<__INST_LAST && !ec;++i)
{
pSV->VarId=i;
lstrcpyn(pSV->buf,GetVar(i),g.NSISStrLen);
DWORD MsgRet;//TRACEF("SyncVars: (%d)%s|\n",i,pSV->buf);
if (!(ec=SendIPCMsg(WM_COPYDATA,(WPARAM)hwndNSIS,(LPARAM)&cds,MsgRet,3000 )))ec=MsgRet-1;
}
}
return ec;
die_GLE:
return GetLastError();
}
DWORD _Exec(HWND hwnd,NSISCH*Verb,NSISCH*Exec,NSISCH*Params,NSISCH*WorkDir,WORD ShowWnd,bool Wait,bool UseCreateProcess)
{
DWORD ec;
NSISCH*buf=0;
SHELLEXECUTEINFO sei={sizeof(SHELLEXECUTEINFO)};
sei.hwnd =hwnd;
sei.nShow =(ShowWnd!=SW_INVALID)?ShowWnd:SW_NORMAL;
sei.fMask =SEE_MASK_FLAG_DDEWAIT;
sei.lpFile =(Exec&&*Exec) ?Exec:0;
sei.lpParameters=(Params&&*Params) ?Params:0;
sei.lpDirectory =(WorkDir&&*WorkDir) ?WorkDir:0;
sei.lpVerb =(Verb&&*Verb) ?Verb:0;
TRACEF("_Exec:%X|%s|%s|%s|wait=%d useCreateProc=%d ShowWnd=%d useShowWnd=%d\n",hwnd,Exec,Params,WorkDir,Wait,UseCreateProcess,ShowWnd,ShowWnd!=SW_INVALID);
if (UseCreateProcess)
{
STARTUPINFO si={sizeof(STARTUPINFO)};
if (ShowWnd != SW_INVALID)
{
si.dwFlags|=STARTF_USESHOWWINDOW;
si.wShowWindow=sei.nShow;
}
PROCESS_INFORMATION pi;
const NSISCH*Q=( (*Exec!='"') && (*Params) && StrContainsWhiteSpace(Exec)) ? _T("\"") : _T("");//Add extra quotes to program part of command-line?
const DWORD len= ((*Q)?2:0) + lstrlen(Exec) + 1 + lstrlen(Params) + 1;
buf=(NSISCH*)NSIS::MemAlloc(len*sizeof(NSISCH));
if (!buf)return ERROR_OUTOFMEMORY;
//Build string for CreateProcess, "[Q]<Exec>[Q][Space]<Params>"
wsprintf(buf,_T("%s%s%s%s%s"),Q,Exec,Q,((*Params)?_T(" "):_T("")),Params);
TRACEF("_Exec: calling CreateProcess>%s< in >%s< addedQ=%d show=%u\n",buf,sei.lpDirectory,*Q,sei.nShow);
if ( !CreateProcess(0,buf,0,0,false,0,0,sei.lpDirectory,&si,&pi) ) goto die_GLE;
CloseHandle(pi.hThread);
sei.hProcess=pi.hProcess;
}
else
{
sei.fMask|=SEE_MASK_NOCLOSEPROCESS;
TRACEF("_Exec: calling ShellExecuteEx...\n");
if ( !ShellExecuteEx(&sei) )goto die_GLE;
}
if (Wait)
{
WaitForSingleObject(sei.hProcess,INFINITE);
GetExitCodeProcess(sei.hProcess,&g.LastWaitExitCode);
}
else WaitForInputIdle(sei.hProcess,1500);//wait a little bit so the finish page window does not go away too fast and cause focus problems
CloseHandle(sei.hProcess);
ec=NO_ERROR;
ret:
if (buf)NSIS::MemFree(buf);
return ec;
die_GLE:
ec=GetLastError();
TRACEF("_Exec>%s failed with error %u (%s)\n",UseCreateProcess?"CreateProcess":"ShExec",ec,buf);
goto ret;
}
WORD GetShowWndCmdFromStr(NSISCH*s)
{
//NOTE: Little used modes are still supported, just not with strings, you must use the actual number or ${SW_xx} defines from WinMessages.h
struct {NSISCH*id;WORD cmd;} swcm[] = {
{_T("SW_HIDE"), SW_HIDE},
{_T("SW_SHOW"), SW_SHOW},
{_T("SW_RESTORE"), SW_RESTORE},
{_T("SW_MAXIMIZE"), SW_MAXIMIZE},
{_T("SW_MINIMIZE"), SW_MINIMIZE},
// {_T("SW_MAX"), SW_MAXIMIZE},
// {_T("SW_MIN"), SW_MINIMIZE},
{_T("SW_SHOWNORMAL"), SW_SHOWNORMAL},
//{_T("SW_NORMAL"), SW_NORMAL},
//{_T("SW_SHOWMINIMIZED"), SW_SHOWMINIMIZED},
//{_T("SW_SHOWMAXIMIZED"), SW_SHOWMAXIMIZED},
//{_T("SW_SHOWNOACTIVATE"), SW_SHOWNOACTIVATE},
//{_T("SW_SHOWNA"), SW_SHOWNA},
//{_T("SW_SHOWMINNOACTIVE"), SW_SHOWMINNOACTIVE},
//{_T("SW_SHOWDEFAULT"), SW_SHOWDEFAULT},
//{_T("SW_FORCEMINIMIZE"), SW_FORCEMINIMIZE},
{0}
};
for (int i=0; swcm[i].id; ++i) if (StrCmpI(s,swcm[i].id)) return swcm[i].cmd;
return SW_INVALID;
}
#define HasIPCServer() (g.UseIPC!=NULL)
void HandleExecExport(bool CreateProc,bool Wait,HWND&hwndNSIS,int&StrSize,NSISCH*&Vars,stack_t**&StackTop,NSIS::extra_parameters*pXParams)
{
DWORD ec=NO_ERROR,ForkExitCode=ERROR_INVALID_FUNCTION;
UINT cch=0,cbStruct;
WORD ShowWnd;
IPC_SHEXEC*pISE=0;
stack_t* const pSIVerb=CreateProc?0:StackPop();//Only ShellExec supports verb's
stack_t* const pSIShowWnd =StackPop();
stack_t* const pSIExec =StackPop();
stack_t* const pSIParams =StackPop();
stack_t* const pSIWorkDir =StackPop();
if (ec=MaintainDllSelfRef())goto ret;
if (!pSIExec || !pSIParams || !pSIWorkDir || !pSIShowWnd || (!pSIVerb && !CreateProc))
{
TRACE("If you see this you probably forgot that all parameters are required!\n");
ec=ERROR_INVALID_PARAMETER;
goto ret;
}
ShowWnd=GetShowWndCmdFromStr(pSIShowWnd->text);
if (ShowWnd==SW_INVALID && *pSIShowWnd->text)
{
BOOL BadCh;
ShowWnd=StrToUInt(pSIShowWnd->text,false,&BadCh);
if (BadCh)ShowWnd=SW_INVALID;
}
TRACEF("HandleExecExport: ipc=%X (%X)\n",g.UseIPC,g.hSrvIPC);
SyncVars(hwndNSIS);
if (!g.UseIPC) //No IPC Server, we are not elevated with UAC
{
ec=_Exec(hwndNSIS,pSIVerb?pSIVerb->text:0,pSIExec->text,pSIParams->text,pSIWorkDir->text,ShowWnd,Wait,CreateProc);
if (Wait)ForkExitCode=g.LastWaitExitCode;
goto ret;
}
cch+=lstrlen(pSIExec->text)+1;
cch+=lstrlen(pSIParams->text)+1;
cch+=lstrlen(pSIWorkDir->text)+1;
if (pSIVerb)cch+=lstrlen(pSIVerb->text)+1;
cbStruct=FIELD_OFFSET( IPC_SHEXEC, buf[cch*sizeof(TCHAR)] );
pISE=(IPC_SHEXEC*)NSIS::MemAlloc(cbStruct);
if (!pISE)ec=GetLastError();
if (!ec)
{
DWORD_PTR MsgRet;
pISE->hwnd =hwndNSIS;
pISE->Wait =Wait;
pISE->ShowMode =ShowWnd;
pISE->UseCreateProcess=CreateProc;
//Just offsets at this point
pISE->strExec =(NSISCH*)0;
pISE->strParams =(NSISCH*)(lstrlen(pSIExec->text) +pISE->strExec+1);
pISE->strWorkDir=(NSISCH*)(lstrlen(pSIParams->text) +pISE->strParams+1);
pISE->strVerb= (NSISCH*)(lstrlen(pSIWorkDir->text)+pISE->strWorkDir+1);
lstrcpy(pISE->buf,pSIExec->text);
lstrcpy(&pISE->buf[(DWORD_PTR)pISE->strParams], pSIParams->text);
lstrcpy(&pISE->buf[(DWORD_PTR)pISE->strWorkDir],pSIWorkDir->text);
if (pSIVerb)lstrcpy(&pISE->buf[(DWORD_PTR)pISE->strVerb], pSIVerb->text);
COPYDATASTRUCT cds;
cds.dwData=CDI_SHEXEC;
cds.cbData=cbStruct;
cds.lpData=pISE;
AllowOuterInstanceWindowFocus();
if (!(ec=SendIPCMsg(WM_COPYDATA,(WPARAM)hwndNSIS,(LPARAM)&cds,MsgRet,Wait?(INFINITE):(IPCTOUT_LONG) )))ec=MsgRet-1;
TRACEF("HandleExecExport: IPC returned %X, ec=%d\n",MsgRet,ec);
if (Wait && NO_ERROR==ec)
{
ec=SendIPCMsg(IPC_GETEXITCODE,0,0,ForkExitCode);
TRACEF("HandleExecExport(Wait): Spawned process exit code=%d",ForkExitCode);
}
}
ret:
NSIS::MemFree(pISE);
StackFreeItem(pSIShowWnd);
StackFreeItem(pSIExec);
StackFreeItem(pSIParams);
StackFreeItem(pSIWorkDir);
StackFreeItem(pSIVerb);
SetVarUINT(INST_0,ec);
if (ec)SetErrorFlag(pXParams);
if (Wait)SetVarUINT(INST_1,ForkExitCode);
}
bool _SupportsUAC(bool VersionTestOnly=false)
{
TOKEN_ELEVATION_TYPE tet;
OSVERSIONINFO ovi={sizeof(ovi)};
if (!GetVersionEx(&ovi))
{
ASSERT(!"_SupportsUAC>GetVersionEx");
return false;
}
if (VersionTestOnly)return ovi.dwMajorVersion>=6;
if (ovi.dwMajorVersion>=6 && _GetElevationType(&tet)==NO_ERROR)
{
const bool ret=tet!=TokenElevationTypeDefault && tet!=NULL;
TRACEF("_SupportsUAC tet=%d, returning %d\n",tet,ret);
return ret;
}
DBGONLY(TRACEF("_SupportsUAC returning false! ver=%d _GetElevationType.ret=%u\n",ovi.dwMajorVersion,_GetElevationType(&tet)));
return false;
}
DWORD _GetElevationType(TOKEN_ELEVATION_TYPE*pTokenElevType)
{
DWORD ec=ERROR_ACCESS_DENIED;
HANDLE hToken=0;
DWORD RetLen;
if (!pTokenElevType)return ERROR_INVALID_PARAMETER;
if (ec=DelayLoadDlls())return ec;
*pTokenElevType=(TOKEN_ELEVATION_TYPE)NULL;
if (!_SupportsUAC(true))return NO_ERROR;
if (!_OpenProcessToken(GetCurrentProcess(),TOKEN_QUERY,&hToken))goto dieLastErr;
if (!_GetTokenInformation(hToken,(_TOKEN_INFORMATION_CLASS)TokenElevationType,pTokenElevType,sizeof(TOKEN_ELEVATION_TYPE),&RetLen))goto dieLastErr;
SetLastError(NO_ERROR);
dieLastErr:
ec=GetLastError();
CloseHandle(hToken);
TRACEF("_GetElevationType ec=%u type=%d\n",ec,*pTokenElevType);
return ec;
}
bool _IsUACEnabled()
{
HKEY hKey;
bool ret=false;
if (GetSysVer()>=6 && NO_ERROR==RegOpenKeyEx(HKEY_LOCAL_MACHINE,_T("Software\\Microsoft\\Windows\\CurrentVersion\\Policies\\System"),0,KEY_READ,&hKey))
{
//Check must be !=0, see http://codereview.chromium.org/3110 & http://src.chromium.org/viewvc/chrome?view=rev&revision=2330
//Apparently, Vista treats !=0 as UAC on, and some machines have EnableLUA=2 !!
DWORD val,type,size=sizeof(DWORD);
if (NO_ERROR==RegQueryValueEx(hKey,_T("EnableLUA"),0,&type,(LPBYTE)&val,&size) && type==REG_DWORD && val!=0) ret=true;
RegCloseKey(hKey);
}
return ret;
}
bool SysQuery_IsServiceRunning(LPCTSTR servicename)
{
bool retval=false;
SC_HANDLE scdbh=NULL,hsvc;
scdbh=OpenSCManager(NULL,NULL,GENERIC_READ);
if (scdbh)
{
if (hsvc=OpenService(scdbh,servicename,SERVICE_QUERY_STATUS))
{
SERVICE_STATUS ss;
if (QueryServiceStatus(hsvc,&ss))retval=(ss.dwCurrentState==SERVICE_RUNNING);
CloseServiceHandle(hsvc);
}
}
CloseServiceHandle(scdbh);
return retval;
}
inline bool SysNT5IsSecondaryLogonSvcRunning()
{
return SysQuery_IsServiceRunning(_T("seclogon"));
}
bool SysElevationPresent() //Will return false on Vista if UAC is off
{
const DWORD vmaj=GetSysVer();
ASSERT(vmaj<=6 && vmaj>=4);
if (vmaj==5) return true; //TODO:Check if secondary logon service is running?
if (vmaj>=6) return _IsUACEnabled();
return false;
}
FORCEINLINE bool SysSupportsRunAs()
{
return GetSysVer()>=5;
}
bool _IsAdmin()
{
#ifdef BUILD_XPTEST
static int _dbgOld=-1;
unsigned _dbg=(unsigned)FindExePathEnd(GetCommandLine());
if (_dbgOld==-1){_dbg=(_dbg && *((TCHAR*)_dbg))?MessageBoxA(0,"Debug: Pretend to be admin?",GetCommandLine(),MB_YESNOCANCEL):IDCANCEL;} else _dbg=_dbgOld;_dbgOld=_dbg;TRACEF("_IsAdmin=%d|%d\n",_dbg,_dbg==IDYES);
if (_dbg!=IDCANCEL){SetLastError(0);return _dbg==IDYES;}
#endif
BOOL isAdmin=false;
DWORD ec;
OSVERSIONINFO ovi={sizeof(ovi)};
if (!GetVersionEx(&ovi))return false;
if (VER_PLATFORM_WIN32_NT != ovi.dwPlatformId) //Not NT
{
SetLastError(NO_ERROR);
return true;
}
if (ec=DelayLoadDlls())
{
TRACEF("DelayLoadDlls failed in _IsAdmin() with err x%X\n",ec);
SetLastError(ec);
return false;
}
ASSERT(_OpenThreadToken && _OpenProcessToken && _AllocateAndInitializeSid && _EqualSid && _FreeSid);
HANDLE hToken;
if (_OpenThreadToken(GetCurrentThread(),TOKEN_QUERY,FALSE,&hToken) || _OpenProcessToken(GetCurrentProcess(),TOKEN_QUERY,&hToken))
{
SID_IDENTIFIER_AUTHORITY SystemSidAuthority = SECURITY_NT_AUTHORITY;
PSID psid=0;
if (_AllocateAndInitializeSid(&SystemSidAuthority,2,SECURITY_BUILTIN_DOMAIN_RID,DOMAIN_ALIAS_RID_ADMINS,0,0,0,0,0,0,&psid))
{
if (_CheckTokenMembership)
{
if (!_CheckTokenMembership(0,psid,&isAdmin))isAdmin=false;
}
else
{
DWORD cbTokenGrps;
if (!_GetTokenInformation(hToken,TokenGroups,0,0,&cbTokenGrps)&&GetLastError()==ERROR_INSUFFICIENT_BUFFER)
{
TOKEN_GROUPS*ptg=0;
if (ptg=(TOKEN_GROUPS*)NSIS::MemAlloc(cbTokenGrps))
{
if (_GetTokenInformation(hToken,TokenGroups,ptg,cbTokenGrps,&cbTokenGrps))
{
for (UINT i=0; i<ptg->GroupCount;i++)
{
if (_EqualSid(ptg->Groups[i].Sid,psid))isAdmin=true;
}
}
NSIS::MemFree(ptg);
}
}
}
_FreeSid(psid);
}
CloseHandle(hToken);
}
if (isAdmin) //UAC Admin with split token check
{
if (_SupportsUAC())
{
TOKEN_ELEVATION_TYPE tet;
if (_GetElevationType(&tet) || tet==TokenElevationTypeLimited)isAdmin=false;
}
else SetLastError(NO_ERROR);
}
return FALSE != isAdmin;
}
LRESULT CALLBACK IPCSrvWndProc(HWND hwnd,UINT uMsg,WPARAM wp,LPARAM lp)
{
switch(uMsg)
{
case WM_DESTROY:
PostQuitMessage(0);
break;
case WM_CLOSE:
return DestroyWindow(hwnd);
case WM_COPYDATA:
if (lp)
{
const COPYDATASTRUCT*pCDS=(COPYDATASTRUCT*)lp;
if (pCDS->dwData==CDI_SHEXEC && pCDS->lpData)
{
if ( pCDS->cbData < sizeof(IPC_SHEXEC) )break;
g.LastWaitExitCode=ERROR_INVALID_FUNCTION;
IPC_SHEXEC& ise=*((IPC_SHEXEC*)pCDS->lpData);
SetForegroundWindow(ise.hwnd);
DWORD ec=_Exec(
ise.hwnd,
&ise.buf[(DWORD_PTR)ise.strVerb],
&ise.buf[(DWORD_PTR)ise.strExec],
&ise.buf[(DWORD_PTR)ise.strParams],
&ise.buf[(DWORD_PTR)ise.strWorkDir],
ise.ShowMode,ise.Wait,ise.UseCreateProcess
);
TRACEF("IPCSrvWndProc>IPC_SHEXEC>_ShExec=%d\n",ec);
return ec+1;
}
else if (pCDS->dwData==CDI_SYNCVAR && pCDS->lpData && pCDS->cbData>1)
{
IPC_SYNCVAR*pSV=(IPC_SYNCVAR*)pCDS->lpData;
if (pSV->VarId>=__INST_LAST)return 1+ERROR_INVALID_PARAMETER;
TRACEF("WM_COPYDATA: CDI_SYNCVAR:%d=%s|\n",pSV->VarId,&pSV->buf[0]);
lstrcpy(GetVar(pSV->VarId),&pSV->buf[0]);
return NO_ERROR+1;
}
else if (pCDS->dwData==CDI_STACKPUSH && pCDS->lpData && pCDS->cbData>=1)
{
TRACEF("WM_COPYDATA: CDI_STACKPUSH:%s|\n",pCDS->lpData);
return StackPush((NSISCH*)pCDS->lpData)+1;
}
}
break;
case IPC_GETEXITCODE:
return g.LastWaitExitCode;
case IPC_ELEVATEAGAIN:
TRACE("IPCSrvWndProc>IPC_ELEVATEAGAIN\n");
return (g.ElevateAgain=true);
case IPC_GETSRVPID:
return GetCurrentProcessId();
case IPC_GETSRVHWND:
return GetWindowLongPtr(hwnd,GWLP_USERDATA);
case IPC_EXECCODESEGMENT:
return 1+(g.pXParams ? ExecuteCodeSegment(g.pXParams,wp,(HWND)lp) : ERROR_INVALID_FUNCTION);
case IPC_GETOUTERPROCESSTOKEN:
if (_OpenProcessToken)
{
HANDLE hToken,hOutToken;
if (_OpenProcessToken(GetCurrentProcess(),TOKEN_ALL_ACCESS,&hToken))
{
TRACEF("IPC_GETOUTERPROCESSTOKEN: hToken=%X targetProcess=%X\n",hToken,g.hElevatedProcess);
if (DuplicateHandle(GetCurrentProcess(),hToken,g.hElevatedProcess,&hOutToken,lp,false,DUPLICATE_CLOSE_SOURCE))
{
TRACEF("IPC_GETOUTERPROCESSTOKEN: %X(%X) > %X(%X)\n",hToken,-1,hOutToken,g.hElevatedProcess);
return (LRESULT)hOutToken;
}
}
}
return NULL;
#ifdef UAC_HACK_FGWND1
case IPC_HACKFINDRUNAS: //super ugly hack to get title of run as dialog
if (wp<200)
{
HWND hRA=GetLastActivePopup((HWND)lp);
TRACEF("IPC_HACKFINDRUNAS:%d %X %X\n",wp,lp,hRA);
if (hRA && hRA !=(HWND)lp ) return PostMessage((HWND)lp,WM_APP,0,(LONG_PTR)hRA);
Sleep(10);PostMessage(hwnd,uMsg,wp+1,lp);
}
break;
#endif
}
return DefWindowProc(hwnd,uMsg,wp,lp);
}
DWORD WINAPI IPCSrvThread(LPVOID lpParameter)
{
CoInitialize(0);
const DWORD WStyle=WS_VISIBLE DBGONLY(|(WS_CAPTION));
const int PosOffset=32700;
MSG msg;
WNDCLASS wc={0};
wc.lpszClassName=GetIPCWndClass();
wc.lpfnWndProc=IPCSrvWndProc;
wc.hInstance=g.hInstance;
if (!RegisterClass(&wc))goto dieLastErr;
if (!(g.hSrvIPC=CreateWindowEx(WS_EX_TOOLWINDOW DBGONLY(&~WS_EX_TOOLWINDOW),
GetIPCWndClass(),
DBGONLY(_T("Debug: NSIS.UAC")+)0,
WStyle,
-PosOffset DBGONLY(+PosOffset),-PosOffset DBGONLY(+PosOffset),DBGONLY(150+)1,DBGONLY(10+)1,
0,0,wc.hInstance,0
)))goto dieLastErr;
SetWindowLongPtr(g.hSrvIPC,GWLP_USERDATA,(LONG_PTR)lpParameter);
TRACEF("IPCSrv=%X server created...\n",g.hSrvIPC);
while (GetMessage(&msg,0,0,0)>0)DispatchMessage(&msg);
SetLastError(NO_ERROR);
dieLastErr:
CoUninitialize();
return g.LastWaitExitCode=GetLastError();
}
DWORD InitIPC(HWND hwndNSIS,NSIS::extra_parameters*pXParams,UINT NSISStrLen)
{
if (g.threadIPC)return NO_ERROR;
TRACEF("InitIPC StrSize=%u vs %u\n",NSIS::StrSize,NSISStrLen);
DWORD tid;
ASSERT(!g.pXParams && pXParams);
ASSERT(NSISStrLen>0 && NSISStrLen==NSIS::StrSize);
g.NSISStrLen=NSISStrLen;
g.pXParams=pXParams;
g.threadIPC=CreateThread(0,0,IPCSrvThread,hwndNSIS,0,&tid);
if (g.threadIPC)
{
while(!g.hSrvIPC && !g.LastWaitExitCode)Sleep(20);
return g.hSrvIPC ? NO_ERROR : g.LastWaitExitCode;
}
return GetLastError();
}
void StopIPCSrv()
{
if (g.threadIPC)
{
TRACEF("StopIPCSrv h=%X \n",g.hSrvIPC);
#ifdef UAC_HACK_Clammerz
if ( GetSysVer()>=5 )
{
//WINBUGFIX: This ugly thing supposedly solves the problem of messagebox'es in .OnInit appearing behind other windows in Vista
HWND hack=CreateWindowEx(WS_EX_TRANSPARENT|WS_EX_LAYERED,_T("Button"),NULL,NULL,0,0,0,0,NULL,NULL,NULL,0);
ShowWindow(hack,SW_SHOW);
SetForegroundWindow(hack);
DestroyWindow(hack);
}
#endif
PostMessage(g.hSrvIPC,WM_CLOSE,0,0);
WaitForSingleObject(g.threadIPC,INFINITE);
CloseHandle(g.threadIPC);
UnregisterClass(GetIPCWndClass(),g.hInstance);//DLL can be loaded more than once, so make sure RegisterClass doesn't fail
g.hSrvIPC=0;
g.threadIPC=0;
}
}
#ifdef UAC_HACK_FGWND1
LRESULT CALLBACK HackWndSubProc(HWND hwnd,UINT Msg,WPARAM wp,LPARAM lp)
{
switch(Msg)
{
case WM_APP:
GetWindowText((HWND)lp,GetVar(0),NSIS::StrSize);
if (*GetVar(0))SendMessage(hwnd,WM_SETTEXT,0,(LONG_PTR)GetVar(0));
break;
}
return DefWindowProc(hwnd,Msg,wp,lp);
}
#endif
inline bool MustUseInternalRunAs()
{
#ifdef BUILD_DBGSELECTELVMODE
TCHAR dbgb[MAX_PATH*4];wsprintf(dbgb,_T("%s.ini"),GetVar(VIDX_EXEPATH));
static int dbg_answer=GetPrivateProfileInt(_T("UACDBG"),_T("MustUseInternalRunAs"),2,dbgb);
if (dbg_answer<2)return !!dbg_answer;WritePrivateProfileString(_T("UACDBG"),_T("MustUseInternalRunAs"),"",dbgb);
if (MessageBox(GetActiveWindow(),"MustUseInternalRunAs?",dbgb,MB_YESNO)==IDYES)return true;
#endif
return GetSysVer()>=6 && !SysElevationPresent();
}
DWORD ForkSelf(HWND hParent,DWORD&ForkExitCode,NSIS::extra_parameters*pXParams,UINT NSISStrLen)
{
DWORD ec=ERROR_ACCESS_DENIED;
SHELLEXECUTEINFO sei={sizeof(sei)};
//STARTUPINFO startInfo={sizeof(STARTUPINFO)};
LPTSTR pszExePathBuf=0;
LPTSTR pszParamBuf=0;
LPTSTR p,pCL=GetCommandLine();
UINT len;
const OSVerMaj=GetOSVerMaj();
#ifdef UAC_HACK_FGWND1
HWND hHack=0;
#endif
ASSERT(pXParams);
//GetStartupInfo(&startInfo);
if (ec=InitIPC(hParent,pXParams,NSISStrLen))goto ret;
ASSERT(IsWindow(g.hSrvIPC));
sei.hwnd=hParent;
sei.nShow=/*(startInfo.dwFlags&STARTF_USESHOWWINDOW) ? startInfo.wShowWindow :*/ SW_SHOWNORMAL;
sei.fMask=SEE_MASK_NOCLOSEPROCESS|SEE_MASK_NOZONECHECKS;
sei.lpVerb=_T("runas");
p=FindExePathEnd(pCL);
len=p-pCL;
if (!p || !len)
{
ec=ERROR_FILE_NOT_FOUND;
goto ret;
}
for (;;)
{
NSIS::MemFree(pszExePathBuf);
if (!(pszExePathBuf=(LPTSTR)NSIS::MemAlloc((++len)*sizeof(TCHAR))))goto dieOOM;
if ( GetModuleFileName(0,pszExePathBuf,len) < len )break; //FUCKO: what if GetModuleFileName returns 0?
len+=MAX_PATH;
}
sei.lpFile=pszExePathBuf;
len=lstrlen(p);
len+=20;//space for "/UAC:xxxxxx /NCRC\0"
if (!(pszParamBuf=(LPTSTR)NSIS::MemAlloc(len*sizeof(TCHAR))))goto dieOOM;
wsprintf(pszParamBuf,_T("/UAC:%X /NCRC%s"),g.hSrvIPC,p);//NOTE: The argument parser depends on our special flag appearing first
sei.lpParameters=pszParamBuf;
if (OSVerMaj==5)
{
bool hasseclogon=SysNT5IsSecondaryLogonSvcRunning();
TRACEF("SysNT5IsSecondaryLogonSvcRunning=%d\n",hasseclogon);
if (!hasseclogon)
{
ec=ERROR_SERVICE_NOT_ACTIVE;
goto ret;
}
}
#ifdef UAC_HACK_FGWND1
if ( OSVerMaj>=5 && !sei.hwnd )
{
//sei.nShow=SW_SHOW;//forced, do we HAVE to do this?
hHack=CreateWindowEx(WS_EX_TRANSPARENT|WS_EX_LAYERED|WS_EX_TOOLWINDOW|WS_EX_APPWINDOW,_T("Button"),GetVar(VIDX_EXEFILENAME),0|WS_MAXIMIZE,0,0,0,0,NULL,NULL,NULL,0);
SetWindowLongPtr(hHack,GWLP_WNDPROC,(LONG_PTR)HackWndSubProc);
if (GetSysVer()<6 || MustUseInternalRunAs())
PostMessage(g.hSrvIPC,IPC_HACKFINDRUNAS,0,(LONG_PTR)hHack);
else
SetWindowLongPtr(hHack,GWL_EXSTYLE,GetWindowLongPtr(hHack,GWL_EXSTYLE)&~WS_EX_APPWINDOW);//kill taskbar btn on vista
HICON hIcon=(HICON)LoadImage(GetModuleHandle(0),MAKEINTRESOURCE(103),IMAGE_ICON,0,0,LR_DEFAULTSIZE|LR_SHARED);
SendMessage(hHack,WM_SETICON,ICON_BIG,(LONG_PTR)hIcon);
ShowWindow(hHack,SW_SHOW);
SetForegroundWindow(hHack);
sei.hwnd=hHack;
}
#endif
if (hParent)SetForegroundWindow(hParent);//try to force taskbar button active (for RunElevatedAndProcessMessages, really important on Vista)
#ifdef FEAT_CUSTOMRUNASDLG
if (MustUseInternalRunAs())
{
ec=MyRunAs(g.hInstance,sei);
}
else
#endif
{
if (GetSysVer()>=6)
{
////////sei.nShow=SW_SHOW;
//if ( _SupportsUAC() )sei.hwnd=0; //Vista does not like it when we provide a HWND
//if (_AllowSetForegroundWindow) _AllowSetForegroundWindow(ASFW_ANY);//TODO: is GrantClientWindowInput() enough?
}
#ifdef FEAT_MSRUNASDLGMODHACK
void* hook=MSRunAsDlgMod_Init();
#endif
TRACEF("ForkSelf:calling ShExec:app=%s|params=%s|vrb=%s|hwnd=%X\n",sei.lpFile,sei.lpParameters,sei.lpVerb,sei.hwnd);
ec=(ShellExecuteEx(&sei)?NO_ERROR:GetLastError());
TRACEF("ForkSelf: ShExec->Runas returned %d hInstApp=%d\n",ec,sei.hInstApp);
#ifdef FEAT_MSRUNASDLGMODHACK
MSRunAsDlgMod_Unload(hook);
#endif
}
#ifdef UAC_HACK_FGWND1
DestroyWindow(hHack);
#endif
if (ec)goto ret;
TRACEF("ForkSelf: waiting for process %X (%s|%s|%s)sei.hwnd=%X\n",(sei.hProcess),sei.lpFile,sei.lpParameters,sei.lpVerb,sei.hwnd);
ASSERT(sei.hProcess);
ASSERT(NO_ERROR==ec);
ShowWindow(g.hSrvIPC,SW_HIDE);
g.hElevatedProcess=sei.hProcess;
if (!IsWindow(sei.hwnd))
{
DWORD w=WaitForSingleObject(sei.hProcess,INFINITE);
if (w==WAIT_OBJECT_0)
VERIFY(GetExitCodeProcess(sei.hProcess,&ForkExitCode));
else
{
ec=GetLastError();
TRACEF("ForkSelf:WaitForSingleObject failed ec=%d w=%d\n",ec,w);ASSERT(!"ForkSelf:WaitForSingleObject");
}
}
else
{
bool abortWait=false;
const DWORD waitCount=1;
const HANDLE handles[waitCount]={sei.hProcess};
do
{
DWORD w=MsgWaitForMultipleObjects(waitCount,handles,false,INFINITE,QS_ALLEVENTS|QS_ALLINPUT);
switch(w)
{
case WAIT_OBJECT_0:
VERIFY(GetExitCodeProcess(sei.hProcess,&ForkExitCode));
abortWait=true;
break;
case WAIT_OBJECT_0+waitCount:
{
const HWND hwnd=sei.hwnd;
MSG msg;
while( !ec && PeekMessage(&msg,hwnd,0,0,PM_REMOVE) )
{
if (msg.message==WM_QUIT)
{
ASSERT(0);
ec=ERROR_CANCELLED;
break;
}
if (!IsDialogMessage(hwnd,&msg))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
}
break;
default:
abortWait=true;
ec=GetLastError();
TRACEF("ForkSelf:MsgWaitForMultipleObjects failed, ec=%u w=%u\n",ec,w);
}
} while( NO_ERROR==ec && !abortWait );
}
TRACEF("ForkSelf: wait complete, ec=%d forkexitcode=%u\n",ec,ForkExitCode);
goto ret;
dieOOM:
ec=ERROR_OUTOFMEMORY;
ret:
StopIPCSrv();
CloseHandle(sei.hProcess);
NSIS::MemFree(pszExePathBuf);
NSIS::MemFree(pszParamBuf);
return ec;
}
bool GetIPCSrvWndFromParams(HWND&hwndOut)
{
LPTSTR p=FindExePathEnd(GetCommandLine());
while(p && *p==' ')p=CharNext(p);TRACEF("GetIPCSrvWndFromParams:%s|\n",p);
if (p && *p++=='/'&&*p++=='U'&&*p++=='A'&&*p++=='C'&&*p++==':')
{
hwndOut=(HWND)StrToUInt(p,true);
return !!IsWindow(hwndOut);
}
return false;
}
/*** RunElevated
Return: r0: Windows error code (0 on success, 1223 if user aborted elevation dialog, anything else should be treated as a fatal error)
r1: If r0==0, r1 is:
0 if UAC is not supported by the OS,
1 if UAC was used to elevate and the current process should act like a wrapper (Call Quit in .OnInit without any further processing),
2 if the process is (already?) running @ HighIL (Member of admin group on other systems),
3 if you should call RunElevated again (This can happen if a user without admin priv. is used in the runas dialog),
r2: If r0==0 && r1==1: r2 is the ExitCode of the elevated fork process (The NSIS errlvl is also set to the ExitCode)
r3: If r0==0: r3 is 1 if the user is a member of the admin group or 0 otherwise
*/
EXPORTNSISFUNC RunElevated(HWND hwndNSIS,int StrSize,NSISCH*Vars,NSIS::stack_t **StackTop,NSIS::extra_parameters* XParams)
{
NSISFUNCSTART(hwndNSIS,StrSize,Vars,StackTop,XParams);
BYTE UACMode=0;
bool UserIsAdmin=false;
DWORD ec=ERROR_ACCESS_DENIED,ForkExitCode;
TOKEN_ELEVATION_TYPE tet;
UserIsAdmin=_IsAdmin();
TRACEF("RunElevated:Init: IsAdmin=%d\n",UserIsAdmin);
#ifdef BUILD_XPTEST
if (!UserIsAdmin)goto DbgXPAsUAC;//Skip UAC detection for special debug builds and force a call to runas on <NT6 systems
#endif
if (!_SupportsUAC() && !SysSupportsRunAs())goto noUAC;
if ((ec=DelayLoadDlls()))goto ret;
if (GetIPCSrvWndFromParams(g.hSrvIPC))
{
if (ec=DllSelfAddRef())goto ret;
if (!IsWindow(g.hSrvIPC))ec=ERRAPP_BADIPCSRV;
UACMode=2;
g.UseIPC=true;
if (!UserIsAdmin) //Elevation used, but we are not Admin, let the wrapper know so it can try again...
{
UACMode=0xFF;//Special invalid mode
DWORD_PTR MsgRet;
if (SendIPCMsg(IPC_ELEVATEAGAIN,0,0,MsgRet) || !MsgRet)ec=ERRAPP_BADIPCSRV;//if we could not notify the need for re-elevation, the IPCSrv must be bad
}
goto ret;
}
if ( (ec=DllSelfAddRef()) || (ec=_GetElevationType(&tet)) )goto ret;
if ( tet==TokenElevationTypeFull || UserIsAdmin )
{
UserIsAdmin=true;
UACMode=2;
goto ret;
}
DBGONLY(DBG_RESETDBGVIEW());
#ifdef BUILD_XPTEST
DbgXPAsUAC:VERIFY(!DllSelfAddRef());
#endif
//OS supports UAC and we need to elevate...
ASSERT(!UserIsAdmin);
UACMode=1;
ec=ForkSelf(hwndNSIS,ForkExitCode,XParams,StrSize);
if (!ec && !g.ElevateAgain)
{
SetVarUINT(INST_2,ForkExitCode);
SetErrLvl(XParams,ForkExitCode);
}
goto ret;
noUAC:
ec=NO_ERROR;ASSERT(UACMode==0);
ret:
if (ec==ERROR_CANCELLED)
{
if (UACMode!=1)ec=ERROR_INVALID_FUNCTION;
if (UACMode<2)g.UseIPC=false;
}
if (UACMode==0xFF && !ec) //We called IPC_ELEVATEAGAIN, so we need to quit so the wrapper gains control
{
ASSERT(g.UseIPC);
UACMode=1;//We pretend to be the wrapper so Quit gets called in .OnInit
SetErrLvl(XParams,0);
_Unload();
}
if (g.ElevateAgain)
{
ASSERT(!g.UseIPC);
UACMode=3;//Fork called IPC_ELEVATEAGAIN, we need to change our UACMode so the wrapper(our instance) can try to elevate again if it wants to
}
if (!g.UseIPC)
_Unload();//The wrapper can call quit in .OnInit without calling UAC::Unload, so we do it here
SetVarUINT(INST_0,ec);
SetVarUINT(INST_1,UACMode);
SetVarUINT(INST_3,UserIsAdmin);
TRACEF("RunElevated returning ec=%X UACMode=%d g.UseIPC=%d g.ElevateAgain=%d IsAdmin=%d\n",ec,UACMode,g.UseIPC,g.ElevateAgain,UserIsAdmin);
NSISFUNCEND();
}
/*** Exec
Notes:
<09> ErrorFlag is also set on error
STACK: <ShowWindow> <File> <Parameters> <WorkingDir>
Return: windows error code in r0, 0 on success
*/
EXPORTNSISFUNC Exec(HWND hwndNSIS,int StrSize,NSISCH*Vars,NSIS::stack_t **StackTop,NSIS::extra_parameters* XParams)
{
NSISFUNCSTART(hwndNSIS,StrSize,Vars,StackTop,XParams);
HandleExecExport(true,false,hwndNSIS,StrSize,Vars,StackTop,XParams);
NSISFUNCEND();
}
/*** ExecWait
Notes:
<09> ErrorFlag is also set on error
STACK: <ShowWindow> <File> <Parameters> <WorkingDir>
Return:
r0: windows error code, 0 on success
r1: exitcode of new process
*/
EXPORTNSISFUNC ExecWait(HWND hwndNSIS,int StrSize,NSISCH*Vars,NSIS::stack_t **StackTop,NSIS::extra_parameters* XParams)
{
NSISFUNCSTART(hwndNSIS,StrSize,Vars,StackTop,XParams);
HandleExecExport(true,true,hwndNSIS,StrSize,Vars,StackTop,XParams);
NSISFUNCEND();
}
/*** ShellExec
Notes:
<09> ErrorFlag is also set on error
STACK: <Verb> <ShowWindow> <File> <Parameters> <WorkingDir>
Return: windows error code in r0, 0 on success
*/
EXPORTNSISFUNC ShellExec(HWND hwndNSIS,int StrSize,NSISCH*Vars,NSIS::stack_t **StackTop,NSIS::extra_parameters* XParams)
{
NSISFUNCSTART(hwndNSIS,StrSize,Vars,StackTop,XParams);
HandleExecExport(false,false,hwndNSIS,StrSize,Vars,StackTop,XParams);
NSISFUNCEND();
}
/*** ShellExecWait
Notes:
<09> ErrorFlag is also set on error
STACK: <Verb> <ShowWindow> <File> <Parameters> <WorkingDir>
Return:
r0: windows error code, 0 on success
r1: exitcode of new process
*/
EXPORTNSISFUNC ShellExecWait(HWND hwndNSIS,int StrSize,NSISCH*Vars,NSIS::stack_t **StackTop,NSIS::extra_parameters* XParams)
{
NSISFUNCSTART(hwndNSIS,StrSize,Vars,StackTop,XParams);
HandleExecExport(false,true,hwndNSIS,StrSize,Vars,StackTop,XParams);
NSISFUNCEND();
}
/*** GetElevationType
Notes:
TokenElevationTypeDefault=1 :User is not using a split token (UAC disabled)
TokenElevationTypeFull=2 :UAC enabled, the process is elevated
TokenElevationTypeLimited=3 :UAC enabled, the process is not elevated
Return: r0: (TOKEN_ELEVATION_TYPE)TokenType, The error flag is set if the function fails and r0==0
*/
EXPORTNSISFUNC GetElevationType(HWND hwndNSIS,int StrSize,NSISCH*Vars,NSIS::stack_t **StackTop,NSIS::extra_parameters* XParams)
{
NSISFUNCSTART(hwndNSIS,StrSize,Vars,StackTop,XParams);
TOKEN_ELEVATION_TYPE tet=(TOKEN_ELEVATION_TYPE)NULL; //Default to invalid value
if (MaintainDllSelfRef() || /*!_SupportsUAC() ||*/ _GetElevationType(&tet)) SetErrorFlag(XParams);
SetVarUINT(INST_0,tet);
NSISFUNCEND();
}
/*** SupportsUAC
Notes: Check if the OS supports UAC (And the user has UAC turned on)
Return: r0: (BOOL)Result
*/
EXPORTNSISFUNC SupportsUAC(HWND hwndNSIS,int StrSize,NSISCH*Vars,NSIS::stack_t **StackTop,NSIS::extra_parameters* XParams)
{
NSISFUNCSTART(hwndNSIS,StrSize,Vars,StackTop,XParams);
BOOL present=false;
MaintainDllSelfRef();
if (_SupportsUAC())present=true;
SetVarUINT(INST_0,present);
NSISFUNCEND();
}
/*** IsAdmin
Return: r0: (BOOL)Result
*/
EXPORTNSISFUNC IsAdmin(HWND hwndNSIS,int StrSize,NSISCH*Vars,NSIS::stack_t **StackTop,NSIS::extra_parameters* XParams)
{
NSISFUNCSTART(hwndNSIS,StrSize,Vars,StackTop,XParams);
bool Admin=false;
DWORD ec;
if ( !(ec=MaintainDllSelfRef()) )
{
Admin=_IsAdmin();
if ( ec=GetLastError() )
{
TRACEF("IsAdmin failed with %d\n",ec);
SetErrorFlag(XParams);
Admin=false;
}
}
SetVarUINT(INST_0,Admin);
NSISFUNCEND();
}
/*** ExecCodeSegment
Notes: Sets error flag on error
There is currently no way to transfer state to/from the executed code segment!
If you use instructions that alter the UI or the stack/variables in the code segment (StrCpy,Push/Pop/Exch,DetailPrint,HideWindow etc.) they will affect the hidden wrapper installer and not "your" installer instance!
*/
EXPORTNSISFUNC ExecCodeSegment(HWND hwndNSIS,int StrSize,NSISCH*Vars,NSIS::stack_t **StackTop,NSIS::extra_parameters* XParams)
{
NSISFUNCSTART(hwndNSIS,StrSize,Vars,StackTop,XParams);
DWORD ec;
if (!(ec=DllSelfAddRef())) //force AddRef since our plugin could be called inside the executed code segment!
{
ec=ERROR_INVALID_PARAMETER;
stack_t* pSI=StackPop();
if (pSI)
{
BOOL badCh;
UINT pos=StrToUInt(pSI->text,false,&badCh);
TRACEF("ExecCodeSegment %d (%s) badinput=%d\n",pos-1,pSI->text,badCh);
if (!badCh && pos--)
{
if (!g.UseIPC)
ec=NSIS::ExecuteCodeSegment(XParams,pos);
else
{
SyncVars(hwndNSIS);
DWORD_PTR MsgRet;
AllowOuterInstanceWindowFocus();
if (!(ec=SendIPCMsg(IPC_EXECCODESEGMENT,pos,0,MsgRet,INFINITE)))ec=MsgRet-1;
}
}
StackFreeItem(pSI);
}
}
if (ec)SetErrorFlag(XParams);
NSISFUNCEND();
}
/*** StackPush */
EXPORTNSISFUNC StackPush(HWND hwndNSIS,int StrSize,NSISCH*Vars,NSIS::stack_t **StackTop,NSIS::extra_parameters* XParams)
{
NSISFUNCSTART(hwndNSIS,StrSize,Vars,StackTop,XParams);
DWORD ec;
if (!(ec=DllSelfAddRef()) && g.UseIPC)
{
stack_t* pSI=StackPop();
ec=ERROR_INVALID_PARAMETER;
if (pSI)
{
DWORD_PTR MsgRet;
COPYDATASTRUCT cds={CDI_STACKPUSH,(lstrlen(pSI->text)+1)*sizeof(NSISCH),pSI->text};
if (!(ec=SendIPCMsg(WM_COPYDATA,(WPARAM)hwndNSIS,(LPARAM)&cds,MsgRet,5000 )))ec=MsgRet-1;
StackFreeItem(pSI);
}
}
if (ec)SetErrorFlag(XParams);
NSISFUNCEND();
}
/*** GetOuterHwnd */
EXPORTNSISFUNC GetOuterHwnd(HWND hwndNSIS,int StrSize,NSISCH*Vars,NSIS::stack_t **StackTop,NSIS::extra_parameters* XParams)
{
NSISFUNCSTART(hwndNSIS,StrSize,Vars,StackTop,XParams);
MaintainDllSelfRef();
DWORD_PTR MsgRet;HWND hSrvIPC;
if (!GetIPCSrvWndFromParams(hSrvIPC)||!IsWindow(hSrvIPC)||SendIPCMsg(IPC_GETSRVHWND,0,0,MsgRet,IPCTOUT_DEF,hSrvIPC))MsgRet=0;
SetVarUINT(INST_0,MsgRet);
NSISFUNCEND();
}
DWORD SetPrivilege(LPCSTR PrivName,bool Enable)
{
DWORD r=NO_ERROR;
HANDLE hToken=NULL;
TOKEN_PRIVILEGES TokenPrivs;
if (!LookupPrivilegeValueA(NULL,PrivName,&TokenPrivs.Privileges[0].Luid))goto dieGLE;
if (!_OpenProcessToken(GetCurrentProcess (),TOKEN_ADJUST_PRIVILEGES,&hToken))goto dieGLE;
TokenPrivs.Privileges[0].Attributes = Enable ? SE_PRIVILEGE_ENABLED : 0 ;
TokenPrivs.PrivilegeCount=1;
if (AdjustTokenPrivileges(hToken,FALSE,&TokenPrivs,0,NULL,NULL))goto ret;
dieGLE:
r=GetLastError();
ret:
CloseHandle(hToken);
return r;
}
#include <shlobj.h>
/*** GetShellFolderPath */
EXPORTNSISFUNC GetShellFolderPath(HWND hwndNSIS,int StrSize,NSISCH*Vars,NSIS::stack_t **StackTop,NSIS::extra_parameters* XParams)
{
/*
WINBUG:
For some reason, even with debug priv. enabled, a call to OpenProcessToken(TOKEN_READ|TOKEN_IMPERSONATE)
will fail, even if we have a PROCESS_ALL_ACCESS handle on XP when running as a member of the Power Users Group.
So we have to ask the outer process to give us the handle.
*/
NSISFUNCSTART(hwndNSIS,StrSize,Vars,StackTop,XParams);
MaintainDllSelfRef();
const unsigned TokenAccessRights=TOKEN_READ|TOKEN_IMPERSONATE;
unsigned clsidFolder;
HWND hSrvIPC;
HANDLE hToken=NULL,hOuterProcess=NULL;
DWORD SrvPID;
HRESULT hr;
FARPROC pfnSHGetFolderPath;
LPTSTR buf=GetVar(INST_0);
stack_t*pssCLSID=StackPop();
stack_t*pssFallback=NULL;
BOOL ConvBadCh;
if (!pssCLSID || !(pssFallback=StackPop())) goto fail;
clsidFolder=StrToUInt(pssCLSID->text,false,&ConvBadCh);
if (ConvBadCh)goto fail;
TRACEF("GetShellFolderPath HasIPCServer=%X param=%s>%X fallback=%s|\n",HasIPCServer(),pssCLSID->text,clsidFolder,pssFallback->text);
pfnSHGetFolderPath=GetProcAddress(GetModuleHandle(_T("SHELL32")),"SHGetFolderPath"__DLD_FUNCSUFFIX);
if (!pfnSHGetFolderPath)goto fail;
if (GetIPCSrvWndFromParams(hSrvIPC) && 0 != GetWindowThreadProcessId(hSrvIPC,&SrvPID))
{
hOuterProcess=OpenProcess(PROCESS_QUERY_INFORMATION,false,SrvPID);
if (hOuterProcess)
{
BOOL bOk=OpenProcessToken(hOuterProcess,TokenAccessRights,&hToken);
CloseHandle(hOuterProcess);
if (bOk)goto gotToken;
}
/* SetPrivilege(SE_DEBUG_NAME,true);
hOuterProcess=OpenProcess(PROCESS_DUP_HANDLE,false,SrvPID);
SetPrivilege(SE_DEBUG_NAME,false);
if (!hOuterProcess)goto fail;
TRACEF("hOuterProcess=%X\n",hOuterProcess);
*/ SendIPCMsg(IPC_GETOUTERPROCESSTOKEN,0,TokenAccessRights,(DWORD_PTR&)hToken,IPCTOUT_DEF,hSrvIPC);
TRACEF("IPC_GETOUTERPROCESSTOKEN=%X\n",hToken);//*/
}
gotToken:
if (HasIPCServer() && !hToken)goto fail;
hr=((HRESULT(WINAPI*)(HWND,int,HANDLE,DWORD,LPTSTR))pfnSHGetFolderPath)(hwndNSIS,clsidFolder,hToken,SHGFP_TYPE_CURRENT,buf);
TRACEF("SHGetFolderPath hr=%X with token=%X, clsidFolder=%X|%s\n",hr,hToken,clsidFolder,buf);
if (FAILED(hr)) goto fail; else goto ret;
fail:
TRACEF("GetShellFolderPath GLE=%X\n",GetLastError());
lstrcpy(buf,pssFallback->text);
TRACEF("%s|%s\n",buf,pssFallback->text);
ret:
// CloseHandle(hOuterProcess);
CloseHandle(hToken);
StackFreeItem(pssFallback);
StackFreeItem(pssCLSID);
NSISFUNCEND();
}
/*** GetOuterPID * /
EXPORTNSISFUNC GetOuterPID(HWND hwndNSIS,int StrSize,NSISCH*Vars,NSIS::stack_t **StackTop,NSIS::extra_parameters* XParams)
{
NSISFUNCSTART(hwndNSIS,StrSize,Vars,StackTop,XParams);
MaintainDllSelfRef();
DWORD_PTR Ret=0;
HWND hSrvIPC;
if (GetIPCSrvWndFromParams(hSrvIPC))
if (0==GetWindowThreadProcessId(hSrvIPC,&Ret))Ret=0;
SetVarUINT(INST_0,Ret);
NSISFUNCEND();
}//*/
/*** Unload
Notes: Call in .OnInstFailed AND .OnInstSuccess !
*/
EXPORTNSISFUNC Unload(HWND hwndNSIS,int StrSize,NSISCH*Vars,NSIS::stack_t **StackTop)
{
NSISFUNCSTART4(hwndNSIS,StrSize,Vars,StackTop);
if (!MaintainDllSelfRef())_Unload(); else ASSERT(!"MaintainDllSelfRef failed in Unload!");
NSISFUNCEND();
}
#ifdef _DEBUG
BOOL WINAPI DllMain(HINSTANCE hInst,DWORD Event,LPVOID lpReserved)
#else
extern "C" BOOL WINAPI _DllMainCRTStartup(HINSTANCE hInst,ULONG Event,LPVOID lpReserved)
#endif
{
if (Event==DLL_PROCESS_ATTACH)
{
TRACEF("************************************ DllMain %u\n",GetCurrentProcessId());
ASSERT(!_OpenProcessToken && !_EqualSid);
g.hInstance=hInst;
}
// DBGONLY( if (Event==DLL_PROCESS_DETACH){ASSERT(g.DllRef==0);}TRACE("DLL_PROCESS_DETACH\n"); );//Make sure we unloaded so we don't lock $pluginsdir
return TRUE;
}