Skip to content

Commit 1019f9f

Browse files
committed
Merge branch 'hidden-ab'
2 parents bcd1157 + c1216de commit 1019f9f

File tree

13 files changed

+293
-111
lines changed

13 files changed

+293
-111
lines changed

Makefile.am

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,7 @@ libgxp_exchange_nsp_la_SOURCES = exch/nsp/common_util.cpp exch/nsp/common_util.h
130130
libgxp_exchange_nsp_la_LDFLAGS = ${default_SYFLAGS}
131131
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
132132
EXTRA_libgxp_exchange_nsp_la_DEPENDENCIES = default.sym
133+
EXTRA_DIST += exch/nsp/repr.cpp
133134
libgxp_exchange_rfr_la_SOURCES = exch/rfr.cpp
134135
libgxp_exchange_rfr_la_LDFLAGS = ${default_SYFLAGS}
135136
libgxp_exchange_rfr_la_LIBADD = ${fmt_LIBS} ${libHX_LIBS} libgromox_common.la libgromox_rpc.la libgxs_mysql_adaptor.la

doc/glossary.rst

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -281,8 +281,12 @@ GLOBCNT / GCV
281281
* otherwise, a ``uint64_t`` holds the GCV in host-endian
282282

283283
Minimal Entry ID, MINID
284-
A global counter for objects in an address book.
284+
A unique identifier for objects in an address book.
285285
Scope: one address book provider. Limit: 2^32 - 16.
286+
The first 16 values are reserved for special values like
287+
minid::BEGINNING_OF_TABLE, minid::UNRESOLVED, etc.
288+
NSPI also implicitly reserves >= 0x80000000, e.g. PR_EMS_AB_MEMBER
289+
makes for a special container, too.
286290

287291
Change number / CN
288292
Scope: one mailbox replica. Limit: 2^48. Every time a folder or message

exch/nsp/nsp_interface.cpp

Lines changed: 55 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
#include <gromox/util.hpp>
3838
#include "common_util.hpp"
3939
#include "nsp_interface.hpp"
40+
#include "repr.cpp"
4041

4142
using namespace std::string_literals;
4243
using namespace gromox;
@@ -904,6 +905,9 @@ ec_error_t nsp_interface_seek_entries(NSPI_HANDLE handle, uint32_t reserved,
904905
{
905906
*pprows = nullptr;
906907
nsp_trace(__func__, 0, pstat);
908+
if (g_nsp_trace >= 2 && ptarget != nullptr)
909+
fprintf(stderr, "seek_entries target={%xh,%s}\n",
910+
ptarget->proptag, ptarget->repr().c_str());
907911
if (handle.handle_type != HANDLE_EXCHANGE_NSP)
908912
return ecError;
909913
if (pstat == nullptr || pstat->codepage == CP_WINUNICODE ||
@@ -1172,6 +1176,22 @@ static std::unordered_set<std::string> delegates_for(const char *dir) try
11721176
return {};
11731177
}
11741178

1179+
/**
1180+
* Used to search the address book based on certain criteria (like a display
1181+
* name prefix), and returns a list of matching entries.
1182+
*
1183+
* (Also see resolve_namesw for differences.)
1184+
*
1185+
* Outlook uses get_matches(container=PR_EMS_AB_MEMBER, cur_rec=<minid of an
1186+
* mlist>, filter=nullptr) to obtain the members of an address list.
1187+
*
1188+
* Outlook uses get_matches(container=0 <gal>, cur_rec=0, filter={PR_ANR,
1189+
* "needle"}) as an alternative to resolve_names().
1190+
*
1191+
* Also note that get_matches(container=0, filter=RES_EXIST{PR_ENTRYID}) yields
1192+
* the same as iterating the GAL with query_rows(container=0). We need to be
1193+
* wary of HIDE flag testing.
1194+
*/
11751195
ec_error_t nsp_interface_get_matches(NSPI_HANDLE handle, uint32_t reserved1,
11761196
STAT *pstat, const MID_ARRAY *ptable, uint32_t reserved2,
11771197
const NSPRES *pfilter, const NSP_PROPNAME *ppropname,
@@ -1181,6 +1201,8 @@ ec_error_t nsp_interface_get_matches(NSPI_HANDLE handle, uint32_t reserved1,
11811201
*ppoutmids = nullptr;
11821202
*pprows = nullptr;
11831203
nsp_trace(__func__, 0, pstat);
1204+
if (g_nsp_trace >= 2 && pfilter != nullptr)
1205+
mlog(LV_DEBUG, "get_matches filter: %s", pfilter->repr().c_str());
11841206
if (handle.handle_type != HANDLE_EXCHANGE_NSP)
11851207
return ecError;
11861208
PROPERTY_VALUE prop_val;
@@ -1236,7 +1258,6 @@ ec_error_t nsp_interface_get_matches(NSPI_HANDLE handle, uint32_t reserved1,
12361258
return ecServerOOM;
12371259
*pproptag = node.mid;
12381260
}
1239-
goto FETCH_ROWS;
12401261
} else if (pstat->container_id == PR_EMS_AB_PUBLIC_DELEGATES) {
12411262
ab_tree::ab_node node(base, pstat->cur_rec);
12421263
if (!node.exists())
@@ -1264,9 +1285,8 @@ ec_error_t nsp_interface_get_matches(NSPI_HANDLE handle, uint32_t reserved1,
12641285
return ecServerOOM;
12651286
*pproptag = node.mid;
12661287
}
1267-
goto FETCH_ROWS;
1268-
}
1269-
if (pfilter == nullptr) {
1288+
} else if (pfilter == nullptr) {
1289+
/* OXNSPI v14 §3.1.4.1.10 pg. 56 item 8 */
12701290
char temp_buff[1024];
12711291
ab_tree::ab_node node = {base, pstat->cur_rec};
12721292
if (node.exists() && nsp_interface_fetch_property(node,
@@ -1278,15 +1298,19 @@ ec_error_t nsp_interface_get_matches(NSPI_HANDLE handle, uint32_t reserved1,
12781298
*pproptag = node.mid;
12791299
}
12801300
} else if (pstat->container_id == 0) {
1301+
/* Alternative attempt by OL to do resolvenames */
12811302
uint32_t start_pos, total;
12821303
nsp_interface_position_in_list(pstat, base.get(), &start_pos, &total);
1283-
for (auto it = base->ubegin() + start_pos; it != base->uend() && it-base->ubegin() < total; ++it)
1284-
if (nsp_interface_match_node({base, *it}, pstat->codepage, pfilter)) {
1285-
auto pproptag = common_util_proptagarray_enlarge(outmids);
1286-
if (pproptag == nullptr)
1287-
return ecServerOOM;
1288-
*pproptag = *it;
1289-
}
1304+
for (auto it = base->ubegin() + start_pos; it != base->uend() && it-base->ubegin() < total; ++it) {
1305+
ab_tree::ab_node node(base, *it);
1306+
if (node.hidden() & (AB_HIDE_RESOLVE | AB_HIDE_FROM_GAL) ||
1307+
!nsp_interface_match_node(node, pstat->codepage, pfilter))
1308+
continue;
1309+
auto pproptag = common_util_proptagarray_enlarge(outmids);
1310+
if (pproptag == nullptr)
1311+
return ecServerOOM;
1312+
*pproptag = *it;
1313+
}
12901314
} else {
12911315
ab_tree::ab_node node(base, pstat->container_id);
12921316
if (!node.exists())
@@ -1301,18 +1325,19 @@ ec_error_t nsp_interface_get_matches(NSPI_HANDLE handle, uint32_t reserved1,
13011325
nsp_trace(__func__, 1, pstat, nullptr, rowset);
13021326
return ecSuccess;
13031327
}
1304-
for (auto it = node.begin() + start_pos; it != node.end(); ++it)
1305-
if (!(node.hidden() & AB_HIDE_FROM_AL) && nsp_interface_match_node({base, *it}, pstat->codepage, pfilter)) {
1306-
auto pproptag = common_util_proptagarray_enlarge(outmids);
1307-
if (pproptag == nullptr)
1308-
return ecServerOOM;
1309-
*pproptag = *it;
1310-
if (outmids->cvalues >= requested)
1311-
break;
1312-
}
1328+
for (auto it = node.begin() + start_pos; it != node.end(); ++it) {
1329+
if (node.hidden() & (AB_HIDE_RESOLVE | AB_HIDE_FROM_AL) ||
1330+
!nsp_interface_match_node({base, *it}, pstat->codepage, pfilter))
1331+
continue;
1332+
auto pproptag = common_util_proptagarray_enlarge(outmids);
1333+
if (pproptag == nullptr)
1334+
return ecServerOOM;
1335+
*pproptag = *it;
1336+
if (outmids->cvalues >= requested)
1337+
break;
1338+
}
13131339
}
13141340

1315-
FETCH_ROWS:
13161341
if (rowset != nullptr) {
13171342
for (size_t i = 0; i < outmids->cvalues; ++i) {
13181343
auto prow = common_util_proprowset_enlarge(rowset);
@@ -2090,6 +2115,15 @@ static ab_tree::minid nsp_interface_resolve_gal(const ab_tree::ab::const_base_re
20902115
return res;
20912116
}
20922117

2118+
/**
2119+
* Used to resolve a list of names into unique address book entries (or fail if
2120+
* ambiguous).
2121+
*
2122+
* (Also see get_matches for differences.)
2123+
*
2124+
* If Outlook does not get a resolution using this call, it will retry with
2125+
* get_matches()!
2126+
*/
20932127
ec_error_t nsp_interface_resolve_namesw(NSPI_HANDLE handle, uint32_t reserved,
20942128
const STAT *pstat, LPROPTAG_ARRAY *&pproptags,
20952129
const STRINGS_ARRAY *pstrs, MID_ARRAY **ppmids, NSP_ROWSET **pprows)

exch/nsp/nsp_types.hpp

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ struct PROPERTY_VALUE {
7171
gromox::proptag_t proptag;
7272
uint32_t reserved;
7373
PROP_VAL_UNION value; /* type is PROP_TYPE(proptag) */
74+
std::string repr() const;
7475
};
7576

7677
struct NSP_PROPROW {
@@ -107,50 +108,59 @@ struct NSPRES;
107108
struct NSPRES_AND_OR {
108109
uint32_t cres;
109110
NSPRES *pres;
111+
std::string repr(const char *sep = ",") const;
110112
};
111113

112114
struct NSPRES_NOT {
113115
NSPRES *pres;
116+
std::string repr() const;
114117
};
115118

116119
struct NSPRES_CONTENT {
117120
uint32_t fuzzy_level;
118121
gromox::proptag_t proptag;
119122
PROPERTY_VALUE *pprop;
123+
std::string repr() const;
120124
};
121125

122126
struct NSPRES_PROPERTY {
123127
enum relop relop;
124128
gromox::proptag_t proptag;
125129
PROPERTY_VALUE *pprop;
130+
std::string repr() const;
126131
};
127132

128133
struct NSPRES_PROPCOMPARE {
129134
enum relop relop;
130135
gromox::proptag_t proptag1, proptag2;
136+
std::string repr() const;
131137
};
132138

133139
struct NSPRES_BITMASK {
134140
enum bm_relop rel_mbr;
135141
gromox::proptag_t proptag;
136142
uint32_t mask;
143+
std::string repr() const;
137144
};
138145

139146
struct NSPRES_SIZE {
140147
enum relop relop;
141148
gromox::proptag_t proptag;
142149
uint32_t cb;
150+
std::string repr() const;
143151
};
144152

145153
struct NSPRES_EXIST {
146154
uint32_t reserved1;
147155
gromox::proptag_t proptag;
148156
uint32_t reserved2;
157+
std::string repr() const;
149158
};
150159

151160
struct NSPRES_SUB {
152161
uint32_t subobject;
153162
NSPRES *pres;
163+
std::string repr() const;
154164
};
155165

156166
union NSPRES_UNION {
@@ -168,6 +178,7 @@ union NSPRES_UNION {
168178
struct NSPRES {
169179
mapi_rtype res_type;
170180
NSPRES_UNION res;
181+
std::string repr() const;
171182
};
172183

173184
struct EPHEMERAL_ENTRYID {

exch/nsp/repr.cpp

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
// SPDX-License-Identifier: AGPL-3.0-or-later
2+
// SPDX-FileCopyrightText: 2022–2025 grommunio GmbH
3+
// This file is part of Gromox.
4+
#include <string>
5+
#include <fmt/core.h>
6+
7+
std::string PROPERTY_VALUE::repr() const
8+
{
9+
#define E(p) TAGGED_PROPVAL{proptag, deconst(p)}.repr()
10+
switch (PROP_TYPE(proptag)) {
11+
case PT_SHORT: return E(&value.s);
12+
case PT_LONG: return E(&value.l);
13+
case PT_I8: return E(&value.ll);
14+
case PT_FLOAT: return E(&value.flt);
15+
case PT_DOUBLE: return E(&value.dbl);
16+
case PT_BOOLEAN: return E(&value.b);
17+
case PT_BINARY: return E(value.pv);
18+
case PT_STRING8: [[fallthrough]];
19+
case PT_UNICODE: return E(value.pstr);
20+
case PT_CLSID: return E(value.pguid);
21+
case PT_SYSTIME: return E(&value.ftime);
22+
case PT_ERROR: return E(&value.err);
23+
case PT_MV_SHORT: return E(&value.short_array);
24+
case PT_MV_LONG: return E(&value.long_array);
25+
case PT_MV_STRING8: [[fallthrough]];
26+
case PT_MV_UNICODE: return E(&value.string_array);
27+
case PT_MV_BINARY: return E(&value.bin_array);
28+
case PT_MV_CLSID: return E(&value.guid_array);
29+
case PT_MV_SYSTIME: return E(&value.ftime_array);
30+
default: return "??";
31+
}
32+
}
33+
34+
std::string NSPRES_AND_OR::repr(const char *sep) const
35+
{
36+
std::string s;
37+
for (size_t i = 0; i < cres; ++i)
38+
if (i == 0)
39+
s = pres[i].repr();
40+
else
41+
s += sep + pres[i].repr();
42+
return "RES_AOR[" + std::to_string(cres) + "]{" + std::move(s) + "}";
43+
}
44+
45+
std::string NSPRES_NOT::repr() const
46+
{
47+
return "RES_NOT{" + pres->repr() + "}";
48+
}
49+
50+
std::string NSPRES_CONTENT::repr() const
51+
{
52+
std::string ss = "RES_CONTENT{";
53+
switch (fuzzy_level & 0xffff) {
54+
case FL_FULLSTRING: ss += "FL_FULLSTRING,"; break;
55+
case FL_SUBSTRING: ss += "FL_SUBSTRING,"; break;
56+
case FL_PREFIX: ss += "FL_PREFIX,"; break;
57+
default: ss += "part??,"; break;
58+
}
59+
if (fuzzy_level & FL_PREFIX_ON_ANY_WORD)
60+
ss += "FL_PREFIX_ON_ANY_WORD,";
61+
if (fuzzy_level & FL_PHRASE_MATCH)
62+
ss += "FL_PHRASE_MATCH,";
63+
if (fuzzy_level & FL_IGNORECASE)
64+
ss += "FL_IGNORECASE,";
65+
if (fuzzy_level & FL_IGNORENONSPACE)
66+
ss += "FL_IGNORE_NON_SPACE,";
67+
if (fuzzy_level & FL_LOOSE)
68+
ss += "FL_LOOSE,";
69+
ss += fmt::format("{:x}h,{}", proptag, pprop->repr());
70+
return ss;
71+
}
72+
73+
std::string NSPRES_PROPERTY::repr() const
74+
{
75+
return fmt::format("RES_PROP{{val({:x}h) {} {}}}",
76+
proptag, gromox::relop_repr(relop), pprop->repr());
77+
}
78+
79+
std::string NSPRES_PROPCOMPARE::repr() const
80+
{
81+
return fmt::format("RES_PROPCMP{{val({:x}h) {} val({})}}",
82+
proptag1, gromox::relop_repr(relop), proptag2);
83+
}
84+
85+
std::string NSPRES_BITMASK::repr() const
86+
{
87+
std::string ss = fmt::format("RES_BITMASK{{val({:x}h & {:x}", proptag, mask);
88+
switch (rel_mbr) {
89+
case BMR_EQZ: ss += "h == 0}"; break;
90+
case BMR_NEZ: ss += "h != 0}"; break;
91+
default: ss += "h ..op?}"; break;
92+
}
93+
return ss;
94+
}
95+
96+
std::string NSPRES_SIZE::repr() const
97+
{
98+
return fmt::format("RES_SIZE{{{},{:x}h,{}}}",
99+
gromox::relop_repr(relop), proptag, cb);
100+
}
101+
102+
std::string NSPRES_EXIST::repr() const
103+
{
104+
return fmt::format("RES_EXIST{{{:x}h}}", proptag);
105+
}
106+
107+
std::string NSPRES_SUB::repr() const
108+
{
109+
return fmt::format("RES_SUBOBJ{{{:x}h,{}}}", subobject, pres->repr());
110+
}
111+
112+
std::string NSPRES::repr() const
113+
{
114+
switch (res_type) {
115+
case RES_AND: return "RES_AND" + res.res_andor.repr(" && ");
116+
case RES_OR: return "RES_OR" + res.res_andor.repr(" || ");
117+
case RES_NOT: return res.res_not.repr();
118+
case RES_CONTENT: return res.res_content.repr();
119+
case RES_PROPERTY: return res.res_property.repr();
120+
case RES_PROPCOMPARE: return res.res_propcompare.repr();
121+
case RES_BITMASK: return res.res_bitmask.repr();
122+
case RES_SIZE: return res.res_size.repr();
123+
case RES_EXIST: return res.res_exist.repr();
124+
case RES_SUBRESTRICTION: return res.res_sub.repr();
125+
case RES_COMMENT: return "RES_COMMENT{..}";
126+
case RES_COUNT: return "RES_COUNT{..}";
127+
case RES_ANNOTATION: return "RES_ANNOTATION{..}";
128+
case RES_NULL: return "RES_NULL{}";
129+
default: return "RES_??{}";
130+
}
131+
}
132+

exch/zcore/ab_tree.cpp

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -546,7 +546,8 @@ BOOL ab_tree_match_minids(const ab_tree::ab_base *pbase, uint32_t container_id,
546546
if (container_id == ab_tree::minid::SC_GAL) {
547547
for (auto it = pbase->ubegin(); it != pbase->uend(); ++it) {
548548
ab_tree::ab_node node(it);
549-
if (node.hidden() & AB_HIDE_FROM_GAL || !ab_tree_match_node(node, pfilter))
549+
if (node.hidden() & AB_HIDE_FROM_GAL ||
550+
!ab_tree_match_node(node, pfilter))
550551
continue;
551552
tlist.push_back(*it);
552553
}
@@ -559,7 +560,7 @@ BOOL ab_tree_match_minids(const ab_tree::ab_base *pbase, uint32_t container_id,
559560
}
560561
for (ab_tree::minid mid : node) {
561562
ab_tree::ab_node child(pbase, mid);
562-
if(child.type() >= ab_tree::abnode_type::containers ||
563+
if (child.type() >= ab_tree::abnode_type::containers ||
563564
child.hidden() & AB_HIDE_FROM_AL ||
564565
!ab_tree_match_node(child, pfilter))
565566
continue;

0 commit comments

Comments
 (0)