mirror of https://github.com/keeweb/keeweb.git
sending origin information in the first message
This commit is contained in:
parent
4280b84459
commit
cbb9a8dbd0
|
@ -4,6 +4,7 @@ const path = require('path');
|
|||
const net = require('net');
|
||||
const { ipcMain, app } = require('electron');
|
||||
const { Logger } = require('../logger');
|
||||
const { getProcessInfo } = require('../util/process-utils');
|
||||
|
||||
ipcMain.handle('browserExtensionConnectorStart', browserExtensionConnectorStart);
|
||||
ipcMain.handle('browserExtensionConnectorStop', browserExtensionConnectorStop);
|
||||
|
@ -13,25 +14,34 @@ ipcMain.handle('browserExtensionConnectorSocketEvent', browserExtensionConnector
|
|||
const logger = new Logger('browser-extension-connector');
|
||||
|
||||
const MaxIncomingDataLength = 10_000;
|
||||
const ExtensionOrigins = {
|
||||
'chrome-extension://aphablpbogbpmocgkpeeadeljldnphon/': 'keeweb-connect',
|
||||
'safari-keeweb-connect': 'keeweb-connect',
|
||||
'keeweb-connect@keeweb.info': 'keeweb-connect',
|
||||
'chrome-extension://oboonakemofpalcgghocfoadofidjkkk/': 'keepassxc-browser',
|
||||
'keepassxc-browser@keepassxc.org': 'keepassxc-browser',
|
||||
'chrome-extension://pdffhmdngciaglkoonimfcmckehcpafo/': 'keepassxc-browser'
|
||||
};
|
||||
|
||||
let connectedSockets = new Map();
|
||||
let connectedSocketState = new WeakMap();
|
||||
let server;
|
||||
let serverSocketName;
|
||||
let socketId = 0;
|
||||
|
||||
async function browserExtensionConnectorStart(e, config) {
|
||||
await prepareBrowserExtensionSocket(config);
|
||||
const sockName = getBrowserExtensionSocketName(config);
|
||||
serverSocketName = getBrowserExtensionSocketName(config);
|
||||
await prepareBrowserExtensionSocket();
|
||||
|
||||
if (isSocketNameTooLong(sockName)) {
|
||||
if (isSocketNameTooLong(serverSocketName)) {
|
||||
logger.error(
|
||||
"Socket name is too long, browser connection won't be possible, probably OS username is very long.",
|
||||
sockName
|
||||
serverSocketName
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
server = net.createServer((socket) => {
|
||||
server = net.createServer(async (socket) => {
|
||||
socketId++;
|
||||
|
||||
logger.info(`New connection with socket ${socketId}`);
|
||||
|
@ -39,12 +49,10 @@ async function browserExtensionConnectorStart(e, config) {
|
|||
connectedSockets.set(socketId, socket);
|
||||
connectedSocketState.set(socket, { socketId });
|
||||
|
||||
checkSocketIdentity(socket);
|
||||
|
||||
socket.on('data', (data) => onSocketData(socket, data));
|
||||
socket.on('close', () => onSocketClose(socket));
|
||||
});
|
||||
server.listen(sockName);
|
||||
server.listen(serverSocketName);
|
||||
|
||||
logger.info('Started');
|
||||
}
|
||||
|
@ -83,22 +91,21 @@ function getBrowserExtensionSocketName(config) {
|
|||
}
|
||||
}
|
||||
|
||||
function prepareBrowserExtensionSocket(config) {
|
||||
function prepareBrowserExtensionSocket() {
|
||||
return new Promise((resolve) => {
|
||||
if (process.platform === 'darwin') {
|
||||
const sockName = getBrowserExtensionSocketName(config);
|
||||
fs.access(sockName, fs.constants.F_OK, (err) => {
|
||||
fs.access(serverSocketName, fs.constants.F_OK, (err) => {
|
||||
if (err) {
|
||||
const dir = path.dirname(sockName);
|
||||
const dir = path.dirname(serverSocketName);
|
||||
fs.mkdir(dir, () => resolve());
|
||||
} else {
|
||||
fs.unlink(sockName, () => resolve());
|
||||
fs.unlink(serverSocketName, () => resolve());
|
||||
}
|
||||
});
|
||||
} else if (process.platform === 'win32') {
|
||||
return resolve();
|
||||
} else {
|
||||
fs.unlink(getBrowserExtensionSocketName(config), () => resolve());
|
||||
fs.unlink(serverSocketName, () => resolve());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -108,38 +115,6 @@ function isSocketNameTooLong(socketName) {
|
|||
return socketName.length > maxLength;
|
||||
}
|
||||
|
||||
function checkSocketIdentity(socket) {
|
||||
const state = connectedSocketState.get(socket);
|
||||
if (!state) {
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: implement this
|
||||
|
||||
state.active = true;
|
||||
state.appName = 'TODO';
|
||||
state.extensionName = 'TODO';
|
||||
state.pid = 0;
|
||||
state.supportsNotifications = state.appName !== 'Safari';
|
||||
|
||||
logger.info(
|
||||
`Socket ${state.socketId} activated`,
|
||||
`app: ${state.appName}`,
|
||||
`extension: ${state.extensionName}`,
|
||||
`pid: ${state.pid}`
|
||||
);
|
||||
|
||||
sendToRenderer('browserExtensionSocketConnected', state.socketId, {
|
||||
connectionId: state.socketId,
|
||||
appName: state.appName,
|
||||
extensionName: state.extensionName,
|
||||
pid: state.pid,
|
||||
supportsNotifications: state.supportsNotifications
|
||||
});
|
||||
|
||||
processPendingSocketData(socket);
|
||||
}
|
||||
|
||||
function onSocketClose(socket) {
|
||||
const state = connectedSocketState.get(socket);
|
||||
connectedSocketState.delete(socket);
|
||||
|
@ -174,7 +149,7 @@ function onSocketData(socket, data) {
|
|||
|
||||
async function processPendingSocketData(socket) {
|
||||
const state = connectedSocketState.get(socket);
|
||||
if (!state?.active) {
|
||||
if (!state) {
|
||||
return;
|
||||
}
|
||||
if (!state.pendingData || state.processingData) {
|
||||
|
@ -218,6 +193,11 @@ async function processPendingSocketData(socket) {
|
|||
return;
|
||||
}
|
||||
|
||||
if (!state.active) {
|
||||
await processFirstMessageFromSocket(socket, request);
|
||||
return;
|
||||
}
|
||||
|
||||
logger.debug(`Extension[${state.socketId}] -> KeeWeb`, request);
|
||||
|
||||
if (!request) {
|
||||
|
@ -251,6 +231,78 @@ async function processPendingSocketData(socket) {
|
|||
sendToRenderer('browserExtensionSocketRequest', state.socketId, request);
|
||||
}
|
||||
|
||||
async function processFirstMessageFromSocket(socket, message) {
|
||||
const state = connectedSocketState.get(socket);
|
||||
if (!state) {
|
||||
return;
|
||||
}
|
||||
|
||||
logger.debug(`Init connection ${state.socketId}`, message);
|
||||
|
||||
if (!message.origin) {
|
||||
logger.error('Empty origin');
|
||||
socket.destroy();
|
||||
return;
|
||||
}
|
||||
if (!message.pid) {
|
||||
logger.error('Empty pid');
|
||||
socket.destroy();
|
||||
return;
|
||||
}
|
||||
|
||||
const extensionName = ExtensionOrigins[message.origin] || 'unknown';
|
||||
const isSafari = message.origin === 'safari-keeweb-connect';
|
||||
let appName;
|
||||
|
||||
if (isSafari) {
|
||||
appName = 'Safari';
|
||||
} else {
|
||||
if (!message.ppid) {
|
||||
logger.error('Empty ppid');
|
||||
socket.destroy();
|
||||
return;
|
||||
}
|
||||
|
||||
let parentProcessInfo;
|
||||
try {
|
||||
parentProcessInfo = await getProcessInfo(message.ppid);
|
||||
} catch (e) {
|
||||
logger.error(`Cannot get info for PID ${message.ppid}: ${e}`);
|
||||
socket.destroy();
|
||||
return;
|
||||
}
|
||||
|
||||
appName = parentProcessInfo.commandLine.split('/').pop();
|
||||
appName = appName[0].toUpperCase() + appName.substr(1);
|
||||
}
|
||||
|
||||
state.active = true;
|
||||
state.appName = appName;
|
||||
state.extensionName = extensionName;
|
||||
state.pid = message.pid;
|
||||
state.ppid = message.ppid;
|
||||
state.supportsNotifications = state.appName !== 'Safari';
|
||||
state.processingData = false;
|
||||
|
||||
logger.info(
|
||||
`Socket ${state.socketId} activated for ` +
|
||||
`app: "${state.appName}", ` +
|
||||
`extension: "${state.extensionName}", ` +
|
||||
`pid: ${state.pid}, ` +
|
||||
`ppid: ${state.ppid}`
|
||||
);
|
||||
|
||||
sendToRenderer('browserExtensionSocketConnected', state.socketId, {
|
||||
connectionId: state.socketId,
|
||||
appName: state.appName,
|
||||
extensionName: state.extensionName,
|
||||
pid: state.pid,
|
||||
supportsNotifications: state.supportsNotifications
|
||||
});
|
||||
|
||||
processPendingSocketData(socket);
|
||||
}
|
||||
|
||||
function sendResultToSocket(socketId, result) {
|
||||
const socket = connectedSockets.get(socketId);
|
||||
if (socket) {
|
||||
|
|
|
@ -0,0 +1,40 @@
|
|||
const childProcess = require('child_process');
|
||||
|
||||
function getProcessInfo(pid) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const process = childProcess.spawn('/bin/ps', ['-opid=,ppid=,command=', '-p', pid]);
|
||||
|
||||
const data = [];
|
||||
process.stdout.on('data', (chunk) => data.push(chunk));
|
||||
|
||||
process.on('close', () => {
|
||||
const output = Buffer.concat(data).toString();
|
||||
try {
|
||||
const result = parsePsOutput(output);
|
||||
if (result.pid !== pid) {
|
||||
throw new Error(`PS pid mismatch: ${result.pid} <> ${pid}`);
|
||||
}
|
||||
resolve(result);
|
||||
} catch (e) {
|
||||
reject(e);
|
||||
}
|
||||
});
|
||||
process.on('error', (e) => {
|
||||
reject(e);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function parsePsOutput(output) {
|
||||
const match = output.trim().match(/^(\d+)\s+(\d+)\s+(.+)$/);
|
||||
if (!match) {
|
||||
throw new Error(`Bad PS output: ${output}`);
|
||||
}
|
||||
return {
|
||||
pid: match[1] | 0,
|
||||
parentPid: match[2] | 0,
|
||||
commandLine: match[3]
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = { getProcessInfo };
|
|
@ -1,7 +1,6 @@
|
|||
#include <uv.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <filesystem>
|
||||
#include <iostream>
|
||||
#include <queue>
|
||||
|
@ -12,20 +11,8 @@
|
|||
// https://developer.chrome.com/docs/apps/nativeMessaging/#native-messaging-host-protocol
|
||||
// https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/Native_messaging#app_side
|
||||
|
||||
constexpr std::array kAllowedOrigins = {
|
||||
// KeeWeb Connect: Chrome
|
||||
std::string_view("chrome-extension://npmnaajonabmkjekongmjhdjpjdlhpkp/"),
|
||||
// KeeWeb Connect: Firefox
|
||||
std::string_view("keeweb-connect@keeweb.info"),
|
||||
// KeePassXC-Browser: Chrome
|
||||
std::string_view("chrome-extension://oboonakemofpalcgghocfoadofidjkkk/"),
|
||||
// KeePassXC-Browser: Firefox
|
||||
std::string_view("keepassxc-browser@keepassxc.org"),
|
||||
// KeePassXC-Browser: Edge
|
||||
std::string_view("chrome-extension://pdffhmdngciaglkoonimfcmckehcpafo/"),
|
||||
};
|
||||
|
||||
struct State {
|
||||
std::string origin;
|
||||
uv_stream_t *tty_in = nullptr;
|
||||
uv_stream_t *tty_out = nullptr;
|
||||
uv_stream_t *keeweb_pipe = nullptr;
|
||||
|
@ -44,24 +31,6 @@ void process_stdout_queue();
|
|||
void close_keeweb_pipe();
|
||||
void connect_keeweb_pipe();
|
||||
|
||||
bool check_args(int argc, char *argv[]) {
|
||||
if (argc < 2) {
|
||||
std::cerr << "Expected origin argument" << std::endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
for (int arg = 1; arg < argc; arg++) {
|
||||
std::string origin = argv[arg];
|
||||
auto found = std::find(kAllowedOrigins.begin(), kAllowedOrigins.end(), origin);
|
||||
if (found != kAllowedOrigins.end()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
std::cerr << "Bad origin" << std::endl;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void alloc_buf(uv_handle_t *, size_t size, uv_buf_t *buf) {
|
||||
buf->base = new char[size];
|
||||
buf->len = static_cast<decltype(uv_buf_t::len)>(size);
|
||||
|
@ -235,6 +204,25 @@ void connect_keeweb_pipe() {
|
|||
|
||||
void start_reading_stdin() { uv_read_start(state.tty_in, alloc_buf, stdin_read_cb); }
|
||||
|
||||
void push_first_message_to_keeweb() {
|
||||
auto origin = state.origin;
|
||||
std::replace(origin.begin(), origin.end(), '"', '\'');
|
||||
|
||||
auto message = "{\"pid\":" + std::to_string(uv_os_getpid()) +
|
||||
",\"ppid\":" + std::to_string(uv_os_getppid()) + ",\"origin\":\"" + origin +
|
||||
"\"}";
|
||||
|
||||
auto message_length = message.length() + sizeof(uint32_t);
|
||||
auto data = new char[message_length];
|
||||
auto size_ptr = reinterpret_cast<uint32_t *>(data);
|
||||
|
||||
*size_ptr = message.length();
|
||||
memcpy(data + sizeof(uint32_t), message.c_str(), message.length());
|
||||
|
||||
state.pending_to_keeweb.emplace(
|
||||
uv_buf_init(data, static_cast<decltype(uv_buf_t::len)>(message_length)));
|
||||
}
|
||||
|
||||
void init_tty() {
|
||||
auto stdin_tty = new uv_tty_t{};
|
||||
uv_tty_init(uv_default_loop(), stdin_tty, 0, 0);
|
||||
|
@ -246,9 +234,13 @@ void init_tty() {
|
|||
}
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
if (!check_args(argc, argv)) {
|
||||
if (argc < 2) {
|
||||
std::cerr << "Expected origin argument" << std::endl;
|
||||
return 1;
|
||||
}
|
||||
state.origin = argv[1];
|
||||
|
||||
push_first_message_to_keeweb();
|
||||
|
||||
init_tty();
|
||||
start_reading_stdin();
|
||||
|
|
Loading…
Reference in New Issue