mirror of
https://github.com/nodemailer/wildduck.git
synced 2025-09-13 08:34:53 +08:00
Updated XAPPLEPUSHSERVICE schema handling
This commit is contained in:
parent
4e96db26ad
commit
4ef3eaabca
5 changed files with 178 additions and 128 deletions
|
@ -54,6 +54,11 @@ ignoredHosts = []
|
||||||
#secure=false
|
#secure=false
|
||||||
#ignoreSTARTTLS=true
|
#ignoreSTARTTLS=true
|
||||||
|
|
||||||
|
# Apple push notificiations
|
||||||
|
# TODO: missing actual implementation for Apple Push Service
|
||||||
|
[aps]
|
||||||
|
enabled = false
|
||||||
|
|
||||||
[setup]
|
[setup]
|
||||||
# Public configuration for IMAP
|
# Public configuration for IMAP
|
||||||
hostname = "localhost"
|
hostname = "localhost"
|
||||||
|
|
|
@ -7,143 +7,178 @@
|
||||||
// tag XAPPLEPUSHSERVICE aps-version 2 aps-account-id 0715A26B-CA09-4730-A419-793000CA982E aps-device-token 2918390218931890821908309283098109381029309829018310983092892829 aps-subtopic com.apple.mobilemail mailboxes (INBOX Notes)
|
// tag XAPPLEPUSHSERVICE aps-version 2 aps-account-id 0715A26B-CA09-4730-A419-793000CA982E aps-device-token 2918390218931890821908309283098109381029309829018310983092892829 aps-subtopic com.apple.mobilemail mailboxes (INBOX Notes)
|
||||||
//
|
//
|
||||||
|
|
||||||
|
const requiredKeys = ['aps-version', 'aps-account-id', 'aps-device-token', 'aps-subtopic', 'mailboxes'];
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
state: ['Authenticated', 'Selected'],
|
state: ['Authenticated', 'Selected'],
|
||||||
|
|
||||||
/*
|
// the input is a key-value set which is not supported by the default schema handler
|
||||||
Schema: [
|
schema: false,
|
||||||
{
|
|
||||||
name: 'aps-version',
|
// [
|
||||||
type: 'number' // always 2
|
// { type: 'ATOM', value: 'aps-version' },
|
||||||
},
|
// { type: 'ATOM', value: '2' },
|
||||||
{
|
// { type: 'ATOM', value: 'aps-account-id' },
|
||||||
name: 'aps-account-id',
|
// { type: 'ATOM', value: 'xxxxxxx' },
|
||||||
type: 'string'
|
// { type: 'ATOM', value: 'aps-device-token' },
|
||||||
},
|
// {
|
||||||
{
|
// type: 'ATOM',
|
||||||
name: 'aps-device-token',
|
// value: 'xxxxxx'
|
||||||
type: 'string'
|
// },
|
||||||
},
|
// { type: 'ATOM', value: 'aps-subtopic' },
|
||||||
{
|
// { type: 'ATOM', value: 'com.apple.mobilemail' },
|
||||||
name: 'aps-subtopic',
|
// { type: 'ATOM', value: 'mailboxes' },
|
||||||
type: 'string' // always "com.apple.mobilemail"
|
// [
|
||||||
},
|
// { type: 'STRING', value: 'Sent Mail' },
|
||||||
// NOTE: this is irrelevant as it won't be used until we figure out how to notify for other than INBOX
|
// { type: 'STRING', value: 'INBOX' }
|
||||||
// <https://github.com/nodemailer/wildduck/issues/711#issuecomment-2251643672>
|
// ]
|
||||||
{
|
// ]
|
||||||
name: 'mailboxes',
|
|
||||||
type: 'string' // e.g. (INBOX Notes)
|
handler(command, callback) {
|
||||||
|
// Command = {
|
||||||
|
// tag: 'I5',
|
||||||
|
// command: 'XAPPLEPUSHSERVICE',
|
||||||
|
// attributes: [
|
||||||
|
// { type: 'ATOM', value: 'aps-version' }, // 0
|
||||||
|
// { type: 'ATOM', value: '2' }, // 1
|
||||||
|
// { type: 'ATOM', value: 'aps-account-id' }, // 2
|
||||||
|
// { type: 'ATOM', value: 'xxxxxx' }, // 3
|
||||||
|
// { type: 'ATOM', value: 'aps-device-token' }, // 4
|
||||||
|
// { // 5
|
||||||
|
// type: 'ATOM',
|
||||||
|
// value: 'xxxxxx'
|
||||||
|
// },
|
||||||
|
// { type: 'ATOM', value: 'aps-subtopic' }, // 6
|
||||||
|
// { type: 'ATOM', value: 'com.apple.mobilemail' }, // 7
|
||||||
|
// { type: 'ATOM', value: 'mailboxes' }, // 8
|
||||||
|
// [ // 9
|
||||||
|
// { type: 'STRING', value: 'Sent Mail' },
|
||||||
|
// { type: 'STRING', value: 'INBOX' }
|
||||||
|
// ]
|
||||||
|
// ]
|
||||||
|
// }
|
||||||
|
|
||||||
|
const apsConfig = this._server.options.aps || {};
|
||||||
|
|
||||||
|
// Reject if not enabled
|
||||||
|
if (!apsConfig.enabled) {
|
||||||
|
return callback(null, {
|
||||||
|
response: 'BAD',
|
||||||
|
message: `Unknown command: ${command.command}`
|
||||||
|
});
|
||||||
}
|
}
|
||||||
],
|
|
||||||
*/
|
|
||||||
|
|
||||||
// it's actually something like this in production
|
// Parse input arguments into a structured object:
|
||||||
// [
|
|
||||||
// { type: 'ATOM', value: 'aps-version' },
|
|
||||||
// { type: 'ATOM', value: '2' },
|
|
||||||
// { type: 'ATOM', value: 'aps-account-id' },
|
|
||||||
// { type: 'ATOM', value: 'xxxxxxx' },
|
|
||||||
// { type: 'ATOM', value: 'aps-device-token' },
|
|
||||||
// {
|
|
||||||
// type: 'ATOM',
|
|
||||||
// value: 'xxxxxx'
|
|
||||||
// },
|
|
||||||
// { type: 'ATOM', value: 'aps-subtopic' },
|
|
||||||
// { type: 'ATOM', value: 'com.apple.mobilemail' },
|
|
||||||
// { type: 'ATOM', value: 'mailboxes' },
|
|
||||||
// [
|
|
||||||
// { type: 'STRING', value: 'Sent Mail' },
|
|
||||||
// { type: 'STRING', value: 'INBOX' }
|
|
||||||
// ]
|
|
||||||
// ]
|
|
||||||
|
|
||||||
// disabled for now
|
// {
|
||||||
schema: false,
|
// "aps-version": "2",
|
||||||
|
// "aps-account-id": "0715A26B-CA09-4730-A419-793000CA982E",
|
||||||
|
// "aps-device-token": "2918390218931890821908309283098109381029309829018310983092892829",
|
||||||
|
// "aps-subtopic": "com.apple.mobilemail",
|
||||||
|
// "mailboxes": [
|
||||||
|
// "INBOX",
|
||||||
|
// "Notes"
|
||||||
|
// ]
|
||||||
|
// }
|
||||||
|
|
||||||
handler(command, callback) {
|
let data = {};
|
||||||
// Command = {
|
let keyName;
|
||||||
// tag: 'I5',
|
for (let i = 0, len = (command.attributes || []).length; i < len; i++) {
|
||||||
// command: 'XAPPLEPUSHSERVICE',
|
let isKey = i % 2 === 0;
|
||||||
// attributes: [
|
let attr = command.attributes[i];
|
||||||
// { type: 'ATOM', value: 'aps-version' }, // 0
|
if (isKey && !['ATOM', 'STRING'].includes(attr.type)) {
|
||||||
// { type: 'ATOM', value: '2' }, // 1
|
return callback(null, {
|
||||||
// { type: 'ATOM', value: 'aps-account-id' }, // 2
|
response: 'BAD',
|
||||||
// { type: 'ATOM', value: 'xxxxxx' }, // 3
|
message: `Invalid argument for ${command.command}`
|
||||||
// { type: 'ATOM', value: 'aps-device-token' }, // 4
|
});
|
||||||
// { // 5
|
}
|
||||||
// type: 'ATOM',
|
if (isKey) {
|
||||||
// value: 'xxxxxx'
|
keyName = (attr.value || '').toString().toLowerCase();
|
||||||
// },
|
continue;
|
||||||
// { type: 'ATOM', value: 'aps-subtopic' }, // 6
|
}
|
||||||
// { type: 'ATOM', value: 'com.apple.mobilemail' }, // 7
|
|
||||||
// { type: 'ATOM', value: 'mailboxes' }, // 8
|
|
||||||
// [ // 9
|
|
||||||
// { type: 'STRING', value: 'Sent Mail' },
|
|
||||||
// { type: 'STRING', value: 'INBOX' }
|
|
||||||
// ]
|
|
||||||
// ]
|
|
||||||
// }
|
|
||||||
|
|
||||||
const version = (command.attributes[1] && command.attributes[1].value) || '';
|
if (!requiredKeys.includes(keyName)) {
|
||||||
if (version !== '2') {
|
// skip unknown keys
|
||||||
return callback(null, {
|
}
|
||||||
response: 'NO',
|
|
||||||
code: 'CLIENTBUG',
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const accountID = (command.attributes[3] && command.attributes[3].value) || '';
|
if (['ATOM', 'STRING'].includes(attr.type)) {
|
||||||
const deviceToken = (command.attributes[5] && command.attributes[5].value) || '';
|
data[keyName] = (attr.value || '').toString();
|
||||||
const subTopic = (command.attributes[7] && command.attributes[7].value) || '';
|
} else if (Array.isArray(attr) && keyName === 'mailboxes') {
|
||||||
|
let mailboxes = attr
|
||||||
|
.map(entry => {
|
||||||
|
if (['ATOM', 'STRING'].includes(entry.type)) {
|
||||||
|
return (entry.value || '').toString();
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
})
|
||||||
|
.filter(name => name);
|
||||||
|
data[keyName] = mailboxes;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (subTopic !== 'com.apple.mobilemail') {
|
// Make sure all required keys (exept mailboxes) are present
|
||||||
return callback(null, {
|
for (let requiredKey of requiredKeys) {
|
||||||
response: 'NO',
|
if (!data[requiredKey] && requiredKey !== 'mailboxes') {
|
||||||
code: 'CLIENTBUG',
|
return callback(null, {
|
||||||
});
|
response: 'BAD',
|
||||||
}
|
message: `Missing required arguments for ${command.command}`
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// NOTE: mailboxes param is not used at this time (it's a list anyways too)
|
const version = data['aps-version'];
|
||||||
const mailboxes = command.attributes[9] && Array.isArray(command.attributes[9]) && command.attributes[9].length > 0 ? command.attributes[9].map(object => object.value) : [];
|
const accountID = data['aps-account-id'];
|
||||||
|
const deviceToken = data['aps-device-token'];
|
||||||
|
const subTopic = data['aps-subtopic'];
|
||||||
|
const mailboxes = data.mailboxes || [];
|
||||||
|
|
||||||
if (typeof this._server.onXAPPLEPUSHSERVICE !== 'function') {
|
if (version !== '2') {
|
||||||
return callback(null, {
|
return callback(null, {
|
||||||
response: 'NO',
|
response: 'NO',
|
||||||
message: command.command + ' not implemented',
|
message: 'Unsupported APS version',
|
||||||
});
|
code: 'CLIENTBUG'
|
||||||
}
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const logdata = {
|
if (subTopic !== 'com.apple.mobilemail') {
|
||||||
short_message: '[XAPPLEPUSHSERVICE]',
|
return callback(null, {
|
||||||
_mail_action: 'xapplepushservice',
|
response: 'NO',
|
||||||
_accountId: accountID,
|
message: `Invalid subtopic for ${command.command}`,
|
||||||
_deviceToken: deviceToken,
|
code: 'CLIENTBUG'
|
||||||
_subTopic: subTopic,
|
});
|
||||||
_mailboxes: mailboxes,
|
}
|
||||||
_user: this.session.user.id.toString(),
|
|
||||||
_sess: this.id,
|
|
||||||
};
|
|
||||||
|
|
||||||
this._server.onXAPPLEPUSHSERVICE(accountID, deviceToken, subTopic, mailboxes, this.session, error => {
|
const logdata = {
|
||||||
if (error) {
|
short_message: '[XAPPLEPUSHSERVICE]',
|
||||||
logdata._error = error.message;
|
_mail_action: 'xapplepushservice',
|
||||||
logdata._code = error.code;
|
_accountId: accountID,
|
||||||
logdata._response = error.response;
|
_deviceToken: deviceToken,
|
||||||
this._server.loggelf(logdata);
|
_subTopic: subTopic,
|
||||||
|
_mailboxes: mailboxes,
|
||||||
|
_user: this.session.user.id.toString(),
|
||||||
|
_sess: this.id
|
||||||
|
};
|
||||||
|
|
||||||
return callback(null, {
|
this._server.onXAPPLEPUSHSERVICE(accountID, deviceToken, subTopic, mailboxes, this.session, error => {
|
||||||
response: 'NO',
|
if (error) {
|
||||||
code: 'TEMPFAIL',
|
logdata._error = error.message;
|
||||||
});
|
logdata._code = error.code;
|
||||||
}
|
logdata._response = error.response;
|
||||||
|
this._server.loggelf(logdata);
|
||||||
|
|
||||||
// <https://opensource.apple.com/source/dovecot/dovecot-293/dovecot/src/imap/cmd-x-apple-push-service.c.auto.html>
|
return callback(null, {
|
||||||
// <https://github.com/st3fan/dovecot-xaps-plugin/blob/3d1c71e0c78cc35ca6ead21f49a8e0e35e948a7c/xaps-imap-plugin.c#L158-L166>
|
response: 'NO',
|
||||||
this.send(`* XAPPLEPUSHSERVICE aps-version "${version}" aps-topic "${subTopic}"`);
|
code: 'TEMPFAIL'
|
||||||
callback(null, {
|
});
|
||||||
response: 'OK',
|
}
|
||||||
message: 'XAPPLEPUSHSERVICE Registration successful.'
|
|
||||||
});
|
// <https://opensource.apple.com/source/dovecot/dovecot-293/dovecot/src/imap/cmd-x-apple-push-service.c.auto.html>
|
||||||
});
|
// <https://github.com/st3fan/dovecot-xaps-plugin/blob/3d1c71e0c78cc35ca6ead21f49a8e0e35e948a7c/xaps-imap-plugin.c#L158-L166>
|
||||||
},
|
this.send(`* XAPPLEPUSHSERVICE aps-version "${version}" aps-topic "${subTopic}"`);
|
||||||
|
callback(null, {
|
||||||
|
response: 'OK',
|
||||||
|
message: 'XAPPLEPUSHSERVICE Registration successful.'
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -717,9 +717,6 @@ module.exports.getQueryResponse = function (query, message, options) {
|
||||||
module.exports.sendCapabilityResponse = connection => {
|
module.exports.sendCapabilityResponse = connection => {
|
||||||
let capabilities = [];
|
let capabilities = [];
|
||||||
|
|
||||||
if (typeof connection._server.onXAPPLEPUSHSERVICE === 'function')
|
|
||||||
capabilities.push('XAPPLEPUSHSERVICE');
|
|
||||||
|
|
||||||
if (!connection.secure) {
|
if (!connection.secure) {
|
||||||
if (!connection._server.options.disableSTARTTLS) {
|
if (!connection._server.options.disableSTARTTLS) {
|
||||||
capabilities.push('STARTTLS');
|
capabilities.push('STARTTLS');
|
||||||
|
@ -766,6 +763,10 @@ module.exports.sendCapabilityResponse = connection => {
|
||||||
if (connection._server.options.maxMessage) {
|
if (connection._server.options.maxMessage) {
|
||||||
capabilities.push('APPENDLIMIT=' + connection._server.options.maxMessage);
|
capabilities.push('APPENDLIMIT=' + connection._server.options.maxMessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (connection._server.options.aps?.enabled) {
|
||||||
|
capabilities.push('XAPPLEPUSHSERVICE');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
capabilities.sort((a, b) => a.localeCompare(b));
|
capabilities.sort((a, b) => a.localeCompare(b));
|
||||||
|
|
6
imap.js
6
imap.js
|
@ -36,7 +36,7 @@ const onMove = require('./lib/handlers/on-move');
|
||||||
const onSearch = require('./lib/handlers/on-search');
|
const onSearch = require('./lib/handlers/on-search');
|
||||||
const onGetQuotaRoot = require('./lib/handlers/on-get-quota-root');
|
const onGetQuotaRoot = require('./lib/handlers/on-get-quota-root');
|
||||||
const onGetQuota = require('./lib/handlers/on-get-quota');
|
const onGetQuota = require('./lib/handlers/on-get-quota');
|
||||||
// const onXAPPLEPUSHSERVICE = require('./lib/handlers/on-xapplepushservice');
|
const onXAPPLEPUSHSERVICE = require('./lib/handlers/on-xapplepushservice');
|
||||||
|
|
||||||
let logger = {
|
let logger = {
|
||||||
info(...args) {
|
info(...args) {
|
||||||
|
@ -78,6 +78,8 @@ let createInterface = (ifaceOptions, callback) => {
|
||||||
vendor: config.imap.vendor || 'Kreata'
|
vendor: config.imap.vendor || 'Kreata'
|
||||||
},
|
},
|
||||||
|
|
||||||
|
aps: config.imap.aps,
|
||||||
|
|
||||||
logger,
|
logger,
|
||||||
|
|
||||||
maxMessage: config.imap.maxMB * 1024 * 1024,
|
maxMessage: config.imap.maxMB * 1024 * 1024,
|
||||||
|
@ -157,7 +159,7 @@ let createInterface = (ifaceOptions, callback) => {
|
||||||
server.onSearch = onSearch(server);
|
server.onSearch = onSearch(server);
|
||||||
server.onGetQuotaRoot = onGetQuotaRoot(server);
|
server.onGetQuotaRoot = onGetQuotaRoot(server);
|
||||||
server.onGetQuota = onGetQuota(server);
|
server.onGetQuota = onGetQuota(server);
|
||||||
// server.onXAPPLEPUSHSERVICE = onXAPPLEPUSHSERVICE(server);
|
server.onXAPPLEPUSHSERVICE = onXAPPLEPUSHSERVICE(server);
|
||||||
|
|
||||||
if (loggelf) {
|
if (loggelf) {
|
||||||
server.loggelf = loggelf;
|
server.loggelf = loggelf;
|
||||||
|
|
|
@ -6,6 +6,12 @@
|
||||||
// <https://github.com/nodemailer/wildduck/issues/711>
|
// <https://github.com/nodemailer/wildduck/issues/711>
|
||||||
// tag XAPPLEPUSHSERVICE aps-version 2 aps-account-id 0715A26B-CA09-4730-A419-793000CA982E aps-device-token 2918390218931890821908309283098109381029309829018310983092892829 aps-subtopic com.apple.mobilemail mailboxes (INBOX Notes)
|
// tag XAPPLEPUSHSERVICE aps-version 2 aps-account-id 0715A26B-CA09-4730-A419-793000CA982E aps-device-token 2918390218931890821908309283098109381029309829018310983092892829 aps-subtopic com.apple.mobilemail mailboxes (INBOX Notes)
|
||||||
//
|
//
|
||||||
|
|
||||||
|
// TODO:
|
||||||
|
// 1. store APS information in DB, each deviceToken separately
|
||||||
|
// 2. on new email use the stored information to push to apple (use mathcing deviceTokens as an array of recipients)
|
||||||
|
// 3. if pushing to a specific deviceToken yields in 410, remove that token
|
||||||
|
|
||||||
module.exports = server => (accountID, deviceToken, subTopic, mailboxes, session, callback) => {
|
module.exports = server => (accountID, deviceToken, subTopic, mailboxes, session, callback) => {
|
||||||
server.logger.debug(
|
server.logger.debug(
|
||||||
{
|
{
|
||||||
|
@ -19,5 +25,6 @@ module.exports = server => (accountID, deviceToken, subTopic, mailboxes, session
|
||||||
subTopic,
|
subTopic,
|
||||||
mailboxes
|
mailboxes
|
||||||
);
|
);
|
||||||
|
|
||||||
return callback(new Error('Not implemented, see <https://github.com/nodemailer/wildduck/issues/711>'));
|
return callback(new Error('Not implemented, see <https://github.com/nodemailer/wildduck/issues/711>'));
|
||||||
};
|
};
|
||||||
|
|
Loading…
Add table
Reference in a new issue