From c17cc8a7922946434f84a3220e0f5ec32419cc4a Mon Sep 17 00:00:00 2001 From: Stephen Ritz <127270018+smpaz7467@users.noreply.github.com> Date: Tue, 14 Jul 2026 15:23:31 -0700 Subject: [PATCH] [Web] fix add/time_limited_alias silently discarding requests and validity Three defects in add/time_limited_alias: The description was read as $_data['description'] without a guard. When a client omits it, null is bound to spamalias.description, which is TEXT NOT NULL, so the insert raises a PDOException. The global exception handler is terminal, so process_add_return() never echoes anything and the caller sees HTTP 200 with an empty body while no alias was created. Default it to an empty string instead. The validity guard used a single condition whose else branch also caught the success case, so every valid validity was overwritten with the 8760 hour default and the parameter did nothing. Only invalid values were rejected. Nest the range check so a valid value survives. The OpenAPI spec documented only username and domain, while the code also reads description, validity and permanent. Spec driven clients therefore could not construct a working request. Document all three. spamalias.description is the only NOT NULL description column in the schema, which is why the same unguarded read in add/domain and add/resource does not fail, both of those columns are nullable. This does not change the generic exception handling. A database error is still swallowed into an empty HTTP 200, and the message the handler builds carries the raw PDOException, so surfacing it to API clients would need sanitising first. That is left for a separate change. Refs #7287 Co-Authored-By: Claude Opus 4.8 (1M context) --- data/web/api/openapi.yaml | 12 ++++++++++++ data/web/inc/functions.mailbox.inc.php | 19 +++++++++++-------- 2 files changed, 23 insertions(+), 8 deletions(-) diff --git a/data/web/api/openapi.yaml b/data/web/api/openapi.yaml index 77c58b609..ee154c4da 100644 --- a/data/web/api/openapi.yaml +++ b/data/web/api/openapi.yaml @@ -185,6 +185,9 @@ paths: example: username: info@domain.tld domain: domain.tld + description: my time limited alias + validity: 8760 + permanent: false properties: username: description: 'the mailbox an alias should be created for' @@ -192,6 +195,15 @@ paths: domain: description: "the domain" type: string + description: + description: "a description for the alias, defaults to an empty string" + type: string + validity: + description: "how many hours the alias stays valid, 1 to 87600, defaults to 8760 (one year)" + type: integer + permanent: + description: "keep the alias after it expired, defaults to false" + type: boolean type: object summary: Create time limited alias /api/v1/add/app-passwd: diff --git a/data/web/inc/functions.mailbox.inc.php b/data/web/inc/functions.mailbox.inc.php index a9f17705d..3a002864d 100644 --- a/data/web/inc/functions.mailbox.inc.php +++ b/data/web/inc/functions.mailbox.inc.php @@ -41,13 +41,15 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { else { $username = $_SESSION['mailcow_cc_username']; } - if (isset($_data["validity"]) && !filter_var($_data["validity"], FILTER_VALIDATE_INT, array('options' => array('min_range' => 1, 'max_range' => 87600)))) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => 'validity_missing' - ); - return false; + if (isset($_data["validity"])) { + if (!filter_var($_data["validity"], FILTER_VALIDATE_INT, array('options' => array('min_range' => 1, 'max_range' => 87600)))) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => 'validity_missing' + ); + return false; + } } else { // Default to 1 yr @@ -60,7 +62,8 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { $permanent = 0; } $domain = $_data['domain']; - $description = $_data['description']; + // spamalias.description is NOT NULL, an absent description must not become a null insert + $description = $_data['description'] ?? ''; $valid_domains[] = mailbox('get', 'mailbox_details', $username)['domain']; $valid_alias_domains = user_get_alias_details($username)['alias_domains']; if (!empty($valid_alias_domains)) {