add support for STARTTLS

fork
mscdex 11 years ago
parent dd3575560d
commit 48e37631c7

@ -36,8 +36,8 @@ var imap = new Imap({
password: 'mygmailpassword', password: 'mygmailpassword',
host: 'imap.gmail.com', host: 'imap.gmail.com',
port: 993, port: 993,
secure: true, tls: true,
secureOptions: { rejectUnauthorized: false } tlsOptions: { rejectUnauthorized: false }
}); });
function openInbox(cb) { 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). * **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" * **host** - < _string_ > - Hostname or IP address of the IMAP server. **Default:** "localhost"
* **port** - < _integer_ > - Port number of the IMAP server. **Default:** 143 * **port** - < _integer_ > - Port number of the IMAP server. **Default:** 143
* **secure** - < _boolean_ > - Use SSL/TLS? **Default:** false * **tls** - < _boolean_ > - Perform implicit TLS connection? **Default:** false
* **secureOptions** - < _object_ > - Options object to pass to tls.connect() **Default:** (none) * **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 * **connTimeout** - < _integer_ > - Number of milliseconds to wait for a connection to be established. **Default:** 10000
* **keepalive** - < _boolean_ > - Enable the keepalive mechnanism. **Default:** true * **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> * **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: Several things not yet implemented in no particular order:
* Support STARTTLS
* Support additional IMAP commands/extensions: * Support additional IMAP commands/extensions:
* NOTIFY (via NOTIFY extension -- RFC5465) * NOTIFY (via NOTIFY extension -- RFC5465)
* STATUS addition to LIST (via LIST-STATUS extension -- RFC5819) * STATUS addition to LIST (via LIST-STATUS extension -- RFC5819)
* CONDSTORE (RFC4551) * CONDSTORE (RFC4551)
* QRESYNC (RFC5162)

@ -52,8 +52,9 @@ function Connection(config) {
this._config = { this._config = {
host: config.host || 'localhost', host: config.host || 'localhost',
port: config.port || 143, port: config.port || 143,
secure: (config.secure === true ? 'implicit' : config.secure), tls: config.tls,
secureOptions: config.secureOptions, tlsOptions: config.tlsOptions,
autotls: config.autotls,
user: config.user, user: config.user,
password: config.password, password: config.password,
connTimeout: config.connTimeout || 10000, connTimeout: config.connTimeout || 10000,
@ -68,6 +69,8 @@ function Connection(config) {
this._queue = []; this._queue = [];
this._box = undefined; this._box = undefined;
this._idle = {}; this._idle = {};
this._parser = undefined;
this._curReq = undefined;
this.delimiter = undefined; this.delimiter = undefined;
this.namespaces = undefined; this.namespaces = undefined;
this.state = 'disconnected'; this.state = 'disconnected';
@ -76,22 +79,22 @@ function Connection(config) {
inherits(Connection, EventEmitter); inherits(Connection, EventEmitter);
Connection.prototype.connect = function() { 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 = new Socket();
socket.setKeepAlive(true); socket.setKeepAlive(true);
socket.setTimeout(0); socket.setTimeout(0);
this.state = 'disconnected'; this.state = 'disconnected';
if (config.secure) { if (config.tls) {
tlsOptions = {}; tlsOptions = {};
for (var k in config.secureOptions) for (var k in config.tlsOptions)
tlsOptions[k] = config.secureOptions[k]; tlsOptions[k] = config.tlsOptions[k];
tlsOptions.socket = socket; tlsOptions.socket = socket;
} }
if (config.secure === 'implicit') if (config.tls)
this._sock = tlsSocket = tls.connect(tlsOptions, onconnect); this._sock = tls.connect(tlsOptions, onconnect);
else { else {
socket.once('connect', onconnect); socket.once('connect', onconnect);
this._sock = socket; this._sock = socket;
@ -100,32 +103,33 @@ Connection.prototype.connect = function() {
function onconnect() { function onconnect() {
clearTimeout(self._tmrConn); clearTimeout(self._tmrConn);
self.state = 'connected'; 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._tmrConn);
clearTimeout(self._tmrKeepalive); clearTimeout(self._tmrKeepalive);
self.debug&&self.debug('[connection] Error: ' + err); self.debug && self.debug('[connection] Error: ' + err);
err.source = 'socket'; err.source = 'socket';
self.emit('error', err); self.emit('error', err);
}); };
this._sock.once('error', this._onError);
socket.once('close', function(had_err) { socket.once('close', function(had_err) {
clearTimeout(self._tmrConn); clearTimeout(self._tmrConn);
clearTimeout(self._tmrKeepalive); clearTimeout(self._tmrKeepalive);
self.debug&&self.debug('[connection] Closed'); self.debug && self.debug('[connection] Closed');
self.emit('close', had_err); self.emit('close', had_err);
}); });
socket.once('end', function() { socket.once('end', function() {
clearTimeout(self._tmrConn); clearTimeout(self._tmrConn);
clearTimeout(self._tmrKeepalive); clearTimeout(self._tmrKeepalive);
self.debug&&self.debug('[connection] Ended'); self.debug && self.debug('[connection] Ended');
self.emit('end'); self.emit('end');
}); });
parser = new Parser(this._sock, this.debug); this._parser = parser = new Parser(this._sock, this.debug);
parser.on('untagged', function(info) { parser.on('untagged', function(info) {
self._resUntagged(info); self._resUntagged(info);
@ -1189,6 +1193,12 @@ Connection.prototype._login = function() {
reentry(); reentry();
}; };
if (self.serverSupports('STARTTLS')
&& (self._config.autotls === 'always'
|| (self._config.autotls === 'required'
&& self.serverSupports('LOGINDISABLED'))))
self._starttls();
if (self.serverSupports('LOGINDISABLED')) { if (self.serverSupports('LOGINDISABLED')) {
err = new Error('Logging in is disabled on this server'); err = new Error('Logging in is disabled on this server');
err.source = 'authentication'; err.source = 'authentication';
@ -1197,12 +1207,16 @@ Connection.prototype._login = function() {
if (self.serverSupports('AUTH=XOAUTH') && self._config.xoauth) { if (self.serverSupports('AUTH=XOAUTH') && self._config.xoauth) {
self._caps = undefined; self._caps = undefined;
self._enqueue('AUTHENTICATE XOAUTH ' + escape(self._config.xoauth), var cmd = 'AUTHENTICATE XOAUTH';
checkCaps); if (self.serverSupports('SASL-IR'))
cmd += ' ' + escape(self._config.xoauth);
self._enqueue(cmd, checkCaps);
} else if (self.serverSupports('AUTH=XOAUTH2') && self._config.xoauth2) { } else if (self.serverSupports('AUTH=XOAUTH2') && self._config.xoauth2) {
self._caps = undefined; self._caps = undefined;
self._enqueue('AUTHENTICATE XOAUTH2 ' + escape(self._config.xoauth2), var cmd = 'AUTHENTICATE XOAUTH2';
checkCaps); if (self.serverSupports('SASL-IR'))
cmd += ' ' + escape(self._config.xoauth2);
self._enqueue(cmd, checkCaps);
} else if (self._config.user && self._config.password) { } else if (self._config.user && self._config.password) {
self._caps = undefined; self._caps = undefined;
self._enqueue('LOGIN "' + escape(self._config.user) + '" "' 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() { Connection.prototype._processQueue = function() {
if (this._curReq || !this._queue.length || !this._sock.writable) if (this._curReq || !this._queue.length || !this._sock.writable)
return; return;
@ -1257,7 +1297,9 @@ Connection.prototype._enqueue = function(fullcmd, promote, cb) {
else else
this._queue.push(info); 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 // defer until next tick for requests like APPEND and FETCH where access to
// the request object is needed immediately after enqueueing // the request object is needed immediately after enqueueing
process.nextTick(function() { self._processQueue(); }); process.nextTick(function() { self._processQueue(); });

@ -25,16 +25,15 @@ function Parser(stream, debug) {
EventEmitter.call(this); EventEmitter.call(this);
if (/^v0\.8\./.test(process.version)) this._stream = undefined;
stream = new ReadableStream().wrap(stream);
this._stream = stream;
this._body = undefined; this._body = undefined;
this._literallen = 0; this._literallen = 0;
this._literals = []; this._literals = [];
this._buffer = ''; this._buffer = '';
this.debug = debug; this.debug = debug;
this.setStream(stream);
var self = this; var self = this;
function cb() { function cb() {
if (self._literallen > 0) if (self._literallen > 0)
@ -47,6 +46,13 @@ function Parser(stream, debug) {
} }
inherits(Parser, EventEmitter); 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) { Parser.prototype._tryread = function(n) {
var r = this._stream.read(n); var r = this._stream.read(n);
r && this._parse(r); r && this._parse(r);

Loading…
Cancel
Save