View difference between Paste ID: B2ny6GNq and BGzHa9Nv
SHOW: | | - or go back to the newest paste.
1
// Proxy AUTH requests selectively by domain
2
var sock  = require('./line_socket');
3
var utils = require('./utils');
4
var smtp_regexp = /^([0-9]{3})([ -])(.*)/;
5
 
6
exports.register = function () {
7
    this.inherits('auth/auth_base');
8
};
9
 
10
exports.hook_capabilities = function (next, connection) {
11
    if (connection.using_tls) {
12
        var methods = [ 'PLAIN', 'LOGIN' ];
13
        connection.capabilities.push('AUTH ' + methods.join(' '));
14
        connection.notes.allowed_auth_methods = methods;
15
    }
16
    next();
17
};
18
 
19
exports.check_plain_passwd = function (connection, user, passwd, cb) {
20
    var domain;
21
    if ((domain = /@([^@]+)$/.exec(user))) {
22
        domain = domain[1].toLowerCase();
23
    }
24
    else {
25
        // AUTH user not in user@domain.com format
26
        connection.logerror(this, 'AUTH user="' + user + '" error="not in required format"');
27
        return cb(false);
28
    }
29
 
30
    // Check if domain exists in configuration file
31
    var config = this.config.get('auth_proxy.ini');
32
    if (!config.domains[domain]) {
33
        connection.logerror(this, 'AUTH user="' + user + '" error="domain \'' + domain + '\' is not defined"');
34
        return cb(false);
35
    }
36
 
37
    this.try_auth_proxy(connection, config.domains[domain].split(/[,; ]/), user, passwd, cb);
38
};
39
 
40
exports.try_auth_proxy = function (connection, hosts, user, passwd, cb) {
41
    if (!hosts || (hosts && !hosts.length)) return cb(false);
42
    if (typeof hosts !== 'object') {
43
        hosts = [ hosts ];
44
    }
45
 
46
    var self = this;
47
    var host = hosts.shift();
48
    var methods = [];
49
    var auth_complete = false;
50
    var auth_success = false;
51
    var command = 'connect';
52
    var response = [];
53
    var secure = false;
54
 
55
    var hostport = host.split(/:/);
56
    var socket = sock.connect(((hostport[1]) ? hostport[1] : 25), hostport[0]);
57
    connection.logdebug(self, 'attempting connection to host=' + hostport[0] + ' port=' + ((hostport[1]) ? hostport[1] : 25) + 'with password: ' + passwd + ', and username: ' + user);
58
    socket.setTimeout(30 * 1000);
59
    socket.on('connect', function () {
60
    });
61
    socket.on('close', function () {
62
        if (!auth_complete) {
63
            // Try next host
64
            return self.try_auth_proxy(connection, hosts, user, passwd, cb);
65
        }
66
        connection.loginfo(self, 'AUTH user="' + user + '" host="' + host + '" success=' + auth_success);
67
        return cb(auth_success);
68
    });
69
    socket.on('timeout', function () {
70
        connection.logerror(self, "connection timed out");
71
        socket.end();
72
        // Try next host
73
        return self.try_auth_proxy(connection, hosts, user, passwd, cb);
74
    });
75
    socket.on('error', function (err) {
76
        connection.logerror(self, "connection failed to host " + host + ": " + err);
77
        return self.try_auth_proxy(connection, hosts, user, passwd, cb);
78
    });
79
    socket.send_command = function (cmd, data) {
80
        var line = cmd + (data ? (' ' + data) : '');
81
        if (cmd === 'dot') {
82
            line = '.';
83
        }
84
        connection.logprotocol(self, "C: " + line);
85
        command = cmd.toLowerCase();
86
        this.write(line + "\r\n");
87
        // Clear response buffer from previous command
88
        response = [];
89
    };
90
    socket.on('line', function (line) {
91
        connection.logprotocol(self, "S: " + line);
92
        var matches = smtp_regexp.exec(line);
93
        if (!matches) {
94
            connection.logdebug(self, "Unmatched line: " + line);
95
            return;
96
        }
97
 
98
        var code = matches[1];
99
        var cont = matches[2];
100
        var rest = matches[3];
101
        response.push(rest);
102
 
103
        connection.logdebug(self, 'code=' + code + ' cont="' + cont + '" rest="' + rest + '"' + 'state=' + command);
104
105
        if (cont !== ' ') {
106
            // We have more lines to receive, so we wait until the last one.
107
            return;
108
        }
109
 
110
        connection.logdebug(self, 'command state: ' + command);
111
        if (command === 'ehlo') {
112
            if (code[0] === '5') {
113
                // EHLO command rejected; abort
114
                socket.send_command('QUIT');
115
                return;
116
            }
117
            // Parse CAPABILITIES
118
            var i;
119
            for (i in response) {
120
                if (/^STARTTLS/.test(response[i])) {
121
                    continue;
122
                    if (secure) continue;    // silly remote, we've already upgraded
123
                    var key = self.config.get('tls_key.pem', 'binary');
124
                    var cert = self.config.get('tls_cert.pem', 'binary');
125
                    // Use TLS opportunistically if we found the key and certificate
126
                    if (key && cert) {
127
                        this.on('secure', function () {
128
                            secure = true;
129
                            socket.send_command('EHLO', self.config.get('me'));
130
                        });
131
                        socket.send_command('STARTTLS');
132
                        return;
133
                    }
134
                }
135
                else if (/^AUTH /.test(response[i])) {
136
                    // Parse supported AUTH methods
137
                    var parse = /^AUTH (.+)$/.exec(response[i]);
138
                    methods = parse[1].split(/\s+/);
139
                    connection.logdebug(self, 'found supported AUTH methods: ' + methods);
140
                    // Prefer PLAIN as it's easiest
141
                    if (methods.indexOf('PLAIN') !== -1) {
142
                        socket.send_command('AUTH','PLAIN ' + utils.base64("\0" + user + "\0" + passwd));
143
                        return;
144
                    }
145
                    else if (methods.indexOf('LOGIN') !== -1) {
146
                        socket.send_command('AUTH','LOGIN');
147
                        return;
148
                    }
149
                    else {
150
                        // No compatible methods; abort...
151
                        connection.logdebug(self, 'no compatible AUTH methods');
152
                        socket.send_command('QUIT');
153
                        return;
154
                    }
155
                }
156
            }
157
        }
158
        if (command === 'auth') {
159
            // Handle LOGIN
160
            if (code[0] === '3' && response[0] === 'VXNlcm5hbWU6') {
161
                // Write to the socket directly to keep the state at 'auth'
162
                this.write(utils.base64(user) + "\r\n");
163
                response = [];
164
                return;
165
            }
166
            else if (code[0] === '3' && response[0] === 'UGFzc3dvcmQ6') {
167
                this.write(utils.base64(passwd) + "\r\n");
168
                response = [];
169
                return;
170
            }
171
            if (code[0] === '5') {
172
                // Initial attempt failed; strip domain and retry.
173
                var u;
174
                if ((u = /^([^@]+)@.+$/.exec(user))) {
175
                    user = u[1];
176
                    if (methods.indexOf('PLAIN') !== -1) {
177
                        socket.send_command('AUTH', 'PLAIN ' + utils.base64("\0" + user + "\0" + passwd));
178
                    }
179
                    else if (methods.indexOf('LOGIN') !== -1) {
180
                        socket.send_command('AUTH', 'LOGIN');
181
                    }
182
                    return;
183
                }
184
                else {
185
                    // Don't attempt any other hosts
186
                    auth_complete = true;
187
                }
188
            }
189
        }
190
        if (/^[345]/.test(code)) {
191
            // Got an unhandled error
192
            connection.logdebug(self, 'error: ' + line);
193
            socket.send_command('QUIT');
194
            return;
195
        }
196
        switch (command) {
197
            case 'starttls':
198
                var tls_options = { key: key, cert: cert };
199
                this.upgrade(tls_options);
200
                break;
201
            case 'connect':
202
                socket.send_command('EHLO', self.config.get('me'));
203
                break;
204
            case 'auth':
205
                // AUTH was successful
206
                auth_complete = true;
207
                auth_success = true;
208
                socket.send_command('QUIT');
209
                break;
210
            case 'ehlo':
211
            case 'helo':
212
            case 'quit':
213
                socket.end();
214
                break;
215
            default:
216
                throw new Error("[auth/auth_proxy] unknown command: " + command);
217
        }
218
    });
219
};