From 0ec4cb51f0a1a39f7fe1ad102f5b4f60a28fdf1d Mon Sep 17 00:00:00 2001 From: Samuel Seiz Date: Mon, 31 Aug 2026 07:34:47 +0200 Subject: [PATCH] misc: report the out-of-office subject prefix Out-of-office replies carry the subject stored alongside the OOF message. Clients configuring OOF over EWS cannot store one, because SetUserOofSettings has no field for it, so gromox falls back to gromox.cfg:autoreply_subject_prefix plus the subject of the incoming message. A client that offers an out-of-office form has no way to tell the user what an empty subject will actually send. Expose the directive so it can. The directive is read on each request rather than cached at startup: it is wanted rarely, and an admin editing gromox.cfg should not have to restart the API. An unset directive is reported as configured=false rather than guessed at, because the default that then applies is compiled into the running gromox build and is not readable from here. --- endpoints/misc.py | 18 +++++++++++++++++- res/openapi.yaml | 35 +++++++++++++++++++++++++++++++++++ tools/config.py | 3 ++- tools/mconf.py | 28 ++++++++++++++++++++++++++++ 4 files changed, 82 insertions(+), 2 deletions(-) diff --git a/endpoints/misc.py b/endpoints/misc.py index 9a8dcf0..c8e1211 100644 --- a/endpoints/misc.py +++ b/endpoints/misc.py @@ -12,7 +12,7 @@ from orm import DB from services import Service -from tools import formats +from tools import formats, mconf from tools.tasq import TasQServer @@ -152,3 +152,19 @@ def getDisabledPluginsOfUser(): disabledPlugins = [p[0] for p in disabledPlugins] return jsonify({ "data": disabledPlugins }) + + +@API.route(api.BaseRoute+"/oofSubjectPrefix", methods=["GET"]) +@secure(requireAuth=False) +def getOofSubjectPrefix(): + """Report the gromox.cfg autoreply_subject_prefix directive. + + A client offering an out-of-office form needs it to tell the user what an + empty subject will send. An unset directive is reported as + configured=false rather than guessed at, because the default that then + applies is compiled into the running gromox build. + """ + prefix, error = mconf.readGromoxDirective("autoreply_subject_prefix") + if error is not None: + return jsonify(message=error), 500 + return jsonify({"data": {"prefix": prefix, "configured": prefix is not None}}) diff --git a/res/openapi.yaml b/res/openapi.yaml index 857b442..c39e754 100644 --- a/res/openapi.yaml +++ b/res/openapi.yaml @@ -2293,6 +2293,41 @@ paths: '503': $ref: '#/components/responses/DatabaseError' + /oofSubjectPrefix: + get: + summary: Get the configured out-of-office reply subject prefix + description: > + Reports the gromox.cfg autoreply_subject_prefix directive, which gromox + prepends to the subject of the incoming message when a mailbox has no + out-of-office subject of its own. Clients offering an out-of-office form + use it to show what an empty subject will send. + operationId: oofSubjectPrefix + tags: + - Misc + responses: + '200': + description: Prefix state returned + content: + application/json: + schema: + type: object + properties: + data: + type: object + properties: + prefix: + type: string + nullable: true + description: The configured prefix, or null when the directive is not set + configured: + type: boolean + description: > + Whether the directive is set. When false, gromox applies the + default compiled into the running build, which cannot be read + from the configuration file. + '500': + $ref: '#/components/responses/ServerError' + /domains/{domainID}/users: get: summary: Get lists of users diff --git a/tools/config.py b/tools/config.py index 41ad2ae..1a86778 100644 --- a/tools/config.py +++ b/tools/config.py @@ -96,7 +96,8 @@ def _defaultConfig(): }, "mconf": { "ldapPath": "/etc/gromox/ldap_adaptor.cfg", - "authmgrPath": "/etc/gromox/authmgr.cfg" + "authmgrPath": "/etc/gromox/authmgr.cfg", + "gromoxPath": "/etc/gromox/gromox.cfg" }, "logs": {}, "sync": { diff --git a/tools/mconf.py b/tools/mconf.py index f612ada..1a64525 100644 --- a/tools/mconf.py +++ b/tools/mconf.py @@ -9,6 +9,8 @@ from .misc import setDirectoryOwner, setDirectoryPermission from services import Service +from os.path import exists + import logging logger = logging.getLogger("mconf") @@ -235,6 +237,32 @@ def dumpAuthmgr(conf=None, file=None, reloadServices=False): ############################################################################### +def readGromoxDirective(name): + """Read a single directive from the main gromox configuration file. + + Not cached, so an admin editing gromox.cfg need not restart the API. + gromox trims trailing whitespace off every configuration line and + _loadConf does too, so this returns the value gromox itself sees. + + Returns + ------- + tuple + (value, error). value is None when the directive is not set. + """ + if "gromoxPath" not in Config["mconf"]: + return None, "mconf.gromoxPath not set" + path = Config["mconf"]["gromoxPath"] + if not exists(path): + return None, "'{}' does not exist".format(path) + try: + return _loadConf(path).get(name), None + except Exception as err: + return None, " - ".join((str(arg) for arg in err.args)) + + +############################################################################### + + def load(): error = loadLdap() if error: