Skip to content

Commit 4822f34

Browse files
authored
Merge pull request #113 from readdle/fix/COR-211-imap-login-postcondition
COR-211, COR-212, COR-213, COR-216 - Fix IMAP login and connection lifetime crashes
2 parents 949edaa + 35710cc commit 4822f34

19 files changed

Lines changed: 301 additions & 26 deletions

‎Package.swift‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -265,7 +265,7 @@ var targets: [Target] = [
265265
"unittest.cpp",
266266
"unittest.mm"
267267
],
268-
sources: ["CertificateUtilsTests.swift", "IMAPConnectionLeaseTests.swift", "IMAPIdleCancellationTests.swift", "IMAPInterruptCurrentCommandTests.swift", "LeaseTestTCPEndpoint.swift", "LibetpanHelperTests.swift", "unittest.swift"],
268+
sources: ["CertificateUtilsTests.swift", "IMAPConnectionLeaseTests.swift", "IMAPConnectionOwnerLifetimeTests.swift", "IMAPIdleCancellationTests.swift", "IMAPInterruptCurrentCommandTests.swift", "IMAPLoginTests.swift", "LeaseTestTCPEndpoint.swift", "LibetpanHelperTests.swift", "unittest.swift"],
269269
resources: [
270270
.copy("data")
271271
]

‎src/async/imap/MCIMAPAsyncConnection.cpp‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -277,6 +277,10 @@ IMAPOperation * IMAPAsyncConnection::disconnectOperation()
277277
{
278278
IMAPDisconnectOperation * op = new IMAPDisconnectOperation();
279279
op->setSession(this);
280+
// Retains the owner like every operation the owner creates: this connection reaches it through
281+
// a raw pointer on each queue start and stop, and the queue's own retain is released before
282+
// a queued operation restarts it.
283+
op->setMainSession(mOwner);
280284
op->autorelease();
281285
return op;
282286
}
@@ -296,6 +300,11 @@ bool IMAPAsyncConnection::needsReconnect()
296300
return mSession->needsReconnect();
297301
}
298302

303+
void IMAPAsyncConnection::scheduleReconnect()
304+
{
305+
mSession->scheduleReconnect();
306+
}
307+
299308
unsigned int IMAPAsyncConnection::operationsCount()
300309
{
301310
return mQueue->count();

‎src/async/imap/MCIMAPAsyncConnection.h‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,9 @@ namespace mailcore {
174174
// (see IMAPSession::needsReconnect). Declared last on purpose: this class is exported, and
175175
// a virtual inserted among the existing ones would shift every vtable slot after it.
176176
virtual bool needsReconnect();
177+
// Makes the next command on this connection rebuild it first; same contract as
178+
// IMAPSession::scheduleReconnect(). Declared last, like needsReconnect().
179+
virtual void scheduleReconnect();
177180
};
178181

179182
}

‎src/async/imap/MCIMAPOperation.h‎

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -50,9 +50,11 @@ namespace mailcore {
5050
the operations queued behind it (a disconnect, most importantly) run immediately.
5151
Does nothing when the operation is not the one running.
5252
53-
Teardown of this connection only - it is left unusable and reconnects on next use, so call
54-
it for a command that is being abandoned (cancelled, or given up on), never to hurry up a
55-
command whose result still matters.
53+
Teardown of this connection only - the cut command fails with a connection error and the
54+
connection is rebuilt before the one after; landing with no command on the wire, it is the
55+
next command that fails and the one after that rebuilds - so call it for a command that is
56+
being abandoned (cancelled, or given up on), never to hurry up a command whose result still
57+
matters.
5658
Returns whether this operation was the one the queue was running at that moment. That may
5759
include a command that finished just as the interrupt landed: its result is intact, but
5860
the stream is cancelled all the same. */

‎src/c/imap/CIMAPAsyncConnection.cpp‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,3 +16,4 @@ C_SYNTHESIZE_FUNC_WITH_SCALAR(unsigned int, leaseGeneration)
1616
C_SYNTHESIZE_FUNC_WITH_SCALAR(unsigned int, operationsCount)
1717
C_SYNTHESIZE_FUNC_WITH_SCALAR(double, lastLoginTime)
1818
C_SYNTHESIZE_FUNC_WITH_OBJ(CIMAPBaseOperation, disconnectOperation)
19+
C_SYNTHESIZE_FUNC_WITH_VOID(scheduleReconnect)

‎src/c/imap/CIMAPAsyncConnection.h‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ extern "C" {
2525
C_SYNTHESIZE_READONLY_PROPERTY_DEFINITION(CIMAPAsyncConnection, double, lastLoginTime)
2626

2727
C_SYNTHESIZE_FUNC_DEFINITION(CIMAPAsyncConnection, CIMAPBaseOperation, disconnectOperation)
28+
C_SYNTHESIZE_FUNC_DEFINITION(CIMAPAsyncConnection, void, scheduleReconnect)
2829

2930
#ifdef __cplusplus
3031
}

‎src/core/imap/MCIMAPSession.cpp‎

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1116,6 +1116,15 @@ void IMAPSession::login(ErrorCode * pError)
11161116
else {
11171117
// TODO: namespace should be shared with other sessions for non automatic namespace.
11181118
}
1119+
1120+
// identity() starts with connectIfNeeded(): with mShouldDisconnect raised meanwhile it tears the
1121+
// connection down and rebuilds it, and its result is ignored above. A rebuilt connection is not
1122+
// logged in - and a rebuild that failed has no mImap at all - so this must not read as a
1123+
// successful login; nor may one whose ignored ID left the stream marked for teardown.
1124+
if (mState != STATE_LOGGEDIN || mShouldDisconnect) {
1125+
* pError = ErrorConnection;
1126+
return;
1127+
}
11191128

11201129
mAutomaticConfigurationDone = true;
11211130

@@ -3747,11 +3756,10 @@ void IMAPSession::interruptCurrentCommand()
37473756
LOCK();
37483757
if (mImap != NULL && mImap->imap_stream != NULL) {
37493758
mailstream_cancel(mImap->imap_stream);
3750-
// libetpan never clears a stream's cancelled state: every read and write on it fails from
3751-
// here on. The next command must reconnect, and connectIfNeeded does that for this flag,
3752-
// so the connection stays pooled and heals on its own instead of relying on the caller
3753-
// to tear it down.
3754-
mShouldDisconnect = true;
3759+
// Deliberately not raising mShouldDisconnect here: the command this cuts fails with a
3760+
// stream error and raises it itself, at a point its caller checks. Raised from this thread
3761+
// it can land between two commands of one login(), where the nested connectIfNeeded()
3762+
// would rebuild the connection under a caller that ignores the result.
37553763
}
37563764
UNLOCK();
37573765
}
@@ -4432,6 +4440,11 @@ bool IMAPSession::needsReconnect()
44324440
return mState == STATE_DISCONNECTED || mShouldDisconnect;
44334441
}
44344442

4443+
void IMAPSession::scheduleReconnect()
4444+
{
4445+
mShouldDisconnect = true;
4446+
}
4447+
44354448
double IMAPSession::lastLoginTime()
44364449
{
44374450
LOCK();

‎src/core/imap/MCIMAPSession.h‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -260,6 +260,11 @@ namespace mailcore {
260260
// Declared last: this class is exported, and a virtual inserted among the existing ones
261261
// would shift every vtable slot after it.
262262
virtual bool needsReconnect();
263+
// Makes the next command rebuild the connection first, as a failed command would have.
264+
// Any thread, but between operations only: raised while one runs, it is met by the next
265+
// connectIfNeeded() inside that operation - login() then fails with ErrorConnection.
266+
// Declared last, for the same reason as needsReconnect().
267+
virtual void scheduleReconnect();
263268

264269
private:
265270
String * mHostname;
@@ -309,6 +314,7 @@ namespace mailcore {
309314
MCB_LOCK_TYPE mIdleLock;
310315
// Written on this session's own thread, read by IMAPAsyncSession's connection selection
311316
// through IMAPAsyncConnection::needsReconnect: atomic so that read is defined.
317+
// mShouldDisconnect has one more writer, scheduleReconnect(), on any thread.
312318
std::atomic<int> mState;
313319
double mLastLoginTime;
314320
mailimap * mImap;

‎src/include/MailCore/CIMAPAsyncConnection.h‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ extern "C" {
2525
C_SYNTHESIZE_READONLY_PROPERTY_DEFINITION(CIMAPAsyncConnection, double, lastLoginTime)
2626

2727
C_SYNTHESIZE_FUNC_DEFINITION(CIMAPAsyncConnection, CIMAPBaseOperation, disconnectOperation)
28+
C_SYNTHESIZE_FUNC_DEFINITION(CIMAPAsyncConnection, void, scheduleReconnect)
2829

2930
#ifdef __cplusplus
3031
}

‎src/include/MailCore/MCIMAPAsyncConnection.h‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,9 @@ namespace mailcore {
174174
// (see IMAPSession::needsReconnect). Declared last on purpose: this class is exported, and
175175
// a virtual inserted among the existing ones would shift every vtable slot after it.
176176
virtual bool needsReconnect();
177+
// Makes the next command on this connection rebuild it first; same contract as
178+
// IMAPSession::scheduleReconnect(). Declared last, like needsReconnect().
179+
virtual void scheduleReconnect();
177180
};
178181

179182
}

0 commit comments

Comments
 (0)