Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

normalize proxy properties #1650

Merged
merged 2 commits into from Oct 10, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
38 changes: 24 additions & 14 deletions connection.js
Expand Up @@ -146,9 +146,9 @@ function setupClient (self) {
if (ha_list.some(function (element, index, array) {
return ipaddr.parse(self.remote.ip).match(element[0], element[1]);
})) {
self.proxy = true;
self.proxy.allowed = true;
// Wait for PROXY command
self.proxy_timer = setTimeout(function () {
self.proxy.timer = setTimeout(function () {
self.respond(421, 'PROXY timeout', function () {
self.disconnect();
});
Expand All @@ -164,7 +164,6 @@ function Connection (client, server) {
this.server = server;
this.local = { // legacy property locations
ip: null, // c.local_ip
proxy: null,
port: null, // c.local_port
host: null,
};
Expand All @@ -187,6 +186,12 @@ function Connection (client, server) {
cipher: {},
authorized: null,
};
this.proxy = {
allowed: false, // c.proxy
ip: null, // c.haproxy_ip
type: null,
timer: null, // c.proxy_timer
};
this.set('tls', 'enabled', (server.has_tls ? true : false));

this.current_data = null;
Expand Down Expand Up @@ -223,15 +228,12 @@ function Connection (client, server) {
tempfail: 0,
reject: 0,
};
this.proxy = false;
this.proxy_timer = false;
this.max_line_length = config.get('max_line_length') || 512;
this.max_data_line_length = config.get('max_data_line_length') || 992;
this.results = new ResultStore(this);
this.errors = 0;
this.last_rcpt_msg = null;
this.hook = null;
this.haproxy_ip = null;
this.header_hide_version = config.get('header_hide_version') ? true : false;
setupClient(this);
}
Expand All @@ -245,7 +247,8 @@ exports.createConnection = function(client, server) {

Connection.prototype.set = function (obj, prop, val) {
if (!this[obj]) this.obj = {}; // initialize
this[obj][prop] = val;

this[obj][prop] = val; // normalized propery location

// sunset 3.0.0
if (obj === 'hello' && prop === 'verb') {
Expand All @@ -254,6 +257,9 @@ Connection.prototype.set = function (obj, prop, val) {
else if (obj === 'tls' && prop === 'enabled') {
this.using_tls = val;
}
else if (obj === 'proxy' && prop === 'ip') {
this.haproxy_ip = val;
}
else {
this[obj + '_' + prop] = val;
}
Expand Down Expand Up @@ -395,16 +401,18 @@ Connection.prototype._process_data = function() {
}
var this_line = this.current_data.slice(0, offset+1);
// Hack: bypass this code to allow HAProxy's PROXY extension
if (this.state === states.STATE_PAUSE && this.proxy && /^PROXY /.test(this_line)) {
if (this.proxy_timer) clearTimeout(this.proxy_timer);
if (this.state === states.STATE_PAUSE &&
this.proxy.allowed &&
/^PROXY /.test(this_line)) {
if (this.proxy.timer) clearTimeout(this.proxy.timer);
this.state = states.STATE_CMD;
this.current_data = this.current_data.slice(this_line.length);
this.process_line(this_line);
}
// Detect early_talker but allow PIPELINING extension (ESMTP)
else if ((this.state === states.STATE_PAUSE || this.state === states.STATE_PAUSE_SMTP) && !this.esmtp) {
// Allow EHLO/HELO to be pipelined with PROXY
if (this.proxy && /^(?:EH|HE)LO /i.test(this_line)) return;
if (this.proxy.allowed && /^(?:EH|HE)LO /i.test(this_line)) return;
if (!this.early_talker) {
this_line = this_line.toString().replace(/\r?\n/,'');
this.logdebug('[early_talker] state=' + this.state + ' esmtp=' + this.esmtp + ' line="' + this_line + '"');
Expand Down Expand Up @@ -1145,13 +1153,13 @@ Connection.prototype.rcpt_respond = function(retval, msg) {
Connection.prototype.cmd_proxy = function (line) {
var self = this;

if (!this.proxy) {
if (!this.proxy.allowed) {
this.respond(421, 'PROXY not allowed from ' + this.remote.ip);
return this.disconnect();
}

var match;
if (!(match = /(TCP4|TCP6|UNKNOWN) (\S+) (\S+) (\d+) (\d+)$/.exec(line))) {
var match = /(TCP4|TCP6|UNKNOWN) (\S+) (\S+) (\d+) (\d+)$/.exec(line);
if (!match) {
this.respond(421, 'Invalid PROXY format');
return this.disconnect();
}
Expand All @@ -1160,6 +1168,7 @@ Connection.prototype.cmd_proxy = function (line) {
var dst_ip = match[3];
var src_port = match[4];
var dst_port = match[5];

// Validate source/destination IP
switch (proto) {
case 'TCP4':
Expand All @@ -1182,7 +1191,8 @@ Connection.prototype.cmd_proxy = function (line) {
' dst_ip=' + dst_ip + ':' + dst_port);

this.reset_transaction(function () {
self.haproxy_ip = self.remote.ip;
self.set('proxy', 'ip', self.remote.ip);
self.set('proxy', 'type', 'haproxy');
self.relaying = false;
self.set('local', 'ip', dst_ip);
self.set('local', 'port', parseInt(dst_port, 10));
Expand Down
43 changes: 16 additions & 27 deletions docs/Connection.md
Expand Up @@ -10,41 +10,30 @@ API

A unique UUID for this connection.

* connection.remote.ip
* connection.remote - info about the host that is connecting to Haraka.

The remote IP address
* ip - remote IP address
* host - reverse DNS of the remote hosts IP
* is_private - true if the remote IP is from a private (loopback, RFC 1918, link local, etc.) IP address.

* connection.remote.host
* connection.local - info about the host that is running Haraka

The rDNS of the remote IP
* ip - the IP of the Haraka server, as reported by the OS
* port - the port number handling the connection.
* host - the rDNS host name of the local IP

* connection.local.ip
* connection.proxy - proxy properties set when a proxy is used (like haproxy)
* allowed - if the remote IP has proxy permission
* ip - when proxied, the proxy servers IP address
* type - currently null or 'haproxy'

The bound IP address of the server as reported by the OS

* connection.local.port

The bound port number of the server which is handling the connection.
If you have specified multiple listen= ports this variable is useful
if you only want a plugin to run when connections are made to a specific
port

* connection.haproxy\_ip

If the connection is being proxied by HAProxy, this variable will
contain the remote IP address of the HAProxy host.

* connection.hello.verb

Either 'EHLO' or 'HELO' whichever the remote end used

* connection.hello.host

The hostname given to HELO or EHLO
* connection.hello
* verb - Either 'EHLO' or 'HELO' whichever the remote end used
* host - The hostname given with HELO or EHLO

* connection.notes

A safe object in which you can store connection-specific variables
An object which persists during the lifetime of the connection. It is used to store connection-specific properties. See also, connection.results.

* connection.transaction

Expand Down
27 changes: 26 additions & 1 deletion tests/connection.js
Expand Up @@ -53,7 +53,6 @@ exports.connectionRaw = {
test.deepEqual(this.connection.local, {
ip: null,
port: null,
proxy: null,
host: null,
});
// backwards compat, sunset v3.0.0
Expand Down Expand Up @@ -146,7 +145,33 @@ exports.connectionRaw = {
test.equal(false, this.connection.remote.is_private);
test.done();
},
'has legacy proxy property set' : function (test) {
test.expect(1);
this.connection.set('proxy', 'ip', '172.16.15.1');
test.equal('172.16.15.1', this.connection.haproxy_ip);
test.done();
},
'has normalized proxy properties, default' : function (test) {
test.expect(4);
test.equal(false, this.connection.proxy.allowed);
test.equal(null, this.connection.proxy.ip);
test.equal(null, this.connection.proxy.type);
test.equal(null, this.connection.proxy.timer);
test.done();
},
'has normalized proxy properties, set' : function (test) {
test.expect(4);
this.connection.set('proxy', 'ip', '172.16.15.1');
this.connection.set('proxy', 'type', 'haproxy');
this.connection.set('proxy', 'timer', setTimeout(function() {}, 1000));
this.connection.set('proxy', 'allowed', true);

test.equal(true, this.connection.proxy.allowed);
test.equal('172.16.15.1', this.connection.proxy.ip);
test.ok(this.connection.proxy.timer);
test.equal(this.connection.proxy.type, 'haproxy');
test.done();
},
/*
'max_data_exceeded_respond' : function (test) {
test.expect(1);
Expand Down