I have successfully created a desktop sharing solution where an RDPViewer connects to an RDPSession. That's all working beautifully. Now, however, I'm trying to get the opposite to work: using the RDPViewer's StartReverseConnectListener method, and RDPSession's ConnectToClient method (where the session side would connect to the viewer side to work around NAT/Firewall issues). I've followed the steps outlined at http://msdn.microsoft.com/en-us/library/windows/desktop/aa373359%28v=vs.85%29.aspx, mainly:
The viewer obtains its connection string by calling the
StartReverseConnectListener method, passing NULL for the
bstrConnectionString, bstrUserName, and bstrPassword parameters.
The viewer initiates a reverse connect listener by calling the
StartReverseConnectListener method, passing NULL for the
pbstrReverseConnectString parameter and valid values for the
bstrConnectionString, bstrUserName, and bstrPassword parameters.
The viewer sends the connection string obtained in step 1 to the
sharer.
Using C# 2010, I've done the following on the RDPSession side:
RDPSession session = new RDPSession();
session.Open();
session.Invitations.CreateInvitation(null, "test", "12345", 1);
Then, on the RDPViewer side, I've done:
string reverseConnectString = axRDPViewer1.StartReverseConnectListener(null, null, null);
(step 1, above)
axRDPViewer1.StartReverseConnectListener(reverseConnectString, "test", "12345");
(step 2, above)
Then, back on the RDPSession side, I attempt to make the connection using the reverseConnectString I obtained from the viewer (I actually saved the string to a file, and then loaded it on the RDPSession side):
session.ConnectToClient(reverseConnectString);
(step 3, above)
As soon as I execute this method, the RDPViewer side disconnects with an error of 1798, which, according to http://msdn.microsoft.com/en-us/library/aa373802%28VS.85%29.aspx, means:
ServerCertificateUnpackErr 1798
Failed to unpack server certificate.
I feel like I'm missing something obvious, but I can't figure out what.
Any suggestions?
Thanks!
The Microsoft documentation is all wrong regarding reverse connections. Here's what you need to do (adapted from your code above):
RDP Session Side:
RDPSession session = new RDPSession();
session.Open();
string hostConnString = session.Invitations.CreateInvitation(null, "My Group Name", "12345", 1);
RDPViewer side (note that hostConnString should be the same value as retrieved on the session side):
string viewerConnString = axRDPViewer1.StartReverseConnectListener(hostConnString, "My Name", "12345");
Now back to the RDP session side (note that the viewerConnString should be the same value as retrieved from the viewer side):
session.ConnectToClient(viewerConnString);
And that should do it. A couple of things to note. The group name specified in CreateInvitation does not need to match anything anywhere else. I think it's just for reference should your program need to enumerate the invitations started by the host. The user name passed to StartReverseConnectListener can also be anything you want. This can be interrogated and used on the host side by looking at the RemoteName property in the IRDPSRAPIAttendee interface.
Related
I'm new to Realm Sync (and Realm). I'm trying to convert a REST / SQL Server system to Realm Sync (to avoid having to write my own local-device caching code).
I got a simple configuration working, with a single API-key user and the null partition, read and write permissions just set to true.
But for my more complex application, I want smaller sub-partitions to reduce the amount of data that needs to be cached on local devices, and I want the sub-partitions to be able to be created dynamically by the client. Ideally, I would like to allow an API-key user to connect to any partition whose name starts with their user id (or some other known string, e.g. the profile name). But I can't find a way to get a "starts with" condition into the permissions.
My best attempt was to try setting Read and Write sync permissions to:
{
"%%partition": {
"$regex": "^%%user.id"
}
}
but my client just fails to connect, saying Permission denied (BIND, REFRESH). (Yes, I tried using "$regex": /^%%user.id/ but the Realm UI rejected that syntax.) The Realm Sync log says "user does not have permission to sync on partition (ProtocolErrorCode=206)".
As you can see in the log image, the partition name was equal to the user id for this test.
Is what I'm trying to do possible? If so, how do I set up the Sync Permissions to make it work?
This can be done using a function. If, like me, you're new to Realm Sync and not fluent in Javascript, don't worry - it turns out to be not too hard to do, after all. (Thanks Jay for encouraging me to try it!)
I followed the instructions on the Define a Function page to create my userCanAccessPartition function like this:
exports = function(partition){
return partition.startsWith(context.user.id);
};
Then I set my sync permissions to:
{
"%%true": {
"%function": {
"name": "userCanAccessPartition",
"arguments": ["%%partition"]
}
}
}
There seems to be no answer online as to how you can use Oracle Data Provider for .NET (ODP.NET) to connect to Oracle (12G and later) in a very specific scenario:
User is identified externally on a database
User is granted access to another schema (application user) by proxy connect
User has been set up like this:
CREATE USER user_in_question
IDENTIFIED EXTERNALLY
-- etc.
And connect by proxy has been set up like this:
ALTER USER specified_app_user GRANT CONNECT THROUGH user_in_question
The logical approach when creating the ODP.NET OracleConnection string would be something like this (using the user friendly OracleConnectionStringBuilder):
var connBuilder = new OracleConnectionStringBuilder
{
UserID = "/", // External login using the user running the program
ProxyUserId = "specified_app_user",
DataSource = "database",
};
This does not work. Nor does providing blank "Password" or blank "Proxy Password". Nor does removing the UserId.
So how do you connect using ODP.NET in these circumstances?
The answer (which I spend an hour searching for without any luck) is actually really simple, yet not very user friendly:
var connBuilder = new OracleConnectionStringBuilder
{
UserID = "[specified_app_user]",
DataSource = "database",
};
//connBuilder.ToString() output:
//"USER ID=[specified_app_user];DATA SOURCE=database"
This works in .NET 4.5+ on Oracle 12G+, but probably also on earlier platforms of .NET/Oracle/ODP.NET. I did not test it in ASP.NET, but it should work there too.
This way the UserId actually functions just like the ProxyUserId, just enclosed within brackets, just as you would normally log in on the Oracle Database using, say, Toad or SQlPlus.
It might also be possible using this format (but in my case the connection string had to be compatible with the OraOLEDB format so that did not work):
//Without the use of the conn string builder class, just for the fun of it...
var connString = "User Id=specified_app_user;Data Source=database;Proxy User Id=/";
EDITED 2nd March 2017: The line above does not seem to work in certain cases. Added comment about it and here is the code that IS working:
USER ID=[specified_app_user];DATA SOURCE=database
This info does not seem to exist anywhere - else I overlooked it, and in that case PLEASE do correct me.
When every I attempt to add a new server callback function I cannot seem to get the callback to show up in the $.connection server callback list.
What do I need to do to refresh the javascript that SignalR produces and sends to the client with the latest list of server callbacks.
I would think that I should be able to just add a server callback for the client to call and rebuild my app and fire up a new instance of Google Chrome and the newly added server callback would be in the list of available callbacks on the client.
For example here is exactly what I've done.
1.) A client joins a group and everyone is notified.
public override Task OnConnected()
{
string pid = this.Context.QueryString["pid"],
uid = this.Context.QueryString["uid"],
ispro = this.Context.QueryString["ispro"];
Groups.Add(this.Context.ConnectionId, pid);
return Clients.Group(pid).joined(new cmsg(Context.ConnectionId, UtilCommon.GetUserMini(new Guid(uid), bool.Parse(ispro))));
}
2.) On the client the joined function is called from the server
this.collaborateHub.client.joined = function (cmsg) {
//
that.chatBox.addAttendee(cmsg);
//let the new attendee know about you
if (cmsg.cnnid !== that.chatBox.getMeAttendee().cnnid) {
that.chatBox.getMeAttendee().newbieid = cmsg.cnnid;
debugger
this.server.addMeNewbie(that.chatBox.getMeAttendee());
};
};
3.) Now, if someone joined and it was not the user of the currently opened window, that means the someone how just joined is someone other than myself, so I need to call the server back and notify the newly joined user to add me to their user list. I stead of having to keep up with the currently signed on users to the given group in a database, I am just using every signed on client of the group as a distributed database and let them manage their own info.
So, I need to call the server callback named addMeNewbie; however, this callback function is not available in the list.
public Task addMeNewbie(cmsg cmsg) {
return Clients.Client(cmsg.newbieid).addCurrAttendee(cmsg);
}
Here is a snapshot of the client side in debug mode
4.) And finally here is the client side callback that i need the server to call so that the newly joined group member can update their list of current group members.
this.collaborateHub.client.addCurrAttendee = function (cmsg) {
debugger
that.chatBox.addAttendee(cmsg);
};
I would think that I should be able to add as many server callbacks as I want and they should show up on the next build and restart of a new instance of Google Chrome; however, this is not the case. What do I need to do to get newly added server callbacks to show up in the available server callbacks on the client side?
This doesn't make since to me, but I am thinking maybe this is a cashing issue or something?
The only callback that is available is the one shown in the snapshot provided. At this time I've only got two callbacks, the one that you see in the snapshot and the one that is not visible.
Somewhere along the way I created a signalr-hubs.js file from the generated JavaScript file that can be retrieved by http://localhost:3250/signalr/hubs and was adding new server functions to my hub but not updating the signalr-hugs.js file.
At this point every time I add a new server callback I retrieve the newly generated proxy by placing this url http://localhost:3250/signalr/hubs into the browser, coping the code, and then updating my signalr-hubs.js file.
I am using the DMO API via .NET to provide an alternative interface to job scheduling functionality on SQL Server 2000 Agent. The working code looks something like this:
using SQLDMO;
internal class TestDmo {
public void StartJob() {
SQLServerClass sqlServer = new SQLServerClass();
sqlServer.Connect("MyServerName", "sql_user_id", "p#ssword"); // no trusted/SSPI overload?
foreach (Job job in sqlServer.JobServer.Jobs) {
if (!job.Name.Equals("MyJob")) continue;
job.Start(null);
}
}
}
Everything works in the above-listed form (SQL Server authentication with uid/pwd provided) but I would also like to provide an option to authenticate as a trusted user (aka SSPI, Trusted Connection)
Is this possible in the DMO API? If so how?
Note: The SQLServerClass.Connect method does not seem to have any overloads, I already tried to pass null values for the user id and password to no avail and the Googles has not been helpful yet. Any ideas?
From the documentation:
object.Connect( [ ServerName ] , [ Login ] , [ Password ] )
[...]
Use the Login and Password arguments to specify values used for SQL Server Authentication. To use Windows Authentication for the connection, set the LoginSecure property to TRUE prior to calling the Connect method. When LoginSecure is TRUE, any values provided in the Login and Password arguments are ignored.
Thus, you have to set the LoginSecure property to true before calling Connect. Then, it does not matter which values you pass for the last two parameters.
Sure, you can use the LoginSecure property:
SQLServerClass sqlServer = new SQLServerClass();
sqlServer.LoginSecure = true;
sqlServer.Connect("MyServerName", null, null);
(actually, I don't remember if you must pas null or empty strings...)
Set SQLServerClass.LoginSecure = true and leave user name and password null.
Have a look here for more information. I just noticed that LoginSecure is deprecated, though. Apparently, SQL-DMO has been superseded by SMO.
Hi all, I seem to have found a discrepancy when testing ASP.NET applications locally on the built-in web server with Visual Studio 2008 (Cassini).
I've set up a host on my local machine associating dev.testhost.com with 127.0.0.1, since I have an application that needs to change its appearance depending on the host header used to call it.
However, when I request my test application using http://dev.testhost.com:1234/index.aspx, the value of Request.Url.Host is always "localhost". Whereas the value of Request.Headers["host"] is "dev.testhost.com:1234" (as I would expect them both to be).
I'm NOT concerned that the second value includes the port number, but I am mighty confused as to why the HOST NAMES are completely different! Does anyone know if this is a known issue, or by design? Or am I being an idiot?!
I'd rather use Request.Url.Host, since that avoids having to strip out the port number when testing... - Removed due to possibly causing confusion! - Sam
Request.Headers["host"] is the value received from the application that connects to the server, while the other value is the one the server gets when it tries to get the domain name.
The browser uses in the request the domain name entered because that is used in the case of virtual domains. The server reports the one set in the server preferences, or the first one it finds.
EDIT: Looking at the code of Cassini to see if it uses some particular settings, I noticed the following code:
public string RootUrl {
get {
if (_port != 80) {
return "http://localhost:" + _port + _virtualPath;
}
else {
return "http://localhost" + _virtualPath;
}
}
}
//
// Socket listening
//
public void Start() {
try {
_socket = CreateSocketBindAndListen(AddressFamily.InterNetwork, IPAddress.Loopback, _port);
}
catch {
_socket = CreateSocketBindAndListen(AddressFamily.InterNetworkV6, IPAddress.IPv6Loopback, _port);
}
// …
}
The explanation seems to be that Cassini makes explicit reference to localhost, and doesn't try to make a reverse DNS lookup. Differently, it would not use return "http://localhost" + _virtualPath;.
The Request.Headers["host"] is the host as specified in the http header from the browser. (e.g. this is what you'd see if you examined the traffic with Fiddler or HttpWatch)
However, ASP.NET loasds this (and other request info) into a System.Uri instance, which parses the request string into its constituent parts. In this case, "Host" refers to literally the host machine part of the original request (e.g. with the tcp port being in the Port) property.
This System.Uri class is a very useful helper class that takes all the pain out of splitting your request into it's parts, whereas the "Host:" (and for that matter the "GET") from the http header are just raw request data.
Although they both have the same name, they are not meant to be the same thing.
It's a matter of what the w3 specs say versus what the Microsoft Uri.Host property is supposed to contain. The naming does not imply an attempt by MS to provide identical functionality. The function that does include port numbers is Uri.Authority.
With the update you posted, you're still facing the same problem, just examining a different aspect of it. The Uri.Host property is not explicity or implicity stated to perform the same function as the headers that are defined in the w3 specs. In long form, here are some quotes from the Uri.Host MSDN page:
Uri.Host Property
Gets the host component of this instance.
Property Value
Type: System.String
A String that contains the host name. This is usually the DNS host name or IP address of the server.
There's no guarantee that this will match what is in the headers, just that it represents the host name in some form.