diff --git a/doc/changelog.rst b/doc/changelog.rst index 1e9a1c59c..164cdbe70 100644 --- a/doc/changelog.rst +++ b/doc/changelog.rst @@ -1,5 +1,5 @@ -Development 2.15.1 -================== +Development 2.15.17 +=================== Fixes: @@ -7,6 +7,11 @@ Fixes: the vCard 4.0 specification. * oxcmail: cease gratuitous RTF conversion of calendar items +Enhancements: + +* Define the "suspended" user state (think of it as a "non-receiving shared + mailbox"). + Gromox 2.15 (2023-10-18) ======================== diff --git a/exch/mysql_adaptor/mysql_adaptor.cpp b/exch/mysql_adaptor/mysql_adaptor.cpp index b1a70589c..4e53e7742 100644 --- a/exch/mysql_adaptor/mysql_adaptor.cpp +++ b/exch/mysql_adaptor/mysql_adaptor.cpp @@ -111,7 +111,7 @@ errno_t mysql_adaptor_meta(const char *username, unsigned int wantpriv, return EACCES; } auto address_status = strtoul(myrow[2], nullptr, 0); - if (address_status != 0 && !(wantpriv & WANTPRIV_METAONLY)) { + if (!afuser_login_allowed(address_status) && !(wantpriv & WANTPRIV_METAONLY)) { auto uval = address_status & AF_USER__MASK; if (address_status & AF_DOMAIN__MASK) mres.errstr = fmt::format("Domain of user \"{}\" is disabled!", username); @@ -921,8 +921,7 @@ bool mysql_adaptor_check_user(const char *user_raw, const char *delim, auto myrow = pmyres.fetch_row(); if (path != nullptr) gx_strlcpy(path, myrow[1], dsize); - unsigned int status = strtoul(myrow[0], nullptr, 0); - return status == AF_USER_NORMAL || status == AF_USER_SHAREDMBOX; + return afuser_store_canrecv(strtoul(myrow[0], nullptr, 0)); } catch (const std::exception &e) { mlog(LV_ERR, "%s: %s", "E-1731", e.what()); return false; diff --git a/include/gromox/mysql_adaptor.hpp b/include/gromox/mysql_adaptor.hpp index ee0fd4477..f841b4578 100644 --- a/include/gromox/mysql_adaptor.hpp +++ b/include/gromox/mysql_adaptor.hpp @@ -9,20 +9,28 @@ enum { /* Reason codes (users.address_status) for forbidden login */ - AF_USER_NORMAL = 0x00, - AF_USER_SUSPENDED = 0x01, + AF_USER_NORMAL = 0x00, // login pres recv + AF_USER_SUSPENDED = 0x01, // pres AF_USER_DELETED = 0x03, - AF_USER_SHAREDMBOX = 0x04, + AF_USER_SHAREDMBOX = 0x04, // pres recv AF_USER_CONTACT = 0x05, AF_USER__MASK = 0x0F, - // historically: groups with AF_GROUP__MASK = 0xC0, with statuses NORMAL..DELETED AF_DOMAIN_NORMAL = 0x00, AF_DOMAIN_SUSPENDED = 0x10, AF_DOMAIN_DELETED = 0x30, AF_DOMAIN__MASK = 0x30, - /* note: users.address_status is a tinyint(4), so only 7 "usable" bits */ + /* + * Note: users.address_status is a tinyint(4), which has a range of + * -127..128, so 7 bits are usable. Oddly enough, historically the high + * bits were also defined to be in use (but probably never were, due to + * the range limit): + * + * AF_GROUP__MASK = 0xC0, + * + * The (4) is the display width. 6 bits are currently in use. + */ }; enum class mlist_type { @@ -129,3 +137,32 @@ extern BOOL mysql_adaptor_get_mlist_memb(const char *username, const char *from, extern bool mysql_adaptor_get_user_info(const char *username, char *maildir, size_t msize, char *lang, size_t lsize, char *timezone, size_t tsize); extern void mysql_adaptor_encode_squote(const char *in, char *out); extern gromox::errno_t mysql_adaptor_get_homeserver(const char *ent, bool is_pvt, std::pair &); + +/** + * Determines whether an arbitrary actor can generally open/read the primary + * store of a target user. (Further restrictions like ACLs not covered here.) + * + * @v: address_status of the target user + */ +static inline bool afuser_store_present(unsigned int st) +{ + st &= AF_USER__MASK; + return st <= AF_USER_SUSPENDED || st == AF_USER_SHAREDMBOX; +} + +/* Should usually be combined with afuser_store_present */ +static inline bool afuser_store_canrecv(unsigned int st) +{ + return st == AF_USER_NORMAL || st == AF_USER_SHAREDMBOX; +} + +/** + * Determines whether an actor A is permitted to utilize the + * ropLogin/mapi_logon_zarafa functionality (on his own store). + * + * allowed := user value is exactly AF_USER_NORMAL && + * domain is allowed (all bits of AF_DOMAIN__MASK are zero) + * + * Thus, the only permissible value is AF_USER_NORMAL. + */ +static inline bool afuser_login_allowed(unsigned int st) { return st == AF_USER_NORMAL; } diff --git a/tools/mkmidb.cpp b/tools/mkmidb.cpp index 6dddd53bd..65f15365a 100644 --- a/tools/mkmidb.cpp +++ b/tools/mkmidb.cpp @@ -127,7 +127,7 @@ int main(int argc, const char **argv) try } unsigned int address_status = strtoul(myrow[1], nullptr, 0); - if (address_status != AF_USER_NORMAL && address_status != AF_USER_SHAREDMBOX) + if (!afuser_store_present(address_status)) printf("Warning: Account status (0x%x) indicates this user object normally does not have a mailbox. Proceeding anyway for now...\n", address_status); std::string dir = znul(myrow[2]); myres.clear(); diff --git a/tools/mkprivate.cpp b/tools/mkprivate.cpp index ee4c4eba3..9e03223ac 100644 --- a/tools/mkprivate.cpp +++ b/tools/mkprivate.cpp @@ -179,7 +179,7 @@ int main(int argc, const char **argv) try } unsigned int address_status = strtoul(myrow[4], nullptr, 0); - if (address_status != AF_USER_NORMAL && address_status != AF_USER_SHAREDMBOX) + if (!afuser_store_present(address_status)) printf("Warning: Account status (0x%x) indicates this user object normally does not have a mailbox. Proceeding anyway for now...\n", address_status); std::string dir = znul(myrow[1]), lang = znul(myrow[2]);