I am trying to communicate between client UWP C# app and server Win32 C++ app, both apps are in same package and I am using the Win32 app as desktop extension to provide additional functionality to UWP app.
From the docs it is clear that named pipe communication between UWP apps in same package but it isn't clear about UWP and Win32 app in same package.
The pipe created by UWP process with name \\\\.\\pipe\\Local\\PipeName is converted to \\\\.\\pipe\\Sessions\\<SessionId>\\AppContainerNamedObjects\\<AppContainerSid>\\PipeName. I can use this to communicate between UWP as server and Win32 as client. But I can't do the reverse even after I set up the ACLs as done in the official RPC sample. Instead of using custom capability I used DeriveAppContainerSidFromAppContainerName to to derive a SID from package family name.
My Win32 C++ server code looks like this:
SID_IDENTIFIER_AUTHORITY SIDAuthWorld = SECURITY_WORLD_SID_AUTHORITY;
PSID everyoneSid = NULL;
PSID packageSid = NULL;
EXPLICIT_ACCESS ea[2] = {};
PACL acl = NULL;
SECURITY_DESCRIPTOR pipeSecurityDescriptor = {};
if (DeriveAppContainerSidFromAppContainerName(Package::Current().Id().FamilyName().c_str(), &packageSid) == S_OK &&
// Get the SID that represents 'everyone' (this doesn't include AppContainers)
AllocateAndInitializeSid(&SIDAuthWorld, 1, SECURITY_WORLD_RID, 0, 0, 0, 0, 0, 0, 0, &everyoneSid))
{
// Now create the Access Control List (ACL) for the Security descriptor
// Everyone GENERIC_ALL access
ea[0].grfAccessMode = SET_ACCESS;
ea[0].grfAccessPermissions = GENERIC_ALL;
ea[0].grfInheritance = NO_INHERITANCE;
ea[0].Trustee.TrusteeForm = TRUSTEE_IS_SID;
ea[0].Trustee.TrusteeType = TRUSTEE_IS_WELL_KNOWN_GROUP;
ea[0].Trustee.ptstrName = static_cast<LPWSTR>(everyoneSid);
// Package Family GENERIC_ALL access
ea[1].grfAccessMode = SET_ACCESS;
ea[1].grfAccessPermissions = GENERIC_ALL;
ea[1].grfInheritance = NO_INHERITANCE;
ea[1].Trustee.TrusteeForm = TRUSTEE_IS_SID;
ea[1].Trustee.TrusteeType = TRUSTEE_IS_UNKNOWN;
ea[1].Trustee.ptstrName = static_cast<LPWSTR>(packageSid);
if (SetEntriesInAcl(ARRAYSIZE(ea), ea, NULL, &acl) != ERROR_SUCCESS &&
// Initialize an empty security descriptor
InitializeSecurityDescriptor(&pipeSecurityDescriptor, SECURITY_DESCRIPTOR_REVISION) &&
// Assign the ACL to the security descriptor
SetSecurityDescriptorDacl(&pipeSecurityDescriptor, TRUE, acl, FALSE))
{
SECURITY_ATTRIBUTES pipeSecurityAttributes{ .nLength = sizeof(SECURITY_ATTRIBUTES), .lpSecurityDescriptor = &pipeSecurityDescriptor, .bInheritHandle = FALSE };
HANDLE hPipe = CreateNamedPipe(L"\\\\.\\pipe\\Sessions\\<SessionId>\\AppContainerNamedObjects\\<AppContainerSid>\\PipeName}", PIPE_ACCESS_DUPLEX, PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE | PIPE_WAIT, PIPE_UNLIMITED_INSTANCES, 0, 0, NMPWAIT_WAIT_FOREVER, &pipeSecurityAttributes);
if (hPipe)
{
ConnectNamedPipe(hPipe, NULL);
// Do something
}
}
}
if (everyoneSid) FreeSid(everyoneSid);
if (packageSid) FreeSid(packageSid);
if (acl) LocalFree(acl);
My UWP C# client code looks like this:
using (var client = new NamedPipeClientStream(".", "Local\\PipeName", PipeDirection.InOut, PipeOptions.Asynchronous))
{
await client.ConnectAsync();
// Do something
}
When I am trying to connect from client I am getting "Access to the path is denied." error.
I was able to get a version of this working-- a C# UWP app pipe client successfully connecting to a non-UWP C++ pipe server. My setup is very similar to yours, but there are a few differences.
I did not have much luck at all using the C# version of connecting with the client. I was successful with using CreateFileW. You can call this function inside of your C# by using an DllImport.
My pipe names are slightly different. My non-UWP program is what is making my pipe server, so I think I do not need to use the "Local" sub-path. My server pipe path is: \\\\.\\pipe\\Pipe. My client pipe path is: \\.\pipe\\Pipe. I was able to get a connection with these names.
In my own experience with using DeriveAppContainerSidFromAppContainerName, I could not seem to get the correct SID from it. I would get one back, but when I would compare it the SID from "CheckNetIsolation.exe LoopbackExempt -s" in Powershell, it was different. Not sure why that is, but I hard-coded the correct SID and that worked. Having the incorrect SID does result in an access denied error.
Related
Below Code is just working fine with IBMMQ 8.0 DLL and server when I switch to 7.5 (both DLL and server) it is giving me this error using same certificate
The SSL key repository cannot be used because MQ cannot obtain a
password to access it. Reasons giving rise to this error include: &B
(a) the key database file and password stash file are not present in
the location configured for the key repository, &B (b) the key
database file exists in the correct place but that no password stash
file has been created for it, &B (c) the files are present in the
correct place but the userid under
public void test() {
Environment.SetEnvironmentVariable("MQCCSID", "437");
MQQueueManager mQQueueManager = null;
MQQueue mQQueue = null;
Hashtable hashTable = null;
try {
hashTable = new Hashtable();
// Setup properties for connection
hashTable.Add(MQC.TRANSPORT_PROPERTY, MQC.TRANSPORT_MQSERIES_MANAGED);
hashTable.Add(MQC.HOST_NAME_PROPERTY, "IP");
hashTable.Add(MQC.PORT_PROPERTY, 1414);
hashTable.Add(MQC.CHANNEL_PROPERTY, "Channel");
hashTable.Add(MQC.PASSWORD_PROPERTY, "123");
hashTable.Add(MQC.USER_ID_PROPERTY, "user");
mQQueueManager = new MQQueueManager("QueueName", hashTable);
// Open queue for browsing
mQQueue = mQQueueManager.AccessQueue("que", MQC.MQOO_BROWSE | MQC.MQOO_FAIL_IF_QUIESCING);
ListOfMessages = new List < MQMessageDto > ();
// In a loop browse all messages till we reach end of queue
while (true) {
try {
// Need to create objects everytime
var mQMessage = new MQMessage();
var mQGetMessageOptions = new MQGetMessageOptions {
// Use browse next option to start browsing
Options = MQC.MQGMO_BROWSE_NEXT
};
mQQueue.Get(mQMessage, mQGetMessageOptions);
ListOfMessages.Add(new MQMessageDto() {
Id = ListOfMessages.Count + 1,
Message = Encoding.UTF8.GetString(mQMessage.ReadBytes(mQMessage.MessageLength))
});
} catch (MQException mqex) {
if (ListOfMessages.Count == 0) {
MessageBox.Show("There is no messages in MQ");
}
mQQueue.Close();
break;
}
}
mQQueueManager.Disconnect();
grdMessages.DataSource = ListOfMessages;
grdMessages.Columns["Id"].Width = (int)(grdMessages.Width * 0.1);
grdMessages.Columns["Message"].Width = (int)(grdMessages.Width * 0.8);
} catch (Exception ex) {
MessageBox.Show(ex.Message);
}
}
What are you describing means you have wrong configuration at IBM side, and Since you are using IBM MQ 7.5. I think you got the path for the SSL key repository wrong, it should point to the key name not the folder.
Also make sure that you have selected Optional from SSL tab inside your Channel.
For more details.. More details about this issue can be found here about error this error code:
2538 error on MQ for SSL channel connection
You didn't mention which specific level of 7.5 you are using. If it is 7.5.0.7 or earlier, the stash file will likely be the problem:
https://www.ibm.com/support/knowledgecenter/en/SSFKSJ_9.1.0/com.ibm.mq.mig.doc/q128820_.htm
Older versions of the cryptographic provider used by MQ (GSKit) use a different stash file format for the keystore password.
While newer GSKit versions can handle the old stash file format, the new format is not readable by older GSKit versions. If you are using a level which uses the new format, you can create a backwards-compatible stash file with the -v1stash option:
runmqakm -keydb -stashpw -db <filename> -pw <password> -v1stash
A better alternative, as MQ 7.5 is out of support, would be to use a newer client level, which can still communicate with a 7.5 queue manager if required.
For reference, the first GSKit level which uses the new stash file format is 8.0.50.69. Levels of GSKit bundled with MQ are listed here: https://www.ibm.com/support/pages/levels-jre-and-gskit-bundled-ibm-mq
Regarding:
When I upgraded my client to V9 I'm getting "MQRC_Q_MGR_NOT_AVAILABLE" on client and "4/23/2020 21:03:22 - Process(11764.64) User() Program(amqrmppa.exe) Host(HOST) Installation(Installation1) VRMF(7.5.0.2) QMgr() Remote channel '' did not specify a CipherSpec. Remote channel '' did not specify a CipherSpec when the local channel expected one to be specified. &P The remote host is '...* (...)'. &P The channel did not start. Change the remote channel '' on host ()' to specify a CipherSpec so that both ends of the channel have matching CipherSpecs." in server
display the cipher spec being used dis chl(xxx) SSLCIPH
You may have specified something which is no longer supported by the underlying TLS support.
dis chl(xxx)
I built an application that can also be ran as a service (using a -service) switch. This works perfectly with no issues when I'm running the service from a command prompt (I have something set up that lets me debug it from a console when not being ran as a true service). However, when I try to run it as a true service then use my application to open the existing memory map, I get the error...
Unable to find the specified file.
How I run it as a service or in console:
[STAThread]
static void Main(string[] args)
{
//Convert all arguments to lower
args = Array.ConvertAll(args, e => e.ToLower());
//Create the container object for the settings to be stored
Settings.Bag = new SettingsBag();
//Check if we want to run this as a service
bool runAsService = args.Contains("-service");
//Check if debugging
bool debug = Environment.UserInteractive;
//Catch all unhandled exceptions as well
if (!debug || debug)
{
Application.SetUnhandledExceptionMode(UnhandledExceptionMode.CatchException);
AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException;
}
if (runAsService)
{
//Create service array
ServiceBase[] ServicesToRun;
ServicesToRun = new ServiceBase[]
{
new CRSService()
};
//Run services in interactive mode if needed
if (debug)
RunInteractive(ServicesToRun);
else
ServiceBase.Run(ServicesToRun);
}
else
{
//Start the main gui
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new MainGUI());
}
}
In my application I have a service side and an application side. The application's purpose is just to control the service. I do all the controlling using memory mapping files and it seems to work great and suits my needs. However, when I run the application as a true service I see from my debug logs it is creating the memory map file with the correct name and access settings. I can also see the file getting created where it should be. Everything seems to working exactly the same in the service as it does when I debug via console. However, my application (when ran as an application instead of the service) tells me it can not find the memory map file. I have it toss the file name path in the error as well so I know it's looking in the right place.
How I open the memory map (where the error is thrown):
m_mmf = MemoryMappedFile.OpenExisting(
m_sMapName,
MemoryMappedFileRights.ReadWrite
);
Note: The service is running as the same account as I run Visual Studio in. As an example the image below shows my task manager, the services.msc gui and my currently identified account.
How can I get my client application to see the memory map file after the service creates it? Why does it work when I run it as a console service and not when I run it as a true service?
Windows Services run in isolation, in Session 0, whilst your Console application runs in a user session, so for them to communicate with each other, the memory mapped file must be created in the Global\ namespace, to make it accessible to other sessions. e.g.
var file = MemoryMappedFile.CreateOrOpen(#"Global\MyMemoryMappedFile", ...
You should also set the appropriate permissions to the file, to make sure all users can access it.
I'd recommend reading this post Implementing Non-Persisted Memory Mapped Files Exposing IPC Style Communications with Windows Services, which explains the above in a lot more detail and has examples on setting the permissions, etc.
Source code copied from the post linked above:
Mutex, Mutex Security & MMF Security Policy Creation
bool mutexCreated;
Mutex mutex;
MutexSecurity mutexSecurity = new MutexSecurity();
MemoryMappedFileSecurity mmfSecurity = new MemoryMappedFileSecurity();
mutexSecurity.AddAccessRule(new MutexAccessRule(new SecurityIdentifier(WellKnownSidType.WorldSid, null),
MutexRights.Synchronize | MutexRights.Modify, AccessControlType.Allow));
mmfSecurity.AddAccessRule(new AccessRule<MemoryMappedFileRights>("everyone", MemoryMappedFileRights.FullControl,
AccessControlType.Allow));
mutex = new Mutex(false, #"Global\MyMutex", out mutexCreated, mutexSecurity);
if (mutexCreated == false) log.DebugFormat("There has been an error creating the mutex");
else log.DebugFormat("mutex created successfully");
Create & Write to the MMF
MemoryMappedFile file = MemoryMappedFile.CreateOrOpen(#"Global\MyMemoryMappedFile", 4096,
MemoryMappedFileAccess.ReadWrite, MemoryMappedFileOptions.DelayAllocatePages, mmfSecurity,
HandleInheritability.Inheritable);
using (MemoryMappedViewAccessor accessor = file.CreateViewAccessor()) {
string xmlData = SerializeToXml(CurrentJobQueue) + "\0"; // \0 terminates the XML to stop badly formed
issues when the next string written is shorter than the current
byte[] buffer = ConvertStringToByteArray(xmlData);
mutex.WaitOne();
accessor.WriteArray<byte>(0, buffer, 0, buffer.Length);
mutex.ReleaseMutex();
}
Reading from the MMF
using (MemoryMappedFile file = MemoryMappedFile.OpenExisting(
#"Global\MyMemoryMappedFile", MemoryMappedFileRights.Read)) {
using (MemoryMappedViewAccessor accessor =
file.CreateViewAccessor(0, 0, MemoryMappedFileAccess.Read)) {
byte[] buffer = new byte[accessor.Capacity];
Mutex mutex = Mutex.OpenExisting(#"Global\MyMutex");
mutex.WaitOne();
accessor.ReadArray<byte>(0, buffer, 0, buffer.Length);
mutex.ReleaseMutex();
string xmlData = ConvertByteArrayToString(buffer);
data = DeserializeFromXML(xmlData);
}
I want to protect my application. So I read hard drive serial number and compare. The application has good result in administrator user mode but it has bad result in standard user mode.
I wrote my application with C#. But for reading hard drive serial number I used a dll file that I wrote in Delphi.
hDevice := CreateFile( '\\.\PhysicalDrive0:', GENERIC_READ or GENERIC_WRITE ,
FILE_SHARE_READ or FILE_SHARE_WRITE , nil, OPEN_EXISTING, 0, 0 );
I try using NET so I used WMI class Win32_DiskDrive but this method has bad results in standard user mode too.
private string getserial()
{
string SerialNumber = "";
string dataForSerial = string.Empty;
ManagementObjectSearcher Finder = new ManagementObjectSearcher("Select * from Win32_OperatingSystem");
string Name = "";
foreach (ManagementObject OS in Finder.Get()) Name = OS["Name"].ToString();
// Name = "Microsoft Windows XP Professional|C:\WINDOWS|\Device\Harddisk0\Partition1"
int ind = Name.IndexOf("Harddisk") + 8;
int HardIndex = Convert.ToInt16(Name.Substring(ind, 1));
Finder = new ManagementObjectSearcher("SELECT * FROM Win32_DiskDrive WHERE Index=" + HardIndex);
foreach (ManagementObject HardDisks in Finder.Get())
foreach (ManagementObject HardDisk in HardDisks.GetRelated("Win32_PhysicalMedia"))
SerialNumber = HardDisk["SerialNumber"].ToString();
// SerialNumber = dataForSerial;
return SerialNumber;
}
In standard user mode:
In case of the Delphi dll it throws an access denied error
In case of the WMI the output is different in standard user vs administrator user.
Note: this problem (NO.2) is only in Windows 7.
Please use the following code when calling CreateFile to access the physical disk. It works without admin rights and allows one to read the drive's properties:
hDisk := CreateFile ('\\.\PHYSICALDRIVE0', 0, FILE_SHARE_WRITE, NIL, OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL or FILE_FLAG_NO_BUFFERING, 0);
Please also take note that the name of the drive passed as the first parameter to CreateFile does not include a colon ":" at the end.
Please see this Link. Results are varying depending on Windows version, on whether the code is run as admin or not, and whether the Win32_PhysicalMedia class is used or the Win32_DiskDrive class. Seems pretty unreliable, you may have to write your own abstraction layer to handle it yourself, as described in these forum posts.
I tried it myself and found I got two different serial numbers depending on admin vs normal and Win32_PhysicalMedia vs Win32_DiskDrive:
VB38bb50ab-0de50c12
and
42563833626230356261302d6564303531632032
Notice that the second string is actually a hex-encoded and byte-reversed version of the first string!
I'm trying to connect to barcode scanner (is a HID device). I was able to receive data using RawInput, but I want to try using CreateFile().
deviceInformation.ReadHandle = Kernel32.CreateFile(
deviceInformation.DevicePathName,
Constants.GenericRead,
Constants.FileShareRead | Constants.FileShareWrite,
IntPtr.Zero,
Constants.OpenExisting,
Constants.FileFlagOverlapped,
0);
int it = Marshal.GetLastWin32Error();
CreateFile() returns this:
Marshal.GetLastWin32Error() returns 5 which is ERROR_ACCESS_DENIED
I'm using this source code as the reference.
My questions are: Is it possible to create a handle to that hid device using CreateFile()? If yes, why do I receive Access denied? and what is the correct way to create a handle?
PS: Windows 10, Visual Studio 2015
For quite a few days now I have been trying to get some custom Active Directory based authentication to work. It all works in theory but apparently my theory is wrong. Users who are logged on to a domain write a string token (e.g. PIN code) to their own property field in Active Directory (doesn't really matter which one, but I used primaryInternationISDNNumber for this) upon logging on to the ASP.NET application This PIN is always generated and written programmatically.
To explain it roughly, the web browser loads a Java applet which then loads a native DLL written in C++, which generates and writes the PIN to current user's Active Directory field. That DLL then returns the generated PIN to the applet which then passes it to the browser, which performs an AJAX call with the data returned to initiate the authentication. The application, which has got access to the AD, reads this field value for the connecting user object and checks if it matches with the one the user supplied. If PIN codes match, the user is successfully authenticated.
This is the sample code the ASP.NET application used to read the AD:
using (var de = new DirectoryEntry("LDAP://" + domainName))
{
using (var adSearch = new DirectorySearcher(de))
{
// Get user from active directory.
adSearch.Filter = "(sAMAccountName=" + userName.Trim().ToLower(CultureInfo.CurrentCulture) + ")";
var adSearchResult = adSearch.FindOne();
var entry = adSearchResult.GetDirectoryEntry();
var pinCodeProp = entry.Properties["primaryInternationISDNNumber"];
return pinCodeProp != null ? pinCodeProp.Value : string.Empty;
}
}
This works fine, often. But often is not acceptable. It needs to always be working.
The trouble is that the ASP.NET application sometimes gets the value which was previously in that field, not the actual value. As if there is some kind of caching. I have tried to add de.UsePropertyCache = false but that yielded the same results.
I have created two Win32 console applications for test purposes. One writes the PIN code, the other reads the PIN code. They always work fine!
I thought, this gotta be some problem with IIS application pool. So I have created a native DLL which gets loaded by the ASP.NET application using Platform Invoke. This DLL creates a new thread, calls CoInitialize and reads the PIN code. This is the code:
pszFqdn = argv[1];
pszUserName = argv[2];
pszPassword = argv[3];
IADs *pObject = NULL;
HRESULT hr = S_OK;
hr = CoInitialize(NULL);
if (SUCCEEDED(hr))
{
hr = ADsOpenObject(pszFqdn, pszUserName, pszPassword, ADS_SECURE_AUTHENTICATION, IID_IADs, (LPVOID*)&pObject);
if (SUCCEEDED(hr) && pObject)
{
VARIANT var;
VariantInit(&var);
hr = pObject->Get(CComBSTR("primaryInternationalISDNNumber"), &var);
if ((SUCCEEDED(hr) && var.bstrVal) || hr == 0x8000500d)
{
if (hr != 0x8000500d)
{
// convert the BSTR received to TCHAR array
std::wstring wsValue(var.bstrVal, SysStringLen(var.bstrVal));
// copy the received value to somewhere
// ... not relevant
}
VariantClear(&var);
}
pObject->Release();
}
}
CoUninitialize();
To my tremendous and unpleasant surprise, this code after a day of working properly, started returning the previous values, just like the managed code before!
So now I thought, alright, I wasn't able to escape the IIS application pool and since this gotta be a problem with IIS application pool, I will create a native Windows application which I will execute by using Process.Start method. I will return my PIN code by means of process exit code (since it's an integer anyway). The application uses the similar C++ code as the DLL above.
So I start my application, wait for it to finish, read the exit code. Returns the bad value!
But okay, I'd say, the process is started using the current user credentials, which is again IIS application pool. So I start the application under different credentials. And guess what..... it returns the old value again (?!?!?!).
And I thought Java was hell... Anyone has got any idea about what could possibly be going on here?
It was the replication indeed. As I didn't want to force the replication before reading the field (that would have been a time-expensive operation probably anyway), I came to an idea to read this field from each domain controller and check if any of them matches with the value supplied.
As it might prove helpful to someone, I did that using the following code.
var ctx = new DirectoryContext(
DirectoryContextType.DirectoryServer,
ipAddress,
userName, // in the form DOMAIN\UserName or else it would fail for a remote directory server
password);
var domain = Domain.GetDomain(ctx);
var values = new List<string>();
foreach (DomainController dc in domain.DomainControllers)
{
using (var entry =
new DirectoryEntry(
"LDAP://" + dc.IPAddress,
userName,
password))
{
using (var search = new DirectorySearcher(entry))
{
search.Filter = "(&(primaryInternationalISDNNumber=*)(sAMaccountName=" + userName + "))";
var result = search.FindOne();
var de = result.GetDirectoryEntry();
if (de.Properties["primaryInternationalISDNNumber"].Value != null)
{
values.Add(de.Properties["primaryInternationalISDNNumber"].Value.ToString());
}
}
}
}