Using MAPI to access the Exchange Server from a Service - c#

I was tasked with building an application that would check email using MAPI. I made use of a wrapper class coded in cpp, which is accessed from c#. I realize that combining managed and unmanaged code is not necessarily the best path, but it was what I could get to work.
After getting it working, I was asked to make the application a service, so it could be run when the system was not logged in.
The client requires us to use MAPI, and is using Outlook 2007, but I would like it to be compatible with both x86 and x64 architecture. A separate program running on several workstations will be allowed to send mail using a single email address. The service will monitor this account, watching for new email from Exchange saying a message could not be delivered. When this happens, it will make a note in the database for future correction.
My understanding of how Extended MAPI works is that it uses the profile of the person logged in to access the Exchange Server. My question is whether the Exchange Server can be accessed through MAPI when nobody is logged into the system? If this is not possible, does OOM allow for access to a specific email account (or profile) when no user is logged in? Would one method be better than the other when predominantly using c#?
Below is a brief sample of how the wrapper class logs in. I added the second method, but never did get it to log in to a profile other than that of the current user's.
BOOL CMAPIEx::Login(LPCTSTR szProfileName, BOOL bInitAsService)
{
DWORD dwFlags=MAPI_EXTENDED | MAPI_USE_DEFAULT | MAPI_NEW_SESSION;
if(bInitAsService) dwFlags|=MAPI_EXPLICIT_PROFILE | MAPI_NT_SERVICE;
return (MAPILogonEx(NULL, (LPTSTR)szProfileName, NULL, dwFlags, &m_pSession)==S_OK);
}
BOOL CMAPIEx::Login(LPCTSTR szProfileName, LPCTSTR szProfilePassword, BOOL bInitAsService)
{
DWORD dwFlags=MAPI_EXTENDED | MAPI_EXPLICIT_PROFILE | MAPI_NEW_SESSION;
if(bInitAsService)
dwFlags|= MAPI_NT_SERVICE;
return (MAPILogonEx(NULL, (LPTSTR)szProfileName, (LPTSTR)szProfilePassword, dwFlags, &m_pSession)==S_OK);
}
Thank you for any suggestions.

You can dynamically create a temporary profile with the MSEMS service and configure it.
See http://support.microsoft.com/kb/306962?wa=wsignin1.0 and scroll to "Use the MAPI IProfAdmin interface"
Make sure the service runs under the identity of the mailbox owner.

Related

Authenticating RPC Server with NTLM

I am currently trying to improve an RPC Server I'm responsible for, both server and client run on the same machine locally, however I would like to restrict the server so that it only allows administrator (including built in /LocalSystem account) to connect to the rpc server through a named pipe.
First of all I am using the following library as a wrapper for the RPCserverApi/RPCClientApi:
https://github.com/csharptest/CSharpTest.Net.RpcLibrary
I create the Server like so:
server = new RpcServerApi(IId, MaxCalls, ushort.MaxValue, true);
server.AddProtocol(RpcProtseq.ncacn_np, Id, MaxCalls);
// Set authentication
server.AddAuthentication(RpcAuthentication.RPC_C_AUTHN_WINNT);
However when I check the named pipes security it still shows like it's not restricted at all, and my client can still connect even though I have yet to change that to specify authentication.
In addition I can check the access to that named pipe and I get:
\\.\pipe\myNamedPipe
RW Everyone
RW NT AUTHORITY\ANONYMOUS LOGON
RW BUILTIN\Administrators
Okay, So for anyone else that ran into this problem There's a few things I needed to do which was not exposed in the library I was using. So instead I created my own wrapper.
When Registering the Rpc Interface with RpcServerRegisterIf2() I had to pass through the flag:
RPC_IF_ALLOW_SECURE_ONLY
Then In addition when setting up the protocols for the RpcServer: RpcServerUseProtseqEp() I also had to pass through an SDDL, to describe the restrictions on the end point. You can find a description of SDDL's here:
https://learn.microsoft.com/en-us/windows/win32/secauthz/security-descriptor-definition-language
To do this I created an Ace String, then used ConvertStringSecurityDescriptorToSecurityDescriptor() to create the correct object. This then locked down the end point like:
\\.\pipe\myNamedPipe
RW BUILTIN\Administrators
But also it enforced on the server that only authenticated accounts could reach it
My issue originally reported was full of misunderstandings about RPC Servers and Named pipes, I thoroughly recommend reading and understanding the following articles, as they were very helpful to me.
https://csandker.io/2021/01/10/Offensive-Windows-IPC-1-NamedPipes.html
https://csandker.io/2021/02/21/Offensive-Windows-IPC-2-RPC.html

How to connect To Skype For Business using UCMA

I have a problem when I call LyncClient.Get() from a Windows Service project, knowing that it works well if I test on a console application.
var lyncClient = LyncClient.GetClient();
Dictionary<PublishableContactInformationType, object> statusData =
new Dictionary<PublishableContactInformationType, object>
{
{PublishableContactInformationType.LocationName, _position},
{PublishableContactInformationType.Availability, ContactAvailability.Busy}
};
The exception I get is:
Microsoft.Lync.Model.ClientNotFoundException: The host process is not running
at Microsoft.Lync.Model.LyncClient.EnsureOI ()
to Microsoft.Lync.Model.LyncClient.GetClient (Boolean sideBySideLync)
When doing research I read that in Windows service, we cannot get the Lync client by calling GetClient() because the service process and Lync process are in different sessions, that's why I'm trying to work with UCMA or UCWA but I do not understand how it works!
What I am trying to do in my application is to change the position of the skype user from a service. As it is not possible to do it with Lync Client SDK, I have to work with UCWA SDK but I can't find an example that i can follow him, A suggestion !
You are not talking about UCMA, you are talking about the Lync Client SDK.
You can think of the SDK's as such:
Lync Client SDK
This SDK allows you to remote control the standard "Lync Client". You can use this SDK to automate the Lync Client for a user to do whatever you can to do OR to extend the functionality of the Lync Client (kind-of limited). To use this SDK the Lync Client must be running in the user you wish to automate / extend. You can't really run it in a windows service context.
Also all Lync Client applications "share" the one Lync Client "session".
There is an option to run the Lync Client SDK in a "side by side" mode, but that mode is very very limited (i.e. no UI) and in most cases is not that useful.
UCMA
This SDK is a SIP endpoint SDK. It allows you to create and use two main type types of SIP endpoints:
User Endpoints
Trusted Application Endpoints
With sip endpoints you can do almost everything that a Lync Client SDK can do when automating the Lync Client (i.e. make calls, answer calls, set presence, subscribe to presence changes, etc, etc). There are some limitations, no video call support. Makes it harder to handle some situations.
UCMA allows you to create "trusted applications" that allow you to create sip endpoints used to extend S4B infrastructure. "trusted applications" / "trusted application endpoints" are "trusted" within S4B and are allowed to do things that you can't normally do with a simple UCMA application e.g. IVR
So it depends on what you are trying to do depends on what SDK you should use.
UCMA applications can be used in windows service applications.
UCWA is a web SDK version of UCMA (kind-of). The UCWA is a lot more limited than the UCMA SDK but UCWA works for Skype for Business Online whereas the UCMA doesn't directly work with Skype for Business Online. You can get UCMA working with Skype for Business Online using federation but that requires on-premise S4B setup federated to Skype for Business Online which is a lot of work.
Update:
To answer the comment question, location is part of the "presence". So what you need to do to set the location is to set the current presence with a location. For UCWA, see this MSDN link on setting the presence. Following the example, change which link you use to the "location" href and post something like:
{"location":"my new location"}
Why do you do it using a service? You could do it with a small console application that would be running in the background and invisible, and started at session startup.
This way you wouldn't need to know the user's login/password, you only need to poll in your code to wait for the Skype for Business client to be started (which I assume would be shortly after the session start)
Here is an example of what I mean :
class Program
{
[DllImport("user32.dll")]
static extern bool ShowWindow(IntPtr hWnd, int nCmdShow);
[DllImport("Kernel32")]
private static extern IntPtr GetConsoleWindow();
const int SW_HIDE = 0;
static void Main(string[] args)
{
// Let's hide the console window first ...
IntPtr hwnd;
hwnd = GetConsoleWindow();
ShowWindow(hwnd, SW_HIDE);
// I recommend you start a separate thread from here, I removed it for the sake of simplicity
Boolean clientConnected = false;
while (!clientConnected)
{
try
{
LyncClient lyncClient = LyncClient.GetClient();
clientConnected = true;
// Do your stuff here...
}
catch (ClientNotFoundException ex)
{
// Client not found : the client is probably not running...
// There is nothing to do besides wait and expect to have the user starting his client...
clientConnected = false; // not needed, just to highlight the fact that we are not connected yet
}
// Don't forget to make your application sleep/do nothing on regular intervals to avoid taking 100% CPU time while you are polling
}
}
Of course users could manually kill the application by looking at their Task Manager, but most end users don't do that.

Send encrypted Mail to LotusNotes from Intranet WebApp

Can someone give me a starting point on how to send an encypted mail from my C# .NET Application to a Lotus Notes inbox (in the company intranet)?
I requested a certificate and Notes User from our support.
But now I'm stuck. I read through this guide, and implemented the code but know the mails in my inbox do not have any content, but just a file named smime.p7m. So I am generally unsure if this is the right method.
Can you give me a hint to a tutorial or tell me the steps I need to do?
Or is the linked guide generally right and I goofed something up? In this case please leave a comment an I'll add my code.
Thank you very much in advance!
UPDATE 1 (26.08.16):
Here is what I'm now at so far:
System.Net.Mail.SmtpClient smtp = new System.Net.Mail.SmtpClient("smtp.services.companyname");
smtp.Credentials = new System.Net.NetworkCredential("NOTESUSER","password");
smtp.Send(message);
In Notes itself I ticket the checkbox for "Send my mails encrypted". The thought behind it was the following:
I assumed this way the Notes User passes the credentials to the Smtp Server and uses the usersettings.
The eMails get delivered, but are not encrypted.
Maybe you could try and break down things a bit further. What about sending an encrypted email from a basic mail client like Thunderbird to a person who will open it in her Notes client ?
The fundamental thing is that the recipient must have a private key symetric to the public key you used for encryption. In normal use, Domino does this very well as it comes with its own two-factors PKI : users can't sign in without their private key, which is stored on their workstation in a tiny (~3 ko) file named something like hername.id or user.id. The corresponding public key is for all to see, as it should, in the Domino Directory (names.nsf)
While based on standard RSA stuff, those usual pairs of keys are managed and deployed in ways very specific to Domino.
Now, it is perfectly possible for a user to import a private key issued by a third-party certification authority. I don't have the exact procedure at hand right now buy you'll find it in the help.nsf available to any Notes client.
But I wonder. You are inside the intranet, which means that you do have access to the Domino Directory, thus to the usual public key of the recipient. Your application will probably need its own user.id and it's more than likely that you'll need to have the 1352 hole punched in various firewalls. By the way, if it helps to alleviate any concern, by virtue of the aformentioned native PKI, it is very easy to encrypt communications on port 1352 from end to end.
Another option is as follow. The Domino server is also a web server. Sometimes this option is activated, sometimes not. If it is, or if you can make it happen, the directory is available as a web application. Zooming in on the public key of a user would require some tinkering and some HTML parsing but should be doable.
One last one for the road, although you may not like it : Domino is a very good platform for intranet applications, be it of the client-server persuasion or of the HTTP creed.
Okay, here is what I finally did:
Domino.NotesSession nSession = new Domino.NotesSession();
nSession.Initialize("secretpassword"); //password for the Notes User
Domino.NotesDatabase nDatabase = nSession.GetDatabase("SERVER", "names"); //Server and location of the names.nfs file
Domino.NotesDocument nDocument = nDatabase.CreateDocument();
NotesStream nStream;
nDocument.ReplaceItemValue("Subject", tmp.Subject);
nBody = nDocument.CreateMIMEEntity();
nStream = nSession.CreateStream();
nStream.WriteText(tmp.Body);
nBody.SetContentFromText(nStream , "text/HTML;charset=UTF-8", MIME_ENCODING.ENC_IDENTITY_7BIT);
nDocument.EncryptOnSend = true;
nDocument.Send(false, user.INS_EMAIL);
This creates a Notes Session with the latest Notes User logged in. So you install the Notes client on the Server, log in with the user and it works so far.

Access Remote MSMQ Count

My machine is in Domain D1 and there are public MSMQs in a remote server in domain D2. I am connected through vpn to D2, i.e I can RDP the machine in D2 and access the MSMQ.
What I want is to access (Know the message count) of the MSMQ without RDPing the system. So I build an application for this. I used Impersonation to impersonate the user of D2(i.e used credentials of D2)but the problem is I am not able to access the "Public" MSMQ ( used Messagequeue.GetPublicQueue() ) and exceptions are thrown with message "A workgroup installation computer does not support the operation." but when I used MessageQueue.GetPrivateQueue() it returned a collection of private queue.
I tried using MSMQManager for messageCount
Path = #"Direct:OS:machine\publicqueue";
FormatName=null;
new MSMQManager.inIt(machineName, path , FormatName);
This also throws an exception either the queue is not present or not open. but I can check that queue is working fine.
Are you comfortable doing a tiny bit of programming? If not, are you comfortable using PowerShell?
Either way - I would check out this post as it seems to contain the answers you are looking for.
Good luck, hope this helps
Your problem might be that you are working remotely.
The method GetPublicQueuesByMachine() is indeed not available over remote access.
You can see this in a feature matrix in the MSDN documentation: MessageQueue.GetPublicQueuesByMachine:
The following table shows whether this method is available in various Workgroup modes.
Workgroup mode Available
-------------- ---------
Local computer No
Local computer and direct format name No
Remote computer No
Remote computer and direct format name No
Also check the access privileges of your queues.
If I am wrong in the previous suggestion, it might be as simple as experimenting with the access rights for specific users in the network.
MSDN article Public and private queues states:
Default security access for public queues gives everyone permission to
send messages to a public queue. Specific permissions must be
granted for read access.
As for the actual message counting, John Opincar wrote a nice article about counting messages here: Counting Messages in an MSMQ MessageQueue from C#

How should I validate a user's credentials against an ADAM instance over SSL?

Apologies in advance as I haven't had much experience with directories before.
I have an ASP.net application, and I have to validate its users against an Active Directory Application Mode instance running on Server 2k3. I was previously attempting a connection with DirectoryEntry and catching the COMException if the user's credentials (userPrincipalName & password) were wrong, but I had a number of problems when trying to bind as users who weren't a member of any ADAM groups (which is a requirement).
I recently found the System.DirectoryServices.AccountManagement library, which seems a lot more promising, but although it works on my local machine, I'm having some troubles when testing this in our testbed environment. Chances are I'm simply misunderstanding how to use these objects correctly, as I wasn't able to find any great documentation on the matter. Currently I am creating a PrincipalContext with a Windows username and password, then calling the AuthenticateCredentials with the user's userPrincipalName and password. Here's a very short exert of what I'm doing:
using (var serviceContext = new PrincipalContext(
ContextType.ApplicationDirectory,
serverAddress,
rootContainer,
ContextOptions.Negotiate | ContextOptions.SecureSocketLayer,
serviceAccountUsername,
serviceAccountPassword)) {
bool credentialsValid = serviceContext.ValidateCredentials(userID, password, ContextOptions.SecureSocketLayer | ContextOptions.SimpleBind)
}
If the user's credentials are valid, I then go on to perform other operations with that principal context. As I said, this works for both users with and without roles in my own environment, but not in our testbed environment. My old DirectoryEntry way of checking the user's credentials still works with the same configuration.
After a very long morning, I was able to figure out the problem!
The exception message I was receiving when calling ValidateCredentials was extremely vague. After installing Visual Studio 2008 in the test environment (which is on the other side of the country, mind you!), I was able to debug and retrieve the HRESULT of the error. After some very deep searching in to Google, I found some very vague comments about "SSL Warnings" being picked up as other exceptions, and that enabling "SCHANNEL logging" (which I'm very unfamiliar with!) might reveal some more insight. So, after switching that on in the registry and retrying the connection, I was presented with this:
The certificate received from the remote server does not contain the expected name. It is therefore not possible to determine whether we are connecting to the correct server. The server name we were expecting is ADAMServer. The SSL connection request has failed. The attached data contains the server certificate.
I found this rather strange, as the old method of connecting via SSL worked fine. In any case, my co-worker was able to spot the problem - the name on the SSL certificate that had been issued on the server was that of the DNS name ("adam2.net") and not the host name ("adamserver"). Although I'm told that's the norm, it just wasn't resolving the correct name when using PrincipalContext.
Long story short; re-issuing a certificate with the computer name and not the DNS name fixed the problem!

Categories