add support for STARTTLS
This commit is contained in:
parent
dd3575560d
commit
48e37631c7
11
README.md
11
README.md
|
@ -36,8 +36,8 @@ var imap = new Imap({
|
|||
password: 'mygmailpassword',
|
||||
host: 'imap.gmail.com',
|
||||
port: 993,
|
||||
secure: true,
|
||||
secureOptions: { rejectUnauthorized: false }
|
||||
tls: true,
|
||||
tlsOptions: { rejectUnauthorized: false }
|
||||
});
|
||||
|
||||
function openInbox(cb) {
|
||||
|
@ -379,8 +379,9 @@ Connection Instance Methods
|
|||
* **xoauth2** - < _string_ > - OAuth2 token for [The SASL XOAUTH2 Mechanism](https://developers.google.com/google-apps/gmail/xoauth2_protocol#the_sasl_xoauth2_mechanism) for servers that support it (See Andris Reinman's [xoauth2](https://github.com/andris9/xoauth2) module to help generate this string).
|
||||
* **host** - < _string_ > - Hostname or IP address of the IMAP server. **Default:** "localhost"
|
||||
* **port** - < _integer_ > - Port number of the IMAP server. **Default:** 143
|
||||
* **secure** - < _boolean_ > - Use SSL/TLS? **Default:** false
|
||||
* **secureOptions** - < _object_ > - Options object to pass to tls.connect() **Default:** (none)
|
||||
* **tls** - < _boolean_ > - Perform implicit TLS connection? **Default:** false
|
||||
* **tlsOptions** - < _object_ > - Options object to pass to tls.connect() **Default:** (none)
|
||||
* **autotls** - < _string_ > - Set to 'always' to always attempt connection upgrades via STARTTLS, 'required' only if upgrading is required, or 'never' to never attempt upgrading. **Default:** 'never'
|
||||
* **connTimeout** - < _integer_ > - Number of milliseconds to wait for a connection to be established. **Default:** 10000
|
||||
* **keepalive** - < _boolean_ > - Enable the keepalive mechnanism. **Default:** true
|
||||
* **debug** - < _function_ > - If set, the function will be called with one argument, a string containing some debug info **Default:** <no debug output>
|
||||
|
@ -675,8 +676,8 @@ TODO
|
|||
|
||||
Several things not yet implemented in no particular order:
|
||||
|
||||
* Support STARTTLS
|
||||
* Support additional IMAP commands/extensions:
|
||||
* NOTIFY (via NOTIFY extension -- RFC5465)
|
||||
* STATUS addition to LIST (via LIST-STATUS extension -- RFC5819)
|
||||
* CONDSTORE (RFC4551)
|
||||
* QRESYNC (RFC5162)
|
||||
|
|
|
@ -52,8 +52,9 @@ function Connection(config) {
|
|||
this._config = {
|
||||
host: config.host || 'localhost',
|
||||
port: config.port || 143,
|
||||
secure: (config.secure === true ? 'implicit' : config.secure),
|
||||
secureOptions: config.secureOptions,
|
||||
tls: config.tls,
|
||||
tlsOptions: config.tlsOptions,
|
||||
autotls: config.autotls,
|
||||
user: config.user,
|
||||
password: config.password,
|
||||
connTimeout: config.connTimeout || 10000,
|
||||
|
@ -68,6 +69,8 @@ function Connection(config) {
|
|||
this._queue = [];
|
||||
this._box = undefined;
|
||||
this._idle = {};
|
||||
this._parser = undefined;
|
||||
this._curReq = undefined;
|
||||
this.delimiter = undefined;
|
||||
this.namespaces = undefined;
|
||||
this.state = 'disconnected';
|
||||
|
@ -76,22 +79,22 @@ function Connection(config) {
|
|||
inherits(Connection, EventEmitter);
|
||||
|
||||
Connection.prototype.connect = function() {
|
||||
var config = this._config, self = this, socket, tlsSocket, parser, tlsOptions;
|
||||
var config = this._config, self = this, socket, parser, tlsOptions;
|
||||
|
||||
socket = new Socket();
|
||||
socket.setKeepAlive(true);
|
||||
socket.setTimeout(0);
|
||||
this.state = 'disconnected';
|
||||
|
||||
if (config.secure) {
|
||||
if (config.tls) {
|
||||
tlsOptions = {};
|
||||
for (var k in config.secureOptions)
|
||||
tlsOptions[k] = config.secureOptions[k];
|
||||
for (var k in config.tlsOptions)
|
||||
tlsOptions[k] = config.tlsOptions[k];
|
||||
tlsOptions.socket = socket;
|
||||
}
|
||||
|
||||
if (config.secure === 'implicit')
|
||||
this._sock = tlsSocket = tls.connect(tlsOptions, onconnect);
|
||||
if (config.tls)
|
||||
this._sock = tls.connect(tlsOptions, onconnect);
|
||||
else {
|
||||
socket.once('connect', onconnect);
|
||||
this._sock = socket;
|
||||
|
@ -100,32 +103,33 @@ Connection.prototype.connect = function() {
|
|||
function onconnect() {
|
||||
clearTimeout(self._tmrConn);
|
||||
self.state = 'connected';
|
||||
self.debug&&self.debug('[connection] Connected to host');
|
||||
self.debug && self.debug('[connection] Connected to host');
|
||||
}
|
||||
|
||||
this._sock.once('error', function(err) {
|
||||
this._onError = function(err) {
|
||||
clearTimeout(self._tmrConn);
|
||||
clearTimeout(self._tmrKeepalive);
|
||||
self.debug&&self.debug('[connection] Error: ' + err);
|
||||
self.debug && self.debug('[connection] Error: ' + err);
|
||||
err.source = 'socket';
|
||||
self.emit('error', err);
|
||||
});
|
||||
};
|
||||
this._sock.once('error', this._onError);
|
||||
|
||||
socket.once('close', function(had_err) {
|
||||
clearTimeout(self._tmrConn);
|
||||
clearTimeout(self._tmrKeepalive);
|
||||
self.debug&&self.debug('[connection] Closed');
|
||||
self.debug && self.debug('[connection] Closed');
|
||||
self.emit('close', had_err);
|
||||
});
|
||||
|
||||
socket.once('end', function() {
|
||||
clearTimeout(self._tmrConn);
|
||||
clearTimeout(self._tmrKeepalive);
|
||||
self.debug&&self.debug('[connection] Ended');
|
||||
self.debug && self.debug('[connection] Ended');
|
||||
self.emit('end');
|
||||
});
|
||||
|
||||
parser = new Parser(this._sock, this.debug);
|
||||
this._parser = parser = new Parser(this._sock, this.debug);
|
||||
|
||||
parser.on('untagged', function(info) {
|
||||
self._resUntagged(info);
|
||||
|
@ -1189,6 +1193,12 @@ Connection.prototype._login = function() {
|
|||
reentry();
|
||||
};
|
||||
|
||||
if (self.serverSupports('STARTTLS')
|
||||
&& (self._config.autotls === 'always'
|
||||
|| (self._config.autotls === 'required'
|
||||
&& self.serverSupports('LOGINDISABLED'))))
|
||||
self._starttls();
|
||||
|
||||
if (self.serverSupports('LOGINDISABLED')) {
|
||||
err = new Error('Logging in is disabled on this server');
|
||||
err.source = 'authentication';
|
||||
|
@ -1197,12 +1207,16 @@ Connection.prototype._login = function() {
|
|||
|
||||
if (self.serverSupports('AUTH=XOAUTH') && self._config.xoauth) {
|
||||
self._caps = undefined;
|
||||
self._enqueue('AUTHENTICATE XOAUTH ' + escape(self._config.xoauth),
|
||||
checkCaps);
|
||||
var cmd = 'AUTHENTICATE XOAUTH';
|
||||
if (self.serverSupports('SASL-IR'))
|
||||
cmd += ' ' + escape(self._config.xoauth);
|
||||
self._enqueue(cmd, checkCaps);
|
||||
} else if (self.serverSupports('AUTH=XOAUTH2') && self._config.xoauth2) {
|
||||
self._caps = undefined;
|
||||
self._enqueue('AUTHENTICATE XOAUTH2 ' + escape(self._config.xoauth2),
|
||||
checkCaps);
|
||||
var cmd = 'AUTHENTICATE XOAUTH2';
|
||||
if (self.serverSupports('SASL-IR'))
|
||||
cmd += ' ' + escape(self._config.xoauth2);
|
||||
self._enqueue(cmd, checkCaps);
|
||||
} else if (self._config.user && self._config.password) {
|
||||
self._caps = undefined;
|
||||
self._enqueue('LOGIN "' + escape(self._config.user) + '" "'
|
||||
|
@ -1218,6 +1232,32 @@ Connection.prototype._login = function() {
|
|||
});
|
||||
};
|
||||
|
||||
Connection.prototype._starttls = function() {
|
||||
var self = this;
|
||||
this._enqueue('STARTTLS', function(err) {
|
||||
if (err) {
|
||||
self.emit('error', err);
|
||||
return self._sock.end();
|
||||
}
|
||||
|
||||
self._caps = undefined;
|
||||
self._sock.removeAllListeners('error');
|
||||
|
||||
var tlsOptions = {};
|
||||
|
||||
for (var k in config.tlsOptions)
|
||||
tlsOptions[k] = config.tlsOptions[k];
|
||||
tlsOptions.socket = self._sock;
|
||||
|
||||
self._sock = tls.connect(tlsOptions, function() {
|
||||
self._login();
|
||||
});
|
||||
|
||||
self._sock.on('error', self._onError);
|
||||
self._parser.setStream(self._sock);
|
||||
});
|
||||
};
|
||||
|
||||
Connection.prototype._processQueue = function() {
|
||||
if (this._curReq || !this._queue.length || !this._sock.writable)
|
||||
return;
|
||||
|
@ -1257,7 +1297,9 @@ Connection.prototype._enqueue = function(fullcmd, promote, cb) {
|
|||
else
|
||||
this._queue.push(info);
|
||||
|
||||
if (!this._curReq) {
|
||||
if (!this._curReq
|
||||
&& this.state !== 'disconnected'
|
||||
&& this.state !== 'upgrading') {
|
||||
// defer until next tick for requests like APPEND and FETCH where access to
|
||||
// the request object is needed immediately after enqueueing
|
||||
process.nextTick(function() { self._processQueue(); });
|
||||
|
|
|
@ -25,16 +25,15 @@ function Parser(stream, debug) {
|
|||
|
||||
EventEmitter.call(this);
|
||||
|
||||
if (/^v0\.8\./.test(process.version))
|
||||
stream = new ReadableStream().wrap(stream);
|
||||
|
||||
this._stream = stream;
|
||||
this._stream = undefined;
|
||||
this._body = undefined;
|
||||
this._literallen = 0;
|
||||
this._literals = [];
|
||||
this._buffer = '';
|
||||
this.debug = debug;
|
||||
|
||||
this.setStream(stream);
|
||||
|
||||
var self = this;
|
||||
function cb() {
|
||||
if (self._literallen > 0)
|
||||
|
@ -47,6 +46,13 @@ function Parser(stream, debug) {
|
|||
}
|
||||
inherits(Parser, EventEmitter);
|
||||
|
||||
Parser.prototype.setStream = function(stream) {
|
||||
if (/^v0\.8\./.test(process.version))
|
||||
stream = new ReadableStream().wrap(stream);
|
||||
|
||||
this._stream = stream;
|
||||
};
|
||||
|
||||
Parser.prototype._tryread = function(n) {
|
||||
var r = this._stream.read(n);
|
||||
r && this._parse(r);
|
||||
|
|
Loading…
Reference in a new issue