Issue 45 Verify Button Only Worked For Microsoft - edmozley/freeitsm GitHub Wiki
The Verify button only ever worked for Microsoft (issue #45)
Click Verify beside the mail folder on a Basic IMAP mailbox and it answered:
Mailbox is not authenticated
The mailbox was authenticated. It was listed as Connected, it was being checked on schedule, and it was collecting mail normally. Only the button disagreed.
Reported by mbsouth on issue #45 β the issue that added Basic IMAP support in the first place, which is exactly what this bug is: somewhere that feature never reached. He had already worked out the important half himself, reporting that mail to and from his Postfix/Dovecot servers was "working fine" and that it was only the warnings he could not identify. He was right on both counts.
Fixed in a1f64426, released as update #1171.
The companion bug reported alongside this one is IMAP mailboxes reported as not authenticated. The general guides are Mailbox authentication and Basic IMAP mailboxes.
1. A button offered on three providers and written for one
verify_mailbox_folder.php was built for Microsoft Graph, back when Graph was the only thing a mailbox could be. Basic IMAP and Google arrived later. The button was shown for all three β the mailbox dialogue hides fields by provider, but the Email folder row carries none of those classes, so every mailbox gets the button β and nothing in the endpoint had ever been taught that the other two exist.
| Provider | What Verify did |
|---|---|
| Microsoft | Worked. The only one it was written for |
| Basic IMAP | Refused at the door, every time, on every install |
| Ran the entire Microsoft flow using Google credentials |
IMAP: refused before it did anything
The first thing the endpoint did was look for an OAuth token:
if (empty($mailbox['token_data'])) {
echo json_encode(['success' => false, 'error' => 'Mailbox is not authenticated']);
exit;
}
token_data holds the OAuth token. A Basic IMAP mailbox does not have one and does not need one β it connects with a stored username and password. No IMAP code path ever writes that column; the only mention of it in save_mailbox.php sets it to NULL.
So the test was not usually false for IMAP, it was structurally false. No credential could ever satisfy it. The failure was deterministic on every install, for every IMAP mailbox, and re-entering the password could not possibly help, because the password was not what was being examined.
The one mercy is that it stopped there. No connection was attempted to anybody's mail server, and nothing was sent to Microsoft.
Google: it got further, and that was worse
A Google mailbox does hold a token β a Google one β so it sailed through the gate and into a function that assumed any token was Microsoft's:
- the access token was sent to
https://graph.microsoft.com/v1.0/..., which rejected it - an expired token was refreshed against
https://login.microsoftonline.com/β¦/oauth2/v2.0/token, usingazure_client_idandazure_client_secretthat a Google mailbox has never had
The user was shown a Microsoft error about a mailbox with nothing to do with Microsoft. Only the identity-mismatch check was properly fenced behind provider === 'microsoft'; everything after it was Microsoft by assumption rather than by decision.
The general lesson, and it is #77's lesson again one turn further on: that write-up ended by saying the same question asked in two places will eventually be answered two different ways. This is what happens when a provider is added and a Microsoft-shaped function is left in the path β the question was never re-asked at all, so two of the three answers were Microsoft's.
2. What was changed
Each provider is verified its own way, and the branch happens before the token gate β which is precisely where IMAP used to die.
Through the same helper the reader uses. IMAP verification opens the folder with imapStreamFor(), the function the inbound poll itself calls. Verify cannot now confirm a folder that reading then rejects, which was the exact failure shape of #77.
A folder that is not found names the folders that are. On a server with an INBOX. personal namespace the real name is INBOX.Support, and Support on its own genuinely will not open β so "not found" without a list is a puzzle rather than an answer:
Folder "NoSuchFolder" was not found in this mailbox.
Folders on the server: INBOX, Drafts, Templates, Snoozed, Sent, Spam, Trash, Archive.
Folder names arrive in IMAP's own modified UTF-7, so they are decoded before being shown β otherwise a folder called Anfragen or Γbergabe comes back mangled in the one message whose whole purpose is to be read.
Connection failures are told apart from missing folders. The connection is made against INBOX, which always exists, so a failure at that point is unambiguously credentials, TLS or the missing PHP extension β not a mistyped folder name.
Gmail is checked against labels, because Gmail has labels. The label is looked up by name, case-insensitively, and a miss lists the labels that exist.
Gmail counts mirror the reader, not the label. gmailGetEmails() always lists with labelIds=INBOX and applies the configured folder as an extra label: filter on top β so it only ever sees mail that is in the Inbox and carries the label. Reporting the label's own message count would have passed a label whose mail is all archived, which reading then collects nothing from. Instead Verify runs the reader's own query, and says so plainly when the label exists but its mail sits outside the Inbox:
The label holds 42 message(s), but none are in the Inbox. FreeITSM only reads mail that is in the Inbox AND labelled "Support", so archived mail carrying this label will not be collected.
That result is shown in amber rather than green, because "found" in green reads as "working".
3. π The files involved
π’ The fix
| File | Role |
|---|---|
includes/mailbox_imap.php |
New: imapVerifyFolder() β connects via imapStreamFor(), matches the folder against the server's own list, returns real message counts. New: imapListFolders() β strips the {host} prefix and decodes modified UTF-7 |
includes/gmail.php |
New: gmailVerifyFolder() β resolves the label, then counts through the reader's own query rather than the label's totals |
π΅ The callers
| File | Role |
|---|---|
api/tickets/verify_mailbox_folder.php |
Branches on provider before the OAuth-token gate; Google refreshes through gmailGetValidAccessToken(), not Microsoft's endpoint. The Microsoft path is untouched |
tickets/settings/index.php |
Renders the Gmail caveat in amber; stopped double-encoding a folder name containing & or a quote (escapeHtml into textContent) |
4. How it was verified
Run live against a real installation carrying all three provider types at once β a Microsoft 365 mailbox, a Gmail mailbox and a Zoho Basic IMAP mailbox β so each result has the other two beside it as controls:
| Check | Result |
|---|---|
IMAP, INBOX |
Found, with counts |
IMAP, a real subfolder (Archive) |
Found |
| IMAP, a folder that does not exist | Named it, and listed the ten real folders on the server β which is itself proof the login succeeded and a genuine IMAP LIST ran |
Google, INBOX |
Found, with counts from the live account |
| Google, a label that does not exist | Named it, and listed the account's real labels |
Microsoft, INBOX |
Unchanged β still resolves to a folder id and reads back |
| Microsoft, unknown folder | Unchanged β still its own "check the spelling, use a slash" message |
The two Microsoft rows are the regression control, and they matter as much as the new ones: the fix had to leave the only provider that already worked exactly as it was.
What is not proven. The mailbox tested is a hosted Zoho account. The reporter's is a self-hosted Postfix/Dovecot pair, which was not available to test against. The defect itself was structural rather than server-specific β the OAuth-token check could not pass for any IMAP server, whoever ran it β but Dovecot commonly presents an INBOX. personal namespace, and that shape was not exercised live. It is precisely the case the folder list in the error message exists to explain.
The IMAP mailbox tested has a flat folder layout, so the delimiter-normalising match (accepting Inbox/Support where the server names it INBOX.Support) was exercised only against flat names. The Gmail out-of-Inbox caveat was reasoned from the reader's query and not reproduced against an account with an archived label. And the PHP IMAP extension was present on the test machine, so the "extension not enabled" message was not seen live β it is raised by imapStreamFor(), which the mail collection has always used.
5. What this means for you
- Verify works on all three provider types now. On IMAP it really opens the folder; on Gmail it really looks the label up.
- If Verify told you a working IMAP mailbox was not authenticated, nothing was wrong with your mailbox and nothing needed reconnecting. Mail was being collected the whole time.
- If a folder is not found, read the list in the message. On many servers the name you want is
INBOX.Something, notSomething. - On Gmail, watch for the amber result. A label whose mail has been archived out of the Inbox will not be collected, and that is worth knowing before you wonder why no tickets appear.
Related pages
- Bugs resolved β the index of write-ups like this one
- IMAP mailboxes reported as not authenticated β the other half of the same report
- Mail could only ever be collected from Inbox β the folder-resolution bug this one rhymes with
- Verifying a mail folder β Developer Guide β how Graph, IMAP and Gmail are each checked, with the code
- Basic IMAP mailboxes Β· Mailbox authentication