using System;
using System.Diagnostics;
using System.IO;
using System.Runtime.InteropServices;
using System.Security.AccessControl;
using System.Security.Principal;
using Microsoft.Win32;
using wyUpdate.Common;
namespace wyUpdate
{
public partial class frmMain
{
[DllImport("advapi32.dll", SetLastError = true)]
private static extern bool GetTokenInformation(
IntPtr TokenHandle,
int TokenInformationClass,
IntPtr TokenInformation,
uint TokenInformationLength,
out uint ReturnLength);
[DllImport("kernel32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool CloseHandle(IntPtr hObject);
private const int TokenLinkedToken = 19;
//mjh
private void StartSelfElevated()
{
ProcessStartInfo psi = new ProcessStartInfo
{
ErrorDialog = true,
ErrorDialogParentHandle = Handle
};
if(SelfUpdateState == SelfUpdateState.WillUpdate)
{
//create the filename for the newly copied client
psi.FileName = Path.Combine(tempDirectory, Path.GetFileName(VersionTools.SelfLocation));
//copy self to the temp folder
File.Copy(VersionTools.SelfLocation, psi.FileName, true);
}
else if(SelfUpdateState == SelfUpdateState.FullUpdate)
{
//launch the newly updated self
psi.FileName = oldSelfLocation;
}
else if(isAutoUpdateMode)
{
psi.FileName = IsNewSelf ? newSelfLocation : oldSelfLocation;
// oldSelfLocation is null when elevation is needed, but no self update is taking place
if(string.IsNullOrEmpty(psi.FileName))
{
psi.FileName = VersionTools.SelfLocation;
}
}
else
{
psi.FileName = VersionTools.SelfLocation;
}
if(needElevation)
{
// elevate to administrator
psi.Verb = "runas";
// check if UAC is enabled on the computer, if not throw an error
bool canUAC = false;
try
{
if(VistaTools.AtLeastVista())
{
using(RegistryKey key = Registry.LocalMachine.OpenSubKey(@"SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System"))
{
// see if UAC is enabled
if(key != null)
{
canUAC = (int)key.GetValue("EnableLUA", null) != 0;
}
}
}
else
{
// Assume XP / 2000 limited user can use the RunAs box
// this may or may not be true.
//TODO: make this more robust
// see: http://www.windowsnetworking.com/kbase/WindowsTips/Windows2003/AdminTips/Admin/DisablingtheRunAsCommand.html
canUAC = true;
}
}
catch { }
if(!canUAC)
{
//TODO: use a more specific error to tell the user
// to re-enable UAC on their computer.
error = clientLang.AdminError;
ShowFrame(Frame.Error);
return;
}
}
try
{
string selfUpdatePath = Path.Combine(tempDirectory, "selfUpdate.sup");
// write necessary info (base/temp dirs, new client files, etc.) to a file
SaveSelfUpdateData(selfUpdatePath);
psi.Arguments = $"-supdf:\"{selfUpdatePath}\"";
if(IsNewSelf)
{
psi.Arguments += " /ns";
}
Process.Start(psi);
Close();
}
catch(Exception ex)
{
// the process couldn't be started. This happens for 1 of 3 reasons:
// 1. The user cancelled the UAC box
// 2. The limited user tried to elevate to an Admin that has a blank password
// 3. The limited user tries to elevate as a Guest account
error = clientLang.AdminError;
errorDetails = ex.Message;
ShowFrame(Frame.Error);
}
}
private bool NeedElevationToUpdate()
{
//mjh
// if only updating local user files, no elevation is needed
if(IsAdmin || OnlyUpdatingLocalUser())
{
return false;
}
// UAC Shield on next button for Windows Vista+
if(VistaTools.AtLeastVista())
{
VistaTools.SetButtonShield(btnNext, true);
}
return true;
}
/// <summary>
/// Checks if a SID is present in the current user's token, even if disabled (for UAC filtered tokens).
/// Checks the linked token (full admin token) if available.
/// </summary>
private static bool IsSidInToken(SecurityIdentifier sid)
{
try
{
WindowsIdentity identity = WindowsIdentity.GetCurrent();
// First check if actively in role (enabled groups)
WindowsPrincipal principal = new WindowsPrincipal(identity);
if(principal.IsInRole(sid))
{
return true;
}
// Check all groups in current token
if(identity.Groups != null)
{
foreach(IdentityReference group in identity.Groups)
{
if(group.Value.Equals(sid.Value, StringComparison.OrdinalIgnoreCase))
{
return true;
}
}
}
// Also check the user SID itself (not just groups)
if(identity.User != null && identity.User.Value.Equals(sid.Value, StringComparison.OrdinalIgnoreCase))
{
return true;
}
// If UAC is enabled and we have a linked token (full admin token), check that too
if(VistaTools.AtLeastVista())
{
IntPtr hToken = identity.Token;
IntPtr linkedToken = IntPtr.Zero;
try
{
uint size = (uint)IntPtr.Size;
IntPtr pLinkedToken = Marshal.AllocHGlobal(IntPtr.Size);
try
{
if(GetTokenInformation(hToken, TokenLinkedToken, pLinkedToken, size, out size))
{
linkedToken = Marshal.ReadIntPtr(pLinkedToken);
if(linkedToken != IntPtr.Zero)
{
using(WindowsIdentity linkedIdentity = new WindowsIdentity(linkedToken))
{
// Check groups in the linked (elevated) token
if(linkedIdentity.Groups != null)
{
foreach(IdentityReference group in linkedIdentity.Groups)
{
if(group.Value.Equals(sid.Value, StringComparison.OrdinalIgnoreCase))
{
return true;
}
}
}
}
}
}
}
finally
{
Marshal.FreeHGlobal(pLinkedToken);
if(linkedToken != IntPtr.Zero)
{
CloseHandle(linkedToken);
}
}
}
catch
{
// Ignore errors accessing linked token
}
}
return false;
}
catch
{
return false;
}
}
/// <summary>
/// Checks if the current process has the necessary permissions to a folder. This allows "custom" elevation of
/// limited users -- allowing them control over normally restricted folders.
/// </summary>
/// <param name="folder">The full path to the folder to check.</param>
/// <returns>True if the user has permission, false if not.</returns>
private static bool HaveFolderPermissions(string folder)
{
try
{
const FileSystemRights RightsNeeded = FileSystemRights.Traverse |
FileSystemRights.DeleteSubdirectoriesAndFiles |
FileSystemRights.ListDirectory | FileSystemRights.CreateFiles |
FileSystemRights.CreateDirectories |
FileSystemRights.Modify; //Read, ExecuteFile, Write, Delete
FileSystemSecurity security = Directory.GetAccessControl(folder);
AuthorizationRuleCollection rules = security.GetAccessRules(true, true, typeof(NTAccount));
WindowsPrincipal currentuser = new WindowsPrincipal(WindowsIdentity.GetCurrent());
FileSystemRights RightsHave = 0;
FileSystemRights RightsDontHave = 0;
foreach(FileSystemAccessRule rule in rules)
{
// First check to see if the current user is even in the role, if not, skip
SecurityIdentifier sid;
if(rule.IdentityReference.Value.StartsWith("S-1-"))
{
sid = new SecurityIdentifier(rule.IdentityReference.Value);
}
else
{
// Convert NTAccount to SID to avoid locale/string matching issues
NTAccount ntAccount = new NTAccount(rule.IdentityReference.Value);
try
{
sid = (SecurityIdentifier)ntAccount.Translate(typeof(SecurityIdentifier));
}
catch
{
// If translation fails, skip this rule
continue;
}
}
// Check if SID is in token (including disabled groups for UAC filtered tokens)
if(!IsSidInToken(sid))
{
continue;
}
if(rule.AccessControlType == AccessControlType.Deny)
{
RightsDontHave |= (rule.FileSystemRights & FileSystemRights.FullControl);
}
else
{
RightsHave |= (rule.FileSystemRights & FileSystemRights.FullControl);
}
}
// exclude "RightsDontHave"
RightsHave &= ~RightsDontHave;
//Note: We're "XOR"ing with RightsNeeded to eliminate permissions that
// "RightsHave" and "RightsNeeded" have in common. Then we're
// ANDing that result with RightsNeeded to get permissions in
// "RightsNeeded" that are missing from "RightsHave". The result
// should be 0 if the user has RightsNeeded over the folder (even
// if "RightsHave" has flags that aren't present in the
// "RightsNeeded" -- which can happen because "RightsNeeded" isn't
// *every* possible flag).
// Check if the user has full control over the folder.
return ((RightsHave ^ RightsNeeded) & RightsNeeded) == 0;
}
catch
{
return false;
}
}
//TODO: Expand this function to allow for artificially elevated limited users.
// For example, a limited user given the permission to write to HKLM
// (something that is normally forbidden).
private bool OnlyUpdatingLocalUser()
{
// if installing
// - system folders
// - non-user registry
// - Windows Services
// - COM files
// then return false
// Also note how we're excluding the "BaseDir".
// This is because the base directory may or may not be in the userprofile
// directory (or otherwise writable by the user), thus it needs a separate check.
// Ditto for the "client.wyc" file.
if(((updateFrom.InstallingTo | InstallingTo.BaseDir) ^ InstallingTo.BaseDir) != 0)
{
return false;
}
string userProfileFolder = SystemFolders.GetUserProfile();
// if the basedir isn't in the userprofile folder (C:\Users\UserName)
// OR
// if the folder isn't in the full control of the current user
// THEN
// we're not "only updating the local user
if((updateFrom.InstallingTo & InstallingTo.BaseDir) != 0 && !(SystemFolders.IsDirInDir(userProfileFolder, baseDirectory) || HaveFolderPermissions(baseDirectory)))
{
return false;
}
// if the client data file isn't in the userprofile folder (or otherwise writable)
// then bail out.
if(!(SystemFolders.IsFileInDirectory(userProfileFolder, clientFileLoc) || HaveFolderPermissions(Path.GetDirectoryName(clientFileLoc))))
{
return false;
}
// when self-updating, if this client isn't in the userprofile folder
if((SelfUpdateState == SelfUpdateState.WillUpdate || SelfUpdateState == SelfUpdateState.FullUpdate || SelfUpdateState == SelfUpdateState.Extracted)
&& !(SystemFolders.IsFileInDirectory(userProfileFolder, VersionTools.SelfLocation)
|| HaveFolderPermissions(Path.GetDirectoryName(VersionTools.SelfLocation))))
{
return false;
}
return true;
}
}
}