Skip to content

Commit 7d9200f

Browse files
committed
Merge branch 'gxl-655'
2 parents cdeec1f + 98c22a3 commit 7d9200f

File tree

3 files changed

+137
-169
lines changed

3 files changed

+137
-169
lines changed

include/gromox/oxcmail.hpp

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
#pragma once
22
#include <memory>
3+
#include <string>
34
#include <vector>
5+
#include <vmime/generationContext.hpp>
6+
#include <vmime/message.hpp>
47
#include <gromox/defs.h>
58
#include <gromox/element_data.hpp>
69
#include <gromox/ext_buffer.hpp>
@@ -28,6 +31,10 @@ struct GX_EXPORT addr_tags {
2831
uint32_t pr_name, pr_addrtype, pr_emaddr, pr_smtpaddr, pr_entryid;
2932
};
3033

34+
extern GX_EXPORT vmime::generationContext vmail_default_genctx();
35+
extern GX_EXPORT std::string vmail_to_string(const vmime::message &);
36+
extern GX_EXPORT bool vmail_to_mail(const vmime::message &, MAIL &);
37+
3138
extern GX_EXPORT bool g_oxcical_allday_ymd;
3239
extern GX_EXPORT unsigned int g_oxvcard_pedantic;
3340

lib/mapi/oxcmail2.cpp

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,18 @@
33
// This file is part of Gromox.
44
#include <algorithm>
55
#include <cstdint>
6+
#include <memory>
67
#include <string>
78
#include <utility>
89
#include <libHX/libxml_helper.h>
910
#include <libxml/HTMLparser.h>
1011
#include <libxml/HTMLtree.h>
12+
#include <vmime/generationContext.hpp>
13+
#include <vmime/utility/outputStreamStringAdapter.hpp>
1114
#include <gromox/mail_func.hpp>
1215
#include <gromox/mapidefs.h>
1316
#include <gromox/mime.hpp>
17+
#include <gromox/oxcmail.hpp>
1418
#include <gromox/textmaps.hpp>
1519
#include <gromox/tie.hpp>
1620
#include <gromox/util.hpp>
@@ -413,3 +417,40 @@ ec_error_t bodyset_multi(MIME_ENUM_PARAM &epar, TPROPVAL_ARRAY &props,
413417
}
414418

415419
}
420+
421+
namespace gromox {
422+
423+
vmime::generationContext vmail_default_genctx()
424+
{
425+
vmime::generationContext c;
426+
/* Outlook is unable to read RFC 2231. */
427+
c.setEncodedParameterValueMode(vmime::generationContext::EncodedParameterValueModes::PARAMETER_VALUE_RFC2231_AND_RFC2047);
428+
/* Outlook is also unable to parse Content-ID:\n id... */
429+
c.setWrapMessageId(false);
430+
return c;
431+
}
432+
433+
std::string vmail_to_string(const vmime::message &msg)
434+
{
435+
std::string ss;
436+
vmime::utility::outputStreamStringAdapter adap(ss);
437+
msg.generate(vmail_default_genctx(), adap);
438+
return ss;
439+
}
440+
441+
bool vmail_to_mail(const vmime::message &in, MAIL &out) try
442+
{
443+
auto str = vmail_to_string(in);
444+
auto len = str.size();
445+
auto buf = std::make_unique<char[]>(len);
446+
memcpy(buf.get(), str.c_str(), len);
447+
if (!out.refonly_parse(buf.get(), len))
448+
return false;
449+
out.buffer = std::move(buf);
450+
return true;
451+
} catch (const std::bad_alloc &) {
452+
mlog(LV_ERR, "%s: ENOMEM", __func__);
453+
return false;
454+
}
455+
456+
}

mda/exmdb_local/auto_response.cpp

Lines changed: 89 additions & 169 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,18 @@
1-
// SPDX-License-Identifier: GPL-2.0-only WITH linking exception
2-
// SPDX-FileCopyrightText: 2020–2025 grommunio GmbH
1+
// SPDX-License-Identifier: AGPL-3.0-or-later
2+
// SPDX-FileCopyrightText: 2025 grommunio GmbH
33
// This file is part of Gromox.
44
#include <cstdint>
5-
#include <cstdio>
65
#include <cstring>
7-
#include <ctime>
8-
#include <fcntl.h>
9-
#include <unistd.h>
106
#include <libHX/string.h>
11-
#include <sys/stat.h>
12-
#include <sys/types.h>
13-
#include <sys/wait.h>
14-
#include <gromox/config_file.hpp>
15-
#include <gromox/defs.h>
7+
#include <vmime/addressList.hpp>
8+
#include <vmime/dateTime.hpp>
9+
#include <vmime/message.hpp>
10+
#include <vmime/stringContentHandler.hpp>
1611
#include <gromox/exmdb_client.hpp>
1712
#include <gromox/exmdb_rpc.hpp>
1813
#include <gromox/hook_common.h>
19-
#include <gromox/mail_func.hpp>
2014
#include <gromox/mysql_adaptor.hpp>
15+
#include <gromox/oxcmail.hpp>
2116
#include <gromox/util.hpp>
2217
#include "exmdb_local.hpp"
2318

@@ -26,186 +21,111 @@ DECLARE_HOOK_API(exmdb_local, extern);
2621
using namespace exmdb_local;
2722
unsigned int autoreply_silence_window;
2823

24+
static int is_same_org(const char *a, const char *b)
25+
{
26+
a = strchr(a, '@');
27+
b = strchr(b, '@');
28+
if (a == nullptr || b == nullptr)
29+
return -1;
30+
++a; ++b;
31+
return strcasecmp(a, b) == 0 ? 1 : mysql_adaptor_check_same_org2(a, b);
32+
}
33+
34+
/**
35+
* @user_home: Maildir for basically @rcpt
36+
* @from: Sender for the autoresponse (Original Envelope-To)
37+
* @rcpt: Recipient for the autoresponse (Original Envelope-From)
38+
*/
2939
void auto_response_reply(const char *user_home,
3040
const char *from, const char *rcpt) try
3141
{
32-
BOOL b_found;
33-
char *pcontent;
34-
BOOL b_internal;
35-
char charset[32]{};
36-
struct tm tm_buff;
37-
int i, j, fd, len;
38-
char subject[1024];
39-
char buff[64*1024];
40-
char date_buff[128];
41-
char temp_path[256];
42-
MIME_FIELD mime_field;
43-
struct stat node_stat;
44-
char content_type[256];
45-
char template_path[256];
46-
char new_buff[128*1024];
47-
MESSAGE_CONTEXT *pcontext;
48-
49-
if (strcasecmp(from, rcpt) == 0 || strcasecmp(rcpt, ENVELOPE_RCPT_NULL) == 0)
50-
return;
51-
auto ptoken = strchr(from, '@');
52-
auto ptoken1 = strchr(rcpt, '@');
53-
if (ptoken == nullptr || ptoken1 == nullptr)
42+
auto same_org = is_same_org(from, rcpt);
43+
if (same_org < 0)
5444
return;
5545

56-
if (0 == strcasecmp(ptoken, ptoken1)) {
57-
b_internal = TRUE;
58-
} else {
59-
auto lcldom = mysql_adaptor_domain_list_query(ptoken + 1);
60-
if (lcldom < 0) {
61-
mlog(LV_ERR, "auto_response: domain_list_query: %s",
62-
strerror(-lcldom));
63-
return;
64-
}
65-
b_internal = lcldom < 1 ? false :
66-
mysql_adaptor_check_same_org2(ptoken + 1, ptoken1 + 1);
67-
}
68-
69-
snprintf(temp_path, 256, "%s/config/autoreply.cfg", user_home);
70-
auto pconfig = config_file_init(temp_path, nullptr);
71-
if (pconfig == nullptr)
72-
return;
73-
auto str_value = pconfig->get_value("OOF_STATE");
74-
if (str_value == nullptr)
46+
static constexpr proptag_t tags_1[] = {PR_OOF_STATE};
47+
static constexpr PROPTAG_ARRAY tags_1r = {std::size(tags_1), deconst(tags_1)};
48+
TPROPVAL_ARRAY store_props;
49+
if (!exmdb_client->get_store_properties(user_home, CP_UTF8, &tags_1r, &store_props)) {
50+
mlog(LV_ERR, "get_store_properties %s failed", user_home);
7551
return;
76-
uint8_t reply_state = strtol(str_value, nullptr, 0);
77-
if (reply_state != 1 && reply_state != 2)
78-
return;
79-
auto cur_time = time(nullptr);
80-
if (2 == reply_state) {
81-
str_value = pconfig->get_value("START_TIME");
82-
if (str_value != nullptr && strtoll(str_value, nullptr, 0) > cur_time)
83-
return;
84-
str_value = pconfig->get_value("END_TIME");
85-
if (str_value != nullptr && cur_time > strtoll(str_value, nullptr, 0))
86-
return;
87-
}
88-
if (b_internal) {
89-
snprintf(template_path, 256, "%s/config/internal-reply", user_home);
90-
} else {
91-
str_value = pconfig->get_value("ALLOW_EXTERNAL_OOF");
92-
if (str_value == nullptr || strtol(str_value, nullptr, 0) == 0)
93-
return;
94-
str_value = pconfig->get_value("EXTERNAL_AUDIENCE");
95-
if (str_value != nullptr && strtol(str_value, nullptr, 0) != 0) {
96-
if (!exmdb_client_remote::check_contact_address(user_home, rcpt,
97-
&b_found) || !b_found)
98-
return;
99-
}
100-
snprintf(template_path, 256, "%s/config/external-reply", user_home);
10152
}
53+
auto bval = store_props.get<const uint8_t>(PR_OOF_STATE);
54+
if (bval == nullptr || *bval == 0)
55+
return;
10256

57+
/* Elvis has left the building */
10358
uint64_t tdiff;
10459
if (exmdb_client->autoreply_tsquery(user_home, rcpt,
10560
autoreply_silence_window, &tdiff) && tdiff < autoreply_silence_window)
10661
/* Autoreply already sent */
10762
return;
108-
fd = open(template_path, O_RDONLY);
109-
if (fd < 0)
110-
return;
111-
if (fstat(fd, &node_stat) != 0 || node_stat.st_size == 0 ||
112-
static_cast<unsigned long long>(node_stat.st_size) > sizeof(buff) - 1 ||
113-
read(fd, buff, node_stat.st_size) != node_stat.st_size) {
114-
close(fd);
63+
64+
static constexpr proptag_t tags_2[] = {
65+
PR_EC_OUTOFOFFICE_SUBJECT, PR_EC_OUTOFOFFICE_MSG,
66+
PR_EC_ALLOW_EXTERNAL, PR_EC_EXTERNAL_AUDIENCE,
67+
PR_EC_EXTERNAL_SUBJECT, PR_EC_EXTERNAL_REPLY,
68+
};
69+
static constexpr PROPTAG_ARRAY tags_2r = {std::size(tags_2), deconst(tags_2)};
70+
TPROPVAL_ARRAY ar_props;
71+
if (!exmdb_client->autoreply_getprop(user_home, CP_UTF8,
72+
&tags_2r, &ar_props)) {
73+
mlog(LV_ERR, "autoreply_getprop %s failed", user_home);
11574
return;
11675
}
117-
close(fd);
11876

119-
if ('\n' == buff[0]) {
120-
new_buff[0] = '\r';
121-
new_buff[1] = '\n';
122-
i = 1;
123-
j = 2;
124-
} else {
125-
new_buff[0] = buff[0];
126-
i = 1;
127-
j = 1;
128-
}
129-
for (; i<node_stat.st_size; i++, j++) {
130-
if (buff[i] == '\n' && buff[i-1] != '\r')
131-
new_buff[j++] = '\r';
132-
new_buff[j] = buff[i];
77+
if (!same_org) {
78+
bval = ar_props.get<const uint8_t>(PR_EC_ALLOW_EXTERNAL);
79+
if (bval == nullptr || *bval == 0)
80+
return;
81+
//Note: counterintuitive but intentional: known (contacts_only) -> 1, all_audiences -> 0
82+
bval = ar_props.get<const uint8_t>(PR_EC_EXTERNAL_AUDIENCE);
83+
if (bval != nullptr && *bval == 0) {
84+
BOOL b_found = false;
85+
if (!exmdb_client_remote::check_contact_address(user_home, rcpt,
86+
&b_found) || !b_found)
87+
return;
88+
}
13389
}
134-
new_buff[j] = '\0';
13590

91+
auto subject_text = znul(ar_props.get<const char>(same_org ? PR_EC_OUTOFOFFICE_SUBJECT : PR_EC_EXTERNAL_SUBJECT));
92+
auto message_text = znul(ar_props.get<const char>(same_org ? PR_EC_OUTOFOFFICE_MSG : PR_EC_EXTERNAL_REPLY));
93+
vmime::parsingContext vpctx;
94+
vpctx.setInternationalizedEmailSupport(true); /* RFC 6532 */
95+
vmime::message vmsg;
13696

137-
i = 0;
138-
pcontent = NULL;
139-
strcpy(content_type, "text/plain");
140-
strcpy(subject, "auto response message");
141-
while (i < j) {
142-
auto parsed_length = parse_mime_field(new_buff + i, j - i, &mime_field);
143-
i += parsed_length;
144-
if (parsed_length == 0)
145-
return;
146-
if (strcasecmp(mime_field.name.c_str(), "Content-Type") == 0) {
147-
gx_strlcpy(content_type, mime_field.value.c_str(), std::size(content_type));
148-
charset[0] = '\0';
149-
auto ptoken2 = strchr(content_type, ';');
150-
if (ptoken2 != nullptr) {
151-
*ptoken2 = '\0';
152-
++ptoken2;
153-
ptoken2 = strcasestr(ptoken2, "charset=");
154-
if (ptoken2 != nullptr) {
155-
gx_strlcpy(charset, &ptoken2[8], std::size(charset));
156-
ptoken2 = strchr(charset, ';');
157-
if (ptoken2 != nullptr)
158-
*ptoken2 = '\0';
159-
HX_strrtrim(charset);
160-
HX_strltrim(charset);
161-
len = strlen(charset);
162-
if ('"' == charset[len - 1]) {
163-
len --;
164-
charset[len] = '\0';
165-
}
166-
if (*charset == '"')
167-
memmove(charset, charset + 1, len);
168-
}
169-
}
170-
} else if (strcasecmp(mime_field.name.c_str(), "Subject") == 0) {
171-
gx_strlcpy(subject, mime_field.value.c_str(), std::size(subject));
172-
}
173-
if ('\r' == new_buff[i] && '\n' == new_buff[i + 1]) {
174-
pcontent = new_buff + i + 2;
175-
break;
176-
}
97+
auto hdr = vmsg.getHeader();
98+
hdr->getField("MIME-Version")->setValue("1.0");
99+
hdr->From()->setValue(from);
100+
{
101+
vmime::addressList target_list;
102+
vmime::mailbox target;
103+
target.setEmail(rcpt);
104+
target_list.appendAddress(vmime::make_shared<vmime::mailbox>(target));
105+
hdr->To()->setValue(target_list);
177106
}
178-
if (pcontent == nullptr)
179-
return;
180-
pcontext = get_context();
181-
if (pcontext == nullptr)
182-
return;
183-
gx_strlcpy(pcontext->ctrl.from, from, std::size(pcontext->ctrl.from));
184-
pcontext->ctrl.rcpt.emplace_back(rcpt);
185-
auto pmime = pcontext->mail.add_head();
186-
if (NULL == pmime) {
187-
put_context(pcontext);
107+
hdr->getField("X-Auto-Response-Suppress")->setValue("All");
108+
hdr->Date()->setValue(vmime::datetime::now());
109+
hdr->Subject()->setValue(vmime::text(subject_text, vmime::charsets::UTF_8));
110+
111+
vmime::encoding enc;
112+
enc.setUsage(vmime::encoding::EncodingUsage::USAGE_TEXT);
113+
vmsg.getBody()->setContents(vmime::make_shared<vmime::stringContentHandler>(message_text, std::move(enc)),
114+
vmime::mediaType(vmime::mediaTypes::TEXT, vmime::mediaTypes::TEXT_HTML),
115+
vmime::charsets::UTF_8);
116+
117+
auto ctx = get_context();
118+
if (ctx == nullptr)
188119
return;
189-
}
190-
if (!pmime->set_content_type(content_type))
191-
mlog(LV_WARN, "W-1777: set_content_type unsuccessful");
192-
if (*charset != '\0')
193-
pmime->set_content_param("charset", charset);
194-
pmime->set_field("From", from);
195-
pmime->set_field("To", rcpt);
196-
pmime->set_field("MIME-Version", "1.0");
197-
pmime->set_field("X-Auto-Response-Suppress", "All");
198-
strftime(date_buff, 128, "%a, %d %b %Y %H:%M:%S %z",
199-
localtime_r(&cur_time, &tm_buff));
200-
pmime->set_field("Date", date_buff);
201-
pmime->set_field("Subject", subject);
202-
if (!pmime->write_content(pcontent,
203-
new_buff + j - pcontent, mime_encoding::automatic)) {
204-
put_context(pcontext);
120+
gx_strlcpy(ctx->ctrl.from, from, std::size(ctx->ctrl.from));
121+
ctx->ctrl.rcpt.emplace_back(rcpt);
122+
if (!vmail_to_mail(vmsg, ctx->mail)) {
123+
mlog(LV_ERR, "%s: vmail_to_mail failed", __func__);
124+
put_context(ctx);
205125
return;
206126
}
207-
enqueue_context(pcontext);
127+
enqueue_context(ctx);
208128
exmdb_client->autoreply_tsupdate(user_home, rcpt);
209129
} catch (const std::bad_alloc &) {
210-
mlog(LV_ERR, "E-1081: ENOMEM");
130+
mlog(LV_ERR, "%s: ENOMEM", __func__);
211131
}

0 commit comments

Comments
 (0)