Store Sieve state in user stores
Debian Trixie package bundle / packages (push) Successful in 22m11s
Debian Trixie package bundle / packages (push) Successful in 22m11s
This commit is contained in:
@@ -8,6 +8,7 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import base64
|
||||
from email.message import EmailMessage
|
||||
import imaplib
|
||||
import os
|
||||
@@ -44,6 +45,75 @@ TLS.check_hostname = False
|
||||
TLS.verify_mode = ssl.CERT_NONE
|
||||
|
||||
|
||||
def sieve_quote(value: str) -> str:
|
||||
return '"' + value.replace("\\", "\\\\").replace('"', '\\"') + '"'
|
||||
|
||||
|
||||
class ManageSieveClient:
|
||||
def __init__(self) -> None:
|
||||
self.socket = socket.create_connection(
|
||||
(HOST, PORTS["sieve"]), timeout=15)
|
||||
self.stream = self.socket.makefile("rb")
|
||||
self.read_response("greeting")
|
||||
|
||||
def close(self) -> None:
|
||||
try:
|
||||
self.stream.close()
|
||||
finally:
|
||||
self.socket.close()
|
||||
|
||||
def send(self, data: bytes) -> None:
|
||||
self.socket.sendall(data)
|
||||
|
||||
def line(self) -> bytes:
|
||||
line = self.stream.readline(65537)
|
||||
if not line or len(line) > 65536 or not line.endswith(b"\n"):
|
||||
raise RuntimeError("ManageSieve connection ended mid-response")
|
||||
return line
|
||||
|
||||
def read_response(self, operation: str) -> list[str]:
|
||||
lines: list[str] = []
|
||||
while True:
|
||||
decoded = self.line().decode("utf-8", "strict").rstrip("\r\n")
|
||||
lines.append(decoded)
|
||||
upper = decoded.upper()
|
||||
if upper == "OK" or upper.startswith("OK ") or \
|
||||
upper == "NO" or upper.startswith("NO ") or \
|
||||
upper == "BYE" or upper.startswith("BYE "):
|
||||
if not upper.startswith("OK"):
|
||||
raise RuntimeError(
|
||||
f"ManageSieve {operation} failed: {decoded}")
|
||||
return lines
|
||||
|
||||
def command(self, operation: str, command: str) -> list[str]:
|
||||
self.send(command.encode("utf-8") + b"\r\n")
|
||||
return self.read_response(operation)
|
||||
|
||||
def starttls(self) -> None:
|
||||
self.command("STARTTLS", "STARTTLS")
|
||||
self.stream.close()
|
||||
self.socket = TLS.wrap_socket(self.socket, server_hostname=HOST)
|
||||
self.stream = self.socket.makefile("rb")
|
||||
|
||||
def literal_command(
|
||||
self, operation: str, command: str, payload: bytes) -> list[str]:
|
||||
self.send(
|
||||
f"{command} {{{len(payload)}+}}\r\n".encode("utf-8") + payload)
|
||||
return self.read_response(operation)
|
||||
|
||||
def get_script(self, name: str) -> bytes:
|
||||
self.send(f"GETSCRIPT {sieve_quote(name)}\r\n".encode("utf-8"))
|
||||
marker = self.line().decode("ascii", "strict").strip()
|
||||
if not marker.startswith("{") or not marker.endswith("}"):
|
||||
raise RuntimeError(f"invalid GETSCRIPT literal marker: {marker}")
|
||||
length = int(marker[1:-1])
|
||||
payload = self.stream.read(length)
|
||||
if len(payload) != length or self.stream.read(2) != b"\r\n":
|
||||
raise RuntimeError("truncated GETSCRIPT literal")
|
||||
self.read_response("GETSCRIPT")
|
||||
return payload
|
||||
|
||||
|
||||
def wait_for_listeners(timeout: float = 30.0) -> str:
|
||||
deadline = time.monotonic() + timeout
|
||||
pending = set(PORTS.values())
|
||||
@@ -97,6 +167,53 @@ def smtp_submit() -> str:
|
||||
return TOKEN
|
||||
|
||||
|
||||
def managesieve() -> str:
|
||||
script_name = f"{TOKEN[:48]} filter"
|
||||
renamed = f"{TOKEN[:48]} renamed"
|
||||
script = (
|
||||
'require ["fileinto"];\r\n'
|
||||
'if header :contains "Subject" "Bongo" { keep; }\r\n'
|
||||
).encode("utf-8")
|
||||
auth = base64.b64encode(
|
||||
b"\0" + USER1.encode("utf-8") + b"\0" + PASSWORD.encode("utf-8")
|
||||
).decode("ascii")
|
||||
client = ManageSieveClient()
|
||||
try:
|
||||
client.starttls()
|
||||
capabilities = client.command("CAPABILITY", "CAPABILITY")
|
||||
if not any('"SASL"' in line and "PLAIN" in line
|
||||
for line in capabilities):
|
||||
raise RuntimeError("SASL PLAIN not advertised after STARTTLS")
|
||||
client.command(
|
||||
"AUTHENTICATE",
|
||||
f'AUTHENTICATE "PLAIN" "{auth}"')
|
||||
client.literal_command(
|
||||
"CHECKSCRIPT", "CHECKSCRIPT", script)
|
||||
client.literal_command(
|
||||
"PUTSCRIPT", f"PUTSCRIPT {sieve_quote(script_name)}", script)
|
||||
listing = client.command("LISTSCRIPTS", "LISTSCRIPTS")
|
||||
if not any(sieve_quote(script_name) in line for line in listing):
|
||||
raise RuntimeError("uploaded script missing from LISTSCRIPTS")
|
||||
client.command(
|
||||
"SETACTIVE", f"SETACTIVE {sieve_quote(script_name)}")
|
||||
if client.get_script(script_name) != script:
|
||||
raise RuntimeError("GETSCRIPT returned different script bytes")
|
||||
client.command(
|
||||
"RENAMESCRIPT",
|
||||
f"RENAMESCRIPT {sieve_quote(script_name)} {sieve_quote(renamed)}")
|
||||
listing = client.command("LISTSCRIPTS", "LISTSCRIPTS")
|
||||
if not any(sieve_quote(renamed) in line and "ACTIVE" in line
|
||||
for line in listing):
|
||||
raise RuntimeError("renamed active script missing")
|
||||
client.command("SETACTIVE", 'SETACTIVE ""')
|
||||
client.command(
|
||||
"DELETESCRIPT", f"DELETESCRIPT {sieve_quote(renamed)}")
|
||||
client.command("LOGOUT", "LOGOUT")
|
||||
finally:
|
||||
client.close()
|
||||
return "upload, activate, retrieve, rename and delete passed"
|
||||
|
||||
|
||||
def smtp_implicit() -> str:
|
||||
with smtplib.SMTP_SSL(HOST, PORTS["smtps"], timeout=15,
|
||||
context=TLS) as client:
|
||||
@@ -188,6 +305,7 @@ def main() -> int:
|
||||
parser.error("BONGO_TEST_PASSWORD must be set")
|
||||
checks = [
|
||||
("listener readiness", wait_for_listeners),
|
||||
("ManageSieve NMAP Store", managesieve),
|
||||
("SMTP submission STARTTLS", smtp_submit),
|
||||
("SMTPS submission", smtp_implicit),
|
||||
("IMAPS delivery", wait_for_imap),
|
||||
|
||||
+3
-2
@@ -43,8 +43,9 @@ The implementation advertises `fileinto`, `envelope`, `body`, `variables`,
|
||||
`vacation`, and `vacation-seconds`. Actions include keep, discard, file into a
|
||||
folder, redirect, reject, and vacation response. Vacation history is durable
|
||||
so repeated delivery does not generate an immediate reply loop. Scripts and
|
||||
vacation state live in `/var/lib/bongo/dbf/sieve.sqlite`; include that database
|
||||
and its SQLite sidecars in backups.
|
||||
vacation state live in the owning user's authoritative Bongo Store and are
|
||||
accessed by `bongosieve` and `bongorules` through NMAP. Include the user's
|
||||
complete Store directory in backups.
|
||||
|
||||
## Filters in Webmail
|
||||
|
||||
|
||||
+5
-5
@@ -46,7 +46,6 @@ designs were present in the archived Bongo history.
|
||||
By contrast, standalone databases first added by the 0.7 development work
|
||||
must be moved behind `bongostore` and its NMAP interface:
|
||||
|
||||
- `/var/lib/bongo/dbf/sieve.sqlite`;
|
||||
- `/var/lib/bongo/dbf/external-accounts.sqlite`;
|
||||
|
||||
TLSRPT and the worker scheduler originally joined that list during
|
||||
@@ -54,14 +53,15 @@ development, but have already moved into the `_system` Store and no longer
|
||||
own separate databases. Because these paths existed only in unreleased 0.7
|
||||
development builds, no production migration is required.
|
||||
|
||||
Sieve scripts and vacation state belong to the Store of the user who owns
|
||||
them. External account definitions, encrypted credentials, destination
|
||||
Sieve scripts and vacation state now belong to the Store of the user who owns
|
||||
them; `bongosieve` and `bongorules` access them only through NMAP. External
|
||||
account definitions, encrypted credentials, destination
|
||||
folder, deletion policy, linked sending identity, remote UID state, and
|
||||
duplicate-detection state likewise belong to that user's Store. The `_system`
|
||||
Store holds only a credential-free due index containing the owner, account ID,
|
||||
and next collection time, so `bongocollector` need not scan every account.
|
||||
The two remaining standalone paths are transitional until their Store
|
||||
protocol migrations have landed.
|
||||
The remaining external-account standalone path is transitional until its
|
||||
Store protocol move has landed.
|
||||
|
||||
Web tasks, sender identities, and signatures are ordinary user Store
|
||||
documents below `/preferences`. Login sessions exist only in `bongoweb`
|
||||
|
||||
@@ -28,6 +28,8 @@
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
struct sqlite3;
|
||||
|
||||
/* Validate one complete UTF-8 RFC 5228 script without executing it. */
|
||||
int BongoSieveValidate(const char *script, size_t length);
|
||||
|
||||
@@ -62,7 +64,14 @@ int BongoSieveEvaluate(const char *script, size_t script_length,
|
||||
|
||||
typedef struct BongoSieveStore BongoSieveStore;
|
||||
|
||||
/* Open a temporary standalone per-user store, primarily for tests and tools. */
|
||||
int BongoSieveStoreOpen(BongoSieveStore **store, const char *path);
|
||||
/* Attach Sieve tables to a database owned and locked by bongostore. */
|
||||
int BongoSieveStoreAttach(BongoSieveStore **store, struct sqlite3 *database);
|
||||
/* Borrow an already initialized database owned and locked by bongostore. */
|
||||
int BongoSieveStoreBorrow(BongoSieveStore **store, struct sqlite3 *database);
|
||||
/* Select one user's authoritative Bongo Store through NMAP. */
|
||||
int BongoSieveStoreOpenNmap(BongoSieveStore **store, const char *user);
|
||||
void BongoSieveStoreClose(BongoSieveStore **store);
|
||||
int BongoSieveStorePut(BongoSieveStore *store, const char *user,
|
||||
const char *name, const char *script, size_t length);
|
||||
|
||||
+16
-20
@@ -40,6 +40,7 @@ RulesAgentGlobals RulesAgent = {0};
|
||||
|
||||
typedef struct {
|
||||
RulesClient *client;
|
||||
BongoSieveStore *store;
|
||||
const char *message;
|
||||
int result;
|
||||
BOOL written;
|
||||
@@ -162,13 +163,13 @@ SieveSendVacation(SieveExecution *execution, const BongoSieveResult *result)
|
||||
interval = result->has_seconds ? result->seconds : result->days * 86400UL;
|
||||
if (interval < 3600) interval = 3600;
|
||||
SieveVacationHandle(result, handle);
|
||||
if (!BongoSieveVacationClaim(RulesAgent.SieveStore, client->recipient,
|
||||
if (!BongoSieveVacationClaim(execution->store, client->recipient,
|
||||
safeSender, handle, interval)) { free(reason); return 1; }
|
||||
replySize = strlen(reason) + strlen(safeSender) + strlen(safeFrom) +
|
||||
strlen(subject) + 320;
|
||||
reply = malloc(replySize);
|
||||
if (!reply) {
|
||||
BongoSieveVacationRelease(RulesAgent.SieveStore, client->recipient,
|
||||
BongoSieveVacationRelease(execution->store, client->recipient,
|
||||
safeSender, handle);
|
||||
free(reason);
|
||||
return 0;
|
||||
@@ -183,7 +184,7 @@ SieveSendVacation(SieveExecution *execution, const BongoSieveResult *result)
|
||||
reason, result->mime ? "" : "\r\n");
|
||||
free(reason);
|
||||
if (code < 0 || (size_t)code >= replySize) {
|
||||
BongoSieveVacationRelease(RulesAgent.SieveStore, client->recipient,
|
||||
BongoSieveVacationRelease(execution->store, client->recipient,
|
||||
safeSender, handle);
|
||||
free(reply);
|
||||
return 0;
|
||||
@@ -203,7 +204,7 @@ SieveSendVacation(SieveExecution *execution, const BongoSieveResult *result)
|
||||
ConnWriteF(client->conn, "QABRT\r\n");
|
||||
ConnFlush(client->conn);
|
||||
NMAPReadAnswer(client->conn, client->line, CONN_BUFSIZE, FALSE);
|
||||
BongoSieveVacationRelease(RulesAgent.SieveStore, client->recipient,
|
||||
BongoSieveVacationRelease(execution->store, client->recipient,
|
||||
safeSender, handle);
|
||||
free(reply);
|
||||
return 0;
|
||||
@@ -364,15 +365,20 @@ SieveAction(const BongoSieveResult *action_result, void *data)
|
||||
static int
|
||||
ProcessSieve(RulesClient *client)
|
||||
{
|
||||
BongoSieveStore *store = NULL;
|
||||
char *script = NULL;
|
||||
char *message = NULL;
|
||||
size_t script_length = 0;
|
||||
int active = 0;
|
||||
long message_length;
|
||||
SieveExecution execution = { client, NULL, 0, FALSE };
|
||||
if (!RulesAgent.SieveStore ||
|
||||
!BongoSieveStoreGet(RulesAgent.SieveStore, client->recipient, NULL,
|
||||
&script, &script_length, &active) || !active) return 0;
|
||||
SieveExecution execution = { client, NULL, NULL, 0, FALSE };
|
||||
if (!BongoSieveStoreOpenNmap(&store, client->recipient)) goto failed;
|
||||
execution.store = store;
|
||||
if (!BongoSieveStoreGet(store, client->recipient, NULL,
|
||||
&script, &script_length, &active) || !active) {
|
||||
BongoSieveStoreClose(&store);
|
||||
return 0;
|
||||
}
|
||||
if (NMAPSendCommandF(client->conn, "QRETR %s MESSAGE\r\n", client->qID) == -1 ||
|
||||
NMAPReadAnswer(client->conn, client->line, CONN_BUFSIZE, TRUE) != 2023) goto failed;
|
||||
message_length = strtol(client->line, NULL, 10);
|
||||
@@ -390,12 +396,14 @@ ProcessSieve(RulesClient *client)
|
||||
}
|
||||
free(message);
|
||||
free(script);
|
||||
BongoSieveStoreClose(&store);
|
||||
return execution.result == -1 ? -1 : 1;
|
||||
failed:
|
||||
Log(LOG_ERROR, "Sieve processing failed for user %s queue %s; keeping message",
|
||||
client->recipient, client->qID);
|
||||
free(message);
|
||||
free(script);
|
||||
BongoSieveStoreClose(&store);
|
||||
return ConnWriteF(client->conn, "QMOD RAW %s\r\n", client->envelopeLine) == -1 ? -1 : 1;
|
||||
}
|
||||
|
||||
@@ -1677,22 +1685,10 @@ XplServiceMain(int argc, char *argv[])
|
||||
sslconfig.key.type = GNUTLS_X509_FMT_PEM;
|
||||
RulesAgent.agent.sslContext = ConnSSLContextAlloc(&sslconfig);
|
||||
|
||||
{
|
||||
char sieveDatabase[XPL_MAX_PATH + 1];
|
||||
snprintf(sieveDatabase, sizeof(sieveDatabase), "%s/sieve.sqlite",
|
||||
XPL_DEFAULT_DBF_DIR);
|
||||
if (!BongoSieveStoreOpen(&RulesAgent.SieveStore, sieveDatabase)) {
|
||||
XplConsolePrintf(AGENT_NAME ": Cannot open Sieve script store\r\n");
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
XplSignalHandler(SignalHandler);
|
||||
|
||||
/* Start the server thread */
|
||||
XplStartMainThread(AGENT_NAME, &id, RulesAgentServer, 8192, NULL, ccode);
|
||||
BongoSieveStoreClose(&RulesAgent.SieveStore);
|
||||
|
||||
XplUnloadApp(XplGetThreadID());
|
||||
|
||||
return 0;
|
||||
|
||||
@@ -116,7 +116,6 @@ typedef struct _RulesAgentGlobals {
|
||||
void *OutgoingPool;
|
||||
BongoThreadPool *OutgoingThreadPool;
|
||||
bongo_ssl_context *SSL_Context;
|
||||
BongoSieveStore *SieveStore;
|
||||
} RulesAgentGlobals;
|
||||
|
||||
extern RulesAgentGlobals RulesAgent;
|
||||
|
||||
@@ -36,7 +36,6 @@
|
||||
static BongoAgent agent;
|
||||
static Connection *listener;
|
||||
static bongo_ssl_context *tls_context;
|
||||
static BongoSieveStore *script_store;
|
||||
static int server_port = 4190;
|
||||
static int maximum_script_size = 1024 * 1024;
|
||||
static int maximum_connections = 64;
|
||||
@@ -182,6 +181,7 @@ static void serve(Connection *connection)
|
||||
{
|
||||
char line[LINE_SIZE + 1];
|
||||
char *user = NULL;
|
||||
BongoSieveStore *script_store = NULL;
|
||||
BongoSaslSession *sasl = NULL;
|
||||
int tls = 0;
|
||||
const char *mechanisms = NULL;
|
||||
@@ -223,8 +223,12 @@ static void serve(Connection *connection)
|
||||
!BongoSaslMechanisms(sasl, &mechanisms)) break;
|
||||
tls = 1;
|
||||
} else if (request.command == BONGO_MANAGESIEVE_AUTHENTICATE && tls && !user) {
|
||||
if (authenticate(connection, sasl, &request, &user)) write_ok(connection, NULL);
|
||||
else {
|
||||
if (authenticate(connection, sasl, &request, &user) &&
|
||||
BongoSieveStoreOpenNmap(&script_store, user)) {
|
||||
write_ok(connection, NULL);
|
||||
} else {
|
||||
free(user);
|
||||
user = NULL;
|
||||
Log(LOG_NOTICE,
|
||||
"Bongo authentication failed: service=sieve source=%s",
|
||||
LOGIP(connection->socketAddress));
|
||||
@@ -276,6 +280,7 @@ static void serve(Connection *connection)
|
||||
free(literal);
|
||||
if (ConnFlush(connection) == -1) break;
|
||||
}
|
||||
BongoSieveStoreClose(&script_store);
|
||||
free(user);
|
||||
BongoSaslSessionDestroy(&sasl);
|
||||
}
|
||||
@@ -368,7 +373,6 @@ XplServiceCode(signal_handler)
|
||||
int XplServiceMain(int argc, char **argv)
|
||||
{
|
||||
ConnSSLConfiguration tls = {0};
|
||||
char database[XPL_MAX_PATH + 1];
|
||||
int result;
|
||||
int wait;
|
||||
(void)argc; (void)argv; (void)GlobalConfig;
|
||||
@@ -387,8 +391,6 @@ int XplServiceMain(int argc, char **argv)
|
||||
(proxy_protocol_networks == NULL ||
|
||||
proxy_protocol_networks->len == 0U)) ||
|
||||
!BongoSaslInitialize()) return 1;
|
||||
snprintf(database, sizeof(database), "%s/sieve.sqlite", XPL_DEFAULT_DBF_DIR);
|
||||
if (!BongoSieveStoreOpen(&script_store, database)) return 1;
|
||||
tls.certificate.file = XPL_DEFAULT_CERT_PATH;
|
||||
tls.certificate.type = GNUTLS_X509_FMT_PEM;
|
||||
tls.key.file = XPL_DEFAULT_KEY_PATH;
|
||||
@@ -413,7 +415,6 @@ int XplServiceMain(int argc, char **argv)
|
||||
XplSafeRead(active_connections));
|
||||
return 1;
|
||||
}
|
||||
BongoSieveStoreClose(&script_store);
|
||||
ConnSSLContextFree(tls_context);
|
||||
BongoSaslShutdown();
|
||||
return 0;
|
||||
|
||||
@@ -32,6 +32,7 @@ add_executable(bongostore
|
||||
ringlog.c
|
||||
search.c
|
||||
scheduler-command.c
|
||||
sieve-command.c
|
||||
store.c
|
||||
stored.c
|
||||
tlsrpt-command.c
|
||||
@@ -47,6 +48,7 @@ target_link_libraries(bongostore
|
||||
bongocal
|
||||
bongotlsrpt
|
||||
bongoscheduler
|
||||
libbongosieve
|
||||
SQLite3::SQLite3
|
||||
GLib2::GLib2
|
||||
LibIcal::ical
|
||||
|
||||
@@ -354,22 +354,16 @@ CCode
|
||||
AccountRename(StoreClient *client, const char *old_username,
|
||||
const char *new_username)
|
||||
{
|
||||
static const AccountRenameColumn sieve_columns[] = {
|
||||
{ "sieve_scripts", "user" },
|
||||
{ "sieve_vacation_replies", "user" }
|
||||
};
|
||||
static const AccountRenameColumn external_columns[] = {
|
||||
{ "external_account_secrets", "owner" },
|
||||
{ "external_accounts", "owner" }
|
||||
};
|
||||
char old_store[XPL_MAX_PATH + 1];
|
||||
char new_store[XPL_MAX_PATH + 1];
|
||||
char sieve[XPL_MAX_PATH + 1];
|
||||
char external[XPL_MAX_PATH + 1];
|
||||
char cookies[XPL_MAX_PATH + 1];
|
||||
struct stat status;
|
||||
int moved_store = 0;
|
||||
int moved_sieve = 0;
|
||||
int moved_external = 0;
|
||||
AccountAliasChange *alias_changes = NULL;
|
||||
size_t alias_change_count = 0;
|
||||
@@ -383,8 +377,6 @@ AccountRename(StoreClient *client, const char *old_username,
|
||||
old_username) >= (int)sizeof(old_store) ||
|
||||
snprintf(new_store, sizeof(new_store), "%s/%s", StoreAgent.store.rootDir,
|
||||
new_username) >= (int)sizeof(new_store) ||
|
||||
snprintf(sieve, sizeof(sieve), "%s/sieve.sqlite",
|
||||
XPL_DEFAULT_DBF_DIR) >= (int)sizeof(sieve) ||
|
||||
snprintf(external, sizeof(external), "%s/external-accounts.sqlite",
|
||||
XPL_DEFAULT_DBF_DIR) >= (int)sizeof(external) ||
|
||||
snprintf(cookies, sizeof(cookies), "%s/cookies.db",
|
||||
@@ -405,11 +397,6 @@ AccountRename(StoreClient *client, const char *old_username,
|
||||
goto fail;
|
||||
if (stat(new_store, &status) == 0 || errno != ENOENT)
|
||||
goto fail;
|
||||
if (RenameDatabaseColumns(sieve, sieve_columns,
|
||||
sizeof(sieve_columns) / sizeof(sieve_columns[0]),
|
||||
old_username, new_username) != 0)
|
||||
goto fail;
|
||||
moved_sieve = 1;
|
||||
if (RenameDatabaseColumns(external, external_columns,
|
||||
sizeof(external_columns) /
|
||||
sizeof(external_columns[0]),
|
||||
@@ -445,10 +432,6 @@ rollback:
|
||||
sizeof(external_columns) /
|
||||
sizeof(external_columns[0]),
|
||||
new_username, old_username);
|
||||
if (moved_sieve)
|
||||
RenameDatabaseColumns(sieve, sieve_columns,
|
||||
sizeof(sieve_columns) / sizeof(sieve_columns[0]),
|
||||
new_username, old_username);
|
||||
fail:
|
||||
StoreEndUserMaintenance();
|
||||
FreeAliasChanges(alias_changes, alias_change_count);
|
||||
|
||||
@@ -35,6 +35,7 @@
|
||||
#include "calendar.h"
|
||||
#include "quota.h"
|
||||
#include "scheduler-command.h"
|
||||
#include "sieve-command.h"
|
||||
#include "tlsrpt-command.h"
|
||||
|
||||
#include "mail.h"
|
||||
@@ -184,6 +185,7 @@ StoreSetupCommands()
|
||||
BongoHashtablePutNoReplace(CommandTable, "NOOP", (void *) STORE_COMMAND_NOOP) ||
|
||||
BongoHashtablePutNoReplace(CommandTable, "QUOTA", (void *) STORE_COMMAND_QUOTA) ||
|
||||
BongoHashtablePutNoReplace(CommandTable, "SCHEDULER", (void *) STORE_COMMAND_SCHEDULER) ||
|
||||
BongoHashtablePutNoReplace(CommandTable, "SIEVE", (void *) STORE_COMMAND_SIEVE) ||
|
||||
BongoHashtablePutNoReplace(CommandTable, "TIMEOUT", (void *) STORE_COMMAND_TIMEOUT) ||
|
||||
BongoHashtablePutNoReplace(CommandTable, "TLSRPT", (void *) STORE_COMMAND_TLSRPT) ||
|
||||
|
||||
@@ -1272,6 +1274,10 @@ StoreCommandLoop(StoreClient *client)
|
||||
ccode = StoreCommandSCHEDULER(client, n, tokens);
|
||||
break;
|
||||
|
||||
case STORE_COMMAND_SIEVE:
|
||||
ccode = StoreCommandSIEVE(client, n, tokens);
|
||||
break;
|
||||
|
||||
case STORE_COMMAND_TLSRPT:
|
||||
ccode = StoreCommandTLSRPT(client, n, tokens);
|
||||
break;
|
||||
|
||||
@@ -98,6 +98,7 @@ typedef enum {
|
||||
STORE_COMMAND_NOOP,
|
||||
STORE_COMMAND_QUOTA,
|
||||
STORE_COMMAND_SCHEDULER,
|
||||
STORE_COMMAND_SIEVE,
|
||||
STORE_COMMAND_TIMEOUT,
|
||||
STORE_COMMAND_TLSRPT,
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#include <memmgr.h>
|
||||
#include <msgapi.h>
|
||||
#include <bongoscheduler.h>
|
||||
#include <bongosieve.h>
|
||||
#include <bongotlsrpt.h>
|
||||
|
||||
#include "stored.h"
|
||||
@@ -130,6 +131,19 @@ StoreDBOpen(StoreClient *client, const char *user)
|
||||
}
|
||||
BongoSchedulerClose(&scheduler);
|
||||
BongoTlsRptStoreClose(&tlsrpt);
|
||||
} else {
|
||||
BongoSieveStore *sieve = NULL;
|
||||
|
||||
if (!BongoSieveStoreAttach(&sieve, entry->handle->db)) {
|
||||
BongoSieveStoreClose(&sieve);
|
||||
MsgSQLClose(entry->handle);
|
||||
entry->handle = NULL;
|
||||
BongoHashtableRemove(StoreAgent.dbpool.entries,
|
||||
(void *)user);
|
||||
DBPoolEntryDelete(entry);
|
||||
goto db_fail;
|
||||
}
|
||||
BongoSieveStoreClose(&sieve);
|
||||
}
|
||||
}
|
||||
entry->clients++;
|
||||
|
||||
@@ -0,0 +1,401 @@
|
||||
/****************************************************************************
|
||||
* <Novell-copyright>
|
||||
* Copyright (c) 2001 Novell, Inc. All Rights Reserved.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of version 2 of the GNU General Public License
|
||||
* as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, contact Novell, Inc.
|
||||
* </Novell-copyright>
|
||||
****************************************************************************/
|
||||
// Parts Copyright (C) 2026 Mario Fetka. See COPYING for details.
|
||||
|
||||
#include <config.h>
|
||||
|
||||
#include "sieve-command.h"
|
||||
#include "command-parsing.h"
|
||||
#include "messages.h"
|
||||
|
||||
#include <bongosieve.h>
|
||||
#include <nmap.h>
|
||||
|
||||
#include <ctype.h>
|
||||
#include <errno.h>
|
||||
#include <inttypes.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#define SIEVE_WIRE_LIMIT (1024U * 1024U)
|
||||
|
||||
static BongoSieveStore *
|
||||
BorrowStore(StoreClient *client)
|
||||
{
|
||||
BongoSieveStore *store = NULL;
|
||||
|
||||
XplMutexLock(client->storedb->transactionLock);
|
||||
if (!BongoSieveStoreBorrow(&store, client->storedb->db)) {
|
||||
XplMutexUnlock(client->storedb->transactionLock);
|
||||
return NULL;
|
||||
}
|
||||
return store;
|
||||
}
|
||||
|
||||
static void
|
||||
ReturnStore(StoreClient *client, BongoSieveStore **store)
|
||||
{
|
||||
BongoSieveStoreClose(store);
|
||||
XplMutexUnlock(client->storedb->transactionLock);
|
||||
}
|
||||
|
||||
static CCode
|
||||
BadSyntax(StoreClient *client)
|
||||
{
|
||||
return ConnWriteStr(client->conn, MSG3022BADSYNTAX);
|
||||
}
|
||||
|
||||
static int
|
||||
HexValue(unsigned char value)
|
||||
{
|
||||
if (value >= '0' && value <= '9') return value - '0';
|
||||
value = (unsigned char)tolower(value);
|
||||
if (value >= 'a' && value <= 'f') return value - 'a' + 10;
|
||||
return -1;
|
||||
}
|
||||
|
||||
static char *
|
||||
HexDecode(const char *input, size_t maximum)
|
||||
{
|
||||
size_t length;
|
||||
size_t index;
|
||||
char *output;
|
||||
|
||||
if (!input || !(length = strlen(input)) || (length & 1U) ||
|
||||
length / 2U > maximum)
|
||||
return NULL;
|
||||
output = malloc(length / 2U + 1U);
|
||||
if (!output) return NULL;
|
||||
for (index = 0; index < length; index += 2U) {
|
||||
int high = HexValue((unsigned char)input[index]);
|
||||
int low = HexValue((unsigned char)input[index + 1U]);
|
||||
if (high < 0 || low < 0 || (high == 0 && low == 0)) {
|
||||
free(output);
|
||||
return NULL;
|
||||
}
|
||||
output[index / 2U] = (char)((high << 4) | low);
|
||||
}
|
||||
output[length / 2U] = '\0';
|
||||
return output;
|
||||
}
|
||||
|
||||
static char *
|
||||
HexEncode(const char *input)
|
||||
{
|
||||
static const char digits[] = "0123456789abcdef";
|
||||
size_t length;
|
||||
size_t index;
|
||||
char *output;
|
||||
|
||||
if (!input || !(length = strlen(input)) ||
|
||||
length > (SIZE_MAX - 1U) / 2U)
|
||||
return NULL;
|
||||
output = malloc(length * 2U + 1U);
|
||||
if (!output) return NULL;
|
||||
for (index = 0; index < length; index++) {
|
||||
unsigned char value = (unsigned char)input[index];
|
||||
output[index * 2U] = digits[value >> 4];
|
||||
output[index * 2U + 1U] = digits[value & 0x0f];
|
||||
}
|
||||
output[length * 2U] = '\0';
|
||||
return output;
|
||||
}
|
||||
|
||||
static int
|
||||
ParseSize(const char *text, size_t *value)
|
||||
{
|
||||
char *end = NULL;
|
||||
uintmax_t parsed;
|
||||
|
||||
if (!text || *text < '0' || *text > '9') return 0;
|
||||
errno = 0;
|
||||
parsed = strtoumax(text, &end, 10);
|
||||
if (errno == ERANGE || *end || parsed == 0 ||
|
||||
parsed > SIEVE_WIRE_LIMIT || parsed > SIZE_MAX)
|
||||
return 0;
|
||||
*value = (size_t)parsed;
|
||||
return 1;
|
||||
}
|
||||
|
||||
static char *
|
||||
ReceivePayload(StoreClient *client, size_t length)
|
||||
{
|
||||
char *payload = malloc(length + 1U);
|
||||
|
||||
if (!payload) {
|
||||
ConnWriteStr(client->conn, MSG5005DBLIBERR);
|
||||
return NULL;
|
||||
}
|
||||
if (ConnWriteStr(client->conn, "2002 Send Sieve script.\r\n") < 0 ||
|
||||
ConnFlush(client->conn) < 0 ||
|
||||
ConnReadCount(client->conn, payload, length) != (int)length) {
|
||||
free(payload);
|
||||
return NULL;
|
||||
}
|
||||
payload[length] = '\0';
|
||||
return payload;
|
||||
}
|
||||
|
||||
static CCode
|
||||
Put(StoreClient *client, const char *encoded_name, const char *length_text)
|
||||
{
|
||||
BongoSieveStore *store;
|
||||
char *name = HexDecode(encoded_name, 128U);
|
||||
char *script;
|
||||
size_t length;
|
||||
int result;
|
||||
|
||||
if (!name || !ParseSize(length_text, &length)) {
|
||||
free(name);
|
||||
return BadSyntax(client);
|
||||
}
|
||||
script = ReceivePayload(client, length);
|
||||
if (!script) {
|
||||
free(name);
|
||||
return -1;
|
||||
}
|
||||
store = BorrowStore(client);
|
||||
if (!store) {
|
||||
free(script);
|
||||
free(name);
|
||||
return ConnWriteStr(client->conn, MSG5005DBLIBERR);
|
||||
}
|
||||
result = BongoSieveStorePut(store, client->storeName, name, script, length);
|
||||
ReturnStore(client, &store);
|
||||
free(script);
|
||||
free(name);
|
||||
return ConnWriteF(client->conn, "1000 %d\r\n", result != 0);
|
||||
}
|
||||
|
||||
static CCode
|
||||
Get(StoreClient *client, const char *encoded_name)
|
||||
{
|
||||
BongoSieveStore *store;
|
||||
char *name = NULL;
|
||||
char *script = NULL;
|
||||
size_t length = 0;
|
||||
int active = 0;
|
||||
int result;
|
||||
CCode code;
|
||||
|
||||
if (strcmp(encoded_name, "ACTIVE") != 0 &&
|
||||
!(name = HexDecode(encoded_name, 128U)))
|
||||
return BadSyntax(client);
|
||||
store = BorrowStore(client);
|
||||
if (!store) {
|
||||
free(name);
|
||||
return ConnWriteStr(client->conn, MSG5005DBLIBERR);
|
||||
}
|
||||
result = BongoSieveStoreGet(store, client->storeName, name,
|
||||
&script, &length, &active);
|
||||
ReturnStore(client, &store);
|
||||
free(name);
|
||||
if (!result) return ConnWriteStr(client->conn, "1000 0\r\n");
|
||||
code = ConnWriteF(client->conn, "2002 %zu %d\r\n", length, active);
|
||||
if (code >= 0) code = ConnWrite(client->conn, script, length);
|
||||
if (code >= 0) code = ConnWriteStr(client->conn, "\r\n1000\r\n");
|
||||
free(script);
|
||||
return code;
|
||||
}
|
||||
|
||||
static CCode
|
||||
SetActive(StoreClient *client, const char *encoded_name)
|
||||
{
|
||||
BongoSieveStore *store;
|
||||
char *name = NULL;
|
||||
int result;
|
||||
|
||||
if (strcmp(encoded_name, "-") != 0 &&
|
||||
!(name = HexDecode(encoded_name, 128U)))
|
||||
return BadSyntax(client);
|
||||
store = BorrowStore(client);
|
||||
if (!store) {
|
||||
free(name);
|
||||
return ConnWriteStr(client->conn, MSG5005DBLIBERR);
|
||||
}
|
||||
result = BongoSieveStoreSetActive(store, client->storeName, name);
|
||||
ReturnStore(client, &store);
|
||||
free(name);
|
||||
return ConnWriteF(client->conn, "1000 %d\r\n", result != 0);
|
||||
}
|
||||
|
||||
static CCode
|
||||
Delete(StoreClient *client, const char *encoded_name)
|
||||
{
|
||||
BongoSieveStore *store;
|
||||
char *name = HexDecode(encoded_name, 128U);
|
||||
int result;
|
||||
|
||||
if (!name) return BadSyntax(client);
|
||||
store = BorrowStore(client);
|
||||
if (!store) {
|
||||
free(name);
|
||||
return ConnWriteStr(client->conn, MSG5005DBLIBERR);
|
||||
}
|
||||
result = BongoSieveStoreDelete(store, client->storeName, name);
|
||||
ReturnStore(client, &store);
|
||||
free(name);
|
||||
return ConnWriteF(client->conn, "1000 %d\r\n", result != 0);
|
||||
}
|
||||
|
||||
static CCode
|
||||
Rename(StoreClient *client, const char *old_encoded, const char *new_encoded)
|
||||
{
|
||||
BongoSieveStore *store;
|
||||
char *old_name = HexDecode(old_encoded, 128U);
|
||||
char *new_name = HexDecode(new_encoded, 128U);
|
||||
int result;
|
||||
|
||||
if (!old_name || !new_name) {
|
||||
free(old_name);
|
||||
free(new_name);
|
||||
return BadSyntax(client);
|
||||
}
|
||||
store = BorrowStore(client);
|
||||
if (!store) {
|
||||
free(old_name);
|
||||
free(new_name);
|
||||
return ConnWriteStr(client->conn, MSG5005DBLIBERR);
|
||||
}
|
||||
result = BongoSieveStoreRename(store, client->storeName,
|
||||
old_name, new_name);
|
||||
ReturnStore(client, &store);
|
||||
free(old_name);
|
||||
free(new_name);
|
||||
return ConnWriteF(client->conn, "1000 %d\r\n", result != 0);
|
||||
}
|
||||
|
||||
typedef struct {
|
||||
StoreClient *client;
|
||||
int ok;
|
||||
} ListContext;
|
||||
|
||||
static int
|
||||
WriteListEntry(const char *name, int active, void *data)
|
||||
{
|
||||
ListContext *context = data;
|
||||
char *encoded = HexEncode(name);
|
||||
|
||||
if (!encoded ||
|
||||
ConnWriteF(context->client->conn, "2001 %d %s\r\n",
|
||||
active != 0, encoded) < 0) {
|
||||
free(encoded);
|
||||
context->ok = 0;
|
||||
return 0;
|
||||
}
|
||||
free(encoded);
|
||||
return 1;
|
||||
}
|
||||
|
||||
static CCode
|
||||
List(StoreClient *client)
|
||||
{
|
||||
BongoSieveStore *store;
|
||||
ListContext context = { client, 1 };
|
||||
int result;
|
||||
|
||||
store = BorrowStore(client);
|
||||
if (!store) return ConnWriteStr(client->conn, MSG5005DBLIBERR);
|
||||
result = BongoSieveStoreList(store, client->storeName,
|
||||
WriteListEntry, &context);
|
||||
ReturnStore(client, &store);
|
||||
if (!result || !context.ok) return -1;
|
||||
return ConnWriteStr(client->conn, MSG1000OK);
|
||||
}
|
||||
|
||||
static CCode
|
||||
VacationClaim(StoreClient *client, const char *encoded_sender,
|
||||
const char *encoded_handle, const char *interval_text)
|
||||
{
|
||||
BongoSieveStore *store;
|
||||
char *sender = HexDecode(encoded_sender, 320U);
|
||||
char *handle = HexDecode(encoded_handle, 128U);
|
||||
unsigned long interval;
|
||||
int result;
|
||||
|
||||
if (!sender || !handle ||
|
||||
ParseUnsignedLong(client, (char *)interval_text, &interval) !=
|
||||
TOKEN_OK) {
|
||||
free(sender);
|
||||
free(handle);
|
||||
return BadSyntax(client);
|
||||
}
|
||||
store = BorrowStore(client);
|
||||
if (!store) {
|
||||
free(sender);
|
||||
free(handle);
|
||||
return ConnWriteStr(client->conn, MSG5005DBLIBERR);
|
||||
}
|
||||
result = BongoSieveVacationClaim(store, client->storeName, sender,
|
||||
handle, interval);
|
||||
ReturnStore(client, &store);
|
||||
free(sender);
|
||||
free(handle);
|
||||
return ConnWriteF(client->conn, "1000 %d\r\n", result != 0);
|
||||
}
|
||||
|
||||
static CCode
|
||||
VacationRelease(StoreClient *client, const char *encoded_sender,
|
||||
const char *encoded_handle)
|
||||
{
|
||||
BongoSieveStore *store;
|
||||
char *sender = HexDecode(encoded_sender, 320U);
|
||||
char *handle = HexDecode(encoded_handle, 128U);
|
||||
|
||||
if (!sender || !handle) {
|
||||
free(sender);
|
||||
free(handle);
|
||||
return BadSyntax(client);
|
||||
}
|
||||
store = BorrowStore(client);
|
||||
if (!store) {
|
||||
free(sender);
|
||||
free(handle);
|
||||
return ConnWriteStr(client->conn, MSG5005DBLIBERR);
|
||||
}
|
||||
BongoSieveVacationRelease(store, client->storeName, sender, handle);
|
||||
ReturnStore(client, &store);
|
||||
free(sender);
|
||||
free(handle);
|
||||
return ConnWriteStr(client->conn, MSG1000OK);
|
||||
}
|
||||
|
||||
CCode
|
||||
StoreCommandSIEVE(StoreClient *client, int token_count, char **tokens)
|
||||
{
|
||||
if (!IS_MANAGER(client) || !client->storedb || !client->storeName ||
|
||||
!strncmp(client->storeName, "_system", 7))
|
||||
return ConnWriteStr(client->conn, MSG3243MANAGERONLY);
|
||||
if (token_count == 4 && !XplStrCaseCmp(tokens[1], "put"))
|
||||
return Put(client, tokens[2], tokens[3]);
|
||||
if (token_count == 3 && !XplStrCaseCmp(tokens[1], "get"))
|
||||
return Get(client, tokens[2]);
|
||||
if (token_count == 3 && !XplStrCaseCmp(tokens[1], "active"))
|
||||
return SetActive(client, tokens[2]);
|
||||
if (token_count == 3 && !XplStrCaseCmp(tokens[1], "delete"))
|
||||
return Delete(client, tokens[2]);
|
||||
if (token_count == 4 && !XplStrCaseCmp(tokens[1], "rename"))
|
||||
return Rename(client, tokens[2], tokens[3]);
|
||||
if (token_count == 2 && !XplStrCaseCmp(tokens[1], "list"))
|
||||
return List(client);
|
||||
if (token_count == 5 && !XplStrCaseCmp(tokens[1], "vacation-claim"))
|
||||
return VacationClaim(client, tokens[2], tokens[3], tokens[4]);
|
||||
if (token_count == 4 && !XplStrCaseCmp(tokens[1], "vacation-release"))
|
||||
return VacationRelease(client, tokens[2], tokens[3]);
|
||||
return BadSyntax(client);
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
/****************************************************************************
|
||||
* <Novell-copyright>
|
||||
* Copyright (c) 2001 Novell, Inc. All Rights Reserved.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of version 2 of the GNU General Public License
|
||||
* as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, contact Novell, Inc.
|
||||
* </Novell-copyright>
|
||||
****************************************************************************/
|
||||
// Parts Copyright (C) 2026 Mario Fetka. See COPYING for details.
|
||||
|
||||
#ifndef BONGO_STORE_SIEVE_COMMAND_H
|
||||
#define BONGO_STORE_SIEVE_COMMAND_H
|
||||
|
||||
#include "stored.h"
|
||||
|
||||
CCode StoreCommandSIEVE(StoreClient *client, int token_count, char **tokens);
|
||||
|
||||
#endif
|
||||
@@ -1,8 +1,14 @@
|
||||
add_library(libbongosieve sieve.c store.c managesieve.c)
|
||||
StrictCompile()
|
||||
|
||||
add_library(libbongosieve sieve.c store.c store-nmap.c managesieve.c)
|
||||
set_target_properties(libbongosieve PROPERTIES OUTPUT_NAME bongosieve)
|
||||
target_compile_definitions(libbongosieve PRIVATE
|
||||
BONGO_SIEVE_VALIDATE_PATH="${LIBEXEC_INSTALL_DIR}/bongo/bongo-sieve-validate")
|
||||
target_link_libraries(libbongosieve PRIVATE Mailutils::Sieve SQLite3::SQLite3)
|
||||
target_link_libraries(libbongosieve PRIVATE
|
||||
Mailutils::Sieve
|
||||
SQLite3::SQLite3
|
||||
bongomsgapi
|
||||
bongoconnio)
|
||||
|
||||
install(TARGETS libbongosieve DESTINATION ${LIB_INSTALL_DIR})
|
||||
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
/****************************************************************************
|
||||
* <Novell-copyright>
|
||||
* Copyright (c) 2001 Novell, Inc. All Rights Reserved.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of version 2 of the GNU General Public License
|
||||
* as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, contact Novell, Inc.
|
||||
* </Novell-copyright>
|
||||
****************************************************************************/
|
||||
// Parts Copyright (C) 2026 Mario Fetka. See COPYING for details.
|
||||
|
||||
#ifndef BONGO_SIEVE_STORE_PRIVATE_H
|
||||
#define BONGO_SIEVE_STORE_PRIVATE_H
|
||||
|
||||
#include <bongosieve.h>
|
||||
#include <xpl.h>
|
||||
#include <connio.h>
|
||||
#include <sqlite3.h>
|
||||
|
||||
struct BongoSieveStore {
|
||||
sqlite3 *database;
|
||||
Connection *connection;
|
||||
char *user;
|
||||
int borrowed_database;
|
||||
};
|
||||
|
||||
int BongoSieveNmapPut(BongoSieveStore *store, const char *name,
|
||||
const char *script, size_t length);
|
||||
int BongoSieveNmapGet(BongoSieveStore *store, const char *name,
|
||||
char **script, size_t *length, int *active);
|
||||
int BongoSieveNmapSetActive(BongoSieveStore *store, const char *name);
|
||||
int BongoSieveNmapDelete(BongoSieveStore *store, const char *name);
|
||||
int BongoSieveNmapRename(BongoSieveStore *store, const char *old_name,
|
||||
const char *new_name);
|
||||
int BongoSieveNmapVacationClaim(BongoSieveStore *store, const char *sender,
|
||||
const char *handle,
|
||||
unsigned long interval_seconds);
|
||||
void BongoSieveNmapVacationRelease(BongoSieveStore *store,
|
||||
const char *sender, const char *handle);
|
||||
int BongoSieveNmapList(BongoSieveStore *store,
|
||||
BongoSieveListCallback callback, void *data);
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,340 @@
|
||||
/****************************************************************************
|
||||
* <Novell-copyright>
|
||||
* Copyright (c) 2001 Novell, Inc. All Rights Reserved.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of version 2 of the GNU General Public License
|
||||
* as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, contact Novell, Inc.
|
||||
* </Novell-copyright>
|
||||
****************************************************************************/
|
||||
// Parts Copyright (C) 2026 Mario Fetka. See COPYING for details.
|
||||
|
||||
#include <config.h>
|
||||
|
||||
#include <xpl.h>
|
||||
#include <nmlib.h>
|
||||
|
||||
#include <ctype.h>
|
||||
#include <errno.h>
|
||||
#include <inttypes.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "sieve-store-private.h"
|
||||
|
||||
#define SIEVE_WIRE_LIMIT (1024U * 1024U)
|
||||
|
||||
static int
|
||||
ValidUser(const char *user)
|
||||
{
|
||||
const unsigned char *cursor;
|
||||
size_t length;
|
||||
|
||||
if (!user || !(length = strlen(user)) || length > 320U) return 0;
|
||||
for (cursor = (const unsigned char *)user; *cursor; cursor++) {
|
||||
if (*cursor <= 0x20 || *cursor == 0x7f) return 0;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int
|
||||
HexValue(unsigned char value)
|
||||
{
|
||||
if (value >= '0' && value <= '9') return value - '0';
|
||||
value = (unsigned char)tolower(value);
|
||||
if (value >= 'a' && value <= 'f') return value - 'a' + 10;
|
||||
return -1;
|
||||
}
|
||||
|
||||
static char *
|
||||
HexEncode(const char *input)
|
||||
{
|
||||
static const char digits[] = "0123456789abcdef";
|
||||
size_t length;
|
||||
size_t index;
|
||||
char *output;
|
||||
|
||||
if (!input || !(length = strlen(input)) ||
|
||||
length > (SIZE_MAX - 1U) / 2U)
|
||||
return NULL;
|
||||
output = malloc(length * 2U + 1U);
|
||||
if (!output) return NULL;
|
||||
for (index = 0; index < length; index++) {
|
||||
unsigned char value = (unsigned char)input[index];
|
||||
output[index * 2U] = digits[value >> 4];
|
||||
output[index * 2U + 1U] = digits[value & 0x0f];
|
||||
}
|
||||
output[length * 2U] = '\0';
|
||||
return output;
|
||||
}
|
||||
|
||||
static char *
|
||||
HexDecode(const char *input, size_t maximum)
|
||||
{
|
||||
size_t length;
|
||||
size_t index;
|
||||
char *output;
|
||||
|
||||
if (!input || !(length = strlen(input)) || (length & 1U) ||
|
||||
length / 2U > maximum)
|
||||
return NULL;
|
||||
output = malloc(length / 2U + 1U);
|
||||
if (!output) return NULL;
|
||||
for (index = 0; index < length; index += 2U) {
|
||||
int high = HexValue((unsigned char)input[index]);
|
||||
int low = HexValue((unsigned char)input[index + 1U]);
|
||||
if (high < 0 || low < 0 || (high == 0 && low == 0)) {
|
||||
free(output);
|
||||
return NULL;
|
||||
}
|
||||
output[index / 2U] = (char)((high << 4) | low);
|
||||
}
|
||||
output[length / 2U] = '\0';
|
||||
return output;
|
||||
}
|
||||
|
||||
static int
|
||||
ParseResult(const char *response, int *result)
|
||||
{
|
||||
int count;
|
||||
|
||||
return response && result &&
|
||||
sscanf(response, "%d%n", result, &count) == 1 &&
|
||||
response[count] == '\0' && (*result == 0 || *result == 1);
|
||||
}
|
||||
|
||||
int
|
||||
BongoSieveStoreOpenNmap(BongoSieveStore **output, const char *user)
|
||||
{
|
||||
BongoSieveStore *store;
|
||||
char response[CONN_BUFSIZE + 1];
|
||||
|
||||
if (!output || !ValidUser(user)) return 0;
|
||||
*output = NULL;
|
||||
store = calloc(1, sizeof(*store));
|
||||
if (!store) return 0;
|
||||
store->user = strdup(user);
|
||||
store->connection = NMAPConnect("127.0.0.1", NULL);
|
||||
if (!store->user || !store->connection ||
|
||||
!ConnSetTimeout(store->connection, 30) ||
|
||||
!NMAPAuthenticateToStore(store->connection, response, CONN_BUFSIZE) ||
|
||||
NMAPRunCommandF(store->connection, response, sizeof(response),
|
||||
"STORE %s\r\n", user) != 1000) {
|
||||
BongoSieveStoreClose(&store);
|
||||
return 0;
|
||||
}
|
||||
*output = store;
|
||||
return 1;
|
||||
}
|
||||
|
||||
int
|
||||
BongoSieveNmapPut(BongoSieveStore *store, const char *name,
|
||||
const char *script, size_t length)
|
||||
{
|
||||
char response[CONN_BUFSIZE + 1];
|
||||
char *encoded = NULL;
|
||||
int result = 0;
|
||||
|
||||
if (!store || !store->connection || !script || !length ||
|
||||
length > SIEVE_WIRE_LIMIT || !(encoded = HexEncode(name)))
|
||||
return 0;
|
||||
if (NMAPSendCommandF(store->connection, "SIEVE PUT %s %zu\r\n",
|
||||
encoded, length) >= 0 &&
|
||||
NMAPReadAnswer(store->connection, response, sizeof(response), TRUE) ==
|
||||
2002 &&
|
||||
ConnWrite(store->connection, script, length) == (int)length &&
|
||||
ConnFlush(store->connection) >= 0 &&
|
||||
NMAPReadAnswer(store->connection, response, sizeof(response), TRUE) ==
|
||||
1000 &&
|
||||
ParseResult(response, &result)) {
|
||||
free(encoded);
|
||||
return result;
|
||||
}
|
||||
free(encoded);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
BongoSieveNmapGet(BongoSieveStore *store, const char *name,
|
||||
char **script, size_t *length, int *active)
|
||||
{
|
||||
char response[CONN_BUFSIZE + 1];
|
||||
char *encoded = NULL;
|
||||
char *end = NULL;
|
||||
uintmax_t parsed;
|
||||
int is_active;
|
||||
int count;
|
||||
int code;
|
||||
|
||||
if (!store || !store->connection || !script || !length ||
|
||||
(name && !(encoded = HexEncode(name))))
|
||||
return 0;
|
||||
if (NMAPSendCommandF(store->connection, "SIEVE GET %s\r\n",
|
||||
name ? encoded : "ACTIVE") < 0) {
|
||||
free(encoded);
|
||||
return 0;
|
||||
}
|
||||
free(encoded);
|
||||
code = NMAPReadAnswer(store->connection, response, sizeof(response), TRUE);
|
||||
if (code == 1000 && strcmp(response, "0") == 0) return 0;
|
||||
if (code != 2002 ||
|
||||
sscanf(response, "%ju %d%n", &parsed, &is_active, &count) != 2 ||
|
||||
response[count] != '\0' || parsed == 0 ||
|
||||
parsed > SIEVE_WIRE_LIMIT || parsed > SIZE_MAX ||
|
||||
(is_active != 0 && is_active != 1))
|
||||
return 0;
|
||||
errno = 0;
|
||||
(void)strtoumax(response, &end, 10);
|
||||
if (errno == ERANGE || end == response) return 0;
|
||||
*script = malloc((size_t)parsed + 1U);
|
||||
if (!*script) return 0;
|
||||
if (NMAPReadCount(store->connection, *script, (size_t)parsed) !=
|
||||
(int)parsed ||
|
||||
NMAPReadCrLf(store->connection) != 2 ||
|
||||
NMAPReadAnswer(store->connection, response, sizeof(response), TRUE) !=
|
||||
1000) {
|
||||
free(*script);
|
||||
*script = NULL;
|
||||
return 0;
|
||||
}
|
||||
(*script)[parsed] = '\0';
|
||||
*length = (size_t)parsed;
|
||||
if (active) *active = is_active;
|
||||
return 1;
|
||||
}
|
||||
|
||||
int
|
||||
BongoSieveNmapSetActive(BongoSieveStore *store, const char *name)
|
||||
{
|
||||
char response[CONN_BUFSIZE + 1];
|
||||
char *encoded = NULL;
|
||||
int result;
|
||||
int code;
|
||||
|
||||
if (!store || !store->connection || (name && *name &&
|
||||
!(encoded = HexEncode(name))))
|
||||
return 0;
|
||||
code = NMAPRunCommandF(store->connection, response, sizeof(response),
|
||||
"SIEVE ACTIVE %s\r\n",
|
||||
name && *name ? encoded : "-");
|
||||
free(encoded);
|
||||
return code == 1000 && ParseResult(response, &result) && result;
|
||||
}
|
||||
|
||||
int
|
||||
BongoSieveNmapDelete(BongoSieveStore *store, const char *name)
|
||||
{
|
||||
char response[CONN_BUFSIZE + 1];
|
||||
char *encoded = HexEncode(name);
|
||||
int result;
|
||||
int code;
|
||||
|
||||
if (!store || !store->connection || !encoded) return 0;
|
||||
code = NMAPRunCommandF(store->connection, response, sizeof(response),
|
||||
"SIEVE DELETE %s\r\n", encoded);
|
||||
free(encoded);
|
||||
return code == 1000 && ParseResult(response, &result) && result;
|
||||
}
|
||||
|
||||
int
|
||||
BongoSieveNmapRename(BongoSieveStore *store, const char *old_name,
|
||||
const char *new_name)
|
||||
{
|
||||
char response[CONN_BUFSIZE + 1];
|
||||
char *old_encoded = HexEncode(old_name);
|
||||
char *new_encoded = HexEncode(new_name);
|
||||
int result;
|
||||
int code;
|
||||
|
||||
if (!store || !store->connection || !old_encoded || !new_encoded) {
|
||||
free(old_encoded);
|
||||
free(new_encoded);
|
||||
return 0;
|
||||
}
|
||||
code = NMAPRunCommandF(store->connection, response, sizeof(response),
|
||||
"SIEVE RENAME %s %s\r\n",
|
||||
old_encoded, new_encoded);
|
||||
free(old_encoded);
|
||||
free(new_encoded);
|
||||
return code == 1000 && ParseResult(response, &result) && result;
|
||||
}
|
||||
|
||||
int
|
||||
BongoSieveNmapVacationClaim(BongoSieveStore *store, const char *sender,
|
||||
const char *handle,
|
||||
unsigned long interval_seconds)
|
||||
{
|
||||
char response[CONN_BUFSIZE + 1];
|
||||
char *sender_encoded = HexEncode(sender);
|
||||
char *handle_encoded = HexEncode(handle);
|
||||
int result;
|
||||
int code;
|
||||
|
||||
if (!store || !store->connection || !sender_encoded || !handle_encoded) {
|
||||
free(sender_encoded);
|
||||
free(handle_encoded);
|
||||
return 0;
|
||||
}
|
||||
code = NMAPRunCommandF(store->connection, response, sizeof(response),
|
||||
"SIEVE VACATION-CLAIM %s %s %lu\r\n",
|
||||
sender_encoded, handle_encoded, interval_seconds);
|
||||
free(sender_encoded);
|
||||
free(handle_encoded);
|
||||
return code == 1000 && ParseResult(response, &result) && result;
|
||||
}
|
||||
|
||||
void
|
||||
BongoSieveNmapVacationRelease(BongoSieveStore *store, const char *sender,
|
||||
const char *handle)
|
||||
{
|
||||
char response[CONN_BUFSIZE + 1];
|
||||
char *sender_encoded = HexEncode(sender);
|
||||
char *handle_encoded = HexEncode(handle);
|
||||
|
||||
if (store && store->connection && sender_encoded && handle_encoded) {
|
||||
(void)NMAPRunCommandF(store->connection, response, sizeof(response),
|
||||
"SIEVE VACATION-RELEASE %s %s\r\n",
|
||||
sender_encoded, handle_encoded);
|
||||
}
|
||||
free(sender_encoded);
|
||||
free(handle_encoded);
|
||||
}
|
||||
|
||||
int
|
||||
BongoSieveNmapList(BongoSieveStore *store,
|
||||
BongoSieveListCallback callback, void *data)
|
||||
{
|
||||
char response[CONN_BUFSIZE + 1];
|
||||
int code;
|
||||
|
||||
if (!store || !store->connection || !callback ||
|
||||
NMAPSendCommandF(store->connection, "SIEVE LIST\r\n") < 0)
|
||||
return 0;
|
||||
while ((code = NMAPReadAnswer(store->connection, response,
|
||||
sizeof(response), TRUE)) == 2001) {
|
||||
char encoded[257];
|
||||
char *name;
|
||||
int active;
|
||||
int count;
|
||||
|
||||
if (sscanf(response, "%d %256s%n", &active, encoded, &count) != 2 ||
|
||||
response[count] != '\0' || (active != 0 && active != 1) ||
|
||||
!(name = HexDecode(encoded, 128U)))
|
||||
return 0;
|
||||
if (!callback(name, active, data)) {
|
||||
free(name);
|
||||
return 0;
|
||||
}
|
||||
free(name);
|
||||
}
|
||||
return code == 1000;
|
||||
}
|
||||
+124
-62
@@ -19,14 +19,26 @@
|
||||
* </Novell-copyright>
|
||||
****************************************************************************/
|
||||
|
||||
#include <config.h>
|
||||
|
||||
#include <bongosieve.h>
|
||||
#include <sqlite3.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
struct BongoSieveStore {
|
||||
sqlite3 *database;
|
||||
};
|
||||
#include "sieve-store-private.h"
|
||||
|
||||
static const char SieveSchema[] =
|
||||
"PRAGMA foreign_keys=ON;"
|
||||
"CREATE TABLE IF NOT EXISTS sieve_scripts ("
|
||||
" name TEXT PRIMARY KEY, script BLOB NOT NULL,"
|
||||
" active INTEGER NOT NULL DEFAULT 0 CHECK(active IN (0,1)),"
|
||||
" revision INTEGER NOT NULL DEFAULT 1, updated_at INTEGER NOT NULL);"
|
||||
"CREATE UNIQUE INDEX IF NOT EXISTS sieve_one_active "
|
||||
" ON sieve_scripts(active) WHERE active=1;"
|
||||
"CREATE TABLE IF NOT EXISTS sieve_vacation_replies ("
|
||||
" sender TEXT NOT NULL, handle TEXT NOT NULL,"
|
||||
" replied_at INTEGER NOT NULL, PRIMARY KEY(sender,handle));";
|
||||
|
||||
static int
|
||||
valid_identity(const char *value, size_t maximum, int script_name)
|
||||
@@ -42,31 +54,33 @@ valid_identity(const char *value, size_t maximum, int script_name)
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int
|
||||
valid_store_user(BongoSieveStore *store, const char *user)
|
||||
{
|
||||
if (!store || !valid_identity(user, 320, 0)) return 0;
|
||||
if (!store->user) {
|
||||
store->user = strdup(user);
|
||||
if (!store->user) return 0;
|
||||
}
|
||||
return strcmp(store->user, user) == 0;
|
||||
}
|
||||
|
||||
int
|
||||
BongoSieveStoreOpen(BongoSieveStore **output, const char *path)
|
||||
{
|
||||
BongoSieveStore *store;
|
||||
static const char schema[] =
|
||||
"PRAGMA journal_mode=WAL;"
|
||||
"PRAGMA foreign_keys=ON;"
|
||||
"CREATE TABLE IF NOT EXISTS sieve_scripts ("
|
||||
" user TEXT NOT NULL, name TEXT NOT NULL, script BLOB NOT NULL,"
|
||||
" active INTEGER NOT NULL DEFAULT 0 CHECK(active IN (0,1)),"
|
||||
" revision INTEGER NOT NULL DEFAULT 1, updated_at INTEGER NOT NULL,"
|
||||
" PRIMARY KEY(user,name));"
|
||||
"CREATE UNIQUE INDEX IF NOT EXISTS sieve_one_active "
|
||||
" ON sieve_scripts(user) WHERE active=1;"
|
||||
"CREATE TABLE IF NOT EXISTS sieve_vacation_replies ("
|
||||
" user TEXT NOT NULL, sender TEXT NOT NULL, handle TEXT NOT NULL,"
|
||||
" replied_at INTEGER NOT NULL, PRIMARY KEY(user,sender,handle));";
|
||||
if (!output || !path || !*path) return 0;
|
||||
*output = NULL;
|
||||
store = calloc(1, sizeof(*store));
|
||||
if (!store) return 0;
|
||||
if (sqlite3_open_v2(path, &store->database,
|
||||
SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE, NULL) != SQLITE_OK ||
|
||||
SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE |
|
||||
SQLITE_OPEN_FULLMUTEX, NULL) != SQLITE_OK ||
|
||||
sqlite3_busy_timeout(store->database, 5000) != SQLITE_OK ||
|
||||
sqlite3_exec(store->database, schema, NULL, NULL, NULL) != SQLITE_OK) {
|
||||
sqlite3_exec(store->database, "PRAGMA journal_mode=WAL;",
|
||||
NULL, NULL, NULL) != SQLITE_OK ||
|
||||
sqlite3_exec(store->database, SieveSchema, NULL, NULL, NULL) !=
|
||||
SQLITE_OK) {
|
||||
BongoSieveStoreClose(&store);
|
||||
return 0;
|
||||
}
|
||||
@@ -74,11 +88,51 @@ BongoSieveStoreOpen(BongoSieveStore **output, const char *path)
|
||||
return 1;
|
||||
}
|
||||
|
||||
int
|
||||
BongoSieveStoreAttach(BongoSieveStore **output, sqlite3 *database)
|
||||
{
|
||||
BongoSieveStore *store;
|
||||
|
||||
if (!output || !database) return 0;
|
||||
*output = NULL;
|
||||
store = calloc(1, sizeof(*store));
|
||||
if (!store) return 0;
|
||||
store->database = database;
|
||||
store->borrowed_database = 1;
|
||||
if (sqlite3_exec(database, SieveSchema, NULL, NULL, NULL) != SQLITE_OK) {
|
||||
BongoSieveStoreClose(&store);
|
||||
return 0;
|
||||
}
|
||||
*output = store;
|
||||
return 1;
|
||||
}
|
||||
|
||||
int
|
||||
BongoSieveStoreBorrow(BongoSieveStore **output, sqlite3 *database)
|
||||
{
|
||||
BongoSieveStore *store;
|
||||
|
||||
if (!output || !database) return 0;
|
||||
*output = NULL;
|
||||
store = calloc(1, sizeof(*store));
|
||||
if (!store) return 0;
|
||||
store->database = database;
|
||||
store->borrowed_database = 1;
|
||||
*output = store;
|
||||
return 1;
|
||||
}
|
||||
|
||||
void
|
||||
BongoSieveStoreClose(BongoSieveStore **store)
|
||||
{
|
||||
if (!store || !*store) return;
|
||||
if ((*store)->database) sqlite3_close((*store)->database);
|
||||
if ((*store)->connection) {
|
||||
ConnClose((*store)->connection);
|
||||
ConnFree((*store)->connection);
|
||||
}
|
||||
if ((*store)->database && !(*store)->borrowed_database)
|
||||
sqlite3_close((*store)->database);
|
||||
free((*store)->user);
|
||||
free(*store);
|
||||
*store = NULL;
|
||||
}
|
||||
@@ -90,15 +144,16 @@ BongoSieveStorePut(BongoSieveStore *store, const char *user,
|
||||
sqlite3_stmt *statement = NULL;
|
||||
int result = 0;
|
||||
static const char sql[] =
|
||||
"INSERT INTO sieve_scripts(user,name,script,updated_at) "
|
||||
"VALUES(?,?,?,unixepoch()) ON CONFLICT(user,name) DO UPDATE SET "
|
||||
"INSERT INTO sieve_scripts(name,script,updated_at) "
|
||||
"VALUES(?,?,unixepoch()) ON CONFLICT(name) DO UPDATE SET "
|
||||
"script=excluded.script, revision=revision+1, updated_at=unixepoch()";
|
||||
if (!store || !valid_identity(user, 320, 0) ||
|
||||
if (!valid_store_user(store, user) ||
|
||||
!valid_identity(name, 128, 1) || !BongoSieveValidate(script, length)) return 0;
|
||||
if (store->connection)
|
||||
return BongoSieveNmapPut(store, name, script, length);
|
||||
if (sqlite3_prepare_v2(store->database, sql, -1, &statement, NULL) == SQLITE_OK &&
|
||||
sqlite3_bind_text(statement, 1, user, -1, SQLITE_TRANSIENT) == SQLITE_OK &&
|
||||
sqlite3_bind_text(statement, 2, name, -1, SQLITE_TRANSIENT) == SQLITE_OK &&
|
||||
sqlite3_bind_blob(statement, 3, script, (int)length, SQLITE_TRANSIENT) == SQLITE_OK &&
|
||||
sqlite3_bind_text(statement, 1, name, -1, SQLITE_TRANSIENT) == SQLITE_OK &&
|
||||
sqlite3_bind_blob(statement, 2, script, (int)length, SQLITE_TRANSIENT) == SQLITE_OK &&
|
||||
sqlite3_step(statement) == SQLITE_DONE) result = 1;
|
||||
sqlite3_finalize(statement);
|
||||
return result;
|
||||
@@ -113,15 +168,17 @@ BongoSieveStoreGet(BongoSieveStore *store, const char *user,
|
||||
int bytes;
|
||||
int result = 0;
|
||||
const char *sql = name ?
|
||||
"SELECT script,active FROM sieve_scripts WHERE user=? AND name=?" :
|
||||
"SELECT script,active FROM sieve_scripts WHERE user=? AND active=1";
|
||||
"SELECT script,active FROM sieve_scripts WHERE name=?" :
|
||||
"SELECT script,active FROM sieve_scripts WHERE active=1";
|
||||
if (!store || !script || !length || !valid_identity(user, 320, 0) ||
|
||||
(name && !valid_identity(name, 128, 1))) return 0;
|
||||
if (!valid_store_user(store, user)) return 0;
|
||||
*script = NULL;
|
||||
*length = 0;
|
||||
if (store->connection)
|
||||
return BongoSieveNmapGet(store, name, script, length, active);
|
||||
if (sqlite3_prepare_v2(store->database, sql, -1, &statement, NULL) != SQLITE_OK ||
|
||||
sqlite3_bind_text(statement, 1, user, -1, SQLITE_TRANSIENT) != SQLITE_OK ||
|
||||
(name && sqlite3_bind_text(statement, 2, name, -1, SQLITE_TRANSIENT) != SQLITE_OK) ||
|
||||
(name && sqlite3_bind_text(statement, 1, name, -1, SQLITE_TRANSIENT) != SQLITE_OK) ||
|
||||
sqlite3_step(statement) != SQLITE_ROW) goto finish;
|
||||
data = sqlite3_column_blob(statement, 0);
|
||||
bytes = sqlite3_column_bytes(statement, 0);
|
||||
@@ -142,23 +199,22 @@ BongoSieveStoreSetActive(BongoSieveStore *store, const char *user, const char *n
|
||||
{
|
||||
sqlite3_stmt *statement = NULL;
|
||||
int result = 0;
|
||||
if (!store || !valid_identity(user, 320, 0) ||
|
||||
if (!valid_store_user(store, user) ||
|
||||
(name && *name && !valid_identity(name, 128, 1))) return 0;
|
||||
if (store->connection) return BongoSieveNmapSetActive(store, name);
|
||||
if (sqlite3_exec(store->database, "BEGIN IMMEDIATE", NULL, NULL, NULL) != SQLITE_OK) return 0;
|
||||
if (sqlite3_prepare_v2(store->database,
|
||||
"UPDATE sieve_scripts SET active=0 WHERE user=?", -1,
|
||||
"UPDATE sieve_scripts SET active=0", -1,
|
||||
&statement, NULL) == SQLITE_OK &&
|
||||
sqlite3_bind_text(statement, 1, user, -1, SQLITE_TRANSIENT) == SQLITE_OK &&
|
||||
sqlite3_step(statement) == SQLITE_DONE) result = 1;
|
||||
sqlite3_finalize(statement);
|
||||
statement = NULL;
|
||||
if (result && name && *name) {
|
||||
result = 0;
|
||||
if (sqlite3_prepare_v2(store->database,
|
||||
"UPDATE sieve_scripts SET active=1 WHERE user=? AND name=?",
|
||||
"UPDATE sieve_scripts SET active=1 WHERE name=?",
|
||||
-1, &statement, NULL) == SQLITE_OK &&
|
||||
sqlite3_bind_text(statement, 1, user, -1, SQLITE_TRANSIENT) == SQLITE_OK &&
|
||||
sqlite3_bind_text(statement, 2, name, -1, SQLITE_TRANSIENT) == SQLITE_OK &&
|
||||
sqlite3_bind_text(statement, 1, name, -1, SQLITE_TRANSIENT) == SQLITE_OK &&
|
||||
sqlite3_step(statement) == SQLITE_DONE &&
|
||||
sqlite3_changes(store->database) == 1) result = 1;
|
||||
sqlite3_finalize(statement);
|
||||
@@ -172,12 +228,12 @@ BongoSieveStoreDelete(BongoSieveStore *store, const char *user, const char *name
|
||||
{
|
||||
sqlite3_stmt *statement = NULL;
|
||||
int result = 0;
|
||||
if (!store || !valid_identity(user, 320, 0) || !valid_identity(name, 128, 1)) return 0;
|
||||
if (!valid_store_user(store, user) || !valid_identity(name, 128, 1)) return 0;
|
||||
if (store->connection) return BongoSieveNmapDelete(store, name);
|
||||
if (sqlite3_prepare_v2(store->database,
|
||||
"DELETE FROM sieve_scripts WHERE user=? AND name=? AND active=0",
|
||||
"DELETE FROM sieve_scripts WHERE name=? AND active=0",
|
||||
-1, &statement, NULL) == SQLITE_OK &&
|
||||
sqlite3_bind_text(statement, 1, user, -1, SQLITE_TRANSIENT) == SQLITE_OK &&
|
||||
sqlite3_bind_text(statement, 2, name, -1, SQLITE_TRANSIENT) == SQLITE_OK &&
|
||||
sqlite3_bind_text(statement, 1, name, -1, SQLITE_TRANSIENT) == SQLITE_OK &&
|
||||
sqlite3_step(statement) == SQLITE_DONE && sqlite3_changes(store->database) == 1) result = 1;
|
||||
sqlite3_finalize(statement);
|
||||
return result;
|
||||
@@ -189,19 +245,19 @@ BongoSieveStoreRename(BongoSieveStore *store, const char *user,
|
||||
{
|
||||
sqlite3_stmt *statement = NULL;
|
||||
int result = 0;
|
||||
if (!store || !valid_identity(user, 320, 0) ||
|
||||
if (!valid_store_user(store, user) ||
|
||||
!valid_identity(old_name, 128, 1) || !valid_identity(new_name, 128, 1) ||
|
||||
strcmp(old_name, new_name) == 0) return 0;
|
||||
if (store->connection)
|
||||
return BongoSieveNmapRename(store, old_name, new_name);
|
||||
if (sqlite3_prepare_v2(store->database,
|
||||
"UPDATE sieve_scripts SET name=?, revision=revision+1, "
|
||||
"updated_at=unixepoch() WHERE user=? AND name=? AND NOT EXISTS "
|
||||
"(SELECT 1 FROM sieve_scripts WHERE user=? AND name=?)",
|
||||
"updated_at=unixepoch() WHERE name=? AND NOT EXISTS "
|
||||
"(SELECT 1 FROM sieve_scripts WHERE name=?)",
|
||||
-1, &statement, NULL) == SQLITE_OK &&
|
||||
sqlite3_bind_text(statement, 1, new_name, -1, SQLITE_TRANSIENT) == SQLITE_OK &&
|
||||
sqlite3_bind_text(statement, 2, user, -1, SQLITE_TRANSIENT) == SQLITE_OK &&
|
||||
sqlite3_bind_text(statement, 3, old_name, -1, SQLITE_TRANSIENT) == SQLITE_OK &&
|
||||
sqlite3_bind_text(statement, 4, user, -1, SQLITE_TRANSIENT) == SQLITE_OK &&
|
||||
sqlite3_bind_text(statement, 5, new_name, -1, SQLITE_TRANSIENT) == SQLITE_OK &&
|
||||
sqlite3_bind_text(statement, 2, old_name, -1, SQLITE_TRANSIENT) == SQLITE_OK &&
|
||||
sqlite3_bind_text(statement, 3, new_name, -1, SQLITE_TRANSIENT) == SQLITE_OK &&
|
||||
sqlite3_step(statement) == SQLITE_DONE && sqlite3_changes(store->database) == 1) {
|
||||
result = 1;
|
||||
}
|
||||
@@ -217,17 +273,19 @@ BongoSieveVacationClaim(BongoSieveStore *store, const char *user,
|
||||
sqlite3_stmt *statement = NULL;
|
||||
int result = 0;
|
||||
static const char sql[] =
|
||||
"INSERT INTO sieve_vacation_replies(user,sender,handle,replied_at) "
|
||||
"VALUES(?,?,?,unixepoch()) ON CONFLICT(user,sender,handle) DO UPDATE SET "
|
||||
"INSERT INTO sieve_vacation_replies(sender,handle,replied_at) "
|
||||
"VALUES(?,?,unixepoch()) ON CONFLICT(sender,handle) DO UPDATE SET "
|
||||
"replied_at=unixepoch() WHERE replied_at <= unixepoch()-?";
|
||||
if (!store || !valid_identity(user, 320, 0) ||
|
||||
if (!valid_store_user(store, user) ||
|
||||
!valid_identity(sender, 320, 0) || !valid_identity(handle, 128, 1) ||
|
||||
interval_seconds > 366UL * 86400UL) return 0;
|
||||
if (store->connection)
|
||||
return BongoSieveNmapVacationClaim(store, sender, handle,
|
||||
interval_seconds);
|
||||
if (sqlite3_prepare_v2(store->database, sql, -1, &statement, NULL) == SQLITE_OK &&
|
||||
sqlite3_bind_text(statement, 1, user, -1, SQLITE_TRANSIENT) == SQLITE_OK &&
|
||||
sqlite3_bind_text(statement, 2, sender, -1, SQLITE_TRANSIENT) == SQLITE_OK &&
|
||||
sqlite3_bind_text(statement, 3, handle, -1, SQLITE_TRANSIENT) == SQLITE_OK &&
|
||||
sqlite3_bind_int64(statement, 4, (sqlite3_int64)interval_seconds) == SQLITE_OK &&
|
||||
sqlite3_bind_text(statement, 1, sender, -1, SQLITE_TRANSIENT) == SQLITE_OK &&
|
||||
sqlite3_bind_text(statement, 2, handle, -1, SQLITE_TRANSIENT) == SQLITE_OK &&
|
||||
sqlite3_bind_int64(statement, 3, (sqlite3_int64)interval_seconds) == SQLITE_OK &&
|
||||
sqlite3_step(statement) == SQLITE_DONE && sqlite3_changes(store->database) == 1) {
|
||||
result = 1;
|
||||
}
|
||||
@@ -240,13 +298,16 @@ BongoSieveVacationRelease(BongoSieveStore *store, const char *user,
|
||||
const char *sender, const char *handle)
|
||||
{
|
||||
sqlite3_stmt *statement = NULL;
|
||||
if (!store || !user || !sender || !handle) return;
|
||||
if (!valid_store_user(store, user) || !sender || !handle) return;
|
||||
if (store->connection) {
|
||||
BongoSieveNmapVacationRelease(store, sender, handle);
|
||||
return;
|
||||
}
|
||||
if (sqlite3_prepare_v2(store->database,
|
||||
"DELETE FROM sieve_vacation_replies WHERE user=? AND sender=? AND handle=?",
|
||||
"DELETE FROM sieve_vacation_replies WHERE sender=? AND handle=?",
|
||||
-1, &statement, NULL) == SQLITE_OK) {
|
||||
sqlite3_bind_text(statement, 1, user, -1, SQLITE_TRANSIENT);
|
||||
sqlite3_bind_text(statement, 2, sender, -1, SQLITE_TRANSIENT);
|
||||
sqlite3_bind_text(statement, 3, handle, -1, SQLITE_TRANSIENT);
|
||||
sqlite3_bind_text(statement, 1, sender, -1, SQLITE_TRANSIENT);
|
||||
sqlite3_bind_text(statement, 2, handle, -1, SQLITE_TRANSIENT);
|
||||
sqlite3_step(statement);
|
||||
}
|
||||
sqlite3_finalize(statement);
|
||||
@@ -258,11 +319,12 @@ BongoSieveStoreList(BongoSieveStore *store, const char *user,
|
||||
{
|
||||
sqlite3_stmt *statement = NULL;
|
||||
int result = 0;
|
||||
if (!store || !callback || !valid_identity(user, 320, 0)) return 0;
|
||||
if (!callback || !valid_store_user(store, user)) return 0;
|
||||
if (store->connection)
|
||||
return BongoSieveNmapList(store, callback, data);
|
||||
if (sqlite3_prepare_v2(store->database,
|
||||
"SELECT name,active FROM sieve_scripts WHERE user=? ORDER BY name",
|
||||
-1, &statement, NULL) != SQLITE_OK ||
|
||||
sqlite3_bind_text(statement, 1, user, -1, SQLITE_TRANSIENT) != SQLITE_OK) {
|
||||
"SELECT name,active FROM sieve_scripts ORDER BY name",
|
||||
-1, &statement, NULL) != SQLITE_OK) {
|
||||
sqlite3_finalize(statement);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -40,6 +40,8 @@ static int store_test(void)
|
||||
!BongoSieveStoreSetActive(store, "alice@example.test", "main") ||
|
||||
!BongoSieveStoreGet(store, "alice@example.test", NULL, &loaded, &length, &active) ||
|
||||
length != strlen(script) || memcmp(loaded, script, length) || !active ||
|
||||
BongoSieveStoreGet(store, "bob@example.test", NULL,
|
||||
&loaded, &length, &active) ||
|
||||
BongoSieveStoreSetActive(store, "alice@example.test", "missing") ||
|
||||
BongoSieveStoreDelete(store, "alice@example.test", "main")) {
|
||||
free(loaded);
|
||||
|
||||
Reference in New Issue
Block a user