Das Problem
Du hast in grommunio-web eine Shared Mailbox geöffnet — sagen wir orders@deathstar.lan. Du tippst einen Suchbegriff ein, wählst “Alle Ordner” und bekommst: nichts. Keine Ergebnisse. Nada.
Wechselst du auf “Posteingang”, findest du die Mail sofort.
Das ist kein Indexing-Problem. Der FTS-Index ist vollständig, die SQLite-Datenbank hat die Daten. Das Problem sitzt tiefer.
TL;DR
grommunio-web 3.13 sendet bei “Alle Ordner”-Suche in Shared Stores den Default-Store des eingeloggten Users statt den Shared Store. Der Server durchsucht dadurch den falschen FTS-Index. Fix: PHP-Methode detectCorrectStore() in class.advancedsearchlistmodule.php, die den Store anhand der Folder-Entryid korrigiert.
Die Diagnose
Schritt 1: Ist der Index vollständig?
# Anzahl indizierter Nachrichten
sqlite3 /var/lib/grommunio-web/sqlite-index/<USER>@<DOMAIN>/index.sqlite3 \
"SELECT count(*) FROM messages;"
# Direkte Suche im Index
sqlite3 /var/lib/grommunio-web/sqlite-index/<USER>@<DOMAIN>/index.sqlite3 \
"SELECT count(*) FROM messages WHERE messages MATCH 'suchbegriff';"
Ergebnis: 18 Treffer im Index. Der FTS-Index hat die Daten. Problem liegt nicht hier.
Schritt 2: Debug-Logging in die PHP-Schicht
Temporäres error_log() in /usr/share/grommunio-web/server/includes/core/class.indexsqlite.php:
// Nach der SQL-Query-Generierung:
error_log("INDEXDEBUG: SQL=" . $sql_string);
error_log("INDEXDEBUG: username=" . $this->username);
Und dann in /var/log/php-fpm.log beobachten:
# Posteingang-Suche (funktioniert):
INDEXDEBUG: username=orders@deathstar.lan recursive=false
INDEXDEBUG: linked msg_id=213828 folder=13
INDEXDEBUG: linked msg_id=213819 folder=13
# ... 15 Treffer
# Alle-Ordner-Suche (kaputt):
INDEXDEBUG: username=luke.skywalker@deathstar.lan recursive=true
# ... 0 Treffer
Da ist der Bug: “Alle Ordner” durchsucht den Index von luke.skywalker@ statt orders@.
Schritt 3: Warum der falsche Store?
Weiteres Logging zeigt:
PR_MDB_PROVIDER=5494a1c0... PR_DEFAULT_STORE=true
ServerShortname='luke.skywalker@deathstar.lan'
Der JS-Client sendet store_entryid des Default-Stores (eigene Mailbox), nicht des Shared Stores. Der PHP-Code prüft PR_MDB_PROVIDER == ZARAFA_STORE_DELEGATE_GUID — aber der falsche Store hat natürlich nicht den Delegate-GUID. Also fällt er in den else-Zweig und öffnet new IndexSqlite() ohne Username — was den eingeloggten User nimmt.
Die Ursache
Drei Probleme, die zusammenwirken:
- JS-Client-Bug: Bei “Alle Ordner” in einer Shared Mailbox wird
store_entryiddes Default-Stores gesendet, nicht des Shared Stores - Unzureichende Erkennung: Der PHP-Code verlässt sich auf
PR_MDB_PROVIDER == ZARAFA_STORE_DELEGATE_GUID, was bei falschem Store nicht matcht - Kaskadeneffekt: Nicht nur
search()benutzt den falschen Store — auchupdatesearch()(Polling für Ergebnis-Updates) bekommt ihn viagetActionStore(). Selbst wenn die initiale Suche Treffer findet, zeigt der Client nichts an, weil das Update-Polling den falschen Store abfragt.
Die Lösung
Vendor-Patch: detectCorrectStore()
Die Idee: In der execute()-Methode von class.advancedsearchlistmodule.php den Store korrigieren, bevor irgendeine Search-Operation startet. So profitieren search(), updatesearch() und stopsearch() gleichermaßen.
/**
* Detect if the folder entryid belongs to a shared store and return it.
* Workaround for JS client sending default store for shared mailbox searches.
*/
private function detectCorrectStore($store, $entryid) {
$store_props = mapi_getprops($store, [PR_DEFAULT_STORE]);
if (!isset($store_props[PR_DEFAULT_STORE]) || !$store_props[PR_DEFAULT_STORE]) {
return $store; // Not default store, no correction needed
}
try {
$otherStores = $GLOBALS["mapisession"]->getOtherUserStore();
foreach ($otherStores as $otherUsername => $otherStore) {
$otherProps = mapi_getprops($otherStore, [PR_IPM_SUBTREE_ENTRYID]);
if (!isset($otherProps[PR_IPM_SUBTREE_ENTRYID])) continue;
// Check subtree itself
if ($GLOBALS['entryid']->compareEntryIds(
bin2hex($entryid),
bin2hex($otherProps[PR_IPM_SUBTREE_ENTRYID])
)) {
return $otherStore;
}
// Check all subfolders
$subtree = mapi_msgstore_openentry(
$otherStore, $otherProps[PR_IPM_SUBTREE_ENTRYID]
);
if (!$subtree) continue;
$ht = mapi_folder_gethierarchytable($subtree, CONVENIENT_DEPTH);
$rows = mapi_table_queryallrows($ht, [PR_ENTRYID]);
foreach ($rows as $row) {
if (isset($row[PR_ENTRYID]) &&
$GLOBALS['entryid']->compareEntryIds(
bin2hex($entryid), bin2hex($row[PR_ENTRYID])
)) {
return $otherStore;
}
}
}
} catch (Exception $e) {
// Fall through to original store
}
return $store;
}
In execute() einhängen:
$store = $this->getActionStore($action);
$entryid = $this->getActionEntryID($action);
// Fix: correct store for shared mailbox searches
if (in_array($actionType, ['search', 'updatesearch', 'stopsearch'])) {
$store = $this->detectCorrectStore($store, $entryid);
}
Und die IndexSqlite-Erstellung ebenfalls anpassen — statt nur ZARAFA_STORE_DELEGATE_GUID zu prüfen, den Username aus dem (ggf. korrigierten) Store ableiten:
$storeEid = mapi_getprops($store, [PR_ENTRYID]);
$storeOwner = $GLOBALS["mapisession"]->getUserNameOfStore(
$storeEid[PR_ENTRYID]
);
if ($storeOwner) {
$indexDB = new IndexSqlite(
$storeOwner,
$GLOBALS["mapisession"]->getSession(),
$store
);
} else {
$indexDB = new IndexSqlite();
}
Patch anwenden
Die Datei liegt in:
/usr/share/grommunio-web/server/includes/modules/class.advancedsearchlistmodule.php
Verifikation
# Suche "Alle Ordner" in Shared Mailbox ausführen
# Dann im Log prüfen:
grep "INDEXDEBUG" /var/log/php-fpm.log | tail -5
# Erwartung: username=orders@deathstar.lan (nicht luke.skywalker@)
Lessons Learned
“Funktioniert in Posteingang aber nicht in Alle Ordner” ist ein starkes Signal für einen Store-Mismatch. Einzelordner-Suche und Alle-Ordner-Suche nehmen in grommunio-web komplett unterschiedliche Codepfade.
Debug-Logging in die PHP-Schicht ist bei MAPI-Problemen Gold wert. Die SQLite-Datenbank direkt abfragen bestätigt, dass der Index OK ist — aber der Fehler liegt in der Schicht darüber.
Vendor-Patches dokumentieren. Bei Package-Updates (
zypper up grommunio-web) wird die Datei überschrieben. README-Eintrag + Gitea-Issue als Reminder.Der “Alle Ordner”-Bug betrifft nicht nur die initiale Suche — auch das
updatesearch-Polling bekommt den falschen Store viagetActionStore(). Fix muss inexecute()sitzen, nicht insearch().