Exemplo n.º 1
0
void zen::loginNetworkShare(const Zstring& dirpathOrig, bool allowUserInteraction) //throw() - user interaction: show OS password prompt
{
    /*
    ATTENTION: it is not safe to retrieve UNC path via ::WNetGetConnection() for every type of network share:

    network type				 |::WNetGetConnection rv   | lpRemoteName				     | existing UNC path
    -----------------------------|-------------------------|---------------------------------|----------------
    inactive local network share | ERROR_CONNECTION_UNAVAIL| \\192.168.1.27\new2			 | YES
    WebDrive					 | NO_ERROR				   | \\Webdrive-ZenJu\GNU		     | NO
    Box.net (WebDav)			 | NO_ERROR				   | \\www.box.net\DavWWWRoot\dav    | YES
    NetDrive					 | ERROR_NOT_CONNECTED     | <empty>						 | NO
    ____________________________________________________________________________________________________________

    Windows Login Prompt Naming Conventions:
    	network share:	\\<server>\<share>	e.g. \\WIN-XP\folder or \\192.168.1.50\folder
    	user account:	<Domain>\<user>		e.g. WIN-XP\Zenju    or 192.168.1.50\Zenju

    Windows Command Line:
    - list *all* active network connections, including deviceless ones which are hidden in Explorer:
    		net use
    - delete active connection:
    		net use /delete \\server\share
    ____________________________________________________________________________________________________________

    Scenario: XP-shared folder is accessed by Win 7 over LAN with access limited to a certain user

    Problems:
    I.   WNetAddConnection2() allows (at least certain) invalid credentials (e.g. username: a/password: a) and establishes an *unusable* connection
    II.  WNetAddConnection2() refuses to overwrite an existing (unusable) connection created in I), but shows prompt repeatedly
    III. WNetAddConnection2() won't bring up the prompt if *wrong* credentials had been entered just recently, even with CONNECT_INTERACTIVE specified! => 2-step proccess
    */

    auto connect = [&](NETRESOURCE& trgRes) //blocks heavily if network is not reachable!!!
    {
        //1. first try to connect without user interaction - blocks!
        DWORD rv = ::WNetAddConnection2(&trgRes, //__in  LPNETRESOURCE lpNetResource,
                                        nullptr, //__in  LPCTSTR lpPassword,
                                        nullptr, //__in  LPCTSTR lpUsername,
                                        0);      //__in  DWORD dwFlags
        //53L	ERROR_BAD_NETPATH		The network path was not found.
        //67L   ERROR_BAD_NET_NAME
        //86L	ERROR_INVALID_PASSWORD
        //1219L	ERROR_SESSION_CREDENTIAL_CONFLICT	Multiple connections to a server or shared resource by the same user, using more than one user name, are not allowed. Disconnect all previous connections to the server or shared resource and try again.
        //1326L	ERROR_LOGON_FAILURE	Logon failure: unknown user name or bad password.
        //1236L ERROR_CONNECTION_ABORTED
        if (somethingExists(trgRes.lpRemoteName)) //blocks!
            return; //success: connection usable! -> don't care about "rv"

        if (rv == ERROR_BAD_NETPATH || //like ERROR_PATH_NOT_FOUND
                rv == ERROR_BAD_NET_NAME|| //like ERROR_FILE_NOT_FOUND
                rv == ERROR_CONNECTION_ABORTED) //failed to connect to a network that existed not too long ago; will later return ERROR_BAD_NETPATH
            return; //no need to show a prompt for an unreachable network device

        //2. if first attempt failed, we need to *force* prompt by using CONNECT_PROMPT
        if (allowUserInteraction)
        {
            //avoid problem II.)
            DWORD rv2= ::WNetCancelConnection2(trgRes.lpRemoteName, //_In_  LPCTSTR lpName,
                                               0,					  //_In_  DWORD dwFlags,
                                               true);				  //_In_  BOOL fForce
            //2250L ERROR_NOT_CONNECTED

            //enforce login prompt
            DWORD rv3 = ::WNetAddConnection2(&trgRes, // __in  LPNETRESOURCE lpNetResource,
                                             nullptr, // __in  LPCTSTR lpPassword,
                                             nullptr, // __in  LPCTSTR lpUsername,
                                             CONNECT_INTERACTIVE | CONNECT_PROMPT); //__in  DWORD dwFlags
            (void)rv2;
            (void)rv3;
        }
    };


    Zstring dirpath = removeLongPathPrefix(dirpathOrig);
    trim(dirpath, true, false);

    //1. locally mapped network share
    if (dirpath.size() >= 2 && iswalpha(dirpath[0]) && dirpath[1] == L':')
    {
        Zstring driveLetter(dirpath.c_str(), 2); //e.g.: "Q:"
        {
            DWORD bufferSize = 10000;
            std::vector<wchar_t> remoteNameBuffer(bufferSize);

            //map local -> remote

            //note: following function call does NOT block!
            DWORD rv = ::WNetGetConnection(driveLetter.c_str(),  //__in     LPCTSTR lpLocalName in the form "<driveletter>:"
                                           &remoteNameBuffer[0], //__out    LPTSTR lpRemoteName,
                                           &bufferSize);         //__inout  LPDWORD lpnLength
            if (rv == ERROR_CONNECTION_UNAVAIL) //remoteNameBuffer will be filled nevertheless!
            {
                //ERROR_CONNECTION_UNAVAIL: network mapping is existing, but not connected

                Zstring networkShare = &remoteNameBuffer[0];
                if (!networkShare.empty())
                {
                    NETRESOURCE trgRes = {};
                    trgRes.dwType = RESOURCETYPE_DISK;
                    trgRes.lpLocalName  = const_cast<LPWSTR>(driveLetter.c_str());  //lpNetResource is marked "__in", seems WNetAddConnection2 is not const correct!
                    trgRes.lpRemoteName = const_cast<LPWSTR>(networkShare.c_str()); //

                    connect(trgRes); //blocks!
                }
            }
        }
    }
    //2. deviceless network connection
    else if (startsWith(dirpath, L"\\\\")) //UNC path
    {
        const Zstring networkShare = [&]() -> Zstring //extract prefix "\\server\share"
        {
            size_t pos = dirpath.find('\\', 2);
            if (pos == Zstring::npos)
                return Zstring();
            pos = dirpath.find('\\', pos + 1);
            return pos == Zstring::npos ? dirpath : Zstring(dirpath.c_str(), pos);
        }();

        if (!networkShare.empty())
        {
            //::WNetGetResourceInformation seems to fail with ERROR_BAD_NET_NAME even for existing unconnected network shares!
            // => unconditionally try to connect to network share, seems we cannot reliably detect connection status otherwise

            NETRESOURCE trgRes  = {};
            trgRes.dwType       = RESOURCETYPE_DISK;
            trgRes.lpRemoteName = const_cast<LPWSTR>(networkShare.c_str()); //trgRes is "__in"

            connect(trgRes); //blocks!
        }
    }
}