PageRenderTime 256ms CodeModel.GetById 100ms app.highlight 75ms RepoModel.GetById 76ms app.codeStats 0ms

/admin/win/nsi/nsis_uac/RunAs.cpp

http://github.com/tomahawk-player/tomahawk
C++ | 277 lines | 239 code | 23 blank | 15 comment | 35 complexity | 6610332630a7de437049e0d5339b3337 MD5 | raw file
  1//Copyright (C) 2007 Anders Kjersem. Licensed under the zlib/libpng license, see License.txt for details.
  2/*
  3If UAC is disabled, the runas verb is broken (Vista RTM) so when running as LUA there is no way to elevate so 
  4we provide our own dialog.
  5*/
  6
  7#include "UAC.h"

  8#ifdef FEAT_CUSTOMRUNASDLG

  9#include <Lmcons.h>//UNLEN && GNLEN && PWLEN

 10#include <WindowsX.h>

 11#include "resource.h"

 12#include "NSISUtil.h"

 13using namespace NSIS;
 14#define ERRAPP_TRYAGAIN (0x20000000|1)

 15#define MYMAX_DOMAIN (2+max(GNLEN,MAX_COMPUTERNAME_LENGTH)+1)

 16
 17
 18static LPCTSTR g_RunAsDlgTitle=_T("Run as");
 19static LPCTSTR g_RunAsHelpText=_T("You may not have the necessary permissions to use all the features of the program you are about to run. You may run this program as a different user or continue to run the program as the current user.");
 20static LPCTSTR g_RunAsCurrUsrFmt=_T("&Current user (%s)");//Max 50 chars!
 21static LPCTSTR g_RunAsSpecHelp=_T("Run the program as the &following user:");
 22
 23FORCEINLINE bool MySetDlgItemText(HWND hDlg,int id,LPCTSTR s) {return MySndDlgItemMsg(hDlg,id,WM_SETTEXT,0,(LPARAM)s)!=0;}
 24
 25typedef struct {
 26	SHELLEXECUTEINFO*pSEI;
 27	bool AsSelf;
 28} RUNASDLGDATA;
 29
 30void MyRunAsFmtCurrUserRadio(HWND hDlg,LPCTSTR Fmt) {
 31	TCHAR bufFullName[MYMAX_DOMAIN+UNLEN+1];
 32	TCHAR buf[50+MYMAX_DOMAIN+UNLEN+1];
 33	*bufFullName=0;
 34	ULONG cch;
 35	if ((!_GetUserNameEx || !_GetUserNameEx(NameSamCompatible,bufFullName,&(cch=COUNTOF(bufFullName)))) && 
 36		!_GetUserName(bufFullName,&(cch=COUNTOF(bufFullName))) ) {
 37		*bufFullName=0;
 38	}
 39	wsprintf(buf,Fmt,*bufFullName?bufFullName:_T("?"));
 40	MySetDlgItemText(hDlg,IDC_RUNASCURR,buf);
 41
 42	// default the "User name:" to Administrator from shell32
 43	if (LoadString(GetModuleHandle(_T("SHELL32.dll")),21763, bufFullName, COUNTOF(bufFullName)) > 0) {
 44		MySetDlgItemText(hDlg,IDC_USERNAME,bufFullName);
 45	}
 46}
 47
 48#ifdef FEAT_CUSTOMRUNASDLG_TRANSLATE

 49void MyRunAsTranslateDlgString(LPCTSTR StrID,LPTSTR Ini,HWND hDlg,INT_PTR DlgItemId,int special=0) {
 50	TCHAR buf[MAX_PATH*2];
 51	DWORD len=GetPrivateProfileString(_T("MyRunAsStrings"),StrID,0,buf,ARRAYSIZE(buf),Ini);
 52	if (len) {
 53		if (IDC_RUNASCURR==special)
 54			MyRunAsFmtCurrUserRadio(hDlg,buf);
 55		else
 56			(DlgItemId==-1) ? SetWindowText(hDlg,buf) : MySetDlgItemText(hDlg,DlgItemId,buf);
 57	}
 58}
 59
 60void MyRunAsTranslateDlg(HWND hDlg) {
 61	DWORD len;
 62	TCHAR buf[MAX_PATH*2];
 63	HMODULE hDll=GetWindowInstance(hDlg);ASSERT(hDll);
 64	if ( (len=GetModuleFileName(hDll,buf,ARRAYSIZE(buf))) <1)return;
 65	buf[len-3]=0;
 66	lstrcat(buf,_T("lng"));
 67	MyRunAsTranslateDlgString(_T("DlgTitle"),buf,hDlg,-1);
 68	MyRunAsTranslateDlgString(_T("HelpText"),buf,hDlg,IDC_HELPTEXT);
 69	MyRunAsTranslateDlgString(_T("OptCurrUser"),buf,hDlg,IDC_RUNASCURR,IDC_RUNASCURR);
 70	MyRunAsTranslateDlgString(_T("OptOtherUser"),buf,hDlg,IDC_RUNASSPEC);
 71	MyRunAsTranslateDlgString(_T("Username"),buf,hDlg,IDC_LBLUSER);
 72	MyRunAsTranslateDlgString(_T("Pwd"),buf,hDlg,IDC_LBLPWD);
 73	MyRunAsTranslateDlgString(_T("OK"),buf,hDlg,IDOK);
 74	MyRunAsTranslateDlgString(_T("Cancel"),buf,hDlg,IDCANCEL);
 75	HWND h=GetDlgItem(hDlg,IDC_RUNASCURR);
 76	if (GetPrivateProfileInt(_T("MyRunAsCfg"),_T("DisableCurrUserOpt"),false,buf))EnableWindow(h,false);
 77	if (GetPrivateProfileInt(_T("MyRunAsCfg"),_T("HideCurrUserOpt"),false,buf))ShowWindow(h,false);
 78}
 79#endif

 80
 81bool ErrorIsLogonError(DWORD err) {
 82	switch (err) {
 83	case ERROR_LOGON_FAILURE:
 84	case ERROR_ACCOUNT_RESTRICTION:
 85	case ERROR_INVALID_LOGON_HOURS:
 86	case ERROR_INVALID_WORKSTATION:
 87	case ERROR_PASSWORD_EXPIRED:
 88	case ERROR_ACCOUNT_DISABLED:
 89	case ERROR_NONE_MAPPED:
 90	case ERROR_NO_SUCH_USER:
 91	case ERROR_INVALID_ACCOUNT_NAME:
 92		return true;
 93	}
 94	return false;
 95}
 96
 97
 98
 99void VerifyOKBtn(HWND hDlg,RUNASDLGDATA*pRADD) {
100	const bool HasText=pRADD?(pRADD->AsSelf?true:MySndDlgItemMsg(hDlg,IDC_USERNAME,WM_GETTEXTLENGTH)>0):false;
101	EnableWindow(GetDlgItem(hDlg,IDOK),HasText);
102}
103
104void SetDlgState(HWND hDlg,bool AsSelf,RUNASDLGDATA*pRADD) {
105	if (pRADD)pRADD->AsSelf=AsSelf;
106	MySndDlgItemMsg(hDlg,IDC_RUNASCURR,BM_SETCHECK,AsSelf?BST_CHECKED:BST_UNCHECKED);
107	MySndDlgItemMsg(hDlg,IDC_RUNASSPEC,BM_SETCHECK,!AsSelf?BST_CHECKED:BST_UNCHECKED);
108	int ids[]={IDC_USERNAME,IDC_PASSWORD,IDC_LBLUSER,IDC_LBLPWD};
109	for (int i=0; i<COUNTOF(ids);++i)EnableWindow(GetDlgItem(hDlg,ids[i]),!AsSelf);
110	VerifyOKBtn(hDlg,pRADD);
111}
112
113INT_PTR CALLBACK MyRunAsDlgProc(HWND hwnd,UINT uMsg,WPARAM wp,LPARAM lp) {
114	RUNASDLGDATA*pRADD=(RUNASDLGDATA*)GetWindowLongPtr(hwnd,GWLP_USERDATA);
115	switch(uMsg) {
116	//case WM_DESTROY:
117	//	break;
118	case WM_CLOSE:
119		return DestroyWindow(hwnd);
120	case WM_INITDIALOG:
121		{	
122			pRADD=(RUNASDLGDATA*)lp;ASSERT(pRADD);
123			SetWindowLongPtr(hwnd,GWLP_USERDATA,lp);
124			Edit_LimitText(GetDlgItem(hwnd,IDC_USERNAME),UNLEN+1+MYMAX_DOMAIN); //room for "foo@BAR" or "BAR\foo"
125			Edit_LimitText(GetDlgItem(hwnd,IDC_PASSWORD),PWLEN);
126			const HINSTANCE hSh32=GetModuleHandle(_T("SHELL32.dll"));
127			const HICON hIco=(HICON)LoadImage(hSh32,MAKEINTRESOURCE(194),IMAGE_ICON,32,32,LR_SHARED);
128			MySndDlgItemMsg(hwnd,IDC_SHICON,STM_SETICON,(WPARAM)hIco);
129			SendMessage(hwnd,WM_SETTEXT,0,(LPARAM)g_RunAsDlgTitle);
130			MySetDlgItemText(hwnd,IDC_HELPTEXT,g_RunAsHelpText);
131			MyRunAsFmtCurrUserRadio(hwnd,g_RunAsCurrUsrFmt);
132			MySetDlgItemText(hwnd,IDC_RUNASSPEC,g_RunAsSpecHelp);
133#ifdef FEAT_CUSTOMRUNASDLG_TRANSLATE

134			MyRunAsTranslateDlg(hwnd);
135#endif

136			SetDlgState(hwnd,false,pRADD);
137
138#if defined(BUILD_DBG) && 0 //auto login used during testing ;)
139			SetDlgItemText(hwnd,IDC_USERNAME,_T("root"));
140			SetDlgItemText(hwnd,IDC_PASSWORD,_T("???"));
141			Sleep(1);PostMessage(hwnd,WM_COMMAND,IDOK,0);
142#endif

143		}
144		return true;
145	case WM_COMMAND:
146		{
147			switch(HIWORD(wp)) {
148			case EN_CHANGE:
149				VerifyOKBtn(hwnd,pRADD);
150				break;
151			case EN_SETFOCUS:
152			case BN_CLICKED:
153				if (LOWORD(wp)<=IDCANCEL)break;
154				SetDlgState(hwnd,LOWORD(wp)==IDC_RUNASCURR,pRADD);
155				return FALSE;
156			}
157			INT_PTR exitcode=!pRADD?-1:IDCANCEL;
158			switch(LOWORD(wp)) {
159			case IDOK:
160				if (pRADD) {
161					SHELLEXECUTEINFO&sei=*pRADD->pSEI;
162					PROCESS_INFORMATION pi={0};
163					DWORD ec=NO_ERROR;
164					WCHAR*wszExec;//Also used as TCHAR buffer in AsSelf mode
165					bool PerformTCharFmt=pRADD->AsSelf;
166					//const DWORD CommonStartupInfoFlags=STARTF_FORCEONFEEDBACK;
167#ifdef UNICODE

168					PerformTCharFmt=true;
169#endif

170					wszExec=(WCHAR*)NSIS::MemAlloc( (pRADD->AsSelf?sizeof(TCHAR):sizeof(WCHAR)) *(lstrlen(sei.lpFile)+1+lstrlen(sei.lpParameters)+1));
171					if (!wszExec)ec=ERROR_OUTOFMEMORY;
172					if (PerformTCharFmt)wsprintf((TCHAR*)wszExec,_T("%s%s%s"),sei.lpFile,((sei.lpParameters&&*sei.lpParameters)?_T(" "):_T("")),sei.lpParameters);
173					if (!ec) {
174						if (pRADD->AsSelf) {
175							STARTUPINFO si={sizeof(si)};
176							TRACEF("MyRunAs:CreateProcess:%s|\n",wszExec);
177							ec=(CreateProcess(0,(TCHAR*)wszExec,0,0,false,0,0,0,&si,&pi)?NO_ERROR:GetLastError());
178						}
179						else {
180							//All Wide strings!
181							WCHAR wszPwd[PWLEN+1];
182							WCHAR wszUName[UNLEN+1+MYMAX_DOMAIN+1];
183							STARTUPINFOW siw={sizeof(siw)};
184							WCHAR*p;
185#ifndef UNICODE

186							//Build unicode string, we already know the buffer is big enough so no error handling
187							p=wszExec;
188							MultiByteToWideChar(CP_THREAD_ACP,0,sei.lpFile,-1,p,0xFFFFFF);
189							if (sei.lpParameters && *sei.lpParameters) {
190								p+=lstrlen(sei.lpFile);*p++=L' ';*p=0;
191								MultiByteToWideChar(CP_THREAD_ACP,0,sei.lpParameters,-1,p,0xFFFFFF);
192							}
193#endif

194							SendMessageW(GetDlgItem(hwnd,IDC_USERNAME),WM_GETTEXT,COUNTOF(wszUName),(LPARAM)wszUName);
195							SendMessageW(GetDlgItem(hwnd,IDC_PASSWORD),WM_GETTEXT,COUNTOF(wszPwd),(LPARAM)wszPwd);
196							
197							//Try to find [\\]domain\user and split into username and domain strings
198							WCHAR*pUName=wszUName,*pDomain=0;
199							p=wszUName;
200							//if (*p==p[1]=='\\')pUName=(p+=2);else \  //Should we still split things up if the string starts with \\ ? Is it possible to use \\machine\user at all?
201							++p;//Don't parse "\something", require at least one char before backslash "?[*\]something"
202							while(*p && *p!='\\')++p;
203							if (*p=='\\') { 
204								pDomain=pUName;
205								pUName=p+1;*p=0;
206							}
207
208							TRACEF("MyRunAs:CreateProcessWithLogonW:%ws|%ws|%ws|%ws|\n",pUName,pDomain?pDomain:L"NO?DOMAIN",wszPwd,wszExec);
209							ec=(_CreateProcessWithLogonW(pUName,pDomain?pDomain:0,wszPwd,LOGON_WITH_PROFILE,0,wszExec,0,0,0,&siw,&pi)?NO_ERROR:GetLastError());
210							TRACEF("MyRunAs:CreateProcessWithLogonW: ret=%u\n",ec);
211							SecureZeroMemory(wszPwd,sizeof(wszPwd));//if (wszPwd) {volatile WCHAR*_p=wszPwd;for(;_p&&*_p;++_p)*_p=1;if (_p)*wszPwd=0;}//Burn password (And attempt to prevent compiler from removing it)	
212							if (ec && ErrorIsLogonError(ec)) {
213								LPTSTR szMsg;
214								DWORD ret=FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER|FORMAT_MESSAGE_FROM_SYSTEM|FORMAT_MESSAGE_IGNORE_INSERTS,0,ec,MAKELANGID(LANG_NEUTRAL,SUBLANG_DEFAULT),(LPTSTR)&szMsg,0,0);
215								if (ret) {
216									ec=ERRAPP_TRYAGAIN;
217									MessageBox(hwnd,szMsg,0,MB_ICONWARNING);
218									LocalFree(szMsg);
219								}
220								else ec=GetLastError();
221							}
222						}
223					}
224					NSIS::MemFree(wszExec);
225					if (pi.hThread)CloseHandle(pi.hThread);
226					if (ERRAPP_TRYAGAIN==ec)break;
227					if (ec) {
228						SetLastError(ec);
229						exitcode=-1;
230					}
231					else {
232						pRADD->pSEI->hProcess=pi.hProcess;
233						exitcode=IDOK;
234					}
235				}
236			case IDCANCEL:
237				EndDialog(hwnd,exitcode);
238			}
239		}
240		break;
241	}
242	return FALSE;
243}
244
245DWORD MyRunAs(HINSTANCE hInstDll,SHELLEXECUTEINFO&sei) {
246	INT_PTR ec;
247	ASSERT(sei.cbSize>=sizeof(SHELLEXECUTEINFO) && hInstDll);
248	if (ec=DelayLoadDlls())return ec;
249	ASSERT(_CreateProcessWithLogonW && _GetUserName);
250	RUNASDLGDATA radd={0};
251	radd.pSEI=&sei;
252	ec=DialogBoxParam(hInstDll,MAKEINTRESOURCE(IDD_MYRUNAS),sei.hwnd,MyRunAsDlgProc,(LPARAM)&radd);
253	TRACEF("MyRunAs returned %d (%s|%s)\n",ec,sei.lpFile,sei.lpParameters);
254	switch(ec) {
255	case 0:
256		return ERROR_INVALID_HANDLE;//DialogBoxParam returns 0 on bad hwnd
257	case IDOK:
258		return NO_ERROR;
259	case IDCANCEL:
260		return ERROR_CANCELLED;
261	}
262	//TODO:BUGBUG: on vista, the last error seems to get lost, should probably put it in RUNASDLGDATA and always return IDOK
263	return GetLastError();
264}
265
266
267#ifdef BUILD_DBG

268// RunDll exports are __stdcall, we dont care about that for this debug export, rundll32.exe is able to handle this mistake
269extern "C" void __declspec(dllexport) __cdecl DBGRDMyRunAs(HWND hwnd,HINSTANCE hinst,LPTSTR lpCmdLine,int nCmdShow) {
270	SHELLEXECUTEINFO sei={sizeof(sei)};
271	sei.lpFile=_T("Notepad.exe");//sei.lpParameters=_T("param1");
272	TRACEF("ec=%d\n",MyRunAs(GetModuleHandle(_T("UAC.dll")),sei));
273}
274#endif

275
276#endif /* FEAT_CUSTOMRUNASDLG */

277