Asked  7 Months ago    Answers:  5   Viewed   46 times

We've run into an interesting situation that needs solving, and my searches have turned up nill. I therefore appeal to the SO community for help.

The issue is this: we have a need to programmatically access a shared file that is not in our domain, and is not within a trusted external domain via remote file sharing / UNC. Naturally, we need to supply credentials to the remote machine.

Typically, one solves this problem in one of two ways:

  1. Map the file share as a drive and supply the credentials at that time. This is typically done using the NET USE command or the Win32 functions that duplicate NET USE.
  2. Access the file with a UNC path as if the remote computer were on the domain and ensure that the account under which the program runs is duplicated (including password) on the remote machine as a local user. Basically leverage the fact that Windows will automatically supply the current user's credentials when the user attempts to access a shared file.
  3. Don't use remote file sharing. Use FTP (or some other means) to transfer the file, work on it locally, then transfer it back.

For various and sundry reasons, our security / network architects have rejected the first two approaches. The second approach is obviously a security hole; if the remote computer is compromised, the local computer is now at risk. The first approach is unsatisfactory because the newly mounted drive is a shared resource available to other programs on the local computer during file access by the program. Even though it's quite possible to make this temporary, it's still a hole in their opinion.

They're open to the third option, but the remote network admins insist on SFTP rather than FTPS, and FtpWebRequest only supports FTPS. SFTP is the more firewall-friendly option and there are a couple libraries I could use for that approach, but I'd prefer to reduce my dependencies if I can.

I've searched MSDN for either a managed or a win32 means of using remote file sharing, but I have failed to come up with anything useful.

And so I ask: Is there another way? Did I miss a super-secret win32 function that does what I want? Or must I pursue some variant of option 3?

 Answers

26

The way to solve your problem is to use a Win32 API called WNetUseConnection.
Use this function to connect to a UNC path with authentication, NOT to map a drive.

This will allow you to connect to a remote machine, even if it is not on the same domain, and even if it has a different username and password.

Once you have used WNetUseConnection you will be able to access the file via a UNC path as if you were on the same domain. The best way is probably through the administrative built in shares.
Example: \computernamec$program filesFolderfile.txt

Here is some sample C# code that uses WNetUseConnection.
Note, for the NetResource, you should pass null for the lpLocalName and lpProvider. The dwType should be RESOURCETYPE_DISK. The lpRemoteName should be \ComputerName.

using System;
using System.Runtime.InteropServices ;
using System.Threading;

namespace ExtremeMirror
{
    public class PinvokeWindowsNetworking
    {
        #region Consts
        const int RESOURCE_CONNECTED = 0x00000001;
        const int RESOURCE_GLOBALNET = 0x00000002;
        const int RESOURCE_REMEMBERED = 0x00000003;

        const int RESOURCETYPE_ANY = 0x00000000;
        const int RESOURCETYPE_DISK = 0x00000001;
        const int RESOURCETYPE_PRINT = 0x00000002;

        const int RESOURCEDISPLAYTYPE_GENERIC = 0x00000000;
        const int RESOURCEDISPLAYTYPE_DOMAIN = 0x00000001;
        const int RESOURCEDISPLAYTYPE_SERVER = 0x00000002;
        const int RESOURCEDISPLAYTYPE_SHARE = 0x00000003;
        const int RESOURCEDISPLAYTYPE_FILE = 0x00000004;
        const int RESOURCEDISPLAYTYPE_GROUP = 0x00000005;

        const int RESOURCEUSAGE_CONNECTABLE = 0x00000001;
        const int RESOURCEUSAGE_CONTAINER = 0x00000002;


        const int CONNECT_INTERACTIVE = 0x00000008;
        const int CONNECT_PROMPT = 0x00000010;
        const int CONNECT_REDIRECT = 0x00000080;
        const int CONNECT_UPDATE_PROFILE = 0x00000001;
        const int CONNECT_COMMANDLINE = 0x00000800;
        const int CONNECT_CMD_SAVECRED = 0x00001000;

        const int CONNECT_LOCALDRIVE = 0x00000100;
        #endregion

        #region Errors
        const int NO_ERROR = 0;

        const int ERROR_ACCESS_DENIED = 5;
        const int ERROR_ALREADY_ASSIGNED = 85;
        const int ERROR_BAD_DEVICE = 1200;
        const int ERROR_BAD_NET_NAME = 67;
        const int ERROR_BAD_PROVIDER = 1204;
        const int ERROR_CANCELLED = 1223;
        const int ERROR_EXTENDED_ERROR = 1208;
        const int ERROR_INVALID_ADDRESS = 487;
        const int ERROR_INVALID_PARAMETER = 87;
        const int ERROR_INVALID_PASSWORD = 1216;
        const int ERROR_MORE_DATA = 234;
        const int ERROR_NO_MORE_ITEMS = 259;
        const int ERROR_NO_NET_OR_BAD_PATH = 1203;
        const int ERROR_NO_NETWORK = 1222;

        const int ERROR_BAD_PROFILE = 1206;
        const int ERROR_CANNOT_OPEN_PROFILE = 1205;
        const int ERROR_DEVICE_IN_USE = 2404;
        const int ERROR_NOT_CONNECTED = 2250;
        const int ERROR_OPEN_FILES  = 2401;

        private struct ErrorClass 
        {
            public int num;
            public string message;
            public ErrorClass(int num, string message) 
            {
                this.num = num;
                this.message = message;
            }
        }


        // Created with excel formula:
        // ="new ErrorClass("&A1&", """&PROPER(SUBSTITUTE(MID(A1,7,LEN(A1)-6), "_", " "))&"""), "
        private static ErrorClass[] ERROR_LIST = new ErrorClass[] {
            new ErrorClass(ERROR_ACCESS_DENIED, "Error: Access Denied"), 
            new ErrorClass(ERROR_ALREADY_ASSIGNED, "Error: Already Assigned"), 
            new ErrorClass(ERROR_BAD_DEVICE, "Error: Bad Device"), 
            new ErrorClass(ERROR_BAD_NET_NAME, "Error: Bad Net Name"), 
            new ErrorClass(ERROR_BAD_PROVIDER, "Error: Bad Provider"), 
            new ErrorClass(ERROR_CANCELLED, "Error: Cancelled"), 
            new ErrorClass(ERROR_EXTENDED_ERROR, "Error: Extended Error"), 
            new ErrorClass(ERROR_INVALID_ADDRESS, "Error: Invalid Address"), 
            new ErrorClass(ERROR_INVALID_PARAMETER, "Error: Invalid Parameter"), 
            new ErrorClass(ERROR_INVALID_PASSWORD, "Error: Invalid Password"), 
            new ErrorClass(ERROR_MORE_DATA, "Error: More Data"), 
            new ErrorClass(ERROR_NO_MORE_ITEMS, "Error: No More Items"), 
            new ErrorClass(ERROR_NO_NET_OR_BAD_PATH, "Error: No Net Or Bad Path"), 
            new ErrorClass(ERROR_NO_NETWORK, "Error: No Network"), 
            new ErrorClass(ERROR_BAD_PROFILE, "Error: Bad Profile"), 
            new ErrorClass(ERROR_CANNOT_OPEN_PROFILE, "Error: Cannot Open Profile"), 
            new ErrorClass(ERROR_DEVICE_IN_USE, "Error: Device In Use"), 
            new ErrorClass(ERROR_EXTENDED_ERROR, "Error: Extended Error"), 
            new ErrorClass(ERROR_NOT_CONNECTED, "Error: Not Connected"), 
            new ErrorClass(ERROR_OPEN_FILES, "Error: Open Files"), 
        };

        private static string getErrorForNumber(int errNum) 
        {
            foreach (ErrorClass er in ERROR_LIST) 
            {
                if (er.num == errNum) return er.message;
            }
            return "Error: Unknown, " + errNum;
        }
        #endregion

        [DllImport("Mpr.dll")] private static extern int WNetUseConnection(
            IntPtr hwndOwner,
            NETRESOURCE lpNetResource,
            string lpPassword,
            string lpUserID,
            int dwFlags,
            string lpAccessName,
            string lpBufferSize,
            string lpResult
        );

        [DllImport("Mpr.dll")] private static extern int WNetCancelConnection2(
            string lpName,
            int dwFlags,
            bool fForce
        );

        [StructLayout(LayoutKind.Sequential)] private class NETRESOURCE
        { 
            public int dwScope = 0;
            public int dwType = 0;
            public int dwDisplayType = 0;
            public int dwUsage = 0;
            public string lpLocalName = "";
            public string lpRemoteName = "";
            public string lpComment = "";
            public string lpProvider = "";
        }


        public static string connectToRemote(string remoteUNC, string username, string password) 
        {
            return connectToRemote(remoteUNC, username, password, false);
        }

        public static string connectToRemote(string remoteUNC, string username, string password, bool promptUser) 
        {
            NETRESOURCE nr = new NETRESOURCE();
            nr.dwType = RESOURCETYPE_DISK;
            nr.lpRemoteName = remoteUNC;
            //          nr.lpLocalName = "F:";

            int ret;
            if (promptUser) 
                ret = WNetUseConnection(IntPtr.Zero, nr, "", "", CONNECT_INTERACTIVE | CONNECT_PROMPT, null, null, null);
            else 
                ret = WNetUseConnection(IntPtr.Zero, nr, password, username, 0, null, null, null);

            if (ret == NO_ERROR) return null;
            return getErrorForNumber(ret);
        }

        public static string disconnectRemote(string remoteUNC) 
        {
            int ret = WNetCancelConnection2(remoteUNC, CONNECT_UPDATE_PROFILE, false);
            if (ret == NO_ERROR) return null;
            return getErrorForNumber(ret);
        }
    }
}
Tuesday, June 1, 2021
 
sohum
answered 7 Months ago
25

Yes, it's possible for a program to open the same file multiple times from different threads. You'll want to avoid reading from the file at the same time you're writing to it, though. You can use TMultiReadExclusiveWriteSynchronizer to control access to the entire file. It's less serialized than, say, a critical section. For more granular control, take a look at LockFileEx to control access to specific regions of the file as you need them. When writing, request an exclusive lock; when reading, a shared lock.

As for the code you posted, specifying File_Share_Write in the initial sharing flags means that all subsequent open operations must also share the file for writing. Quoting from the documentation:

If this flag is not specified, but the file or device has been opened for write access or has a file mapping with write access, the function fails.

Your second open request was saying that it did not want anybody else to be allowed to write to the file while that handle remained open. Since there was already another handle open that did allow writing, the second request could not be fulfilled. GetLastError should have returned 32, which is Error_Sharing_Violation, exactly what the documentation says should happen.

Specifying File_Flag_Delete_On_Close means all subsequent open requests need to share the file for deletion. The documentation again:

Subsequent open requests for the file fail, unless the FILE_SHARE_DELETE share mode is specified.

Then, since the second open request shares the file for deletion, all other open handles must have also shared it for deletion. The documentation:

If there are existing open handles to a file, the call fails unless they were all opened with the FILE_SHARE_DELETE share mode.

The bottom line is that either everybody shares alike or nobody shares at all.

FFileSystem := CreateFile(PChar(FFileName),
  Generic_Read or Generic_Write
  File_Share_Read or File_Share_Write or File_Share_Delete,
  nil,
  Create_Always,
  File_Attribute_Normal or File_Flag_Random_Access
    or File_Attribute_Temporary or File_Flag_Delete_On_Close,
  0);

FFileSystem2 := CreateFile(PChar(FFileName),
  Generic_Read,
  File_Share_Read or File_Share_Write or File_Share_Delete,
  nil,
  Open_Existing,
  File_Attribute_Normal or File_Flag_Random_Access
    or File_Attribute_Temporary or File_Flag_Delete_On_Close,
  0);

In other words, all the parameters are the same except for the fifth one.

These rules apply to two attempts to open on the same thread as well as attempts from different threads.

Thursday, July 1, 2021
 
JakeGR
answered 5 Months ago
38

Put the global in main.c and declare it extern in the shared object, and try this:

MACOSX_DEPLOYMENT_TARGET=10.3 ld -dylib -undefined dynamic_lookup -o multiply.so multiply.o

or

MACOSX_DEPLOYMENT_TARGET=10.3 libtool -dynamic -undefined dynamic_lookup -o multiply.so multiply.o

It worked for me on Mac OS X 10.4

Wednesday, July 28, 2021
 
ajaybc
answered 5 Months ago
21

I found it !!

It's some extra non-printable bytes added in file path when Copying / Pasting from some labels in Windows 10 explorer.

Consider this piece of code :

Console.WriteLine(new DirectoryInfo(@"c:a"));
Console.WriteLine(new DirectoryInfo(@"‪c:a"));  

These line looks the same and should not raise any exception (even if directory c:a doesn't exist) but actually if you copy/paste the code above in an application, the second line will raise NotSupportedException with words : "The given path's format is not supported".

I've ended up checking .NET source code and I found method StringExpressionSet.Canonicalize which raised NotSupportedException :

...
 if (path.IndexOf( ':', 2 ) != -1)
      throw new NotSupportedException( Environment.GetResourceString( "Argument_PathFormatNotSupported" ) );
...

And actually :

Console.WriteLine(@"c:a".IndexOf( ':', 2 )); //  results -1
Console.WriteLine(@"‪c:a".IndexOf( ':', 2 )); //  result 2
// Copy/Paste to test

Where did I caught it ?

In order to not making any typing mistake, I'm used to copy directory path from Right Click to a file -> Properties -> Security

enter image description here

You are warned now !

Sunday, September 12, 2021
 
assylias
answered 3 Months ago
84

If you're making a Window's service, then you'll want to do it programmatically. I usually keep forms out of my services and make a separate interface for them to communicate. Now the FileSystemWatcher doesn't have an event to watch solely for size, so you'll want to make a method that ties to FileSystemWatcher.Changed to check for modifications to existing files. Declare and initialize the control in your OnStart method and tie together the events as well. Do any cleanup code in your OnStop method. It should look something like this:

protected override void OnStart(string[] args)
{
FileSystemWatcher Watcher = new FileSystemWatcher("PATH HERE");
Watcher.EnableRaisingEvents = true;
Watcher.Changed += new FileSystemEventHandler(Watcher_Changed);
} 

// This event is raised when a file is changed
private void Watcher_Changed(object sender, FileSystemEventArgs e)
{
// your code here
}

Also note, the FileSystemWatcher will fire off multiple events for a single file, so when you're debugging watch for patterns to work around it.

Tuesday, November 2, 2021
 
Octopus
answered 1 Month ago
Only authorized users can answer the question. Please sign in first, or register a free account.
Not the answer you're looking for? Browse other questions tagged :
 
Share