From 9f3403f12c3a254b3fbd7888a957c289d9d75980 Mon Sep 17 00:00:00 2001 From: Mike Williams Date: Tue, 21 Oct 2025 15:52:09 +0200 Subject: [PATCH 01/32] ews: add room list entry and room type structures Add room list entry and room type EWS structures together with serialization support so GetRoomLists/GetRooms payloads can be produced. References: GXL-621 --- exch/ews/serialization.cpp | 28 ++++++++++++++++ exch/ews/structures.hpp | 65 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 93 insertions(+) diff --git a/exch/ews/serialization.cpp b/exch/ews/serialization.cpp index 7cf424640..8757ab0a8 100644 --- a/exch/ews/serialization.cpp +++ b/exch/ews/serialization.cpp @@ -905,6 +905,15 @@ void tEmailAddressDictionaryEntry::serialize(tinyxml2::XMLElement* xml) const XMLDUMPA(MailboxType); } +tRoomType::tRoomType(const tinyxml2::XMLElement* xml) : + XMLINIT(Id) +{} + +void tRoomType::serialize(tinyxml2::XMLElement* xml) const +{ + XMLDUMPT(Id); +} + tFileAttachment::tFileAttachment(const XMLElement *xml) { if (const XMLElement *xp = xml->FirstChildElement("Name")) @@ -1834,6 +1843,25 @@ void mGetMailTipsResponse::serialize(XMLElement* xml) const XMLDUMPM(ResponseMessages); } +mGetRoomListsRequest::mGetRoomListsRequest(const XMLElement*) +{} + +void mGetRoomListsResponse::serialize(XMLElement* xml) const +{ + mResponseMessageType::serialize(xml); + XMLDUMPM(RoomLists); +} + +mGetRoomsRequest::mGetRoomsRequest(const XMLElement* xml) : + XMLINIT(RoomList) +{} + +void mGetRoomsResponse::serialize(XMLElement* xml) const +{ + mResponseMessageType::serialize(xml); + XMLDUMPM(Rooms); +} + mGetServiceConfigurationRequest::mGetServiceConfigurationRequest(const XMLElement* xml) : XMLINIT(ActingAs), XMLINIT(RequestedConfiguration) {} diff --git a/exch/ews/structures.hpp b/exch/ews/structures.hpp index 0e9910404..3a96a497c 100644 --- a/exch/ews/structures.hpp +++ b/exch/ews/structures.hpp @@ -728,6 +728,29 @@ struct tEmailAddressDictionaryEntry : public NS_EWS_Types { std::optional MailboxType; //Attribute }; +/** + * Types.xsd:312 + */ +struct tRoomListEntry : public tEmailAddressType { + static constexpr char NAME[] = "Address"; + + using tEmailAddressType::tEmailAddressType; +}; + +/** + * Types.xsd:320 + */ +struct tRoomType : public NS_EWS_Types { + static constexpr char NAME[] = "Room"; + + tRoomType() = default; + explicit tRoomType(const tinyxml2::XMLElement*); + + void serialize(tinyxml2::XMLElement*) const; + + std::optional Id; +}; + /** * Types.xsd */ @@ -3487,6 +3510,48 @@ struct mGetMailTipsResponse : public mResponseMessageType { void serialize(tinyxml2::XMLElement*) const; }; +/** + * Messages.xsd:2685 + */ +struct mGetRoomListsRequest { + explicit mGetRoomListsRequest(const tinyxml2::XMLElement*); +}; + +/** + * Messages.xsd:2696 + */ +struct mGetRoomListsResponse : public mResponseMessageType { + static constexpr char NAME[] = "GetRoomListsResponse"; + + using mResponseMessageType::success; + + std::optional> RoomLists; + + void serialize(tinyxml2::XMLElement*) const; +}; + +/** + * Messages.xsd:2709 + */ +struct mGetRoomsRequest { + explicit mGetRoomsRequest(const tinyxml2::XMLElement*); + + tEmailAddressType RoomList; +}; + +/** + * Messages.xsd:2723 + */ +struct mGetRoomsResponse : public mResponseMessageType { + static constexpr char NAME[] = "GetRoomsResponse"; + + using mResponseMessageType::success; + + std::optional> Rooms; + + void serialize(tinyxml2::XMLElement*) const; +}; + /** * Messages.xsd:2815 */ From a1d40a7e7685f4887a8f8773c454d9c932cebb8b Mon Sep 17 00:00:00 2001 From: Mike Williams Date: Tue, 21 Oct 2025 15:52:18 +0200 Subject: [PATCH 02/32] ews: implement GetRoomLists and GetRooms handlers Implement SQL-backed helpers and new GetRoomLists/GetRooms handlers that gather visible resource mailboxes and return them over EWS. Also optimize organization validation by directly comparing org_ids instead of making an additional database call. References: GXL-621 --- exch/ews/requests.cpp | 142 ++++++++++++++++++++++++++++++++++++++++++ exch/ews/requests.hpp | 2 + 2 files changed, 144 insertions(+) diff --git a/exch/ews/requests.cpp b/exch/ews/requests.cpp index 98a4f9463..bbea57bf8 100644 --- a/exch/ews/requests.cpp +++ b/exch/ews/requests.cpp @@ -180,6 +180,75 @@ static bool ab_tree_resolvename(const ab_tree::ab_base &base, const char *needle return false; } +static std::string extract_domain(const char *address) +{ + if (address == nullptr) + return {}; + const char *at = strchr(address, '@'); + if (at == nullptr || at[1] == '\0') + return {}; + std::string domain(at + 1); + return tolower_inplace(domain); +} + +static void resolve_domain_ids(const std::string& domain, unsigned int& domain_id, unsigned int& org_id) +{ + if (!mysql_adaptor_get_domain_ids(domain.c_str(), &domain_id, &org_id)) + throw DispatchError(E3027); +} + +static bool is_visible_room(const sql_user& user) +{ + return user.dtypx == DT_ROOM && !(user.cloak_bits & AB_HIDE_FROM_GAL) && !user.username.empty(); +} + +static tRoomType make_room(const sql_user& user) +{ + tRoomType room; + auto &id = room.Id.emplace(); + auto it = user.propvals.find(PR_DISPLAY_NAME); + if (it != user.propvals.end() && !it->second.empty()) + id.Name = it->second; + else + id.Name = user.username; + id.EmailAddress = user.username; + id.RoutingType = "SMTP"; + id.MailboxType = Enum::MailboxTypeType(Enum::Mailbox); + return room; +} + +static bool collect_rooms(unsigned int domain_id, std::vector* rooms=nullptr) +{ + std::vector users; + if (!mysql_adaptor_get_domain_users(domain_id, users)) + throw DispatchError(E3027); + bool found = false; + if (rooms) { + rooms->clear(); + rooms->reserve(users.size()); + } + for (const auto &user : users) { + if (!is_visible_room(user)) + continue; + found = true; + if (rooms) + rooms->emplace_back(make_room(user)); + else + break; + } + return found; +} + +static tRoomListEntry make_room_list_entry(const sql_domain& domain) +{ + tRoomListEntry entry; + entry.EmailAddress = std::string("rooms@") + domain.name; + entry.RoutingType = "SMTP"; + entry.MailboxType = Enum::MailboxTypeType(Enum::PublicDL); + entry.Name = domain.title.empty() ? domain.name : domain.title; + return entry; +} + } //anonymous namespace /////////////////////////////////////////////////////////////////////// //Request implementations @@ -1093,6 +1162,79 @@ void process(mGetMailTipsRequest&& request, XMLElement* response, const EWSConte data.serialize(response); } +/** + * @brief Process GetRoomListsRequest + */ +void process(mGetRoomListsRequest&&, XMLElement* response, const EWSContext& ctx) +{ + response->SetName("m:GetRoomListsResponse"); + + auto user_domain = extract_domain(ctx.auth_info().username); + if (user_domain.empty()) + throw DispatchError(E3090(ctx.auth_info().username)); + + unsigned int user_domain_id = 0, org_id = 0; + resolve_domain_ids(user_domain, user_domain_id, org_id); + (void)user_domain_id; + + std::vector domain_ids; + if (!mysql_adaptor_get_org_domains(org_id, domain_ids)) + throw DispatchError(E3027); + + mGetRoomListsResponse data; + std::vector lists; + lists.reserve(domain_ids.size()); + + for (unsigned int domain_id : domain_ids) { + sql_domain info; + if (!mysql_adaptor_get_domain_info(domain_id, info)) + throw DispatchError(E3027); + if (!collect_rooms(domain_id)) + continue; + lists.emplace_back(make_room_list_entry(info)); + } + + if (!lists.empty()) + data.RoomLists = std::move(lists); + data.success(); + data.serialize(response); +} + +/** + * @brief Process GetRoomsRequest + */ +void process(mGetRoomsRequest&& request, XMLElement* response, const EWSContext& ctx) +{ + response->SetName("m:GetRoomsResponse"); + + ctx.normalize(request.RoomList); + if (!request.RoomList.EmailAddress) + throw DispatchError(E3090("RoomList")); + + auto user_domain = extract_domain(ctx.auth_info().username); + if (user_domain.empty()) + throw DispatchError(E3090(ctx.auth_info().username)); + unsigned int user_domain_id = 0, user_org_id = 0; + resolve_domain_ids(user_domain, user_domain_id, user_org_id); + + auto target_domain = extract_domain(request.RoomList.EmailAddress->c_str()); + if (target_domain.empty()) + throw DispatchError(E3090(*request.RoomList.EmailAddress)); + unsigned int target_domain_id = 0, target_org_id = 0; + resolve_domain_ids(target_domain, target_domain_id, target_org_id); + + if (user_org_id != target_org_id) + throw EWSError::AccessDenied(E3018); + + std::vector rooms; + collect_rooms(target_domain_id, &rooms); + + mGetRoomsResponse data; + data.Rooms = std::move(rooms); + data.success(); + data.serialize(response); +} + /** * @brief Process GetServiceConfigurationRequest * diff --git a/exch/ews/requests.hpp b/exch/ews/requests.hpp index 8dd8d65d8..5a6b96361 100644 --- a/exch/ews/requests.hpp +++ b/exch/ews/requests.hpp @@ -34,6 +34,8 @@ EWSFUNC(mGetFolderRequest); EWSFUNC(mGetInboxRulesRequest); EWSFUNC(mGetItemRequest); EWSFUNC(mGetMailTipsRequest); +EWSFUNC(mGetRoomListsRequest); +EWSFUNC(mGetRoomsRequest); EWSFUNC(mGetServiceConfigurationRequest); EWSFUNC_NC(mGetStreamingEventsRequest); EWSFUNC(mGetUserAvailabilityRequest); From 37db70a9a73195a3359ab9191004a0bda55d98b7 Mon Sep 17 00:00:00 2001 From: Mike Williams Date: Tue, 21 Oct 2025 15:52:27 +0200 Subject: [PATCH 03/32] ews: register GetRoomLists and GetRooms handlers Register the room search handlers in the EWS request dispatch table so clients (including Outlook4Win) can invoke them. References: GXL-621 --- exch/ews/ews.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/exch/ews/ews.cpp b/exch/ews/ews.cpp index 0fae4c28d..525609819 100644 --- a/exch/ews/ews.cpp +++ b/exch/ews/ews.cpp @@ -247,6 +247,8 @@ const std::unordered_map EWSPlugin::requestMap {"GetInboxRules", process}, {"GetItem", process}, {"GetMailTips", process}, + {"GetRoomLists", process}, + {"GetRooms", process}, {"GetServiceConfiguration", process}, {"GetStreamingEvents", process}, {"GetUserAvailabilityRequest", process}, From da2c5f56fcdcbd799640f6e0fb2098944a2f45cc Mon Sep 17 00:00:00 2001 From: Andreas Lang Date: Tue, 28 Oct 2025 21:55:12 +0100 Subject: [PATCH 04/32] ews: add UserConfigurationDictionaryObjectTypesType enum References: GXL-578 (#578) --- exch/ews/enums.hpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/exch/ews/enums.hpp b/exch/ews/enums.hpp index b1a8d581f..0f89db1b2 100644 --- a/exch/ews/enums.hpp +++ b/exch/ews/enums.hpp @@ -113,6 +113,8 @@ struct Enum { STR(BusinessPhone); STR(BusinessPhone2); STR(Busy); + STR(Byte); + STR(ByteArray); STR(CalendarAssistant); STR(Callback); STR(CarPhone); @@ -138,6 +140,7 @@ struct Enum { STR(Custom); STR(CustomMailTip); STR(CreatedEvent); + STR(DateTime); STR(Day); STR(December); STR(Decline); @@ -201,6 +204,8 @@ struct Enum { STR(InProgress); STR(Integer); STR(IntegerArray); + STR(Integer32); + STR(Integer64); STR(InternetHeaders); STR(InvalidRecipient); STR(IPPhone); @@ -334,6 +339,8 @@ struct Enum { STR(Tuesday); STR(UnifiedMessaging); STR(UnifiedMessagingConfiguration); + STR(UnsignedInteger32); + STR(UnsignedInteger64); STR(Unknown); STR(User); STR(WaitingOnOthers); @@ -465,6 +472,7 @@ struct Enum { using SuggestionQuality = StrEnum; ///< Types.xsd:6423 using SyncFolderItemsScopeType = StrEnum; ///< Types.xsd:6256 using UserConfigurationPropertyType = StrEnum; ///< Types.xsd:7256 + using UserConfigurationDictionaryObjectTypesType = StrEnum; ///; ///< Types.xsd:4072 }; From ce585b8ab76310e18bb96c005cd3e13584aaaee7 Mon Sep 17 00:00:00 2001 From: Andreas Lang Date: Wed, 29 Oct 2025 20:31:56 +0100 Subject: [PATCH 05/32] ews: capitalize FolderId of tTargetFolderIdType Match the TargetFolderIdType's element in Types.xsd. --- exch/ews/requests.cpp | 14 +++++++------- exch/ews/serialization.cpp | 2 +- exch/ews/structures.cpp | 2 +- exch/ews/structures.hpp | 2 +- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/exch/ews/requests.cpp b/exch/ews/requests.cpp index 63f26e981..3c24b3d08 100644 --- a/exch/ews/requests.cpp +++ b/exch/ews/requests.cpp @@ -365,7 +365,7 @@ void process(mCreateFolderRequest&& request, XMLElement* response, const EWSCont mCreateFolderResponse data; - sFolderSpec parent = ctx.resolveFolder(request.ParentFolderId.folderId); + sFolderSpec parent = ctx.resolveFolder(request.ParentFolderId.FolderId); std::string dir = ctx.getDir(parent); bool hasAccess = ctx.permissions(dir, parent.folderId); @@ -397,7 +397,7 @@ void process(mCreateItemRequest&& request, XMLElement* response, const EWSContex std::optional targetFolder; if (request.SavedItemFolderId) - targetFolder = ctx.resolveFolder(request.SavedItemFolderId->folderId); + targetFolder = ctx.resolveFolder(request.SavedItemFolderId->FolderId); else targetFolder = ctx.resolveFolder(tDistinguishedFolderId("outbox")); std::string dir = ctx.getDir(*targetFolder); @@ -1371,7 +1371,7 @@ void process(const mBaseMoveCopyFolder& request, XMLElement* response, const EWS { response->SetName(request.copy ? "m:CopyFolderResponse" : "m:MoveFolderResponse"); - sFolderSpec dstFolder = ctx.resolveFolder(request.ToFolderId.folderId); + sFolderSpec dstFolder = ctx.resolveFolder(request.ToFolderId.FolderId); std::string dir = ctx.getDir(dstFolder); uint32_t accountId = ctx.getAccountId(ctx.auth_info().username, false); @@ -1414,7 +1414,7 @@ void process(const mBaseMoveCopyItem& request, XMLElement* response, const EWSCo { response->SetName(request.copy ? "m:CopyItemResponse" : "m:MoveItemResponse"); - sFolderSpec dstFolder = ctx.resolveFolder(request.ToFolderId.folderId); + sFolderSpec dstFolder = ctx.resolveFolder(request.ToFolderId.FolderId); std::string dir = ctx.getDir(dstFolder); bool dstAccess = ctx.permissions(dir, dstFolder.folderId); @@ -1528,7 +1528,7 @@ void process(mSyncFolderHierarchyRequest&& request, XMLElement* response, const syncState.init(*request.SyncState); syncState.convert(); - sFolderSpec folder = ctx.resolveFolder(request.SyncFolderId->folderId); + sFolderSpec folder = ctx.resolveFolder(request.SyncFolderId->FolderId); if (!folder.target) folder.target = ctx.auth_info().username; std::string dir = ctx.getDir(folder.normalize()); @@ -1587,7 +1587,7 @@ void process(mSyncFolderItemsRequest&& request, XMLElement* response, const EWSC { response->SetName("m:SyncFolderItemsResponse"); - sFolderSpec folder = ctx.resolveFolder(request.SyncFolderId.folderId); + sFolderSpec folder = ctx.resolveFolder(request.SyncFolderId.FolderId); sSyncState syncState; if (request.SyncState && !request.SyncState->empty()) @@ -1817,7 +1817,7 @@ void process(mSendItemRequest&& request, XMLElement* response, const EWSContext& return; } sFolderSpec saveFolder = request.SavedItemFolderId ? - ctx.resolveFolder(request.SavedItemFolderId->folderId) : + ctx.resolveFolder(request.SavedItemFolderId->FolderId) : sFolderSpec(tDistinguishedFolderId(Enum::sentitems)); if (request.SavedItemFolderId && !(ctx.permissions(ctx.getDir(saveFolder), saveFolder.folderId) & frightsCreate)) { data.Responses.emplace_back(EWSError::AccessDenied(E3141)); diff --git a/exch/ews/serialization.cpp b/exch/ews/serialization.cpp index 7cf424640..8efe02a1d 100644 --- a/exch/ews/serialization.cpp +++ b/exch/ews/serialization.cpp @@ -1548,7 +1548,7 @@ void tSyncFolderItemsReadFlag::serialize(tinyxml2::XMLElement* xml) const } tTargetFolderIdType::tTargetFolderIdType(const XMLElement* xml) : - VXMLINIT(folderId) + VXMLINIT(FolderId) {} tUserConfigurationName::tUserConfigurationName(const tinyxml2::XMLElement* xml) : diff --git a/exch/ews/structures.cpp b/exch/ews/structures.cpp index 060614f94..11047c0da 100644 --- a/exch/ews/structures.cpp +++ b/exch/ews/structures.cpp @@ -4255,7 +4255,7 @@ tSyncFolderItemsDelete::tSyncFolderItemsDelete(const sBase64Binary& meid) : Item /////////////////////////////////////////////////////////////////////////////// tTargetFolderIdType::tTargetFolderIdType(sFolderId&& id) : - folderId(std::move(id)) + FolderId(std::move(id)) {} /////////////////////////////////////////////////////////////////////////////// diff --git a/exch/ews/structures.hpp b/exch/ews/structures.hpp index 0e9910404..f11596edb 100644 --- a/exch/ews/structures.hpp +++ b/exch/ews/structures.hpp @@ -2865,7 +2865,7 @@ struct tTargetFolderIdType { explicit tTargetFolderIdType(sFolderId&&); explicit tTargetFolderIdType(const tinyxml2::XMLElement*); - sFolderId folderId; + sFolderId FolderId; }; /** From d8b7712f2f83528ab14d900b8ba07a241aea2668 Mon Sep 17 00:00:00 2001 From: Andreas Lang Date: Wed, 29 Oct 2025 21:17:07 +0100 Subject: [PATCH 06/32] ews: implement tUserConfigurationName according to docs tUserConfigurationName expands tTargetFolderIdType instead of having own optional FolderId variants. --- exch/ews/serialization.cpp | 4 ++-- exch/ews/structures.hpp | 4 +--- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/exch/ews/serialization.cpp b/exch/ews/serialization.cpp index 8efe02a1d..a1166ae4e 100644 --- a/exch/ews/serialization.cpp +++ b/exch/ews/serialization.cpp @@ -1552,8 +1552,8 @@ tTargetFolderIdType::tTargetFolderIdType(const XMLElement* xml) : {} tUserConfigurationName::tUserConfigurationName(const tinyxml2::XMLElement* xml) : - XMLINITA(Name), - XMLINIT(FolderId) + tTargetFolderIdType(xml), + XMLINITA(Name) {} tUserId::tUserId(const tinyxml2::XMLElement* xml) : diff --git a/exch/ews/structures.hpp b/exch/ews/structures.hpp index f11596edb..122bf3cc4 100644 --- a/exch/ews/structures.hpp +++ b/exch/ews/structures.hpp @@ -4071,12 +4071,10 @@ struct mUpdateItemResponse { /* * Types.xsd:7203 */ -struct tUserConfigurationName { +struct tUserConfigurationName : public tTargetFolderIdType { explicit tUserConfigurationName(const tinyxml2::XMLElement*); std::string Name; //Attribute - std::optional FolderId; - std::optional DistinguishedFolderId; }; /** From f9dd7250533e31af0bb8247ae99b722fe8ecff9e Mon Sep 17 00:00:00 2001 From: Andreas Lang Date: Wed, 29 Oct 2025 22:37:40 +0100 Subject: [PATCH 07/32] ews: structs for CategoryList in UserConfiguration References: GXL-578 (#578) --- exch/ews/serialization.cpp | 50 ++++++++++++++++++++++++++++++++++++ exch/ews/structures.hpp | 52 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 102 insertions(+) diff --git a/exch/ews/serialization.cpp b/exch/ews/serialization.cpp index a1166ae4e..57a5e6edb 100644 --- a/exch/ews/serialization.cpp +++ b/exch/ews/serialization.cpp @@ -849,6 +849,13 @@ tDistinguishedFolderId::tDistinguishedFolderId(const tinyxml2::XMLElement* xml) XMLINITA(Id) {} +void tDistinguishedFolderId::serialize(tinyxml2::XMLElement* xml) const +{ + XMLDUMPT(Mailbox); + XMLDUMPA(ChangeKey); + XMLDUMPA(Id); +} + tDuration::tDuration(const XMLElement* xml) : XMLINIT(StartTime), XMLINIT(EndTime) {} @@ -1551,11 +1558,48 @@ tTargetFolderIdType::tTargetFolderIdType(const XMLElement* xml) : VXMLINIT(FolderId) {} +void tTargetFolderIdType::serialize(tinyxml2::XMLElement* xml) const +{ + XMLDUMPT(FolderId); +} + tUserConfigurationName::tUserConfigurationName(const tinyxml2::XMLElement* xml) : tTargetFolderIdType(xml), XMLINITA(Name) {} +void tUserConfigurationName::serialize(XMLElement* xml) const +{ + tTargetFolderIdType::serialize(xml); + XMLDUMPA(Name); +} + +void tUserConfigurationDictionaryObject::serialize(tinyxml2::XMLElement* xml) const +{ + XMLDUMPT(Type); + XMLDUMPT(Value); +} + +void tUserConfigurationDictionaryEntry::serialize(tinyxml2::XMLElement* xml) const +{ + XMLDUMPT(DictionaryKey); + XMLDUMPT(DictionaryValue); +} + +void tUserConfigurationDictionaryType::serialize(tinyxml2::XMLElement* xml) const +{ + XMLDUMPT(DictionaryEntry); +} + +void tUserConfigurationType::serialize(tinyxml2::XMLElement* xml) const +{ + XMLDUMPT(UserConfigurationName); + XMLDUMPT(ItemId); + XMLDUMPT(Dictionary); + XMLDUMPT(XmlData); + XMLDUMPT(BinaryData); +} + tUserId::tUserId(const tinyxml2::XMLElement* xml) : XMLINIT(PrimarySmtpAddress), XMLINIT(DisplayName), @@ -1889,6 +1933,12 @@ mGetUserConfigurationRequest::mGetUserConfigurationRequest(const tinyxml2::XMLEl XMLINIT(UserConfigurationProperties) {} +void mGetUserConfigurationResponseMessage::serialize(tinyxml2::XMLElement* xml) const +{ + mResponseMessageType::serialize(xml); + XMLDUMPT(UserConfiguration); +} + void mGetUserConfigurationResponse::serialize(XMLElement* xml) const { XMLDUMPM(ResponseMessages); diff --git a/exch/ews/structures.hpp b/exch/ews/structures.hpp index 122bf3cc4..ca1c06e51 100644 --- a/exch/ews/structures.hpp +++ b/exch/ews/structures.hpp @@ -2681,6 +2681,8 @@ struct tDistinguishedFolderId { std::optional Mailbox; std::optional ChangeKey; //Attribute Enum::DistinguishedFolderIdNameType Id; //Attribute + + void serialize(tinyxml2::XMLElement*) const; }; struct tFolderChange { @@ -2866,6 +2868,8 @@ struct tTargetFolderIdType { explicit tTargetFolderIdType(const tinyxml2::XMLElement*); sFolderId FolderId; + + void serialize(tinyxml2::XMLElement*) const; }; /** @@ -4075,6 +4079,50 @@ struct tUserConfigurationName : public tTargetFolderIdType { explicit tUserConfigurationName(const tinyxml2::XMLElement*); std::string Name; //Attribute + + void serialize(tinyxml2::XMLElement*) const; +}; + +/** + * Types.xsd:7227 + */ +struct tUserConfigurationDictionaryObject { + Enum::UserConfigurationDictionaryObjectTypesType Type; + std::vector Value; + + void serialize(tinyxml2::XMLElement*) const; +}; + +/** + * Types.xsd:7234 + */ +struct tUserConfigurationDictionaryEntry { + tUserConfigurationDictionaryObject DictionaryKey; + std::optional DictionaryValue; + + void serialize(tinyxml2::XMLElement*) const; +}; + +/** + * Types.xsd:7241 + */ +struct tUserConfigurationDictionaryType { + std::vector DictionaryEntry; + + void serialize(tinyxml2::XMLElement*) const; +}; + +/* + * Types.xsd:7247 + */ +struct tUserConfigurationType { + tUserConfigurationName UserConfigurationName; + std::optional ItemId; + std::optional Dictionary; + std::optional XmlData; + std::optional BinaryData; + + void serialize(tinyxml2::XMLElement*) const; }; /** @@ -4092,6 +4140,10 @@ struct mGetUserConfigurationRequest { */ struct mGetUserConfigurationResponseMessage : public mResponseMessageType { static constexpr char NAME[] = "GetUserConfigurationResponseMessage"; + + std::optional UserConfiguration; + + void serialize(tinyxml2::XMLElement*) const; }; /** From 9e04c8514eace63349ee8c66cabd604a96a4a153 Mon Sep 17 00:00:00 2001 From: Andreas Lang Date: Thu, 30 Oct 2025 21:43:42 +0100 Subject: [PATCH 08/32] ews: replace spaces with tabs in structures.hpp --- exch/ews/structures.hpp | 64 ++++++++++++++++++++--------------------- 1 file changed, 32 insertions(+), 32 deletions(-) diff --git a/exch/ews/structures.hpp b/exch/ews/structures.hpp index ca1c06e51..02709880c 100644 --- a/exch/ews/structures.hpp +++ b/exch/ews/structures.hpp @@ -747,11 +747,11 @@ struct tPhoneNumberDictionaryEntry : public NS_EWS_Types { * Types.xsd:8508 (simplified) */ struct tPersona : public NS_EWS_Types { - static constexpr char NAME[] = "Persona"; + static constexpr char NAME[] = "Persona"; - void serialize(tinyxml2::XMLElement *) const; + void serialize(tinyxml2::XMLElement *) const; - std::optional DisplayName, EmailAddress, Title, Nickname, + std::optional DisplayName, EmailAddress, Title, Nickname, BusinessPhoneNumber, MobilePhoneNumber, HomeAddress, Comment; }; @@ -1120,9 +1120,9 @@ struct tUserId { * Types.xsd:6909 */ struct tDelegateUser { - tUserId UserId; + tUserId UserId; - void serialize(tinyxml2::XMLElement*) const; + void serialize(tinyxml2::XMLElement*) const; }; /** @@ -2518,39 +2518,39 @@ struct tMeetingCancellationMessage : public tMeetingMessage { * Types.xsd:3913 */ struct tAcceptItem : public tMessage { - static constexpr char NAME[] = "AcceptItem"; + static constexpr char NAME[] = "AcceptItem"; - using tMessage::tMessage; + using tMessage::tMessage; - tAcceptItem(const tinyxml2::XMLElement *); - void serialize(tinyxml2::XMLElement *) const; + tAcceptItem(const tinyxml2::XMLElement *); + void serialize(tinyxml2::XMLElement *) const; - std::optional ProposedStart, ProposedEnd; - std::optional ReferenceItemId; + std::optional ProposedStart, ProposedEnd; + std::optional ReferenceItemId; }; struct tTentativelyAcceptItem : public tMessage { - static constexpr char NAME[] = "TentativelyAcceptItem"; + static constexpr char NAME[] = "TentativelyAcceptItem"; - using tMessage::tMessage; + using tMessage::tMessage; - tTentativelyAcceptItem(const tinyxml2::XMLElement *); - void serialize(tinyxml2::XMLElement *) const; + tTentativelyAcceptItem(const tinyxml2::XMLElement *); + void serialize(tinyxml2::XMLElement *) const; - std::optional ProposedStart, ProposedEnd; - std::optional ReferenceItemId; + std::optional ProposedStart, ProposedEnd; + std::optional ReferenceItemId; }; struct tDeclineItem : public tMessage { - static constexpr char NAME[] = "DeclineItem"; + static constexpr char NAME[] = "DeclineItem"; - using tMessage::tMessage; + using tMessage::tMessage; - tDeclineItem(const tinyxml2::XMLElement *); - void serialize(tinyxml2::XMLElement *) const; + tDeclineItem(const tinyxml2::XMLElement *); + void serialize(tinyxml2::XMLElement *) const; - std::optional ProposedStart, ProposedEnd; - std::optional ReferenceItemId; + std::optional ProposedStart, ProposedEnd; + std::optional ReferenceItemId; }; /** @@ -3862,29 +3862,29 @@ struct mGetItemResponse { * Messages.xsd:2781 (simplified) */ struct mFindPeopleRequest { - explicit mFindPeopleRequest(const tinyxml2::XMLElement *); + explicit mFindPeopleRequest(const tinyxml2::XMLElement *); - std::string QueryString; + std::string QueryString; }; /** * Messages.xsd:2788 (simplified) */ struct mFindPeopleResponseMessage : public mResponseMessageType { - static constexpr char NAME[] = "FindPeopleResponseMessage"; + static constexpr char NAME[] = "FindPeopleResponseMessage"; - using mResponseMessageType::mResponseMessageType; + using mResponseMessageType::mResponseMessageType; - std::optional> People; - std::optional TotalNumberOfPeopleInView; + std::optional> People; + std::optional TotalNumberOfPeopleInView; - void serialize(tinyxml2::XMLElement *) const; + void serialize(tinyxml2::XMLElement *) const; }; struct mFindPeopleResponse { - std::vector ResponseMessages; + std::vector ResponseMessages; - void serialize(tinyxml2::XMLElement *) const; + void serialize(tinyxml2::XMLElement *) const; }; /** From 16938419783ac1c47a7af7f8a9667bb3c49b01b3 Mon Sep 17 00:00:00 2001 From: Andreas Lang Date: Thu, 30 Oct 2025 21:49:11 +0100 Subject: [PATCH 09/32] ews: extend mGetUserConfigurationResponseMessage using mResponseMessageType::mResponseMessageType so that the possible exception might be added to the ResponseMessages. --- exch/ews/structures.hpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/exch/ews/structures.hpp b/exch/ews/structures.hpp index 02709880c..45da63c20 100644 --- a/exch/ews/structures.hpp +++ b/exch/ews/structures.hpp @@ -1,5 +1,5 @@ // SPDX-License-Identifier: AGPL-3.0-or-later -// SPDX-FileCopyrightText: 2022-2024 grommunio GmbH +// SPDX-FileCopyrightText: 2022-2025 grommunio GmbH // This file is part of Gromox. #pragma once @@ -4141,6 +4141,8 @@ struct mGetUserConfigurationRequest { struct mGetUserConfigurationResponseMessage : public mResponseMessageType { static constexpr char NAME[] = "GetUserConfigurationResponseMessage"; + using mResponseMessageType::mResponseMessageType; + std::optional UserConfiguration; void serialize(tinyxml2::XMLElement*) const; From 1cd076dd2c297839608e73410c67a2ba61e5b432 Mon Sep 17 00:00:00 2001 From: Andreas Lang Date: Thu, 30 Oct 2025 21:52:23 +0100 Subject: [PATCH 10/32] ews: enable missing PR_ROAMING_* props PR_ROAMING_XMLSTREAM and PR_ROAMING_BINARYSTREAM are required for GetUserConfigurationResponse. References: GXL-578 (#578) --- include/gromox/mapitags.hpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/include/gromox/mapitags.hpp b/include/gromox/mapitags.hpp index 3b88bd2d0..2f8f0e5a3 100644 --- a/include/gromox/mapitags.hpp +++ b/include/gromox/mapitags.hpp @@ -1022,8 +1022,8 @@ enum { // PR_OFFLINE_FOLDER = PROP_TAG(PT_BINARY, 0x7C05), // PR_ROAMING_DATATYPES = PROP_TAG(PT_LONG, 0x7C06), /* PidTagRoamingDatatypes */ // PR_ROAMING_DICTIONARY = PROP_TAG(PT_BINARY, 0x7C07), /* PidTagRoamingDictionary */ - // PR_ROAMING_XMLSTREAM = PROP_TAG(PT_BINARY, 0x7C08), /* PidTagRoamingXmlStream */ - // PR_ROAMING_BINARYSTREAM = PROP_TAG(PT_BINARY, 0x7C09), /* PidTagRoamingBinary */ + PR_ROAMING_XMLSTREAM = PROP_TAG(PT_BINARY, 0x7C08), /* PidTagRoamingXmlStream */ + PR_ROAMING_BINARYSTREAM = PROP_TAG(PT_BINARY, 0x7C09), /* PidTagRoamingBinary */ // PR_STORE_SLOWLINK = PROP_TAG(PT_BOOLEAN, 0x7C0A), // PR_OSC_SYNC_ENABLEDONSERVER = PROP_TAG(PT_BOOLEAN, 0x7C24), /* PidTagOscSyncEnabled */ // PR_FORCE_USE_ENTRYID_SERVER = PROP_TAG(PT_BOOLEAN, 0x7CFE), From 499083f6449d7c233d522d15fe01aa5120a9b929 Mon Sep 17 00:00:00 2001 From: Andreas Lang Date: Thu, 30 Oct 2025 21:54:21 +0100 Subject: [PATCH 11/32] ews: implement CategoryList in UserConfiguration request References: GXL-578 (#578) --- exch/ews/requests.cpp | 82 +++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 79 insertions(+), 3 deletions(-) diff --git a/exch/ews/requests.cpp b/exch/ews/requests.cpp index 3c24b3d08..65155ca4b 100644 --- a/exch/ews/requests.cpp +++ b/exch/ews/requests.cpp @@ -1232,14 +1232,90 @@ void process(mGetStreamingEventsRequest&& request, XMLElement* response, EWSCont * @param response XMLElement to store response in * @param ctx Request context */ -void process(mGetUserConfigurationRequest&&, XMLElement* response, const EWSContext&) +void process(mGetUserConfigurationRequest&& request, XMLElement* response, const EWSContext& ctx) { response->SetName("m:GetUserConfigurationResponse"); mGetUserConfigurationResponse data; - mGetUserConfigurationResponseMessage& msg = data.ResponseMessages.emplace_back(); + try { + auto &exmdb = ctx.plugin().exmdb; + const auto &reqName = request.UserConfigurationName; + const auto &folderId = reqName.FolderId; + sFolderSpec folder; + + if (auto raw = std::get_if(&folderId)) + folder = ctx.resolveFolder(*raw); + else if (auto dist = std::get_if(&folderId)) + folder = ctx.resolveFolder(*dist); + else if (reqName.FolderId.valueless_by_exception()) + throw EWSError::InvalidFolderId(E3252); + + std::string dir = ctx.getDir(folder); + if (!(ctx.permissions(dir, folder.folderId) & frightsVisible)) + throw EWSError::AccessDenied(E3218); + + std::string configClass = "IPM.Configuration." + reqName.Name; + RESTRICTION_PROPERTY resProp{RELOP_EQ, PR_MESSAGE_CLASS, + {PR_MESSAGE_CLASS, const_cast(configClass.c_str())}}; + RESTRICTION res{RES_PROPERTY, {&resProp}}; + + uint32_t tableId = 0, rowCount = 0; + const char *username = ctx.effectiveUser(folder); + if (!exmdb.load_content_table(dir.c_str(), CP_UTF8, folder.folderId, username, + TABLE_FLAG_ASSOCIATED, &res, nullptr, &tableId, &rowCount)) + throw EWSError::ItemPropertyRequestFailed(E3245); + auto unloadTable = HX::make_scope_exit([&, tableId]{exmdb.unload_table(dir.c_str(), tableId);}); + if (rowCount == 0) + throw EWSError::ItemNotFound(E3143); + + static constexpr uint32_t midTag = PidTagMid; + static constexpr PROPTAG_ARRAY midTags = {1, deconst(&midTag)}; + TARRAY_SET rows; + exmdb.query_table(dir.c_str(), username, CP_UTF8, tableId, &midTags, 0, 1, &rows); + if (rows.count == 0 || rows.pparray[0] == nullptr) + throw EWSError::ItemNotFound(E3143); + auto mid = rows.pparray[0]->get(PidTagMid); + if (mid == nullptr) + throw EWSError::ItemNotFound(E3143); - msg.error("ErrorItemNotFound", "Object not found in the information store"); + static constexpr uint32_t propTags[] = { + PR_ENTRYID, PR_CHANGE_KEY, PR_ROAMING_XMLSTREAM, PR_ROAMING_BINARYSTREAM, + }; + const PROPTAG_ARRAY props = {std::size(propTags), deconst(propTags)}; + TPROPVAL_ARRAY propvals = ctx.getItemProps(dir, *mid, props); + + mGetUserConfigurationResponseMessage& msg = data.ResponseMessages.emplace_back(); + msg.UserConfiguration.emplace(tUserConfigurationType{reqName}); + auto &config = *msg.UserConfiguration; + config.UserConfigurationName = reqName; + + auto propType = request.UserConfigurationProperties; + bool includeAll = propType == Enum::All; + + if (includeAll || propType == Enum::Id) { + if (const auto *entryId = propvals.get(PR_ENTRYID)) + config.ItemId.emplace(sBase64Binary(entryId), tBaseItemId::ID_ITEM); + else + throw EWSError::ItemPropertyRequestFailed(E3024); + if (const auto *changeKey = propvals.get(PR_CHANGE_KEY)) + config.ItemId->ChangeKey.emplace(sBase64Binary(changeKey)); + } + + // Dictionary support (PR_ROAMING_DICTIONARY) is not implemented yet + if (includeAll || propType == Enum::XmlData) { + if (const auto *xmlData = propvals.get(PR_ROAMING_XMLSTREAM)) + config.XmlData.emplace(xmlData); + } + if (includeAll || propType == Enum::BinaryData) { + if (const auto *binData = propvals.get(PR_ROAMING_BINARYSTREAM)) + config.BinaryData.emplace(binData); + } + + msg.success(); + } catch (const EWSError &err) { + data.ResponseMessages.clear(); + data.ResponseMessages.emplace_back(err); + } data.serialize(response); } From 9a171edf34a44f41fa0698167140b6aaffb24e11 Mon Sep 17 00:00:00 2001 From: Jan Engelhardt Date: Wed, 29 Oct 2025 20:31:13 +0100 Subject: [PATCH 12/32] http: new implementation of mod_rewrite The original documentation can be seen with ``git show 9f4de7ee5``. It says that \N refers to capture substrings; this is in line with admin expectations. But mod_rewrite does not produce that result *at all*. The original rules ``` /OAB/oab.xml => \0/ews/oab.php \(/Microsoft-Server-ActiveSync\) => \1/z-push/index.php ``` produce just ``/ews/oab.php`` and ``/z-push/index.php``, respectively, and \0 or \1 translates to nothing. Here is a new implementation, using a better substitution. There is no more limitation on the URI length, number of capture groups, and the stack usage is reduced too (hence marking as GXL-51). Fixes: gromox-0~666 --- Makefile.am | 2 +- doc/mod_rewrite.4gx | 15 ++- exch/http/rewrite.cpp | 220 ----------------------------------------- exch/http/rewrite2.cpp | 119 ++++++++++++++++++++++ 4 files changed, 131 insertions(+), 225 deletions(-) delete mode 100644 exch/http/rewrite.cpp create mode 100644 exch/http/rewrite2.cpp diff --git a/Makefile.am b/Makefile.am index 24d180294..4058176f4 100644 --- a/Makefile.am +++ b/Makefile.am @@ -110,7 +110,7 @@ libgxs_midb_agent_la_LDFLAGS = ${default_SYFLAGS} libgxs_midb_agent_la_LIBADD = -lpthread ${fmt_LIBS} ${libHX_LIBS} libgromox_common.la EXTRA_libgxs_midb_agent_la_DEPENDENCIES = default.sym -http_SOURCES = exch/http/cache.cpp exch/http/cache.hpp exch/http/fastcgi.cpp exch/http/fastcgi.hpp exch/http/hpm_processor.cpp exch/http/hpm_processor.hpp exch/http/http_parser.cpp exch/http/http_parser.hpp exch/http/listener.cpp exch/http/listener.hpp exch/http/main.cpp exch/http/pdu_ndr.cpp exch/http/pdu_ndr.hpp exch/http/pdu_ndr_ids.hpp exch/http/pdu_processor.cpp exch/http/pdu_processor.hpp exch/http/resource.hpp exch/http/rewrite.cpp exch/http/rewrite.hpp exch/http/system_services.cpp exch/http/system_services.hpp +http_SOURCES = exch/http/cache.cpp exch/http/cache.hpp exch/http/fastcgi.cpp exch/http/fastcgi.hpp exch/http/hpm_processor.cpp exch/http/hpm_processor.hpp exch/http/http_parser.cpp exch/http/http_parser.hpp exch/http/listener.cpp exch/http/listener.hpp exch/http/main.cpp exch/http/pdu_ndr.cpp exch/http/pdu_ndr.hpp exch/http/pdu_ndr_ids.hpp exch/http/pdu_processor.cpp exch/http/pdu_processor.hpp exch/http/resource.hpp exch/http/rewrite2.cpp exch/http/rewrite.hpp exch/http/system_services.cpp exch/http/system_services.hpp http_LDADD = -lpthread ${libcrypto_LIBS} ${fmt_LIBS} ${gss_LIBS} ${libHX_LIBS} ${libssl_LIBS} libgromox_auth.la libgromox_authz.la libgromox_common.la libgromox_epoll.la libgromox_rpc.la libgromox_mapi.la libgxh_ews.la libgxh_mh_emsmdb.la libgxh_mh_nsp.la libgxh_oab.la libgxh_oxdisco.la libgxp_exchange_emsmdb.la libgxp_exchange_nsp.la libgxp_exchange_rfr.la libgxs_exmdb_provider.la libgxs_mysql_adaptor.la libgxs_timer_agent.la midb_SOURCES = exch/midb/cmd_parser.cpp exch/midb/cmd_parser.hpp exch/midb/common_util.cpp exch/midb/common_util.hpp exch/midb/exmdb_client.hpp exch/midb/mail_engine.cpp exch/midb/mail_engine.hpp exch/midb/main.cpp exch/midb/system_services.hpp midb_LDADD = -lpthread ${libHX_LIBS} ${fmt_LIBS} ${iconv_LIBS} ${jsoncpp_LIBS} ${libssl_LIBS} ${sqlite_LIBS} ${vmime_LIBS} libgromox_auth.la libgromox_common.la libgromox_dbop.la libgromox_exrpc.la libgromox_mapi.la libgxs_event_proxy.la libgxs_mysql_adaptor.la diff --git a/doc/mod_rewrite.4gx b/doc/mod_rewrite.4gx index fcbb5b8fc..79bc201fd 100644 --- a/doc/mod_rewrite.4gx +++ b/doc/mod_rewrite.4gx @@ -17,18 +17,25 @@ which is searched for in \fIconfig_file_path\fP. The usual location is Each line in this file consists of 3 columns separated by whitespace: .IP \(bu 4 A POSIX Basic Regular Expression (cf. regcomp(3)) for matching the original URI. +For safety, this should always be anchored with the beginning-of-line +metacharacter (^; circumflex). .IP \(bu 4 The fixed sequence "=>". .IP \(bu 4 -Replacement string. Captures can be spliced using \fB\\1\fP, \fB\\2\fP, .. up -to a maximum of \fB\\9\fP. The sequence \fB\\0\fP splices the entire string -(equivalent of Perl's \fB$&\fP). +Replacement string. Captures can be spliced using \fB\\1\fP, \fB\\2\fP, etc. +The sequence \fB\\0\fP splices the entire string +(equivalent of Perl's \fB$&\fP). If a particular pattern has successfully +matched, no other rewrite rules are processed. .PP If the file has no lines, no paths will be rewritten. If the file is absent however, a set of default entries will be used. .SH Default rules .nf -\\(/Microsoft-Server-ActiveSync\\) => \\1/grommunio-sync/index.php +^/Microsoft-Server-ActiveSync\\(/\\|$\\) => /sync/index.php +.fi +.SH Examples +.nf +^/\\([a-z+]\\)/\\([a-z]+\\).txt$ => /\\2/\\1.txt .fi .SH Files .IP \(bu 4 diff --git a/exch/http/rewrite.cpp b/exch/http/rewrite.cpp deleted file mode 100644 index 687359d52..000000000 --- a/exch/http/rewrite.cpp +++ /dev/null @@ -1,220 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-only WITH linking exception -// SPDX-FileCopyrightText: 2021 grommunio GmbH -// This file is part of Gromox. -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include "http_parser.hpp" -#include "rewrite.hpp" -#define MAX_LINE 16*1024 - -using namespace gromox; - -namespace { - -class rewrite_list; -class rewrite_rule { - public: - rewrite_rule() = default; - rewrite_rule(rewrite_rule &&) noexcept; - ~rewrite_rule(); - void operator=(rewrite_rule &&) = delete; - bool try_replace(char *uri, int uri_size); - - private: - regex_t search_pattern{}; - std::string replace_string; - bool reg_set = false; - - friend class rewrite_list; -}; -using REWRITE_NODE = rewrite_rule; - -class rewrite_list : public std::vector { - public: - int emplace(const char *from, const char *to); -}; - -} - -static rewrite_list g_rewrite_list; - -rewrite_rule::rewrite_rule(rewrite_rule &&o) noexcept : - replace_string(std::move(o.replace_string)) -{ - if (reg_set) - regfree(&search_pattern); - memcpy(&search_pattern, &o.search_pattern, sizeof(search_pattern)); - reg_set = o.reg_set; - o.reg_set = false; -} - -rewrite_rule::~rewrite_rule() -{ - if (reg_set) - regfree(&search_pattern); -} - -bool rewrite_rule::try_replace(char *buf, int size) -{ - char *pos; - int last_pos; - int i, len, offset; - int rp_offsets[10]; - regmatch_t pmatch[10]; /* regoff_t is int so size is int */ - char original_rp[8192]; - char original_buf[8192]; - - if (regexec(&search_pattern, buf, 10, pmatch, 0)) - return FALSE; - auto &rp = replace_string; - if ('\\' == rp[0] && '0' == rp[1]) { - gx_strlcpy(buf, &rp[2], size); - return TRUE; - } - gx_strlcpy(original_buf, buf, std::size(original_buf)); - gx_strlcpy(original_rp, rp.c_str(), std::size(original_rp)); - for (i = 0; i < 10; ++i) - rp_offsets[i] = -1; - for (pos=original_rp; '\0'!=*pos; pos++) { - if (pos[0] == '\\' && pos[1] > '0' && pos[1] <= '9') { - rp_offsets[pos[1]-'0'] = pos + 2 - original_rp; - *pos = '\0'; - } - } - last_pos = 0; - for (i=1,offset=0; i<=10&&offset= size) - break; - strcpy(buf + offset, original_buf + last_pos); - return TRUE; - } - if (-1 != rp_offsets[i]) { - len = pmatch[i].rm_so - last_pos; - if (offset + len >= size) - break; - memcpy(buf + offset, original_buf + last_pos, len); - offset += len; - len = strlen(original_rp + rp_offsets[i]); - if (offset + len >= size) - break; - strcpy(buf + offset, original_rp + rp_offsets[i]); - } else { - len = pmatch[i].rm_eo - last_pos; - if (offset + len >= size) - break; - memcpy(buf + offset, original_buf + last_pos, len); - } - offset += len; - last_pos = pmatch[i].rm_eo; - } - return FALSE; -} - -int rewrite_list::emplace(const char *from, const char *to) -{ - static constexpr size_t ebufsize = 512; - auto errbuf = std::make_unique(ebufsize); - rewrite_rule node; - - node.replace_string = to; - auto ret = regcomp(&node.search_pattern, from, REG_ICASE); - if (ret != 0) { - regerror(ret, &node.search_pattern, errbuf.get(), ebufsize); - mlog(LV_ERR, "mod_rewrite %s: regcomp: %s", from, errbuf.get()); - return -EINVAL; - } - node.reg_set = true; - g_rewrite_list.push_back(std::move(node)); - return 0; -} - -static int mod_rewrite_default() -{ - mlog(LV_INFO, "mod_rewrite: defaulting to built-in rule list"); - return g_rewrite_list.emplace("\\(/Microsoft-Server-ActiveSync\\)", "\\1/grommunio-sync/index.php"); -} - -int mod_rewrite_run(const char *sdlist) try -{ - int line_no; - char *ptoken; - char line[MAX_LINE]; - static constexpr size_t ebufsize = 512; - auto errbuf = std::make_unique(ebufsize); - - line_no = 0; - auto file_ptr = fopen_sd("rewrite.txt", sdlist); - if (file_ptr == nullptr && errno == ENOENT) - return mod_rewrite_default(); - if (file_ptr == nullptr) { - int se = errno; - mlog(LV_ERR, "mod_rewrite: fopen_sd rewrite.txt: %s", strerror(errno)); - return -(errno = se); - } - while (fgets(line, std::size(line), file_ptr.get())) { - line_no ++; - if (*line == '#' || newline_size(line, 2) > 0) - /* skip empty line or comments */ - continue; - /* prevent line exceed maximum length ---MAX_LEN */ - line[sizeof(line) - 1] = '\0'; - HX_chomp(line); - HX_strrtrim(line); - HX_strltrim(line); - ptoken = strstr(line, "=>"); - if (NULL == ptoken) { - mlog(LV_ERR, "mod_rewrite: invalid line %d, cannot " - "find seperator \"=>\"", line_no); - continue; - } - *ptoken = '\0'; - HX_strrtrim(line); - ptoken += 2; - HX_strltrim(ptoken); - if ('\\' != ptoken[0] || ptoken[1] < '0' || ptoken[1] > '9') { - mlog(LV_ERR, "mod_rewrite: invalid line %d, cannot" - " find replace sequence number", line_no); - continue; - } - auto err = g_rewrite_list.emplace(line, ptoken); - if (err != 0) - return err; - } - return 0; -} catch (const std::bad_alloc &) { - return -ENOMEM; -} - -bool mod_rewrite_process(const char *uri_buff, size_t uri_len, - std::string &f_request_uri) try -{ - char tmp_buff[http_request::uri_limit]; - - if (uri_len >= sizeof(tmp_buff)) - return FALSE; - for (auto &node : g_rewrite_list) { - memcpy(tmp_buff, uri_buff, uri_len); - tmp_buff[uri_len] = '\0'; - if (node.try_replace(tmp_buff, std::size(tmp_buff))) { - f_request_uri = tmp_buff; - return TRUE; - } - } - return FALSE; -} catch (const std::bad_alloc &) { - mlog(LV_ERR, "E-1086: ENOMEM"); - return false; -} diff --git a/exch/http/rewrite2.cpp b/exch/http/rewrite2.cpp new file mode 100644 index 000000000..ca8e75c2f --- /dev/null +++ b/exch/http/rewrite2.cpp @@ -0,0 +1,119 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later +// SPDX-FileCopyrightText: 2025 grommunio GmbH +// This file is part of Gromox. +/* is said to be too slow, so don't bother switching to it */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "rewrite.hpp" + +using namespace gromox; + +namespace { + +struct regex_plus : regex_t { + regex_plus() = default; + regex_plus(const char *s) { set = regcomp(this, s, REG_ICASE) == 0; } + regex_plus(regex_plus &&o) { + if (set) + regfree(this); + memcpy(static_cast(this), static_cast(&o), sizeof(o)); + o.set = false; + } + ~regex_plus() { if (set) regfree(this); } + void operator=(regex_plus &&) = delete; + bool set = false; +}; + +struct rewrite_rule { + regex_plus search_pattern; + std::string replace_string; +}; + +} + +static std::vector g_rewrite_list; + +int mod_rewrite_run(const char *sdlist) try +{ + auto file_ptr = fopen_sd("rewrite.txt", sdlist); + if (file_ptr == nullptr) { + if (errno == ENOENT) { + mlog(LV_INFO, "mod_rewrite: defaulting to built-in rule list"); + g_rewrite_list.emplace_back("/Microsoft-Server-ActiveSync\\(/\\|$\\)", "/sync/index.php\\1"); + return g_rewrite_list.back().search_pattern.set ? 0 : -EINVAL; + } + int se = errno; + mlog(LV_ERR, "mod_rewrite: fopen_sd rewrite.txt: %s", strerror(errno)); + return -(errno = se); + } + hxmc_t *line = nullptr; + auto cl_0 = HX::make_scope_exit([&]() { HXmc_free(line); }); + while (HX_getl(&line, file_ptr.get()) != nullptr) { + if (*line == '#') + continue; + auto lhs = line; + while (HX_isspace(*lhs)) + ++lhs; + auto rhs = strstr(lhs, " => "); + if (rhs == nullptr) + continue; + *rhs = '\0'; + HX_strrtrim(lhs); + rhs += 4; + while (HX_isspace(*rhs)) + ++rhs; + HX_chomp(rhs); + HX_strrtrim(rhs); + g_rewrite_list.emplace_back(lhs, rhs); + if (!g_rewrite_list.back().search_pattern.set) { + mlog(LV_ERR, "rewrite.txt: problem parsing %s", lhs); + g_rewrite_list.pop_back(); + continue; + } + } + return 0; +} catch (const std::bad_alloc &) { + return -ENOMEM; +} + +bool mod_rewrite_process(const char *uri_buff, size_t uri_len, + std::string &f_request_uri) try +{ + std::string uri(uri_buff, uri_len); + for (const auto &node : g_rewrite_list) { + std::vector matches(node.search_pattern.re_nsub + 1); + if (regexec(&node.search_pattern, uri.c_str(), + node.search_pattern.re_nsub + 1, matches.data(), 0) != 0) + continue; + f_request_uri.clear(); + auto ri = node.replace_string.c_str(); + do { + auto seglen = strcspn(ri, "\\"); + f_request_uri.append(ri, seglen); + ri += seglen; + if (*ri != '\\') + break; + char *end = nullptr; + auto capnum = strtoul(&ri[1], &end, 10); + if (end != &ri[1] && capnum < matches.size()) + f_request_uri.append(&uri[matches[capnum].rm_so], + matches[capnum].rm_eo - matches[capnum].rm_so); + ri = end; + } while (*ri != '\0'); + return true; + } + return FALSE; +} catch (const std::bad_alloc &) { + mlog(LV_ERR, "E-1086: ENOMEM"); + return false; +} From 7ab7c573828b1c704e24138b29a1661fc8ffad37 Mon Sep 17 00:00:00 2001 From: Jan Engelhardt Date: Sun, 26 Oct 2025 18:12:20 +0100 Subject: [PATCH 13/32] build: dissolve lib/rpc directory and move files --- Makefile.am | 20 +++---- {lib/rpc => exch/http}/ntlmssp.cpp | 53 ++++++++++++++++- {include/gromox => exch/http}/ntlmssp.hpp | 8 ++- exch/http/pdu_processor.hpp | 2 +- {lib/rpc => exch}/ndr.cpp | 0 include/gromox/arcfour.hpp | 13 ----- lib/rpc/arcfour.cpp | 69 ----------------------- 7 files changed, 69 insertions(+), 96 deletions(-) rename {lib/rpc => exch/http}/ntlmssp.cpp (97%) rename {include/gromox => exch/http}/ntlmssp.hpp (97%) rename {lib/rpc => exch}/ndr.cpp (100%) delete mode 100644 include/gromox/arcfour.hpp delete mode 100644 lib/rpc/arcfour.cpp diff --git a/Makefile.am b/Makefile.am index 4058176f4..8b0a15d3e 100644 --- a/Makefile.am +++ b/Makefile.am @@ -9,7 +9,7 @@ AM_DISTCHECK_CONFIGURE_FLAGS = --with-dcprefix='$${prefix}' AM_TESTS_ENVIRONMENT = export TEST_PATH=${top_srcdir}/data; # lib_LTL must be dependency-ordered, or make install fails -lib_LTLIBRARIES = libgromox_common.la libgromox_dbop.la libgromox_epoll.la libgromox_mapi.la libgromox_exrpc.la libgromox_rpc.la libgxs_mysql_adaptor.la libgromox_abtree.la libgromox_auth.la libgromox_authz.la libgxm_alias_resolve.la libgxm_exmdb_local.la libgxh_ews.la libgxh_mh_emsmdb.la libgxh_mh_nsp.la libgxh_oab.la libgxh_oxdisco.la libgxp_exchange_emsmdb.la libgxp_exchange_nsp.la libgxp_exchange_rfr.la libgxs_exmdb_provider.la libgxs_event_proxy.la libgxs_event_stub.la libgxs_midb_agent.la libgxs_timer_agent.la libgxs_ruleproc.la +lib_LTLIBRARIES = libgromox_common.la libgromox_dbop.la libgromox_epoll.la libgromox_mapi.la libgromox_exrpc.la libgromox_ndr.la libgxs_mysql_adaptor.la libgromox_abtree.la libgromox_auth.la libgromox_authz.la libgxm_alias_resolve.la libgxm_exmdb_local.la libgxh_ews.la libgxh_mh_emsmdb.la libgxh_mh_nsp.la libgxh_oab.la libgxh_oxdisco.la libgxp_exchange_emsmdb.la libgxp_exchange_nsp.la libgxp_exchange_rfr.la libgxs_exmdb_provider.la libgxs_event_proxy.la libgxs_event_stub.la libgxs_midb_agent.la libgxs_timer_agent.la libgxs_ruleproc.la pkglibexec_PROGRAMS = delivery delivery-queue event gromox-snapshot http imap midb pop3 timer zcore tools/authtry tools/eidprint tools/textmapquery if WITH_GNU_LD @@ -77,8 +77,8 @@ libgromox_exrpc_la_SOURCES = lib/exmdb_client.cpp lib/exmdb_ext.cpp lib/exmdb_rp libgromox_exrpc_la_LIBADD = libgromox_mapi.la libgromox_mapi_la_SOURCES = lib/email/dsn.cpp lib/email/ical.cpp lib/email/ical2.cpp lib/email/mail.cpp lib/email/mime.cpp lib/email/mjson.cpp lib/email/send.cpp lib/email/vcard.cpp lib/mapi/eid_array.cpp lib/mapi/element_data.cpp lib/mapi/html.cpp lib/mapi/idset.cpp lib/mapi/lzxpress.cpp lib/mapi/oxcical.cpp lib/mapi/oxcmail.cpp lib/mapi/oxcmail2.cpp lib/mapi/oxvcard.cpp lib/mapi/pcl.cpp lib/mapi/proptag_array.cpp lib/mapi/propval.cpp lib/mapi/restriction.cpp lib/mapi/restriction2.cpp lib/mapi/rop_util.cpp lib/mapi/rtf.cpp lib/mapi/rtfcp.cpp lib/mapi/rule_actions.cpp lib/mapi/sortorder_set.cpp lib/mapi/tarray_set.cpp lib/mapi/tnef.cpp lib/mapi/tpropval_array.cpp lib/mapi/usercvt.cpp libgromox_mapi_la_LIBADD = ${fmt_LIBS} ${libHX_LIBS} ${iconv_LIBS} ${vmime_LIBS} ${libxml2_LIBS} libgromox_common.la -libgromox_rpc_la_SOURCES = lib/rpc/arcfour.cpp lib/rpc/ndr.cpp lib/rpc/ntlmssp.cpp -libgromox_rpc_la_LIBADD = ${libcrypto_LIBS} ${libHX_LIBS} ${iconv_LIBS} libgromox_common.la +libgromox_ndr_la_SOURCES = exch/ndr.cpp +libgromox_ndr_la_LIBADD = libgromox_common.la delivery_SOURCES = mda/delivery_app/delivery.hpp mda/delivery_app/main.cpp mda/delivery_app/message_dequeue.cpp mda/delivery_app/transporter.cpp delivery_LDADD = -lpthread ${libHX_LIBS} ${libssl_LIBS} ${vmime_LIBS} libgromox_auth.la libgromox_common.la libgromox_mapi.la libgxm_alias_resolve.la libgxm_exmdb_local.la libgxs_mysql_adaptor.la libgxs_ruleproc.la @@ -110,8 +110,8 @@ libgxs_midb_agent_la_LDFLAGS = ${default_SYFLAGS} libgxs_midb_agent_la_LIBADD = -lpthread ${fmt_LIBS} ${libHX_LIBS} libgromox_common.la EXTRA_libgxs_midb_agent_la_DEPENDENCIES = default.sym -http_SOURCES = exch/http/cache.cpp exch/http/cache.hpp exch/http/fastcgi.cpp exch/http/fastcgi.hpp exch/http/hpm_processor.cpp exch/http/hpm_processor.hpp exch/http/http_parser.cpp exch/http/http_parser.hpp exch/http/listener.cpp exch/http/listener.hpp exch/http/main.cpp exch/http/pdu_ndr.cpp exch/http/pdu_ndr.hpp exch/http/pdu_ndr_ids.hpp exch/http/pdu_processor.cpp exch/http/pdu_processor.hpp exch/http/resource.hpp exch/http/rewrite2.cpp exch/http/rewrite.hpp exch/http/system_services.cpp exch/http/system_services.hpp -http_LDADD = -lpthread ${libcrypto_LIBS} ${fmt_LIBS} ${gss_LIBS} ${libHX_LIBS} ${libssl_LIBS} libgromox_auth.la libgromox_authz.la libgromox_common.la libgromox_epoll.la libgromox_rpc.la libgromox_mapi.la libgxh_ews.la libgxh_mh_emsmdb.la libgxh_mh_nsp.la libgxh_oab.la libgxh_oxdisco.la libgxp_exchange_emsmdb.la libgxp_exchange_nsp.la libgxp_exchange_rfr.la libgxs_exmdb_provider.la libgxs_mysql_adaptor.la libgxs_timer_agent.la +http_SOURCES = exch/http/cache.cpp exch/http/cache.hpp exch/http/fastcgi.cpp exch/http/fastcgi.hpp exch/http/hpm_processor.cpp exch/http/hpm_processor.hpp exch/http/http_parser.cpp exch/http/http_parser.hpp exch/http/listener.cpp exch/http/listener.hpp exch/http/main.cpp exch/http/ntlmssp.cpp exch/http/ntlmssp.hpp exch/http/pdu_ndr.cpp exch/http/pdu_ndr.hpp exch/http/pdu_ndr_ids.hpp exch/http/pdu_processor.cpp exch/http/pdu_processor.hpp exch/http/resource.hpp exch/http/rewrite2.cpp exch/http/rewrite.hpp exch/http/system_services.cpp exch/http/system_services.hpp +http_LDADD = -lpthread ${libcrypto_LIBS} ${fmt_LIBS} ${gss_LIBS} ${iconv_LIBS} ${libHX_LIBS} ${libssl_LIBS} libgromox_auth.la libgromox_authz.la libgromox_common.la libgromox_epoll.la libgromox_ndr.la libgromox_mapi.la libgxh_ews.la libgxh_mh_emsmdb.la libgxh_mh_nsp.la libgxh_oab.la libgxh_oxdisco.la libgxp_exchange_emsmdb.la libgxp_exchange_nsp.la libgxp_exchange_rfr.la libgxs_exmdb_provider.la libgxs_mysql_adaptor.la libgxs_timer_agent.la midb_SOURCES = exch/midb/cmd_parser.cpp exch/midb/cmd_parser.hpp exch/midb/common_util.cpp exch/midb/common_util.hpp exch/midb/exmdb_client.hpp exch/midb/mail_engine.cpp exch/midb/mail_engine.hpp exch/midb/main.cpp exch/midb/system_services.hpp midb_LDADD = -lpthread ${libHX_LIBS} ${fmt_LIBS} ${iconv_LIBS} ${jsoncpp_LIBS} ${libssl_LIBS} ${sqlite_LIBS} ${vmime_LIBS} libgromox_auth.la libgromox_common.la libgromox_dbop.la libgromox_exrpc.la libgromox_mapi.la libgxs_event_proxy.la libgxs_mysql_adaptor.la zcore_SOURCES = exch/gab.cpp exch/zcore/ab_tree.cpp exch/zcore/ab_tree.hpp exch/zcore/attachment_object.cpp exch/zcore/bounce_producer.hpp exch/zcore/common_util.cpp exch/zcore/common_util.hpp exch/zcore/container_object.cpp exch/zcore/exmdb_client.cpp exch/zcore/exmdb_client.hpp exch/zcore/folder_object.cpp exch/zcore/ics_state.cpp exch/zcore/ics_state.hpp exch/zcore/icsdownctx_object.cpp exch/zcore/icsupctx_object.cpp exch/zcore/main.cpp exch/zcore/message_object.cpp exch/zcore/names.cpp exch/zcore/object_tree.cpp exch/zcore/object_tree.hpp exch/zcore/objects.hpp exch/zcore/rpc_ext.cpp exch/zcore/rpc_ext.hpp exch/zcore/rpc_parser.cpp exch/zcore/rpc_parser.hpp exch/zcore/store_object.cpp exch/zcore/store_object.hpp exch/zcore/system_services.hpp exch/zcore/table_object.cpp exch/zcore/table_object.hpp exch/zcore/user_object.cpp exch/zcore/zserver.cpp exch/zcore/zserver.hpp @@ -127,16 +127,16 @@ libgxs_timer_agent_la_LIBADD = -lpthread ${libHX_LIBS} libgromox_common.la EXTRA_libgxs_timer_agent_la_DEPENDENCIES = default.sym libgxp_exchange_emsmdb_la_SOURCES = exch/emsmdb/asyncemsmdb_interface.cpp exch/emsmdb/asyncemsmdb_interface.hpp exch/emsmdb/attachment_object.cpp exch/emsmdb/attachment_object.hpp exch/emsmdb/aux_ext.cpp exch/emsmdb/aux_types.hpp exch/emsmdb/common_util.cpp exch/emsmdb/common_util.hpp exch/emsmdb/emsmdb_interface.cpp exch/emsmdb/emsmdb_interface.hpp exch/emsmdb/emsmdb_ndr.cpp exch/emsmdb/emsmdb_ndr.hpp exch/emsmdb/exmdb_client.cpp exch/emsmdb/exmdb_client.hpp exch/emsmdb/fastdownctx_object.cpp exch/emsmdb/fastdownctx_object.hpp exch/emsmdb/fastupctx_object.cpp exch/emsmdb/fastupctx_object.hpp exch/emsmdb/folder_object.cpp exch/emsmdb/folder_object.hpp exch/emsmdb/ftstream_parser.cpp exch/emsmdb/ftstream_parser.hpp exch/emsmdb/ftstream_producer.cpp exch/emsmdb/ftstream_producer.hpp exch/emsmdb/ics_state.cpp exch/emsmdb/ics_state.hpp exch/emsmdb/icsdownctx_object.cpp exch/emsmdb/icsdownctx_object.hpp exch/emsmdb/logon_object.cpp exch/emsmdb/logon_object.hpp exch/emsmdb/main.cpp exch/emsmdb/message_object.cpp exch/emsmdb/message_object.hpp exch/emsmdb/names.cpp exch/emsmdb/notify.cpp exch/emsmdb/notify_response.hpp exch/emsmdb/oxcfold.cpp exch/emsmdb/oxcfxics.cpp exch/emsmdb/oxcmsg.cpp exch/emsmdb/oxcprpt.cpp exch/emsmdb/oxcstore.cpp exch/emsmdb/oxctabl.cpp exch/emsmdb/oxomsg.cpp exch/emsmdb/processor_types.hpp exch/emsmdb/rop_dispatch.cpp exch/emsmdb/rop_dispatch.hpp exch/emsmdb/rop_ext.cpp exch/emsmdb/rop_ext.hpp exch/emsmdb/rop_funcs.hpp exch/emsmdb/rop_ids.hpp exch/emsmdb/rop_processor.cpp exch/emsmdb/rop_processor.hpp exch/emsmdb/stream_object.cpp exch/emsmdb/stream_object.hpp exch/emsmdb/table_object.cpp exch/emsmdb/table_object.hpp libgxp_exchange_emsmdb_la_LDFLAGS = ${default_SYFLAGS} -libgxp_exchange_emsmdb_la_LIBADD = -lpthread ${libHX_LIBS} ${iconv_LIBS} ${vmime_LIBS} libgromox_common.la libgromox_mapi.la libgromox_rpc.la libgxs_mysql_adaptor.la +libgxp_exchange_emsmdb_la_LIBADD = -lpthread ${libHX_LIBS} ${iconv_LIBS} ${vmime_LIBS} libgromox_common.la libgromox_mapi.la libgromox_ndr.la libgxs_mysql_adaptor.la EXTRA_libgxp_exchange_emsmdb_la_DEPENDENCIES = default.sym libgxp_exchange_nsp_la_SOURCES = exch/nsp/common_util.cpp exch/nsp/common_util.hpp exch/nsp/main.cpp exch/nsp/nsp_interface.cpp exch/nsp/nsp_interface.hpp exch/nsp/nsp_ndr.cpp exch/nsp/nsp_ndr.hpp exch/nsp/nsp_types.hpp libgxp_exchange_nsp_la_LDFLAGS = ${default_SYFLAGS} -libgxp_exchange_nsp_la_LIBADD = -lpthread ${libcrypto_LIBS} ${fmt_LIBS} ${libHX_LIBS} ${iconv_LIBS} libgromox_common.la libgromox_mapi.la libgromox_rpc.la libgxs_mysql_adaptor.la libgromox_abtree.la +libgxp_exchange_nsp_la_LIBADD = -lpthread ${libcrypto_LIBS} ${fmt_LIBS} ${libHX_LIBS} ${iconv_LIBS} libgromox_common.la libgromox_mapi.la libgromox_ndr.la libgxs_mysql_adaptor.la libgromox_abtree.la EXTRA_libgxp_exchange_nsp_la_DEPENDENCIES = default.sym EXTRA_DIST += exch/nsp/repr.cpp libgxp_exchange_rfr_la_SOURCES = exch/rfr.cpp libgxp_exchange_rfr_la_LDFLAGS = ${default_SYFLAGS} -libgxp_exchange_rfr_la_LIBADD = ${fmt_LIBS} ${libHX_LIBS} libgromox_common.la libgromox_rpc.la libgxs_mysql_adaptor.la +libgxp_exchange_rfr_la_LIBADD = ${fmt_LIBS} ${libHX_LIBS} libgromox_common.la libgromox_ndr.la libgxs_mysql_adaptor.la EXTRA_libgxp_exchange_rfr_la_DEPENDENCIES = default.sym libgxh_ews_la_SOURCES = exch/ews/ObjectCache.hpp exch/ews/context.cpp exch/ews/enums.hpp exch/ews/ews.cpp exch/ews/ews.hpp exch/ews/exceptions.hpp exch/ews/hash.hpp exch/ews/namedtags.hpp exch/ews/requests.cpp exch/ews/requests.hpp exch/ews/serialization.cpp exch/ews/serialization.hpp exch/ews/soaputil.cpp exch/ews/soaputil.hpp exch/ews/structures.cpp exch/ews/structures.hpp libgxh_ews_la_LDFLAGS = ${default_SYFLAGS} @@ -327,8 +327,8 @@ tzd_files = data/AUS_Central.tzd data/AUS_Eastern.tzd data/Afghanistan.tzd data/ tzd_files += data/Greenwich.tzd data/Haiti.tzd data/Hawaiian.tzd data/India.tzd data/Iran.tzd data/Israel.tzd data/Jordan.tzd data/Kaliningrad.tzd data/Korea.tzd data/Libya.tzd data/Line_Islands.tzd data/Lord_Howe.tzd data/Magadan.tzd data/Magallanes.tzd data/Marquesas.tzd data/Mauritius.tzd data/Middle_East.tzd data/Montevideo.tzd data/Morocco.tzd data/Mountain.tzd data/Mountain__Mexico_.tzd data/Myanmar.tzd data/N__Central_Asia.tzd data/Namibia.tzd data/Nepal.tzd data/New_Zealand.tzd data/Newfoundland.tzd data/Norfolk.tzd data/North_Asia.tzd data/North_Asia_East.tzd data/North_Korea.tzd data/Omsk.tzd data/Pacific.tzd data/Pacific_SA.tzd data/Pacific__Mexico_.tzd data/Pakistan.tzd data/Paraguay.tzd data/Qyzylorda.tzd data/Romance.tzd data/Russia_Time_Zone_10.tzd data/Russia_Time_Zone_11.tzd data/Russia_Time_Zone_3.tzd data/Russian.tzd data/SA_Eastern.tzd data/SA_Pacific.tzd data/SA_Western.tzd data/SE_Asia.tzd data/Saint_Pierre.tzd data/Sakhalin.tzd data/Samoa.tzd data/Sao_Tome.tzd tzd_files += data/Saratov.tzd data/Singapore.tzd data/South_Africa.tzd data/South_Sudan.tzd data/Sri_Lanka.tzd data/Sudan.tzd data/Syria.tzd data/Taipei.tzd data/Tasmania.tzd data/Tocantins.tzd data/Tokyo.tzd data/Tomsk.tzd data/Tonga.tzd data/Transbaikal.tzd data/Turkey.tzd data/Turks_And_Caicos.tzd data/US_Eastern.tzd data/US_Mountain.tzd data/UTC+12.tzd data/UTC+13.tzd data/UTC-02.tzd data/UTC-08.tzd data/UTC-09.tzd data/UTC-11.tzd data/UTC.tzd data/Ulaanbaatar.tzd data/Venezuela.tzd data/Vladivostok.tzd data/Volgograd.tzd data/W__Australia.tzd data/W__Central_Africa.tzd data/W__Europe.tzd data/W__Mongolia.tzd data/West_Asia.tzd data/West_Bank.tzd data/West_Pacific.tzd data/Yakutsk.tzd data/Yukon.tzd tzd_files += data/windowsZones.xml -header_files = include/gromox/ab_tree.hpp include/gromox/algorithm.hpp include/gromox/arcfour.hpp include/gromox/archive.hpp include/gromox/atomic.hpp include/gromox/authmgr.hpp include/gromox/bounce_gen.hpp include/gromox/clock.hpp include/gromox/common_types.hpp include/gromox/config_file.hpp include/gromox/contexts_pool.hpp include/gromox/cookie_parser.hpp include/gromox/cryptoutil.hpp include/gromox/database.h include/gromox/database_mysql.hpp include/gromox/dbop.h include/gromox/dcerpc.hpp include/gromox/defs.h include/gromox/double_list.hpp include/gromox/dsn.hpp include/gromox/eid_array.hpp include/gromox/element_data.hpp include/gromox/exmdb_client.hpp include/gromox/exmdb_common_util.hpp include/gromox/exmdb_ext.hpp include/gromox/exmdb_idef.hpp include/gromox/exmdb_provider_client.hpp include/gromox/exmdb_rpc.hpp include/gromox/exmdb_server.hpp include/gromox/ext_buffer.hpp -header_files += include/gromox/fileio.h include/gromox/flat_set.hpp include/gromox/flusher_common.h include/gromox/freebusy.hpp include/gromox/gab.hpp include/gromox/generic_connection.hpp include/gromox/hook_common.h include/gromox/hpm_common.h include/gromox/http.hpp include/gromox/ical.hpp include/gromox/icase.hpp include/gromox/json.hpp include/gromox/list_file.hpp include/gromox/lzxpress.hpp include/gromox/mail.hpp include/gromox/mail_func.hpp include/gromox/mapi_types.hpp include/gromox/mapidefs.h include/gromox/mapierr.hpp include/gromox/mapitags.hpp include/gromox/midb.hpp include/gromox/midb_agent.hpp include/gromox/mime.hpp include/gromox/mjson.hpp include/gromox/mysql_adaptor.hpp include/gromox/ndr.hpp include/gromox/ntlmssp.hpp include/gromox/oxcmail.hpp include/gromox/oxoabkt.hpp +header_files = include/gromox/ab_tree.hpp include/gromox/algorithm.hpp include/gromox/archive.hpp include/gromox/atomic.hpp include/gromox/authmgr.hpp include/gromox/bounce_gen.hpp include/gromox/clock.hpp include/gromox/common_types.hpp include/gromox/config_file.hpp include/gromox/contexts_pool.hpp include/gromox/cookie_parser.hpp include/gromox/cryptoutil.hpp include/gromox/database.h include/gromox/database_mysql.hpp include/gromox/dbop.h include/gromox/dcerpc.hpp include/gromox/defs.h include/gromox/double_list.hpp include/gromox/dsn.hpp include/gromox/eid_array.hpp include/gromox/element_data.hpp include/gromox/exmdb_client.hpp include/gromox/exmdb_common_util.hpp include/gromox/exmdb_ext.hpp include/gromox/exmdb_idef.hpp include/gromox/exmdb_provider_client.hpp include/gromox/exmdb_rpc.hpp include/gromox/exmdb_server.hpp include/gromox/ext_buffer.hpp +header_files += include/gromox/fileio.h include/gromox/flat_set.hpp include/gromox/flusher_common.h include/gromox/freebusy.hpp include/gromox/gab.hpp include/gromox/generic_connection.hpp include/gromox/hook_common.h include/gromox/hpm_common.h include/gromox/http.hpp include/gromox/ical.hpp include/gromox/icase.hpp include/gromox/json.hpp include/gromox/list_file.hpp include/gromox/lzxpress.hpp include/gromox/mail.hpp include/gromox/mail_func.hpp include/gromox/mapi_types.hpp include/gromox/mapidefs.h include/gromox/mapierr.hpp include/gromox/mapitags.hpp include/gromox/midb.hpp include/gromox/midb_agent.hpp include/gromox/mime.hpp include/gromox/mjson.hpp include/gromox/mysql_adaptor.hpp include/gromox/ndr.hpp include/gromox/oxcmail.hpp include/gromox/oxoabkt.hpp header_files += include/gromox/paths.h include/gromox/pcl.hpp include/gromox/plugin.hpp include/gromox/proc_common.h include/gromox/process.hpp include/gromox/proptag_array.hpp include/gromox/propval.hpp include/gromox/range_set.hpp include/gromox/resource_pool.hpp include/gromox/restriction.hpp include/gromox/rop_util.hpp include/gromox/rpc_types.hpp include/gromox/rule_actions.hpp include/gromox/safeint.hpp include/gromox/simple_tree.hpp include/gromox/sortorder_set.hpp include/gromox/stream.hpp include/gromox/svc_common.h include/gromox/svc_loader.hpp include/gromox/textmaps.hpp include/gromox/threads_pool.hpp include/gromox/tie.hpp include/gromox/tnef.hpp include/gromox/usercvt.hpp include/gromox/util.hpp include/gromox/vcard.hpp include/gromox/xarray2.hpp include/gromox/zcore_client.hpp include/gromox/zcore_rpc.hpp include/gromox/zcore_types.hpp include/gromox/zz_ndr_stack.hpp if ENABLE_PRIVATE_HEADERS pkginclude_HEADERS = ${header_files} diff --git a/lib/rpc/ntlmssp.cpp b/exch/http/ntlmssp.cpp similarity index 97% rename from lib/rpc/ntlmssp.cpp rename to exch/http/ntlmssp.cpp index 083b2027e..96cb7fd27 100644 --- a/lib/rpc/ntlmssp.cpp +++ b/exch/http/ntlmssp.cpp @@ -16,13 +16,12 @@ #include #include #include -#include #include #include #include #include -#include #include +#include "ntlmssp.hpp" #define MSVAVEOL 0 #define MSVAVNBCOMPUTERNAME 1 @@ -136,6 +135,56 @@ static uint32_t crc32_calc_buffer(const uint8_t *p, size_t z) return ~crc; } +/* + * An implementation of the arcfour algorithm + * Copyright (C) Andrew Tridgell 1998 + */ +/* initialise the arcfour sbox with key */ +static void arcfour_init(ARCFOUR_STATE *pstate, const uint8_t *keydata, size_t keylen) +{ + uint8_t tc; + uint8_t j = 0; + + for (size_t i = 0; i < sizeof(pstate->sbox); ++i) + pstate->sbox[i] = (uint8_t)i; + for (size_t i = 0; i < sizeof(pstate->sbox); ++i) { + j += pstate->sbox[i] + keydata[i%keylen]; + tc = pstate->sbox[i]; + pstate->sbox[i] = pstate->sbox[j]; + pstate->sbox[j] = tc; + } + pstate->index_i = 0; + pstate->index_j = 0; +} + +/* crypt the data with arcfour */ +static void arcfour_crypt_sbox(ARCFOUR_STATE *pstate, uint8_t *pdata, int len) +{ + int i; + uint8_t t; + uint8_t tc; + + for (i = 0; i < len; i++) { + + pstate->index_i++; + pstate->index_j += pstate->sbox[pstate->index_i]; + + tc = pstate->sbox[pstate->index_i]; + pstate->sbox[pstate->index_i] = pstate->sbox[pstate->index_j]; + pstate->sbox[pstate->index_j] = tc; + + t = pstate->sbox[pstate->index_i] + pstate->sbox[pstate->index_j]; + pdata[i] = pdata[i] ^ pstate->sbox[t]; + } +} + +static void arcfour_crypt(uint8_t *pdata, const uint8_t keystr[16], int len) +{ + ARCFOUR_STATE state; + arcfour_init(&state, keystr, 16); + arcfour_crypt_sbox(&state, pdata, len); +} + static void str_to_key(const uint8_t *s, uint8_t *k) { k[0] = s[0] >> 1; diff --git a/include/gromox/ntlmssp.hpp b/exch/http/ntlmssp.hpp similarity index 97% rename from include/gromox/ntlmssp.hpp rename to exch/http/ntlmssp.hpp index a9458143c..fae7a0231 100644 --- a/include/gromox/ntlmssp.hpp +++ b/exch/http/ntlmssp.hpp @@ -1,8 +1,8 @@ #pragma once #include #include -#include #include + #define NTLMSSP_PROCESS_NEGOTIATE 1 #define NTLMSSP_PROCESS_CHALLENGE 2 #define NTLMSSP_PROCESS_AUTH 3 @@ -35,6 +35,12 @@ #define NTLMSSP_NEGOTIATE_KEY_EXCH 0x40000000 #define NTLMSSP_NEGOTIATE_56 0x80000000 +struct ARCFOUR_STATE { + uint8_t sbox[256]; + uint8_t index_i; + uint8_t index_j; +}; + struct GX_EXPORT NTLMSSP_SESSION_INFO { char username[UADDR_SIZE]; DATA_BLOB session_key; diff --git a/exch/http/pdu_processor.hpp b/exch/http/pdu_processor.hpp index 42a170de8..cbfbcc440 100644 --- a/exch/http/pdu_processor.hpp +++ b/exch/http/pdu_processor.hpp @@ -8,9 +8,9 @@ #include #include #include -#include #include #include +#include "ntlmssp.hpp" #include "pdu_ndr.hpp" #define DCERPC_BASE_MARSHALL_SIZE (16*1024) #define DISPATCH_FAIL 0 diff --git a/lib/rpc/ndr.cpp b/exch/ndr.cpp similarity index 100% rename from lib/rpc/ndr.cpp rename to exch/ndr.cpp diff --git a/include/gromox/arcfour.hpp b/include/gromox/arcfour.hpp deleted file mode 100644 index 633a59ef0..000000000 --- a/include/gromox/arcfour.hpp +++ /dev/null @@ -1,13 +0,0 @@ -#pragma once -#include -#include - -struct GX_EXPORT ARCFOUR_STATE { - uint8_t sbox[256]; - uint8_t index_i; - uint8_t index_j; -}; - -extern GX_EXPORT void arcfour_init(ARCFOUR_STATE *pstate, const uint8_t *key, size_t keylen); -extern GX_EXPORT void arcfour_crypt_sbox(ARCFOUR_STATE *pstate, uint8_t *pdata, int len); -extern GX_EXPORT void arcfour_crypt(uint8_t *pdata, const uint8_t keystr[16], int len); diff --git a/lib/rpc/arcfour.cpp b/lib/rpc/arcfour.cpp deleted file mode 100644 index b01080349..000000000 --- a/lib/rpc/arcfour.cpp +++ /dev/null @@ -1,69 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-or-later -/* - Unix SMB/CIFS implementation. - - An implementation of the arcfour algorithm - - Copyright (C) Andrew Tridgell 1998 - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . -*/ -#include -#include - -/* initialise the arcfour sbox with key */ -void arcfour_init(ARCFOUR_STATE *pstate, const uint8_t *keydata, size_t keylen) -{ - uint8_t tc; - uint8_t j = 0; - - for (size_t i = 0; i < sizeof(pstate->sbox); ++i) - pstate->sbox[i] = (uint8_t)i; - for (size_t i = 0; i < sizeof(pstate->sbox); ++i) { - j += pstate->sbox[i] + keydata[i%keylen]; - tc = pstate->sbox[i]; - pstate->sbox[i] = pstate->sbox[j]; - pstate->sbox[j] = tc; - } - pstate->index_i = 0; - pstate->index_j = 0; -} - -/* crypt the data with arcfour */ -void arcfour_crypt_sbox(ARCFOUR_STATE *pstate, uint8_t *pdata, int len) -{ - int i; - uint8_t t; - uint8_t tc; - - for (i=0; iindex_i++; - pstate->index_j += pstate->sbox[pstate->index_i]; - - tc = pstate->sbox[pstate->index_i]; - pstate->sbox[pstate->index_i] = pstate->sbox[pstate->index_j]; - pstate->sbox[pstate->index_j] = tc; - - t = pstate->sbox[pstate->index_i] + pstate->sbox[pstate->index_j]; - pdata[i] = pdata[i] ^ pstate->sbox[t]; - } -} - -void arcfour_crypt(uint8_t *pdata, const uint8_t keystr[16], int len) -{ - ARCFOUR_STATE state; - arcfour_init(&state, keystr, 16); - arcfour_crypt_sbox(&state, pdata, len); -} From d2a2d37ac2072e04dbfbe7a2e91886dbe72fa3d5 Mon Sep 17 00:00:00 2001 From: Jan Engelhardt Date: Sun, 26 Oct 2025 21:08:03 +0100 Subject: [PATCH 14/32] lib: move HMACMD5_CTX to exch/http/ntlmssp.cpp --- exch/http/ntlmssp.cpp | 52 +++++++++++++++++++++++++++++++++++ include/gromox/cryptoutil.hpp | 14 +--------- lib/cryptoutil.cpp | 39 -------------------------- 3 files changed, 53 insertions(+), 52 deletions(-) diff --git a/exch/http/ntlmssp.cpp b/exch/http/ntlmssp.cpp index 96cb7fd27..d50645917 100644 --- a/exch/http/ntlmssp.cpp +++ b/exch/http/ntlmssp.cpp @@ -72,6 +72,19 @@ struct NTLMSSP_VERSION { uint8_t ntlm_revers; }; +struct GX_EXPORT HMACMD5_CTX { + HMACMD5_CTX() = default; + HMACMD5_CTX(const void *key, size_t len); + bool update(const void *text, size_t len); + bool finish(void *output); + bool is_valid() const { return valid_flag; } + + protected: + std::unique_ptr osslctx; + uint8_t k_ipad[65]{}, k_opad[65]{}; + bool valid_flag = false; +}; + } /* G(x) = x^32+x^26+x^23+x^22+x^16+x^12+x^11+x^10+x^8+x^7+x^5+x^4+x^2+x^1+x^0 */ @@ -185,6 +198,45 @@ static void arcfour_crypt(uint8_t *pdata, const uint8_t keystr[16], int len) arcfour_crypt_sbox(&state, pdata, len); } +/* the microsoft version of hmac_md5 initialisation */ +HMACMD5_CTX::HMACMD5_CTX(const void *key, size_t key_len) : + osslctx(EVP_MD_CTX_new()) +{ + if (osslctx == nullptr) + return; + if (key_len > 64) + key_len = 64; + memcpy(k_ipad, key, key_len); + memcpy(k_opad, key, key_len); + /* XOR key with ipad and opad values */ + for (size_t i = 0; i < 64; ++i) { + k_ipad[i] ^= 0x36; + k_opad[i] ^= 0x5c; + } + if (EVP_DigestInit(osslctx.get(), EVP_md5()) <= 0 || + EVP_DigestUpdate(osslctx.get(), k_ipad, 64) <= 0) + return; + valid_flag = true; +} + +bool HMACMD5_CTX::update(const void *text, size_t text_len) +{ + return EVP_DigestUpdate(osslctx.get(), text, text_len) > 0; +} + +bool HMACMD5_CTX::finish(void *digest) +{ + decltype(osslctx) ctx_o(EVP_MD_CTX_new()); + if (ctx_o == nullptr || + EVP_DigestFinal(osslctx.get(), static_cast(digest), nullptr) <= 0 || + EVP_DigestInit(ctx_o.get(), EVP_md5()) <= 0 || + EVP_DigestUpdate(ctx_o.get(), k_opad, 64) <= 0 || + EVP_DigestUpdate(ctx_o.get(), digest, 16) <= 0 || + EVP_DigestFinal(ctx_o.get(), static_cast(digest), nullptr) <= 0) + return false; + return true; +} + static void str_to_key(const uint8_t *s, uint8_t *k) { k[0] = s[0] >> 1; diff --git a/include/gromox/cryptoutil.hpp b/include/gromox/cryptoutil.hpp index 0f3a06e88..2651ce6ab 100644 --- a/include/gromox/cryptoutil.hpp +++ b/include/gromox/cryptoutil.hpp @@ -1,6 +1,7 @@ #pragma once #include #include +#include #include #include #include @@ -12,19 +13,6 @@ struct GX_EXPORT sslfree { inline void operator()(EVP_MD_CTX *x) const { EVP_MD_CTX_free(x); } }; -struct GX_EXPORT HMACMD5_CTX { - HMACMD5_CTX() = default; - HMACMD5_CTX(const void *key, size_t len); - bool update(const void *text, size_t len); - bool finish(void *output); - bool is_valid() const { return valid_flag; } - - protected: - std::unique_ptr osslctx; - uint8_t k_ipad[65]{}, k_opad[65]{}; - bool valid_flag = false; -}; - extern GX_EXPORT int tls_set_min_proto(SSL_CTX *, const char *); extern GX_EXPORT void tls_set_renego(SSL_CTX *); extern GX_EXPORT std::string sss_obf_reverse(const std::string_view &); diff --git a/lib/cryptoutil.cpp b/lib/cryptoutil.cpp index 20373c955..b06f6f924 100644 --- a/lib/cryptoutil.cpp +++ b/lib/cryptoutil.cpp @@ -12,45 +12,6 @@ namespace gromox { -/* the microsoft version of hmac_md5 initialisation */ -HMACMD5_CTX::HMACMD5_CTX(const void *key, size_t key_len) : - osslctx(EVP_MD_CTX_new()) -{ - if (osslctx == nullptr) - return; - if (key_len > 64) - key_len = 64; - memcpy(k_ipad, key, key_len); - memcpy(k_opad, key, key_len); - /* XOR key with ipad and opad values */ - for (size_t i = 0; i < 64; ++i) { - k_ipad[i] ^= 0x36; - k_opad[i] ^= 0x5c; - } - if (EVP_DigestInit(osslctx.get(), EVP_md5()) <= 0 || - EVP_DigestUpdate(osslctx.get(), k_ipad, 64) <= 0) - return; - valid_flag = true; -} - -bool HMACMD5_CTX::update(const void *text, size_t text_len) -{ - return EVP_DigestUpdate(osslctx.get(), text, text_len) > 0; -} - -bool HMACMD5_CTX::finish(void *digest) -{ - decltype(osslctx) ctx_o(EVP_MD_CTX_new()); - if (ctx_o == nullptr || - EVP_DigestFinal(osslctx.get(), static_cast(digest), nullptr) <= 0 || - EVP_DigestInit(ctx_o.get(), EVP_md5()) <= 0 || - EVP_DigestUpdate(ctx_o.get(), k_opad, 64) <= 0 || - EVP_DigestUpdate(ctx_o.get(), digest, 16) <= 0 || - EVP_DigestFinal(ctx_o.get(), static_cast(digest), nullptr) <= 0) - return false; - return true; -} - int tls_set_min_proto(SSL_CTX *ctx, const char *p) { if (p == nullptr) From c0df0941c7468166da67cd3b4c56a6a643263aaa Mon Sep 17 00:00:00 2001 From: Jan Engelhardt Date: Sun, 26 Oct 2025 22:34:39 +0100 Subject: [PATCH 15/32] http: convert ARCFOUR_STATE functions to member functions --- exch/http/ntlmssp.cpp | 48 ++++++++++++++++++------------------------- exch/http/ntlmssp.hpp | 4 ++++ 2 files changed, 24 insertions(+), 28 deletions(-) diff --git a/exch/http/ntlmssp.cpp b/exch/http/ntlmssp.cpp index d50645917..1e342a842 100644 --- a/exch/http/ntlmssp.cpp +++ b/exch/http/ntlmssp.cpp @@ -153,8 +153,9 @@ static uint32_t crc32_calc_buffer(const uint8_t *p, size_t z) * Copyright (C) Andrew Tridgell 1998 */ /* initialise the arcfour sbox with key */ -static void arcfour_init(ARCFOUR_STATE *pstate, const uint8_t *keydata, size_t keylen) +void ARCFOUR_STATE::init(const uint8_t *keydata, size_t keylen) { + auto pstate = this; uint8_t tc; uint8_t j = 0; @@ -171,8 +172,9 @@ static void arcfour_init(ARCFOUR_STATE *pstate, const uint8_t *keydata, size_t k } /* crypt the data with arcfour */ -static void arcfour_crypt_sbox(ARCFOUR_STATE *pstate, uint8_t *pdata, int len) +void ARCFOUR_STATE::crypt_sbox(uint8_t *pdata, int len) { + auto pstate = this; int i; uint8_t t; uint8_t tc; @@ -191,11 +193,11 @@ static void arcfour_crypt_sbox(ARCFOUR_STATE *pstate, uint8_t *pdata, int len) } } -static void arcfour_crypt(uint8_t *pdata, const uint8_t keystr[16], int len) +void ARCFOUR_STATE::crypt(uint8_t *pdata, const uint8_t keystr[16], int len) { ARCFOUR_STATE state; - arcfour_init(&state, keystr, 16); - arcfour_crypt_sbox(&state, pdata, len); + state.init(keystr, 16); + state.crypt_sbox(pdata, len); } /* the microsoft version of hmac_md5 initialisation */ @@ -1264,8 +1266,7 @@ static bool ntlmssp_sign_init(NTLMSSP_CTX *pntlmssp) /* SEND: seal ARCFOUR pad */ if (!ntlmssp_calc_ntlm2_key(send_seal_buff, weak_key, send_seal_const)) return false; - arcfour_init(&pntlmssp->crypt.ntlm2.sending.seal_state, - send_seal_blob.pb, send_seal_blob.cb); + pntlmssp->crypt.ntlm2.sending.seal_state.init(send_seal_blob.pb, send_seal_blob.cb); /* SEND: seq num */ pntlmssp->crypt.ntlm2.sending.seq_num = 0; @@ -1279,8 +1280,7 @@ static bool ntlmssp_sign_init(NTLMSSP_CTX *pntlmssp) if (!ntlmssp_calc_ntlm2_key(recv_seal_buff, weak_key, recv_seal_const)) return false; - arcfour_init(&pntlmssp->crypt.ntlm2.receiving.seal_state, - recv_seal_blob.pb, recv_seal_blob.cb); + pntlmssp->crypt.ntlm2.receiving.seal_state.init(recv_seal_blob.pb, recv_seal_blob.cb); /* RECV: seq num */ pntlmssp->crypt.ntlm2.receiving.seq_num = 0; @@ -1306,8 +1306,7 @@ static bool ntlmssp_sign_init(NTLMSSP_CTX *pntlmssp) } } - arcfour_init(&pntlmssp->crypt.ntlm.seal_state, - seal_key.pb, seal_key.cb); + pntlmssp->crypt.ntlm.seal_state.init(seal_key.pb, seal_key.cb); pntlmssp->crypt.ntlm.seq_num = 0; } return true; @@ -1385,7 +1384,7 @@ static bool ntlmssp_server_postauth(NTLMSSP_CTX *pntlmssp, session_key.cb); pntlmssp->session_key.cb = session_key.cb; } else { - arcfour_crypt(pauth->encrypted_session_key.pb, session_key.pb, + ARCFOUR_STATE::crypt(pauth->encrypted_session_key.pb, session_key.pb, pauth->encrypted_session_key.cb); memcpy(pntlmssp->session_key.pb, pauth->encrypted_session_key.pb, pauth->encrypted_session_key.cb); @@ -1495,8 +1494,7 @@ static bool ntlmssp_make_packet_signature(NTLMSSP_CTX *pntlmssp, 0, crc, pntlmssp->crypt.ntlm.seq_num)) return false; pntlmssp->crypt.ntlm.seq_num ++; - arcfour_crypt_sbox(&pntlmssp->crypt.ntlm.seal_state, - &psig->pb[4], psig->cb - 4); + pntlmssp->crypt.ntlm.seal_state.crypt_sbox(&psig->pb[4], psig->cb - 4); return true; } @@ -1521,12 +1519,10 @@ static bool ntlmssp_make_packet_signature(NTLMSSP_CTX *pntlmssp, if (encrypt_sig && (pntlmssp->neg_flags & NTLMSSP_NEGOTIATE_KEY_EXCH)) { switch (direction) { case NTLMSSP_DIRECTION_SEND: - arcfour_crypt_sbox(&pntlmssp->crypt.ntlm2.sending.seal_state, - digest, 8); + pntlmssp->crypt.ntlm2.sending.seal_state.crypt_sbox(digest, 8); break; case NTLMSSP_DIRECTION_RECEIVE: - arcfour_crypt_sbox(&pntlmssp->crypt.ntlm2.receiving.seal_state, - digest, 8); + pntlmssp->crypt.ntlm2.receiving.seal_state.crypt_sbox(digest, 8); break; } } @@ -1621,19 +1617,16 @@ bool ntlmssp_ctx::seal_packet(uint8_t *pdata, size_t length, if (!ntlmssp_make_packet_signature(pntlmssp, pdata, length, pwhole_pdu, pdu_length, NTLMSSP_DIRECTION_SEND, psig, false)) return false; - arcfour_crypt_sbox(&pntlmssp->crypt.ntlm2.sending.seal_state, - pdata, length); + pntlmssp->crypt.ntlm2.sending.seal_state.crypt_sbox(pdata, length); if (pntlmssp->neg_flags & NTLMSSP_NEGOTIATE_KEY_EXCH) - arcfour_crypt_sbox(&pntlmssp->crypt.ntlm2.sending.seal_state, - &psig->pb[4], 8); + pntlmssp->crypt.ntlm2.sending.seal_state.crypt_sbox(&psig->pb[4], 8); } else { crc = crc32_calc_buffer(pdata, length); if (!ntlmssp_gen_packet(psig, "dddd", NTLMSSP_SIGN_VERSION, 0, crc, pntlmssp->crypt.ntlm.seq_num)) return false; - arcfour_crypt_sbox(&pntlmssp->crypt.ntlm.seal_state, pdata, length); - arcfour_crypt_sbox(&pntlmssp->crypt.ntlm.seal_state, - &psig->pb[4], psig->cb - 4); + pntlmssp->crypt.ntlm.seal_state.crypt_sbox(pdata, length); + pntlmssp->crypt.ntlm.seal_state.crypt_sbox(&psig->pb[4], psig->cb - 4); pntlmssp->crypt.ntlm.seq_num ++; } return true; @@ -1651,10 +1644,9 @@ bool ntlmssp_ctx::unseal_packet(uint8_t *pdata, } if (pntlmssp->neg_flags & NTLMSSP_NEGOTIATE_NTLM2) /* First unseal the data. */ - arcfour_crypt_sbox(&pntlmssp->crypt.ntlm2.receiving.seal_state, - pdata, length); + pntlmssp->crypt.ntlm2.receiving.seal_state.crypt_sbox(pdata, length); else - arcfour_crypt_sbox(&pntlmssp->crypt.ntlm.seal_state, pdata, length); + pntlmssp->crypt.ntlm.seal_state.crypt_sbox(pdata, length); if (!ntlmssp_check_packet_internal(pntlmssp, pdata, length, pwhole_pdu, pdu_length, psig)) return false; diff --git a/exch/http/ntlmssp.hpp b/exch/http/ntlmssp.hpp index fae7a0231..d79f2c4fd 100644 --- a/exch/http/ntlmssp.hpp +++ b/exch/http/ntlmssp.hpp @@ -36,6 +36,10 @@ #define NTLMSSP_NEGOTIATE_56 0x80000000 struct ARCFOUR_STATE { + void init(const uint8_t *key, size_t len); + void crypt_sbox(uint8_t *data, int len); + static void crypt(uint8_t *data, const uint8_t key[16], int datalen); + uint8_t sbox[256]; uint8_t index_i; uint8_t index_j; From 9e5a113d2747b270522df3c21540cfa763968b49 Mon Sep 17 00:00:00 2001 From: Jan Engelhardt Date: Sun, 26 Oct 2025 23:07:24 +0100 Subject: [PATCH 16/32] build: rename gxh and gxp component libraries to have more natural names --- Makefile.am | 68 +++++++++++++++++++++++----------------------- exch/http/main.cpp | 16 +++++------ 2 files changed, 42 insertions(+), 42 deletions(-) diff --git a/Makefile.am b/Makefile.am index 8b0a15d3e..b9d336dd8 100644 --- a/Makefile.am +++ b/Makefile.am @@ -9,7 +9,7 @@ AM_DISTCHECK_CONFIGURE_FLAGS = --with-dcprefix='$${prefix}' AM_TESTS_ENVIRONMENT = export TEST_PATH=${top_srcdir}/data; # lib_LTL must be dependency-ordered, or make install fails -lib_LTLIBRARIES = libgromox_common.la libgromox_dbop.la libgromox_epoll.la libgromox_mapi.la libgromox_exrpc.la libgromox_ndr.la libgxs_mysql_adaptor.la libgromox_abtree.la libgromox_auth.la libgromox_authz.la libgxm_alias_resolve.la libgxm_exmdb_local.la libgxh_ews.la libgxh_mh_emsmdb.la libgxh_mh_nsp.la libgxh_oab.la libgxh_oxdisco.la libgxp_exchange_emsmdb.la libgxp_exchange_nsp.la libgxp_exchange_rfr.la libgxs_exmdb_provider.la libgxs_event_proxy.la libgxs_event_stub.la libgxs_midb_agent.la libgxs_timer_agent.la libgxs_ruleproc.la +lib_LTLIBRARIES = libgromox_common.la libgromox_dbop.la libgromox_epoll.la libgromox_mapi.la libgromox_exrpc.la libgromox_ndr.la libgxs_mysql_adaptor.la libgromox_abtree.la libgromox_auth.la libgromox_authz.la libgxm_alias_resolve.la libgxm_exmdb_local.la libgromox_ews.la libgromox_mh_emsmdb.la libgromox_mh_nsp.la libgromox_oab.la libgromox_oxdisco.la libgromox_emsmdb.la libgromox_nsp.la libgromox_rfr.la libgxs_exmdb_provider.la libgxs_event_proxy.la libgxs_event_stub.la libgxs_midb_agent.la libgxs_timer_agent.la libgxs_ruleproc.la pkglibexec_PROGRAMS = delivery delivery-queue event gromox-snapshot http imap midb pop3 timer zcore tools/authtry tools/eidprint tools/textmapquery if WITH_GNU_LD @@ -111,7 +111,7 @@ libgxs_midb_agent_la_LIBADD = -lpthread ${fmt_LIBS} ${libHX_LIBS} libgromox_comm EXTRA_libgxs_midb_agent_la_DEPENDENCIES = default.sym http_SOURCES = exch/http/cache.cpp exch/http/cache.hpp exch/http/fastcgi.cpp exch/http/fastcgi.hpp exch/http/hpm_processor.cpp exch/http/hpm_processor.hpp exch/http/http_parser.cpp exch/http/http_parser.hpp exch/http/listener.cpp exch/http/listener.hpp exch/http/main.cpp exch/http/ntlmssp.cpp exch/http/ntlmssp.hpp exch/http/pdu_ndr.cpp exch/http/pdu_ndr.hpp exch/http/pdu_ndr_ids.hpp exch/http/pdu_processor.cpp exch/http/pdu_processor.hpp exch/http/resource.hpp exch/http/rewrite2.cpp exch/http/rewrite.hpp exch/http/system_services.cpp exch/http/system_services.hpp -http_LDADD = -lpthread ${libcrypto_LIBS} ${fmt_LIBS} ${gss_LIBS} ${iconv_LIBS} ${libHX_LIBS} ${libssl_LIBS} libgromox_auth.la libgromox_authz.la libgromox_common.la libgromox_epoll.la libgromox_ndr.la libgromox_mapi.la libgxh_ews.la libgxh_mh_emsmdb.la libgxh_mh_nsp.la libgxh_oab.la libgxh_oxdisco.la libgxp_exchange_emsmdb.la libgxp_exchange_nsp.la libgxp_exchange_rfr.la libgxs_exmdb_provider.la libgxs_mysql_adaptor.la libgxs_timer_agent.la +http_LDADD = -lpthread ${libcrypto_LIBS} ${fmt_LIBS} ${gss_LIBS} ${iconv_LIBS} ${libHX_LIBS} ${libssl_LIBS} libgromox_auth.la libgromox_authz.la libgromox_common.la libgromox_epoll.la libgromox_ndr.la libgromox_mapi.la libgromox_ews.la libgromox_mh_emsmdb.la libgromox_mh_nsp.la libgromox_oab.la libgromox_oxdisco.la libgromox_emsmdb.la libgromox_nsp.la libgromox_rfr.la libgxs_exmdb_provider.la libgxs_mysql_adaptor.la libgxs_timer_agent.la midb_SOURCES = exch/midb/cmd_parser.cpp exch/midb/cmd_parser.hpp exch/midb/common_util.cpp exch/midb/common_util.hpp exch/midb/exmdb_client.hpp exch/midb/mail_engine.cpp exch/midb/mail_engine.hpp exch/midb/main.cpp exch/midb/system_services.hpp midb_LDADD = -lpthread ${libHX_LIBS} ${fmt_LIBS} ${iconv_LIBS} ${jsoncpp_LIBS} ${libssl_LIBS} ${sqlite_LIBS} ${vmime_LIBS} libgromox_auth.la libgromox_common.la libgromox_dbop.la libgromox_exrpc.la libgromox_mapi.la libgxs_event_proxy.la libgxs_mysql_adaptor.la zcore_SOURCES = exch/gab.cpp exch/zcore/ab_tree.cpp exch/zcore/ab_tree.hpp exch/zcore/attachment_object.cpp exch/zcore/bounce_producer.hpp exch/zcore/common_util.cpp exch/zcore/common_util.hpp exch/zcore/container_object.cpp exch/zcore/exmdb_client.cpp exch/zcore/exmdb_client.hpp exch/zcore/folder_object.cpp exch/zcore/ics_state.cpp exch/zcore/ics_state.hpp exch/zcore/icsdownctx_object.cpp exch/zcore/icsupctx_object.cpp exch/zcore/main.cpp exch/zcore/message_object.cpp exch/zcore/names.cpp exch/zcore/object_tree.cpp exch/zcore/object_tree.hpp exch/zcore/objects.hpp exch/zcore/rpc_ext.cpp exch/zcore/rpc_ext.hpp exch/zcore/rpc_parser.cpp exch/zcore/rpc_parser.hpp exch/zcore/store_object.cpp exch/zcore/store_object.hpp exch/zcore/system_services.hpp exch/zcore/table_object.cpp exch/zcore/table_object.hpp exch/zcore/user_object.cpp exch/zcore/zserver.cpp exch/zcore/zserver.hpp @@ -125,39 +125,39 @@ libgxs_timer_agent_la_SOURCES = exch/timer_agent.cpp libgxs_timer_agent_la_LDFLAGS = ${default_SYFLAGS} libgxs_timer_agent_la_LIBADD = -lpthread ${libHX_LIBS} libgromox_common.la EXTRA_libgxs_timer_agent_la_DEPENDENCIES = default.sym -libgxp_exchange_emsmdb_la_SOURCES = exch/emsmdb/asyncemsmdb_interface.cpp exch/emsmdb/asyncemsmdb_interface.hpp exch/emsmdb/attachment_object.cpp exch/emsmdb/attachment_object.hpp exch/emsmdb/aux_ext.cpp exch/emsmdb/aux_types.hpp exch/emsmdb/common_util.cpp exch/emsmdb/common_util.hpp exch/emsmdb/emsmdb_interface.cpp exch/emsmdb/emsmdb_interface.hpp exch/emsmdb/emsmdb_ndr.cpp exch/emsmdb/emsmdb_ndr.hpp exch/emsmdb/exmdb_client.cpp exch/emsmdb/exmdb_client.hpp exch/emsmdb/fastdownctx_object.cpp exch/emsmdb/fastdownctx_object.hpp exch/emsmdb/fastupctx_object.cpp exch/emsmdb/fastupctx_object.hpp exch/emsmdb/folder_object.cpp exch/emsmdb/folder_object.hpp exch/emsmdb/ftstream_parser.cpp exch/emsmdb/ftstream_parser.hpp exch/emsmdb/ftstream_producer.cpp exch/emsmdb/ftstream_producer.hpp exch/emsmdb/ics_state.cpp exch/emsmdb/ics_state.hpp exch/emsmdb/icsdownctx_object.cpp exch/emsmdb/icsdownctx_object.hpp exch/emsmdb/logon_object.cpp exch/emsmdb/logon_object.hpp exch/emsmdb/main.cpp exch/emsmdb/message_object.cpp exch/emsmdb/message_object.hpp exch/emsmdb/names.cpp exch/emsmdb/notify.cpp exch/emsmdb/notify_response.hpp exch/emsmdb/oxcfold.cpp exch/emsmdb/oxcfxics.cpp exch/emsmdb/oxcmsg.cpp exch/emsmdb/oxcprpt.cpp exch/emsmdb/oxcstore.cpp exch/emsmdb/oxctabl.cpp exch/emsmdb/oxomsg.cpp exch/emsmdb/processor_types.hpp exch/emsmdb/rop_dispatch.cpp exch/emsmdb/rop_dispatch.hpp exch/emsmdb/rop_ext.cpp exch/emsmdb/rop_ext.hpp exch/emsmdb/rop_funcs.hpp exch/emsmdb/rop_ids.hpp exch/emsmdb/rop_processor.cpp exch/emsmdb/rop_processor.hpp exch/emsmdb/stream_object.cpp exch/emsmdb/stream_object.hpp exch/emsmdb/table_object.cpp exch/emsmdb/table_object.hpp -libgxp_exchange_emsmdb_la_LDFLAGS = ${default_SYFLAGS} -libgxp_exchange_emsmdb_la_LIBADD = -lpthread ${libHX_LIBS} ${iconv_LIBS} ${vmime_LIBS} libgromox_common.la libgromox_mapi.la libgromox_ndr.la libgxs_mysql_adaptor.la -EXTRA_libgxp_exchange_emsmdb_la_DEPENDENCIES = default.sym -libgxp_exchange_nsp_la_SOURCES = exch/nsp/common_util.cpp exch/nsp/common_util.hpp exch/nsp/main.cpp exch/nsp/nsp_interface.cpp exch/nsp/nsp_interface.hpp exch/nsp/nsp_ndr.cpp exch/nsp/nsp_ndr.hpp exch/nsp/nsp_types.hpp -libgxp_exchange_nsp_la_LDFLAGS = ${default_SYFLAGS} -libgxp_exchange_nsp_la_LIBADD = -lpthread ${libcrypto_LIBS} ${fmt_LIBS} ${libHX_LIBS} ${iconv_LIBS} libgromox_common.la libgromox_mapi.la libgromox_ndr.la libgxs_mysql_adaptor.la libgromox_abtree.la -EXTRA_libgxp_exchange_nsp_la_DEPENDENCIES = default.sym +libgromox_emsmdb_la_SOURCES = exch/emsmdb/asyncemsmdb_interface.cpp exch/emsmdb/asyncemsmdb_interface.hpp exch/emsmdb/attachment_object.cpp exch/emsmdb/attachment_object.hpp exch/emsmdb/aux_ext.cpp exch/emsmdb/aux_types.hpp exch/emsmdb/common_util.cpp exch/emsmdb/common_util.hpp exch/emsmdb/emsmdb_interface.cpp exch/emsmdb/emsmdb_interface.hpp exch/emsmdb/emsmdb_ndr.cpp exch/emsmdb/emsmdb_ndr.hpp exch/emsmdb/exmdb_client.cpp exch/emsmdb/exmdb_client.hpp exch/emsmdb/fastdownctx_object.cpp exch/emsmdb/fastdownctx_object.hpp exch/emsmdb/fastupctx_object.cpp exch/emsmdb/fastupctx_object.hpp exch/emsmdb/folder_object.cpp exch/emsmdb/folder_object.hpp exch/emsmdb/ftstream_parser.cpp exch/emsmdb/ftstream_parser.hpp exch/emsmdb/ftstream_producer.cpp exch/emsmdb/ftstream_producer.hpp exch/emsmdb/ics_state.cpp exch/emsmdb/ics_state.hpp exch/emsmdb/icsdownctx_object.cpp exch/emsmdb/icsdownctx_object.hpp exch/emsmdb/logon_object.cpp exch/emsmdb/logon_object.hpp exch/emsmdb/main.cpp exch/emsmdb/message_object.cpp exch/emsmdb/message_object.hpp exch/emsmdb/names.cpp exch/emsmdb/notify.cpp exch/emsmdb/notify_response.hpp exch/emsmdb/oxcfold.cpp exch/emsmdb/oxcfxics.cpp exch/emsmdb/oxcmsg.cpp exch/emsmdb/oxcprpt.cpp exch/emsmdb/oxcstore.cpp exch/emsmdb/oxctabl.cpp exch/emsmdb/oxomsg.cpp exch/emsmdb/processor_types.hpp exch/emsmdb/rop_dispatch.cpp exch/emsmdb/rop_dispatch.hpp exch/emsmdb/rop_ext.cpp exch/emsmdb/rop_ext.hpp exch/emsmdb/rop_funcs.hpp exch/emsmdb/rop_ids.hpp exch/emsmdb/rop_processor.cpp exch/emsmdb/rop_processor.hpp exch/emsmdb/stream_object.cpp exch/emsmdb/stream_object.hpp exch/emsmdb/table_object.cpp exch/emsmdb/table_object.hpp +libgromox_emsmdb_la_LDFLAGS = ${default_SYFLAGS} +libgromox_emsmdb_la_LIBADD = -lpthread ${libHX_LIBS} ${iconv_LIBS} ${vmime_LIBS} libgromox_common.la libgromox_mapi.la libgromox_ndr.la libgxs_mysql_adaptor.la +EXTRA_libgromox_emsmdb_la_DEPENDENCIES = default.sym +libgromox_nsp_la_SOURCES = exch/nsp/common_util.cpp exch/nsp/common_util.hpp exch/nsp/main.cpp exch/nsp/nsp_interface.cpp exch/nsp/nsp_interface.hpp exch/nsp/nsp_ndr.cpp exch/nsp/nsp_ndr.hpp exch/nsp/nsp_types.hpp +libgromox_nsp_la_LDFLAGS = ${default_SYFLAGS} +libgromox_nsp_la_LIBADD = -lpthread ${libcrypto_LIBS} ${fmt_LIBS} ${libHX_LIBS} ${iconv_LIBS} libgromox_common.la libgromox_mapi.la libgromox_ndr.la libgxs_mysql_adaptor.la libgromox_abtree.la +EXTRA_libgromox_nsp_la_DEPENDENCIES = default.sym EXTRA_DIST += exch/nsp/repr.cpp -libgxp_exchange_rfr_la_SOURCES = exch/rfr.cpp -libgxp_exchange_rfr_la_LDFLAGS = ${default_SYFLAGS} -libgxp_exchange_rfr_la_LIBADD = ${fmt_LIBS} ${libHX_LIBS} libgromox_common.la libgromox_ndr.la libgxs_mysql_adaptor.la -EXTRA_libgxp_exchange_rfr_la_DEPENDENCIES = default.sym -libgxh_ews_la_SOURCES = exch/ews/ObjectCache.hpp exch/ews/context.cpp exch/ews/enums.hpp exch/ews/ews.cpp exch/ews/ews.hpp exch/ews/exceptions.hpp exch/ews/hash.hpp exch/ews/namedtags.hpp exch/ews/requests.cpp exch/ews/requests.hpp exch/ews/serialization.cpp exch/ews/serialization.hpp exch/ews/soaputil.cpp exch/ews/soaputil.hpp exch/ews/structures.cpp exch/ews/structures.hpp -libgxh_ews_la_LDFLAGS = ${default_SYFLAGS} -libgxh_ews_la_LIBADD = ${libHX_LIBS} ${fmt_LIBS} ${tinyxml2_LIBS} ${vmime_LIBS} libgromox_abtree.la libgromox_common.la libgromox_mapi.la libgromox_exrpc.la libgxs_mysql_adaptor.la -EXTRA_libgxh_ews_la_DEPENDENCIES = default.sym -libgxh_mh_emsmdb_la_SOURCES = exch/mh/emsmdb.cpp exch/mh/mh_common.cpp exch/mh/mh_common.hpp -libgxh_mh_emsmdb_la_LDFLAGS = ${default_SYFLAGS} -libgxh_mh_emsmdb_la_LIBADD = -lpthread ${fmt_LIBS} libgromox_common.la libgromox_mapi.la -EXTRA_libgxh_mh_emsmdb_la_DEPENDENCIES = default.sym -libgxh_mh_nsp_la_SOURCES = exch/mh/mh_common.cpp exch/mh/mh_common.hpp exch/mh/nsp.cpp exch/mh/nsp_bridge.cpp exch/mh/nsp_bridge.hpp exch/mh/nsp_common.cpp exch/mh/nsp_common.hpp exch/mh/nsp_ops.cpp exch/mh/nsp_ops.hpp -libgxh_mh_nsp_la_LDFLAGS = ${default_SYFLAGS} -libgxh_mh_nsp_la_LIBADD = -lpthread ${fmt_LIBS} libgromox_common.la libgromox_mapi.la libgxs_mysql_adaptor.la -EXTRA_libgxh_mh_nsp_la_DEPENDENCIES = default.sym -libgxh_oxdisco_la_SOURCES = exch/oxdisco.cpp -libgxh_oxdisco_la_LDFLAGS = ${default_SYFLAGS} -libgxh_oxdisco_la_LIBADD = ${libHX_LIBS} ${fmt_LIBS} ${tinyxml2_LIBS} libgromox_common.la libgromox_mapi.la libgxs_mysql_adaptor.la -EXTRA_libgxh_oxdisco_la_DEPENDENCIES = default.sym -libgxh_oab_la_SOURCES = exch/oab.cpp -libgxh_oab_la_LDFLAGS = ${default_SYFLAGS} -libgxh_oab_la_LIBADD = libgromox_common.la -EXTRA_libgxh_oab_la_DEPENDENCIES = default.sym +libgromox_rfr_la_SOURCES = exch/rfr.cpp +libgromox_rfr_la_LDFLAGS = ${default_SYFLAGS} +libgromox_rfr_la_LIBADD = ${fmt_LIBS} ${libHX_LIBS} libgromox_common.la libgromox_ndr.la libgxs_mysql_adaptor.la +EXTRA_libgromox_rfr_la_DEPENDENCIES = default.sym +libgromox_ews_la_SOURCES = exch/ews/ObjectCache.hpp exch/ews/context.cpp exch/ews/enums.hpp exch/ews/ews.cpp exch/ews/ews.hpp exch/ews/exceptions.hpp exch/ews/hash.hpp exch/ews/namedtags.hpp exch/ews/requests.cpp exch/ews/requests.hpp exch/ews/serialization.cpp exch/ews/serialization.hpp exch/ews/soaputil.cpp exch/ews/soaputil.hpp exch/ews/structures.cpp exch/ews/structures.hpp +libgromox_ews_la_LDFLAGS = ${default_SYFLAGS} +libgromox_ews_la_LIBADD = ${libHX_LIBS} ${fmt_LIBS} ${tinyxml2_LIBS} ${vmime_LIBS} libgromox_abtree.la libgromox_common.la libgromox_mapi.la libgromox_exrpc.la libgxs_mysql_adaptor.la +EXTRA_libgromox_ews_la_DEPENDENCIES = default.sym +libgromox_mh_emsmdb_la_SOURCES = exch/mh/emsmdb.cpp exch/mh/mh_common.cpp exch/mh/mh_common.hpp +libgromox_mh_emsmdb_la_LDFLAGS = ${default_SYFLAGS} +libgromox_mh_emsmdb_la_LIBADD = -lpthread ${fmt_LIBS} libgromox_common.la libgromox_mapi.la +EXTRA_libgromox_mh_emsmdb_la_DEPENDENCIES = default.sym +libgromox_mh_nsp_la_SOURCES = exch/mh/mh_common.cpp exch/mh/mh_common.hpp exch/mh/nsp.cpp exch/mh/nsp_bridge.cpp exch/mh/nsp_bridge.hpp exch/mh/nsp_common.cpp exch/mh/nsp_common.hpp exch/mh/nsp_ops.cpp exch/mh/nsp_ops.hpp +libgromox_mh_nsp_la_LDFLAGS = ${default_SYFLAGS} +libgromox_mh_nsp_la_LIBADD = -lpthread ${fmt_LIBS} libgromox_common.la libgromox_mapi.la libgxs_mysql_adaptor.la +EXTRA_libgromox_mh_nsp_la_DEPENDENCIES = default.sym +libgromox_oxdisco_la_SOURCES = exch/oxdisco.cpp +libgromox_oxdisco_la_LDFLAGS = ${default_SYFLAGS} +libgromox_oxdisco_la_LIBADD = ${libHX_LIBS} ${fmt_LIBS} ${tinyxml2_LIBS} libgromox_common.la libgromox_mapi.la libgxs_mysql_adaptor.la +EXTRA_libgromox_oxdisco_la_DEPENDENCIES = default.sym +libgromox_oab_la_SOURCES = exch/oab.cpp +libgromox_oab_la_LDFLAGS = ${default_SYFLAGS} +libgromox_oab_la_LIBADD = libgromox_common.la +EXTRA_libgromox_oab_la_DEPENDENCIES = default.sym libgxs_mysql_adaptor_la_SOURCES = exch/mysql_adaptor/mysql_adaptor.cpp exch/mysql_adaptor/sql2.cpp exch/mysql_adaptor/sql2.hpp libgxs_mysql_adaptor_la_LDFLAGS = ${default_SYFLAGS} libgxs_mysql_adaptor_la_LIBADD = -lpthread ${crypt_LIBS} ${libHX_LIBS} ${fmt_LIBS} ${mysql_LIBS} libgromox_common.la libgromox_dbop.la libgromox_mapi.la diff --git a/exch/http/main.cpp b/exch/http/main.cpp index a5ce78c6b..dca893f7c 100644 --- a/exch/http/main.cpp +++ b/exch/http/main.cpp @@ -55,16 +55,16 @@ static constexpr HXoption g_options_table[] = { }; static constexpr static_module g_dfl_hpm_plugins[] = { - {"libgxh_ews.so", HPM_ews}, - {"libgxh_mh_emsmdb.so", HPM_mh_emsmdb}, - {"libgxh_mh_nsp.so", HPM_mh_nsp}, - {"libgxh_oxdisco.so", HPM_oxdisco}, - {"libgxh_oab.so", HPM_oab}, + {"libgromox_ews.so", HPM_ews}, + {"libgromox_mh_emsmdb.so", HPM_mh_emsmdb}, + {"libgromox_mh_nsp.so", HPM_mh_nsp}, + {"libgromox_oxdisco.so", HPM_oxdisco}, + {"libgromox_oab.so", HPM_oab}, }; static constexpr static_module g_dfl_proc_plugins[] = { - {"libgxp_exchange_emsmdb.so", PROC_exchange_emsmdb}, - {"libgxp_exchange_nsp.so", PROC_exchange_nsp}, - {"libgxp_exchange_rfr.so", PROC_exchange_rfr}, + {"libgromox_emsmdb.so", PROC_exchange_emsmdb}, + {"libgromox_nsp.so", PROC_exchange_nsp}, + {"libgromox_rfr.so", PROC_exchange_rfr}, }; static constexpr static_module g_dfl_svc_plugins[] = { {"libgxs_mysql_adaptor.so", SVC_mysql_adaptor}, From eb85655ff4d5f2b7b70e3926e458783acec325b5 Mon Sep 17 00:00:00 2001 From: Jan Engelhardt Date: Fri, 31 Oct 2025 17:40:57 +0100 Subject: [PATCH 17/32] exch: trim some unnecessary static_cast<>s --- exch/ews/structures.hpp | 2 +- exch/http/cache.cpp | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/exch/ews/structures.hpp b/exch/ews/structures.hpp index 7af541d98..f32d5ef8c 100644 --- a/exch/ews/structures.hpp +++ b/exch/ews/structures.hpp @@ -1097,7 +1097,7 @@ struct tPath : public std::variant(*this);} + inline const Base &asVariant() const { return *this; } }; /** diff --git a/exch/http/cache.cpp b/exch/http/cache.cpp index 78606d887..ff1650fe6 100644 --- a/exch/http/cache.cpp +++ b/exch/http/cache.cpp @@ -1,5 +1,5 @@ // SPDX-License-Identifier: GPL-2.0-only WITH linking exception -// SPDX-FileCopyrightText: 2021-2023 grommunio GmbH +// SPDX-FileCopyrightText: 2021-2025 grommunio GmbH // This file is part of Gromox. #include #include @@ -104,7 +104,7 @@ void directory_list::emplace(const char *dom, const char *p1, const char *d1) path.pop_back(); if (dir.size() > 0 && dir.back() == '/') dir.pop_back(); - static_cast(*this).emplace_back(dom, std::move(path), std::move(dir)); + emplace_back(dom, std::move(path), std::move(dir)); } std::vector::const_iterator directory_list::find(const char *host, const char *uri) const From 2cc29916f959ecb887d8e30df46907e3a873c5a0 Mon Sep 17 00:00:00 2001 From: Jan Engelhardt Date: Fri, 31 Oct 2025 17:41:10 +0100 Subject: [PATCH 18/32] http: trim unnecessary directory_list::base type alias --- exch/http/cache.cpp | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/exch/http/cache.cpp b/exch/http/cache.cpp index ff1650fe6..8f4241563 100644 --- a/exch/http/cache.cpp +++ b/exch/http/cache.cpp @@ -73,12 +73,9 @@ struct dir_node { }; class directory_list : public std::vector { - private: - using base = std::vector; - public: void emplace(const char *dom, const char *path, const char *dir); - base::const_iterator find(const char *host, const char *uri) const; + std::vector::const_iterator find(const char *host, const char *uri) const; }; } From a15d0275daa2f539315db1cfc72144eae98d2efc Mon Sep 17 00:00:00 2001 From: Jan Engelhardt Date: Fri, 31 Oct 2025 18:05:42 +0100 Subject: [PATCH 19/32] lib: ditch use of streambuf for json_from_str --- lib/rfbl.cpp | 25 +------------------------ 1 file changed, 1 insertion(+), 24 deletions(-) diff --git a/lib/rfbl.cpp b/lib/rfbl.cpp index b46a20ca3..1d63d360d 100644 --- a/lib/rfbl.cpp +++ b/lib/rfbl.cpp @@ -19,14 +19,11 @@ #include #include #include -#include #include #include #include #include #include -#include -#include #include #include #include @@ -1459,29 +1456,9 @@ errno_t gx_compress_tofile(std::string_view inbuf, const char *outfile, return fd.close_wr(); } -namespace { - -struct iomembuf : public std::streambuf { - iomembuf(const char *p, size_t z) { - auto q = const_cast(p); - setg(q, q, q + z); - } -}; - -struct imemstream : public virtual iomembuf, public std::istream { - imemstream(const char *p, size_t z) : - iomembuf(p, z), - std::istream(static_cast(this)) - {} -}; - -} - bool json_from_str(std::string_view sv, Json::Value &jv) { - imemstream strm(sv.data(), sv.size()); - return Json::parseFromStream(Json::CharReaderBuilder(), - strm, &jv, nullptr); + return Json::Reader().parse(sv.data(), sv.data() + sv.size(), jv); } std::string json_to_str(const Json::Value &jv) From 40aea04100eaab4a2cf4e0963342ec0a1c5932b6 Mon Sep 17 00:00:00 2001 From: Jan Engelhardt Date: Fri, 31 Oct 2025 18:14:47 +0100 Subject: [PATCH 20/32] lib: rename json_from_str to str_to_json Avoid x_from_y function nomenclature. --- exch/authmgr.cpp | 2 +- exch/exmdb/message.cpp | 6 +++--- exch/midb/mail_engine.cpp | 4 ++-- include/gromox/json.hpp | 2 +- lib/email/mjson.cpp | 4 ++-- lib/oxoabkt.cpp | 2 +- lib/rfbl.cpp | 6 +++--- mra/midb_agent.cpp | 2 +- tests/jsontest.cpp | 2 +- tools/kdb2mt.cpp | 2 +- 10 files changed, 16 insertions(+), 16 deletions(-) diff --git a/exch/authmgr.cpp b/exch/authmgr.cpp index 6c1494a89..2d3fbbf00 100644 --- a/exch/authmgr.cpp +++ b/exch/authmgr.cpp @@ -124,7 +124,7 @@ static bool verify_token(std::string token, std::string &ex_user) /* Grab username */ Json::Value root; - if (!json_from_str(base64_decode(std::move(payload)), root)) + if (!str_to_json(base64_decode(std::move(payload)), root)) return false; payload.clear(); ex_user = root["email"].asString(); diff --git a/exch/exmdb/message.cpp b/exch/exmdb/message.cpp index 57f4fc42f..f0a9ba16e 100644 --- a/exch/exmdb/message.cpp +++ b/exch/exmdb/message.cpp @@ -3631,7 +3631,7 @@ BOOL exmdb_server::deliver_message(const char *dir, const char *from_address, std::optional digest; if (pdigest != nullptr) { digest.emplace(); - if (!json_from_str(pdigest, *digest)) + if (!str_to_json(pdigest, *digest)) digest.reset(); } if (digest.has_value() && @@ -3780,7 +3780,7 @@ BOOL exmdb_server::write_message(const char *dir, cpid_t cpid, if (digest_stream.size() > 0) { Json::Value digest; std::string mid_string; - if (json_from_str(digest_stream, digest) && + if (str_to_json(digest_stream, digest) && digest["file"].asString().size() > 0) { std::string ext_file = exmdb_server::get_dir() + "/ext/"s + digest["file"].asString(); auto ret = gx_mkbasedir(ext_file.c_str(), FMODE_PRIVATE); @@ -3878,7 +3878,7 @@ BOOL exmdb_server::rule_new_message(const char *dir, const char *username, std::unique_ptr slurp_data(HX_slurp_file(ext_path.c_str(), &slurp_size)); if (slurp_data != nullptr) { digest.emplace(); - if (!json_from_str({slurp_data.get(), slurp_size}, *digest)) + if (!str_to_json({slurp_data.get(), slurp_size}, *digest)) digest.reset(); } } diff --git a/exch/midb/mail_engine.cpp b/exch/midb/mail_engine.cpp index 996de2541..381d69ea1 100644 --- a/exch/midb/mail_engine.cpp +++ b/exch/midb/mail_engine.cpp @@ -241,7 +241,7 @@ static uint64_t me_get_digest(sqlite3 *psqlite, const char *mid_string, /* ext files may be absent (only midb generates them) */ if (exmdb_client->imapfile_read(dir, "ext", mid_string, &slurp_data)) - if (json_from_str(slurp_data.c_str(), digest) && + if (str_to_json(slurp_data.c_str(), digest) && digest.isMember("structure") && digest.isMember("mimes")) have_ext = true; if (!have_ext) { @@ -1344,7 +1344,7 @@ static void me_insert_message(xstmt &stm_insert, uint32_t *puidnext, e.midstr.clear(); Json::Value digest; if (djson.size() > 0) { - if (!json_from_str(djson.c_str(), digest) || + if (!str_to_json(djson.c_str(), digest) || !digest.isMember("structure") || !digest.isMember("mimes")) { djson.clear(); digest = {}; diff --git a/include/gromox/json.hpp b/include/gromox/json.hpp index b307a364b..8a4ef6184 100644 --- a/include/gromox/json.hpp +++ b/include/gromox/json.hpp @@ -3,7 +3,7 @@ #include #include namespace gromox { -extern GX_EXPORT bool json_from_str(std::string_view, Json::Value &); +extern GX_EXPORT bool str_to_json(std::string_view, Json::Value &); extern GX_EXPORT std::string json_to_str(const Json::Value &); extern GX_EXPORT bool get_digest(const Json::Value &src, const char *tag, char *out, size_t outmax); extern GX_EXPORT bool get_digest(const Json::Value &src, const char *tag, std::string &out); diff --git a/lib/email/mjson.cpp b/lib/email/mjson.cpp index bc22fbafc..d25a43fb0 100644 --- a/lib/email/mjson.cpp +++ b/lib/email/mjson.cpp @@ -398,7 +398,7 @@ static int mjson_fetch_mime_structure(mjson_io &io, const MJSON_MIME *pmime, if (eml_content == nullptr) goto RFC822_FAILURE; Json::Value digest; - if (!json_from_str(*eml_content, digest)) + if (!str_to_json(*eml_content, digest)) goto RFC822_FAILURE; MJSON temp_mjson; if (!temp_mjson.load_from_json(digest)) @@ -677,7 +677,7 @@ BOOL MJSON::rfc822_get(mjson_io &io, MJSON *pjson, const char *storage_path, continue; pjson->clear(); Json::Value digest; - if (!json_from_str(*eml_content, digest) || + if (!str_to_json(*eml_content, digest) || !pjson->load_from_json(digest)) return false; pjson->path = temp_path; diff --git a/lib/oxoabkt.cpp b/lib/oxoabkt.cpp index 98dc59922..a1f6ddc2f 100644 --- a/lib/oxoabkt.cpp +++ b/lib/oxoabkt.cpp @@ -270,7 +270,7 @@ std::string abkt_tojson(std::string_view bin, cpid_t codepage) std::string abkt_tobinary(std::string_view json, cpid_t codepage, bool dogap) { Json::Value jval; - if (!json_from_str(std::move(json), jval)) + if (!str_to_json(std::move(json), jval)) throw std::runtime_error("Invalid JSON input"); EXT_PUSH writer; if (!writer.init(nullptr, 0, EXT_FLAG_UTF16 | EXT_FLAG_WCOUNT, nullptr)) diff --git a/lib/rfbl.cpp b/lib/rfbl.cpp index 1d63d360d..2901907fa 100644 --- a/lib/rfbl.cpp +++ b/lib/rfbl.cpp @@ -991,7 +991,7 @@ bool get_digest(const Json::Value &jval, const char *key, char *out, size_t outm bool get_digest(const char *json, const char *key, char *out, size_t outmax) try { Json::Value jval; - if (!gromox::json_from_str(json, jval)) + if (!gromox::str_to_json(json, jval)) return false; return get_digest(jval, key, out, outmax); } catch (const std::bad_alloc &) { @@ -1003,7 +1003,7 @@ template static bool set_digest2(char *json, size_t iomax, const char *key, T &&val) try { Json::Value jval; - if (!gromox::json_from_str(json, jval)) + if (!gromox::str_to_json(json, jval)) return false; jval[key] = val; Json::StreamWriterBuilder swb; @@ -1456,7 +1456,7 @@ errno_t gx_compress_tofile(std::string_view inbuf, const char *outfile, return fd.close_wr(); } -bool json_from_str(std::string_view sv, Json::Value &jv) +bool str_to_json(std::string_view sv, Json::Value &jv) { return Json::Reader().parse(sv.data(), sv.data() + sv.size(), jv); } diff --git a/mra/midb_agent.cpp b/mra/midb_agent.cpp index 13f7173ad..8992dd9ab 100644 --- a/mra/midb_agent.cpp +++ b/mra/midb_agent.cpp @@ -1456,7 +1456,7 @@ int fetch_detail_uid(const char *path, const std::string &folder, line_pos - (pspace + 1 - temp_line); MITEM mitem; if (pspace == nullptr || - !json_from_str(std::string_view(&pspace[1], temp_len), mitem.digest)) { + !str_to_json(std::string_view(&pspace[1], temp_len), mitem.digest)) { b_format_error = TRUE; } else if (get_digest(mitem.digest, "file", mitem.mid) && get_digest_integer(mitem.digest, "uid", mitem.uid)) { diff --git a/tests/jsontest.cpp b/tests/jsontest.cpp index 24cc64003..afde5caec 100644 --- a/tests/jsontest.cpp +++ b/tests/jsontest.cpp @@ -76,7 +76,7 @@ static int t_digest() static int t_extparse(const char *s) { Json::Value json; - if (!json_from_str(s, json)) + if (!str_to_json(s, json)) return EXIT_FAILURE; MJSON m; if (!m.load_from_json(json)) { diff --git a/tools/kdb2mt.cpp b/tools/kdb2mt.cpp index 1ef0f95e9..1d2f8bf35 100644 --- a/tools/kdb2mt.cpp +++ b/tools/kdb2mt.cpp @@ -1431,7 +1431,7 @@ static int usermap_read(const char *file, LR_map &ku, LR_map &na, LR_map &ze) return EXIT_FAILURE; } Json::Value jval; - if (!json_from_str({slurp_data.get(), slurp_len}, jval) || + if (!str_to_json({slurp_data.get(), slurp_len}, jval) || !jval.isArray()) { fprintf(stderr, "%s: parse error\n", file); return EXIT_FAILURE; From 042839b15a55e2f8133568cbb27c04caee8186ac Mon Sep 17 00:00:00 2001 From: Mike Williams Date: Tue, 21 Oct 2025 13:35:35 +0200 Subject: [PATCH 21/32] oxvcard: move address array assertions to file scope References: GXL-641 --- lib/mapi/oxvcard.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/mapi/oxvcard.cpp b/lib/mapi/oxvcard.cpp index efd4c9767..3c67fd048 100644 --- a/lib/mapi/oxvcard.cpp +++ b/lib/mapi/oxvcard.cpp @@ -55,6 +55,8 @@ static constexpr uint32_t g_otheraddr_proptags[] = {PR_OTHER_ADDRESS_POST_OFFICE_BOX, PR_OTHER_ADDRESS_STREET, PR_OTHER_ADDRESS_CITY, PR_OTHER_ADDRESS_STATE_OR_PROVINCE, PR_OTHER_ADDRESS_POSTAL_CODE, PR_OTHER_ADDRESS_COUNTRY}; +static_assert(std::size(g_workaddr_proptags) == std::size(g_homeaddr_proptags)); +static_assert(std::size(g_workaddr_proptags) == std::size(g_otheraddr_proptags)); static constexpr uint32_t g_email_proptags[] = {0x8006001F, 0x8007001F, 0x8008001F}; static constexpr uint32_t g_addrtype_proptags[] = From da15f2268a58d3c1ba0adfca798e9636945dd66f Mon Sep 17 00:00:00 2001 From: Mike Williams Date: Mon, 20 Oct 2025 13:43:47 +0200 Subject: [PATCH 22/32] oxvcard: skip empty scalar properties References: GXL-641 --- lib/mapi/oxvcard.cpp | 76 +++++++++++++++++++++++++++----------------- 1 file changed, 46 insertions(+), 30 deletions(-) diff --git a/lib/mapi/oxvcard.cpp b/lib/mapi/oxvcard.cpp index 3c67fd048..8a5290241 100644 --- a/lib/mapi/oxvcard.cpp +++ b/lib/mapi/oxvcard.cpp @@ -163,6 +163,11 @@ static bool is_fax_param(const vcard_param &p) return strcasecmp(p.name(), "fax") == 0; } +static inline bool has_content(const char *value) +{ + return value != nullptr && *value != '\0'; +} + static std::string join(const char *gn, const char *mn, const char *sn) { std::string r = znul(gn); @@ -750,28 +755,35 @@ BOOL oxvcard_export(const MESSAGE_CONTENT *pmsg, const char *log_id, vcard.append_line("PRODID", "gromox-oxvcard"); pvalue = pmsg->proplist.get(PR_DISPLAY_NAME); - if (pvalue == nullptr) + if (!has_content(pvalue)) pvalue = pmsg->proplist.get(PR_NORMALIZED_SUBJECT); - if (pvalue != nullptr) + if (has_content(pvalue)) vcard.append_line("FN", pvalue); - - auto &n_line = vcard.append_line("N"); + + const char *name_parts[std::size(g_n_proptags)]{}; + bool has_name_part = false; for (size_t i = 0; i < std::size(g_n_proptags); ++i) { pvalue = pmsg->proplist.get(g_n_proptags[i]); - if (pvalue == nullptr) - continue; - n_line.append_value(pvalue); + if (has_content(pvalue)) { + name_parts[i] = pvalue; + has_name_part = true; + } } - + if (has_name_part) { + auto &n_line = vcard.append_line("N"); + for (const auto *part : name_parts) + n_line.append_value(znul(part)); + } + pvalue = pmsg->proplist.get(PR_NICKNAME); - if (pvalue != nullptr) + if (has_content(pvalue)) vcard.append_line("NICKNAME", pvalue); for (size_t i = 0; i < std::size(g_email_proptags); ++i) { auto propid = PROP_ID(g_email_proptags[i]); auto proptag = PROP_TAG(PROP_TYPE(g_email_proptags[i]), propids[propid - 0x8000]); pvalue = pmsg->proplist.get(proptag); - if (pvalue == nullptr) + if (!has_content(pvalue)) continue; auto &email_line = vcard.append_line("EMAIL"); auto &type_param = email_line.append_param("TYPE"); @@ -805,14 +817,16 @@ BOOL oxvcard_export(const MESSAGE_CONTENT *pmsg, const char *log_id, } pvalue = pmsg->proplist.get(PR_BODY); - if (pvalue != nullptr) + if (has_content(pvalue)) vcard.append_line("NOTE", pvalue); - auto &org_line = vcard.append_line("ORG"); - pvalue = pmsg->proplist.get(PR_COMPANY_NAME); - org_line.append_value(pvalue); - pvalue = pmsg->proplist.get(PR_DEPARTMENT_NAME); - org_line.append_value(pvalue); + const char *company = pmsg->proplist.get(PR_COMPANY_NAME); + const char *department = pmsg->proplist.get(PR_DEPARTMENT_NAME); + if (has_content(company) || has_content(department)) { + auto &org_line = vcard.append_line("ORG"); + org_line.append_value(znul(company)); + org_line.append_value(znul(department)); + } auto num = pmsg->proplist.get(PR_SENSITIVITY); if (num == nullptr) @@ -859,7 +873,7 @@ BOOL oxvcard_export(const MESSAGE_CONTENT *pmsg, const char *log_id, for (size_t i = 0; i < std::size(tel_proptags); ++i) { pvalue = pmsg->proplist.get(tel_proptags[i]); - if (pvalue == nullptr) + if (!has_content(pvalue)) continue; auto &tel_line = vcard.append_line("TEL"); tel_line.append_param("TYPE", tel_types[i]); @@ -867,7 +881,7 @@ BOOL oxvcard_export(const MESSAGE_CONTENT *pmsg, const char *log_id, } pvalue = pmsg->proplist.get(PR_HOME_FAX_NUMBER); - if (NULL != pvalue) { + if (has_content(pvalue)) { auto &tel_line = vcard.append_line("TEL"); tel_line.append_param("TYPE", "HOME"); tel_line.append_param("FAX"); @@ -875,7 +889,7 @@ BOOL oxvcard_export(const MESSAGE_CONTENT *pmsg, const char *log_id, } pvalue = pmsg->proplist.get(PR_BUSINESS_FAX_NUMBER); - if (NULL != pvalue) { + if (has_content(pvalue)) { auto &tel_line = vcard.append_line("TEL"); tel_line.append_param("TYPE", "WORK"); tel_line.append_param("FAX"); @@ -893,18 +907,18 @@ BOOL oxvcard_export(const MESSAGE_CONTENT *pmsg, const char *log_id, } pvalue = pmsg->proplist.get(PR_PROFESSION); - if (pvalue != nullptr) + if (has_content(pvalue)) vcard.append_line("ROLE", pvalue); pvalue = pmsg->proplist.get(PR_PERSONAL_HOME_PAGE); - if (NULL != pvalue) { + if (has_content(pvalue)) { auto &url_line = vcard.append_line("URL"); url_line.append_param("TYPE", "HOME"); url_line.append_value(pvalue); } pvalue = pmsg->proplist.get(PR_BUSINESS_HOME_PAGE); - if (NULL != pvalue) { + if (has_content(pvalue)) { auto &url_line = vcard.append_line("URL"); url_line.append_param("TYPE", "WORK"); url_line.append_value(pvalue); @@ -913,7 +927,7 @@ BOOL oxvcard_export(const MESSAGE_CONTENT *pmsg, const char *log_id, propid = PROP_ID(g_bcd_proptag); proptag = PROP_TAG(PROP_TYPE(g_bcd_proptag), propids[propid - 0x8000]); pvalue = pmsg->proplist.get(proptag); - if (pvalue != nullptr) + if (has_content(pvalue)) vcard.append_line("X-MS-OL-DESIGN", pvalue); saval = pmsg->proplist.get(PR_CHILDRENS_NAMES); @@ -925,14 +939,14 @@ BOOL oxvcard_export(const MESSAGE_CONTENT *pmsg, const char *log_id, propid = PROP_ID(g_ufld_proptags[i]); proptag = PROP_TAG(PROP_TYPE(g_ufld_proptags[i]), propids[propid - 0x8000]); pvalue = pmsg->proplist.get(proptag); - if (pvalue == nullptr) + if (!has_content(pvalue)) continue; vcard.append_line("X-MS-TEXT", pvalue); } for (size_t i = 0; i < std::size(ms_tel_proptags); ++i) { pvalue = pmsg->proplist.get(ms_tel_proptags[i]); - if (pvalue == nullptr) + if (!has_content(pvalue)) continue; auto &tel_line = vcard.append_line("X-MS-TEL"); tel_line.append_param("TYPE", ms_tel_types[i]); @@ -961,18 +975,18 @@ BOOL oxvcard_export(const MESSAGE_CONTENT *pmsg, const char *log_id, } pvalue = pmsg->proplist.get(PROP_TAG(PROP_TYPE(g_vcarduid_proptag), propids[PROP_ID(g_vcarduid_proptag)-0x8000])); - if (pvalue == nullptr) { + if (!has_content(pvalue)) { auto guid = GUID::random_new(); vcarduid = "uuid:" + bin2hex(guid); pvalue = vcarduid.c_str(); } - if (pvalue != nullptr) + if (has_content(pvalue)) vcard.append_line("UID", pvalue); propid = PROP_ID(g_fbl_proptag); proptag = PROP_TAG(PROP_TYPE(g_fbl_proptag), propids[propid - 0x8000]); pvalue = pmsg->proplist.get(proptag); - if (pvalue != nullptr) + if (has_content(pvalue)) vcard.append_line("FBURL", pvalue); saval = pmsg->proplist.get(PR_HOBBIES); @@ -995,12 +1009,14 @@ BOOL oxvcard_export(const MESSAGE_CONTENT *pmsg, const char *log_id, } pvalue = pmsg->proplist.get(PR_TITLE); - vcard.append_line("TITLE", pvalue); + if (has_content(pvalue)) + vcard.append_line("TITLE", pvalue); propid = PROP_ID(g_im_proptag); proptag = PROP_TAG(PROP_TYPE(g_im_proptag), propids[propid - 0x8000]); pvalue = pmsg->proplist.get(proptag); - vcard.append_line("X-MS-IMADDRESS", pvalue); + if (has_content(pvalue)) + vcard.append_line("X-MS-IMADDRESS", pvalue); auto lnum = pmsg->proplist.get(PR_BIRTHDAY); if (lnum != nullptr) { From beb355da69fa19cf117a666e7294a12a8e1bbb45 Mon Sep 17 00:00:00 2001 From: Mike Williams Date: Mon, 20 Oct 2025 13:44:51 +0200 Subject: [PATCH 23/32] oxvcard: factor out code for person line emission In doing so, skip empty properties. References: GXL-641 --- lib/mapi/oxvcard.cpp | 34 ++++++++++++++-------------------- 1 file changed, 14 insertions(+), 20 deletions(-) diff --git a/lib/mapi/oxvcard.cpp b/lib/mapi/oxvcard.cpp index 8a5290241..ea14b8fd0 100644 --- a/lib/mapi/oxvcard.cpp +++ b/lib/mapi/oxvcard.cpp @@ -168,6 +168,17 @@ static inline bool has_content(const char *value) return value != nullptr && *value != '\0'; } +static void add_person(vcard &card, const MESSAGE_CONTENT &msg, + proptag_t proptag, const char *line_key) +{ + auto value = msg.proplist.get(proptag); + if (!has_content(value)) + return; + auto &line = card.append_line(line_key); + line.append_param("N"); + line.append_value(value); +} + static std::string join(const char *gn, const char *mn, const char *sn) { std::string r = znul(gn); @@ -953,26 +964,9 @@ BOOL oxvcard_export(const MESSAGE_CONTENT *pmsg, const char *log_id, tel_line.append_value(pvalue); } - pvalue = pmsg->proplist.get(PR_SPOUSE_NAME); - if (NULL != pvalue && *pvalue != '\0') { - auto &sp_line = vcard.append_line("X-MS-SPOUSE"); - sp_line.append_param("N"); - sp_line.append_value(pvalue); - } - - pvalue = pmsg->proplist.get(PR_MANAGER_NAME); - if (NULL != pvalue && *pvalue != '\0') { - auto &mgr_line = vcard.append_line("X-MS-MANAGER"); - mgr_line.append_param("N"); - mgr_line.append_value(pvalue); - } - - pvalue = pmsg->proplist.get(PR_ASSISTANT); - if (NULL != pvalue && *pvalue != '\0') { - auto &as_line = vcard.append_line("X-MS-ASSISTANT"); - as_line.append_param("N"); - as_line.append_value(pvalue); - } + add_person(vcard, *pmsg, PR_SPOUSE_NAME, "X-MS-SPOUSE"); + add_person(vcard, *pmsg, PR_MANAGER_NAME, "X-MS-MANAGER"); + add_person(vcard, *pmsg, PR_ASSISTANT, "X-MS-ASSISTANT"); pvalue = pmsg->proplist.get(PROP_TAG(PROP_TYPE(g_vcarduid_proptag), propids[PROP_ID(g_vcarduid_proptag)-0x8000])); if (!has_content(pvalue)) { From e6a43327bc6e79f82348370328b9a44887053833 Mon Sep 17 00:00:00 2001 From: Jan Engelhardt Date: Sat, 1 Nov 2025 00:31:29 +0100 Subject: [PATCH 24/32] oxvcard: factor out code for string array emission Eliminates code duplication between CATEGORIES and X-MS-INTERESTS handling by extracting a reusable helper function that also skips empty array entries. References: GXL-641 --- lib/mapi/oxvcard.cpp | 41 ++++++++++++++++++++++------------------- 1 file changed, 22 insertions(+), 19 deletions(-) diff --git a/lib/mapi/oxvcard.cpp b/lib/mapi/oxvcard.cpp index ea14b8fd0..0fa1e8a3d 100644 --- a/lib/mapi/oxvcard.cpp +++ b/lib/mapi/oxvcard.cpp @@ -179,6 +179,24 @@ static void add_person(vcard &card, const MESSAGE_CONTENT &msg, line.append_value(value); } +static void add_string_array(vcard &card, const STRING_ARRAY *arr, + const char *line_key) +{ + if (arr == nullptr) + return; + vcard_value *value = nullptr; + for (size_t i = 0; i < arr->count; ++i) { + auto entry = arr->ppstr[i]; + if (!has_content(entry)) + continue; + if (value == nullptr) { + auto &line = card.append_line(line_key); + value = &line.append_value(); + } + value->append_subval(entry); + } +} + static std::string join(const char *gn, const char *mn, const char *sn) { std::string r = znul(gn); @@ -909,13 +927,7 @@ BOOL oxvcard_export(const MESSAGE_CONTENT *pmsg, const char *log_id, auto propid = PROP_ID(g_categories_proptag); auto proptag = PROP_TAG(PROP_TYPE(g_categories_proptag), propids[propid - 0x8000]); - auto saval = pmsg->proplist.get(proptag); - if (saval != nullptr) { - auto &cat_line = vcard.append_line("CATEGORIES"); - auto &val = cat_line.append_value(); - for (size_t i = 0; i < saval->count; ++i) - val.append_subval(saval->ppstr[i]); - } + add_string_array(vcard, pmsg->proplist.get(proptag), "CATEGORIES"); pvalue = pmsg->proplist.get(PR_PROFESSION); if (has_content(pvalue)) @@ -940,11 +952,8 @@ BOOL oxvcard_export(const MESSAGE_CONTENT *pmsg, const char *log_id, pvalue = pmsg->proplist.get(proptag); if (has_content(pvalue)) vcard.append_line("X-MS-OL-DESIGN", pvalue); - - saval = pmsg->proplist.get(PR_CHILDRENS_NAMES); - if (saval != nullptr) - for (size_t i = 0; i < saval->count; ++i) - vcard.append_line("X-MS-CHILD", saval->ppstr[i]); + + add_string_array(vcard, pmsg->proplist.get(PR_CHILDRENS_NAMES), "X-MS-CHILD"); for (size_t i = 0; i < std::size(g_ufld_proptags); ++i) { propid = PROP_ID(g_ufld_proptags[i]); @@ -983,13 +992,7 @@ BOOL oxvcard_export(const MESSAGE_CONTENT *pmsg, const char *log_id, if (has_content(pvalue)) vcard.append_line("FBURL", pvalue); - saval = pmsg->proplist.get(PR_HOBBIES); - if (NULL != pvalue) { - auto &int_line = vcard.append_line("X-MS-INTERESTS"); - auto &val = int_line.append_value(); - for (size_t i = 0; i < saval->count; ++i) - val.append_subval(saval->ppstr[i]); - } + add_string_array(vcard, pmsg->proplist.get(PR_HOBBIES), "X-MS-INTERESTS"); auto ba = pmsg->proplist.get(PR_USER_X509_CERTIFICATE); if (ba != nullptr && ba->count != 0) { From 71d781533c5d8edda3cc456f570c1ef7c9b86f8f Mon Sep 17 00:00:00 2001 From: Mike Williams Date: Tue, 21 Oct 2025 13:36:13 +0200 Subject: [PATCH 25/32] oxvcard: factor out code for address line emission Replaces inline lambda with a reusable template function for address handling. Template-based approach enables compile-time optimization while maintaining type safety. References: GXL-641 --- lib/mapi/oxvcard.cpp | 66 +++++++++++++++++++++++--------------------- 1 file changed, 34 insertions(+), 32 deletions(-) diff --git a/lib/mapi/oxvcard.cpp b/lib/mapi/oxvcard.cpp index 0fa1e8a3d..f3a9fa94c 100644 --- a/lib/mapi/oxvcard.cpp +++ b/lib/mapi/oxvcard.cpp @@ -1,6 +1,7 @@ // SPDX-License-Identifier: GPL-2.0-only WITH linking exception // SPDX-FileCopyrightText: 2020–2024 grommunio GmbH // This file is part of Gromox. +#include #include #include #include @@ -197,6 +198,27 @@ static void add_string_array(vcard &card, const STRING_ARRAY *arr, } } +template +static void add_adr(vcard &card, const char *type, Func &&get_part) +{ + std::array parts{}; + bool has_value = false; + for (size_t idx = 0; idx < N; ++idx) { + const char *part = get_part(idx); + if (has_content(part)) { + parts[idx] = part; + has_value = true; + } + } + if (!has_value) + return; + auto &adr_line = card.append_line("ADR"); + adr_line.append_param("TYPE", type); + for (const auto *part : parts) + adr_line.append_value(znul(part)); + adr_line.append_value(); +} + static std::string join(const char *gn, const char *mn, const char *sn) { std::string r = znul(gn); @@ -868,38 +890,18 @@ BOOL oxvcard_export(const MESSAGE_CONTENT *pmsg, const char *log_id, pvalue = "PUBLIC"; vcard.append_line("CLASS", pvalue); - auto adr_line = &vcard.append_line("ADR"); - adr_line->append_param("TYPE", "WORK"); - for (size_t i = 0; i < std::size(g_workaddr_proptags); ++i) { - auto propid = PROP_ID(g_workaddr_proptags[i]); - auto proptag = PROP_TAG(PROP_TYPE(g_workaddr_proptags[i]), propids[propid - 0x8000]); - pvalue = pmsg->proplist.get(proptag); - if (pvalue == nullptr) - continue; - adr_line->append_value(pvalue); - } - adr_line->append_value(); - - adr_line = &vcard.append_line("ADR"); - adr_line->append_param("TYPE", "HOME"); - for (size_t i = 0; i < std::size(g_homeaddr_proptags); ++i) { - pvalue = pmsg->proplist.get(g_homeaddr_proptags[i]); - if (pvalue == nullptr) - continue; - adr_line->append_value(pvalue); - } - adr_line->append_value(); - - adr_line = &vcard.append_line("ADR"); - adr_line->append_param("TYPE", "POSTAL"); - for (size_t i = 0; i < std::size(g_otheraddr_proptags); ++i) { - pvalue = pmsg->proplist.get(g_otheraddr_proptags[i]); - if (pvalue == nullptr) - continue; - adr_line->append_value(pvalue); - } - adr_line->append_value(); - + add_adr(vcard, "WORK", [&](unsigned int idx) { + auto propid = PROP_ID(g_workaddr_proptags[idx]); + auto proptag = PROP_TAG(PROP_TYPE(g_workaddr_proptags[idx]), propids[propid - 0x8000]); + return pmsg->proplist.get(proptag); + }); + add_adr(vcard, "HOME", [&](unsigned int idx) { + return pmsg->proplist.get(g_homeaddr_proptags[idx]); + }); + add_adr(vcard, "POSTAL", [&](unsigned int idx) { + return pmsg->proplist.get(g_otheraddr_proptags[idx]); + }); + for (size_t i = 0; i < std::size(tel_proptags); ++i) { pvalue = pmsg->proplist.get(tel_proptags[i]); if (!has_content(pvalue)) From 87d194ac9d11fdff4e8b8bd1e15cdc81c38cb40e Mon Sep 17 00:00:00 2001 From: Mike Williams Date: Tue, 21 Oct 2025 13:46:40 +0200 Subject: [PATCH 26/32] oxvcard: add bounds check after encode64 Validates that out_len from encode64 is within buffer bounds before writing null terminator. Defense in depth against potential buffer overflow if encode64 behaves unexpectedly. References: GXL-641 --- lib/mapi/oxvcard.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/mapi/oxvcard.cpp b/lib/mapi/oxvcard.cpp index f3a9fa94c..3602d2ad8 100644 --- a/lib/mapi/oxvcard.cpp +++ b/lib/mapi/oxvcard.cpp @@ -861,6 +861,8 @@ BOOL oxvcard_export(const MESSAGE_CONTENT *pmsg, const char *log_id, photo_line.append_param("ENCODING", "B"); if (encode64(bv->pb, bv->cb, tmp_buff, VCARD_MAX_BUFFER_LEN - 1, &out_len) != 0) return exp_false; + if (out_len >= VCARD_MAX_BUFFER_LEN) + return exp_false; tmp_buff[out_len] = '\0'; photo_line.append_value(tmp_buff); break; @@ -1003,6 +1005,8 @@ BOOL oxvcard_export(const MESSAGE_CONTENT *pmsg, const char *log_id, if (encode64(ba->pbin->pb, ba->pbin->cb, tmp_buff, std::size(tmp_buff) - 1, &out_len) != 0) return exp_false; + if (out_len >= std::size(tmp_buff)) + return exp_false; tmp_buff[out_len] = '\0'; key_line.append_value(tmp_buff); } From b1c2ffb31fe88f9cb3aaccb4b9a973272323ae32 Mon Sep 17 00:00:00 2001 From: Mike Williams Date: Tue, 21 Oct 2025 13:47:31 +0200 Subject: [PATCH 27/32] oxvcard: add bounds check for propids array access Prevents potential out-of-bounds read if a malformed vCard contains property IDs outside the expected range. Adds defensive validation of array index before accessing propids array. References: GXL-641 --- lib/mapi/oxvcard.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/mapi/oxvcard.cpp b/lib/mapi/oxvcard.cpp index 3602d2ad8..87a7432bb 100644 --- a/lib/mapi/oxvcard.cpp +++ b/lib/mapi/oxvcard.cpp @@ -759,7 +759,10 @@ message_content *oxvcard_import(const vcard *pvcard, GET_PROPIDS get_propids) tr auto propid = PROP_ID(proptag); if (!is_nameprop_id(propid)) continue; - proptag = propids[propid - 0x8000]; + uint16_t idx = propid - 0x8000; + if (idx >= propids.size()) + continue; /* Skip invalid propids */ + proptag = propids[idx]; pmsg->proplist.ppropval[i].proptag = PROP_TAG(PROP_TYPE(pmsg->proplist.ppropval[i].proptag), proptag); } From 0c0985c9b37d0f10fc2919d604c38c48c00910dd Mon Sep 17 00:00:00 2001 From: Michael Becker Date: Fri, 31 Oct 2025 13:35:31 +0100 Subject: [PATCH 28/32] lib: add missing reset of errno when iconvtext() performs a no-op References: GXL-633 --- lib/rfbl.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/rfbl.cpp b/lib/rfbl.cpp index 2901907fa..5f4f5055d 100644 --- a/lib/rfbl.cpp +++ b/lib/rfbl.cpp @@ -1564,8 +1564,10 @@ size_t utf8_printable_prefix(const void *vinput, size_t max) std::string iconvtext(const char *src, size_t src_size, const char *from, const char *to) { - if (strcasecmp(from, to) == 0) + if (strcasecmp(from, to) == 0) { + errno = 0; return {reinterpret_cast(src), src_size}; + } auto cs = to + "//IGNORE"s; auto cd = iconv_open(cs.c_str(), from); if (cd == reinterpret_cast(-1)) { From 3294fd9f83cea8b3074eaff2ce00379fde5513aa Mon Sep 17 00:00:00 2001 From: Michael Becker Date: Fri, 31 Oct 2025 13:40:40 +0100 Subject: [PATCH 29/32] mapi_lib: normalize newline handling in html_write_string() Skip stray carriage returns. Only emit \line after real content, and track whitespace, so that the RTF output matches the intended layout. References: GXL-633 --- lib/mapi/html.cpp | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/lib/mapi/html.cpp b/lib/mapi/html.cpp index f7071e5a1..7f362eeab 100644 --- a/lib/mapi/html.cpp +++ b/lib/mapi/html.cpp @@ -358,9 +358,21 @@ static ec_error_t html_write_string(RTF_WRITER *pwriter, const char *string) char tmp_buff[24]; const char *ptr = string, *pend = string + strlen(string); + bool seen_non_ws = false; while ('\0' != *ptr) { + if (*ptr == '\r') { + ++ptr; + continue; + } + if (*ptr == '\n') { + if (seen_non_ws) + QRF(pwriter->ext_push.p_bytes("\\line ", 6)); + ++ptr; + continue; + } static_assert(UCHAR_MAX <= std::size(utf8_byte_num)); - auto len = utf8_byte_num[static_cast(*ptr)]; + char cur = *ptr; + auto len = utf8_byte_num[static_cast(cur)]; if (len == 0) { ++ptr; continue; @@ -377,6 +389,8 @@ static ec_error_t html_write_string(RTF_WRITER *pwriter, const char *string) else QRF(pwriter->ext_push.p_uint8(*ptr)); ptr += len; + if (!HX_isspace(cur)) + seen_non_ws = true; continue; } auto [w1, w2] = html_utf8_to_utf16(pwriter->cd, ptr, len); @@ -391,6 +405,7 @@ static ec_error_t html_write_string(RTF_WRITER *pwriter, const char *string) tmp_len = strlen(tmp_buff); QRF(pwriter->ext_push.p_bytes(tmp_buff, tmp_len)); + seen_non_ws = true; } return ecSuccess; } From f7f514ae84af4245bef4f932833989e0a8a6e744 Mon Sep 17 00:00:00 2001 From: Michael Becker Date: Fri, 31 Oct 2025 21:27:07 +0100 Subject: [PATCH 30/32] mapi_lib: fix RTF \uc fallback handling for Unicode output MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Keep the \uc fallback accounting in scope even after nested groups have been processed, so that Unicode escapes keep working. This change drops any pending fallback bytes before writing text, so as to avoid stray “?” characters. It also resets the Unicode fallback state when a reader starts, so that conversions stay consistent. References: GXL-633 --- lib/mapi/rtf.cpp | 77 ++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 62 insertions(+), 15 deletions(-) diff --git a/lib/mapi/rtf.cpp b/lib/mapi/rtf.cpp index 3892e80d7..3344cda4d 100644 --- a/lib/mapi/rtf.cpp +++ b/lib/mapi/rtf.cpp @@ -255,6 +255,7 @@ struct rtf_reader final { const FONTENTRY *lookup_font(int) const; bool build_font_table(SIMPLE_TREE_NODE *); bool escape_output(char *); + bool push_text_encoded(const char *, size_t); bool word_output_date(SIMPLE_TREE_NODE *); int push_da_pic(EXT_PUSH &, const char *, const char *, const char *, const char *); @@ -277,12 +278,12 @@ struct rtf_reader final { bool is_within_table = false, b_printed_row_begin = false; bool b_printed_cell_begin = false, b_printed_row_end = false; bool b_printed_cell_end = false, b_simulate_smallcaps = false; - bool b_simulate_allcaps = false, b_ubytes_switch = false; + bool b_simulate_allcaps = false, b_ubytes_switch = true; bool is_within_picture = false, have_printed_body = false; bool is_within_header = true, have_ansicpg = false; bool have_fromhtml = false, is_within_htmltag = false; bool is_within_htmlrtf = false; - int coming_pars_tabular = 0, ubytes_num = 0, ubytes_left = 0; + int coming_pars_tabular = 0, ubytes_num = 1, ubytes_left = 0; int picture_file_number = 1; char picture_path[256]{}; int picture_width = 0, picture_height = 0, picture_bits_per_pixel = 1; @@ -366,10 +367,16 @@ bool rtf_reader::riconv_open(const char *fromcode) bool rtf_reader::escape_output(char *string) { auto preader = this; - int i; - int tmp_len; + size_t tmp_len = strlen(string); + if (ubytes_left > 0 && tmp_len > 0) { + auto skip = std::min(ubytes_left, tmp_len); + ubytes_left -= skip; + if (skip >= tmp_len) + return true; + string += skip; + tmp_len -= skip; + } - tmp_len = strlen(string); if (preader->is_within_htmltag) { QRF(preader->ext_push.p_bytes(string, tmp_len)); return true; @@ -378,7 +385,7 @@ bool rtf_reader::escape_output(char *string) HX_strupper(string); if (preader->b_simulate_smallcaps) HX_strlower(string); - for (i=0; iext_push.p_bytes("<", 4)); @@ -397,6 +404,23 @@ bool rtf_reader::escape_output(char *string) return true; } +bool rtf_reader::push_text_encoded(const char *string, size_t len) +{ + if (b_ubytes_switch && ubytes_left > 0 && len > 0) { + auto skip = std::min(ubytes_left, len); + string += skip; + len -= skip; + ubytes_left -= skip; + if (len == 0) + return true; + } + if (len == 0) + return true; + if (iconv_push.p_bytes(string, len) != pack_result::ok) + return false; + return riconv_flush(); +} + bool rtf_reader::riconv_flush() { char *out_buff; @@ -535,6 +559,9 @@ bool rtf_reader::init_reader(const char *prtf_buff, uint32_t rtf_length, if (!preader->ext_push.init(nullptr, 0, 0) || !preader->iconv_push.init(nullptr, 0, 0)) return false; + b_ubytes_switch = true; + ubytes_num = 1; + ubytes_left = 0; preader->pattachments = pattachments; return true; } @@ -1554,11 +1581,13 @@ bool rtf_reader::process_info_group(SIMPLE_TREE_NODE *pword) if (pword2->cdata[0] != '\\') { if (!riconv_flush()) return false; - if (!escape_output(pword2->cdata)) + auto slen = strlen(pword2->cdata); + if (!push_text_encoded(pword2->cdata, slen)) return false; } else if (pword2->cdata[1] == '\'') { ch = rtf_decode_hex_char(&pword2->cdata[2]); - QRF(preader->iconv_push.p_uint8(ch)); + if (!put_iconv_cache(ch)) + return false; } } if (!riconv_flush()) @@ -1573,11 +1602,13 @@ bool rtf_reader::process_info_group(SIMPLE_TREE_NODE *pword) if (pword2->cdata[0] != '\\') { if (!riconv_flush()) return false; - if (!escape_output(pword2->cdata)) + auto slen = strlen(pword2->cdata); + if (!push_text_encoded(pword2->cdata, slen)) return false; } else if (pword2->cdata[1] == '\'') { ch = rtf_decode_hex_char(&pword2->cdata[2]); - QRF(preader->iconv_push.p_uint8(ch)); + if (!put_iconv_cache(ch)) + return false; } } if (!riconv_flush()) @@ -2231,7 +2262,14 @@ int rtf_reader::cmd_u(SIMPLE_TREE_NODE *pword, int align, int rtf_reader::cmd_uc(SIMPLE_TREE_NODE *pword, int align, bool have_param, int num) { - return astk_pushx(ATTR_UBYTES, num) ? CMD_RESULT_CONTINUE : CMD_RESULT_ERROR; + if (!have_param) + num = ubytes_num != 0 ? ubytes_num : 1; + if (num < 0) + num = 0; + b_ubytes_switch = true; + ubytes_num = num; + ubytes_left = 0; + return CMD_RESULT_CONTINUE; } int rtf_reader::cmd_dn(SIMPLE_TREE_NODE *pword, int align, @@ -2641,6 +2679,13 @@ int rtf_reader::convert_group_node(SIMPLE_TREE_NODE *pnode) if (!check_for_table()) return -EINVAL; auto preader = this; + auto uc_prev_active = b_ubytes_switch; + auto uc_prev_num = ubytes_num; + auto uc_guard = HX::make_scope_exit([&,this]() { + b_ubytes_switch = uc_prev_active; + ubytes_num = uc_prev_num; + ubytes_left = 0; + }); try { preader->attr_stack_list.emplace_back(); } catch (const std::bad_alloc &) { @@ -2695,14 +2740,16 @@ int rtf_reader::convert_group_node(SIMPLE_TREE_NODE *pnode) return -ENOBUFS; } else { rtf_unescape_string(string); - preader->total_chars_in_line += strlen(string); - if (!escape_output(string)) + auto slen = strlen(string); + total_chars_in_line += slen; + if (!push_text_encoded(string, slen)) return -ENOMEM; } } else if (string[1] == '\\' || string[1] == '{' || string[1] == '}') { rtf_unescape_string(string); - preader->total_chars_in_line += strlen(string); - if (!escape_output(string)) + auto slen = strlen(string); + total_chars_in_line += slen; + if (!push_text_encoded(string, slen)) return -EINVAL; } else { string ++; From 1e77826f5fc4d817f537089f66b2e973f044f10c Mon Sep 17 00:00:00 2001 From: Jan Engelhardt Date: Sat, 1 Nov 2025 18:22:31 +0100 Subject: [PATCH 31/32] mapi_lib: use std::min+static_cast in place of std::min `std::min(a, b)` is short, but if the type of `a` or `b` were to change and become incompatible, the compiler would stay silent. Thus, using `std::min(a, static_cast(b))` like in the rest of the codebase is preferred as there is still much refactoring expected generally. --- lib/mapi/rtf.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/mapi/rtf.cpp b/lib/mapi/rtf.cpp index 3344cda4d..84d99f0c6 100644 --- a/lib/mapi/rtf.cpp +++ b/lib/mapi/rtf.cpp @@ -369,7 +369,7 @@ bool rtf_reader::escape_output(char *string) auto preader = this; size_t tmp_len = strlen(string); if (ubytes_left > 0 && tmp_len > 0) { - auto skip = std::min(ubytes_left, tmp_len); + auto skip = std::min(static_cast(ubytes_left), tmp_len); ubytes_left -= skip; if (skip >= tmp_len) return true; @@ -407,7 +407,7 @@ bool rtf_reader::escape_output(char *string) bool rtf_reader::push_text_encoded(const char *string, size_t len) { if (b_ubytes_switch && ubytes_left > 0 && len > 0) { - auto skip = std::min(ubytes_left, len); + auto skip = std::min(static_cast(ubytes_left), len); string += skip; len -= skip; ubytes_left -= skip; From ad84c9fe639ca16825f6c1000919e71e55ede2a0 Mon Sep 17 00:00:00 2001 From: Alex MacCuish Date: Sat, 1 Nov 2025 17:17:50 +0000 Subject: [PATCH 32/32] ews: include PR_CONTAINER_CLASS in tFolderResponseShape Some clients (like Thunderbird) require the FolderClass as they filter out non-mail folders. References: GXF-2362 --- exch/ews/structures.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/exch/ews/structures.hpp b/exch/ews/structures.hpp index b8f19e9f3..da705f4be 100644 --- a/exch/ews/structures.hpp +++ b/exch/ews/structures.hpp @@ -2648,7 +2648,7 @@ struct tFolderResponseShape { "All" = "all the properties used by the Exchange Business Logic layer", for whatever that means. Here, it means tagsDefault + {our extra list}. */ - static constexpr uint32_t tagsAll[] = {PR_PARENT_ENTRYID, PR_CREATION_TIME, PR_LAST_MODIFICATION_TIME, PR_ATTR_HIDDEN, PR_ATTR_READONLY, PR_CONTAINER_FLAGS, PR_RECORD_KEY, PR_STORE_ENTRYID, PR_ACCESS, PR_ACCESS_LEVEL}; + static constexpr uint32_t tagsAll[] = {PR_CONTAINER_CLASS, PR_PARENT_ENTRYID, PR_CREATION_TIME, PR_LAST_MODIFICATION_TIME, PR_ATTR_HIDDEN, PR_ATTR_READONLY, PR_CONTAINER_FLAGS, PR_RECORD_KEY, PR_STORE_ENTRYID, PR_ACCESS, PR_ACCESS_LEVEL}; static constexpr uint32_t tagsAllRootOnly[] = {PR_IPM_SUBTREE_ENTRYID, PR_SENTMAIL_ENTRYID}; };