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

http://github.com/tomahawk-player/tomahawk · C++ · 277 lines · 239 code · 23 blank · 15 comment · 40 complexity · 6610332630a7de437049e0d5339b3337 MD5 · raw file

  1. //Copyright (C) 2007 Anders Kjersem. Licensed under the zlib/libpng license, see License.txt for details.
  2. /*
  3. If UAC is disabled, the runas verb is broken (Vista RTM) so when running as LUA there is no way to elevate so
  4. we provide our own dialog.
  5. */
  6. #include "UAC.h"
  7. #ifdef FEAT_CUSTOMRUNASDLG
  8. #include <Lmcons.h>//UNLEN && GNLEN && PWLEN
  9. #include <WindowsX.h>
  10. #include "resource.h"
  11. #include "NSISUtil.h"
  12. using namespace NSIS;
  13. #define ERRAPP_TRYAGAIN (0x20000000|1)
  14. #define MYMAX_DOMAIN (2+max(GNLEN,MAX_COMPUTERNAME_LENGTH)+1)
  15. static LPCTSTR g_RunAsDlgTitle=_T("Run as");
  16. static 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.");
  17. static LPCTSTR g_RunAsCurrUsrFmt=_T("&Current user (%s)");//Max 50 chars!
  18. static LPCTSTR g_RunAsSpecHelp=_T("Run the program as the &following user:");
  19. FORCEINLINE bool MySetDlgItemText(HWND hDlg,int id,LPCTSTR s) {return MySndDlgItemMsg(hDlg,id,WM_SETTEXT,0,(LPARAM)s)!=0;}
  20. typedef struct {
  21. SHELLEXECUTEINFO*pSEI;
  22. bool AsSelf;
  23. } RUNASDLGDATA;
  24. void MyRunAsFmtCurrUserRadio(HWND hDlg,LPCTSTR Fmt) {
  25. TCHAR bufFullName[MYMAX_DOMAIN+UNLEN+1];
  26. TCHAR buf[50+MYMAX_DOMAIN+UNLEN+1];
  27. *bufFullName=0;
  28. ULONG cch;
  29. if ((!_GetUserNameEx || !_GetUserNameEx(NameSamCompatible,bufFullName,&(cch=COUNTOF(bufFullName)))) &&
  30. !_GetUserName(bufFullName,&(cch=COUNTOF(bufFullName))) ) {
  31. *bufFullName=0;
  32. }
  33. wsprintf(buf,Fmt,*bufFullName?bufFullName:_T("?"));
  34. MySetDlgItemText(hDlg,IDC_RUNASCURR,buf);
  35. // default the "User name:" to Administrator from shell32
  36. if (LoadString(GetModuleHandle(_T("SHELL32.dll")),21763, bufFullName, COUNTOF(bufFullName)) > 0) {
  37. MySetDlgItemText(hDlg,IDC_USERNAME,bufFullName);
  38. }
  39. }
  40. #ifdef FEAT_CUSTOMRUNASDLG_TRANSLATE
  41. void MyRunAsTranslateDlgString(LPCTSTR StrID,LPTSTR Ini,HWND hDlg,INT_PTR DlgItemId,int special=0) {
  42. TCHAR buf[MAX_PATH*2];
  43. DWORD len=GetPrivateProfileString(_T("MyRunAsStrings"),StrID,0,buf,ARRAYSIZE(buf),Ini);
  44. if (len) {
  45. if (IDC_RUNASCURR==special)
  46. MyRunAsFmtCurrUserRadio(hDlg,buf);
  47. else
  48. (DlgItemId==-1) ? SetWindowText(hDlg,buf) : MySetDlgItemText(hDlg,DlgItemId,buf);
  49. }
  50. }
  51. void MyRunAsTranslateDlg(HWND hDlg) {
  52. DWORD len;
  53. TCHAR buf[MAX_PATH*2];
  54. HMODULE hDll=GetWindowInstance(hDlg);ASSERT(hDll);
  55. if ( (len=GetModuleFileName(hDll,buf,ARRAYSIZE(buf))) <1)return;
  56. buf[len-3]=0;
  57. lstrcat(buf,_T("lng"));
  58. MyRunAsTranslateDlgString(_T("DlgTitle"),buf,hDlg,-1);
  59. MyRunAsTranslateDlgString(_T("HelpText"),buf,hDlg,IDC_HELPTEXT);
  60. MyRunAsTranslateDlgString(_T("OptCurrUser"),buf,hDlg,IDC_RUNASCURR,IDC_RUNASCURR);
  61. MyRunAsTranslateDlgString(_T("OptOtherUser"),buf,hDlg,IDC_RUNASSPEC);
  62. MyRunAsTranslateDlgString(_T("Username"),buf,hDlg,IDC_LBLUSER);
  63. MyRunAsTranslateDlgString(_T("Pwd"),buf,hDlg,IDC_LBLPWD);
  64. MyRunAsTranslateDlgString(_T("OK"),buf,hDlg,IDOK);
  65. MyRunAsTranslateDlgString(_T("Cancel"),buf,hDlg,IDCANCEL);
  66. HWND h=GetDlgItem(hDlg,IDC_RUNASCURR);
  67. if (GetPrivateProfileInt(_T("MyRunAsCfg"),_T("DisableCurrUserOpt"),false,buf))EnableWindow(h,false);
  68. if (GetPrivateProfileInt(_T("MyRunAsCfg"),_T("HideCurrUserOpt"),false,buf))ShowWindow(h,false);
  69. }
  70. #endif
  71. bool ErrorIsLogonError(DWORD err) {
  72. switch (err) {
  73. case ERROR_LOGON_FAILURE:
  74. case ERROR_ACCOUNT_RESTRICTION:
  75. case ERROR_INVALID_LOGON_HOURS:
  76. case ERROR_INVALID_WORKSTATION:
  77. case ERROR_PASSWORD_EXPIRED:
  78. case ERROR_ACCOUNT_DISABLED:
  79. case ERROR_NONE_MAPPED:
  80. case ERROR_NO_SUCH_USER:
  81. case ERROR_INVALID_ACCOUNT_NAME:
  82. return true;
  83. }
  84. return false;
  85. }
  86. void VerifyOKBtn(HWND hDlg,RUNASDLGDATA*pRADD) {
  87. const bool HasText=pRADD?(pRADD->AsSelf?true:MySndDlgItemMsg(hDlg,IDC_USERNAME,WM_GETTEXTLENGTH)>0):false;
  88. EnableWindow(GetDlgItem(hDlg,IDOK),HasText);
  89. }
  90. void SetDlgState(HWND hDlg,bool AsSelf,RUNASDLGDATA*pRADD) {
  91. if (pRADD)pRADD->AsSelf=AsSelf;
  92. MySndDlgItemMsg(hDlg,IDC_RUNASCURR,BM_SETCHECK,AsSelf?BST_CHECKED:BST_UNCHECKED);
  93. MySndDlgItemMsg(hDlg,IDC_RUNASSPEC,BM_SETCHECK,!AsSelf?BST_CHECKED:BST_UNCHECKED);
  94. int ids[]={IDC_USERNAME,IDC_PASSWORD,IDC_LBLUSER,IDC_LBLPWD};
  95. for (int i=0; i<COUNTOF(ids);++i)EnableWindow(GetDlgItem(hDlg,ids[i]),!AsSelf);
  96. VerifyOKBtn(hDlg,pRADD);
  97. }
  98. INT_PTR CALLBACK MyRunAsDlgProc(HWND hwnd,UINT uMsg,WPARAM wp,LPARAM lp) {
  99. RUNASDLGDATA*pRADD=(RUNASDLGDATA*)GetWindowLongPtr(hwnd,GWLP_USERDATA);
  100. switch(uMsg) {
  101. //case WM_DESTROY:
  102. // break;
  103. case WM_CLOSE:
  104. return DestroyWindow(hwnd);
  105. case WM_INITDIALOG:
  106. {
  107. pRADD=(RUNASDLGDATA*)lp;ASSERT(pRADD);
  108. SetWindowLongPtr(hwnd,GWLP_USERDATA,lp);
  109. Edit_LimitText(GetDlgItem(hwnd,IDC_USERNAME),UNLEN+1+MYMAX_DOMAIN); //room for "foo@BAR" or "BAR\foo"
  110. Edit_LimitText(GetDlgItem(hwnd,IDC_PASSWORD),PWLEN);
  111. const HINSTANCE hSh32=GetModuleHandle(_T("SHELL32.dll"));
  112. const HICON hIco=(HICON)LoadImage(hSh32,MAKEINTRESOURCE(194),IMAGE_ICON,32,32,LR_SHARED);
  113. MySndDlgItemMsg(hwnd,IDC_SHICON,STM_SETICON,(WPARAM)hIco);
  114. SendMessage(hwnd,WM_SETTEXT,0,(LPARAM)g_RunAsDlgTitle);
  115. MySetDlgItemText(hwnd,IDC_HELPTEXT,g_RunAsHelpText);
  116. MyRunAsFmtCurrUserRadio(hwnd,g_RunAsCurrUsrFmt);
  117. MySetDlgItemText(hwnd,IDC_RUNASSPEC,g_RunAsSpecHelp);
  118. #ifdef FEAT_CUSTOMRUNASDLG_TRANSLATE
  119. MyRunAsTranslateDlg(hwnd);
  120. #endif
  121. SetDlgState(hwnd,false,pRADD);
  122. #if defined(BUILD_DBG) && 0 //auto login used during testing ;)
  123. SetDlgItemText(hwnd,IDC_USERNAME,_T("root"));
  124. SetDlgItemText(hwnd,IDC_PASSWORD,_T("???"));
  125. Sleep(1);PostMessage(hwnd,WM_COMMAND,IDOK,0);
  126. #endif
  127. }
  128. return true;
  129. case WM_COMMAND:
  130. {
  131. switch(HIWORD(wp)) {
  132. case EN_CHANGE:
  133. VerifyOKBtn(hwnd,pRADD);
  134. break;
  135. case EN_SETFOCUS:
  136. case BN_CLICKED:
  137. if (LOWORD(wp)<=IDCANCEL)break;
  138. SetDlgState(hwnd,LOWORD(wp)==IDC_RUNASCURR,pRADD);
  139. return FALSE;
  140. }
  141. INT_PTR exitcode=!pRADD?-1:IDCANCEL;
  142. switch(LOWORD(wp)) {
  143. case IDOK:
  144. if (pRADD) {
  145. SHELLEXECUTEINFO&sei=*pRADD->pSEI;
  146. PROCESS_INFORMATION pi={0};
  147. DWORD ec=NO_ERROR;
  148. WCHAR*wszExec;//Also used as TCHAR buffer in AsSelf mode
  149. bool PerformTCharFmt=pRADD->AsSelf;
  150. //const DWORD CommonStartupInfoFlags=STARTF_FORCEONFEEDBACK;
  151. #ifdef UNICODE
  152. PerformTCharFmt=true;
  153. #endif
  154. wszExec=(WCHAR*)NSIS::MemAlloc( (pRADD->AsSelf?sizeof(TCHAR):sizeof(WCHAR)) *(lstrlen(sei.lpFile)+1+lstrlen(sei.lpParameters)+1));
  155. if (!wszExec)ec=ERROR_OUTOFMEMORY;
  156. if (PerformTCharFmt)wsprintf((TCHAR*)wszExec,_T("%s%s%s"),sei.lpFile,((sei.lpParameters&&*sei.lpParameters)?_T(" "):_T("")),sei.lpParameters);
  157. if (!ec) {
  158. if (pRADD->AsSelf) {
  159. STARTUPINFO si={sizeof(si)};
  160. TRACEF("MyRunAs:CreateProcess:%s|\n",wszExec);
  161. ec=(CreateProcess(0,(TCHAR*)wszExec,0,0,false,0,0,0,&si,&pi)?NO_ERROR:GetLastError());
  162. }
  163. else {
  164. //All Wide strings!
  165. WCHAR wszPwd[PWLEN+1];
  166. WCHAR wszUName[UNLEN+1+MYMAX_DOMAIN+1];
  167. STARTUPINFOW siw={sizeof(siw)};
  168. WCHAR*p;
  169. #ifndef UNICODE
  170. //Build unicode string, we already know the buffer is big enough so no error handling
  171. p=wszExec;
  172. MultiByteToWideChar(CP_THREAD_ACP,0,sei.lpFile,-1,p,0xFFFFFF);
  173. if (sei.lpParameters && *sei.lpParameters) {
  174. p+=lstrlen(sei.lpFile);*p++=L' ';*p=0;
  175. MultiByteToWideChar(CP_THREAD_ACP,0,sei.lpParameters,-1,p,0xFFFFFF);
  176. }
  177. #endif
  178. SendMessageW(GetDlgItem(hwnd,IDC_USERNAME),WM_GETTEXT,COUNTOF(wszUName),(LPARAM)wszUName);
  179. SendMessageW(GetDlgItem(hwnd,IDC_PASSWORD),WM_GETTEXT,COUNTOF(wszPwd),(LPARAM)wszPwd);
  180. //Try to find [\\]domain\user and split into username and domain strings
  181. WCHAR*pUName=wszUName,*pDomain=0;
  182. p=wszUName;
  183. //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?
  184. ++p;//Don't parse "\something", require at least one char before backslash "?[*\]something"
  185. while(*p && *p!='\\')++p;
  186. if (*p=='\\') {
  187. pDomain=pUName;
  188. pUName=p+1;*p=0;
  189. }
  190. TRACEF("MyRunAs:CreateProcessWithLogonW:%ws|%ws|%ws|%ws|\n",pUName,pDomain?pDomain:L"NO?DOMAIN",wszPwd,wszExec);
  191. ec=(_CreateProcessWithLogonW(pUName,pDomain?pDomain:0,wszPwd,LOGON_WITH_PROFILE,0,wszExec,0,0,0,&siw,&pi)?NO_ERROR:GetLastError());
  192. TRACEF("MyRunAs:CreateProcessWithLogonW: ret=%u\n",ec);
  193. 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)
  194. if (ec && ErrorIsLogonError(ec)) {
  195. LPTSTR szMsg;
  196. 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);
  197. if (ret) {
  198. ec=ERRAPP_TRYAGAIN;
  199. MessageBox(hwnd,szMsg,0,MB_ICONWARNING);
  200. LocalFree(szMsg);
  201. }
  202. else ec=GetLastError();
  203. }
  204. }
  205. }
  206. NSIS::MemFree(wszExec);
  207. if (pi.hThread)CloseHandle(pi.hThread);
  208. if (ERRAPP_TRYAGAIN==ec)break;
  209. if (ec) {
  210. SetLastError(ec);
  211. exitcode=-1;
  212. }
  213. else {
  214. pRADD->pSEI->hProcess=pi.hProcess;
  215. exitcode=IDOK;
  216. }
  217. }
  218. case IDCANCEL:
  219. EndDialog(hwnd,exitcode);
  220. }
  221. }
  222. break;
  223. }
  224. return FALSE;
  225. }
  226. DWORD MyRunAs(HINSTANCE hInstDll,SHELLEXECUTEINFO&sei) {
  227. INT_PTR ec;
  228. ASSERT(sei.cbSize>=sizeof(SHELLEXECUTEINFO) && hInstDll);
  229. if (ec=DelayLoadDlls())return ec;
  230. ASSERT(_CreateProcessWithLogonW && _GetUserName);
  231. RUNASDLGDATA radd={0};
  232. radd.pSEI=&sei;
  233. ec=DialogBoxParam(hInstDll,MAKEINTRESOURCE(IDD_MYRUNAS),sei.hwnd,MyRunAsDlgProc,(LPARAM)&radd);
  234. TRACEF("MyRunAs returned %d (%s|%s)\n",ec,sei.lpFile,sei.lpParameters);
  235. switch(ec) {
  236. case 0:
  237. return ERROR_INVALID_HANDLE;//DialogBoxParam returns 0 on bad hwnd
  238. case IDOK:
  239. return NO_ERROR;
  240. case IDCANCEL:
  241. return ERROR_CANCELLED;
  242. }
  243. //TODO:BUGBUG: on vista, the last error seems to get lost, should probably put it in RUNASDLGDATA and always return IDOK
  244. return GetLastError();
  245. }
  246. #ifdef BUILD_DBG
  247. // RunDll exports are __stdcall, we dont care about that for this debug export, rundll32.exe is able to handle this mistake
  248. extern "C" void __declspec(dllexport) __cdecl DBGRDMyRunAs(HWND hwnd,HINSTANCE hinst,LPTSTR lpCmdLine,int nCmdShow) {
  249. SHELLEXECUTEINFO sei={sizeof(sei)};
  250. sei.lpFile=_T("Notepad.exe");//sei.lpParameters=_T("param1");
  251. TRACEF("ec=%d\n",MyRunAs(GetModuleHandle(_T("UAC.dll")),sei));
  252. }
  253. #endif
  254. #endif /* FEAT_CUSTOMRUNASDLG */