summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorAndrej Mihajlov <and@mullvad.net>2018-06-20 14:43:51 +0200
committerAndrej Mihajlov <and@mullvad.net>2018-07-03 13:37:54 +0200
commit67e82627564f8e4a2d8da4bcf5f0fd00867876bc (patch)
tree814494d59045d8b3f02cb310dc7728aa818be509
parentbe096ee87bb1256b67c3b24b61be77d523aff9cc (diff)
downloadmullvadvpn-67e82627564f8e4a2d8da4bcf5f0fd00867876bc.tar.xz
mullvadvpn-67e82627564f8e4a2d8da4bcf5f0fd00867876bc.zip
Move RPC file related stuff into RpcAddressFile
-rw-r--r--app/app.android.js2
-rw-r--r--app/app.js19
-rw-r--r--app/lib/backend.js23
-rw-r--r--app/lib/rpc-address-file.js120
-rw-r--r--app/lib/rpc-file-security.js36
-rw-r--r--app/main.js121
6 files changed, 166 insertions, 155 deletions
diff --git a/app/app.android.js b/app/app.android.js
index d362f25283..dbbb0c1c26 100644
--- a/app/app.android.js
+++ b/app/app.android.js
@@ -22,7 +22,7 @@ const store = configureStore(initialState, memoryHistory);
//////////////////////////////////////////////////////////////////////////
const backend = new Backend(store);
-DeviceEventEmitter.addListener('com.mullvad.backend-info', async (_event, args) => {
+DeviceEventEmitter.addListener('com.mullvad.daemon-connection', async (_event, args) => {
backend.setCredentials(args.credentials);
backend.sync();
try {
diff --git a/app/app.js b/app/app.js
index dadc2177c2..70d1052560 100644
--- a/app/app.js
+++ b/app/app.js
@@ -10,7 +10,6 @@ import { log } from './lib/platform';
import makeRoutes from './routes';
import configureStore from './redux/store';
import { Backend, NoAccountError } from './lib/backend';
-
import { setShutdownHandler } from './shutdown-handler';
import type { ConnectionState } from './redux/connection/reducers';
@@ -20,11 +19,8 @@ const initialState = null;
const memoryHistory = createMemoryHistory();
const store = configureStore(initialState, memoryHistory);
-//////////////////////////////////////////////////////////////////////////
-// Backend
-//////////////////////////////////////////////////////////////////////////
const backend = new Backend(store);
-ipcRenderer.on('backend-info', async (_event, args) => {
+ipcRenderer.on('daemon-connection', async (_event, args) => {
backend.setCredentials(args.credentials);
backend.sync();
try {
@@ -40,6 +36,8 @@ ipcRenderer.on('backend-info', async (_event, args) => {
}
});
+ipcRenderer.send('daemon-connection');
+
setShutdownHandler(async () => {
log.info('Executing a shutdown handler');
@@ -51,13 +49,6 @@ setShutdownHandler(async () => {
}
});
-//////////////////////////////////////////////////////////////////////////
-//////////////////////////////////////////////////////////////////////////
-
-//////////////////////////////////////////////////////////////////////////
-// Tray icon
-//////////////////////////////////////////////////////////////////////////
-
/**
* Get tray icon type based on connection state
*/
@@ -86,14 +77,10 @@ store.subscribe(updateTrayIcon);
// force update tray
updateTrayIcon();
-//////////////////////////////////////////////////////////////////////////
-//////////////////////////////////////////////////////////////////////////
// disable smart pinch.
webFrame.setVisualZoomLevelLimits(1, 1);
-ipcRenderer.send('on-browser-window-ready');
-
export default class App extends Component {
render() {
return (
diff --git a/app/lib/backend.js b/app/lib/backend.js
index b24b495449..39521d8edb 100644
--- a/app/lib/backend.js
+++ b/app/lib/backend.js
@@ -8,6 +8,7 @@ import connectionActions from '../redux/connection/actions';
import settingsActions from '../redux/settings/actions';
import { push } from 'react-router-redux';
+import type { RpcCredentials } from './rpc-address-file';
import type { ReduxStore } from '../redux/store';
import type { AccountToken, BackendState, RelaySettingsUpdate } from './ipc-facade';
import type { ConnectionState } from '../redux/connection/reducers';
@@ -74,32 +75,16 @@ export class UnknownError extends Error {
}
}
-export type IpcCredentials = {
- connectionString: string,
- sharedSecret: string,
-};
-export function parseIpcCredentials(data: string): ?IpcCredentials {
- const [connectionString, sharedSecret] = data.split('\n', 2);
- if (connectionString && sharedSecret !== undefined) {
- return {
- connectionString,
- sharedSecret,
- };
- } else {
- return null;
- }
-}
-
/**
* Backend implementation
*/
export class Backend {
_ipc: IpcFacade;
- _credentials: ?IpcCredentials;
+ _credentials: ?RpcCredentials;
_authenticationPromise: ?Promise<void>;
_store: ReduxStore;
- constructor(store: ReduxStore, credentials?: IpcCredentials, ipc: ?IpcFacade) {
+ constructor(store: ReduxStore, credentials?: RpcCredentials, ipc: ?IpcFacade) {
this._store = store;
this._credentials = credentials;
@@ -116,7 +101,7 @@ export class Backend {
}
}
- setCredentials(credentials: IpcCredentials) {
+ setCredentials(credentials: RpcCredentials) {
log.debug('Got connection info to backend', credentials.connectionString);
this._credentials = credentials;
diff --git a/app/lib/rpc-address-file.js b/app/lib/rpc-address-file.js
new file mode 100644
index 0000000000..5185ddc749
--- /dev/null
+++ b/app/lib/rpc-address-file.js
@@ -0,0 +1,120 @@
+// @flow
+
+import fs from 'fs';
+import path from 'path';
+import { promisify } from 'util';
+import { getSystemTemporaryDirectory } from './tempdir';
+
+const fsReadFileAsync = promisify(fs.readFile);
+
+const POLL_INTERVAL = 200;
+
+const appDirectoryName = 'Mullvad VPN';
+
+export type RpcCredentials = {
+ connectionString: string,
+ sharedSecret: string,
+};
+
+export class RpcAddressFile {
+ _filePath: string;
+ _pollIntervalId: ?IntervalID;
+ _pollPromise: ?Promise<void>;
+
+ constructor() {
+ this._filePath = getRpcAddressFilePath();
+ }
+
+ get filePath(): string {
+ return this._filePath;
+ }
+
+ poll(): Promise<void> {
+ let promise = this._pollPromise;
+
+ if (!promise) {
+ promise = new Promise((resolve, _reject) => {
+ const timer = setInterval(() => {
+ fs.exists(this._filePath, (exists) => {
+ if (exists) {
+ clearInterval(timer);
+ resolve();
+
+ this._pollPromise = null;
+ }
+ });
+ }, POLL_INTERVAL);
+ });
+
+ this._pollPromise = promise;
+ }
+
+ return promise;
+ }
+
+ isTrusted() {
+ const filePath = this._filePath;
+ switch (process.platform) {
+ case 'win32':
+ return isOwnedByLocalSystem(filePath);
+ case 'darwin':
+ case 'linux':
+ return isOwnedAndOnlyWritableByRoot(filePath);
+ default:
+ throw new Error(`Unknown platform: ${process.platform}`);
+ }
+ }
+
+ async waitUntilExists(): Promise<RpcCredentials> {
+ const data = await fsReadFileAsync(this._filePath, 'utf8');
+ const [connectionString, sharedSecret] = data.split('\n', 2);
+
+ if (connectionString && sharedSecret !== undefined) {
+ return {
+ connectionString,
+ sharedSecret,
+ };
+ } else {
+ throw new Error('Cannot parse the RPC address file');
+ }
+ }
+}
+
+function getRpcAddressFilePath() {
+ const rpcAddressFileName = '.mullvad_rpc_address';
+
+ switch (process.platform) {
+ case 'win32': {
+ // Windows: %ALLUSERSPROFILE%\{appname}
+ const programDataDirectory = process.env.ALLUSERSPROFILE;
+ if (programDataDirectory) {
+ const appDataDirectory = path.join(programDataDirectory, appDirectoryName);
+ return path.join(appDataDirectory, rpcAddressFileName);
+ } else {
+ throw new Error('Missing %ALLUSERSPROFILE% environment variable');
+ }
+ }
+ default:
+ return path.join(getSystemTemporaryDirectory(), rpcAddressFileName);
+ }
+}
+
+function isOwnedAndOnlyWritableByRoot(path: string): boolean {
+ const stat = fs.statSync(path);
+ const isOwnedByRoot = stat.uid === 0;
+ const isOnlyWritableByOwner = (stat.mode & parseInt('022', 8)) === 0;
+
+ return isOwnedByRoot && isOnlyWritableByOwner;
+}
+
+function isOwnedByLocalSystem(path: string): boolean {
+ // $FlowFixMe: this module is only available on Windows
+ const winsec = require('windows-security');
+ const ownerSid = winsec.getFileOwnerSid(path, null);
+ const isWellKnownSid = winsec.isWellKnownSid(
+ ownerSid,
+ winsec.WellKnownSid.BuiltinAdministratorsSid,
+ );
+
+ return isWellKnownSid;
+}
diff --git a/app/lib/rpc-file-security.js b/app/lib/rpc-file-security.js
deleted file mode 100644
index fa88111c02..0000000000
--- a/app/lib/rpc-file-security.js
+++ /dev/null
@@ -1,36 +0,0 @@
-// @flow
-
-import fs from 'fs';
-
-export function canTrustRpcAddressFile(path: string): boolean {
- const platform = process.platform;
- switch (platform) {
- case 'win32':
- return isOwnedByLocalSystem(path);
- case 'darwin':
- case 'linux':
- return isOwnedAndOnlyWritableByRoot(path);
- default:
- throw new Error(`Unknown platform: ${platform}`);
- }
-}
-
-function isOwnedAndOnlyWritableByRoot(path: string): boolean {
- const stat = fs.statSync(path);
- const isOwnedByRoot = stat.uid === 0;
- const isOnlyWritableByOwner = (stat.mode & parseInt('022', 8)) === 0;
-
- return isOwnedByRoot && isOnlyWritableByOwner;
-}
-
-function isOwnedByLocalSystem(path: string): boolean {
- // $FlowFixMe: this module is only available on Windows
- const winsec = require('windows-security');
- const ownerSid = winsec.getFileOwnerSid(path, null);
- const isWellKnownSid = winsec.isWellKnownSid(
- ownerSid,
- winsec.WellKnownSid.BuiltinAdministratorsSid,
- );
-
- return isWellKnownSid;
-}
diff --git a/app/main.js b/app/main.js
index 64c341dc60..3a10f6808c 100644
--- a/app/main.js
+++ b/app/main.js
@@ -1,6 +1,5 @@
// @flow
import path from 'path';
-import fs from 'fs';
import mkdirp from 'mkdirp';
import { log } from './lib/platform';
import { app, BrowserWindow, ipcMain, Tray, Menu, nativeImage } from 'electron';
@@ -8,8 +7,7 @@ import TrayIconController from './tray-icon-controller';
import WindowController from './window-controller';
import { version } from '../package.json';
import { resolveBin } from './lib/proc';
-import { getSystemTemporaryDirectory } from './lib/tempdir';
-import { canTrustRpcAddressFile } from './lib/rpc-file-security';
+import { RpcAddressFile } from './lib/rpc-address-file';
import { execFile } from 'child_process';
import uuid from 'uuid';
@@ -162,8 +160,43 @@ const ApplicationMain = {
},
_registerIpcListeners() {
- ipcMain.on('on-browser-window-ready', () => {
- this._pollConnectionInfoFile();
+ ipcMain.on('daemon-connection', async (event) => {
+ const addressFile = new RpcAddressFile();
+
+ log.debug(`Reading the RPC connection info from "${addressFile.filePath}"`);
+
+ try {
+ await addressFile.waitUntilExists();
+ } catch (error) {
+ log.error(`Cannot finish polling the RPC address file: ${error.message}`);
+ return;
+ }
+
+ try {
+ if (!addressFile.isTrusted()) {
+ log.error(`Cannot verify the credibility of RPC address file`);
+ return;
+ }
+ } catch (error) {
+ log.error(`An error occurred during the credibility check: ${error.message}`);
+ return;
+ }
+
+ // There is a race condition here where the owner and permissions of
+ // the file can change in the time between we validate the owner and
+ // permissions and read the contents of the file. We deem the chance
+ // of that to be small enough to ignore.
+
+ try {
+ const credentials = await addressFile.parse();
+
+ log.debug('Read RPC connection info', credentials.connectionString);
+
+ event.sender.send('daemon-connection', { credentials });
+ } catch (error) {
+ log.error(`Cannot parse the RPC address file: ${error.message}`);
+ return;
+ }
});
ipcMain.on('show-window', () => {
@@ -249,84 +282,6 @@ const ApplicationMain = {
);
},
- _getRpcAddressFilePath() {
- const rpcAddressFileName = '.mullvad_rpc_address';
-
- switch (process.platform) {
- case 'win32': {
- // Windows: %ALLUSERSPROFILE%\{appname}
- const programDataDirectory = process.env.ALLUSERSPROFILE;
- if (programDataDirectory) {
- const appDataDirectory = path.join(programDataDirectory, appDirectoryName);
- return path.join(appDataDirectory, rpcAddressFileName);
- } else {
- throw new Error('Missing %ALLUSERSPROFILE% environment variable');
- }
- }
- default:
- return path.join(getSystemTemporaryDirectory(), rpcAddressFileName);
- }
- },
-
- _pollConnectionInfoFile() {
- if (this._connectionFilePollInterval) {
- log.warn(
- 'Attempted to start polling for the RPC connection info file while another polling was already running',
- );
- return;
- }
-
- const pollIntervalMs = 200;
- const rpcAddressFile = this._getRpcAddressFilePath();
-
- this._connectionFilePollInterval = setInterval(() => {
- if (fs.existsSync(rpcAddressFile)) {
- if (this._connectionFilePollInterval) {
- clearInterval(this._connectionFilePollInterval);
- this._connectionFilePollInterval = null;
- }
-
- this._sendDaemonConnectionInfo(rpcAddressFile);
- }
- }, pollIntervalMs);
- },
-
- _sendDaemonConnectionInfo(rpcAddressFile: string) {
- log.debug(`Reading the ipc connection info from "${rpcAddressFile}"`);
-
- try {
- if (!canTrustRpcAddressFile(rpcAddressFile)) {
- log.error(`Not trusting the contents of "${rpcAddressFile}".`);
- return;
- }
- } catch (e) {
- log.error(`Cannot verify the credibility of RPC address file: ${e.message}`);
- return;
- }
-
- // There is a race condition here where the owner and permissions of
- // the file can change in the time between we validate the owner and
- // permissions and read the contents of the file. We deem the chance
- // of that to be small enough to ignore.
-
- fs.readFile(rpcAddressFile, 'utf8', (err, data) => {
- if (err) {
- return log.error('Could not find backend connection info', err);
- }
-
- const credentials = parseIpcCredentials(data);
- if (credentials) {
- log.debug('Read IPC connection info', credentials.connectionString);
- const windowController = this._windowController;
- if (windowController) {
- windowController.window.webContents.send('backend-info', { credentials });
- }
- } else {
- log.error('Could not parse IPC credentials.');
- }
- });
- },
-
async _installDevTools() {
const installer = require('electron-devtools-installer');
const extensions = ['REACT_DEVELOPER_TOOLS', 'REDUX_DEVTOOLS'];