Skip to content

Commit

Permalink
Merge branch 'ews'
Browse files Browse the repository at this point in the history
  • Loading branch information
jengelh committed Sep 22, 2023
2 parents ff5c014 + a121536 commit 2ad0333
Show file tree
Hide file tree
Showing 9 changed files with 317 additions and 27 deletions.
46 changes: 45 additions & 1 deletion exch/ews/context.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -671,7 +671,6 @@ std::unique_ptr<BINARY, detail::Cleaner> EWSContext::mkPCL(const XID& xid, PCL p
{
if(!pcl.append(xid))
throw DispatchError(E3121);
pcl.serialize();
std::unique_ptr<BINARY, detail::Cleaner> pcltemp(pcl.serialize());
if(!pcltemp)
throw EWSError::NotEnoughMemory(E3122);
Expand Down Expand Up @@ -724,6 +723,30 @@ uint64_t EWSContext::moveCopyFolder(const std::string& dir, const sFolderSpec& f
return newFolderId;
}

/**
* @brief Move or copy message object
*
* @param dir Store directory
* @param itemId Message iD
* @param newParent Destination folder
* @param accountId Account ID of executing user
* @param copy Whether to copy (instead of moving)
*
* @return New message ID
*/
uint64_t EWSContext::moveCopyItem(const std::string& dir, const sMessageEntryId& meid, uint64_t newParent, bool copy) const
{
auto& exmdb = plugin.exmdb;
uint64_t newId;
if(!exmdb.allocate_message_id(dir.c_str(), newParent, &newId))
throw DispatchError(E3182);
BOOL success;
if(!plugin.exmdb.movecopy_message(dir.c_str(), 0, CP_ACP, meid.messageId(), newParent, newId, copy? false : TRUE, &success)
|| !success)
throw EWSError::MoveCopyFailed(E3183);
return newId;
}

/**
* @brief Normalize mailbox specification
*
Expand Down Expand Up @@ -1222,6 +1245,27 @@ std::string EWSContext::username_to_essdn(const std::string& username) const
plugin.x500_org_name.c_str(), domainId, userId, userpart);
}

/**
* @brief Validate consistency of message entry id
*
* @param meid Message entry id to validate
* @param throwOnError Whether to throw an appropriate EWSError instead of returning
*
* @return true if entry id is consistent, false otherwise (and throwOnError is false)
*/
void EWSContext::validate(const std::string& dir, const sMessageEntryId& meid) const
{
const uint64_t* parentFid = nullptr;
try {
parentFid = getItemProp<uint64_t>(dir, meid.messageId(), PidTagParentFolderId);
} catch (const DispatchError&) {
}
if(!parentFid)
throw EWSError::ItemNotFound(E3187);
if(rop_util_get_gc_value(*parentFid) != meid.folderId())
throw EWSError::InvalidId(E3188);
}

/**
* @brief Convert EXT_PUSH/EXT_PULL return code to exception
*
Expand Down
3 changes: 3 additions & 0 deletions exch/ews/ews.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -145,10 +145,12 @@ static void process(const XMLElement* request, XMLElement* response, const EWSCo
const std::unordered_map<std::string, EWSPlugin::Handler> EWSPlugin::requestMap =
{
{"CopyFolder", process<Structures::mCopyFolderRequest>},
{"CopyItem", process<Structures::mCopyItemRequest>},
{"CreateFolder", process<Structures::mCreateFolderRequest>},
{"CreateItem", process<Structures::mCreateItemRequest>},
{"DeleteFolder", process<Structures::mDeleteFolderRequest>},
{"DeleteItem", process<Structures::mDeleteItemRequest>},
{"EmptyFolder", process<Structures::mEmptyFolderRequest>},
{"GetAttachment", process<Structures::mGetAttachmentRequest>},
{"GetFolder", process<Structures::mGetFolderRequest>},
{"GetItem", process<Structures::mGetItemRequest>},
Expand All @@ -157,6 +159,7 @@ const std::unordered_map<std::string, EWSPlugin::Handler> EWSPlugin::requestMap
{"GetUserAvailabilityRequest", process<Structures::mGetUserAvailabilityRequest>},
{"GetUserOofSettingsRequest", process<Structures::mGetUserOofSettingsRequest>},
{"MoveFolder", process<Structures::mMoveFolderRequest>},
{"MoveItem", process<Structures::mMoveItemRequest>},
{"ResolveNames", process<Structures::mResolveNamesRequest>},
{"SendItem", process<Structures::mSendItemRequest>},
{"SetUserOofSettingsRequest", process<Structures::mSetUserOofSettingsRequest>},
Expand Down
2 changes: 2 additions & 0 deletions exch/ews/ews.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,7 @@ class EWSContext
Structures::sItem loadItem(const std::string&, uint64_t, uint64_t, Structures::sShape&) const;
std::unique_ptr<BINARY, detail::Cleaner> mkPCL(const XID&, PCL=PCL()) const;
uint64_t moveCopyFolder(const std::string&, const Structures::sFolderSpec&, uint64_t, uint32_t, bool) const;
uint64_t moveCopyItem(const std::string&, const Structures::sMessageEntryId&, uint64_t, bool) const;
void normalize(Structures::tEmailAddressType&) const;
void normalize(Structures::tMailbox&) const;
uint32_t permissions(const char*, const Structures::sFolderSpec&, const char* = nullptr) const;
Expand All @@ -220,6 +221,7 @@ class EWSContext
void updated(const std::string&, const Structures::sFolderSpec&) const;
void updated(const std::string&, const Structures::sMessageEntryId&) const;
std::string username_to_essdn(const std::string&) const;
void validate(const std::string&, const Structures::sMessageEntryId&) const;

void experimental() const;

Expand Down
14 changes: 14 additions & 0 deletions exch/ews/exceptions.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ class EWSError : public DispatchError
ERR(ItemCorrupt) ///< Item could not be loaded properly
ERR(ItemNotFound) ///< Requested message object does not exist
ERR(ItemPropertyRequestFailed) ///< Failed to retrieve item property
ERR(ItemSave); ///< Failed to set item properties
ERR(MailRecipientNotFound) ///< Username could not be resolved internally
ERR(MissingRecipients) ///< Failed to send item because no recipients were specified
ERR(MoveCopyFailed) ///< Exmdb `movecopy_message` operation failed
Expand Down Expand Up @@ -290,6 +291,19 @@ E(3175, "failed to set folder properties");
E(3176, "failed to remove folder properties");
E(3177, "no valid folder object found");
E(3178, "missing child node in SetFolderField object");
E(3179, "cannot modify target folder");
E(3180, "failed to empty folder");
E(3181, "empty folder to deleted items is not supported");
E(3182, "failed to allocate message id");
E(3183, "movecopy opertaion failed");
E(3184, "cannot write to destination folder");
E(3185, "cannot read from source directory");
E(3186, "move/copy between stores is not supported");
E(3187, "item not found");
E(3188, "inconsistent item id");
E(3189, "source and destination folder are the same");
E(3190, "cannot write to object");
E(3191, "cannot write to target folder");

#undef E
}
112 changes: 103 additions & 9 deletions exch/ews/requests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ void process(mCreateFolderRequest&& request, XMLElement* response, const EWSCont

for(const sFolder& folder : request.Folders) try {
if(!hasAccess)
throw EWSError::AccessDenied("cannot write to target folder");
throw EWSError::AccessDenied(E3191);
mCreateFolderResponseMessage msg;
msg.Folders.emplace_back(ctx.create(dir, parent, folder));
data.ResponseMessages.emplace_back(std::move(msg)).success();
Expand Down Expand Up @@ -266,6 +266,7 @@ void process(mDeleteItemRequest&& request, XMLElement* response, const EWSContex
sMessageEntryId meid(itemId.Id.data(), itemId.Id.size());
sFolderSpec parent = ctx.resolveFolder(meid);
std::string dir = ctx.getDir(parent);
ctx.validate(dir, meid);
if(!(ctx.permissions(ctx.auth_info.username, parent, dir.c_str()) & frightsDeleteAny))
throw EWSError::AccessDenied(E3131);
if(request.DeleteType == Enum::MoveToDeletedItems) {
Expand Down Expand Up @@ -299,6 +300,44 @@ void process(mDeleteItemRequest&& request, XMLElement* response, const EWSContex
data.serialize(response);
}

/**
* @brief Process EmptyFolder
*
* @param request Request data
* @param response XMLElement to store response in
* @param ctx Request context
*/
void process(mEmptyFolderRequest&& request, XMLElement* response, const EWSContext& ctx)
{
ctx.experimental();

response->SetName("m:EmptyFolderResponse");

mEmptyFolderResponse data;
data.ResponseMessages.reserve(request.FolderIds.size());

if(request.DeleteType == Enum::MoveToDeletedItems)
throw DispatchError(E3181);
uint32_t deleteFlags = DEL_MESSAGES | DEL_ASSOCIATED;
deleteFlags |= (request.DeleteType == Enum::HardDelete? DELETE_HARD_DELETE : 0) |
(request.DeleteSubFolders? DEL_FOLDERS : 0);
for(const sFolderId& folderId : request.FolderIds) try {
sFolderSpec folder = ctx.resolveFolder(folderId);
std::string dir = ctx.getDir(folder);
if(!(ctx.permissions(ctx.auth_info.username, folder, dir.c_str()) & frightsDeleteAny))
throw EWSError::AccessDenied(E3179);
BOOL partial;
if(!ctx.plugin.exmdb.empty_folder(dir.c_str(), CP_ACP, nullptr, folder.folderId, deleteFlags, &partial)
|| partial)
throw EWSError::CannotEmptyFolder(E3180);
data.ResponseMessages.emplace_back().success();
} catch(const EWSError& err) {
data.ResponseMessages.emplace_back(err);
}

data.serialize(response);
}

/**
* @brief Process GetAttachment
*
Expand All @@ -319,14 +358,15 @@ void process(mGetAttachmentRequest&& request, XMLElement* response, const EWSCon
sAttachmentId aid(raid.Id.data(), raid.Id.size());
sFolderSpec parentFolder = ctx.resolveFolder(aid);
std::string dir = ctx.getDir(parentFolder);
ctx.validate(dir, aid);
if(!(ctx.permissions(ctx.auth_info.username, parentFolder) & frightsReadAny))
throw EWSError::AccessDenied(E3135);
mGetAttachmentResponseMessage msg;
msg.Attachments.emplace_back(ctx.loadAttachment(dir, aid));
msg.success();
data.ResponseMessages.emplace_back(std::move(msg));
} catch(const EWSError& err) {
data.ResponseMessages.emplace_back(err);
data.ResponseMessages.emplace_back(err);
}

data.serialize(response);
Expand Down Expand Up @@ -554,7 +594,7 @@ void process(mGetUserOofSettingsRequest&& request, XMLElement* response, const E
}

/**
* @brief Process MoveFolder
* @brief Process CopyFolder or MoveFolder
*
* @param request Request data
* @param response XMLElement to store response in
Expand All @@ -573,7 +613,7 @@ void process(const mBaseMoveCopyFolder& request, XMLElement* response, const EWS
bool dstAccess = ctx.permissions(ctx.auth_info.username, dstFolder, dir.c_str());

using MCResponse = std::variant<mCopyFolderResponse, mMoveFolderResponse>;
auto mkData = [&]{return request.copy? MCResponse(std::in_place_index_t<0>{}) : MCResponse(std::in_place_index_t<0>{});};
auto mkData = [&]{return request.copy? MCResponse(std::in_place_index_t<0>{}) : MCResponse(std::in_place_index_t<1>{});};
MCResponse data = mkData();
std::visit([&](auto& d){d.ResponseMessages.reserve(request.FolderIds.size());}, data);

Expand All @@ -597,6 +637,54 @@ void process(const mBaseMoveCopyFolder& request, XMLElement* response, const EWS
std::visit([&](auto& d){d.serialize(response);}, data);
}

/**
* @brief Process CopyItem or MoveItem
*
* @param request Request data
* @param response XMLElement to store response in
* @param ctx Request context
*/
void process(const mBaseMoveCopyItem& request, XMLElement* response, const EWSContext& ctx)
{
response->SetName(request.copy? "m:CopyItemResponse" : "m:MoveItemResponse");

ctx.experimental();

sFolderSpec dstFolder = ctx.resolveFolder(request.ToFolderId.folderId);
std::string dir = ctx.getDir(dstFolder);

bool dstAccess = ctx.permissions(ctx.auth_info.username, dstFolder, dir.c_str());

using MCResponse = std::variant<mCopyItemResponse, mMoveItemResponse>;
auto mkData = [&]{return request.copy? MCResponse(std::in_place_index_t<0>{}) : MCResponse(std::in_place_index_t<1>{});};
MCResponse data = mkData();
std::visit([&](auto& d){d.ResponseMessages.reserve(request.ItemIds.size());}, data);

sShape shape = sShape(tItemResponseShape());

for(const tItemId& itemId : request.ItemIds) try {
if(!dstAccess)
throw EWSError::AccessDenied(E3184);
sMessageEntryId meid(itemId.Id.data(), itemId.Id.size());
sFolderSpec sourceFolder = ctx.resolveFolder(meid);
if(sourceFolder.target != dstFolder.target)
throw EWSError::CrossMailboxMoveCopy(E3186);
ctx.validate(dir, meid);
if(!(ctx.permissions(ctx.auth_info.username, sourceFolder, dir.c_str()) & frightsReadAny))
throw EWSError::AccessDenied(E3185);
uint64_t newItemId = ctx.moveCopyItem(dir, meid, dstFolder.folderId, request.copy);
auto& msg = std::visit([&](auto& d) -> mItemInfoResponseMessage&
{return static_cast<mItemInfoResponseMessage&>(d.ResponseMessages.emplace_back());}, data);
if(request.ReturnNewItemIds && *request.ReturnNewItemIds)
msg.Items.emplace_back(ctx.loadItem(dir, dstFolder.folderId, newItemId, shape));
msg.success();
} catch(const EWSError& err) {
std::visit([&](auto& d){d.ResponseMessages.emplace_back(err);}, data);
}

std::visit([&](auto& d){d.serialize(response);}, data);
}

/**
* @brief Process SetUserOofSettingsRequest
*
Expand Down Expand Up @@ -866,6 +954,7 @@ void process(mGetItemRequest&& request, XMLElement* response, const EWSContext&
sMessageEntryId eid(itemId.Id.data(), itemId.Id.size());
sFolderSpec parentFolder = ctx.resolveFolder(eid);
std::string dir = ctx.getDir(parentFolder);
ctx.validate(dir, eid);
if(!(ctx.permissions(ctx.auth_info.username, parentFolder) & frightsReadAny))
throw EWSError::AccessDenied(E3139);
mGetItemResponseMessage& msg = data.ResponseMessages.emplace_back();
Expand Down Expand Up @@ -1004,7 +1093,7 @@ void process(mUpdateFolderRequest&& request, XMLElement* response, const EWSCont
for(const auto& change : request.FolderChanges) try {
sFolderSpec folder = ctx.resolveFolder(change.folderId);
std::string dir = ctx.getDir(folder);
if(!(ctx.permissions(ctx.auth_info.username, folder, dir.c_str()) | frightsEditAny))
if(!(ctx.permissions(ctx.auth_info.username, folder, dir.c_str()) & frightsEditAny))
throw EWSError::AccessDenied(E3174);
sShape shape(change);
ctx.getNamedTags(dir, shape, true);
Expand Down Expand Up @@ -1050,11 +1139,13 @@ void process(mUpdateItemRequest&& request, XMLElement* response, const EWSContex

sShape idOnly;
idOnly.add(PR_ENTRYID, sShape::FL_FIELD).add(PR_CHANGE_KEY, sShape::FL_FIELD).add(PR_MESSAGE_CLASS);
for(const auto& change : request.ItemChanges) {
mUpdateItemResponseMessage& msg = data.ResponseMessages.emplace_back();
for(const auto& change : request.ItemChanges) try {
sMessageEntryId mid(change.ItemId.Id.data(), change.ItemId.Id.size());
sFolderSpec parentFolder = ctx.resolveFolder(mid);
std::string dir = ctx.getDir(parentFolder);
ctx.validate(dir, mid);
if(!(ctx.permissions(ctx.auth_info.username, parentFolder, dir.c_str()) & frightsEditAny))
throw EWSError::AccessDenied(E3190);
sShape shape(change);
ctx.getNamedTags(dir, shape, true);
for(const auto& update : change.Updates) {
Expand All @@ -1065,13 +1156,16 @@ void process(mUpdateItemRequest&& request, XMLElement* response, const EWSContex
PROPTAG_ARRAY tagsRm = shape.remove();
PROBLEM_ARRAY problems;
if(!ctx.plugin.exmdb.set_message_properties(dir.c_str(), nullptr, CP_ACP, mid.messageId(), &props, &problems))
throw DispatchError(E3092);
throw EWSError::ItemSave(E3092);
if(!ctx.plugin.exmdb.remove_message_properties(dir.c_str(), CP_ACP, mid.messageId(), &tagsRm))
throw DispatchError(E3093);
throw EWSError::ItemSave(E3093);
ctx.updated(dir, mid);
mUpdateItemResponseMessage& msg = data.ResponseMessages.emplace_back();
msg.Items.emplace_back(ctx.loadItem(dir, mid.folderId(), mid.messageId(), idOnly));
msg.ConflictResults.Count = problems.count;
msg.success();
} catch(const EWSError& err) {
data.ResponseMessages.emplace_back(err);
}

data.serialize(response);
Expand Down
2 changes: 2 additions & 0 deletions exch/ews/requests.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ EWSFUNC(mCreateFolderRequest);
EWSFUNC(mCreateItemRequest);
EWSFUNC(mDeleteFolderRequest);
EWSFUNC(mDeleteItemRequest);
EWSFUNC(mEmptyFolderRequest);
EWSFUNC(mGetAttachmentRequest);
EWSFUNC(mGetFolderRequest);
EWSFUNC(mGetItemRequest);
Expand All @@ -28,6 +29,7 @@ EWSFUNC(mGetServiceConfigurationRequest);
EWSFUNC(mGetUserAvailabilityRequest);
EWSFUNC(mGetUserOofSettingsRequest);
void process(const Structures::mBaseMoveCopyFolder&, tinyxml2::XMLElement*, const gromox::EWS::EWSContext&);
void process(const Structures::mBaseMoveCopyItem&, tinyxml2::XMLElement*, const gromox::EWS::EWSContext&);
EWSFUNC(mResolveNamesRequest);
EWSFUNC(mSendItemRequest);
EWSFUNC(mSetUserOofSettingsRequest);
Expand Down
Loading

0 comments on commit 2ad0333

Please sign in to comment.