I have code in a Windows Service that successfully connects to an FTP server when I run it locally through a test harness (with the FTP server being on another machine on the local network).
When I install it in the production hosting environment, though, I get the dreaded WebException "Unable to connect to the remote server". It doesn't seem to matter whether I'm using ACTV or PASV FTP, all I get it this WebException. If I try to FTP from the Windows command line, however, it works perfectly well (so it's not the firewall at fault).
The code I'm using (adapted from How to List Directory Contents with FTP in C#?) reads:
private static readonly string __ftpSourceServer = "ftp://"
+ ConfigurationManager.AppSettings["FtpServer"] + "/";
private static readonly NetworkCredential __ftpCreds = new NetworkCredential(
ConfigurationManager.AppSettings["Username"],
ConfigurationManager.AppSettings["Password"]);
// And now the method MediaSyncDaemon.GetFilesToFetch:
bool usePassive = Boolean.TryParse(ConfigurationManager.AppSettings["UsePassive"]
, out usePassive) && usePassive;
Uri ftpSrv = new Uri(__ftpSourceServer + Uri.EscapeUriString(
ConfigurationManager.AppSettings["FtpPath"]));
Logger.Debug("Connecting to FTP server at " + ftpSrv + "; PASV? " + usePassive);
FtpWebRequest listRequest = (FtpWebRequest) WebRequest.Create(ftpSrv);
listRequest.Method = WebRequestMethods.Ftp.ListDirectory;
listRequest.Credentials = __ftpCreds;
listRequest.UsePassive = usePassive;
listRequest.UseBinary = false;
using (FtpWebResponse listResponse = (FtpWebResponse) listRequest.GetResponse())
{
// ReSharper disable AssignNullToNotNullAttribute
return new StreamReader(listResponse.GetResponseStream())
.ReadToEnd().Split(new[] { '\n', '\r' },
StringSplitOptions.RemoveEmptyEntries)
.Where(s => s.EndsWith(".zip", true, CultureInfo.InvariantCulture))
.ToList();
// ReSharper restore AssignNullToNotNullAttribute
}
The exception is thrown at the FtpWebRequest.GetResponse() call in the using line (outside the return statement), with nothing else in the stack trace:
System.Net.WebException: Unable to connect to the remote server
at System.Net.FtpWebRequest.GetResponse()
at (that line number in that file)
The only real difference between my test harness (which works) and the production environment (which doesn't) is the presence of a firewall in the production environment — all four servers are on slightly different subnets:
Dev client 10.16.6.155 subnet 255.255.255.128
Dev server 10.16.7.242 subnet 255.255.255.0
Prod client 192.168.102.107 subnet 255.255.255.0
Prod server 192.168.203.110 subnet 255.255.255.0
but the firewall can't be the problem is I can FTP from Prod client to Prod server interactively, just not programmatically.
I've tried changing the bool appSettings value for UsePassive and that makes no difference and, in every case, nothing shows up in the FTP server log (so it's not getting that far).
Now I'm not expecting anyone to be able to debug the hardware infrastructure of my hosting environment, but I'm struggling to think of what else I could vary to get this to work. I've seen it work locally in my test harness, calling the same method. In case it helps, the test harness code reads as follows:
[NUnit.Framework.Test]
public void FtpFileListTest()
{
ICollection<string> files = MediaSyncDaemon.GetFilesToFetch();
Assert.IsNotNull(files);
Assert.Greater(files.Count, 0);
}
Does anyone have any ideas of what else I could try, please?
Thanks!
Update
Having had some suggestions of places to look in the comments, I can update this a little further:
The problem does not appear to be user permissions — the service is running in the context of the Local System account (which has more permissions than Administrator does)
The problem does not appear to be code-access security. I've added a SocketPermission.Demand call to the entry method for this chunk of code:
System.Security.CodeAccessPermission socketPermission;
socketPermission = new SocketPermission(NetworkAccess.Connect,
TransportType.Tcp, ConfigurationManager.AppSettings["FtpServer"], 20);
socketPermission.Demand();
socketPermission = new SocketPermission(NetworkAccess.Connect,
TransportType.Tcp, ConfigurationManager.AppSettings["FtpServer"], 21);
socketPermission.Demand();
And I'm not seeing any SecurityException being thrown as a result; I'm still getting the same WebException, at the new line number for that same code position.
Does anyone have any further suggestions of what I could try?
I would like to share our problem and solution:
We were not able to connect to the FTP server with ftpwebrequest on a corporative PC, but on ours it would work fine.
The issue was that ftpwebrequest() was grabbing the proxy configuration that the company’s IT forces on the PC.
To resolve this issue, we added (ftpwebrequest object).proxy = null; before connecting.
Related
I am trying to automatically create an SSH connection for a program that uses an SSH tunnel to update a local database from a remote PostgreSQL server. Up to this time, I have been manually opening a tunnel with PuTTY (including local port forwarding instructions with the -L command). I want to use ssh.net to automatically open the port when the program is run. Once the connection is made, the program uses Entity Framework Core to access the remote database.
When I open the SSH connection with PuTTY, the program runs fine. This is the PuTTY command:
//plink.exe -i "C:\Users\user.name\Desktop\host_private_key.ppk" -L 6544:111.22.33.66:6543 -N user#address.io -pw *PASSWORD*"
(login details removed for privacy)
This is the ssh.net code that I have trying to open the same connection:
public void MakeSSHTunnel()
{
string password = "password";
// path of RSA private key in openSSH format:
string privateKeyPath = "C:/Users/user.name/.ssh/id_rsa";
try
{
// creates variable to transmit RSA private key + passphrase to server via SSH.NET, openSSH compatible.
var privateKeyFile = new PrivateKeyFile(privateKeyPath, password);
string serverAddress = "address.io";
string user = "user";
// allows for the remote port forwarding options required by the server
using (var client = new SshClient(serverAddress, user, privateKeyFile))
{
client.Connect();
var tunnel = new ForwardedPortLocal(6544, "111.22.33.66", 6543);
client.AddForwardedPort(tunnel);
// testing weather the connection has been successful:
if (client.IsConnected)
{
Console.WriteLine("OPENTUNNEL.CS: Connection to {0} successful.", serverAddress);
state = "Open";
}
else
{
Console.WriteLine("Connection to {0} failed.");
state = "Closed";
}
tunnel.Exception += delegate (object sender, ExceptionEventArgs e)
{
Console.WriteLine(e.Exception.ToString());
};
tunnel.Start();
Program.RunBackup();
// ... closes the port ... //
tunnel.Stop();
client.Disconnect();
}
}
catch (UnauthorizedAccessException e)
{
Console.WriteLine(e);
}
}
I am confused since in the above, the if (client.IsConnected) returns true.
The error seems to be occurring when the Entity Framework Core OnConfiguring() method passes details of the connection with its optionsBuilder:
optionsBuilder.UseNpgsql($"Host=127.0.0.1;Port=6544;Database=user;Username=user;Password=databasePassworh;CommandTimeout=300;Timeout=300;SSL Mode=Require;Trust Server Certificate=true;Convert Infinity DateTime=true");
The errors that are arising are:
NpgsqlException: Exception while connecting
and:
ExtendedSocketException: No connection could be made because the target machine actively refused it. 127.0.0.1:6544
I have double checked all passwords, and read through all the SSH.NET documentation and code examples, and left all the previously working (via PuTTY) code untouched.
If anyone can see what I'm doing wrong, I would be grateful. C#, SSH.NET and port forwarding are new to me, please tell me where I'm being an idiot.
This code is now working. I believe the problem was that in line:
var tunnel = new ForwardedPortLocal(6544, "111.22.33.66", 6543);
the 'bound port' did not include an address. I had seen examples where this was undefined, and had followed these. On stepping through the code, I noticed that the field was blank, and decided to try 127.0.0.1. This is now successfully connecting to the database. It works with:
var tunnel = new ForwardedPortLocal("127.0.0.1", 6544, "111.22.33.66", 6543);
Thanks for looking into this and for your contributions.
I am attempting to login into multiple win 10 computers via rdp from a list of computer names then reboot them. From my best knowledge rdp is the way to do this. I am in the beginning stages and I am not able to even connect one machine.
I have attempted and tested code from every resource I could find. I have tested the multiple Microsoft RDP Client Control versions and verified I have version 10 on my Windows 10 Machine. Below is the code I have so far.
private void startBtn_Click(object sender, EventArgs e)
{
try
{
dmain = #"MGROUPNET\";
lines = listBox.Lines;
Server = lines[0];
dmain = dmain + nUser;
//Testing to verify correct details
usernameLabel.Text = dmain;
passLabel.Text = nPass;
serverLabel.Text = Server;
rdp1.Server = Server;
rdp1.UserName = dmain;
rdp1.AdvancedSettings9.NegotiateSecurityLayer = true;
rdp1.AdvancedSettings8.ClearTextPassword = nPass;
rdp1.Connect();
connectLabel.Text = this.rdp1.Connected.ToString();
}
catch (Exception Ex)
{
MessageBox.Show("Error Disconnecting: " + Ex);
}
}
After this code, I get a connection status of 2. This then changes to 0 without displaying anything. Any help or guidance would be appreciated.
I suggest you to use PsExec instead of RDP. You can download it from here:
https://learn.microsoft.com/en-us/sysinternals/downloads/psexec
Now, all you have to do is just open a cmd window with your c# program with the following command:
psexec_path \\target_IPv4 -u local_username -p password shutdown.exe -t 0 -r
If connection is succeed, the device will try restart with the specified user credentials. Make sure that the user has the required permissions to do this (e.g. if you are in a domain).
I have to notice that the connection connection is not secured. Use PsExec v2.1 at least to encrypt your connection.
I'm not sure why am I getting this result. I'm running this on a Linux server. (It's my small web site's shared web hosting account.)
The files are grouped as follows:
and the Another Dir has one file inside:
So I'm trying to retrieve the contents of the badname directory inside 1somelongdir1234567 directory that doesn't exist on the server, using this code:
try
{
FtpWebRequest ftpRequest = (FtpWebRequest)WebRequest.Create(
"ftp://server12.some-domain.com/public_html/1somelongdir1234567/badname");
ftpRequest.EnableSsl = true;
ftpRequest.Credentials = new NetworkCredential("user", "password");
ftpRequest.KeepAlive = true;
ftpRequest.Timeout = -1;
ftpRequest.Method = WebRequestMethods.Ftp.ListDirectoryDetails;
using (FtpWebResponse response1 = (FtpWebResponse)ftpRequest.GetResponse())
{
//*****BEGIN OF EDIT*****
Console.WriteLine(response1.StatusDescription);
Console.WriteLine(response1.StatusCode);
//*****END OF EDIT*****
using (StreamReader streamReader = new StreamReader(response1.GetResponseStream()))
{
List<string> arrList = new List<string>();
for (; ; )
{
string line = streamReader.ReadLine();
//I get to here, where `line` is null????
if (string.IsNullOrEmpty(line))
break;
arrList.Add(line);
//*****BEGIN OF EDIT*****
Console.WriteLine(line);
//*****END OF EDIT*****
}
}
}
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
So as you see, there's no such folder badname but instead of throwing an exception, my ftpRequest.GetResponse() succeeds and then streamReader.ReadLine() returns null like I showed in the code above.
More strangely, if I provide an actual directory as such:
FtpWebRequest ftpRequest = (FtpWebRequest)WebRequest.Create(
"ftp://server12.some-domain.com/public_html/1somelongdir1234567/Another%20Dir");
the streamReader.ReadLine() still returns null.
Can someone explain why?
Edit: OK, guys, I updated the code above to retrieve the status code. I'm still puzzled though.
First, here's three values of connection URI and the response/output that I'm getting:
Example 1:
//Existing folder
"ftp://server12.some-domain.com/public_html/1somelongdir1234567"
Output:
150 Accepted data connection
OpeningData
drwxr-xr-x 3 username username 4096 Sep 5 05:51 .
drwxr-x--- 118 username 99 4096 Sep 5 05:54 ..
drwxr-xr-x 2 username username 4096 Sep 5 05:52 Another Dir
-rw-r--r-- 1 username username 11 Sep 5 05:51 test123.txt
Example 2:
//Another existing folder
"ftp://server12.some-domain.com/public_html/1somelongdir1234567/Another%20Dir"
Output:
150 Accepted data connection
OpeningData
Example 3:
//Nonexistent folder
"ftp://server12.some-domain.com/public_html/1somelongdir1234567/SomeBogusName"
Output:
150 Accepted data connection
OpeningData
So why is it giving me the same result for example 2 as I'm getting for 3?
As for what ftp server it is, I wasn't able to see it in the network logs nor in FtpWebRequest itself. Here's what I was able to get from Microsoft Network Monitor:
Welcome to Pure-FTPd [privsep] [TLS] ----------..
220-You are user number 4 of 50 allowed...
220-Local time is now 13:14. Server port: 21...
220-This is a private system - No anonymous login..
220-IPv6 connections are also welcome on this server...
220 You will be disconnected after 15 minutes of inactivity...
The URL for the ListDirectory/ListDirectoryDetails method should end with a slash, in general.
Without a slash, results tend to be uncertain.
WebRequest.Create("ftp://example.com/public_html/1somelongdir1234567/Another%20Dir/");
As stuartd noted, you get an FTP status code on the response, As far as I know, because of the way FTP commands work, you will never get an exception when a server fails to execute a command you requested. The server will instead just tell you it failed through the FTP response code.
One thing you have to realize is that FTP has no standards for the text it sends back, because it was never designed for its responses to be machine-interpreted. If you got some time, look up the code of FileZilla, and check their class for interpreting the file listings (directorylistingparser.cpp). There are dozens of ways to interpret it, and the application will just try them all until one works. Because, unless you know what kind of FTP you're connecting to, that's the only way to do it.
So if that specific server decides to send an empty string back if you request a nonexistent directory, that's just how their FTP implementation does it, and it's the client's problem to figure out what that means.
If you can't figure out it's a failure from the FTP response code, you can just test if it returns something different for an existing but empty folder. If so, you can easily distinguish between that and this empty response, and treat the empty response as "not found" error in your program.
I'm attempting to use SSH.NET to create a tunnel from localhost:3306 to port 3306 on a remote machine:
PrivateKeyFile file = new PrivateKeyFile(#" .. path to private key .. ");
using (var client = new SshClient(" .. remote server .. ", "ubuntu", file))
{
client.Connect();
var port = new ForwardedPortLocal(3306, "localhost", 3306);
client.AddForwardedPort(port);
port.Start();
// breakpoint set within the code here
client.Disconnect();
}
When the breakpoint is hit, client.IsConnected is returning true, but telnet localhost 3306 is not connecting. If I create the connection using Putty instead, and set up the same tunnel there, it succeeds. What have I missed?
By changing the parameters of ForwardedPortLocal to:
var port = new ForwardedPortLocal("localhost", 3306, "localhost", 3306);
(to make it explicit which interface I was binding to), and adding the following code in just before port.Start();:
port.RequestReceived += delegate(object sender, PortForwardEventArgs e)
{
Console.WriteLine(e.OriginatorHost + ":" + e.OriginatorPort);
};
I noticed the following being output:
::1:60309
The e.OriginatorHost part of this was ::1, which is the IPv6 equivalent of localhost; however, the destination server was using IPv4. Changing the parameters to:
var port = new ForwardedPortLocal("127.0.0.1", 3306, "localhost", 3306);
forced the tunnel to run over IPv4 instead, and my code then worked exactly as I'd expected it to.
After hitting the same problem and analyzing it, and considering I came to the conclusion that it is a bug (though it might be considered arguable, clearly the behavior surprises users of SSH.NET API).
So I reported it on Unexpected behavior (a.k.a. bug) on local tunnel · Issue #117 · sshnet/SSH.NET · GitHub.
Until it is fixed, the workaround in c# - Creating a forwarded port within an SSH tunnel - Stack Overflow works.
I'm creating an application for Minecraft Classic to send "Heartbeats" to an external website and my application wants to send IPv6 heartbeats rather than IPv4 heartbeats if both IPv6 and IPv4 are present on the system.
Here's what I've tried (through looking through Google search):
Ipv6Element DisableIPv6 = null;
DisableIPv6.enabled = false;
The issue here is that when I add the System.Net.Configuration import, my other portions of code from a .dll won't work anymore because I've attempted to use this method of disabling IPv6.
Here's an example of the .cs file where the heartbeat is sent to the external website (Either Minecraft.net or ClassiCube.net. Since Minecraft.net only supports IPv4 for its Classic servers, the software works fine, but since ClassiCube accepts both 4 and 6, if the machine the server is running has both, it will only take 6, but my software doesn't support IPv6 yet. The .cs file is here. (the Pastebin link expires in 2 weeks)
I've been trying to disable IPv6 if this is the case, but now that I realize that IPv6 will someday replace IPv4, I know I need to support IPv6 now before it is too late
I want to be able to support IPv6 in the application. How can I fix my code to support both IPv4 and 6?
Disclaimer
This code, in fact, is Visual C#, not java. The software has been created in Visual Studio 2013. This program is used to host Minecraft Classic Servers, (Not Premium). Many people don't believe the program was writen in C#, but I'm going to state that now so there is no confusion down the road.
Under the salt initialization:
// Dns lookup, to make sure that IPv4 is preferred for heartbeats
static readonly Dictionary<string, IPAddress> TargetAddresses = new Dictionary<string, IPAddress>();
static DateTime nextDnsLookup = DateTime.MinValue;
static readonly TimeSpan DnsRefreshInterval = TimeSpan.FromMinutes(30);
static IPAddress RefreshTargetAddress([NotNull] Uri requestUri)
{
if (requestUri == null) throw new ArgumentNullException("requestUri");
string hostName = requestUri.Host.ToLowerInvariant();
IPAddress targetAddress;
if (!TargetAddresses.TryGetValue(hostName, out targetAddress) || DateTime.UtcNow >= nextDnsLookup)
{
try
{
// Perform a DNS lookup on given host. Throws SocketException if no host found.
IPAddress[] allAddresses = Dns.GetHostAddresses(requestUri.Host);
// Find a suitable IPv4 address. Throws InvalidOperationException if none found.
targetAddress = allAddresses.First(ip => ip.AddressFamily == AddressFamily.InterNetwork);
}
catch (SocketException ex)
{
Logger.Log(LogType.Error,
"Heartbeat.RefreshTargetAddress: Error looking up heartbeat server URLs: {0}",
ex);
}
catch (InvalidOperationException)
{
Logger.Log(LogType.Warning,
"Heartbeat.RefreshTargetAddress: {0} does not have an IPv4 address!", requestUri.Host);
}
TargetAddresses[hostName] = targetAddress;
nextDnsLookup = DateTime.UtcNow + DnsRefreshInterval;
}
return targetAddress;
}
And then, within static HttpWebRequest CreateRequest, under request.UserAgent, insert
if (uri.Scheme == "http")
{
request.Proxy = new WebProxy("http://" + RefreshTargetAddress(uri) + ":" + uri.Port);
}
That should work, I have no way of testing it. Hopefully you or someone else has ipv6 so they can test. All of this code was basically taken from fCraft by the way, all credit to them. http://www.fcraft.net