I'm modifying an old C++ program to run on Vista. It does not require Admin privileges.
I've changed the code to put logfiles in \ProgramData\MyApp\. These logfiles are written with the stdio functions (fopen, fprintf, fclose).
Here's the problem:
UserA runs the program first, it creates \ProgramData\MyApp\MyLogFile.txt using CreateFile()
UserB runs the program next, it tries to append to MyLogFile.txt and gets access denied.
I tried creating a null SECURITY_DESCRIPTOR and passing that to CreateFile(). That does create a file with "no permissions assigned", but it seems as if the first user to write to the file takes ownership and afterwards all the other non-admin users are out of luck.
It's important that all users share the same logfiles, but it's also important that I change as little code as possible.
Edited to add:
\ProgramData\MyApp is created by a standard Visual Studio installer. (I don't see any place to set directory security.) When it creates \MyApp it grants Users these permissions:
Read & execute
List folder contents
Read
Special permissions
Under Advanced I see that Special permissions includes:
Create files / write data
Create folders / append data
Write attributes
Write extended attributes
+1 to everyone for trying, but eventually I found the answer here:
how to change the ACLs from c++?
I did have to change one line of that solution, from this:
ea[0].grfAccessMode = DENY_ACCESS;
to this:
ea[0].grfAccessMode = GRANT_ACCESS;
Probably that your application uses an installer. When the installer creates your folder "MyApp", assign read/write rights for everyone. This will probably fix your problem. There are different ways of doing this, but it depends on the type of the setup that you use.
Added custom action info.
If after install the folder does not have the required permissions you could add for example a custom action as a visual basic script on the install sequence, that will set the required permissions.
VBS Examble:
Function SetPermissions()
Dim strHomeFolder, strHome, strUser
Dim intRunError, objShell, objFSO
strHomeFolder = "C:\Test"
Set objShell = CreateObject("Wscript.Shell")
Set objFSO = CreateObject("Scripting.FileSystemObject")
If objFSO.FolderExists(strHomeFolder) Then
intRunError = objShell.Run("%COMSPEC% /c Echo Y| cacls " _
& strHomeFolder & " /t /c /g everyone:F ", 2, True)
If intRunError <> 0 Then
Wscript.Echo "Error assigning permissions for user " _
& strUser & " to home folder " & strHomeFolder
End If
End If
End Function
You need to add an allow rule for the user "Everyone" if that is what you truly want.
A null descriptor will defer to the directory's security if memory serves...
You must definetely use CreateFile. See more about security and access rights. I am sure that functions from the standard C library use CreateFile (it can't use anything else on Windows) but with default security parameters which are not helpful in your case.
I tried also to look inside SECURITY_ATTRIBUTES and SECURITY_DESCRIPTOR structures but it's not very easy to understand how to do it, though it may be a chance.
Related
In Windows 7 (32 bit), I consistently get error 5 (access denied) when I call ::RegOpenKeyEx on HKEY_LOCAL_MACHINE if I am not running in Administrator mode.
This is my code:
result = ::RegOpenKeyEx(HKEY_LOCAL_MACHINE, _T("SOFTWARE\\MyCompany\\MyApp"), 0, KEY_QUERY_VALUE, &keySoftware)
I am trying to build an app that can be installed for an entire machine as opposed to a specific user. Therefore my installer (which does run in Administrator mode) writes to HKEY_LOCAL_MACHINE, and the installer works fine.
I'd like my app to be able to read the data that my installer has put into the registry. I don't want to change any of the registry data. I have also tried using KEY_READ and KEY_EXECUTE instead of KEY_QUERY_VALUE. But it seems that no matter what I do, I cannot read from HKEY_LOCAL_MACHINE without using an elevated status. What am I missing here?
Your installer needs to adjust the security permissions on your Registry key so that non-admin users are allowed to access it. Have a look at RegSetKeySecurity(), or your installer's equivalent, or any number of command-line tools that are available. You can create a DACL that enables read-only access for the Everyone user group, or for specific user accounts.
Update: since you are using Inno Setup, try this in your Registry setup:
[Registry]
Root: HKLM; Subkey: "Software\MyCompany\MyApp"; Permissions: everyone-read
Or:
[Registry]
Root: HKLM; Subkey: "Software\MyCompany\MyApp"; Permissions: users-read
Your code as I was writing this answer:
result = ::RegOpenKeyEx(HKEY_LOCAL_MACHINE, _T("SOFTWARE\\MyCompany\\MyApp"), 0, KEY_QUERY_VALUE, &keySoftware)
RegOpenKeyEx doesn't support general paths, just direct key names.
To use it you would have to iterate your way down the hierarchy.
SHRegOpenUSKey is one alternative that does support general paths:
HUSKEY keySoftware;
LSTATUS const result = ::SHRegOpenUSKey(
L"Software\\Microsoft\\MediaPlayer", KEY_QUERY_VALUE, 0, &keySoftware, TRUE
);
Windows/C++
Looking for some recommendations on how to reset the security attributes on a file after it's been moved to a new folder.
Our standard way of creating files (and download from the server) is to create the file in a temporary folder, then as the file streams down, the file is appended. Once the download is completed, we move the file to it's final destination.
MoveFile() will transfer the security on the file when the file is moved. In certain configuration this causes a problem -- where the security defaults of final folder don't match the original folder. We cannot mess with folder security....
So, ultimately, I would like to perform an operation on the file after I move it. My current thinking is that I should fetch the security attributes of the folder it goes into, and then apply to the file after the move is completed.
To expand on Harry's answer, here is the full code:
// blank acl used to restore permissions after a file move
ACL g_null_acl = { 0 };
InitializeAcl(&g_null_acl, sizeof(g_null_acl), ACL_REVISION);
DWORD error = SetNamedSecurityInfo(file_path, SE_FILE_OBJECT,
DACL_SECURITY_INFORMATION | UNPROTECTED_DACL_SECURITY_INFORMATION,
NULL, NULL, (PACL)&g_null_acl, NULL);
Keep in mind that calling SetNamedSecurityInfo (in this instance) requires SE_RESTORE_NAME privileges, so it cannot be called from a service running as Network Service (or Local Service), as they have limited permissions.
Use SetNamedSecurityInfo with the UNPROTECTED_DACL_SECURITY_INFORMATION flag. Just pass an empty ACL to remove the entries the file got from its previous parent. This would look something like this:
error = SetNamedSecurityInfo(
path_to_file,
SE_FILE_OBJECT,
DACL_SECURITY_INFORMATION | UNPROTECTED_DACL_SECURITY_INFORMATION,
NULL,
NULL,
empty_acl,
NULL);
Hi I am completely new to programming. And please someone help me.
I am trying to start a pocess from a service.
I need to start the new process by prompting user to enter admin credentials.
I was trying to use CreateProcessWithLogonW().
Am I using the right function.
I tried to give input username, password, domain as localhost. I gave full pathe to the .exe file that i need to start.
Here is the piece of code.
CreateProcessWithLogonW(L"Administrator",
L"localhost",
L"password",
0,
NULL,
L"c:\myupdates\myapp.exe",
NORMAL_PRIORITY_CLASS | CREATE_CONSOLE,
NULL,
NULL,
&si,
&pi);
Si.cb = sizeof(si);
Si.lpDesktop = L"winsta0\\default";
But the process never started. Can you guys tell me what I am doing wrong.
And what do I need to do to promt user to enter credentials of administrator instead of hardcoding it.
None of CreateProcess* functions will do any promting. They are low level APIs and know nothing about GUI.
If you want user to be prompted, use ShellExecuteEx with runas command. Windows will first ask a permission to elevate and then prompt for credentials.
You may want to escape the program string properly as well:
L"c:\myupdates\myapp.exe"
should at least be:
L"c:\\myupdates\\myapp.exe"
Frankly, there are a multitude of things wrong with this code, from improper setup of SI, to the parameters passed to the API itself. I suggest you read up more.
One problem is passing a string literal as the command-line argument, as that argument must be modifiable. From CreateProcessWithLogon() in relation to the command-line argument:
The function can modify the contents of this string. Therefore, this parameter cannot be a pointer to read-only memory (such as a const variable or a literal string). If this parameter is a constant string, the function may cause an access violation.
You also need to escape the backslashes. Change to:
WCHAR cmdLine[] = L"c:\\myupdates\\myapp.exe"; /* 'cmdLine' is a
copy of the string
literal. */
and pass cmdLine instead.
After any WINAPI function failure check GetLastError() as it will inform you of the reason for failure.
Maybe it's too late to help you. But it might be helpful for others, though. If you use the CreateProcessWithLogonW function and you are using the Default desktop just keep lpDesktop as NULL.
If lpDesktop is not Null you have to enter the user's sid (getting with LookupAccountNamean) as a ACE in desktops and winstation's DACL
So here are the steps you have to do to add an ACE for a desktop:
get Desktop handle with OpenDesktop, use the right dwDesiredAccess
get the Security Descriptor with GetSecurityInfo and DACL_SECURITY_INFORMATIONas securityinfo
get the DACL from your Security Descriptor
Add AddAccessAllowedAcewith the sid of your User's sid
Set the modified DACL to your Desktop handle
now repeat those steps for winsta0 winstation
The commantary of Mr. Furious in the documantary helped me alot to solve this problem.
The access violation is down to the lpCommandLine parameter. That is meant to a editable memory, LPWSTR and the API function does modify the buffer. But you pass a pointer to non-modifiable memory.
But there's a more fundamental problem. You say you want to prompt for credentials from a service. Services should not show UI and in modern versions of Windows, a service simply cannot show UI. Your design is flawed and you need to re-consider it.
I've written a very simple service application based on this code example.
The application as part of its normal running assumes there exists a file in the directory it is found, or in its execution path.
When I 'install' the service and then subsequently 'start' the service from the service manager in control panel. The application fails because it can't find the file to open and read from (even though the file is in the same directory as the installed executable).
My question is when a windows service is run, which is the expected running path supposed to be?
When calling 'CreateService' there only seems to be a path parameter for the binary, not for execution. Is there someway to indicate where the binary should be executed from?
I've tried this on windows vista and windows 7. Getting the same issues.
Since Windows services are run from a different context than normal user-mode applications, it's best if you don't make any assumptions about working directories or relative paths. Aside from differences in working directories, a service could run using a completely different set of permissions, etc.
Using an absolute path to the file that your service needs should avoid this problem entirely. Absolute paths will be interpreted the same regardless of the working directory, so this should make the working directory of your service irrelevant. There are several ways to go about this:
Hard-code the absolute path - This is perhaps the easiest way to avoid the problem, however it's also the least flexible. This method is probably fine for basic development and testing work, but you probably want something a bit more sophisticated before other people start using your program.
Store the absolute path in an environment variable - This gives you an extra layer of flexibility since the path can now be set to any arbitrary value and changed as needed. Since a service can run as a different user with a different set of environment variables, there are still some gotchas with this approach.
Store an absolute path in the registry - This is probably the most fool-proof method. Retrieving the path from the registry will give you the same result for all user accounts, plus this is relatively easy to set up at install time.
By default, the current directory for your Windows service is the System32 folder.
A promising solution is creating an environment variable that keeps the full path of your input location and retrieving the path from this variable at runtime.
If you use the same path as binary, you could just read binary path and modify it accordingly. But this is rather quick-fix rather than designed-solution. If I were you, I would either create system-wide environment variable and store value there, or (even better) use windows registry to store service configuration.
Note:
You will need to add Yourself some privileges using AdjustTokenPrivileges function, you can see an example here in ModifyPrivilege function.
Also be sure to use HKEY_LOCAL_MACHINE and not HKEY_CURRENT_USER. Services ar running under different user account so it's HKCU's will be different than what you can see in your registry editor.
Today I solved this problem as it was needed for some software I was developing.
As people above have said; you can hardcode the directory to a specific file - but that would mean whatever config files are needed to load would have to be placed there.
For me, this service was being installed on > 50,000 computers.
We designed it to load from directory in which the service executable is running from.
Now, this is easy enough to set up and achieve as a non-system process (I did most of my testing as a non-system process). But the thing is that the system wrapper that you used (and I used as well) uses Unicode formatting (and depends on it) so traditional ways of doing it doesn't work as well.
Commented parts of the code should explain this. There are some redundancies, I know, but I just wanted a working version when I wrote this.
Fortunately, you can just use GetModuleFileNameA to process it in ASCII format
The code I used is:
char buffer[MAX_PATH]; // create buffer
DWORD size = GetModuleFileNameA(NULL, buffer, MAX_PATH); // Get file path in ASCII
std::string configLoc; // make string
for (int i = 0; i < strlen(buffer); i++) // iterate through characters of buffer
{
if (buffer[i] == '\\') // if buffer has a '\' in it, replace with doubles
{
configLoc = configLoc + "\\\\"; // doubles needed for parsing. 4 = 2(str)
}
else
{
configLoc = configLoc + buffer[i]; // else just add char as normal
}
}
// Complete location
configLoc = configLoc.substr(0, configLoc.length() - 17); //cut the .exe off the end
//(change this to fit needs)
configLoc += "\\\\login.cfg"; // add config file to end of string
From here on, you can simple parse configLoc into a new ifsteam - and then process the contents.
Use this function to adjust the working directory of the service to be the same as the working directory of the exe it's running.
void AdjustCurrentWorkingDir() {
TCHAR szBuff[1024];
DWORD dwRet = 0;
dwRet = GetModuleFileName(NULL, szBuff, 1024); //gets path of exe
if (dwRet != 0 && GetLastError() != ERROR_INSUFFICIENT_BUFFER) {
*(_tcsrchr(szBuff, '\\') + 1) = 0; //get parent directory of exe
if (SetCurrentDirectory(szBuff) == 0) {
//Error
}
}
}
I created a .properties file that contains a few simple key = value pairs.
I tried it out from a sample c++ console application, using imported java classes, and I was able to access it, no problem.
Now, I am trying to use it in the same way, from a C++ dll, which is being called by another (unmanaged) c++ project.
For some reason, the file is not being accessed.
Maybe my file location is wrong. Where should I be storing it?
What else might be the issue?
TIA
As you are mentioning "DLL" i guess, that you are using MS Windows. Finding a file there from a DLL, and independently from the logged on user is a restricted item. The best way is to store the file in a path assembled from the environment variable ALLUSERSPROFILE. This is the only location that is equal to all users and where all users usually have write access. Your applications data should reside in a private subdirectory named like < MyCompany > or < MyApplicationsName >. Type
echo %ALLUSERSPROFILE%
on a windows command line prompt to find out the actual location on a machine.
Store your data in i.e.:
%ALLUSERSPROFILE%\MyApp\
Your dll can then query the location of ALLUSERSPROFILE using getenv:
char *allUsersData = getenv("ALLUSERSPROFILE");