michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: // This source is mostly a bunch of Windows API calls. It is only compiled for michael@0: // Windows builds. michael@0: // michael@0: // Registry entries for Autodial mappings are located here: michael@0: // HKEY_CURRENT_USER\Software\Microsoft\RAS Autodial\Addresses michael@0: michael@0: #include michael@0: #include michael@0: #include "nsString.h" michael@0: #include "nsAutodialWin.h" michael@0: #include "prlog.h" michael@0: #include "nsWindowsHelpers.h" michael@0: michael@0: #define AUTODIAL_DEFAULT AUTODIAL_NEVER michael@0: michael@0: // michael@0: // Log module for autodial logging... michael@0: // michael@0: // To enable logging (see prlog.h for full details): michael@0: // michael@0: // set NSPR_LOG_MODULES=Autodial:5 michael@0: // set NSPR_LOG_FILE=nspr.log michael@0: // michael@0: // this enables PR_LOG_DEBUG level information and places all output in michael@0: // the file nspr.log michael@0: // michael@0: michael@0: #ifdef PR_LOGGING michael@0: static PRLogModuleInfo* gLog = nullptr; michael@0: #endif michael@0: michael@0: #undef LOGD michael@0: #undef LOGE michael@0: #define LOGD(args) PR_LOG(gLog, PR_LOG_DEBUG, args) michael@0: #define LOGE(args) PR_LOG(gLog, PR_LOG_ERROR, args) michael@0: michael@0: // Don't try to dial again within a few seconds of when user pressed cancel. michael@0: #define NO_RETRY_PERIOD_SEC 5 michael@0: PRIntervalTime nsAutodial::mDontRetryUntil = 0; michael@0: michael@0: // ctor. michael@0: nsAutodial::nsAutodial() michael@0: : mAutodialBehavior(AUTODIAL_DEFAULT), michael@0: mAutodialServiceDialingLocation(-1), michael@0: mNumRASConnectionEntries(0) michael@0: { michael@0: // Initializations that can be made again since RAS OS settings can michael@0: // change. michael@0: Init(); michael@0: } michael@0: michael@0: // dtor michael@0: nsAutodial::~nsAutodial() michael@0: { michael@0: } michael@0: michael@0: michael@0: // Get settings from the OS. These are settings that might change during michael@0: // the OS session. Call Init() again to pick up those changes if required. michael@0: // Returns NS_ERROR_FAILURE if error or NS_OK if success. michael@0: nsresult nsAutodial::Init() michael@0: { michael@0: #ifdef PR_LOGGING michael@0: if (!gLog) michael@0: gLog = PR_NewLogModule("Autodial"); michael@0: #endif michael@0: michael@0: mDefaultEntryName[0] = '\0'; michael@0: mNumRASConnectionEntries = 0; michael@0: mAutodialBehavior = QueryAutodialBehavior(); michael@0: michael@0: // No need to continue in this case. michael@0: if (mAutodialBehavior == AUTODIAL_NEVER) michael@0: { michael@0: return NS_OK; michael@0: } michael@0: michael@0: michael@0: // Get the number of dialup entries in the phonebook. michael@0: mNumRASConnectionEntries = NumRASEntries(); michael@0: michael@0: // Get the name of the default entry. michael@0: nsresult result = GetDefaultEntryName(mDefaultEntryName, michael@0: sizeof(mDefaultEntryName)); michael@0: michael@0: return result; michael@0: } michael@0: michael@0: michael@0: // Should we attempt to dial on a network error? Yes if the Internet Options michael@0: // configured as such. Yes if the RAS autodial service is running (we'll try to michael@0: // force it to dial in that case by adding the network address to its db.) michael@0: bool nsAutodial::ShouldDialOnNetworkError() michael@0: { michael@0: // Don't try to dial again within a few seconds of when user pressed cancel. michael@0: if (mDontRetryUntil) michael@0: { michael@0: PRIntervalTime intervalNow = PR_IntervalNow(); michael@0: if (intervalNow < mDontRetryUntil) michael@0: { michael@0: LOGD(("Autodial: Not dialing: too soon.")); michael@0: return false; michael@0: } michael@0: } michael@0: michael@0: michael@0: return ((mAutodialBehavior == AUTODIAL_ALWAYS) michael@0: || (mAutodialBehavior == AUTODIAL_ON_NETWORKERROR) michael@0: || (mAutodialBehavior == AUTODIAL_USE_SERVICE)); michael@0: } michael@0: michael@0: michael@0: // The autodial info is set in Control Panel | Internet Options | Connections. michael@0: // The values are stored in the registry. This function gets those values from michael@0: // the registry and determines if we should never dial, always dial, or dial michael@0: // when there is no network found. michael@0: int nsAutodial::QueryAutodialBehavior() michael@0: { michael@0: if (IsAutodialServiceRunning()) michael@0: { michael@0: // Is Autodial service enabled for the current login session? michael@0: DWORD disabled = 0; michael@0: DWORD size = sizeof(DWORD); michael@0: if (RasGetAutodialParamW(RASADP_LoginSessionDisable, &disabled, &size) == ERROR_SUCCESS) michael@0: { michael@0: if (!disabled) michael@0: { michael@0: // If current dialing location has autodial on, we'll let the service dial. michael@0: mAutodialServiceDialingLocation = GetCurrentLocation(); michael@0: if (IsAutodialServiceEnabled(mAutodialServiceDialingLocation)) michael@0: { michael@0: return AUTODIAL_USE_SERVICE; michael@0: } michael@0: } michael@0: } michael@0: } michael@0: michael@0: // If we get to here, then the service is not going to dial on error, so we michael@0: // can dial ourselves if the control panel settings are set up that way. michael@0: HKEY hKey = 0; michael@0: LONG result = ::RegOpenKeyExW( michael@0: HKEY_CURRENT_USER, michael@0: L"Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings", michael@0: 0, michael@0: KEY_READ, michael@0: &hKey); michael@0: michael@0: if (result != ERROR_SUCCESS) michael@0: { michael@0: LOGE(("Autodial: Error opening reg key Internet Settings")); michael@0: return AUTODIAL_NEVER; michael@0: } michael@0: michael@0: DWORD entryType = 0; michael@0: DWORD autodial = 0; michael@0: DWORD onDemand = 0; michael@0: DWORD paramSize = sizeof(DWORD); michael@0: michael@0: result = ::RegQueryValueExW(hKey, L"EnableAutodial", nullptr, &entryType, (LPBYTE)&autodial, ¶mSize); michael@0: if (result != ERROR_SUCCESS) michael@0: { michael@0: ::RegCloseKey(hKey); michael@0: LOGE(("Autodial: Error reading reg value EnableAutodial.")); michael@0: return AUTODIAL_NEVER; michael@0: } michael@0: michael@0: result = ::RegQueryValueExW(hKey, L"NoNetAutodial", nullptr, &entryType, (LPBYTE)&onDemand, ¶mSize); michael@0: if (result != ERROR_SUCCESS) michael@0: { michael@0: ::RegCloseKey(hKey); michael@0: LOGE(("Autodial: Error reading reg value NoNetAutodial.")); michael@0: return AUTODIAL_NEVER; michael@0: } michael@0: michael@0: ::RegCloseKey(hKey); michael@0: michael@0: if (!autodial) michael@0: { michael@0: return AUTODIAL_NEVER; michael@0: } michael@0: else michael@0: { michael@0: if (onDemand) michael@0: { michael@0: return AUTODIAL_ON_NETWORKERROR; michael@0: } michael@0: else michael@0: { michael@0: return AUTODIAL_ALWAYS; michael@0: } michael@0: } michael@0: } michael@0: michael@0: // If the RAS autodial service is running, use it. Otherwise, dial michael@0: // the default RAS connection. There are two possible RAS dialogs: michael@0: // one that dials a single entry, and one that lets the user choose which michael@0: // to dial. If there is only one connection entry in the phone book, or michael@0: // there are multiple entries but one is defined as the default, we'll use michael@0: // the single entry dial dialog. If there are multiple connection entries, michael@0: // and none is specified as default, we'll bring up the diallog which lets michael@0: // the user select the connection entry to use. michael@0: // michael@0: // Return values: michael@0: // NS_OK: dialing was successful and caller should retry michael@0: // all other values indicate that the caller should not retry michael@0: nsresult nsAutodial::DialDefault(const char16_t* hostName) michael@0: { michael@0: mDontRetryUntil = 0; michael@0: michael@0: if (mAutodialBehavior == AUTODIAL_NEVER) michael@0: { michael@0: return NS_ERROR_FAILURE; // don't retry the network error michael@0: } michael@0: michael@0: // If already a RAS connection, bail. michael@0: if (IsRASConnected()) michael@0: { michael@0: LOGD(("Autodial: Not dialing: active connection.")); michael@0: return NS_ERROR_FAILURE; // don't retry michael@0: } michael@0: michael@0: // If no dialup connections configured, bail. michael@0: if (mNumRASConnectionEntries <= 0) michael@0: { michael@0: LOGD(("Autodial: Not dialing: no entries.")); michael@0: return NS_ERROR_FAILURE; // don't retry michael@0: } michael@0: michael@0: michael@0: // If autodial service is running, let it dial. In order for it to dial more michael@0: // reliably, we have to add the target address to the autodial database. michael@0: // This is the only way the autodial service dial if there is a network michael@0: // adapter installed. But even then it might not dial. We have to assume that michael@0: // it will though, or we could end up with two attempts to dial on the same michael@0: // network error if the user cancels the first one: one from the service and michael@0: // one from us. michael@0: // See http://msdn.microsoft.com/library/default.asp?url=/library/en-us/rras/ras4over_3dwl.asp michael@0: if (mAutodialBehavior == AUTODIAL_USE_SERVICE) michael@0: { michael@0: AddAddressToAutodialDirectory(hostName); michael@0: return NS_ERROR_FAILURE; // don't retry michael@0: } michael@0: michael@0: // Do the dialing ourselves. michael@0: else michael@0: { michael@0: // If a default dial entry is configured, use it. michael@0: if (mDefaultEntryName[0] != '\0') michael@0: { michael@0: LOGD(("Autodial: Dialing default: %s.",mDefaultEntryName)); michael@0: michael@0: RASDIALDLG rasDialDlg; michael@0: memset(&rasDialDlg, 0, sizeof(rasDialDlg)); michael@0: rasDialDlg.dwSize = sizeof(rasDialDlg); michael@0: michael@0: BOOL dialed = michael@0: RasDialDlgW(nullptr, mDefaultEntryName, nullptr, &rasDialDlg); michael@0: michael@0: if (!dialed) michael@0: { michael@0: if (rasDialDlg.dwError != 0) michael@0: { michael@0: LOGE(("Autodial ::RasDialDlg failed: Error: %d.", michael@0: rasDialDlg.dwError)); michael@0: } michael@0: else michael@0: { michael@0: mDontRetryUntil = PR_IntervalNow() + PR_SecondsToInterval(NO_RETRY_PERIOD_SEC); michael@0: LOGD(("Autodial: User cancelled dial.")); michael@0: } michael@0: return NS_ERROR_FAILURE; // don't retry michael@0: } michael@0: michael@0: LOGD(("Autodial: RAS dialup connection successful.")); michael@0: } michael@0: michael@0: // If no default connection specified, open the dialup dialog that lets michael@0: // the user specifiy which connection to dial. michael@0: else michael@0: { michael@0: LOGD(("Autodial: Prompting for phonebook entry.")); michael@0: michael@0: RASPBDLG rasPBDlg; michael@0: memset(&rasPBDlg, 0, sizeof(rasPBDlg)); michael@0: rasPBDlg.dwSize = sizeof(rasPBDlg); michael@0: michael@0: BOOL dialed = RasPhonebookDlgW(nullptr, nullptr, &rasPBDlg); michael@0: michael@0: if (!dialed) michael@0: { michael@0: if (rasPBDlg.dwError != 0) michael@0: { michael@0: LOGE(("Autodial: ::RasPhonebookDlg failed: Error = %d.", michael@0: rasPBDlg.dwError)); michael@0: } michael@0: else michael@0: { michael@0: mDontRetryUntil = PR_IntervalNow() + PR_SecondsToInterval(NO_RETRY_PERIOD_SEC); michael@0: LOGD(("Autodial: User cancelled dial.")); michael@0: } michael@0: michael@0: return NS_ERROR_FAILURE; // don't retry michael@0: } michael@0: michael@0: LOGD(("Autodial: RAS dialup connection successful.")); michael@0: } michael@0: } michael@0: michael@0: // Retry because we just established a dialup connection. michael@0: return NS_OK; michael@0: } michael@0: michael@0: michael@0: // Check to see if RAS is already connected. michael@0: bool nsAutodial::IsRASConnected() michael@0: { michael@0: DWORD connections; michael@0: RASCONN rasConn; michael@0: rasConn.dwSize = sizeof(rasConn); michael@0: DWORD structSize = sizeof(rasConn); michael@0: michael@0: DWORD result = RasEnumConnectionsW(&rasConn, &structSize, &connections); michael@0: michael@0: // ERROR_BUFFER_TOO_SMALL is OK because we only need one struct. michael@0: if (result == ERROR_SUCCESS || result == ERROR_BUFFER_TOO_SMALL) michael@0: { michael@0: return (connections > 0); michael@0: } michael@0: michael@0: LOGE(("Autodial: ::RasEnumConnections failed: Error = %d", result)); michael@0: return false; michael@0: } michael@0: michael@0: // Get the first RAS dial entry name from the phonebook. michael@0: nsresult nsAutodial::GetFirstEntryName(wchar_t* entryName, int bufferSize) michael@0: { michael@0: RASENTRYNAMEW rasEntryName; michael@0: rasEntryName.dwSize = sizeof(rasEntryName); michael@0: DWORD cb = sizeof(rasEntryName); michael@0: DWORD cEntries = 0; michael@0: michael@0: DWORD result = michael@0: RasEnumEntriesW(nullptr, nullptr, &rasEntryName, &cb, &cEntries); michael@0: michael@0: // ERROR_BUFFER_TOO_SMALL is OK because we only need one struct. michael@0: if (result == ERROR_SUCCESS || result == ERROR_BUFFER_TOO_SMALL) michael@0: { michael@0: wcsncpy(entryName, rasEntryName.szEntryName, michael@0: bufferSize / sizeof(*entryName)); michael@0: return NS_OK; michael@0: } michael@0: michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: // Get the number of RAS dial entries in the phonebook. michael@0: int nsAutodial::NumRASEntries() michael@0: { michael@0: RASENTRYNAMEW rasEntryName; michael@0: rasEntryName.dwSize = sizeof(rasEntryName); michael@0: DWORD cb = sizeof(rasEntryName); michael@0: DWORD cEntries = 0; michael@0: michael@0: michael@0: DWORD result = michael@0: RasEnumEntriesW(nullptr, nullptr, &rasEntryName, &cb, &cEntries); michael@0: michael@0: // ERROR_BUFFER_TOO_SMALL is OK because we only need one struct. michael@0: if (result == ERROR_SUCCESS || result == ERROR_BUFFER_TOO_SMALL) michael@0: { michael@0: return (int)cEntries; michael@0: } michael@0: michael@0: return 0; michael@0: } michael@0: michael@0: // Get the name of the default dial entry. michael@0: nsresult nsAutodial::GetDefaultEntryName(wchar_t* entryName, int bufferSize) michael@0: { michael@0: // No RAS dialup entries. michael@0: if (mNumRASConnectionEntries <= 0) michael@0: { michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: // Single RAS dialup entry. Use it as the default to autodial. michael@0: if (mNumRASConnectionEntries == 1) michael@0: { michael@0: return GetFirstEntryName(entryName, bufferSize); michael@0: } michael@0: michael@0: // Multiple RAS dialup entries. If a default configured in the registry, michael@0: // use it. michael@0: // michael@0: // For Windows XP: HKCU/Software/Microsoft/RAS Autodial/Default/DefaultInternet. michael@0: // or HKLM/Software/Microsoft/RAS Autodial/Default/DefaultInternet. michael@0: michael@0: const wchar_t* key = L"Software\\Microsoft\\RAS Autodial\\Default"; michael@0: const wchar_t* val = L"DefaultInternet"; michael@0: michael@0: HKEY hKey = 0; michael@0: LONG result = 0; michael@0: michael@0: michael@0: // Try HKCU first. michael@0: result = ::RegOpenKeyExW( michael@0: HKEY_CURRENT_USER, michael@0: key, michael@0: 0, michael@0: KEY_READ, michael@0: &hKey); michael@0: michael@0: if (result != ERROR_SUCCESS) michael@0: { michael@0: // If not present, try HKLM. michael@0: result = ::RegOpenKeyExW( michael@0: HKEY_LOCAL_MACHINE, michael@0: key, michael@0: 0, michael@0: KEY_READ, michael@0: &hKey); michael@0: michael@0: if (result != ERROR_SUCCESS) michael@0: { michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: } michael@0: michael@0: michael@0: DWORD entryType = 0; michael@0: DWORD buffSize = bufferSize; michael@0: michael@0: result = ::RegQueryValueExW(hKey, michael@0: val, michael@0: nullptr, michael@0: &entryType, michael@0: (LPBYTE)entryName, michael@0: &buffSize); michael@0: michael@0: ::RegCloseKey(hKey); michael@0: michael@0: michael@0: if (result != ERROR_SUCCESS) michael@0: { michael@0: // Results in a prompt for which to use at dial time. michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: michael@0: // Determine if the autodial service is running on this PC. michael@0: bool nsAutodial::IsAutodialServiceRunning() michael@0: { michael@0: nsAutoServiceHandle hSCManager(OpenSCManager(nullptr, michael@0: SERVICES_ACTIVE_DATABASE, michael@0: SERVICE_QUERY_STATUS)); michael@0: michael@0: if (hSCManager == nullptr) michael@0: { michael@0: LOGE(("Autodial: failed to open service control manager. Error %d.", michael@0: ::GetLastError())); michael@0: michael@0: return false; michael@0: } michael@0: michael@0: nsAutoServiceHandle hService(OpenServiceW(hSCManager, michael@0: L"RasAuto", michael@0: SERVICE_QUERY_STATUS)); michael@0: michael@0: if (hSCManager == nullptr) michael@0: { michael@0: LOGE(("Autodial: failed to open RasAuto service.")); michael@0: return false; michael@0: } michael@0: michael@0: SERVICE_STATUS status; michael@0: if (!QueryServiceStatus(hService, &status)) michael@0: { michael@0: LOGE(("Autodial: ::QueryServiceStatus() failed. Error: %d", michael@0: ::GetLastError())); michael@0: michael@0: return false; michael@0: } michael@0: michael@0: return (status.dwCurrentState == SERVICE_RUNNING); michael@0: } michael@0: michael@0: // Add the specified address to the autodial directory. michael@0: bool nsAutodial::AddAddressToAutodialDirectory(char16ptr_t hostName) michael@0: { michael@0: // First see if there is already a db entry for this address. michael@0: RASAUTODIALENTRYW autodialEntry; michael@0: autodialEntry.dwSize = sizeof(autodialEntry); michael@0: DWORD size = sizeof(autodialEntry); michael@0: DWORD entries = 0; michael@0: michael@0: DWORD result = RasGetAutodialAddressW(hostName, michael@0: nullptr, michael@0: &autodialEntry, michael@0: &size, michael@0: &entries); michael@0: michael@0: // If there is already at least 1 entry in db for this address, return. michael@0: if (result != ERROR_FILE_NOT_FOUND) michael@0: { michael@0: LOGD(("Autodial: Address %s already in autodial db.", hostName)); michael@0: return false; michael@0: } michael@0: michael@0: autodialEntry.dwSize = sizeof(autodialEntry); michael@0: autodialEntry.dwFlags = 0; michael@0: autodialEntry.dwDialingLocation = mAutodialServiceDialingLocation; michael@0: GetDefaultEntryName(autodialEntry.szEntry, sizeof(autodialEntry.szEntry)); michael@0: michael@0: result = RasSetAutodialAddressW(hostName, michael@0: 0, michael@0: &autodialEntry, michael@0: sizeof(autodialEntry), michael@0: 1); michael@0: michael@0: if (result != ERROR_SUCCESS) michael@0: { michael@0: LOGE(("Autodial ::RasSetAutodialAddress failed result %d.", result)); michael@0: return false; michael@0: } michael@0: michael@0: LOGD(("Autodial: Added address %s to RAS autodial db for entry %s.", michael@0: hostName, NS_ConvertUTF16toUTF8(autodialEntry.szEntry).get())); michael@0: michael@0: return true; michael@0: } michael@0: michael@0: // Get the current TAPI dialing location. michael@0: int nsAutodial::GetCurrentLocation() michael@0: { michael@0: HKEY hKey = 0; michael@0: LONG result = ::RegOpenKeyExW( michael@0: HKEY_LOCAL_MACHINE, michael@0: L"Software\\Microsoft\\Windows\\CurrentVersion\\Telephony\\Locations", michael@0: 0, michael@0: KEY_READ, michael@0: &hKey); michael@0: michael@0: if (result != ERROR_SUCCESS) michael@0: { michael@0: LOGE(("Autodial: Error opening reg key ...CurrentVersion\\Telephony\\Locations")); michael@0: return -1; michael@0: } michael@0: michael@0: DWORD entryType = 0; michael@0: DWORD location = 0; michael@0: DWORD paramSize = sizeof(DWORD); michael@0: michael@0: result = ::RegQueryValueExW(hKey, L"CurrentID", nullptr, &entryType, (LPBYTE)&location, ¶mSize); michael@0: if (result != ERROR_SUCCESS) michael@0: { michael@0: ::RegCloseKey(hKey); michael@0: LOGE(("Autodial: Error reading reg value CurrentID.")); michael@0: return -1; michael@0: } michael@0: michael@0: ::RegCloseKey(hKey); michael@0: return location; michael@0: michael@0: } michael@0: michael@0: // Check to see if autodial for the specified location is enabled. michael@0: bool nsAutodial::IsAutodialServiceEnabled(int location) michael@0: { michael@0: if (location < 0) michael@0: return false; michael@0: michael@0: BOOL enabled; michael@0: if (RasGetAutodialEnableW(location, &enabled) != ERROR_SUCCESS) michael@0: { michael@0: LOGE(("Autodial: Error calling RasGetAutodialEnable()")); michael@0: return false; michael@0: } michael@0: michael@0: return enabled; michael@0: }