Skip to content
This repository has been archived by the owner on Nov 3, 2021. It is now read-only.

[B2G][Email] POP3 account creation hangs when an incorrect password is used with a POP3 server that immediately disconnects on incorrect password (ex: aol.com POP3 server) #283

Merged
merged 1 commit into from Jan 22, 2014
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
5 changes: 5 additions & 0 deletions data/lib/pop3/pop3.js
Expand Up @@ -195,6 +195,11 @@ function(module, exports, log, net, crypto,
});
}.bind(this));

this.socket.on('close', function() {
this.protocol.onclose();
this.die();
}.bind(this));

// To track requests/responses in the presence of a server
// greeting, store an empty request here. Our request/response
// matching logic will pair the server's greeting with this
Expand Down
36 changes: 36 additions & 0 deletions data/lib/pop3/transport.js
Expand Up @@ -236,6 +236,16 @@ define(['exports'], function(exports) {
this.command + (this.args.length ? ' ' + this.args.join(' ') : '') + '\r\n');
}

/**
* Trigger the response callback with '-ERR desc\r\n'.
*/
Request.prototype._respondWithError = function(desc) {
var rsp = new Response([textEncoder.encode(
'-ERR ' + desc + '\r\n')], false);
rsp.request = this;
this.onresponse(rsp, null);
}

/**
* Couple a POP3 parser with a request/response model, such that
* you can easily hook Pop3Protocol up to a socket (or other
Expand All @@ -253,6 +263,7 @@ define(['exports'], function(exports) {
this.unsentRequests = []; // if not pipelining, queue requests one at a time
this.pipeline = false;
this.pendingRequests = [];
this.closed = false;
}

exports.Response = Response;
Expand Down Expand Up @@ -280,6 +291,12 @@ define(['exports'], function(exports) {
} else {
req = new Request(cmd, args, expectMultiline, cb);
}

if (this.closed) {
req._respondWithError('(request sent after connection closed)');
return;
}

if (this.pipeline || this.pendingRequests.length === 0) {
this.onsend(req.toByteArray());
this.pendingRequests.push(req);
Expand Down Expand Up @@ -323,4 +340,23 @@ define(['exports'], function(exports) {
}
}
}

/**
* Call this function when the socket attached to this protocol is
* closed. Any current requests that have been enqueued but not yet
* responded to will be sent a dummy "-ERR" response, indicating
* that the underlying connection closed without actually
* responding. This avoids the case where we hang if we never
* receive a response from the server.
*/
Pop3Protocol.prototype.onclose = function() {
this.closed = true;
var requestsToRespond = this.pendingRequests.concat(this.unsentRequests);
this.pendingRequests = [];
this.unsentRequests = [];
for (var i = 0; i < requestsToRespond.length; i++) {
var req = requestsToRespond[i];
req._respondWithError('(connection closed, no response)');
}
}
});
2 changes: 1 addition & 1 deletion package.json
Expand Up @@ -23,7 +23,7 @@
"url": "https://github.com/mozilla-b2g/gaia-email-libs-and-more/issues"
},
"devDependencies": {
"mail-fakeservers": "~0.0.12",
"mail-fakeservers": "~0.0.13",
"mozilla-download": "~0.4"
}
}
8 changes: 5 additions & 3 deletions test/unit/resources/fault_injecting_socket.js
Expand Up @@ -225,8 +225,10 @@ FawltySocket.prototype = {
// from it.
this._queueEvent('close');
this._queueEvent('end');
this._sock.end();
this._sock = null;
if (this._sock) {
this._sock.end();
this._sock = null;
}
FawltySocketFactory.__deadSocket(this);
break;
case 'detach':
Expand Down Expand Up @@ -274,7 +276,7 @@ FawltySocket.prototype = {

if (!this._sock) {
sendText = new TextDecoder('utf-8').decode(data);
console.log('Ignoring send beacuse no sock or watch:', sendText);
console.log('Ignoring send because no sock or watch:', sendText);
return null;
}

Expand Down
7 changes: 7 additions & 0 deletions test/unit/resources/th_fake_pop3_server.js
Expand Up @@ -289,6 +289,13 @@ var TestFakePOP3ServerMixins = {
credentials: newCreds
});
},

setDropOnAuthFailure: function(dropOnAuthFailure) {
return this._backdoor({
command: 'setDropOnAuthFailure',
dropOnAuthFailure: dropOnAuthFailure
});
}
};


Expand Down
48 changes: 48 additions & 0 deletions test/unit/test_account_bad_password_error.js
Expand Up @@ -49,6 +49,16 @@ TD.commonCase('reports bad password', function(T, RT) {
});
}

/**
* Set whether or not the server will drop the connection after
* failed authentication.
*/
function setDropOnAuthFailure(dropOnAuthFailure) {
T.action('set dropOnAuthFailure = ', dropOnAuthFailure.toString(),
eCheck, function() {
testAccount.testServer.setDropOnAuthFailure(dropOnAuthFailure);
});
}

T.group('use bad password on initial connect');
changeServerPassword('newPassword1', 'mismatch');
Expand Down Expand Up @@ -80,6 +90,44 @@ TD.commonCase('reports bad password', function(T, RT) {

}).timeoutMS = 5000;

if (testAccount.type === 'pop3') {
T.group('pop3 handles connection drop on auth failure');
// clear problems from the previous failure so that we still
// receive proper onbadlogin events below
T.action('clear account problems', eCheck, function() {
var acct = testUniverse.allAccountsSlice.items[0];
acct.clearProblems();
});

setDropOnAuthFailure(true);
changeServerPassword('newPassword1', 'mismatch');
T.action('create connection, should fail, generate MailAPI event',
eCheck, testAccount.eBackoff, function() {
eCheck.expect_namedValue('accountCheck:err', true);
eCheck.expect_namedValue('account:enabled', false);
eCheck.expect_namedValue('account:problems',
['bad-user-or-pass', 'connection']);
eCheck.expect_event('badlogin');

testUniverse.MailAPI.onbadlogin = function(acct) {
eCheck.event('badlogin');
};

testAccount.folderAccount.checkAccount(function(err) {
eCheck.namedValue('accountCheck:err', !!err);
eCheck.namedValue('account:enabled',
testAccount.folderAccount.enabled);
eCheck.namedValue('account:problems',
(testAccount.compositeAccount ||
testAccount.folderAccount).problems);
});

}).timeoutMS = 5000;

// reset it back to normal
setDropOnAuthFailure(false);
}

T.group('use good password on initial connect');
changeClientPassword('newPassword1', 'match');

Expand Down
48 changes: 48 additions & 0 deletions test/unit/test_incoming_prober.js
Expand Up @@ -381,6 +381,54 @@ TD.commonCase('POP3 selects preferredAuthMethod', function(T, RT) {
});


/**
* Some servers (ex: aol.com) will hang-up on us on an auth error with a bad
* password.
*/
TD.commonCase('POP3 bad creds on server that hangs up', function(T, RT) {
if (RT.envOptions.type !== 'pop3') { return; }

$th_main.thunkConsoleForNonTestUniverse();
var eCheck = T.lazyLogger('check'),
prober = null;

var fireTimeout = thunkTimeouts(eCheck);
var cci = makeCredsAndConnInfo();

cci.connInfo.port = PORT;

T.action(eCheck, 'hangup gives bad-user-or-pass', function() {
var precommands = [];
precommands.push({
match: /AUTH/,
actions: [
{
cmd: 'fake-receive',
data: '-ERR\r\n',
},
{
cmd: 'instant-close',
}
],
});
FawltySocketFactory.precommand(
HOST, PORT,
{
cmd: 'fake',
data: '+OK hey\r\n',
},
precommands);
eCheck.expect_namedValue('incoming:setTimeout', proberTimeout(RT));
prober = new (proberClass(RT))(cci.credentials, cci.connInfo, eCheck._logger);
prober.onresult = function(err, conn) {
eCheck.namedValue('err', err);
};
eCheck.expect_event('incoming:clearTimeout');
eCheck.expect_namedValue('err', 'bad-user-or-pass');
});
});


var KEEP_ALIVE_TIMEOUT_MS = 10000;

function cannedLoginTest(T, RT, opts) {
Expand Down