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:

  1. JS-Client-Bug: Bei “Alle Ordner” in einer Shared Mailbox wird store_entryid des Default-Stores gesendet, nicht des Shared Stores
  2. Unzureichende Erkennung: Der PHP-Code verlässt sich auf PR_MDB_PROVIDER == ZARAFA_STORE_DELEGATE_GUID, was bei falschem Store nicht matcht
  3. Kaskadeneffekt: Nicht nur search() benutzt den falschen Store — auch updatesearch() (Polling für Ergebnis-Updates) bekommt ihn via getActionStore(). 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

  1. “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.

  2. 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.

  3. Vendor-Patches dokumentieren. Bei Package-Updates (zypper up grommunio-web) wird die Datei überschrieben. README-Eintrag + Gitea-Issue als Reminder.

  4. Der “Alle Ordner”-Bug betrifft nicht nur die initiale Suche — auch das updatesearch-Polling bekommt den falschen Store via getActionStore(). Fix muss in execute() sitzen, nicht in search().