sync: send AS 16.0 draft messages (email2:Send) with an explicit sender - #31
sync: send AS 16.0 draft messages (email2:Send) with an explicit sender#31Squareys wants to merge 2 commits into
Conversation
AS 16.0 clients (e.g. Samsung Email) send a Drafts message via Cmd=Sync with an empty <Send/> tag ([MS-ASEMAIL] 2.2.2.69). The Sync parser did not expect <Send/> after </ApplicationData> and aborted with an uncaught WBXMLException -> HTTP 500; the client reports it as a network error and the message stays silently stuck in the Outbox. lib/request/sync.php: consume the empty email2:Send tag and flag the message for sending. lib/grommunio/importer.php: send the flagged draft. These drafts often carry an empty From, so set PR_SENDER_* / PR_SENT_REPRESENTING_* to the authenticated mailbox owner explicitly (via mapi_createoneoff) before mapi_message_submitmessage, rather than relying on transport auto-fill (SendMail() strips PR_SENT_REPRESENTING_*) which is absent on some gromox versions and would let a From-less message be dropped downstream after the draft was already deleted. The draft is removed only after a successful submit; on failure a StatusException is thrown and the draft is kept.
|
Hi @Squareys, thank you for the contribution. I have a couple of questions/notes though. I'll address them as comments in the relevant parts of code. |
|
|
||
| $response->serverid = $this->prefix . bin2hex((string) $sourcekeyprops[PR_SOURCE_KEY]); | ||
|
|
||
| // AS 16.0+: send the message if the client requested it (draft send). |
There was a problem hiding this comment.
That's very similar to the commit message. In my opinion this comment may be shorter.
There was a problem hiding this comment.
Trimmed it a bit, let me know if that's better.
| // has already been removed => invisible mail loss. Instead set the | ||
| // sender explicitly to the authenticated mailbox owner, so a valid From | ||
| // is guaranteed regardless of the deployed gromox version, then submit. | ||
| if ($message instanceof SyncMail && !empty($message->send)) { |
There was a problem hiding this comment.
Why do it here and not in the MAPIProvider::setEmail()? That would reduce the number of mapi_*() calls.
There was a problem hiding this comment.
No particular reason, just new to the codebase! Thanks for the suggestion, feels much cleaner.
Review feedback on PR grommunio#31: the explicit-sender stamping added to ImportChangesICS->ImportMessageChange() duplicated MAPI work that MAPIProvider->setEmail() already does. Fold the PR_SENDER_* / PR_SENT_REPRESENTING_* assignment into the mapi_setprops() that setEmail already issues, so the send path drops one mapi_setprops() and one mapi_savechanges() (the importer's existing mapi_savechanges() persists the sender before submit). The importer now only submits the flagged draft and removes it after a successful submit; on failure a StatusException is thrown and the draft is kept. Behaviour is unchanged: an AS 16.0 draft send still goes out with the authenticated mailbox owner as From regardless of gromox version.
|
Thanks for the review @gromandreas! I addressed your comments with e31e8e6 Feel free to add further comments, if you notice anything else. |
|
@gromandreas Happy to do another iteration, if you find anything you'd like changed! |
| } | ||
| // a valid one-off ENTRYID is required or the spooler rejects the empty | ||
| // sent-representing the client left behind | ||
| $owneroneoff = mapi_createoneoff($ownername, "SMTP", $owneraddr); |
There was a problem hiding this comment.
In SendMail() in grommunio.php (
grommunio-sync/lib/grommunio/grommunio.php
Line 490 in 0d70954
I'll to do some more testing this week if I get to it. Some of the colleagues are on vacation and I'm pretty swamped with work, sorry if it takes somewhat longer.
Problem
AS 16.0 clients (e.g. Samsung Email) send a message saved in Drafts via a
Cmd=Syncrequest carrying an empty<Send/>tag ([MS-ASEMAIL] §2.2.2.69). The Sync parser didn't expect<Send/>after</ApplicationData>, so it aborted with an uncaughtWBXMLException→ HTTP 500. The client wraps the 500 as a network error, so the message is stuck in the Outbox, silently, and retries fail identically. One-stepCmd=SendMailis unaffected — only the "was in Drafts" path breaks.Fix
lib/request/sync.php— consume the emptyemail2:Sendtag (sibling ofApplicationDatainsideAdd/Change) and flag the message for sending. This alone fixes the 500.lib/grommunio/importer.php— actually send the flagged draft: set the sender (PR_SENDER_*+PR_SENT_REPRESENTING_*) explicitly to the authenticated mailbox owner viamapi_createoneoff, thenmapi_message_submitmessage. The draft is removed only after a successful submit; on failure aStatusExceptionis thrown and the draft is kept.These drafts typically carry an empty From. Setting the sender explicitly — rather than routing through
SendMail(), which stripsPR_SENT_REPRESENTING_*to rely on transport auto-fill — guarantees a valid From regardless of the deployed gromox version or a From-strict MX. Otherwise a sender-less message can be silently dropped downstream after the draft is already deleted (invisible mail loss).Testing
Fromdraft-send delivers withFrom:= the mailbox owner, and the draft is removed only after a successful submit.From, submit called, draft deleted only after success, and draft kept on submit failure. Not included here since the repo has no test suite — happy to add it under atests/dir if you'd like.Reference
[MS-ASEMAIL] §2.2.2.69 —
Sendelement: empty tag; presence = send, absence = save as draft; supported in protocol 16.0 / 16.1.