add support for STARTTLS

fork
mscdex 12 years ago
parent dd3575560d
commit 48e37631c7

@ -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…
Cancel
Save