Browse files

Allow communication with an IMAP server proxy program

This change lets node-imap start a proxy program whose stout and stdin
behave like an IMAP server would. For example, it can now connect to a
locally-running docecot instance (http://wiki.dovecot.org/PreAuth).

This also allows connecting to imap servers running behind spiped
tunnels and other non-standard topologies.
  • Loading branch information...
1 parent 6348873 commit 7b1a037c6958d50abc0dd9c3f3f7eccfa5770b2c @antifuchs committed Sep 21, 2012
Showing with 73 additions and 14 deletions.
  1. +73 −14 lib/imap.js
View
87 lib/imap.js
@@ -42,7 +42,9 @@ function ImapConnection (options) {
port: 143,
secure: false,
connTimeout: 10000, // connection timeout in msecs
- debug: false
+ debug: false,
+ proxy_program: false,
+ proxy_program_args: [],
};
this._state = {
status: STATES.NOCONNECT,
@@ -93,6 +95,57 @@ function ImapConnection (options) {
this.capabilities = [];
}
+function ImapProxy(program, args) {
+ if (!(this instanceof ImapProxy))
+ return new ImapProxy(program, args);
+
+ EventEmitter.call(this);
+
+ this._program = program;
+ this._args = args;
+}
+
+inherits(ImapProxy, EventEmitter);
+
+ImapProxy.prototype.start = function() {
+ var self = this;
+ var sp = require('child_process').spawn(this._program, this._args,
+ {stdio: ['pipe', 'pipe', process.stderr]});
+ sp.stdout.on('data', function(data) {
+ self.emit('data', data);
+ });
+ sp.on('exit', function(code) {
+ if(code != 0) {
+ var error = new Error("Subprocess "+ self._program + self._args + " exited with non-zero status " + code);
+ self.emit('error', error)
+ self.emit('close', error);
+ } else {
+ self.emit('close');
+ }
+ });
+ this._process = sp;
+}
+
+ImapProxy.prototype.write = function(string, encoding) {
+ return this._process.stdin.write(new Buffer(string, encoding));
+}
+
+ImapProxy.prototype.destroy = function() {
+ this._process.kill();
+}
+
+ImapProxy.prototype.end = function() {
+ this._process.kill();
+}
+
+ImapProxy.prototype.connect = function(host, port) {
+ this.emit('connect');
+ this.once('data', function(data) {
+ this.emit('data', data);
+ this.emit('ready');
+ });
+}
+
inherits(ImapConnection, EventEmitter);
exports.ImapConnection = ImapConnection;
@@ -107,26 +160,32 @@ ImapConnection.prototype.connect = function(loginCb) {
state.conn = new Socket();
state.conn.setKeepAlive(true);
- if (this._options.secure) {
- // TODO: support STARTTLS
- state.conn.cleartext = utils.setSecure(state.conn);
- state.conn.on('secure', function() {
- self.debug&&self.debug('[connection] Secure connection made.');
+ if (!this._options.proxy_program) {
+ if (this._options.secure) {
+ // TODO: support STARTTLS
+ state.conn.cleartext = utils.setSecure(state.conn);
+ state.conn.on('secure', function() {
+ self.debug&&self.debug('[connection] Secure connection made.');
+ });
+ } else
+ state.conn.cleartext = state.conn;
+
+ state.conn.on('end', function() {
+ self.debug&&self.debug('[connection] FIN packet received. Disconnecting...');
+ self.emit('end');
});
- } else
+ } else {
+ state.conn = new ImapProxy(this._options.proxy_program, this._options.proxy_program_args);
state.conn.cleartext = state.conn;
-
+ state.conn.start();
+ }
state.conn.on('connect', function() {
clearTimeout(state.tmrConn);
self.debug&&self.debug('[connection] Connected to host.');
state.conn.cleartext.write('');
state.status = STATES.NOAUTH;
});
- state.conn.on('end', function() {
- self.debug&&self.debug('[connection] FIN packet received. Disconnecting...');
- self.emit('end');
- });
function errorHandler(err) {
clearTimeout(state.tmrConn);
@@ -234,7 +293,7 @@ ImapConnection.prototype.connect = function(loginCb) {
indata.expect = (m ? parseInt(m[2], 10) : -1);
if (indata.expect > -1) {
if ((m = /\* (\d+) FETCH/i.exec(indata.line))
- && /^BODY\[/i.test(litType)) {
+ && /^BODY\[/i.test(litType)) { // / (placate emacs)
var msg = new ImapMessage();
msg.seqno = parseInt(m[1], 10);
requests[0]._msg = msg;
@@ -625,9 +684,9 @@ ImapConnection.prototype.connect = function(loginCb) {
state.conn.cleartext.on('data', ondata);
- state.conn.connect(this._options.port, this._options.host);
state.tmrConn = setTimeout(this._fnTmrConn.bind(this, loginCb),
this._options.connTimeout);
+ state.conn.connect(this._options.port, this._options.host);
};
ImapConnection.prototype.isAuthenticated = function() {

0 comments on commit 7b1a037

Please sign in to comment.