Skip to content

Commit 2ba32bd

Browse files
grammmikejengelh
authored andcommitted
exmdb: add autoreply/OOF RPC endpoints
Introduced new RPC endpoints ``autoreply_getprop`` and ``autoreply_setprop``, enabling remote retrieval and storage of Out of Office settings without direct filesystem access. Implemented server-side handlers that read and write the autoreply configuration files for the requested property tags. References: GXL-216
1 parent 3aaad5f commit 2ba32bd

File tree

6 files changed

+302
-4
lines changed

6 files changed

+302
-4
lines changed

exch/exmdb/names.cpp

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,8 @@ static constexpr const char *exmdb_rpc_names[] = {
157157
E(cgkreset),
158158
E(write_message),
159159
E(set_maintenance),
160+
E(autoreply_getprop),
161+
E(autoreply_setprop),
160162
};
161163
#undef E
162164

@@ -165,7 +167,7 @@ namespace exmdb {
165167
const char *exmdb_rpc_idtoname(exmdb_callid i)
166168
{
167169
auto j = static_cast<uint8_t>(i);
168-
static_assert(std::size(exmdb_rpc_names) == static_cast<uint8_t>(exmdb_callid::set_maintenance) + 1);
170+
static_assert(std::size(exmdb_rpc_names) == static_cast<uint8_t>(exmdb_callid::autoreply_setprop) + 1);
169171
auto s = j < std::size(exmdb_rpc_names) ? exmdb_rpc_names[j] : nullptr;
170172
return znul(s);
171173
}

exch/exmdb/store2.cpp

Lines changed: 281 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@
2323
#include <gromox/exmdb_common_util.hpp>
2424
#include <gromox/exmdb_server.hpp>
2525
#include <gromox/fileio.h>
26+
#include <gromox/config_file.hpp>
27+
#include <gromox/mail_func.hpp>
2628
#include <gromox/mapi_types.hpp>
2729
#include <gromox/mapidefs.h>
2830
#include <gromox/mysql_adaptor.hpp>
@@ -43,6 +45,13 @@ struct sql_del {
4345

4446
}
4547

48+
static constexpr cfg_directive oof_defaults[] = {
49+
{"allow_external_oof", "0", CFG_BOOL},
50+
{"external_audience", "0", CFG_BOOL},
51+
{"oof_state", "0"},
52+
CFG_TABLE_END,
53+
};
54+
4655
BOOL exmdb_server::vacuum(const char *dir)
4756
{
4857
return db_engine_vacuum(dir);
@@ -569,6 +578,278 @@ BOOL exmdb_server::autoreply_tsupdate(const char *dir, const char *peer) try
569578
return false;
570579
}
571580

581+
static std::string autoreply_fspath(const char *dir, proptag_t proptag)
582+
{
583+
switch (proptag) {
584+
case PR_EC_OUTOFOFFICE:
585+
case PR_EC_OUTOFOFFICE_FROM:
586+
case PR_EC_OUTOFOFFICE_UNTIL:
587+
case PR_EC_ALLOW_EXTERNAL:
588+
case PR_EC_EXTERNAL_AUDIENCE:
589+
return dir + "/config/autoreply.cfg"s;
590+
case PR_EC_OUTOFOFFICE_MSG:
591+
case PR_EC_OUTOFOFFICE_SUBJECT:
592+
return dir + "/config/internal-reply"s;
593+
case PR_EC_EXTERNAL_REPLY:
594+
case PR_EC_EXTERNAL_SUBJECT:
595+
return dir + "/config/external-reply"s;
596+
default:
597+
return {};
598+
}
599+
}
600+
601+
static ec_error_t autoreply_getprop1(const char *dir,
602+
proptag_t proptag, void *&value)
603+
{
604+
char subject[1024];
605+
MIME_FIELD mime_field;
606+
auto path = autoreply_fspath(dir, proptag);
607+
608+
switch (proptag) {
609+
case PR_EC_OUTOFOFFICE: {
610+
auto oofstate = cu_alloc<uint32_t>();
611+
if (oofstate == nullptr)
612+
return ecServerOOM;
613+
auto cfg = config_file_init(path.c_str(), oof_defaults);
614+
*oofstate = cfg != nullptr ? cfg->get_ll("oof_state") : 0;
615+
value = oofstate;
616+
return ecSuccess;
617+
}
618+
case PR_EC_OUTOFOFFICE_MSG:
619+
case PR_EC_EXTERNAL_REPLY: {
620+
struct stat st;
621+
wrapfd fd = open(path.c_str(), O_RDONLY);
622+
if (fd.get() < 0 || fstat(fd.get(), &st) != 0)
623+
return ecNotFound;
624+
auto buf = cu_alloc<char>(st.st_size + 1);
625+
if (buf == nullptr)
626+
return ecServerOOM;
627+
if (read(fd.get(), buf, st.st_size) != st.st_size)
628+
return ecReadFault;
629+
buf[st.st_size] = '\0';
630+
auto ptr = strstr(buf, "\r\n\r\n");
631+
value = ptr != nullptr ? &ptr[4] : nullptr;
632+
return value != nullptr ? ecSuccess : ecNotFound;
633+
}
634+
case PR_EC_OUTOFOFFICE_SUBJECT:
635+
case PR_EC_EXTERNAL_SUBJECT: {
636+
struct stat st;
637+
wrapfd fd = open(path.c_str(), O_RDONLY);
638+
if (fd.get() < 0 || fstat(fd.get(), &st) != 0)
639+
return ecNotFound;
640+
auto buf = cu_alloc<char>(st.st_size);
641+
if (buf == nullptr)
642+
return ecServerOOM;
643+
if (buf == nullptr || read(fd.get(), buf, st.st_size) != st.st_size)
644+
return ecReadFault;
645+
size_t offset = 0;
646+
while (auto parsed = parse_mime_field(&buf[offset], st.st_size - offset, &mime_field)) {
647+
offset += parsed;
648+
if (strcasecmp(mime_field.name.c_str(), "Subject") == 0 &&
649+
mime_field.value.size() < sizeof(subject) &&
650+
mime_string_to_utf8("utf-8", mime_field.value.c_str(), subject, sizeof(subject))) {
651+
value = common_util_dup(subject);
652+
return value != nullptr ? ecSuccess : ecServerOOM;
653+
}
654+
if (buf[offset] == '\r' && buf[offset+1] == '\n')
655+
break;
656+
}
657+
return ecNotFound;
658+
}
659+
case PR_EC_OUTOFOFFICE_FROM:
660+
case PR_EC_OUTOFOFFICE_UNTIL: {
661+
auto cfg = config_file_init(path.c_str(), oof_defaults);
662+
if (cfg == nullptr)
663+
return ecNotFound;
664+
auto ts = cu_alloc<mapitime_t>();
665+
if (ts == nullptr)
666+
return ecServerOOM;
667+
auto key = proptag == PR_EC_OUTOFOFFICE_FROM ? "START_TIME" : "END_TIME";
668+
auto sval = cfg->get_value(key);
669+
if (sval == nullptr)
670+
return ecNotFound;
671+
*ts = rop_util_unix_to_nttime(strtoll(sval, nullptr, 0));
672+
value = ts;
673+
return ecSuccess;
674+
}
675+
case PR_EC_ALLOW_EXTERNAL:
676+
case PR_EC_EXTERNAL_AUDIENCE: {
677+
static constexpr uint8_t fake_true = true, fake_false = false;
678+
auto cfg = config_file_init(path.c_str(), oof_defaults);
679+
if (cfg == nullptr) {
680+
value = deconst(&fake_false);
681+
return ecSuccess;
682+
}
683+
auto key = proptag == PR_EC_ALLOW_EXTERNAL ? "allow_external_oof" : "external_audience";
684+
value = deconst(cfg->get_ll(key) == 0 ? &fake_false : &fake_true);
685+
return ecSuccess;
686+
}
687+
}
688+
return ecNotFound;
689+
}
690+
691+
BOOL exmdb_server::autoreply_getprop(const char *dir, cpid_t cpid,
692+
const PROPTAG_ARRAY *pproptags, TPROPVAL_ARRAY *ppropvals) try
693+
{
694+
ppropvals->count = 0;
695+
ppropvals->ppropval = cu_alloc<TAGGED_PROPVAL>(pproptags->count);
696+
if (ppropvals->ppropval == nullptr)
697+
return false;
698+
for (proptag_t tag : *pproptags) {
699+
void *value = nullptr;
700+
auto err = autoreply_getprop1(dir, tag, value);
701+
if (err == ecSuccess) {
702+
if (value != nullptr) {
703+
ppropvals->emplace_back(tag, value);
704+
continue;
705+
}
706+
err = ecNotFound;
707+
}
708+
auto erp = cu_alloc<uint32_t>();
709+
if (erp == nullptr)
710+
return false;
711+
*erp = err;
712+
ppropvals->emplace_back(CHANGE_PROP_TYPE(PT_ERROR, tag), erp);
713+
}
714+
return true;
715+
} catch (const std::bad_alloc &) {
716+
mlog(LV_ERR, "E-2227: ENOMEM");
717+
return false;
718+
}
719+
720+
static BOOL autoreply_setprop1(const char *dir, const TAGGED_PROPVAL &pv)
721+
{
722+
auto path = autoreply_fspath(dir, pv.proptag);
723+
724+
/* Ensure file exists for the sake of config_file_init() */
725+
auto fdtest = open(path.c_str(), O_CREAT | O_WRONLY, FMODE_PUBLIC);
726+
if (fdtest < 0)
727+
return false;
728+
close(fdtest);
729+
730+
switch (pv.proptag) {
731+
case PR_EC_OUTOFOFFICE: {
732+
auto cfg = config_file_init(path.c_str(), oof_defaults);
733+
if (cfg == nullptr)
734+
return false;
735+
auto v = *static_cast<const uint32_t *>(pv.pvalue);
736+
cfg->set_value("OOF_STATE", v == 1 ? "1" : v == 2 ? "2" : "0");
737+
return cfg->save();
738+
}
739+
case PR_EC_OUTOFOFFICE_FROM:
740+
case PR_EC_OUTOFOFFICE_UNTIL: {
741+
auto cfg = config_file_init(path.c_str(), oof_defaults);
742+
if (cfg == nullptr)
743+
return false;
744+
auto ts = rop_util_nttime_to_unix(*static_cast<const mapitime_t *>(pv.pvalue));
745+
cfg->set_value(pv.proptag == PR_EC_OUTOFOFFICE_FROM ?
746+
"START_TIME" : "END_TIME", std::to_string(ts).c_str());
747+
return cfg->save();
748+
}
749+
case PR_EC_OUTOFOFFICE_MSG:
750+
case PR_EC_EXTERNAL_REPLY: {
751+
wrapfd fd = open(path.c_str(), O_RDONLY);
752+
struct stat st{};
753+
ssize_t buff_len = 0;
754+
char *buf = nullptr;
755+
756+
if (fd.get() < 0 || fstat(fd.get(), &st) != 0) {
757+
buff_len = strlen(static_cast<const char *>(pv.pvalue));
758+
buf = cu_alloc<char>(buff_len + 256);
759+
if (buf == nullptr)
760+
return false;
761+
buff_len = gx_snprintf(buf, buff_len + 256,
762+
"Content-Type: text/html; charset=\"utf-8\"\r\n\r\n%s",
763+
static_cast<const char *>(pv.pvalue));
764+
} else {
765+
buff_len = st.st_size;
766+
buf = cu_alloc<char>(buff_len + strlen(static_cast<const char *>(pv.pvalue)) + 1);
767+
if (buf == nullptr || read(fd.get(), buf, buff_len) != buff_len)
768+
return false;
769+
buf[buff_len] = '\0';
770+
auto token = strstr(buf, "\r\n\r\n");
771+
if (token != nullptr) {
772+
strcpy(&token[4], static_cast<const char *>(pv.pvalue));
773+
buff_len = strlen(buf);
774+
} else {
775+
buff_len = sprintf(buf,
776+
"Content-Type: text/html;\r\n\tcharset=\"utf-8\"\r\n\r\n%s",
777+
static_cast<const char *>(pv.pvalue));
778+
}
779+
}
780+
gromox::tmpfile tf;
781+
auto fdw = tf.open_linkable((dir + "/config"s).c_str(), O_WRONLY, FMODE_PUBLIC);
782+
if (fdw < 0 || HXio_fullwrite(fdw, buf, buff_len) != buff_len)
783+
return false;
784+
return tf.link_to(path.c_str()) == 0;
785+
}
786+
case PR_EC_OUTOFOFFICE_SUBJECT:
787+
case PR_EC_EXTERNAL_SUBJECT: {
788+
wrapfd fd = open(path.c_str(), O_RDONLY);
789+
struct stat st{};
790+
ssize_t buff_len = 0;
791+
char *buf = nullptr;
792+
793+
if (fd.get() < 0 || fstat(fd.get(), &st) != 0) {
794+
buff_len = strlen(static_cast<const char *>(pv.pvalue));
795+
buf = cu_alloc<char>(buff_len + 256);
796+
if (buf == nullptr)
797+
return false;
798+
buff_len = sprintf(buf,
799+
"Content-Type: text/html;\r\n\tcharset=\"utf-8\"\r\nSubject: %s\r\n\r\n",
800+
static_cast<const char *>(pv.pvalue));
801+
} else {
802+
buff_len = st.st_size;
803+
buf = cu_alloc<char>(buff_len + strlen(static_cast<const char *>(pv.pvalue)) + 16);
804+
if (buf == nullptr)
805+
return false;
806+
auto token = cu_alloc<char>(buff_len + 1);
807+
if (token == nullptr ||
808+
read(fd.get(), token, buff_len) != buff_len)
809+
return false;
810+
token[buff_len] = '\0';
811+
auto body = strstr(token, "\r\n\r\n");
812+
if (body == nullptr)
813+
buff_len = sprintf(buf,
814+
"Content-Type: text/html;\r\n\tcharset=\"utf-8\"\r\nSubject: %s\r\n\r\n",
815+
static_cast<const char *>(pv.pvalue));
816+
else
817+
buff_len = sprintf(buf,
818+
"Content-Type: text/html;\r\n\tcharset=\"utf-8\"\r\nSubject: %s%s",
819+
static_cast<const char *>(pv.pvalue), body);
820+
}
821+
gromox::tmpfile tf;
822+
auto fdw = tf.open_linkable((dir + "/config"s).c_str(), O_WRONLY, FMODE_PUBLIC);
823+
if (fdw < 0 || HXio_fullwrite(fdw, buf, buff_len) != buff_len)
824+
return false;
825+
return tf.link_to(path.c_str()) == 0;
826+
}
827+
case PR_EC_ALLOW_EXTERNAL:
828+
case PR_EC_EXTERNAL_AUDIENCE: {
829+
auto cfg = config_file_init(path.c_str(), oof_defaults);
830+
if (cfg == nullptr)
831+
return false;
832+
auto key = pv.proptag == PR_EC_ALLOW_EXTERNAL ?
833+
"ALLOW_EXTERNAL_OOF" : "EXTERNAL_AUDIENCE";
834+
cfg->set_value(key, *static_cast<const uint8_t *>(pv.pvalue) ? "1" : "0");
835+
return cfg->save();
836+
}
837+
}
838+
return false;
839+
}
840+
841+
BOOL exmdb_server::autoreply_setprop(const char *dir, cpid_t cpid,
842+
const TPROPVAL_ARRAY *ppropvals, PROBLEM_ARRAY *pproblems) try
843+
{
844+
for (const auto &p : *ppropvals)
845+
if (!autoreply_setprop1(dir, p))
846+
return false;
847+
return true;
848+
} catch (const std::bad_alloc &) {
849+
mlog(LV_ERR, "E-2229: ENOMEM");
850+
return false;
851+
}
852+
572853
BOOL exmdb_server::recalc_store_size(const char *dir, uint32_t flags)
573854
{
574855
auto db = db_engine_get_db(dir);

include/gromox/exmdb_idef.hpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,3 +141,5 @@ EXMIDL(imapfile_write, (const char *dir, const std::string &type, const std::str
141141
EXMIDL(imapfile_delete, (const char *dir, const std::string &type, const std::string &mid))
142142
EXMIDL(cgkreset, (const char *dir, uint32_t flags))
143143
EXMIDL(set_maintenance, (const char *dir, uint32_t mode))
144+
EXMIDL(autoreply_getprop, (const char *dir, cpid_t cpid, const PROPTAG_ARRAY *pproptags, IDLOUT TPROPVAL_ARRAY *propvals))
145+
EXMIDL(autoreply_setprop, (const char *dir, cpid_t cpid, const TPROPVAL_ARRAY *ppropvals, IDLOUT PROBLEM_ARRAY *problems))

include/gromox/exmdb_rpc.hpp

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,8 @@ enum class exmdb_callid : uint8_t {
173173
cgkreset = 0x91,
174174
write_message /* v3 */ = 0x92,
175175
set_maintenance = 0x93,
176+
autoreply_getprop = 0x94,
177+
autoreply_setprop = 0x95,
176178
/* update exch/exmdb/names.cpp:exmdb_rpc_idtoname! */
177179
};
178180

@@ -220,6 +222,10 @@ struct exreq_set_store_properties final : public exreq {
220222
TPROPVAL_ARRAY *ppropvals;
221223
};
222224

225+
/* @cpid fields unused, always CP_UTF8 */
226+
using exreq_autoreply_getprop = exreq_get_store_properties;
227+
using exreq_autoreply_setprop = exreq_set_store_properties;
228+
223229
struct exreq_remove_store_properties final : public exreq {
224230
PROPTAG_ARRAY *pproptags;
225231
};
@@ -932,6 +938,9 @@ struct exresp_set_store_properties final : public exresp {
932938
PROBLEM_ARRAY problems;
933939
};
934940

941+
using exresp_autoreply_getprop = exresp_get_store_properties;
942+
using exresp_autoreply_setprop = exresp_set_store_properties;
943+
935944
struct exresp_get_mbox_perm final : public exresp {
936945
uint32_t permission;
937946
};

include/gromox/mapierr.hpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -362,7 +362,7 @@ enum ec_error_t
362362
ecUnableToAbort = 0x80040114, /* MAPI_E_UNABLE_TO_ABORT */
363363
ecRpcFailed = 0x80040115,
364364
ecNetwork = 0x80040115, /* MAPI_E_NETWORK_ERROR */
365-
// ecReadFault = 0x80040116, /* ecWriteFault, MAPI_E_DISK_ERROR */
365+
ecReadFault = 0x80040116, /* ecWriteFault, MAPI_E_DISK_ERROR */
366366
ecTooComplex = 0x80040117, /* MAPI_E_TOO_COMPLEX */
367367
// MAPI_E_BAD_COLUMN = 0x80040118,
368368
// MAPI_E_EXTENDED_ERROR = 0x80040119,

lib/exmdb_ext.cpp

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2419,7 +2419,9 @@ static pack_result exmdb_push(EXT_PUSH &x, const exreq_set_maintenance &d)
24192419
E(imapfile_write) \
24202420
E(imapfile_delete) \
24212421
E(cgkreset) \
2422-
E(set_maintenance)
2422+
E(set_maintenance) \
2423+
E(autoreply_getprop) \
2424+
E(autoreply_setprop)
24232425

24242426
/**
24252427
* This uses *& because we do not know which request type we are going to get
@@ -3856,7 +3858,9 @@ static pack_result exmdb_push(EXT_PUSH &x, const exresp_imapfile_read &d)
38563858
E(get_public_folder_unread_count) \
38573859
E(store_eid_to_user) \
38583860
E(autoreply_tsquery) \
3859-
E(imapfile_read)
3861+
E(imapfile_read) \
3862+
E(autoreply_getprop) \
3863+
E(autoreply_setprop)
38603864

38613865
/* exmdb_callid::connect, exmdb_callid::listen_notification not included */
38623866
/*

0 commit comments

Comments
 (0)