*/
#define CITADEL PACKAGE_STRING
-#define REV_LEVEL 929 // This version
+#define REV_LEVEL 930 // This version
#define REV_MIN 591 // Oldest compatible database
#define EXPORT_REV_MIN 760 // Oldest compatible export files
#define LIBCITADEL_MIN 922 // Minimum required version of libcitadel
long msgnum_bio; // msgnum of user's profile (bio)
long msgnum_pic; // msgnum of user's avatar (photo)
char emailaddrs[512]; // Internet email addresses
+ long msgnum_inboxrules; // msgnum of user's inbox filtering rules
};
char c_journal_dest[128];
char c_default_cal_zone[128];
int c_pftcpdict_port;
- int c_managesieve_port;
+ int c_niu_9;
int c_auth_mode;
char c_niu_8[256];
- int c_niu_9;
- char c_niu_10[256];
+ int c_niu_10;
char c_niu_11[256];
+ char c_niu_12[256];
char c_rbl_at_greeting;
char c_master_user[32];
char c_master_pass[32];
)
-dnl Checks for the libsieve mailbox sorting library.
-AC_CHECK_HEADER(sieve2.h,
- [AC_CHECK_LIB(sieve, sieve2_license,
- [
- SERVER_LIBS="-lsieve $SERVER_LIBS"
- ],
- [
- AC_MSG_ERROR(libsieve was not found and is required. More info: http://www.citadel.org/doku.php/installation:start)
- ]
- ,
- )],
- [
- AC_MSG_ERROR(sieve2.h was not found and is required. More info: http://www.citadel.org/doku.php/installation:start)
- ]
-)
-
saved_CFLAGS="$CFLAGS"
CFLAGS="$CFLAGS $SERVER_LIBS"
dnl Check for libcitadel
/*
* This module handles states which are global to the entire server.
*
- * Copyright (c) 1987-2019 by the citadel.org team
+ * Copyright (c) 1987-2020 by the citadel.org team
*
* This program is open source software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 3.
/*
* CtdlGetCurrentMessageNumber() - Obtain the current highest message number in the system
* This provides a quick way to initialise a variable that might be used to indicate
- * messages that should not be processed. EG. a new Sieve script will use this
+ * messages that should not be processed. For example, an inbox rules script will use this
* to record determine that messages older than this should not be processed.
*
* (Why is this function here? Can't we just go straight to the config variable it fetches?)
Register or remove a function with the room processing system.
Registered functions are called in the order they are registered when a message
-is added to a room. This allows modules such as Sieve to process new messages
-appearing in a room.
+is added to a room. This allows modules to process new messages appearing in a room.
void CtdlRegisterXmsgHook(int (*fcn_ptr) (char *, char *, char *), int order)
+++ /dev/null
-SYSTEM TESTING PROPOSAL
-
-This document is intended as a discussion of possible automated tests. It does
-not describe any existing tests.
-
-
----
-
-
-First we should create a client that leverages expect (or something similar) to
-perform automated testing of the client interface. Tests can be written as
-expect scripts.
-
-Each system being tested will need to create an aide level user for the test
-client to connect as.
-
-The test client will create another user to carry out the tests. This allows the
-aide level user to vary the level of the test user and check the access level
-code.
-
-----
-
-
-For a first step each test site should create a test user that can send internet
-mail.
-This test user needs some sieve rules to forward mail around (eventually this
-will be created automatically by the test client). These rules will forward mail
-to other test users at other sites participating in the test system and to a
-networked room.
-Each system participating in the test should share some rooms.
-
-The idea is:
- 1. A test site posts a message to its test user using citmail or some
-other email prog.
- 2. The sieve rules forward the message to each of the other test users
-at the other sites.
- 3. The sieve rules for the other test users detect that the message was
-forwarded to them and they file it into a networked room
- 4. By virtue of the networked room the message returns to the
-originating system where the administrator can see it.
-
-Once I (davew) have written my module to alter the message body we can have it
-add text to the message to indicate the full path of the message.
-
-
/*
* CtdlGetCurrentMessageNumber() - Obtain the current highest message number in the system
* This provides a quick way to initialise a variable that might be used to indicate
- * messages that should not be processed. EG. a new Sieve script will use this
+ * messages that should not be processed. For example, a new inbox script will use this
* to record determine that messages older than this should not be processed.
* This function is defined in control.c
*/
* This file contains functions which handle the mapping of Internet addresses
* to users on the Citadel system.
*
- * Copyright (c) 1987-2019 by the citadel.org team
+ * Copyright (c) 1987-2020 by the citadel.org team
*
* This program is open source software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 3.
/* If the last item in a list of recipients was truncated to a partial address,
- * remove it completely in order to avoid choking libSieve
+ * remove it completely in order to avoid choking library functions.
*/
void sanitize_truncated_recipient(char *str)
{
/*
* Server functions which perform operations on user objects.
*
- * Copyright (c) 1987-2019 by the citadel.org team
+ * Copyright (c) 1987-2020 by the citadel.org team
*
* This program is open source software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License, version 3.
cprintf("%s\n", CtdlGetConfigStr("c_moreprompt"));
cprintf("1\n"); /* 1 = yes, this system supports floors */
cprintf("1\n"); /* 1 = we support the extended paging options */
- cprintf("\n"); /* nonce no longer supported */
+ cprintf("\n"); /* no longer used */
cprintf("1\n"); /* 1 = yes, this system supports the QNOP command */
#ifdef HAVE_LDAP
cprintf("%s\n", CtdlGetConfigStr("c_default_cal_zone"));
- cprintf("0\n"); /* load average (no longer used) */
- cprintf("0\n"); /* worker average (no longer used) */
- cprintf("0\n"); /* thread count (no longer used) */
- cprintf("1\n"); /* yes, Sieve mail filtering is supported */
+ cprintf("0\n"); /* no longer used */
+ cprintf("0\n"); /* no longer used */
+ cprintf("0\n"); /* no longer used */
+ cprintf("0\n"); /* no longer used */
cprintf("%d\n", CtdlGetConfigInt("c_enable_fulltext"));
cprintf("%s\n", svn_revision());
/*
* This module dumps and/or loads the Citadel database in XML format.
*
- * Copyright (c) 1987-2019 by the citadel.org team
+ * Copyright (c) 1987-2020 by the citadel.org team
*
* This program is open source software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 3.
client_write(HKEY("<u_fullname>")); xml_strout(u.fullname); client_write(HKEY("</u_fullname>\n"));
cprintf("<u_msgnum_bio>%ld</u_msgnum_bio>\n", u.msgnum_bio);
cprintf("<u_msgnum_pic>%ld</u_msgnum_pic>\n", u.msgnum_pic);
+ cprintf("<u_emailaddrs>%s</u_emailaddrs>\n", u.emailaddrs);
+ cprintf("<u_msgnum_inboxrules>%ld</u_msgnum_inboxrules>\n", u.msgnum_inboxrules);
client_write(HKEY("</user>\n"));
}
else if (!strcasecmp(el, "u_fullname")) safestrncpy(usbuf.fullname, ChrPtr(migr_chardata), sizeof usbuf.fullname);
else if (!strcasecmp(el, "u_msgnum_bio")) usbuf.msgnum_bio = atol(ChrPtr(migr_chardata));
else if (!strcasecmp(el, "u_msgnum_pic")) usbuf.msgnum_pic = atol(ChrPtr(migr_chardata));
+ else if (!strcasecmp(el, "u_emailaddrs")) safestrncpy(usbuf.emailaddrs, ChrPtr(migr_chardata), sizeof usbuf.emailaddrs);
+ else if (!strcasecmp(el, "u_msgnum_inboxrules")) usbuf.msgnum_inboxrules = atol(ChrPtr(migr_chardata));
else return 0;
return 1;
}
/*
- * This module glues libSieve to the Citadel server in order to implement
- * the Sieve mailbox filtering language (RFC 3028).
+ * Inbox handling rules
*
* Copyright (c) 1987-2020 by the citadel.org team
*
#include "msgbase.h"
#include "internet_addressing.h"
#include "ctdl_module.h"
-#include "serv_sieve.h"
-struct RoomProcList *sieve_list = NULL;
-char *msiv_extensions = NULL;
-
-
-/*
- * Callback function to send libSieve trace messages to Citadel log facility
- */
-int ctdl_debug(sieve2_context_t *s, void *my)
-{
- syslog(LOG_DEBUG, "%s", sieve2_getvalue_string(s, "message"));
- return SIEVE2_OK;
-}
-
-
-/*
- * Callback function to log script parsing errors
- */
-int ctdl_errparse(sieve2_context_t *s, void *my)
-{
- syslog(LOG_WARNING, "Error in script, line %d: %s",
- sieve2_getvalue_int(s, "lineno"),
- sieve2_getvalue_string(s, "message")
- );
- return SIEVE2_OK;
-}
-
-
-/*
- * Callback function to log script execution errors
- */
-int ctdl_errexec(sieve2_context_t *s, void *my)
-{
- syslog(LOG_WARNING, "Error executing script: %s",
- sieve2_getvalue_string(s, "message")
- );
- return SIEVE2_OK;
-}
+#if 0
/*
* Callback function to redirect a message to a different folder
*/
-int ctdl_redirect(sieve2_context_t *s, void *my)
+int ctdl_redirect(void)
{
struct ctdl_sieve *cs = (struct ctdl_sieve *)my;
struct CtdlMessage *msg = NULL;
}
-#if 0
-/*
- * Callback function to parse addresses per local system convention
- * It is disabled because we don't support subaddresses.
- */
-int ctdl_getsubaddress(sieve2_context_t *s, void *my)
-{
- struct ctdl_sieve *cs = (struct ctdl_sieve *)my;
-
- /* libSieve does not take ownership of the memory used here. But, since we
- * are just pointing to locations inside a struct which we are going to free
- * later, we're ok.
- */
- sieve2_setvalue_string(s, "user", cs->recp_user);
- sieve2_setvalue_string(s, "detail", "");
- sieve2_setvalue_string(s, "localpart", cs->recp_user);
- sieve2_setvalue_string(s, "domain", cs->recp_node);
- return SIEVE2_OK;
-}
-#endif
-
-
/*
* Callback function to parse message envelope
*/
}
-#if 0
-/*
- * Callback function to fetch message body
- * (Uncomment the code if we implement this extension)
- *
- */
-int ctdl_getbody(sieve2_context_t *s, void *my)
-{
- return SIEVE2_ERROR_UNSUPPORTED;
-}
-#endif
-
/*
* Callback function to fetch message size
}
-/*
- * Add a room to the list of those rooms which potentially require sieve processing
- */
-void sieve_queue_room(struct ctdlroom *which_room) {
- struct RoomProcList *ptr;
-
- ptr = (struct RoomProcList *) malloc(sizeof (struct RoomProcList));
- if (ptr == NULL) return;
-
- safestrncpy(ptr->name, which_room->QRname, sizeof ptr->name);
- begin_critical_section(S_SIEVELIST);
- ptr->next = sieve_list;
- sieve_list = ptr;
- end_critical_section(S_SIEVELIST);
- syslog(LOG_DEBUG, "<%s> queued for Sieve processing", which_room->QRname);
-}
-
/*
* Perform sieve processing for one message (called by sieve_do_room() for each message)
{ SIEVE2_MESSAGE_GETALLHEADERS, ctdl_getheaders },
{ SIEVE2_MESSAGE_GETSIZE, ctdl_getsize },
{ SIEVE2_MESSAGE_GETENVELOPE, ctdl_getenvelope },
-/*
- * These actions are unsupported by Citadel so we don't declare them.
- *
- { SIEVE2_ACTION_NOTIFY, ctdl_notify },
- { SIEVE2_MESSAGE_GETSUBADDRESS, ctdl_getsubaddress },
- { SIEVE2_MESSAGE_GETBODY, ctdl_getbody },
- *
- */
{ 0 }
};
* Find the sieve scripts and control record and do something
*/
u.config_msgnum = (-1);
- CtdlForEachMessage(MSGS_LAST, 1, NULL, SIEVECONFIG, NULL,
- get_sieve_config_backend, (void *)&u );
+ CtdlForEachMessage(MSGS_LAST, 1, NULL, SIEVECONFIG, NULL, get_sieve_config_backend, (void *)&u );
if (u.config_msgnum < 0) {
syslog(LOG_DEBUG, "No Sieve rules exist. No processing is required.");
/* Validate the script */
- struct ctdl_sieve my; /* dummy ctdl_sieve struct just to pass "u" slong */
+ struct ctdl_sieve my; /* dummy ctdl_sieve struct just to pass "u" along */
memset(&my, 0, sizeof my);
my.u = &u;
res = sieve2_validate(sieve2_context, &my);
/* Do something useful */
u.sieve2_context = sieve2_context;
orig_lastproc = u.lastproc;
- CtdlForEachMessage(MSGS_GT, u.lastproc, NULL, NULL, NULL,
- sieve_do_msg,
- (void *) &u
- );
+ CtdlForEachMessage(MSGS_GT, u.lastproc, NULL, NULL, NULL, sieve_do_msg, (void *) &u);
BAIL:
res = sieve2_free(&sieve2_context);
syslog(LOG_ERR, "sieve2_free() returned %d: %s", res, sieve2_errstr(res));
}
- /* Rewrite the config if we have to */
+ /* Rewrite the config if we have to (we're not the user right now) */
rewrite_ctdl_sieve_config(&u, (u.lastproc > orig_lastproc) ) ;
}
-/*
- * Perform sieve processing for all rooms which require it
- */
-void perform_sieve_processing(void) {
- struct RoomProcList *ptr = NULL;
-
- if (sieve_list != NULL) {
- syslog(LOG_DEBUG, "Begin Sieve processing");
- while (sieve_list != NULL) {
- char spoolroomname[ROOMNAMELEN];
- safestrncpy(spoolroomname, sieve_list->name, sizeof spoolroomname);
- begin_critical_section(S_SIEVELIST);
-
- /* pop this record off the list */
- ptr = sieve_list;
- sieve_list = sieve_list->next;
- free(ptr);
-
- /* invalidate any duplicate entries to prevent double processing */
- for (ptr=sieve_list; ptr!=NULL; ptr=ptr->next) {
- if (!strcasecmp(ptr->name, spoolroomname)) {
- ptr->name[0] = 0;
- }
- }
-
- end_critical_section(S_SIEVELIST);
- if (spoolroomname[0] != 0) {
- sieve_do_room(spoolroomname);
- }
- }
- }
-}
-
-
-void msiv_load(struct sdm_userdata *u) {
- char hold_rm[ROOMNAMELEN];
-
- strcpy(hold_rm, CC->room.QRname); /* save current room */
-
- /* Take a spin through the user's personal address book */
- if (CtdlGetRoom(&CC->room, USERCONFIGROOM) == 0) {
-
- u->config_msgnum = (-1);
- strcpy(u->config_roomname, CC->room.QRname);
- CtdlForEachMessage(MSGS_LAST, 1, NULL, SIEVECONFIG, NULL,
- get_sieve_config_backend, (void *)u );
-
- }
-
- if (strcmp(CC->room.QRname, hold_rm)) {
- CtdlGetRoom(&CC->room, hold_rm); /* return to saved room */
- }
-}
-
-void msiv_store(struct sdm_userdata *u, int yes_write_to_disk) {
-/*
- * Initialise the sieve configs last processed message number.
- * We don't need to get the highest message number for the users inbox since the systems
- * highest message number will be higher than that and loer than this scripts message number
- * This prevents this new script from processing any old messages in the inbox.
- * Most importantly it will prevent vacation messages being sent to lots of old messages
- * in the inbox.
- */
- u->lastproc = CtdlGetCurrentMessageNumber();
- rewrite_ctdl_sieve_config(u, yes_write_to_disk);
-}
-
-
-/*
- * Select the active script.
- * (Set script_name to an empty string to disable all scripts)
- *
- * Returns 0 on success or nonzero for error.
- */
-int msiv_setactive(struct sdm_userdata *u, char *script_name) {
- int ok = 0;
- struct sdm_script *s;
-
- /* First see if the supplied value is ok */
-
- if (IsEmptyStr(script_name)) {
- ok = 1;
- }
- else {
- for (s=u->first_script; s!=NULL; s=s->next) {
- if (!strcasecmp(s->script_name, script_name)) {
- ok = 1;
- }
- }
- }
-
- if (!ok) return(-1);
-
- /* Now set the active script */
- for (s=u->first_script; s!=NULL; s=s->next) {
- if (!strcasecmp(s->script_name, script_name)) {
- s->script_active = 1;
- }
- else {
- s->script_active = 0;
- }
- }
-
- return(0);
-}
-/*
- * Fetch a script by name.
- *
- * Returns NULL if the named script was not found, or a pointer to the script
- * if it was found. NOTE: the caller does *not* own the memory returned by
- * this function. Copy it if you need to keep it.
- */
-char *msiv_getscript(struct sdm_userdata *u, char *script_name) {
- struct sdm_script *s;
+#endif
- for (s=u->first_script; s!=NULL; s=s->next) {
- if (!strcasecmp(s->script_name, script_name)) {
- if (s->script_content != NULL) {
- return (s->script_content);
- }
- }
- }
- return(NULL);
-}
/*
- * Delete a script by name.
+ * Get InBox Rules
*
- * Returns 0 if the script was deleted.
- * 1 if the script was not found.
- * 2 if the script cannot be deleted because it is active.
+ * This is a client-facing function which fetches the user's inbox rules -- it omits all lines containing anything other than a rule.
*/
-int msiv_deletescript(struct sdm_userdata *u, char *script_name) {
- struct sdm_script *s = NULL;
- struct sdm_script *script_to_delete = NULL;
-
- for (s=u->first_script; s!=NULL; s=s->next) {
- if (!strcasecmp(s->script_name, script_name)) {
- script_to_delete = s;
- if (s->script_active) {
- return(2);
- }
- }
- }
+void cmd_gibr(char *argbuf) {
- if (script_to_delete == NULL) return(1);
+ if (CtdlAccessCheck(ac_logged_in)) return;
- if (u->first_script == script_to_delete) {
- u->first_script = u->first_script->next;
- }
- else for (s=u->first_script; s!=NULL; s=s->next) {
- if (s->next == script_to_delete) {
- s->next = s->next->next;
- }
- }
+ cprintf("%d inbox rules for %s\n", LISTING_FOLLOWS, CC->user.fullname);
- free(script_to_delete->script_content);
- free(script_to_delete);
- return(0);
-}
+ struct CtdlMessage *msg = CtdlFetchMessage(CC->user.msgnum_inboxrules, 1, 1);
+ if (msg != NULL) {
+ if (!CM_IsEmpty(msg, eMesageText)) {
+ char *token;
+ char *rest = msg->cm_fields[eMesageText];
+ while ((token = strtok_r(rest, "\n", &rest))) {
+ // for backwards compatibility, "# WEBCIT_RULE" is an alias for "rule"
+ if (!strncasecmp(token, "# WEBCIT_RULE|", 14)) {
+ strcpy(token, "rule|");
+ strcpy(&token[5], &token[14]);
+ }
-/*
- * Add or replace a new script.
- * NOTE: after this function returns, "u" owns the memory that "script_content"
- * was pointing to.
- */
-void msiv_putscript(struct sdm_userdata *u, char *script_name, char *script_content) {
- int replaced = 0;
- struct sdm_script *s, *sptr;
-
- for (s=u->first_script; s!=NULL; s=s->next) {
- if (!strcasecmp(s->script_name, script_name)) {
- if (s->script_content != NULL) {
- free(s->script_content);
+ // Output only lines containing rules.
+ if (!strncasecmp(token, "rule|", 5)) {
+ cprintf("%s\n", token);
+ }
}
- s->script_content = script_content;
- replaced = 1;
}
+ CM_Free(msg);
}
-
- if (replaced == 0) {
- sptr = malloc(sizeof(struct sdm_script));
- safestrncpy(sptr->script_name, script_name, sizeof sptr->script_name);
- sptr->script_content = script_content;
- sptr->script_active = 0;
- sptr->next = u->first_script;
- u->first_script = sptr;
- }
+ cprintf("000\n");
}
-
/*
- * Citadel protocol to manage sieve scripts.
- * This is basically a simplified (read: doesn't resemble IMAP) version
- * of the 'managesieve' protocol.
+ * Put InBox Rules
+ *
+ * User transmits the new inbox rules for the account. They are inserted into the account, replacing the ones already there.
*/
-void cmd_msiv(char *argbuf) {
- char subcmd[256];
- struct sdm_userdata u;
- char script_name[256];
- char *script_content = NULL;
- struct sdm_script *s;
- int i;
- int changes_made = 0;
-
- memset(&u, 0, sizeof(struct sdm_userdata));
-
+void cmd_pibr(char *argbuf) {
if (CtdlAccessCheck(ac_logged_in)) return;
- extract_token(subcmd, argbuf, 0, '|', sizeof subcmd);
- msiv_load(&u);
-
- if (!strcasecmp(subcmd, "putscript")) {
- extract_token(script_name, argbuf, 1, '|', sizeof script_name);
- if (!IsEmptyStr(script_name)) {
- cprintf("%d Transmit script now\n", SEND_LISTING);
- script_content = CtdlReadMessageBody(HKEY("000"), CtdlGetConfigLong("c_maxmsglen"), NULL, 0);
- msiv_putscript(&u, script_name, script_content);
- changes_made = 1;
- }
- else {
- cprintf("%d Invalid script name.\n", ERROR + ILLEGAL_VALUE);
- }
- }
-
- else if (!strcasecmp(subcmd, "listscripts")) {
- cprintf("%d Scripts:\n", LISTING_FOLLOWS);
- for (s=u.first_script; s!=NULL; s=s->next) {
- if (s->script_content != NULL) {
- cprintf("%s|%d|\n", s->script_name, s->script_active);
- }
- }
- cprintf("000\n");
- }
- else if (!strcasecmp(subcmd, "setactive")) {
- extract_token(script_name, argbuf, 1, '|', sizeof script_name);
- if (msiv_setactive(&u, script_name) == 0) {
- cprintf("%d ok\n", CIT_OK);
- changes_made = 1;
- }
- else {
- cprintf("%d Script '%s' does not exist.\n",
- ERROR + ILLEGAL_VALUE,
- script_name
- );
- }
- }
-
- else if (!strcasecmp(subcmd, "getscript")) {
- extract_token(script_name, argbuf, 1, '|', sizeof script_name);
- script_content = msiv_getscript(&u, script_name);
- if (script_content != NULL) {
- int script_len;
-
- cprintf("%d Script:\n", LISTING_FOLLOWS);
- script_len = strlen(script_content);
- client_write(script_content, script_len);
- if (script_content[script_len-1] != '\n') {
- cprintf("\n");
+ unbuffer_output();
+ cprintf("%d send new rules\n", SEND_LISTING);
+ char *newrules = CtdlReadMessageBody(HKEY("000"), CtdlGetConfigLong("c_maxmsglen"), NULL, 0);
+ StrBuf *NewConfig = NewStrBufPlain("Content-type: application/x-citadel-sieve-config; charset=UTF-8\nContent-transfer-encoding: 8bit\n\n", -1);
+
+ char *token;
+ char *rest = newrules;
+ while ((token = strtok_r(rest, "\n", &rest))) {
+ // Accept only lines containing rules
+ if (!strncasecmp(token, "rule|", 5)) {
+ StrBufAppendBufPlain(NewConfig, token, -1, 0);
+ StrBufAppendBufPlain(NewConfig, HKEY("\n"), 0);
+ }
+ }
+ free(newrules);
+
+ // Fetch the existing config so we can merge in anything that is NOT a rule
+ // (Does not start with "rule|" but has at least one vertical bar)
+ struct CtdlMessage *msg = CtdlFetchMessage(CC->user.msgnum_inboxrules, 1, 1);
+ if (msg != NULL) {
+ if (!CM_IsEmpty(msg, eMesageText)) {
+ rest = msg->cm_fields[eMesageText];
+ while ((token = strtok_r(rest, "\n", &rest))) {
+ // for backwards compatibility, "# WEBCIT_RULE" is an alias for "rule"
+ if ((strncasecmp(token, "# WEBCIT_RULE|", 14)) && (strncasecmp(token, "rule|", 5)) && (haschar(token, '|'))) {
+ StrBufAppendBufPlain(NewConfig, token, -1, 0);
+ StrBufAppendBufPlain(NewConfig, HKEY("\n"), 0);
+ }
}
- cprintf("000\n");
- }
- else {
- cprintf("%d Invalid script name.\n", ERROR + ILLEGAL_VALUE);
}
+ CM_Free(msg);
}
- else if (!strcasecmp(subcmd, "deletescript")) {
- extract_token(script_name, argbuf, 1, '|', sizeof script_name);
- i = msiv_deletescript(&u, script_name);
- if (i == 0) {
- cprintf("%d ok\n", CIT_OK);
- changes_made = 1;
- }
- else if (i == 1) {
- cprintf("%d Script '%s' does not exist.\n",
- ERROR + ILLEGAL_VALUE,
- script_name
- );
- }
- else if (i == 2) {
- cprintf("%d Script '%s' is active and cannot be deleted.\n",
- ERROR + ILLEGAL_VALUE,
- script_name
- );
- }
- else {
- cprintf("%d unknown error\n", ERROR);
- }
- }
-
- else {
- cprintf("%d Invalid subcommand\n", ERROR + CMD_NOT_SUPPORTED);
- }
-
- msiv_store(&u, changes_made);
-}
-
-
-
-void ctdl_sieve_init(void) {
- char *cred = NULL;
- sieve2_context_t *sieve2_context = NULL;
- int res;
-
- /*
- * We don't really care about dumping the entire credits to the log
- * every time the server is initialized. The documentation will suffice
- * for that purpose. We are making a call to sieve2_credits() in order
- * to demonstrate that we have successfully linked in to libsieve.
- */
- cred = strdup(sieve2_credits());
- if (cred == NULL) return;
-
- if (strlen(cred) > 60) {
- strcpy(&cred[55], "...");
- }
-
- syslog(LOG_INFO, "%s",cred);
- free(cred);
-
- /* Briefly initialize a Sieve parser instance just so we can list the
- * extensions that are available.
- */
- res = sieve2_alloc(&sieve2_context);
- if (res != SIEVE2_OK) {
- syslog(LOG_ERR, "sieve2_alloc() returned %d: %s", res, sieve2_errstr(res));
- return;
- }
-
- res = sieve2_callbacks(sieve2_context, ctdl_sieve_callbacks);
- if (res != SIEVE2_OK) {
- syslog(LOG_ERR, "sieve2_callbacks() returned %d: %s", res, sieve2_errstr(res));
- goto BAIL;
- }
-
- msiv_extensions = strdup(sieve2_listextensions(sieve2_context));
- syslog(LOG_INFO, "Extensions: %s", msiv_extensions);
-
-BAIL: res = sieve2_free(&sieve2_context);
- if (res != SIEVE2_OK) {
- syslog(LOG_ERR, "sieve2_free() returned %d: %s", res, sieve2_errstr(res));
- }
-
-}
-
-
-void cleanup_sieve(void)
-{
- struct RoomProcList *ptr, *ptr2;
-
- if (msiv_extensions != NULL)
- free(msiv_extensions);
- msiv_extensions = NULL;
-
- begin_critical_section(S_SIEVELIST);
- ptr=sieve_list;
- while (ptr != NULL) {
- ptr2 = ptr->next;
- free(ptr);
- ptr = ptr2;
- }
- sieve_list = NULL;
- end_critical_section(S_SIEVELIST);
-}
-
-
-int serv_sieve_room(struct ctdlroom *room)
-{
- if (!strcasecmp(&room->QRname[11], MAILROOM)) {
- sieve_queue_room(room);
+ /* we have composed the new configuration , now save it */
+ long old_msgnum = CC->user.msgnum_inboxrules;
+ char userconfigroomname[ROOMNAMELEN];
+ CtdlMailboxName(userconfigroomname, sizeof userconfigroomname, &CC->user, USERCONFIGROOM);
+ long new_msgnum = quickie_message("Citadel", NULL, NULL, userconfigroomname, ChrPtr(NewConfig), FMT_RFC822, "inbox rules configuration");
+ FreeStrBuf(&NewConfig);
+ CtdlGetUserLock(&CC->user, CC->curr_user);
+ CC->user.msgnum_inboxrules = new_msgnum;
+ CtdlPutUserLock(&CC->user);
+ if (old_msgnum > 0) {
+ syslog(LOG_DEBUG, "Deleting old message %ld from %s", old_msgnum, userconfigroomname);
+ CtdlDeleteMessages(userconfigroomname, &old_msgnum, 1, "");
}
- return 0;
}
{
if (!threading)
{
- ctdl_sieve_init();
- CtdlRegisterProtoHook(cmd_msiv, "MSIV", "Manage Sieve scripts");
- CtdlRegisterRoomHook(serv_sieve_room);
- CtdlRegisterSessionHook(perform_sieve_processing, EVT_HOUSE, PRIO_HOUSE + 10);
- CtdlRegisterCleanupHook(cleanup_sieve);
+ // ctdl_sieve_init();
+ CtdlRegisterProtoHook(cmd_gibr, "GIBR", "Get InBox Rules");
+ CtdlRegisterProtoHook(cmd_pibr, "PIBR", "Put InBox Rules");
+ // CtdlRegisterSessionHook(perform_sieve_processing, EVT_HOUSE, PRIO_HOUSE + 10);
+ // CtdlRegisterCleanupHook(cleanup_sieve);
}
/* return our module name for the log */
}
+
+
+/*
+ * We found the legacy sieve config in the user's config room. Store the message number in the user record.
+ */
+void mifm_found_config(long msgnum, void *userdata) {
+ struct ctdluser *us = (struct ctdluser *)userdata;
+
+ us->msgnum_inboxrules = msgnum;
+ syslog(LOG_DEBUG, "user: <%s> inbox filter msgnum: <%ld>", us->fullname, us->msgnum_inboxrules);
+}
+
+
+/*
+ * Helper function for migrate_inbox_filter_msgnums()
+ */
+void mifm_backend(char *username, void *data) {
+ struct ctdluser us;
+ char roomname[ROOMNAMELEN];
+
+ if (CtdlGetUserLock(&us, username) == 0) {
+ /* Take a spin through the user's personal config room */
+ syslog(LOG_DEBUG, "Processing <%s> (%ld)", us.fullname, us.usernum);
+ snprintf(roomname, sizeof roomname, "%010ld.%s", us.usernum, USERCONFIGROOM);
+ if (CtdlGetRoom(&CC->room, roomname) == 0) {
+ CtdlForEachMessage(MSGS_LAST, 1, NULL, SIEVECONFIG, NULL, mifm_found_config, (void *)&us );
+ }
+ CtdlPutUserLock(&us);
+ }
+}
+
+
+/*
+ * Prior to version 930 we used a MIME type search to locate the user's inbox filter rules.
+ * This function locates those ruleset messages and simply stores the message number in the user record.
+ */
+void migrate_inbox_filter_msgnums(void)
+{
+ ForEachUser(mifm_backend, NULL);
+}
+
+
/*
* Create a default administrator account so we can log in to a new installation
*/
if ((oldver > 000) && (oldver < 922)) {
ProcessOldStyleAdjRefCountQueue();
}
+
+ if ((oldver > 000) && (oldver < 930)) {
+ migrate_inbox_filter_msgnums();
+ }
+
}
* Citadel Extension Loader
* Originally written by Brian Costello <btx@calyx.net>
*
- * Copyright (c) 1987-2019 by the citadel.org team
+ * Copyright (c) 1987-2020 by the citadel.org team
*
* This program is open source software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License, version 3.
/*
* RoomFunctionHook extensions are used for hooks which impliment room
- * processing functions when new messages are added EG. SIEVE.
+ * processing functions when new messages are added.
*/
typedef struct RoomFunctionHook RoomFunctionHook;
struct RoomFunctionHook {
+++ /dev/null
-
-#include <sieve2.h>
-#include <sieve2_error.h>
-
-struct sdm_script {
- struct sdm_script *next;
- char script_name[256];
- int script_active;
- char *script_content;
-};
-
-struct sdm_vacation {
- struct sdm_vacation *next;
- char fromaddr[256];
- time_t timestamp;
-};
-
-struct sdm_userdata {
- sieve2_context_t *sieve2_context; /* for libsieve's use */
- long config_msgnum; /* confirms that a sieve config was located */
- char config_roomname[ROOMNAMELEN];
- long lastproc; /* last message processed */
- struct sdm_script *first_script;
- struct sdm_vacation *first_vacation;
-};
-
-struct ctdl_sieve {
- char *rfc822headers;
- int cancel_implicit_keep; /* Set to 1 if the message was successfully acted upon */
- int keep; /* Set to 1 to suppress message deletion from the inbox */
- long usernum; /* Owner of the mailbox we're processing */
- long msgnum; /* Message base ID of the message being processed */
- struct sdm_userdata *u; /* Info related to the current session */
- char recp_user[256];
- char recp_node[256];
- char recp_name[256];
- char sender[256]; /* To whom shall we send reject bounces or vacation messages? */
- char subject[1024]; /* Retain msg subject so we can use it in vacation messages */
- char envelope_from[1024];
- char envelope_to[1024];
-};
-
-
-/* If you change this string you will break all of your Sieve configs. */
-#define CTDLSIEVECONFIGSEPARATOR "\n-=<CtdlSieveConfigSeparator>=-\n"
-
-/* Maximum time we keep vacation fromaddr records online. This implies that a vacation
- * rule cannot exceed this amount of time. (Any more than 30 days is a ridiculously
- * long vacation which the person probably doesn't deserve.)
- */
-#define MAX_VACATION 30
-
-extern struct RoomProcList *sieve_list;
-
-void sieve_queue_room(struct ctdlroom *);
-void perform_sieve_processing(void);
-
-void msiv_load(struct sdm_userdata *u);
-void msiv_store(struct sdm_userdata *u, int changes_made);
-int msiv_setactive(struct sdm_userdata *u, char *script_name);
-char *msiv_getscript(struct sdm_userdata *u, char *script_name);
-int msiv_deletescript(struct sdm_userdata *u, char *script_name);
-void msiv_putscript(struct sdm_userdata *u, char *script_name, char *script_content);
-extern char *msiv_extensions;
/*
- * Copyright (c) 1996-2012 by the citadel.org team
+ * Copyright (c) 1996-2020 by the citadel.org team
*
* This program is open source software. You can redistribute it and/or
* modify it under the terms of the GNU General Public License version 3.
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
- * FIXME: add logic to exclude the webcit-generated script from the manual script selection
+ *
+ * Implementation note: this was kind of hacked up when we switched from Sieve to custom rules.
+ * As a result there's probably some cruft in here...
+ * ajc 2020jul12
+ *
*/
#include "webcit.h"
#define RULES_SCRIPT "__WebCit_Generated_Script__"
-/*
- * Helper function for output_sieve_rule() to output strings with quotes escaped
- */
-void osr_sanitize(char *str) {
- int i, len;
-
- if (str == NULL) return;
- len = strlen(str);
- for (i=0; i<len; ++i) {
- if (str[i]=='\"') {
- str[i] = '\'' ;
- }
- else if (isspace(str[i])) {
- str[i] = ' ';
- }
- }
-}
-
-
-/*
- * Output parseable Sieve script code based on rules input
- */
-void output_sieve_rule(char *hfield, char *compare, char *htext, char *sizecomp, int sizeval,
- char *action, char *fileinto, char *redirect, char *automsg, char *final,
- char *my_addresses)
-{
- char *comp1 = "";
- char *comp2 = "";
-
- osr_sanitize(htext);
- osr_sanitize(fileinto);
- osr_sanitize(redirect);
- osr_sanitize(automsg);
-
- /* Prepare negation and match operators that will be used iff we apply a conditional */
-
- if (!strcasecmp(compare, "contains")) {
- comp1 = "";
- comp2 = ":contains";
- }
- else if (!strcasecmp(compare, "notcontains")) {
- comp1 = "not";
- comp2 = ":contains";
- }
- else if (!strcasecmp(compare, "is")) {
- comp1 = "";
- comp2 = ":is";
- }
- else if (!strcasecmp(compare, "isnot")) {
- comp1 = "not";
- comp2 = ":is";
- }
- else if (!strcasecmp(compare, "matches")) {
- comp1 = "";
- comp2 = ":matches";
- }
- else if (!strcasecmp(compare, "notmatches")) {
- comp1 = "not";
- comp2 = ":matches";
- }
-
- /* Now do the conditional */
-
- if (!strcasecmp(hfield, "from")) {
- serv_printf("if%s header %s \"From\" \"%s\"",
- comp1, comp2,
- htext
- );
- }
-
- else if (!strcasecmp(hfield, "tocc")) {
- serv_printf("if%s header %s [\"To\", \"Cc\"] \"%s\"",
- comp1, comp2,
- htext
- );
- }
-
- else if (!strcasecmp(hfield, "subject")) {
- serv_printf("if%s header %s \"Subject\" \"%s\"",
- comp1, comp2,
- htext
- );
- }
-
- else if (!strcasecmp(hfield, "replyto")) {
- serv_printf("if%s header %s \"Reply-to\" \"%s\"",
- comp1, comp2,
- htext
- );
- }
-
- else if (!strcasecmp(hfield, "sender")) {
- serv_printf("if%s header %s \"Sender\" \"%s\"",
- comp1, comp2,
- htext
- );
- }
-
- else if (!strcasecmp(hfield, "resentfrom")) {
- serv_printf("if%s header %s \"Resent-from\" \"%s\"",
- comp1, comp2,
- htext
- );
- }
-
- else if (!strcasecmp(hfield, "resentto")) {
- serv_printf("if%s header %s \"Resent-to\" \"%s\"",
- comp1, comp2,
- htext
- );
- }
-
- else if (!strcasecmp(hfield, "xmailer")) {
- serv_printf("if%s header %s \"X-Mailer\" \"%s\"",
- comp1, comp2,
- htext
- );
- }
-
- else if (!strcasecmp(hfield, "xspamflag")) {
- serv_printf("if%s header %s \"X-Spam-Flag\" \"%s\"",
- comp1, comp2,
- htext
- );
- }
-
- else if (!strcasecmp(hfield, "xspamstatus")) {
- serv_printf("if%s header %s \"X-Spam-Status\" \"%s\"",
- comp1, comp2,
- htext
- );
- }
-
- else if (!strcasecmp(hfield, "listid")) {
- serv_printf("if%s header %s \"List-ID\" \"%s\"",
- comp1, comp2,
- htext
- );
- }
-
- else if (!strcasecmp(hfield, "envfrom")) {
- serv_printf("if%s envelope %s \"From\" \"%s\"",
- comp1, comp2,
- htext
- );
- }
-
- else if (!strcasecmp(hfield, "envto")) {
- serv_printf("if%s envelope %s \"To\" \"%s\"",
- comp1, comp2,
- htext
- );
- }
-
- else if (!strcasecmp(hfield, "size")) {
- if (!strcasecmp(sizecomp, "larger")) {
- serv_printf("if size :over %d", sizeval);
- }
- else if (!strcasecmp(sizecomp, "smaller")) {
- serv_printf("if size :under %d", sizeval);
- }
- else { /* failsafe - should never get here, but just in case... */
- serv_printf("if size :over 1");
- }
- }
-
- /* Open braces if we're in a conditional loop */
-
- if (strcasecmp(hfield, "all")) {
- serv_printf("{");
- }
-
- /* Do action */
-
- if (!strcasecmp(action, "keep")) {
- serv_printf("keep;");
- }
-
- else if (!strcasecmp(action, "discard")) {
- serv_printf("discard;");
- }
-
- else if (!strcasecmp(action, "reject")) {
- serv_printf("reject \"%s\";", automsg);
- }
-
- else if (!strcasecmp(action, "fileinto")) {
- serv_printf("fileinto \"%s\";", fileinto);
- }
-
- else if (!strcasecmp(action, "redirect")) {
- serv_printf("redirect \"%s\";", redirect);
- }
-
- else if (!strcasecmp(action, "vacation")) {
- serv_printf("vacation :addresses [%s]\n\"%s\";", my_addresses, automsg);
- }
-
- /* Do 'final' action */
-
- if (!strcasecmp(final, "stop")) {
- serv_printf("stop;");
- }
-
- /* Close the braces if we're in a conditional loop */
-
- if (strcasecmp(hfield, "all")) {
- serv_printf("}");
- }
-
- /* End of rule. */
-}
-
-
/*
* Translate the fields from the rule editor into something we can save...
*/
}
/* Now generate the script and write it to the Citadel server */
- serv_printf("MSIV putscript|%s|", RULES_SCRIPT);
+ serv_printf("PIBR");
serv_getln(buf, sizeof buf);
if (buf[0] != '4') {
return;
}
- serv_puts("# THIS SCRIPT WAS AUTOMATICALLY GENERATED BY WEBCIT.");
- serv_puts("# ");
- serv_puts("# Do not attempt to manually edit it. If you do so,");
- serv_puts("# your changes will be overwritten the next time WebCit");
- serv_puts("# saves its mail filtering rule set. If you really want");
- serv_puts("# to use these rules as the basis for another script,");
- serv_puts("# copy them to another script and save that instead.");
- serv_puts("");
- serv_puts("require \"fileinto\";");
- serv_puts("require \"reject\";");
- serv_puts("require \"vacation\";");
- serv_puts("require \"envelope\";");
- serv_puts("");
-
for (i=0; i<MAX_RULES; ++i) {
strcpy(rule, "");
if (encoded_rule[len - 1] == '\n') {
encoded_rule[len - 1] = '\0';
}
- serv_printf("# WEBCIT_RULE|%d|%s|", i, encoded_rule);
- output_sieve_rule(hfield, compare, htext, sizecomp, sizeval,
- action, fileinto, redirect, automsg, final, my_addresses);
+ serv_printf("rule|%d|%s|", i, encoded_rule);
serv_puts("");
}
-
-
}
- serv_puts("stop;");
serv_puts("000");
}
* save sieve config
*/
void save_sieve(void) {
- int bigaction;
- char script_names[MAX_SCRIPTS][64];
- int num_scripts = 0;
- int i;
- char this_name[64];
- char buf[256];
if (!havebstr("save_button")) {
AppendImportantMessage(_("Cancelled. Changes were not saved."), -1);
parse_fields_from_rule_editor();
- serv_puts("MSIV listscripts");
- serv_getln(buf, sizeof(buf));
- if (buf[0] == '1') while (serv_getln(buf, sizeof(buf)), strcmp(buf, "000")) {
- if (num_scripts < MAX_SCRIPTS) {
- extract_token(script_names[num_scripts], buf, 0, '|', 64);
- ++num_scripts;
- }
- }
-
- bigaction = ibstr("bigaction");
-
- if (bigaction == 0) {
- serv_puts("MSIV setactive||");
- serv_getln(buf, sizeof buf);
- }
-
- else if (bigaction == 1) {
- serv_printf("MSIV setactive|%s|", RULES_SCRIPT);
- serv_getln(buf, sizeof buf);
- }
-
- else if (bigaction == 2) {
- serv_printf("MSIV setactive|%s|", bstr("active_script"));
- serv_getln(buf, sizeof buf);
- }
-
- if (num_scripts > 0) {
- for (i=0; i<num_scripts; ++i) {
- /*
- * We only want to save the scripts from the "manually edited scripts"
- * screen. The script that WebCit generates from its ruleset will be
- * auto-generated by parse_fields_from_rule_editor() and saved there.
- */
- if (strcasecmp(script_names[i], RULES_SCRIPT)) {
- serv_printf("MSIV putscript|%s|", script_names[i]);
- serv_getln(buf, sizeof buf);
- if (buf[0] == '4') {
- snprintf(this_name, sizeof this_name, "text_%s", script_names[i]);
- striplt((char *)BSTR(this_name)); /* TODO: get rid of typecast*/
- serv_write(BSTR(this_name), strlen(BSTR(this_name)));
- serv_puts("\n000");
- }
- }
- }
- }
-
AppendImportantMessage(_("Your changes have been saved."), -1);
display_main_menu();
return;
-/*
- * create a new script
- * take the web environment script name and create it on the citadel server
- */
-void create_script(void) {
- char buf[256];
-
- serv_printf("MSIV getscript|%s", bstr("script_name"));
- serv_getln(buf, sizeof buf);
- if (buf[0] == '1') { // does script exist already?
- while (serv_getln(buf, sizeof(buf)), strcmp(buf, "000")) {
- // yes -- flush the output
- }
- }
- else {
- // no -- safe to create a new one by this name
- serv_printf("MSIV putscript|%s", bstr("script_name"));
- serv_getln(buf, sizeof buf);
- if (buf[0] == '4') {
- serv_puts("keep;");
- serv_puts("000");
- }
- }
-
- display_sieve_add_or_delete();
-}
-
-
-/*
- * delete a script
- */
-void delete_script(void) {
- char buf[256];
-
- serv_printf("MSIV deletescript|%s", bstr("script_name"));
- serv_getln(buf, sizeof buf);
- display_sieve_add_or_delete();
-}
-
-
/*
* dummy panel indicating to the user that the server doesn't support Sieve
*/
free(Rule);
}
-#define WC_RULE_HEADER "# WEBCIT_RULE|"
+#define WC_RULE_HEADER "rule|"
HashList *GetSieveRules(StrBuf *Target, WCTemplputParams *TP)
{
StrBuf *Line = NULL;
SieveRule *Rule = NULL;
SieveRules = NewHash(1, Flathash);
- serv_printf("MSIV getscript|"RULES_SCRIPT);
+ serv_printf("GIBR");
Line = NewStrBuf();
EncodedRule = NewStrBuf();
StrBuf_ServGetln(Line);
{
pch = NULL;
/* We just care for our encoded header and skip everything else */
- if ((StrLength(Line) > sizeof(WC_RULE_HEADER) - 1) &&
- (!strncasecmp(ChrPtr(Line), HKEY(WC_RULE_HEADER))))
+ if ((StrLength(Line) > sizeof(WC_RULE_HEADER) - 1) && (!strncasecmp(ChrPtr(Line), HKEY(WC_RULE_HEADER))))
{
StrBufSkip_NTokenS(Line, &pch, '|', 1);
n = StrBufExtractNext_int(Line, &pch, '|');
/* fetch our room into WCC->ThisRoom, to evaluate while iterating over rooms with COND:THIS:THAT:ROOM */
RegisterNamespace("SIEVE:SCRIPT:LOOKUP_FILEINTO", 0, 1, tmplput_SieveRule_lookup_FileIntoRoom, NULL, CTX_SIEVESCRIPT);
WebcitAddUrlHandler(HKEY("save_sieve"), "", 0, save_sieve, 0);
- WebcitAddUrlHandler(HKEY("create_script"), "", 0, create_script, 0);
- WebcitAddUrlHandler(HKEY("delete_script"), "", 0, delete_script, 0);
WebcitAddUrlHandler(HKEY("display_sieve_add_or_delete"), "", 0, display_sieve_add_or_delete, 0);
}
#define DEVELOPER_ID 0
#define CLIENT_ID 4
#define CLIENT_VERSION 927 /* This version of WebCit */
-#define MINIMUM_CIT_VERSION 924 /* Minimum required version of Citadel server */
+#define MINIMUM_CIT_VERSION 930 /* Minimum required version of Citadel server */
#define LIBCITADEL_MIN 924 /* Minimum required version of libcitadel */
#define DEFAULT_HOST "localhost" /* Default Citadel server */
#define DEFAULT_PORT "504"