nixos-config/packages/add_passwordcommand_smtp.patch

299 lines
8.7 KiB
Diff

--- a/comm/mailnews/base/src/MailAuthenticator.sys.mjs
+++ b/comm/mailnews/base/src/MailAuthenticator.sys.mjs
@@ -229,7 +229,7 @@
this._server.forgetPassword();
}
- getPassword() {
+ async getPassword() {
if (this._server.password) {
return this._server.password;
}
@@ -261,8 +261,8 @@
*
* @returns {string}
*/
- getByteStringPassword() {
- return MailStringUtils.stringToByteString(this.getPassword());
+ async getByteStringPassword() {
+ return MailStringUtils.stringToByteString(await this.getPassword());
}
/**
@@ -270,10 +270,12 @@
*
* @returns {string}
*/
- getPlainToken() {
+ async getPlainToken() {
// According to rfc4616#section-2, password should be UTF-8 BinaryString
// before base64 encoded.
- return btoa("\0" + this.username + "\0" + this.getByteStringPassword());
+ return btoa(
+ "\0" + this.username + "\0" + (await this.getByteStringPassword())
+ );
}
async getOAuthToken() {
--- a/comm/mailnews/compose/src/SmtpServer.sys.mjs
+++ b/comm/mailnews/compose/src/SmtpServer.sys.mjs
@@ -7,9 +7,22 @@
const lazy = {};
ChromeUtils.defineESModuleGetters(lazy, {
+ FileUtils: "resource:///modules/FileUtils.sys.mjs",
SmtpClient: "resource:///modules/SmtpClient.sys.mjs",
});
+const UnixServerSocket = Components.Constructor(
+ "@mozilla.org/network/server-socket;1",
+ "nsIServerSocket",
+ "initWithFilename"
+);
+const currentThread = Cc["@mozilla.org/thread-manager;1"].getService().currentThread;
+const BinaryInputStream = Components.Constructor(
+ "@mozilla.org/binaryinputstream;1",
+ "nsIBinaryInputStream",
+ "setInputStream"
+);
+
/**
* This class represents a single SMTP server.
*
@@ -318,7 +331,161 @@
this._password = password;
}
- getPasswordWithUI(promptMessage, promptTitle) {
+ getPasswordCommand() {
+ let serializedCommand = this._getCharPrefWithDefault("passwordCommand");
+ if (serializedCommand === "")
+ return null;
+
+ // Serialization is achieved by joining the arguments with a comma.
+ // Comma are themselves allowed to be backslash-espaced.
+
+ let currentArgument = "";
+ let nextShouldBeEscaped = false;
+ let commandArguments = [];
+
+ for (let i = 0; i < serializedCommand.length; i++) {
+ let c = serializedCommand[i];
+
+ switch (c) {
+ case ',':
+ if (nextShouldBeEscaped) {
+ currentArgument += ',';
+ nextShouldBeEscaped = false;
+ } else {
+ commandArguments.push(currentArgument);
+ currentArgument = '';
+ }
+ break;
+
+ case '\\':
+ if (nextShouldBeEscaped)
+ currentArgument += '\\';
+
+ nextShouldBeEscaped = !nextShouldBeEscaped;
+ break;
+
+ default:
+ currentArgument += c;
+ break;
+ }
+ }
+
+ if (nextShouldBeEscaped)
+ currentArgument += '\\';
+ commandArguments.push(currentArgument);
+
+ return commandArguments;
+ }
+
+ async runPasswordCommand(passwordCommand) {
+ let executable = passwordCommand[0];
+ let args = passwordCommand.splice(1);
+
+ let executableFile = new lazy.FileUtils.File(executable);
+
+ function genSocketFile() {
+ let tmpDir = lazy.FileUtils.getDir("TmpD", [], false);
+ let random = Math.round(Math.random() * 36 ** 3).toString(36);
+ return new lazy.FileUtils.File(
+ PathUtils.join(tmpDir.path, `tmp-${random}.sock`)
+ );
+ }
+ let socketFile = genSocketFile();
+
+ let socket = new UnixServerSocket(
+ socketFile,
+ parseInt("600", 8),
+ -1
+ );
+
+ var process = Cc['@mozilla.org/process/util;1'].createInstance(Ci.nsIProcess);
+ process.init(executableFile);
+ let processPromise = new Promise((resolve, reject) => {
+ process.runAsync(
+ args.concat(socketFile.path),
+ args.length + 1,
+ {
+ observe(subject, topic, data) {
+ switch (topic) {
+ case "process-finished":
+ resolve();
+ break;
+
+ case "process-failed":
+ reject();
+ break;
+ }
+ }
+ },
+ false
+ );
+ });
+
+ let passwordPromise = new Promise((resolve, reject) => {
+ socket.asyncListen({
+ "onSocketAccepted": (server, transport) => {
+ // The socket can be closed. This does not close the existing connection.
+ socket.close();
+
+ var stream = transport.openInputStream(0, 0, 0);
+
+ let received = [];
+ let observer = {
+ onInputStreamReady(stream) {
+ let len = 0;
+ try {
+ len = stream.available();
+ } catch (e) {
+ // note: stream.available() can also return -1.
+ len = -1;
+ }
+
+ if (len == -1) {
+ let decoder = new TextDecoder();
+ let result = decoder.decode(new Uint8Array(received));
+
+ resolve(result);
+ } else {
+ if (len > 0) {
+ let bin = new BinaryInputStream(stream);
+ let data = Array.from(bin.readByteArray(len));
+ received = received.concat(data);
+ }
+
+ stream.asyncWait(observer, 0, 0, currentThread);
+ }
+ }
+ };
+ stream.asyncWait(observer, 0, 0, currentThread);
+ }
+ });
+ });
+
+ let [_, result] = await Promise.allSettled([
+ processPromise,
+ passwordPromise,
+ ]);
+
+ socketFile.remove(false);
+
+ if (result.status == "fulfilled")
+ return result.value;
+ else
+ return Promise.reject(result.reason);
+ }
+
+ async getPasswordWithUI(promptMessage, promptTitle) {
+ let passwordCommand = this.getPasswordCommand();
+ if (passwordCommand !== null) {
+ let password = await this.runPasswordCommand(passwordCommand);
+
+ if (password === null)
+ throw Components.Exception("Password command failure", Cr.NS_ERROR_ABORT);
+
+ this.password = password;
+ return this.password;
+ }
+
// This prompt has a checkbox for saving password.
const authPrompt = Cc["@mozilla.org/messenger/msgAuthPrompt;1"].getService(
Ci.nsIAuthPrompt
--- a/comm/mailnews/compose/src/SmtpClient.sys.mjs
+++ b/comm/mailnews/compose/src/SmtpClient.sys.mjs
@@ -362,7 +362,7 @@
*
* @param {string} chunk Chunk of data received from the server
*/
- _parse(chunk) {
+ async _parse(chunk) {
// Lines should always end with <CR><LF> but you never know, might be only <LF> as well
var lines = (this._parseRemainder + (chunk || "")).split(/\r?\n/);
this._parseRemainder = lines.pop(); // not sure if the line has completely arrived yet
@@ -399,14 +399,14 @@
success: statusCode >= 200 && statusCode < 300,
};
- this._onCommand(response);
+ await this._onCommand(response);
this._parseBlock = {
data: [],
statusCode: null,
};
}
} else {
- this._onCommand({
+ await this._onCommand({
success: false,
statusCode: this._parseBlock.statusCode || null,
data: [lines[i]].join("\n"),
@@ -456,7 +456,7 @@
// rejects AUTH PLAIN then closes the connection, the client then sends AUTH
// LOGIN. This line guarantees onclose is called before sending AUTH LOGIN.
await new Promise(resolve => setTimeout(resolve));
- this._parse(stringPayload);
+ await this._parse(stringPayload);
};
/**
@@ -732,7 +732,7 @@
this.logger.debug("Authentication via AUTH PLAIN");
this._currentAction = this._actionAUTHComplete;
this._sendCommand(
- "AUTH PLAIN " + this._authenticator.getPlainToken(),
+ "AUTH PLAIN " + (await this._authenticator.getPlainToken()),
true
);
return;
@@ -1065,7 +1065,7 @@
* @param {{statusCode: number, data: string}} command - Parsed command from
* the server.
*/
- _actionAUTH_LOGIN_PASS(command) {
+ async _actionAUTH_LOGIN_PASS(command) {
if (
command.statusCode !== 334 ||
(command.data !== btoa("Password:") && command.data !== btoa("password:"))
@@ -1075,7 +1075,7 @@
}
this.logger.debug("AUTH LOGIN PASS");
this._currentAction = this._actionAUTHComplete;
- let password = this._getPassword();
+ let password = await this._getPassword();
if (
!Services.prefs.getBoolPref(
"mail.smtp_login_pop3_user_pass_auth_is_latin1",
@@ -1104,7 +1104,7 @@
}
this._currentAction = this._actionAUTHComplete;
this._sendCommand(
- this._authenticator.getCramMd5Token(this._getPassword(), command.data),
+ this._authenticator.getCramMd5Token(await this._getPassword(), command.data),
true
);
}