Moved rest of serv_* into modules/* where it can be done without altering the contents of the file or the code
modified Makefile.in to build them.
CLIENT_TARGETS=citadel$(EXEEXT) whobbs$(EXEEXT) stress$(EXEEXT)
SERVER_TARGETS=citserver
-SERV_MODULES=serv_chat.o \
- serv_upgrade.o \
- serv_smtp.o \
- serv_spam.o \
- serv_pop3.o \
- serv_vcard.o vcard.o \
- serv_mrtg.o \
+SERV_MODULES=modules/chat/serv_chat.o \
+ modules/upgrade/serv_upgrade.o \
+ modules/smtp/serv_smtp.o \
+ modules/spam/serv_spam.o \
+ modules/pop3/serv_pop3.o \
+ modules/vcard/serv_vcard.o \
+ vcard.o \
+ modules/mrtg/serv_mrtg.o \
serv_imap.o \
imap_fetch.o \
imap_misc.o \
imap_metadata.o \
imap_tools.o \
imap_list.o \
- serv_fulltext.o \
- ft_wordbreaker.o \
+ modules/fulltext/serv_fulltext.o \
+ modules/fulltext/ft_wordbreaker.o \
crc16.o \
- serv_network.o \
- serv_listsub.o \
- serv_netfilter.o \
- serv_newuser.o \
- serv_notes.o \
- serv_pas2.o md5.o \
- serv_inetcfg.o \
- serv_rwho.o \
- serv_bio.o \
- serv_expire.o \
- serv_vandelay.o \
- serv_calendar.o \
- serv_sieve.o \
- serv_managesieve.o \
+ modules/network/serv_network.o \
+ modules/listsub/serv_listsub.o \
+ modules/netfilter/serv_netfilter.o \
+ modules/newuser/serv_newuser.o \
+ modules/notes/serv_notes.o \
+ modules/pas2/serv_pas2.o \
+ md5.o \
+ modules/inetcfg/serv_inetcfg.o \
+ modules/rwho/serv_rwho.o \
+ modules/bio/serv_bio.o \
+ modules/expire/serv_expire.o \
+ modules/vandelay/serv_vandelay.o \
+ modules/calendar/serv_calendar.o \
+ modules/crypto/serv_crypto.o \
+ modules/sieve/serv_sieve.o \
+ modules/managesieve/serv_managesieve.o \
ical_dezonify.o \
- serv_ldap.o \
- serv_autocompletion.o \
- serv_funambol.o \
+ modules/ldap/serv_ldap.o \
+ modules/autocompletion/serv_autocompletion.o \
+ modules/funambol/serv_funambol.o \
modules/test/serv_test.o
UTIL_TARGETS=aidepost msgform \
domain.c serv_extensions.c file_ops.c genstamp.c getutline.c \
housekeeping.c html.c ical_dezonify.c imap_fetch.c imap_misc.c \
imap_search.c imap_store.c imap_tools.c internet_addressing.c \
- ipc_c_tcp.c locate_host.c md5.c messages.c serv_autocompletion.c \
+ ipc_c_tcp.c locate_host.c md5.c messages.c \
+ modules/autocompletion/serv_autocompletion.c \
mime_parser.c msgbase.c msgform.c parsedate.c policy.c \
room_ops.c rooms.c routines.c routines2.c euidindex.c \
- screen.c sendcommand.c serv_bio.c serv_calendar.c serv_chat.c \
- serv_crypto.c serv_expire.c serv_imap.c serv_inetcfg.c \
- serv_listsub.c serv_mrtg.c serv_netfilter.c serv_network.c \
- serv_newuser.c serv_pas2.c serv_pop3.c serv_rwho.c serv_smtp.c \
- serv_spam.c serv_mrtg.c serv_spam.c serv_upgrade.c \
- serv_vandelay.c serv_vcard.c serv_managesieve.c server_main.c \
- serv_sieve.c serv_funambol.c setup.c snprintf.c imap_acl.c \
+ screen.c sendcommand.c \
+ modules/bio/serv_bio.c \
+ modules/calendar/serv_calendar.c \
+ modules/chat/serv_chat.c \
+ modules/crypto/serv_crypto.c \
+ modules/expire/serv_expire.c \
+ serv_imap.c \
+ modules/inetcfg/serv_inetcfg.c \
+ modules/listsub/serv_listsub.c \
+ modules/mrtg/serv_mrtg.c \
+ modules/netfilter/serv_netfilter.c \
+ modules/network/serv_network.c \
+ modules/ldap/serv_ldap.c \
+ modules/newuser/serv_newuser.c \
+ modules/pas2/serv_pas2.c \
+ modules/pop3/serv_pop3.c \
+ modules/rwho/serv_rwho.c \
+ modules/smtp/serv_smtp.c \
+ modules/spam/serv_spam.c \
+ modules/upgrade/serv_upgrade.c \
+ modules/vandelay/serv_vandelay.c \
+ modules/vcard/serv_vcard.c \
+ modules/managesieve/serv_managesieve.c \
+ server_main.c \
+ modules/sieve/serv_sieve.c \
+ modules/funambol/serv_funambol.c \
+ setup.c snprintf.c imap_acl.c \
stress.c support.c sysdep.c tools.c user_ops.c userlist.c \
- whobbs.c vcard.c serv_notes.c serv_fulltext.c ft_wordbreaker.c \
+ whobbs.c vcard.c \
+ modules/notes/serv_notes.c \
+ modules/fulltext/serv_fulltext.c \
+ modules/fulltext/ft_wordbreaker.c \
crc16.c journaling.c citadel_dirs.c imap_list.c imap_metadata.c \
modules/test/serv_test.c
file_ops.o msgbase.o euidindex.o \
locate_host.o housekeeping.o mime_parser.o html.o \
internet_addressing.o journaling.o \
- serv_crypto.o parsedate.o genstamp.o \
+ parsedate.o genstamp.o \
clientsocket.o modules_init.o $(AUTH) $(SERV_MODULES)
citserver: $(SERV_OBJS)
+++ /dev/null
-/*
- * $Id$
- *
- * Default wordbreaker module for full text indexing.
- *
- */
-
-
-#include "sysdep.h"
-#include <stdlib.h>
-#include <unistd.h>
-#include <stdio.h>
-#include <fcntl.h>
-#include <signal.h>
-#include <pwd.h>
-#include <errno.h>
-#include <sys/types.h>
-
-#if TIME_WITH_SYS_TIME
-# include <sys/time.h>
-# include <time.h>
-#else
-# if HAVE_SYS_TIME_H
-# include <sys/time.h>
-# else
-# include <time.h>
-# endif
-#endif
-
-#include <sys/wait.h>
-#include <ctype.h>
-#include <string.h>
-#include <limits.h>
-#include "citadel.h"
-#include "server.h"
-#include "sysdep_decls.h"
-#include "citserver.h"
-#include "support.h"
-#include "config.h"
-#include "database.h"
-#include "msgbase.h"
-#include "control.h"
-#include "tools.h"
-#include "ft_wordbreaker.h"
-#include "crc16.h"
-
-/*
- * Noise words are not included in search indices.
- * NOTE: if the noise word list is altered in any way, the FT_WORDBREAKER_ID
- * must also be changed, so that the index is rebuilt.
- */
-static char *noise_words[] = {
- "about",
- "after",
- "all",
- "also",
- "an",
- "and",
- "another",
- "any",
- "are",
- "as",
- "at",
- "be",
- "because",
- "been",
- "before",
- "being",
- "between",
- "both",
- "but",
- "by",
- "came",
- "can",
- "come",
- "could",
- "did",
- "do",
- "each",
- "for",
- "from",
- "get",
- "got",
- "had",
- "has",
- "have",
- "he",
- "her",
- "here",
- "him",
- "himself",
- "his",
- "how",
- "if",
- "in",
- "into",
- "is",
- "it",
- "like",
- "make",
- "many",
- "me",
- "might",
- "more",
- "most",
- "much",
- "must",
- "my",
- "never",
- "now",
- "of",
- "on",
- "only",
- "or",
- "other",
- "our",
- "out",
- "over",
- "said",
- "same",
- "see",
- "should",
- "since",
- "some",
- "still",
- "such",
- "take",
- "than",
- "that",
- "the",
- "their",
- "them",
- "then",
- "there",
- "these",
- "they",
- "this",
- "those",
- "through",
- "to",
- "too",
- "under",
- "up",
- "very",
- "was",
- "way",
- "we",
- "well",
- "were",
- "what",
- "where",
- "which",
- "while",
- "with",
- "would",
- "you",
- "your"
-};
-
-/*
- * Compare function
- */
-int intcmp(const void *rec1, const void *rec2) {
- int i1, i2;
-
- i1 = *(const int *)rec1;
- i2 = *(const int *)rec2;
-
- if (i1 > i2) return(1);
- if (i1 < i2) return(-1);
- return(0);
-}
-
-
-void wordbreaker(char *text, int *num_tokens, int **tokens) {
-
- int wb_num_tokens = 0;
- int wb_num_alloc = 0;
- int *wb_tokens = NULL;
-
- char *ptr;
- char *word_start;
- char *word_end;
- char ch;
- int word_len;
- char word[256];
- int i;
- int word_crc;
-
- if (text == NULL) { /* no NULL text please */
- *num_tokens = 0;
- *tokens = NULL;
- return;
- }
-
- if (text[0] == 0) { /* no empty text either */
- *num_tokens = 0;
- *tokens = NULL;
- return;
- }
-
- ptr = text;
- word_start = NULL;
- while (*ptr) {
- ch = *ptr;
- if (isalnum(ch)) {
- if (!word_start) {
- word_start = ptr;
- }
- }
- ++ptr;
- ch = *ptr;
- if ( (!isalnum(ch)) && (word_start) ) {
- word_end = ptr;
- --word_end;
-
- /* extract the word */
- word_len = word_end - word_start + 1;
- safestrncpy(word, word_start, sizeof word);
- if (word_len >= sizeof word) {
- lprintf(CTDL_DEBUG, "Invalid word length: %d\n", word_len);
- word[(sizeof word_len) - 1] = 0;
- }
- else {
- word[word_len] = 0;
- }
- word_start = NULL;
-
- /* disqualify noise words */
- for (i=0; i<(sizeof(noise_words)/sizeof(char *)); ++i) {
- if (!strcasecmp(word, noise_words[i])) {
- word_len = 0;
- break;
- }
- }
-
- /* are we ok with the length? */
- if ( (word_len >= WB_MIN)
- && (word_len <= WB_MAX) ) {
- for (i=0; i<word_len; ++i) {
- word[i] = tolower(word[i]);
- }
- word_crc = (int)
- CalcCRC16Bytes(word_len, word);
-
- ++wb_num_tokens;
- if (wb_num_tokens > wb_num_alloc) {
- wb_num_alloc += 512;
- wb_tokens = realloc(wb_tokens, (sizeof(int) * wb_num_alloc));
- }
- wb_tokens[wb_num_tokens - 1] = word_crc;
- }
- }
- }
-
- /* sort and purge dups */
- if (wb_num_tokens > 1) {
- qsort(wb_tokens, wb_num_tokens, sizeof(int), intcmp);
- for (i=0; i<(wb_num_tokens-1); ++i) {
- if (wb_tokens[i] == wb_tokens[i+1]) {
- memmove(&wb_tokens[i], &wb_tokens[i+1],
- ((wb_num_tokens - i - 1)*sizeof(int)));
- --wb_num_tokens;
- --i;
- }
- }
- }
-
- *num_tokens = wb_num_tokens;
- *tokens = wb_tokens;
-}
-
+++ /dev/null
-/*
- * $Id$
- *
- */
-
-
-/*
- * This is an ID for the wordbreaker module. If we do pluggable wordbreakers
- * later on, or even if we update this one, we can use a different ID so the
- * system knows it needs to throw away the existing index and rebuild it.
- */
-#define FT_WORDBREAKER_ID 0x001f
-
-/*
- * Minimum and maximum length of words to index
- */
-#define WB_MIN 3
-#define WB_MAX 40
-
-void wordbreaker(char *text, int *num_tokens, int **tokens);
-
--- /dev/null
+/*
+ * $Id$
+ *
+ * Autocompletion of email recipients, etc.
+ */
+
+#include "sysdep.h"
+#include <stdlib.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <fcntl.h>
+#include <ctype.h>
+#include <signal.h>
+#include <pwd.h>
+#include <errno.h>
+#include <sys/types.h>
+
+#if TIME_WITH_SYS_TIME
+# include <sys/time.h>
+# include <time.h>
+#else
+# if HAVE_SYS_TIME_H
+# include <sys/time.h>
+# else
+# include <time.h>
+# endif
+#endif
+
+#include <sys/wait.h>
+#include <string.h>
+#include <limits.h>
+#include "citadel.h"
+#include "server.h"
+#include "citserver.h"
+#include "support.h"
+#include "config.h"
+#include "tools.h"
+#include "msgbase.h"
+#include "user_ops.h"
+#include "room_ops.h"
+#include "database.h"
+#include "vcard.h"
+#include "serv_fulltext.h"
+#include "serv_autocompletion.h"
+
+#include "ctdl_module.h"
+
+
+#ifndef HAVE_SNPRINTF
+#include "snprintf.h"
+#endif
+
+
+
+/*
+ * Convert a structured name into a friendly name. Caller must free the
+ * returned pointer.
+ */
+char *n_to_fn(char *value) {
+ char *nnn = NULL;
+ int i;
+
+ nnn = malloc(strlen(value) + 10);
+ strcpy(nnn, "");
+ extract_token(&nnn[strlen(nnn)] , value, 3, ';', 999);
+ strcat(nnn, " ");
+ extract_token(&nnn[strlen(nnn)] , value, 1, ';', 999);
+ strcat(nnn, " ");
+ extract_token(&nnn[strlen(nnn)] , value, 2, ';', 999);
+ strcat(nnn, " ");
+ extract_token(&nnn[strlen(nnn)] , value, 0, ';', 999);
+ strcat(nnn, " ");
+ extract_token(&nnn[strlen(nnn)] , value, 4, ';', 999);
+ strcat(nnn, " ");
+ for (i=0; i<strlen(nnn); ++i) {
+ if (!strncmp(&nnn[i], " ", 2)) strcpy(&nnn[i], &nnn[i+1]);
+ }
+ striplt(nnn);
+ return(nnn);
+}
+
+
+
+
+/*
+ * Back end for cmd_auto()
+ */
+void hunt_for_autocomplete(long msgnum, char *search_string) {
+ struct CtdlMessage *msg;
+ struct vCard *v;
+ char *value = NULL;
+ char *value2 = NULL;
+ int i = 0;
+ char *nnn = NULL;
+
+ msg = CtdlFetchMessage(msgnum, 1);
+ if (msg == NULL) return;
+
+ v = vcard_load(msg->cm_fields['M']);
+ CtdlFreeMessage(msg);
+
+ /*
+ * Try to match from a friendly name (the "fn" field). If there is
+ * a match, return the entry in the form of:
+ * Display Name <user@domain.org>
+ */
+ value = vcard_get_prop(v, "fn", 0, 0, 0);
+ if (value != NULL) if (bmstrcasestr(value, search_string)) {
+ value2 = vcard_get_prop(v, "email", 1, 0, 0);
+ if (value2 == NULL) value2 = "";
+ cprintf("%s <%s>\n", value, value2);
+ vcard_free(v);
+ return;
+ }
+
+ /*
+ * Try to match from a structured name (the "n" field). If there is
+ * a match, return the entry in the form of:
+ * Display Name <user@domain.org>
+ */
+ value = vcard_get_prop(v, "n", 0, 0, 0);
+ if (value != NULL) if (bmstrcasestr(value, search_string)) {
+
+ value2 = vcard_get_prop(v, "email", 1, 0, 0);
+ if (value2 == NULL) value2 = "";
+ nnn = n_to_fn(value);
+ cprintf("%s <%s>\n", nnn, value2);
+ free(nnn);
+ vcard_free(v);
+ return;
+ }
+
+ /*
+ * Try a partial match on all listed email addresses.
+ */
+ i = 0;
+ while (value = vcard_get_prop(v, "email", 1, i++, 0), value != NULL) {
+ if (bmstrcasestr(value, search_string)) {
+ if (vcard_get_prop(v, "fn", 0, 0, 0)) {
+ cprintf("%s <%s>\n", vcard_get_prop(v, "fn", 0, 0, 0), value);
+ }
+ else if (vcard_get_prop(v, "n", 0, 0, 0)) {
+ nnn = n_to_fn(vcard_get_prop(v, "n", 0, 0, 0));
+ cprintf("%s <%s>\n", nnn, value);
+ free(nnn);
+
+ }
+ else {
+ cprintf("%s\n", value);
+ }
+ vcard_free(v);
+ return;
+ }
+ }
+
+ vcard_free(v);
+}
+
+
+
+/*
+ * Attempt to autocomplete an address based on a partial...
+ */
+void cmd_auto(char *argbuf) {
+ char hold_rm[ROOMNAMELEN];
+ char search_string[256];
+ long *msglist = NULL;
+ int num_msgs = 0;
+ long *fts_msgs = NULL;
+ int fts_num_msgs = 0;
+ struct cdbdata *cdbfr;
+ int r = 0;
+ int i = 0;
+ int j = 0;
+ int search_match = 0;
+ char *rooms_to_try[] = { USERCONTACTSROOM, ADDRESS_BOOK_ROOM };
+
+ if (CtdlAccessCheck(ac_logged_in)) return;
+ extract_token(search_string, argbuf, 0, '|', sizeof search_string);
+ if (strlen(search_string) == 0) {
+ cprintf("%d You supplied an empty partial.\n",
+ ERROR + ILLEGAL_VALUE);
+ return;
+ }
+
+ strcpy(hold_rm, CC->room.QRname); /* save current room */
+ cprintf("%d try these:\n", LISTING_FOLLOWS);
+
+ /*
+ * Gather up message pointers in rooms containing vCards
+ */
+ for (r=0; r < (sizeof(rooms_to_try) / sizeof(char *)); ++r) {
+ if (getroom(&CC->room, rooms_to_try[r]) == 0) {
+ cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long));
+ if (cdbfr != NULL) {
+ msglist = realloc(msglist, (num_msgs * sizeof(long)) + cdbfr->len + 1);
+ memcpy(&msglist[num_msgs], cdbfr->ptr, cdbfr->len);
+ num_msgs += (cdbfr->len / sizeof(long));
+ cdb_free(cdbfr);
+ }
+ }
+ }
+
+ /*
+ * Search-reduce the results if we have the full text index available
+ */
+ if (config.c_enable_fulltext) {
+ ft_search(&fts_num_msgs, &fts_msgs, search_string);
+ if (fts_msgs) {
+ for (i=0; i<num_msgs; ++i) {
+ search_match = 0;
+ for (j=0; j<fts_num_msgs; ++j) {
+ if (msglist[i] == fts_msgs[j]) {
+ search_match = 1;
+ j = fts_num_msgs + 1; /* end the search */
+ }
+ }
+ if (!search_match) {
+ msglist[i] = 0; /* invalidate this result */
+ }
+ }
+ free(fts_msgs);
+ }
+ else {
+ /* If no results, invalidate the whole list */
+ free(msglist);
+ msglist = NULL;
+ num_msgs = 0;
+ }
+ }
+
+ /*
+ * Now output the ones that look interesting
+ */
+ if (num_msgs > 0) for (i=0; i<num_msgs; ++i) {
+ if (msglist[i] != 0) {
+ hunt_for_autocomplete(msglist[i], search_string);
+ }
+ }
+
+ cprintf("000\n");
+ if (strcmp(CC->room.QRname, hold_rm)) {
+ getroom(&CC->room, hold_rm); /* return to saved room */
+ }
+
+ if (msglist) {
+ free(msglist);
+ }
+
+}
+
+
+CTDL_MODULE_INIT(autocompletion) {
+ CtdlRegisterProtoHook(cmd_auto, "AUTO", "Do recipient autocompletion");
+
+ /* return our Subversion id for the Log */
+ return "$Id$";
+}
--- /dev/null
+/*
+ * $Id$
+ *
+ */
+
+
+char *serv_autocompletion_init(void);
--- /dev/null
+/*
+ * $Id$
+ *
+ * This module implementsserver commands related to the display and
+ * manipulation of user "bio" files.
+ *
+ */
+
+#include "sysdep.h"
+#include <stdlib.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <pwd.h>
+#include <errno.h>
+#include <sys/types.h>
+
+#if TIME_WITH_SYS_TIME
+# include <sys/time.h>
+# include <time.h>
+#else
+# if HAVE_SYS_TIME_H
+# include <sys/time.h>
+# else
+# include <time.h>
+# endif
+#endif
+
+#include <sys/wait.h>
+#include <string.h>
+#include <limits.h>
+#include "citadel.h"
+#include "server.h"
+#include "citserver.h"
+#include "support.h"
+#include "config.h"
+#include "control.h"
+#include "room_ops.h"
+#include "user_ops.h"
+#include "policy.h"
+#include "database.h"
+#include "msgbase.h"
+#include "tools.h"
+#include "citadel_dirs.h"
+
+#include "ctdl_module.h"
+
+/*
+ * enter user bio
+ */
+void cmd_ebio(char *cmdbuf) {
+ char buf[SIZ];
+ FILE *fp;
+
+ unbuffer_output();
+
+ if (!(CC->logged_in)) {
+ cprintf("%d Not logged in.\n",ERROR + NOT_LOGGED_IN);
+ return;
+ }
+
+ snprintf(buf, sizeof buf, "%s%ld",ctdl_bio_dir,CC->user.usernum);
+ fp = fopen(buf,"w");
+ if (fp == NULL) {
+ cprintf("%d Cannot create file: %s\n", ERROR + INTERNAL_ERROR,
+ strerror(errno));
+ return;
+ }
+ cprintf("%d \n",SEND_LISTING);
+ while(client_getln(buf, sizeof buf), strcmp(buf,"000")) {
+ if (ftell(fp) < config.c_maxmsglen) {
+ fprintf(fp,"%s\n",buf);
+ }
+ }
+ fclose(fp);
+}
+
+/*
+ * read user bio
+ */
+void cmd_rbio(char *cmdbuf)
+{
+ struct ctdluser ruser;
+ char buf[256];
+ FILE *fp;
+
+ extract_token(buf, cmdbuf, 0, '|', sizeof buf);
+ if (getuser(&ruser, buf) != 0) {
+ cprintf("%d No such user.\n",ERROR + NO_SUCH_USER);
+ return;
+ }
+ snprintf(buf, sizeof buf, "%s%ld",ctdl_bio_dir,ruser.usernum);
+
+ cprintf("%d OK|%s|%ld|%d|%ld|%ld|%ld\n", LISTING_FOLLOWS,
+ ruser.fullname, ruser.usernum, ruser.axlevel,
+ (long)ruser.lastcall, ruser.timescalled, ruser.posted);
+ fp = fopen(buf,"r");
+ if (fp == NULL)
+ cprintf("%s has no bio on file.\n", ruser.fullname);
+ else {
+ while (fgets(buf, sizeof buf, fp) != NULL) cprintf("%s",buf);
+ fclose(fp);
+ }
+ cprintf("000\n");
+}
+
+/*
+ * list of users who have entered bios
+ */
+void cmd_lbio(char *cmdbuf) {
+ char buf[256];
+ FILE *ls;
+ struct ctdluser usbuf;
+ char listbios[256];
+
+ snprintf(listbios, sizeof(listbios),"cd %s; ls",ctdl_bio_dir);
+ ls = popen(listbios, "r");
+ if (ls == NULL) {
+ cprintf("%d Cannot open listing.\n", ERROR + FILE_NOT_FOUND);
+ return;
+ }
+
+ cprintf("%d\n", LISTING_FOLLOWS);
+ while (fgets(buf, sizeof buf, ls)!=NULL)
+ if (getuserbynumber(&usbuf,atol(buf))==0)
+ cprintf("%s\n", usbuf.fullname);
+ pclose(ls);
+ cprintf("000\n");
+}
+
+
+
+
+CTDL_MODULE_INIT(bio)
+{
+ CtdlRegisterProtoHook(cmd_ebio, "EBIO", "Enter your bio");
+ CtdlRegisterProtoHook(cmd_rbio, "RBIO", "Read a user's bio");
+ CtdlRegisterProtoHook(cmd_lbio, "LBIO", "List users with bios");
+
+ /* return our Subversion id for the Log */
+ return "$Id$";
+}
+
+
--- /dev/null
+/*
+ * $Id$
+ *
+ * This module implements iCalendar object processing and the Calendar>
+ * room on a Citadel server. It handles iCalendar objects using the
+ * iTIP protocol. See RFCs 2445 and 2446.
+ *
+ */
+
+#define PRODID "-//Citadel//NONSGML Citadel Calendar//EN"
+
+#include "sysdep.h"
+#include <stdlib.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <limits.h>
+#include <stdio.h>
+#include <string.h>
+#ifdef HAVE_STRINGS_H
+#include <strings.h>
+#endif
+#include "citadel.h"
+#include "server.h"
+#include "citserver.h"
+#include "support.h"
+#include "config.h"
+#include "user_ops.h"
+#include "room_ops.h"
+#include "tools.h"
+#include "msgbase.h"
+#include "mime_parser.h"
+#include "internet_addressing.h"
+#include "serv_calendar.h"
+#include "euidindex.h"
+#include "ctdl_module.h"
+
+#ifdef CITADEL_WITH_CALENDAR_SERVICE
+
+#include <ical.h>
+#include "ical_dezonify.h"
+
+
+
+struct ical_respond_data {
+ char desired_partnum[SIZ];
+ icalcomponent *cal;
+};
+
+
+/*
+ * Utility function to create a new VCALENDAR component with some of the
+ * required fields already set the way we like them.
+ */
+icalcomponent *icalcomponent_new_citadel_vcalendar(void) {
+ icalcomponent *encaps;
+
+ encaps = icalcomponent_new_vcalendar();
+ if (encaps == NULL) {
+ lprintf(CTDL_CRIT, "Error at %s:%d - could not allocate component!\n",
+ __FILE__, __LINE__);
+ return NULL;
+ }
+
+ /* Set the Product ID */
+ icalcomponent_add_property(encaps, icalproperty_new_prodid(PRODID));
+
+ /* Set the Version Number */
+ icalcomponent_add_property(encaps, icalproperty_new_version("2.0"));
+
+ return(encaps);
+}
+
+
+/*
+ * Utility function to encapsulate a subcomponent into a full VCALENDAR
+ */
+icalcomponent *ical_encapsulate_subcomponent(icalcomponent *subcomp) {
+ icalcomponent *encaps;
+
+ /* If we're already looking at a full VCALENDAR component,
+ * don't bother ... just return itself.
+ */
+ if (icalcomponent_isa(subcomp) == ICAL_VCALENDAR_COMPONENT) {
+ return subcomp;
+ }
+
+ /* Encapsulate the VEVENT component into a complete VCALENDAR */
+ encaps = icalcomponent_new_citadel_vcalendar();
+ if (encaps == NULL) return NULL;
+
+ /* Encapsulate the subcomponent inside */
+ icalcomponent_add_component(encaps, subcomp);
+
+ /* Convert all timestamps to UTC so we don't have to deal with
+ * stupid VTIMEZONE crap.
+ */
+ ical_dezonify(encaps);
+
+ /* Return the object we just created. */
+ return(encaps);
+}
+
+
+
+
+/*
+ * Write a calendar object into the specified user's calendar room.
+ */
+void ical_write_to_cal(struct ctdluser *u, icalcomponent *cal) {
+ char temp[PATH_MAX];
+ FILE *fp;
+ char *ser;
+ icalcomponent *encaps;
+
+ if (cal == NULL) return;
+
+ /* If the supplied object is a subcomponent, encapsulate it in
+ * a full VCALENDAR component, and save that instead.
+ */
+ if (icalcomponent_isa(cal) != ICAL_VCALENDAR_COMPONENT) {
+ encaps = ical_encapsulate_subcomponent(
+ icalcomponent_new_clone(cal)
+ );
+ ical_write_to_cal(u, encaps);
+ icalcomponent_free(encaps);
+ return;
+ }
+
+ CtdlMakeTempFileName(temp, sizeof temp);
+ ser = icalcomponent_as_ical_string(cal);
+ if (ser == NULL) return;
+
+ /* Make a temp file out of it */
+ fp = fopen(temp, "w");
+ if (fp == NULL) return;
+ fwrite(ser, strlen(ser), 1, fp);
+ fclose(fp);
+
+ /* This handy API function does all the work for us.
+ */
+ CtdlWriteObject(USERCALENDARROOM, /* which room */
+ "text/calendar", /* MIME type */
+ temp, /* temp file */
+ u, /* which user */
+ 0, /* not binary */
+ 0, /* don't delete others of this type */
+ 0); /* no flags */
+
+ unlink(temp);
+}
+
+
+/*
+ * Add a calendar object to the user's calendar
+ *
+ * ok because it uses ical_write_to_cal()
+ */
+void ical_add(icalcomponent *cal, int recursion_level) {
+ icalcomponent *c;
+
+ /*
+ * The VEVENT subcomponent is the one we're interested in saving.
+ */
+ if (icalcomponent_isa(cal) == ICAL_VEVENT_COMPONENT) {
+
+ ical_write_to_cal(&CC->user, cal);
+
+ }
+
+ /* If the component has subcomponents, recurse through them. */
+ for (c = icalcomponent_get_first_component(cal, ICAL_ANY_COMPONENT);
+ (c != 0);
+ c = icalcomponent_get_next_component(cal, ICAL_ANY_COMPONENT)) {
+ /* Recursively process subcomponent */
+ ical_add(c, recursion_level+1);
+ }
+
+}
+
+
+
+/*
+ * Send a reply to a meeting invitation.
+ *
+ * 'request' is the invitation to reply to.
+ * 'action' is the string "accept" or "decline" or "tentative".
+ *
+ */
+void ical_send_a_reply(icalcomponent *request, char *action) {
+ icalcomponent *the_reply = NULL;
+ icalcomponent *vevent = NULL;
+ icalproperty *attendee = NULL;
+ char attendee_string[SIZ];
+ icalproperty *organizer = NULL;
+ char organizer_string[SIZ];
+ icalproperty *summary = NULL;
+ char summary_string[SIZ];
+ icalproperty *me_attend = NULL;
+ struct recptypes *recp = NULL;
+ icalparameter *partstat = NULL;
+ char *serialized_reply = NULL;
+ char *reply_message_text = NULL;
+ struct CtdlMessage *msg = NULL;
+ struct recptypes *valid = NULL;
+
+ strcpy(organizer_string, "");
+ strcpy(summary_string, "Calendar item");
+
+ if (request == NULL) {
+ lprintf(CTDL_ERR, "ERROR: trying to reply to NULL event?\n");
+ return;
+ }
+
+ the_reply = icalcomponent_new_clone(request);
+ if (the_reply == NULL) {
+ lprintf(CTDL_ERR, "ERROR: cannot clone request\n");
+ return;
+ }
+
+ /* Change the method from REQUEST to REPLY */
+ icalcomponent_set_method(the_reply, ICAL_METHOD_REPLY);
+
+ vevent = icalcomponent_get_first_component(the_reply, ICAL_VEVENT_COMPONENT);
+ if (vevent != NULL) {
+ /* Hunt for attendees, removing ones that aren't us.
+ * (Actually, remove them all, cloning our own one so we can
+ * re-insert it later)
+ */
+ while (attendee = icalcomponent_get_first_property(vevent,
+ ICAL_ATTENDEE_PROPERTY), (attendee != NULL)
+ ) {
+ if (icalproperty_get_attendee(attendee)) {
+ strcpy(attendee_string,
+ icalproperty_get_attendee(attendee) );
+ if (!strncasecmp(attendee_string, "MAILTO:", 7)) {
+ strcpy(attendee_string, &attendee_string[7]);
+ striplt(attendee_string);
+ recp = validate_recipients(attendee_string);
+ if (recp != NULL) {
+ if (!strcasecmp(recp->recp_local, CC->user.fullname)) {
+ if (me_attend) icalproperty_free(me_attend);
+ me_attend = icalproperty_new_clone(attendee);
+ }
+ free_recipients(recp);
+ }
+ }
+ }
+ /* Remove it... */
+ icalcomponent_remove_property(vevent, attendee);
+ icalproperty_free(attendee);
+ }
+
+ /* We found our own address in the attendee list. */
+ if (me_attend) {
+ /* Change the partstat from NEEDS-ACTION to ACCEPT or DECLINE */
+ icalproperty_remove_parameter(me_attend, ICAL_PARTSTAT_PARAMETER);
+
+ if (!strcasecmp(action, "accept")) {
+ partstat = icalparameter_new_partstat(ICAL_PARTSTAT_ACCEPTED);
+ }
+ else if (!strcasecmp(action, "decline")) {
+ partstat = icalparameter_new_partstat(ICAL_PARTSTAT_DECLINED);
+ }
+ else if (!strcasecmp(action, "tentative")) {
+ partstat = icalparameter_new_partstat(ICAL_PARTSTAT_TENTATIVE);
+ }
+
+ if (partstat) icalproperty_add_parameter(me_attend, partstat);
+
+ /* Now insert it back into the vevent. */
+ icalcomponent_add_property(vevent, me_attend);
+ }
+
+ /* Figure out who to send this thing to */
+ organizer = icalcomponent_get_first_property(vevent, ICAL_ORGANIZER_PROPERTY);
+ if (organizer != NULL) {
+ if (icalproperty_get_organizer(organizer)) {
+ strcpy(organizer_string,
+ icalproperty_get_organizer(organizer) );
+ }
+ }
+ if (!strncasecmp(organizer_string, "MAILTO:", 7)) {
+ strcpy(organizer_string, &organizer_string[7]);
+ striplt(organizer_string);
+ } else {
+ strcpy(organizer_string, "");
+ }
+
+ /* Extract the summary string -- we'll use it as the
+ * message subject for the reply
+ */
+ summary = icalcomponent_get_first_property(vevent, ICAL_SUMMARY_PROPERTY);
+ if (summary != NULL) {
+ if (icalproperty_get_summary(summary)) {
+ strcpy(summary_string,
+ icalproperty_get_summary(summary) );
+ }
+ }
+ }
+
+ /* Now generate the reply message and send it out. */
+ serialized_reply = strdup(icalcomponent_as_ical_string(the_reply));
+ icalcomponent_free(the_reply); /* don't need this anymore */
+ if (serialized_reply == NULL) return;
+
+ reply_message_text = malloc(strlen(serialized_reply) + SIZ);
+ if (reply_message_text != NULL) {
+ sprintf(reply_message_text,
+ "Content-type: text/calendar charset=\"utf-8\"\r\n\r\n%s\r\n",
+ serialized_reply
+ );
+
+ msg = CtdlMakeMessage(&CC->user,
+ organizer_string, /* to */
+ "", /* cc */
+ CC->room.QRname, 0, FMT_RFC822,
+ "",
+ "",
+ summary_string, /* Use summary for subject */
+ NULL,
+ reply_message_text);
+
+ if (msg != NULL) {
+ valid = validate_recipients(organizer_string);
+ CtdlSubmitMsg(msg, valid, "");
+ CtdlFreeMessage(msg);
+ free_recipients(valid);
+ }
+ }
+ free(serialized_reply);
+}
+
+
+
+/*
+ * Callback function for mime parser that hunts for calendar content types
+ * and turns them into calendar objects
+ */
+void ical_locate_part(char *name, char *filename, char *partnum, char *disp,
+ void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
+ void *cbuserdata) {
+
+ struct ical_respond_data *ird = NULL;
+
+ ird = (struct ical_respond_data *) cbuserdata;
+
+ /* desired_partnum can be set to "_HUNT_" to have it just look for
+ * the first part with a content type of text/calendar. Otherwise
+ * we have to only process the right one.
+ */
+ if (strcasecmp(ird->desired_partnum, "_HUNT_")) {
+ if (strcasecmp(partnum, ird->desired_partnum)) {
+ return;
+ }
+ }
+
+ if (strcasecmp(cbtype, "text/calendar")) {
+ return;
+ }
+
+ if (ird->cal != NULL) {
+ icalcomponent_free(ird->cal);
+ ird->cal = NULL;
+ }
+
+ ird->cal = icalcomponent_new_from_string(content);
+ if (ird->cal != NULL) {
+ ical_dezonify(ird->cal);
+ }
+}
+
+
+/*
+ * Respond to a meeting request.
+ */
+void ical_respond(long msgnum, char *partnum, char *action) {
+ struct CtdlMessage *msg = NULL;
+ struct ical_respond_data ird;
+
+ if (
+ (strcasecmp(action, "accept"))
+ && (strcasecmp(action, "decline"))
+ ) {
+ cprintf("%d Action must be 'accept' or 'decline'\n",
+ ERROR + ILLEGAL_VALUE
+ );
+ return;
+ }
+
+ msg = CtdlFetchMessage(msgnum, 1);
+ if (msg == NULL) {
+ cprintf("%d Message %ld not found.\n",
+ ERROR + ILLEGAL_VALUE,
+ (long)msgnum
+ );
+ return;
+ }
+
+ memset(&ird, 0, sizeof ird);
+ strcpy(ird.desired_partnum, partnum);
+ mime_parser(msg->cm_fields['M'],
+ NULL,
+ *ical_locate_part, /* callback function */
+ NULL, NULL,
+ (void *) &ird, /* user data */
+ 0
+ );
+
+ /* We're done with the incoming message, because we now have a
+ * calendar object in memory.
+ */
+ CtdlFreeMessage(msg);
+
+ /*
+ * Here is the real meat of this function. Handle the event.
+ */
+ if (ird.cal != NULL) {
+ /* Save this in the user's calendar if necessary */
+ if (!strcasecmp(action, "accept")) {
+ ical_add(ird.cal, 0);
+ }
+
+ /* Send a reply if necessary */
+ if (icalcomponent_get_method(ird.cal) == ICAL_METHOD_REQUEST) {
+ ical_send_a_reply(ird.cal, action);
+ }
+
+ /* Now that we've processed this message, we don't need it
+ * anymore. So delete it. (NOTE we don't do this anymore.)
+ CtdlDeleteMessages(CC->room.QRname, &msgnum, 1, "");
+ */
+
+ /* Free the memory we allocated and return a response. */
+ icalcomponent_free(ird.cal);
+ ird.cal = NULL;
+ cprintf("%d ok\n", CIT_OK);
+ return;
+ }
+ else {
+ cprintf("%d No calendar object found\n", ERROR + ROOM_NOT_FOUND);
+ return;
+ }
+
+ /* should never get here */
+}
+
+
+/*
+ * Figure out the UID of the calendar event being referred to in a
+ * REPLY object. This function is recursive.
+ */
+void ical_learn_uid_of_reply(char *uidbuf, icalcomponent *cal) {
+ icalcomponent *subcomponent;
+ icalproperty *p;
+
+ /* If this object is a REPLY, then extract the UID. */
+ if (icalcomponent_isa(cal) == ICAL_VEVENT_COMPONENT) {
+ p = icalcomponent_get_first_property(cal, ICAL_UID_PROPERTY);
+ if (p != NULL) {
+ strcpy(uidbuf, icalproperty_get_comment(p));
+ }
+ }
+
+ /* Otherwise, recurse through any VEVENT subcomponents. We do NOT want the
+ * UID of the reply; we want the UID of the invitation being replied to.
+ */
+ for (subcomponent = icalcomponent_get_first_component(cal, ICAL_VEVENT_COMPONENT);
+ subcomponent != NULL;
+ subcomponent = icalcomponent_get_next_component(cal, ICAL_VEVENT_COMPONENT) ) {
+ ical_learn_uid_of_reply(uidbuf, subcomponent);
+ }
+}
+
+
+/*
+ * ical_update_my_calendar_with_reply() refers to this callback function; when we
+ * locate the message containing the calendar event we're replying to, this function
+ * gets called. It basically just sticks the message number in a supplied buffer.
+ */
+void ical_hunt_for_event_to_update(long msgnum, void *data) {
+ long *msgnumptr;
+
+ msgnumptr = (long *) data;
+ *msgnumptr = msgnum;
+}
+
+
+struct original_event_container {
+ icalcomponent *c;
+};
+
+/*
+ * Callback function for mime parser that hunts for calendar content types
+ * and turns them into calendar objects (called by ical_update_my_calendar_with_reply()
+ * to fetch the object being updated)
+ */
+void ical_locate_original_event(char *name, char *filename, char *partnum, char *disp,
+ void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
+ void *cbuserdata) {
+
+ struct original_event_container *oec = NULL;
+
+ if (strcasecmp(cbtype, "text/calendar")) {
+ return;
+ }
+ oec = (struct original_event_container *) cbuserdata;
+ if (oec->c != NULL) {
+ icalcomponent_free(oec->c);
+ }
+ oec->c = icalcomponent_new_from_string(content);
+}
+
+
+/*
+ * Merge updated attendee information from a REPLY into an existing event.
+ */
+void ical_merge_attendee_reply(icalcomponent *event, icalcomponent *reply) {
+ icalcomponent *c;
+ icalproperty *e_attendee, *r_attendee;
+
+ /* First things first. If we're not looking at a VEVENT component,
+ * recurse through subcomponents until we find one.
+ */
+ if (icalcomponent_isa(event) != ICAL_VEVENT_COMPONENT) {
+ for (c = icalcomponent_get_first_component(event, ICAL_VEVENT_COMPONENT);
+ c != NULL;
+ c = icalcomponent_get_next_component(event, ICAL_VEVENT_COMPONENT) ) {
+ ical_merge_attendee_reply(c, reply);
+ }
+ return;
+ }
+
+ /* Now do the same thing with the reply.
+ */
+ if (icalcomponent_isa(reply) != ICAL_VEVENT_COMPONENT) {
+ for (c = icalcomponent_get_first_component(reply, ICAL_VEVENT_COMPONENT);
+ c != NULL;
+ c = icalcomponent_get_next_component(reply, ICAL_VEVENT_COMPONENT) ) {
+ ical_merge_attendee_reply(event, c);
+ }
+ return;
+ }
+
+ /* Clone the reply, because we're going to rip its guts out. */
+ reply = icalcomponent_new_clone(reply);
+
+ /* At this point we're looking at the correct subcomponents.
+ * Iterate through the attendees looking for a match.
+ */
+STARTOVER:
+ for (e_attendee = icalcomponent_get_first_property(event, ICAL_ATTENDEE_PROPERTY);
+ e_attendee != NULL;
+ e_attendee = icalcomponent_get_next_property(event, ICAL_ATTENDEE_PROPERTY)) {
+
+ for (r_attendee = icalcomponent_get_first_property(reply, ICAL_ATTENDEE_PROPERTY);
+ r_attendee != NULL;
+ r_attendee = icalcomponent_get_next_property(reply, ICAL_ATTENDEE_PROPERTY)) {
+
+ /* Check to see if these two attendees match...
+ */
+ if (!strcasecmp(
+ icalproperty_get_attendee(e_attendee),
+ icalproperty_get_attendee(r_attendee)
+ )) {
+ /* ...and if they do, remove the attendee from the event
+ * and replace it with the attendee from the reply. (The
+ * reply's copy will have the same address, but an updated
+ * status.)
+ */
+ icalcomponent_remove_property(event, e_attendee);
+ icalproperty_free(e_attendee);
+ icalcomponent_remove_property(reply, r_attendee);
+ icalcomponent_add_property(event, r_attendee);
+
+ /* Since we diddled both sets of attendees, we have to start
+ * the iteration over again. This will not create an infinite
+ * loop because we removed the attendee from the reply. (That's
+ * why we cloned the reply, and that's what we mean by "ripping
+ * its guts out.")
+ */
+ goto STARTOVER;
+ }
+
+ }
+ }
+
+ /* Free the *clone* of the reply. */
+ icalcomponent_free(reply);
+}
+
+
+
+
+/*
+ * Handle an incoming RSVP (object with method==ICAL_METHOD_REPLY) for a
+ * calendar event. The object has already been deserialized for us; all
+ * we have to do here is hunt for the event in our calendar, merge in the
+ * updated attendee status, and save it again.
+ *
+ * This function returns 0 on success, 1 if the event was not found in the
+ * user's calendar, or 2 if an internal error occurred.
+ */
+int ical_update_my_calendar_with_reply(icalcomponent *cal) {
+ char uid[SIZ];
+ char hold_rm[ROOMNAMELEN];
+ long msgnum_being_replaced = 0;
+ struct CtdlMessage *msg = NULL;
+ struct original_event_container oec;
+ icalcomponent *original_event;
+ char *serialized_event = NULL;
+ char roomname[ROOMNAMELEN];
+ char *message_text = NULL;
+
+ /* Figure out just what event it is we're dealing with */
+ strcpy(uid, "--==<< InVaLiD uId >>==--");
+ ical_learn_uid_of_reply(uid, cal);
+ lprintf(CTDL_DEBUG, "UID of event being replied to is <%s>\n", uid);
+
+ strcpy(hold_rm, CC->room.QRname); /* save current room */
+
+ if (getroom(&CC->room, USERCALENDARROOM) != 0) {
+ getroom(&CC->room, hold_rm);
+ lprintf(CTDL_CRIT, "cannot get user calendar room\n");
+ return(2);
+ }
+
+ /*
+ * Look in the EUID index for a message with
+ * the Citadel EUID set to the value we're looking for. Since
+ * Citadel always sets the message EUID to the vCalendar UID of
+ * the event, this will work.
+ */
+ msgnum_being_replaced = locate_message_by_euid(uid, &CC->room);
+
+ getroom(&CC->room, hold_rm); /* return to saved room */
+
+ lprintf(CTDL_DEBUG, "msgnum_being_replaced == %ld\n", msgnum_being_replaced);
+ if (msgnum_being_replaced == 0) {
+ return(1); /* no calendar event found */
+ }
+
+ /* Now we know the ID of the message containing the event being updated.
+ * We don't actually have to delete it; that'll get taken care of by the
+ * server when we save another event with the same UID. This just gives
+ * us the ability to load the event into memory so we can diddle the
+ * attendees.
+ */
+ msg = CtdlFetchMessage(msgnum_being_replaced, 1);
+ if (msg == NULL) {
+ return(2); /* internal error */
+ }
+ oec.c = NULL;
+ mime_parser(msg->cm_fields['M'],
+ NULL,
+ *ical_locate_original_event, /* callback function */
+ NULL, NULL,
+ &oec, /* user data */
+ 0
+ );
+ CtdlFreeMessage(msg);
+
+ original_event = oec.c;
+ if (original_event == NULL) {
+ lprintf(CTDL_ERR, "ERROR: Original_component is NULL.\n");
+ return(2);
+ }
+
+ /* Merge the attendee's updated status into the event */
+ ical_merge_attendee_reply(original_event, cal);
+
+ /* Serialize it */
+ serialized_event = strdup(icalcomponent_as_ical_string(original_event));
+ icalcomponent_free(original_event); /* Don't need this anymore. */
+ if (serialized_event == NULL) return(2);
+
+ MailboxName(roomname, sizeof roomname, &CC->user, USERCALENDARROOM);
+
+ message_text = malloc(strlen(serialized_event) + SIZ);
+ if (message_text != NULL) {
+ sprintf(message_text,
+ "Content-type: text/calendar charset=\"utf-8\"\r\n\r\n%s\r\n",
+ serialized_event
+ );
+
+ msg = CtdlMakeMessage(&CC->user,
+ "", /* No recipient */
+ "", /* No recipient */
+ roomname,
+ 0, FMT_RFC822,
+ "",
+ "",
+ "", /* no subject */
+ NULL,
+ message_text);
+
+ if (msg != NULL) {
+ CIT_ICAL->avoid_sending_invitations = 1;
+ CtdlSubmitMsg(msg, NULL, roomname);
+ CtdlFreeMessage(msg);
+ CIT_ICAL->avoid_sending_invitations = 0;
+ }
+ }
+ free(serialized_event);
+ return(0);
+}
+
+
+/*
+ * Handle an incoming RSVP for an event. (This is the server subcommand part; it
+ * simply extracts the calendar object from the message, deserializes it, and
+ * passes it up to ical_update_my_calendar_with_reply() for processing.
+ */
+void ical_handle_rsvp(long msgnum, char *partnum, char *action) {
+ struct CtdlMessage *msg = NULL;
+ struct ical_respond_data ird;
+ int ret;
+
+ if (
+ (strcasecmp(action, "update"))
+ && (strcasecmp(action, "ignore"))
+ ) {
+ cprintf("%d Action must be 'update' or 'ignore'\n",
+ ERROR + ILLEGAL_VALUE
+ );
+ return;
+ }
+
+ msg = CtdlFetchMessage(msgnum, 1);
+ if (msg == NULL) {
+ cprintf("%d Message %ld not found.\n",
+ ERROR + ILLEGAL_VALUE,
+ (long)msgnum
+ );
+ return;
+ }
+
+ memset(&ird, 0, sizeof ird);
+ strcpy(ird.desired_partnum, partnum);
+ mime_parser(msg->cm_fields['M'],
+ NULL,
+ *ical_locate_part, /* callback function */
+ NULL, NULL,
+ (void *) &ird, /* user data */
+ 0
+ );
+
+ /* We're done with the incoming message, because we now have a
+ * calendar object in memory.
+ */
+ CtdlFreeMessage(msg);
+
+ /*
+ * Here is the real meat of this function. Handle the event.
+ */
+ if (ird.cal != NULL) {
+ /* Update the user's calendar if necessary */
+ if (!strcasecmp(action, "update")) {
+ ret = ical_update_my_calendar_with_reply(ird.cal);
+ if (ret == 0) {
+ cprintf("%d Your calendar has been updated with this reply.\n",
+ CIT_OK);
+ }
+ else if (ret == 1) {
+ cprintf("%d This event does not exist in your calendar.\n",
+ ERROR + FILE_NOT_FOUND);
+ }
+ else {
+ cprintf("%d An internal error occurred.\n",
+ ERROR + INTERNAL_ERROR);
+ }
+ }
+ else {
+ cprintf("%d This reply has been ignored.\n", CIT_OK);
+ }
+
+ /* Now that we've processed this message, we don't need it
+ * anymore. So delete it. (Don't do this anymore.)
+ CtdlDeleteMessages(CC->room.QRname, &msgnum, 1, "");
+ */
+
+ /* Free the memory we allocated and return a response. */
+ icalcomponent_free(ird.cal);
+ ird.cal = NULL;
+ return;
+ }
+ else {
+ cprintf("%d No calendar object found\n", ERROR + ROOM_NOT_FOUND);
+ return;
+ }
+
+ /* should never get here */
+}
+
+
+/*
+ * Search for a property in both the top level and in a VEVENT subcomponent
+ */
+icalproperty *ical_ctdl_get_subprop(
+ icalcomponent *cal,
+ icalproperty_kind which_prop
+) {
+ icalproperty *p;
+ icalcomponent *c;
+
+ p = icalcomponent_get_first_property(cal, which_prop);
+ if (p == NULL) {
+ c = icalcomponent_get_first_component(cal,
+ ICAL_VEVENT_COMPONENT);
+ if (c != NULL) {
+ p = icalcomponent_get_first_property(c, which_prop);
+ }
+ }
+ return p;
+}
+
+
+/*
+ * Check to see if two events overlap. Returns nonzero if they do.
+ * (This function is used in both Citadel and WebCit. If you change it in
+ * one place, change it in the other. Better yet, put it in a library.)
+ */
+int ical_ctdl_is_overlap(
+ struct icaltimetype t1start,
+ struct icaltimetype t1end,
+ struct icaltimetype t2start,
+ struct icaltimetype t2end
+) {
+
+ if (icaltime_is_null_time(t1start)) return(0);
+ if (icaltime_is_null_time(t2start)) return(0);
+
+ /* First, check for all-day events */
+ if (t1start.is_date) {
+ if (!icaltime_compare_date_only(t1start, t2start)) {
+ return(1);
+ }
+ if (!icaltime_is_null_time(t2end)) {
+ if (!icaltime_compare_date_only(t1start, t2end)) {
+ return(1);
+ }
+ }
+ }
+
+ if (t2start.is_date) {
+ if (!icaltime_compare_date_only(t2start, t1start)) {
+ return(1);
+ }
+ if (!icaltime_is_null_time(t1end)) {
+ if (!icaltime_compare_date_only(t2start, t1end)) {
+ return(1);
+ }
+ }
+ }
+
+ /* Now check for overlaps using date *and* time. */
+
+ /* First, bail out if either event 1 or event 2 is missing end time. */
+ if (icaltime_is_null_time(t1end)) return(0);
+ if (icaltime_is_null_time(t2end)) return(0);
+
+ /* If event 1 ends before event 2 starts, we're in the clear. */
+ if (icaltime_compare(t1end, t2start) <= 0) return(0);
+
+ /* If event 2 ends before event 1 starts, we're also ok. */
+ if (icaltime_compare(t2end, t1start) <= 0) return(0);
+
+ /* Otherwise, they overlap. */
+ return(1);
+}
+
+
+
+/*
+ * Backend for ical_hunt_for_conflicts()
+ */
+void ical_hunt_for_conflicts_backend(long msgnum, void *data) {
+ icalcomponent *cal;
+ struct CtdlMessage *msg = NULL;
+ struct ical_respond_data ird;
+ struct icaltimetype t1start, t1end, t2start, t2end;
+ icalproperty *p;
+ char conflict_event_uid[SIZ];
+ char conflict_event_summary[SIZ];
+ char compare_uid[SIZ];
+
+ cal = (icalcomponent *)data;
+ strcpy(compare_uid, "");
+ strcpy(conflict_event_uid, "");
+ strcpy(conflict_event_summary, "");
+
+ msg = CtdlFetchMessage(msgnum, 1);
+ if (msg == NULL) return;
+ memset(&ird, 0, sizeof ird);
+ strcpy(ird.desired_partnum, "_HUNT_");
+ mime_parser(msg->cm_fields['M'],
+ NULL,
+ *ical_locate_part, /* callback function */
+ NULL, NULL,
+ (void *) &ird, /* user data */
+ 0
+ );
+ CtdlFreeMessage(msg);
+
+ if (ird.cal == NULL) return;
+
+ t1start = icaltime_null_time();
+ t1end = icaltime_null_time();
+ t2start = icaltime_null_time();
+ t1end = icaltime_null_time();
+
+ /* Now compare cal to ird.cal */
+ p = ical_ctdl_get_subprop(ird.cal, ICAL_DTSTART_PROPERTY);
+ if (p == NULL) return;
+ if (p != NULL) t2start = icalproperty_get_dtstart(p);
+
+ p = ical_ctdl_get_subprop(ird.cal, ICAL_DTEND_PROPERTY);
+ if (p != NULL) t2end = icalproperty_get_dtend(p);
+
+ p = ical_ctdl_get_subprop(cal, ICAL_DTSTART_PROPERTY);
+ if (p == NULL) return;
+ if (p != NULL) t1start = icalproperty_get_dtstart(p);
+
+ p = ical_ctdl_get_subprop(cal, ICAL_DTEND_PROPERTY);
+ if (p != NULL) t1end = icalproperty_get_dtend(p);
+
+ p = ical_ctdl_get_subprop(cal, ICAL_UID_PROPERTY);
+ if (p != NULL) {
+ strcpy(compare_uid, icalproperty_get_comment(p));
+ }
+
+ p = ical_ctdl_get_subprop(ird.cal, ICAL_UID_PROPERTY);
+ if (p != NULL) {
+ strcpy(conflict_event_uid, icalproperty_get_comment(p));
+ }
+
+ p = ical_ctdl_get_subprop(ird.cal, ICAL_SUMMARY_PROPERTY);
+ if (p != NULL) {
+ strcpy(conflict_event_summary, icalproperty_get_comment(p));
+ }
+
+ icalcomponent_free(ird.cal);
+
+ if (ical_ctdl_is_overlap(t1start, t1end, t2start, t2end)) {
+ cprintf("%ld||%s|%s|%d|\n",
+ msgnum,
+ conflict_event_uid,
+ conflict_event_summary,
+ ( ((strlen(compare_uid)>0)
+ &&(!strcasecmp(compare_uid,
+ conflict_event_uid))) ? 1 : 0
+ )
+ );
+ }
+}
+
+
+
+/*
+ * Phase 2 of "hunt for conflicts" operation.
+ * At this point we have a calendar object which represents the VEVENT that
+ * we're considering adding to the calendar. Now hunt through the user's
+ * calendar room, and output zero or more existing VEVENTs which conflict
+ * with this one.
+ */
+void ical_hunt_for_conflicts(icalcomponent *cal) {
+ char hold_rm[ROOMNAMELEN];
+
+ strcpy(hold_rm, CC->room.QRname); /* save current room */
+
+ if (getroom(&CC->room, USERCALENDARROOM) != 0) {
+ getroom(&CC->room, hold_rm);
+ cprintf("%d You do not have a calendar.\n", ERROR + ROOM_NOT_FOUND);
+ return;
+ }
+
+ cprintf("%d Conflicting events:\n", LISTING_FOLLOWS);
+
+ CtdlForEachMessage(MSGS_ALL, 0, NULL,
+ NULL,
+ NULL,
+ ical_hunt_for_conflicts_backend,
+ (void *) cal
+ );
+
+ cprintf("000\n");
+ getroom(&CC->room, hold_rm); /* return to saved room */
+
+}
+
+
+
+/*
+ * Hunt for conflicts (Phase 1 -- retrieve the object and call Phase 2)
+ */
+void ical_conflicts(long msgnum, char *partnum) {
+ struct CtdlMessage *msg = NULL;
+ struct ical_respond_data ird;
+
+ msg = CtdlFetchMessage(msgnum, 1);
+ if (msg == NULL) {
+ cprintf("%d Message %ld not found.\n",
+ ERROR + ILLEGAL_VALUE,
+ (long)msgnum
+ );
+ return;
+ }
+
+ memset(&ird, 0, sizeof ird);
+ strcpy(ird.desired_partnum, partnum);
+ mime_parser(msg->cm_fields['M'],
+ NULL,
+ *ical_locate_part, /* callback function */
+ NULL, NULL,
+ (void *) &ird, /* user data */
+ 0
+ );
+
+ CtdlFreeMessage(msg);
+
+ if (ird.cal != NULL) {
+ ical_hunt_for_conflicts(ird.cal);
+ icalcomponent_free(ird.cal);
+ return;
+ }
+ else {
+ cprintf("%d No calendar object found\n", ERROR + ROOM_NOT_FOUND);
+ return;
+ }
+
+ /* should never get here */
+}
+
+
+
+/*
+ * Look for busy time in a VEVENT and add it to the supplied VFREEBUSY.
+ */
+void ical_add_to_freebusy(icalcomponent *fb, icalcomponent *cal) {
+ icalproperty *p;
+ icalvalue *v;
+ struct icalperiodtype my_period;
+
+ if (cal == NULL) return;
+ my_period = icalperiodtype_null_period();
+
+ if (icalcomponent_isa(cal) != ICAL_VEVENT_COMPONENT) {
+ ical_add_to_freebusy(fb,
+ icalcomponent_get_first_component(
+ cal, ICAL_VEVENT_COMPONENT
+ )
+ );
+ return;
+ }
+
+ ical_dezonify(cal);
+
+ /* If this event is not opaque, the user isn't publishing it as
+ * busy time, so don't bother doing anything else.
+ */
+ p = icalcomponent_get_first_property(cal, ICAL_TRANSP_PROPERTY);
+ if (p != NULL) {
+ v = icalproperty_get_value(p);
+ if (v != NULL) {
+ if (icalvalue_get_transp(v) != ICAL_TRANSP_OPAQUE) {
+ return;
+ }
+ }
+ }
+
+ /* Convert the DTSTART and DTEND properties to an icalperiod. */
+ p = icalcomponent_get_first_property(cal, ICAL_DTSTART_PROPERTY);
+ if (p != NULL) {
+ my_period.start = icalproperty_get_dtstart(p);
+ }
+
+ p = icalcomponent_get_first_property(cal, ICAL_DTEND_PROPERTY);
+ if (p != NULL) {
+ my_period.end = icalproperty_get_dtstart(p);
+ }
+
+ /* Now add it. */
+ icalcomponent_add_property(fb,
+ icalproperty_new_freebusy(my_period)
+ );
+
+ /* Make sure the DTSTART property of the freebusy *list* is set to
+ * the DTSTART property of the *earliest event*.
+ */
+ p = icalcomponent_get_first_property(fb, ICAL_DTSTART_PROPERTY);
+ if (p == NULL) {
+ icalcomponent_set_dtstart(fb,
+ icalcomponent_get_dtstart(cal) );
+ }
+ else {
+ if (icaltime_compare(
+ icalcomponent_get_dtstart(cal),
+ icalcomponent_get_dtstart(fb)
+ ) < 0) {
+ icalcomponent_set_dtstart(fb,
+ icalcomponent_get_dtstart(cal) );
+ }
+ }
+
+ /* Make sure the DTEND property of the freebusy *list* is set to
+ * the DTEND property of the *latest event*.
+ */
+ p = icalcomponent_get_first_property(fb, ICAL_DTEND_PROPERTY);
+ if (p == NULL) {
+ icalcomponent_set_dtend(fb,
+ icalcomponent_get_dtend(cal) );
+ }
+ else {
+ if (icaltime_compare(
+ icalcomponent_get_dtend(cal),
+ icalcomponent_get_dtend(fb)
+ ) > 0) {
+ icalcomponent_set_dtend(fb,
+ icalcomponent_get_dtend(cal) );
+ }
+ }
+
+}
+
+
+
+/*
+ * Backend for ical_freebusy()
+ *
+ * This function simply loads the messages in the user's calendar room,
+ * which contain VEVENTs, then strips them of all non-freebusy data, and
+ * adds them to the supplied VCALENDAR.
+ *
+ */
+void ical_freebusy_backend(long msgnum, void *data) {
+ icalcomponent *cal;
+ struct CtdlMessage *msg = NULL;
+ struct ical_respond_data ird;
+
+ cal = (icalcomponent *)data;
+
+ msg = CtdlFetchMessage(msgnum, 1);
+ if (msg == NULL) return;
+ memset(&ird, 0, sizeof ird);
+ strcpy(ird.desired_partnum, "_HUNT_");
+ mime_parser(msg->cm_fields['M'],
+ NULL,
+ *ical_locate_part, /* callback function */
+ NULL, NULL,
+ (void *) &ird, /* user data */
+ 0
+ );
+ CtdlFreeMessage(msg);
+
+ if (ird.cal == NULL) return;
+
+ ical_add_to_freebusy(cal, ird.cal);
+
+ /* Now free the memory. */
+ icalcomponent_free(ird.cal);
+}
+
+
+
+/*
+ * Grab another user's free/busy times
+ */
+void ical_freebusy(char *who) {
+ struct ctdluser usbuf;
+ char calendar_room_name[ROOMNAMELEN];
+ char hold_rm[ROOMNAMELEN];
+ char *serialized_request = NULL;
+ icalcomponent *encaps = NULL;
+ icalcomponent *fb = NULL;
+ int found_user = (-1);
+ struct recptypes *recp = NULL;
+ char buf[256];
+ char host[256];
+ char type[256];
+ int i = 0;
+ int config_lines = 0;
+
+ /* First try an exact match. */
+ found_user = getuser(&usbuf, who);
+
+ /* If not found, try it as an unqualified email address. */
+ if (found_user != 0) {
+ strcpy(buf, who);
+ recp = validate_recipients(buf);
+ lprintf(CTDL_DEBUG, "Trying <%s>\n", buf);
+ if (recp != NULL) {
+ if (recp->num_local == 1) {
+ found_user = getuser(&usbuf, recp->recp_local);
+ }
+ free_recipients(recp);
+ }
+ }
+
+ /* If still not found, try it as an address qualified with the
+ * primary FQDN of this Citadel node.
+ */
+ if (found_user != 0) {
+ snprintf(buf, sizeof buf, "%s@%s", who, config.c_fqdn);
+ lprintf(CTDL_DEBUG, "Trying <%s>\n", buf);
+ recp = validate_recipients(buf);
+ if (recp != NULL) {
+ if (recp->num_local == 1) {
+ found_user = getuser(&usbuf, recp->recp_local);
+ }
+ free_recipients(recp);
+ }
+ }
+
+ /* Still not found? Try qualifying it with every domain we
+ * might have addresses in.
+ */
+ if (found_user != 0) {
+ config_lines = num_tokens(inetcfg, '\n');
+ for (i=0; ((i < config_lines) && (found_user != 0)); ++i) {
+ extract_token(buf, inetcfg, i, '\n', sizeof buf);
+ extract_token(host, buf, 0, '|', sizeof host);
+ extract_token(type, buf, 1, '|', sizeof type);
+
+ if ( (!strcasecmp(type, "localhost"))
+ || (!strcasecmp(type, "directory")) ) {
+ snprintf(buf, sizeof buf, "%s@%s", who, host);
+ lprintf(CTDL_DEBUG, "Trying <%s>\n", buf);
+ recp = validate_recipients(buf);
+ if (recp != NULL) {
+ if (recp->num_local == 1) {
+ found_user = getuser(&usbuf, recp->recp_local);
+ }
+ free_recipients(recp);
+ }
+ }
+ }
+ }
+
+ if (found_user != 0) {
+ cprintf("%d No such user.\n", ERROR + NO_SUCH_USER);
+ return;
+ }
+
+ MailboxName(calendar_room_name, sizeof calendar_room_name,
+ &usbuf, USERCALENDARROOM);
+
+ strcpy(hold_rm, CC->room.QRname); /* save current room */
+
+ if (getroom(&CC->room, calendar_room_name) != 0) {
+ cprintf("%d Cannot open calendar\n", ERROR + ROOM_NOT_FOUND);
+ getroom(&CC->room, hold_rm);
+ return;
+ }
+
+ /* Create a VFREEBUSY subcomponent */
+ lprintf(CTDL_DEBUG, "Creating VFREEBUSY component\n");
+ fb = icalcomponent_new_vfreebusy();
+ if (fb == NULL) {
+ cprintf("%d Internal error: cannot allocate memory.\n",
+ ERROR + INTERNAL_ERROR);
+ icalcomponent_free(encaps);
+ getroom(&CC->room, hold_rm);
+ return;
+ }
+
+ /* Set the method to PUBLISH */
+ icalcomponent_set_method(fb, ICAL_METHOD_PUBLISH);
+
+ /* Set the DTSTAMP to right now. */
+ icalcomponent_set_dtstamp(fb, icaltime_from_timet(time(NULL), 0));
+
+ /* Add the user's email address as ORGANIZER */
+ sprintf(buf, "MAILTO:%s", who);
+ if (strchr(buf, '@') == NULL) {
+ strcat(buf, "@");
+ strcat(buf, config.c_fqdn);
+ }
+ for (i=0; i<strlen(buf); ++i) {
+ if (buf[i]==' ') buf[i] = '_';
+ }
+ icalcomponent_add_property(fb, icalproperty_new_organizer(buf));
+
+ /* Add busy time from events */
+ lprintf(CTDL_DEBUG, "Adding busy time from events\n");
+ CtdlForEachMessage(MSGS_ALL, 0, NULL, NULL, NULL, ical_freebusy_backend, (void *)fb );
+
+ /* If values for DTSTART and DTEND are still not present, set them
+ * to yesterday and tomorrow as default values.
+ */
+ if (icalcomponent_get_first_property(fb, ICAL_DTSTART_PROPERTY) == NULL) {
+ icalcomponent_set_dtstart(fb, icaltime_from_timet(time(NULL)-86400L, 0));
+ }
+ if (icalcomponent_get_first_property(fb, ICAL_DTEND_PROPERTY) == NULL) {
+ icalcomponent_set_dtend(fb, icaltime_from_timet(time(NULL)+86400L, 0));
+ }
+
+ /* Put the freebusy component into the calendar component */
+ lprintf(CTDL_DEBUG, "Encapsulating\n");
+ encaps = ical_encapsulate_subcomponent(fb);
+ if (encaps == NULL) {
+ icalcomponent_free(fb);
+ cprintf("%d Internal error: cannot allocate memory.\n",
+ ERROR + INTERNAL_ERROR);
+ getroom(&CC->room, hold_rm);
+ return;
+ }
+
+ /* Set the method to PUBLISH */
+ lprintf(CTDL_DEBUG, "Setting method\n");
+ icalcomponent_set_method(encaps, ICAL_METHOD_PUBLISH);
+
+ /* Serialize it */
+ lprintf(CTDL_DEBUG, "Serializing\n");
+ serialized_request = strdup(icalcomponent_as_ical_string(encaps));
+ icalcomponent_free(encaps); /* Don't need this anymore. */
+
+ cprintf("%d Here is the free/busy data:\n", LISTING_FOLLOWS);
+ if (serialized_request != NULL) {
+ client_write(serialized_request, strlen(serialized_request));
+ free(serialized_request);
+ }
+ cprintf("\n000\n");
+
+ /* Go back to the room from which we came... */
+ getroom(&CC->room, hold_rm);
+}
+
+
+
+/*
+ * Backend for ical_getics()
+ *
+ * This is a ForEachMessage() callback function that searches the current room
+ * for calendar events and adds them each into one big calendar component.
+ */
+void ical_getics_backend(long msgnum, void *data) {
+ icalcomponent *encaps, *c;
+ struct CtdlMessage *msg = NULL;
+ struct ical_respond_data ird;
+
+ encaps = (icalcomponent *)data;
+ if (encaps == NULL) return;
+
+ /* Look for the calendar event... */
+
+ msg = CtdlFetchMessage(msgnum, 1);
+ if (msg == NULL) return;
+ memset(&ird, 0, sizeof ird);
+ strcpy(ird.desired_partnum, "_HUNT_");
+ mime_parser(msg->cm_fields['M'],
+ NULL,
+ *ical_locate_part, /* callback function */
+ NULL, NULL,
+ (void *) &ird, /* user data */
+ 0
+ );
+ CtdlFreeMessage(msg);
+
+ if (ird.cal == NULL) return;
+
+ /* Here we go: put the VEVENT into the VCALENDAR. We now no longer
+ * are responsible for "the_request"'s memory -- it will be freed
+ * when we free "encaps".
+ */
+
+ /* If the top-level component is *not* a VCALENDAR, we can drop it right
+ * in. This will almost never happen.
+ */
+ if (icalcomponent_isa(ird.cal) != ICAL_VCALENDAR_COMPONENT) {
+ icalcomponent_add_component(encaps, ird.cal);
+ }
+ /*
+ * In the more likely event that we're looking at a VCALENDAR with the VEVENT
+ * and other components encapsulated inside, we have to extract them.
+ */
+ else {
+ for (c = icalcomponent_get_first_component(ird.cal, ICAL_ANY_COMPONENT);
+ (c != NULL);
+ c = icalcomponent_get_next_component(ird.cal, ICAL_ANY_COMPONENT)) {
+ icalcomponent_add_component(encaps, icalcomponent_new_clone(c));
+ }
+ icalcomponent_free(ird.cal);
+ }
+}
+
+
+
+/*
+ * Retrieve all of the calendar items in the current room, and output them
+ * as a single icalendar object.
+ */
+void ical_getics(void)
+{
+ icalcomponent *encaps = NULL;
+ char *ser = NULL;
+
+ if ( (CC->room.QRdefaultview != VIEW_CALENDAR)
+ &&(CC->room.QRdefaultview != VIEW_TASKS) ) {
+ cprintf("%d Not a calendar room\n", ERROR+NOT_HERE);
+ return; /* Not a vCalendar-centric room */
+ }
+
+ encaps = icalcomponent_new_vcalendar();
+ if (encaps == NULL) {
+ lprintf(CTDL_DEBUG, "Error at %s:%d - could not allocate component!\n",
+ __FILE__, __LINE__);
+ cprintf("%d Could not allocate memory\n", ERROR+INTERNAL_ERROR);
+ return;
+ }
+
+ cprintf("%d one big calendar\n", LISTING_FOLLOWS);
+
+ /* Set the Product ID */
+ icalcomponent_add_property(encaps, icalproperty_new_prodid(PRODID));
+
+ /* Set the Version Number */
+ icalcomponent_add_property(encaps, icalproperty_new_version("2.0"));
+
+ /* Set the method to PUBLISH */
+ icalcomponent_set_method(encaps, ICAL_METHOD_PUBLISH);
+
+ /* Now go through the room encapsulating all calendar items. */
+ CtdlForEachMessage(MSGS_ALL, 0, NULL,
+ NULL,
+ NULL,
+ ical_getics_backend,
+ (void *) encaps
+ );
+
+ ser = strdup(icalcomponent_as_ical_string(encaps));
+ client_write(ser, strlen(ser));
+ free(ser);
+ cprintf("\n000\n");
+ icalcomponent_free(encaps); /* Don't need this anymore. */
+
+}
+
+
+/*
+ * Delete all of the calendar items in the current room, and replace them
+ * with calendar items from a client-supplied data stream.
+ */
+void ical_putics(void)
+{
+ char *calstream = NULL;
+ icalcomponent *cal;
+ icalcomponent *c;
+
+ if ( (CC->room.QRdefaultview != VIEW_CALENDAR)
+ &&(CC->room.QRdefaultview != VIEW_TASKS) ) {
+ cprintf("%d Not a calendar room\n", ERROR+NOT_HERE);
+ return; /* Not a vCalendar-centric room */
+ }
+
+ if (!CtdlDoIHavePermissionToDeleteMessagesFromThisRoom()) {
+ cprintf("%d Permission denied.\n", ERROR+HIGHER_ACCESS_REQUIRED);
+ return;
+ }
+
+ cprintf("%d Transmit data now\n", SEND_LISTING);
+ calstream = CtdlReadMessageBody("000", config.c_maxmsglen, NULL, 0);
+ if (calstream == NULL) {
+ return;
+ }
+
+ cal = icalcomponent_new_from_string(calstream);
+ free(calstream);
+ ical_dezonify(cal);
+
+ /* We got our data stream -- now do something with it. */
+
+ /* Delete the existing messages in the room, because we are replacing
+ * the entire calendar with an entire new (or updated) calendar.
+ * (Careful: this opens an S_ROOMS critical section!)
+ */
+ CtdlDeleteMessages(CC->room.QRname, NULL, 0, "");
+
+ /* If the top-level component is *not* a VCALENDAR, we can drop it right
+ * in. This will almost never happen.
+ */
+ if (icalcomponent_isa(cal) != ICAL_VCALENDAR_COMPONENT) {
+ ical_write_to_cal(&CC->user, cal);
+ }
+ /*
+ * In the more likely event that we're looking at a VCALENDAR with the VEVENT
+ * and other components encapsulated inside, we have to extract them.
+ */
+ else {
+ for (c = icalcomponent_get_first_component(cal, ICAL_ANY_COMPONENT);
+ (c != NULL);
+ c = icalcomponent_get_next_component(cal, ICAL_ANY_COMPONENT)) {
+ ical_write_to_cal(&CC->user, c);
+ }
+ }
+
+ icalcomponent_free(cal);
+}
+
+
+/*
+ * All Citadel calendar commands from the client come through here.
+ */
+void cmd_ical(char *argbuf)
+{
+ char subcmd[64];
+ long msgnum;
+ char partnum[256];
+ char action[256];
+ char who[256];
+
+ extract_token(subcmd, argbuf, 0, '|', sizeof subcmd);
+
+ /* Allow "test" and "freebusy" subcommands without logging in. */
+
+ if (!strcasecmp(subcmd, "test")) {
+ cprintf("%d This server supports calendaring\n", CIT_OK);
+ return;
+ }
+
+ if (!strcasecmp(subcmd, "freebusy")) {
+ extract_token(who, argbuf, 1, '|', sizeof who);
+ ical_freebusy(who);
+ return;
+ }
+
+ if (!strcasecmp(subcmd, "sgi")) {
+ CIT_ICAL->server_generated_invitations =
+ (extract_int(argbuf, 1) ? 1 : 0) ;
+ cprintf("%d %d\n",
+ CIT_OK, CIT_ICAL->server_generated_invitations);
+ return;
+ }
+
+ if (CtdlAccessCheck(ac_logged_in)) return;
+
+ if (!strcasecmp(subcmd, "respond")) {
+ msgnum = extract_long(argbuf, 1);
+ extract_token(partnum, argbuf, 2, '|', sizeof partnum);
+ extract_token(action, argbuf, 3, '|', sizeof action);
+ ical_respond(msgnum, partnum, action);
+ return;
+ }
+
+ if (!strcasecmp(subcmd, "handle_rsvp")) {
+ msgnum = extract_long(argbuf, 1);
+ extract_token(partnum, argbuf, 2, '|', sizeof partnum);
+ extract_token(action, argbuf, 3, '|', sizeof action);
+ ical_handle_rsvp(msgnum, partnum, action);
+ return;
+ }
+
+ if (!strcasecmp(subcmd, "conflicts")) {
+ msgnum = extract_long(argbuf, 1);
+ extract_token(partnum, argbuf, 2, '|', sizeof partnum);
+ ical_conflicts(msgnum, partnum);
+ return;
+ }
+
+ if (!strcasecmp(subcmd, "getics")) {
+ ical_getics();
+ return;
+ }
+
+ if (!strcasecmp(subcmd, "putics")) {
+ ical_putics();
+ return;
+ }
+
+ cprintf("%d Invalid subcommand\n", ERROR + CMD_NOT_SUPPORTED);
+}
+
+
+
+/*
+ * We don't know if the calendar room exists so we just create it at login
+ */
+void ical_create_room(void)
+{
+ struct ctdlroom qr;
+ struct visit vbuf;
+
+ /* Create the calendar room if it doesn't already exist */
+ create_room(USERCALENDARROOM, 4, "", 0, 1, 0, VIEW_CALENDAR);
+
+ /* Set expiration policy to manual; otherwise objects will be lost! */
+ if (lgetroom(&qr, USERCALENDARROOM)) {
+ lprintf(CTDL_CRIT, "Couldn't get the user calendar room!\n");
+ return;
+ }
+ qr.QRep.expire_mode = EXPIRE_MANUAL;
+ qr.QRdefaultview = VIEW_CALENDAR; /* 3 = calendar view */
+ lputroom(&qr);
+
+ /* Set the view to a calendar view */
+ CtdlGetRelationship(&vbuf, &CC->user, &qr);
+ vbuf.v_view = VIEW_CALENDAR;
+ CtdlSetRelationship(&vbuf, &CC->user, &qr);
+
+ /* Create the tasks list room if it doesn't already exist */
+ create_room(USERTASKSROOM, 4, "", 0, 1, 0, VIEW_TASKS);
+
+ /* Set expiration policy to manual; otherwise objects will be lost! */
+ if (lgetroom(&qr, USERTASKSROOM)) {
+ lprintf(CTDL_CRIT, "Couldn't get the user calendar room!\n");
+ return;
+ }
+ qr.QRep.expire_mode = EXPIRE_MANUAL;
+ qr.QRdefaultview = VIEW_TASKS;
+ lputroom(&qr);
+
+ /* Set the view to a task list view */
+ CtdlGetRelationship(&vbuf, &CC->user, &qr);
+ vbuf.v_view = VIEW_TASKS;
+ CtdlSetRelationship(&vbuf, &CC->user, &qr);
+
+ /* Create the notes room if it doesn't already exist */
+ create_room(USERNOTESROOM, 4, "", 0, 1, 0, VIEW_NOTES);
+
+ /* Set expiration policy to manual; otherwise objects will be lost! */
+ if (lgetroom(&qr, USERNOTESROOM)) {
+ lprintf(CTDL_CRIT, "Couldn't get the user calendar room!\n");
+ return;
+ }
+ qr.QRep.expire_mode = EXPIRE_MANUAL;
+ qr.QRdefaultview = VIEW_NOTES;
+ lputroom(&qr);
+
+ /* Set the view to a notes view */
+ CtdlGetRelationship(&vbuf, &CC->user, &qr);
+ vbuf.v_view = VIEW_NOTES;
+ CtdlSetRelationship(&vbuf, &CC->user, &qr);
+
+ return;
+}
+
+
+/*
+ * ical_send_out_invitations() is called by ical_saving_vevent() when it
+ * finds a VEVENT.
+ */
+void ical_send_out_invitations(icalcomponent *cal) {
+ icalcomponent *the_request = NULL;
+ char *serialized_request = NULL;
+ icalcomponent *encaps = NULL;
+ char *request_message_text = NULL;
+ struct CtdlMessage *msg = NULL;
+ struct recptypes *valid = NULL;
+ char attendees_string[SIZ];
+ int num_attendees = 0;
+ char this_attendee[256];
+ icalproperty *attendee = NULL;
+ char summary_string[SIZ];
+ icalproperty *summary = NULL;
+
+ if (cal == NULL) {
+ lprintf(CTDL_ERR, "ERROR: trying to reply to NULL event?\n");
+ return;
+ }
+
+
+ /* If this is a VCALENDAR component, look for a VEVENT subcomponent. */
+ if (icalcomponent_isa(cal) == ICAL_VCALENDAR_COMPONENT) {
+ ical_send_out_invitations(
+ icalcomponent_get_first_component(
+ cal, ICAL_VEVENT_COMPONENT
+ )
+ );
+ return;
+ }
+
+ /* Clone the event */
+ the_request = icalcomponent_new_clone(cal);
+ if (the_request == NULL) {
+ lprintf(CTDL_ERR, "ERROR: cannot clone calendar object\n");
+ return;
+ }
+
+ /* Extract the summary string -- we'll use it as the
+ * message subject for the request
+ */
+ strcpy(summary_string, "Meeting request");
+ summary = icalcomponent_get_first_property(the_request, ICAL_SUMMARY_PROPERTY);
+ if (summary != NULL) {
+ if (icalproperty_get_summary(summary)) {
+ strcpy(summary_string,
+ icalproperty_get_summary(summary) );
+ }
+ }
+
+ /* Determine who the recipients of this message are (the attendees) */
+ strcpy(attendees_string, "");
+ for (attendee = icalcomponent_get_first_property(the_request, ICAL_ATTENDEE_PROPERTY); attendee != NULL; attendee = icalcomponent_get_next_property(the_request, ICAL_ATTENDEE_PROPERTY)) {
+ if (icalproperty_get_attendee(attendee)) {
+ safestrncpy(this_attendee, icalproperty_get_attendee(attendee), sizeof this_attendee);
+ if (!strncasecmp(this_attendee, "MAILTO:", 7)) {
+ strcpy(this_attendee, &this_attendee[7]);
+
+ if (!CtdlIsMe(this_attendee, sizeof this_attendee)) { /* don't send an invitation to myself! */
+ snprintf(&attendees_string[strlen(attendees_string)],
+ sizeof(attendees_string) - strlen(attendees_string),
+ "%s, ",
+ this_attendee
+ );
+ ++num_attendees;
+ }
+ }
+ }
+ }
+
+ lprintf(CTDL_DEBUG, "<%d> attendees: <%s>\n", num_attendees, attendees_string);
+
+ /* If there are no attendees, there are no invitations to send, so...
+ * don't bother putting one together! Punch out, Maverick!
+ */
+ if (num_attendees == 0) {
+ icalcomponent_free(the_request);
+ return;
+ }
+
+ /* Encapsulate the VEVENT component into a complete VCALENDAR */
+ encaps = icalcomponent_new_vcalendar();
+ if (encaps == NULL) {
+ lprintf(CTDL_DEBUG, "Error at %s:%d - could not allocate component!\n",
+ __FILE__, __LINE__);
+ icalcomponent_free(the_request);
+ return;
+ }
+
+ /* Set the Product ID */
+ icalcomponent_add_property(encaps, icalproperty_new_prodid(PRODID));
+
+ /* Set the Version Number */
+ icalcomponent_add_property(encaps, icalproperty_new_version("2.0"));
+
+ /* Set the method to REQUEST */
+ icalcomponent_set_method(encaps, ICAL_METHOD_REQUEST);
+
+ /* Now make sure all of the DTSTART and DTEND properties are UTC. */
+ ical_dezonify(the_request);
+
+ /* Here we go: put the VEVENT into the VCALENDAR. We now no longer
+ * are responsible for "the_request"'s memory -- it will be freed
+ * when we free "encaps".
+ */
+ icalcomponent_add_component(encaps, the_request);
+
+ /* Serialize it */
+ serialized_request = strdup(icalcomponent_as_ical_string(encaps));
+ icalcomponent_free(encaps); /* Don't need this anymore. */
+ if (serialized_request == NULL) return;
+
+ request_message_text = malloc(strlen(serialized_request) + SIZ);
+ if (request_message_text != NULL) {
+ sprintf(request_message_text,
+ "Content-type: text/calendar\r\n\r\n%s\r\n",
+ serialized_request
+ );
+
+ msg = CtdlMakeMessage(&CC->user,
+ "", /* No single recipient here */
+ "", /* No single recipient here */
+ CC->room.QRname, 0, FMT_RFC822,
+ "",
+ "",
+ summary_string, /* Use summary for subject */
+ NULL,
+ request_message_text);
+
+ if (msg != NULL) {
+ valid = validate_recipients(attendees_string);
+ CtdlSubmitMsg(msg, valid, "");
+ CtdlFreeMessage(msg);
+ free_recipients(valid);
+ }
+ }
+ free(serialized_request);
+}
+
+
+/*
+ * When a calendar object is being saved, determine whether it's a VEVENT
+ * and the user saving it is the organizer. If so, send out invitations
+ * to any listed attendees.
+ *
+ */
+void ical_saving_vevent(icalcomponent *cal) {
+ icalcomponent *c;
+ icalproperty *organizer = NULL;
+ char organizer_string[SIZ];
+
+ lprintf(CTDL_DEBUG, "ical_saving_vevent() has been called!\n");
+
+ /* Don't send out invitations unless the client wants us to. */
+ if (CIT_ICAL->server_generated_invitations == 0) {
+ return;
+ }
+
+ /* Don't send out invitations if we've been asked not to. */
+ if (CIT_ICAL->avoid_sending_invitations > 0) {
+ return;
+ }
+
+ strcpy(organizer_string, "");
+ /*
+ * The VEVENT subcomponent is the one we're interested in.
+ * Send out invitations if, and only if, this user is the Organizer.
+ */
+ if (icalcomponent_isa(cal) == ICAL_VEVENT_COMPONENT) {
+ organizer = icalcomponent_get_first_property(cal, ICAL_ORGANIZER_PROPERTY);
+ if (organizer != NULL) {
+ if (icalproperty_get_organizer(organizer)) {
+ strcpy(organizer_string,
+ icalproperty_get_organizer(organizer));
+ }
+ }
+ if (!strncasecmp(organizer_string, "MAILTO:", 7)) {
+ strcpy(organizer_string, &organizer_string[7]);
+ striplt(organizer_string);
+ /*
+ * If the user saving the event is listed as the
+ * organizer, then send out invitations.
+ */
+ if (CtdlIsMe(organizer_string, sizeof organizer_string)) {
+ ical_send_out_invitations(cal);
+ }
+ }
+ }
+
+ /* If the component has subcomponents, recurse through them. */
+ for (c = icalcomponent_get_first_component(cal, ICAL_ANY_COMPONENT);
+ (c != NULL);
+ c = icalcomponent_get_next_component(cal, ICAL_ANY_COMPONENT)) {
+ /* Recursively process subcomponent */
+ ical_saving_vevent(c);
+ }
+
+}
+
+
+
+/*
+ * Back end for ical_obj_beforesave()
+ * This hunts for the UID of the calendar event (becomes Citadel msg EUID),
+ * the summary of the event (becomes message subject),
+ * and the start time (becomes message date/time).
+ */
+void ical_ctdl_set_exclusive_msgid(char *name, char *filename, char *partnum,
+ char *disp, void *content, char *cbtype, char *cbcharset, size_t length,
+ char *encoding, void *cbuserdata)
+{
+ icalcomponent *cal, *nested_event, *nested_todo, *whole_cal;
+ icalproperty *p;
+ struct icalmessagemod *imm;
+ char new_uid[SIZ];
+
+ imm = (struct icalmessagemod *)cbuserdata;
+
+ /* We're only interested in calendar data. */
+ if (strcasecmp(cbtype, "text/calendar")) {
+ return;
+ }
+
+ /* Hunt for the UID and drop it in
+ * the "user data" pointer for the MIME parser. When
+ * ical_obj_beforesave() sees it there, it'll set the Exclusive msgid
+ * to that string.
+ */
+ whole_cal = icalcomponent_new_from_string(content);
+ cal = whole_cal;
+ if (cal != NULL) {
+ if (icalcomponent_isa(cal) == ICAL_VCALENDAR_COMPONENT) {
+ nested_event = icalcomponent_get_first_component(
+ cal, ICAL_VEVENT_COMPONENT);
+ if (nested_event != NULL) {
+ cal = nested_event;
+ }
+ else {
+ nested_todo = icalcomponent_get_first_component(
+ cal, ICAL_VTODO_COMPONENT);
+ if (nested_todo != NULL) {
+ cal = nested_todo;
+ }
+ }
+ }
+
+ if (cal != NULL) {
+ p = ical_ctdl_get_subprop(cal, ICAL_UID_PROPERTY);
+ if (p == NULL) {
+ /* If there's no uid we must generate one */
+ generate_uuid(new_uid);
+ icalcomponent_add_property(cal, icalproperty_new_uid(new_uid));
+ p = ical_ctdl_get_subprop(cal, ICAL_UID_PROPERTY);
+ }
+ if (p != NULL) {
+ strcpy(imm->uid, icalproperty_get_comment(p));
+ }
+ p = ical_ctdl_get_subprop(cal, ICAL_SUMMARY_PROPERTY);
+ if (p != NULL) {
+ strcpy(imm->subject, icalproperty_get_comment(p));
+ }
+ p = ical_ctdl_get_subprop(cal, ICAL_DTSTART_PROPERTY);
+ if (p != NULL) {
+ imm->dtstart = icaltime_as_timet(icalproperty_get_dtstart(p));
+ }
+ }
+ icalcomponent_free(cal);
+ if (whole_cal != cal) {
+ icalcomponent_free(whole_cal);
+ }
+ }
+}
+
+
+
+
+/*
+ * See if we need to prevent the object from being saved (we don't allow
+ * MIME types other than text/calendar in "calendar" or "tasks" rooms). Also,
+ * when saving an event to the calendar, set the message's Citadel exclusive
+ * message ID to the UID of the object. This causes our replication checker to
+ * automatically delete any existing instances of the same object. (Isn't
+ * that cool?)
+ *
+ * We also set the message's Subject to the event summary, and the Date/time to
+ * the event start time.
+ */
+int ical_obj_beforesave(struct CtdlMessage *msg)
+{
+ struct icalmessagemod imm;
+
+ /* First determine if this is a calendar or tasks room */
+ if ( (CC->room.QRdefaultview != VIEW_CALENDAR)
+ && (CC->room.QRdefaultview != VIEW_TASKS)
+ ) {
+ return(0); /* Not a vCalendar-centric room */
+ }
+
+ /* It must be an RFC822 message! */
+ if (msg->cm_format_type != 4) {
+ lprintf(CTDL_DEBUG, "Rejecting non-RFC822 message\n");
+ return(1); /* You tried to save a non-RFC822 message! */
+ }
+
+ if (msg->cm_fields['M'] == NULL) {
+ return(1); /* You tried to save a null message! */
+ }
+
+ memset(&imm, 0, sizeof(struct icalmessagemod));
+
+ /* Do all of our lovely back-end parsing */
+ mime_parser(msg->cm_fields['M'],
+ NULL,
+ *ical_ctdl_set_exclusive_msgid,
+ NULL, NULL,
+ (void *)&imm,
+ 0
+ );
+
+ if (strlen(imm.uid) > 0) {
+ if (msg->cm_fields['E'] != NULL) {
+ free(msg->cm_fields['E']);
+ }
+ msg->cm_fields['E'] = strdup(imm.uid);
+ lprintf(CTDL_DEBUG, "Saving calendar UID <%s>\n", msg->cm_fields['E']);
+ }
+ if (strlen(imm.subject) > 0) {
+ if (msg->cm_fields['U'] != NULL) {
+ free(msg->cm_fields['U']);
+ }
+ msg->cm_fields['U'] = strdup(imm.subject);
+ }
+ if (imm.dtstart > 0) {
+ if (msg->cm_fields['T'] != NULL) {
+ free(msg->cm_fields['T']);
+ }
+ msg->cm_fields['T'] = strdup("000000000000000000");
+ sprintf(msg->cm_fields['T'], "%ld", imm.dtstart);
+ }
+
+ return(0);
+}
+
+
+/*
+ * Things we need to do after saving a calendar event.
+ */
+void ical_obj_aftersave_backend(char *name, char *filename, char *partnum,
+ char *disp, void *content, char *cbtype, char *cbcharset, size_t length,
+ char *encoding, void *cbuserdata)
+{
+ icalcomponent *cal;
+
+ /* We're only interested in calendar items here. */
+ if (strcasecmp(cbtype, "text/calendar")) {
+ return;
+ }
+
+ /* Hunt for the UID and drop it in
+ * the "user data" pointer for the MIME parser. When
+ * ical_obj_beforesave() sees it there, it'll set the Exclusive msgid
+ * to that string.
+ */
+ if (!strcasecmp(cbtype, "text/calendar")) {
+ cal = icalcomponent_new_from_string(content);
+ if (cal != NULL) {
+ ical_saving_vevent(cal);
+ icalcomponent_free(cal);
+ }
+ }
+}
+
+
+/*
+ * Things we need to do after saving a calendar event.
+ * (This will start back end tasks such as automatic generation of invitations,
+ * if such actions are appropriate.)
+ */
+int ical_obj_aftersave(struct CtdlMessage *msg)
+{
+ char roomname[ROOMNAMELEN];
+
+ /*
+ * If this isn't the Calendar> room, no further action is necessary.
+ */
+
+ /* First determine if this is our room */
+ MailboxName(roomname, sizeof roomname, &CC->user, USERCALENDARROOM);
+ if (strcasecmp(roomname, CC->room.QRname)) {
+ return(0); /* Not the Calendar room -- don't do anything. */
+ }
+
+ /* It must be an RFC822 message! */
+ if (msg->cm_format_type != 4) return(1);
+
+ /* Reject null messages */
+ if (msg->cm_fields['M'] == NULL) return(1);
+
+ /* Now recurse through it looking for our icalendar data */
+ mime_parser(msg->cm_fields['M'],
+ NULL,
+ *ical_obj_aftersave_backend,
+ NULL, NULL,
+ NULL,
+ 0
+ );
+
+ return(0);
+}
+
+
+void ical_session_startup(void) {
+ CIT_ICAL = malloc(sizeof(struct cit_ical));
+ memset(CIT_ICAL, 0, sizeof(struct cit_ical));
+}
+
+void ical_session_shutdown(void) {
+ free(CIT_ICAL);
+}
+
+
+/*
+ * Back end for ical_fixed_output()
+ */
+void ical_fixed_output_backend(icalcomponent *cal,
+ int recursion_level
+) {
+ icalcomponent *c;
+ icalproperty *p;
+ char buf[256];
+
+ p = icalcomponent_get_first_property(cal, ICAL_SUMMARY_PROPERTY);
+ if (p != NULL) {
+ cprintf("%s\n", (const char *)icalproperty_get_comment(p));
+ }
+
+ p = icalcomponent_get_first_property(cal, ICAL_LOCATION_PROPERTY);
+ if (p != NULL) {
+ cprintf("%s\n", (const char *)icalproperty_get_comment(p));
+ }
+
+ p = icalcomponent_get_first_property(cal, ICAL_DESCRIPTION_PROPERTY);
+ if (p != NULL) {
+ cprintf("%s\n", (const char *)icalproperty_get_comment(p));
+ }
+
+ /* If the component has attendees, iterate through them. */
+ for (p = icalcomponent_get_first_property(cal, ICAL_ATTENDEE_PROPERTY); (p != NULL); p = icalcomponent_get_next_property(cal, ICAL_ATTENDEE_PROPERTY)) {
+ safestrncpy(buf, icalproperty_get_attendee(p), sizeof buf);
+ if (!strncasecmp(buf, "MAILTO:", 7)) {
+
+ /* screen name or email address */
+ strcpy(buf, &buf[7]);
+ striplt(buf);
+ cprintf("%s ", buf);
+ }
+ cprintf("\n");
+ }
+
+ /* If the component has subcomponents, recurse through them. */
+ for (c = icalcomponent_get_first_component(cal, ICAL_ANY_COMPONENT);
+ (c != 0);
+ c = icalcomponent_get_next_component(cal, ICAL_ANY_COMPONENT)) {
+ /* Recursively process subcomponent */
+ ical_fixed_output_backend(c, recursion_level+1);
+ }
+}
+
+
+
+/*
+ * Function to output vcalendar data as plain text. Nobody uses MSG0
+ * anymore, so really this is just so we expose the vCard data to the full
+ * text indexer.
+ */
+void ical_fixed_output(char *ptr, int len) {
+ icalcomponent *cal;
+ char *stringy_cal;
+
+ stringy_cal = malloc(len + 1);
+ safestrncpy(stringy_cal, ptr, len + 1);
+ cal = icalcomponent_new_from_string(stringy_cal);
+ free(stringy_cal);
+
+ if (cal == NULL) {
+ return;
+ }
+
+ ical_dezonify(cal);
+ ical_fixed_output_backend(cal, 0);
+
+ /* Free the memory we obtained from libical's constructor */
+ icalcomponent_free(cal);
+}
+
+
+#endif /* CITADEL_WITH_CALENDAR_SERVICE */
+
+/*
+ * Register this module with the Citadel server.
+ */
+CTDL_MODULE_INIT(calendar)
+{
+#ifdef CITADEL_WITH_CALENDAR_SERVICE
+ CtdlRegisterMessageHook(ical_obj_beforesave, EVT_BEFORESAVE);
+ CtdlRegisterMessageHook(ical_obj_aftersave, EVT_AFTERSAVE);
+ CtdlRegisterSessionHook(ical_create_room, EVT_LOGIN);
+ CtdlRegisterProtoHook(cmd_ical, "ICAL", "Citadel iCal commands");
+ CtdlRegisterSessionHook(ical_session_startup, EVT_START);
+ CtdlRegisterSessionHook(ical_session_shutdown, EVT_STOP);
+ CtdlRegisterFixedOutputHook("text/calendar", ical_fixed_output);
+#endif
+
+ /* return our Subversion id for the Log */
+ return "$Id$";
+}
+
+
+
+void serv_calendar_destroy(void)
+{
+#ifdef CITADEL_WITH_CALENDAR_SERVICE
+ icaltimezone_free_builtin_timezones();
+#endif
+}
--- /dev/null
+/*
+ * $Id$
+ *
+ * iCalendar implementation for Citadel
+ *
+ */
+
+/*
+ * "server_generated_invitations" tells the Citadel server that the
+ * client wants invitations to be generated and sent out by the
+ * server. Set to 1 to enable this functionality.
+ *
+ * "avoid_sending_invitations" is a server-internal variable. It is
+ * set internally during certain transactions and cleared
+ * automatically.
+ */
+struct cit_ical {
+ int server_generated_invitations;
+ int avoid_sending_invitations;
+};
+
+#define CIT_ICAL CC->CIT_ICAL
+
+/*
+ * When saving a message containing calendar information, we keep track of
+ * some components in the calendar object that need to be inserted into
+ * message fields.
+ */
+struct icalmessagemod {
+ char subject[SIZ];
+ char uid[SIZ];
+ time_t dtstart;
+};
--- /dev/null
+/*
+ * $Id$
+ *
+ * This module handles all "real time" communication between users. The
+ * modes of communication currently supported are Chat and Paging.
+ *
+ */
+#include "sysdep.h"
+#include <stdlib.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <pwd.h>
+#include <errno.h>
+#include <sys/types.h>
+
+#if TIME_WITH_SYS_TIME
+# include <sys/time.h>
+# include <time.h>
+#else
+# if HAVE_SYS_TIME_H
+# include <sys/time.h>
+# else
+# include <time.h>
+# endif
+#endif
+
+#include <sys/wait.h>
+#include <string.h>
+#include <limits.h>
+#include "citadel.h"
+#include "server.h"
+#include "serv_chat.h"
+#include "citserver.h"
+#include "support.h"
+#include "config.h"
+#include "tools.h"
+#include "msgbase.h"
+#include "user_ops.h"
+#include "room_ops.h"
+
+#ifndef HAVE_SNPRINTF
+#include "snprintf.h"
+#endif
+
+
+#include "ctdl_module.h"
+
+
+
+struct ChatLine *ChatQueue = NULL;
+int ChatLastMsg = 0;
+
+/*
+ * This message can be set to anything you want, but it is
+ * checked for consistency so don't move it away from here.
+ */
+#define KICKEDMSG "You have been kicked out of this room."
+
+void allwrite(char *cmdbuf, int flag, char *username)
+{
+ FILE *fp;
+ char bcast[SIZ];
+ char *un;
+ struct ChatLine *clptr, *clnew;
+ time_t now;
+
+ if (CC->fake_username[0])
+ un = CC->fake_username;
+ else
+ un = CC->user.fullname;
+ if (flag == 1) {
+ snprintf(bcast, sizeof bcast, ":|<%s %s>", un, cmdbuf);
+ } else if (flag == 0) {
+ snprintf(bcast, sizeof bcast, "%s|%s", un, cmdbuf);
+ } else if (flag == 2) {
+ snprintf(bcast, sizeof bcast, ":|<%s whispers %s>", un, cmdbuf);
+ } else if (flag == 3) {
+ snprintf(bcast, sizeof bcast, ":|%s", KICKEDMSG);
+ }
+ if ((strcasecmp(cmdbuf, "NOOP")) && (flag != 2)) {
+ fp = fopen(CHATLOG, "a");
+ if (fp != NULL)
+ fprintf(fp, "%s\n", bcast);
+ fclose(fp);
+ }
+ clnew = (struct ChatLine *) malloc(sizeof(struct ChatLine));
+ memset(clnew, 0, sizeof(struct ChatLine));
+ if (clnew == NULL) {
+ fprintf(stderr, "citserver: cannot alloc chat line: %s\n",
+ strerror(errno));
+ return;
+ }
+ time(&now);
+ clnew->next = NULL;
+ clnew->chat_time = now;
+ safestrncpy(clnew->chat_room, CC->room.QRname,
+ sizeof clnew->chat_room);
+ clnew->chat_room[sizeof clnew->chat_room - 1] = 0;
+ if (username) {
+ safestrncpy(clnew->chat_username, username,
+ sizeof clnew->chat_username);
+ clnew->chat_username[sizeof clnew->chat_username - 1] = 0;
+ } else
+ clnew->chat_username[0] = '\0';
+ safestrncpy(clnew->chat_text, bcast, sizeof clnew->chat_text);
+
+ /* Here's the critical section.
+ * First, add the new message to the queue...
+ */
+ begin_critical_section(S_CHATQUEUE);
+ ++ChatLastMsg;
+ clnew->chat_seq = ChatLastMsg;
+ if (ChatQueue == NULL) {
+ ChatQueue = clnew;
+ } else {
+ for (clptr = ChatQueue; clptr->next != NULL; clptr = clptr->next);;
+ clptr->next = clnew;
+ }
+
+ /* Then, before releasing the lock, free the expired messages */
+ while ((ChatQueue != NULL) && (now - ChatQueue->chat_time >= 120L)) {
+ clptr = ChatQueue;
+ ChatQueue = ChatQueue->next;
+ free(clptr);
+ }
+ end_critical_section(S_CHATQUEUE);
+}
+
+
+t_context *find_context(char **unstr)
+{
+ t_context *t_cc, *found_cc = NULL;
+ char *name, *tptr;
+
+ if ((!*unstr) || (!unstr))
+ return (NULL);
+
+ begin_critical_section(S_SESSION_TABLE);
+ for (t_cc = ContextList; ((t_cc) && (!found_cc)); t_cc = t_cc->next) {
+ if (t_cc->fake_username[0])
+ name = t_cc->fake_username;
+ else
+ name = t_cc->curr_user;
+ tptr = *unstr;
+ if ((!strncasecmp(name, tptr, strlen(name))) && (tptr[strlen(name)] == ' ')) {
+ found_cc = t_cc;
+ *unstr = &(tptr[strlen(name) + 1]);
+ }
+ }
+ end_critical_section(S_SESSION_TABLE);
+
+ return (found_cc);
+}
+
+/*
+ * List users in chat.
+ * allflag == 0 = list users in chat
+ * 1 = list users in chat, followed by users not in chat
+ * 2 = display count only
+ */
+
+void do_chat_listing(int allflag)
+{
+ struct CitContext *ccptr;
+ int count = 0;
+ int count_elsewhere = 0;
+ char roomname[ROOMNAMELEN];
+
+ if ((allflag == 0) || (allflag == 1))
+ cprintf(":|\n:| Users currently in chat:\n");
+ begin_critical_section(S_SESSION_TABLE);
+ for (ccptr = ContextList; ccptr != NULL; ccptr = ccptr->next) {
+ if (ccptr->cs_flags & CS_CHAT) {
+ if (!strcasecmp(ccptr->room.QRname,
+ CC->room.QRname)) {
+ ++count;
+ }
+ else {
+ ++count_elsewhere;
+ }
+ }
+
+ GenerateRoomDisplay(roomname, ccptr, CC);
+ if ((CC->user.axlevel < 6)
+ && (strlen(ccptr->fake_roomname)>0)) {
+ strcpy(roomname, ccptr->fake_roomname);
+ }
+
+ if ((ccptr->cs_flags & CS_CHAT)
+ && ((ccptr->cs_flags & CS_STEALTH) == 0)) {
+ if ((allflag == 0) || (allflag == 1)) {
+ cprintf(":| %-25s <%s>:\n",
+ (ccptr->fake_username[0]) ? ccptr->fake_username : ccptr->curr_user,
+ roomname);
+ }
+ }
+ }
+
+ if (allflag == 1) {
+ cprintf(":|\n:| Users not in chat:\n");
+ for (ccptr = ContextList; ccptr != NULL; ccptr = ccptr->next) {
+
+ GenerateRoomDisplay(roomname, ccptr, CC);
+ if ((CC->user.axlevel < 6)
+ && (strlen(ccptr->fake_roomname)>0)) {
+ strcpy(roomname, ccptr->fake_roomname);
+ }
+
+ if (((ccptr->cs_flags & CS_CHAT) == 0)
+ && ((ccptr->cs_flags & CS_STEALTH) == 0)) {
+ cprintf(":| %-25s <%s>:\n",
+ (ccptr->fake_username[0]) ? ccptr->fake_username : ccptr->curr_user,
+ roomname);
+ }
+ }
+ }
+ end_critical_section(S_SESSION_TABLE);
+
+ if (allflag == 2) {
+ if (count > 1) {
+ cprintf(":|There are %d users here.\n", count);
+ }
+ else {
+ cprintf(":|Note: you are the only one here.\n");
+ }
+ if (count_elsewhere > 0) {
+ cprintf(":|There are %d users chatting in other rooms.\n", count_elsewhere);
+ }
+ }
+
+ cprintf(":|\n");
+}
+
+
+void cmd_chat(char *argbuf)
+{
+ char cmdbuf[SIZ];
+ char *un;
+ char *strptr1;
+ int MyLastMsg, ThisLastMsg;
+ struct ChatLine *clptr;
+ struct CitContext *t_context;
+ int retval;
+
+ if (!(CC->logged_in)) {
+ cprintf("%d Not logged in.\n", ERROR + NOT_LOGGED_IN);
+ return;
+ }
+
+ CC->cs_flags = CC->cs_flags | CS_CHAT;
+ cprintf("%d Entering chat mode (type '/help' for available commands)\n",
+ START_CHAT_MODE);
+ unbuffer_output();
+
+ MyLastMsg = ChatLastMsg;
+
+ if ((CC->cs_flags & CS_STEALTH) == 0) {
+ allwrite("<entering chat>", 0, NULL);
+ }
+ strcpy(cmdbuf, "");
+
+ do_chat_listing(2);
+
+ while (1) {
+ int ok_cmd;
+ int linelen;
+
+ ok_cmd = 0;
+ linelen = strlen(cmdbuf);
+ if (linelen > 100) --linelen; /* truncate too-long lines */
+ cmdbuf[linelen + 1] = 0;
+
+ retval = client_read_to(&cmdbuf[linelen], 1, 2);
+
+ if (retval < 0 || CC->kill_me) { /* socket broken? */
+ if ((CC->cs_flags & CS_STEALTH) == 0) {
+ allwrite("<disconnected>", 0, NULL);
+ }
+ return;
+ }
+
+ /* if we have a complete line, do send processing */
+ if (strlen(cmdbuf) > 0)
+ if (cmdbuf[strlen(cmdbuf) - 1] == 10) {
+ cmdbuf[strlen(cmdbuf) - 1] = 0;
+ time(&CC->lastcmd);
+ time(&CC->lastidle);
+
+ if ((!strcasecmp(cmdbuf, "exit"))
+ || (!strcasecmp(cmdbuf, "/exit"))
+ || (!strcasecmp(cmdbuf, "quit"))
+ || (!strcasecmp(cmdbuf, "logout"))
+ || (!strcasecmp(cmdbuf, "logoff"))
+ || (!strcasecmp(cmdbuf, "/q"))
+ || (!strcasecmp(cmdbuf, ".q"))
+ || (!strcasecmp(cmdbuf, "/quit"))
+ )
+ strcpy(cmdbuf, "000");
+
+ if (!strcmp(cmdbuf, "000")) {
+ if ((CC->cs_flags & CS_STEALTH) == 0) {
+ allwrite("<exiting chat>", 0, NULL);
+ }
+ sleep(1);
+ cprintf("000\n");
+ CC->cs_flags = CC->cs_flags - CS_CHAT;
+ return;
+ }
+ if ((!strcasecmp(cmdbuf, "/help"))
+ || (!strcasecmp(cmdbuf, "help"))
+ || (!strcasecmp(cmdbuf, "/?"))
+ || (!strcasecmp(cmdbuf, "?"))) {
+ cprintf(":|\n");
+ cprintf(":|Available commands: \n");
+ cprintf(":|/help (prints this message) \n");
+ cprintf(":|/who (list users currently in chat) \n");
+ cprintf(":|/whobbs (list users in chat -and- elsewhere) \n");
+ cprintf(":|/me ('action' line, ala irc) \n");
+ cprintf(":|/msg (send private message, ala irc) \n");
+ if (is_room_aide()) {
+ cprintf(":|/kick (kick another user out of this room) \n");
+ }
+ cprintf(":|/quit (exit from this chat) \n");
+ cprintf(":|\n");
+ ok_cmd = 1;
+ }
+ if (!strcasecmp(cmdbuf, "/who")) {
+ do_chat_listing(0);
+ ok_cmd = 1;
+ }
+ if (!strcasecmp(cmdbuf, "/whobbs")) {
+ do_chat_listing(1);
+ ok_cmd = 1;
+ }
+ if (!strncasecmp(cmdbuf, "/me ", 4)) {
+ allwrite(&cmdbuf[4], 1, NULL);
+ ok_cmd = 1;
+ }
+ if (!strncasecmp(cmdbuf, "/msg ", 5)) {
+ ok_cmd = 1;
+ strptr1 = &cmdbuf[5];
+ if ((t_context = find_context(&strptr1))) {
+ allwrite(strptr1, 2, CC->curr_user);
+ if (strcasecmp(CC->curr_user, t_context->curr_user))
+ allwrite(strptr1, 2, t_context->curr_user);
+ } else
+ cprintf(":|User not found.\n");
+ cprintf("\n");
+ }
+ /* The /kick function is implemented by sending a specific
+ * message to the kicked-out user's context. When that message
+ * is processed by the read loop, that context will exit.
+ */
+ if ( (!strncasecmp(cmdbuf, "/kick ", 6)) && (is_room_aide()) ) {
+ ok_cmd = 1;
+ strptr1 = &cmdbuf[6];
+ strcat(strptr1, " ");
+ if ((t_context = find_context(&strptr1))) {
+ if (strcasecmp(CC->curr_user, t_context->curr_user))
+ allwrite(strptr1, 3, t_context->curr_user);
+ } else
+ cprintf(":|User not found.\n");
+ cprintf("\n");
+ }
+ if ((cmdbuf[0] != '/') && (strlen(cmdbuf) > 0)) {
+ ok_cmd = 1;
+ allwrite(cmdbuf, 0, NULL);
+ }
+ if ((!ok_cmd) && (cmdbuf[0]) && (cmdbuf[0] != '\n'))
+ cprintf(":|Command %s is not understood.\n", cmdbuf);
+
+ strcpy(cmdbuf, "");
+
+ }
+ /* now check the queue for new incoming stuff */
+
+ if (CC->fake_username[0])
+ un = CC->fake_username;
+ else
+ un = CC->curr_user;
+ if (ChatLastMsg > MyLastMsg) {
+ ThisLastMsg = ChatLastMsg;
+ for (clptr = ChatQueue; clptr != NULL; clptr = clptr->next) {
+ if ((clptr->chat_seq > MyLastMsg) && ((!clptr->chat_username[0]) || (!strncasecmp(un, clptr->chat_username, 32)))) {
+ if ((!clptr->chat_room[0]) || (!strncasecmp(CC->room.QRname, clptr->chat_room, ROOMNAMELEN))) {
+ /* Output new chat data */
+ cprintf("%s\n", clptr->chat_text);
+
+ /* See if we've been force-quitted (kicked etc.) */
+ if (!strcmp(&clptr->chat_text[2], KICKEDMSG)) {
+ allwrite("<kicked out of this room>", 0, NULL);
+ cprintf("000\n");
+ CC->cs_flags = CC->cs_flags - CS_CHAT;
+
+ /* Kick user out of room */
+ CtdlInvtKick(CC->user.fullname, 0);
+
+ /* And return to the Lobby */
+ usergoto(config.c_baseroom, 0, 0, NULL, NULL);
+ return;
+ }
+ }
+ }
+ }
+ MyLastMsg = ThisLastMsg;
+ }
+ }
+}
+
+
+
+/*
+ * Delete any remaining instant messages
+ */
+void delete_instant_messages(void) {
+ struct ExpressMessage *ptr;
+
+ begin_critical_section(S_SESSION_TABLE);
+ while (CC->FirstExpressMessage != NULL) {
+ ptr = CC->FirstExpressMessage->next;
+ if (CC->FirstExpressMessage->text != NULL)
+ free(CC->FirstExpressMessage->text);
+ free(CC->FirstExpressMessage);
+ CC->FirstExpressMessage = ptr;
+ }
+ end_critical_section(S_SESSION_TABLE);
+ }
+
+
+
+
+/*
+ * Poll for instant messages (OLD METHOD -- ***DEPRECATED ***)
+ */
+void cmd_pexp(char *argbuf)
+{
+ struct ExpressMessage *ptr, *holdptr;
+
+ if (CC->FirstExpressMessage == NULL) {
+ cprintf("%d No instant messages waiting.\n", ERROR + MESSAGE_NOT_FOUND);
+ return;
+ }
+ begin_critical_section(S_SESSION_TABLE);
+ ptr = CC->FirstExpressMessage;
+ CC->FirstExpressMessage = NULL;
+ end_critical_section(S_SESSION_TABLE);
+
+ cprintf("%d Express msgs:\n", LISTING_FOLLOWS);
+ while (ptr != NULL) {
+ if (ptr->flags && EM_BROADCAST)
+ cprintf("Broadcast message ");
+ else if (ptr->flags && EM_CHAT)
+ cprintf("Chat request ");
+ else if (ptr->flags && EM_GO_AWAY)
+ cprintf("Please logoff now, as requested ");
+ else
+ cprintf("Message ");
+ cprintf("from %s:\n", ptr->sender);
+ if (ptr->text != NULL)
+ memfmout(ptr->text, 0, "\n");
+
+ holdptr = ptr->next;
+ if (ptr->text != NULL) free(ptr->text);
+ free(ptr);
+ ptr = holdptr;
+ }
+ cprintf("000\n");
+}
+
+
+/*
+ * Get instant messages (new method)
+ */
+void cmd_gexp(char *argbuf) {
+ struct ExpressMessage *ptr;
+
+ if (CC->FirstExpressMessage == NULL) {
+ cprintf("%d No instant messages waiting.\n", ERROR + MESSAGE_NOT_FOUND);
+ return;
+ }
+
+ begin_critical_section(S_SESSION_TABLE);
+ ptr = CC->FirstExpressMessage;
+ CC->FirstExpressMessage = CC->FirstExpressMessage->next;
+ end_critical_section(S_SESSION_TABLE);
+
+ cprintf("%d %d|%ld|%d|%s|%s\n",
+ LISTING_FOLLOWS,
+ ((ptr->next != NULL) ? 1 : 0), /* more msgs? */
+ (long)ptr->timestamp, /* time sent */
+ ptr->flags, /* flags */
+ ptr->sender, /* sender of msg */
+ config.c_nodename /* static for now */
+ );
+
+ if (ptr->text != NULL) {
+ memfmout(ptr->text, 0, "\n");
+ if (ptr->text[strlen(ptr->text)-1] != '\n') cprintf("\n");
+ free(ptr->text);
+ }
+
+ cprintf("000\n");
+ free(ptr);
+}
+
+/*
+ * Asynchronously deliver instant messages
+ */
+void cmd_gexp_async(void) {
+
+ /* Only do this if the session can handle asynchronous protocol */
+ if (CC->is_async == 0) return;
+
+ /* And don't do it if there's nothing to send. */
+ if (CC->FirstExpressMessage == NULL) return;
+
+ cprintf("%d instant msg\n", ASYNC_MSG + ASYNC_GEXP);
+}
+
+/*
+ * Back end support function for send_instant_message() and company
+ */
+void add_xmsg_to_context(struct CitContext *ccptr,
+ struct ExpressMessage *newmsg)
+{
+ struct ExpressMessage *findend;
+
+ if (ccptr->FirstExpressMessage == NULL) {
+ ccptr->FirstExpressMessage = newmsg;
+ }
+ else {
+ findend = ccptr->FirstExpressMessage;
+ while (findend->next != NULL) {
+ findend = findend->next;
+ }
+ findend->next = newmsg;
+ }
+
+ /* If the target context is a session which can handle asynchronous
+ * messages, go ahead and set the flag for that.
+ */
+ if (ccptr->is_async) {
+ ccptr->async_waiting = 1;
+ if (ccptr->state == CON_IDLE) {
+ ccptr->state = CON_READY;
+ }
+ }
+}
+
+
+
+
+/*
+ * This is the back end to the instant message sending function.
+ * Returns the number of users to which the message was sent.
+ * Sending a zero-length message tests for recipients without sending messages.
+ */
+int send_instant_message(char *lun, char *x_user, char *x_msg)
+{
+ int message_sent = 0; /* number of successful sends */
+ struct CitContext *ccptr;
+ struct ExpressMessage *newmsg;
+ char *un;
+ size_t msglen = 0;
+ int do_send = 0; /* set to 1 to actually page, not
+ * just check to see if we can.
+ */
+ struct savelist *sl = NULL; /* list of rooms to save this page */
+ struct savelist *sptr;
+ struct CtdlMessage *logmsg = NULL;
+ long msgnum;
+
+ if (strlen(x_msg) > 0) {
+ msglen = strlen(x_msg) + 4;
+ do_send = 1;
+ }
+
+ /* find the target user's context and append the message */
+ begin_critical_section(S_SESSION_TABLE);
+ for (ccptr = ContextList; ccptr != NULL; ccptr = ccptr->next) {
+
+ if (ccptr->fake_username[0]) {
+ un = ccptr->fake_username;
+ }
+ else {
+ un = ccptr->user.fullname;
+ }
+
+ if ( ((!strcasecmp(un, x_user))
+ || (!strcasecmp(x_user, "broadcast")))
+ && ((ccptr->disable_exp == 0)
+ || (CC->user.axlevel >= 6)) ) {
+ if (do_send) {
+ newmsg = (struct ExpressMessage *)
+ malloc(sizeof (struct ExpressMessage));
+ memset(newmsg, 0,
+ sizeof (struct ExpressMessage));
+ time(&(newmsg->timestamp));
+ safestrncpy(newmsg->sender, lun,
+ sizeof newmsg->sender);
+ if (!strcasecmp(x_user, "broadcast"))
+ newmsg->flags |= EM_BROADCAST;
+ newmsg->text = strdup(x_msg);
+
+ add_xmsg_to_context(ccptr, newmsg);
+
+ /* and log it ... */
+ if (ccptr != CC) {
+ sptr = (struct savelist *)
+ malloc(sizeof(struct savelist));
+ sptr->next = sl;
+ MailboxName(sptr->roomname,
+ sizeof sptr->roomname,
+ &ccptr->user, PAGELOGROOM);
+ sl = sptr;
+ }
+ }
+ ++message_sent;
+ }
+ }
+ end_critical_section(S_SESSION_TABLE);
+
+ /* Log the page to disk if configured to do so */
+ if ( (do_send) && (message_sent) ) {
+
+ logmsg = malloc(sizeof(struct CtdlMessage));
+ memset(logmsg, 0, sizeof(struct CtdlMessage));
+ logmsg->cm_magic = CTDLMESSAGE_MAGIC;
+ logmsg->cm_anon_type = MES_NORMAL;
+ logmsg->cm_format_type = 0;
+ logmsg->cm_fields['A'] = strdup(lun);
+ logmsg->cm_fields['N'] = strdup(NODENAME);
+ logmsg->cm_fields['O'] = strdup(PAGELOGROOM);
+ logmsg->cm_fields['R'] = strdup(x_user);
+ logmsg->cm_fields['M'] = strdup(x_msg);
+
+
+ /* Save a copy of the message in the sender's log room,
+ * creating the room if necessary.
+ */
+ create_room(PAGELOGROOM, 4, "", 0, 1, 0, VIEW_BBS);
+ msgnum = CtdlSubmitMsg(logmsg, NULL, PAGELOGROOM);
+
+ /* Now save a copy in the global log room, if configured */
+ if (strlen(config.c_logpages) > 0) {
+ create_room(config.c_logpages, 3, "", 0, 1, 1, VIEW_BBS);
+ CtdlSaveMsgPointerInRoom(config.c_logpages, msgnum, 0, NULL);
+ }
+
+ /* Save a copy in each recipient's log room, creating those
+ * rooms if necessary. Note that we create as a type 5 room
+ * rather than 4, which indicates that it's a personal room
+ * but we've already supplied the namespace prefix.
+ */
+ while (sl != NULL) {
+ create_room(sl->roomname, 5, "", 0, 1, 1, VIEW_BBS);
+ CtdlSaveMsgPointerInRoom(sl->roomname, msgnum, 0, NULL);
+ sptr = sl->next;
+ free(sl);
+ sl = sptr;
+ }
+
+ CtdlFreeMessage(logmsg);
+ }
+
+ return (message_sent);
+}
+
+/*
+ * send instant messages
+ */
+void cmd_sexp(char *argbuf)
+{
+ int message_sent = 0;
+ char x_user[USERNAME_SIZE];
+ char x_msg[1024];
+ char *lun;
+ char *x_big_msgbuf = NULL;
+
+ if ((!(CC->logged_in)) && (!(CC->internal_pgm))) {
+ cprintf("%d Not logged in.\n", ERROR + NOT_LOGGED_IN);
+ return;
+ }
+ if (CC->fake_username[0])
+ lun = CC->fake_username;
+ else
+ lun = CC->user.fullname;
+
+ extract_token(x_user, argbuf, 0, '|', sizeof x_user);
+ extract_token(x_msg, argbuf, 1, '|', sizeof x_msg);
+
+ if (!x_user[0]) {
+ cprintf("%d You were not previously paged.\n", ERROR + NO_SUCH_USER);
+ return;
+ }
+ if ((!strcasecmp(x_user, "broadcast")) && (CC->user.axlevel < 6)) {
+ cprintf("%d Higher access required to send a broadcast.\n",
+ ERROR + HIGHER_ACCESS_REQUIRED);
+ return;
+ }
+ /* This loop handles text-transfer pages */
+ if (!strcmp(x_msg, "-")) {
+ message_sent = PerformXmsgHooks(lun, x_user, "");
+ if (message_sent == 0) {
+ if (getuser(NULL, x_user))
+ cprintf("%d '%s' does not exist.\n",
+ ERROR + NO_SUCH_USER, x_user);
+ else
+ cprintf("%d '%s' is not logged in "
+ "or is not accepting pages.\n",
+ ERROR + RESOURCE_NOT_OPEN, x_user);
+ return;
+ }
+ unbuffer_output();
+ cprintf("%d Transmit message (will deliver to %d users)\n",
+ SEND_LISTING, message_sent);
+ x_big_msgbuf = malloc(SIZ);
+ memset(x_big_msgbuf, 0, SIZ);
+ while (client_getln(x_msg, sizeof x_msg),
+ strcmp(x_msg, "000")) {
+ x_big_msgbuf = realloc(x_big_msgbuf,
+ strlen(x_big_msgbuf) + strlen(x_msg) + 4);
+ if (strlen(x_big_msgbuf) > 0)
+ if (x_big_msgbuf[strlen(x_big_msgbuf)] != '\n')
+ strcat(x_big_msgbuf, "\n");
+ strcat(x_big_msgbuf, x_msg);
+ }
+ PerformXmsgHooks(lun, x_user, x_big_msgbuf);
+ free(x_big_msgbuf);
+
+ /* This loop handles inline pages */
+ } else {
+ message_sent = PerformXmsgHooks(lun, x_user, x_msg);
+
+ if (message_sent > 0) {
+ if (strlen(x_msg) > 0)
+ cprintf("%d Message sent", CIT_OK);
+ else
+ cprintf("%d Ok to send message", CIT_OK);
+ if (message_sent > 1)
+ cprintf(" to %d users", message_sent);
+ cprintf(".\n");
+ } else {
+ if (getuser(NULL, x_user))
+ cprintf("%d '%s' does not exist.\n",
+ ERROR + NO_SUCH_USER, x_user);
+ else
+ cprintf("%d '%s' is not logged in "
+ "or is not accepting pages.\n",
+ ERROR + RESOURCE_NOT_OPEN, x_user);
+ }
+
+
+ }
+}
+
+
+
+/*
+ * Enter or exit paging-disabled mode
+ */
+void cmd_dexp(char *argbuf)
+{
+ int new_state;
+
+ if (CtdlAccessCheck(ac_logged_in)) return;
+
+ new_state = extract_int(argbuf, 0);
+ if ((new_state == 0) || (new_state == 1)) {
+ CC->disable_exp = new_state;
+ }
+
+ cprintf("%d %d\n", CIT_OK, CC->disable_exp);
+}
+
+
+/*
+ * Request client termination
+ */
+void cmd_reqt(char *argbuf) {
+ struct CitContext *ccptr;
+ int sessions = 0;
+ int which_session;
+ struct ExpressMessage *newmsg;
+
+ if (CtdlAccessCheck(ac_aide)) return;
+ which_session = extract_int(argbuf, 0);
+
+ begin_critical_section(S_SESSION_TABLE);
+ for (ccptr = ContextList; ccptr != NULL; ccptr = ccptr->next) {
+ if ((ccptr->cs_pid == which_session) || (which_session == 0)) {
+
+ newmsg = (struct ExpressMessage *)
+ malloc(sizeof (struct ExpressMessage));
+ memset(newmsg, 0,
+ sizeof (struct ExpressMessage));
+ time(&(newmsg->timestamp));
+ safestrncpy(newmsg->sender, CC->user.fullname,
+ sizeof newmsg->sender);
+ newmsg->flags |= EM_GO_AWAY;
+ newmsg->text = strdup("Automatic logoff requested.");
+
+ add_xmsg_to_context(ccptr, newmsg);
+ ++sessions;
+
+ }
+ }
+ end_critical_section(S_SESSION_TABLE);
+ cprintf("%d Sent termination request to %d sessions.\n", CIT_OK, sessions);
+}
+
+
+
+CTDL_MODULE_INIT(chat)
+{
+ CtdlRegisterProtoHook(cmd_chat, "CHAT", "Begin real-time chat");
+ CtdlRegisterProtoHook(cmd_pexp, "PEXP", "Poll for instant messages");
+ CtdlRegisterProtoHook(cmd_gexp, "GEXP", "Get instant messages");
+ CtdlRegisterProtoHook(cmd_sexp, "SEXP", "Send an instant message");
+ CtdlRegisterProtoHook(cmd_dexp, "DEXP", "Disable instant messages");
+ CtdlRegisterProtoHook(cmd_reqt, "REQT", "Request client termination");
+ CtdlRegisterSessionHook(cmd_gexp_async, EVT_ASYNC);
+ CtdlRegisterSessionHook(delete_instant_messages, EVT_STOP);
+ CtdlRegisterXmsgHook(send_instant_message, XMSG_PRI_LOCAL);
+
+ /* return our Subversion id for the Log */
+ return "$Id$";
+}
+
--- /dev/null
+/* $Id$ */
+void ChatUnloadingTest(void);
+void allwrite (char *cmdbuf, int flag, char *username);
+t_context *find_context (char **unstr);
+void do_chat_listing (int allflag);
+void cmd_chat (char *argbuf);
+void cmd_pexp (char *argbuf); /* arg unused */
+void cmd_sexp (char *argbuf);
+void delete_instant_messages(void);
+void cmd_gexp(char *);
+int send_instant_message(char *, char *, char *);
+
+struct savelist {
+ struct savelist *next;
+ char roomname[ROOMNAMELEN];
+};
--- /dev/null
+/* $Id$ */
+
+#include <string.h>
+#include <unistd.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include "sysdep.h"
+
+#ifdef HAVE_OPENSSL
+#include <openssl/ssl.h>
+#include <openssl/err.h>
+#include <openssl/rand.h>
+#endif
+
+#if TIME_WITH_SYS_TIME
+# include <sys/time.h>
+# include <time.h>
+#else
+# if HAVE_SYS_TIME_H
+# include <sys/time.h>
+# else
+# include <time.h>
+# endif
+#endif
+
+#ifdef HAVE_PTHREAD_H
+#include <pthread.h>
+#endif
+
+#ifdef HAVE_SYS_SELECT_H
+#include <sys/select.h>
+#endif
+
+#include <stdio.h>
+#include "server.h"
+#include "serv_crypto.h"
+#include "sysdep_decls.h"
+#include "citadel.h"
+#include "config.h"
+
+
+#include "ctdl_module.h"
+/* TODO: should we use the standard module init stuff to start this? */
+/* TODO: should we register an event handler to call destruct_ssl? */
+
+#ifdef HAVE_OPENSSL
+SSL_CTX *ssl_ctx; /* SSL context */
+pthread_mutex_t **SSLCritters; /* Things needing locking */
+
+static unsigned long id_callback(void)
+{
+ return (unsigned long) pthread_self();
+}
+
+void destruct_ssl(void)
+{
+ int a;
+ CtdlUnregisterProtoHook(cmd_stls, "STLS");
+ CtdlUnregisterProtoHook(cmd_gtls, "GTLS");
+ for (a = 0; a < CRYPTO_num_locks(); a++)
+ free(SSLCritters[a]);
+ free (SSLCritters);
+}
+
+void init_ssl(void)
+{
+ SSL_METHOD *ssl_method;
+ DH *dh;
+ RSA *rsa=NULL;
+ X509_REQ *req = NULL;
+ X509 *cer = NULL;
+ EVP_PKEY *pk = NULL;
+ EVP_PKEY *req_pkey = NULL;
+ X509_NAME *name = NULL;
+ FILE *fp;
+
+ if (!access(EGD_POOL, F_OK))
+ RAND_egd(EGD_POOL);
+
+ if (!RAND_status()) {
+ lprintf(CTDL_CRIT,
+ "PRNG not adequately seeded, won't do SSL/TLS\n");
+ return;
+ }
+ SSLCritters =
+ malloc(CRYPTO_num_locks() * sizeof(pthread_mutex_t *));
+ if (!SSLCritters) {
+ lprintf(CTDL_EMERG, "citserver: can't allocate memory!!\n");
+ /* Nothing's been initialized, just die */
+ exit(1);
+ } else {
+ int a;
+
+ for (a = 0; a < CRYPTO_num_locks(); a++) {
+ SSLCritters[a] = malloc(sizeof(pthread_mutex_t));
+ if (!SSLCritters[a]) {
+ lprintf(CTDL_EMERG,
+ "citserver: can't allocate memory!!\n");
+ /* Nothing's been initialized, just die */
+ exit(1);
+ }
+ pthread_mutex_init(SSLCritters[a], NULL);
+ }
+ }
+
+ /*
+ * Initialize SSL transport layer
+ */
+ SSL_library_init();
+ SSL_load_error_strings();
+ ssl_method = SSLv23_server_method();
+ if (!(ssl_ctx = SSL_CTX_new(ssl_method))) {
+ lprintf(CTDL_CRIT, "SSL_CTX_new failed: %s\n",
+ ERR_reason_error_string(ERR_get_error()));
+ return;
+ }
+ if (!(SSL_CTX_set_cipher_list(ssl_ctx, CIT_CIPHERS))) {
+ lprintf(CTDL_CRIT, "SSL: No ciphers available\n");
+ SSL_CTX_free(ssl_ctx);
+ ssl_ctx = NULL;
+ return;
+ }
+#if 0
+#if SSLEAY_VERSION_NUMBER >= 0x00906000L
+ SSL_CTX_set_mode(ssl_ctx, SSL_CTX_get_mode(ssl_ctx) |
+ SSL_MODE_AUTO_RETRY);
+#endif
+#endif
+
+ CRYPTO_set_locking_callback(ssl_lock);
+ CRYPTO_set_id_callback(id_callback);
+
+ /* Load DH parameters into the context */
+ dh = DH_new();
+ if (!dh) {
+ lprintf(CTDL_CRIT, "init_ssl() can't allocate a DH object: %s\n",
+ ERR_reason_error_string(ERR_get_error()));
+ SSL_CTX_free(ssl_ctx);
+ ssl_ctx = NULL;
+ return;
+ }
+ if (!(BN_hex2bn(&(dh->p), DH_P))) {
+ lprintf(CTDL_CRIT, "init_ssl() can't assign DH_P: %s\n",
+ ERR_reason_error_string(ERR_get_error()));
+ SSL_CTX_free(ssl_ctx);
+ ssl_ctx = NULL;
+ return;
+ }
+ if (!(BN_hex2bn(&(dh->g), DH_G))) {
+ lprintf(CTDL_CRIT, "init_ssl() can't assign DH_G: %s\n",
+ ERR_reason_error_string(ERR_get_error()));
+ SSL_CTX_free(ssl_ctx);
+ ssl_ctx = NULL;
+ return;
+ }
+ dh->length = DH_L;
+ SSL_CTX_set_tmp_dh(ssl_ctx, dh);
+ DH_free(dh);
+
+ /* Get our certificates in order.
+ * First, create the key/cert directory if it's not there already...
+ */
+ mkdir(ctdl_key_dir, 0700);
+
+ /*
+ * Generate a key pair if we don't have one.
+ */
+ if (access(file_crpt_file_key, R_OK) != 0) {
+ lprintf(CTDL_INFO, "Generating RSA key pair.\n");
+ rsa = RSA_generate_key(1024, /* modulus size */
+ 65537, /* exponent */
+ NULL, /* no callback */
+ NULL); /* no callback */
+ if (rsa == NULL) {
+ lprintf(CTDL_CRIT, "Key generation failed: %s\n",
+ ERR_reason_error_string(ERR_get_error()));
+ }
+ if (rsa != NULL) {
+ fp = fopen(file_crpt_file_key, "w");
+ if (fp != NULL) {
+ chmod(file_crpt_file_key, 0600);
+ if (PEM_write_RSAPrivateKey(fp, /* the file */
+ rsa, /* the key */
+ NULL, /* no enc */
+ NULL, /* no passphr */
+ 0, /* no passphr */
+ NULL, /* no callbk */
+ NULL /* no callbk */
+ ) != 1) {
+ lprintf(CTDL_CRIT, "Cannot write key: %s\n",
+ ERR_reason_error_string(ERR_get_error()));
+ unlink(file_crpt_file_key);
+ }
+ fclose(fp);
+ }
+ RSA_free(rsa);
+ }
+ }
+
+ /*
+ * Generate a CSR if we don't have one.
+ */
+ if (access(file_crpt_file_csr, R_OK) != 0) {
+ lprintf(CTDL_INFO, "Generating a certificate signing request.\n");
+
+ /*
+ * Read our key from the file. No, we don't just keep this
+ * in memory from the above key-generation function, because
+ * there is the possibility that the key was already on disk
+ * and we didn't just generate it now.
+ */
+ fp = fopen(file_crpt_file_key, "r");
+ if (fp) {
+ rsa = PEM_read_RSAPrivateKey(fp, NULL, NULL, NULL);
+ fclose(fp);
+ }
+
+ if (rsa) {
+
+ /* Create a public key from the private key */
+ if (pk=EVP_PKEY_new(), pk != NULL) {
+ EVP_PKEY_assign_RSA(pk, rsa);
+ if (req = X509_REQ_new(), req != NULL) {
+
+ /* Set the public key */
+ X509_REQ_set_pubkey(req, pk);
+ X509_REQ_set_version(req, 0L);
+
+ name = X509_REQ_get_subject_name(req);
+
+ /* Tell it who we are */
+
+ /*
+ X509_NAME_add_entry_by_txt(name, "C",
+ MBSTRING_ASC, "US", -1, -1, 0);
+
+ X509_NAME_add_entry_by_txt(name, "ST",
+ MBSTRING_ASC, "New York", -1, -1, 0);
+
+ X509_NAME_add_entry_by_txt(name, "L",
+ MBSTRING_ASC, "Mount Kisco", -1, -1, 0);
+ */
+
+ X509_NAME_add_entry_by_txt(name, "O",
+ MBSTRING_ASC, config.c_humannode, -1, -1, 0);
+
+ X509_NAME_add_entry_by_txt(name, "OU",
+ MBSTRING_ASC, "Citadel server", -1, -1, 0);
+
+ /* X509_NAME_add_entry_by_txt(name, "CN",
+ MBSTRING_ASC, config.c_fqdn, -1, -1, 0);
+ */
+
+ X509_NAME_add_entry_by_txt(name, "CN",
+ MBSTRING_ASC, "*", -1, -1, 0);
+
+ X509_REQ_set_subject_name(req, name);
+
+ /* Sign the CSR */
+ if (!X509_REQ_sign(req, pk, EVP_md5())) {
+ lprintf(CTDL_CRIT, "X509_REQ_sign(): error\n");
+ }
+ else {
+ /* Write it to disk. */
+ fp = fopen(file_crpt_file_csr, "w");
+ if (fp != NULL) {
+ chmod(file_crpt_file_csr, 0600);
+ PEM_write_X509_REQ(fp, req);
+ fclose(fp);
+ }
+ }
+
+ X509_REQ_free(req);
+ }
+ }
+
+ RSA_free(rsa);
+ }
+
+ else {
+ lprintf(CTDL_CRIT, "Unable to read private key.\n");
+ }
+ }
+
+
+
+ /*
+ * Generate a self-signed certificate if we don't have one.
+ */
+ if (access(file_crpt_file_cer, R_OK) != 0) {
+ lprintf(CTDL_INFO, "Generating a self-signed certificate.\n");
+
+ /* Same deal as before: always read the key from disk because
+ * it may or may not have just been generated.
+ */
+ fp = fopen(file_crpt_file_key, "r");
+ if (fp) {
+ rsa = PEM_read_RSAPrivateKey(fp, NULL, NULL, NULL);
+ fclose(fp);
+ }
+
+ /* This also holds true for the CSR. */
+ req = NULL;
+ cer = NULL;
+ pk = NULL;
+ if (rsa) {
+ if (pk=EVP_PKEY_new(), pk != NULL) {
+ EVP_PKEY_assign_RSA(pk, rsa);
+ }
+
+ fp = fopen(file_crpt_file_csr, "r");
+ if (fp) {
+ req = PEM_read_X509_REQ(fp, NULL, NULL, NULL);
+ fclose(fp);
+ }
+
+ if (req) {
+ if (cer = X509_new(), cer != NULL) {
+
+ ASN1_INTEGER_set(X509_get_serialNumber(cer), 0);
+ X509_set_issuer_name(cer, req->req_info->subject);
+ X509_set_subject_name(cer, req->req_info->subject);
+ X509_gmtime_adj(X509_get_notBefore(cer),0);
+ X509_gmtime_adj(X509_get_notAfter(cer),(long)60*60*24*SIGN_DAYS);
+ req_pkey = X509_REQ_get_pubkey(req);
+ X509_set_pubkey(cer, req_pkey);
+ EVP_PKEY_free(req_pkey);
+
+ /* Sign the cert */
+ if (!X509_sign(cer, pk, EVP_md5())) {
+ lprintf(CTDL_CRIT, "X509_sign(): error\n");
+ }
+ else {
+ /* Write it to disk. */
+ fp = fopen(file_crpt_file_cer, "w");
+ if (fp != NULL) {
+ chmod(file_crpt_file_cer, 0600);
+ PEM_write_X509(fp, cer);
+ fclose(fp);
+ }
+ }
+ X509_free(cer);
+ }
+ }
+
+ RSA_free(rsa);
+ }
+ }
+
+
+ /*
+ * Now try to bind to the key and certificate.
+ */
+ SSL_CTX_use_certificate_chain_file(ssl_ctx, file_crpt_file_cer);
+ SSL_CTX_use_PrivateKey_file(ssl_ctx, file_crpt_file_key, SSL_FILETYPE_PEM);
+ if ( !SSL_CTX_check_private_key(ssl_ctx) ) {
+ lprintf(CTDL_CRIT, "Cannot install certificate: %s\n",
+ ERR_reason_error_string(ERR_get_error()));
+ }
+
+ /* Finally let the server know we're here */
+ CtdlRegisterProtoHook(cmd_stls, "STLS", "Start SSL/TLS session");
+ CtdlRegisterProtoHook(cmd_gtls, "GTLS",
+ "Get SSL/TLS session status");
+ CtdlRegisterSessionHook(endtls, EVT_STOP);
+}
+
+
+/*
+ * client_write_ssl() Send binary data to the client encrypted.
+ */
+void client_write_ssl(char *buf, int nbytes)
+{
+ int retval;
+ int nremain;
+ char junk[1];
+
+ nremain = nbytes;
+
+ while (nremain > 0) {
+ if (SSL_want_write(CC->ssl)) {
+ if ((SSL_read(CC->ssl, junk, 0)) < 1) {
+ lprintf(CTDL_DEBUG, "SSL_read in client_write: %s\n", ERR_reason_error_string(ERR_get_error()));
+ }
+ }
+ retval =
+ SSL_write(CC->ssl, &buf[nbytes - nremain], nremain);
+ if (retval < 1) {
+ long errval;
+
+ errval = SSL_get_error(CC->ssl, retval);
+ if (errval == SSL_ERROR_WANT_READ ||
+ errval == SSL_ERROR_WANT_WRITE) {
+ sleep(1);
+ continue;
+ }
+ lprintf(CTDL_DEBUG, "SSL_write got error %ld, ret %d\n", errval, retval);
+ if (retval == -1)
+ lprintf(CTDL_DEBUG, "errno is %d\n", errno);
+ endtls();
+ client_write(&buf[nbytes - nremain], nremain);
+ return;
+ }
+ nremain -= retval;
+ }
+}
+
+
+/*
+ * client_read_ssl() - read data from the encrypted layer.
+ */
+int client_read_ssl(char *buf, int bytes, int timeout)
+{
+#if 0
+ fd_set rfds;
+ struct timeval tv;
+ int retval;
+ int s;
+#endif
+ int len, rlen;
+ char junk[1];
+
+ len = 0;
+ while (len < bytes) {
+#if 0
+ /*
+ * This code is disabled because we don't need it when
+ * using blocking reads (which we are). -IO
+ */
+ FD_ZERO(&rfds);
+ s = BIO_get_fd(CC->ssl->rbio, NULL);
+ FD_SET(s, &rfds);
+ tv.tv_sec = timeout;
+ tv.tv_usec = 0;
+
+ retval = select(s + 1, &rfds, NULL, NULL, &tv);
+
+ if (FD_ISSET(s, &rfds) == 0) {
+ return (0);
+ }
+
+#endif
+ if (SSL_want_read(CC->ssl)) {
+ if ((SSL_write(CC->ssl, junk, 0)) < 1) {
+ lprintf(CTDL_DEBUG, "SSL_write in client_read: %s\n", ERR_reason_error_string(ERR_get_error()));
+ }
+ }
+ rlen = SSL_read(CC->ssl, &buf[len], bytes - len);
+ if (rlen < 1) {
+ long errval;
+
+ errval = SSL_get_error(CC->ssl, rlen);
+ if (errval == SSL_ERROR_WANT_READ ||
+ errval == SSL_ERROR_WANT_WRITE) {
+ sleep(1);
+ continue;
+ }
+ lprintf(CTDL_DEBUG, "SSL_read got error %ld\n", errval);
+ endtls();
+ return (client_read_to
+ (&buf[len], bytes - len, timeout));
+ }
+ len += rlen;
+ }
+ return (1);
+}
+
+
+/*
+ * CtdlStartTLS() starts SSL/TLS encryption for the current session. It
+ * must be supplied with pre-generated strings for responses of "ok," "no
+ * support for TLS," and "error" so that we can use this in any protocol.
+ */
+void CtdlStartTLS(char *ok_response, char *nosup_response,
+ char *error_response) {
+
+ int retval, bits, alg_bits;
+
+ if (!ssl_ctx) {
+ lprintf(CTDL_CRIT, "SSL failed: no ssl_ctx exists?\n");
+ if (nosup_response != NULL) cprintf("%s", nosup_response);
+ return;
+ }
+ if (!(CC->ssl = SSL_new(ssl_ctx))) {
+ lprintf(CTDL_CRIT, "SSL_new failed: %s\n",
+ ERR_reason_error_string(ERR_get_error()));
+ if (error_response != NULL) cprintf("%s", error_response);
+ return;
+ }
+ if (!(SSL_set_fd(CC->ssl, CC->client_socket))) {
+ lprintf(CTDL_CRIT, "SSL_set_fd failed: %s\n",
+ ERR_reason_error_string(ERR_get_error()));
+ SSL_free(CC->ssl);
+ CC->ssl = NULL;
+ if (error_response != NULL) cprintf("%s", error_response);
+ return;
+ }
+ if (ok_response != NULL) cprintf("%s", ok_response);
+ retval = SSL_accept(CC->ssl);
+ if (retval < 1) {
+ /*
+ * Can't notify the client of an error here; they will
+ * discover the problem at the SSL layer and should
+ * revert to unencrypted communications.
+ */
+ long errval;
+ char error_string[128];
+
+ errval = SSL_get_error(CC->ssl, retval);
+ lprintf(CTDL_CRIT, "SSL_accept failed: retval=%d, errval=%ld, err=%s\n",
+ retval,
+ errval,
+ ERR_error_string(errval, error_string)
+ );
+ SSL_free(CC->ssl);
+ CC->ssl = NULL;
+ return;
+ }
+ BIO_set_close(CC->ssl->rbio, BIO_NOCLOSE);
+ bits = SSL_CIPHER_get_bits(SSL_get_current_cipher(CC->ssl), &alg_bits);
+ lprintf(CTDL_INFO, "SSL/TLS using %s on %s (%d of %d bits)\n",
+ SSL_CIPHER_get_name(SSL_get_current_cipher(CC->ssl)),
+ SSL_CIPHER_get_version(SSL_get_current_cipher(CC->ssl)),
+ bits, alg_bits);
+ CC->redirect_ssl = 1;
+}
+
+
+/*
+ * cmd_stls() starts SSL/TLS encryption for the current session
+ */
+void cmd_stls(char *params)
+{
+ char ok_response[SIZ];
+ char nosup_response[SIZ];
+ char error_response[SIZ];
+
+ unbuffer_output();
+
+ sprintf(ok_response,
+ "%d Begin TLS negotiation now\n",
+ CIT_OK);
+ sprintf(nosup_response,
+ "%d TLS not supported here\n",
+ ERROR + CMD_NOT_SUPPORTED);
+ sprintf(error_response,
+ "%d TLS negotiation error\n",
+ ERROR + INTERNAL_ERROR);
+
+ CtdlStartTLS(ok_response, nosup_response, error_response);
+}
+
+
+/*
+ * cmd_gtls() returns status info about the TLS connection
+ */
+void cmd_gtls(char *params)
+{
+ int bits, alg_bits;
+
+ if (!CC->ssl || !CC->redirect_ssl) {
+ cprintf("%d Session is not encrypted.\n", ERROR);
+ return;
+ }
+ bits =
+ SSL_CIPHER_get_bits(SSL_get_current_cipher(CC->ssl),
+ &alg_bits);
+ cprintf("%d %s|%s|%d|%d\n", CIT_OK,
+ SSL_CIPHER_get_version(SSL_get_current_cipher(CC->ssl)),
+ SSL_CIPHER_get_name(SSL_get_current_cipher(CC->ssl)),
+ alg_bits, bits);
+}
+
+
+/*
+ * endtls() shuts down the TLS connection
+ *
+ * WARNING: This may make your session vulnerable to a known plaintext
+ * attack in the current implmentation.
+ */
+void endtls(void)
+{
+ if (!CC->ssl) {
+ CC->redirect_ssl = 0;
+ return;
+ }
+
+ lprintf(CTDL_INFO, "Ending SSL/TLS\n");
+ SSL_shutdown(CC->ssl);
+ SSL_free(CC->ssl);
+ CC->ssl = NULL;
+ CC->redirect_ssl = 0;
+}
+
+
+/*
+ * ssl_lock() callback for OpenSSL mutex locks
+ */
+void ssl_lock(int mode, int n, const char *file, int line)
+{
+ if (mode & CRYPTO_LOCK)
+ pthread_mutex_lock(SSLCritters[n]);
+ else
+ pthread_mutex_unlock(SSLCritters[n]);
+}
+#endif /* HAVE_OPENSSL */
--- /dev/null
+/*
+ * $Id$
+ *
+ * This module handles the expiry of old messages and the purging of old users.
+ *
+ */
+
+
+/*
+ * A brief technical discussion:
+ *
+ * Several of the purge operations found in this module operate in two
+ * stages: the first stage generates a linked list of objects to be deleted,
+ * then the second stage deletes all listed objects from the database.
+ *
+ * At first glance this may seem cumbersome and unnecessary. The reason it is
+ * implemented in this way is because Berkeley DB, and possibly other backends
+ * we may hook into in the future, explicitly do _not_ support the deletion of
+ * records from a file while the file is being traversed. The delete operation
+ * will succeed, but the traversal is not guaranteed to visit every object if
+ * this is done. Therefore we utilize the two-stage purge.
+ *
+ * When using Berkeley DB, there's another reason for the two-phase purge: we
+ * don't want the entire thing being done as one huge transaction.
+ */
+
+
+#include "sysdep.h"
+#include <stdlib.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <pwd.h>
+#include <errno.h>
+#include <sys/types.h>
+
+#if TIME_WITH_SYS_TIME
+# include <sys/time.h>
+# include <time.h>
+#else
+# if HAVE_SYS_TIME_H
+# include <sys/time.h>
+# else
+# include <time.h>
+# endif
+#endif
+
+#include <sys/wait.h>
+#include <string.h>
+#include <limits.h>
+#include "citadel.h"
+#include "server.h"
+#include "citserver.h"
+#include "support.h"
+#include "config.h"
+#include "room_ops.h"
+#include "policy.h"
+#include "database.h"
+#include "msgbase.h"
+#include "user_ops.h"
+#include "control.h"
+#include "serv_network.h"
+#include "tools.h"
+
+
+#include "ctdl_module.h"
+
+
+struct PurgeList {
+ struct PurgeList *next;
+ char name[ROOMNAMELEN]; /* use the larger of username or roomname */
+};
+
+struct VPurgeList {
+ struct VPurgeList *next;
+ long vp_roomnum;
+ long vp_roomgen;
+ long vp_usernum;
+};
+
+struct ValidRoom {
+ struct ValidRoom *next;
+ long vr_roomnum;
+ long vr_roomgen;
+};
+
+struct ValidUser {
+ struct ValidUser *next;
+ long vu_usernum;
+};
+
+
+struct ctdlroomref {
+ struct ctdlroomref *next;
+ long msgnum;
+};
+
+struct UPurgeList {
+ struct UPurgeList *next;
+ char up_key[256];
+};
+
+struct EPurgeList {
+ struct EPurgeList *next;
+ int ep_keylen;
+ char *ep_key;
+};
+
+
+struct PurgeList *UserPurgeList = NULL;
+struct PurgeList *RoomPurgeList = NULL;
+struct ValidRoom *ValidRoomList = NULL;
+struct ValidUser *ValidUserList = NULL;
+int messages_purged;
+int users_not_purged;
+
+struct ctdlroomref *rr = NULL;
+
+extern struct CitContext *ContextList;
+
+
+/*
+ * First phase of message purge -- gather the locations of messages which
+ * qualify for purging and write them to a temp file.
+ */
+void GatherPurgeMessages(struct ctdlroom *qrbuf, void *data) {
+ struct ExpirePolicy epbuf;
+ long delnum;
+ time_t xtime, now;
+ struct CtdlMessage *msg = NULL;
+ int a;
+ struct cdbdata *cdbfr;
+ long *msglist = NULL;
+ int num_msgs = 0;
+ FILE *purgelist;
+
+ purgelist = (FILE *)data;
+ fprintf(purgelist, "r=%s\n", qrbuf->QRname);
+
+ time(&now);
+ GetExpirePolicy(&epbuf, qrbuf);
+
+ /* If the room is set to never expire messages ... do nothing */
+ if (epbuf.expire_mode == EXPIRE_NEXTLEVEL) return;
+ if (epbuf.expire_mode == EXPIRE_MANUAL) return;
+
+ cdbfr = cdb_fetch(CDB_MSGLISTS, &qrbuf->QRnumber, sizeof(long));
+
+ if (cdbfr != NULL) {
+ msglist = malloc(cdbfr->len);
+ memcpy(msglist, cdbfr->ptr, cdbfr->len);
+ num_msgs = cdbfr->len / sizeof(long);
+ cdb_free(cdbfr);
+ }
+
+ /* Nothing to do if there aren't any messages */
+ if (num_msgs == 0) {
+ if (msglist != NULL) free(msglist);
+ return;
+ }
+
+ /* If the room is set to expire by count, do that */
+ if (epbuf.expire_mode == EXPIRE_NUMMSGS) {
+ if (num_msgs > epbuf.expire_value) {
+ for (a=0; a<(num_msgs - epbuf.expire_value); ++a) {
+ fprintf(purgelist, "m=%ld\n", msglist[a]);
+ ++messages_purged;
+ }
+ }
+ }
+
+ /* If the room is set to expire by age... */
+ if (epbuf.expire_mode == EXPIRE_AGE) {
+ for (a=0; a<num_msgs; ++a) {
+ delnum = msglist[a];
+
+ msg = CtdlFetchMessage(delnum, 0); /* dont need body */
+ if (msg != NULL) {
+ xtime = atol(msg->cm_fields['T']);
+ CtdlFreeMessage(msg);
+ } else {
+ xtime = 0L;
+ }
+
+ if ((xtime > 0L)
+ && (now - xtime > (time_t)(epbuf.expire_value * 86400L))) {
+ fprintf(purgelist, "m=%ld\n", delnum);
+ ++messages_purged;
+ }
+ }
+ }
+
+ if (msglist != NULL) free(msglist);
+}
+
+
+/*
+ * Second phase of message purge -- read list of msgs from temp file and
+ * delete them.
+ */
+void DoPurgeMessages(FILE *purgelist) {
+ char roomname[ROOMNAMELEN];
+ long msgnum;
+ char buf[SIZ];
+
+ rewind(purgelist);
+ strcpy(roomname, "nonexistent room ___ ___");
+ while (fgets(buf, sizeof buf, purgelist) != NULL) {
+ buf[strlen(buf)-1]=0;
+ if (!strncasecmp(buf, "r=", 2)) {
+ strcpy(roomname, &buf[2]);
+ }
+ if (!strncasecmp(buf, "m=", 2)) {
+ msgnum = atol(&buf[2]);
+ if (msgnum > 0L) {
+ CtdlDeleteMessages(roomname, &msgnum, 1, "");
+ }
+ }
+ }
+}
+
+
+void PurgeMessages(void) {
+ FILE *purgelist;
+
+ lprintf(CTDL_DEBUG, "PurgeMessages() called\n");
+ messages_purged = 0;
+
+ purgelist = tmpfile();
+ if (purgelist == NULL) {
+ lprintf(CTDL_CRIT, "Can't create purgelist temp file: %s\n",
+ strerror(errno));
+ return;
+ }
+
+ ForEachRoom(GatherPurgeMessages, (void *)purgelist );
+ DoPurgeMessages(purgelist);
+ fclose(purgelist);
+}
+
+
+void AddValidUser(struct ctdluser *usbuf, void *data) {
+ struct ValidUser *vuptr;
+
+ vuptr = (struct ValidUser *)malloc(sizeof(struct ValidUser));
+ vuptr->next = ValidUserList;
+ vuptr->vu_usernum = usbuf->usernum;
+ ValidUserList = vuptr;
+}
+
+void AddValidRoom(struct ctdlroom *qrbuf, void *data) {
+ struct ValidRoom *vrptr;
+
+ vrptr = (struct ValidRoom *)malloc(sizeof(struct ValidRoom));
+ vrptr->next = ValidRoomList;
+ vrptr->vr_roomnum = qrbuf->QRnumber;
+ vrptr->vr_roomgen = qrbuf->QRgen;
+ ValidRoomList = vrptr;
+}
+
+void DoPurgeRooms(struct ctdlroom *qrbuf, void *data) {
+ time_t age, purge_secs;
+ struct PurgeList *pptr;
+ struct ValidUser *vuptr;
+ int do_purge = 0;
+
+ /* For mailbox rooms, there's only one purging rule: if the user who
+ * owns the room still exists, we keep the room; otherwise, we purge
+ * it. Bypass any other rules.
+ */
+ if (qrbuf->QRflags & QR_MAILBOX) {
+ /* if user not found, do_purge will be 1 */
+ do_purge = 1;
+ for (vuptr=ValidUserList; vuptr!=NULL; vuptr=vuptr->next) {
+ if (vuptr->vu_usernum == atol(qrbuf->QRname)) {
+ do_purge = 0;
+ }
+ }
+ }
+ else {
+ /* Any of these attributes render a room non-purgable */
+ if (qrbuf->QRflags & QR_PERMANENT) return;
+ if (qrbuf->QRflags & QR_DIRECTORY) return;
+ if (qrbuf->QRflags & QR_NETWORK) return;
+ if (!strcasecmp(qrbuf->QRname, SYSCONFIGROOM)) return;
+ if (is_noneditable(qrbuf)) return;
+
+ /* If we don't know the modification date, be safe and don't purge */
+ if (qrbuf->QRmtime <= (time_t)0) return;
+
+ /* If no room purge time is set, be safe and don't purge */
+ if (config.c_roompurge < 0) return;
+
+ /* Otherwise, check the date of last modification */
+ age = time(NULL) - (qrbuf->QRmtime);
+ purge_secs = (time_t)config.c_roompurge * (time_t)86400;
+ if (purge_secs <= (time_t)0) return;
+ lprintf(CTDL_DEBUG, "<%s> is <%ld> seconds old\n", qrbuf->QRname, (long)age);
+ if (age > purge_secs) do_purge = 1;
+ } /* !QR_MAILBOX */
+
+ if (do_purge) {
+ pptr = (struct PurgeList *) malloc(sizeof(struct PurgeList));
+ pptr->next = RoomPurgeList;
+ strcpy(pptr->name, qrbuf->QRname);
+ RoomPurgeList = pptr;
+ }
+
+}
+
+
+
+int PurgeRooms(void) {
+ struct PurgeList *pptr;
+ int num_rooms_purged = 0;
+ struct ctdlroom qrbuf;
+ struct ValidUser *vuptr;
+ char *transcript = NULL;
+
+ lprintf(CTDL_DEBUG, "PurgeRooms() called\n");
+
+
+ /* Load up a table full of valid user numbers so we can delete
+ * user-owned rooms for users who no longer exist */
+ ForEachUser(AddValidUser, NULL);
+
+ /* Then cycle through the room file */
+ ForEachRoom(DoPurgeRooms, NULL);
+
+ /* Free the valid user list */
+ while (ValidUserList != NULL) {
+ vuptr = ValidUserList->next;
+ free(ValidUserList);
+ ValidUserList = vuptr;
+ }
+
+
+ transcript = malloc(SIZ);
+ strcpy(transcript, "The following rooms have been auto-purged:\n");
+
+ while (RoomPurgeList != NULL) {
+ if (getroom(&qrbuf, RoomPurgeList->name) == 0) {
+ transcript=realloc(transcript, strlen(transcript)+SIZ);
+ snprintf(&transcript[strlen(transcript)], SIZ, " %s\n",
+ qrbuf.QRname);
+ delete_room(&qrbuf);
+ }
+ pptr = RoomPurgeList->next;
+ free(RoomPurgeList);
+ RoomPurgeList = pptr;
+ ++num_rooms_purged;
+ }
+
+ if (num_rooms_purged > 0) aide_message(transcript, "Room Autopurger Message");
+ free(transcript);
+
+ lprintf(CTDL_DEBUG, "Purged %d rooms.\n", num_rooms_purged);
+ return(num_rooms_purged);
+}
+
+
+/*
+ * Back end function to check user accounts for associated Unix accounts
+ * which no longer exist. (Only relevant for host auth mode.)
+ */
+void do_uid_user_purge(struct ctdluser *us, void *data) {
+ struct PurgeList *pptr;
+
+ if ((us->uid != (-1)) && (us->uid != CTDLUID)) {
+ if (getpwuid(us->uid) == NULL) {
+ pptr = (struct PurgeList *)
+ malloc(sizeof(struct PurgeList));
+ pptr->next = UserPurgeList;
+ strcpy(pptr->name, us->fullname);
+ UserPurgeList = pptr;
+ }
+ }
+ else {
+ ++users_not_purged;
+ }
+}
+
+
+
+/*
+ * Back end function to check user accounts for expiration.
+ */
+void do_user_purge(struct ctdluser *us, void *data) {
+ int purge;
+ time_t now;
+ time_t purge_time;
+ struct PurgeList *pptr;
+
+ /* Set purge time; if the user overrides the system default, use it */
+ if (us->USuserpurge > 0) {
+ purge_time = ((time_t)us->USuserpurge) * 86400L;
+ }
+ else {
+ purge_time = ((time_t)config.c_userpurge) * 86400L;
+ }
+
+ /* The default rule is to not purge. */
+ purge = 0;
+
+ /* If the user hasn't called in two months, his/her account
+ * has expired, so purge the record.
+ */
+ now = time(NULL);
+ if ((now - us->lastcall) > purge_time) purge = 1;
+
+ /* If the user set his/her password to 'deleteme', he/she
+ * wishes to be deleted, so purge the record.
+ */
+ if (!strcasecmp(us->password, "deleteme")) purge = 1;
+
+ /* If the record is marked as permanent, don't purge it.
+ */
+ if (us->flags & US_PERM) purge = 0;
+
+ /* If the user is an Aide, don't purge him/her/it.
+ */
+ if (us->axlevel == 6) purge = 0;
+
+ /* If the access level is 0, the record should already have been
+ * deleted, but maybe the user was logged in at the time or something.
+ * Delete the record now.
+ */
+ if (us->axlevel == 0) purge = 1;
+
+ /* 0 calls is impossible. If there are 0 calls, it must
+ * be a corrupted record, so purge it.
+ */
+ if (us->timescalled == 0) purge = 1;
+
+ /* User number 0, as well as any negative user number, is
+ * also impossible.
+ */
+ if (us->usernum < 1L) purge = 1;
+
+ if (purge == 1) {
+ pptr = (struct PurgeList *) malloc(sizeof(struct PurgeList));
+ pptr->next = UserPurgeList;
+ strcpy(pptr->name, us->fullname);
+ UserPurgeList = pptr;
+ }
+ else {
+ ++users_not_purged;
+ }
+
+}
+
+
+
+int PurgeUsers(void) {
+ struct PurgeList *pptr;
+ int num_users_purged = 0;
+ char *transcript = NULL;
+
+ lprintf(CTDL_DEBUG, "PurgeUsers() called\n");
+ users_not_purged = 0;
+
+ if (config.c_auth_mode == 1) {
+ /* host auth mode */
+ ForEachUser(do_uid_user_purge, NULL);
+ }
+ else {
+ /* native auth mode */
+ if (config.c_userpurge > 0) {
+ ForEachUser(do_user_purge, NULL);
+ }
+ }
+
+ transcript = malloc(SIZ);
+
+ if (users_not_purged == 0) {
+ strcpy(transcript, "The auto-purger was told to purge every user. It is\n"
+ "refusing to do this because it usually indicates a problem\n"
+ "such as an inability to communicate with a name service.\n"
+ );
+ while (UserPurgeList != NULL) {
+ pptr = UserPurgeList->next;
+ free(UserPurgeList);
+ UserPurgeList = pptr;
+ ++num_users_purged;
+ }
+ }
+
+ else {
+ strcpy(transcript, "The following users have been auto-purged:\n");
+ while (UserPurgeList != NULL) {
+ transcript=realloc(transcript, strlen(transcript)+SIZ);
+ snprintf(&transcript[strlen(transcript)], SIZ, " %s\n",
+ UserPurgeList->name);
+ purge_user(UserPurgeList->name);
+ pptr = UserPurgeList->next;
+ free(UserPurgeList);
+ UserPurgeList = pptr;
+ ++num_users_purged;
+ }
+ }
+
+ if (num_users_purged > 0) aide_message(transcript, "User Purge Message");
+ free(transcript);
+
+ lprintf(CTDL_DEBUG, "Purged %d users.\n", num_users_purged);
+ return(num_users_purged);
+}
+
+
+/*
+ * Purge visits
+ *
+ * This is a really cumbersome "garbage collection" function. We have to
+ * delete visits which refer to rooms and/or users which no longer exist. In
+ * order to prevent endless traversals of the room and user files, we first
+ * build linked lists of rooms and users which _do_ exist on the system, then
+ * traverse the visit file, checking each record against those two lists and
+ * purging the ones that do not have a match on _both_ lists. (Remember, if
+ * either the room or user being referred to is no longer on the system, the
+ * record is completely useless.)
+ */
+int PurgeVisits(void) {
+ struct cdbdata *cdbvisit;
+ struct visit vbuf;
+ struct VPurgeList *VisitPurgeList = NULL;
+ struct VPurgeList *vptr;
+ int purged = 0;
+ char IndexBuf[32];
+ int IndexLen;
+ struct ValidRoom *vrptr;
+ struct ValidUser *vuptr;
+ int RoomIsValid, UserIsValid;
+
+ /* First, load up a table full of valid room/gen combinations */
+ ForEachRoom(AddValidRoom, NULL);
+
+ /* Then load up a table full of valid user numbers */
+ ForEachUser(AddValidUser, NULL);
+
+ /* Now traverse through the visits, purging irrelevant records... */
+ cdb_rewind(CDB_VISIT);
+ while(cdbvisit = cdb_next_item(CDB_VISIT), cdbvisit != NULL) {
+ memset(&vbuf, 0, sizeof(struct visit));
+ memcpy(&vbuf, cdbvisit->ptr,
+ ( (cdbvisit->len > sizeof(struct visit)) ?
+ sizeof(struct visit) : cdbvisit->len) );
+ cdb_free(cdbvisit);
+
+ RoomIsValid = 0;
+ UserIsValid = 0;
+
+ /* Check to see if the room exists */
+ for (vrptr=ValidRoomList; vrptr!=NULL; vrptr=vrptr->next) {
+ if ( (vrptr->vr_roomnum==vbuf.v_roomnum)
+ && (vrptr->vr_roomgen==vbuf.v_roomgen))
+ RoomIsValid = 1;
+ }
+
+ /* Check to see if the user exists */
+ for (vuptr=ValidUserList; vuptr!=NULL; vuptr=vuptr->next) {
+ if (vuptr->vu_usernum == vbuf.v_usernum)
+ UserIsValid = 1;
+ }
+
+ /* Put the record on the purge list if it's dead */
+ if ((RoomIsValid==0) || (UserIsValid==0)) {
+ vptr = (struct VPurgeList *)
+ malloc(sizeof(struct VPurgeList));
+ vptr->next = VisitPurgeList;
+ vptr->vp_roomnum = vbuf.v_roomnum;
+ vptr->vp_roomgen = vbuf.v_roomgen;
+ vptr->vp_usernum = vbuf.v_usernum;
+ VisitPurgeList = vptr;
+ }
+
+ }
+
+ /* Free the valid room/gen combination list */
+ while (ValidRoomList != NULL) {
+ vrptr = ValidRoomList->next;
+ free(ValidRoomList);
+ ValidRoomList = vrptr;
+ }
+
+ /* Free the valid user list */
+ while (ValidUserList != NULL) {
+ vuptr = ValidUserList->next;
+ free(ValidUserList);
+ ValidUserList = vuptr;
+ }
+
+ /* Now delete every visit on the purged list */
+ while (VisitPurgeList != NULL) {
+ IndexLen = GenerateRelationshipIndex(IndexBuf,
+ VisitPurgeList->vp_roomnum,
+ VisitPurgeList->vp_roomgen,
+ VisitPurgeList->vp_usernum);
+ cdb_delete(CDB_VISIT, IndexBuf, IndexLen);
+ vptr = VisitPurgeList->next;
+ free(VisitPurgeList);
+ VisitPurgeList = vptr;
+ ++purged;
+ }
+
+ return(purged);
+}
+
+/*
+ * Purge the use table of old entries.
+ *
+ */
+int PurgeUseTable(void) {
+ int purged = 0;
+ struct cdbdata *cdbut;
+ struct UseTable ut;
+ struct UPurgeList *ul = NULL;
+ struct UPurgeList *uptr;
+
+ /* Phase 1: traverse through the table, discovering old records... */
+ lprintf(CTDL_DEBUG, "Purge use table: phase 1\n");
+ cdb_rewind(CDB_USETABLE);
+ while(cdbut = cdb_next_item(CDB_USETABLE), cdbut != NULL) {
+
+ memcpy(&ut, cdbut->ptr,
+ ((cdbut->len > sizeof(struct UseTable)) ?
+ sizeof(struct UseTable) : cdbut->len));
+ cdb_free(cdbut);
+
+ if ( (time(NULL) - ut.ut_timestamp) > USETABLE_RETAIN ) {
+ uptr = (struct UPurgeList *) malloc(sizeof(struct UPurgeList));
+ if (uptr != NULL) {
+ uptr->next = ul;
+ safestrncpy(uptr->up_key, ut.ut_msgid, sizeof uptr->up_key);
+ ul = uptr;
+ }
+ ++purged;
+ }
+
+ }
+
+ /* Phase 2: delete the records */
+ lprintf(CTDL_DEBUG, "Purge use table: phase 2\n");
+ while (ul != NULL) {
+ cdb_delete(CDB_USETABLE, ul->up_key, strlen(ul->up_key));
+ uptr = ul->next;
+ free(ul);
+ ul = uptr;
+ }
+
+ lprintf(CTDL_DEBUG, "Purge use table: finished (purged %d records)\n", purged);
+ return(purged);
+}
+
+
+
+/*
+ * Purge the EUID Index of old records.
+ *
+ */
+int PurgeEuidIndexTable(void) {
+ int purged = 0;
+ struct cdbdata *cdbei;
+ struct EPurgeList *el = NULL;
+ struct EPurgeList *eptr;
+ long msgnum;
+ struct CtdlMessage *msg = NULL;
+
+ /* Phase 1: traverse through the table, discovering old records... */
+ lprintf(CTDL_DEBUG, "Purge EUID index: phase 1\n");
+ cdb_rewind(CDB_EUIDINDEX);
+ while(cdbei = cdb_next_item(CDB_EUIDINDEX), cdbei != NULL) {
+
+ memcpy(&msgnum, cdbei->ptr, sizeof(long));
+
+ msg = CtdlFetchMessage(msgnum, 0);
+ if (msg != NULL) {
+ CtdlFreeMessage(msg); /* it still exists, so do nothing */
+ }
+ else {
+ eptr = (struct EPurgeList *) malloc(sizeof(struct EPurgeList));
+ if (eptr != NULL) {
+ eptr->next = el;
+ eptr->ep_keylen = cdbei->len - sizeof(long);
+ eptr->ep_key = malloc(cdbei->len);
+ memcpy(eptr->ep_key, &cdbei->ptr[sizeof(long)], eptr->ep_keylen);
+ el = eptr;
+ }
+ ++purged;
+ }
+
+ cdb_free(cdbei);
+
+ }
+
+ /* Phase 2: delete the records */
+ lprintf(CTDL_DEBUG, "Purge euid index: phase 2\n");
+ while (el != NULL) {
+ cdb_delete(CDB_EUIDINDEX, el->ep_key, el->ep_keylen);
+ free(el->ep_key);
+ eptr = el->next;
+ free(el);
+ el = eptr;
+ }
+
+ lprintf(CTDL_DEBUG, "Purge euid index: finished (purged %d records)\n", purged);
+ return(purged);
+}
+
+
+void purge_databases(void) {
+ int retval;
+ static time_t last_purge = 0;
+ time_t now;
+ struct tm tm;
+
+ /* Do the auto-purge if the current hour equals the purge hour,
+ * but not if the operation has already been performed in the
+ * last twelve hours. This is usually enough granularity.
+ */
+ now = time(NULL);
+ localtime_r(&now, &tm);
+ if (tm.tm_hour != config.c_purge_hour) return;
+ if ((now - last_purge) < 43200) return;
+
+ lprintf(CTDL_INFO, "Auto-purger: starting.\n");
+
+ retval = PurgeUsers();
+ lprintf(CTDL_NOTICE, "Purged %d users.\n", retval);
+
+ PurgeMessages();
+ lprintf(CTDL_NOTICE, "Expired %d messages.\n", messages_purged);
+
+ retval = PurgeRooms();
+ lprintf(CTDL_NOTICE, "Expired %d rooms.\n", retval);
+
+ retval = PurgeVisits();
+ lprintf(CTDL_NOTICE, "Purged %d visits.\n", retval);
+
+ retval = PurgeUseTable();
+ lprintf(CTDL_NOTICE, "Purged %d entries from the use table.\n", retval);
+
+ retval = PurgeEuidIndexTable();
+ lprintf(CTDL_NOTICE, "Purged %d entries from the EUID index.\n", retval);
+
+ retval = TDAP_ProcessAdjRefCountQueue();
+ lprintf(CTDL_NOTICE, "Processed %d message reference count adjustments.\n", retval);
+
+ lprintf(CTDL_INFO, "Auto-purger: finished.\n");
+
+ last_purge = now; /* So we don't do it again soon */
+}
+
+/*****************************************************************************/
+
+
+void do_fsck_msg(long msgnum, void *userdata) {
+ struct ctdlroomref *ptr;
+
+ ptr = (struct ctdlroomref *)malloc(sizeof(struct ctdlroomref));
+ ptr->next = rr;
+ ptr->msgnum = msgnum;
+ rr = ptr;
+}
+
+void do_fsck_room(struct ctdlroom *qrbuf, void *data)
+{
+ getroom(&CC->room, qrbuf->QRname);
+ CtdlForEachMessage(MSGS_ALL, 0L, NULL, NULL, NULL, do_fsck_msg, NULL);
+}
+
+/*
+ * Check message reference counts
+ */
+void cmd_fsck(char *argbuf) {
+ long msgnum;
+ struct cdbdata *cdbmsg;
+ struct MetaData smi;
+ struct ctdlroomref *ptr;
+ int realcount;
+
+ if (CtdlAccessCheck(ac_aide)) return;
+
+ /* Lame way of checking whether anyone else is doing this now */
+ if (rr != NULL) {
+ cprintf("%d Another FSCK is already running.\n", ERROR + RESOURCE_BUSY);
+ return;
+ }
+
+ cprintf("%d Checking message reference counts\n", LISTING_FOLLOWS);
+
+ cprintf("\nThis could take a while. Please be patient!\n\n");
+ cprintf("Gathering pointers...\n");
+ ForEachRoom(do_fsck_room, NULL);
+
+ get_control();
+ cprintf("Checking message base...\n");
+ for (msgnum = 0L; msgnum <= CitControl.MMhighest; ++msgnum) {
+
+ cdbmsg = cdb_fetch(CDB_MSGMAIN, &msgnum, sizeof(long));
+ if (cdbmsg != NULL) {
+ cdb_free(cdbmsg);
+ cprintf("Message %7ld ", msgnum);
+
+ GetMetaData(&smi, msgnum);
+ cprintf("refcount=%-2d ", smi.meta_refcount);
+
+ realcount = 0;
+ for (ptr = rr; ptr != NULL; ptr = ptr->next) {
+ if (ptr->msgnum == msgnum) ++realcount;
+ }
+ cprintf("realcount=%-2d\n", realcount);
+
+ if ( (smi.meta_refcount != realcount)
+ || (realcount == 0) ) {
+ AdjRefCount(msgnum, (smi.meta_refcount - realcount));
+ }
+
+ }
+
+ }
+
+ cprintf("Freeing memory...\n");
+ while (rr != NULL) {
+ ptr = rr->next;
+ free(rr);
+ rr = ptr;
+ }
+
+ cprintf("Done!\n");
+ cprintf("000\n");
+
+}
+
+
+
+
+/*****************************************************************************/
+
+CTDL_MODULE_INIT(expire)
+{
+ CtdlRegisterSessionHook(purge_databases, EVT_TIMER);
+ CtdlRegisterProtoHook(cmd_fsck, "FSCK", "Check message ref counts");
+
+ /* return our Subversion id for the Log */
+ return "$Id$";
+}
--- /dev/null
+/*
+ * $Id$
+ *
+ * Default wordbreaker module for full text indexing.
+ *
+ */
+
+
+#include "sysdep.h"
+#include <stdlib.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <pwd.h>
+#include <errno.h>
+#include <sys/types.h>
+
+#if TIME_WITH_SYS_TIME
+# include <sys/time.h>
+# include <time.h>
+#else
+# if HAVE_SYS_TIME_H
+# include <sys/time.h>
+# else
+# include <time.h>
+# endif
+#endif
+
+#include <sys/wait.h>
+#include <ctype.h>
+#include <string.h>
+#include <limits.h>
+#include "citadel.h"
+#include "server.h"
+#include "sysdep_decls.h"
+#include "citserver.h"
+#include "support.h"
+#include "config.h"
+#include "database.h"
+#include "msgbase.h"
+#include "control.h"
+#include "tools.h"
+#include "ft_wordbreaker.h"
+#include "crc16.h"
+
+/*
+ * Noise words are not included in search indices.
+ * NOTE: if the noise word list is altered in any way, the FT_WORDBREAKER_ID
+ * must also be changed, so that the index is rebuilt.
+ */
+static char *noise_words[] = {
+ "about",
+ "after",
+ "all",
+ "also",
+ "an",
+ "and",
+ "another",
+ "any",
+ "are",
+ "as",
+ "at",
+ "be",
+ "because",
+ "been",
+ "before",
+ "being",
+ "between",
+ "both",
+ "but",
+ "by",
+ "came",
+ "can",
+ "come",
+ "could",
+ "did",
+ "do",
+ "each",
+ "for",
+ "from",
+ "get",
+ "got",
+ "had",
+ "has",
+ "have",
+ "he",
+ "her",
+ "here",
+ "him",
+ "himself",
+ "his",
+ "how",
+ "if",
+ "in",
+ "into",
+ "is",
+ "it",
+ "like",
+ "make",
+ "many",
+ "me",
+ "might",
+ "more",
+ "most",
+ "much",
+ "must",
+ "my",
+ "never",
+ "now",
+ "of",
+ "on",
+ "only",
+ "or",
+ "other",
+ "our",
+ "out",
+ "over",
+ "said",
+ "same",
+ "see",
+ "should",
+ "since",
+ "some",
+ "still",
+ "such",
+ "take",
+ "than",
+ "that",
+ "the",
+ "their",
+ "them",
+ "then",
+ "there",
+ "these",
+ "they",
+ "this",
+ "those",
+ "through",
+ "to",
+ "too",
+ "under",
+ "up",
+ "very",
+ "was",
+ "way",
+ "we",
+ "well",
+ "were",
+ "what",
+ "where",
+ "which",
+ "while",
+ "with",
+ "would",
+ "you",
+ "your"
+};
+
+/*
+ * Compare function
+ */
+int intcmp(const void *rec1, const void *rec2) {
+ int i1, i2;
+
+ i1 = *(const int *)rec1;
+ i2 = *(const int *)rec2;
+
+ if (i1 > i2) return(1);
+ if (i1 < i2) return(-1);
+ return(0);
+}
+
+
+void wordbreaker(char *text, int *num_tokens, int **tokens) {
+
+ int wb_num_tokens = 0;
+ int wb_num_alloc = 0;
+ int *wb_tokens = NULL;
+
+ char *ptr;
+ char *word_start;
+ char *word_end;
+ char ch;
+ int word_len;
+ char word[256];
+ int i;
+ int word_crc;
+
+ if (text == NULL) { /* no NULL text please */
+ *num_tokens = 0;
+ *tokens = NULL;
+ return;
+ }
+
+ if (text[0] == 0) { /* no empty text either */
+ *num_tokens = 0;
+ *tokens = NULL;
+ return;
+ }
+
+ ptr = text;
+ word_start = NULL;
+ while (*ptr) {
+ ch = *ptr;
+ if (isalnum(ch)) {
+ if (!word_start) {
+ word_start = ptr;
+ }
+ }
+ ++ptr;
+ ch = *ptr;
+ if ( (!isalnum(ch)) && (word_start) ) {
+ word_end = ptr;
+ --word_end;
+
+ /* extract the word */
+ word_len = word_end - word_start + 1;
+ safestrncpy(word, word_start, sizeof word);
+ if (word_len >= sizeof word) {
+ lprintf(CTDL_DEBUG, "Invalid word length: %d\n", word_len);
+ word[(sizeof word_len) - 1] = 0;
+ }
+ else {
+ word[word_len] = 0;
+ }
+ word_start = NULL;
+
+ /* disqualify noise words */
+ for (i=0; i<(sizeof(noise_words)/sizeof(char *)); ++i) {
+ if (!strcasecmp(word, noise_words[i])) {
+ word_len = 0;
+ break;
+ }
+ }
+
+ /* are we ok with the length? */
+ if ( (word_len >= WB_MIN)
+ && (word_len <= WB_MAX) ) {
+ for (i=0; i<word_len; ++i) {
+ word[i] = tolower(word[i]);
+ }
+ word_crc = (int)
+ CalcCRC16Bytes(word_len, word);
+
+ ++wb_num_tokens;
+ if (wb_num_tokens > wb_num_alloc) {
+ wb_num_alloc += 512;
+ wb_tokens = realloc(wb_tokens, (sizeof(int) * wb_num_alloc));
+ }
+ wb_tokens[wb_num_tokens - 1] = word_crc;
+ }
+ }
+ }
+
+ /* sort and purge dups */
+ if (wb_num_tokens > 1) {
+ qsort(wb_tokens, wb_num_tokens, sizeof(int), intcmp);
+ for (i=0; i<(wb_num_tokens-1); ++i) {
+ if (wb_tokens[i] == wb_tokens[i+1]) {
+ memmove(&wb_tokens[i], &wb_tokens[i+1],
+ ((wb_num_tokens - i - 1)*sizeof(int)));
+ --wb_num_tokens;
+ --i;
+ }
+ }
+ }
+
+ *num_tokens = wb_num_tokens;
+ *tokens = wb_tokens;
+}
+
--- /dev/null
+/*
+ * $Id$
+ *
+ */
+
+
+/*
+ * This is an ID for the wordbreaker module. If we do pluggable wordbreakers
+ * later on, or even if we update this one, we can use a different ID so the
+ * system knows it needs to throw away the existing index and rebuild it.
+ */
+#define FT_WORDBREAKER_ID 0x001f
+
+/*
+ * Minimum and maximum length of words to index
+ */
+#define WB_MIN 3
+#define WB_MAX 40
+
+void wordbreaker(char *text, int *num_tokens, int **tokens);
+
--- /dev/null
+/*
+ * $Id$
+ *
+ * This module handles fulltext indexing of the message base.
+ *
+ */
+
+
+#include "sysdep.h"
+#include <stdlib.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <pwd.h>
+#include <errno.h>
+#include <sys/types.h>
+
+#if TIME_WITH_SYS_TIME
+# include <sys/time.h>
+# include <time.h>
+#else
+# if HAVE_SYS_TIME_H
+# include <sys/time.h>
+# else
+# include <time.h>
+# endif
+#endif
+
+#include <sys/wait.h>
+#include <string.h>
+#include <limits.h>
+#include "citadel.h"
+#include "server.h"
+#include "citserver.h"
+#include "support.h"
+#include "config.h"
+#include "database.h"
+#include "msgbase.h"
+#include "control.h"
+#include "room_ops.h"
+#include "tools.h"
+#include "serv_fulltext.h"
+#include "ft_wordbreaker.h"
+
+
+#include "ctdl_module.h"
+
+
+
+long ft_newhighest = 0L;
+long *ft_newmsgs = NULL;
+int ft_num_msgs = 0;
+int ft_num_alloc = 0;
+
+
+int ftc_num_msgs[65536];
+long *ftc_msgs[65536];
+
+
+/*
+ * Compare function
+ */
+int longcmp(const void *rec1, const void *rec2) {
+ long i1, i2;
+
+ i1 = *(const long *)rec1;
+ i2 = *(const long *)rec2;
+
+ if (i1 > i2) return(1);
+ if (i1 < i2) return(-1);
+ return(0);
+}
+
+/*
+ * Flush our index cache out to disk.
+ */
+void ft_flush_cache(void) {
+ int i;
+ time_t last_update = 0;
+
+ for (i=0; i<65536; ++i) {
+ if ((time(NULL) - last_update) >= 10) {
+ lprintf(CTDL_INFO,
+ "Flushing index cache to disk (%d%% complete)\n",
+ (i * 100 / 65536)
+ );
+ last_update = time(NULL);
+ }
+ if (ftc_msgs[i] != NULL) {
+ cdb_store(CDB_FULLTEXT, &i, sizeof(int), ftc_msgs[i],
+ (ftc_num_msgs[i] * sizeof(long)));
+ ftc_num_msgs[i] = 0;
+ free(ftc_msgs[i]);
+ ftc_msgs[i] = NULL;
+ }
+ }
+ lprintf(CTDL_INFO, "Flushed index cache to disk (100%% complete)\n");
+}
+
+
+/*
+ * Index or de-index a message. (op == 1 to index, 0 to de-index)
+ */
+void ft_index_message(long msgnum, int op) {
+ int num_tokens = 0;
+ int *tokens = NULL;
+ int i, j;
+ struct cdbdata *cdb_bucket;
+ char *msgtext;
+ int tok;
+
+ lprintf(CTDL_DEBUG, "ft_index_message() %s msg %ld\n",
+ (op ? "adding" : "removing") , msgnum
+ );
+
+ /* Output the message as text before indexing it, so we don't end up
+ * indexing a bunch of encoded base64, etc.
+ */
+ CC->redirect_buffer = malloc(SIZ);
+ CC->redirect_len = 0;
+ CC->redirect_alloc = SIZ;
+ CtdlOutputMsg(msgnum, MT_CITADEL, HEADERS_ALL, 0, 1, NULL);
+ msgtext = CC->redirect_buffer;
+ CC->redirect_buffer = NULL;
+ CC->redirect_len = 0;
+ CC->redirect_alloc = 0;
+ lprintf(CTDL_DEBUG, "Wordbreaking message %ld...\n", msgnum);
+ wordbreaker(msgtext, &num_tokens, &tokens);
+ free(msgtext);
+
+ lprintf(CTDL_DEBUG, "Indexing message %ld [%d tokens]\n", msgnum, num_tokens);
+ if (num_tokens > 0) {
+ for (i=0; i<num_tokens; ++i) {
+
+ /* Add the message to the relevant token bucket */
+
+ /* search for tokens[i] */
+ tok = tokens[i];
+
+ if ( (tok >= 0) && (tok <= 65535) ) {
+ /* fetch the bucket, Liza */
+ if (ftc_msgs[tok] == NULL) {
+ cdb_bucket = cdb_fetch(CDB_FULLTEXT, &tok, sizeof(int));
+ if (cdb_bucket != NULL) {
+ ftc_num_msgs[tok] = cdb_bucket->len / sizeof(long);
+ ftc_msgs[tok] = (long *)cdb_bucket->ptr;
+ cdb_bucket->ptr = NULL;
+ cdb_free(cdb_bucket);
+ }
+ else {
+ ftc_num_msgs[tok] = 0;
+ ftc_msgs[tok] = malloc(sizeof(long));
+ }
+ }
+
+
+ if (op == 1) { /* add to index */
+ ++ftc_num_msgs[tok];
+ ftc_msgs[tok] = realloc(ftc_msgs[tok],
+ ftc_num_msgs[tok]*sizeof(long));
+ ftc_msgs[tok][ftc_num_msgs[tok] - 1] = msgnum;
+ }
+
+ if (op == 0) { /* remove from index */
+ if (ftc_num_msgs[tok] >= 1) {
+ for (j=0; j<ftc_num_msgs[tok]; ++j) {
+ if (ftc_msgs[tok][j] == msgnum) {
+ memmove(&ftc_msgs[tok][j], &ftc_msgs[tok][j+1], ((ftc_num_msgs[tok] - j - 1)*sizeof(long)));
+ --ftc_num_msgs[tok];
+ --j;
+ }
+ }
+ }
+ }
+ }
+ else {
+ lprintf(CTDL_ALERT, "Invalid token %d !!\n", tok);
+ }
+ }
+
+ free(tokens);
+ }
+}
+
+
+
+/*
+ * Add a message to the list of those to be indexed.
+ */
+void ft_index_msg(long msgnum, void *userdata) {
+
+ if ((msgnum > CitControl.MMfulltext) && (msgnum <= ft_newhighest)) {
+ ++ft_num_msgs;
+ if (ft_num_msgs > ft_num_alloc) {
+ ft_num_alloc += 1024;
+ ft_newmsgs = realloc(ft_newmsgs,
+ (ft_num_alloc * sizeof(long)));
+ }
+ ft_newmsgs[ft_num_msgs - 1] = msgnum;
+ }
+
+}
+
+/*
+ * Scan a room for messages to index.
+ */
+void ft_index_room(struct ctdlroom *qrbuf, void *data)
+{
+ getroom(&CC->room, qrbuf->QRname);
+ CtdlForEachMessage(MSGS_ALL, 0L, NULL, NULL, NULL, ft_index_msg, NULL);
+}
+
+
+/*
+ * Begin the fulltext indexing process. (Called as an EVT_TIMER event)
+ */
+void do_fulltext_indexing(void) {
+ int i;
+ static time_t last_index = 0L;
+ static time_t last_progress = 0L;
+
+ /*
+ * Don't do this if the site doesn't have it enabled.
+ */
+ if (!config.c_enable_fulltext) {
+ return;
+ }
+
+ /*
+ * Make sure we don't run the indexer too frequently.
+ * FIXME move the setting into config
+ */
+ if ( (time(NULL) - last_index) < 300L) {
+ return;
+ }
+
+ /*
+ * Check to see whether the fulltext index is up to date; if there
+ * are no messages to index, don't waste any more time trying.
+ */
+ if (CitControl.MMfulltext >= CitControl.MMhighest) {
+ return; /* nothing to do! */
+ }
+
+ lprintf(CTDL_DEBUG, "do_fulltext_indexing() started\n");
+
+ /*
+ * If we've switched wordbreaker modules, burn the index and start
+ * over.
+ */
+ begin_critical_section(S_CONTROL);
+ lprintf(CTDL_DEBUG, "wb ver on disk = %d, code ver = %d\n",
+ CitControl.fulltext_wordbreaker, FT_WORDBREAKER_ID);
+ if (CitControl.fulltext_wordbreaker != FT_WORDBREAKER_ID) {
+ lprintf(CTDL_INFO, "(re)initializing full text index\n");
+ cdb_trunc(CDB_FULLTEXT);
+ CitControl.MMfulltext = 0L;
+ put_control();
+ }
+ end_critical_section(S_CONTROL);
+
+ /*
+ * Now go through each room and find messages to index.
+ */
+ ft_newhighest = CitControl.MMhighest;
+ ForEachRoom(ft_index_room, NULL); /* load all msg pointers */
+
+ if (ft_num_msgs > 0) {
+ qsort(ft_newmsgs, ft_num_msgs, sizeof(long), longcmp);
+ for (i=0; i<(ft_num_msgs-1); ++i) { /* purge dups */
+ if (ft_newmsgs[i] == ft_newmsgs[i+1]) {
+ memmove(&ft_newmsgs[i], &ft_newmsgs[i+1],
+ ((ft_num_msgs - i - 1)*sizeof(long)));
+ --ft_num_msgs;
+ --i;
+ }
+ }
+
+ /* Here it is ... do each message! */
+ for (i=0; i<ft_num_msgs; ++i) {
+ if (time(NULL) != last_progress) {
+ lprintf(CTDL_DEBUG,
+ "Indexed %d of %d messages (%d%%)\n",
+ i, ft_num_msgs,
+ ((i*100) / ft_num_msgs)
+ );
+ last_progress = time(NULL);
+ }
+ ft_index_message(ft_newmsgs[i], 1);
+
+ /* Check to see if we need to quit early */
+ if (time_to_die) {
+ lprintf(CTDL_DEBUG, "Indexer quitting early\n");
+ ft_newhighest = ft_newmsgs[i];
+ break;
+ }
+
+ /* Check to see if we have to maybe flush to disk */
+ if (i >= FT_MAX_CACHE) {
+ lprintf(CTDL_DEBUG, "Time to flush.\n");
+ ft_newhighest = ft_newmsgs[i];
+ break;
+ }
+
+ }
+
+ free(ft_newmsgs);
+ ft_num_msgs = 0;
+ ft_num_alloc = 0;
+ ft_newmsgs = NULL;
+ }
+
+ /* Save our place so we don't have to do this again */
+ ft_flush_cache();
+ begin_critical_section(S_CONTROL);
+ CitControl.MMfulltext = ft_newhighest;
+ CitControl.fulltext_wordbreaker = FT_WORDBREAKER_ID;
+ put_control();
+ end_critical_section(S_CONTROL);
+ last_index = time(NULL);
+
+ lprintf(CTDL_DEBUG, "do_fulltext_indexing() finished\n");
+ return;
+}
+
+/*
+ * Main loop for the indexer thread.
+ */
+void *indexer_thread(void *arg) {
+ struct CitContext indexerCC;
+
+ lprintf(CTDL_DEBUG, "indexer_thread() initializing\n");
+
+ memset(&indexerCC, 0, sizeof(struct CitContext));
+ indexerCC.internal_pgm = 1;
+ indexerCC.cs_pid = 0;
+ pthread_setspecific(MyConKey, (void *)&indexerCC );
+
+ cdb_allocate_tsd();
+
+ while (!time_to_die) {
+ do_fulltext_indexing();
+ sleep(1);
+ }
+
+ lprintf(CTDL_DEBUG, "indexer_thread() exiting\n");
+ pthread_exit(NULL);
+}
+
+
+
+/*
+ * API call to perform searches.
+ * (This one does the "all of these words" search.)
+ * Caller is responsible for freeing the message list.
+ */
+void ft_search(int *fts_num_msgs, long **fts_msgs, char *search_string) {
+ int num_tokens = 0;
+ int *tokens = NULL;
+ int i, j;
+ struct cdbdata *cdb_bucket;
+ int num_all_msgs = 0;
+ long *all_msgs = NULL;
+ int num_ret_msgs = 0;
+ int num_ret_alloc = 0;
+ long *ret_msgs = NULL;
+ int tok;
+
+ wordbreaker(search_string, &num_tokens, &tokens);
+ if (num_tokens > 0) {
+ for (i=0; i<num_tokens; ++i) {
+
+ /* search for tokens[i] */
+ tok = tokens[i];
+
+ /* fetch the bucket, Liza */
+ if (ftc_msgs[tok] == NULL) {
+ cdb_bucket = cdb_fetch(CDB_FULLTEXT, &tok, sizeof(int));
+ if (cdb_bucket != NULL) {
+ ftc_num_msgs[tok] = cdb_bucket->len / sizeof(long);
+ ftc_msgs[tok] = (long *)cdb_bucket->ptr;
+ cdb_bucket->ptr = NULL;
+ cdb_free(cdb_bucket);
+ }
+ else {
+ ftc_num_msgs[tok] = 0;
+ ftc_msgs[tok] = malloc(sizeof(long));
+ }
+ }
+
+ num_all_msgs += ftc_num_msgs[tok];
+ if (num_all_msgs > 0) {
+ all_msgs = realloc(all_msgs, num_all_msgs*sizeof(long) );
+ memcpy(&all_msgs[num_all_msgs-ftc_num_msgs[tok]],
+ ftc_msgs[tok], ftc_num_msgs[tok]*sizeof(long) );
+ }
+
+ }
+ free(tokens);
+ qsort(all_msgs, num_all_msgs, sizeof(long), longcmp);
+
+ /*
+ * At this point, if a message appears num_tokens times in the
+ * list, then it contains all of the search tokens.
+ */
+ if (num_all_msgs >= num_tokens)
+ for (j=0; j<(num_all_msgs-num_tokens+1); ++j) {
+ if (all_msgs[j] == all_msgs[j+num_tokens-1]) {
+
+ ++num_ret_msgs;
+ if (num_ret_msgs > num_ret_alloc) {
+ num_ret_alloc += 64;
+ ret_msgs = realloc(ret_msgs,
+ (num_ret_alloc*sizeof(long)) );
+ }
+ ret_msgs[num_ret_msgs - 1] = all_msgs[j];
+
+ }
+ }
+
+ free(all_msgs);
+ }
+
+ *fts_num_msgs = num_ret_msgs;
+ *fts_msgs = ret_msgs;
+}
+
+
+/*
+ * This search command is for diagnostic purposes and may be removed or replaced.
+ */
+void cmd_srch(char *argbuf) {
+ int num_msgs = 0;
+ long *msgs = NULL;
+ int i;
+ char search_string[256];
+
+ if (CtdlAccessCheck(ac_logged_in)) return;
+
+ if (!config.c_enable_fulltext) {
+ cprintf("%d Full text index is not enabled on this server.\n",
+ ERROR + CMD_NOT_SUPPORTED);
+ return;
+ }
+
+ extract_token(search_string, argbuf, 0, '|', sizeof search_string);
+ ft_search(&num_msgs, &msgs, search_string);
+
+ cprintf("%d %d msgs match all search words:\n",
+ LISTING_FOLLOWS, num_msgs);
+ if (num_msgs > 0) {
+ for (i=0; i<num_msgs; ++i) {
+ cprintf("%ld\n", msgs[i]);
+ }
+ }
+ if (msgs != NULL) free(msgs);
+ cprintf("000\n");
+}
+
+/*
+ * Zero out our index cache.
+ */
+void initialize_ft_cache(void) {
+ memset(ftc_num_msgs, 0, (65536 * sizeof(int)));
+ memset(ftc_msgs, 0, (65536 * sizeof(long *)));
+}
+
+
+/*****************************************************************************/
+
+CTDL_MODULE_INIT(fulltext)
+{
+ initialize_ft_cache();
+ CtdlRegisterProtoHook(cmd_srch, "SRCH", "Full text search");
+
+ /* return our Subversion id for the Log */
+ return "$Id$";
+}
--- /dev/null
+/*
+ * This module implements a notifier for Funambol push email.
+ * Based on bits of serv_spam, serv_smtp
+ */
+
+#define FUNAMBOL_WS "/funambol/services/admin"
+#include "sysdep.h"
+#include <stdlib.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <pwd.h>
+#include <errno.h>
+#include <sys/types.h>
+
+#if TIME_WITH_SYS_TIME
+# include <sys/time.h>
+# include <time.h>
+#else
+# if HAVE_SYS_TIME_H
+# include <sys/time.h>
+# else
+# include <time.h>
+# endif
+#endif
+
+#include <sys/wait.h>
+#include <string.h>
+#include <limits.h>
+#include <sys/socket.h>
+#include "citadel.h"
+#include "server.h"
+#include "citserver.h"
+#include "support.h"
+#include "config.h"
+#include "control.h"
+#include "room_ops.h"
+#include "user_ops.h"
+#include "policy.h"
+#include "database.h"
+#include "msgbase.h"
+#include "tools.h"
+#include "internet_addressing.h"
+#include "domain.h"
+#include "clientsocket.h"
+#include "serv_funambol.h"
+
+
+
+#include "ctdl_module.h"
+
+
+/*
+ * Create the notify message queue
+ */
+void create_notify_queue(void) {
+ struct ctdlroom qrbuf;
+
+ create_room(FNBL_QUEUE_ROOM, 3, "", 0, 1, 0, VIEW_MAILBOX);
+
+ /*
+ * Make sure it's set to be a "system room" so it doesn't show up
+ * in the <K>nown rooms list for Aides.
+ */
+ if (lgetroom(&qrbuf, FNBL_QUEUE_ROOM) == 0) {
+ qrbuf.QRflags2 |= QR2_SYSTEM;
+ lputroom(&qrbuf);
+ }
+}
+void do_notify_queue(void) {
+ static int doing_queue = 0;
+
+ /*
+ * This is a simple concurrency check to make sure only one queue run
+ * is done at a time. We could do this with a mutex, but since we
+ * don't really require extremely fine granularity here, we'll do it
+ * with a static variable instead.
+ */
+ if (doing_queue) return;
+ doing_queue = 1;
+
+ /*
+ * Go ahead and run the queue
+ */
+ lprintf(CTDL_INFO, "serv_funambol: processing notify queue\n");
+
+ if (getroom(&CC->room, FNBL_QUEUE_ROOM) != 0) {
+ lprintf(CTDL_ERR, "Cannot find room <%s>\n", FNBL_QUEUE_ROOM);
+ return;
+ }
+ CtdlForEachMessage(MSGS_ALL, 0L, NULL,
+ SPOOLMIME, NULL, notify_funambol, NULL);
+
+ lprintf(CTDL_INFO, "serv_funambol: queue run completed\n");
+ doing_queue = 0;
+}
+
+/*
+ * Connect to the Funambol server and scan a message.
+ */
+void notify_funambol(long msgnum, void *userdata) {
+ struct CtdlMessage *msg;
+ int sock = (-1);
+ char buf[SIZ];
+ char SOAPHeader[SIZ];
+ char SOAPData[SIZ];
+ char port[SIZ];
+ /* W means 'Wireless'... */
+ msg = CtdlFetchMessage(msgnum, 1);
+ if ( msg->cm_fields['W'] == NULL) {
+ goto nuke;
+ }
+ /* Are we allowed to push? */
+ if ( strlen(config.c_funambol_host) == 0) {
+ goto nuke;
+ } else {
+ lprintf(CTDL_INFO, "Push enabled\n");
+ }
+
+ sprintf(port, "%d", config.c_funambol_port);
+ lprintf(CTDL_INFO, "Connecting to Funambol at <%s>\n", config.c_funambol_host);
+ sock = sock_connect(config.c_funambol_host, port, "tcp");
+ if (sock >= 0) lprintf(CTDL_DEBUG, "Connected!\n");
+
+ if (sock < 0) {
+ /* If the service isn't running, pass for now */
+ return;
+ }
+
+ /* Build a SOAP message, delicately, by hand */
+ sprintf(SOAPData, "<?xml version=\"1.0\" encoding=\"UTF-8\"?><soapenv:Envelope xmlns:soapenv=\"http://schemas.xmlsoap.org/soap/envelope/\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\">");
+ strcat(SOAPData, "<soapenv:Body><sendNotificationMessages soapenv:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">");
+ strcat(SOAPData, "<arg0 xsi:type=\"soapenc:string\" xmlns:soapenc=\"http://schemas.xmlsoap.org/soap/encoding/\">");
+ strcat(SOAPData, msg->cm_fields['W']);
+ strcat(SOAPData, "</arg0>");
+ strcat(SOAPData, "<arg1 xsi:type=\"soapenc:string\" xmlns:soapenc=\"http://schemas.xmlsoap.org/soap/encoding/\"><?xml version="1.0" encoding="UTF-8"?>\r\n");
+ strcat(SOAPData, "<java version="1.5.0_10" class="java.beans.XMLDecoder"> \r\n");
+ strcat(SOAPData, " <array class="com.funambol.framework.core.Alert" length="1">\r\n");
+ strcat(SOAPData, " <void index="0">\r\n");
+ strcat(SOAPData, " <object class="com.funambol.framework.core.Alert">\r\n");
+ strcat(SOAPData, " <void property="cmdID">\r\n");
+ strcat(SOAPData, " <object class="com.funambol.framework.core.CmdID"/>\r\n");
+ strcat(SOAPData, " </void>");
+ strcat(SOAPData, " <void property="data">\r\n");
+ strcat(SOAPData, " <int>210</int>\r\n");
+ strcat(SOAPData, " </void>\r\n");
+ strcat(SOAPData, " <void property="items">\r\n");
+ strcat(SOAPData, " <void method="add">\r\n");
+ strcat(SOAPData, " <object class="com.funambol.framework.core.Item">\r\n");
+ strcat(SOAPData, " <void property="meta">\r\n");
+ strcat(SOAPData, " <object class="com.funambol.framework.core.Meta">\r\n");
+ strcat(SOAPData, " <void property="metInf">\r\n");
+ strcat(SOAPData, " <void property="type">\r\n");
+ strcat(SOAPData, " <string>application/vnd.omads-email+xml</string>\r\n");
+ strcat(SOAPData, " </void>\r\n");
+ strcat(SOAPData, " </void>\r\n");
+ strcat(SOAPData, " </object>\r\n");
+ strcat(SOAPData, " </void>\r\n");
+ strcat(SOAPData, " <void property="target">\r\n");
+ strcat(SOAPData, " <object class="com.funambol.framework.core.Target">\r\n");
+ strcat(SOAPData, " <void property="locURI">\r\n");
+ strcat(SOAPData, " <string>");
+ strcat(SOAPData, config.c_funambol_source);
+ strcat(SOAPData, "</string>\r\n");
+ strcat(SOAPData, " </void>\r\n");
+ strcat(SOAPData, " </object>\r\n");
+ strcat(SOAPData, " </void>\r\n");
+ strcat(SOAPData, " </object>\r\n");
+ strcat(SOAPData, " </void>\r\n");
+ strcat(SOAPData, " </void>\r\n");
+ strcat(SOAPData, " </object>\r\n");
+ strcat(SOAPData, " </void>\r\n");
+ strcat(SOAPData, " </array>\r\n");
+ strcat(SOAPData, "</java>");
+ strcat(SOAPData,"</arg1><arg2 href=\"#id0\"/></sendNotificationMessages><multiRef id=\"id0\" soapenc:root=\"0\"\r\n");
+ strcat(SOAPData,"soapenv:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\" xsi:type=\"soapenc:int\" xmlns:soapenc=\"http://schemas.xmlsoap.org/soap/encoding/\">1</multiRef></soapenv:Body></soapenv:Envelope>");
+
+ /* Command */
+ lprintf(CTDL_DEBUG, "Transmitting command\n");
+ sprintf(SOAPHeader, "POST %s HTTP/1.0\r\nContent-type: text/xml; charset=utf-8\r\n",
+ FUNAMBOL_WS);
+ strcat(SOAPHeader,"Accept: application/soap+xml, application/dime, multipart/related, text/*\r\n");
+ sprintf(buf, "User-Agent: %s/%d\r\nHost: %s:%d\r\nCache-control: no-cache\r\n",
+ "Citadel",
+ REV_LEVEL,
+ config.c_funambol_host,
+ config.c_funambol_port
+ );
+ strcat(SOAPHeader,buf);
+ strcat(SOAPHeader,"Pragma: no-cache\r\nSOAPAction: \"\"\r\n");
+ sprintf(buf, "Content-Length: %d\r\n",
+ strlen(SOAPData));
+ strcat(SOAPHeader, buf);
+ sprintf(buf, "Authorization: Basic %s\r\n\r\n",
+ config.c_funambol_auth);
+ strcat(SOAPHeader, buf);
+
+ sock_write(sock, SOAPHeader, strlen(SOAPHeader));
+ sock_write(sock, SOAPData, strlen(SOAPData));
+ sock_shutdown(sock, SHUT_WR);
+
+ /* Response */
+ lprintf(CTDL_DEBUG, "Awaiting response\n");
+ if (sock_gets(sock, buf) < 0) {
+ goto bail;
+ }
+ lprintf(CTDL_DEBUG, "<%s\n", buf);
+ if (strncasecmp(buf, "HTTP/1.1 200 OK", strlen("HTTP/1.1 200 OK"))) {
+
+ goto bail;
+ }
+ lprintf(CTDL_DEBUG, "Funambol notified\n");
+ /* We should allow retries here but for now purge after one go */
+ bail:
+ close(sock);
+ nuke:
+ CtdlFreeMessage(msg);
+ long todelete[1];
+ todelete[0] = msgnum;
+ CtdlDeleteMessages(FNBL_QUEUE_ROOM, todelete, 1, "");
+}
+
+
+
+CTDL_MODULE_INIT(funambol)
+{
+ create_notify_queue();
+ CtdlRegisterSessionHook(do_notify_queue, EVT_TIMER);
+
+ /* return our Subversion id for the Log */
+ return "$Id$";
+}
--- /dev/null
+void notify_funambol(long msgnum, void *userdata);
--- /dev/null
+/*
+ * $Id$
+ *
+ * This module handles the loading/saving and maintenance of the
+ * system's Internet configuration. It's not an optional component; I
+ * wrote it as a module merely to keep things as clean and loosely coupled
+ * as possible.
+ *
+ */
+
+#include "sysdep.h"
+#include <stdlib.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <pwd.h>
+#include <errno.h>
+#include <sys/types.h>
+
+#if TIME_WITH_SYS_TIME
+# include <sys/time.h>
+# include <time.h>
+#else
+# if HAVE_SYS_TIME_H
+# include <sys/time.h>
+# else
+# include <time.h>
+# endif
+#endif
+
+#include <sys/wait.h>
+#include <string.h>
+#include <limits.h>
+#include "citadel.h"
+#include "server.h"
+#include "citserver.h"
+#include "support.h"
+#include "config.h"
+#include "room_ops.h"
+#include "user_ops.h"
+#include "policy.h"
+#include "database.h"
+#include "msgbase.h"
+#include "tools.h"
+#include "internet_addressing.h"
+#include "genstamp.h"
+#include "domain.h"
+
+
+#include "ctdl_module.h"
+
+
+void inetcfg_setTo(struct CtdlMessage *msg) {
+ char *conf;
+ char buf[SIZ];
+
+ if (msg->cm_fields['M']==NULL) return;
+ conf = strdup(msg->cm_fields['M']);
+
+ if (conf != NULL) {
+ do {
+ extract_token(buf, conf, 0, '\n', sizeof buf);
+ strcpy(conf, &conf[strlen(buf)+1]);
+ } while ( (strlen(conf)>0) && (strlen(buf)>0) );
+
+ if (inetcfg != NULL) free(inetcfg);
+ inetcfg = conf;
+ }
+}
+
+
+#ifdef ___NOT_CURRENTLY_IN_USE___
+void spamstrings_setTo(struct CtdlMessage *msg) {
+ char buf[SIZ];
+ char *conf;
+ struct spamstrings_t *sptr;
+ int i, n;
+
+ /* Clear out the existing list */
+ while (spamstrings != NULL) {
+ sptr = spamstrings;
+ spamstrings = spamstrings->next;
+ free(sptr->string);
+ free(sptr);
+ }
+
+ /* Read in the new list */
+ if (msg->cm_fields['M']==NULL) return;
+ conf = strdup(msg->cm_fields['M']);
+ if (conf == NULL) return;
+
+ n = num_tokens(conf, '\n');
+ for (i=0; i<n; ++i) {
+ extract_token(buf, conf, i, '\n', sizeof buf);
+ sptr = malloc(sizeof(struct spamstrings_t));
+ sptr->string = strdup(buf);
+ sptr->next = spamstrings;
+ spamstrings = sptr;
+ }
+
+}
+#endif
+
+
+/*
+ * This handler detects changes being made to the system's Internet
+ * configuration.
+ */
+int inetcfg_aftersave(struct CtdlMessage *msg) {
+ char *ptr;
+ int linelen;
+
+ /* If this isn't the configuration room, or if this isn't a MIME
+ * message, don't bother.
+ */
+ if (strcasecmp(msg->cm_fields['O'], SYSCONFIGROOM)) return(0);
+ if (msg->cm_format_type != 4) return(0);
+
+ ptr = msg->cm_fields['M'];
+ while (ptr != NULL) {
+
+ linelen = strcspn(ptr, "\n");
+ if (linelen == 0) return(0); /* end of headers */
+
+ if (!strncasecmp(ptr, "Content-type: ", 14)) {
+ if (!strncasecmp(&ptr[14], INTERNETCFG,
+ strlen(INTERNETCFG))) {
+ inetcfg_setTo(msg); /* changing configs */
+ }
+ }
+
+ ptr = strchr((char *)ptr, '\n');
+ if (ptr != NULL) ++ptr;
+ }
+
+ return(0);
+}
+
+
+void inetcfg_init_backend(long msgnum, void *userdata) {
+ struct CtdlMessage *msg;
+
+ msg = CtdlFetchMessage(msgnum, 1);
+ if (msg != NULL) {
+ inetcfg_setTo(msg);
+ CtdlFreeMessage(msg);
+ }
+}
+
+
+#ifdef ___NOT_CURRENTLY_IN_USE___
+void spamstrings_init_backend(long msgnum, void *userdata) {
+ struct CtdlMessage *msg;
+
+ msg = CtdlFetchMessage(msgnum, 1);
+ if (msg != NULL) {
+ spamstrings_setTo(msg);
+ CtdlFreeMessage(msg);
+ }
+}
+#endif
+
+
+void inetcfg_init(void) {
+ if (getroom(&CC->room, SYSCONFIGROOM) != 0) return;
+ CtdlForEachMessage(MSGS_LAST, 1, NULL, INTERNETCFG, NULL,
+ inetcfg_init_backend, NULL);
+}
+
+
+
+
+/*****************************************************************************/
+/* MODULE INITIALIZATION STUFF */
+/*****************************************************************************/
+
+
+CTDL_MODULE_INIT(inetcfg)
+{
+ CtdlRegisterMessageHook(inetcfg_aftersave, EVT_AFTERSAVE);
+ inetcfg_init();
+
+ /* return our Subversion id for the Log */
+ return "$Id$";
+}
+
--- /dev/null
+/*
+ * $Id$
+ *
+ * A module which implements the LDAP connector for Citadel.
+ *
+ */
+
+#include "sysdep.h"
+#include <stdlib.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <pwd.h>
+#include <errno.h>
+#include <sys/types.h>
+
+#if TIME_WITH_SYS_TIME
+# include <sys/time.h>
+# include <time.h>
+#else
+# if HAVE_SYS_TIME_H
+# include <sys/time.h>
+# else
+# include <time.h>
+# endif
+#endif
+
+#include <sys/wait.h>
+#include <string.h>
+#include <limits.h>
+#include "citadel.h"
+#include "server.h"
+#include "citserver.h"
+#include "support.h"
+#include "config.h"
+#include "room_ops.h"
+#include "policy.h"
+#include "database.h"
+#include "msgbase.h"
+#include "serv_ldap.h"
+#include "vcard.h"
+#include "tools.h"
+
+
+#include "ctdl_module.h"
+
+
+
+#ifdef HAVE_LDAP
+
+#include <ldap.h>
+
+LDAP *dirserver = NULL;
+
+/*
+ * LDAP connector cleanup function
+ */
+void serv_ldap_cleanup(void)
+{
+ if (!dirserver) return;
+
+ lprintf(CTDL_INFO, "Unbinding from directory server\n");
+ ldap_unbind(dirserver);
+ dirserver = NULL;
+}
+
+
+
+/*
+ * Create the root node. If it's already there, so what?
+ */
+void CtdlCreateLdapRoot(void) {
+ char *dc_values[2];
+ char *objectClass_values[3];
+ LDAPMod dc, objectClass;
+ LDAPMod *mods[3];
+ char topdc[SIZ];
+ int i;
+
+ /* We just want the top-level dc, not the whole hierarchy */
+ strcpy(topdc, config.c_ldap_base_dn);
+ for (i=0; i<strlen(topdc); ++i) {
+ if (topdc[i] == ',') topdc[i] = 0;
+ }
+ for (i=0; i<strlen(topdc); ++i) {
+ if (topdc[i] == '=') strcpy(topdc, &topdc[i+1]);
+ }
+
+ /* Set up the transaction */
+ dc.mod_op = LDAP_MOD_ADD;
+ dc.mod_type = "dc";
+ dc_values[0] = topdc;
+ dc_values[1] = NULL;
+ dc.mod_values = dc_values;
+ objectClass.mod_op = LDAP_MOD_ADD;
+ objectClass.mod_type = "objectClass";
+ objectClass_values[0] = "top";
+ objectClass_values[1] = "domain";
+ objectClass_values[2] = NULL;
+ objectClass.mod_values = objectClass_values;
+ mods[0] = &dc;
+ mods[1] = &objectClass;
+ mods[2] = NULL;
+
+ /* Perform the transaction */
+ lprintf(CTDL_DEBUG, "Setting up Base DN node...\n");
+ begin_critical_section(S_LDAP);
+ i = ldap_add_s(dirserver, config.c_ldap_base_dn, mods);
+ end_critical_section(S_LDAP);
+
+ if (i == LDAP_ALREADY_EXISTS) {
+ lprintf(CTDL_INFO, "Base DN is already present in the directory; no need to add it again.\n");
+ }
+ else if (i != LDAP_SUCCESS) {
+ lprintf(CTDL_CRIT, "ldap_add_s() failed: %s (%d)\n",
+ ldap_err2string(i), i);
+ }
+}
+
+
+/*
+ * Create an OU node representing a Citadel host.
+ */
+void CtdlCreateHostOU(char *host) {
+ char *dc_values[2];
+ char *objectClass_values[3];
+ LDAPMod dc, objectClass;
+ LDAPMod *mods[3];
+ int i;
+ char dn[SIZ];
+
+ /* The DN is this OU plus the base. */
+ snprintf(dn, sizeof dn, "ou=%s,%s", host, config.c_ldap_base_dn);
+
+ /* Set up the transaction */
+ dc.mod_op = LDAP_MOD_ADD;
+ dc.mod_type = "ou";
+ dc_values[0] = host;
+ dc_values[1] = NULL;
+ dc.mod_values = dc_values;
+ objectClass.mod_op = LDAP_MOD_ADD;
+ objectClass.mod_type = "objectClass";
+ objectClass_values[0] = "top";
+ objectClass_values[1] = "organizationalUnit";
+ objectClass_values[2] = NULL;
+ objectClass.mod_values = objectClass_values;
+ mods[0] = &dc;
+ mods[1] = &objectClass;
+ mods[2] = NULL;
+
+ /* Perform the transaction */
+ lprintf(CTDL_DEBUG, "Setting up Host OU node...\n");
+ begin_critical_section(S_LDAP);
+ i = ldap_add_s(dirserver, dn, mods);
+ end_critical_section(S_LDAP);
+
+ if (i == LDAP_ALREADY_EXISTS) {
+ lprintf(CTDL_INFO, "Host OU is already present in the directory; no need to add it again.\n");
+ }
+ else if (i != LDAP_SUCCESS) {
+ lprintf(CTDL_CRIT, "ldap_add_s() failed: %s (%d)\n",
+ ldap_err2string(i), i);
+ }
+}
+
+
+
+
+
+
+
+
+void CtdlConnectToLdap(void) {
+ int i;
+ int ldap_version = 3;
+
+ lprintf(CTDL_INFO, "Connecting to LDAP server %s:%d...\n",
+ config.c_ldap_host, config.c_ldap_port);
+
+ dirserver = ldap_init(config.c_ldap_host, config.c_ldap_port);
+ if (dirserver == NULL) {
+ lprintf(CTDL_CRIT, "Could not connect to %s:%d : %s\n",
+ config.c_ldap_host,
+ config.c_ldap_port,
+ strerror(errno));
+ return;
+ }
+
+ ldap_set_option(dirserver, LDAP_OPT_PROTOCOL_VERSION, &ldap_version);
+
+ lprintf(CTDL_INFO, "Binding to %s\n", config.c_ldap_bind_dn);
+
+ i = ldap_simple_bind_s(dirserver,
+ config.c_ldap_bind_dn,
+ config.c_ldap_bind_pw
+ );
+ if (i != LDAP_SUCCESS) {
+ lprintf(CTDL_CRIT, "Cannot bind: %s (%d)\n", ldap_err2string(i), i);
+ dirserver = NULL; /* FIXME disconnect from ldap */
+ return;
+ }
+
+ CtdlCreateLdapRoot();
+}
+
+
+/*
+ * vCard-to-LDAP conversions.
+ *
+ * If 'op' is set to V2L_WRITE, then write
+ * (add, or change if already exists) a directory entry to the
+ * LDAP server, based on the information supplied in a vCard.
+ *
+ * If 'op' is set to V2L_DELETE, then delete the entry from LDAP.
+ */
+void ctdl_vcard_to_ldap(struct CtdlMessage *msg, int op) {
+ struct vCard *v = NULL;
+ int i, j;
+ char this_dn[SIZ];
+ LDAPMod **attrs = NULL;
+ int num_attrs = 0;
+ int num_emails = 0;
+ int alias_attr = (-1);
+ int num_phones = 0;
+ int phone_attr = (-1);
+ int have_addr = 0;
+ int have_cn = 0;
+
+ char givenname[128];
+ char sn[128];
+ char uid[256];
+ char street[256];
+ char city[128];
+ char state[3];
+ char zipcode[10];
+ char calFBURL[256];
+
+ if (dirserver == NULL) return;
+ if (msg == NULL) return;
+ if (msg->cm_fields['M'] == NULL) return;
+ if (msg->cm_fields['A'] == NULL) return;
+ if (msg->cm_fields['N'] == NULL) return;
+
+ /* Initialize variables */
+ strcpy(givenname, "");
+ strcpy(sn, "");
+ strcpy(calFBURL, "");
+
+ sprintf(this_dn, "cn=%s,ou=%s,%s",
+ msg->cm_fields['A'],
+ msg->cm_fields['N'],
+ config.c_ldap_base_dn
+ );
+
+ sprintf(uid, "%s@%s",
+ msg->cm_fields['A'],
+ msg->cm_fields['N']
+ );
+
+ /* Are we just deleting? If so, it's simple... */
+ if (op == V2L_DELETE) {
+ lprintf(CTDL_DEBUG, "Calling ldap_delete_s()\n");
+ begin_critical_section(S_LDAP);
+ i = ldap_delete_s(dirserver, this_dn);
+ end_critical_section(S_LDAP);
+ if (i != LDAP_SUCCESS) {
+ lprintf(CTDL_ERR, "ldap_delete_s() failed: %s (%d)\n",
+ ldap_err2string(i), i);
+ }
+ return;
+ }
+
+ /*
+ * If we get to this point then it must be a V2L_WRITE operation.
+ */
+
+ /* First make sure the OU for the user's home Citadel host is created */
+ CtdlCreateHostOU(msg->cm_fields['N']);
+
+ /* The first LDAP attribute will be an 'objectclass' list. Citadel
+ * doesn't do anything with this. It's just there for compatibility
+ * with Kolab.
+ */
+ num_attrs = 1;
+ attrs = malloc( (sizeof(LDAPMod *) * num_attrs) );
+ attrs[0] = malloc(sizeof(LDAPMod));
+ memset(attrs[0], 0, sizeof(LDAPMod));
+ attrs[0]->mod_op = LDAP_MOD_ADD;
+ attrs[0]->mod_type = "objectclass";
+ attrs[0]->mod_values = malloc(3 * sizeof(char *));
+ attrs[0]->mod_values[0] = strdup("citadelInetOrgPerson");
+ attrs[0]->mod_values[1] = NULL;
+
+ /* Convert the vCard fields to LDAP properties */
+ v = vcard_load(msg->cm_fields['M']);
+ if (v->numprops) for (i=0; i<(v->numprops); ++i) if (striplt(v->prop[i].value), strlen(v->prop[i].value) > 0) {
+
+ if (!strcasecmp(v->prop[i].name, "n")) {
+ extract_token(sn, v->prop[i].value, 0, ';', sizeof sn);
+ extract_token(givenname, v->prop[i].value, 1, ';', sizeof givenname);
+ }
+
+ if (!strcasecmp(v->prop[i].name, "fn")) {
+ attrs = realloc(attrs, (sizeof(LDAPMod *) * ++num_attrs) );
+ attrs[num_attrs-1] = malloc(sizeof(LDAPMod));
+ memset(attrs[num_attrs-1], 0, sizeof(LDAPMod));
+ attrs[num_attrs-1]->mod_op = LDAP_MOD_ADD;
+ attrs[num_attrs-1]->mod_type = "cn";
+ attrs[num_attrs-1]->mod_values = malloc(2 * sizeof(char *));
+ attrs[num_attrs-1]->mod_values[0] = strdup(v->prop[i].value);
+ attrs[num_attrs-1]->mod_values[1] = NULL;
+ have_cn = 1;
+ }
+
+ if (!strcasecmp(v->prop[i].name, "title")) {
+ attrs = realloc(attrs, (sizeof(LDAPMod *) * ++num_attrs) );
+ attrs[num_attrs-1] = malloc(sizeof(LDAPMod));
+ memset(attrs[num_attrs-1], 0, sizeof(LDAPMod));
+ attrs[num_attrs-1]->mod_op = LDAP_MOD_ADD;
+ attrs[num_attrs-1]->mod_type = "title";
+ attrs[num_attrs-1]->mod_values = malloc(2 * sizeof(char *));
+ attrs[num_attrs-1]->mod_values[0] = strdup(v->prop[i].value);
+ attrs[num_attrs-1]->mod_values[1] = NULL;
+ }
+
+ if (!strcasecmp(v->prop[i].name, "org")) {
+ attrs = realloc(attrs, (sizeof(LDAPMod *) * ++num_attrs) );
+ attrs[num_attrs-1] = malloc(sizeof(LDAPMod));
+ memset(attrs[num_attrs-1], 0, sizeof(LDAPMod));
+ attrs[num_attrs-1]->mod_op = LDAP_MOD_ADD;
+ attrs[num_attrs-1]->mod_type = "o";
+ attrs[num_attrs-1]->mod_values = malloc(2 * sizeof(char *));
+ attrs[num_attrs-1]->mod_values[0] = strdup(v->prop[i].value);
+ attrs[num_attrs-1]->mod_values[1] = NULL;
+ }
+
+ if ( (!strcasecmp(v->prop[i].name, "adr"))
+ ||(!strncasecmp(v->prop[i].name, "adr;", 4)) ) {
+ /* Unfortunately, we can only do a single address */
+ if (!have_addr) {
+ have_addr = 1;
+ strcpy(street, "");
+ extract_token(&street[strlen(street)],
+ v->prop[i].value, 0, ';', (sizeof street - strlen(street))); /* po box */
+ strcat(street, " ");
+ extract_token(&street[strlen(street)],
+ v->prop[i].value, 1, ';', (sizeof street - strlen(street))); /* extend addr */
+ strcat(street, " ");
+ extract_token(&street[strlen(street)],
+ v->prop[i].value, 2, ';', (sizeof street - strlen(street))); /* street */
+ striplt(street);
+ extract_token(city, v->prop[i].value, 3, ';', sizeof city);
+ extract_token(state, v->prop[i].value, 4, ';', sizeof state);
+ extract_token(zipcode, v->prop[i].value, 5, ';', sizeof zipcode);
+
+ attrs = realloc(attrs, (sizeof(LDAPMod *) * ++num_attrs) );
+ attrs[num_attrs-1] = malloc(sizeof(LDAPMod));
+ memset(attrs[num_attrs-1], 0, sizeof(LDAPMod));
+ attrs[num_attrs-1]->mod_op = LDAP_MOD_ADD;
+ attrs[num_attrs-1]->mod_type = "street";
+ attrs[num_attrs-1]->mod_values = malloc(2 * sizeof(char *));
+ attrs[num_attrs-1]->mod_values[0] = strdup(street);
+ attrs[num_attrs-1]->mod_values[1] = NULL;
+
+ attrs = realloc(attrs, (sizeof(LDAPMod *) * ++num_attrs) );
+ attrs[num_attrs-1] = malloc(sizeof(LDAPMod));
+ memset(attrs[num_attrs-1], 0, sizeof(LDAPMod));
+ attrs[num_attrs-1]->mod_op = LDAP_MOD_ADD;
+ attrs[num_attrs-1]->mod_type = "l";
+ attrs[num_attrs-1]->mod_values = malloc(2 * sizeof(char *));
+ attrs[num_attrs-1]->mod_values[0] = strdup(city);
+ attrs[num_attrs-1]->mod_values[1] = NULL;
+
+ attrs = realloc(attrs, (sizeof(LDAPMod *) * ++num_attrs) );
+ attrs[num_attrs-1] = malloc(sizeof(LDAPMod));
+ memset(attrs[num_attrs-1], 0, sizeof(LDAPMod));
+ attrs[num_attrs-1]->mod_op = LDAP_MOD_ADD;
+ attrs[num_attrs-1]->mod_type = "st";
+ attrs[num_attrs-1]->mod_values = malloc(2 * sizeof(char *));
+ attrs[num_attrs-1]->mod_values[0] = strdup(state);
+ attrs[num_attrs-1]->mod_values[1] = NULL;
+
+ attrs = realloc(attrs, (sizeof(LDAPMod *) * ++num_attrs) );
+ attrs[num_attrs-1] = malloc(sizeof(LDAPMod));
+ memset(attrs[num_attrs-1], 0, sizeof(LDAPMod));
+ attrs[num_attrs-1]->mod_op = LDAP_MOD_ADD;
+ attrs[num_attrs-1]->mod_type = "postalcode";
+ attrs[num_attrs-1]->mod_values = malloc(2 * sizeof(char *));
+ attrs[num_attrs-1]->mod_values[0] = strdup(zipcode);
+ attrs[num_attrs-1]->mod_values[1] = NULL;
+ }
+ }
+
+ if ( (!strcasecmp(v->prop[i].name, "tel"))
+ ||(!strncasecmp(v->prop[i].name, "tel;", 4)) ) {
+ ++num_phones;
+ /* The first 'tel' property creates the 'telephoneNumber' attribute */
+ if (num_phones == 1) {
+ attrs = realloc(attrs, (sizeof(LDAPMod *) * ++num_attrs) );
+ phone_attr = num_attrs-1;
+ attrs[phone_attr] = malloc(sizeof(LDAPMod));
+ memset(attrs[phone_attr], 0, sizeof(LDAPMod));
+ attrs[phone_attr]->mod_op = LDAP_MOD_ADD;
+ attrs[phone_attr]->mod_type = "telephoneNumber";
+ attrs[phone_attr]->mod_values = malloc(2 * sizeof(char *));
+ attrs[phone_attr]->mod_values[0] = strdup(v->prop[i].value);
+ attrs[phone_attr]->mod_values[1] = NULL;
+ }
+ /* Subsequent 'tel' properties *add to* the 'telephoneNumber' attribute */
+ else {
+ attrs[phone_attr]->mod_values = realloc(attrs[phone_attr]->mod_values,
+ num_phones * sizeof(char *));
+ attrs[phone_attr]->mod_values[num_phones-1]
+ = strdup(v->prop[i].value);
+ attrs[phone_attr]->mod_values[num_phones]
+ = NULL;
+ }
+ }
+
+
+ if ( (!strcasecmp(v->prop[i].name, "email"))
+ ||(!strcasecmp(v->prop[i].name, "email;internet")) ) {
+
+ ++num_emails;
+ lprintf(CTDL_DEBUG, "email addr %d\n", num_emails);
+
+ /* The first email address creates the 'mail' attribute */
+ if (num_emails == 1) {
+ attrs = realloc(attrs, (sizeof(LDAPMod *) * ++num_attrs) );
+ attrs[num_attrs-1] = malloc(sizeof(LDAPMod));
+ memset(attrs[num_attrs-1], 0, sizeof(LDAPMod));
+ attrs[num_attrs-1]->mod_op = LDAP_MOD_ADD;
+ attrs[num_attrs-1]->mod_type = "mail";
+ attrs[num_attrs-1]->mod_values = malloc(2 * sizeof(char *));
+ attrs[num_attrs-1]->mod_values[0] = strdup(v->prop[i].value);
+ attrs[num_attrs-1]->mod_values[1] = NULL;
+ }
+ /* The second email address creates the 'alias' attribute */
+ else if (num_emails == 2) {
+ attrs = realloc(attrs, (sizeof(LDAPMod *) * ++num_attrs) );
+ alias_attr = num_attrs-1;
+ attrs[alias_attr] = malloc(sizeof(LDAPMod));
+ memset(attrs[alias_attr], 0, sizeof(LDAPMod));
+ attrs[alias_attr]->mod_op = LDAP_MOD_ADD;
+ attrs[alias_attr]->mod_type = "alias";
+ attrs[alias_attr]->mod_values = malloc(2 * sizeof(char *));
+ attrs[alias_attr]->mod_values[0] = strdup(v->prop[i].value);
+ attrs[alias_attr]->mod_values[1] = NULL;
+ }
+ /* Subsequent email addresses *add to* the 'alias' attribute */
+ else if (num_emails > 2) {
+ attrs[alias_attr]->mod_values = realloc(attrs[alias_attr]->mod_values,
+ num_emails * sizeof(char *));
+ attrs[alias_attr]->mod_values[num_emails-2]
+ = strdup(v->prop[i].value);
+ attrs[alias_attr]->mod_values[num_emails-1]
+ = NULL;
+ }
+
+
+ }
+
+ /* Calendar free/busy URL (take the first one we find, but if a subsequent
+ * one contains the "pref" designation then we go with that instead.)
+ */
+ if ( (!strcasecmp(v->prop[i].name, "fburl"))
+ ||(!strncasecmp(v->prop[i].name, "fburl;", 6)) ) {
+ if ( (strlen(calFBURL) == 0)
+ || (!strncasecmp(v->prop[i].name, "fburl;pref", 10)) ) {
+ safestrncpy(calFBURL, v->prop[i].value, sizeof calFBURL);
+ }
+ }
+
+ }
+ vcard_free(v); /* Don't need this anymore. */
+
+ /* "sn" (surname) based on info in vCard */
+ attrs = realloc(attrs, (sizeof(LDAPMod *) * ++num_attrs) );
+ attrs[num_attrs-1] = malloc(sizeof(LDAPMod));
+ memset(attrs[num_attrs-1], 0, sizeof(LDAPMod));
+ attrs[num_attrs-1]->mod_op = LDAP_MOD_ADD;
+ attrs[num_attrs-1]->mod_type = "sn";
+ attrs[num_attrs-1]->mod_values = malloc(2 * sizeof(char *));
+ attrs[num_attrs-1]->mod_values[0] = strdup(sn);
+ attrs[num_attrs-1]->mod_values[1] = NULL;
+
+ /* "givenname" (first name) based on info in vCard */
+ if (strlen(givenname) == 0) strcpy(givenname, "_");
+ if (strlen(sn) == 0) strcpy(sn, "_");
+ attrs = realloc(attrs, (sizeof(LDAPMod *) * ++num_attrs) );
+ attrs[num_attrs-1] = malloc(sizeof(LDAPMod));
+ memset(attrs[num_attrs-1], 0, sizeof(LDAPMod));
+ attrs[num_attrs-1]->mod_op = LDAP_MOD_ADD;
+ attrs[num_attrs-1]->mod_type = "givenname";
+ attrs[num_attrs-1]->mod_values = malloc(2 * sizeof(char *));
+ attrs[num_attrs-1]->mod_values[0] = strdup(givenname);
+ attrs[num_attrs-1]->mod_values[1] = NULL;
+
+ /* "uid" is a Kolab compatibility thing. We just do cituser@citnode */
+ attrs = realloc(attrs, (sizeof(LDAPMod *) * ++num_attrs) );
+ attrs[num_attrs-1] = malloc(sizeof(LDAPMod));
+ memset(attrs[num_attrs-1], 0, sizeof(LDAPMod));
+ attrs[num_attrs-1]->mod_op = LDAP_MOD_ADD;
+ attrs[num_attrs-1]->mod_type = "uid";
+ attrs[num_attrs-1]->mod_values = malloc(2 * sizeof(char *));
+ attrs[num_attrs-1]->mod_values[0] = strdup(uid);
+ attrs[num_attrs-1]->mod_values[1] = NULL;
+
+ /* Add a "cn" (Common Name) attribute based on the user's screen name,
+ * but only there was no 'fn' (full name) property in the vCard
+ */
+ if (!have_cn) {
+ attrs = realloc(attrs, (sizeof(LDAPMod *) * ++num_attrs) );
+ attrs[num_attrs-1] = malloc(sizeof(LDAPMod));
+ memset(attrs[num_attrs-1], 0, sizeof(LDAPMod));
+ attrs[num_attrs-1]->mod_op = LDAP_MOD_ADD;
+ attrs[num_attrs-1]->mod_type = "cn";
+ attrs[num_attrs-1]->mod_values = malloc(2 * sizeof(char *));
+ attrs[num_attrs-1]->mod_values[0] = strdup(msg->cm_fields['A']);
+ attrs[num_attrs-1]->mod_values[1] = NULL;
+ }
+
+ /* Add a "calFBURL" attribute if a calendar free/busy URL exists */
+ if (strlen(calFBURL) > 0) {
+ attrs = realloc(attrs, (sizeof(LDAPMod *) * ++num_attrs) );
+ attrs[num_attrs-1] = malloc(sizeof(LDAPMod));
+ memset(attrs[num_attrs-1], 0, sizeof(LDAPMod));
+ attrs[num_attrs-1]->mod_op = LDAP_MOD_ADD;
+ attrs[num_attrs-1]->mod_type = "calFBURL";
+ attrs[num_attrs-1]->mod_values = malloc(2 * sizeof(char *));
+ attrs[num_attrs-1]->mod_values[0] = strdup(calFBURL);
+ attrs[num_attrs-1]->mod_values[1] = NULL;
+ }
+
+ /* The last attribute must be a NULL one. */
+ attrs = realloc(attrs, (sizeof(LDAPMod *) * ++num_attrs) );
+ attrs[num_attrs - 1] = NULL;
+
+ lprintf(CTDL_DEBUG, "Calling ldap_add_s() for '%s'\n", this_dn);
+ begin_critical_section(S_LDAP);
+ i = ldap_add_s(dirserver, this_dn, attrs);
+ end_critical_section(S_LDAP);
+
+ /* If the entry already exists, repopulate it instead */
+ if (i == LDAP_ALREADY_EXISTS) {
+ for (j=0; j<(num_attrs-1); ++j) {
+ attrs[j]->mod_op = LDAP_MOD_REPLACE;
+ }
+ lprintf(CTDL_DEBUG, "Calling ldap_modify_s() for '%s'\n", this_dn);
+ begin_critical_section(S_LDAP);
+ i = ldap_modify_s(dirserver, this_dn, attrs);
+ end_critical_section(S_LDAP);
+ }
+
+ if (i != LDAP_SUCCESS) {
+ lprintf(CTDL_ERR, "ldap_add_s() failed: %s (%d)\n",
+ ldap_err2string(i), i);
+ }
+
+ lprintf(CTDL_DEBUG, "Freeing attributes\n");
+ /* Free the attributes */
+ for (i=0; i<num_attrs; ++i) {
+ if (attrs[i] != NULL) {
+
+ /* First, free the value strings */
+ if (attrs[i]->mod_values != NULL) {
+ for (j=0; attrs[i]->mod_values[j] != NULL; ++j) {
+ free(attrs[i]->mod_values[j]);
+ }
+ }
+
+ /* Free the value strings pointer list */
+ if (attrs[i]->mod_values != NULL) {
+ free(attrs[i]->mod_values);
+ }
+
+ /* Now free the LDAPMod struct itself. */
+ free(attrs[i]);
+ }
+ }
+ free(attrs);
+ lprintf(CTDL_DEBUG, "LDAP write operation complete.\n");
+}
+
+
+#endif /* HAVE_LDAP */
+
+
+/*
+ * Initialize the LDAP connector module ... or don't, if we don't have LDAP.
+ */
+CTDL_MODULE_INIT(ldap)
+{
+#ifdef HAVE_LDAP
+ CtdlRegisterCleanupHook(serv_ldap_cleanup);
+
+ if (strlen(config.c_ldap_host) > 0) {
+ CtdlConnectToLdap();
+ }
+
+#endif /* HAVE_LDAP */
+
+ /* return our Subversion id for the Log */
+ return "$Id$";
+}
--- /dev/null
+/*
+ * $Id$
+ *
+ * This module handles self-service subscription/unsubscription to mail lists.
+ *
+ * Copyright (C) 2002-2005 by Art Cancro and others.
+ * This code is released under the terms of the GNU General Public License.
+ *
+ */
+
+#include "sysdep.h"
+#include <stdlib.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <fcntl.h>
+#include <ctype.h>
+#include <signal.h>
+#include <pwd.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <dirent.h>
+#if TIME_WITH_SYS_TIME
+# include <sys/time.h>
+# include <time.h>
+#else
+# if HAVE_SYS_TIME_H
+# include <sys/time.h>
+# else
+# include <time.h>
+# endif
+#endif
+
+#include <sys/wait.h>
+#include <string.h>
+#include <limits.h>
+#include "citadel.h"
+#include "server.h"
+#include "citserver.h"
+#include "support.h"
+#include "config.h"
+#include "room_ops.h"
+#include "user_ops.h"
+#include "policy.h"
+#include "database.h"
+#include "msgbase.h"
+#include "tools.h"
+#include "internet_addressing.h"
+#include "serv_network.h"
+#include "clientsocket.h"
+#include "file_ops.h"
+
+#ifndef HAVE_SNPRINTF
+#include "snprintf.h"
+#endif
+
+
+
+#include "ctdl_module.h"
+
+
+/*
+ * Generate a randomizationalisticized token to use for authentication of
+ * a subscribe or unsubscribe request.
+ */
+void listsub_generate_token(char *buf) {
+ char sourcebuf[SIZ];
+ static int seq = 0;
+
+ /* Theo, please sit down and shut up. This key doesn't have to be
+ * tinfoil-hat secure, it just needs to be reasonably unguessable
+ * and unique.
+ */
+ sprintf(sourcebuf, "%lx",
+ (long) (++seq + getpid() + time(NULL))
+ );
+
+ /* Convert it to base64 so it looks cool */
+ CtdlEncodeBase64(buf, sourcebuf, strlen(sourcebuf));
+}
+
+
+/*
+ * Enter a subscription request
+ */
+void do_subscribe(char *room, char *email, char *subtype, char *webpage) {
+ struct ctdlroom qrbuf;
+ FILE *ncfp;
+ char filename[256];
+ char token[256];
+ char confirmation_request[2048];
+ char buf[512];
+ char urlroom[ROOMNAMELEN];
+ char scancmd[64];
+ char scanemail[256];
+ int found_sub = 0;
+
+ if (getroom(&qrbuf, room) != 0) {
+ cprintf("%d There is no list called '%s'\n", ERROR + ROOM_NOT_FOUND, room);
+ return;
+ }
+
+ if ((qrbuf.QRflags2 & QR2_SELFLIST) == 0) {
+ cprintf("%d '%s' "
+ "does not accept subscribe/unsubscribe requests.\n",
+ ERROR + HIGHER_ACCESS_REQUIRED, qrbuf.QRname);
+ return;
+ }
+
+ listsub_generate_token(token);
+
+ assoc_file_name(filename, sizeof filename, &qrbuf, ctdl_netcfg_dir);
+
+ /*
+ * Make sure the requested address isn't already subscribed
+ */
+ begin_critical_section(S_NETCONFIGS);
+ ncfp = fopen(filename, "r");
+ if (ncfp != NULL) {
+ while (fgets(buf, sizeof buf, ncfp) != NULL) {
+ buf[strlen(buf)-1] = 0;
+ extract_token(scancmd, buf, 0, '|', sizeof scancmd);
+ extract_token(scanemail, buf, 1, '|', sizeof scanemail);
+ if ((!strcasecmp(scancmd, "listrecp"))
+ || (!strcasecmp(scancmd, "digestrecp"))) {
+ if (!strcasecmp(scanemail, email)) {
+ ++found_sub;
+ }
+ }
+ }
+ fclose(ncfp);
+ }
+ end_critical_section(S_NETCONFIGS);
+
+ if (found_sub != 0) {
+ cprintf("%d '%s' is already subscribed to '%s'.\n",
+ ERROR + ALREADY_EXISTS,
+ email, qrbuf.QRname);
+ return;
+ }
+
+ /*
+ * Now add it to the file
+ */
+ begin_critical_section(S_NETCONFIGS);
+ ncfp = fopen(filename, "a");
+ if (ncfp != NULL) {
+ fprintf(ncfp, "subpending|%s|%s|%s|%ld|%s\n",
+ email,
+ subtype,
+ token,
+ time(NULL),
+ webpage
+ );
+ fclose(ncfp);
+ }
+ end_critical_section(S_NETCONFIGS);
+
+ /* Generate and send the confirmation request */
+
+ urlesc(urlroom, qrbuf.QRname);
+
+ snprintf(confirmation_request, sizeof confirmation_request,
+
+ "MIME-Version: 1.0\n"
+ "Content-Type: multipart/alternative; boundary=\"__ctdlmultipart__\"\n"
+ "\n"
+ "This is a multipart message in MIME format.\n"
+ "\n"
+ "--__ctdlmultipart__\n"
+ "Content-type: text/plain\n"
+ "\n"
+ "Someone (probably you) has submitted a request to subscribe\n"
+ "<%s> to the '%s' mailing list.\n"
+ "\n"
+ "Please go here to confirm this request:\n"
+ " %s?room=%s&token=%s&cmd=confirm \n"
+ "\n"
+ "If this request has been submitted in error and you do not\n"
+ "wish to receive the '%s' mailing list, simply do nothing,\n"
+ "and you will not receive any further mailings.\n"
+ "\n"
+ "--__ctdlmultipart__\n"
+ "Content-type: text/html\n"
+ "\n"
+ "<HTML><BODY>\n"
+ "Someone (probably you) has submitted a request to subscribe\n"
+ "<%s> to the <B>%s</B> mailing list.<BR><BR>\n"
+ "Please click here to confirm this request:<BR>\n"
+ "<A HREF=\"%s?room=%s&token=%s&cmd=confirm\">"
+ "%s?room=%s&token=%s&cmd=confirm</A><BR><BR>\n"
+ "If this request has been submitted in error and you do not\n"
+ "wish to receive the '%s' mailing list, simply do nothing,\n"
+ "and you will not receive any further mailings.\n"
+ "</BODY></HTML>\n"
+ "\n"
+ "--__ctdlmultipart__--\n",
+
+ email, qrbuf.QRname,
+ webpage, urlroom, token,
+ qrbuf.QRname,
+
+ email, qrbuf.QRname,
+ webpage, urlroom, token,
+ webpage, urlroom, token,
+ qrbuf.QRname
+ );
+
+ quickie_message( /* This delivers the message */
+ "Citadel",
+ NULL,
+ email,
+ NULL,
+ confirmation_request,
+ FMT_RFC822,
+ "Please confirm your list subscription"
+ );
+
+ cprintf("%d Subscription entered; confirmation request sent\n", CIT_OK);
+}
+
+
+/*
+ * Enter an unsubscription request
+ */
+void do_unsubscribe(char *room, char *email, char *webpage) {
+ struct ctdlroom qrbuf;
+ FILE *ncfp;
+ char filename[256];
+ char token[256];
+ char buf[512];
+ char confirmation_request[2048];
+ char urlroom[ROOMNAMELEN];
+ char scancmd[256];
+ char scanemail[256];
+ int found_sub = 0;
+
+ if (getroom(&qrbuf, room) != 0) {
+ cprintf("%d There is no list called '%s'\n",
+ ERROR + ROOM_NOT_FOUND, room);
+ return;
+ }
+
+ if ((qrbuf.QRflags2 & QR2_SELFLIST) == 0) {
+ cprintf("%d '%s' "
+ "does not accept subscribe/unsubscribe requests.\n",
+ ERROR + HIGHER_ACCESS_REQUIRED, qrbuf.QRname);
+ return;
+ }
+
+ listsub_generate_token(token);
+
+ assoc_file_name(filename, sizeof filename, &qrbuf, ctdl_netcfg_dir);
+
+ /*
+ * Make sure there's actually a subscription there to remove
+ */
+ begin_critical_section(S_NETCONFIGS);
+ ncfp = fopen(filename, "r");
+ if (ncfp != NULL) {
+ while (fgets(buf, sizeof buf, ncfp) != NULL) {
+ buf[strlen(buf)-1] = 0;
+ extract_token(scancmd, buf, 0, '|', sizeof scancmd);
+ extract_token(scanemail, buf, 1, '|', sizeof scanemail);
+ if ((!strcasecmp(scancmd, "listrecp"))
+ || (!strcasecmp(scancmd, "digestrecp"))) {
+ if (!strcasecmp(scanemail, email)) {
+ ++found_sub;
+ }
+ }
+ }
+ fclose(ncfp);
+ }
+ end_critical_section(S_NETCONFIGS);
+
+ if (found_sub == 0) {
+ cprintf("%d '%s' is not subscribed to '%s'.\n",
+ ERROR + NO_SUCH_USER,
+ email, qrbuf.QRname);
+ return;
+ }
+
+ /*
+ * Ok, now enter the unsubscribe-pending entry.
+ */
+ begin_critical_section(S_NETCONFIGS);
+ ncfp = fopen(filename, "a");
+ if (ncfp != NULL) {
+ fprintf(ncfp, "unsubpending|%s|%s|%ld|%s\n",
+ email,
+ token,
+ time(NULL),
+ webpage
+ );
+ fclose(ncfp);
+ }
+ end_critical_section(S_NETCONFIGS);
+
+ /* Generate and send the confirmation request */
+
+ urlesc(urlroom, qrbuf.QRname);
+
+ snprintf(confirmation_request, sizeof confirmation_request,
+
+ "MIME-Version: 1.0\n"
+ "Content-Type: multipart/alternative; boundary=\"__ctdlmultipart__\"\n"
+ "\n"
+ "This is a multipart message in MIME format.\n"
+ "\n"
+ "--__ctdlmultipart__\n"
+ "Content-type: text/plain\n"
+ "\n"
+ "Someone (probably you) has submitted a request to unsubscribe\n"
+ "<%s> from the '%s' mailing list.\n"
+ "\n"
+ "Please go here to confirm this request:\n"
+ " %s?room=%s&token=%s&cmd=confirm \n"
+ "\n"
+ "If this request has been submitted in error and you do not\n"
+ "wish to unsubscribe from the '%s' mailing list, simply do nothing,\n"
+ "and the request will not be processed.\n"
+ "\n"
+ "--__ctdlmultipart__\n"
+ "Content-type: text/html\n"
+ "\n"
+ "<HTML><BODY>\n"
+ "Someone (probably you) has submitted a request to unsubscribe\n"
+ "<%s> from the <B>%s</B> mailing list.<BR><BR>\n"
+ "Please click here to confirm this request:<BR>\n"
+ "<A HREF=\"%s?room=%s&token=%s&cmd=confirm\">"
+ "%s?room=%s&token=%s&cmd=confirm</A><BR><BR>\n"
+ "If this request has been submitted in error and you do not\n"
+ "wish to unsubscribe from the '%s' mailing list, simply do nothing,\n"
+ "and the request will not be processed.\n"
+ "</BODY></HTML>\n"
+ "\n"
+ "--__ctdlmultipart__--\n",
+
+ email, qrbuf.QRname,
+ webpage, urlroom, token,
+ qrbuf.QRname,
+
+ email, qrbuf.QRname,
+ webpage, urlroom, token,
+ webpage, urlroom, token,
+ qrbuf.QRname
+ );
+
+ quickie_message( /* This delivers the message */
+ "Citadel",
+ NULL,
+ email,
+ NULL,
+ confirmation_request,
+ FMT_RFC822,
+ "Please confirm your unsubscribe request"
+ );
+
+ cprintf("%d Unubscription noted; confirmation request sent\n", CIT_OK);
+}
+
+
+/*
+ * Confirm a subscribe/unsubscribe request.
+ */
+void do_confirm(char *room, char *token) {
+ struct ctdlroom qrbuf;
+ FILE *ncfp;
+ char filename[256];
+ char line_token[256];
+ long line_offset;
+ int line_length;
+ char buf[512];
+ char cmd[256];
+ char email[256];
+ char subtype[128];
+ int success = 0;
+ char address_to_unsubscribe[256];
+ char scancmd[256];
+ char scanemail[256];
+ char *holdbuf = NULL;
+ int linelen = 0;
+ int buflen = 0;
+
+ strcpy(address_to_unsubscribe, "");
+
+ if (getroom(&qrbuf, room) != 0) {
+ cprintf("%d There is no list called '%s'\n",
+ ERROR + ROOM_NOT_FOUND, room);
+ return;
+ }
+
+ if ((qrbuf.QRflags2 & QR2_SELFLIST) == 0) {
+ cprintf("%d '%s' "
+ "does not accept subscribe/unsubscribe requests.\n",
+ ERROR + HIGHER_ACCESS_REQUIRED, qrbuf.QRname);
+ return;
+ }
+
+ /*
+ * Now start scanning this room's netconfig file for the
+ * specified token.
+ */
+ assoc_file_name(filename, sizeof filename, &qrbuf, ctdl_netcfg_dir);
+ begin_critical_section(S_NETCONFIGS);
+ ncfp = fopen(filename, "r+");
+ if (ncfp != NULL) {
+ while (line_offset = ftell(ncfp),
+ (fgets(buf, sizeof buf, ncfp) != NULL) ) {
+ buf[strlen(buf)-1] = 0;
+ line_length = strlen(buf);
+ extract_token(cmd, buf, 0, '|', sizeof cmd);
+ if (!strcasecmp(cmd, "subpending")) {
+ extract_token(email, buf, 1, '|', sizeof email);
+ extract_token(subtype, buf, 2, '|', sizeof subtype);
+ extract_token(line_token, buf, 3, '|', sizeof line_token);
+ if (!strcasecmp(token, line_token)) {
+ if (!strcasecmp(subtype, "digest")) {
+ safestrncpy(buf, "digestrecp|", sizeof buf);
+ }
+ else {
+ safestrncpy(buf, "listrecp|", sizeof buf);
+ }
+ strcat(buf, email);
+ strcat(buf, "|");
+ /* SLEAZY HACK: pad the line out so
+ * it's the same length as the line
+ * we're replacing.
+ */
+ while (strlen(buf) < line_length) {
+ strcat(buf, " ");
+ }
+ fseek(ncfp, line_offset, SEEK_SET);
+ fprintf(ncfp, "%s\n", buf);
+ ++success;
+ }
+ }
+ if (!strcasecmp(cmd, "unsubpending")) {
+ extract_token(line_token, buf, 2, '|', sizeof line_token);
+ if (!strcasecmp(token, line_token)) {
+ extract_token(address_to_unsubscribe, buf, 1, '|',
+ sizeof address_to_unsubscribe);
+ }
+ }
+ }
+ fclose(ncfp);
+ }
+ end_critical_section(S_NETCONFIGS);
+
+ /*
+ * If "address_to_unsubscribe" contains something, then we have to
+ * make another pass at the file, stripping out lines referring to
+ * that address.
+ */
+ if (strlen(address_to_unsubscribe) > 0) {
+ holdbuf = malloc(SIZ);
+ begin_critical_section(S_NETCONFIGS);
+ ncfp = fopen(filename, "r+");
+ if (ncfp != NULL) {
+ while (line_offset = ftell(ncfp),
+ (fgets(buf, sizeof buf, ncfp) != NULL) ) {
+ buf[strlen(buf)-1]=0;
+ extract_token(scancmd, buf, 0, '|', sizeof scancmd);
+ extract_token(scanemail, buf, 1, '|', sizeof scanemail);
+ if ( (!strcasecmp(scancmd, "listrecp"))
+ && (!strcasecmp(scanemail,
+ address_to_unsubscribe)) ) {
+ ++success;
+ }
+ else if ( (!strcasecmp(scancmd, "digestrecp"))
+ && (!strcasecmp(scanemail,
+ address_to_unsubscribe)) ) {
+ ++success;
+ }
+ else if ( (!strcasecmp(scancmd, "subpending"))
+ && (!strcasecmp(scanemail,
+ address_to_unsubscribe)) ) {
+ ++success;
+ }
+ else if ( (!strcasecmp(scancmd, "unsubpending"))
+ && (!strcasecmp(scanemail,
+ address_to_unsubscribe)) ) {
+ ++success;
+ }
+ else { /* Not relevant, so *keep* it! */
+ linelen = strlen(buf);
+ holdbuf = realloc(holdbuf,
+ (buflen + linelen + 2) );
+ strcpy(&holdbuf[buflen], buf);
+ buflen += linelen;
+ strcpy(&holdbuf[buflen], "\n");
+ buflen += 1;
+ }
+ }
+ fclose(ncfp);
+ }
+ ncfp = fopen(filename, "w");
+ if (ncfp != NULL) {
+ fwrite(holdbuf, buflen+1, 1, ncfp);
+ fclose(ncfp);
+ }
+ end_critical_section(S_NETCONFIGS);
+ free(holdbuf);
+ }
+
+ /*
+ * Did we do anything useful today?
+ */
+ if (success) {
+ cprintf("%d %d operation(s) confirmed.\n", CIT_OK, success);
+ lprintf(CTDL_NOTICE, "Mailing list: %s %ssubscribed to %s with token %s\n", email, (strlen(address_to_unsubscribe) > 0) ? "un" : "", room, token);
+ }
+ else {
+ cprintf("%d Invalid token.\n", ERROR + ILLEGAL_VALUE);
+ }
+
+}
+
+
+
+/*
+ * process subscribe/unsubscribe requests and confirmations
+ */
+void cmd_subs(char *cmdbuf) {
+
+ char opr[256];
+ char room[ROOMNAMELEN];
+ char email[256];
+ char subtype[256];
+ char token[256];
+ char webpage[256];
+
+ extract_token(opr, cmdbuf, 0, '|', sizeof opr);
+ if (!strcasecmp(opr, "subscribe")) {
+ extract_token(subtype, cmdbuf, 3, '|', sizeof subtype);
+ if ( (strcasecmp(subtype, "list"))
+ && (strcasecmp(subtype, "digest")) ) {
+ cprintf("%d Invalid subscription type '%s'\n",
+ ERROR + ILLEGAL_VALUE, subtype);
+ }
+ else {
+ extract_token(room, cmdbuf, 1, '|', sizeof room);
+ extract_token(email, cmdbuf, 2, '|', sizeof email);
+ extract_token(webpage, cmdbuf, 4, '|', sizeof webpage);
+ do_subscribe(room, email, subtype, webpage);
+ }
+ }
+ else if (!strcasecmp(opr, "unsubscribe")) {
+ extract_token(room, cmdbuf, 1, '|', sizeof room);
+ extract_token(email, cmdbuf, 2, '|', sizeof email);
+ extract_token(webpage, cmdbuf, 3, '|', sizeof webpage);
+ do_unsubscribe(room, email, webpage);
+ }
+ else if (!strcasecmp(opr, "confirm")) {
+ extract_token(room, cmdbuf, 1, '|', sizeof room);
+ extract_token(token, cmdbuf, 2, '|', sizeof token);
+ do_confirm(room, token);
+ }
+ else {
+ cprintf("%d Invalid command\n", ERROR + ILLEGAL_VALUE);
+ }
+}
+
+
+/*
+ * Module entry point
+ */
+CTDL_MODULE_INIT(listsub)
+{
+ CtdlRegisterProtoHook(cmd_subs, "SUBS", "List subscribe/unsubscribe");
+
+ /* return our Subversion id for the Log */
+ return "$Id$";
+}
--- /dev/null
+/**
+ * $Id$
+ *
+ * This module is an managesieve implementation for the Citadel system.
+ * It is compliant with all of the following:
+ *
+ * http://tools.ietf.org/html/draft-martin-managesieve-06
+ * as this draft expires with this writing, you might need to search for
+ * the new one.
+ */
+
+#include "sysdep.h"
+#include <stdlib.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <pwd.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <syslog.h>
+
+#if TIME_WITH_SYS_TIME
+# include <sys/time.h>
+# include <time.h>
+#else
+# if HAVE_SYS_TIME_H
+# include <sys/time.h>
+# else
+# include <time.h>
+# endif
+#endif
+
+#include <sys/wait.h>
+#include <ctype.h>
+#include <string.h>
+#include <limits.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include "citadel.h"
+#include "server.h"
+#include "citserver.h"
+#include "support.h"
+#include "config.h"
+#include "control.h"
+#include "room_ops.h"
+#include "user_ops.h"
+#include "policy.h"
+#include "database.h"
+#include "msgbase.h"
+#include "tools.h"
+#include "internet_addressing.h"
+#include "imap_tools.h"
+#include "genstamp.h"
+#include "domain.h"
+#include "clientsocket.h"
+#include "locate_host.h"
+#include "citadel_dirs.h"
+
+#ifdef HAVE_OPENSSL
+#include "serv_crypto.h"
+#endif
+
+#ifndef HAVE_SNPRINTF
+#include "snprintf.h"
+#endif
+
+
+#include "ctdl_module.h"
+
+
+
+#ifdef HAVE_LIBSIEVE
+
+#include "serv_sieve.h"
+
+
+/**
+ * http://tools.ietf.org/html/draft-martin-managesieve-06
+ *
+ * this is the draft this code tries to implement.
+ */
+
+
+struct citmgsve {
+ int command_state; /**< Information about the current session */
+ char *transmitted_message;
+ size_t transmitted_length;
+ char *imap_format_outstring;
+ int imap_outstring_length;
+};
+
+enum { /** Command states for login authentication */
+ mgsve_command,
+ mgsve_tls,
+ mgsve_user,
+ mgsve_password,
+ mgsve_plain
+};
+
+#define MGSVE CC->MGSVE
+
+/*****************************************************************************/
+/* MANAGESIEVE Server */
+/*****************************************************************************/
+
+void sieve_outbuf_append(char *str)
+{
+ size_t newlen = strlen(str)+1;
+ size_t oldlen = (MGSVE->imap_format_outstring==NULL)? 0 : strlen(MGSVE->imap_format_outstring)+2;
+ char *buf = malloc ( newlen + oldlen + 10 );
+ buf[0]='\0';
+
+ if (oldlen!=0)
+ sprintf(buf,"%s%s",MGSVE->imap_format_outstring, str);
+ else
+ memcpy(buf, str, newlen);
+
+ if (oldlen != 0) free (MGSVE->imap_format_outstring);
+ MGSVE->imap_format_outstring = buf;
+}
+
+
+/**
+ * Capability listing. Printed as greeting or on "CAPABILITIES"
+ * see Section 1.8 ; 2.4
+ */
+void cmd_mgsve_caps(void)
+{
+ cprintf("\"IMPLEMENTATION\" \"CITADEL Sieve v6.84\"\r\n" /* TODO: put citversion here. */
+ "\"SASL\" \"PLAIN\"\r\n" /*DIGEST-MD5 GSSAPI SASL sucks.*/
+#ifdef HAVE_OPENSSL
+/* if TLS is already there, should we say that again? */
+ "\"STARTTLS\"\r\n"
+#endif
+ "\"SIEVE\" \"%s\"\r\n"
+ "OK\r\n", msiv_extensions);
+}
+
+
+/*
+ * Here's where our managesieve session begins its happy day.
+ */
+void managesieve_greeting(void) {
+
+ strcpy(CC->cs_clientname, "Managesieve session");
+
+ CC->internal_pgm = 1;
+ CC->cs_flags |= CS_STEALTH;
+ MGSVE = malloc(sizeof(struct citmgsve));
+ memset(MGSVE, 0, sizeof(struct citmgsve));
+ cmd_mgsve_caps();
+}
+
+
+long GetSizeToken(char * token)
+{
+ char *cursor = token;
+ char *number;
+
+ while ((*cursor != '\0') &&
+ (*cursor != '{'))
+ {
+ cursor++;
+ }
+ if (*cursor == '\0')
+ return -1;
+ number = cursor + 1;
+ while ((*cursor != '\0') &&
+ (*cursor != '}'))
+ {
+ cursor++;
+ }
+
+ if (cursor[-1] == '+')
+ cursor--;
+
+ if (*cursor == '\0')
+ return -1;
+
+ return atol(number);
+}
+
+char *ReadString(long size, char *command)
+{
+ long ret;
+ if (size < 1) {
+ cprintf("NO %s: %ld BAD Message length must be at least 1.\r\n",
+ command, size);
+ CC->kill_me = 1;
+ return NULL;
+ }
+ MGSVE->transmitted_message = malloc(size + 2);
+ if (MGSVE->transmitted_message == NULL) {
+ cprintf("NO %s Cannot allocate memory.\r\n", command);
+ CC->kill_me = 1;
+ return NULL;
+ }
+ MGSVE->transmitted_length = size;
+
+ ret = client_read(MGSVE->transmitted_message, size);
+ MGSVE->transmitted_message[size] = '\0';
+
+ if (ret != 1) {
+ cprintf("%s NO Read failed.\r\n", command);
+ return NULL;
+ }
+ return MGSVE->transmitted_message;
+
+}
+/* AUTHENTICATE command; 2.1 */
+void cmd_mgsve_auth(int num_parms, char **parms, struct sdm_userdata *u)
+{
+ if ((num_parms == 3) && !strncasecmp(parms[1], "PLAIN", 5))
+ /* todo, check length*/
+ {
+ char auth[SIZ];
+ int retval;
+ char *message = ReadString(GetSizeToken(parms[2]), parms[0]);
+
+ if (message != NULL) {/**< do we have tokenized login? */
+ retval = CtdlDecodeBase64(auth, MGSVE->transmitted_message, SIZ);
+ }
+ else
+ retval = CtdlDecodeBase64(auth, parms[2], SIZ);
+
+ if (login_ok == CtdlLoginExistingUser(NULL, auth))
+ {
+ char *pass;
+ pass = &(auth[strlen(auth)+1]);
+ /* for some reason the php script sends us the username twice. y? */
+ pass = &(pass[strlen(pass)+1]);
+
+ if (pass_ok == CtdlTryPassword(pass))
+ {
+ MGSVE->command_state = mgsve_password;
+ cprintf("OK\r\n");
+ return;
+ }
+ }
+ }
+
+ cprintf("NO \"Authentication Failure.\"\r\n");/* we just support auth plain. */
+ CC->kill_me = 1;
+}
+
+
+#ifdef HAVE_OPENSSL
+/**
+ * STARTTLS command chapter 2.2
+ */
+void cmd_mgsve_starttls(void)
+{ /** answer with OK, and fire off tls session. */
+ cprintf("OK\r\n");
+ CtdlStartTLS(NULL, NULL, NULL);
+ cmd_mgsve_caps();
+}
+#endif
+
+
+
+/**
+ *LOGOUT command, see chapter 2.3
+ */
+void cmd_mgsve_logout(struct sdm_userdata *u)
+{
+ cprintf("OK\r\n");
+ lprintf(CTDL_NOTICE, "MgSve bye.");
+ CC->kill_me = 1;
+}
+
+
+/**
+ * HAVESPACE command. see chapter 2.5
+ */
+void cmd_mgsve_havespace(void)
+{
+/* as we don't have quotas in citadel we should always answer with OK;
+ * pherhaps we should have a max-scriptsize.
+ */
+ if (MGSVE->command_state != mgsve_password)
+ {
+ cprintf("NO\r\n");
+ CC->kill_me = 1;
+ }
+ else
+ {
+ cprintf("OK");
+/* citadel doesn't have quotas. in case of change, please add code here. */
+
+ }
+}
+
+/**
+ * PUTSCRIPT command, see chapter 2.6
+ */
+void cmd_mgsve_putscript(int num_parms, char **parms, struct sdm_userdata *u)
+{
+/* "scriptname" {nnn+} */
+/* AFTER we have the whole script overwrite existing scripts */
+/* spellcheck the script before overwrite old ones, and reply with "no" */
+ if (num_parms == 3)
+ {
+ char *ScriptName;
+ char *Script;
+ long slength;
+
+ if (parms[1][0]=='"')
+ ScriptName = &parms[1][1];
+ else
+ ScriptName = parms[1];
+
+ slength = strlen (ScriptName);
+
+ if (ScriptName[slength] == '"')
+ ScriptName[slength] = '\0';
+
+ Script = ReadString(GetSizeToken(parms[2]),parms[0]);
+
+ if (Script == NULL) return;
+
+ // TODO: do we spellcheck?
+ msiv_putscript(u, ScriptName, Script);
+ cprintf("OK\r\n");
+ }
+ else {
+ cprintf("%s NO Read failed.\r\n", parms[0]);
+ CC->kill_me = 1;
+ return;
+ }
+
+
+
+}
+
+
+
+
+/**
+ * LISTSCRIPT command. see chapter 2.7
+ */
+void cmd_mgsve_listscript(int num_parms, char **parms, struct sdm_userdata *u)
+{
+
+ struct sdm_script *s;
+ long nScripts = 0;
+
+ MGSVE->imap_format_outstring = NULL;
+ for (s=u->first_script; s!=NULL; s=s->next) {
+ if (s->script_content != NULL) {
+ cprintf("\"%s\"%s\r\n",
+ s->script_name,
+ (s->script_active)?" ACTIVE":"");
+ nScripts++;
+ }
+ }
+ cprintf("OK\r\n");
+}
+
+
+/**
+ * \brief SETACTIVE command. see chapter 2.8
+ */
+void cmd_mgsve_setactive(int num_parms, char **parms, struct sdm_userdata *u)
+{
+ if (num_parms == 2)
+ {
+ if (msiv_setactive(u, parms[1]) == 0) {
+ cprintf("OK\r\n");
+ }
+ else
+ cprintf("No \"there is no script by that name %s \"\r\n", parms[1]);
+ }
+ else
+ cprintf("NO \"unexpected parameters.\"\r\n");
+
+}
+
+
+/**
+ * \brief GETSCRIPT command. see chapter 2.9
+ */
+void cmd_mgsve_getscript(int num_parms, char **parms, struct sdm_userdata *u)
+{
+ if (num_parms == 2){
+ char *script_content;
+ long slen;
+
+ script_content = msiv_getscript(u, parms[1]);
+ if (script_content != NULL){
+ char *outbuf;
+
+ slen = strlen(script_content);
+ outbuf = malloc (slen + 64);
+ snprintf(outbuf, slen + 64, "{%ld+}\r\n%s\r\nOK\r\n",slen, script_content);
+ cprintf(outbuf);
+ }
+ else
+ cprintf("No \"there is no script by that name %s \"\r\n", parms[1]);
+ }
+ else
+ cprintf("NO \"unexpected parameters.\"\r\n");
+}
+
+
+/**
+ * \brief DELETESCRIPT command. see chapter 2.10
+ */
+void cmd_mgsve_deletescript(int num_parms, char **parms, struct sdm_userdata *u)
+{
+ int i=-1;
+
+ if (num_parms == 2)
+ i = msiv_deletescript(u, parms[1]);
+ switch (i){
+ case 0:
+ cprintf("OK\r\n");
+ break;
+ case 1:
+ cprintf("NO \"no script by that name: %s\"\r\n", parms[1]);
+ break;
+ case 2:
+ cprintf("NO \"can't delete active Script: %s\"\r\n", parms[1]);
+ break;
+ default:
+ case -1:
+ cprintf("NO \"unexpected parameters.\"\r\n");
+ break;
+ }
+}
+
+
+/**
+ * \brief Attempt to perform authenticated managesieve
+ */
+void mgsve_auth(char *argbuf) {
+ char username_prompt[64];
+ char method[64];
+ char encoded_authstring[1024];
+
+ if (CC->logged_in) {
+ cprintf("NO \"Already logged in.\"\r\n");
+ return;
+ }
+
+ extract_token(method, argbuf, 0, ' ', sizeof method);
+
+ if (!strncasecmp(method, "login", 5) ) {
+ if (strlen(argbuf) >= 7) {
+ }
+ else {
+ CtdlEncodeBase64(username_prompt, "Username:", 9);
+ cprintf("334 %s\r\n", username_prompt);
+ }
+ return;
+ }
+
+ if (!strncasecmp(method, "plain", 5) ) {
+ if (num_tokens(argbuf, ' ') < 2) {
+ cprintf("334 \r\n");
+ return;
+ }
+ extract_token(encoded_authstring, argbuf, 1, ' ', sizeof encoded_authstring);
+ return;
+ }
+
+ if (strncasecmp(method, "login", 5) ) {
+ cprintf("NO \"Unknown authentication method.\"\r\n");
+ return;
+ }
+
+}
+
+
+
+/*
+ * implements the STARTTLS command (Citadel API version)
+ */
+#ifdef HAVE_OPENSSL
+void _mgsve_starttls(void)
+{
+ char ok_response[SIZ];
+ char nosup_response[SIZ];
+ char error_response[SIZ];
+
+ sprintf(ok_response,
+ "200 2.0.0 Begin TLS negotiation now\r\n");
+ sprintf(nosup_response,
+ "554 5.7.3 TLS not supported here\r\n");
+ sprintf(error_response,
+ "554 5.7.3 Internal error\r\n");
+ CtdlStartTLS(ok_response, nosup_response, error_response);
+}
+#endif
+
+
+/*
+ * Main command loop for managesieve sessions.
+ */
+void managesieve_command_loop(void) {
+ char cmdbuf[SIZ];
+ char *parms[SIZ];
+ int length;
+ int num_parms;
+ struct sdm_userdata u;
+ int changes_made = 0;
+
+ memset(&u, 0, sizeof(struct sdm_userdata));
+
+ time(&CC->lastcmd);
+ memset(cmdbuf, 0, sizeof cmdbuf); /* Clear it, just in case */
+ length = client_getln(cmdbuf, sizeof cmdbuf);
+ if (length >= 1) {
+ num_parms = imap_parameterize(parms, cmdbuf);
+ if (num_parms == 0) return;
+ length = strlen(parms[0]);
+ }
+ if (length < 1) {
+ lprintf(CTDL_CRIT, "Client disconnected: ending session.\n");
+ CC->kill_me = 1;
+ return;
+ }
+ lprintf(CTDL_INFO, "MANAGESIEVE: %s\n", cmdbuf);
+ if ((length>= 12) && (!strncasecmp(parms[0], "AUTHENTICATE", 12))){
+ cmd_mgsve_auth(num_parms, parms, &u);
+ }
+
+#ifdef HAVE_OPENSSL
+ else if ((length>= 8) && (!strncasecmp(parms[0], "STARTTLS", 8))){
+ cmd_mgsve_starttls();
+ }
+#endif
+ else if ((length>= 6) && (!strncasecmp(parms[0], "LOGOUT", 6))){
+ cmd_mgsve_logout(&u);
+ }
+ else if ((length>= 6) && (!strncasecmp(parms[0], "CAPABILITY", 10))){
+ cmd_mgsve_caps();
+ }
+ /** these commands need to be authenticated. throw it out if it tries. */
+ else if (!CtdlAccessCheck(ac_logged_in))
+ {
+ msiv_load(&u);
+ if ((length>= 9) && (!strncasecmp(parms[0], "HAVESPACE", 9))){
+ cmd_mgsve_havespace();
+ }
+ else if ((length>= 6) && (!strncasecmp(parms[0], "PUTSCRIPT", 9))){
+ cmd_mgsve_putscript(num_parms, parms, &u);
+ changes_made = 1;
+ }
+ else if ((length>= 6) && (!strncasecmp(parms[0], "LISTSCRIPT", 10))){
+ cmd_mgsve_listscript(num_parms, parms,&u);
+ }
+ else if ((length>= 6) && (!strncasecmp(parms[0], "SETACTIVE", 9))){
+ cmd_mgsve_setactive(num_parms, parms,&u);
+ changes_made = 1;
+ }
+ else if ((length>= 6) && (!strncasecmp(parms[0], "GETSCRIPT", 9))){
+ cmd_mgsve_getscript(num_parms, parms, &u);
+ }
+ else if ((length>= 6) && (!strncasecmp(parms[0], "DELETESCRIPT", 11))){
+ cmd_mgsve_deletescript(num_parms, parms, &u);
+ changes_made = 1;
+ }
+ msiv_store(&u, changes_made);
+ }
+ else {
+ cprintf("No\r\n");
+ lprintf(CTDL_INFO, "illegal Managesieve command: %s", parms[0]);
+ CC->kill_me = 1;
+ }
+
+
+}
+
+
+#endif /* HAVE_LIBSIEVE */
+
+CTDL_MODULE_INIT(managesieve)
+{
+
+#ifdef HAVE_LIBSIEVE
+
+ CtdlRegisterServiceHook(config.c_managesieve_port, /* MGSVE */
+ NULL,
+ managesieve_greeting,
+ managesieve_command_loop,
+ NULL);
+
+#else /* HAVE_LIBSIEVE */
+
+ lprintf(CTDL_INFO, "This server is missing libsieve. Managesieve protocol is disabled..\n");
+
+#endif /* HAVE_LIBSIEVE */
+
+ /* return our Subversion id for the Log */
+ return "$Id$";
+}
+
+
--- /dev/null
+/*
+ * $Id$
+ *
+ * This module supplies statistics about the activity levels of your Citadel
+ * system. We didn't bother writing a reporting module, because there is
+ * already an excellent tool called MRTG (Multi Router Traffic Grapher) which
+ * is available at http://www.mrtg.org that can fetch data using external
+ * scripts. This module supplies data in the format expected by MRTG.
+ *
+ */
+
+#include "sysdep.h"
+#include <stdlib.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <pwd.h>
+#include <errno.h>
+#include <sys/types.h>
+
+#if TIME_WITH_SYS_TIME
+# include <sys/time.h>
+# include <time.h>
+#else
+# if HAVE_SYS_TIME_H
+# include <sys/time.h>
+# else
+# include <time.h>
+# endif
+#endif
+
+#include <sys/wait.h>
+#include <string.h>
+#include <limits.h>
+#include "citadel.h"
+#include "server.h"
+#include "citserver.h"
+#include "support.h"
+#include "config.h"
+#include "control.h"
+#include "room_ops.h"
+#include "user_ops.h"
+#include "policy.h"
+#include "database.h"
+#include "msgbase.h"
+#include "tools.h"
+
+
+#include "ctdl_module.h"
+
+
+/*
+ * Other functions call this one to output data in MRTG format
+ */
+void mrtg_output(long value1, long value2) {
+ time_t uptime_t;
+ int uptime_days, uptime_hours, uptime_minutes;
+
+ uptime_t = time(NULL) - server_startup_time;
+ uptime_days = (int) (uptime_t / 86400L);
+ uptime_hours = (int) ((uptime_t % 86400L) / 3600L);
+ uptime_minutes = (int) ((uptime_t % 3600L) / 60L);
+
+ cprintf("%d ok\n", LISTING_FOLLOWS);
+ cprintf("%ld\n", value1);
+ cprintf("%ld\n", value2);
+ cprintf("%d days, %d hours, %d minutes\n",
+ uptime_days, uptime_hours, uptime_minutes);
+ cprintf("%s\n", config.c_humannode);
+ cprintf("000\n");
+}
+
+
+
+
+/*
+ * Tell us how many users are online
+ */
+void mrtg_users(void) {
+ long connected_users = 0;
+ long active_users = 0;
+
+ struct CitContext *cptr;
+
+ for (cptr = ContextList; cptr != NULL; cptr = cptr->next) {
+
+ if (cptr->internal_pgm == 0) {
+ ++connected_users;
+
+ if ( (time(NULL) - (cptr->lastidle)) < 900L) {
+ ++active_users;
+ }
+ }
+
+ }
+
+ mrtg_output(connected_users, active_users);
+}
+
+
+/*
+ * Volume of messages submitted
+ */
+void mrtg_messages(void) {
+ mrtg_output(CitControl.MMhighest, 0L);
+}
+
+
+/*
+ * Fetch data for MRTG
+ */
+void cmd_mrtg(char *argbuf) {
+ char which[32];
+
+ extract_token(which, argbuf, 0, '|', sizeof which);
+
+ if (!strcasecmp(which, "users")) {
+ mrtg_users();
+ }
+ else if (!strcasecmp(which, "messages")) {
+ mrtg_messages();
+ }
+ else {
+ cprintf("%d Unrecognized keyword '%s'\n",
+ ERROR + ILLEGAL_VALUE, which);
+ }
+}
+
+
+CTDL_MODULE_INIT(mrtg)
+{
+ CtdlRegisterProtoHook(cmd_mrtg, "MRTG", "Supply stats to MRTG");
+
+ /* return our Subversion id for the Log */
+ return "$Id$";
+}
--- /dev/null
+/*
+ * $Id$
+ *
+ * A server-side module for Citadel designed to filter idiots off the network.
+ *
+ * Copyright (c) 2002 / released under the GNU General Public License
+ */
+
+#include "sysdep.h"
+#include <stdlib.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <pwd.h>
+#include <errno.h>
+#include <sys/types.h>
+
+#if TIME_WITH_SYS_TIME
+# include <sys/time.h>
+# include <time.h>
+#else
+# if HAVE_SYS_TIME_H
+# include <sys/time.h>
+# else
+# include <time.h>
+# endif
+#endif
+
+#include <sys/wait.h>
+#include <string.h>
+#include <limits.h>
+#include "citadel.h"
+#include "server.h"
+#include "citserver.h"
+#include "support.h"
+#include "config.h"
+#include "control.h"
+#include "room_ops.h"
+#include "user_ops.h"
+#include "policy.h"
+#include "database.h"
+#include "msgbase.h"
+#include "serv_network.h"
+#include "tools.h"
+
+
+#include "ctdl_module.h"
+
+
+/*
+ * This handler detects whether an incoming network message is from some
+ * moron user who the site operator has elected to filter out. If a match
+ * is found, the message is rejected.
+ */
+int filter_the_idiots(struct CtdlMessage *msg, char *target_room) {
+ struct FilterList *fptr;
+ int zap_user = 0;
+ int zap_room = 0;
+ int zap_node = 0;
+
+ if ( (msg == NULL) || (filterlist == NULL) ) {
+ return(0);
+ }
+
+ for (fptr = filterlist; fptr != NULL; fptr = fptr->next) {
+
+ zap_user = 0;
+ zap_room = 0;
+ zap_node = 0;
+
+ if (msg->cm_fields['A'] != NULL) {
+ if ( (!strcasecmp(msg->cm_fields['A'], fptr->fl_user))
+ || (fptr->fl_user[0] == 0) ) {
+ zap_user = 1;
+ }
+ }
+
+ if (msg->cm_fields['C'] != NULL) {
+ if ( (!strcasecmp(msg->cm_fields['C'], fptr->fl_room))
+ || (fptr->fl_room[0] == 0) ) {
+ zap_room = 1;
+ }
+ }
+
+ if (msg->cm_fields['O'] != NULL) {
+ if ( (!strcasecmp(msg->cm_fields['O'], fptr->fl_room))
+ || (fptr->fl_room[0] == 0) ) {
+ zap_room = 1;
+ }
+ }
+
+ if (msg->cm_fields['N'] != NULL) {
+ if ( (!strcasecmp(msg->cm_fields['N'], fptr->fl_node))
+ || (fptr->fl_node[0] == 0) ) {
+ zap_node = 1;
+ }
+ }
+
+ if (zap_user + zap_room + zap_node == 3) return(1);
+
+ }
+
+ return(0);
+}
+
+
+CTDL_MODULE_INIT(netfilter)
+{
+ CtdlRegisterNetprocHook(filter_the_idiots);
+
+ /* return our Subversion id for the Log */
+ return "$Id$";
+}
--- /dev/null
+/*
+ * $Id$
+ *
+ * This module handles shared rooms, inter-Citadel mail, and outbound
+ * mailing list processing.
+ *
+ * Copyright (C) 2000-2005 by Art Cancro and others.
+ * This code is released under the terms of the GNU General Public License.
+ *
+ * ** NOTE ** A word on the S_NETCONFIGS semaphore:
+ * This is a fairly high-level type of critical section. It ensures that no
+ * two threads work on the netconfigs files at the same time. Since we do
+ * so many things inside these, here are the rules:
+ * 1. begin_critical_section(S_NETCONFIGS) *before* begin_ any others.
+ * 2. Do *not* perform any I/O with the client during these sections.
+ *
+ */
+
+/*
+ * Duration of time (in seconds) after which pending list subscribe/unsubscribe
+ * requests that have not been confirmed will be deleted.
+ */
+#define EXP 259200 /* three days */
+
+#include "sysdep.h"
+#include <stdlib.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <fcntl.h>
+#include <ctype.h>
+#include <signal.h>
+#include <pwd.h>
+#include <errno.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <dirent.h>
+#if TIME_WITH_SYS_TIME
+# include <sys/time.h>
+# include <time.h>
+#else
+# if HAVE_SYS_TIME_H
+# include <sys/time.h>
+# else
+# include <time.h>
+# endif
+#endif
+
+#include <sys/wait.h>
+#include <string.h>
+#include <limits.h>
+#include "citadel.h"
+#include "server.h"
+#include "citserver.h"
+#include "support.h"
+#include "config.h"
+#include "room_ops.h"
+#include "user_ops.h"
+#include "policy.h"
+#include "database.h"
+#include "msgbase.h"
+#include "tools.h"
+#include "internet_addressing.h"
+#include "serv_network.h"
+#include "clientsocket.h"
+#include "file_ops.h"
+#include "citadel_dirs.h"
+
+#ifndef HAVE_SNPRINTF
+#include "snprintf.h"
+#endif
+
+
+#include "ctdl_module.h"
+
+
+
+/* Nonzero while we are doing network processing */
+static int doing_queue = 0;
+
+/*
+ * When we do network processing, it's accomplished in two passes; one to
+ * gather a list of rooms and one to actually do them. It's ok that rplist
+ * is global; we have a mutex that keeps it safe.
+ */
+struct RoomProcList *rplist = NULL;
+
+/*
+ * We build a map of network nodes during processing.
+ */
+struct NetMap *the_netmap = NULL;
+int netmap_changed = 0;
+char *working_ignetcfg = NULL;
+
+/*
+ * Load or refresh the Citadel network (IGnet) configuration for this node.
+ */
+void load_working_ignetcfg(void) {
+ char *cfg;
+ char *oldcfg;
+
+ cfg = CtdlGetSysConfig(IGNETCFG);
+ if (cfg == NULL) {
+ cfg = strdup("");
+ }
+
+ oldcfg = working_ignetcfg;
+ working_ignetcfg = cfg;
+ if (oldcfg != NULL) {
+ free(oldcfg);
+ }
+}
+
+
+
+
+
+/*
+ * Keep track of what messages to reject
+ */
+struct FilterList *load_filter_list(void) {
+ char *serialized_list = NULL;
+ int i;
+ char buf[SIZ];
+ struct FilterList *newlist = NULL;
+ struct FilterList *nptr;
+
+ serialized_list = CtdlGetSysConfig(FILTERLIST);
+ if (serialized_list == NULL) return(NULL); /* if null, no entries */
+
+ /* Use the string tokenizer to grab one line at a time */
+ for (i=0; i<num_tokens(serialized_list, '\n'); ++i) {
+ extract_token(buf, serialized_list, i, '\n', sizeof buf);
+ nptr = (struct FilterList *) malloc(sizeof(struct FilterList));
+ extract_token(nptr->fl_user, buf, 0, '|', sizeof nptr->fl_user);
+ striplt(nptr->fl_user);
+ extract_token(nptr->fl_room, buf, 1, '|', sizeof nptr->fl_room);
+ striplt(nptr->fl_room);
+ extract_token(nptr->fl_node, buf, 2, '|', sizeof nptr->fl_node);
+ striplt(nptr->fl_node);
+
+ /* Cowardly refuse to add an any/any/any entry that would
+ * end up filtering every single message.
+ */
+ if (strlen(nptr->fl_user) + strlen(nptr->fl_room)
+ + strlen(nptr->fl_node) == 0) {
+ free(nptr);
+ }
+ else {
+ nptr->next = newlist;
+ newlist = nptr;
+ }
+ }
+
+ free(serialized_list);
+ return newlist;
+}
+
+
+void free_filter_list(struct FilterList *fl) {
+ if (fl == NULL) return;
+ free_filter_list(fl->next);
+ free(fl);
+}
+
+
+
+/*
+ * Check the use table. This is a list of messages which have recently
+ * arrived on the system. It is maintained and queried to prevent the same
+ * message from being entered into the database multiple times if it happens
+ * to arrive multiple times by accident.
+ */
+int network_usetable(struct CtdlMessage *msg) {
+
+ char msgid[SIZ];
+ struct cdbdata *cdbut;
+ struct UseTable ut;
+
+ /* Bail out if we can't generate a message ID */
+ if (msg == NULL) {
+ return(0);
+ }
+ if (msg->cm_fields['I'] == NULL) {
+ return(0);
+ }
+ if (strlen(msg->cm_fields['I']) == 0) {
+ return(0);
+ }
+
+ /* Generate the message ID */
+ strcpy(msgid, msg->cm_fields['I']);
+ if (haschar(msgid, '@') == 0) {
+ strcat(msgid, "@");
+ if (msg->cm_fields['N'] != NULL) {
+ strcat(msgid, msg->cm_fields['N']);
+ }
+ else {
+ return(0);
+ }
+ }
+
+ cdbut = cdb_fetch(CDB_USETABLE, msgid, strlen(msgid));
+ if (cdbut != NULL) {
+ cdb_free(cdbut);
+ return(1);
+ }
+
+ /* If we got to this point, it's unique: add it. */
+ strcpy(ut.ut_msgid, msgid);
+ ut.ut_timestamp = time(NULL);
+ cdb_store(CDB_USETABLE, msgid, strlen(msgid),
+ &ut, sizeof(struct UseTable) );
+ return(0);
+}
+
+
+/*
+ * Read the network map from its configuration file into memory.
+ */
+void read_network_map(void) {
+ char *serialized_map = NULL;
+ int i;
+ char buf[SIZ];
+ struct NetMap *nmptr;
+
+ serialized_map = CtdlGetSysConfig(IGNETMAP);
+ if (serialized_map == NULL) return; /* if null, no entries */
+
+ /* Use the string tokenizer to grab one line at a time */
+ for (i=0; i<num_tokens(serialized_map, '\n'); ++i) {
+ extract_token(buf, serialized_map, i, '\n', sizeof buf);
+ nmptr = (struct NetMap *) malloc(sizeof(struct NetMap));
+ extract_token(nmptr->nodename, buf, 0, '|', sizeof nmptr->nodename);
+ nmptr->lastcontact = extract_long(buf, 1);
+ extract_token(nmptr->nexthop, buf, 2, '|', sizeof nmptr->nexthop);
+ nmptr->next = the_netmap;
+ the_netmap = nmptr;
+ }
+
+ free(serialized_map);
+ netmap_changed = 0;
+}
+
+
+/*
+ * Write the network map from memory back to the configuration file.
+ */
+void write_network_map(void) {
+ char *serialized_map = NULL;
+ struct NetMap *nmptr;
+
+
+ if (netmap_changed) {
+ serialized_map = strdup("");
+
+ if (the_netmap != NULL) {
+ for (nmptr = the_netmap; nmptr != NULL; nmptr = nmptr->next) {
+ serialized_map = realloc(serialized_map,
+ (strlen(serialized_map)+SIZ) );
+ if (strlen(nmptr->nodename) > 0) {
+ snprintf(&serialized_map[strlen(serialized_map)],
+ SIZ,
+ "%s|%ld|%s\n",
+ nmptr->nodename,
+ (long)nmptr->lastcontact,
+ nmptr->nexthop);
+ }
+ }
+ }
+
+ CtdlPutSysConfig(IGNETMAP, serialized_map);
+ free(serialized_map);
+ }
+
+ /* Now free the list */
+ while (the_netmap != NULL) {
+ nmptr = the_netmap->next;
+ free(the_netmap);
+ the_netmap = nmptr;
+ }
+ netmap_changed = 0;
+}
+
+
+
+/*
+ * Check the network map and determine whether the supplied node name is
+ * valid. If it is not a neighbor node, supply the name of a neighbor node
+ * which is the next hop. If it *is* a neighbor node, we also fill in the
+ * shared secret.
+ */
+int is_valid_node(char *nexthop, char *secret, char *node) {
+ int i;
+ char linebuf[SIZ];
+ char buf[SIZ];
+ int retval;
+ struct NetMap *nmptr;
+
+ if (node == NULL) {
+ return(-1);
+ }
+
+ /*
+ * First try the neighbor nodes
+ */
+ if (working_ignetcfg == NULL) {
+ lprintf(CTDL_ERR, "working_ignetcfg is NULL!\n");
+ if (nexthop != NULL) {
+ strcpy(nexthop, "");
+ }
+ return(-1);
+ }
+
+ retval = (-1);
+ if (nexthop != NULL) {
+ strcpy(nexthop, "");
+ }
+
+ /* Use the string tokenizer to grab one line at a time */
+ for (i=0; i<num_tokens(working_ignetcfg, '\n'); ++i) {
+ extract_token(linebuf, working_ignetcfg, i, '\n', sizeof linebuf);
+ extract_token(buf, linebuf, 0, '|', sizeof buf);
+ if (!strcasecmp(buf, node)) {
+ if (nexthop != NULL) {
+ strcpy(nexthop, "");
+ }
+ if (secret != NULL) {
+ extract_token(secret, linebuf, 1, '|', 256);
+ }
+ retval = 0;
+ }
+ }
+
+ if (retval == 0) {
+ return(retval); /* yup, it's a direct neighbor */
+ }
+
+ /*
+ * If we get to this point we have to see if we know the next hop
+ */
+ if (the_netmap != NULL) {
+ for (nmptr = the_netmap; nmptr != NULL; nmptr = nmptr->next) {
+ if (!strcasecmp(nmptr->nodename, node)) {
+ if (nexthop != NULL) {
+ strcpy(nexthop, nmptr->nexthop);
+ }
+ return(0);
+ }
+ }
+ }
+
+ /*
+ * If we get to this point, the supplied node name is bogus.
+ */
+ lprintf(CTDL_ERR, "Invalid node name <%s>\n", node);
+ return(-1);
+}
+
+
+
+
+
+void cmd_gnet(char *argbuf) {
+ char filename[SIZ];
+ char buf[SIZ];
+ FILE *fp;
+
+ if (CtdlAccessCheck(ac_room_aide)) return;
+ assoc_file_name(filename, sizeof filename, &CC->room, ctdl_netcfg_dir);
+ cprintf("%d Network settings for room #%ld <%s>\n",
+ LISTING_FOLLOWS,
+ CC->room.QRnumber, CC->room.QRname);
+
+ fp = fopen(filename, "r");
+ if (fp != NULL) {
+ while (fgets(buf, sizeof buf, fp) != NULL) {
+ buf[strlen(buf)-1] = 0;
+ cprintf("%s\n", buf);
+ }
+ fclose(fp);
+ }
+
+ cprintf("000\n");
+}
+
+
+void cmd_snet(char *argbuf) {
+ char tempfilename[SIZ];
+ char filename[SIZ];
+ char buf[SIZ];
+ FILE *fp;
+
+ unbuffer_output();
+
+ if (CtdlAccessCheck(ac_room_aide)) return;
+ CtdlMakeTempFileName(tempfilename, sizeof tempfilename);
+ assoc_file_name(filename, sizeof filename, &CC->room, ctdl_netcfg_dir);
+
+ fp = fopen(tempfilename, "w");
+ if (fp == NULL) {
+ cprintf("%d Cannot open %s: %s\n",
+ ERROR + INTERNAL_ERROR,
+ tempfilename,
+ strerror(errno));
+ }
+
+ cprintf("%d %s\n", SEND_LISTING, tempfilename);
+ while (client_getln(buf, sizeof buf), strcmp(buf, "000")) {
+ fprintf(fp, "%s\n", buf);
+ }
+ fclose(fp);
+
+ /* Now copy the temp file to its permanent location
+ * (We use /bin/mv instead of link() because they may be on
+ * different filesystems)
+ */
+ unlink(filename);
+ snprintf(buf, sizeof buf, "/bin/mv %s %s", tempfilename, filename);
+ begin_critical_section(S_NETCONFIGS);
+ system(buf);
+ end_critical_section(S_NETCONFIGS);
+}
+
+
+/*
+ * Deliver digest messages
+ */
+void network_deliver_digest(struct SpoolControl *sc) {
+ char buf[SIZ];
+ int i;
+ struct CtdlMessage *msg = NULL;
+ long msglen;
+ char *recps = NULL;
+ size_t recps_len = SIZ;
+ struct recptypes *valid;
+ struct namelist *nptr;
+
+ if (sc->num_msgs_spooled < 1) {
+ fclose(sc->digestfp);
+ sc->digestfp = NULL;
+ return;
+ }
+
+ msg = malloc(sizeof(struct CtdlMessage));
+ memset(msg, 0, sizeof(struct CtdlMessage));
+ msg->cm_magic = CTDLMESSAGE_MAGIC;
+ msg->cm_format_type = FMT_RFC822;
+ msg->cm_anon_type = MES_NORMAL;
+
+ sprintf(buf, "%ld", time(NULL));
+ msg->cm_fields['T'] = strdup(buf);
+ msg->cm_fields['A'] = strdup(CC->room.QRname);
+ snprintf(buf, sizeof buf, "[%s]", CC->room.QRname);
+ msg->cm_fields['U'] = strdup(buf);
+ sprintf(buf, "room_%s@%s", CC->room.QRname, config.c_fqdn);
+ for (i=0; i<strlen(buf); ++i) {
+ if (isspace(buf[i])) buf[i]='_';
+ buf[i] = tolower(buf[i]);
+ }
+ msg->cm_fields['F'] = strdup(buf);
+ msg->cm_fields['R'] = strdup(buf);
+
+ /*
+ * Go fetch the contents of the digest
+ */
+ fseek(sc->digestfp, 0L, SEEK_END);
+ msglen = ftell(sc->digestfp);
+
+ msg->cm_fields['M'] = malloc(msglen + 1);
+ fseek(sc->digestfp, 0L, SEEK_SET);
+ fread(msg->cm_fields['M'], (size_t)msglen, 1, sc->digestfp);
+ msg->cm_fields['M'][msglen] = 0;
+
+ fclose(sc->digestfp);
+ sc->digestfp = NULL;
+
+ /* Now generate the delivery instructions */
+
+ /*
+ * Figure out how big a buffer we need to allocate
+ */
+ for (nptr = sc->digestrecps; nptr != NULL; nptr = nptr->next) {
+ recps_len = recps_len + strlen(nptr->name) + 2;
+ }
+
+ recps = malloc(recps_len);
+
+ if (recps == NULL) {
+ lprintf(CTDL_EMERG, "Cannot allocate %ld bytes for recps...\n", (long)recps_len);
+ abort();
+ }
+
+ strcpy(recps, "");
+
+ /* Each recipient */
+ for (nptr = sc->digestrecps; nptr != NULL; nptr = nptr->next) {
+ if (nptr != sc->digestrecps) {
+ strcat(recps, ",");
+ }
+ strcat(recps, nptr->name);
+ }
+
+ /* Now submit the message */
+ valid = validate_recipients(recps);
+ free(recps);
+ CtdlSubmitMsg(msg, valid, NULL);
+ CtdlFreeMessage(msg);
+ free_recipients(valid);
+}
+
+
+/*
+ * Deliver list messages to everyone on the list ... efficiently
+ */
+void network_deliver_list(struct CtdlMessage *msg, struct SpoolControl *sc) {
+ char *recps = NULL;
+ size_t recps_len = SIZ;
+ struct recptypes *valid;
+ struct namelist *nptr;
+
+ /* Don't do this if there were no recipients! */
+ if (sc->listrecps == NULL) return;
+
+ /* Now generate the delivery instructions */
+
+ /*
+ * Figure out how big a buffer we need to allocate
+ */
+ for (nptr = sc->listrecps; nptr != NULL; nptr = nptr->next) {
+ recps_len = recps_len + strlen(nptr->name) + 2;
+ }
+
+ recps = malloc(recps_len);
+
+ if (recps == NULL) {
+ lprintf(CTDL_EMERG, "Cannot allocate %ld bytes for recps...\n", (long)recps_len);
+ abort();
+ }
+
+ strcpy(recps, "");
+
+ /* Each recipient */
+ for (nptr = sc->listrecps; nptr != NULL; nptr = nptr->next) {
+ if (nptr != sc->listrecps) {
+ strcat(recps, ",");
+ }
+ strcat(recps, nptr->name);
+ }
+
+ /* Now submit the message */
+ valid = validate_recipients(recps);
+ free(recps);
+ CtdlSubmitMsg(msg, valid, NULL);
+ free_recipients(valid);
+ /* Do not call CtdlFreeMessage(msg) here; the caller will free it. */
+}
+
+
+
+
+/*
+ * Spools out one message from the list.
+ */
+void network_spool_msg(long msgnum, void *userdata) {
+ struct SpoolControl *sc;
+ int i;
+ char *newpath = NULL;
+ size_t instr_len = SIZ;
+ struct CtdlMessage *msg = NULL;
+ struct namelist *nptr;
+ struct maplist *mptr;
+ struct ser_ret sermsg;
+ FILE *fp;
+ char filename[SIZ];
+ char buf[SIZ];
+ int bang = 0;
+ int send = 1;
+ int delete_after_send = 0; /* Set to 1 to delete after spooling */
+ int ok_to_participate = 0;
+ struct recptypes *valid;
+
+ sc = (struct SpoolControl *)userdata;
+
+ /*
+ * Process mailing list recipients
+ */
+ instr_len = SIZ;
+ if (sc->listrecps != NULL) {
+ /* Fetch the message. We're going to need to modify it
+ * in order to insert the [list name] in it, etc.
+ */
+ msg = CtdlFetchMessage(msgnum, 1);
+ if (msg != NULL) {
+
+ /* Prepend "[List name]" to the subject */
+ if (msg->cm_fields['U'] == NULL) {
+ msg->cm_fields['U'] = strdup("(no subject)");
+ }
+ snprintf(buf, sizeof buf, "[%s] %s", CC->room.QRname, msg->cm_fields['U']);
+ free(msg->cm_fields['U']);
+ msg->cm_fields['U'] = strdup(buf);
+
+ /* Set the recipient of the list message to the
+ * email address of the room itself.
+ * FIXME ... I want to be able to pick any address
+ */
+ if (msg->cm_fields['R'] != NULL) {
+ free(msg->cm_fields['R']);
+ }
+ msg->cm_fields['R'] = malloc(256);
+ snprintf(msg->cm_fields['R'], 256,
+ "room_%s@%s", CC->room.QRname,
+ config.c_fqdn);
+ for (i=0; i<strlen(msg->cm_fields['R']); ++i) {
+ if (isspace(msg->cm_fields['R'][i])) {
+ msg->cm_fields['R'][i] = '_';
+ }
+ }
+
+ /* Handle delivery */
+ network_deliver_list(msg, sc);
+ CtdlFreeMessage(msg);
+ }
+ }
+
+ /*
+ * Process digest recipients
+ */
+ if ((sc->digestrecps != NULL) && (sc->digestfp != NULL)) {
+ msg = CtdlFetchMessage(msgnum, 1);
+ if (msg != NULL) {
+ fprintf(sc->digestfp, " -----------------------------------"
+ "------------------------------------"
+ "-------\n");
+ fprintf(sc->digestfp, "From: ");
+ if (msg->cm_fields['A'] != NULL) {
+ fprintf(sc->digestfp, "%s ", msg->cm_fields['A']);
+ }
+ if (msg->cm_fields['F'] != NULL) {
+ fprintf(sc->digestfp, "<%s> ", msg->cm_fields['F']);
+ }
+ else if (msg->cm_fields['N'] != NULL) {
+ fprintf(sc->digestfp, "@%s ", msg->cm_fields['N']);
+ }
+ fprintf(sc->digestfp, "\n");
+ if (msg->cm_fields['U'] != NULL) {
+ fprintf(sc->digestfp, "Subject: %s\n", msg->cm_fields['U']);
+ }
+
+ CC->redirect_buffer = malloc(SIZ);
+ CC->redirect_len = 0;
+ CC->redirect_alloc = SIZ;
+
+ safestrncpy(CC->preferred_formats, "text/plain", sizeof CC->preferred_formats);
+ CtdlOutputPreLoadedMsg(msg, MT_CITADEL, HEADERS_NONE, 0, 0);
+
+ striplt(CC->redirect_buffer);
+ fprintf(sc->digestfp, "\n%s\n", CC->redirect_buffer);
+
+ free(CC->redirect_buffer);
+ CC->redirect_buffer = NULL;
+ CC->redirect_len = 0;
+ CC->redirect_alloc = 0;
+
+ sc->num_msgs_spooled += 1;
+ free(msg);
+ }
+ }
+
+ /*
+ * Process client-side list participations for this room
+ */
+ instr_len = SIZ;
+ if (sc->participates != NULL) {
+ msg = CtdlFetchMessage(msgnum, 1);
+ if (msg != NULL) {
+
+ /* Only send messages which originated on our own Citadel
+ * network, otherwise we'll end up sending the remote
+ * mailing list's messages back to it, which is rude...
+ */
+ ok_to_participate = 0;
+ if (msg->cm_fields['N'] != NULL) {
+ if (!strcasecmp(msg->cm_fields['N'], config.c_nodename)) {
+ ok_to_participate = 1;
+ }
+ if (is_valid_node(NULL, NULL, msg->cm_fields['N']) == 0) {
+ ok_to_participate = 1;
+ }
+ }
+ if (ok_to_participate) {
+ if (msg->cm_fields['F'] != NULL) {
+ free(msg->cm_fields['F']);
+ }
+ msg->cm_fields['F'] = malloc(SIZ);
+ /* Replace the Internet email address of the actual
+ * author with the email address of the room itself,
+ * so the remote listserv doesn't reject us.
+ * FIXME ... I want to be able to pick any address
+ */
+ snprintf(msg->cm_fields['F'], SIZ,
+ "room_%s@%s", CC->room.QRname,
+ config.c_fqdn);
+ for (i=0; i<strlen(msg->cm_fields['F']); ++i) {
+ if (isspace(msg->cm_fields['F'][i])) {
+ msg->cm_fields['F'][i] = '_';
+ }
+ }
+
+ /*
+ * Figure out how big a buffer we need to allocate
+ */
+ for (nptr = sc->participates; nptr != NULL; nptr = nptr->next) {
+
+ if (msg->cm_fields['R'] == NULL) {
+ free(msg->cm_fields['R']);
+ }
+ msg->cm_fields['R'] = strdup(nptr->name);
+
+ valid = validate_recipients(nptr->name);
+ CtdlSubmitMsg(msg, valid, "");
+ free_recipients(valid);
+ }
+
+ }
+ CtdlFreeMessage(msg);
+ }
+ }
+
+ /*
+ * Process IGnet push shares
+ */
+ msg = CtdlFetchMessage(msgnum, 1);
+ if (msg != NULL) {
+ size_t newpath_len;
+
+ /* Prepend our node name to the Path field whenever
+ * sending a message to another IGnet node
+ */
+ if (msg->cm_fields['P'] == NULL) {
+ msg->cm_fields['P'] = strdup("username");
+ }
+ newpath_len = strlen(msg->cm_fields['P']) +
+ strlen(config.c_nodename) + 2;
+ newpath = malloc(newpath_len);
+ snprintf(newpath, newpath_len, "%s!%s",
+ config.c_nodename, msg->cm_fields['P']);
+ free(msg->cm_fields['P']);
+ msg->cm_fields['P'] = newpath;
+
+ /*
+ * Determine if this message is set to be deleted
+ * after sending out on the network
+ */
+ if (msg->cm_fields['S'] != NULL) {
+ if (!strcasecmp(msg->cm_fields['S'], "CANCEL")) {
+ delete_after_send = 1;
+ }
+ }
+
+ /* Now send it to every node */
+ if (sc->ignet_push_shares != NULL)
+ for (mptr = sc->ignet_push_shares; mptr != NULL;
+ mptr = mptr->next) {
+
+ send = 1;
+
+ /* Check for valid node name */
+ if (is_valid_node(NULL, NULL, mptr->remote_nodename) != 0) {
+ lprintf(CTDL_ERR, "Invalid node <%s>\n",
+ mptr->remote_nodename);
+ send = 0;
+ }
+
+ /* Check for split horizon */
+ lprintf(CTDL_DEBUG, "Path is %s\n", msg->cm_fields['P']);
+ bang = num_tokens(msg->cm_fields['P'], '!');
+ if (bang > 1) for (i=0; i<(bang-1); ++i) {
+ extract_token(buf, msg->cm_fields['P'],
+ i, '!', sizeof buf);
+ if (!strcasecmp(buf, mptr->remote_nodename)) {
+ send = 0;
+ }
+ }
+
+ /* Send the message */
+ if (send == 1) {
+
+ /*
+ * Force the message to appear in the correct room
+ * on the far end by setting the C field correctly
+ */
+ if (msg->cm_fields['C'] != NULL) {
+ free(msg->cm_fields['C']);
+ }
+ if (strlen(mptr->remote_roomname) > 0) {
+ msg->cm_fields['C'] = strdup(mptr->remote_roomname);
+ }
+ else {
+ msg->cm_fields['C'] = strdup(CC->room.QRname);
+ }
+
+ /* serialize it for transmission */
+ serialize_message(&sermsg, msg);
+ if (sermsg.len > 0) {
+
+ /* write it to the spool file */
+ snprintf(filename, sizeof filename,"%s/%s",
+ ctdl_netout_dir,
+ mptr->remote_nodename);
+ lprintf(CTDL_DEBUG, "Appending to %s\n", filename);
+ fp = fopen(filename, "ab");
+ if (fp != NULL) {
+ fwrite(sermsg.ser,
+ sermsg.len, 1, fp);
+ fclose(fp);
+ }
+ else {
+ lprintf(CTDL_ERR, "%s: %s\n", filename, strerror(errno));
+ }
+
+ /* free the serialized version */
+ free(sermsg.ser);
+ }
+
+ }
+ }
+ CtdlFreeMessage(msg);
+ }
+
+ /* update lastsent */
+ sc->lastsent = msgnum;
+
+ /* Delete this message if delete-after-send is set */
+ if (delete_after_send) {
+ CtdlDeleteMessages(CC->room.QRname, &msgnum, 1, "");
+ }
+
+}
+
+
+/*
+ * Batch up and send all outbound traffic from the current room
+ */
+void network_spoolout_room(char *room_to_spool) {
+ char filename[SIZ];
+ char buf[SIZ];
+ char instr[SIZ];
+ char nodename[256];
+ char roomname[ROOMNAMELEN];
+ char nexthop[256];
+ FILE *fp;
+ struct SpoolControl sc;
+ struct namelist *nptr = NULL;
+ struct maplist *mptr = NULL;
+ size_t miscsize = 0;
+ size_t linesize = 0;
+ int skipthisline = 0;
+ int i;
+
+ /*
+ * If the room doesn't exist, don't try to perform its networking tasks.
+ * Normally this should never happen, but once in a while maybe a room gets
+ * queued for networking and then deleted before it can happen.
+ */
+ if (getroom(&CC->room, room_to_spool) != 0) {
+ lprintf(CTDL_CRIT, "ERROR: cannot load <%s>\n", room_to_spool);
+ return;
+ }
+
+ memset(&sc, 0, sizeof(struct SpoolControl));
+ assoc_file_name(filename, sizeof filename, &CC->room, ctdl_netcfg_dir);
+
+ begin_critical_section(S_NETCONFIGS);
+
+ /* Only do net processing for rooms that have netconfigs */
+ fp = fopen(filename, "r");
+ if (fp == NULL) {
+ end_critical_section(S_NETCONFIGS);
+ return;
+ }
+
+ lprintf(CTDL_INFO, "Networking started for <%s>\n", CC->room.QRname);
+
+ while (fgets(buf, sizeof buf, fp) != NULL) {
+ buf[strlen(buf)-1] = 0;
+
+ extract_token(instr, buf, 0, '|', sizeof instr);
+ if (!strcasecmp(instr, "lastsent")) {
+ sc.lastsent = extract_long(buf, 1);
+ }
+ else if (!strcasecmp(instr, "listrecp")) {
+ nptr = (struct namelist *)
+ malloc(sizeof(struct namelist));
+ nptr->next = sc.listrecps;
+ extract_token(nptr->name, buf, 1, '|', sizeof nptr->name);
+ sc.listrecps = nptr;
+ }
+ else if (!strcasecmp(instr, "participate")) {
+ nptr = (struct namelist *)
+ malloc(sizeof(struct namelist));
+ nptr->next = sc.participates;
+ extract_token(nptr->name, buf, 1, '|', sizeof nptr->name);
+ sc.participates = nptr;
+ }
+ else if (!strcasecmp(instr, "digestrecp")) {
+ nptr = (struct namelist *)
+ malloc(sizeof(struct namelist));
+ nptr->next = sc.digestrecps;
+ extract_token(nptr->name, buf, 1, '|', sizeof nptr->name);
+ sc.digestrecps = nptr;
+ }
+ else if (!strcasecmp(instr, "ignet_push_share")) {
+ /* by checking each node's validity, we automatically
+ * purge nodes which do not exist from room network
+ * configurations at this time.
+ */
+ extract_token(nodename, buf, 1, '|', sizeof nodename);
+ extract_token(roomname, buf, 2, '|', sizeof roomname);
+ strcpy(nexthop, "xxx");
+ if (is_valid_node(nexthop, NULL, nodename) == 0) {
+ if (strlen(nexthop) == 0) {
+ mptr = (struct maplist *)
+ malloc(sizeof(struct maplist));
+ mptr->next = sc.ignet_push_shares;
+ strcpy(mptr->remote_nodename, nodename);
+ strcpy(mptr->remote_roomname, roomname);
+ sc.ignet_push_shares = mptr;
+ }
+ }
+ }
+ else {
+ /* Preserve 'other' lines ... *unless* they happen to
+ * be subscribe/unsubscribe pendings with expired
+ * timestamps.
+ */
+ skipthisline = 0;
+ if (!strncasecmp(buf, "subpending|", 11)) {
+ if (time(NULL) - extract_long(buf, 4) > EXP) {
+ skipthisline = 1;
+ }
+ }
+ if (!strncasecmp(buf, "unsubpending|", 13)) {
+ if (time(NULL) - extract_long(buf, 3) > EXP) {
+ skipthisline = 1;
+ }
+ }
+
+ if (skipthisline == 0) {
+ linesize = strlen(buf);
+ sc.misc = realloc(sc.misc,
+ (miscsize + linesize + 2) );
+ sprintf(&sc.misc[miscsize], "%s\n", buf);
+ miscsize = miscsize + linesize + 1;
+ }
+ }
+
+
+ }
+ fclose(fp);
+
+ /* If there are digest recipients, we have to build a digest */
+ if (sc.digestrecps != NULL) {
+ sc.digestfp = tmpfile();
+ fprintf(sc.digestfp, "Content-type: text/plain\n\n");
+ }
+
+ /* Do something useful */
+ CtdlForEachMessage(MSGS_GT, sc.lastsent, NULL, NULL, NULL,
+ network_spool_msg, &sc);
+
+ /* If we wrote a digest, deliver it and then close it */
+ snprintf(buf, sizeof buf, "room_%s@%s",
+ CC->room.QRname, config.c_fqdn);
+ for (i=0; i<strlen(buf); ++i) {
+ buf[i] = tolower(buf[i]);
+ if (isspace(buf[i])) buf[i] = '_';
+ }
+ if (sc.digestfp != NULL) {
+ fprintf(sc.digestfp, " -----------------------------------"
+ "------------------------------------"
+ "-------\n"
+ "You are subscribed to the '%s' "
+ "list.\n"
+ "To post to the list: %s\n",
+ CC->room.QRname, buf
+ );
+ network_deliver_digest(&sc); /* deliver and close */
+ }
+
+ /* Now rewrite the config file */
+ fp = fopen(filename, "w");
+ if (fp == NULL) {
+ lprintf(CTDL_CRIT, "ERROR: cannot open %s: %s\n",
+ filename, strerror(errno));
+ }
+ else {
+ fprintf(fp, "lastsent|%ld\n", sc.lastsent);
+
+ /* Write out the listrecps while freeing from memory at the
+ * same time. Am I clever or what? :)
+ */
+ while (sc.listrecps != NULL) {
+ fprintf(fp, "listrecp|%s\n", sc.listrecps->name);
+ nptr = sc.listrecps->next;
+ free(sc.listrecps);
+ sc.listrecps = nptr;
+ }
+ /* Do the same for digestrecps */
+ while (sc.digestrecps != NULL) {
+ fprintf(fp, "digestrecp|%s\n", sc.digestrecps->name);
+ nptr = sc.digestrecps->next;
+ free(sc.digestrecps);
+ sc.digestrecps = nptr;
+ }
+ /* Do the same for participates */
+ while (sc.participates != NULL) {
+ fprintf(fp, "participate|%s\n", sc.participates->name);
+ nptr = sc.participates->next;
+ free(sc.participates);
+ sc.participates = nptr;
+ }
+ while (sc.ignet_push_shares != NULL) {
+ /* by checking each node's validity, we automatically
+ * purge nodes which do not exist from room network
+ * configurations at this time.
+ */
+ if (is_valid_node(NULL, NULL, sc.ignet_push_shares->remote_nodename) == 0) {
+ }
+ fprintf(fp, "ignet_push_share|%s",
+ sc.ignet_push_shares->remote_nodename);
+ if (strlen(sc.ignet_push_shares->remote_roomname) > 0) {
+ fprintf(fp, "|%s", sc.ignet_push_shares->remote_roomname);
+ }
+ fprintf(fp, "\n");
+ mptr = sc.ignet_push_shares->next;
+ free(sc.ignet_push_shares);
+ sc.ignet_push_shares = mptr;
+ }
+ if (sc.misc != NULL) {
+ fwrite(sc.misc, strlen(sc.misc), 1, fp);
+ }
+ free(sc.misc);
+
+ fclose(fp);
+ }
+ end_critical_section(S_NETCONFIGS);
+}
+
+
+
+/*
+ * Send the *entire* contents of the current room to one specific network node,
+ * ignoring anything we know about which messages have already undergone
+ * network processing. This can be used to bring a new node into sync.
+ */
+int network_sync_to(char *target_node) {
+ struct SpoolControl sc;
+ int num_spooled = 0;
+ int found_node = 0;
+ char buf[256];
+ char sc_type[256];
+ char sc_node[256];
+ char sc_room[256];
+ char filename[256];
+ FILE *fp;
+
+ /* Grab the configuration line we're looking for */
+ assoc_file_name(filename, sizeof filename, &CC->room, ctdl_netcfg_dir);
+ begin_critical_section(S_NETCONFIGS);
+ fp = fopen(filename, "r");
+ if (fp == NULL) {
+ end_critical_section(S_NETCONFIGS);
+ return(-1);
+ }
+ while (fgets(buf, sizeof buf, fp) != NULL) {
+ buf[strlen(buf)-1] = 0;
+ extract_token(sc_type, buf, 0, '|', sizeof sc_type);
+ extract_token(sc_node, buf, 1, '|', sizeof sc_node);
+ extract_token(sc_room, buf, 2, '|', sizeof sc_room);
+ if ( (!strcasecmp(sc_type, "ignet_push_share"))
+ && (!strcasecmp(sc_node, target_node)) ) {
+ found_node = 1;
+
+ /* Concise syntax because we don't need a full linked-list */
+ memset(&sc, 0, sizeof(struct SpoolControl));
+ sc.ignet_push_shares = (struct maplist *)
+ malloc(sizeof(struct maplist));
+ sc.ignet_push_shares->next = NULL;
+ safestrncpy(sc.ignet_push_shares->remote_nodename,
+ sc_node,
+ sizeof sc.ignet_push_shares->remote_nodename);
+ safestrncpy(sc.ignet_push_shares->remote_roomname,
+ sc_room,
+ sizeof sc.ignet_push_shares->remote_roomname);
+ }
+ }
+ fclose(fp);
+ end_critical_section(S_NETCONFIGS);
+
+ if (!found_node) return(-1);
+
+ /* Send ALL messages */
+ num_spooled = CtdlForEachMessage(MSGS_ALL, 0L, NULL, NULL, NULL,
+ network_spool_msg, &sc);
+
+ /* Concise cleanup because we know there's only one node in the sc */
+ free(sc.ignet_push_shares);
+
+ lprintf(CTDL_NOTICE, "Synchronized %d messages to <%s>\n",
+ num_spooled, target_node);
+ return(num_spooled);
+}
+
+
+/*
+ * Implements the NSYN command
+ */
+void cmd_nsyn(char *argbuf) {
+ int num_spooled;
+ char target_node[256];
+
+ if (CtdlAccessCheck(ac_aide)) return;
+
+ extract_token(target_node, argbuf, 0, '|', sizeof target_node);
+ num_spooled = network_sync_to(target_node);
+ if (num_spooled >= 0) {
+ cprintf("%d Spooled %d messages.\n", CIT_OK, num_spooled);
+ }
+ else {
+ cprintf("%d No such room/node share exists.\n",
+ ERROR + ROOM_NOT_FOUND);
+ }
+}
+
+
+
+/*
+ * Batch up and send all outbound traffic from the current room
+ */
+void network_queue_room(struct ctdlroom *qrbuf, void *data) {
+ struct RoomProcList *ptr;
+
+ ptr = (struct RoomProcList *) malloc(sizeof (struct RoomProcList));
+ if (ptr == NULL) return;
+
+ safestrncpy(ptr->name, qrbuf->QRname, sizeof ptr->name);
+ begin_critical_section(S_RPLIST);
+ ptr->next = rplist;
+ rplist = ptr;
+ end_critical_section(S_RPLIST);
+}
+
+void destroy_network_queue_room(void)
+{
+ struct RoomProcList *cur, *p;
+ struct NetMap *nmcur, *nmp;
+
+ cur = rplist;
+ begin_critical_section(S_RPLIST);
+ while (cur != NULL)
+ {
+ p = cur->next;
+ free (cur);
+ cur = p;
+ }
+ rplist = NULL;
+ end_critical_section(S_RPLIST);
+
+ nmcur = the_netmap;
+ while (nmcur != NULL)
+ {
+ nmp = nmcur->next;
+ free (nmcur);
+ nmcur = nmp;
+ }
+ the_netmap = NULL;
+ if (working_ignetcfg != NULL)
+ free (working_ignetcfg);
+ working_ignetcfg = NULL;
+}
+
+
+/*
+ * Learn topology from path fields
+ */
+void network_learn_topology(char *node, char *path) {
+ char nexthop[256];
+ struct NetMap *nmptr;
+
+ strcpy(nexthop, "");
+
+ if (num_tokens(path, '!') < 3) return;
+ for (nmptr = the_netmap; nmptr != NULL; nmptr = nmptr->next) {
+ if (!strcasecmp(nmptr->nodename, node)) {
+ extract_token(nmptr->nexthop, path, 0, '!', sizeof nmptr->nexthop);
+ nmptr->lastcontact = time(NULL);
+ ++netmap_changed;
+ return;
+ }
+ }
+
+ /* If we got here then it's not in the map, so add it. */
+ nmptr = (struct NetMap *) malloc(sizeof (struct NetMap));
+ strcpy(nmptr->nodename, node);
+ nmptr->lastcontact = time(NULL);
+ extract_token(nmptr->nexthop, path, 0, '!', sizeof nmptr->nexthop);
+ nmptr->next = the_netmap;
+ the_netmap = nmptr;
+ ++netmap_changed;
+}
+
+
+
+
+/*
+ * Bounce a message back to the sender
+ */
+void network_bounce(struct CtdlMessage *msg, char *reason) {
+ char *oldpath = NULL;
+ char buf[SIZ];
+ char bouncesource[SIZ];
+ char recipient[SIZ];
+ struct recptypes *valid = NULL;
+ char force_room[ROOMNAMELEN];
+ static int serialnum = 0;
+ size_t size;
+
+ lprintf(CTDL_DEBUG, "entering network_bounce()\n");
+
+ if (msg == NULL) return;
+
+ snprintf(bouncesource, sizeof bouncesource, "%s@%s", BOUNCESOURCE, config.c_nodename);
+
+ /*
+ * Give it a fresh message ID
+ */
+ if (msg->cm_fields['I'] != NULL) {
+ free(msg->cm_fields['I']);
+ }
+ snprintf(buf, sizeof buf, "%ld.%04lx.%04x@%s",
+ (long)time(NULL), (long)getpid(), ++serialnum, config.c_fqdn);
+ msg->cm_fields['I'] = strdup(buf);
+
+ /*
+ * FIXME ... right now we're just sending a bounce; we really want to
+ * include the text of the bounced message.
+ */
+ if (msg->cm_fields['M'] != NULL) {
+ free(msg->cm_fields['M']);
+ }
+ msg->cm_fields['M'] = strdup(reason);
+ msg->cm_format_type = 0;
+
+ /*
+ * Turn the message around
+ */
+ if (msg->cm_fields['R'] == NULL) {
+ free(msg->cm_fields['R']);
+ }
+
+ if (msg->cm_fields['D'] == NULL) {
+ free(msg->cm_fields['D']);
+ }
+
+ snprintf(recipient, sizeof recipient, "%s@%s",
+ msg->cm_fields['A'], msg->cm_fields['N']);
+
+ if (msg->cm_fields['A'] == NULL) {
+ free(msg->cm_fields['A']);
+ }
+
+ if (msg->cm_fields['N'] == NULL) {
+ free(msg->cm_fields['N']);
+ }
+
+ if (msg->cm_fields['U'] == NULL) {
+ free(msg->cm_fields['U']);
+ }
+
+ msg->cm_fields['A'] = strdup(BOUNCESOURCE);
+ msg->cm_fields['N'] = strdup(config.c_nodename);
+ msg->cm_fields['U'] = strdup("Delivery Status Notification (Failure)");
+
+ /* prepend our node to the path */
+ if (msg->cm_fields['P'] != NULL) {
+ oldpath = msg->cm_fields['P'];
+ msg->cm_fields['P'] = NULL;
+ }
+ else {
+ oldpath = strdup("unknown_user");
+ }
+ size = strlen(oldpath) + SIZ;
+ msg->cm_fields['P'] = malloc(size);
+ snprintf(msg->cm_fields['P'], size, "%s!%s", config.c_nodename, oldpath);
+ free(oldpath);
+
+ /* Now submit the message */
+ valid = validate_recipients(recipient);
+ if (valid != NULL) if (valid->num_error != 0) {
+ free_recipients(valid);
+ valid = NULL;
+ }
+ if ( (valid == NULL) || (!strcasecmp(recipient, bouncesource)) ) {
+ strcpy(force_room, config.c_aideroom);
+ }
+ else {
+ strcpy(force_room, "");
+ }
+ if ( (valid == NULL) && (strlen(force_room) == 0) ) {
+ strcpy(force_room, config.c_aideroom);
+ }
+ CtdlSubmitMsg(msg, valid, force_room);
+
+ /* Clean up */
+ if (valid != NULL) free_recipients(valid);
+ CtdlFreeMessage(msg);
+ lprintf(CTDL_DEBUG, "leaving network_bounce()\n");
+}
+
+
+
+
+/*
+ * Process a buffer containing a single message from a single file
+ * from the inbound queue
+ */
+void network_process_buffer(char *buffer, long size) {
+ struct CtdlMessage *msg = NULL;
+ long pos;
+ int field;
+ struct recptypes *recp = NULL;
+ char target_room[ROOMNAMELEN];
+ struct ser_ret sermsg;
+ char *oldpath = NULL;
+ char filename[SIZ];
+ FILE *fp;
+ char nexthop[SIZ];
+ unsigned char firstbyte;
+ unsigned char lastbyte;
+
+ /* Validate just a little bit. First byte should be FF and
+ * last byte should be 00.
+ */
+ memcpy(&firstbyte, &buffer[0], 1);
+ memcpy(&lastbyte, &buffer[size-1], 1);
+ if ( (firstbyte != 255) || (lastbyte != 0) ) {
+ lprintf(CTDL_ERR, "Corrupt message! Ignoring.\n");
+ return;
+ }
+
+ /* Set default target room to trash */
+ strcpy(target_room, TWITROOM);
+
+ /* Load the message into memory */
+ msg = (struct CtdlMessage *) malloc(sizeof(struct CtdlMessage));
+ memset(msg, 0, sizeof(struct CtdlMessage));
+ msg->cm_magic = CTDLMESSAGE_MAGIC;
+ msg->cm_anon_type = buffer[1];
+ msg->cm_format_type = buffer[2];
+
+ for (pos = 3; pos < size; ++pos) {
+ field = buffer[pos];
+ msg->cm_fields[field] = strdup(&buffer[pos+1]);
+ pos = pos + strlen(&buffer[(int)pos]);
+ }
+
+ /* Check for message routing */
+ if (msg->cm_fields['D'] != NULL) {
+ if (strcasecmp(msg->cm_fields['D'], config.c_nodename)) {
+
+ /* route the message */
+ strcpy(nexthop, "");
+ if (is_valid_node(nexthop, NULL,
+ msg->cm_fields['D']) == 0) {
+
+ /* prepend our node to the path */
+ if (msg->cm_fields['P'] != NULL) {
+ oldpath = msg->cm_fields['P'];
+ msg->cm_fields['P'] = NULL;
+ }
+ else {
+ oldpath = strdup("unknown_user");
+ }
+ size = strlen(oldpath) + SIZ;
+ msg->cm_fields['P'] = malloc(size);
+ snprintf(msg->cm_fields['P'], size, "%s!%s",
+ config.c_nodename, oldpath);
+ free(oldpath);
+
+ /* serialize the message */
+ serialize_message(&sermsg, msg);
+
+ /* now send it */
+ if (strlen(nexthop) == 0) {
+ strcpy(nexthop, msg->cm_fields['D']);
+ }
+ snprintf(filename,
+ sizeof filename,
+ "%s/%s",
+ ctdl_netout_dir,
+ nexthop);
+ lprintf(CTDL_DEBUG, "Appending to %s\n", filename);
+ fp = fopen(filename, "ab");
+ if (fp != NULL) {
+ fwrite(sermsg.ser,
+ sermsg.len, 1, fp);
+ fclose(fp);
+ }
+ else {
+ lprintf(CTDL_ERR, "%s: %s\n", filename, strerror(errno));
+ }
+ free(sermsg.ser);
+ CtdlFreeMessage(msg);
+ return;
+ }
+
+ else { /* invalid destination node name */
+
+ network_bounce(msg,
+"A message you sent could not be delivered due to an invalid destination node"
+" name. Please check the address and try sending the message again.\n");
+ msg = NULL;
+ return;
+
+ }
+ }
+ }
+
+ /*
+ * Check to see if we already have a copy of this message, and
+ * abort its processing if so. (We used to post a warning to Aide>
+ * every time this happened, but the network is now so densely
+ * connected that it's inevitable.)
+ */
+ if (network_usetable(msg) != 0) {
+ return;
+ }
+
+ /* Learn network topology from the path */
+ if ((msg->cm_fields['N'] != NULL) && (msg->cm_fields['P'] != NULL)) {
+ network_learn_topology(msg->cm_fields['N'],
+ msg->cm_fields['P']);
+ }
+
+ /* Is the sending node giving us a very persuasive suggestion about
+ * which room this message should be saved in? If so, go with that.
+ */
+ if (msg->cm_fields['C'] != NULL) {
+ safestrncpy(target_room,
+ msg->cm_fields['C'],
+ sizeof target_room);
+ }
+
+ /* Otherwise, does it have a recipient? If so, validate it... */
+ else if (msg->cm_fields['R'] != NULL) {
+ recp = validate_recipients(msg->cm_fields['R']);
+ if (recp != NULL) if (recp->num_error != 0) {
+ network_bounce(msg,
+ "A message you sent could not be delivered due to an invalid address.\n"
+ "Please check the address and try sending the message again.\n");
+ msg = NULL;
+ free_recipients(recp);
+ return;
+ }
+ strcpy(target_room, ""); /* no target room if mail */
+ }
+
+ /* Our last shot at finding a home for this message is to see if
+ * it has the O field (Originating room) set.
+ */
+ else if (msg->cm_fields['O'] != NULL) {
+ safestrncpy(target_room,
+ msg->cm_fields['O'],
+ sizeof target_room);
+ }
+
+ /* Strip out fields that are only relevant during transit */
+ if (msg->cm_fields['D'] != NULL) {
+ free(msg->cm_fields['D']);
+ msg->cm_fields['D'] = NULL;
+ }
+ if (msg->cm_fields['C'] != NULL) {
+ free(msg->cm_fields['C']);
+ msg->cm_fields['C'] = NULL;
+ }
+
+ /* save the message into a room */
+ if (PerformNetprocHooks(msg, target_room) == 0) {
+ msg->cm_flags = CM_SKIP_HOOKS;
+ CtdlSubmitMsg(msg, recp, target_room);
+ }
+ CtdlFreeMessage(msg);
+ free_recipients(recp);
+}
+
+
+/*
+ * Process a single message from a single file from the inbound queue
+ */
+void network_process_message(FILE *fp, long msgstart, long msgend) {
+ long hold_pos;
+ long size;
+ char *buffer;
+
+ hold_pos = ftell(fp);
+ size = msgend - msgstart + 1;
+ buffer = malloc(size);
+ if (buffer != NULL) {
+ fseek(fp, msgstart, SEEK_SET);
+ fread(buffer, size, 1, fp);
+ network_process_buffer(buffer, size);
+ free(buffer);
+ }
+
+ fseek(fp, hold_pos, SEEK_SET);
+}
+
+
+/*
+ * Process a single file from the inbound queue
+ */
+void network_process_file(char *filename) {
+ FILE *fp;
+ long msgstart = (-1L);
+ long msgend = (-1L);
+ long msgcur = 0L;
+ int ch;
+
+
+ fp = fopen(filename, "rb");
+ if (fp == NULL) {
+ lprintf(CTDL_CRIT, "Error opening %s: %s\n",
+ filename, strerror(errno));
+ return;
+ }
+
+ lprintf(CTDL_INFO, "network: processing <%s>\n", filename);
+
+ /* Look for messages in the data stream and break them out */
+ while (ch = getc(fp), ch >= 0) {
+
+ if (ch == 255) {
+ if (msgstart >= 0L) {
+ msgend = msgcur - 1;
+ network_process_message(fp, msgstart, msgend);
+ }
+ msgstart = msgcur;
+ }
+
+ ++msgcur;
+ }
+
+ msgend = msgcur - 1;
+ if (msgstart >= 0L) {
+ network_process_message(fp, msgstart, msgend);
+ }
+
+ fclose(fp);
+ unlink(filename);
+}
+
+
+/*
+ * Process anything in the inbound queue
+ */
+void network_do_spoolin(void) {
+ DIR *dp;
+ struct dirent *d;
+ struct stat statbuf;
+ char filename[256];
+ static time_t last_spoolin_mtime = 0L;
+
+ /*
+ * Check the spoolin directory's modification time. If it hasn't
+ * been touched, we don't need to scan it.
+ */
+ if (stat(ctdl_netin_dir, &statbuf)) return;
+ if (statbuf.st_mtime == last_spoolin_mtime) {
+ lprintf(CTDL_DEBUG, "network: nothing in inbound queue\n");
+ return;
+ }
+ last_spoolin_mtime = statbuf.st_mtime;
+ lprintf(CTDL_DEBUG, "network: processing inbound queue\n");
+
+ /*
+ * Ok, there's something interesting in there, so scan it.
+ */
+ dp = opendir(ctdl_netin_dir);
+ if (dp == NULL) return;
+
+ while (d = readdir(dp), d != NULL) {
+ if ((strcmp(d->d_name, ".")) && (strcmp(d->d_name, ".."))) {
+ snprintf(filename,
+ sizeof filename,
+ "%s/%s",
+ ctdl_netin_dir,
+ d->d_name);
+ network_process_file(filename);
+ }
+ }
+
+ closedir(dp);
+}
+
+/*
+ * Delete any files in the outbound queue that were intended
+ * to be sent to nodes which no longer exist.
+ */
+void network_purge_spoolout(void) {
+ DIR *dp;
+ struct dirent *d;
+ char filename[256];
+ char nexthop[256];
+ int i;
+
+ dp = opendir(ctdl_netout_dir);
+ if (dp == NULL) return;
+
+ while (d = readdir(dp), d != NULL) {
+ if (!strcmp(d->d_name, ".") || !strcmp(d->d_name, ".."))
+ continue;
+ snprintf(filename,
+ sizeof filename,
+ "%s/%s",
+ ctdl_netout_dir,
+ d->d_name);
+
+ strcpy(nexthop, "");
+ i = is_valid_node(nexthop, NULL, d->d_name);
+
+ if ( (i != 0) || (strlen(nexthop) > 0) ) {
+ unlink(filename);
+ }
+ }
+
+
+ closedir(dp);
+}
+
+
+/*
+ * receive network spool from the remote system
+ */
+void receive_spool(int sock, char *remote_nodename) {
+ long download_len;
+ long bytes_received;
+ char buf[SIZ];
+ static char pbuf[IGNET_PACKET_SIZE];
+ char tempfilename[PATH_MAX];
+ long plen;
+ FILE *fp;
+
+ CtdlMakeTempFileName(tempfilename, sizeof tempfilename);
+ if (sock_puts(sock, "NDOP") < 0) return;
+ if (sock_gets(sock, buf) < 0) return;
+ lprintf(CTDL_DEBUG, "<%s\n", buf);
+ if (buf[0] != '2') {
+ return;
+ }
+ download_len = extract_long(&buf[4], 0);
+
+ bytes_received = 0L;
+ fp = fopen(tempfilename, "w");
+ if (fp == NULL) {
+ lprintf(CTDL_CRIT, "cannot open download file locally: %s\n",
+ strerror(errno));
+ return;
+ }
+
+ while (bytes_received < download_len) {
+ snprintf(buf, sizeof buf, "READ %ld|%ld",
+ bytes_received,
+ ((download_len - bytes_received > IGNET_PACKET_SIZE)
+ ? IGNET_PACKET_SIZE : (download_len - bytes_received)));
+ if (sock_puts(sock, buf) < 0) {
+ fclose(fp);
+ unlink(tempfilename);
+ return;
+ }
+ if (sock_gets(sock, buf) < 0) {
+ fclose(fp);
+ unlink(tempfilename);
+ return;
+ }
+ if (buf[0] == '6') {
+ plen = extract_long(&buf[4], 0);
+ if (sock_read(sock, pbuf, plen) < 0) {
+ fclose(fp);
+ unlink(tempfilename);
+ return;
+ }
+ fwrite((char *) pbuf, plen, 1, fp);
+ bytes_received = bytes_received + plen;
+ }
+ }
+
+ fclose(fp);
+ if (sock_puts(sock, "CLOS") < 0) {
+ unlink(tempfilename);
+ return;
+ }
+ if (sock_gets(sock, buf) < 0) {
+ unlink(tempfilename);
+ return;
+ }
+ if (download_len > 0)
+ lprintf(CTDL_NOTICE, "Received %ld octets from <%s>",
+ download_len, remote_nodename);
+ lprintf(CTDL_DEBUG, "%s", buf);
+ /* TODO: make move inline. forking is verry expensive. */
+ snprintf(buf,
+ sizeof buf,
+ "mv %s %s/%s.%ld",
+ tempfilename,
+ ctdl_netin_dir,
+ remote_nodename,
+ (long) getpid());
+ system(buf);
+}
+
+
+
+/*
+ * transmit network spool to the remote system
+ */
+void transmit_spool(int sock, char *remote_nodename)
+{
+ char buf[SIZ];
+ char pbuf[4096];
+ long plen;
+ long bytes_to_write, thisblock, bytes_written;
+ int fd;
+ char sfname[128];
+
+ if (sock_puts(sock, "NUOP") < 0) return;
+ if (sock_gets(sock, buf) < 0) return;
+ lprintf(CTDL_DEBUG, "<%s\n", buf);
+ if (buf[0] != '2') {
+ return;
+ }
+
+ snprintf(sfname, sizeof sfname,
+ "%s/%s",
+ ctdl_netout_dir,
+ remote_nodename);
+ fd = open(sfname, O_RDONLY);
+ if (fd < 0) {
+ if (errno != ENOENT) {
+ lprintf(CTDL_CRIT, "cannot open upload file locally: %s\n",
+ strerror(errno));
+ }
+ return;
+ }
+ bytes_written = 0;
+ while (plen = (long) read(fd, pbuf, IGNET_PACKET_SIZE), plen > 0L) {
+ bytes_to_write = plen;
+ while (bytes_to_write > 0L) {
+ snprintf(buf, sizeof buf, "WRIT %ld", bytes_to_write);
+ if (sock_puts(sock, buf) < 0) {
+ close(fd);
+ return;
+ }
+ if (sock_gets(sock, buf) < 0) {
+ close(fd);
+ return;
+ }
+ thisblock = atol(&buf[4]);
+ if (buf[0] == '7') {
+ if (sock_write(sock, pbuf,
+ (int) thisblock) < 0) {
+ close(fd);
+ return;
+ }
+ bytes_to_write -= thisblock;
+ bytes_written += thisblock;
+ } else {
+ goto ABORTUPL;
+ }
+ }
+ }
+
+ABORTUPL:
+ close(fd);
+ if (sock_puts(sock, "UCLS 1") < 0) return;
+ if (sock_gets(sock, buf) < 0) return;
+ lprintf(CTDL_NOTICE, "Sent %ld octets to <%s>\n",
+ bytes_written, remote_nodename);
+ lprintf(CTDL_DEBUG, "<%s\n", buf);
+ if (buf[0] == '2') {
+ lprintf(CTDL_DEBUG, "Removing <%s>\n", sfname);
+ unlink(sfname);
+ }
+}
+
+
+
+/*
+ * Poll one Citadel node (called by network_poll_other_citadel_nodes() below)
+ */
+void network_poll_node(char *node, char *secret, char *host, char *port) {
+ int sock;
+ char buf[SIZ];
+
+ if (network_talking_to(node, NTT_CHECK)) return;
+ network_talking_to(node, NTT_ADD);
+ lprintf(CTDL_NOTICE, "Connecting to <%s> at %s:%s\n", node, host, port);
+
+ sock = sock_connect(host, port, "tcp");
+ if (sock < 0) {
+ lprintf(CTDL_ERR, "Could not connect: %s\n", strerror(errno));
+ network_talking_to(node, NTT_REMOVE);
+ return;
+ }
+
+ lprintf(CTDL_DEBUG, "Connected!\n");
+
+ /* Read the server greeting */
+ if (sock_gets(sock, buf) < 0) goto bail;
+ lprintf(CTDL_DEBUG, ">%s\n", buf);
+
+ /* Identify ourselves */
+ snprintf(buf, sizeof buf, "NETP %s|%s", config.c_nodename, secret);
+ lprintf(CTDL_DEBUG, "<%s\n", buf);
+ if (sock_puts(sock, buf) <0) goto bail;
+ if (sock_gets(sock, buf) < 0) goto bail;
+ lprintf(CTDL_DEBUG, ">%s\n", buf);
+ if (buf[0] != '2') goto bail;
+
+ /* At this point we are authenticated. */
+ receive_spool(sock, node);
+ transmit_spool(sock, node);
+
+ sock_puts(sock, "QUIT");
+bail: sock_close(sock);
+ network_talking_to(node, NTT_REMOVE);
+}
+
+
+
+/*
+ * Poll other Citadel nodes and transfer inbound/outbound network data.
+ * Set "full" to nonzero to force a poll of every node, or to zero to poll
+ * only nodes to which we have data to send.
+ */
+void network_poll_other_citadel_nodes(int full_poll) {
+ int i;
+ char linebuf[256];
+ char node[SIZ];
+ char host[256];
+ char port[256];
+ char secret[256];
+ int poll = 0;
+ char spoolfile[256];
+
+ if (working_ignetcfg == NULL) {
+ lprintf(CTDL_DEBUG, "No nodes defined - not polling\n");
+ return;
+ }
+
+ /* Use the string tokenizer to grab one line at a time */
+ for (i=0; i<num_tokens(working_ignetcfg, '\n'); ++i) {
+ extract_token(linebuf, working_ignetcfg, i, '\n', sizeof linebuf);
+ extract_token(node, linebuf, 0, '|', sizeof node);
+ extract_token(secret, linebuf, 1, '|', sizeof secret);
+ extract_token(host, linebuf, 2, '|', sizeof host);
+ extract_token(port, linebuf, 3, '|', sizeof port);
+ if ( (strlen(node) > 0) && (strlen(secret) > 0)
+ && (strlen(host) > 0) && strlen(port) > 0) {
+ poll = full_poll;
+ if (poll == 0) {
+ snprintf(spoolfile,
+ sizeof spoolfile,
+ "%s/%s",
+ ctdl_netout_dir,
+ node);
+ if (access(spoolfile, R_OK) == 0) {
+ poll = 1;
+ }
+ }
+ if (poll) {
+ network_poll_node(node, secret, host, port);
+ }
+ }
+ }
+
+}
+
+
+
+
+/*
+ * It's ok if these directories already exist. Just fail silently.
+ */
+void create_spool_dirs(void) {
+ mkdir(ctdl_spool_dir, 0700);
+ chown(ctdl_spool_dir, CTDLUID, (-1));
+ mkdir(ctdl_netin_dir, 0700);
+ chown(ctdl_netin_dir, CTDLUID, (-1));
+ mkdir(ctdl_netout_dir, 0700);
+ chown(ctdl_netout_dir, CTDLUID, (-1));
+}
+
+
+
+
+
+/*
+ * network_do_queue()
+ *
+ * Run through the rooms doing various types of network stuff.
+ */
+void network_do_queue(void) {
+ static time_t last_run = 0L;
+ struct RoomProcList *ptr;
+ int full_processing = 1;
+
+ /*
+ * Run the full set of processing tasks no more frequently
+ * than once every n seconds
+ */
+ if ( (time(NULL) - last_run) < config.c_net_freq ) {
+ full_processing = 0;
+ }
+
+ /*
+ * This is a simple concurrency check to make sure only one queue run
+ * is done at a time. We could do this with a mutex, but since we
+ * don't really require extremely fine granularity here, we'll do it
+ * with a static variable instead.
+ */
+ if (doing_queue) return;
+ doing_queue = 1;
+
+ /* Load the IGnet Configuration into memory */
+ load_working_ignetcfg();
+
+ /*
+ * Poll other Citadel nodes. Maybe. If "full_processing" is set
+ * then we poll everyone. Otherwise we only poll nodes we have stuff
+ * to send to.
+ */
+ network_poll_other_citadel_nodes(full_processing);
+
+ /*
+ * Load the network map and filter list into memory.
+ */
+ read_network_map();
+ filterlist = load_filter_list();
+
+ /*
+ * Go ahead and run the queue
+ */
+ if (full_processing) {
+ lprintf(CTDL_DEBUG, "network: loading outbound queue\n");
+ ForEachRoom(network_queue_room, NULL);
+ }
+
+ if (rplist != NULL) {
+ lprintf(CTDL_DEBUG, "network: running outbound queue\n");
+ while (rplist != NULL) {
+ char spoolroomname[ROOMNAMELEN];
+ safestrncpy(spoolroomname, rplist->name, sizeof spoolroomname);
+ begin_critical_section(S_RPLIST);
+
+ /* pop this record off the list */
+ ptr = rplist;
+ rplist = rplist->next;
+ free(ptr);
+
+ /* invalidate any duplicate entries to prevent double processing */
+ for (ptr=rplist; ptr!=NULL; ptr=ptr->next) {
+ if (!strcasecmp(ptr->name, spoolroomname)) {
+ ptr->name[0] = 0;
+ }
+ }
+
+ end_critical_section(S_RPLIST);
+ if (spoolroomname[0] != 0) {
+ network_spoolout_room(spoolroomname);
+ }
+ }
+ }
+
+ /* If there is anything in the inbound queue, process it */
+ network_do_spoolin();
+
+ /* Save the network map back to disk */
+ write_network_map();
+
+ /* Free the filter list in memory */
+ free_filter_list(filterlist);
+ filterlist = NULL;
+
+ network_purge_spoolout();
+
+ lprintf(CTDL_DEBUG, "network: queue run completed\n");
+
+ if (full_processing) {
+ last_run = time(NULL);
+ }
+
+ doing_queue = 0;
+}
+
+
+/*
+ * cmd_netp() - authenticate to the server as another Citadel node polling
+ * for network traffic
+ */
+void cmd_netp(char *cmdbuf)
+{
+ char node[256];
+ char pass[256];
+ int v;
+
+ char secret[256];
+ char nexthop[256];
+
+ /* Authenticate */
+ extract_token(node, cmdbuf, 0, '|', sizeof node);
+ extract_token(pass, cmdbuf, 1, '|', sizeof pass);
+
+ if (doing_queue) {
+ lprintf(CTDL_WARNING, "Network node <%s> refused - spooling", node);
+ cprintf("%d spooling - try again in a few minutes\n",
+ ERROR + RESOURCE_BUSY);
+ return;
+ }
+
+ /* load the IGnet Configuration to check node validity */
+ load_working_ignetcfg();
+ v = is_valid_node(nexthop, secret, node);
+
+ if (v != 0) {
+ lprintf(CTDL_WARNING, "Unknown node <%s>\n", node);
+ cprintf("%d authentication failed\n",
+ ERROR + PASSWORD_REQUIRED);
+ return;
+ }
+
+ if (strcasecmp(pass, secret)) {
+ lprintf(CTDL_WARNING, "Bad password for network node <%s>", node);
+ cprintf("%d authentication failed\n", ERROR + PASSWORD_REQUIRED);
+ return;
+ }
+
+ if (network_talking_to(node, NTT_CHECK)) {
+ lprintf(CTDL_WARNING, "Duplicate session for network node <%s>", node);
+ cprintf("%d Already talking to %s right now\n", ERROR + RESOURCE_BUSY, node);
+ return;
+ }
+
+ safestrncpy(CC->net_node, node, sizeof CC->net_node);
+ network_talking_to(node, NTT_ADD);
+ lprintf(CTDL_NOTICE, "Network node <%s> logged in\n", CC->net_node);
+ cprintf("%d authenticated as network node '%s'\n", CIT_OK,
+ CC->net_node);
+}
+
+int network_room_handler (struct ctdlroom *room)
+{
+ network_queue_room(room, NULL);
+ return 0;
+}
+
+/*
+ * Module entry point
+ */
+CTDL_MODULE_INIT(network)
+{
+ create_spool_dirs();
+ CtdlRegisterProtoHook(cmd_gnet, "GNET", "Get network config");
+ CtdlRegisterProtoHook(cmd_snet, "SNET", "Set network config");
+ CtdlRegisterProtoHook(cmd_netp, "NETP", "Identify as network poller");
+ CtdlRegisterProtoHook(cmd_nsyn, "NSYN", "Synchronize room to node");
+ CtdlRegisterSessionHook(network_do_queue, EVT_TIMER);
+ CtdlRegisterRoomHook(network_room_handler);
+
+ /* return our Subversion id for the Log */
+ return "$Id$";
+}
--- /dev/null
+/*
+ * $Id$
+ *
+ * Automaticalyl copies the contents of a "New User Greetings" room to the
+ * inbox of any new user upon account creation.
+ *
+ */
+
+/*
+ * Name of the New User Greetings room.
+ */
+#define NEWUSERGREETINGS "New User Greetings"
+
+
+#include "sysdep.h"
+#include <stdlib.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <pwd.h>
+#include <errno.h>
+#include <sys/types.h>
+
+#if TIME_WITH_SYS_TIME
+# include <sys/time.h>
+# include <time.h>
+#else
+# if HAVE_SYS_TIME_H
+# include <sys/time.h>
+# else
+# include <time.h>
+# endif
+#endif
+
+#include <sys/wait.h>
+#include <string.h>
+#include <limits.h>
+#include "citadel.h"
+#include "server.h"
+#include "citserver.h"
+#include "support.h"
+#include "config.h"
+#include "room_ops.h"
+#include "user_ops.h"
+#include "policy.h"
+#include "database.h"
+#include "msgbase.h"
+
+
+#include "ctdl_module.h"
+
+
+
+extern struct CitContext *ContextList;
+
+
+/*
+ * Copy the contents of the New User Greetings> room to the user's Mail> room.
+ */
+void CopyNewUserGreetings(void) {
+ struct cdbdata *cdbfr;
+ long *msglist = NULL;
+ int num_msgs = 0;
+ char mailboxname[ROOMNAMELEN];
+
+
+ /* Only do this for new users. */
+ if (CC->user.timescalled != 1) return;
+
+ /* This user's mailbox. */
+ MailboxName(mailboxname, sizeof mailboxname, &CC->user, MAILROOM);
+
+ /* Go to the source room ... bail out silently if it's not there,
+ * or if it's not private.
+ */
+ if (getroom(&CC->room, NEWUSERGREETINGS) != 0) return;
+ if (! CC->room.QRflags & QR_PRIVATE ) return;
+
+ cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long));
+
+ if (cdbfr != NULL) {
+ msglist = malloc(cdbfr->len);
+ memcpy(msglist, cdbfr->ptr, cdbfr->len);
+ num_msgs = cdbfr->len / sizeof(long);
+ cdb_free(cdbfr);
+ }
+
+ if (num_msgs > 0) {
+ CtdlCopyMsgsToRoom(msglist, num_msgs, mailboxname);
+ }
+
+ /* Now free the memory we used, and go away. */
+ if (msglist != NULL) free(msglist);
+}
+
+
+CTDL_MODULE_INIT(newuser)
+{
+ CtdlRegisterSessionHook(CopyNewUserGreetings, EVT_LOGIN);
+
+ /* return our Subversion id for the Log */
+ return "$Id$";
+}
--- /dev/null
+/*
+ * $Id$
+ *
+ * Handles functions related to yellow sticky notes.
+ *
+ */
+
+#include "sysdep.h"
+#include <stdlib.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <pwd.h>
+#include <errno.h>
+#include <sys/types.h>
+
+#if TIME_WITH_SYS_TIME
+# include <sys/time.h>
+# include <time.h>
+#else
+# if HAVE_SYS_TIME_H
+# include <sys/time.h>
+# else
+# include <time.h>
+# endif
+#endif
+
+#include <sys/wait.h>
+#include <string.h>
+#include <limits.h>
+#include "citadel.h"
+#include "server.h"
+#include "citserver.h"
+#include "support.h"
+#include "config.h"
+#include "room_ops.h"
+#include "user_ops.h"
+#include "policy.h"
+#include "database.h"
+#include "msgbase.h"
+#include "tools.h"
+
+#include "ctdl_module.h"
+
+
+
+/*
+ * If we are in a "notes" view room, and the client has sent an RFC822
+ * message containing an X-KOrg-Note-Id: field (Aethera does this, as
+ * do some Kolab clients) then set both the Subject and the Exclusive ID
+ * of the message to that. It's going to be a UUID so we want to replace
+ * any existing message containing that UUID.
+ */
+int serv_notes_beforesave(struct CtdlMessage *msg)
+{
+ char *p;
+ int a, i;
+ char uuid[SIZ];
+
+ /* First determine if this room has the "notes" view set */
+
+ if (CC->room.QRdefaultview != VIEW_NOTES) {
+ return(0); /* not notes; do nothing */
+ }
+
+ /* It must be an RFC822 message! */
+ if (msg->cm_format_type != 4) {
+ return(0); /* You tried to save a non-RFC822 message! */
+ }
+
+ /* Find the X-KOrg-Note-Id: header */
+ strcpy(uuid, "");
+ p = msg->cm_fields['M'];
+ a = strlen(p);
+ while (--a > 0) {
+ if (!strncasecmp(p, "X-KOrg-Note-Id: ", 16)) { /* Found it */
+ safestrncpy(uuid, p + 16, sizeof(uuid));
+ for (i = 0; i<strlen(uuid); ++i) {
+ if ( (uuid[i] == '\r') || (uuid[i] == '\n') ) {
+ uuid[i] = 0;
+ }
+ }
+
+ lprintf(9, "UUID of note is: %s\n", uuid);
+ if (strlen(uuid) > 0) {
+
+ if (msg->cm_fields['E'] != NULL) {
+ free(msg->cm_fields['E']);
+ }
+ msg->cm_fields['E'] = strdup(uuid);
+
+ if (msg->cm_fields['U'] != NULL) {
+ free(msg->cm_fields['U']);
+ }
+ msg->cm_fields['U'] = strdup(uuid);
+ }
+ }
+ p++;
+ }
+
+ return(0);
+}
+
+
+CTDL_MODULE_INIT(notes)
+{
+ CtdlRegisterMessageHook(serv_notes_beforesave, EVT_BEFORESAVE);
+
+ /* return our Subversion id for the Log */
+ return "$Id$";
+}
--- /dev/null
+/*
+ * cmd_pas2 - MD5 APOP style auth keyed off of the hash of the password
+ * plus a nonce displayed at the login banner.
+ */
+
+#include <stdlib.h>
+#include <unistd.h>
+#include <stdio.h>
+
+#if TIME_WITH_SYS_TIME
+# include <sys/time.h>
+# include <time.h>
+#else
+# if HAVE_SYS_TIME_H
+# include <sys/time.h>
+# else
+# include <time.h>
+# endif
+#endif
+
+#include <ctype.h>
+#include <string.h>
+#include <errno.h>
+#include "citadel.h"
+#include "server.h"
+#include "citserver.h"
+#include "support.h"
+#include "user_ops.h"
+#include "md5.h"
+#include "tools.h"
+
+
+#include "ctdl_module.h"
+
+
+void cmd_pas2(char *argbuf)
+{
+ char pw[256];
+ char hexstring[MD5_HEXSTRING_SIZE];
+
+
+ if (!strcmp(CC->curr_user, NLI))
+ {
+ cprintf("%d You must enter a user with the USER command first.\n", ERROR + USERNAME_REQUIRED);
+ return;
+ }
+
+ if (CC->logged_in)
+ {
+ cprintf("%d Already logged in.\n", ERROR + ALREADY_LOGGED_IN);
+ return;
+ }
+
+ extract_token(pw, argbuf, 0, '|', sizeof pw);
+
+ if (getuser(&CC->user, CC->curr_user))
+ {
+ cprintf("%d Unable to find user record for %s.\n", ERROR + NO_SUCH_USER, CC->curr_user);
+ return;
+ }
+
+ strproc(pw);
+ strproc(CC->user.password);
+
+ if (strlen(pw) != (MD5_HEXSTRING_SIZE-1))
+ {
+ cprintf("%d Auth string of length %ld is the wrong length (should be %d).\n", ERROR + ILLEGAL_VALUE, (long)strlen(pw), MD5_HEXSTRING_SIZE-1);
+ return;
+ }
+
+ make_apop_string(CC->user.password, CC->cs_nonce, hexstring, sizeof hexstring);
+
+ if (!strcmp(hexstring, pw))
+ {
+ do_login();
+ return;
+ }
+ else
+ {
+ cprintf("%d Wrong password.\n", ERROR + PASSWORD_REQUIRED);
+ return;
+ }
+}
+
+
+
+
+
+CTDL_MODULE_INIT(pas2)
+{
+ CtdlRegisterProtoHook(cmd_pas2, "PAS2", "APOP-based login");
+
+ /* return our Subversion id for the Log */
+ return "$Id$";
+}
--- /dev/null
+/*
+ * $Id$
+ *
+ * POP3 service for the Citadel system
+ * Copyright (C) 1998-2001 by Art Cancro and others.
+ * This code is released under the terms of the GNU General Public License.
+ *
+ * Current status of standards conformance:
+ *
+ * -> All required POP3 commands described in RFC1939 are implemented.
+ * -> All optional POP3 commands described in RFC1939 are also implemented.
+ * -> The deprecated "LAST" command is included in this implementation, because
+ * there exist mail clients which insist on using it (such as Bynari
+ * TradeMail, and certain versions of Eudora).
+ * -> Capability detection via the method described in RFC2449 is implemented.
+ *
+ */
+
+#include "sysdep.h"
+#include <stdlib.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <pwd.h>
+#include <errno.h>
+#include <sys/types.h>
+
+#if TIME_WITH_SYS_TIME
+# include <sys/time.h>
+# include <time.h>
+#else
+# if HAVE_SYS_TIME_H
+# include <sys/time.h>
+# else
+# include <time.h>
+# endif
+#endif
+
+#include <sys/wait.h>
+#include <string.h>
+#include <limits.h>
+#include <ctype.h>
+#include "citadel.h"
+#include "server.h"
+#include "citserver.h"
+#include "support.h"
+#include "config.h"
+#include "room_ops.h"
+#include "user_ops.h"
+#include "policy.h"
+#include "database.h"
+#include "msgbase.h"
+#include "tools.h"
+#include "internet_addressing.h"
+#include "serv_pop3.h"
+#include "md5.h"
+
+#ifdef HAVE_OPENSSL
+#include "serv_crypto.h"
+#endif
+
+
+#include "ctdl_module.h"
+
+
+
+/*
+ * This cleanup function blows away the temporary memory and files used by
+ * the POP3 server.
+ */
+void pop3_cleanup_function(void) {
+
+ /* Don't do this stuff if this is not a POP3 session! */
+ if (CC->h_command_function != pop3_command_loop) return;
+
+ lprintf(CTDL_DEBUG, "Performing POP3 cleanup hook\n");
+ if (POP3->msgs != NULL) free(POP3->msgs);
+
+ free(POP3);
+}
+
+
+
+/*
+ * Here's where our POP3 session begins its happy day.
+ */
+void pop3_greeting(void) {
+ strcpy(CC->cs_clientname, "POP3 session");
+ CC->internal_pgm = 1;
+ POP3 = malloc(sizeof(struct citpop3));
+ memset(POP3, 0, sizeof(struct citpop3));
+
+ cprintf("+OK Citadel POP3 server %s\r\n",
+ CC->cs_nonce);
+}
+
+
+/*
+ * POP3S is just like POP3, except it goes crypto right away.
+ */
+#ifdef HAVE_OPENSSL
+void pop3s_greeting(void) {
+ CtdlStartTLS(NULL, NULL, NULL);
+ pop3_greeting();
+}
+#endif
+
+
+
+/*
+ * Specify user name (implements POP3 "USER" command)
+ */
+void pop3_user(char *argbuf) {
+ char username[SIZ];
+
+ if (CC->logged_in) {
+ cprintf("-ERR You are already logged in.\r\n");
+ return;
+ }
+
+ strcpy(username, argbuf);
+ striplt(username);
+
+ /* lprintf(CTDL_DEBUG, "Trying <%s>\n", username); */
+ if (CtdlLoginExistingUser(NULL, username) == login_ok) {
+ cprintf("+OK Password required for %s\r\n", username);
+ }
+ else {
+ cprintf("-ERR No such user.\r\n");
+ }
+}
+
+
+
+/*
+ * Back end for pop3_grab_mailbox()
+ */
+void pop3_add_message(long msgnum, void *userdata) {
+ struct MetaData smi;
+
+ ++POP3->num_msgs;
+ if (POP3->num_msgs < 2) POP3->msgs = malloc(sizeof(struct pop3msg));
+ else POP3->msgs = realloc(POP3->msgs,
+ (POP3->num_msgs * sizeof(struct pop3msg)) ) ;
+ POP3->msgs[POP3->num_msgs-1].msgnum = msgnum;
+ POP3->msgs[POP3->num_msgs-1].deleted = 0;
+
+ /* We need to know the length of this message when it is printed in
+ * RFC822 format. Perhaps we have cached this length in the message's
+ * metadata record. If so, great; if not, measure it and then cache
+ * it for next time.
+ */
+ GetMetaData(&smi, msgnum);
+ if (smi.meta_rfc822_length <= 0L) {
+ CC->redirect_buffer = malloc(SIZ);
+ CC->redirect_len = 0;
+ CC->redirect_alloc = SIZ;
+ CtdlOutputMsg(msgnum, MT_RFC822, HEADERS_ALL, 0, 1, NULL);
+ smi.meta_rfc822_length = CC->redirect_len;
+ free(CC->redirect_buffer);
+ CC->redirect_buffer = NULL;
+ CC->redirect_len = 0;
+ CC->redirect_alloc = 0;
+ PutMetaData(&smi);
+ }
+ POP3->msgs[POP3->num_msgs-1].rfc822_length = smi.meta_rfc822_length;
+}
+
+
+
+/*
+ * Open the inbox and read its contents.
+ * (This should be called only once, by pop3_pass(), and returns the number
+ * of messages in the inbox, or -1 for error)
+ */
+int pop3_grab_mailbox(void) {
+ struct visit vbuf;
+ int i;
+
+ if (getroom(&CC->room, MAILROOM) != 0) return(-1);
+
+ /* Load up the messages */
+ CtdlForEachMessage(MSGS_ALL, 0L, NULL, NULL, NULL,
+ pop3_add_message, NULL);
+
+ /* Figure out which are old and which are new */
+ CtdlGetRelationship(&vbuf, &CC->user, &CC->room);
+ POP3->lastseen = (-1);
+ if (POP3->num_msgs) for (i=0; i<POP3->num_msgs; ++i) {
+ if (is_msg_in_sequence_set(vbuf.v_seen,
+ (POP3->msgs[POP3->num_msgs-1].msgnum) )) {
+ POP3->lastseen = i;
+ }
+ }
+
+ return(POP3->num_msgs);
+}
+
+void pop3_login(void)
+{
+ int msgs;
+
+ msgs = pop3_grab_mailbox();
+ if (msgs >= 0) {
+ cprintf("+OK %s is logged in (%d messages)\r\n",
+ CC->user.fullname, msgs);
+ lprintf(CTDL_NOTICE, "POP3 authenticated %s\n", CC->user.fullname);
+ }
+ else {
+ cprintf("-ERR Can't open your mailbox\r\n");
+ }
+
+}
+
+void pop3_apop(char *argbuf)
+{
+ char username[SIZ];
+ char userdigest[MD5_HEXSTRING_SIZE];
+ char realdigest[MD5_HEXSTRING_SIZE];
+ char *sptr;
+
+ if (CC->logged_in)
+ {
+ cprintf("-ERR You are already logged in; not in the AUTHORIZATION phase.\r\n");
+ return;
+ }
+
+ if ((sptr = strchr(argbuf, ' ')) == NULL)
+ {
+ cprintf("-ERR Invalid APOP line.\r\n");
+ return;
+ }
+
+ *sptr++ = '\0';
+
+ while ((*sptr) && isspace(*sptr))
+ sptr++;
+
+ strncpy(username, argbuf, sizeof(username)-1);
+ username[sizeof(username)-1] = '\0';
+
+ memset(userdigest, MD5_HEXSTRING_SIZE, 0);
+ strncpy(userdigest, sptr, MD5_HEXSTRING_SIZE-1);
+
+ if (CtdlLoginExistingUser(NULL, username) != login_ok)
+ {
+ cprintf("-ERR No such user.\r\n");
+ return;
+ }
+
+ if (getuser(&CC->user, CC->curr_user))
+ {
+ cprintf("-ERR No such user.\r\n");
+ return;
+ }
+
+ make_apop_string(CC->user.password, CC->cs_nonce, realdigest, sizeof realdigest);
+ if (!strncasecmp(realdigest, userdigest, MD5_HEXSTRING_SIZE-1))
+ {
+ do_login();
+ pop3_login();
+ }
+ else
+ {
+ cprintf("-ERR That is NOT the password.\r\n");
+ }
+}
+
+
+/*
+ * Authorize with password (implements POP3 "PASS" command)
+ */
+void pop3_pass(char *argbuf) {
+ char password[SIZ];
+
+ strcpy(password, argbuf);
+ striplt(password);
+
+ /* lprintf(CTDL_DEBUG, "Trying <%s>\n", password); */
+ if (CtdlTryPassword(password) == pass_ok) {
+ pop3_login();
+ }
+ else {
+ cprintf("-ERR That is NOT the password.\r\n");
+ }
+}
+
+
+
+/*
+ * list available msgs
+ */
+void pop3_list(char *argbuf) {
+ int i;
+ int which_one;
+
+ which_one = atoi(argbuf);
+
+ /* "list one" mode */
+ if (which_one > 0) {
+ if (which_one > POP3->num_msgs) {
+ cprintf("-ERR no such message, only %d are here\r\n",
+ POP3->num_msgs);
+ return;
+ }
+ else if (POP3->msgs[which_one-1].deleted) {
+ cprintf("-ERR Sorry, you deleted that message.\r\n");
+ return;
+ }
+ else {
+ cprintf("+OK %d %ld\r\n",
+ which_one,
+ (long)POP3->msgs[which_one-1].rfc822_length
+ );
+ return;
+ }
+ }
+
+ /* "list all" (scan listing) mode */
+ else {
+ cprintf("+OK Here's your mail:\r\n");
+ if (POP3->num_msgs > 0) for (i=0; i<POP3->num_msgs; ++i) {
+ if (! POP3->msgs[i].deleted) {
+ cprintf("%d %ld\r\n",
+ i+1,
+ (long)POP3->msgs[i].rfc822_length);
+ }
+ }
+ cprintf(".\r\n");
+ }
+}
+
+
+/*
+ * STAT (tally up the total message count and byte count) command
+ */
+void pop3_stat(char *argbuf) {
+ int total_msgs = 0;
+ size_t total_octets = 0;
+ int i;
+
+ if (POP3->num_msgs > 0) for (i=0; i<POP3->num_msgs; ++i) {
+ if (! POP3->msgs[i].deleted) {
+ ++total_msgs;
+ total_octets += POP3->msgs[i].rfc822_length;
+ }
+ }
+
+ cprintf("+OK %d %ld\r\n", total_msgs, (long)total_octets);
+}
+
+
+
+/*
+ * RETR command (fetch a message)
+ */
+void pop3_retr(char *argbuf) {
+ int which_one;
+
+ which_one = atoi(argbuf);
+ if ( (which_one < 1) || (which_one > POP3->num_msgs) ) {
+ cprintf("-ERR No such message.\r\n");
+ return;
+ }
+
+ if (POP3->msgs[which_one - 1].deleted) {
+ cprintf("-ERR Sorry, you deleted that message.\r\n");
+ return;
+ }
+
+ cprintf("+OK Message %d:\r\n", which_one);
+ CtdlOutputMsg(POP3->msgs[which_one - 1].msgnum, MT_RFC822, HEADERS_ALL, 0, 1, NULL);
+ cprintf(".\r\n");
+}
+
+
+/*
+ * TOP command (dumb way of fetching a partial message or headers-only)
+ */
+void pop3_top(char *argbuf) {
+ int which_one;
+ int lines_requested = 0;
+ int lines_dumped = 0;
+ char buf[1024];
+ char *msgtext;
+ char *ptr;
+ int in_body = 0;
+ int done = 0;
+
+ sscanf(argbuf, "%d %d", &which_one, &lines_requested);
+ if ( (which_one < 1) || (which_one > POP3->num_msgs) ) {
+ cprintf("-ERR No such message.\r\n");
+ return;
+ }
+
+ if (POP3->msgs[which_one - 1].deleted) {
+ cprintf("-ERR Sorry, you deleted that message.\r\n");
+ return;
+ }
+
+ CC->redirect_buffer = malloc(SIZ);
+ CC->redirect_len = 0;
+ CC->redirect_alloc = SIZ;
+ CtdlOutputMsg(POP3->msgs[which_one - 1].msgnum,
+ MT_RFC822, HEADERS_ALL, 0, 1, NULL);
+ msgtext = CC->redirect_buffer;
+ CC->redirect_buffer = NULL;
+ CC->redirect_len = 0;
+ CC->redirect_alloc = 0;
+
+ cprintf("+OK Message %d:\r\n", which_one);
+
+ ptr = msgtext;
+
+ while (ptr = memreadline(ptr, buf, (sizeof buf - 2)),
+ ( (*ptr != 0) && (done == 0))) {
+ strcat(buf, "\r\n");
+ if (in_body == 1) {
+ if (lines_dumped >= lines_requested) {
+ done = 1;
+ }
+ }
+ if ((in_body == 0) || (done == 0)) {
+ client_write(buf, strlen(buf));
+ }
+ if (in_body) {
+ ++lines_dumped;
+ }
+ if ((buf[0]==13)||(buf[0]==10)) in_body = 1;
+ }
+
+ if (buf[strlen(buf)-1] != 10) cprintf("\n");
+ free(msgtext);
+
+ cprintf(".\r\n");
+}
+
+
+/*
+ * DELE (delete message from mailbox)
+ */
+void pop3_dele(char *argbuf) {
+ int which_one;
+
+ which_one = atoi(argbuf);
+ if ( (which_one < 1) || (which_one > POP3->num_msgs) ) {
+ cprintf("-ERR No such message.\r\n");
+ return;
+ }
+
+ if (POP3->msgs[which_one - 1].deleted) {
+ cprintf("-ERR You already deleted that message.\r\n");
+ return;
+ }
+
+ /* Flag the message as deleted. Will expunge during QUIT command. */
+ POP3->msgs[which_one - 1].deleted = 1;
+ cprintf("+OK Message %d deleted.\r\n",
+ which_one);
+}
+
+
+/* Perform "UPDATE state" stuff
+ */
+void pop3_update(void) {
+ int i;
+ struct visit vbuf;
+
+ long *deletemsgs = NULL;
+ int num_deletemsgs = 0;
+
+ /* Remove messages marked for deletion */
+ if (POP3->num_msgs > 0) {
+ deletemsgs = malloc(POP3->num_msgs * sizeof(long));
+ for (i=0; i<POP3->num_msgs; ++i) {
+ if (POP3->msgs[i].deleted) {
+ deletemsgs[num_deletemsgs++] = POP3->msgs[i].msgnum;
+ }
+ }
+ if (num_deletemsgs > 0) {
+ CtdlDeleteMessages(MAILROOM, deletemsgs, num_deletemsgs, "");
+ }
+ free(deletemsgs);
+ }
+
+ /* Set last read pointer */
+ if (POP3->num_msgs > 0) {
+ lgetuser(&CC->user, CC->curr_user);
+
+ CtdlGetRelationship(&vbuf, &CC->user, &CC->room);
+ snprintf(vbuf.v_seen, sizeof vbuf.v_seen, "*:%ld",
+ POP3->msgs[POP3->num_msgs-1].msgnum);
+ CtdlSetRelationship(&vbuf, &CC->user, &CC->room);
+
+ lputuser(&CC->user);
+ }
+
+}
+
+
+/*
+ * RSET (reset, i.e. undelete any deleted messages) command
+ */
+void pop3_rset(char *argbuf) {
+ int i;
+
+ if (POP3->num_msgs > 0) for (i=0; i<POP3->num_msgs; ++i) {
+ if (POP3->msgs[i].deleted) {
+ POP3->msgs[i].deleted = 0;
+ }
+ }
+ cprintf("+OK Reset completed.\r\n");
+}
+
+
+
+/*
+ * LAST (Determine which message is the last unread message)
+ */
+void pop3_last(char *argbuf) {
+ cprintf("+OK %d\r\n", POP3->lastseen + 1);
+}
+
+
+/*
+ * CAPA is a command which tells the client which POP3 extensions
+ * are supported.
+ */
+void pop3_capa(void) {
+ cprintf("+OK Capability list follows\r\n"
+ "TOP\r\n"
+ "USER\r\n"
+ "UIDL\r\n"
+ "IMPLEMENTATION %s\r\n"
+ ".\r\n"
+ ,
+ CITADEL
+ );
+}
+
+
+
+/*
+ * UIDL (Universal IDentifier Listing) is easy. Our 'unique' message
+ * identifiers are simply the Citadel message numbers in the database.
+ */
+void pop3_uidl(char *argbuf) {
+ int i;
+ int which_one;
+
+ which_one = atoi(argbuf);
+
+ /* "list one" mode */
+ if (which_one > 0) {
+ if (which_one > POP3->num_msgs) {
+ cprintf("-ERR no such message, only %d are here\r\n",
+ POP3->num_msgs);
+ return;
+ }
+ else if (POP3->msgs[which_one-1].deleted) {
+ cprintf("-ERR Sorry, you deleted that message.\r\n");
+ return;
+ }
+ else {
+ cprintf("+OK %d %ld\r\n",
+ which_one,
+ POP3->msgs[which_one-1].msgnum
+ );
+ return;
+ }
+ }
+
+ /* "list all" (scan listing) mode */
+ else {
+ cprintf("+OK Here's your mail:\r\n");
+ if (POP3->num_msgs > 0) for (i=0; i<POP3->num_msgs; ++i) {
+ if (! POP3->msgs[i].deleted) {
+ cprintf("%d %ld\r\n",
+ i+1,
+ POP3->msgs[i].msgnum);
+ }
+ }
+ cprintf(".\r\n");
+ }
+}
+
+
+/*
+ * implements the STLS command (Citadel API version)
+ */
+#ifdef HAVE_OPENSSL
+void pop3_stls(void)
+{
+ char ok_response[SIZ];
+ char nosup_response[SIZ];
+ char error_response[SIZ];
+
+ sprintf(ok_response,
+ "+OK Begin TLS negotiation now\r\n");
+ sprintf(nosup_response,
+ "-ERR TLS not supported here\r\n");
+ sprintf(error_response,
+ "-ERR Internal error\r\n");
+ CtdlStartTLS(ok_response, nosup_response, error_response);
+}
+#endif
+
+
+
+
+
+
+
+/*
+ * Main command loop for POP3 sessions.
+ */
+void pop3_command_loop(void) {
+ char cmdbuf[SIZ];
+
+ time(&CC->lastcmd);
+ memset(cmdbuf, 0, sizeof cmdbuf); /* Clear it, just in case */
+ if (client_getln(cmdbuf, sizeof cmdbuf) < 1) {
+ lprintf(CTDL_ERR, "Client disconnected: ending session.\r\n");
+ CC->kill_me = 1;
+ return;
+ }
+ if (!strncasecmp(cmdbuf, "PASS", 4)) {
+ lprintf(CTDL_INFO, "POP3: PASS...\r\n");
+ }
+ else {
+ lprintf(CTDL_INFO, "POP3: %s\r\n", cmdbuf);
+ }
+ while (strlen(cmdbuf) < 5) strcat(cmdbuf, " ");
+
+ if (!strncasecmp(cmdbuf, "NOOP", 4)) {
+ cprintf("+OK No operation.\r\n");
+ }
+
+ else if (!strncasecmp(cmdbuf, "CAPA", 4)) {
+ pop3_capa();
+ }
+
+ else if (!strncasecmp(cmdbuf, "QUIT", 4)) {
+ cprintf("+OK Goodbye...\r\n");
+ pop3_update();
+ CC->kill_me = 1;
+ return;
+ }
+
+ else if (!strncasecmp(cmdbuf, "USER", 4)) {
+ pop3_user(&cmdbuf[5]);
+ }
+
+ else if (!strncasecmp(cmdbuf, "PASS", 4)) {
+ pop3_pass(&cmdbuf[5]);
+ }
+
+ else if (!strncasecmp(cmdbuf, "APOP", 4))
+ {
+ pop3_apop(&cmdbuf[5]);
+ }
+
+#ifdef HAVE_OPENSSL
+ else if (!strncasecmp(cmdbuf, "STLS", 4)) {
+ pop3_stls();
+ }
+#endif
+
+ else if (!CC->logged_in) {
+ cprintf("-ERR Not logged in.\r\n");
+ }
+
+ else if (!strncasecmp(cmdbuf, "LIST", 4)) {
+ pop3_list(&cmdbuf[5]);
+ }
+
+ else if (!strncasecmp(cmdbuf, "STAT", 4)) {
+ pop3_stat(&cmdbuf[5]);
+ }
+
+ else if (!strncasecmp(cmdbuf, "RETR", 4)) {
+ pop3_retr(&cmdbuf[5]);
+ }
+
+ else if (!strncasecmp(cmdbuf, "DELE", 4)) {
+ pop3_dele(&cmdbuf[5]);
+ }
+
+ else if (!strncasecmp(cmdbuf, "RSET", 4)) {
+ pop3_rset(&cmdbuf[5]);
+ }
+
+ else if (!strncasecmp(cmdbuf, "UIDL", 4)) {
+ pop3_uidl(&cmdbuf[5]);
+ }
+
+ else if (!strncasecmp(cmdbuf, "TOP", 3)) {
+ pop3_top(&cmdbuf[4]);
+ }
+
+ else if (!strncasecmp(cmdbuf, "LAST", 4)) {
+ pop3_last(&cmdbuf[4]);
+ }
+
+ else {
+ cprintf("-ERR I'm afraid I can't do that.\r\n");
+ }
+
+}
+
+
+
+CTDL_MODULE_INIT(pop3)
+{
+ CtdlRegisterServiceHook(config.c_pop3_port,
+ NULL,
+ pop3_greeting,
+ pop3_command_loop,
+ NULL);
+#ifdef HAVE_OPENSSL
+ CtdlRegisterServiceHook(config.c_pop3s_port,
+ NULL,
+ pop3s_greeting,
+ pop3_command_loop,
+ NULL);
+#endif
+ CtdlRegisterSessionHook(pop3_cleanup_function, EVT_STOP);
+
+ /* return our Subversion id for the Log */
+ return "$Id$";
+}
--- /dev/null
+/*
+ * $Id$
+ *
+ */
+
+struct pop3msg {
+ long msgnum;
+ size_t rfc822_length;
+ int deleted;
+};
+
+struct citpop3 { /* Information about the current session */
+ struct pop3msg *msgs; /* Array of message pointers */
+ int num_msgs; /* Number of messages in array */
+ int lastseen; /* Offset of last-read message in array */
+};
+ /* Note: the "lastseen" is represented as the
+ * offset in this array (zero-based), so when
+ * displaying it to a POP3 client, it must be
+ * incremented by one.
+ */
+
+#define POP3 CC->POP3
+
+void pop3_cleanup_function(void);
+void pop3_greeting(void);
+void pop3_user(char *argbuf);
+void pop3_pass(char *argbuf);
+void pop3_list(char *argbuf);
+void pop3_command_loop(void);
+void pop3_login(void);
+
--- /dev/null
+/*
+ * $Id$
+ *
+ * This module implementsserver commands related to the display and
+ * manipulation of the "Who's online" list.
+ *
+ */
+
+#include "sysdep.h"
+#include <stdlib.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <pwd.h>
+#include <errno.h>
+#include <sys/types.h>
+
+#if TIME_WITH_SYS_TIME
+# include <sys/time.h>
+# include <time.h>
+#else
+# if HAVE_SYS_TIME_H
+# include <sys/time.h>
+# else
+# include <time.h>
+# endif
+#endif
+
+#include <sys/wait.h>
+#include <string.h>
+#include <limits.h>
+#include "citadel.h"
+#include "server.h"
+#include "citserver.h"
+#include "support.h"
+#include "config.h"
+#include "control.h"
+#include "room_ops.h"
+#include "user_ops.h"
+#include "policy.h"
+#include "database.h"
+#include "msgbase.h"
+#include "tools.h"
+
+
+#include "ctdl_module.h"
+
+
+/*
+ * display who's online
+ */
+void cmd_rwho(char *argbuf) {
+ struct CitContext *cptr;
+ int spoofed = 0;
+ int user_spoofed = 0;
+ int room_spoofed = 0;
+ int host_spoofed = 0;
+ int aide;
+ char un[40];
+ char real_room[ROOMNAMELEN], room[ROOMNAMELEN];
+ char host[64], flags[5];
+
+ aide = CC->user.axlevel >= 6;
+ cprintf("%d%c \n", LISTING_FOLLOWS, CtdlCheckExpress() );
+
+ for (cptr = ContextList; cptr != NULL; cptr = cptr->next)
+ {
+ flags[0] = '\0';
+ spoofed = 0;
+ user_spoofed = 0;
+ room_spoofed = 0;
+ host_spoofed = 0;
+
+ if (cptr->cs_flags & CS_POSTING)
+ strcat(flags, "*");
+ else
+ strcat(flags, ".");
+
+ if (cptr->fake_username[0])
+ {
+ strcpy(un, cptr->fake_username);
+ spoofed = 1;
+ user_spoofed = 1;
+ }
+ else
+ strcpy(un, cptr->curr_user);
+
+ if (cptr->fake_hostname[0])
+ {
+ strcpy(host, cptr->fake_hostname);
+ spoofed = 1;
+ host_spoofed = 1;
+ }
+ else
+ strcpy(host, cptr->cs_host);
+
+ GenerateRoomDisplay(real_room, cptr, CC);
+
+ if (cptr->fake_roomname[0]) {
+ strcpy(room, cptr->fake_roomname);
+ spoofed = 1;
+ room_spoofed = 1;
+ }
+ else {
+ strcpy(room, real_room);
+ }
+
+ if ((aide) && (spoofed)) {
+ strcat(flags, "+");
+ }
+
+ if ((cptr->cs_flags & CS_STEALTH) && (aide)) {
+ strcat(flags, "-");
+ }
+
+ if (((cptr->cs_flags&CS_STEALTH)==0) || (aide))
+ {
+ cprintf("%d|%s|%s|%s|%s|%ld|%s|%s|",
+ cptr->cs_pid, un, room,
+ host, cptr->cs_clientname,
+ (long)(cptr->lastidle),
+ cptr->lastcmdname, flags
+ );
+
+ if ((user_spoofed) && (aide)) {
+ cprintf("%s|", cptr->curr_user);
+ }
+ else {
+ cprintf("|");
+ }
+
+ if ((room_spoofed) && (aide)) {
+ cprintf("%s|", real_room);
+ }
+ else {
+ cprintf("|");
+ }
+
+ if ((host_spoofed) && (aide)) {
+ cprintf("%s|", cptr->cs_host);
+ }
+ else {
+ cprintf("|");
+ }
+
+ cprintf("%d\n", cptr->logged_in);
+ }
+ }
+
+ /* Now it's magic time. Before we finish, call any EVT_RWHO hooks
+ * so that external paging modules such as serv_icq can add more
+ * content to the Wholist.
+ */
+ PerformSessionHooks(EVT_RWHO);
+ cprintf("000\n");
+ }
+
+
+/*
+ * Masquerade roomname
+ */
+void cmd_rchg(char *argbuf)
+{
+ char newroomname[ROOMNAMELEN];
+
+ extract_token(newroomname, argbuf, 0, '|', sizeof newroomname);
+ newroomname[ROOMNAMELEN-1] = 0;
+ if (strlen(newroomname) > 0) {
+ safestrncpy(CC->fake_roomname, newroomname,
+ sizeof(CC->fake_roomname) );
+ }
+ else {
+ safestrncpy(CC->fake_roomname, "", sizeof CC->fake_roomname);
+ }
+ cprintf("%d OK\n", CIT_OK);
+}
+
+/*
+ * Masquerade hostname
+ */
+void cmd_hchg(char *argbuf)
+{
+ char newhostname[64];
+
+ extract_token(newhostname, argbuf, 0, '|', sizeof newhostname);
+ if (strlen(newhostname) > 0) {
+ safestrncpy(CC->fake_hostname, newhostname,
+ sizeof(CC->fake_hostname) );
+ }
+ else {
+ safestrncpy(CC->fake_hostname, "", sizeof CC->fake_hostname);
+ }
+ cprintf("%d OK\n", CIT_OK);
+}
+
+
+/*
+ * Masquerade username (aides only)
+ */
+void cmd_uchg(char *argbuf)
+{
+
+ char newusername[USERNAME_SIZE];
+
+ extract_token(newusername, argbuf, 0, '|', sizeof newusername);
+
+ if (CtdlAccessCheck(ac_aide)) return;
+
+ if (strlen(newusername) > 0) {
+ CC->cs_flags &= ~CS_STEALTH;
+ memset(CC->fake_username, 0, 32);
+ if (strncasecmp(newusername, CC->curr_user,
+ strlen(CC->curr_user)))
+ safestrncpy(CC->fake_username, newusername,
+ sizeof(CC->fake_username));
+ }
+ else {
+ CC->fake_username[0] = '\0';
+ CC->cs_flags |= CS_STEALTH;
+ }
+ cprintf("%d\n",CIT_OK);
+}
+
+
+
+
+/*
+ * enter or exit "stealth mode"
+ */
+void cmd_stel(char *cmdbuf)
+{
+ int requested_mode;
+
+ requested_mode = extract_int(cmdbuf,0);
+
+ if (CtdlAccessCheck(ac_logged_in)) return;
+
+ if (requested_mode == 1) {
+ CC->cs_flags = CC->cs_flags | CS_STEALTH;
+ }
+ if (requested_mode == 0) {
+ CC->cs_flags = CC->cs_flags & ~CS_STEALTH;
+ }
+
+ cprintf("%d %d\n", CIT_OK,
+ ((CC->cs_flags & CS_STEALTH) ? 1 : 0) );
+}
+
+
+
+
+
+
+
+CTDL_MODULE_INIT(rwho)
+{
+ CtdlRegisterProtoHook(cmd_rwho, "RWHO", "Display who is online");
+ CtdlRegisterProtoHook(cmd_hchg, "HCHG", "Masquerade hostname");
+ CtdlRegisterProtoHook(cmd_rchg, "RCHG", "Masquerade roomname");
+ CtdlRegisterProtoHook(cmd_uchg, "UCHG", "Masquerade username");
+ CtdlRegisterProtoHook(cmd_stel, "STEL", "Enter/exit stealth mode");
+
+ /* return our Subversion id for the Log */
+ return "$Id$";
+}
--- /dev/null
+/*
+ * $Id$
+ *
+ * This module glues libSieve to the Citadel server in order to implement
+ * the Sieve mailbox filtering language (RFC 3028).
+ *
+ * This code is released under the terms of the GNU General Public License.
+ */
+
+#include "sysdep.h"
+#include <stdlib.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <fcntl.h>
+#include <ctype.h>
+#include <pwd.h>
+#include <errno.h>
+#include <sys/types.h>
+
+#if TIME_WITH_SYS_TIME
+# include <sys/time.h>
+# include <time.h>
+#else
+# if HAVE_SYS_TIME_H
+# include <sys/time.h>
+# else
+# include <time.h>
+# endif
+#endif
+
+#include <sys/wait.h>
+#include <string.h>
+#include <limits.h>
+#include "citadel.h"
+#include "server.h"
+#include "citserver.h"
+#include "support.h"
+#include "config.h"
+#include "room_ops.h"
+#include "policy.h"
+#include "database.h"
+#include "msgbase.h"
+#include "internet_addressing.h"
+#include "tools.h"
+
+
+#include "ctdl_module.h"
+
+
+#ifdef HAVE_LIBSIEVE
+
+#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)
+{
+ lprintf(CTDL_DEBUG, "Sieve: %s\n", sieve2_getvalue_string(s, "message"));
+ return SIEVE2_OK;
+}
+
+
+/*
+ * Callback function to log script parsing errors
+ */
+int ctdl_errparse(sieve2_context_t *s, void *my)
+{
+ lprintf(CTDL_WARNING, "Error in script, line %d: %s\n",
+ 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)
+{
+ lprintf(CTDL_WARNING, "Error executing script: %s\n",
+ sieve2_getvalue_string(s, "message")
+ );
+ return SIEVE2_OK;
+}
+
+
+/*
+ * Callback function to redirect a message to a different folder
+ */
+int ctdl_redirect(sieve2_context_t *s, void *my)
+{
+ struct ctdl_sieve *cs = (struct ctdl_sieve *)my;
+ struct CtdlMessage *msg = NULL;
+ struct recptypes *valid = NULL;
+ char recp[256];
+
+ safestrncpy(recp, sieve2_getvalue_string(s, "address"), sizeof recp);
+
+ lprintf(CTDL_DEBUG, "Action is REDIRECT, recipient <%s>\n", recp);
+
+ valid = validate_recipients(recp);
+ if (valid == NULL) {
+ lprintf(CTDL_WARNING, "REDIRECT failed: bad recipient <%s>\n", recp);
+ return SIEVE2_ERROR_BADARGS;
+ }
+ if (valid->num_error > 0) {
+ lprintf(CTDL_WARNING, "REDIRECT failed: bad recipient <%s>\n", recp);
+ free_recipients(valid);
+ return SIEVE2_ERROR_BADARGS;
+ }
+
+ msg = CtdlFetchMessage(cs->msgnum, 1);
+ if (msg == NULL) {
+ lprintf(CTDL_WARNING, "REDIRECT failed: unable to fetch msg %ld\n", cs->msgnum);
+ free_recipients(valid);
+ return SIEVE2_ERROR_BADARGS;
+ }
+
+ CtdlSubmitMsg(msg, valid, NULL);
+ cs->cancel_implicit_keep = 1;
+ free_recipients(valid);
+ CtdlFreeMessage(msg);
+ return SIEVE2_OK;
+}
+
+
+/*
+ * Callback function to indicate that a message *will* be kept in the inbox
+ */
+int ctdl_keep(sieve2_context_t *s, void *my)
+{
+ struct ctdl_sieve *cs = (struct ctdl_sieve *)my;
+
+ lprintf(CTDL_DEBUG, "Action is KEEP\n");
+
+ cs->keep = 1;
+ cs->cancel_implicit_keep = 1;
+ return SIEVE2_OK;
+}
+
+
+/*
+ * Callback function to file a message into a different mailbox
+ */
+int ctdl_fileinto(sieve2_context_t *s, void *my)
+{
+ struct ctdl_sieve *cs = (struct ctdl_sieve *)my;
+ const char *dest_folder = sieve2_getvalue_string(s, "mailbox");
+ int c;
+ char foldername[256];
+ char original_room_name[ROOMNAMELEN];
+
+ lprintf(CTDL_DEBUG, "Action is FILEINTO, destination is <%s>\n", dest_folder);
+
+ /* FILEINTO 'INBOX' is the same thing as KEEP */
+ if ( (!strcasecmp(dest_folder, "INBOX")) || (!strcasecmp(dest_folder, MAILROOM)) ) {
+ cs->keep = 1;
+ cs->cancel_implicit_keep = 1;
+ return SIEVE2_OK;
+ }
+
+ /* Remember what room we came from */
+ safestrncpy(original_room_name, CC->room.QRname, sizeof original_room_name);
+
+ /* First try a mailbox name match (check personal mail folders first) */
+ snprintf(foldername, sizeof foldername, "%010ld.%s", cs->usernum, dest_folder);
+ c = getroom(&CC->room, foldername);
+
+ /* Then a regular room name match (public and private rooms) */
+ if (c != 0) {
+ safestrncpy(foldername, dest_folder, sizeof foldername);
+ c = getroom(&CC->room, foldername);
+ }
+
+ if (c != 0) {
+ lprintf(CTDL_WARNING, "FILEINTO failed: target <%s> does not exist\n", dest_folder);
+ return SIEVE2_ERROR_BADARGS;
+ }
+
+ /* Yes, we actually have to go there */
+ usergoto(NULL, 0, 0, NULL, NULL);
+
+ c = CtdlSaveMsgPointersInRoom(NULL, &cs->msgnum, 1, 0, NULL);
+
+ /* Go back to the room we came from */
+ if (strcasecmp(original_room_name, CC->room.QRname)) {
+ usergoto(original_room_name, 0, 0, NULL, NULL);
+ }
+
+ if (c == 0) {
+ cs->cancel_implicit_keep = 1;
+ return SIEVE2_OK;
+ }
+ else {
+ return SIEVE2_ERROR_BADARGS;
+ }
+}
+
+
+/*
+ * Callback function to indicate that a message should be discarded.
+ */
+int ctdl_discard(sieve2_context_t *s, void *my)
+{
+ struct ctdl_sieve *cs = (struct ctdl_sieve *)my;
+
+ lprintf(CTDL_DEBUG, "Action is DISCARD\n");
+
+ /* Cancel the implicit keep. That's all there is to it. */
+ cs->cancel_implicit_keep = 1;
+ return SIEVE2_OK;
+}
+
+
+
+/*
+ * Callback function to indicate that a message should be rejected
+ */
+int ctdl_reject(sieve2_context_t *s, void *my)
+{
+ struct ctdl_sieve *cs = (struct ctdl_sieve *)my;
+ char *reject_text = NULL;
+
+ lprintf(CTDL_DEBUG, "Action is REJECT\n");
+
+ /* If we don't know who sent the message, do a DISCARD instead. */
+ if (strlen(cs->sender) == 0) {
+ lprintf(CTDL_INFO, "Unknown sender. Doing DISCARD instead of REJECT.\n");
+ return ctdl_discard(s, my);
+ }
+
+ /* Assemble the reject message. */
+ reject_text = malloc(strlen(sieve2_getvalue_string(s, "message")) + 1024);
+ if (reject_text == NULL) {
+ return SIEVE2_ERROR_FAIL;
+ }
+
+ sprintf(reject_text,
+ "Content-type: text/plain\n"
+ "\n"
+ "The message was refused by the recipient's mail filtering program.\n"
+ "The reason given was as follows:\n"
+ "\n"
+ "%s\n"
+ "\n"
+ ,
+ sieve2_getvalue_string(s, "message")
+ );
+
+ quickie_message( /* This delivers the message */
+ NULL,
+ cs->envelope_to,
+ cs->sender,
+ NULL,
+ reject_text,
+ FMT_RFC822,
+ "Delivery status notification"
+ );
+
+ free(reject_text);
+ cs->cancel_implicit_keep = 1;
+ return SIEVE2_OK;
+}
+
+
+
+/*
+ * Callback function to indicate that a vacation message should be generated
+ */
+int ctdl_vacation(sieve2_context_t *s, void *my)
+{
+ struct ctdl_sieve *cs = (struct ctdl_sieve *)my;
+ struct sdm_vacation *vptr;
+ int days = 1;
+ const char *message;
+ char *vacamsg_text = NULL;
+ char vacamsg_subject[1024];
+
+ lprintf(CTDL_DEBUG, "Action is VACATION\n");
+
+ message = sieve2_getvalue_string(s, "message");
+ if (message == NULL) return SIEVE2_ERROR_BADARGS;
+
+ if (sieve2_getvalue_string(s, "subject") != NULL) {
+ safestrncpy(vacamsg_subject, sieve2_getvalue_string(s, "subject"), sizeof vacamsg_subject);
+ }
+ else {
+ snprintf(vacamsg_subject, sizeof vacamsg_subject, "Re: %s", cs->subject);
+ }
+
+ days = sieve2_getvalue_int(s, "days");
+ if (days < 1) days = 1;
+ if (days > MAX_VACATION) days = MAX_VACATION;
+
+ /* Check to see whether we've already alerted this sender that we're on vacation. */
+ for (vptr = cs->u->first_vacation; vptr != NULL; vptr = vptr->next) {
+ if (!strcasecmp(vptr->fromaddr, cs->sender)) {
+ if ( (time(NULL) - vptr->timestamp) < (days * 86400) ) {
+ lprintf(CTDL_DEBUG, "Already alerted <%s> recently.\n", cs->sender);
+ return SIEVE2_OK;
+ }
+ }
+ }
+
+ /* Assemble the reject message. */
+ vacamsg_text = malloc(strlen(message) + 1024);
+ if (vacamsg_text == NULL) {
+ return SIEVE2_ERROR_FAIL;
+ }
+
+ sprintf(vacamsg_text,
+ "Content-type: text/plain\n"
+ "\n"
+ "%s\n"
+ "\n"
+ ,
+ message
+ );
+
+ quickie_message( /* This delivers the message */
+ NULL,
+ cs->envelope_to,
+ cs->sender,
+ NULL,
+ vacamsg_text,
+ FMT_RFC822,
+ vacamsg_subject
+ );
+
+ free(vacamsg_text);
+
+ /* Now update the list to reflect the fact that we've alerted this sender.
+ * If they're already in the list, just update the timestamp.
+ */
+ for (vptr = cs->u->first_vacation; vptr != NULL; vptr = vptr->next) {
+ if (!strcasecmp(vptr->fromaddr, cs->sender)) {
+ vptr->timestamp = time(NULL);
+ return SIEVE2_OK;
+ }
+ }
+
+ /* If we get to this point, create a new record.
+ */
+ vptr = malloc(sizeof(struct sdm_vacation));
+ vptr->timestamp = time(NULL);
+ safestrncpy(vptr->fromaddr, cs->sender, sizeof vptr->fromaddr);
+ vptr->next = cs->u->first_vacation;
+ cs->u->first_vacation = vptr;
+
+ return SIEVE2_OK;
+}
+
+
+/*
+ * Callback function to parse addresses per local system convention
+ * It is disabled because we don't support subaddresses.
+ */
+#if 0
+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
+ */
+int ctdl_getenvelope(sieve2_context_t *s, void *my)
+{
+ struct ctdl_sieve *cs = (struct ctdl_sieve *)my;
+
+ lprintf(CTDL_DEBUG, "Action is GETENVELOPE\n");
+ sieve2_setvalue_string(s, "to", cs->envelope_to);
+ sieve2_setvalue_string(s, "from", cs->envelope_from);
+ return SIEVE2_OK;
+}
+
+
+/*
+ * 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;
+}
+ *
+ */
+
+
+/*
+ * Callback function to fetch message size
+ */
+int ctdl_getsize(sieve2_context_t *s, void *my)
+{
+ struct ctdl_sieve *cs = (struct ctdl_sieve *)my;
+ struct MetaData smi;
+
+ GetMetaData(&smi, cs->msgnum);
+
+ if (smi.meta_rfc822_length > 0L) {
+ sieve2_setvalue_int(s, "size", (int)smi.meta_rfc822_length);
+ return SIEVE2_OK;
+ }
+
+ return SIEVE2_ERROR_UNSUPPORTED;
+}
+
+
+/*
+ * Callback function to retrieve the sieve script
+ */
+int ctdl_getscript(sieve2_context_t *s, void *my) {
+ struct sdm_script *sptr;
+ struct ctdl_sieve *cs = (struct ctdl_sieve *)my;
+
+ for (sptr=cs->u->first_script; sptr!=NULL; sptr=sptr->next) {
+ if (sptr->script_active > 0) {
+ lprintf(CTDL_DEBUG, "ctdl_getscript() is using script '%s'\n", sptr->script_name);
+ sieve2_setvalue_string(s, "script", sptr->script_content);
+ return SIEVE2_OK;
+ }
+ }
+
+ lprintf(CTDL_DEBUG, "ctdl_getscript() found no active script\n");
+ return SIEVE2_ERROR_GETSCRIPT;
+}
+
+/*
+ * Callback function to retrieve message headers
+ */
+int ctdl_getheaders(sieve2_context_t *s, void *my) {
+
+ struct ctdl_sieve *cs = (struct ctdl_sieve *)my;
+
+ lprintf(CTDL_DEBUG, "ctdl_getheaders() was called\n");
+ sieve2_setvalue_string(s, "allheaders", cs->rfc822headers);
+ return SIEVE2_OK;
+}
+
+
+
+/*
+ * 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);
+ lprintf(CTDL_DEBUG, "<%s> queued for Sieve processing\n", which_room->QRname);
+}
+
+
+
+/*
+ * Perform sieve processing for one message (called by sieve_do_room() for each message)
+ */
+void sieve_do_msg(long msgnum, void *userdata) {
+ struct sdm_userdata *u = (struct sdm_userdata *) userdata;
+ sieve2_context_t *sieve2_context = u->sieve2_context;
+ struct ctdl_sieve my;
+ int res;
+ struct CtdlMessage *msg;
+ int i;
+ size_t headers_len = 0;
+ int len = 0;
+
+ lprintf(CTDL_DEBUG, "Performing sieve processing on msg <%ld>\n", msgnum);
+
+ msg = CtdlFetchMessage(msgnum, 0);
+ if (msg == NULL) return;
+
+ /*
+ * Grab the message headers so we can feed them to libSieve.
+ */
+ CC->redirect_buffer = malloc(SIZ);
+ CC->redirect_len = 0;
+ CC->redirect_alloc = SIZ;
+ CtdlOutputPreLoadedMsg(msg, MT_RFC822, HEADERS_ONLY, 0, 1);
+ my.rfc822headers = CC->redirect_buffer;
+ headers_len = CC->redirect_len;
+ CC->redirect_buffer = NULL;
+ CC->redirect_len = 0;
+ CC->redirect_alloc = 0;
+
+ /*
+ * libSieve clobbers the stack if it encounters badly formed
+ * headers. Sanitize our headers by stripping nonprintable
+ * characters.
+ */
+ for (i=0; i<headers_len; ++i) {
+ if (!isascii(my.rfc822headers[i])) {
+ my.rfc822headers[i] = '_';
+ }
+ }
+
+ my.keep = 0; /* Set to 1 to declare an *explicit* keep */
+ my.cancel_implicit_keep = 0; /* Some actions will cancel the implicit keep */
+ my.usernum = atol(CC->room.QRname); /* Keep track of the owner of the room's namespace */
+ my.msgnum = msgnum; /* Keep track of the message number in our local store */
+ my.u = u; /* Hand off a pointer to the rest of this info */
+
+ /* Keep track of the recipient so we can do handling based on it later */
+ process_rfc822_addr(msg->cm_fields['R'], my.recp_user, my.recp_node, my.recp_name);
+
+ /* Keep track of the sender so we can use it for REJECT and VACATION responses */
+ if (msg->cm_fields['F'] != NULL) {
+ safestrncpy(my.sender, msg->cm_fields['F'], sizeof my.sender);
+ }
+ else if ( (msg->cm_fields['A'] != NULL) && (msg->cm_fields['N'] != NULL) ) {
+ snprintf(my.sender, sizeof my.sender, "%s@%s", msg->cm_fields['A'], msg->cm_fields['N']);
+ }
+ else if (msg->cm_fields['A'] != NULL) {
+ safestrncpy(my.sender, msg->cm_fields['A'], sizeof my.sender);
+ }
+ else {
+ strcpy(my.sender, "");
+ }
+
+ /* Keep track of the subject so we can use it for VACATION responses */
+ if (msg->cm_fields['U'] != NULL) {
+ safestrncpy(my.subject, msg->cm_fields['U'], sizeof my.subject);
+ }
+ else {
+ strcpy(my.subject, "");
+ }
+
+ /* Keep track of the envelope-from address (use body-from if not found) */
+ if (msg->cm_fields['P'] != NULL) {
+ safestrncpy(my.envelope_from, msg->cm_fields['P'], sizeof my.envelope_from);
+ }
+ else if (msg->cm_fields['F'] != NULL) {
+ safestrncpy(my.envelope_from, msg->cm_fields['F'], sizeof my.envelope_from);
+ }
+ else {
+ strcpy(my.envelope_from, "");
+ }
+
+ len = strlen(my.envelope_from);
+ for (i=0; i<len; ++i) {
+ if (isspace(my.envelope_from[i])) my.envelope_from[i] = '_';
+ }
+ if (haschar(my.envelope_from, '@') == 0) {
+ strcat(my.envelope_from, "@");
+ strcat(my.envelope_from, config.c_fqdn);
+ }
+
+ /* Keep track of the envelope-to address (use body-to if not found) */
+ if (msg->cm_fields['V'] != NULL) {
+ safestrncpy(my.envelope_to, msg->cm_fields['V'], sizeof my.envelope_to);
+ }
+ else if (msg->cm_fields['R'] != NULL) {
+ safestrncpy(my.envelope_to, msg->cm_fields['R'], sizeof my.envelope_to);
+ if (msg->cm_fields['D'] != NULL) {
+ strcat(my.envelope_to, "@");
+ strcat(my.envelope_to, msg->cm_fields['D']);
+ }
+ }
+ else {
+ strcpy(my.envelope_to, "");
+ }
+
+ len = strlen(my.envelope_to);
+ for (i=0; i<len; ++i) {
+ if (isspace(my.envelope_to[i])) my.envelope_to[i] = '_';
+ }
+ if (haschar(my.envelope_to, '@') == 0) {
+ strcat(my.envelope_to, "@");
+ strcat(my.envelope_to, config.c_fqdn);
+ }
+
+ CtdlFreeMessage(msg);
+
+ sieve2_setvalue_string(sieve2_context, "allheaders", my.rfc822headers);
+
+ lprintf(CTDL_DEBUG, "Calling sieve2_execute()\n");
+ res = sieve2_execute(sieve2_context, &my);
+ if (res != SIEVE2_OK) {
+ lprintf(CTDL_CRIT, "sieve2_execute() returned %d: %s\n", res, sieve2_errstr(res));
+ }
+
+ free(my.rfc822headers);
+ my.rfc822headers = NULL;
+
+ /*
+ * Delete the message from the inbox unless either we were told not to, or
+ * if no other action was successfully taken.
+ */
+ if ( (!my.keep) && (my.cancel_implicit_keep) ) {
+ lprintf(CTDL_DEBUG, "keep is 0 -- deleting message from inbox\n");
+ CtdlDeleteMessages(CC->room.QRname, &msgnum, 1, "");
+ }
+
+ lprintf(CTDL_DEBUG, "Completed sieve processing on msg <%ld>\n", msgnum);
+ u->lastproc = msgnum;
+
+ return;
+}
+
+
+
+/*
+ * Given the on-disk representation of our Sieve config, load
+ * it into an in-memory data structure.
+ */
+void parse_sieve_config(char *conf, struct sdm_userdata *u) {
+ char *ptr;
+ char *c, *vacrec;
+ char keyword[256];
+ struct sdm_script *sptr;
+ struct sdm_vacation *vptr;
+
+ ptr = conf;
+ while (c = ptr, ptr = bmstrcasestr(ptr, CTDLSIEVECONFIGSEPARATOR), ptr != NULL) {
+ *ptr = 0;
+ ptr += strlen(CTDLSIEVECONFIGSEPARATOR);
+
+ extract_token(keyword, c, 0, '|', sizeof keyword);
+
+ if (!strcasecmp(keyword, "lastproc")) {
+ u->lastproc = extract_long(c, 1);
+ }
+
+ else if (!strcasecmp(keyword, "script")) {
+ sptr = malloc(sizeof(struct sdm_script));
+ extract_token(sptr->script_name, c, 1, '|', sizeof sptr->script_name);
+ sptr->script_active = extract_int(c, 2);
+ remove_token(c, 0, '|');
+ remove_token(c, 0, '|');
+ remove_token(c, 0, '|');
+ sptr->script_content = strdup(c);
+ sptr->next = u->first_script;
+ u->first_script = sptr;
+ }
+
+ else if (!strcasecmp(keyword, "vacation")) {
+
+ if (c != NULL) while (vacrec=c, c=strchr(c, '\n'), (c != NULL)) {
+
+ *c = 0;
+ ++c;
+
+ if (strncasecmp(vacrec, "vacation|", 9)) {
+ vptr = malloc(sizeof(struct sdm_vacation));
+ extract_token(vptr->fromaddr, vacrec, 0, '|', sizeof vptr->fromaddr);
+ vptr->timestamp = extract_long(vacrec, 1);
+ vptr->next = u->first_vacation;
+ u->first_vacation = vptr;
+ }
+ }
+ }
+
+ /* ignore unknown keywords */
+ }
+}
+
+/*
+ * We found the Sieve configuration for this user.
+ * Now do something with it.
+ */
+void get_sieve_config_backend(long msgnum, void *userdata) {
+ struct sdm_userdata *u = (struct sdm_userdata *) userdata;
+ struct CtdlMessage *msg;
+ char *conf;
+
+ u->config_msgnum = msgnum;
+ msg = CtdlFetchMessage(msgnum, 1);
+ if (msg == NULL) {
+ u->config_msgnum = (-1) ;
+ return;
+ }
+
+ conf = msg->cm_fields['M'];
+ msg->cm_fields['M'] = NULL;
+ CtdlFreeMessage(msg);
+
+ if (conf != NULL) {
+ parse_sieve_config(conf, u);
+ free(conf);
+ }
+
+}
+
+
+/*
+ * Write our citadel sieve config back to disk
+ *
+ * (Set yes_write_to_disk to nonzero to make it actually write the config;
+ * otherwise it just frees the data structures.)
+ */
+void rewrite_ctdl_sieve_config(struct sdm_userdata *u, int yes_write_to_disk) {
+ char *text;
+ struct sdm_script *sptr;
+ struct sdm_vacation *vptr;
+ size_t tsize;
+
+ text = malloc(1024);
+ tsize = 1024;
+ snprintf(text, 1024,
+ "Content-type: application/x-citadel-sieve-config\n"
+ "\n"
+ CTDLSIEVECONFIGSEPARATOR
+ "lastproc|%ld"
+ CTDLSIEVECONFIGSEPARATOR
+ ,
+ u->lastproc
+ );
+
+ while (u->first_script != NULL) {
+ size_t tlen;
+ tlen = strlen(text);
+ tsize = tlen + strlen(u->first_script->script_content) +256;
+ text = realloc(text, tsize);
+ sprintf(&text[strlen(text)], "script|%s|%d|%s" CTDLSIEVECONFIGSEPARATOR,
+ u->first_script->script_name,
+ u->first_script->script_active,
+ u->first_script->script_content
+ );
+ sptr = u->first_script;
+ u->first_script = u->first_script->next;
+ free(sptr->script_content);
+ free(sptr);
+ }
+
+ if (u->first_vacation != NULL) {
+
+ tsize = strlen(text) + 256;
+ for (vptr = u->first_vacation; vptr != NULL; vptr = vptr->next) {
+ tsize += strlen(vptr->fromaddr + 32);
+ }
+ text = realloc(text, tsize);
+
+ sprintf(&text[strlen(text)], "vacation|\n");
+ while (u->first_vacation != NULL) {
+ if ( (time(NULL) - u->first_vacation->timestamp) < (MAX_VACATION * 86400)) {
+ sprintf(&text[strlen(text)], "%s|%ld\n",
+ u->first_vacation->fromaddr,
+ u->first_vacation->timestamp
+ );
+ }
+ vptr = u->first_vacation;
+ u->first_vacation = u->first_vacation->next;
+ free(vptr);
+ }
+ sprintf(&text[strlen(text)], CTDLSIEVECONFIGSEPARATOR);
+ }
+
+ /* Save the config */
+ quickie_message("Citadel", NULL, NULL, u->config_roomname,
+ text,
+ 4,
+ "Sieve configuration"
+ );
+
+ free (text);
+ /* And delete the old one */
+ if (u->config_msgnum > 0) {
+ CtdlDeleteMessages(u->config_roomname, &u->config_msgnum, 1, "");
+ }
+
+}
+
+
+/*
+ * This is our callback registration table for libSieve.
+ */
+sieve2_callback_t ctdl_sieve_callbacks[] = {
+ { SIEVE2_ACTION_REJECT, ctdl_reject },
+ { SIEVE2_ACTION_VACATION, ctdl_vacation },
+ { SIEVE2_ERRCALL_PARSE, ctdl_errparse },
+ { SIEVE2_ERRCALL_RUNTIME, ctdl_errexec },
+ { SIEVE2_ACTION_FILEINTO, ctdl_fileinto },
+ { SIEVE2_ACTION_REDIRECT, ctdl_redirect },
+ { SIEVE2_ACTION_DISCARD, ctdl_discard },
+ { SIEVE2_ACTION_KEEP, ctdl_keep },
+ { SIEVE2_SCRIPT_GETSCRIPT, ctdl_getscript },
+ { SIEVE2_DEBUG_TRACE, ctdl_debug },
+ { 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 }
+};
+
+
+/*
+ * Perform sieve processing for a single room
+ */
+void sieve_do_room(char *roomname) {
+
+ struct sdm_userdata u;
+ sieve2_context_t *sieve2_context = NULL; /* Context for sieve parser */
+ int res; /* Return code from libsieve calls */
+ long orig_lastproc = 0;
+
+ memset(&u, 0, sizeof u);
+
+ /* See if the user who owns this 'mailbox' has any Sieve scripts that
+ * require execution.
+ */
+ snprintf(u.config_roomname, sizeof u.config_roomname, "%010ld.%s", atol(roomname), USERCONFIGROOM);
+ if (getroom(&CC->room, u.config_roomname) != 0) {
+ lprintf(CTDL_DEBUG, "<%s> does not exist. No processing is required.\n", u.config_roomname);
+ return;
+ }
+
+ /*
+ * 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 );
+
+ if (u.config_msgnum < 0) {
+ lprintf(CTDL_DEBUG, "No Sieve rules exist. No processing is required.\n");
+ return;
+ }
+
+ lprintf(CTDL_DEBUG, "Rules found. Performing Sieve processing for <%s>\n", roomname);
+
+ if (getroom(&CC->room, roomname) != 0) {
+ lprintf(CTDL_CRIT, "ERROR: cannot load <%s>\n", roomname);
+ return;
+ }
+
+ /* Initialize the Sieve parser */
+
+ res = sieve2_alloc(&sieve2_context);
+ if (res != SIEVE2_OK) {
+ lprintf(CTDL_CRIT, "sieve2_alloc() returned %d: %s\n", res, sieve2_errstr(res));
+ return;
+ }
+
+ res = sieve2_callbacks(sieve2_context, ctdl_sieve_callbacks);
+ if (res != SIEVE2_OK) {
+ lprintf(CTDL_CRIT, "sieve2_callbacks() returned %d: %s\n", res, sieve2_errstr(res));
+ goto BAIL;
+ }
+
+ /* Validate the script */
+
+ struct ctdl_sieve my; /* dummy ctdl_sieve struct just to pass "u" slong */
+ memset(&my, 0, sizeof my);
+ my.u = &u;
+ res = sieve2_validate(sieve2_context, &my);
+ if (res != SIEVE2_OK) {
+ lprintf(CTDL_CRIT, "sieve2_validate() returned %d: %s\n", res, sieve2_errstr(res));
+ goto BAIL;
+ }
+
+ /* 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
+ );
+
+BAIL:
+ res = sieve2_free(&sieve2_context);
+ if (res != SIEVE2_OK) {
+ lprintf(CTDL_CRIT, "sieve2_free() returned %d: %s\n", res, sieve2_errstr(res));
+ }
+
+ /* Rewrite the config if we have to */
+ 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) {
+ lprintf(CTDL_DEBUG, "Begin Sieve processing\n");
+ 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 (getroom(&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)) {
+ getroom(&CC->room, hold_rm); /* return to saved room */
+ }
+}
+
+void msiv_store(struct sdm_userdata *u, int yes_write_to_disk) {
+ 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 (strlen(script_name) == 0) {
+ 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;
+
+ 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.
+ *
+ * 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.
+ */
+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);
+ }
+ }
+ }
+
+ if (script_to_delete == NULL) return(1);
+
+ 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;
+ }
+ }
+
+ free(script_to_delete->script_content);
+ free(script_to_delete);
+ return(0);
+}
+
+
+/*
+ * 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);
+ }
+ s->script_content = script_content;
+ replaced = 1;
+ }
+ }
+
+ 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;
+ }
+}
+
+
+
+/*
+ * Citadel protocol to manage sieve scripts.
+ * This is basically a simplified (read: doesn't resemble IMAP) version
+ * of the 'managesieve' protocol.
+ */
+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));
+
+ 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 (strlen(script_name) > 0) {
+ cprintf("%d Transmit script now\n", SEND_LISTING);
+ script_content = CtdlReadMessageBody("000", config.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");
+ }
+ cprintf("000\n");
+ }
+ else {
+ cprintf("%d Invalid script name.\n", ERROR + ILLEGAL_VALUE);
+ }
+ }
+
+ 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], "...");
+ }
+
+ lprintf(CTDL_INFO, "%s\n",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) {
+ lprintf(CTDL_CRIT, "sieve2_alloc() returned %d: %s\n", res, sieve2_errstr(res));
+ return;
+ }
+
+ res = sieve2_callbacks(sieve2_context, ctdl_sieve_callbacks);
+ if (res != SIEVE2_OK) {
+ lprintf(CTDL_CRIT, "sieve2_callbacks() returned %d: %s\n", res, sieve2_errstr(res));
+ goto BAIL;
+ }
+
+ msiv_extensions = strdup(sieve2_listextensions(sieve2_context));
+ lprintf(CTDL_INFO, "Extensions: %s\n", msiv_extensions);
+
+BAIL: res = sieve2_free(&sieve2_context);
+ if (res != SIEVE2_OK) {
+ lprintf(CTDL_CRIT, "sieve2_free() returned %d: %s\n", res, sieve2_errstr(res));
+ }
+
+}
+
+int serv_sieve_room(struct ctdlroom *room)
+{
+ if (!strcasecmp(&room->QRname[11], MAILROOM)) {
+ sieve_queue_room(room);
+ }
+ return 0;
+}
+
+#endif /* HAVE_LIBSIEVE */
+
+CTDL_MODULE_INIT(sieve)
+{
+
+#ifdef HAVE_LIBSIEVE
+
+ ctdl_sieve_init();
+ CtdlRegisterProtoHook(cmd_msiv, "MSIV", "Manage Sieve scripts");
+
+ CtdlRegisterRoomHook(serv_sieve_room);
+
+ CtdlRegisterSessionHook(perform_sieve_processing, EVT_HOUSE);
+
+#else /* HAVE_LIBSIEVE */
+
+ lprintf(CTDL_INFO, "This server is missing libsieve. Mailbox filtering will be disabled.\n");
+
+#endif /* HAVE_LIBSIEVE */
+
+ /* return our Subversion id for the Log */
+ return "$Id$";
+}
+
--- /dev/null
+/*
+ * $Id$
+ *
+ * This module is an SMTP and ESMTP implementation for the Citadel system.
+ * It is compliant with all of the following:
+ *
+ * RFC 821 - Simple Mail Transfer Protocol
+ * RFC 876 - Survey of SMTP Implementations
+ * RFC 1047 - Duplicate messages and SMTP
+ * RFC 1854 - command pipelining
+ * RFC 1869 - Extended Simple Mail Transfer Protocol
+ * RFC 1870 - SMTP Service Extension for Message Size Declaration
+ * RFC 1893 - Enhanced Mail System Status Codes
+ * RFC 2033 - Local Mail Transfer Protocol
+ * RFC 2034 - SMTP Service Extension for Returning Enhanced Error Codes
+ * RFC 2197 - SMTP Service Extension for Command Pipelining
+ * RFC 2476 - Message Submission
+ * RFC 2487 - SMTP Service Extension for Secure SMTP over TLS
+ * RFC 2554 - SMTP Service Extension for Authentication
+ * RFC 2821 - Simple Mail Transfer Protocol
+ * RFC 2822 - Internet Message Format
+ * RFC 2920 - SMTP Service Extension for Command Pipelining
+ *
+ * The VRFY and EXPN commands have been removed from this implementation
+ * because nobody uses these commands anymore, except for spammers.
+ *
+ */
+
+#include "sysdep.h"
+#include <stdlib.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <pwd.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <syslog.h>
+
+#if TIME_WITH_SYS_TIME
+# include <sys/time.h>
+# include <time.h>
+#else
+# if HAVE_SYS_TIME_H
+# include <sys/time.h>
+# else
+# include <time.h>
+# endif
+#endif
+
+#include <sys/wait.h>
+#include <ctype.h>
+#include <string.h>
+#include <limits.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include "citadel.h"
+#include "server.h"
+#include "citserver.h"
+#include "support.h"
+#include "config.h"
+#include "control.h"
+#include "room_ops.h"
+#include "user_ops.h"
+#include "policy.h"
+#include "database.h"
+#include "msgbase.h"
+#include "tools.h"
+#include "internet_addressing.h"
+#include "genstamp.h"
+#include "domain.h"
+#include "clientsocket.h"
+#include "locate_host.h"
+#include "citadel_dirs.h"
+
+#ifdef HAVE_OPENSSL
+#include "serv_crypto.h"
+#endif
+
+
+
+#ifndef HAVE_SNPRINTF
+#include "snprintf.h"
+#endif
+
+
+#include "ctdl_module.h"
+
+
+
+struct citsmtp { /* Information about the current session */
+ int command_state;
+ char helo_node[SIZ];
+ char from[SIZ];
+ char recipients[SIZ];
+ int number_of_recipients;
+ int delivery_mode;
+ int message_originated_locally;
+ int is_lmtp;
+ int is_unfiltered;
+ int is_msa;
+};
+
+enum { /* Command states for login authentication */
+ smtp_command,
+ smtp_user,
+ smtp_password,
+ smtp_plain
+};
+
+#define SMTP CC->SMTP
+
+
+int run_queue_now = 0; /* Set to 1 to ignore SMTP send retry times */
+
+
+
+/*****************************************************************************/
+/* SMTP SERVER (INBOUND) STUFF */
+/*****************************************************************************/
+
+
+/*
+ * Here's where our SMTP session begins its happy day.
+ */
+void smtp_greeting(int is_msa)
+{
+ char message_to_spammer[1024];
+
+ strcpy(CC->cs_clientname, "SMTP session");
+ CC->internal_pgm = 1;
+ CC->cs_flags |= CS_STEALTH;
+ SMTP = malloc(sizeof(struct citsmtp));
+ memset(SMTP, 0, sizeof(struct citsmtp));
+ SMTP->is_msa = is_msa;
+
+ /* If this config option is set, reject connections from problem
+ * addresses immediately instead of after they execute a RCPT
+ */
+ if ( (config.c_rbl_at_greeting) && (SMTP->is_msa == 0) ) {
+ if (rbl_check(message_to_spammer)) {
+ cprintf("550 %s\r\n", message_to_spammer);
+ CC->kill_me = 1;
+ /* no need to free_recipients(valid), it's not allocated yet */
+ return;
+ }
+ }
+
+ /* Otherwise we're either clean or we check later. */
+
+ if (CC->nologin==1) {
+ cprintf("500 Too many users are already online (maximum is %d)\r\n",
+ config.c_maxsessions
+ );
+ CC->kill_me = 1;
+ /* no need to free_recipients(valid), it's not allocated yet */
+ return;
+ }
+
+ /* Note: the FQDN *must* appear as the first thing after the 220 code.
+ * Some clients (including citmail.c) depend on it being there.
+ */
+ cprintf("220 %s ESMTP Citadel server ready.\r\n", config.c_fqdn);
+}
+
+
+/*
+ * SMTPS is just like SMTP, except it goes crypto right away.
+ */
+#ifdef HAVE_OPENSSL
+void smtps_greeting(void) {
+ CtdlStartTLS(NULL, NULL, NULL);
+ smtp_greeting(0);
+}
+#endif
+
+
+/*
+ * SMTP MSA port requires authentication.
+ */
+void smtp_msa_greeting(void) {
+ smtp_greeting(1);
+}
+
+
+/*
+ * LMTP is like SMTP but with some extra bonus footage added.
+ */
+void lmtp_greeting(void) {
+ smtp_greeting(0);
+ SMTP->is_lmtp = 1;
+}
+
+
+/*
+ * Generic SMTP MTA greeting
+ */
+void smtp_mta_greeting(void) {
+ smtp_greeting(0);
+}
+
+
+/*
+ * We also have an unfiltered LMTP socket that bypasses spam filters.
+ */
+void lmtp_unfiltered_greeting(void) {
+ smtp_greeting(0);
+ SMTP->is_lmtp = 1;
+ SMTP->is_unfiltered = 1;
+}
+
+
+/*
+ * Login greeting common to all auth methods
+ */
+void smtp_auth_greeting(void) {
+ cprintf("235 2.0.0 Hello, %s\r\n", CC->user.fullname);
+ lprintf(CTDL_NOTICE, "SMTP authenticated %s\n", CC->user.fullname);
+ CC->internal_pgm = 0;
+ CC->cs_flags &= ~CS_STEALTH;
+}
+
+
+/*
+ * Implement HELO and EHLO commands.
+ *
+ * which_command: 0=HELO, 1=EHLO, 2=LHLO
+ */
+void smtp_hello(char *argbuf, int which_command) {
+
+ safestrncpy(SMTP->helo_node, argbuf, sizeof SMTP->helo_node);
+
+ if ( (which_command != 2) && (SMTP->is_lmtp) ) {
+ cprintf("500 Only LHLO is allowed when running LMTP\r\n");
+ return;
+ }
+
+ if ( (which_command == 2) && (SMTP->is_lmtp == 0) ) {
+ cprintf("500 LHLO is only allowed when running LMTP\r\n");
+ return;
+ }
+
+ if (which_command == 0) {
+ cprintf("250 Hello %s (%s [%s])\r\n",
+ SMTP->helo_node,
+ CC->cs_host,
+ CC->cs_addr
+ );
+ }
+ else {
+ if (which_command == 1) {
+ cprintf("250-Hello %s (%s [%s])\r\n",
+ SMTP->helo_node,
+ CC->cs_host,
+ CC->cs_addr
+ );
+ }
+ else {
+ cprintf("250-Greetings and joyous salutations.\r\n");
+ }
+ cprintf("250-HELP\r\n");
+ cprintf("250-SIZE %ld\r\n", config.c_maxmsglen);
+
+#ifdef HAVE_OPENSSL
+
+ /* Only offer the PIPELINING command if TLS is inactive,
+ * because of flow control issues. Also, avoid offering TLS
+ * if TLS is already active. Finally, we only offer TLS on
+ * the SMTP-MSA port, not on the SMTP-MTA port, due to
+ * questionable reliability of TLS in certain sending MTA's.
+ */
+ if ( (!CC->redirect_ssl) && (SMTP->is_msa) ) {
+ cprintf("250-PIPELINING\r\n");
+ cprintf("250-STARTTLS\r\n");
+ }
+
+#else /* HAVE_OPENSSL */
+
+ /* Non SSL enabled server, so always offer PIPELINING. */
+ cprintf("250-PIPELINING\r\n");
+
+#endif /* HAVE_OPENSSL */
+
+ cprintf("250-AUTH LOGIN PLAIN\r\n");
+ cprintf("250-AUTH=LOGIN PLAIN\r\n");
+
+ cprintf("250 ENHANCEDSTATUSCODES\r\n");
+ }
+}
+
+
+
+/*
+ * Implement HELP command.
+ */
+void smtp_help(void) {
+ cprintf("214-Commands accepted:\r\n");
+ cprintf("214- DATA\r\n");
+ cprintf("214- EHLO\r\n");
+ cprintf("214- HELO\r\n");
+ cprintf("214- HELP\r\n");
+ cprintf("214- MAIL\r\n");
+ cprintf("214- NOOP\r\n");
+ cprintf("214- QUIT\r\n");
+ cprintf("214- RCPT\r\n");
+ cprintf("214- RSET\r\n");
+ cprintf("214 \r\n");
+}
+
+
+/*
+ *
+ */
+void smtp_get_user(char *argbuf) {
+ char buf[SIZ];
+ char username[SIZ];
+
+ CtdlDecodeBase64(username, argbuf, SIZ);
+ /* lprintf(CTDL_DEBUG, "Trying <%s>\n", username); */
+ if (CtdlLoginExistingUser(NULL, username) == login_ok) {
+ CtdlEncodeBase64(buf, "Password:", 9);
+ cprintf("334 %s\r\n", buf);
+ SMTP->command_state = smtp_password;
+ }
+ else {
+ cprintf("500 5.7.0 No such user.\r\n");
+ SMTP->command_state = smtp_command;
+ }
+}
+
+
+/*
+ *
+ */
+void smtp_get_pass(char *argbuf) {
+ char password[SIZ];
+
+ CtdlDecodeBase64(password, argbuf, SIZ);
+ /* lprintf(CTDL_DEBUG, "Trying <%s>\n", password); */
+ if (CtdlTryPassword(password) == pass_ok) {
+ smtp_auth_greeting();
+ }
+ else {
+ cprintf("535 5.7.0 Authentication failed.\r\n");
+ }
+ SMTP->command_state = smtp_command;
+}
+
+
+/*
+ * Back end for PLAIN auth method (either inline or multistate)
+ */
+void smtp_try_plain(char *encoded_authstring) {
+ char decoded_authstring[1024];
+ char ident[256];
+ char user[256];
+ char pass[256];
+ int result;
+
+ CtdlDecodeBase64(decoded_authstring, encoded_authstring, strlen(encoded_authstring) );
+ safestrncpy(ident, decoded_authstring, sizeof ident);
+ safestrncpy(user, &decoded_authstring[strlen(ident) + 1], sizeof user);
+ safestrncpy(pass, &decoded_authstring[strlen(ident) + strlen(user) + 2], sizeof pass);
+
+ SMTP->command_state = smtp_command;
+
+ if (strlen(ident) > 0) {
+ result = CtdlLoginExistingUser(user, ident);
+ }
+ else {
+ result = CtdlLoginExistingUser(NULL, user);
+ }
+
+ if (result == login_ok) {
+ if (CtdlTryPassword(pass) == pass_ok) {
+ smtp_auth_greeting();
+ return;
+ }
+ }
+ cprintf("504 5.7.4 Authentication failed.\r\n");
+}
+
+
+/*
+ * Attempt to perform authenticated SMTP
+ */
+void smtp_auth(char *argbuf) {
+ char username_prompt[64];
+ char method[64];
+ char encoded_authstring[1024];
+
+ if (CC->logged_in) {
+ cprintf("504 5.7.4 Already logged in.\r\n");
+ return;
+ }
+
+ extract_token(method, argbuf, 0, ' ', sizeof method);
+
+ if (!strncasecmp(method, "login", 5) ) {
+ if (strlen(argbuf) >= 7) {
+ smtp_get_user(&argbuf[6]);
+ }
+ else {
+ CtdlEncodeBase64(username_prompt, "Username:", 9);
+ cprintf("334 %s\r\n", username_prompt);
+ SMTP->command_state = smtp_user;
+ }
+ return;
+ }
+
+ if (!strncasecmp(method, "plain", 5) ) {
+ if (num_tokens(argbuf, ' ') < 2) {
+ cprintf("334 \r\n");
+ SMTP->command_state = smtp_plain;
+ return;
+ }
+
+ extract_token(encoded_authstring, argbuf, 1, ' ', sizeof encoded_authstring);
+
+ smtp_try_plain(encoded_authstring);
+ return;
+ }
+
+ if (strncasecmp(method, "login", 5) ) {
+ cprintf("504 5.7.4 Unknown authentication method.\r\n");
+ return;
+ }
+
+}
+
+
+/*
+ * Implements the RSET (reset state) command.
+ * Currently this just zeroes out the state buffer. If pointers to data
+ * allocated with malloc() are ever placed in the state buffer, we have to
+ * be sure to free() them first!
+ *
+ * Set do_response to nonzero to output the SMTP RSET response code.
+ */
+void smtp_rset(int do_response) {
+ int is_lmtp;
+ int is_unfiltered;
+
+ /*
+ * Our entire SMTP state is discarded when a RSET command is issued,
+ * but we need to preserve this one little piece of information, so
+ * we save it for later.
+ */
+ is_lmtp = SMTP->is_lmtp;
+ is_unfiltered = SMTP->is_unfiltered;
+
+ memset(SMTP, 0, sizeof(struct citsmtp));
+
+ /*
+ * It is somewhat ambiguous whether we want to log out when a RSET
+ * command is issued. Here's the code to do it. It is commented out
+ * because some clients (such as Pine) issue RSET commands before
+ * each message, but still expect to be logged in.
+ *
+ * if (CC->logged_in) {
+ * logout(CC);
+ * }
+ */
+
+ /*
+ * Reinstate this little piece of information we saved (see above).
+ */
+ SMTP->is_lmtp = is_lmtp;
+ SMTP->is_unfiltered = is_unfiltered;
+
+ if (do_response) {
+ cprintf("250 2.0.0 Zap!\r\n");
+ }
+}
+
+/*
+ * Clear out the portions of the state buffer that need to be cleared out
+ * after the DATA command finishes.
+ */
+void smtp_data_clear(void) {
+ strcpy(SMTP->from, "");
+ strcpy(SMTP->recipients, "");
+ SMTP->number_of_recipients = 0;
+ SMTP->delivery_mode = 0;
+ SMTP->message_originated_locally = 0;
+}
+
+
+
+/*
+ * Implements the "MAIL From:" command
+ */
+void smtp_mail(char *argbuf) {
+ char user[SIZ];
+ char node[SIZ];
+ char name[SIZ];
+
+ if (strlen(SMTP->from) != 0) {
+ cprintf("503 5.1.0 Only one sender permitted\r\n");
+ return;
+ }
+
+ if (strncasecmp(argbuf, "From:", 5)) {
+ cprintf("501 5.1.7 Syntax error\r\n");
+ return;
+ }
+
+ strcpy(SMTP->from, &argbuf[5]);
+ striplt(SMTP->from);
+ if (haschar(SMTP->from, '<') > 0) {
+ stripallbut(SMTP->from, '<', '>');
+ }
+
+ /* We used to reject empty sender names, until it was brought to our
+ * attention that RFC1123 5.2.9 requires that this be allowed. So now
+ * we allow it, but replace the empty string with a fake
+ * address so we don't have to contend with the empty string causing
+ * other code to fail when it's expecting something there.
+ */
+ if (strlen(SMTP->from) == 0) {
+ strcpy(SMTP->from, "someone@somewhere.org");
+ }
+
+ /* If this SMTP connection is from a logged-in user, force the 'from'
+ * to be the user's Internet e-mail address as Citadel knows it.
+ */
+ if (CC->logged_in) {
+ safestrncpy(SMTP->from, CC->cs_inet_email, sizeof SMTP->from);
+ cprintf("250 2.1.0 Sender ok <%s>\r\n", SMTP->from);
+ SMTP->message_originated_locally = 1;
+ return;
+ }
+
+ else if (SMTP->is_lmtp) {
+ /* Bypass forgery checking for LMTP */
+ }
+
+ /* Otherwise, make sure outsiders aren't trying to forge mail from
+ * this system (unless, of course, c_allow_spoofing is enabled)
+ */
+ else if (config.c_allow_spoofing == 0) {
+ process_rfc822_addr(SMTP->from, user, node, name);
+ if (CtdlHostAlias(node) != hostalias_nomatch) {
+ cprintf("550 5.7.1 "
+ "You must log in to send mail from %s\r\n",
+ node);
+ strcpy(SMTP->from, "");
+ return;
+ }
+ }
+
+ cprintf("250 2.0.0 Sender ok\r\n");
+}
+
+
+
+/*
+ * Implements the "RCPT To:" command
+ */
+void smtp_rcpt(char *argbuf) {
+ char recp[1024];
+ char message_to_spammer[SIZ];
+ struct recptypes *valid = NULL;
+
+ if (strlen(SMTP->from) == 0) {
+ cprintf("503 5.5.1 Need MAIL before RCPT\r\n");
+ return;
+ }
+
+ if (strncasecmp(argbuf, "To:", 3)) {
+ cprintf("501 5.1.7 Syntax error\r\n");
+ return;
+ }
+
+ if ( (SMTP->is_msa) && (!CC->logged_in) ) {
+ cprintf("550 5.1.8 "
+ "You must log in to send mail on this port.\r\n");
+ strcpy(SMTP->from, "");
+ return;
+ }
+
+ safestrncpy(recp, &argbuf[3], sizeof recp);
+ striplt(recp);
+ stripallbut(recp, '<', '>');
+
+ if ( (strlen(recp) + strlen(SMTP->recipients) + 1 ) >= SIZ) {
+ cprintf("452 4.5.3 Too many recipients\r\n");
+ return;
+ }
+
+ /* RBL check */
+ if ( (!CC->logged_in) /* Don't RBL authenticated users */
+ && (!SMTP->is_lmtp) ) { /* Don't RBL LMTP clients */
+ if (config.c_rbl_at_greeting == 0) { /* Don't RBL again if we already did it */
+ if (rbl_check(message_to_spammer)) {
+ cprintf("550 %s\r\n", message_to_spammer);
+ /* no need to free_recipients(valid), it's not allocated yet */
+ return;
+ }
+ }
+ }
+
+ valid = validate_recipients(recp);
+ if (valid->num_error != 0) {
+ cprintf("599 5.1.1 Error: %s\r\n", valid->errormsg);
+ free_recipients(valid);
+ return;
+ }
+
+ if (valid->num_internet > 0) {
+ if (CC->logged_in) {
+ if (CtdlCheckInternetMailPermission(&CC->user)==0) {
+ cprintf("551 5.7.1 <%s> - you do not have permission to send Internet mail\r\n", recp);
+ free_recipients(valid);
+ return;
+ }
+ }
+ }
+
+ if (valid->num_internet > 0) {
+ if ( (SMTP->message_originated_locally == 0)
+ && (SMTP->is_lmtp == 0) ) {
+ cprintf("551 5.7.1 <%s> - relaying denied\r\n", recp);
+ free_recipients(valid);
+ return;
+ }
+ }
+
+ cprintf("250 2.1.5 RCPT ok <%s>\r\n", recp);
+ if (strlen(SMTP->recipients) > 0) {
+ strcat(SMTP->recipients, ",");
+ }
+ strcat(SMTP->recipients, recp);
+ SMTP->number_of_recipients += 1;
+ if (valid != NULL) {
+ free_recipients(valid);
+ }
+}
+
+
+
+
+/*
+ * Implements the DATA command
+ */
+void smtp_data(void) {
+ char *body;
+ struct CtdlMessage *msg = NULL;
+ long msgnum = (-1L);
+ char nowstamp[SIZ];
+ struct recptypes *valid;
+ int scan_errors;
+ int i;
+ char result[SIZ];
+
+ if (strlen(SMTP->from) == 0) {
+ cprintf("503 5.5.1 Need MAIL command first.\r\n");
+ return;
+ }
+
+ if (SMTP->number_of_recipients < 1) {
+ cprintf("503 5.5.1 Need RCPT command first.\r\n");
+ return;
+ }
+
+ cprintf("354 Transmit message now - terminate with '.' by itself\r\n");
+
+ datestring(nowstamp, sizeof nowstamp, time(NULL), DATESTRING_RFC822);
+ body = malloc(4096);
+
+ if (body != NULL) snprintf(body, 4096,
+ "Received: from %s (%s [%s])\n"
+ " by %s; %s\n",
+ SMTP->helo_node,
+ CC->cs_host,
+ CC->cs_addr,
+ config.c_fqdn,
+ nowstamp);
+
+ body = CtdlReadMessageBody(".", config.c_maxmsglen, body, 1);
+ if (body == NULL) {
+ cprintf("550 5.6.5 "
+ "Unable to save message: internal error.\r\n");
+ return;
+ }
+
+ lprintf(CTDL_DEBUG, "Converting message...\n");
+ msg = convert_internet_message(body);
+
+ /* If the user is locally authenticated, FORCE the From: header to
+ * show up as the real sender. Yes, this violates the RFC standard,
+ * but IT MAKES SENSE. If you prefer strict RFC adherence over
+ * common sense, you can disable this in the configuration.
+ *
+ * We also set the "message room name" ('O' field) to MAILROOM
+ * (which is Mail> on most systems) to prevent it from getting set
+ * to something ugly like "0000058008.Sent Items>" when the message
+ * is read with a Citadel client.
+ */
+ if ( (CC->logged_in) && (config.c_rfc822_strict_from == 0) ) {
+ if (msg->cm_fields['A'] != NULL) free(msg->cm_fields['A']);
+ if (msg->cm_fields['N'] != NULL) free(msg->cm_fields['N']);
+ if (msg->cm_fields['H'] != NULL) free(msg->cm_fields['H']);
+ if (msg->cm_fields['F'] != NULL) free(msg->cm_fields['F']);
+ if (msg->cm_fields['O'] != NULL) free(msg->cm_fields['O']);
+ msg->cm_fields['A'] = strdup(CC->user.fullname);
+ msg->cm_fields['N'] = strdup(config.c_nodename);
+ msg->cm_fields['H'] = strdup(config.c_humannode);
+ msg->cm_fields['F'] = strdup(CC->cs_inet_email);
+ msg->cm_fields['O'] = strdup(MAILROOM);
+ }
+
+ /* Set the "envelope from" address */
+ if (msg->cm_fields['P'] != NULL) {
+ free(msg->cm_fields['P']);
+ }
+ msg->cm_fields['P'] = strdup(SMTP->from);
+
+ /* Set the "envelope to" address */
+ if (msg->cm_fields['V'] != NULL) {
+ free(msg->cm_fields['V']);
+ }
+ msg->cm_fields['V'] = strdup(SMTP->recipients);
+
+ /* Submit the message into the Citadel system. */
+ valid = validate_recipients(SMTP->recipients);
+
+ /* If there are modules that want to scan this message before final
+ * submission (such as virus checkers or spam filters), call them now
+ * and give them an opportunity to reject the message.
+ */
+ if (SMTP->is_unfiltered) {
+ scan_errors = 0;
+ }
+ else {
+ scan_errors = PerformMessageHooks(msg, EVT_SMTPSCAN);
+ }
+
+ if (scan_errors > 0) { /* We don't want this message! */
+
+ if (msg->cm_fields['0'] == NULL) {
+ msg->cm_fields['0'] = strdup(
+ "5.7.1 Message rejected by filter");
+ }
+
+ sprintf(result, "550 %s\r\n", msg->cm_fields['0']);
+ }
+
+ else { /* Ok, we'll accept this message. */
+ msgnum = CtdlSubmitMsg(msg, valid, "");
+ if (msgnum > 0L) {
+ sprintf(result, "250 2.0.0 Message accepted.\r\n");
+ }
+ else {
+ sprintf(result, "550 5.5.0 Internal delivery error\r\n");
+ }
+ }
+
+ /* For SMTP and ESTMP, just print the result message. For LMTP, we
+ * have to print one result message for each recipient. Since there
+ * is nothing in Citadel which would cause different recipients to
+ * have different results, we can get away with just spitting out the
+ * same message once for each recipient.
+ */
+ if (SMTP->is_lmtp) {
+ for (i=0; i<SMTP->number_of_recipients; ++i) {
+ cprintf("%s", result);
+ }
+ }
+ else {
+ cprintf("%s", result);
+ }
+
+ /* Write something to the syslog (which may or may not be where the
+ * rest of the Citadel logs are going; some sysadmins want LOG_MAIL).
+ */
+ if (enable_syslog) {
+ syslog((LOG_MAIL | LOG_INFO),
+ "%ld: from=<%s>, nrcpts=%d, relay=%s [%s], stat=%s",
+ msgnum,
+ SMTP->from,
+ SMTP->number_of_recipients,
+ CC->cs_host,
+ CC->cs_addr,
+ result
+ );
+ }
+
+ /* Clean up */
+ CtdlFreeMessage(msg);
+ free_recipients(valid);
+ smtp_data_clear(); /* clear out the buffers now */
+}
+
+
+/*
+ * implements the STARTTLS command (Citadel API version)
+ */
+#ifdef HAVE_OPENSSL
+void smtp_starttls(void)
+{
+ char ok_response[SIZ];
+ char nosup_response[SIZ];
+ char error_response[SIZ];
+
+ sprintf(ok_response,
+ "200 2.0.0 Begin TLS negotiation now\r\n");
+ sprintf(nosup_response,
+ "554 5.7.3 TLS not supported here\r\n");
+ sprintf(error_response,
+ "554 5.7.3 Internal error\r\n");
+ CtdlStartTLS(ok_response, nosup_response, error_response);
+ smtp_rset(0);
+}
+#endif
+
+
+
+/*
+ * Main command loop for SMTP sessions.
+ */
+void smtp_command_loop(void) {
+ char cmdbuf[SIZ];
+
+ time(&CC->lastcmd);
+ memset(cmdbuf, 0, sizeof cmdbuf); /* Clear it, just in case */
+ if (client_getln(cmdbuf, sizeof cmdbuf) < 1) {
+ lprintf(CTDL_CRIT, "Client disconnected: ending session.\n");
+ CC->kill_me = 1;
+ return;
+ }
+ lprintf(CTDL_INFO, "SMTP: %s\n", cmdbuf);
+ while (strlen(cmdbuf) < 5) strcat(cmdbuf, " ");
+
+ if (SMTP->command_state == smtp_user) {
+ smtp_get_user(cmdbuf);
+ }
+
+ else if (SMTP->command_state == smtp_password) {
+ smtp_get_pass(cmdbuf);
+ }
+
+ else if (SMTP->command_state == smtp_plain) {
+ smtp_try_plain(cmdbuf);
+ }
+
+ else if (!strncasecmp(cmdbuf, "AUTH", 4)) {
+ smtp_auth(&cmdbuf[5]);
+ }
+
+ else if (!strncasecmp(cmdbuf, "DATA", 4)) {
+ smtp_data();
+ }
+
+ else if (!strncasecmp(cmdbuf, "HELO", 4)) {
+ smtp_hello(&cmdbuf[5], 0);
+ }
+
+ else if (!strncasecmp(cmdbuf, "EHLO", 4)) {
+ smtp_hello(&cmdbuf[5], 1);
+ }
+
+ else if (!strncasecmp(cmdbuf, "LHLO", 4)) {
+ smtp_hello(&cmdbuf[5], 2);
+ }
+
+ else if (!strncasecmp(cmdbuf, "HELP", 4)) {
+ smtp_help();
+ }
+
+ else if (!strncasecmp(cmdbuf, "MAIL", 4)) {
+ smtp_mail(&cmdbuf[5]);
+ }
+
+ else if (!strncasecmp(cmdbuf, "NOOP", 4)) {
+ cprintf("250 NOOP\r\n");
+ }
+
+ else if (!strncasecmp(cmdbuf, "QUIT", 4)) {
+ cprintf("221 Goodbye...\r\n");
+ CC->kill_me = 1;
+ return;
+ }
+
+ else if (!strncasecmp(cmdbuf, "RCPT", 4)) {
+ smtp_rcpt(&cmdbuf[5]);
+ }
+
+ else if (!strncasecmp(cmdbuf, "RSET", 4)) {
+ smtp_rset(1);
+ }
+#ifdef HAVE_OPENSSL
+ else if (!strcasecmp(cmdbuf, "STARTTLS")) {
+ smtp_starttls();
+ }
+#endif
+ else {
+ cprintf("502 5.0.0 I'm afraid I can't do that.\r\n");
+ }
+
+
+}
+
+
+
+
+/*****************************************************************************/
+/* SMTP CLIENT (OUTBOUND PROCESSING) STUFF */
+/*****************************************************************************/
+
+
+
+/*
+ * smtp_try()
+ *
+ * Called by smtp_do_procmsg() to attempt delivery to one SMTP host
+ *
+ */
+void smtp_try(const char *key, const char *addr, int *status,
+ char *dsn, size_t n, long msgnum)
+{
+ int sock = (-1);
+ char mxhosts[1024];
+ int num_mxhosts;
+ int mx;
+ int i;
+ char user[1024], node[1024], name[1024];
+ char buf[1024];
+ char mailfrom[1024];
+ char mx_user[256];
+ char mx_pass[256];
+ char mx_host[256];
+ char mx_port[256];
+ int lp, rp;
+ char *msgtext;
+ char *ptr;
+ size_t msg_size;
+ int scan_done;
+
+ /* Parse out the host portion of the recipient address */
+ process_rfc822_addr(addr, user, node, name);
+
+ lprintf(CTDL_DEBUG, "Attempting SMTP delivery to <%s> @ <%s> (%s)\n",
+ user, node, name);
+
+ /* Load the message out of the database */
+ CC->redirect_buffer = malloc(SIZ);
+ CC->redirect_len = 0;
+ CC->redirect_alloc = SIZ;
+ CtdlOutputMsg(msgnum, MT_RFC822, HEADERS_ALL, 0, 1, NULL);
+ msgtext = CC->redirect_buffer;
+ msg_size = CC->redirect_len;
+ CC->redirect_buffer = NULL;
+ CC->redirect_len = 0;
+ CC->redirect_alloc = 0;
+
+ /* Extract something to send later in the 'MAIL From:' command */
+ strcpy(mailfrom, "");
+ scan_done = 0;
+ ptr = msgtext;
+ do {
+ if (ptr = memreadline(ptr, buf, sizeof buf), *ptr == 0) {
+ scan_done = 1;
+ }
+ if (!strncasecmp(buf, "From:", 5)) {
+ safestrncpy(mailfrom, &buf[5], sizeof mailfrom);
+ striplt(mailfrom);
+ for (i=0; i<strlen(mailfrom); ++i) {
+ if (!isprint(mailfrom[i])) {
+ strcpy(&mailfrom[i], &mailfrom[i+1]);
+ i=0;
+ }
+ }
+
+ /* Strip out parenthesized names */
+ lp = (-1);
+ rp = (-1);
+ for (i=0; i<strlen(mailfrom); ++i) {
+ if (mailfrom[i] == '(') lp = i;
+ if (mailfrom[i] == ')') rp = i;
+ }
+ if ((lp>0)&&(rp>lp)) {
+ strcpy(&mailfrom[lp-1], &mailfrom[rp+1]);
+ }
+
+ /* Prefer brokketized names */
+ lp = (-1);
+ rp = (-1);
+ for (i=0; i<strlen(mailfrom); ++i) {
+ if (mailfrom[i] == '<') lp = i;
+ if (mailfrom[i] == '>') rp = i;
+ }
+ if ( (lp>=0) && (rp>lp) ) {
+ mailfrom[rp] = 0;
+ strcpy(mailfrom, &mailfrom[lp]);
+ }
+
+ scan_done = 1;
+ }
+ } while (scan_done == 0);
+ if (strlen(mailfrom)==0) strcpy(mailfrom, "someone@somewhere.org");
+ stripallbut(mailfrom, '<', '>');
+
+ /* Figure out what mail exchanger host we have to connect to */
+ num_mxhosts = getmx(mxhosts, node);
+ lprintf(CTDL_DEBUG, "Number of MX hosts for <%s> is %d\n", node, num_mxhosts);
+ if (num_mxhosts < 1) {
+ *status = 5;
+ snprintf(dsn, SIZ, "No MX hosts found for <%s>", node);
+ return;
+ }
+
+ sock = (-1);
+ for (mx=0; (mx<num_mxhosts && sock < 0); ++mx) {
+ char *endpart;
+ extract_token(buf, mxhosts, mx, '|', sizeof buf);
+ strcpy(mx_user, "");
+ strcpy(mx_pass, "");
+ if (num_tokens(buf, '@') > 1) {
+ strcpy (mx_user, buf);
+ endpart = strrchr(mx_user, '@');
+ *endpart = '\0';
+ strcpy (mx_host, endpart + 1);
+ endpart = strrchr(mx_user, ':');
+ if (endpart != NULL) {
+ strcpy(mx_pass, endpart+1);
+ *endpart = '\0';
+ }
+ }
+ else
+ strcpy (mx_host, buf);
+ endpart = strrchr(mx_host, ':');
+ if (endpart != 0){
+ *endpart = '\0';
+ strcpy(mx_port, endpart + 1);
+ }
+ else {
+ strcpy(mx_port, "25");
+ }
+ lprintf(CTDL_DEBUG, "Trying %s : %s ...\n", mx_host, mx_port);
+ sock = sock_connect(mx_host, mx_port, "tcp");
+ snprintf(dsn, SIZ, "Could not connect: %s", strerror(errno));
+ if (sock >= 0) lprintf(CTDL_DEBUG, "Connected!\n");
+ if (sock < 0) {
+ if (errno > 0) {
+ snprintf(dsn, SIZ, "%s", strerror(errno));
+ }
+ else {
+ snprintf(dsn, SIZ, "Unable to connect to %s : %s\n", mx_host, mx_port);
+ }
+ }
+ }
+
+ if (sock < 0) {
+ *status = 4; /* dsn is already filled in */
+ return;
+ }
+
+ /* Process the SMTP greeting from the server */
+ if (ml_sock_gets(sock, buf) < 0) {
+ *status = 4;
+ strcpy(dsn, "Connection broken during SMTP conversation");
+ goto bail;
+ }
+ lprintf(CTDL_DEBUG, "<%s\n", buf);
+ if (buf[0] != '2') {
+ if (buf[0] == '4') {
+ *status = 4;
+ safestrncpy(dsn, &buf[4], 1023);
+ goto bail;
+ }
+ else {
+ *status = 5;
+ safestrncpy(dsn, &buf[4], 1023);
+ goto bail;
+ }
+ }
+
+ /* At this point we know we are talking to a real SMTP server */
+
+ /* Do a EHLO command. If it fails, try the HELO command. */
+ snprintf(buf, sizeof buf, "EHLO %s\r\n", config.c_fqdn);
+ lprintf(CTDL_DEBUG, ">%s", buf);
+ sock_write(sock, buf, strlen(buf));
+ if (ml_sock_gets(sock, buf) < 0) {
+ *status = 4;
+ strcpy(dsn, "Connection broken during SMTP HELO");
+ goto bail;
+ }
+ lprintf(CTDL_DEBUG, "<%s\n", buf);
+ if (buf[0] != '2') {
+ snprintf(buf, sizeof buf, "HELO %s\r\n", config.c_fqdn);
+ lprintf(CTDL_DEBUG, ">%s", buf);
+ sock_write(sock, buf, strlen(buf));
+ if (ml_sock_gets(sock, buf) < 0) {
+ *status = 4;
+ strcpy(dsn, "Connection broken during SMTP HELO");
+ goto bail;
+ }
+ }
+ if (buf[0] != '2') {
+ if (buf[0] == '4') {
+ *status = 4;
+ safestrncpy(dsn, &buf[4], 1023);
+ goto bail;
+ }
+ else {
+ *status = 5;
+ safestrncpy(dsn, &buf[4], 1023);
+ goto bail;
+ }
+ }
+
+ /* Do an AUTH command if necessary */
+ if (strlen(mx_user) > 0) {
+ char encoded[1024];
+ sprintf(buf, "%s%c%s%c%s", mx_user, '\0', mx_user, '\0', mx_pass);
+ CtdlEncodeBase64(encoded, buf, strlen(mx_user) + strlen(mx_user) + strlen(mx_pass) + 2);
+ snprintf(buf, sizeof buf, "AUTH PLAIN %s\r\n", encoded);
+ lprintf(CTDL_DEBUG, ">%s", buf);
+ sock_write(sock, buf, strlen(buf));
+ if (ml_sock_gets(sock, buf) < 0) {
+ *status = 4;
+ strcpy(dsn, "Connection broken during SMTP AUTH");
+ goto bail;
+ }
+ lprintf(CTDL_DEBUG, "<%s\n", buf);
+ if (buf[0] != '2') {
+ if (buf[0] == '4') {
+ *status = 4;
+ safestrncpy(dsn, &buf[4], 1023);
+ goto bail;
+ }
+ else {
+ *status = 5;
+ safestrncpy(dsn, &buf[4], 1023);
+ goto bail;
+ }
+ }
+ }
+
+ /* previous command succeeded, now try the MAIL From: command */
+ snprintf(buf, sizeof buf, "MAIL From: <%s>\r\n", mailfrom);
+ lprintf(CTDL_DEBUG, ">%s", buf);
+ sock_write(sock, buf, strlen(buf));
+ if (ml_sock_gets(sock, buf) < 0) {
+ *status = 4;
+ strcpy(dsn, "Connection broken during SMTP MAIL");
+ goto bail;
+ }
+ lprintf(CTDL_DEBUG, "<%s\n", buf);
+ if (buf[0] != '2') {
+ if (buf[0] == '4') {
+ *status = 4;
+ safestrncpy(dsn, &buf[4], 1023);
+ goto bail;
+ }
+ else {
+ *status = 5;
+ safestrncpy(dsn, &buf[4], 1023);
+ goto bail;
+ }
+ }
+
+ /* MAIL succeeded, now try the RCPT To: command */
+ snprintf(buf, sizeof buf, "RCPT To: <%s@%s>\r\n", user, node);
+ lprintf(CTDL_DEBUG, ">%s", buf);
+ sock_write(sock, buf, strlen(buf));
+ if (ml_sock_gets(sock, buf) < 0) {
+ *status = 4;
+ strcpy(dsn, "Connection broken during SMTP RCPT");
+ goto bail;
+ }
+ lprintf(CTDL_DEBUG, "<%s\n", buf);
+ if (buf[0] != '2') {
+ if (buf[0] == '4') {
+ *status = 4;
+ safestrncpy(dsn, &buf[4], 1023);
+ goto bail;
+ }
+ else {
+ *status = 5;
+ safestrncpy(dsn, &buf[4], 1023);
+ goto bail;
+ }
+ }
+
+ /* RCPT succeeded, now try the DATA command */
+ lprintf(CTDL_DEBUG, ">DATA\n");
+ sock_write(sock, "DATA\r\n", 6);
+ if (ml_sock_gets(sock, buf) < 0) {
+ *status = 4;
+ strcpy(dsn, "Connection broken during SMTP DATA");
+ goto bail;
+ }
+ lprintf(CTDL_DEBUG, "<%s\n", buf);
+ if (buf[0] != '3') {
+ if (buf[0] == '4') {
+ *status = 3;
+ safestrncpy(dsn, &buf[4], 1023);
+ goto bail;
+ }
+ else {
+ *status = 5;
+ safestrncpy(dsn, &buf[4], 1023);
+ goto bail;
+ }
+ }
+
+ /* If we reach this point, the server is expecting data */
+ sock_write(sock, msgtext, msg_size);
+ if (msgtext[msg_size-1] != 10) {
+ lprintf(CTDL_WARNING, "Possible problem: message did not "
+ "correctly terminate. (expecting 0x10, got 0x%02x)\n",
+ buf[msg_size-1]);
+ }
+
+ sock_write(sock, ".\r\n", 3);
+ if (ml_sock_gets(sock, buf) < 0) {
+ *status = 4;
+ strcpy(dsn, "Connection broken during SMTP message transmit");
+ goto bail;
+ }
+ lprintf(CTDL_DEBUG, "%s\n", buf);
+ if (buf[0] != '2') {
+ if (buf[0] == '4') {
+ *status = 4;
+ safestrncpy(dsn, &buf[4], 1023);
+ goto bail;
+ }
+ else {
+ *status = 5;
+ safestrncpy(dsn, &buf[4], 1023);
+ goto bail;
+ }
+ }
+
+ /* We did it! */
+ safestrncpy(dsn, &buf[4], 1023);
+ *status = 2;
+
+ lprintf(CTDL_DEBUG, ">QUIT\n");
+ sock_write(sock, "QUIT\r\n", 6);
+ ml_sock_gets(sock, buf);
+ lprintf(CTDL_DEBUG, "<%s\n", buf);
+ lprintf(CTDL_INFO, "SMTP delivery to <%s> @ <%s> (%s) succeeded\n",
+ user, node, name);
+
+bail: free(msgtext);
+ sock_close(sock);
+
+ /* Write something to the syslog (which may or may not be where the
+ * rest of the Citadel logs are going; some sysadmins want LOG_MAIL).
+ */
+ if (enable_syslog) {
+ syslog((LOG_MAIL | LOG_INFO),
+ "%ld: to=<%s>, relay=%s, stat=%s",
+ msgnum,
+ addr,
+ mx_host,
+ dsn
+ );
+ }
+
+ return;
+}
+
+
+
+/*
+ * smtp_do_bounce() is caled by smtp_do_procmsg() to scan a set of delivery
+ * instructions for "5" codes (permanent fatal errors) and produce/deliver
+ * a "bounce" message (delivery status notification).
+ */
+void smtp_do_bounce(char *instr) {
+ int i;
+ int lines;
+ int status;
+ char buf[1024];
+ char key[1024];
+ char addr[1024];
+ char dsn[1024];
+ char bounceto[1024];
+ char boundary[64];
+ int num_bounces = 0;
+ int bounce_this = 0;
+ long bounce_msgid = (-1);
+ time_t submitted = 0L;
+ struct CtdlMessage *bmsg = NULL;
+ int give_up = 0;
+ struct recptypes *valid;
+ int successful_bounce = 0;
+ static int seq = 0;
+ char *omsgtext;
+ size_t omsgsize;
+ long omsgid = (-1);
+
+ lprintf(CTDL_DEBUG, "smtp_do_bounce() called\n");
+ strcpy(bounceto, "");
+ sprintf(boundary, "=_Citadel_Multipart_%s_%04x%04x", config.c_fqdn, getpid(), ++seq);
+ lines = num_tokens(instr, '\n');
+
+ /* See if it's time to give up on delivery of this message */
+ for (i=0; i<lines; ++i) {
+ extract_token(buf, instr, i, '\n', sizeof buf);
+ extract_token(key, buf, 0, '|', sizeof key);
+ extract_token(addr, buf, 1, '|', sizeof addr);
+ if (!strcasecmp(key, "submitted")) {
+ submitted = atol(addr);
+ }
+ }
+
+ if ( (time(NULL) - submitted) > SMTP_GIVE_UP ) {
+ give_up = 1;
+ }
+
+ /* Start building our bounce message */
+
+ bmsg = (struct CtdlMessage *) malloc(sizeof(struct CtdlMessage));
+ if (bmsg == NULL) return;
+ memset(bmsg, 0, sizeof(struct CtdlMessage));
+
+ bmsg->cm_magic = CTDLMESSAGE_MAGIC;
+ bmsg->cm_anon_type = MES_NORMAL;
+ bmsg->cm_format_type = FMT_RFC822;
+ bmsg->cm_fields['A'] = strdup("Citadel");
+ bmsg->cm_fields['O'] = strdup(MAILROOM);
+ bmsg->cm_fields['N'] = strdup(config.c_nodename);
+ bmsg->cm_fields['U'] = strdup("Delivery Status Notification (Failure)");
+ bmsg->cm_fields['M'] = malloc(1024);
+
+ strcpy(bmsg->cm_fields['M'], "Content-type: multipart/mixed; boundary=\"");
+ strcat(bmsg->cm_fields['M'], boundary);
+ strcat(bmsg->cm_fields['M'], "\"\r\n");
+ strcat(bmsg->cm_fields['M'], "MIME-Version: 1.0\r\n");
+ strcat(bmsg->cm_fields['M'], "X-Mailer: " CITADEL "\r\n");
+ strcat(bmsg->cm_fields['M'], "\r\nThis is a multipart message in MIME format.\r\n\r\n");
+ strcat(bmsg->cm_fields['M'], "--");
+ strcat(bmsg->cm_fields['M'], boundary);
+ strcat(bmsg->cm_fields['M'], "\r\n");
+ strcat(bmsg->cm_fields['M'], "Content-type: text/plain\r\n\r\n");
+
+ if (give_up) strcat(bmsg->cm_fields['M'],
+"A message you sent could not be delivered to some or all of its recipients\n"
+"due to prolonged unavailability of its destination(s).\n"
+"Giving up on the following addresses:\n\n"
+);
+
+ else strcat(bmsg->cm_fields['M'],
+"A message you sent could not be delivered to some or all of its recipients.\n"
+"The following addresses were undeliverable:\n\n"
+);
+
+ /*
+ * Now go through the instructions checking for stuff.
+ */
+ for (i=0; i<lines; ++i) {
+ extract_token(buf, instr, i, '\n', sizeof buf);
+ extract_token(key, buf, 0, '|', sizeof key);
+ extract_token(addr, buf, 1, '|', sizeof addr);
+ status = extract_int(buf, 2);
+ extract_token(dsn, buf, 3, '|', sizeof dsn);
+ bounce_this = 0;
+
+ lprintf(CTDL_DEBUG, "key=<%s> addr=<%s> status=%d dsn=<%s>\n",
+ key, addr, status, dsn);
+
+ if (!strcasecmp(key, "bounceto")) {
+ strcpy(bounceto, addr);
+ }
+
+ if (!strcasecmp(key, "msgid")) {
+ omsgid = atol(addr);
+ }
+
+ if (!strcasecmp(key, "remote")) {
+ if (status == 5) bounce_this = 1;
+ if (give_up) bounce_this = 1;
+ }
+
+ if (bounce_this) {
+ ++num_bounces;
+
+ if (bmsg->cm_fields['M'] == NULL) {
+ lprintf(CTDL_ERR, "ERROR ... M field is null "
+ "(%s:%d)\n", __FILE__, __LINE__);
+ }
+
+ bmsg->cm_fields['M'] = realloc(bmsg->cm_fields['M'],
+ strlen(bmsg->cm_fields['M']) + 1024 );
+ strcat(bmsg->cm_fields['M'], addr);
+ strcat(bmsg->cm_fields['M'], ": ");
+ strcat(bmsg->cm_fields['M'], dsn);
+ strcat(bmsg->cm_fields['M'], "\r\n");
+
+ remove_token(instr, i, '\n');
+ --i;
+ --lines;
+ }
+ }
+
+ /* Attach the original message */
+ if (omsgid >= 0) {
+ strcat(bmsg->cm_fields['M'], "--");
+ strcat(bmsg->cm_fields['M'], boundary);
+ strcat(bmsg->cm_fields['M'], "\r\n");
+ strcat(bmsg->cm_fields['M'], "Content-type: message/rfc822\r\n");
+ strcat(bmsg->cm_fields['M'], "Content-Transfer-Encoding: 7bit\r\n");
+ strcat(bmsg->cm_fields['M'], "Content-Disposition: inline\r\n");
+ strcat(bmsg->cm_fields['M'], "\r\n");
+
+ CC->redirect_buffer = malloc(SIZ);
+ CC->redirect_len = 0;
+ CC->redirect_alloc = SIZ;
+ CtdlOutputMsg(omsgid, MT_RFC822, HEADERS_ALL, 0, 1, NULL);
+ omsgtext = CC->redirect_buffer;
+ omsgsize = CC->redirect_len;
+ CC->redirect_buffer = NULL;
+ CC->redirect_len = 0;
+ CC->redirect_alloc = 0;
+ bmsg->cm_fields['M'] = realloc(bmsg->cm_fields['M'],
+ (strlen(bmsg->cm_fields['M']) + omsgsize + 1024) );
+ strcat(bmsg->cm_fields['M'], omsgtext);
+ free(omsgtext);
+ }
+
+ /* Close the multipart MIME scope */
+ strcat(bmsg->cm_fields['M'], "--");
+ strcat(bmsg->cm_fields['M'], boundary);
+ strcat(bmsg->cm_fields['M'], "--\r\n");
+
+ /* Deliver the bounce if there's anything worth mentioning */
+ lprintf(CTDL_DEBUG, "num_bounces = %d\n", num_bounces);
+ if (num_bounces > 0) {
+
+ /* First try the user who sent the message */
+ lprintf(CTDL_DEBUG, "bounce to user? <%s>\n", bounceto);
+ if (strlen(bounceto) == 0) {
+ lprintf(CTDL_ERR, "No bounce address specified\n");
+ bounce_msgid = (-1L);
+ }
+
+ /* Can we deliver the bounce to the original sender? */
+ valid = validate_recipients(bounceto);
+ if (valid != NULL) {
+ if (valid->num_error == 0) {
+ CtdlSubmitMsg(bmsg, valid, "");
+ successful_bounce = 1;
+ }
+ }
+
+ /* If not, post it in the Aide> room */
+ if (successful_bounce == 0) {
+ CtdlSubmitMsg(bmsg, NULL, config.c_aideroom);
+ }
+
+ /* Free up the memory we used */
+ if (valid != NULL) {
+ free_recipients(valid);
+ }
+ }
+
+ CtdlFreeMessage(bmsg);
+ lprintf(CTDL_DEBUG, "Done processing bounces\n");
+}
+
+
+/*
+ * smtp_purge_completed_deliveries() is caled by smtp_do_procmsg() to scan a
+ * set of delivery instructions for completed deliveries and remove them.
+ *
+ * It returns the number of incomplete deliveries remaining.
+ */
+int smtp_purge_completed_deliveries(char *instr) {
+ int i;
+ int lines;
+ int status;
+ char buf[1024];
+ char key[1024];
+ char addr[1024];
+ char dsn[1024];
+ int completed;
+ int incomplete = 0;
+
+ lines = num_tokens(instr, '\n');
+ for (i=0; i<lines; ++i) {
+ extract_token(buf, instr, i, '\n', sizeof buf);
+ extract_token(key, buf, 0, '|', sizeof key);
+ extract_token(addr, buf, 1, '|', sizeof addr);
+ status = extract_int(buf, 2);
+ extract_token(dsn, buf, 3, '|', sizeof dsn);
+
+ completed = 0;
+
+ if (!strcasecmp(key, "remote")) {
+ if (status == 2) completed = 1;
+ else ++incomplete;
+ }
+
+ if (completed) {
+ remove_token(instr, i, '\n');
+ --i;
+ --lines;
+ }
+ }
+
+ return(incomplete);
+}
+
+
+/*
+ * smtp_do_procmsg()
+ *
+ * Called by smtp_do_queue() to handle an individual message.
+ */
+void smtp_do_procmsg(long msgnum, void *userdata) {
+ struct CtdlMessage *msg = NULL;
+ char *instr = NULL;
+ char *results = NULL;
+ int i;
+ int lines;
+ int status;
+ char buf[1024];
+ char key[1024];
+ char addr[1024];
+ char dsn[1024];
+ long text_msgid = (-1);
+ int incomplete_deliveries_remaining;
+ time_t attempted = 0L;
+ time_t last_attempted = 0L;
+ time_t retry = SMTP_RETRY_INTERVAL;
+
+ lprintf(CTDL_DEBUG, "smtp_do_procmsg(%ld)\n", msgnum);
+
+ msg = CtdlFetchMessage(msgnum, 1);
+ if (msg == NULL) {
+ lprintf(CTDL_ERR, "SMTP: tried %ld but no such message!\n", msgnum);
+ return;
+ }
+
+ instr = strdup(msg->cm_fields['M']);
+ CtdlFreeMessage(msg);
+
+ /* Strip out the headers amd any other non-instruction line */
+ lines = num_tokens(instr, '\n');
+ for (i=0; i<lines; ++i) {
+ extract_token(buf, instr, i, '\n', sizeof buf);
+ if (num_tokens(buf, '|') < 2) {
+ remove_token(instr, i, '\n');
+ --lines;
+ --i;
+ }
+ }
+
+ /* Learn the message ID and find out about recent delivery attempts */
+ lines = num_tokens(instr, '\n');
+ for (i=0; i<lines; ++i) {
+ extract_token(buf, instr, i, '\n', sizeof buf);
+ extract_token(key, buf, 0, '|', sizeof key);
+ if (!strcasecmp(key, "msgid")) {
+ text_msgid = extract_long(buf, 1);
+ }
+ if (!strcasecmp(key, "retry")) {
+ /* double the retry interval after each attempt */
+ retry = extract_long(buf, 1) * 2L;
+ if (retry > SMTP_RETRY_MAX) {
+ retry = SMTP_RETRY_MAX;
+ }
+ remove_token(instr, i, '\n');
+ }
+ if (!strcasecmp(key, "attempted")) {
+ attempted = extract_long(buf, 1);
+ if (attempted > last_attempted)
+ last_attempted = attempted;
+ }
+ }
+
+ /*
+ * Postpone delivery if we've already tried recently.
+ */
+ if (((time(NULL) - last_attempted) < retry) && (run_queue_now == 0)) {
+ lprintf(CTDL_DEBUG, "Retry time not yet reached.\n");
+ free(instr);
+ return;
+ }
+
+
+ /*
+ * Bail out if there's no actual message associated with this
+ */
+ if (text_msgid < 0L) {
+ lprintf(CTDL_ERR, "SMTP: no 'msgid' directive found!\n");
+ free(instr);
+ return;
+ }
+
+ /* Plow through the instructions looking for 'remote' directives and
+ * a status of 0 (no delivery yet attempted) or 3/4 (transient errors
+ * were experienced and it's time to try again)
+ */
+ lines = num_tokens(instr, '\n');
+ for (i=0; i<lines; ++i) {
+ extract_token(buf, instr, i, '\n', sizeof buf);
+ extract_token(key, buf, 0, '|', sizeof key);
+ extract_token(addr, buf, 1, '|', sizeof addr);
+ status = extract_int(buf, 2);
+ extract_token(dsn, buf, 3, '|', sizeof dsn);
+ if ( (!strcasecmp(key, "remote"))
+ && ((status==0)||(status==3)||(status==4)) ) {
+
+ /* Remove this "remote" instruction from the set,
+ * but replace the set's final newline if
+ * remove_token() stripped it. It has to be there.
+ */
+ remove_token(instr, i, '\n');
+ if (instr[strlen(instr)-1] != '\n') {
+ strcat(instr, "\n");
+ }
+
+ --i;
+ --lines;
+ lprintf(CTDL_DEBUG, "SMTP: Trying <%s>\n", addr);
+ smtp_try(key, addr, &status, dsn, sizeof dsn, text_msgid);
+ if (status != 2) {
+ if (results == NULL) {
+ results = malloc(1024);
+ memset(results, 0, 1024);
+ }
+ else {
+ results = realloc(results,
+ strlen(results) + 1024);
+ }
+ snprintf(&results[strlen(results)], 1024,
+ "%s|%s|%d|%s\n",
+ key, addr, status, dsn);
+ }
+ }
+ }
+
+ if (results != NULL) {
+ instr = realloc(instr, strlen(instr) + strlen(results) + 2);
+ strcat(instr, results);
+ free(results);
+ }
+
+
+ /* Generate 'bounce' messages */
+ smtp_do_bounce(instr);
+
+ /* Go through the delivery list, deleting completed deliveries */
+ incomplete_deliveries_remaining =
+ smtp_purge_completed_deliveries(instr);
+
+
+ /*
+ * No delivery instructions remain, so delete both the instructions
+ * message and the message message.
+ */
+ if (incomplete_deliveries_remaining <= 0) {
+ long delmsgs[2];
+ delmsgs[0] = msgnum;
+ delmsgs[1] = text_msgid;
+ CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, delmsgs, 2, "");
+ }
+
+ /*
+ * Uncompleted delivery instructions remain, so delete the old
+ * instructions and replace with the updated ones.
+ */
+ if (incomplete_deliveries_remaining > 0) {
+ CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, &msgnum, 1, "");
+ msg = malloc(sizeof(struct CtdlMessage));
+ memset(msg, 0, sizeof(struct CtdlMessage));
+ msg->cm_magic = CTDLMESSAGE_MAGIC;
+ msg->cm_anon_type = MES_NORMAL;
+ msg->cm_format_type = FMT_RFC822;
+ msg->cm_fields['M'] = malloc(strlen(instr)+SIZ);
+ snprintf(msg->cm_fields['M'],
+ strlen(instr)+SIZ,
+ "Content-type: %s\n\n%s\n"
+ "attempted|%ld\n"
+ "retry|%ld\n",
+ SPOOLMIME, instr, (long)time(NULL), (long)retry );
+ CtdlSubmitMsg(msg, NULL, SMTP_SPOOLOUT_ROOM);
+ CtdlFreeMessage(msg);
+ }
+
+ free(instr);
+}
+
+
+
+/*
+ * smtp_do_queue()
+ *
+ * Run through the queue sending out messages.
+ */
+void smtp_do_queue(void) {
+ static int doing_queue = 0;
+
+ /*
+ * This is a simple concurrency check to make sure only one queue run
+ * is done at a time. We could do this with a mutex, but since we
+ * don't really require extremely fine granularity here, we'll do it
+ * with a static variable instead.
+ */
+ if (doing_queue) return;
+ doing_queue = 1;
+
+ /*
+ * Go ahead and run the queue
+ */
+ lprintf(CTDL_INFO, "SMTP: processing outbound queue\n");
+
+ if (getroom(&CC->room, SMTP_SPOOLOUT_ROOM) != 0) {
+ lprintf(CTDL_ERR, "Cannot find room <%s>\n", SMTP_SPOOLOUT_ROOM);
+ return;
+ }
+ CtdlForEachMessage(MSGS_ALL, 0L, NULL,
+ SPOOLMIME, NULL, smtp_do_procmsg, NULL);
+
+ lprintf(CTDL_INFO, "SMTP: queue run completed\n");
+ run_queue_now = 0;
+ doing_queue = 0;
+}
+
+
+
+/*****************************************************************************/
+/* SMTP UTILITY COMMANDS */
+/*****************************************************************************/
+
+void cmd_smtp(char *argbuf) {
+ char cmd[64];
+ char node[256];
+ char buf[1024];
+ int i;
+ int num_mxhosts;
+
+ if (CtdlAccessCheck(ac_aide)) return;
+
+ extract_token(cmd, argbuf, 0, '|', sizeof cmd);
+
+ if (!strcasecmp(cmd, "mx")) {
+ extract_token(node, argbuf, 1, '|', sizeof node);
+ num_mxhosts = getmx(buf, node);
+ cprintf("%d %d MX hosts listed for %s\n",
+ LISTING_FOLLOWS, num_mxhosts, node);
+ for (i=0; i<num_mxhosts; ++i) {
+ extract_token(node, buf, i, '|', sizeof node);
+ cprintf("%s\n", node);
+ }
+ cprintf("000\n");
+ return;
+ }
+
+ else if (!strcasecmp(cmd, "runqueue")) {
+ run_queue_now = 1;
+ cprintf("%d All outbound SMTP will be retried now.\n", CIT_OK);
+ return;
+ }
+
+ else {
+ cprintf("%d Invalid command.\n", ERROR + ILLEGAL_VALUE);
+ }
+
+}
+
+
+/*
+ * Initialize the SMTP outbound queue
+ */
+void smtp_init_spoolout(void) {
+ struct ctdlroom qrbuf;
+
+ /*
+ * Create the room. This will silently fail if the room already
+ * exists, and that's perfectly ok, because we want it to exist.
+ */
+ create_room(SMTP_SPOOLOUT_ROOM, 3, "", 0, 1, 0, VIEW_MAILBOX);
+
+ /*
+ * Make sure it's set to be a "system room" so it doesn't show up
+ * in the <K>nown rooms list for Aides.
+ */
+ if (lgetroom(&qrbuf, SMTP_SPOOLOUT_ROOM) == 0) {
+ qrbuf.QRflags2 |= QR2_SYSTEM;
+ lputroom(&qrbuf);
+ }
+}
+
+
+
+
+/*****************************************************************************/
+/* MODULE INITIALIZATION STUFF */
+/*****************************************************************************/
+/*
+ * This cleanup function blows away the temporary memory used by
+ * the SMTP server.
+ */
+void smtp_cleanup_function(void) {
+
+ /* Don't do this stuff if this is not an SMTP session! */
+ if (CC->h_command_function != smtp_command_loop) return;
+
+ lprintf(CTDL_DEBUG, "Performing SMTP cleanup hook\n");
+ free(SMTP);
+}
+
+
+
+
+
+CTDL_MODULE_INIT(smtp)
+{
+ CtdlRegisterServiceHook(config.c_smtp_port, /* SMTP MTA */
+ NULL,
+ smtp_mta_greeting,
+ smtp_command_loop,
+ NULL);
+
+#ifdef HAVE_OPENSSL
+ CtdlRegisterServiceHook(config.c_smtps_port,
+ NULL,
+ smtps_greeting,
+ smtp_command_loop,
+ NULL);
+#endif
+
+ CtdlRegisterServiceHook(config.c_msa_port, /* SMTP MSA */
+ NULL,
+ smtp_msa_greeting,
+ smtp_command_loop,
+ NULL);
+
+ CtdlRegisterServiceHook(0, /* local LMTP */
+ file_lmtp_socket,
+ lmtp_greeting,
+ smtp_command_loop,
+ NULL);
+
+ CtdlRegisterServiceHook(0, /* local LMTP */
+ file_lmtp_unfiltered_socket,
+ lmtp_unfiltered_greeting,
+ smtp_command_loop,
+ NULL);
+
+ smtp_init_spoolout();
+ CtdlRegisterSessionHook(smtp_do_queue, EVT_TIMER);
+ CtdlRegisterSessionHook(smtp_cleanup_function, EVT_STOP);
+ CtdlRegisterProtoHook(cmd_smtp, "SMTP", "SMTP utility commands");
+
+ /* return our Subversion id for the Log */
+ return "$Id$";
+}
--- /dev/null
+/*
+ * $Id$
+ *
+ * This module allows Citadel to use SpamAssassin to filter incoming messages
+ * arriving via SMTP. For more information on SpamAssassin, visit
+ * http://www.spamassassin.org (the SpamAssassin project is not in any way
+ * affiliated with the Citadel project).
+ */
+
+#define SPAMASSASSIN_PORT "783"
+
+#include "sysdep.h"
+#include <stdlib.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <pwd.h>
+#include <errno.h>
+#include <sys/types.h>
+
+#if TIME_WITH_SYS_TIME
+# include <sys/time.h>
+# include <time.h>
+#else
+# if HAVE_SYS_TIME_H
+# include <sys/time.h>
+# else
+# include <time.h>
+# endif
+#endif
+
+#include <sys/wait.h>
+#include <string.h>
+#include <limits.h>
+#include <sys/socket.h>
+#include "citadel.h"
+#include "server.h"
+#include "citserver.h"
+#include "support.h"
+#include "config.h"
+#include "control.h"
+#include "room_ops.h"
+#include "user_ops.h"
+#include "policy.h"
+#include "database.h"
+#include "msgbase.h"
+#include "tools.h"
+#include "internet_addressing.h"
+#include "domain.h"
+#include "clientsocket.h"
+
+
+#include "ctdl_module.h"
+
+
+
+/*
+ * Connect to the SpamAssassin server and scan a message.
+ */
+int spam_assassin(struct CtdlMessage *msg) {
+ int sock = (-1);
+ char sahosts[SIZ];
+ int num_sahosts;
+ char buf[SIZ];
+ int is_spam = 0;
+ int sa;
+ char *msgtext;
+ size_t msglen;
+
+ /* For users who have authenticated to this server we never want to
+ * apply spam filtering, because presumably they're trustworthy.
+ */
+ if (CC->logged_in) return(0);
+
+ /* See if we have any SpamAssassin hosts configured */
+ num_sahosts = get_hosts(sahosts, "spamassassin");
+ if (num_sahosts < 1) return(0);
+
+ /* Try them one by one until we get a working one */
+ for (sa=0; sa<num_sahosts; ++sa) {
+ extract_token(buf, sahosts, sa, '|', sizeof buf);
+ lprintf(CTDL_INFO, "Connecting to SpamAssassin at <%s>\n", buf);
+ sock = sock_connect(buf, SPAMASSASSIN_PORT, "tcp");
+ if (sock >= 0) lprintf(CTDL_DEBUG, "Connected!\n");
+ }
+
+ if (sock < 0) {
+ /* If the service isn't running, just pass the mail
+ * through. Potentially throwing away mails isn't good.
+ */
+ return(0);
+ }
+
+ /* Command */
+ lprintf(CTDL_DEBUG, "Transmitting command\n");
+ sprintf(buf, "CHECK SPAMC/1.2\r\n\r\n");
+ sock_write(sock, buf, strlen(buf));
+
+ /* Message */
+ CC->redirect_buffer = malloc(SIZ);
+ CC->redirect_len = 0;
+ CC->redirect_alloc = SIZ;
+ CtdlOutputPreLoadedMsg(msg, MT_RFC822, HEADERS_ALL, 0, 1);
+ msgtext = CC->redirect_buffer;
+ msglen = CC->redirect_len;
+ CC->redirect_buffer = NULL;
+ CC->redirect_len = 0;
+ CC->redirect_alloc = 0;
+
+ sock_write(sock, msgtext, msglen);
+ free(msgtext);
+
+ /* Close one end of the socket connection; this tells SpamAssassin
+ * that we're done.
+ */
+ sock_shutdown(sock, SHUT_WR);
+
+ /* Response */
+ lprintf(CTDL_DEBUG, "Awaiting response\n");
+ if (sock_gets(sock, buf) < 0) {
+ goto bail;
+ }
+ lprintf(CTDL_DEBUG, "<%s\n", buf);
+ if (strncasecmp(buf, "SPAMD", 5)) {
+ goto bail;
+ }
+ if (sock_gets(sock, buf) < 0) {
+ goto bail;
+ }
+ lprintf(CTDL_DEBUG, "<%s\n", buf);
+ if (!strncasecmp(buf, "Spam: True", 10)) {
+ is_spam = 1;
+ }
+
+ if (is_spam) {
+ if (msg->cm_fields['0'] != NULL) {
+ free(msg->cm_fields['0']);
+ }
+ msg->cm_fields['0'] = strdup("5.7.1 message rejected by spam filter");
+ }
+
+bail: close(sock);
+ return(is_spam);
+}
+
+
+
+CTDL_MODULE_INIT(spam)
+{
+ CtdlRegisterMessageHook(spam_assassin, EVT_SMTPSCAN);
+
+ /* return our Subversion id for the Log */
+ return "$Id$";
+}
--- /dev/null
+/*
+ * $Id$
+ *
+ * Transparently handle the upgrading of server data formats.
+ *
+ */
+
+#include "sysdep.h"
+#include <stdlib.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <pwd.h>
+#include <errno.h>
+#include <sys/types.h>
+
+#if TIME_WITH_SYS_TIME
+# include <sys/time.h>
+# include <time.h>
+#else
+# if HAVE_SYS_TIME_H
+# include <sys/time.h>
+# else
+# include <time.h>
+# endif
+#endif
+
+#include <sys/wait.h>
+#include <string.h>
+#include <limits.h>
+#include "citadel.h"
+#include "server.h"
+#include "citserver.h"
+#include "support.h"
+#include "config.h"
+#include "control.h"
+#include "database.h"
+#include "room_ops.h"
+#include "user_ops.h"
+#include "msgbase.h"
+#include "tools.h"
+#include "serv_upgrade.h"
+#include "euidindex.h"
+
+
+#include "ctdl_module.h"
+
+
+/*
+ * Back end processing function for cmd_bmbx
+ */
+void cmd_bmbx_backend(struct ctdlroom *qrbuf, void *data) {
+ static struct RoomProcList *rplist = NULL;
+ struct RoomProcList *ptr;
+ struct ctdlroom qr;
+
+ /* Lazy programming here. Call this function as a ForEachRoom backend
+ * in order to queue up the room names, or call it with a null room
+ * to make it do the processing.
+ */
+ if (qrbuf != NULL) {
+ ptr = (struct RoomProcList *)
+ malloc(sizeof (struct RoomProcList));
+ if (ptr == NULL) return;
+
+ safestrncpy(ptr->name, qrbuf->QRname, sizeof ptr->name);
+ ptr->next = rplist;
+ rplist = ptr;
+ return;
+ }
+
+ while (rplist != NULL) {
+
+ if (lgetroom(&qr, rplist->name) == 0) {
+ lprintf(CTDL_DEBUG, "Processing <%s>...\n", rplist->name);
+ if ( (qr.QRflags & QR_MAILBOX) == 0) {
+ lprintf(CTDL_DEBUG, " -- not a mailbox\n");
+ }
+ else {
+
+ qr.QRgen = time(NULL);
+ lprintf(CTDL_DEBUG, " -- fixed!\n");
+ }
+ lputroom(&qr);
+ }
+
+ ptr = rplist;
+ rplist = rplist->next;
+ free(ptr);
+ }
+}
+
+/*
+ * quick fix to bump mailbox generation numbers
+ */
+void bump_mailbox_generation_numbers(void) {
+ lprintf(CTDL_WARNING, "Applying security fix to mailbox rooms\n");
+ ForEachRoom(cmd_bmbx_backend, NULL);
+ cmd_bmbx_backend(NULL, NULL);
+ return;
+}
+
+
+/*
+ * Back end processing function for convert_ctdluid_to_minusone()
+ */
+void cbtm_backend(struct ctdluser *usbuf, void *data) {
+ static struct UserProcList *uplist = NULL;
+ struct UserProcList *ptr;
+ struct ctdluser us;
+
+ /* Lazy programming here. Call this function as a ForEachUser backend
+ * in order to queue up the room names, or call it with a null user
+ * to make it do the processing.
+ */
+ if (usbuf != NULL) {
+ ptr = (struct UserProcList *)
+ malloc(sizeof (struct UserProcList));
+ if (ptr == NULL) return;
+
+ safestrncpy(ptr->user, usbuf->fullname, sizeof ptr->user);
+ ptr->next = uplist;
+ uplist = ptr;
+ return;
+ }
+
+ while (uplist != NULL) {
+
+ if (lgetuser(&us, uplist->user) == 0) {
+ lprintf(CTDL_DEBUG, "Processing <%s>...\n", uplist->user);
+ if (us.uid == CTDLUID) {
+ us.uid = (-1);
+ }
+ lputuser(&us);
+ }
+
+ ptr = uplist;
+ uplist = uplist->next;
+ free(ptr);
+ }
+}
+
+/*
+ * quick fix to change all CTDLUID users to (-1)
+ */
+void convert_ctdluid_to_minusone(void) {
+ lprintf(CTDL_WARNING, "Applying uid changes\n");
+ ForEachUser(cbtm_backend, NULL);
+ cbtm_backend(NULL, NULL);
+ return;
+}
+
+/*
+ * Do various things to our configuration file
+ */
+void update_config(void) {
+ get_config();
+
+ if (CitControl.version < 606) {
+ config.c_rfc822_strict_from = 0;
+ }
+
+ if (CitControl.version < 609) {
+ config.c_purge_hour = 3;
+ }
+
+ if (CitControl.version < 615) {
+ config.c_ldap_port = 389;
+ }
+
+ if (CitControl.version < 623) {
+ strcpy(config.c_ip_addr, "0.0.0.0");
+ }
+
+ if (CitControl.version < 650) {
+ config.c_enable_fulltext = 0;
+ }
+
+ if (CitControl.version < 652) {
+ config.c_auto_cull = 1;
+ }
+
+ put_config();
+}
+
+
+
+
+void check_server_upgrades(void) {
+
+ get_control();
+ lprintf(CTDL_INFO, "Server-hosted upgrade level is %d.%02d\n",
+ (CitControl.version / 100),
+ (CitControl.version % 100) );
+
+ if (CitControl.version < REV_LEVEL) {
+ lprintf(CTDL_WARNING,
+ "Server hosted updates need to be processed at "
+ "this time. Please wait...\n");
+ }
+ else {
+ return;
+ }
+
+ update_config();
+
+ if ((CitControl.version > 000) && (CitControl.version < 555)) {
+ lprintf(CTDL_EMERG,
+ "Your data files are from a version of Citadel\n"
+ "that is too old to be upgraded. Sorry.\n");
+ exit(EXIT_FAILURE);
+ }
+ if ((CitControl.version > 000) && (CitControl.version < 591)) {
+ bump_mailbox_generation_numbers();
+ }
+ if ((CitControl.version > 000) && (CitControl.version < 608)) {
+ convert_ctdluid_to_minusone();
+ }
+ if ((CitControl.version > 000) && (CitControl.version < 659)) {
+ rebuild_euid_index();
+ }
+
+ CitControl.version = REV_LEVEL;
+ put_control();
+}
+
+
+CTDL_MODULE_INIT(upgrade)
+{
+ check_server_upgrades();
+
+ /* return our Subversion id for the Log */
+ return "$Id$";
+}
--- /dev/null
+/*
+ * $Id$
+ *
+ */
+
--- /dev/null
+/*
+ * $Id$
+ *
+ * This is the "Art Vandelay" module. It is an importer/exporter.
+ *
+ */
+
+#include "sysdep.h"
+#include <stdlib.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <pwd.h>
+#include <errno.h>
+#include <sys/types.h>
+
+#if TIME_WITH_SYS_TIME
+# include <sys/time.h>
+# include <time.h>
+#else
+# if HAVE_SYS_TIME_H
+# include <sys/time.h>
+# else
+# include <time.h>
+# endif
+#endif
+
+#include <sys/wait.h>
+#include <string.h>
+#include <ctype.h>
+#include <limits.h>
+#include "citadel.h"
+#include "server.h"
+#include "citserver.h"
+#include "support.h"
+#include "config.h"
+#include "database.h"
+#include "msgbase.h"
+#include "tools.h"
+#include "user_ops.h"
+#include "room_ops.h"
+#include "control.h"
+#include "euidindex.h"
+
+
+#include "ctdl_module.h"
+
+
+
+
+#define END_OF_MESSAGE "---eom---dbd---"
+
+char artv_tempfilename1[PATH_MAX];
+char artv_tempfilename2[PATH_MAX];
+FILE *artv_global_message_list;
+
+void artv_export_users_backend(struct ctdluser *buf, void *data) {
+ cprintf("user\n");
+/*
+#include "artv_serialize.h"
+#include "dtds/user-defs.h"
+#include "undef_data.h"
+*/
+ cprintf("%d\n", buf->version);
+ cprintf("%ld\n", (long)buf->uid);
+ cprintf("%s\n", buf->password);
+ cprintf("%u\n", buf->flags);
+ cprintf("%ld\n", buf->timescalled);
+ cprintf("%ld\n", buf->posted);
+ cprintf("%d\n", buf->axlevel);
+ cprintf("%ld\n", buf->usernum);
+ cprintf("%ld\n", (long)buf->lastcall);
+ cprintf("%d\n", buf->USuserpurge);
+ cprintf("%s\n", buf->fullname);
+ cprintf("%d\n", buf->USscreenwidth);
+ cprintf("%d\n", buf->USscreenheight);
+}
+
+
+void artv_export_users(void) {
+ ForEachUser(artv_export_users_backend, NULL);
+}
+
+
+void artv_export_room_msg(long msgnum, void *userdata) {
+ cprintf("%ld\n", msgnum);
+ fprintf(artv_global_message_list, "%ld\n", msgnum);
+}
+
+
+void artv_export_rooms_backend(struct ctdlroom *buf, void *data) {
+ cprintf("room\n");
+/*
+#include "artv_serialize.h"
+#include "dtds/room-defs.h"
+#include "undef_data.h"
+*/
+ cprintf("%s\n", buf->QRname);
+ cprintf("%s\n", buf->QRpasswd);
+ cprintf("%ld\n", buf->QRroomaide);
+ cprintf("%ld\n", buf->QRhighest);
+ cprintf("%ld\n", (long)buf->QRgen);
+ cprintf("%u\n", buf->QRflags);
+ cprintf("%s\n", buf->QRdirname);
+ cprintf("%ld\n", buf->QRinfo);
+ cprintf("%d\n", buf->QRfloor);
+ cprintf("%ld\n", (long)buf->QRmtime);
+ cprintf("%d\n", buf->QRep.expire_mode);
+ cprintf("%d\n", buf->QRep.expire_value);
+ cprintf("%ld\n", buf->QRnumber);
+ cprintf("%d\n", buf->QRorder);
+ cprintf("%u\n", buf->QRflags2);
+ cprintf("%d\n", buf->QRdefaultview);
+
+ getroom(&CC->room, buf->QRname);
+ /* format of message list export is all message numbers output
+ * one per line terminated by a 0.
+ */
+//*/
+ getroom(&CC->room, buf->QRname);
+ /* format of message list export is all message numbers output
+ * one per line terminated by a 0.
+ */
+ CtdlForEachMessage(MSGS_ALL, 0L, NULL, NULL, NULL,
+ artv_export_room_msg, NULL);
+ cprintf("0\n");
+
+}
+
+
+
+void artv_export_rooms(void) {
+ char cmd[SIZ];
+ artv_global_message_list = fopen(artv_tempfilename1, "w");
+ if (artv_global_message_list != NULL) {
+ ForEachRoom(artv_export_rooms_backend, NULL);
+ fclose(artv_global_message_list);
+ }
+
+ /*
+ * Process the 'global' message list. (Sort it and remove dups.
+ * Dups are ok because a message may be in more than one room, but
+ * this will be handled by exporting the reference count, not by
+ * exporting the message multiple times.)
+ */
+ snprintf(cmd, sizeof cmd, "sort <%s >%s", artv_tempfilename1, artv_tempfilename2);
+ system(cmd);
+ snprintf(cmd, sizeof cmd, "uniq <%s >%s", artv_tempfilename2, artv_tempfilename1);
+ system(cmd);
+}
+
+
+void artv_export_floors(void) {
+ struct floor qfbuf, *buf;
+ int i;
+
+ for (i=0; i < MAXFLOORS; ++i) {
+ cprintf("floor\n");
+ cprintf("%d\n", i);
+ getfloor(&qfbuf, i);
+ buf = &qfbuf;
+/*
+#include "artv_serialize.h"
+#include "dtds/floor-defs.h"
+#include "undef_data.h"
+/*/
+ cprintf("%u\n", buf->f_flags);
+ cprintf("%s\n", buf->f_name);
+ cprintf("%d\n", buf->f_ref_count);
+ cprintf("%d\n", buf->f_ep.expire_mode);
+ cprintf("%d\n", buf->f_ep.expire_value);
+//*/
+ }
+}
+
+
+
+
+
+/*
+ * Traverse the visits file...
+ */
+void artv_export_visits(void) {
+ struct visit vbuf;
+ struct cdbdata *cdbv;
+
+ cdb_rewind(CDB_VISIT);
+
+ while (cdbv = cdb_next_item(CDB_VISIT), cdbv != NULL) {
+ memset(&vbuf, 0, sizeof(struct visit));
+ memcpy(&vbuf, cdbv->ptr,
+ ((cdbv->len > sizeof(struct visit)) ?
+ sizeof(struct visit) : cdbv->len));
+ cdb_free(cdbv);
+
+ cprintf("visit\n");
+ cprintf("%ld\n", vbuf.v_roomnum);
+ cprintf("%ld\n", vbuf.v_roomgen);
+ cprintf("%ld\n", vbuf.v_usernum);
+
+ if (strlen(vbuf.v_seen) > 0) {
+ cprintf("%s\n", vbuf.v_seen);
+ }
+ else {
+ cprintf("%ld\n", vbuf.v_lastseen);
+ }
+
+ cprintf("%s\n", vbuf.v_answered);
+ cprintf("%u\n", vbuf.v_flags);
+ cprintf("%d\n", vbuf.v_view);
+ }
+}
+
+
+void artv_export_message(long msgnum) {
+ struct MetaData smi;
+ struct CtdlMessage *msg;
+ struct ser_ret smr;
+ FILE *fp;
+ char buf[SIZ];
+ char tempfile[PATH_MAX];
+
+ msg = CtdlFetchMessage(msgnum, 1);
+ if (msg == NULL) return; /* fail silently */
+
+ cprintf("message\n");
+ GetMetaData(&smi, msgnum);
+ cprintf("%ld\n", msgnum);
+ cprintf("%d\n", smi.meta_refcount);
+ cprintf("%s\n", smi.meta_content_type);
+
+ serialize_message(&smr, msg);
+ CtdlFreeMessage(msg);
+
+ /* write it in base64 */
+ CtdlMakeTempFileName(tempfile, sizeof tempfile);
+ snprintf(buf, sizeof buf, "%s -e >%s", file_base64, tempfile);
+ fp = popen(buf, "w");
+ fwrite(smr.ser, smr.len, 1, fp);
+ pclose(fp);
+
+ free(smr.ser);
+
+ fp = fopen(tempfile, "r");
+ unlink(tempfile);
+ if (fp != NULL) {
+ while (fgets(buf, sizeof(buf), fp) != NULL) {
+ buf[strlen(buf)-1] = 0;
+ cprintf("%s\n", buf);
+ }
+ fclose(fp);
+ }
+ cprintf("%s\n", END_OF_MESSAGE);
+}
+
+
+
+void artv_export_messages(void) {
+ char buf[SIZ];
+ long msgnum;
+ int count = 0;
+
+ artv_global_message_list = fopen(artv_tempfilename1, "r");
+ if (artv_global_message_list != NULL) {
+ lprintf(CTDL_INFO, "Opened %s\n", artv_tempfilename1);
+ while (fgets(buf, sizeof(buf),
+ artv_global_message_list) != NULL) {
+ msgnum = atol(buf);
+ if (msgnum > 0L) {
+ artv_export_message(msgnum);
+ ++count;
+ }
+ }
+ fclose(artv_global_message_list);
+ }
+ lprintf(CTDL_INFO, "Exported %d messages.\n", count);
+}
+
+
+
+
+void artv_do_export(void) {
+ struct config *buf;
+ buf = &config;
+ cprintf("%d Exporting all Citadel databases.\n", LISTING_FOLLOWS);
+
+ cprintf("version\n%d\n", REV_LEVEL);
+
+ /* export the config file */
+ cprintf("config\n");
+
+#include "artv_serialize.h"
+#include "dtds/config-defs.h"
+#include "undef_data.h"
+
+/*
+ cprintf("%s\n", config.c_nodename);
+ cprintf("%s\n", config.c_fqdn);
+ cprintf("%s\n", config.c_humannode);
+ cprintf("%s\n", config.c_phonenum);
+ cprintf("%ld\n", (long)config.c_ctdluid);
+ cprintf("%d\n", config.c_creataide);
+ cprintf("%d\n", config.c_sleeping);
+ cprintf("%d\n", config.c_initax);
+ cprintf("%d\n", config.c_regiscall);
+ cprintf("%d\n", config.c_twitdetect);
+ cprintf("%s\n", config.c_twitroom);
+ cprintf("%s\n", config.c_moreprompt);
+ cprintf("%d\n", config.c_restrict);
+ cprintf("%s\n", config.c_site_location);
+ cprintf("%s\n", config.c_sysadm);
+ cprintf("%d\n", config.c_setup_level);
+ cprintf("%d\n", config.c_maxsessions);
+ cprintf("%d\n", config.c_port_number);
+ cprintf("%d\n", config.c_ep.expire_mode);
+ cprintf("%d\n", config.c_ep.expire_value);
+ cprintf("%d\n", config.c_userpurge);
+ cprintf("%d\n", config.c_roompurge);
+ cprintf("%s\n", config.c_logpages);
+ cprintf("%d\n", config.c_createax);
+ cprintf("%ld\n", config.c_maxmsglen);
+ cprintf("%d\n", config.c_min_workers);
+ cprintf("%d\n", config.c_max_workers);
+ cprintf("%d\n", config.c_pop3_port);
+ cprintf("%d\n", config.c_smtp_port);
+ cprintf("%d\n", config.c_purge_hour);
+ cprintf("%d\n", config.c_mbxep.expire_mode);
+ cprintf("%d\n", config.c_mbxep.expire_value);
+ cprintf("%s\n", config.c_ldap_host);
+ cprintf("%d\n", config.c_ldap_port);
+ cprintf("%s\n", config.c_ldap_base_dn);
+ cprintf("%s\n", config.c_ldap_bind_dn);
+ cprintf("%s\n", config.c_ldap_bind_pw);
+ cprintf("%s\n", config.c_ip_addr);
+ cprintf("%d\n", config.c_msa_port);
+ cprintf("%d\n", config.c_imaps_port);
+ cprintf("%d\n", config.c_pop3s_port);
+ cprintf("%d\n", config.c_smtps_port);
+ cprintf("%d\n", config.c_rfc822_strict_from);
+ cprintf("%d\n", config.c_aide_zap);
+ cprintf("%d\n", config.c_imap_port);
+ cprintf("%ld\n", config.c_net_freq);
+ cprintf("%d\n", config.c_disable_newu);
+ cprintf("%s\n", config.c_baseroom);
+ cprintf("%s\n", config.c_aideroom);
+ cprintf("%d\n", config.c_auto_cull);
+ cprintf("%d\n", config.c_instant_expunge);
+ cprintf("%d\n", config.c_allow_spoofing);
+ cprintf("%d\n", config.c_journal_email);
+ cprintf("%d\n", config.c_journal_pubmsgs);
+ cprintf("%s\n", config.c_journal_dest);
+ cprintf("%s\n", config.c_default_cal_zone);
+ cprintf("%d\n", config.c_pftcpdict_port);
+ cprintf("%d\n", config.c_managesieve_port);
+ cprintf("%d\n", config.c_auth_mode);
+ cprintf("%s\n", config.c_funambol_host);
+ cprintf("%d\n", config.c_funambol_port);
+ cprintf("%s\n", config.c_funambol_source);
+ cprintf("%s\n", config.c_funambol_auth);
+ cprintf("%d\n", config.c_rbl_at_greeting);
+*/
+ /* Export the control file */
+ get_control();
+ cprintf("control\n");
+ cprintf("%ld\n", CitControl.MMhighest);
+ cprintf("%u\n", CitControl.MMflags);
+ cprintf("%ld\n", CitControl.MMnextuser);
+ cprintf("%ld\n", CitControl.MMnextroom);
+ cprintf("%d\n", CitControl.version);
+
+ artv_export_users();
+ artv_export_rooms();
+ artv_export_floors();
+ artv_export_visits();
+ artv_export_messages();
+
+ cprintf("000\n");
+}
+
+
+
+void artv_import_config(void) {
+ char cbuf[SIZ];
+ struct config *buf;
+ buf = &config;
+
+ lprintf(CTDL_DEBUG, "Importing config file\n");
+
+#include "artv_deserialize.h"
+#include "dtds/config-defs.h"
+#include "undef_data.h"
+
+/*
+ client_getln(config.c_nodename, sizeof config.c_nodename);
+ client_getln(config.c_fqdn, sizeof config.c_fqdn);
+ client_getln(config.c_humannode, sizeof config.c_humannode);
+ client_getln(config.c_phonenum, sizeof config.c_phonenum);
+ client_getln(buf, sizeof buf); config.c_ctdluid = atoi(buf);
+ client_getln(buf, sizeof buf); config.c_creataide = atoi(buf);
+ client_getln(buf, sizeof buf); config.c_sleeping = atoi(buf);
+ client_getln(buf, sizeof buf); config.c_initax = atoi(buf);
+ client_getln(buf, sizeof buf); config.c_regiscall = atoi(buf);
+ client_getln(buf, sizeof buf); config.c_twitdetect = atoi(buf);
+ client_getln(config.c_twitroom, sizeof config.c_twitroom);
+ client_getln(config.c_moreprompt, sizeof config.c_moreprompt);
+ client_getln(buf, sizeof buf); config.c_restrict = atoi(buf);
+ client_getln(config.c_site_location, sizeof config.c_site_location);
+ client_getln(config.c_sysadm, sizeof config.c_sysadm);
+ client_getln(buf, sizeof buf); config.c_setup_level = atoi(buf);
+ client_getln(buf, sizeof buf); config.c_maxsessions = atoi(buf);
+ client_getln(buf, sizeof buf); config.c_port_number = atoi(buf);
+ client_getln(buf, sizeof buf); config.c_ep.expire_mode = atoi(buf);
+ client_getln(buf, sizeof buf); config.c_ep.expire_value = atoi(buf);
+ client_getln(buf, sizeof buf); config.c_userpurge = atoi(buf);
+ client_getln(buf, sizeof buf); config.c_roompurge = atoi(buf);
+ client_getln(config.c_logpages, sizeof config.c_logpages);
+ client_getln(buf, sizeof buf); config.c_createax = atoi(buf);
+ client_getln(buf, sizeof buf); config.c_maxmsglen = atol(buf);
+ client_getln(buf, sizeof buf); config.c_min_workers = atoi(buf);
+ client_getln(buf, sizeof buf); config.c_max_workers = atoi(buf);
+ client_getln(buf, sizeof buf); config.c_pop3_port = atoi(buf);
+ client_getln(buf, sizeof buf); config.c_smtp_port = atoi(buf);
+ client_getln(buf, sizeof buf); config.c_purge_hour = atoi(buf);
+ client_getln(buf, sizeof buf); config.c_mbxep.expire_mode = atoi(buf);
+ client_getln(buf, sizeof buf); config.c_mbxep.expire_value = atoi(buf);
+ client_getln(config.c_ldap_host, sizeof config.c_ldap_host);
+ client_getln(buf, sizeof buf); config.c_ldap_port = atoi(buf);
+ client_getln(config.c_ldap_base_dn, sizeof config.c_ldap_base_dn);
+ client_getln(config.c_ldap_bind_dn, sizeof config.c_ldap_bind_dn);
+ client_getln(config.c_ldap_bind_pw, sizeof config.c_ldap_bind_pw);
+ client_getln(config.c_ip_addr, sizeof config.c_ip_addr);
+ client_getln(buf, sizeof buf); config.c_msa_port = atoi(buf);
+ client_getln(buf, sizeof buf); config.c_imaps_port = atoi(buf);
+ client_getln(buf, sizeof buf); config.c_pop3s_port = atoi(buf);
+ client_getln(buf, sizeof buf); config.c_smtps_port = atoi(buf);
+ client_getln(buf, sizeof buf); config.c_rfc822_strict_from = atoi(buf);
+ client_getln(buf, sizeof buf); config.c_aide_zap = atoi(buf);
+ client_getln(buf, sizeof buf); config.c_imap_port = atoi(buf);
+ client_getln(buf, sizeof buf); config.c_net_freq = atol(buf);
+ client_getln(buf, sizeof buf); config.c_disable_newu = atoi(buf);
+ client_getln(config.c_baseroom, sizeof config.c_baseroom);
+ client_getln(config.c_aideroom, sizeof config.c_aideroom);
+ client_getln(buf, sizeof buf); config.c_auto_cull = atoi(buf);
+ client_getln(buf, sizeof buf); config.c_instant_expunge = atoi(buf);
+ client_getln(buf, sizeof buf); config.c_allow_spoofing = atoi(buf);
+ client_getln(buf, sizeof buf); config.c_journal_email = atoi(buf);
+ client_getln(buf, sizeof buf); config.c_journal_pubmsgs = atoi(buf);
+ client_getln(config.c_journal_dest, sizeof config.c_journal_dest);
+ client_getln(config.c_default_cal_zone, sizeof config.c_default_cal_zone);
+ client_getln(buf, sizeof buf); config.c_pftcpdict_port = atoi(buf);
+ client_getln(buf, sizeof buf); config.c_managesieve_port = atoi(buf);
+ client_getln(buf, sizeof buf); config.c_auth_mode = atoi(buf);
+ client_getln(config.c_funambol_host, sizeof config.c_funambol_host);
+ client_getln(buf, sizeof buf); config.c_funambol_port = atoi(buf);
+ client_getln(config.c_funambol_source, sizeof config.c_funambol_source);
+ client_getln(config.c_funambol_auth, sizeof config.c_funambol_auth);
+ client_getln(buf, sizeof buf); config.c_rbl_at_greeting = atoi(buf);
+*/
+ config.c_enable_fulltext = 0; /* always disable */
+ put_config();
+ lprintf(CTDL_INFO, "Imported config file\n");
+}
+
+
+void artv_import_control(void) {
+ char buf[SIZ];
+
+ lprintf(CTDL_DEBUG, "Importing control file\n");
+ client_getln(buf, sizeof buf); CitControl.MMhighest = atol(buf);
+ client_getln(buf, sizeof buf); CitControl.MMflags = atoi(buf);
+ client_getln(buf, sizeof buf); CitControl.MMnextuser = atol(buf);
+ client_getln(buf, sizeof buf); CitControl.MMnextroom = atol(buf);
+ client_getln(buf, sizeof buf); CitControl.version = atoi(buf);
+ CitControl.MMfulltext = (-1L); /* always flush */
+ put_control();
+ lprintf(CTDL_INFO, "Imported control file\n");
+}
+
+
+void artv_import_user(void) {
+ char cbuf[SIZ];
+ struct ctdluser usbuf, *buf;
+ buf = &usbuf;
+/*
+#include "artv_deserialize.h"
+#include "dtds/user-defs.h"
+#include "undef_data.h"
+
+/*/
+ client_getln(cbuf, sizeof cbuf); buf->version = atoi(cbuf);
+ client_getln(cbuf, sizeof cbuf); buf->uid = atoi(cbuf);
+ client_getln(buf->password, sizeof buf->password);
+ client_getln(cbuf, sizeof cbuf); buf->flags = atoi(cbuf);
+ client_getln(cbuf, sizeof cbuf); buf->timescalled = atol(cbuf);
+ client_getln(cbuf, sizeof cbuf); buf->posted = atol(cbuf);
+ client_getln(cbuf, sizeof cbuf); buf->axlevel = atoi(cbuf);
+ client_getln(cbuf, sizeof cbuf); buf->usernum = atol(cbuf);
+ client_getln(cbuf, sizeof cbuf); buf->lastcall = atol(cbuf);
+ client_getln(cbuf, sizeof cbuf); buf->USuserpurge = atoi(cbuf);
+ client_getln(buf->fullname, sizeof buf->fullname);
+ client_getln(cbuf, sizeof cbuf); buf->USscreenwidth = atoi(cbuf);
+ client_getln(cbuf, sizeof cbuf); buf->USscreenheight = atoi(cbuf);
+//*/
+ putuser(buf);
+}
+
+
+void artv_import_room(void) {
+ char cbuf[SIZ];
+ struct ctdlroom qrbuf, *buf;
+ long msgnum;
+ int msgcount = 0;
+
+ buf = &qrbuf;
+/*
+#include "artv_deserialize.h"
+#include "dtds/room-defs.h"
+#include "undef_data.h"
+
+/*/
+ client_getln(buf->QRname, sizeof buf->QRname);
+ client_getln(buf->QRpasswd, sizeof buf->QRpasswd);
+ client_getln(cbuf, sizeof cbuf); buf->QRroomaide = atol(cbuf);
+ client_getln(cbuf, sizeof cbuf); buf->QRhighest = atol(cbuf);
+ client_getln(cbuf, sizeof cbuf); buf->QRgen = atol(cbuf);
+ client_getln(cbuf, sizeof cbuf); buf->QRflags = atoi(cbuf);
+ client_getln(buf->QRdirname, sizeof buf->QRdirname);
+ client_getln(cbuf, sizeof cbuf); buf->QRinfo = atol(cbuf);
+ client_getln(cbuf, sizeof cbuf); buf->QRfloor = atoi(cbuf);
+ client_getln(cbuf, sizeof cbuf); buf->QRmtime = atol(cbuf);
+ client_getln(cbuf, sizeof cbuf); buf->QRep.expire_mode = atoi(cbuf);
+ client_getln(cbuf, sizeof cbuf); buf->QRep.expire_value = atoi(cbuf);
+ client_getln(cbuf, sizeof cbuf); buf->QRnumber = atol(cbuf);
+ client_getln(cbuf, sizeof cbuf); buf->QRorder = atoi(cbuf);
+ client_getln(cbuf, sizeof cbuf); buf->QRflags2 = atoi(cbuf);
+ client_getln(cbuf, sizeof cbuf); buf->QRdefaultview = atoi(cbuf);
+//*/
+ putroom(buf);
+ lprintf(CTDL_INFO, "Imported room <%s>\n", qrbuf.QRname);
+ /* format of message list export is all message numbers output
+ * one per line terminated by a 0.
+ */
+ while (client_getln(cbuf, sizeof cbuf), msgnum = atol(cbuf), msgnum > 0) {
+ CtdlSaveMsgPointerInRoom(qrbuf.QRname, msgnum, 0, NULL);
+ ++msgcount;
+ }
+ lprintf(CTDL_INFO, "(%d messages)\n", msgcount);
+}
+
+
+void artv_import_floor(void) {
+ struct floor flbuf, *buf;
+ int i;
+ char cbuf[SIZ];
+
+ buf = & flbuf;
+ memset(buf, 0, sizeof(buf));
+ client_getln(cbuf, sizeof cbuf); i = atoi(cbuf);
+/*
+#include "artv_deserialize.h"
+#include "dtds/floor-defs.h"
+#include "undef_data.h"
+/*/
+ client_getln(cbuf, sizeof cbuf); buf->f_flags = atoi(cbuf);
+ client_getln(buf->f_name, sizeof buf->f_name);
+ client_getln(cbuf, sizeof cbuf); buf->f_ref_count = atoi(cbuf);
+ client_getln(cbuf, sizeof cbuf); buf->f_ep.expire_mode = atoi(cbuf);
+ client_getln(cbuf, sizeof cbuf); buf->f_ep.expire_value = atoi(cbuf);
+//*/
+ putfloor(buf, i);
+ lprintf(CTDL_INFO, "Imported floor #%d (%s)\n", i, flbuf.f_name);
+}
+
+
+/*
+ */
+void artv_import_visit(void) {
+ struct visit vbuf;
+ char buf[SIZ];
+ int i;
+ int is_textual_seen = 0;
+
+ client_getln(buf, sizeof buf); vbuf.v_roomnum = atol(buf);
+ client_getln(buf, sizeof buf); vbuf.v_roomgen = atol(buf);
+ client_getln(buf, sizeof buf); vbuf.v_usernum = atol(buf);
+
+ client_getln(buf, sizeof buf);
+ vbuf.v_lastseen = atol(buf);
+ for (i=0; i<strlen(buf); ++i) if (!isdigit(buf[i])) is_textual_seen = 1;
+ if (is_textual_seen) strcpy(vbuf.v_seen, buf);
+
+ client_getln(vbuf.v_answered, sizeof vbuf.v_answered);
+ client_getln(buf, sizeof buf); vbuf.v_flags = atoi(buf);
+ client_getln(buf, sizeof buf); vbuf.v_view = atoi(buf);
+ put_visit(&vbuf);
+ lprintf(CTDL_INFO, "Imported visit %ld/%ld/%ld\n",
+ vbuf.v_roomnum, vbuf.v_roomgen, vbuf.v_usernum);
+}
+
+
+
+void artv_import_message(void) {
+ struct MetaData smi;
+ long msgnum;
+ long msglen;
+ FILE *fp;
+ char buf[SIZ];
+ char tempfile[PATH_MAX];
+ char *mbuf;
+
+ memset(&smi, 0, sizeof(struct MetaData));
+ client_getln(buf, sizeof buf); msgnum = atol(buf);
+ smi.meta_msgnum = msgnum;
+ client_getln(buf, sizeof buf); smi.meta_refcount = atoi(buf);
+ client_getln(smi.meta_content_type, sizeof smi.meta_content_type);
+
+ lprintf(CTDL_INFO, "message #%ld\n", msgnum);
+
+ /* decode base64 message text */
+ CtdlMakeTempFileName(tempfile, sizeof tempfile);
+ snprintf(buf, sizeof buf, "%s -d >%s", file_base64, tempfile);
+ fp = popen(buf, "w");
+ while (client_getln(buf, sizeof buf), strcasecmp(buf, END_OF_MESSAGE)) {
+ fprintf(fp, "%s\n", buf);
+ }
+ pclose(fp);
+ fp = fopen(tempfile, "rb");
+ fseek(fp, 0L, SEEK_END);
+ msglen = ftell(fp);
+ fclose(fp);
+ lprintf(CTDL_DEBUG, "msglen = %ld\n", msglen);
+
+ mbuf = malloc(msglen);
+ fp = fopen(tempfile, "rb");
+ fread(mbuf, msglen, 1, fp);
+ fclose(fp);
+
+ cdb_store(CDB_MSGMAIN, &msgnum, sizeof(long), mbuf, msglen);
+
+ free(mbuf);
+ unlink(tempfile);
+
+ PutMetaData(&smi);
+ lprintf(CTDL_INFO, "Imported message %ld\n", msgnum);
+}
+
+
+
+
+void artv_do_import(void) {
+ char buf[SIZ];
+ char abuf[SIZ];
+ char s_version[SIZ];
+ int version;
+ long iterations;
+
+ unbuffer_output();
+
+ cprintf("%d sock it to me\n", SEND_LISTING);
+ abuf[0] = '\0';
+ unbuffer_output();
+ iterations = 0;
+ while (client_getln(buf, sizeof buf), strcmp(buf, "000")) {
+
+ lprintf(CTDL_DEBUG, "import keyword: <%s>\n", buf);
+ if ((abuf[0] == '\0') || (strcasecmp(buf, abuf))) {
+ cprintf ("\n\nImporting datatype %s\n", buf);
+ strncpy (abuf, buf, SIZ);
+ iterations = 0;
+ }
+ else {
+ cprintf(".");
+ if (iterations % 64 == 0)
+ cprintf("\n");
+
+ }
+
+ if (!strcasecmp(buf, "version")) {
+ client_getln(s_version, sizeof s_version);
+ version = atoi(s_version);
+ if ((version<EXPORT_REV_MIN) || (version>REV_LEVEL)) {
+ lprintf(CTDL_ERR, "Version mismatch in ARTV import; aborting\n");
+ break;
+ }
+ }
+ else if (!strcasecmp(buf, "config")) artv_import_config();
+ else if (!strcasecmp(buf, "control")) artv_import_control();
+ else if (!strcasecmp(buf, "user")) artv_import_user();
+ else if (!strcasecmp(buf, "room")) artv_import_room();
+ else if (!strcasecmp(buf, "floor")) artv_import_floor();
+ else if (!strcasecmp(buf, "visit")) artv_import_visit();
+ else if (!strcasecmp(buf, "message")) artv_import_message();
+ else break;
+ iterations ++;
+ }
+ lprintf(CTDL_INFO, "Invalid keyword <%s>. Flushing input.\n", buf);
+ while (client_getln(buf, sizeof buf), strcmp(buf, "000")) ;;
+ rebuild_euid_index();
+}
+
+
+
+void cmd_artv(char *cmdbuf) {
+ char cmd[32];
+ static int is_running = 0;
+
+ if (CtdlAccessCheck(ac_internal)) return;
+ if (is_running) {
+ cprintf("%d The importer/exporter is already running.\n",
+ ERROR + RESOURCE_BUSY);
+ return;
+ }
+ is_running = 1;
+
+ CtdlMakeTempFileName(artv_tempfilename1, sizeof artv_tempfilename1);
+ CtdlMakeTempFileName(artv_tempfilename2, sizeof artv_tempfilename2);
+
+ extract_token(cmd, cmdbuf, 0, '|', sizeof cmd);
+ if (!strcasecmp(cmd, "export")) artv_do_export();
+ else if (!strcasecmp(cmd, "import")) artv_do_import();
+ else cprintf("%d illegal command\n", ERROR + ILLEGAL_VALUE);
+
+ unlink(artv_tempfilename1);
+ unlink(artv_tempfilename2);
+
+ is_running = 0;
+}
+
+
+
+
+CTDL_MODULE_INIT(vandelay)
+{
+ CtdlRegisterProtoHook(cmd_artv, "ARTV", "import/export data store");
+
+ /* return our Subversion id for the Log */
+ return "$Id$";
+}
--- /dev/null
+/*
+ * $Id$
+ *
+ * A server-side module for Citadel which supports address book information
+ * using the standard vCard format.
+ *
+ * Copyright (c) 1999-2007 / released under the GNU General Public License
+ */
+
+/*
+ * Format of the "Exclusive ID" field of the message containing a user's
+ * vCard. Doesn't matter what it really looks like as long as it's both
+ * unique and consistent (because we use it for replication checking to
+ * delete the old vCard network-wide when the user enters a new one).
+ */
+#define VCARD_EXT_FORMAT "Citadel vCard: personal card for %s at %s"
+
+/*
+ * Citadel will accept either text/vcard or text/x-vcard as the MIME type
+ * for a vCard. The following definition determines which one it *generates*
+ * when serializing.
+ */
+#define VCARD_MIME_TYPE "text/x-vcard"
+
+#include "sysdep.h"
+#include <stdlib.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <pwd.h>
+#include <errno.h>
+#include <ctype.h>
+#include <sys/types.h>
+
+#if TIME_WITH_SYS_TIME
+# include <sys/time.h>
+# include <time.h>
+#else
+# if HAVE_SYS_TIME_H
+# include <sys/time.h>
+# else
+# include <time.h>
+# endif
+#endif
+
+#include <sys/wait.h>
+#include <string.h>
+#include <limits.h>
+#include "citadel.h"
+#include "server.h"
+#include "citserver.h"
+#include "support.h"
+#include "config.h"
+#include "control.h"
+#include "room_ops.h"
+#include "user_ops.h"
+#include "policy.h"
+#include "database.h"
+#include "msgbase.h"
+#include "internet_addressing.h"
+#include "tools.h"
+#include "mime_parser.h"
+#include "vcard.h"
+#include "serv_ldap.h"
+#include "serv_vcard.h"
+
+
+#include "ctdl_module.h"
+
+
+
+/*
+ * set global flag calling for an aide to validate new users
+ */
+void set_mm_valid(void) {
+ begin_critical_section(S_CONTROL);
+ get_control();
+ CitControl.MMflags = CitControl.MMflags | MM_VALID ;
+ put_control();
+ end_critical_section(S_CONTROL);
+}
+
+
+
+/*
+ * Extract Internet e-mail addresses from a message containing a vCard, and
+ * perform a callback for any found.
+ */
+void vcard_extract_internet_addresses(struct CtdlMessage *msg,
+ void (*callback)(char *, char *) ) {
+ struct vCard *v;
+ char *s;
+ char *addr;
+ char citadel_address[SIZ];
+ int instance = 0;
+ int found_something = 0;
+
+ if (msg->cm_fields['A'] == NULL) return;
+ if (msg->cm_fields['N'] == NULL) return;
+ snprintf(citadel_address, sizeof citadel_address, "%s @ %s",
+ msg->cm_fields['A'], msg->cm_fields['N']);
+
+ v = vcard_load(msg->cm_fields['M']);
+ if (v == NULL) return;
+
+ /* Go through the vCard searching for *all* instances of
+ * the "email;internet" key
+ */
+ do {
+ s = vcard_get_prop(v, "email;internet", 0, instance++, 0);
+ if (s != NULL) {
+ addr = strdup(s);
+ striplt(addr);
+ if (strlen(addr) > 0) {
+ if (callback != NULL) {
+ callback(addr, citadel_address);
+ }
+ }
+ free(addr);
+ found_something = 1;
+ }
+ else {
+ found_something = 0;
+ }
+ } while(found_something);
+
+ vcard_free(v);
+}
+
+
+
+/*
+ * Callback for vcard_add_to_directory()
+ * (Lotsa ugly nested callbacks. Oh well.)
+ */
+void vcard_directory_add_user(char *internet_addr, char *citadel_addr) {
+ char buf[SIZ];
+
+ /* We have to validate that we're not stepping on someone else's
+ * email address ... but only if we're logged in. Otherwise it's
+ * probably just the networker or something.
+ */
+ if (CC->logged_in) {
+ lprintf(CTDL_DEBUG, "Checking for <%s>...\n", internet_addr);
+ if (CtdlDirectoryLookup(buf, internet_addr, sizeof buf) == 0) {
+ if (strcasecmp(buf, citadel_addr)) {
+ /* This address belongs to someone else.
+ * Bail out silently without saving.
+ */
+ lprintf(CTDL_DEBUG, "DOOP!\n");
+ return;
+ }
+ }
+ }
+ lprintf(CTDL_INFO, "Adding %s (%s) to directory\n",
+ citadel_addr, internet_addr);
+ CtdlDirectoryAddUser(internet_addr, citadel_addr);
+}
+
+
+/*
+ * Back end function for cmd_igab()
+ */
+void vcard_add_to_directory(long msgnum, void *data) {
+ struct CtdlMessage *msg;
+
+ msg = CtdlFetchMessage(msgnum, 1);
+ if (msg != NULL) {
+ vcard_extract_internet_addresses(msg, vcard_directory_add_user);
+ }
+
+#ifdef HAVE_LDAP
+ ctdl_vcard_to_ldap(msg, V2L_WRITE);
+#endif
+
+ CtdlFreeMessage(msg);
+}
+
+
+/*
+ * Initialize Global Adress Book
+ */
+void cmd_igab(char *argbuf) {
+ char hold_rm[ROOMNAMELEN];
+
+ if (CtdlAccessCheck(ac_aide)) return;
+
+ strcpy(hold_rm, CC->room.QRname); /* save current room */
+
+ if (getroom(&CC->room, ADDRESS_BOOK_ROOM) != 0) {
+ getroom(&CC->room, hold_rm);
+ cprintf("%d cannot get address book room\n", ERROR + ROOM_NOT_FOUND);
+ return;
+ }
+
+ /* Empty the existing database first.
+ */
+ CtdlDirectoryInit();
+
+ /* We want *all* vCards in this room */
+ CtdlForEachMessage(MSGS_ALL, 0, NULL, "^[Tt][Ee][Xx][Tt]/.*[Vv][Cc][Aa][Rr][Dd]$",
+ NULL, vcard_add_to_directory, NULL);
+
+ getroom(&CC->room, hold_rm); /* return to saved room */
+ cprintf("%d Directory has been rebuilt.\n", CIT_OK);
+}
+
+
+
+
+/*
+ * See if there is a valid Internet address in a vCard to use for outbound
+ * Internet messages. If there is, stick it in the buffer.
+ */
+void extract_inet_email_addrs(char *emailaddrbuf, size_t emailaddrbuf_len,
+ char *secemailaddrbuf, size_t secemailaddrbuf_len,
+ struct vCard *v, int local_addrs_only) {
+ char *s, *addr;
+ int instance = 0;
+ int saved_instance = 0;
+
+ /* Go through the vCard searching for *all* instances of
+ * the "email;internet" key
+ */
+ while (s = vcard_get_prop(v, "email;internet", 0, instance++, 0), s != NULL) {
+ addr = strdup(s);
+ striplt(addr);
+ if (strlen(addr) > 0) {
+ if ( (IsDirectory(addr, 1)) ||
+ (!local_addrs_only) ) {
+ ++saved_instance;
+ if ((saved_instance == 1) && (emailaddrbuf != NULL)) {
+ safestrncpy(emailaddrbuf, addr, emailaddrbuf_len);
+ }
+ else if ((saved_instance == 2) && (secemailaddrbuf != NULL)) {
+ safestrncpy(secemailaddrbuf, addr, secemailaddrbuf_len);
+ }
+ else if ((saved_instance > 2) && (secemailaddrbuf != NULL)) {
+ if ( (strlen(addr) + strlen(secemailaddrbuf) + 2)
+ < secemailaddrbuf_len ) {
+ strcat(secemailaddrbuf, "|");
+ strcat(secemailaddrbuf, addr);
+ }
+ }
+ }
+ }
+ free(addr);
+ }
+}
+
+
+
+/*
+ * See if there is a name / screen name / friendly name in a vCard to use for outbound
+ * Internet messages. If there is, stick it in the buffer.
+ */
+void extract_friendly_name(char *namebuf, size_t namebuf_len, struct vCard *v)
+{
+ char *s;
+
+ s = vcard_get_prop(v, "fn", 0, 0, 0);
+ if (s == NULL) {
+ s = vcard_get_prop(v, "n", 0, 0, 0);
+ }
+
+ if (s != NULL) {
+ safestrncpy(namebuf, s, namebuf_len);
+ }
+}
+
+
+/*
+ * Callback function for vcard_upload_beforesave() hunts for the real vcard in the MIME structure
+ */
+void vcard_extract_vcard(char *name, char *filename, char *partnum, char *disp,
+ void *content, char *cbtype, char *cbcharset, size_t length,
+ char *encoding, void *cbuserdata)
+{
+ struct vCard **v = (struct vCard **) cbuserdata;
+
+ if ( (!strcasecmp(cbtype, "text/x-vcard"))
+ || (!strcasecmp(cbtype, "text/vcard")) ) {
+
+ lprintf(CTDL_DEBUG, "Part %s contains a vCard! Loading...\n", partnum);
+ if (*v != NULL) {
+ vcard_free(*v);
+ }
+ *v = vcard_load(content);
+ }
+}
+
+
+/*
+ * This handler detects whether the user is attempting to save a new
+ * vCard as part of his/her personal configuration, and handles the replace
+ * function accordingly (delete the user's existing vCard in the config room
+ * and in the global address book).
+ */
+int vcard_upload_beforesave(struct CtdlMessage *msg) {
+ char *ptr;
+ char *s;
+ char buf[SIZ];
+ struct ctdluser usbuf;
+ long what_user;
+ struct vCard *v = NULL;
+ char *ser = NULL;
+ int i = 0;
+ int yes_my_citadel_config = 0;
+ int yes_any_vcard_room = 0;
+
+ if (!CC->logged_in) return(0); /* Only do this if logged in. */
+
+ /* Is this some user's "My Citadel Config" room? */
+ if ( (CC->room.QRflags && QR_MAILBOX)
+ && (!strcasecmp(&CC->room.QRname[11], USERCONFIGROOM)) ) {
+ /* Yes, we want to do this */
+ yes_my_citadel_config = 1;
+
+#ifdef VCARD_SAVES_BY_AIDES_ONLY
+ /* Prevent non-aides from performing registration changes */
+ if (CC->user.axlevel < 6) {
+ return(1);
+ }
+#endif
+
+ }
+
+ /* Is this a room with an address book in it? */
+ if (CC->room.QRdefaultview == VIEW_ADDRESSBOOK) {
+ yes_any_vcard_room = 1;
+ }
+
+ /* If neither condition exists, don't run this hook. */
+ if ( (!yes_my_citadel_config) && (!yes_any_vcard_room) ) {
+ return(0);
+ }
+
+ /* If this isn't a MIME message, don't bother. */
+ if (msg->cm_format_type != 4) return(0);
+
+ /* Ok, if we got this far, look into the situation further... */
+
+ ptr = msg->cm_fields['M'];
+ if (ptr == NULL) return(0);
+
+ mime_parser(msg->cm_fields['M'],
+ NULL,
+ *vcard_extract_vcard,
+ NULL, NULL,
+ &v, /* user data ptr - put the vcard here */
+ 0
+ );
+
+ if (v == NULL) return(0); /* no vCards were found in this message */
+
+ /* If users cannot create their own accounts, they cannot re-register either. */
+ if ( (yes_my_citadel_config) && (config.c_disable_newu) && (CC->user.axlevel < 6) ) {
+ return(1);
+ }
+
+ s = vcard_get_prop(v, "FN", 0, 0, 0);
+ if (s) lprintf(CTDL_DEBUG, "vCard beforesave hook running for <%s>\n", s);
+
+ if (yes_my_citadel_config) {
+ /* Bingo! The user is uploading a new vCard, so
+ * delete the old one. First, figure out which user
+ * is being re-registered...
+ */
+ what_user = atol(CC->room.QRname);
+
+ if (what_user == CC->user.usernum) {
+ /* It's the logged in user. That was easy. */
+ memcpy(&usbuf, &CC->user, sizeof(struct ctdluser));
+ }
+
+ else if (getuserbynumber(&usbuf, what_user) == 0) {
+ /* We fetched a valid user record */
+ }
+
+ else {
+ /* somebody set up us the bomb! */
+ yes_my_citadel_config = 0;
+ }
+ }
+
+ if (yes_my_citadel_config) {
+ /* Delete the user's old vCard. This would probably
+ * get taken care of by the replication check, but we
+ * want to make sure there is absolutely only one
+ * vCard in the user's config room at all times.
+ *
+ */
+ CtdlDeleteMessages(CC->room.QRname, NULL, 0, "^[Tt][Ee][Xx][Tt]/.*[Vv][Cc][Aa][Rr][Dd]$");
+
+ /* Make the author of the message the name of the user. */
+ if (msg->cm_fields['A'] != NULL) {
+ free(msg->cm_fields['A']);
+ }
+ msg->cm_fields['A'] = strdup(usbuf.fullname);
+ }
+
+ /* Insert or replace RFC2739-compliant free/busy URL */
+ if (yes_my_citadel_config) {
+ sprintf(buf, "http://%s/%s.vfb",
+ config.c_fqdn,
+ usbuf.fullname);
+ for (i=0; i<strlen(buf); ++i) {
+ if (buf[i] == ' ') buf[i] = '_';
+ }
+ vcard_set_prop(v, "FBURL;PREF", buf, 0);
+ }
+
+ /* If the vCard has no UID, then give it one. */
+ s = vcard_get_prop(v, "UID", 0, 0, 0);
+ if (s == NULL) {
+ generate_uuid(buf);
+ vcard_set_prop(v, "UID", buf, 0);
+ }
+
+ /* Enforce local UID policy if applicable */
+ if (yes_my_citadel_config) {
+ snprintf(buf, sizeof buf, VCARD_EXT_FORMAT, msg->cm_fields['A'], NODENAME);
+ vcard_set_prop(v, "UID", buf, 0);
+ }
+
+ /*
+ * Set the EUID of the message to the UID of the vCard.
+ */
+ if (msg->cm_fields['E'] != NULL) free(msg->cm_fields['E']);
+ s = vcard_get_prop(v, "UID", 0, 0, 0);
+ if (s != NULL) {
+ msg->cm_fields['E'] = strdup(s);
+ if (msg->cm_fields['U'] == NULL) {
+ msg->cm_fields['U'] = strdup(s);
+ }
+ }
+
+ /*
+ * Set the Subject to the name in the vCard.
+ */
+ s = vcard_get_prop(v, "FN", 0, 0, 0);
+ if (s == NULL) {
+ s = vcard_get_prop(v, "N", 0, 0, 0);
+ }
+ if (s != NULL) {
+ if (msg->cm_fields['U'] != NULL) {
+ free(msg->cm_fields['U']);
+ }
+ msg->cm_fields['U'] = strdup(s);
+ }
+
+ /* Re-serialize it back into the msg body */
+ ser = vcard_serialize(v);
+ if (ser != NULL) {
+ msg->cm_fields['M'] = realloc(msg->cm_fields['M'], strlen(ser) + 1024);
+ sprintf(msg->cm_fields['M'],
+ "Content-type: " VCARD_MIME_TYPE
+ "\r\n\r\n%s\r\n", ser);
+ free(ser);
+ }
+
+ /* Now allow the save to complete. */
+ vcard_free(v);
+ return(0);
+}
+
+
+
+/*
+ * This handler detects whether the user is attempting to save a new
+ * vCard as part of his/her personal configuration, and handles the replace
+ * function accordingly (copy the vCard from the config room to the global
+ * address book).
+ */
+int vcard_upload_aftersave(struct CtdlMessage *msg) {
+ char *ptr;
+ int linelen;
+ long I;
+ struct vCard *v;
+
+ if (!CC->logged_in) return(0); /* Only do this if logged in. */
+
+ /* If this isn't the configuration room, or if this isn't a MIME
+ * message, don't bother.
+ */
+ if (msg->cm_fields['O'] == NULL) return(0);
+ if (strcasecmp(msg->cm_fields['O'], USERCONFIGROOM)) return(0);
+ if (msg->cm_format_type != 4) return(0);
+
+ ptr = msg->cm_fields['M'];
+ if (ptr == NULL) return(0);
+ while (ptr != NULL) {
+
+ linelen = strcspn(ptr, "\n");
+ if (linelen == 0) return(0); /* end of headers */
+
+ if ( (!strncasecmp(ptr, "Content-type: text/x-vcard", 26))
+ || (!strncasecmp(ptr, "Content-type: text/vcard", 24)) ) {
+ /*
+ * Bingo! The user is uploading a new vCard, so
+ * copy it to the Global Address Book room.
+ */
+
+ I = atol(msg->cm_fields['I']);
+ if (I < 0L) return(0);
+
+ /* Store our Internet return address in memory */
+ v = vcard_load(msg->cm_fields['M']);
+ extract_inet_email_addrs(CC->cs_inet_email, sizeof CC->cs_inet_email,
+ CC->cs_inet_other_emails, sizeof CC->cs_inet_other_emails,
+ v, 1);
+ extract_friendly_name(CC->cs_inet_fn, sizeof CC->cs_inet_fn, v);
+ vcard_free(v);
+
+ /* Put it in the Global Address Book room... */
+ CtdlSaveMsgPointerInRoom(ADDRESS_BOOK_ROOM, I, 1, msg);
+
+ /* ...and also in the directory database. */
+ vcard_add_to_directory(I, NULL);
+
+ /* Some sites want an Aide to be notified when a
+ * user registers or re-registers...
+ */
+ set_mm_valid();
+
+ /* ...which also means we need to flag the user */
+ lgetuser(&CC->user, CC->curr_user);
+ CC->user.flags |= (US_REGIS|US_NEEDVALID);
+ lputuser(&CC->user);
+
+ return(0);
+ }
+
+ ptr = strchr((char *)ptr, '\n');
+ if (ptr != NULL) ++ptr;
+ }
+
+ return(0);
+}
+
+
+
+/*
+ * back end function used for callbacks
+ */
+void vcard_gu_backend(long supplied_msgnum, void *userdata) {
+ long *msgnum;
+
+ msgnum = (long *) userdata;
+ *msgnum = supplied_msgnum;
+}
+
+
+/*
+ * If this user has a vcard on disk, read it into memory, otherwise allocate
+ * and return an empty vCard.
+ */
+struct vCard *vcard_get_user(struct ctdluser *u) {
+ char hold_rm[ROOMNAMELEN];
+ char config_rm[ROOMNAMELEN];
+ struct CtdlMessage *msg = NULL;
+ struct vCard *v;
+ long VCmsgnum;
+
+ strcpy(hold_rm, CC->room.QRname); /* save current room */
+ MailboxName(config_rm, sizeof config_rm, u, USERCONFIGROOM);
+
+ if (getroom(&CC->room, config_rm) != 0) {
+ getroom(&CC->room, hold_rm);
+ return vcard_new();
+ }
+
+ /* We want the last (and probably only) vcard in this room */
+ VCmsgnum = (-1);
+ CtdlForEachMessage(MSGS_LAST, 1, NULL, "^[Tt][Ee][Xx][Tt]/.*[Vv][Cc][Aa][Rr][Dd]$",
+ NULL, vcard_gu_backend, (void *)&VCmsgnum );
+ getroom(&CC->room, hold_rm); /* return to saved room */
+
+ if (VCmsgnum < 0L) return vcard_new();
+
+ msg = CtdlFetchMessage(VCmsgnum, 1);
+ if (msg == NULL) return vcard_new();
+
+ v = vcard_load(msg->cm_fields['M']);
+ CtdlFreeMessage(msg);
+ return v;
+}
+
+
+/*
+ * Store this user's vCard in the appropriate place
+ */
+/*
+ * Write our config to disk
+ */
+void vcard_write_user(struct ctdluser *u, struct vCard *v) {
+ char temp[PATH_MAX];
+ FILE *fp;
+ char *ser;
+
+ CtdlMakeTempFileName(temp, sizeof temp);
+ ser = vcard_serialize(v);
+
+ fp = fopen(temp, "w");
+ if (fp == NULL) return;
+ if (ser == NULL) {
+ fprintf(fp, "begin:vcard\r\nend:vcard\r\n");
+ } else {
+ fwrite(ser, strlen(ser), 1, fp);
+ free(ser);
+ }
+ fclose(fp);
+
+ /* This handy API function does all the work for us.
+ * NOTE: normally we would want to set that last argument to 1, to
+ * force the system to delete the user's old vCard. But it doesn't
+ * have to, because the vcard_upload_beforesave() hook above
+ * is going to notice what we're trying to do, and delete the old vCard.
+ */
+ CtdlWriteObject(USERCONFIGROOM, /* which room */
+ VCARD_MIME_TYPE,/* MIME type */
+ temp, /* temp file */
+ u, /* which user */
+ 0, /* not binary */
+ 0, /* don't delete others of this type */
+ 0); /* no flags */
+
+ unlink(temp);
+}
+
+
+
+/*
+ * Old style "enter registration info" command. This function simply honors
+ * the REGI protocol command, translates the entered parameters into a vCard,
+ * and enters the vCard into the user's configuration.
+ */
+void cmd_regi(char *argbuf) {
+ int a,b,c;
+ char buf[SIZ];
+ struct vCard *my_vcard;
+
+ char tmpaddr[SIZ];
+ char tmpcity[SIZ];
+ char tmpstate[SIZ];
+ char tmpzip[SIZ];
+ char tmpaddress[SIZ];
+ char tmpcountry[SIZ];
+
+ unbuffer_output();
+
+ if (!(CC->logged_in)) {
+ cprintf("%d Not logged in.\n",ERROR + NOT_LOGGED_IN);
+ return;
+ }
+
+ /* If users cannot create their own accounts, they cannot re-register either. */
+ if ( (config.c_disable_newu) && (CC->user.axlevel < 6) ) {
+ cprintf("%d Self-service registration is not allowed here.\n",
+ ERROR + HIGHER_ACCESS_REQUIRED);
+ }
+
+ my_vcard = vcard_get_user(&CC->user);
+ strcpy(tmpaddr, "");
+ strcpy(tmpcity, "");
+ strcpy(tmpstate, "");
+ strcpy(tmpzip, "");
+ strcpy(tmpcountry, "USA");
+
+ cprintf("%d Send registration...\n", SEND_LISTING);
+ a=0;
+ while (client_getln(buf, sizeof buf), strcmp(buf,"000")) {
+ if (a==0) vcard_set_prop(my_vcard, "n", buf, 0);
+ if (a==1) strcpy(tmpaddr, buf);
+ if (a==2) strcpy(tmpcity, buf);
+ if (a==3) strcpy(tmpstate, buf);
+ if (a==4) {
+ for (c=0; c<strlen(buf); ++c) {
+ if ((buf[c]>='0') && (buf[c]<='9')) {
+ b = strlen(tmpzip);
+ tmpzip[b] = buf[c];
+ tmpzip[b+1] = 0;
+ }
+ }
+ }
+ if (a==5) vcard_set_prop(my_vcard, "tel;home", buf, 0);
+ if (a==6) vcard_set_prop(my_vcard, "email;internet", buf, 0);
+ if (a==7) strcpy(tmpcountry, buf);
+ ++a;
+ }
+
+ snprintf(tmpaddress, sizeof tmpaddress, ";;%s;%s;%s;%s;%s",
+ tmpaddr, tmpcity, tmpstate, tmpzip, tmpcountry);
+ vcard_set_prop(my_vcard, "adr", tmpaddress, 0);
+ vcard_write_user(&CC->user, my_vcard);
+ vcard_free(my_vcard);
+}
+
+
+/*
+ * Protocol command to fetch registration info for a user
+ */
+void cmd_greg(char *argbuf)
+{
+ struct ctdluser usbuf;
+ struct vCard *v;
+ char *s;
+ char who[USERNAME_SIZE];
+ char adr[256];
+ char buf[256];
+
+ extract_token(who, argbuf, 0, '|', sizeof who);
+
+ if (!(CC->logged_in)) {
+ cprintf("%d Not logged in.\n", ERROR + NOT_LOGGED_IN);
+ return;
+ }
+
+ if (!strcasecmp(who,"_SELF_")) strcpy(who,CC->curr_user);
+
+ if ((CC->user.axlevel < 6) && (strcasecmp(who,CC->curr_user))) {
+ cprintf("%d Higher access required.\n",
+ ERROR + HIGHER_ACCESS_REQUIRED);
+ return;
+ }
+
+ if (getuser(&usbuf, who) != 0) {
+ cprintf("%d '%s' not found.\n", ERROR + NO_SUCH_USER, who);
+ return;
+ }
+
+ v = vcard_get_user(&usbuf);
+
+ cprintf("%d %s\n", LISTING_FOLLOWS, usbuf.fullname);
+ cprintf("%ld\n", usbuf.usernum);
+ cprintf("%s\n", usbuf.password);
+ s = vcard_get_prop(v, "n", 0, 0, 0);
+ cprintf("%s\n", s ? s : " "); /* name */
+
+ s = vcard_get_prop(v, "adr", 0, 0, 0);
+ snprintf(adr, sizeof adr, "%s", s ? s : " ");/* address... */
+
+ extract_token(buf, adr, 2, ';', sizeof buf);
+ cprintf("%s\n", buf); /* street */
+ extract_token(buf, adr, 3, ';', sizeof buf);
+ cprintf("%s\n", buf); /* city */
+ extract_token(buf, adr, 4, ';', sizeof buf);
+ cprintf("%s\n", buf); /* state */
+ extract_token(buf, adr, 5, ';', sizeof buf);
+ cprintf("%s\n", buf); /* zip */
+
+ s = vcard_get_prop(v, "tel;home", 0, 0, 0);
+ if (s == NULL) s = vcard_get_prop(v, "tel", 1, 0, 0);
+ if (s != NULL) {
+ cprintf("%s\n", s);
+ }
+ else {
+ cprintf(" \n");
+ }
+
+ cprintf("%d\n", usbuf.axlevel);
+
+ s = vcard_get_prop(v, "email;internet", 0, 0, 0);
+ cprintf("%s\n", s ? s : " ");
+ s = vcard_get_prop(v, "adr", 0, 0, 0);
+ snprintf(adr, sizeof adr, "%s", s ? s : " ");/* address... */
+
+ extract_token(buf, adr, 6, ';', sizeof buf);
+ cprintf("%s\n", buf); /* country */
+ cprintf("000\n");
+ vcard_free(v);
+}
+
+
+
+/*
+ * When a user is being created, create his/her vCard.
+ */
+void vcard_newuser(struct ctdluser *usbuf) {
+ char vname[256];
+ char buf[256];
+ int i;
+ struct vCard *v;
+
+ vcard_fn_to_n(vname, usbuf->fullname, sizeof vname);
+ lprintf(CTDL_DEBUG, "Converted <%s> to <%s>\n", usbuf->fullname, vname);
+
+ /* Create and save the vCard */
+ v = vcard_new();
+ if (v == NULL) return;
+ sprintf(buf, "%s@%s", usbuf->fullname, config.c_fqdn);
+ for (i=0; i<strlen(buf); ++i) {
+ if (buf[i] == ' ') buf[i] = '_';
+ }
+ vcard_add_prop(v, "fn", usbuf->fullname);
+ vcard_add_prop(v, "n", vname);
+ vcard_add_prop(v, "adr", "adr:;;_;_;_;00000;__");
+ vcard_add_prop(v, "email;internet", buf);
+ vcard_write_user(usbuf, v);
+ vcard_free(v);
+}
+
+
+/*
+ * When a user is being deleted, we have to remove his/her vCard.
+ * This is accomplished by issuing a message with 'CANCEL' in the S (special)
+ * field, and the same Exclusive ID as the existing card.
+ */
+void vcard_purge(struct ctdluser *usbuf) {
+ struct CtdlMessage *msg;
+ char buf[SIZ];
+
+ msg = (struct CtdlMessage *) malloc(sizeof(struct CtdlMessage));
+ if (msg == NULL) return;
+ memset(msg, 0, sizeof(struct CtdlMessage));
+
+ msg->cm_magic = CTDLMESSAGE_MAGIC;
+ msg->cm_anon_type = MES_NORMAL;
+ msg->cm_format_type = 0;
+ msg->cm_fields['A'] = strdup(usbuf->fullname);
+ msg->cm_fields['O'] = strdup(ADDRESS_BOOK_ROOM);
+ msg->cm_fields['N'] = strdup(NODENAME);
+ msg->cm_fields['M'] = strdup("Purge this vCard\n");
+
+ snprintf(buf, sizeof buf, VCARD_EXT_FORMAT,
+ msg->cm_fields['A'], NODENAME);
+ msg->cm_fields['E'] = strdup(buf);
+
+ msg->cm_fields['S'] = strdup("CANCEL");
+
+ CtdlSubmitMsg(msg, NULL, ADDRESS_BOOK_ROOM);
+ CtdlFreeMessage(msg);
+}
+
+
+/*
+ * Grab vCard directory stuff out of incoming network messages
+ */
+int vcard_extract_from_network(struct CtdlMessage *msg, char *target_room) {
+ char *ptr;
+ int linelen;
+
+ if (msg == NULL) return(0);
+
+ if (strcasecmp(target_room, ADDRESS_BOOK_ROOM)) {
+ return(0);
+ }
+
+ if (msg->cm_format_type != 4) return(0);
+
+ ptr = msg->cm_fields['M'];
+ if (ptr == NULL) return(0);
+ while (ptr != NULL) {
+
+ linelen = strcspn(ptr, "\n");
+ if (linelen == 0) return(0); /* end of headers */
+
+ if ( (!strncasecmp(ptr, "Content-type: text/x-vcard", 26))
+ || (!strncasecmp(ptr, "Content-type: text/vcard", 24)) ) {
+ /* It's a vCard. Add it to the directory. */
+ vcard_extract_internet_addresses(msg, CtdlDirectoryAddUser);
+ return(0);
+ }
+
+ ptr = strchr((char *)ptr, '\n');
+ if (ptr != NULL) ++ptr;
+ }
+
+ return(0);
+}
+
+
+
+/*
+ * When a vCard is being removed from the Global Address Book room, remove it
+ * from the directory as well.
+ */
+void vcard_delete_remove(char *room, long msgnum) {
+ struct CtdlMessage *msg;
+ char *ptr;
+ int linelen;
+
+ if (msgnum <= 0L) return;
+
+ if (strcasecmp(room, ADDRESS_BOOK_ROOM)) {
+ return;
+ }
+
+ msg = CtdlFetchMessage(msgnum, 1);
+ if (msg == NULL) return;
+
+ ptr = msg->cm_fields['M'];
+ if (ptr == NULL) goto EOH;
+ while (ptr != NULL) {
+ linelen = strcspn(ptr, "\n");
+ if (linelen == 0) goto EOH;
+
+ if ( (!strncasecmp(ptr, "Content-type: text/x-vcard", 26))
+ || (!strncasecmp(ptr, "Content-type: text/vcard", 24)) ) {
+ /* Bingo! A vCard is being deleted. */
+ vcard_extract_internet_addresses(msg, CtdlDirectoryDelUser);
+#ifdef HAVE_LDAP
+ ctdl_vcard_to_ldap(msg, V2L_DELETE);
+#endif
+ }
+ ptr = strchr((char *)ptr, '\n');
+ if (ptr != NULL) ++ptr;
+ }
+
+EOH: CtdlFreeMessage(msg);
+}
+
+
+
+/*
+ * Get Valid Screen Names
+ */
+void cmd_gvsn(char *argbuf)
+{
+ if (CtdlAccessCheck(ac_logged_in)) return;
+
+ cprintf("%d valid screen names:\n", LISTING_FOLLOWS);
+ cprintf("%s\n", CC->user.fullname);
+ if ( (strlen(CC->cs_inet_fn) > 0) && (strcasecmp(CC->user.fullname, CC->cs_inet_fn)) ) {
+ cprintf("%s\n", CC->cs_inet_fn);
+ }
+ cprintf("000\n");
+}
+
+
+/*
+ * Get Valid Email Addresses
+ */
+void cmd_gvea(char *argbuf)
+{
+ int num_secondary_emails = 0;
+ int i;
+ char buf[256];
+
+ if (CtdlAccessCheck(ac_logged_in)) return;
+
+ cprintf("%d valid email addresses:\n", LISTING_FOLLOWS);
+ if (strlen(CC->cs_inet_email) > 0) {
+ cprintf("%s\n", CC->cs_inet_email);
+ }
+ if (strlen(CC->cs_inet_other_emails) > 0) {
+ num_secondary_emails = num_tokens(CC->cs_inet_other_emails, '|');
+ for (i=0; i<num_secondary_emails; ++i) {
+ extract_token(buf, CC->cs_inet_other_emails,i,'|',sizeof CC->cs_inet_other_emails);
+ cprintf("%s\n", buf);
+ }
+ }
+ cprintf("000\n");
+}
+
+
+
+
+/*
+ * Callback function for cmd_dvca() that hunts for vCard content types
+ * and outputs any email addresses found within.
+ */
+void dvca_mime_callback(char *name, char *filename, char *partnum, char *disp,
+ void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
+ void *cbuserdata) {
+
+ struct vCard *v;
+ char displayname[256];
+ int displayname_len;
+ char emailaddr[256];
+ int i;
+ int has_commas = 0;
+
+ if ( (strcasecmp(cbtype, "text/vcard")) && (strcasecmp(cbtype, "text/x-vcard")) ) {
+ return;
+ }
+
+ v = vcard_load(content);
+ if (v == NULL) return;
+
+ extract_friendly_name(displayname, sizeof displayname, v);
+ extract_inet_email_addrs(emailaddr, sizeof emailaddr, NULL, 0, v, 0);
+
+ displayname_len = strlen(displayname);
+ for (i=0; i<displayname_len; ++i) {
+ if (displayname[i] == '\"') displayname[i] = ' ';
+ if (displayname[i] == ';') displayname[i] = ',';
+ if (displayname[i] == ',') has_commas = 1;
+ }
+ striplt(displayname);
+
+ cprintf("%s%s%s <%s>\n",
+ (has_commas ? "\"" : ""),
+ displayname,
+ (has_commas ? "\"" : ""),
+ emailaddr
+ );
+
+ vcard_free(v);
+}
+
+
+/*
+ * Back end callback function for cmd_dvca()
+ *
+ * It's basically just passed a list of message numbers, which we're going
+ * to fetch off the disk and then pass along to the MIME parser via another
+ * layer of callback...
+ */
+void dvca_callback(long msgnum, void *userdata) {
+ struct CtdlMessage *msg = NULL;
+
+ msg = CtdlFetchMessage(msgnum, 1);
+ if (msg == NULL) return;
+ mime_parser(msg->cm_fields['M'],
+ NULL,
+ *dvca_mime_callback, /* callback function */
+ NULL, NULL,
+ NULL, /* user data */
+ 0
+ );
+ CtdlFreeMessage(msg);
+}
+
+
+/*
+ * Dump VCard Addresses
+ */
+void cmd_dvca(char *argbuf)
+{
+ if (CtdlAccessCheck(ac_logged_in)) return;
+
+ cprintf("%d addresses:\n", LISTING_FOLLOWS);
+ CtdlForEachMessage(MSGS_ALL, 0, NULL, NULL, NULL, dvca_callback, NULL);
+ cprintf("000\n");
+}
+
+
+/*
+ * Query Directory
+ */
+void cmd_qdir(char *argbuf) {
+ char citadel_addr[256];
+ char internet_addr[256];
+
+ if (CtdlAccessCheck(ac_logged_in)) return;
+
+ extract_token(internet_addr, argbuf, 0, '|', sizeof internet_addr);
+
+ if (CtdlDirectoryLookup(citadel_addr, internet_addr, sizeof citadel_addr) != 0) {
+ cprintf("%d %s was not found.\n",
+ ERROR + NO_SUCH_USER, internet_addr);
+ return;
+ }
+
+ cprintf("%d %s\n", CIT_OK, citadel_addr);
+}
+
+/*
+ * Query Directory, in fact an alias to match postfix tcp auth.
+ */
+void check_get(void) {
+ char internet_addr[256];
+
+ char cmdbuf[SIZ];
+
+ time(&CC->lastcmd);
+ memset(cmdbuf, 0, sizeof cmdbuf); /* Clear it, just in case */
+ if (client_getln(cmdbuf, sizeof cmdbuf) < 1) {
+ lprintf(CTDL_CRIT, "Client disconnected: ending session.\n");
+ CC->kill_me = 1;
+ return;
+ }
+ lprintf(CTDL_INFO, ": %s\n", cmdbuf);
+ while (strlen(cmdbuf) < 3) strcat(cmdbuf, " ");
+
+ if (strcasecmp(cmdbuf, "GET "));
+ {
+ struct recptypes *rcpt;
+ char *argbuf = &cmdbuf[4];
+
+ extract_token(internet_addr, argbuf, 0, '|', sizeof internet_addr);
+ rcpt = validate_recipients(internet_addr);
+ if ((rcpt != NULL)&&
+ (
+ (*rcpt->recp_local != '\0')||
+ (*rcpt->recp_room != '\0')||
+ (*rcpt->recp_ignet != '\0')))
+ {
+
+ cprintf("200 OK %s\n", internet_addr);
+ lprintf(CTDL_INFO, "sending 200 OK for the room %s\n", rcpt->display_recp);
+ }
+ else
+ {
+ cprintf("500 REJECT noone here by that name.\n");
+
+ lprintf(CTDL_INFO, "sending 500 REJECT noone here by that name: %s\n", internet_addr);
+ }
+ if (rcpt != NULL) free_recipients(rcpt);
+ }
+}
+
+void check_get_greeting(void) {
+/* dummy function, we have no greeting in this verry simple protocol. */
+}
+
+
+/*
+ * We don't know if the Contacts room exists so we just create it at login
+ */
+void vcard_create_room(void)
+{
+ struct ctdlroom qr;
+ struct visit vbuf;
+
+ /* Create the calendar room if it doesn't already exist */
+ create_room(USERCONTACTSROOM, 4, "", 0, 1, 0, VIEW_ADDRESSBOOK);
+
+ /* Set expiration policy to manual; otherwise objects will be lost! */
+ if (lgetroom(&qr, USERCONTACTSROOM)) {
+ lprintf(CTDL_ERR, "Couldn't get the user CONTACTS room!\n");
+ return;
+ }
+ qr.QRep.expire_mode = EXPIRE_MANUAL;
+ qr.QRdefaultview = VIEW_ADDRESSBOOK; /* 2 = address book view */
+ lputroom(&qr);
+
+ /* Set the view to a calendar view */
+ CtdlGetRelationship(&vbuf, &CC->user, &qr);
+ vbuf.v_view = 2; /* 2 = address book view */
+ CtdlSetRelationship(&vbuf, &CC->user, &qr);
+
+ return;
+}
+
+
+
+
+/*
+ * When a user logs in...
+ */
+void vcard_session_login_hook(void) {
+ struct vCard *v = NULL;
+
+ v = vcard_get_user(&CC->user);
+ extract_inet_email_addrs(CC->cs_inet_email, sizeof CC->cs_inet_email,
+ CC->cs_inet_other_emails, sizeof CC->cs_inet_other_emails,
+ v, 1);
+ extract_friendly_name(CC->cs_inet_fn, sizeof CC->cs_inet_fn, v);
+ vcard_free(v);
+
+ vcard_create_room();
+}
+
+
+/*
+ * Turn an arbitrary RFC822 address into a struct vCard for possible
+ * inclusion into an address book.
+ */
+struct vCard *vcard_new_from_rfc822_addr(char *addr) {
+ struct vCard *v;
+ char user[256], node[256], name[256], email[256], n[256], uid[256];
+ int i;
+
+ v = vcard_new();
+ if (v == NULL) return(NULL);
+
+ process_rfc822_addr(addr, user, node, name);
+ vcard_set_prop(v, "fn", name, 0);
+
+ vcard_fn_to_n(n, name, sizeof n);
+ vcard_set_prop(v, "n", n, 0);
+
+ snprintf(email, sizeof email, "%s@%s", user, node);
+ vcard_set_prop(v, "email;internet", email, 0);
+
+ snprintf(uid, sizeof uid, "collected: %s %s@%s", name, user, node);
+ for (i=0; i<strlen(uid); ++i) {
+ if (isspace(uid[i])) uid[i] = '_';
+ uid[i] = tolower(uid[i]);
+ }
+ vcard_set_prop(v, "UID", uid, 0);
+
+ return(v);
+}
+
+
+
+/*
+ * This is called by store_harvested_addresses() to remove from the
+ * list any addresses we already have in our address book.
+ */
+void strip_addresses_already_have(long msgnum, void *userdata) {
+ char *collected_addresses;
+ struct CtdlMessage *msg = NULL;
+ struct vCard *v;
+ char *value = NULL;
+ int i, j;
+ char addr[256], user[256], node[256], name[256];
+
+ collected_addresses = (char *)userdata;
+
+ msg = CtdlFetchMessage(msgnum, 1);
+ if (msg == NULL) return;
+ v = vcard_load(msg->cm_fields['M']);
+ CtdlFreeMessage(msg);
+
+ i = 0;
+ while (value = vcard_get_prop(v, "email", 1, i++, 0), value != NULL) {
+
+ for (j=0; j<num_tokens(collected_addresses, ','); ++j) {
+ extract_token(addr, collected_addresses, j, ',', sizeof addr);
+
+ /* Remove the address if we already have it! */
+ process_rfc822_addr(addr, user, node, name);
+ snprintf(addr, sizeof addr, "%s@%s", user, node);
+ if (!strcasecmp(value, addr)) {
+ remove_token(collected_addresses, j, ',');
+ }
+ }
+
+ }
+
+ vcard_free(v);
+}
+
+
+
+/*
+ * Back end function for store_harvested_addresses()
+ */
+void store_this_ha(struct addresses_to_be_filed *aptr) {
+ struct CtdlMessage *vmsg = NULL;
+ long vmsgnum = (-1L);
+ char *ser = NULL;
+ struct vCard *v = NULL;
+ char recipient[256];
+ int i;
+
+ /* First remove any addresses we already have in the address book */
+ usergoto(aptr->roomname, 0, 0, NULL, NULL);
+ CtdlForEachMessage(MSGS_ALL, 0, NULL, "^[Tt][Ee][Xx][Tt]/.*[Vv][Cc][Aa][Rr][Dd]$", NULL,
+ strip_addresses_already_have, aptr->collected_addresses);
+
+ if (strlen(aptr->collected_addresses) > 0)
+ for (i=0; i<num_tokens(aptr->collected_addresses, ','); ++i) {
+
+ /* Make a vCard out of each address */
+ extract_token(recipient, aptr->collected_addresses, i, ',', sizeof recipient);
+ striplt(recipient);
+ v = vcard_new_from_rfc822_addr(recipient);
+ if (v != NULL) {
+ vmsg = malloc(sizeof(struct CtdlMessage));
+ memset(vmsg, 0, sizeof(struct CtdlMessage));
+ vmsg->cm_magic = CTDLMESSAGE_MAGIC;
+ vmsg->cm_anon_type = MES_NORMAL;
+ vmsg->cm_format_type = FMT_RFC822;
+ vmsg->cm_fields['A'] = strdup("Citadel");
+ vmsg->cm_fields['E'] = strdup(vcard_get_prop(v, "UID", 0, 0, 0));
+ ser = vcard_serialize(v);
+ if (ser != NULL) {
+ vmsg->cm_fields['M'] = malloc(strlen(ser) + 1024);
+ sprintf(vmsg->cm_fields['M'],
+ "Content-type: " VCARD_MIME_TYPE
+ "\r\n\r\n%s\r\n", ser);
+ free(ser);
+ }
+ vcard_free(v);
+
+ lprintf(CTDL_DEBUG, "Adding contact: %s\n", recipient);
+ vmsgnum = CtdlSubmitMsg(vmsg, NULL, aptr->roomname);
+ CtdlFreeMessage(vmsg);
+ }
+ }
+
+ free(aptr->roomname);
+ free(aptr->collected_addresses);
+ free(aptr);
+}
+
+
+/*
+ * When a user sends a message, we may harvest one or more email addresses
+ * from the recipient list to be added to the user's address book. But we
+ * want to do this asynchronously so it doesn't keep the user waiting.
+ */
+void store_harvested_addresses(void) {
+
+ struct addresses_to_be_filed *aptr = NULL;
+
+ if (atbf == NULL) return;
+
+ begin_critical_section(S_ATBF);
+ while (atbf != NULL) {
+ aptr = atbf;
+ atbf = atbf->next;
+ end_critical_section(S_ATBF);
+ store_this_ha(aptr);
+ begin_critical_section(S_ATBF);
+ }
+ end_critical_section(S_ATBF);
+}
+
+
+/*
+ * Function to output vCard data as plain text. Nobody uses MSG0 anymore, so
+ * really this is just so we expose the vCard data to the full text indexer.
+ */
+void vcard_fixed_output(char *ptr, int len) {
+ char *serialized_vcard;
+ struct vCard *v;
+ char *key, *value;
+ int i = 0;
+
+ serialized_vcard = malloc(len + 1);
+ safestrncpy(serialized_vcard, ptr, len+1);
+ v = vcard_load(serialized_vcard);
+ free(serialized_vcard);
+
+ i = 0;
+ while (key = vcard_get_prop(v, "", 0, i, 1), key != NULL) {
+ value = vcard_get_prop(v, "", 0, i++, 0);
+ cprintf("%s\n", value);
+ }
+
+ vcard_free(v);
+}
+
+
+
+
+CTDL_MODULE_INIT(vcard)
+{
+ struct ctdlroom qr;
+ char filename[256];
+ FILE *fp;
+
+ CtdlRegisterSessionHook(vcard_session_login_hook, EVT_LOGIN);
+ CtdlRegisterMessageHook(vcard_upload_beforesave, EVT_BEFORESAVE);
+ CtdlRegisterMessageHook(vcard_upload_aftersave, EVT_AFTERSAVE);
+ CtdlRegisterDeleteHook(vcard_delete_remove);
+ CtdlRegisterProtoHook(cmd_regi, "REGI", "Enter registration info");
+ CtdlRegisterProtoHook(cmd_greg, "GREG", "Get registration info");
+ CtdlRegisterProtoHook(cmd_igab, "IGAB",
+ "Initialize Global Address Book");
+ CtdlRegisterProtoHook(cmd_qdir, "QDIR", "Query Directory");
+ CtdlRegisterProtoHook(cmd_gvsn, "GVSN", "Get Valid Screen Names");
+ CtdlRegisterProtoHook(cmd_gvea, "GVEA", "Get Valid Email Addresses");
+ CtdlRegisterProtoHook(cmd_dvca, "DVCA", "Dump VCard Addresses");
+ CtdlRegisterUserHook(vcard_newuser, EVT_NEWUSER);
+ CtdlRegisterUserHook(vcard_purge, EVT_PURGEUSER);
+ CtdlRegisterNetprocHook(vcard_extract_from_network);
+ CtdlRegisterSessionHook(store_harvested_addresses, EVT_TIMER);
+ CtdlRegisterFixedOutputHook("text/x-vcard", vcard_fixed_output);
+ CtdlRegisterFixedOutputHook("text/vcard", vcard_fixed_output);
+
+ /* Create the Global ADdress Book room if necessary */
+ create_room(ADDRESS_BOOK_ROOM, 3, "", 0, 1, 0, VIEW_ADDRESSBOOK);
+
+ /* Set expiration policy to manual; otherwise objects will be lost! */
+ if (!lgetroom(&qr, ADDRESS_BOOK_ROOM)) {
+ qr.QRep.expire_mode = EXPIRE_MANUAL;
+ qr.QRdefaultview = VIEW_ADDRESSBOOK; /* 2 = address book view */
+ lputroom(&qr);
+
+ /*
+ * Also make sure it has a netconfig file, so the networker runs
+ * on this room even if we don't share it with any other nodes.
+ * This allows the CANCEL messages (i.e. "Purge this vCard") to be
+ * purged.
+ */
+ assoc_file_name(filename, sizeof filename, &qr, ctdl_netcfg_dir);
+ fp = fopen(filename, "a");
+ if (fp != NULL) fclose(fp);
+ chown(filename, CTDLUID, (-1));
+ }
+
+ /* for postfix tcpdict */
+ CtdlRegisterServiceHook(config.c_pftcpdict_port, /* Postfix */
+ NULL,
+ check_get_greeting,
+ check_get,
+ NULL);
+
+ /* return our Subversion id for the Log */
+ return "$Id$";
+}
+++ /dev/null
-/*
- * $Id$
- *
- * Autocompletion of email recipients, etc.
- */
-
-#include "sysdep.h"
-#include <stdlib.h>
-#include <unistd.h>
-#include <stdio.h>
-#include <fcntl.h>
-#include <ctype.h>
-#include <signal.h>
-#include <pwd.h>
-#include <errno.h>
-#include <sys/types.h>
-
-#if TIME_WITH_SYS_TIME
-# include <sys/time.h>
-# include <time.h>
-#else
-# if HAVE_SYS_TIME_H
-# include <sys/time.h>
-# else
-# include <time.h>
-# endif
-#endif
-
-#include <sys/wait.h>
-#include <string.h>
-#include <limits.h>
-#include "citadel.h"
-#include "server.h"
-#include "citserver.h"
-#include "support.h"
-#include "config.h"
-#include "tools.h"
-#include "msgbase.h"
-#include "user_ops.h"
-#include "room_ops.h"
-#include "database.h"
-#include "vcard.h"
-#include "serv_fulltext.h"
-#include "serv_autocompletion.h"
-
-#include "ctdl_module.h"
-
-
-#ifndef HAVE_SNPRINTF
-#include "snprintf.h"
-#endif
-
-
-
-/*
- * Convert a structured name into a friendly name. Caller must free the
- * returned pointer.
- */
-char *n_to_fn(char *value) {
- char *nnn = NULL;
- int i;
-
- nnn = malloc(strlen(value) + 10);
- strcpy(nnn, "");
- extract_token(&nnn[strlen(nnn)] , value, 3, ';', 999);
- strcat(nnn, " ");
- extract_token(&nnn[strlen(nnn)] , value, 1, ';', 999);
- strcat(nnn, " ");
- extract_token(&nnn[strlen(nnn)] , value, 2, ';', 999);
- strcat(nnn, " ");
- extract_token(&nnn[strlen(nnn)] , value, 0, ';', 999);
- strcat(nnn, " ");
- extract_token(&nnn[strlen(nnn)] , value, 4, ';', 999);
- strcat(nnn, " ");
- for (i=0; i<strlen(nnn); ++i) {
- if (!strncmp(&nnn[i], " ", 2)) strcpy(&nnn[i], &nnn[i+1]);
- }
- striplt(nnn);
- return(nnn);
-}
-
-
-
-
-/*
- * Back end for cmd_auto()
- */
-void hunt_for_autocomplete(long msgnum, char *search_string) {
- struct CtdlMessage *msg;
- struct vCard *v;
- char *value = NULL;
- char *value2 = NULL;
- int i = 0;
- char *nnn = NULL;
-
- msg = CtdlFetchMessage(msgnum, 1);
- if (msg == NULL) return;
-
- v = vcard_load(msg->cm_fields['M']);
- CtdlFreeMessage(msg);
-
- /*
- * Try to match from a friendly name (the "fn" field). If there is
- * a match, return the entry in the form of:
- * Display Name <user@domain.org>
- */
- value = vcard_get_prop(v, "fn", 0, 0, 0);
- if (value != NULL) if (bmstrcasestr(value, search_string)) {
- value2 = vcard_get_prop(v, "email", 1, 0, 0);
- if (value2 == NULL) value2 = "";
- cprintf("%s <%s>\n", value, value2);
- vcard_free(v);
- return;
- }
-
- /*
- * Try to match from a structured name (the "n" field). If there is
- * a match, return the entry in the form of:
- * Display Name <user@domain.org>
- */
- value = vcard_get_prop(v, "n", 0, 0, 0);
- if (value != NULL) if (bmstrcasestr(value, search_string)) {
-
- value2 = vcard_get_prop(v, "email", 1, 0, 0);
- if (value2 == NULL) value2 = "";
- nnn = n_to_fn(value);
- cprintf("%s <%s>\n", nnn, value2);
- free(nnn);
- vcard_free(v);
- return;
- }
-
- /*
- * Try a partial match on all listed email addresses.
- */
- i = 0;
- while (value = vcard_get_prop(v, "email", 1, i++, 0), value != NULL) {
- if (bmstrcasestr(value, search_string)) {
- if (vcard_get_prop(v, "fn", 0, 0, 0)) {
- cprintf("%s <%s>\n", vcard_get_prop(v, "fn", 0, 0, 0), value);
- }
- else if (vcard_get_prop(v, "n", 0, 0, 0)) {
- nnn = n_to_fn(vcard_get_prop(v, "n", 0, 0, 0));
- cprintf("%s <%s>\n", nnn, value);
- free(nnn);
-
- }
- else {
- cprintf("%s\n", value);
- }
- vcard_free(v);
- return;
- }
- }
-
- vcard_free(v);
-}
-
-
-
-/*
- * Attempt to autocomplete an address based on a partial...
- */
-void cmd_auto(char *argbuf) {
- char hold_rm[ROOMNAMELEN];
- char search_string[256];
- long *msglist = NULL;
- int num_msgs = 0;
- long *fts_msgs = NULL;
- int fts_num_msgs = 0;
- struct cdbdata *cdbfr;
- int r = 0;
- int i = 0;
- int j = 0;
- int search_match = 0;
- char *rooms_to_try[] = { USERCONTACTSROOM, ADDRESS_BOOK_ROOM };
-
- if (CtdlAccessCheck(ac_logged_in)) return;
- extract_token(search_string, argbuf, 0, '|', sizeof search_string);
- if (strlen(search_string) == 0) {
- cprintf("%d You supplied an empty partial.\n",
- ERROR + ILLEGAL_VALUE);
- return;
- }
-
- strcpy(hold_rm, CC->room.QRname); /* save current room */
- cprintf("%d try these:\n", LISTING_FOLLOWS);
-
- /*
- * Gather up message pointers in rooms containing vCards
- */
- for (r=0; r < (sizeof(rooms_to_try) / sizeof(char *)); ++r) {
- if (getroom(&CC->room, rooms_to_try[r]) == 0) {
- cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long));
- if (cdbfr != NULL) {
- msglist = realloc(msglist, (num_msgs * sizeof(long)) + cdbfr->len + 1);
- memcpy(&msglist[num_msgs], cdbfr->ptr, cdbfr->len);
- num_msgs += (cdbfr->len / sizeof(long));
- cdb_free(cdbfr);
- }
- }
- }
-
- /*
- * Search-reduce the results if we have the full text index available
- */
- if (config.c_enable_fulltext) {
- ft_search(&fts_num_msgs, &fts_msgs, search_string);
- if (fts_msgs) {
- for (i=0; i<num_msgs; ++i) {
- search_match = 0;
- for (j=0; j<fts_num_msgs; ++j) {
- if (msglist[i] == fts_msgs[j]) {
- search_match = 1;
- j = fts_num_msgs + 1; /* end the search */
- }
- }
- if (!search_match) {
- msglist[i] = 0; /* invalidate this result */
- }
- }
- free(fts_msgs);
- }
- else {
- /* If no results, invalidate the whole list */
- free(msglist);
- msglist = NULL;
- num_msgs = 0;
- }
- }
-
- /*
- * Now output the ones that look interesting
- */
- if (num_msgs > 0) for (i=0; i<num_msgs; ++i) {
- if (msglist[i] != 0) {
- hunt_for_autocomplete(msglist[i], search_string);
- }
- }
-
- cprintf("000\n");
- if (strcmp(CC->room.QRname, hold_rm)) {
- getroom(&CC->room, hold_rm); /* return to saved room */
- }
-
- if (msglist) {
- free(msglist);
- }
-
-}
-
-
-CTDL_MODULE_INIT(autocompletion) {
- CtdlRegisterProtoHook(cmd_auto, "AUTO", "Do recipient autocompletion");
-
- /* return our Subversion id for the Log */
- return "$Id$";
-}
+++ /dev/null
-/*
- * $Id$
- *
- */
-
-
-char *serv_autocompletion_init(void);
+++ /dev/null
-/*
- * $Id$
- *
- * This module implementsserver commands related to the display and
- * manipulation of user "bio" files.
- *
- */
-
-#include "sysdep.h"
-#include <stdlib.h>
-#include <unistd.h>
-#include <stdio.h>
-#include <fcntl.h>
-#include <signal.h>
-#include <pwd.h>
-#include <errno.h>
-#include <sys/types.h>
-
-#if TIME_WITH_SYS_TIME
-# include <sys/time.h>
-# include <time.h>
-#else
-# if HAVE_SYS_TIME_H
-# include <sys/time.h>
-# else
-# include <time.h>
-# endif
-#endif
-
-#include <sys/wait.h>
-#include <string.h>
-#include <limits.h>
-#include "citadel.h"
-#include "server.h"
-#include "citserver.h"
-#include "support.h"
-#include "config.h"
-#include "control.h"
-#include "room_ops.h"
-#include "user_ops.h"
-#include "policy.h"
-#include "database.h"
-#include "msgbase.h"
-#include "tools.h"
-#include "citadel_dirs.h"
-
-#include "ctdl_module.h"
-
-/*
- * enter user bio
- */
-void cmd_ebio(char *cmdbuf) {
- char buf[SIZ];
- FILE *fp;
-
- unbuffer_output();
-
- if (!(CC->logged_in)) {
- cprintf("%d Not logged in.\n",ERROR + NOT_LOGGED_IN);
- return;
- }
-
- snprintf(buf, sizeof buf, "%s%ld",ctdl_bio_dir,CC->user.usernum);
- fp = fopen(buf,"w");
- if (fp == NULL) {
- cprintf("%d Cannot create file: %s\n", ERROR + INTERNAL_ERROR,
- strerror(errno));
- return;
- }
- cprintf("%d \n",SEND_LISTING);
- while(client_getln(buf, sizeof buf), strcmp(buf,"000")) {
- if (ftell(fp) < config.c_maxmsglen) {
- fprintf(fp,"%s\n",buf);
- }
- }
- fclose(fp);
-}
-
-/*
- * read user bio
- */
-void cmd_rbio(char *cmdbuf)
-{
- struct ctdluser ruser;
- char buf[256];
- FILE *fp;
-
- extract_token(buf, cmdbuf, 0, '|', sizeof buf);
- if (getuser(&ruser, buf) != 0) {
- cprintf("%d No such user.\n",ERROR + NO_SUCH_USER);
- return;
- }
- snprintf(buf, sizeof buf, "%s%ld",ctdl_bio_dir,ruser.usernum);
-
- cprintf("%d OK|%s|%ld|%d|%ld|%ld|%ld\n", LISTING_FOLLOWS,
- ruser.fullname, ruser.usernum, ruser.axlevel,
- (long)ruser.lastcall, ruser.timescalled, ruser.posted);
- fp = fopen(buf,"r");
- if (fp == NULL)
- cprintf("%s has no bio on file.\n", ruser.fullname);
- else {
- while (fgets(buf, sizeof buf, fp) != NULL) cprintf("%s",buf);
- fclose(fp);
- }
- cprintf("000\n");
-}
-
-/*
- * list of users who have entered bios
- */
-void cmd_lbio(char *cmdbuf) {
- char buf[256];
- FILE *ls;
- struct ctdluser usbuf;
- char listbios[256];
-
- snprintf(listbios, sizeof(listbios),"cd %s; ls",ctdl_bio_dir);
- ls = popen(listbios, "r");
- if (ls == NULL) {
- cprintf("%d Cannot open listing.\n", ERROR + FILE_NOT_FOUND);
- return;
- }
-
- cprintf("%d\n", LISTING_FOLLOWS);
- while (fgets(buf, sizeof buf, ls)!=NULL)
- if (getuserbynumber(&usbuf,atol(buf))==0)
- cprintf("%s\n", usbuf.fullname);
- pclose(ls);
- cprintf("000\n");
-}
-
-
-
-
-CTDL_MODULE_INIT(bio)
-{
- CtdlRegisterProtoHook(cmd_ebio, "EBIO", "Enter your bio");
- CtdlRegisterProtoHook(cmd_rbio, "RBIO", "Read a user's bio");
- CtdlRegisterProtoHook(cmd_lbio, "LBIO", "List users with bios");
-
- /* return our Subversion id for the Log */
- return "$Id$";
-}
-
-
+++ /dev/null
-/*
- * $Id$
- *
- * This module implements iCalendar object processing and the Calendar>
- * room on a Citadel server. It handles iCalendar objects using the
- * iTIP protocol. See RFCs 2445 and 2446.
- *
- */
-
-#define PRODID "-//Citadel//NONSGML Citadel Calendar//EN"
-
-#include "sysdep.h"
-#include <stdlib.h>
-#include <unistd.h>
-#include <sys/types.h>
-#include <limits.h>
-#include <stdio.h>
-#include <string.h>
-#ifdef HAVE_STRINGS_H
-#include <strings.h>
-#endif
-#include "citadel.h"
-#include "server.h"
-#include "citserver.h"
-#include "support.h"
-#include "config.h"
-#include "user_ops.h"
-#include "room_ops.h"
-#include "tools.h"
-#include "msgbase.h"
-#include "mime_parser.h"
-#include "internet_addressing.h"
-#include "serv_calendar.h"
-#include "euidindex.h"
-#include "ctdl_module.h"
-
-#ifdef CITADEL_WITH_CALENDAR_SERVICE
-
-#include <ical.h>
-#include "ical_dezonify.h"
-
-
-
-struct ical_respond_data {
- char desired_partnum[SIZ];
- icalcomponent *cal;
-};
-
-
-/*
- * Utility function to create a new VCALENDAR component with some of the
- * required fields already set the way we like them.
- */
-icalcomponent *icalcomponent_new_citadel_vcalendar(void) {
- icalcomponent *encaps;
-
- encaps = icalcomponent_new_vcalendar();
- if (encaps == NULL) {
- lprintf(CTDL_CRIT, "Error at %s:%d - could not allocate component!\n",
- __FILE__, __LINE__);
- return NULL;
- }
-
- /* Set the Product ID */
- icalcomponent_add_property(encaps, icalproperty_new_prodid(PRODID));
-
- /* Set the Version Number */
- icalcomponent_add_property(encaps, icalproperty_new_version("2.0"));
-
- return(encaps);
-}
-
-
-/*
- * Utility function to encapsulate a subcomponent into a full VCALENDAR
- */
-icalcomponent *ical_encapsulate_subcomponent(icalcomponent *subcomp) {
- icalcomponent *encaps;
-
- /* If we're already looking at a full VCALENDAR component,
- * don't bother ... just return itself.
- */
- if (icalcomponent_isa(subcomp) == ICAL_VCALENDAR_COMPONENT) {
- return subcomp;
- }
-
- /* Encapsulate the VEVENT component into a complete VCALENDAR */
- encaps = icalcomponent_new_citadel_vcalendar();
- if (encaps == NULL) return NULL;
-
- /* Encapsulate the subcomponent inside */
- icalcomponent_add_component(encaps, subcomp);
-
- /* Convert all timestamps to UTC so we don't have to deal with
- * stupid VTIMEZONE crap.
- */
- ical_dezonify(encaps);
-
- /* Return the object we just created. */
- return(encaps);
-}
-
-
-
-
-/*
- * Write a calendar object into the specified user's calendar room.
- */
-void ical_write_to_cal(struct ctdluser *u, icalcomponent *cal) {
- char temp[PATH_MAX];
- FILE *fp;
- char *ser;
- icalcomponent *encaps;
-
- if (cal == NULL) return;
-
- /* If the supplied object is a subcomponent, encapsulate it in
- * a full VCALENDAR component, and save that instead.
- */
- if (icalcomponent_isa(cal) != ICAL_VCALENDAR_COMPONENT) {
- encaps = ical_encapsulate_subcomponent(
- icalcomponent_new_clone(cal)
- );
- ical_write_to_cal(u, encaps);
- icalcomponent_free(encaps);
- return;
- }
-
- CtdlMakeTempFileName(temp, sizeof temp);
- ser = icalcomponent_as_ical_string(cal);
- if (ser == NULL) return;
-
- /* Make a temp file out of it */
- fp = fopen(temp, "w");
- if (fp == NULL) return;
- fwrite(ser, strlen(ser), 1, fp);
- fclose(fp);
-
- /* This handy API function does all the work for us.
- */
- CtdlWriteObject(USERCALENDARROOM, /* which room */
- "text/calendar", /* MIME type */
- temp, /* temp file */
- u, /* which user */
- 0, /* not binary */
- 0, /* don't delete others of this type */
- 0); /* no flags */
-
- unlink(temp);
-}
-
-
-/*
- * Add a calendar object to the user's calendar
- *
- * ok because it uses ical_write_to_cal()
- */
-void ical_add(icalcomponent *cal, int recursion_level) {
- icalcomponent *c;
-
- /*
- * The VEVENT subcomponent is the one we're interested in saving.
- */
- if (icalcomponent_isa(cal) == ICAL_VEVENT_COMPONENT) {
-
- ical_write_to_cal(&CC->user, cal);
-
- }
-
- /* If the component has subcomponents, recurse through them. */
- for (c = icalcomponent_get_first_component(cal, ICAL_ANY_COMPONENT);
- (c != 0);
- c = icalcomponent_get_next_component(cal, ICAL_ANY_COMPONENT)) {
- /* Recursively process subcomponent */
- ical_add(c, recursion_level+1);
- }
-
-}
-
-
-
-/*
- * Send a reply to a meeting invitation.
- *
- * 'request' is the invitation to reply to.
- * 'action' is the string "accept" or "decline" or "tentative".
- *
- */
-void ical_send_a_reply(icalcomponent *request, char *action) {
- icalcomponent *the_reply = NULL;
- icalcomponent *vevent = NULL;
- icalproperty *attendee = NULL;
- char attendee_string[SIZ];
- icalproperty *organizer = NULL;
- char organizer_string[SIZ];
- icalproperty *summary = NULL;
- char summary_string[SIZ];
- icalproperty *me_attend = NULL;
- struct recptypes *recp = NULL;
- icalparameter *partstat = NULL;
- char *serialized_reply = NULL;
- char *reply_message_text = NULL;
- struct CtdlMessage *msg = NULL;
- struct recptypes *valid = NULL;
-
- strcpy(organizer_string, "");
- strcpy(summary_string, "Calendar item");
-
- if (request == NULL) {
- lprintf(CTDL_ERR, "ERROR: trying to reply to NULL event?\n");
- return;
- }
-
- the_reply = icalcomponent_new_clone(request);
- if (the_reply == NULL) {
- lprintf(CTDL_ERR, "ERROR: cannot clone request\n");
- return;
- }
-
- /* Change the method from REQUEST to REPLY */
- icalcomponent_set_method(the_reply, ICAL_METHOD_REPLY);
-
- vevent = icalcomponent_get_first_component(the_reply, ICAL_VEVENT_COMPONENT);
- if (vevent != NULL) {
- /* Hunt for attendees, removing ones that aren't us.
- * (Actually, remove them all, cloning our own one so we can
- * re-insert it later)
- */
- while (attendee = icalcomponent_get_first_property(vevent,
- ICAL_ATTENDEE_PROPERTY), (attendee != NULL)
- ) {
- if (icalproperty_get_attendee(attendee)) {
- strcpy(attendee_string,
- icalproperty_get_attendee(attendee) );
- if (!strncasecmp(attendee_string, "MAILTO:", 7)) {
- strcpy(attendee_string, &attendee_string[7]);
- striplt(attendee_string);
- recp = validate_recipients(attendee_string);
- if (recp != NULL) {
- if (!strcasecmp(recp->recp_local, CC->user.fullname)) {
- if (me_attend) icalproperty_free(me_attend);
- me_attend = icalproperty_new_clone(attendee);
- }
- free_recipients(recp);
- }
- }
- }
- /* Remove it... */
- icalcomponent_remove_property(vevent, attendee);
- icalproperty_free(attendee);
- }
-
- /* We found our own address in the attendee list. */
- if (me_attend) {
- /* Change the partstat from NEEDS-ACTION to ACCEPT or DECLINE */
- icalproperty_remove_parameter(me_attend, ICAL_PARTSTAT_PARAMETER);
-
- if (!strcasecmp(action, "accept")) {
- partstat = icalparameter_new_partstat(ICAL_PARTSTAT_ACCEPTED);
- }
- else if (!strcasecmp(action, "decline")) {
- partstat = icalparameter_new_partstat(ICAL_PARTSTAT_DECLINED);
- }
- else if (!strcasecmp(action, "tentative")) {
- partstat = icalparameter_new_partstat(ICAL_PARTSTAT_TENTATIVE);
- }
-
- if (partstat) icalproperty_add_parameter(me_attend, partstat);
-
- /* Now insert it back into the vevent. */
- icalcomponent_add_property(vevent, me_attend);
- }
-
- /* Figure out who to send this thing to */
- organizer = icalcomponent_get_first_property(vevent, ICAL_ORGANIZER_PROPERTY);
- if (organizer != NULL) {
- if (icalproperty_get_organizer(organizer)) {
- strcpy(organizer_string,
- icalproperty_get_organizer(organizer) );
- }
- }
- if (!strncasecmp(organizer_string, "MAILTO:", 7)) {
- strcpy(organizer_string, &organizer_string[7]);
- striplt(organizer_string);
- } else {
- strcpy(organizer_string, "");
- }
-
- /* Extract the summary string -- we'll use it as the
- * message subject for the reply
- */
- summary = icalcomponent_get_first_property(vevent, ICAL_SUMMARY_PROPERTY);
- if (summary != NULL) {
- if (icalproperty_get_summary(summary)) {
- strcpy(summary_string,
- icalproperty_get_summary(summary) );
- }
- }
- }
-
- /* Now generate the reply message and send it out. */
- serialized_reply = strdup(icalcomponent_as_ical_string(the_reply));
- icalcomponent_free(the_reply); /* don't need this anymore */
- if (serialized_reply == NULL) return;
-
- reply_message_text = malloc(strlen(serialized_reply) + SIZ);
- if (reply_message_text != NULL) {
- sprintf(reply_message_text,
- "Content-type: text/calendar charset=\"utf-8\"\r\n\r\n%s\r\n",
- serialized_reply
- );
-
- msg = CtdlMakeMessage(&CC->user,
- organizer_string, /* to */
- "", /* cc */
- CC->room.QRname, 0, FMT_RFC822,
- "",
- "",
- summary_string, /* Use summary for subject */
- NULL,
- reply_message_text);
-
- if (msg != NULL) {
- valid = validate_recipients(organizer_string);
- CtdlSubmitMsg(msg, valid, "");
- CtdlFreeMessage(msg);
- free_recipients(valid);
- }
- }
- free(serialized_reply);
-}
-
-
-
-/*
- * Callback function for mime parser that hunts for calendar content types
- * and turns them into calendar objects
- */
-void ical_locate_part(char *name, char *filename, char *partnum, char *disp,
- void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
- void *cbuserdata) {
-
- struct ical_respond_data *ird = NULL;
-
- ird = (struct ical_respond_data *) cbuserdata;
-
- /* desired_partnum can be set to "_HUNT_" to have it just look for
- * the first part with a content type of text/calendar. Otherwise
- * we have to only process the right one.
- */
- if (strcasecmp(ird->desired_partnum, "_HUNT_")) {
- if (strcasecmp(partnum, ird->desired_partnum)) {
- return;
- }
- }
-
- if (strcasecmp(cbtype, "text/calendar")) {
- return;
- }
-
- if (ird->cal != NULL) {
- icalcomponent_free(ird->cal);
- ird->cal = NULL;
- }
-
- ird->cal = icalcomponent_new_from_string(content);
- if (ird->cal != NULL) {
- ical_dezonify(ird->cal);
- }
-}
-
-
-/*
- * Respond to a meeting request.
- */
-void ical_respond(long msgnum, char *partnum, char *action) {
- struct CtdlMessage *msg = NULL;
- struct ical_respond_data ird;
-
- if (
- (strcasecmp(action, "accept"))
- && (strcasecmp(action, "decline"))
- ) {
- cprintf("%d Action must be 'accept' or 'decline'\n",
- ERROR + ILLEGAL_VALUE
- );
- return;
- }
-
- msg = CtdlFetchMessage(msgnum, 1);
- if (msg == NULL) {
- cprintf("%d Message %ld not found.\n",
- ERROR + ILLEGAL_VALUE,
- (long)msgnum
- );
- return;
- }
-
- memset(&ird, 0, sizeof ird);
- strcpy(ird.desired_partnum, partnum);
- mime_parser(msg->cm_fields['M'],
- NULL,
- *ical_locate_part, /* callback function */
- NULL, NULL,
- (void *) &ird, /* user data */
- 0
- );
-
- /* We're done with the incoming message, because we now have a
- * calendar object in memory.
- */
- CtdlFreeMessage(msg);
-
- /*
- * Here is the real meat of this function. Handle the event.
- */
- if (ird.cal != NULL) {
- /* Save this in the user's calendar if necessary */
- if (!strcasecmp(action, "accept")) {
- ical_add(ird.cal, 0);
- }
-
- /* Send a reply if necessary */
- if (icalcomponent_get_method(ird.cal) == ICAL_METHOD_REQUEST) {
- ical_send_a_reply(ird.cal, action);
- }
-
- /* Now that we've processed this message, we don't need it
- * anymore. So delete it. (NOTE we don't do this anymore.)
- CtdlDeleteMessages(CC->room.QRname, &msgnum, 1, "");
- */
-
- /* Free the memory we allocated and return a response. */
- icalcomponent_free(ird.cal);
- ird.cal = NULL;
- cprintf("%d ok\n", CIT_OK);
- return;
- }
- else {
- cprintf("%d No calendar object found\n", ERROR + ROOM_NOT_FOUND);
- return;
- }
-
- /* should never get here */
-}
-
-
-/*
- * Figure out the UID of the calendar event being referred to in a
- * REPLY object. This function is recursive.
- */
-void ical_learn_uid_of_reply(char *uidbuf, icalcomponent *cal) {
- icalcomponent *subcomponent;
- icalproperty *p;
-
- /* If this object is a REPLY, then extract the UID. */
- if (icalcomponent_isa(cal) == ICAL_VEVENT_COMPONENT) {
- p = icalcomponent_get_first_property(cal, ICAL_UID_PROPERTY);
- if (p != NULL) {
- strcpy(uidbuf, icalproperty_get_comment(p));
- }
- }
-
- /* Otherwise, recurse through any VEVENT subcomponents. We do NOT want the
- * UID of the reply; we want the UID of the invitation being replied to.
- */
- for (subcomponent = icalcomponent_get_first_component(cal, ICAL_VEVENT_COMPONENT);
- subcomponent != NULL;
- subcomponent = icalcomponent_get_next_component(cal, ICAL_VEVENT_COMPONENT) ) {
- ical_learn_uid_of_reply(uidbuf, subcomponent);
- }
-}
-
-
-/*
- * ical_update_my_calendar_with_reply() refers to this callback function; when we
- * locate the message containing the calendar event we're replying to, this function
- * gets called. It basically just sticks the message number in a supplied buffer.
- */
-void ical_hunt_for_event_to_update(long msgnum, void *data) {
- long *msgnumptr;
-
- msgnumptr = (long *) data;
- *msgnumptr = msgnum;
-}
-
-
-struct original_event_container {
- icalcomponent *c;
-};
-
-/*
- * Callback function for mime parser that hunts for calendar content types
- * and turns them into calendar objects (called by ical_update_my_calendar_with_reply()
- * to fetch the object being updated)
- */
-void ical_locate_original_event(char *name, char *filename, char *partnum, char *disp,
- void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
- void *cbuserdata) {
-
- struct original_event_container *oec = NULL;
-
- if (strcasecmp(cbtype, "text/calendar")) {
- return;
- }
- oec = (struct original_event_container *) cbuserdata;
- if (oec->c != NULL) {
- icalcomponent_free(oec->c);
- }
- oec->c = icalcomponent_new_from_string(content);
-}
-
-
-/*
- * Merge updated attendee information from a REPLY into an existing event.
- */
-void ical_merge_attendee_reply(icalcomponent *event, icalcomponent *reply) {
- icalcomponent *c;
- icalproperty *e_attendee, *r_attendee;
-
- /* First things first. If we're not looking at a VEVENT component,
- * recurse through subcomponents until we find one.
- */
- if (icalcomponent_isa(event) != ICAL_VEVENT_COMPONENT) {
- for (c = icalcomponent_get_first_component(event, ICAL_VEVENT_COMPONENT);
- c != NULL;
- c = icalcomponent_get_next_component(event, ICAL_VEVENT_COMPONENT) ) {
- ical_merge_attendee_reply(c, reply);
- }
- return;
- }
-
- /* Now do the same thing with the reply.
- */
- if (icalcomponent_isa(reply) != ICAL_VEVENT_COMPONENT) {
- for (c = icalcomponent_get_first_component(reply, ICAL_VEVENT_COMPONENT);
- c != NULL;
- c = icalcomponent_get_next_component(reply, ICAL_VEVENT_COMPONENT) ) {
- ical_merge_attendee_reply(event, c);
- }
- return;
- }
-
- /* Clone the reply, because we're going to rip its guts out. */
- reply = icalcomponent_new_clone(reply);
-
- /* At this point we're looking at the correct subcomponents.
- * Iterate through the attendees looking for a match.
- */
-STARTOVER:
- for (e_attendee = icalcomponent_get_first_property(event, ICAL_ATTENDEE_PROPERTY);
- e_attendee != NULL;
- e_attendee = icalcomponent_get_next_property(event, ICAL_ATTENDEE_PROPERTY)) {
-
- for (r_attendee = icalcomponent_get_first_property(reply, ICAL_ATTENDEE_PROPERTY);
- r_attendee != NULL;
- r_attendee = icalcomponent_get_next_property(reply, ICAL_ATTENDEE_PROPERTY)) {
-
- /* Check to see if these two attendees match...
- */
- if (!strcasecmp(
- icalproperty_get_attendee(e_attendee),
- icalproperty_get_attendee(r_attendee)
- )) {
- /* ...and if they do, remove the attendee from the event
- * and replace it with the attendee from the reply. (The
- * reply's copy will have the same address, but an updated
- * status.)
- */
- icalcomponent_remove_property(event, e_attendee);
- icalproperty_free(e_attendee);
- icalcomponent_remove_property(reply, r_attendee);
- icalcomponent_add_property(event, r_attendee);
-
- /* Since we diddled both sets of attendees, we have to start
- * the iteration over again. This will not create an infinite
- * loop because we removed the attendee from the reply. (That's
- * why we cloned the reply, and that's what we mean by "ripping
- * its guts out.")
- */
- goto STARTOVER;
- }
-
- }
- }
-
- /* Free the *clone* of the reply. */
- icalcomponent_free(reply);
-}
-
-
-
-
-/*
- * Handle an incoming RSVP (object with method==ICAL_METHOD_REPLY) for a
- * calendar event. The object has already been deserialized for us; all
- * we have to do here is hunt for the event in our calendar, merge in the
- * updated attendee status, and save it again.
- *
- * This function returns 0 on success, 1 if the event was not found in the
- * user's calendar, or 2 if an internal error occurred.
- */
-int ical_update_my_calendar_with_reply(icalcomponent *cal) {
- char uid[SIZ];
- char hold_rm[ROOMNAMELEN];
- long msgnum_being_replaced = 0;
- struct CtdlMessage *msg = NULL;
- struct original_event_container oec;
- icalcomponent *original_event;
- char *serialized_event = NULL;
- char roomname[ROOMNAMELEN];
- char *message_text = NULL;
-
- /* Figure out just what event it is we're dealing with */
- strcpy(uid, "--==<< InVaLiD uId >>==--");
- ical_learn_uid_of_reply(uid, cal);
- lprintf(CTDL_DEBUG, "UID of event being replied to is <%s>\n", uid);
-
- strcpy(hold_rm, CC->room.QRname); /* save current room */
-
- if (getroom(&CC->room, USERCALENDARROOM) != 0) {
- getroom(&CC->room, hold_rm);
- lprintf(CTDL_CRIT, "cannot get user calendar room\n");
- return(2);
- }
-
- /*
- * Look in the EUID index for a message with
- * the Citadel EUID set to the value we're looking for. Since
- * Citadel always sets the message EUID to the vCalendar UID of
- * the event, this will work.
- */
- msgnum_being_replaced = locate_message_by_euid(uid, &CC->room);
-
- getroom(&CC->room, hold_rm); /* return to saved room */
-
- lprintf(CTDL_DEBUG, "msgnum_being_replaced == %ld\n", msgnum_being_replaced);
- if (msgnum_being_replaced == 0) {
- return(1); /* no calendar event found */
- }
-
- /* Now we know the ID of the message containing the event being updated.
- * We don't actually have to delete it; that'll get taken care of by the
- * server when we save another event with the same UID. This just gives
- * us the ability to load the event into memory so we can diddle the
- * attendees.
- */
- msg = CtdlFetchMessage(msgnum_being_replaced, 1);
- if (msg == NULL) {
- return(2); /* internal error */
- }
- oec.c = NULL;
- mime_parser(msg->cm_fields['M'],
- NULL,
- *ical_locate_original_event, /* callback function */
- NULL, NULL,
- &oec, /* user data */
- 0
- );
- CtdlFreeMessage(msg);
-
- original_event = oec.c;
- if (original_event == NULL) {
- lprintf(CTDL_ERR, "ERROR: Original_component is NULL.\n");
- return(2);
- }
-
- /* Merge the attendee's updated status into the event */
- ical_merge_attendee_reply(original_event, cal);
-
- /* Serialize it */
- serialized_event = strdup(icalcomponent_as_ical_string(original_event));
- icalcomponent_free(original_event); /* Don't need this anymore. */
- if (serialized_event == NULL) return(2);
-
- MailboxName(roomname, sizeof roomname, &CC->user, USERCALENDARROOM);
-
- message_text = malloc(strlen(serialized_event) + SIZ);
- if (message_text != NULL) {
- sprintf(message_text,
- "Content-type: text/calendar charset=\"utf-8\"\r\n\r\n%s\r\n",
- serialized_event
- );
-
- msg = CtdlMakeMessage(&CC->user,
- "", /* No recipient */
- "", /* No recipient */
- roomname,
- 0, FMT_RFC822,
- "",
- "",
- "", /* no subject */
- NULL,
- message_text);
-
- if (msg != NULL) {
- CIT_ICAL->avoid_sending_invitations = 1;
- CtdlSubmitMsg(msg, NULL, roomname);
- CtdlFreeMessage(msg);
- CIT_ICAL->avoid_sending_invitations = 0;
- }
- }
- free(serialized_event);
- return(0);
-}
-
-
-/*
- * Handle an incoming RSVP for an event. (This is the server subcommand part; it
- * simply extracts the calendar object from the message, deserializes it, and
- * passes it up to ical_update_my_calendar_with_reply() for processing.
- */
-void ical_handle_rsvp(long msgnum, char *partnum, char *action) {
- struct CtdlMessage *msg = NULL;
- struct ical_respond_data ird;
- int ret;
-
- if (
- (strcasecmp(action, "update"))
- && (strcasecmp(action, "ignore"))
- ) {
- cprintf("%d Action must be 'update' or 'ignore'\n",
- ERROR + ILLEGAL_VALUE
- );
- return;
- }
-
- msg = CtdlFetchMessage(msgnum, 1);
- if (msg == NULL) {
- cprintf("%d Message %ld not found.\n",
- ERROR + ILLEGAL_VALUE,
- (long)msgnum
- );
- return;
- }
-
- memset(&ird, 0, sizeof ird);
- strcpy(ird.desired_partnum, partnum);
- mime_parser(msg->cm_fields['M'],
- NULL,
- *ical_locate_part, /* callback function */
- NULL, NULL,
- (void *) &ird, /* user data */
- 0
- );
-
- /* We're done with the incoming message, because we now have a
- * calendar object in memory.
- */
- CtdlFreeMessage(msg);
-
- /*
- * Here is the real meat of this function. Handle the event.
- */
- if (ird.cal != NULL) {
- /* Update the user's calendar if necessary */
- if (!strcasecmp(action, "update")) {
- ret = ical_update_my_calendar_with_reply(ird.cal);
- if (ret == 0) {
- cprintf("%d Your calendar has been updated with this reply.\n",
- CIT_OK);
- }
- else if (ret == 1) {
- cprintf("%d This event does not exist in your calendar.\n",
- ERROR + FILE_NOT_FOUND);
- }
- else {
- cprintf("%d An internal error occurred.\n",
- ERROR + INTERNAL_ERROR);
- }
- }
- else {
- cprintf("%d This reply has been ignored.\n", CIT_OK);
- }
-
- /* Now that we've processed this message, we don't need it
- * anymore. So delete it. (Don't do this anymore.)
- CtdlDeleteMessages(CC->room.QRname, &msgnum, 1, "");
- */
-
- /* Free the memory we allocated and return a response. */
- icalcomponent_free(ird.cal);
- ird.cal = NULL;
- return;
- }
- else {
- cprintf("%d No calendar object found\n", ERROR + ROOM_NOT_FOUND);
- return;
- }
-
- /* should never get here */
-}
-
-
-/*
- * Search for a property in both the top level and in a VEVENT subcomponent
- */
-icalproperty *ical_ctdl_get_subprop(
- icalcomponent *cal,
- icalproperty_kind which_prop
-) {
- icalproperty *p;
- icalcomponent *c;
-
- p = icalcomponent_get_first_property(cal, which_prop);
- if (p == NULL) {
- c = icalcomponent_get_first_component(cal,
- ICAL_VEVENT_COMPONENT);
- if (c != NULL) {
- p = icalcomponent_get_first_property(c, which_prop);
- }
- }
- return p;
-}
-
-
-/*
- * Check to see if two events overlap. Returns nonzero if they do.
- * (This function is used in both Citadel and WebCit. If you change it in
- * one place, change it in the other. Better yet, put it in a library.)
- */
-int ical_ctdl_is_overlap(
- struct icaltimetype t1start,
- struct icaltimetype t1end,
- struct icaltimetype t2start,
- struct icaltimetype t2end
-) {
-
- if (icaltime_is_null_time(t1start)) return(0);
- if (icaltime_is_null_time(t2start)) return(0);
-
- /* First, check for all-day events */
- if (t1start.is_date) {
- if (!icaltime_compare_date_only(t1start, t2start)) {
- return(1);
- }
- if (!icaltime_is_null_time(t2end)) {
- if (!icaltime_compare_date_only(t1start, t2end)) {
- return(1);
- }
- }
- }
-
- if (t2start.is_date) {
- if (!icaltime_compare_date_only(t2start, t1start)) {
- return(1);
- }
- if (!icaltime_is_null_time(t1end)) {
- if (!icaltime_compare_date_only(t2start, t1end)) {
- return(1);
- }
- }
- }
-
- /* Now check for overlaps using date *and* time. */
-
- /* First, bail out if either event 1 or event 2 is missing end time. */
- if (icaltime_is_null_time(t1end)) return(0);
- if (icaltime_is_null_time(t2end)) return(0);
-
- /* If event 1 ends before event 2 starts, we're in the clear. */
- if (icaltime_compare(t1end, t2start) <= 0) return(0);
-
- /* If event 2 ends before event 1 starts, we're also ok. */
- if (icaltime_compare(t2end, t1start) <= 0) return(0);
-
- /* Otherwise, they overlap. */
- return(1);
-}
-
-
-
-/*
- * Backend for ical_hunt_for_conflicts()
- */
-void ical_hunt_for_conflicts_backend(long msgnum, void *data) {
- icalcomponent *cal;
- struct CtdlMessage *msg = NULL;
- struct ical_respond_data ird;
- struct icaltimetype t1start, t1end, t2start, t2end;
- icalproperty *p;
- char conflict_event_uid[SIZ];
- char conflict_event_summary[SIZ];
- char compare_uid[SIZ];
-
- cal = (icalcomponent *)data;
- strcpy(compare_uid, "");
- strcpy(conflict_event_uid, "");
- strcpy(conflict_event_summary, "");
-
- msg = CtdlFetchMessage(msgnum, 1);
- if (msg == NULL) return;
- memset(&ird, 0, sizeof ird);
- strcpy(ird.desired_partnum, "_HUNT_");
- mime_parser(msg->cm_fields['M'],
- NULL,
- *ical_locate_part, /* callback function */
- NULL, NULL,
- (void *) &ird, /* user data */
- 0
- );
- CtdlFreeMessage(msg);
-
- if (ird.cal == NULL) return;
-
- t1start = icaltime_null_time();
- t1end = icaltime_null_time();
- t2start = icaltime_null_time();
- t1end = icaltime_null_time();
-
- /* Now compare cal to ird.cal */
- p = ical_ctdl_get_subprop(ird.cal, ICAL_DTSTART_PROPERTY);
- if (p == NULL) return;
- if (p != NULL) t2start = icalproperty_get_dtstart(p);
-
- p = ical_ctdl_get_subprop(ird.cal, ICAL_DTEND_PROPERTY);
- if (p != NULL) t2end = icalproperty_get_dtend(p);
-
- p = ical_ctdl_get_subprop(cal, ICAL_DTSTART_PROPERTY);
- if (p == NULL) return;
- if (p != NULL) t1start = icalproperty_get_dtstart(p);
-
- p = ical_ctdl_get_subprop(cal, ICAL_DTEND_PROPERTY);
- if (p != NULL) t1end = icalproperty_get_dtend(p);
-
- p = ical_ctdl_get_subprop(cal, ICAL_UID_PROPERTY);
- if (p != NULL) {
- strcpy(compare_uid, icalproperty_get_comment(p));
- }
-
- p = ical_ctdl_get_subprop(ird.cal, ICAL_UID_PROPERTY);
- if (p != NULL) {
- strcpy(conflict_event_uid, icalproperty_get_comment(p));
- }
-
- p = ical_ctdl_get_subprop(ird.cal, ICAL_SUMMARY_PROPERTY);
- if (p != NULL) {
- strcpy(conflict_event_summary, icalproperty_get_comment(p));
- }
-
- icalcomponent_free(ird.cal);
-
- if (ical_ctdl_is_overlap(t1start, t1end, t2start, t2end)) {
- cprintf("%ld||%s|%s|%d|\n",
- msgnum,
- conflict_event_uid,
- conflict_event_summary,
- ( ((strlen(compare_uid)>0)
- &&(!strcasecmp(compare_uid,
- conflict_event_uid))) ? 1 : 0
- )
- );
- }
-}
-
-
-
-/*
- * Phase 2 of "hunt for conflicts" operation.
- * At this point we have a calendar object which represents the VEVENT that
- * we're considering adding to the calendar. Now hunt through the user's
- * calendar room, and output zero or more existing VEVENTs which conflict
- * with this one.
- */
-void ical_hunt_for_conflicts(icalcomponent *cal) {
- char hold_rm[ROOMNAMELEN];
-
- strcpy(hold_rm, CC->room.QRname); /* save current room */
-
- if (getroom(&CC->room, USERCALENDARROOM) != 0) {
- getroom(&CC->room, hold_rm);
- cprintf("%d You do not have a calendar.\n", ERROR + ROOM_NOT_FOUND);
- return;
- }
-
- cprintf("%d Conflicting events:\n", LISTING_FOLLOWS);
-
- CtdlForEachMessage(MSGS_ALL, 0, NULL,
- NULL,
- NULL,
- ical_hunt_for_conflicts_backend,
- (void *) cal
- );
-
- cprintf("000\n");
- getroom(&CC->room, hold_rm); /* return to saved room */
-
-}
-
-
-
-/*
- * Hunt for conflicts (Phase 1 -- retrieve the object and call Phase 2)
- */
-void ical_conflicts(long msgnum, char *partnum) {
- struct CtdlMessage *msg = NULL;
- struct ical_respond_data ird;
-
- msg = CtdlFetchMessage(msgnum, 1);
- if (msg == NULL) {
- cprintf("%d Message %ld not found.\n",
- ERROR + ILLEGAL_VALUE,
- (long)msgnum
- );
- return;
- }
-
- memset(&ird, 0, sizeof ird);
- strcpy(ird.desired_partnum, partnum);
- mime_parser(msg->cm_fields['M'],
- NULL,
- *ical_locate_part, /* callback function */
- NULL, NULL,
- (void *) &ird, /* user data */
- 0
- );
-
- CtdlFreeMessage(msg);
-
- if (ird.cal != NULL) {
- ical_hunt_for_conflicts(ird.cal);
- icalcomponent_free(ird.cal);
- return;
- }
- else {
- cprintf("%d No calendar object found\n", ERROR + ROOM_NOT_FOUND);
- return;
- }
-
- /* should never get here */
-}
-
-
-
-/*
- * Look for busy time in a VEVENT and add it to the supplied VFREEBUSY.
- */
-void ical_add_to_freebusy(icalcomponent *fb, icalcomponent *cal) {
- icalproperty *p;
- icalvalue *v;
- struct icalperiodtype my_period;
-
- if (cal == NULL) return;
- my_period = icalperiodtype_null_period();
-
- if (icalcomponent_isa(cal) != ICAL_VEVENT_COMPONENT) {
- ical_add_to_freebusy(fb,
- icalcomponent_get_first_component(
- cal, ICAL_VEVENT_COMPONENT
- )
- );
- return;
- }
-
- ical_dezonify(cal);
-
- /* If this event is not opaque, the user isn't publishing it as
- * busy time, so don't bother doing anything else.
- */
- p = icalcomponent_get_first_property(cal, ICAL_TRANSP_PROPERTY);
- if (p != NULL) {
- v = icalproperty_get_value(p);
- if (v != NULL) {
- if (icalvalue_get_transp(v) != ICAL_TRANSP_OPAQUE) {
- return;
- }
- }
- }
-
- /* Convert the DTSTART and DTEND properties to an icalperiod. */
- p = icalcomponent_get_first_property(cal, ICAL_DTSTART_PROPERTY);
- if (p != NULL) {
- my_period.start = icalproperty_get_dtstart(p);
- }
-
- p = icalcomponent_get_first_property(cal, ICAL_DTEND_PROPERTY);
- if (p != NULL) {
- my_period.end = icalproperty_get_dtstart(p);
- }
-
- /* Now add it. */
- icalcomponent_add_property(fb,
- icalproperty_new_freebusy(my_period)
- );
-
- /* Make sure the DTSTART property of the freebusy *list* is set to
- * the DTSTART property of the *earliest event*.
- */
- p = icalcomponent_get_first_property(fb, ICAL_DTSTART_PROPERTY);
- if (p == NULL) {
- icalcomponent_set_dtstart(fb,
- icalcomponent_get_dtstart(cal) );
- }
- else {
- if (icaltime_compare(
- icalcomponent_get_dtstart(cal),
- icalcomponent_get_dtstart(fb)
- ) < 0) {
- icalcomponent_set_dtstart(fb,
- icalcomponent_get_dtstart(cal) );
- }
- }
-
- /* Make sure the DTEND property of the freebusy *list* is set to
- * the DTEND property of the *latest event*.
- */
- p = icalcomponent_get_first_property(fb, ICAL_DTEND_PROPERTY);
- if (p == NULL) {
- icalcomponent_set_dtend(fb,
- icalcomponent_get_dtend(cal) );
- }
- else {
- if (icaltime_compare(
- icalcomponent_get_dtend(cal),
- icalcomponent_get_dtend(fb)
- ) > 0) {
- icalcomponent_set_dtend(fb,
- icalcomponent_get_dtend(cal) );
- }
- }
-
-}
-
-
-
-/*
- * Backend for ical_freebusy()
- *
- * This function simply loads the messages in the user's calendar room,
- * which contain VEVENTs, then strips them of all non-freebusy data, and
- * adds them to the supplied VCALENDAR.
- *
- */
-void ical_freebusy_backend(long msgnum, void *data) {
- icalcomponent *cal;
- struct CtdlMessage *msg = NULL;
- struct ical_respond_data ird;
-
- cal = (icalcomponent *)data;
-
- msg = CtdlFetchMessage(msgnum, 1);
- if (msg == NULL) return;
- memset(&ird, 0, sizeof ird);
- strcpy(ird.desired_partnum, "_HUNT_");
- mime_parser(msg->cm_fields['M'],
- NULL,
- *ical_locate_part, /* callback function */
- NULL, NULL,
- (void *) &ird, /* user data */
- 0
- );
- CtdlFreeMessage(msg);
-
- if (ird.cal == NULL) return;
-
- ical_add_to_freebusy(cal, ird.cal);
-
- /* Now free the memory. */
- icalcomponent_free(ird.cal);
-}
-
-
-
-/*
- * Grab another user's free/busy times
- */
-void ical_freebusy(char *who) {
- struct ctdluser usbuf;
- char calendar_room_name[ROOMNAMELEN];
- char hold_rm[ROOMNAMELEN];
- char *serialized_request = NULL;
- icalcomponent *encaps = NULL;
- icalcomponent *fb = NULL;
- int found_user = (-1);
- struct recptypes *recp = NULL;
- char buf[256];
- char host[256];
- char type[256];
- int i = 0;
- int config_lines = 0;
-
- /* First try an exact match. */
- found_user = getuser(&usbuf, who);
-
- /* If not found, try it as an unqualified email address. */
- if (found_user != 0) {
- strcpy(buf, who);
- recp = validate_recipients(buf);
- lprintf(CTDL_DEBUG, "Trying <%s>\n", buf);
- if (recp != NULL) {
- if (recp->num_local == 1) {
- found_user = getuser(&usbuf, recp->recp_local);
- }
- free_recipients(recp);
- }
- }
-
- /* If still not found, try it as an address qualified with the
- * primary FQDN of this Citadel node.
- */
- if (found_user != 0) {
- snprintf(buf, sizeof buf, "%s@%s", who, config.c_fqdn);
- lprintf(CTDL_DEBUG, "Trying <%s>\n", buf);
- recp = validate_recipients(buf);
- if (recp != NULL) {
- if (recp->num_local == 1) {
- found_user = getuser(&usbuf, recp->recp_local);
- }
- free_recipients(recp);
- }
- }
-
- /* Still not found? Try qualifying it with every domain we
- * might have addresses in.
- */
- if (found_user != 0) {
- config_lines = num_tokens(inetcfg, '\n');
- for (i=0; ((i < config_lines) && (found_user != 0)); ++i) {
- extract_token(buf, inetcfg, i, '\n', sizeof buf);
- extract_token(host, buf, 0, '|', sizeof host);
- extract_token(type, buf, 1, '|', sizeof type);
-
- if ( (!strcasecmp(type, "localhost"))
- || (!strcasecmp(type, "directory")) ) {
- snprintf(buf, sizeof buf, "%s@%s", who, host);
- lprintf(CTDL_DEBUG, "Trying <%s>\n", buf);
- recp = validate_recipients(buf);
- if (recp != NULL) {
- if (recp->num_local == 1) {
- found_user = getuser(&usbuf, recp->recp_local);
- }
- free_recipients(recp);
- }
- }
- }
- }
-
- if (found_user != 0) {
- cprintf("%d No such user.\n", ERROR + NO_SUCH_USER);
- return;
- }
-
- MailboxName(calendar_room_name, sizeof calendar_room_name,
- &usbuf, USERCALENDARROOM);
-
- strcpy(hold_rm, CC->room.QRname); /* save current room */
-
- if (getroom(&CC->room, calendar_room_name) != 0) {
- cprintf("%d Cannot open calendar\n", ERROR + ROOM_NOT_FOUND);
- getroom(&CC->room, hold_rm);
- return;
- }
-
- /* Create a VFREEBUSY subcomponent */
- lprintf(CTDL_DEBUG, "Creating VFREEBUSY component\n");
- fb = icalcomponent_new_vfreebusy();
- if (fb == NULL) {
- cprintf("%d Internal error: cannot allocate memory.\n",
- ERROR + INTERNAL_ERROR);
- icalcomponent_free(encaps);
- getroom(&CC->room, hold_rm);
- return;
- }
-
- /* Set the method to PUBLISH */
- icalcomponent_set_method(fb, ICAL_METHOD_PUBLISH);
-
- /* Set the DTSTAMP to right now. */
- icalcomponent_set_dtstamp(fb, icaltime_from_timet(time(NULL), 0));
-
- /* Add the user's email address as ORGANIZER */
- sprintf(buf, "MAILTO:%s", who);
- if (strchr(buf, '@') == NULL) {
- strcat(buf, "@");
- strcat(buf, config.c_fqdn);
- }
- for (i=0; i<strlen(buf); ++i) {
- if (buf[i]==' ') buf[i] = '_';
- }
- icalcomponent_add_property(fb, icalproperty_new_organizer(buf));
-
- /* Add busy time from events */
- lprintf(CTDL_DEBUG, "Adding busy time from events\n");
- CtdlForEachMessage(MSGS_ALL, 0, NULL, NULL, NULL, ical_freebusy_backend, (void *)fb );
-
- /* If values for DTSTART and DTEND are still not present, set them
- * to yesterday and tomorrow as default values.
- */
- if (icalcomponent_get_first_property(fb, ICAL_DTSTART_PROPERTY) == NULL) {
- icalcomponent_set_dtstart(fb, icaltime_from_timet(time(NULL)-86400L, 0));
- }
- if (icalcomponent_get_first_property(fb, ICAL_DTEND_PROPERTY) == NULL) {
- icalcomponent_set_dtend(fb, icaltime_from_timet(time(NULL)+86400L, 0));
- }
-
- /* Put the freebusy component into the calendar component */
- lprintf(CTDL_DEBUG, "Encapsulating\n");
- encaps = ical_encapsulate_subcomponent(fb);
- if (encaps == NULL) {
- icalcomponent_free(fb);
- cprintf("%d Internal error: cannot allocate memory.\n",
- ERROR + INTERNAL_ERROR);
- getroom(&CC->room, hold_rm);
- return;
- }
-
- /* Set the method to PUBLISH */
- lprintf(CTDL_DEBUG, "Setting method\n");
- icalcomponent_set_method(encaps, ICAL_METHOD_PUBLISH);
-
- /* Serialize it */
- lprintf(CTDL_DEBUG, "Serializing\n");
- serialized_request = strdup(icalcomponent_as_ical_string(encaps));
- icalcomponent_free(encaps); /* Don't need this anymore. */
-
- cprintf("%d Here is the free/busy data:\n", LISTING_FOLLOWS);
- if (serialized_request != NULL) {
- client_write(serialized_request, strlen(serialized_request));
- free(serialized_request);
- }
- cprintf("\n000\n");
-
- /* Go back to the room from which we came... */
- getroom(&CC->room, hold_rm);
-}
-
-
-
-/*
- * Backend for ical_getics()
- *
- * This is a ForEachMessage() callback function that searches the current room
- * for calendar events and adds them each into one big calendar component.
- */
-void ical_getics_backend(long msgnum, void *data) {
- icalcomponent *encaps, *c;
- struct CtdlMessage *msg = NULL;
- struct ical_respond_data ird;
-
- encaps = (icalcomponent *)data;
- if (encaps == NULL) return;
-
- /* Look for the calendar event... */
-
- msg = CtdlFetchMessage(msgnum, 1);
- if (msg == NULL) return;
- memset(&ird, 0, sizeof ird);
- strcpy(ird.desired_partnum, "_HUNT_");
- mime_parser(msg->cm_fields['M'],
- NULL,
- *ical_locate_part, /* callback function */
- NULL, NULL,
- (void *) &ird, /* user data */
- 0
- );
- CtdlFreeMessage(msg);
-
- if (ird.cal == NULL) return;
-
- /* Here we go: put the VEVENT into the VCALENDAR. We now no longer
- * are responsible for "the_request"'s memory -- it will be freed
- * when we free "encaps".
- */
-
- /* If the top-level component is *not* a VCALENDAR, we can drop it right
- * in. This will almost never happen.
- */
- if (icalcomponent_isa(ird.cal) != ICAL_VCALENDAR_COMPONENT) {
- icalcomponent_add_component(encaps, ird.cal);
- }
- /*
- * In the more likely event that we're looking at a VCALENDAR with the VEVENT
- * and other components encapsulated inside, we have to extract them.
- */
- else {
- for (c = icalcomponent_get_first_component(ird.cal, ICAL_ANY_COMPONENT);
- (c != NULL);
- c = icalcomponent_get_next_component(ird.cal, ICAL_ANY_COMPONENT)) {
- icalcomponent_add_component(encaps, icalcomponent_new_clone(c));
- }
- icalcomponent_free(ird.cal);
- }
-}
-
-
-
-/*
- * Retrieve all of the calendar items in the current room, and output them
- * as a single icalendar object.
- */
-void ical_getics(void)
-{
- icalcomponent *encaps = NULL;
- char *ser = NULL;
-
- if ( (CC->room.QRdefaultview != VIEW_CALENDAR)
- &&(CC->room.QRdefaultview != VIEW_TASKS) ) {
- cprintf("%d Not a calendar room\n", ERROR+NOT_HERE);
- return; /* Not a vCalendar-centric room */
- }
-
- encaps = icalcomponent_new_vcalendar();
- if (encaps == NULL) {
- lprintf(CTDL_DEBUG, "Error at %s:%d - could not allocate component!\n",
- __FILE__, __LINE__);
- cprintf("%d Could not allocate memory\n", ERROR+INTERNAL_ERROR);
- return;
- }
-
- cprintf("%d one big calendar\n", LISTING_FOLLOWS);
-
- /* Set the Product ID */
- icalcomponent_add_property(encaps, icalproperty_new_prodid(PRODID));
-
- /* Set the Version Number */
- icalcomponent_add_property(encaps, icalproperty_new_version("2.0"));
-
- /* Set the method to PUBLISH */
- icalcomponent_set_method(encaps, ICAL_METHOD_PUBLISH);
-
- /* Now go through the room encapsulating all calendar items. */
- CtdlForEachMessage(MSGS_ALL, 0, NULL,
- NULL,
- NULL,
- ical_getics_backend,
- (void *) encaps
- );
-
- ser = strdup(icalcomponent_as_ical_string(encaps));
- client_write(ser, strlen(ser));
- free(ser);
- cprintf("\n000\n");
- icalcomponent_free(encaps); /* Don't need this anymore. */
-
-}
-
-
-/*
- * Delete all of the calendar items in the current room, and replace them
- * with calendar items from a client-supplied data stream.
- */
-void ical_putics(void)
-{
- char *calstream = NULL;
- icalcomponent *cal;
- icalcomponent *c;
-
- if ( (CC->room.QRdefaultview != VIEW_CALENDAR)
- &&(CC->room.QRdefaultview != VIEW_TASKS) ) {
- cprintf("%d Not a calendar room\n", ERROR+NOT_HERE);
- return; /* Not a vCalendar-centric room */
- }
-
- if (!CtdlDoIHavePermissionToDeleteMessagesFromThisRoom()) {
- cprintf("%d Permission denied.\n", ERROR+HIGHER_ACCESS_REQUIRED);
- return;
- }
-
- cprintf("%d Transmit data now\n", SEND_LISTING);
- calstream = CtdlReadMessageBody("000", config.c_maxmsglen, NULL, 0);
- if (calstream == NULL) {
- return;
- }
-
- cal = icalcomponent_new_from_string(calstream);
- free(calstream);
- ical_dezonify(cal);
-
- /* We got our data stream -- now do something with it. */
-
- /* Delete the existing messages in the room, because we are replacing
- * the entire calendar with an entire new (or updated) calendar.
- * (Careful: this opens an S_ROOMS critical section!)
- */
- CtdlDeleteMessages(CC->room.QRname, NULL, 0, "");
-
- /* If the top-level component is *not* a VCALENDAR, we can drop it right
- * in. This will almost never happen.
- */
- if (icalcomponent_isa(cal) != ICAL_VCALENDAR_COMPONENT) {
- ical_write_to_cal(&CC->user, cal);
- }
- /*
- * In the more likely event that we're looking at a VCALENDAR with the VEVENT
- * and other components encapsulated inside, we have to extract them.
- */
- else {
- for (c = icalcomponent_get_first_component(cal, ICAL_ANY_COMPONENT);
- (c != NULL);
- c = icalcomponent_get_next_component(cal, ICAL_ANY_COMPONENT)) {
- ical_write_to_cal(&CC->user, c);
- }
- }
-
- icalcomponent_free(cal);
-}
-
-
-/*
- * All Citadel calendar commands from the client come through here.
- */
-void cmd_ical(char *argbuf)
-{
- char subcmd[64];
- long msgnum;
- char partnum[256];
- char action[256];
- char who[256];
-
- extract_token(subcmd, argbuf, 0, '|', sizeof subcmd);
-
- /* Allow "test" and "freebusy" subcommands without logging in. */
-
- if (!strcasecmp(subcmd, "test")) {
- cprintf("%d This server supports calendaring\n", CIT_OK);
- return;
- }
-
- if (!strcasecmp(subcmd, "freebusy")) {
- extract_token(who, argbuf, 1, '|', sizeof who);
- ical_freebusy(who);
- return;
- }
-
- if (!strcasecmp(subcmd, "sgi")) {
- CIT_ICAL->server_generated_invitations =
- (extract_int(argbuf, 1) ? 1 : 0) ;
- cprintf("%d %d\n",
- CIT_OK, CIT_ICAL->server_generated_invitations);
- return;
- }
-
- if (CtdlAccessCheck(ac_logged_in)) return;
-
- if (!strcasecmp(subcmd, "respond")) {
- msgnum = extract_long(argbuf, 1);
- extract_token(partnum, argbuf, 2, '|', sizeof partnum);
- extract_token(action, argbuf, 3, '|', sizeof action);
- ical_respond(msgnum, partnum, action);
- return;
- }
-
- if (!strcasecmp(subcmd, "handle_rsvp")) {
- msgnum = extract_long(argbuf, 1);
- extract_token(partnum, argbuf, 2, '|', sizeof partnum);
- extract_token(action, argbuf, 3, '|', sizeof action);
- ical_handle_rsvp(msgnum, partnum, action);
- return;
- }
-
- if (!strcasecmp(subcmd, "conflicts")) {
- msgnum = extract_long(argbuf, 1);
- extract_token(partnum, argbuf, 2, '|', sizeof partnum);
- ical_conflicts(msgnum, partnum);
- return;
- }
-
- if (!strcasecmp(subcmd, "getics")) {
- ical_getics();
- return;
- }
-
- if (!strcasecmp(subcmd, "putics")) {
- ical_putics();
- return;
- }
-
- cprintf("%d Invalid subcommand\n", ERROR + CMD_NOT_SUPPORTED);
-}
-
-
-
-/*
- * We don't know if the calendar room exists so we just create it at login
- */
-void ical_create_room(void)
-{
- struct ctdlroom qr;
- struct visit vbuf;
-
- /* Create the calendar room if it doesn't already exist */
- create_room(USERCALENDARROOM, 4, "", 0, 1, 0, VIEW_CALENDAR);
-
- /* Set expiration policy to manual; otherwise objects will be lost! */
- if (lgetroom(&qr, USERCALENDARROOM)) {
- lprintf(CTDL_CRIT, "Couldn't get the user calendar room!\n");
- return;
- }
- qr.QRep.expire_mode = EXPIRE_MANUAL;
- qr.QRdefaultview = VIEW_CALENDAR; /* 3 = calendar view */
- lputroom(&qr);
-
- /* Set the view to a calendar view */
- CtdlGetRelationship(&vbuf, &CC->user, &qr);
- vbuf.v_view = VIEW_CALENDAR;
- CtdlSetRelationship(&vbuf, &CC->user, &qr);
-
- /* Create the tasks list room if it doesn't already exist */
- create_room(USERTASKSROOM, 4, "", 0, 1, 0, VIEW_TASKS);
-
- /* Set expiration policy to manual; otherwise objects will be lost! */
- if (lgetroom(&qr, USERTASKSROOM)) {
- lprintf(CTDL_CRIT, "Couldn't get the user calendar room!\n");
- return;
- }
- qr.QRep.expire_mode = EXPIRE_MANUAL;
- qr.QRdefaultview = VIEW_TASKS;
- lputroom(&qr);
-
- /* Set the view to a task list view */
- CtdlGetRelationship(&vbuf, &CC->user, &qr);
- vbuf.v_view = VIEW_TASKS;
- CtdlSetRelationship(&vbuf, &CC->user, &qr);
-
- /* Create the notes room if it doesn't already exist */
- create_room(USERNOTESROOM, 4, "", 0, 1, 0, VIEW_NOTES);
-
- /* Set expiration policy to manual; otherwise objects will be lost! */
- if (lgetroom(&qr, USERNOTESROOM)) {
- lprintf(CTDL_CRIT, "Couldn't get the user calendar room!\n");
- return;
- }
- qr.QRep.expire_mode = EXPIRE_MANUAL;
- qr.QRdefaultview = VIEW_NOTES;
- lputroom(&qr);
-
- /* Set the view to a notes view */
- CtdlGetRelationship(&vbuf, &CC->user, &qr);
- vbuf.v_view = VIEW_NOTES;
- CtdlSetRelationship(&vbuf, &CC->user, &qr);
-
- return;
-}
-
-
-/*
- * ical_send_out_invitations() is called by ical_saving_vevent() when it
- * finds a VEVENT.
- */
-void ical_send_out_invitations(icalcomponent *cal) {
- icalcomponent *the_request = NULL;
- char *serialized_request = NULL;
- icalcomponent *encaps = NULL;
- char *request_message_text = NULL;
- struct CtdlMessage *msg = NULL;
- struct recptypes *valid = NULL;
- char attendees_string[SIZ];
- int num_attendees = 0;
- char this_attendee[256];
- icalproperty *attendee = NULL;
- char summary_string[SIZ];
- icalproperty *summary = NULL;
-
- if (cal == NULL) {
- lprintf(CTDL_ERR, "ERROR: trying to reply to NULL event?\n");
- return;
- }
-
-
- /* If this is a VCALENDAR component, look for a VEVENT subcomponent. */
- if (icalcomponent_isa(cal) == ICAL_VCALENDAR_COMPONENT) {
- ical_send_out_invitations(
- icalcomponent_get_first_component(
- cal, ICAL_VEVENT_COMPONENT
- )
- );
- return;
- }
-
- /* Clone the event */
- the_request = icalcomponent_new_clone(cal);
- if (the_request == NULL) {
- lprintf(CTDL_ERR, "ERROR: cannot clone calendar object\n");
- return;
- }
-
- /* Extract the summary string -- we'll use it as the
- * message subject for the request
- */
- strcpy(summary_string, "Meeting request");
- summary = icalcomponent_get_first_property(the_request, ICAL_SUMMARY_PROPERTY);
- if (summary != NULL) {
- if (icalproperty_get_summary(summary)) {
- strcpy(summary_string,
- icalproperty_get_summary(summary) );
- }
- }
-
- /* Determine who the recipients of this message are (the attendees) */
- strcpy(attendees_string, "");
- for (attendee = icalcomponent_get_first_property(the_request, ICAL_ATTENDEE_PROPERTY); attendee != NULL; attendee = icalcomponent_get_next_property(the_request, ICAL_ATTENDEE_PROPERTY)) {
- if (icalproperty_get_attendee(attendee)) {
- safestrncpy(this_attendee, icalproperty_get_attendee(attendee), sizeof this_attendee);
- if (!strncasecmp(this_attendee, "MAILTO:", 7)) {
- strcpy(this_attendee, &this_attendee[7]);
-
- if (!CtdlIsMe(this_attendee, sizeof this_attendee)) { /* don't send an invitation to myself! */
- snprintf(&attendees_string[strlen(attendees_string)],
- sizeof(attendees_string) - strlen(attendees_string),
- "%s, ",
- this_attendee
- );
- ++num_attendees;
- }
- }
- }
- }
-
- lprintf(CTDL_DEBUG, "<%d> attendees: <%s>\n", num_attendees, attendees_string);
-
- /* If there are no attendees, there are no invitations to send, so...
- * don't bother putting one together! Punch out, Maverick!
- */
- if (num_attendees == 0) {
- icalcomponent_free(the_request);
- return;
- }
-
- /* Encapsulate the VEVENT component into a complete VCALENDAR */
- encaps = icalcomponent_new_vcalendar();
- if (encaps == NULL) {
- lprintf(CTDL_DEBUG, "Error at %s:%d - could not allocate component!\n",
- __FILE__, __LINE__);
- icalcomponent_free(the_request);
- return;
- }
-
- /* Set the Product ID */
- icalcomponent_add_property(encaps, icalproperty_new_prodid(PRODID));
-
- /* Set the Version Number */
- icalcomponent_add_property(encaps, icalproperty_new_version("2.0"));
-
- /* Set the method to REQUEST */
- icalcomponent_set_method(encaps, ICAL_METHOD_REQUEST);
-
- /* Now make sure all of the DTSTART and DTEND properties are UTC. */
- ical_dezonify(the_request);
-
- /* Here we go: put the VEVENT into the VCALENDAR. We now no longer
- * are responsible for "the_request"'s memory -- it will be freed
- * when we free "encaps".
- */
- icalcomponent_add_component(encaps, the_request);
-
- /* Serialize it */
- serialized_request = strdup(icalcomponent_as_ical_string(encaps));
- icalcomponent_free(encaps); /* Don't need this anymore. */
- if (serialized_request == NULL) return;
-
- request_message_text = malloc(strlen(serialized_request) + SIZ);
- if (request_message_text != NULL) {
- sprintf(request_message_text,
- "Content-type: text/calendar\r\n\r\n%s\r\n",
- serialized_request
- );
-
- msg = CtdlMakeMessage(&CC->user,
- "", /* No single recipient here */
- "", /* No single recipient here */
- CC->room.QRname, 0, FMT_RFC822,
- "",
- "",
- summary_string, /* Use summary for subject */
- NULL,
- request_message_text);
-
- if (msg != NULL) {
- valid = validate_recipients(attendees_string);
- CtdlSubmitMsg(msg, valid, "");
- CtdlFreeMessage(msg);
- free_recipients(valid);
- }
- }
- free(serialized_request);
-}
-
-
-/*
- * When a calendar object is being saved, determine whether it's a VEVENT
- * and the user saving it is the organizer. If so, send out invitations
- * to any listed attendees.
- *
- */
-void ical_saving_vevent(icalcomponent *cal) {
- icalcomponent *c;
- icalproperty *organizer = NULL;
- char organizer_string[SIZ];
-
- lprintf(CTDL_DEBUG, "ical_saving_vevent() has been called!\n");
-
- /* Don't send out invitations unless the client wants us to. */
- if (CIT_ICAL->server_generated_invitations == 0) {
- return;
- }
-
- /* Don't send out invitations if we've been asked not to. */
- if (CIT_ICAL->avoid_sending_invitations > 0) {
- return;
- }
-
- strcpy(organizer_string, "");
- /*
- * The VEVENT subcomponent is the one we're interested in.
- * Send out invitations if, and only if, this user is the Organizer.
- */
- if (icalcomponent_isa(cal) == ICAL_VEVENT_COMPONENT) {
- organizer = icalcomponent_get_first_property(cal, ICAL_ORGANIZER_PROPERTY);
- if (organizer != NULL) {
- if (icalproperty_get_organizer(organizer)) {
- strcpy(organizer_string,
- icalproperty_get_organizer(organizer));
- }
- }
- if (!strncasecmp(organizer_string, "MAILTO:", 7)) {
- strcpy(organizer_string, &organizer_string[7]);
- striplt(organizer_string);
- /*
- * If the user saving the event is listed as the
- * organizer, then send out invitations.
- */
- if (CtdlIsMe(organizer_string, sizeof organizer_string)) {
- ical_send_out_invitations(cal);
- }
- }
- }
-
- /* If the component has subcomponents, recurse through them. */
- for (c = icalcomponent_get_first_component(cal, ICAL_ANY_COMPONENT);
- (c != NULL);
- c = icalcomponent_get_next_component(cal, ICAL_ANY_COMPONENT)) {
- /* Recursively process subcomponent */
- ical_saving_vevent(c);
- }
-
-}
-
-
-
-/*
- * Back end for ical_obj_beforesave()
- * This hunts for the UID of the calendar event (becomes Citadel msg EUID),
- * the summary of the event (becomes message subject),
- * and the start time (becomes message date/time).
- */
-void ical_ctdl_set_exclusive_msgid(char *name, char *filename, char *partnum,
- char *disp, void *content, char *cbtype, char *cbcharset, size_t length,
- char *encoding, void *cbuserdata)
-{
- icalcomponent *cal, *nested_event, *nested_todo, *whole_cal;
- icalproperty *p;
- struct icalmessagemod *imm;
- char new_uid[SIZ];
-
- imm = (struct icalmessagemod *)cbuserdata;
-
- /* We're only interested in calendar data. */
- if (strcasecmp(cbtype, "text/calendar")) {
- return;
- }
-
- /* Hunt for the UID and drop it in
- * the "user data" pointer for the MIME parser. When
- * ical_obj_beforesave() sees it there, it'll set the Exclusive msgid
- * to that string.
- */
- whole_cal = icalcomponent_new_from_string(content);
- cal = whole_cal;
- if (cal != NULL) {
- if (icalcomponent_isa(cal) == ICAL_VCALENDAR_COMPONENT) {
- nested_event = icalcomponent_get_first_component(
- cal, ICAL_VEVENT_COMPONENT);
- if (nested_event != NULL) {
- cal = nested_event;
- }
- else {
- nested_todo = icalcomponent_get_first_component(
- cal, ICAL_VTODO_COMPONENT);
- if (nested_todo != NULL) {
- cal = nested_todo;
- }
- }
- }
-
- if (cal != NULL) {
- p = ical_ctdl_get_subprop(cal, ICAL_UID_PROPERTY);
- if (p == NULL) {
- /* If there's no uid we must generate one */
- generate_uuid(new_uid);
- icalcomponent_add_property(cal, icalproperty_new_uid(new_uid));
- p = ical_ctdl_get_subprop(cal, ICAL_UID_PROPERTY);
- }
- if (p != NULL) {
- strcpy(imm->uid, icalproperty_get_comment(p));
- }
- p = ical_ctdl_get_subprop(cal, ICAL_SUMMARY_PROPERTY);
- if (p != NULL) {
- strcpy(imm->subject, icalproperty_get_comment(p));
- }
- p = ical_ctdl_get_subprop(cal, ICAL_DTSTART_PROPERTY);
- if (p != NULL) {
- imm->dtstart = icaltime_as_timet(icalproperty_get_dtstart(p));
- }
- }
- icalcomponent_free(cal);
- if (whole_cal != cal) {
- icalcomponent_free(whole_cal);
- }
- }
-}
-
-
-
-
-/*
- * See if we need to prevent the object from being saved (we don't allow
- * MIME types other than text/calendar in "calendar" or "tasks" rooms). Also,
- * when saving an event to the calendar, set the message's Citadel exclusive
- * message ID to the UID of the object. This causes our replication checker to
- * automatically delete any existing instances of the same object. (Isn't
- * that cool?)
- *
- * We also set the message's Subject to the event summary, and the Date/time to
- * the event start time.
- */
-int ical_obj_beforesave(struct CtdlMessage *msg)
-{
- struct icalmessagemod imm;
-
- /* First determine if this is a calendar or tasks room */
- if ( (CC->room.QRdefaultview != VIEW_CALENDAR)
- && (CC->room.QRdefaultview != VIEW_TASKS)
- ) {
- return(0); /* Not a vCalendar-centric room */
- }
-
- /* It must be an RFC822 message! */
- if (msg->cm_format_type != 4) {
- lprintf(CTDL_DEBUG, "Rejecting non-RFC822 message\n");
- return(1); /* You tried to save a non-RFC822 message! */
- }
-
- if (msg->cm_fields['M'] == NULL) {
- return(1); /* You tried to save a null message! */
- }
-
- memset(&imm, 0, sizeof(struct icalmessagemod));
-
- /* Do all of our lovely back-end parsing */
- mime_parser(msg->cm_fields['M'],
- NULL,
- *ical_ctdl_set_exclusive_msgid,
- NULL, NULL,
- (void *)&imm,
- 0
- );
-
- if (strlen(imm.uid) > 0) {
- if (msg->cm_fields['E'] != NULL) {
- free(msg->cm_fields['E']);
- }
- msg->cm_fields['E'] = strdup(imm.uid);
- lprintf(CTDL_DEBUG, "Saving calendar UID <%s>\n", msg->cm_fields['E']);
- }
- if (strlen(imm.subject) > 0) {
- if (msg->cm_fields['U'] != NULL) {
- free(msg->cm_fields['U']);
- }
- msg->cm_fields['U'] = strdup(imm.subject);
- }
- if (imm.dtstart > 0) {
- if (msg->cm_fields['T'] != NULL) {
- free(msg->cm_fields['T']);
- }
- msg->cm_fields['T'] = strdup("000000000000000000");
- sprintf(msg->cm_fields['T'], "%ld", imm.dtstart);
- }
-
- return(0);
-}
-
-
-/*
- * Things we need to do after saving a calendar event.
- */
-void ical_obj_aftersave_backend(char *name, char *filename, char *partnum,
- char *disp, void *content, char *cbtype, char *cbcharset, size_t length,
- char *encoding, void *cbuserdata)
-{
- icalcomponent *cal;
-
- /* We're only interested in calendar items here. */
- if (strcasecmp(cbtype, "text/calendar")) {
- return;
- }
-
- /* Hunt for the UID and drop it in
- * the "user data" pointer for the MIME parser. When
- * ical_obj_beforesave() sees it there, it'll set the Exclusive msgid
- * to that string.
- */
- if (!strcasecmp(cbtype, "text/calendar")) {
- cal = icalcomponent_new_from_string(content);
- if (cal != NULL) {
- ical_saving_vevent(cal);
- icalcomponent_free(cal);
- }
- }
-}
-
-
-/*
- * Things we need to do after saving a calendar event.
- * (This will start back end tasks such as automatic generation of invitations,
- * if such actions are appropriate.)
- */
-int ical_obj_aftersave(struct CtdlMessage *msg)
-{
- char roomname[ROOMNAMELEN];
-
- /*
- * If this isn't the Calendar> room, no further action is necessary.
- */
-
- /* First determine if this is our room */
- MailboxName(roomname, sizeof roomname, &CC->user, USERCALENDARROOM);
- if (strcasecmp(roomname, CC->room.QRname)) {
- return(0); /* Not the Calendar room -- don't do anything. */
- }
-
- /* It must be an RFC822 message! */
- if (msg->cm_format_type != 4) return(1);
-
- /* Reject null messages */
- if (msg->cm_fields['M'] == NULL) return(1);
-
- /* Now recurse through it looking for our icalendar data */
- mime_parser(msg->cm_fields['M'],
- NULL,
- *ical_obj_aftersave_backend,
- NULL, NULL,
- NULL,
- 0
- );
-
- return(0);
-}
-
-
-void ical_session_startup(void) {
- CIT_ICAL = malloc(sizeof(struct cit_ical));
- memset(CIT_ICAL, 0, sizeof(struct cit_ical));
-}
-
-void ical_session_shutdown(void) {
- free(CIT_ICAL);
-}
-
-
-/*
- * Back end for ical_fixed_output()
- */
-void ical_fixed_output_backend(icalcomponent *cal,
- int recursion_level
-) {
- icalcomponent *c;
- icalproperty *p;
- char buf[256];
-
- p = icalcomponent_get_first_property(cal, ICAL_SUMMARY_PROPERTY);
- if (p != NULL) {
- cprintf("%s\n", (const char *)icalproperty_get_comment(p));
- }
-
- p = icalcomponent_get_first_property(cal, ICAL_LOCATION_PROPERTY);
- if (p != NULL) {
- cprintf("%s\n", (const char *)icalproperty_get_comment(p));
- }
-
- p = icalcomponent_get_first_property(cal, ICAL_DESCRIPTION_PROPERTY);
- if (p != NULL) {
- cprintf("%s\n", (const char *)icalproperty_get_comment(p));
- }
-
- /* If the component has attendees, iterate through them. */
- for (p = icalcomponent_get_first_property(cal, ICAL_ATTENDEE_PROPERTY); (p != NULL); p = icalcomponent_get_next_property(cal, ICAL_ATTENDEE_PROPERTY)) {
- safestrncpy(buf, icalproperty_get_attendee(p), sizeof buf);
- if (!strncasecmp(buf, "MAILTO:", 7)) {
-
- /* screen name or email address */
- strcpy(buf, &buf[7]);
- striplt(buf);
- cprintf("%s ", buf);
- }
- cprintf("\n");
- }
-
- /* If the component has subcomponents, recurse through them. */
- for (c = icalcomponent_get_first_component(cal, ICAL_ANY_COMPONENT);
- (c != 0);
- c = icalcomponent_get_next_component(cal, ICAL_ANY_COMPONENT)) {
- /* Recursively process subcomponent */
- ical_fixed_output_backend(c, recursion_level+1);
- }
-}
-
-
-
-/*
- * Function to output vcalendar data as plain text. Nobody uses MSG0
- * anymore, so really this is just so we expose the vCard data to the full
- * text indexer.
- */
-void ical_fixed_output(char *ptr, int len) {
- icalcomponent *cal;
- char *stringy_cal;
-
- stringy_cal = malloc(len + 1);
- safestrncpy(stringy_cal, ptr, len + 1);
- cal = icalcomponent_new_from_string(stringy_cal);
- free(stringy_cal);
-
- if (cal == NULL) {
- return;
- }
-
- ical_dezonify(cal);
- ical_fixed_output_backend(cal, 0);
-
- /* Free the memory we obtained from libical's constructor */
- icalcomponent_free(cal);
-}
-
-
-#endif /* CITADEL_WITH_CALENDAR_SERVICE */
-
-/*
- * Register this module with the Citadel server.
- */
-CTDL_MODULE_INIT(calendar)
-{
-#ifdef CITADEL_WITH_CALENDAR_SERVICE
- CtdlRegisterMessageHook(ical_obj_beforesave, EVT_BEFORESAVE);
- CtdlRegisterMessageHook(ical_obj_aftersave, EVT_AFTERSAVE);
- CtdlRegisterSessionHook(ical_create_room, EVT_LOGIN);
- CtdlRegisterProtoHook(cmd_ical, "ICAL", "Citadel iCal commands");
- CtdlRegisterSessionHook(ical_session_startup, EVT_START);
- CtdlRegisterSessionHook(ical_session_shutdown, EVT_STOP);
- CtdlRegisterFixedOutputHook("text/calendar", ical_fixed_output);
-#endif
-
- /* return our Subversion id for the Log */
- return "$Id$";
-}
-
-
-
-void serv_calendar_destroy(void)
-{
-#ifdef CITADEL_WITH_CALENDAR_SERVICE
- icaltimezone_free_builtin_timezones();
-#endif
-}
+++ /dev/null
-/*
- * $Id$
- *
- * iCalendar implementation for Citadel
- *
- */
-
-/*
- * "server_generated_invitations" tells the Citadel server that the
- * client wants invitations to be generated and sent out by the
- * server. Set to 1 to enable this functionality.
- *
- * "avoid_sending_invitations" is a server-internal variable. It is
- * set internally during certain transactions and cleared
- * automatically.
- */
-struct cit_ical {
- int server_generated_invitations;
- int avoid_sending_invitations;
-};
-
-#define CIT_ICAL CC->CIT_ICAL
-
-/*
- * When saving a message containing calendar information, we keep track of
- * some components in the calendar object that need to be inserted into
- * message fields.
- */
-struct icalmessagemod {
- char subject[SIZ];
- char uid[SIZ];
- time_t dtstart;
-};
+++ /dev/null
-/*
- * $Id$
- *
- * This module handles all "real time" communication between users. The
- * modes of communication currently supported are Chat and Paging.
- *
- */
-#include "sysdep.h"
-#include <stdlib.h>
-#include <unistd.h>
-#include <stdio.h>
-#include <fcntl.h>
-#include <signal.h>
-#include <pwd.h>
-#include <errno.h>
-#include <sys/types.h>
-
-#if TIME_WITH_SYS_TIME
-# include <sys/time.h>
-# include <time.h>
-#else
-# if HAVE_SYS_TIME_H
-# include <sys/time.h>
-# else
-# include <time.h>
-# endif
-#endif
-
-#include <sys/wait.h>
-#include <string.h>
-#include <limits.h>
-#include "citadel.h"
-#include "server.h"
-#include "serv_chat.h"
-#include "citserver.h"
-#include "support.h"
-#include "config.h"
-#include "tools.h"
-#include "msgbase.h"
-#include "user_ops.h"
-#include "room_ops.h"
-
-#ifndef HAVE_SNPRINTF
-#include "snprintf.h"
-#endif
-
-
-#include "ctdl_module.h"
-
-
-
-struct ChatLine *ChatQueue = NULL;
-int ChatLastMsg = 0;
-
-/*
- * This message can be set to anything you want, but it is
- * checked for consistency so don't move it away from here.
- */
-#define KICKEDMSG "You have been kicked out of this room."
-
-void allwrite(char *cmdbuf, int flag, char *username)
-{
- FILE *fp;
- char bcast[SIZ];
- char *un;
- struct ChatLine *clptr, *clnew;
- time_t now;
-
- if (CC->fake_username[0])
- un = CC->fake_username;
- else
- un = CC->user.fullname;
- if (flag == 1) {
- snprintf(bcast, sizeof bcast, ":|<%s %s>", un, cmdbuf);
- } else if (flag == 0) {
- snprintf(bcast, sizeof bcast, "%s|%s", un, cmdbuf);
- } else if (flag == 2) {
- snprintf(bcast, sizeof bcast, ":|<%s whispers %s>", un, cmdbuf);
- } else if (flag == 3) {
- snprintf(bcast, sizeof bcast, ":|%s", KICKEDMSG);
- }
- if ((strcasecmp(cmdbuf, "NOOP")) && (flag != 2)) {
- fp = fopen(CHATLOG, "a");
- if (fp != NULL)
- fprintf(fp, "%s\n", bcast);
- fclose(fp);
- }
- clnew = (struct ChatLine *) malloc(sizeof(struct ChatLine));
- memset(clnew, 0, sizeof(struct ChatLine));
- if (clnew == NULL) {
- fprintf(stderr, "citserver: cannot alloc chat line: %s\n",
- strerror(errno));
- return;
- }
- time(&now);
- clnew->next = NULL;
- clnew->chat_time = now;
- safestrncpy(clnew->chat_room, CC->room.QRname,
- sizeof clnew->chat_room);
- clnew->chat_room[sizeof clnew->chat_room - 1] = 0;
- if (username) {
- safestrncpy(clnew->chat_username, username,
- sizeof clnew->chat_username);
- clnew->chat_username[sizeof clnew->chat_username - 1] = 0;
- } else
- clnew->chat_username[0] = '\0';
- safestrncpy(clnew->chat_text, bcast, sizeof clnew->chat_text);
-
- /* Here's the critical section.
- * First, add the new message to the queue...
- */
- begin_critical_section(S_CHATQUEUE);
- ++ChatLastMsg;
- clnew->chat_seq = ChatLastMsg;
- if (ChatQueue == NULL) {
- ChatQueue = clnew;
- } else {
- for (clptr = ChatQueue; clptr->next != NULL; clptr = clptr->next);;
- clptr->next = clnew;
- }
-
- /* Then, before releasing the lock, free the expired messages */
- while ((ChatQueue != NULL) && (now - ChatQueue->chat_time >= 120L)) {
- clptr = ChatQueue;
- ChatQueue = ChatQueue->next;
- free(clptr);
- }
- end_critical_section(S_CHATQUEUE);
-}
-
-
-t_context *find_context(char **unstr)
-{
- t_context *t_cc, *found_cc = NULL;
- char *name, *tptr;
-
- if ((!*unstr) || (!unstr))
- return (NULL);
-
- begin_critical_section(S_SESSION_TABLE);
- for (t_cc = ContextList; ((t_cc) && (!found_cc)); t_cc = t_cc->next) {
- if (t_cc->fake_username[0])
- name = t_cc->fake_username;
- else
- name = t_cc->curr_user;
- tptr = *unstr;
- if ((!strncasecmp(name, tptr, strlen(name))) && (tptr[strlen(name)] == ' ')) {
- found_cc = t_cc;
- *unstr = &(tptr[strlen(name) + 1]);
- }
- }
- end_critical_section(S_SESSION_TABLE);
-
- return (found_cc);
-}
-
-/*
- * List users in chat.
- * allflag == 0 = list users in chat
- * 1 = list users in chat, followed by users not in chat
- * 2 = display count only
- */
-
-void do_chat_listing(int allflag)
-{
- struct CitContext *ccptr;
- int count = 0;
- int count_elsewhere = 0;
- char roomname[ROOMNAMELEN];
-
- if ((allflag == 0) || (allflag == 1))
- cprintf(":|\n:| Users currently in chat:\n");
- begin_critical_section(S_SESSION_TABLE);
- for (ccptr = ContextList; ccptr != NULL; ccptr = ccptr->next) {
- if (ccptr->cs_flags & CS_CHAT) {
- if (!strcasecmp(ccptr->room.QRname,
- CC->room.QRname)) {
- ++count;
- }
- else {
- ++count_elsewhere;
- }
- }
-
- GenerateRoomDisplay(roomname, ccptr, CC);
- if ((CC->user.axlevel < 6)
- && (strlen(ccptr->fake_roomname)>0)) {
- strcpy(roomname, ccptr->fake_roomname);
- }
-
- if ((ccptr->cs_flags & CS_CHAT)
- && ((ccptr->cs_flags & CS_STEALTH) == 0)) {
- if ((allflag == 0) || (allflag == 1)) {
- cprintf(":| %-25s <%s>:\n",
- (ccptr->fake_username[0]) ? ccptr->fake_username : ccptr->curr_user,
- roomname);
- }
- }
- }
-
- if (allflag == 1) {
- cprintf(":|\n:| Users not in chat:\n");
- for (ccptr = ContextList; ccptr != NULL; ccptr = ccptr->next) {
-
- GenerateRoomDisplay(roomname, ccptr, CC);
- if ((CC->user.axlevel < 6)
- && (strlen(ccptr->fake_roomname)>0)) {
- strcpy(roomname, ccptr->fake_roomname);
- }
-
- if (((ccptr->cs_flags & CS_CHAT) == 0)
- && ((ccptr->cs_flags & CS_STEALTH) == 0)) {
- cprintf(":| %-25s <%s>:\n",
- (ccptr->fake_username[0]) ? ccptr->fake_username : ccptr->curr_user,
- roomname);
- }
- }
- }
- end_critical_section(S_SESSION_TABLE);
-
- if (allflag == 2) {
- if (count > 1) {
- cprintf(":|There are %d users here.\n", count);
- }
- else {
- cprintf(":|Note: you are the only one here.\n");
- }
- if (count_elsewhere > 0) {
- cprintf(":|There are %d users chatting in other rooms.\n", count_elsewhere);
- }
- }
-
- cprintf(":|\n");
-}
-
-
-void cmd_chat(char *argbuf)
-{
- char cmdbuf[SIZ];
- char *un;
- char *strptr1;
- int MyLastMsg, ThisLastMsg;
- struct ChatLine *clptr;
- struct CitContext *t_context;
- int retval;
-
- if (!(CC->logged_in)) {
- cprintf("%d Not logged in.\n", ERROR + NOT_LOGGED_IN);
- return;
- }
-
- CC->cs_flags = CC->cs_flags | CS_CHAT;
- cprintf("%d Entering chat mode (type '/help' for available commands)\n",
- START_CHAT_MODE);
- unbuffer_output();
-
- MyLastMsg = ChatLastMsg;
-
- if ((CC->cs_flags & CS_STEALTH) == 0) {
- allwrite("<entering chat>", 0, NULL);
- }
- strcpy(cmdbuf, "");
-
- do_chat_listing(2);
-
- while (1) {
- int ok_cmd;
- int linelen;
-
- ok_cmd = 0;
- linelen = strlen(cmdbuf);
- if (linelen > 100) --linelen; /* truncate too-long lines */
- cmdbuf[linelen + 1] = 0;
-
- retval = client_read_to(&cmdbuf[linelen], 1, 2);
-
- if (retval < 0 || CC->kill_me) { /* socket broken? */
- if ((CC->cs_flags & CS_STEALTH) == 0) {
- allwrite("<disconnected>", 0, NULL);
- }
- return;
- }
-
- /* if we have a complete line, do send processing */
- if (strlen(cmdbuf) > 0)
- if (cmdbuf[strlen(cmdbuf) - 1] == 10) {
- cmdbuf[strlen(cmdbuf) - 1] = 0;
- time(&CC->lastcmd);
- time(&CC->lastidle);
-
- if ((!strcasecmp(cmdbuf, "exit"))
- || (!strcasecmp(cmdbuf, "/exit"))
- || (!strcasecmp(cmdbuf, "quit"))
- || (!strcasecmp(cmdbuf, "logout"))
- || (!strcasecmp(cmdbuf, "logoff"))
- || (!strcasecmp(cmdbuf, "/q"))
- || (!strcasecmp(cmdbuf, ".q"))
- || (!strcasecmp(cmdbuf, "/quit"))
- )
- strcpy(cmdbuf, "000");
-
- if (!strcmp(cmdbuf, "000")) {
- if ((CC->cs_flags & CS_STEALTH) == 0) {
- allwrite("<exiting chat>", 0, NULL);
- }
- sleep(1);
- cprintf("000\n");
- CC->cs_flags = CC->cs_flags - CS_CHAT;
- return;
- }
- if ((!strcasecmp(cmdbuf, "/help"))
- || (!strcasecmp(cmdbuf, "help"))
- || (!strcasecmp(cmdbuf, "/?"))
- || (!strcasecmp(cmdbuf, "?"))) {
- cprintf(":|\n");
- cprintf(":|Available commands: \n");
- cprintf(":|/help (prints this message) \n");
- cprintf(":|/who (list users currently in chat) \n");
- cprintf(":|/whobbs (list users in chat -and- elsewhere) \n");
- cprintf(":|/me ('action' line, ala irc) \n");
- cprintf(":|/msg (send private message, ala irc) \n");
- if (is_room_aide()) {
- cprintf(":|/kick (kick another user out of this room) \n");
- }
- cprintf(":|/quit (exit from this chat) \n");
- cprintf(":|\n");
- ok_cmd = 1;
- }
- if (!strcasecmp(cmdbuf, "/who")) {
- do_chat_listing(0);
- ok_cmd = 1;
- }
- if (!strcasecmp(cmdbuf, "/whobbs")) {
- do_chat_listing(1);
- ok_cmd = 1;
- }
- if (!strncasecmp(cmdbuf, "/me ", 4)) {
- allwrite(&cmdbuf[4], 1, NULL);
- ok_cmd = 1;
- }
- if (!strncasecmp(cmdbuf, "/msg ", 5)) {
- ok_cmd = 1;
- strptr1 = &cmdbuf[5];
- if ((t_context = find_context(&strptr1))) {
- allwrite(strptr1, 2, CC->curr_user);
- if (strcasecmp(CC->curr_user, t_context->curr_user))
- allwrite(strptr1, 2, t_context->curr_user);
- } else
- cprintf(":|User not found.\n");
- cprintf("\n");
- }
- /* The /kick function is implemented by sending a specific
- * message to the kicked-out user's context. When that message
- * is processed by the read loop, that context will exit.
- */
- if ( (!strncasecmp(cmdbuf, "/kick ", 6)) && (is_room_aide()) ) {
- ok_cmd = 1;
- strptr1 = &cmdbuf[6];
- strcat(strptr1, " ");
- if ((t_context = find_context(&strptr1))) {
- if (strcasecmp(CC->curr_user, t_context->curr_user))
- allwrite(strptr1, 3, t_context->curr_user);
- } else
- cprintf(":|User not found.\n");
- cprintf("\n");
- }
- if ((cmdbuf[0] != '/') && (strlen(cmdbuf) > 0)) {
- ok_cmd = 1;
- allwrite(cmdbuf, 0, NULL);
- }
- if ((!ok_cmd) && (cmdbuf[0]) && (cmdbuf[0] != '\n'))
- cprintf(":|Command %s is not understood.\n", cmdbuf);
-
- strcpy(cmdbuf, "");
-
- }
- /* now check the queue for new incoming stuff */
-
- if (CC->fake_username[0])
- un = CC->fake_username;
- else
- un = CC->curr_user;
- if (ChatLastMsg > MyLastMsg) {
- ThisLastMsg = ChatLastMsg;
- for (clptr = ChatQueue; clptr != NULL; clptr = clptr->next) {
- if ((clptr->chat_seq > MyLastMsg) && ((!clptr->chat_username[0]) || (!strncasecmp(un, clptr->chat_username, 32)))) {
- if ((!clptr->chat_room[0]) || (!strncasecmp(CC->room.QRname, clptr->chat_room, ROOMNAMELEN))) {
- /* Output new chat data */
- cprintf("%s\n", clptr->chat_text);
-
- /* See if we've been force-quitted (kicked etc.) */
- if (!strcmp(&clptr->chat_text[2], KICKEDMSG)) {
- allwrite("<kicked out of this room>", 0, NULL);
- cprintf("000\n");
- CC->cs_flags = CC->cs_flags - CS_CHAT;
-
- /* Kick user out of room */
- CtdlInvtKick(CC->user.fullname, 0);
-
- /* And return to the Lobby */
- usergoto(config.c_baseroom, 0, 0, NULL, NULL);
- return;
- }
- }
- }
- }
- MyLastMsg = ThisLastMsg;
- }
- }
-}
-
-
-
-/*
- * Delete any remaining instant messages
- */
-void delete_instant_messages(void) {
- struct ExpressMessage *ptr;
-
- begin_critical_section(S_SESSION_TABLE);
- while (CC->FirstExpressMessage != NULL) {
- ptr = CC->FirstExpressMessage->next;
- if (CC->FirstExpressMessage->text != NULL)
- free(CC->FirstExpressMessage->text);
- free(CC->FirstExpressMessage);
- CC->FirstExpressMessage = ptr;
- }
- end_critical_section(S_SESSION_TABLE);
- }
-
-
-
-
-/*
- * Poll for instant messages (OLD METHOD -- ***DEPRECATED ***)
- */
-void cmd_pexp(char *argbuf)
-{
- struct ExpressMessage *ptr, *holdptr;
-
- if (CC->FirstExpressMessage == NULL) {
- cprintf("%d No instant messages waiting.\n", ERROR + MESSAGE_NOT_FOUND);
- return;
- }
- begin_critical_section(S_SESSION_TABLE);
- ptr = CC->FirstExpressMessage;
- CC->FirstExpressMessage = NULL;
- end_critical_section(S_SESSION_TABLE);
-
- cprintf("%d Express msgs:\n", LISTING_FOLLOWS);
- while (ptr != NULL) {
- if (ptr->flags && EM_BROADCAST)
- cprintf("Broadcast message ");
- else if (ptr->flags && EM_CHAT)
- cprintf("Chat request ");
- else if (ptr->flags && EM_GO_AWAY)
- cprintf("Please logoff now, as requested ");
- else
- cprintf("Message ");
- cprintf("from %s:\n", ptr->sender);
- if (ptr->text != NULL)
- memfmout(ptr->text, 0, "\n");
-
- holdptr = ptr->next;
- if (ptr->text != NULL) free(ptr->text);
- free(ptr);
- ptr = holdptr;
- }
- cprintf("000\n");
-}
-
-
-/*
- * Get instant messages (new method)
- */
-void cmd_gexp(char *argbuf) {
- struct ExpressMessage *ptr;
-
- if (CC->FirstExpressMessage == NULL) {
- cprintf("%d No instant messages waiting.\n", ERROR + MESSAGE_NOT_FOUND);
- return;
- }
-
- begin_critical_section(S_SESSION_TABLE);
- ptr = CC->FirstExpressMessage;
- CC->FirstExpressMessage = CC->FirstExpressMessage->next;
- end_critical_section(S_SESSION_TABLE);
-
- cprintf("%d %d|%ld|%d|%s|%s\n",
- LISTING_FOLLOWS,
- ((ptr->next != NULL) ? 1 : 0), /* more msgs? */
- (long)ptr->timestamp, /* time sent */
- ptr->flags, /* flags */
- ptr->sender, /* sender of msg */
- config.c_nodename /* static for now */
- );
-
- if (ptr->text != NULL) {
- memfmout(ptr->text, 0, "\n");
- if (ptr->text[strlen(ptr->text)-1] != '\n') cprintf("\n");
- free(ptr->text);
- }
-
- cprintf("000\n");
- free(ptr);
-}
-
-/*
- * Asynchronously deliver instant messages
- */
-void cmd_gexp_async(void) {
-
- /* Only do this if the session can handle asynchronous protocol */
- if (CC->is_async == 0) return;
-
- /* And don't do it if there's nothing to send. */
- if (CC->FirstExpressMessage == NULL) return;
-
- cprintf("%d instant msg\n", ASYNC_MSG + ASYNC_GEXP);
-}
-
-/*
- * Back end support function for send_instant_message() and company
- */
-void add_xmsg_to_context(struct CitContext *ccptr,
- struct ExpressMessage *newmsg)
-{
- struct ExpressMessage *findend;
-
- if (ccptr->FirstExpressMessage == NULL) {
- ccptr->FirstExpressMessage = newmsg;
- }
- else {
- findend = ccptr->FirstExpressMessage;
- while (findend->next != NULL) {
- findend = findend->next;
- }
- findend->next = newmsg;
- }
-
- /* If the target context is a session which can handle asynchronous
- * messages, go ahead and set the flag for that.
- */
- if (ccptr->is_async) {
- ccptr->async_waiting = 1;
- if (ccptr->state == CON_IDLE) {
- ccptr->state = CON_READY;
- }
- }
-}
-
-
-
-
-/*
- * This is the back end to the instant message sending function.
- * Returns the number of users to which the message was sent.
- * Sending a zero-length message tests for recipients without sending messages.
- */
-int send_instant_message(char *lun, char *x_user, char *x_msg)
-{
- int message_sent = 0; /* number of successful sends */
- struct CitContext *ccptr;
- struct ExpressMessage *newmsg;
- char *un;
- size_t msglen = 0;
- int do_send = 0; /* set to 1 to actually page, not
- * just check to see if we can.
- */
- struct savelist *sl = NULL; /* list of rooms to save this page */
- struct savelist *sptr;
- struct CtdlMessage *logmsg = NULL;
- long msgnum;
-
- if (strlen(x_msg) > 0) {
- msglen = strlen(x_msg) + 4;
- do_send = 1;
- }
-
- /* find the target user's context and append the message */
- begin_critical_section(S_SESSION_TABLE);
- for (ccptr = ContextList; ccptr != NULL; ccptr = ccptr->next) {
-
- if (ccptr->fake_username[0]) {
- un = ccptr->fake_username;
- }
- else {
- un = ccptr->user.fullname;
- }
-
- if ( ((!strcasecmp(un, x_user))
- || (!strcasecmp(x_user, "broadcast")))
- && ((ccptr->disable_exp == 0)
- || (CC->user.axlevel >= 6)) ) {
- if (do_send) {
- newmsg = (struct ExpressMessage *)
- malloc(sizeof (struct ExpressMessage));
- memset(newmsg, 0,
- sizeof (struct ExpressMessage));
- time(&(newmsg->timestamp));
- safestrncpy(newmsg->sender, lun,
- sizeof newmsg->sender);
- if (!strcasecmp(x_user, "broadcast"))
- newmsg->flags |= EM_BROADCAST;
- newmsg->text = strdup(x_msg);
-
- add_xmsg_to_context(ccptr, newmsg);
-
- /* and log it ... */
- if (ccptr != CC) {
- sptr = (struct savelist *)
- malloc(sizeof(struct savelist));
- sptr->next = sl;
- MailboxName(sptr->roomname,
- sizeof sptr->roomname,
- &ccptr->user, PAGELOGROOM);
- sl = sptr;
- }
- }
- ++message_sent;
- }
- }
- end_critical_section(S_SESSION_TABLE);
-
- /* Log the page to disk if configured to do so */
- if ( (do_send) && (message_sent) ) {
-
- logmsg = malloc(sizeof(struct CtdlMessage));
- memset(logmsg, 0, sizeof(struct CtdlMessage));
- logmsg->cm_magic = CTDLMESSAGE_MAGIC;
- logmsg->cm_anon_type = MES_NORMAL;
- logmsg->cm_format_type = 0;
- logmsg->cm_fields['A'] = strdup(lun);
- logmsg->cm_fields['N'] = strdup(NODENAME);
- logmsg->cm_fields['O'] = strdup(PAGELOGROOM);
- logmsg->cm_fields['R'] = strdup(x_user);
- logmsg->cm_fields['M'] = strdup(x_msg);
-
-
- /* Save a copy of the message in the sender's log room,
- * creating the room if necessary.
- */
- create_room(PAGELOGROOM, 4, "", 0, 1, 0, VIEW_BBS);
- msgnum = CtdlSubmitMsg(logmsg, NULL, PAGELOGROOM);
-
- /* Now save a copy in the global log room, if configured */
- if (strlen(config.c_logpages) > 0) {
- create_room(config.c_logpages, 3, "", 0, 1, 1, VIEW_BBS);
- CtdlSaveMsgPointerInRoom(config.c_logpages, msgnum, 0, NULL);
- }
-
- /* Save a copy in each recipient's log room, creating those
- * rooms if necessary. Note that we create as a type 5 room
- * rather than 4, which indicates that it's a personal room
- * but we've already supplied the namespace prefix.
- */
- while (sl != NULL) {
- create_room(sl->roomname, 5, "", 0, 1, 1, VIEW_BBS);
- CtdlSaveMsgPointerInRoom(sl->roomname, msgnum, 0, NULL);
- sptr = sl->next;
- free(sl);
- sl = sptr;
- }
-
- CtdlFreeMessage(logmsg);
- }
-
- return (message_sent);
-}
-
-/*
- * send instant messages
- */
-void cmd_sexp(char *argbuf)
-{
- int message_sent = 0;
- char x_user[USERNAME_SIZE];
- char x_msg[1024];
- char *lun;
- char *x_big_msgbuf = NULL;
-
- if ((!(CC->logged_in)) && (!(CC->internal_pgm))) {
- cprintf("%d Not logged in.\n", ERROR + NOT_LOGGED_IN);
- return;
- }
- if (CC->fake_username[0])
- lun = CC->fake_username;
- else
- lun = CC->user.fullname;
-
- extract_token(x_user, argbuf, 0, '|', sizeof x_user);
- extract_token(x_msg, argbuf, 1, '|', sizeof x_msg);
-
- if (!x_user[0]) {
- cprintf("%d You were not previously paged.\n", ERROR + NO_SUCH_USER);
- return;
- }
- if ((!strcasecmp(x_user, "broadcast")) && (CC->user.axlevel < 6)) {
- cprintf("%d Higher access required to send a broadcast.\n",
- ERROR + HIGHER_ACCESS_REQUIRED);
- return;
- }
- /* This loop handles text-transfer pages */
- if (!strcmp(x_msg, "-")) {
- message_sent = PerformXmsgHooks(lun, x_user, "");
- if (message_sent == 0) {
- if (getuser(NULL, x_user))
- cprintf("%d '%s' does not exist.\n",
- ERROR + NO_SUCH_USER, x_user);
- else
- cprintf("%d '%s' is not logged in "
- "or is not accepting pages.\n",
- ERROR + RESOURCE_NOT_OPEN, x_user);
- return;
- }
- unbuffer_output();
- cprintf("%d Transmit message (will deliver to %d users)\n",
- SEND_LISTING, message_sent);
- x_big_msgbuf = malloc(SIZ);
- memset(x_big_msgbuf, 0, SIZ);
- while (client_getln(x_msg, sizeof x_msg),
- strcmp(x_msg, "000")) {
- x_big_msgbuf = realloc(x_big_msgbuf,
- strlen(x_big_msgbuf) + strlen(x_msg) + 4);
- if (strlen(x_big_msgbuf) > 0)
- if (x_big_msgbuf[strlen(x_big_msgbuf)] != '\n')
- strcat(x_big_msgbuf, "\n");
- strcat(x_big_msgbuf, x_msg);
- }
- PerformXmsgHooks(lun, x_user, x_big_msgbuf);
- free(x_big_msgbuf);
-
- /* This loop handles inline pages */
- } else {
- message_sent = PerformXmsgHooks(lun, x_user, x_msg);
-
- if (message_sent > 0) {
- if (strlen(x_msg) > 0)
- cprintf("%d Message sent", CIT_OK);
- else
- cprintf("%d Ok to send message", CIT_OK);
- if (message_sent > 1)
- cprintf(" to %d users", message_sent);
- cprintf(".\n");
- } else {
- if (getuser(NULL, x_user))
- cprintf("%d '%s' does not exist.\n",
- ERROR + NO_SUCH_USER, x_user);
- else
- cprintf("%d '%s' is not logged in "
- "or is not accepting pages.\n",
- ERROR + RESOURCE_NOT_OPEN, x_user);
- }
-
-
- }
-}
-
-
-
-/*
- * Enter or exit paging-disabled mode
- */
-void cmd_dexp(char *argbuf)
-{
- int new_state;
-
- if (CtdlAccessCheck(ac_logged_in)) return;
-
- new_state = extract_int(argbuf, 0);
- if ((new_state == 0) || (new_state == 1)) {
- CC->disable_exp = new_state;
- }
-
- cprintf("%d %d\n", CIT_OK, CC->disable_exp);
-}
-
-
-/*
- * Request client termination
- */
-void cmd_reqt(char *argbuf) {
- struct CitContext *ccptr;
- int sessions = 0;
- int which_session;
- struct ExpressMessage *newmsg;
-
- if (CtdlAccessCheck(ac_aide)) return;
- which_session = extract_int(argbuf, 0);
-
- begin_critical_section(S_SESSION_TABLE);
- for (ccptr = ContextList; ccptr != NULL; ccptr = ccptr->next) {
- if ((ccptr->cs_pid == which_session) || (which_session == 0)) {
-
- newmsg = (struct ExpressMessage *)
- malloc(sizeof (struct ExpressMessage));
- memset(newmsg, 0,
- sizeof (struct ExpressMessage));
- time(&(newmsg->timestamp));
- safestrncpy(newmsg->sender, CC->user.fullname,
- sizeof newmsg->sender);
- newmsg->flags |= EM_GO_AWAY;
- newmsg->text = strdup("Automatic logoff requested.");
-
- add_xmsg_to_context(ccptr, newmsg);
- ++sessions;
-
- }
- }
- end_critical_section(S_SESSION_TABLE);
- cprintf("%d Sent termination request to %d sessions.\n", CIT_OK, sessions);
-}
-
-
-
-CTDL_MODULE_INIT(chat)
-{
- CtdlRegisterProtoHook(cmd_chat, "CHAT", "Begin real-time chat");
- CtdlRegisterProtoHook(cmd_pexp, "PEXP", "Poll for instant messages");
- CtdlRegisterProtoHook(cmd_gexp, "GEXP", "Get instant messages");
- CtdlRegisterProtoHook(cmd_sexp, "SEXP", "Send an instant message");
- CtdlRegisterProtoHook(cmd_dexp, "DEXP", "Disable instant messages");
- CtdlRegisterProtoHook(cmd_reqt, "REQT", "Request client termination");
- CtdlRegisterSessionHook(cmd_gexp_async, EVT_ASYNC);
- CtdlRegisterSessionHook(delete_instant_messages, EVT_STOP);
- CtdlRegisterXmsgHook(send_instant_message, XMSG_PRI_LOCAL);
-
- /* return our Subversion id for the Log */
- return "$Id$";
-}
-
+++ /dev/null
-/* $Id$ */
-void ChatUnloadingTest(void);
-void allwrite (char *cmdbuf, int flag, char *username);
-t_context *find_context (char **unstr);
-void do_chat_listing (int allflag);
-void cmd_chat (char *argbuf);
-void cmd_pexp (char *argbuf); /* arg unused */
-void cmd_sexp (char *argbuf);
-void delete_instant_messages(void);
-void cmd_gexp(char *);
-int send_instant_message(char *, char *, char *);
-
-struct savelist {
- struct savelist *next;
- char roomname[ROOMNAMELEN];
-};
+++ /dev/null
-/* $Id$ */
-
-#include <string.h>
-#include <unistd.h>
-#include <sys/stat.h>
-#include <sys/types.h>
-#include "sysdep.h"
-
-#ifdef HAVE_OPENSSL
-#include <openssl/ssl.h>
-#include <openssl/err.h>
-#include <openssl/rand.h>
-#endif
-
-#if TIME_WITH_SYS_TIME
-# include <sys/time.h>
-# include <time.h>
-#else
-# if HAVE_SYS_TIME_H
-# include <sys/time.h>
-# else
-# include <time.h>
-# endif
-#endif
-
-#ifdef HAVE_PTHREAD_H
-#include <pthread.h>
-#endif
-
-#ifdef HAVE_SYS_SELECT_H
-#include <sys/select.h>
-#endif
-
-#include <stdio.h>
-#include "server.h"
-#include "serv_crypto.h"
-#include "sysdep_decls.h"
-#include "citadel.h"
-#include "config.h"
-
-
-#include "ctdl_module.h"
-/* TODO: should we use the standard module init stuff to start this? */
-/* TODO: should we register an event handler to call destruct_ssl? */
-
-#ifdef HAVE_OPENSSL
-SSL_CTX *ssl_ctx; /* SSL context */
-pthread_mutex_t **SSLCritters; /* Things needing locking */
-
-static unsigned long id_callback(void)
-{
- return (unsigned long) pthread_self();
-}
-
-void destruct_ssl(void)
-{
- int a;
- CtdlUnregisterProtoHook(cmd_stls, "STLS");
- CtdlUnregisterProtoHook(cmd_gtls, "GTLS");
- for (a = 0; a < CRYPTO_num_locks(); a++)
- free(SSLCritters[a]);
- free (SSLCritters);
-}
-
-void init_ssl(void)
-{
- SSL_METHOD *ssl_method;
- DH *dh;
- RSA *rsa=NULL;
- X509_REQ *req = NULL;
- X509 *cer = NULL;
- EVP_PKEY *pk = NULL;
- EVP_PKEY *req_pkey = NULL;
- X509_NAME *name = NULL;
- FILE *fp;
-
- if (!access(EGD_POOL, F_OK))
- RAND_egd(EGD_POOL);
-
- if (!RAND_status()) {
- lprintf(CTDL_CRIT,
- "PRNG not adequately seeded, won't do SSL/TLS\n");
- return;
- }
- SSLCritters =
- malloc(CRYPTO_num_locks() * sizeof(pthread_mutex_t *));
- if (!SSLCritters) {
- lprintf(CTDL_EMERG, "citserver: can't allocate memory!!\n");
- /* Nothing's been initialized, just die */
- exit(1);
- } else {
- int a;
-
- for (a = 0; a < CRYPTO_num_locks(); a++) {
- SSLCritters[a] = malloc(sizeof(pthread_mutex_t));
- if (!SSLCritters[a]) {
- lprintf(CTDL_EMERG,
- "citserver: can't allocate memory!!\n");
- /* Nothing's been initialized, just die */
- exit(1);
- }
- pthread_mutex_init(SSLCritters[a], NULL);
- }
- }
-
- /*
- * Initialize SSL transport layer
- */
- SSL_library_init();
- SSL_load_error_strings();
- ssl_method = SSLv23_server_method();
- if (!(ssl_ctx = SSL_CTX_new(ssl_method))) {
- lprintf(CTDL_CRIT, "SSL_CTX_new failed: %s\n",
- ERR_reason_error_string(ERR_get_error()));
- return;
- }
- if (!(SSL_CTX_set_cipher_list(ssl_ctx, CIT_CIPHERS))) {
- lprintf(CTDL_CRIT, "SSL: No ciphers available\n");
- SSL_CTX_free(ssl_ctx);
- ssl_ctx = NULL;
- return;
- }
-#if 0
-#if SSLEAY_VERSION_NUMBER >= 0x00906000L
- SSL_CTX_set_mode(ssl_ctx, SSL_CTX_get_mode(ssl_ctx) |
- SSL_MODE_AUTO_RETRY);
-#endif
-#endif
-
- CRYPTO_set_locking_callback(ssl_lock);
- CRYPTO_set_id_callback(id_callback);
-
- /* Load DH parameters into the context */
- dh = DH_new();
- if (!dh) {
- lprintf(CTDL_CRIT, "init_ssl() can't allocate a DH object: %s\n",
- ERR_reason_error_string(ERR_get_error()));
- SSL_CTX_free(ssl_ctx);
- ssl_ctx = NULL;
- return;
- }
- if (!(BN_hex2bn(&(dh->p), DH_P))) {
- lprintf(CTDL_CRIT, "init_ssl() can't assign DH_P: %s\n",
- ERR_reason_error_string(ERR_get_error()));
- SSL_CTX_free(ssl_ctx);
- ssl_ctx = NULL;
- return;
- }
- if (!(BN_hex2bn(&(dh->g), DH_G))) {
- lprintf(CTDL_CRIT, "init_ssl() can't assign DH_G: %s\n",
- ERR_reason_error_string(ERR_get_error()));
- SSL_CTX_free(ssl_ctx);
- ssl_ctx = NULL;
- return;
- }
- dh->length = DH_L;
- SSL_CTX_set_tmp_dh(ssl_ctx, dh);
- DH_free(dh);
-
- /* Get our certificates in order.
- * First, create the key/cert directory if it's not there already...
- */
- mkdir(ctdl_key_dir, 0700);
-
- /*
- * Generate a key pair if we don't have one.
- */
- if (access(file_crpt_file_key, R_OK) != 0) {
- lprintf(CTDL_INFO, "Generating RSA key pair.\n");
- rsa = RSA_generate_key(1024, /* modulus size */
- 65537, /* exponent */
- NULL, /* no callback */
- NULL); /* no callback */
- if (rsa == NULL) {
- lprintf(CTDL_CRIT, "Key generation failed: %s\n",
- ERR_reason_error_string(ERR_get_error()));
- }
- if (rsa != NULL) {
- fp = fopen(file_crpt_file_key, "w");
- if (fp != NULL) {
- chmod(file_crpt_file_key, 0600);
- if (PEM_write_RSAPrivateKey(fp, /* the file */
- rsa, /* the key */
- NULL, /* no enc */
- NULL, /* no passphr */
- 0, /* no passphr */
- NULL, /* no callbk */
- NULL /* no callbk */
- ) != 1) {
- lprintf(CTDL_CRIT, "Cannot write key: %s\n",
- ERR_reason_error_string(ERR_get_error()));
- unlink(file_crpt_file_key);
- }
- fclose(fp);
- }
- RSA_free(rsa);
- }
- }
-
- /*
- * Generate a CSR if we don't have one.
- */
- if (access(file_crpt_file_csr, R_OK) != 0) {
- lprintf(CTDL_INFO, "Generating a certificate signing request.\n");
-
- /*
- * Read our key from the file. No, we don't just keep this
- * in memory from the above key-generation function, because
- * there is the possibility that the key was already on disk
- * and we didn't just generate it now.
- */
- fp = fopen(file_crpt_file_key, "r");
- if (fp) {
- rsa = PEM_read_RSAPrivateKey(fp, NULL, NULL, NULL);
- fclose(fp);
- }
-
- if (rsa) {
-
- /* Create a public key from the private key */
- if (pk=EVP_PKEY_new(), pk != NULL) {
- EVP_PKEY_assign_RSA(pk, rsa);
- if (req = X509_REQ_new(), req != NULL) {
-
- /* Set the public key */
- X509_REQ_set_pubkey(req, pk);
- X509_REQ_set_version(req, 0L);
-
- name = X509_REQ_get_subject_name(req);
-
- /* Tell it who we are */
-
- /*
- X509_NAME_add_entry_by_txt(name, "C",
- MBSTRING_ASC, "US", -1, -1, 0);
-
- X509_NAME_add_entry_by_txt(name, "ST",
- MBSTRING_ASC, "New York", -1, -1, 0);
-
- X509_NAME_add_entry_by_txt(name, "L",
- MBSTRING_ASC, "Mount Kisco", -1, -1, 0);
- */
-
- X509_NAME_add_entry_by_txt(name, "O",
- MBSTRING_ASC, config.c_humannode, -1, -1, 0);
-
- X509_NAME_add_entry_by_txt(name, "OU",
- MBSTRING_ASC, "Citadel server", -1, -1, 0);
-
- /* X509_NAME_add_entry_by_txt(name, "CN",
- MBSTRING_ASC, config.c_fqdn, -1, -1, 0);
- */
-
- X509_NAME_add_entry_by_txt(name, "CN",
- MBSTRING_ASC, "*", -1, -1, 0);
-
- X509_REQ_set_subject_name(req, name);
-
- /* Sign the CSR */
- if (!X509_REQ_sign(req, pk, EVP_md5())) {
- lprintf(CTDL_CRIT, "X509_REQ_sign(): error\n");
- }
- else {
- /* Write it to disk. */
- fp = fopen(file_crpt_file_csr, "w");
- if (fp != NULL) {
- chmod(file_crpt_file_csr, 0600);
- PEM_write_X509_REQ(fp, req);
- fclose(fp);
- }
- }
-
- X509_REQ_free(req);
- }
- }
-
- RSA_free(rsa);
- }
-
- else {
- lprintf(CTDL_CRIT, "Unable to read private key.\n");
- }
- }
-
-
-
- /*
- * Generate a self-signed certificate if we don't have one.
- */
- if (access(file_crpt_file_cer, R_OK) != 0) {
- lprintf(CTDL_INFO, "Generating a self-signed certificate.\n");
-
- /* Same deal as before: always read the key from disk because
- * it may or may not have just been generated.
- */
- fp = fopen(file_crpt_file_key, "r");
- if (fp) {
- rsa = PEM_read_RSAPrivateKey(fp, NULL, NULL, NULL);
- fclose(fp);
- }
-
- /* This also holds true for the CSR. */
- req = NULL;
- cer = NULL;
- pk = NULL;
- if (rsa) {
- if (pk=EVP_PKEY_new(), pk != NULL) {
- EVP_PKEY_assign_RSA(pk, rsa);
- }
-
- fp = fopen(file_crpt_file_csr, "r");
- if (fp) {
- req = PEM_read_X509_REQ(fp, NULL, NULL, NULL);
- fclose(fp);
- }
-
- if (req) {
- if (cer = X509_new(), cer != NULL) {
-
- ASN1_INTEGER_set(X509_get_serialNumber(cer), 0);
- X509_set_issuer_name(cer, req->req_info->subject);
- X509_set_subject_name(cer, req->req_info->subject);
- X509_gmtime_adj(X509_get_notBefore(cer),0);
- X509_gmtime_adj(X509_get_notAfter(cer),(long)60*60*24*SIGN_DAYS);
- req_pkey = X509_REQ_get_pubkey(req);
- X509_set_pubkey(cer, req_pkey);
- EVP_PKEY_free(req_pkey);
-
- /* Sign the cert */
- if (!X509_sign(cer, pk, EVP_md5())) {
- lprintf(CTDL_CRIT, "X509_sign(): error\n");
- }
- else {
- /* Write it to disk. */
- fp = fopen(file_crpt_file_cer, "w");
- if (fp != NULL) {
- chmod(file_crpt_file_cer, 0600);
- PEM_write_X509(fp, cer);
- fclose(fp);
- }
- }
- X509_free(cer);
- }
- }
-
- RSA_free(rsa);
- }
- }
-
-
- /*
- * Now try to bind to the key and certificate.
- */
- SSL_CTX_use_certificate_chain_file(ssl_ctx, file_crpt_file_cer);
- SSL_CTX_use_PrivateKey_file(ssl_ctx, file_crpt_file_key, SSL_FILETYPE_PEM);
- if ( !SSL_CTX_check_private_key(ssl_ctx) ) {
- lprintf(CTDL_CRIT, "Cannot install certificate: %s\n",
- ERR_reason_error_string(ERR_get_error()));
- }
-
- /* Finally let the server know we're here */
- CtdlRegisterProtoHook(cmd_stls, "STLS", "Start SSL/TLS session");
- CtdlRegisterProtoHook(cmd_gtls, "GTLS",
- "Get SSL/TLS session status");
- CtdlRegisterSessionHook(endtls, EVT_STOP);
-}
-
-
-/*
- * client_write_ssl() Send binary data to the client encrypted.
- */
-void client_write_ssl(char *buf, int nbytes)
-{
- int retval;
- int nremain;
- char junk[1];
-
- nremain = nbytes;
-
- while (nremain > 0) {
- if (SSL_want_write(CC->ssl)) {
- if ((SSL_read(CC->ssl, junk, 0)) < 1) {
- lprintf(CTDL_DEBUG, "SSL_read in client_write: %s\n", ERR_reason_error_string(ERR_get_error()));
- }
- }
- retval =
- SSL_write(CC->ssl, &buf[nbytes - nremain], nremain);
- if (retval < 1) {
- long errval;
-
- errval = SSL_get_error(CC->ssl, retval);
- if (errval == SSL_ERROR_WANT_READ ||
- errval == SSL_ERROR_WANT_WRITE) {
- sleep(1);
- continue;
- }
- lprintf(CTDL_DEBUG, "SSL_write got error %ld, ret %d\n", errval, retval);
- if (retval == -1)
- lprintf(CTDL_DEBUG, "errno is %d\n", errno);
- endtls();
- client_write(&buf[nbytes - nremain], nremain);
- return;
- }
- nremain -= retval;
- }
-}
-
-
-/*
- * client_read_ssl() - read data from the encrypted layer.
- */
-int client_read_ssl(char *buf, int bytes, int timeout)
-{
-#if 0
- fd_set rfds;
- struct timeval tv;
- int retval;
- int s;
-#endif
- int len, rlen;
- char junk[1];
-
- len = 0;
- while (len < bytes) {
-#if 0
- /*
- * This code is disabled because we don't need it when
- * using blocking reads (which we are). -IO
- */
- FD_ZERO(&rfds);
- s = BIO_get_fd(CC->ssl->rbio, NULL);
- FD_SET(s, &rfds);
- tv.tv_sec = timeout;
- tv.tv_usec = 0;
-
- retval = select(s + 1, &rfds, NULL, NULL, &tv);
-
- if (FD_ISSET(s, &rfds) == 0) {
- return (0);
- }
-
-#endif
- if (SSL_want_read(CC->ssl)) {
- if ((SSL_write(CC->ssl, junk, 0)) < 1) {
- lprintf(CTDL_DEBUG, "SSL_write in client_read: %s\n", ERR_reason_error_string(ERR_get_error()));
- }
- }
- rlen = SSL_read(CC->ssl, &buf[len], bytes - len);
- if (rlen < 1) {
- long errval;
-
- errval = SSL_get_error(CC->ssl, rlen);
- if (errval == SSL_ERROR_WANT_READ ||
- errval == SSL_ERROR_WANT_WRITE) {
- sleep(1);
- continue;
- }
- lprintf(CTDL_DEBUG, "SSL_read got error %ld\n", errval);
- endtls();
- return (client_read_to
- (&buf[len], bytes - len, timeout));
- }
- len += rlen;
- }
- return (1);
-}
-
-
-/*
- * CtdlStartTLS() starts SSL/TLS encryption for the current session. It
- * must be supplied with pre-generated strings for responses of "ok," "no
- * support for TLS," and "error" so that we can use this in any protocol.
- */
-void CtdlStartTLS(char *ok_response, char *nosup_response,
- char *error_response) {
-
- int retval, bits, alg_bits;
-
- if (!ssl_ctx) {
- lprintf(CTDL_CRIT, "SSL failed: no ssl_ctx exists?\n");
- if (nosup_response != NULL) cprintf("%s", nosup_response);
- return;
- }
- if (!(CC->ssl = SSL_new(ssl_ctx))) {
- lprintf(CTDL_CRIT, "SSL_new failed: %s\n",
- ERR_reason_error_string(ERR_get_error()));
- if (error_response != NULL) cprintf("%s", error_response);
- return;
- }
- if (!(SSL_set_fd(CC->ssl, CC->client_socket))) {
- lprintf(CTDL_CRIT, "SSL_set_fd failed: %s\n",
- ERR_reason_error_string(ERR_get_error()));
- SSL_free(CC->ssl);
- CC->ssl = NULL;
- if (error_response != NULL) cprintf("%s", error_response);
- return;
- }
- if (ok_response != NULL) cprintf("%s", ok_response);
- retval = SSL_accept(CC->ssl);
- if (retval < 1) {
- /*
- * Can't notify the client of an error here; they will
- * discover the problem at the SSL layer and should
- * revert to unencrypted communications.
- */
- long errval;
- char error_string[128];
-
- errval = SSL_get_error(CC->ssl, retval);
- lprintf(CTDL_CRIT, "SSL_accept failed: retval=%d, errval=%ld, err=%s\n",
- retval,
- errval,
- ERR_error_string(errval, error_string)
- );
- SSL_free(CC->ssl);
- CC->ssl = NULL;
- return;
- }
- BIO_set_close(CC->ssl->rbio, BIO_NOCLOSE);
- bits = SSL_CIPHER_get_bits(SSL_get_current_cipher(CC->ssl), &alg_bits);
- lprintf(CTDL_INFO, "SSL/TLS using %s on %s (%d of %d bits)\n",
- SSL_CIPHER_get_name(SSL_get_current_cipher(CC->ssl)),
- SSL_CIPHER_get_version(SSL_get_current_cipher(CC->ssl)),
- bits, alg_bits);
- CC->redirect_ssl = 1;
-}
-
-
-/*
- * cmd_stls() starts SSL/TLS encryption for the current session
- */
-void cmd_stls(char *params)
-{
- char ok_response[SIZ];
- char nosup_response[SIZ];
- char error_response[SIZ];
-
- unbuffer_output();
-
- sprintf(ok_response,
- "%d Begin TLS negotiation now\n",
- CIT_OK);
- sprintf(nosup_response,
- "%d TLS not supported here\n",
- ERROR + CMD_NOT_SUPPORTED);
- sprintf(error_response,
- "%d TLS negotiation error\n",
- ERROR + INTERNAL_ERROR);
-
- CtdlStartTLS(ok_response, nosup_response, error_response);
-}
-
-
-/*
- * cmd_gtls() returns status info about the TLS connection
- */
-void cmd_gtls(char *params)
-{
- int bits, alg_bits;
-
- if (!CC->ssl || !CC->redirect_ssl) {
- cprintf("%d Session is not encrypted.\n", ERROR);
- return;
- }
- bits =
- SSL_CIPHER_get_bits(SSL_get_current_cipher(CC->ssl),
- &alg_bits);
- cprintf("%d %s|%s|%d|%d\n", CIT_OK,
- SSL_CIPHER_get_version(SSL_get_current_cipher(CC->ssl)),
- SSL_CIPHER_get_name(SSL_get_current_cipher(CC->ssl)),
- alg_bits, bits);
-}
-
-
-/*
- * endtls() shuts down the TLS connection
- *
- * WARNING: This may make your session vulnerable to a known plaintext
- * attack in the current implmentation.
- */
-void endtls(void)
-{
- if (!CC->ssl) {
- CC->redirect_ssl = 0;
- return;
- }
-
- lprintf(CTDL_INFO, "Ending SSL/TLS\n");
- SSL_shutdown(CC->ssl);
- SSL_free(CC->ssl);
- CC->ssl = NULL;
- CC->redirect_ssl = 0;
-}
-
-
-/*
- * ssl_lock() callback for OpenSSL mutex locks
- */
-void ssl_lock(int mode, int n, const char *file, int line)
-{
- if (mode & CRYPTO_LOCK)
- pthread_mutex_lock(SSLCritters[n]);
- else
- pthread_mutex_unlock(SSLCritters[n]);
-}
-#endif /* HAVE_OPENSSL */
+++ /dev/null
-/*
- * $Id$
- *
- * This module handles the expiry of old messages and the purging of old users.
- *
- */
-
-
-/*
- * A brief technical discussion:
- *
- * Several of the purge operations found in this module operate in two
- * stages: the first stage generates a linked list of objects to be deleted,
- * then the second stage deletes all listed objects from the database.
- *
- * At first glance this may seem cumbersome and unnecessary. The reason it is
- * implemented in this way is because Berkeley DB, and possibly other backends
- * we may hook into in the future, explicitly do _not_ support the deletion of
- * records from a file while the file is being traversed. The delete operation
- * will succeed, but the traversal is not guaranteed to visit every object if
- * this is done. Therefore we utilize the two-stage purge.
- *
- * When using Berkeley DB, there's another reason for the two-phase purge: we
- * don't want the entire thing being done as one huge transaction.
- */
-
-
-#include "sysdep.h"
-#include <stdlib.h>
-#include <unistd.h>
-#include <stdio.h>
-#include <fcntl.h>
-#include <signal.h>
-#include <pwd.h>
-#include <errno.h>
-#include <sys/types.h>
-
-#if TIME_WITH_SYS_TIME
-# include <sys/time.h>
-# include <time.h>
-#else
-# if HAVE_SYS_TIME_H
-# include <sys/time.h>
-# else
-# include <time.h>
-# endif
-#endif
-
-#include <sys/wait.h>
-#include <string.h>
-#include <limits.h>
-#include "citadel.h"
-#include "server.h"
-#include "citserver.h"
-#include "support.h"
-#include "config.h"
-#include "room_ops.h"
-#include "policy.h"
-#include "database.h"
-#include "msgbase.h"
-#include "user_ops.h"
-#include "control.h"
-#include "serv_network.h"
-#include "tools.h"
-
-
-#include "ctdl_module.h"
-
-
-struct PurgeList {
- struct PurgeList *next;
- char name[ROOMNAMELEN]; /* use the larger of username or roomname */
-};
-
-struct VPurgeList {
- struct VPurgeList *next;
- long vp_roomnum;
- long vp_roomgen;
- long vp_usernum;
-};
-
-struct ValidRoom {
- struct ValidRoom *next;
- long vr_roomnum;
- long vr_roomgen;
-};
-
-struct ValidUser {
- struct ValidUser *next;
- long vu_usernum;
-};
-
-
-struct ctdlroomref {
- struct ctdlroomref *next;
- long msgnum;
-};
-
-struct UPurgeList {
- struct UPurgeList *next;
- char up_key[256];
-};
-
-struct EPurgeList {
- struct EPurgeList *next;
- int ep_keylen;
- char *ep_key;
-};
-
-
-struct PurgeList *UserPurgeList = NULL;
-struct PurgeList *RoomPurgeList = NULL;
-struct ValidRoom *ValidRoomList = NULL;
-struct ValidUser *ValidUserList = NULL;
-int messages_purged;
-int users_not_purged;
-
-struct ctdlroomref *rr = NULL;
-
-extern struct CitContext *ContextList;
-
-
-/*
- * First phase of message purge -- gather the locations of messages which
- * qualify for purging and write them to a temp file.
- */
-void GatherPurgeMessages(struct ctdlroom *qrbuf, void *data) {
- struct ExpirePolicy epbuf;
- long delnum;
- time_t xtime, now;
- struct CtdlMessage *msg = NULL;
- int a;
- struct cdbdata *cdbfr;
- long *msglist = NULL;
- int num_msgs = 0;
- FILE *purgelist;
-
- purgelist = (FILE *)data;
- fprintf(purgelist, "r=%s\n", qrbuf->QRname);
-
- time(&now);
- GetExpirePolicy(&epbuf, qrbuf);
-
- /* If the room is set to never expire messages ... do nothing */
- if (epbuf.expire_mode == EXPIRE_NEXTLEVEL) return;
- if (epbuf.expire_mode == EXPIRE_MANUAL) return;
-
- cdbfr = cdb_fetch(CDB_MSGLISTS, &qrbuf->QRnumber, sizeof(long));
-
- if (cdbfr != NULL) {
- msglist = malloc(cdbfr->len);
- memcpy(msglist, cdbfr->ptr, cdbfr->len);
- num_msgs = cdbfr->len / sizeof(long);
- cdb_free(cdbfr);
- }
-
- /* Nothing to do if there aren't any messages */
- if (num_msgs == 0) {
- if (msglist != NULL) free(msglist);
- return;
- }
-
- /* If the room is set to expire by count, do that */
- if (epbuf.expire_mode == EXPIRE_NUMMSGS) {
- if (num_msgs > epbuf.expire_value) {
- for (a=0; a<(num_msgs - epbuf.expire_value); ++a) {
- fprintf(purgelist, "m=%ld\n", msglist[a]);
- ++messages_purged;
- }
- }
- }
-
- /* If the room is set to expire by age... */
- if (epbuf.expire_mode == EXPIRE_AGE) {
- for (a=0; a<num_msgs; ++a) {
- delnum = msglist[a];
-
- msg = CtdlFetchMessage(delnum, 0); /* dont need body */
- if (msg != NULL) {
- xtime = atol(msg->cm_fields['T']);
- CtdlFreeMessage(msg);
- } else {
- xtime = 0L;
- }
-
- if ((xtime > 0L)
- && (now - xtime > (time_t)(epbuf.expire_value * 86400L))) {
- fprintf(purgelist, "m=%ld\n", delnum);
- ++messages_purged;
- }
- }
- }
-
- if (msglist != NULL) free(msglist);
-}
-
-
-/*
- * Second phase of message purge -- read list of msgs from temp file and
- * delete them.
- */
-void DoPurgeMessages(FILE *purgelist) {
- char roomname[ROOMNAMELEN];
- long msgnum;
- char buf[SIZ];
-
- rewind(purgelist);
- strcpy(roomname, "nonexistent room ___ ___");
- while (fgets(buf, sizeof buf, purgelist) != NULL) {
- buf[strlen(buf)-1]=0;
- if (!strncasecmp(buf, "r=", 2)) {
- strcpy(roomname, &buf[2]);
- }
- if (!strncasecmp(buf, "m=", 2)) {
- msgnum = atol(&buf[2]);
- if (msgnum > 0L) {
- CtdlDeleteMessages(roomname, &msgnum, 1, "");
- }
- }
- }
-}
-
-
-void PurgeMessages(void) {
- FILE *purgelist;
-
- lprintf(CTDL_DEBUG, "PurgeMessages() called\n");
- messages_purged = 0;
-
- purgelist = tmpfile();
- if (purgelist == NULL) {
- lprintf(CTDL_CRIT, "Can't create purgelist temp file: %s\n",
- strerror(errno));
- return;
- }
-
- ForEachRoom(GatherPurgeMessages, (void *)purgelist );
- DoPurgeMessages(purgelist);
- fclose(purgelist);
-}
-
-
-void AddValidUser(struct ctdluser *usbuf, void *data) {
- struct ValidUser *vuptr;
-
- vuptr = (struct ValidUser *)malloc(sizeof(struct ValidUser));
- vuptr->next = ValidUserList;
- vuptr->vu_usernum = usbuf->usernum;
- ValidUserList = vuptr;
-}
-
-void AddValidRoom(struct ctdlroom *qrbuf, void *data) {
- struct ValidRoom *vrptr;
-
- vrptr = (struct ValidRoom *)malloc(sizeof(struct ValidRoom));
- vrptr->next = ValidRoomList;
- vrptr->vr_roomnum = qrbuf->QRnumber;
- vrptr->vr_roomgen = qrbuf->QRgen;
- ValidRoomList = vrptr;
-}
-
-void DoPurgeRooms(struct ctdlroom *qrbuf, void *data) {
- time_t age, purge_secs;
- struct PurgeList *pptr;
- struct ValidUser *vuptr;
- int do_purge = 0;
-
- /* For mailbox rooms, there's only one purging rule: if the user who
- * owns the room still exists, we keep the room; otherwise, we purge
- * it. Bypass any other rules.
- */
- if (qrbuf->QRflags & QR_MAILBOX) {
- /* if user not found, do_purge will be 1 */
- do_purge = 1;
- for (vuptr=ValidUserList; vuptr!=NULL; vuptr=vuptr->next) {
- if (vuptr->vu_usernum == atol(qrbuf->QRname)) {
- do_purge = 0;
- }
- }
- }
- else {
- /* Any of these attributes render a room non-purgable */
- if (qrbuf->QRflags & QR_PERMANENT) return;
- if (qrbuf->QRflags & QR_DIRECTORY) return;
- if (qrbuf->QRflags & QR_NETWORK) return;
- if (!strcasecmp(qrbuf->QRname, SYSCONFIGROOM)) return;
- if (is_noneditable(qrbuf)) return;
-
- /* If we don't know the modification date, be safe and don't purge */
- if (qrbuf->QRmtime <= (time_t)0) return;
-
- /* If no room purge time is set, be safe and don't purge */
- if (config.c_roompurge < 0) return;
-
- /* Otherwise, check the date of last modification */
- age = time(NULL) - (qrbuf->QRmtime);
- purge_secs = (time_t)config.c_roompurge * (time_t)86400;
- if (purge_secs <= (time_t)0) return;
- lprintf(CTDL_DEBUG, "<%s> is <%ld> seconds old\n", qrbuf->QRname, (long)age);
- if (age > purge_secs) do_purge = 1;
- } /* !QR_MAILBOX */
-
- if (do_purge) {
- pptr = (struct PurgeList *) malloc(sizeof(struct PurgeList));
- pptr->next = RoomPurgeList;
- strcpy(pptr->name, qrbuf->QRname);
- RoomPurgeList = pptr;
- }
-
-}
-
-
-
-int PurgeRooms(void) {
- struct PurgeList *pptr;
- int num_rooms_purged = 0;
- struct ctdlroom qrbuf;
- struct ValidUser *vuptr;
- char *transcript = NULL;
-
- lprintf(CTDL_DEBUG, "PurgeRooms() called\n");
-
-
- /* Load up a table full of valid user numbers so we can delete
- * user-owned rooms for users who no longer exist */
- ForEachUser(AddValidUser, NULL);
-
- /* Then cycle through the room file */
- ForEachRoom(DoPurgeRooms, NULL);
-
- /* Free the valid user list */
- while (ValidUserList != NULL) {
- vuptr = ValidUserList->next;
- free(ValidUserList);
- ValidUserList = vuptr;
- }
-
-
- transcript = malloc(SIZ);
- strcpy(transcript, "The following rooms have been auto-purged:\n");
-
- while (RoomPurgeList != NULL) {
- if (getroom(&qrbuf, RoomPurgeList->name) == 0) {
- transcript=realloc(transcript, strlen(transcript)+SIZ);
- snprintf(&transcript[strlen(transcript)], SIZ, " %s\n",
- qrbuf.QRname);
- delete_room(&qrbuf);
- }
- pptr = RoomPurgeList->next;
- free(RoomPurgeList);
- RoomPurgeList = pptr;
- ++num_rooms_purged;
- }
-
- if (num_rooms_purged > 0) aide_message(transcript, "Room Autopurger Message");
- free(transcript);
-
- lprintf(CTDL_DEBUG, "Purged %d rooms.\n", num_rooms_purged);
- return(num_rooms_purged);
-}
-
-
-/*
- * Back end function to check user accounts for associated Unix accounts
- * which no longer exist. (Only relevant for host auth mode.)
- */
-void do_uid_user_purge(struct ctdluser *us, void *data) {
- struct PurgeList *pptr;
-
- if ((us->uid != (-1)) && (us->uid != CTDLUID)) {
- if (getpwuid(us->uid) == NULL) {
- pptr = (struct PurgeList *)
- malloc(sizeof(struct PurgeList));
- pptr->next = UserPurgeList;
- strcpy(pptr->name, us->fullname);
- UserPurgeList = pptr;
- }
- }
- else {
- ++users_not_purged;
- }
-}
-
-
-
-/*
- * Back end function to check user accounts for expiration.
- */
-void do_user_purge(struct ctdluser *us, void *data) {
- int purge;
- time_t now;
- time_t purge_time;
- struct PurgeList *pptr;
-
- /* Set purge time; if the user overrides the system default, use it */
- if (us->USuserpurge > 0) {
- purge_time = ((time_t)us->USuserpurge) * 86400L;
- }
- else {
- purge_time = ((time_t)config.c_userpurge) * 86400L;
- }
-
- /* The default rule is to not purge. */
- purge = 0;
-
- /* If the user hasn't called in two months, his/her account
- * has expired, so purge the record.
- */
- now = time(NULL);
- if ((now - us->lastcall) > purge_time) purge = 1;
-
- /* If the user set his/her password to 'deleteme', he/she
- * wishes to be deleted, so purge the record.
- */
- if (!strcasecmp(us->password, "deleteme")) purge = 1;
-
- /* If the record is marked as permanent, don't purge it.
- */
- if (us->flags & US_PERM) purge = 0;
-
- /* If the user is an Aide, don't purge him/her/it.
- */
- if (us->axlevel == 6) purge = 0;
-
- /* If the access level is 0, the record should already have been
- * deleted, but maybe the user was logged in at the time or something.
- * Delete the record now.
- */
- if (us->axlevel == 0) purge = 1;
-
- /* 0 calls is impossible. If there are 0 calls, it must
- * be a corrupted record, so purge it.
- */
- if (us->timescalled == 0) purge = 1;
-
- /* User number 0, as well as any negative user number, is
- * also impossible.
- */
- if (us->usernum < 1L) purge = 1;
-
- if (purge == 1) {
- pptr = (struct PurgeList *) malloc(sizeof(struct PurgeList));
- pptr->next = UserPurgeList;
- strcpy(pptr->name, us->fullname);
- UserPurgeList = pptr;
- }
- else {
- ++users_not_purged;
- }
-
-}
-
-
-
-int PurgeUsers(void) {
- struct PurgeList *pptr;
- int num_users_purged = 0;
- char *transcript = NULL;
-
- lprintf(CTDL_DEBUG, "PurgeUsers() called\n");
- users_not_purged = 0;
-
- if (config.c_auth_mode == 1) {
- /* host auth mode */
- ForEachUser(do_uid_user_purge, NULL);
- }
- else {
- /* native auth mode */
- if (config.c_userpurge > 0) {
- ForEachUser(do_user_purge, NULL);
- }
- }
-
- transcript = malloc(SIZ);
-
- if (users_not_purged == 0) {
- strcpy(transcript, "The auto-purger was told to purge every user. It is\n"
- "refusing to do this because it usually indicates a problem\n"
- "such as an inability to communicate with a name service.\n"
- );
- while (UserPurgeList != NULL) {
- pptr = UserPurgeList->next;
- free(UserPurgeList);
- UserPurgeList = pptr;
- ++num_users_purged;
- }
- }
-
- else {
- strcpy(transcript, "The following users have been auto-purged:\n");
- while (UserPurgeList != NULL) {
- transcript=realloc(transcript, strlen(transcript)+SIZ);
- snprintf(&transcript[strlen(transcript)], SIZ, " %s\n",
- UserPurgeList->name);
- purge_user(UserPurgeList->name);
- pptr = UserPurgeList->next;
- free(UserPurgeList);
- UserPurgeList = pptr;
- ++num_users_purged;
- }
- }
-
- if (num_users_purged > 0) aide_message(transcript, "User Purge Message");
- free(transcript);
-
- lprintf(CTDL_DEBUG, "Purged %d users.\n", num_users_purged);
- return(num_users_purged);
-}
-
-
-/*
- * Purge visits
- *
- * This is a really cumbersome "garbage collection" function. We have to
- * delete visits which refer to rooms and/or users which no longer exist. In
- * order to prevent endless traversals of the room and user files, we first
- * build linked lists of rooms and users which _do_ exist on the system, then
- * traverse the visit file, checking each record against those two lists and
- * purging the ones that do not have a match on _both_ lists. (Remember, if
- * either the room or user being referred to is no longer on the system, the
- * record is completely useless.)
- */
-int PurgeVisits(void) {
- struct cdbdata *cdbvisit;
- struct visit vbuf;
- struct VPurgeList *VisitPurgeList = NULL;
- struct VPurgeList *vptr;
- int purged = 0;
- char IndexBuf[32];
- int IndexLen;
- struct ValidRoom *vrptr;
- struct ValidUser *vuptr;
- int RoomIsValid, UserIsValid;
-
- /* First, load up a table full of valid room/gen combinations */
- ForEachRoom(AddValidRoom, NULL);
-
- /* Then load up a table full of valid user numbers */
- ForEachUser(AddValidUser, NULL);
-
- /* Now traverse through the visits, purging irrelevant records... */
- cdb_rewind(CDB_VISIT);
- while(cdbvisit = cdb_next_item(CDB_VISIT), cdbvisit != NULL) {
- memset(&vbuf, 0, sizeof(struct visit));
- memcpy(&vbuf, cdbvisit->ptr,
- ( (cdbvisit->len > sizeof(struct visit)) ?
- sizeof(struct visit) : cdbvisit->len) );
- cdb_free(cdbvisit);
-
- RoomIsValid = 0;
- UserIsValid = 0;
-
- /* Check to see if the room exists */
- for (vrptr=ValidRoomList; vrptr!=NULL; vrptr=vrptr->next) {
- if ( (vrptr->vr_roomnum==vbuf.v_roomnum)
- && (vrptr->vr_roomgen==vbuf.v_roomgen))
- RoomIsValid = 1;
- }
-
- /* Check to see if the user exists */
- for (vuptr=ValidUserList; vuptr!=NULL; vuptr=vuptr->next) {
- if (vuptr->vu_usernum == vbuf.v_usernum)
- UserIsValid = 1;
- }
-
- /* Put the record on the purge list if it's dead */
- if ((RoomIsValid==0) || (UserIsValid==0)) {
- vptr = (struct VPurgeList *)
- malloc(sizeof(struct VPurgeList));
- vptr->next = VisitPurgeList;
- vptr->vp_roomnum = vbuf.v_roomnum;
- vptr->vp_roomgen = vbuf.v_roomgen;
- vptr->vp_usernum = vbuf.v_usernum;
- VisitPurgeList = vptr;
- }
-
- }
-
- /* Free the valid room/gen combination list */
- while (ValidRoomList != NULL) {
- vrptr = ValidRoomList->next;
- free(ValidRoomList);
- ValidRoomList = vrptr;
- }
-
- /* Free the valid user list */
- while (ValidUserList != NULL) {
- vuptr = ValidUserList->next;
- free(ValidUserList);
- ValidUserList = vuptr;
- }
-
- /* Now delete every visit on the purged list */
- while (VisitPurgeList != NULL) {
- IndexLen = GenerateRelationshipIndex(IndexBuf,
- VisitPurgeList->vp_roomnum,
- VisitPurgeList->vp_roomgen,
- VisitPurgeList->vp_usernum);
- cdb_delete(CDB_VISIT, IndexBuf, IndexLen);
- vptr = VisitPurgeList->next;
- free(VisitPurgeList);
- VisitPurgeList = vptr;
- ++purged;
- }
-
- return(purged);
-}
-
-/*
- * Purge the use table of old entries.
- *
- */
-int PurgeUseTable(void) {
- int purged = 0;
- struct cdbdata *cdbut;
- struct UseTable ut;
- struct UPurgeList *ul = NULL;
- struct UPurgeList *uptr;
-
- /* Phase 1: traverse through the table, discovering old records... */
- lprintf(CTDL_DEBUG, "Purge use table: phase 1\n");
- cdb_rewind(CDB_USETABLE);
- while(cdbut = cdb_next_item(CDB_USETABLE), cdbut != NULL) {
-
- memcpy(&ut, cdbut->ptr,
- ((cdbut->len > sizeof(struct UseTable)) ?
- sizeof(struct UseTable) : cdbut->len));
- cdb_free(cdbut);
-
- if ( (time(NULL) - ut.ut_timestamp) > USETABLE_RETAIN ) {
- uptr = (struct UPurgeList *) malloc(sizeof(struct UPurgeList));
- if (uptr != NULL) {
- uptr->next = ul;
- safestrncpy(uptr->up_key, ut.ut_msgid, sizeof uptr->up_key);
- ul = uptr;
- }
- ++purged;
- }
-
- }
-
- /* Phase 2: delete the records */
- lprintf(CTDL_DEBUG, "Purge use table: phase 2\n");
- while (ul != NULL) {
- cdb_delete(CDB_USETABLE, ul->up_key, strlen(ul->up_key));
- uptr = ul->next;
- free(ul);
- ul = uptr;
- }
-
- lprintf(CTDL_DEBUG, "Purge use table: finished (purged %d records)\n", purged);
- return(purged);
-}
-
-
-
-/*
- * Purge the EUID Index of old records.
- *
- */
-int PurgeEuidIndexTable(void) {
- int purged = 0;
- struct cdbdata *cdbei;
- struct EPurgeList *el = NULL;
- struct EPurgeList *eptr;
- long msgnum;
- struct CtdlMessage *msg = NULL;
-
- /* Phase 1: traverse through the table, discovering old records... */
- lprintf(CTDL_DEBUG, "Purge EUID index: phase 1\n");
- cdb_rewind(CDB_EUIDINDEX);
- while(cdbei = cdb_next_item(CDB_EUIDINDEX), cdbei != NULL) {
-
- memcpy(&msgnum, cdbei->ptr, sizeof(long));
-
- msg = CtdlFetchMessage(msgnum, 0);
- if (msg != NULL) {
- CtdlFreeMessage(msg); /* it still exists, so do nothing */
- }
- else {
- eptr = (struct EPurgeList *) malloc(sizeof(struct EPurgeList));
- if (eptr != NULL) {
- eptr->next = el;
- eptr->ep_keylen = cdbei->len - sizeof(long);
- eptr->ep_key = malloc(cdbei->len);
- memcpy(eptr->ep_key, &cdbei->ptr[sizeof(long)], eptr->ep_keylen);
- el = eptr;
- }
- ++purged;
- }
-
- cdb_free(cdbei);
-
- }
-
- /* Phase 2: delete the records */
- lprintf(CTDL_DEBUG, "Purge euid index: phase 2\n");
- while (el != NULL) {
- cdb_delete(CDB_EUIDINDEX, el->ep_key, el->ep_keylen);
- free(el->ep_key);
- eptr = el->next;
- free(el);
- el = eptr;
- }
-
- lprintf(CTDL_DEBUG, "Purge euid index: finished (purged %d records)\n", purged);
- return(purged);
-}
-
-
-void purge_databases(void) {
- int retval;
- static time_t last_purge = 0;
- time_t now;
- struct tm tm;
-
- /* Do the auto-purge if the current hour equals the purge hour,
- * but not if the operation has already been performed in the
- * last twelve hours. This is usually enough granularity.
- */
- now = time(NULL);
- localtime_r(&now, &tm);
- if (tm.tm_hour != config.c_purge_hour) return;
- if ((now - last_purge) < 43200) return;
-
- lprintf(CTDL_INFO, "Auto-purger: starting.\n");
-
- retval = PurgeUsers();
- lprintf(CTDL_NOTICE, "Purged %d users.\n", retval);
-
- PurgeMessages();
- lprintf(CTDL_NOTICE, "Expired %d messages.\n", messages_purged);
-
- retval = PurgeRooms();
- lprintf(CTDL_NOTICE, "Expired %d rooms.\n", retval);
-
- retval = PurgeVisits();
- lprintf(CTDL_NOTICE, "Purged %d visits.\n", retval);
-
- retval = PurgeUseTable();
- lprintf(CTDL_NOTICE, "Purged %d entries from the use table.\n", retval);
-
- retval = PurgeEuidIndexTable();
- lprintf(CTDL_NOTICE, "Purged %d entries from the EUID index.\n", retval);
-
- retval = TDAP_ProcessAdjRefCountQueue();
- lprintf(CTDL_NOTICE, "Processed %d message reference count adjustments.\n", retval);
-
- lprintf(CTDL_INFO, "Auto-purger: finished.\n");
-
- last_purge = now; /* So we don't do it again soon */
-}
-
-/*****************************************************************************/
-
-
-void do_fsck_msg(long msgnum, void *userdata) {
- struct ctdlroomref *ptr;
-
- ptr = (struct ctdlroomref *)malloc(sizeof(struct ctdlroomref));
- ptr->next = rr;
- ptr->msgnum = msgnum;
- rr = ptr;
-}
-
-void do_fsck_room(struct ctdlroom *qrbuf, void *data)
-{
- getroom(&CC->room, qrbuf->QRname);
- CtdlForEachMessage(MSGS_ALL, 0L, NULL, NULL, NULL, do_fsck_msg, NULL);
-}
-
-/*
- * Check message reference counts
- */
-void cmd_fsck(char *argbuf) {
- long msgnum;
- struct cdbdata *cdbmsg;
- struct MetaData smi;
- struct ctdlroomref *ptr;
- int realcount;
-
- if (CtdlAccessCheck(ac_aide)) return;
-
- /* Lame way of checking whether anyone else is doing this now */
- if (rr != NULL) {
- cprintf("%d Another FSCK is already running.\n", ERROR + RESOURCE_BUSY);
- return;
- }
-
- cprintf("%d Checking message reference counts\n", LISTING_FOLLOWS);
-
- cprintf("\nThis could take a while. Please be patient!\n\n");
- cprintf("Gathering pointers...\n");
- ForEachRoom(do_fsck_room, NULL);
-
- get_control();
- cprintf("Checking message base...\n");
- for (msgnum = 0L; msgnum <= CitControl.MMhighest; ++msgnum) {
-
- cdbmsg = cdb_fetch(CDB_MSGMAIN, &msgnum, sizeof(long));
- if (cdbmsg != NULL) {
- cdb_free(cdbmsg);
- cprintf("Message %7ld ", msgnum);
-
- GetMetaData(&smi, msgnum);
- cprintf("refcount=%-2d ", smi.meta_refcount);
-
- realcount = 0;
- for (ptr = rr; ptr != NULL; ptr = ptr->next) {
- if (ptr->msgnum == msgnum) ++realcount;
- }
- cprintf("realcount=%-2d\n", realcount);
-
- if ( (smi.meta_refcount != realcount)
- || (realcount == 0) ) {
- AdjRefCount(msgnum, (smi.meta_refcount - realcount));
- }
-
- }
-
- }
-
- cprintf("Freeing memory...\n");
- while (rr != NULL) {
- ptr = rr->next;
- free(rr);
- rr = ptr;
- }
-
- cprintf("Done!\n");
- cprintf("000\n");
-
-}
-
-
-
-
-/*****************************************************************************/
-
-CTDL_MODULE_INIT(expire)
-{
- CtdlRegisterSessionHook(purge_databases, EVT_TIMER);
- CtdlRegisterProtoHook(cmd_fsck, "FSCK", "Check message ref counts");
-
- /* return our Subversion id for the Log */
- return "$Id$";
-}
+++ /dev/null
-/*
- * $Id$
- *
- * This module handles fulltext indexing of the message base.
- *
- */
-
-
-#include "sysdep.h"
-#include <stdlib.h>
-#include <unistd.h>
-#include <stdio.h>
-#include <fcntl.h>
-#include <signal.h>
-#include <pwd.h>
-#include <errno.h>
-#include <sys/types.h>
-
-#if TIME_WITH_SYS_TIME
-# include <sys/time.h>
-# include <time.h>
-#else
-# if HAVE_SYS_TIME_H
-# include <sys/time.h>
-# else
-# include <time.h>
-# endif
-#endif
-
-#include <sys/wait.h>
-#include <string.h>
-#include <limits.h>
-#include "citadel.h"
-#include "server.h"
-#include "citserver.h"
-#include "support.h"
-#include "config.h"
-#include "database.h"
-#include "msgbase.h"
-#include "control.h"
-#include "room_ops.h"
-#include "tools.h"
-#include "serv_fulltext.h"
-#include "ft_wordbreaker.h"
-
-
-#include "ctdl_module.h"
-
-
-
-long ft_newhighest = 0L;
-long *ft_newmsgs = NULL;
-int ft_num_msgs = 0;
-int ft_num_alloc = 0;
-
-
-int ftc_num_msgs[65536];
-long *ftc_msgs[65536];
-
-
-/*
- * Compare function
- */
-int longcmp(const void *rec1, const void *rec2) {
- long i1, i2;
-
- i1 = *(const long *)rec1;
- i2 = *(const long *)rec2;
-
- if (i1 > i2) return(1);
- if (i1 < i2) return(-1);
- return(0);
-}
-
-/*
- * Flush our index cache out to disk.
- */
-void ft_flush_cache(void) {
- int i;
- time_t last_update = 0;
-
- for (i=0; i<65536; ++i) {
- if ((time(NULL) - last_update) >= 10) {
- lprintf(CTDL_INFO,
- "Flushing index cache to disk (%d%% complete)\n",
- (i * 100 / 65536)
- );
- last_update = time(NULL);
- }
- if (ftc_msgs[i] != NULL) {
- cdb_store(CDB_FULLTEXT, &i, sizeof(int), ftc_msgs[i],
- (ftc_num_msgs[i] * sizeof(long)));
- ftc_num_msgs[i] = 0;
- free(ftc_msgs[i]);
- ftc_msgs[i] = NULL;
- }
- }
- lprintf(CTDL_INFO, "Flushed index cache to disk (100%% complete)\n");
-}
-
-
-/*
- * Index or de-index a message. (op == 1 to index, 0 to de-index)
- */
-void ft_index_message(long msgnum, int op) {
- int num_tokens = 0;
- int *tokens = NULL;
- int i, j;
- struct cdbdata *cdb_bucket;
- char *msgtext;
- int tok;
-
- lprintf(CTDL_DEBUG, "ft_index_message() %s msg %ld\n",
- (op ? "adding" : "removing") , msgnum
- );
-
- /* Output the message as text before indexing it, so we don't end up
- * indexing a bunch of encoded base64, etc.
- */
- CC->redirect_buffer = malloc(SIZ);
- CC->redirect_len = 0;
- CC->redirect_alloc = SIZ;
- CtdlOutputMsg(msgnum, MT_CITADEL, HEADERS_ALL, 0, 1, NULL);
- msgtext = CC->redirect_buffer;
- CC->redirect_buffer = NULL;
- CC->redirect_len = 0;
- CC->redirect_alloc = 0;
- lprintf(CTDL_DEBUG, "Wordbreaking message %ld...\n", msgnum);
- wordbreaker(msgtext, &num_tokens, &tokens);
- free(msgtext);
-
- lprintf(CTDL_DEBUG, "Indexing message %ld [%d tokens]\n", msgnum, num_tokens);
- if (num_tokens > 0) {
- for (i=0; i<num_tokens; ++i) {
-
- /* Add the message to the relevant token bucket */
-
- /* search for tokens[i] */
- tok = tokens[i];
-
- if ( (tok >= 0) && (tok <= 65535) ) {
- /* fetch the bucket, Liza */
- if (ftc_msgs[tok] == NULL) {
- cdb_bucket = cdb_fetch(CDB_FULLTEXT, &tok, sizeof(int));
- if (cdb_bucket != NULL) {
- ftc_num_msgs[tok] = cdb_bucket->len / sizeof(long);
- ftc_msgs[tok] = (long *)cdb_bucket->ptr;
- cdb_bucket->ptr = NULL;
- cdb_free(cdb_bucket);
- }
- else {
- ftc_num_msgs[tok] = 0;
- ftc_msgs[tok] = malloc(sizeof(long));
- }
- }
-
-
- if (op == 1) { /* add to index */
- ++ftc_num_msgs[tok];
- ftc_msgs[tok] = realloc(ftc_msgs[tok],
- ftc_num_msgs[tok]*sizeof(long));
- ftc_msgs[tok][ftc_num_msgs[tok] - 1] = msgnum;
- }
-
- if (op == 0) { /* remove from index */
- if (ftc_num_msgs[tok] >= 1) {
- for (j=0; j<ftc_num_msgs[tok]; ++j) {
- if (ftc_msgs[tok][j] == msgnum) {
- memmove(&ftc_msgs[tok][j], &ftc_msgs[tok][j+1], ((ftc_num_msgs[tok] - j - 1)*sizeof(long)));
- --ftc_num_msgs[tok];
- --j;
- }
- }
- }
- }
- }
- else {
- lprintf(CTDL_ALERT, "Invalid token %d !!\n", tok);
- }
- }
-
- free(tokens);
- }
-}
-
-
-
-/*
- * Add a message to the list of those to be indexed.
- */
-void ft_index_msg(long msgnum, void *userdata) {
-
- if ((msgnum > CitControl.MMfulltext) && (msgnum <= ft_newhighest)) {
- ++ft_num_msgs;
- if (ft_num_msgs > ft_num_alloc) {
- ft_num_alloc += 1024;
- ft_newmsgs = realloc(ft_newmsgs,
- (ft_num_alloc * sizeof(long)));
- }
- ft_newmsgs[ft_num_msgs - 1] = msgnum;
- }
-
-}
-
-/*
- * Scan a room for messages to index.
- */
-void ft_index_room(struct ctdlroom *qrbuf, void *data)
-{
- getroom(&CC->room, qrbuf->QRname);
- CtdlForEachMessage(MSGS_ALL, 0L, NULL, NULL, NULL, ft_index_msg, NULL);
-}
-
-
-/*
- * Begin the fulltext indexing process. (Called as an EVT_TIMER event)
- */
-void do_fulltext_indexing(void) {
- int i;
- static time_t last_index = 0L;
- static time_t last_progress = 0L;
-
- /*
- * Don't do this if the site doesn't have it enabled.
- */
- if (!config.c_enable_fulltext) {
- return;
- }
-
- /*
- * Make sure we don't run the indexer too frequently.
- * FIXME move the setting into config
- */
- if ( (time(NULL) - last_index) < 300L) {
- return;
- }
-
- /*
- * Check to see whether the fulltext index is up to date; if there
- * are no messages to index, don't waste any more time trying.
- */
- if (CitControl.MMfulltext >= CitControl.MMhighest) {
- return; /* nothing to do! */
- }
-
- lprintf(CTDL_DEBUG, "do_fulltext_indexing() started\n");
-
- /*
- * If we've switched wordbreaker modules, burn the index and start
- * over.
- */
- begin_critical_section(S_CONTROL);
- lprintf(CTDL_DEBUG, "wb ver on disk = %d, code ver = %d\n",
- CitControl.fulltext_wordbreaker, FT_WORDBREAKER_ID);
- if (CitControl.fulltext_wordbreaker != FT_WORDBREAKER_ID) {
- lprintf(CTDL_INFO, "(re)initializing full text index\n");
- cdb_trunc(CDB_FULLTEXT);
- CitControl.MMfulltext = 0L;
- put_control();
- }
- end_critical_section(S_CONTROL);
-
- /*
- * Now go through each room and find messages to index.
- */
- ft_newhighest = CitControl.MMhighest;
- ForEachRoom(ft_index_room, NULL); /* load all msg pointers */
-
- if (ft_num_msgs > 0) {
- qsort(ft_newmsgs, ft_num_msgs, sizeof(long), longcmp);
- for (i=0; i<(ft_num_msgs-1); ++i) { /* purge dups */
- if (ft_newmsgs[i] == ft_newmsgs[i+1]) {
- memmove(&ft_newmsgs[i], &ft_newmsgs[i+1],
- ((ft_num_msgs - i - 1)*sizeof(long)));
- --ft_num_msgs;
- --i;
- }
- }
-
- /* Here it is ... do each message! */
- for (i=0; i<ft_num_msgs; ++i) {
- if (time(NULL) != last_progress) {
- lprintf(CTDL_DEBUG,
- "Indexed %d of %d messages (%d%%)\n",
- i, ft_num_msgs,
- ((i*100) / ft_num_msgs)
- );
- last_progress = time(NULL);
- }
- ft_index_message(ft_newmsgs[i], 1);
-
- /* Check to see if we need to quit early */
- if (time_to_die) {
- lprintf(CTDL_DEBUG, "Indexer quitting early\n");
- ft_newhighest = ft_newmsgs[i];
- break;
- }
-
- /* Check to see if we have to maybe flush to disk */
- if (i >= FT_MAX_CACHE) {
- lprintf(CTDL_DEBUG, "Time to flush.\n");
- ft_newhighest = ft_newmsgs[i];
- break;
- }
-
- }
-
- free(ft_newmsgs);
- ft_num_msgs = 0;
- ft_num_alloc = 0;
- ft_newmsgs = NULL;
- }
-
- /* Save our place so we don't have to do this again */
- ft_flush_cache();
- begin_critical_section(S_CONTROL);
- CitControl.MMfulltext = ft_newhighest;
- CitControl.fulltext_wordbreaker = FT_WORDBREAKER_ID;
- put_control();
- end_critical_section(S_CONTROL);
- last_index = time(NULL);
-
- lprintf(CTDL_DEBUG, "do_fulltext_indexing() finished\n");
- return;
-}
-
-/*
- * Main loop for the indexer thread.
- */
-void *indexer_thread(void *arg) {
- struct CitContext indexerCC;
-
- lprintf(CTDL_DEBUG, "indexer_thread() initializing\n");
-
- memset(&indexerCC, 0, sizeof(struct CitContext));
- indexerCC.internal_pgm = 1;
- indexerCC.cs_pid = 0;
- pthread_setspecific(MyConKey, (void *)&indexerCC );
-
- cdb_allocate_tsd();
-
- while (!time_to_die) {
- do_fulltext_indexing();
- sleep(1);
- }
-
- lprintf(CTDL_DEBUG, "indexer_thread() exiting\n");
- pthread_exit(NULL);
-}
-
-
-
-/*
- * API call to perform searches.
- * (This one does the "all of these words" search.)
- * Caller is responsible for freeing the message list.
- */
-void ft_search(int *fts_num_msgs, long **fts_msgs, char *search_string) {
- int num_tokens = 0;
- int *tokens = NULL;
- int i, j;
- struct cdbdata *cdb_bucket;
- int num_all_msgs = 0;
- long *all_msgs = NULL;
- int num_ret_msgs = 0;
- int num_ret_alloc = 0;
- long *ret_msgs = NULL;
- int tok;
-
- wordbreaker(search_string, &num_tokens, &tokens);
- if (num_tokens > 0) {
- for (i=0; i<num_tokens; ++i) {
-
- /* search for tokens[i] */
- tok = tokens[i];
-
- /* fetch the bucket, Liza */
- if (ftc_msgs[tok] == NULL) {
- cdb_bucket = cdb_fetch(CDB_FULLTEXT, &tok, sizeof(int));
- if (cdb_bucket != NULL) {
- ftc_num_msgs[tok] = cdb_bucket->len / sizeof(long);
- ftc_msgs[tok] = (long *)cdb_bucket->ptr;
- cdb_bucket->ptr = NULL;
- cdb_free(cdb_bucket);
- }
- else {
- ftc_num_msgs[tok] = 0;
- ftc_msgs[tok] = malloc(sizeof(long));
- }
- }
-
- num_all_msgs += ftc_num_msgs[tok];
- if (num_all_msgs > 0) {
- all_msgs = realloc(all_msgs, num_all_msgs*sizeof(long) );
- memcpy(&all_msgs[num_all_msgs-ftc_num_msgs[tok]],
- ftc_msgs[tok], ftc_num_msgs[tok]*sizeof(long) );
- }
-
- }
- free(tokens);
- qsort(all_msgs, num_all_msgs, sizeof(long), longcmp);
-
- /*
- * At this point, if a message appears num_tokens times in the
- * list, then it contains all of the search tokens.
- */
- if (num_all_msgs >= num_tokens)
- for (j=0; j<(num_all_msgs-num_tokens+1); ++j) {
- if (all_msgs[j] == all_msgs[j+num_tokens-1]) {
-
- ++num_ret_msgs;
- if (num_ret_msgs > num_ret_alloc) {
- num_ret_alloc += 64;
- ret_msgs = realloc(ret_msgs,
- (num_ret_alloc*sizeof(long)) );
- }
- ret_msgs[num_ret_msgs - 1] = all_msgs[j];
-
- }
- }
-
- free(all_msgs);
- }
-
- *fts_num_msgs = num_ret_msgs;
- *fts_msgs = ret_msgs;
-}
-
-
-/*
- * This search command is for diagnostic purposes and may be removed or replaced.
- */
-void cmd_srch(char *argbuf) {
- int num_msgs = 0;
- long *msgs = NULL;
- int i;
- char search_string[256];
-
- if (CtdlAccessCheck(ac_logged_in)) return;
-
- if (!config.c_enable_fulltext) {
- cprintf("%d Full text index is not enabled on this server.\n",
- ERROR + CMD_NOT_SUPPORTED);
- return;
- }
-
- extract_token(search_string, argbuf, 0, '|', sizeof search_string);
- ft_search(&num_msgs, &msgs, search_string);
-
- cprintf("%d %d msgs match all search words:\n",
- LISTING_FOLLOWS, num_msgs);
- if (num_msgs > 0) {
- for (i=0; i<num_msgs; ++i) {
- cprintf("%ld\n", msgs[i]);
- }
- }
- if (msgs != NULL) free(msgs);
- cprintf("000\n");
-}
-
-/*
- * Zero out our index cache.
- */
-void initialize_ft_cache(void) {
- memset(ftc_num_msgs, 0, (65536 * sizeof(int)));
- memset(ftc_msgs, 0, (65536 * sizeof(long *)));
-}
-
-
-/*****************************************************************************/
-
-CTDL_MODULE_INIT(fulltext)
-{
- initialize_ft_cache();
- CtdlRegisterProtoHook(cmd_srch, "SRCH", "Full text search");
-
- /* return our Subversion id for the Log */
- return "$Id$";
-}
+++ /dev/null
-/*
- * This module implements a notifier for Funambol push email.
- * Based on bits of serv_spam, serv_smtp
- */
-
-#define FUNAMBOL_WS "/funambol/services/admin"
-#include "sysdep.h"
-#include <stdlib.h>
-#include <unistd.h>
-#include <stdio.h>
-#include <fcntl.h>
-#include <signal.h>
-#include <pwd.h>
-#include <errno.h>
-#include <sys/types.h>
-
-#if TIME_WITH_SYS_TIME
-# include <sys/time.h>
-# include <time.h>
-#else
-# if HAVE_SYS_TIME_H
-# include <sys/time.h>
-# else
-# include <time.h>
-# endif
-#endif
-
-#include <sys/wait.h>
-#include <string.h>
-#include <limits.h>
-#include <sys/socket.h>
-#include "citadel.h"
-#include "server.h"
-#include "citserver.h"
-#include "support.h"
-#include "config.h"
-#include "control.h"
-#include "room_ops.h"
-#include "user_ops.h"
-#include "policy.h"
-#include "database.h"
-#include "msgbase.h"
-#include "tools.h"
-#include "internet_addressing.h"
-#include "domain.h"
-#include "clientsocket.h"
-#include "serv_funambol.h"
-
-
-
-#include "ctdl_module.h"
-
-
-/*
- * Create the notify message queue
- */
-void create_notify_queue(void) {
- struct ctdlroom qrbuf;
-
- create_room(FNBL_QUEUE_ROOM, 3, "", 0, 1, 0, VIEW_MAILBOX);
-
- /*
- * Make sure it's set to be a "system room" so it doesn't show up
- * in the <K>nown rooms list for Aides.
- */
- if (lgetroom(&qrbuf, FNBL_QUEUE_ROOM) == 0) {
- qrbuf.QRflags2 |= QR2_SYSTEM;
- lputroom(&qrbuf);
- }
-}
-void do_notify_queue(void) {
- static int doing_queue = 0;
-
- /*
- * This is a simple concurrency check to make sure only one queue run
- * is done at a time. We could do this with a mutex, but since we
- * don't really require extremely fine granularity here, we'll do it
- * with a static variable instead.
- */
- if (doing_queue) return;
- doing_queue = 1;
-
- /*
- * Go ahead and run the queue
- */
- lprintf(CTDL_INFO, "serv_funambol: processing notify queue\n");
-
- if (getroom(&CC->room, FNBL_QUEUE_ROOM) != 0) {
- lprintf(CTDL_ERR, "Cannot find room <%s>\n", FNBL_QUEUE_ROOM);
- return;
- }
- CtdlForEachMessage(MSGS_ALL, 0L, NULL,
- SPOOLMIME, NULL, notify_funambol, NULL);
-
- lprintf(CTDL_INFO, "serv_funambol: queue run completed\n");
- doing_queue = 0;
-}
-
-/*
- * Connect to the Funambol server and scan a message.
- */
-void notify_funambol(long msgnum, void *userdata) {
- struct CtdlMessage *msg;
- int sock = (-1);
- char buf[SIZ];
- char SOAPHeader[SIZ];
- char SOAPData[SIZ];
- char port[SIZ];
- /* W means 'Wireless'... */
- msg = CtdlFetchMessage(msgnum, 1);
- if ( msg->cm_fields['W'] == NULL) {
- goto nuke;
- }
- /* Are we allowed to push? */
- if ( strlen(config.c_funambol_host) == 0) {
- goto nuke;
- } else {
- lprintf(CTDL_INFO, "Push enabled\n");
- }
-
- sprintf(port, "%d", config.c_funambol_port);
- lprintf(CTDL_INFO, "Connecting to Funambol at <%s>\n", config.c_funambol_host);
- sock = sock_connect(config.c_funambol_host, port, "tcp");
- if (sock >= 0) lprintf(CTDL_DEBUG, "Connected!\n");
-
- if (sock < 0) {
- /* If the service isn't running, pass for now */
- return;
- }
-
- /* Build a SOAP message, delicately, by hand */
- sprintf(SOAPData, "<?xml version=\"1.0\" encoding=\"UTF-8\"?><soapenv:Envelope xmlns:soapenv=\"http://schemas.xmlsoap.org/soap/envelope/\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\">");
- strcat(SOAPData, "<soapenv:Body><sendNotificationMessages soapenv:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">");
- strcat(SOAPData, "<arg0 xsi:type=\"soapenc:string\" xmlns:soapenc=\"http://schemas.xmlsoap.org/soap/encoding/\">");
- strcat(SOAPData, msg->cm_fields['W']);
- strcat(SOAPData, "</arg0>");
- strcat(SOAPData, "<arg1 xsi:type=\"soapenc:string\" xmlns:soapenc=\"http://schemas.xmlsoap.org/soap/encoding/\"><?xml version="1.0" encoding="UTF-8"?>\r\n");
- strcat(SOAPData, "<java version="1.5.0_10" class="java.beans.XMLDecoder"> \r\n");
- strcat(SOAPData, " <array class="com.funambol.framework.core.Alert" length="1">\r\n");
- strcat(SOAPData, " <void index="0">\r\n");
- strcat(SOAPData, " <object class="com.funambol.framework.core.Alert">\r\n");
- strcat(SOAPData, " <void property="cmdID">\r\n");
- strcat(SOAPData, " <object class="com.funambol.framework.core.CmdID"/>\r\n");
- strcat(SOAPData, " </void>");
- strcat(SOAPData, " <void property="data">\r\n");
- strcat(SOAPData, " <int>210</int>\r\n");
- strcat(SOAPData, " </void>\r\n");
- strcat(SOAPData, " <void property="items">\r\n");
- strcat(SOAPData, " <void method="add">\r\n");
- strcat(SOAPData, " <object class="com.funambol.framework.core.Item">\r\n");
- strcat(SOAPData, " <void property="meta">\r\n");
- strcat(SOAPData, " <object class="com.funambol.framework.core.Meta">\r\n");
- strcat(SOAPData, " <void property="metInf">\r\n");
- strcat(SOAPData, " <void property="type">\r\n");
- strcat(SOAPData, " <string>application/vnd.omads-email+xml</string>\r\n");
- strcat(SOAPData, " </void>\r\n");
- strcat(SOAPData, " </void>\r\n");
- strcat(SOAPData, " </object>\r\n");
- strcat(SOAPData, " </void>\r\n");
- strcat(SOAPData, " <void property="target">\r\n");
- strcat(SOAPData, " <object class="com.funambol.framework.core.Target">\r\n");
- strcat(SOAPData, " <void property="locURI">\r\n");
- strcat(SOAPData, " <string>");
- strcat(SOAPData, config.c_funambol_source);
- strcat(SOAPData, "</string>\r\n");
- strcat(SOAPData, " </void>\r\n");
- strcat(SOAPData, " </object>\r\n");
- strcat(SOAPData, " </void>\r\n");
- strcat(SOAPData, " </object>\r\n");
- strcat(SOAPData, " </void>\r\n");
- strcat(SOAPData, " </void>\r\n");
- strcat(SOAPData, " </object>\r\n");
- strcat(SOAPData, " </void>\r\n");
- strcat(SOAPData, " </array>\r\n");
- strcat(SOAPData, "</java>");
- strcat(SOAPData,"</arg1><arg2 href=\"#id0\"/></sendNotificationMessages><multiRef id=\"id0\" soapenc:root=\"0\"\r\n");
- strcat(SOAPData,"soapenv:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\" xsi:type=\"soapenc:int\" xmlns:soapenc=\"http://schemas.xmlsoap.org/soap/encoding/\">1</multiRef></soapenv:Body></soapenv:Envelope>");
-
- /* Command */
- lprintf(CTDL_DEBUG, "Transmitting command\n");
- sprintf(SOAPHeader, "POST %s HTTP/1.0\r\nContent-type: text/xml; charset=utf-8\r\n",
- FUNAMBOL_WS);
- strcat(SOAPHeader,"Accept: application/soap+xml, application/dime, multipart/related, text/*\r\n");
- sprintf(buf, "User-Agent: %s/%d\r\nHost: %s:%d\r\nCache-control: no-cache\r\n",
- "Citadel",
- REV_LEVEL,
- config.c_funambol_host,
- config.c_funambol_port
- );
- strcat(SOAPHeader,buf);
- strcat(SOAPHeader,"Pragma: no-cache\r\nSOAPAction: \"\"\r\n");
- sprintf(buf, "Content-Length: %d\r\n",
- strlen(SOAPData));
- strcat(SOAPHeader, buf);
- sprintf(buf, "Authorization: Basic %s\r\n\r\n",
- config.c_funambol_auth);
- strcat(SOAPHeader, buf);
-
- sock_write(sock, SOAPHeader, strlen(SOAPHeader));
- sock_write(sock, SOAPData, strlen(SOAPData));
- sock_shutdown(sock, SHUT_WR);
-
- /* Response */
- lprintf(CTDL_DEBUG, "Awaiting response\n");
- if (sock_gets(sock, buf) < 0) {
- goto bail;
- }
- lprintf(CTDL_DEBUG, "<%s\n", buf);
- if (strncasecmp(buf, "HTTP/1.1 200 OK", strlen("HTTP/1.1 200 OK"))) {
-
- goto bail;
- }
- lprintf(CTDL_DEBUG, "Funambol notified\n");
- /* We should allow retries here but for now purge after one go */
- bail:
- close(sock);
- nuke:
- CtdlFreeMessage(msg);
- long todelete[1];
- todelete[0] = msgnum;
- CtdlDeleteMessages(FNBL_QUEUE_ROOM, todelete, 1, "");
-}
-
-
-
-CTDL_MODULE_INIT(funambol)
-{
- create_notify_queue();
- CtdlRegisterSessionHook(do_notify_queue, EVT_TIMER);
-
- /* return our Subversion id for the Log */
- return "$Id$";
-}
+++ /dev/null
-void notify_funambol(long msgnum, void *userdata);
+++ /dev/null
-/*
- * $Id$
- *
- * This module handles the loading/saving and maintenance of the
- * system's Internet configuration. It's not an optional component; I
- * wrote it as a module merely to keep things as clean and loosely coupled
- * as possible.
- *
- */
-
-#include "sysdep.h"
-#include <stdlib.h>
-#include <unistd.h>
-#include <stdio.h>
-#include <fcntl.h>
-#include <signal.h>
-#include <pwd.h>
-#include <errno.h>
-#include <sys/types.h>
-
-#if TIME_WITH_SYS_TIME
-# include <sys/time.h>
-# include <time.h>
-#else
-# if HAVE_SYS_TIME_H
-# include <sys/time.h>
-# else
-# include <time.h>
-# endif
-#endif
-
-#include <sys/wait.h>
-#include <string.h>
-#include <limits.h>
-#include "citadel.h"
-#include "server.h"
-#include "citserver.h"
-#include "support.h"
-#include "config.h"
-#include "room_ops.h"
-#include "user_ops.h"
-#include "policy.h"
-#include "database.h"
-#include "msgbase.h"
-#include "tools.h"
-#include "internet_addressing.h"
-#include "genstamp.h"
-#include "domain.h"
-
-
-#include "ctdl_module.h"
-
-
-void inetcfg_setTo(struct CtdlMessage *msg) {
- char *conf;
- char buf[SIZ];
-
- if (msg->cm_fields['M']==NULL) return;
- conf = strdup(msg->cm_fields['M']);
-
- if (conf != NULL) {
- do {
- extract_token(buf, conf, 0, '\n', sizeof buf);
- strcpy(conf, &conf[strlen(buf)+1]);
- } while ( (strlen(conf)>0) && (strlen(buf)>0) );
-
- if (inetcfg != NULL) free(inetcfg);
- inetcfg = conf;
- }
-}
-
-
-#ifdef ___NOT_CURRENTLY_IN_USE___
-void spamstrings_setTo(struct CtdlMessage *msg) {
- char buf[SIZ];
- char *conf;
- struct spamstrings_t *sptr;
- int i, n;
-
- /* Clear out the existing list */
- while (spamstrings != NULL) {
- sptr = spamstrings;
- spamstrings = spamstrings->next;
- free(sptr->string);
- free(sptr);
- }
-
- /* Read in the new list */
- if (msg->cm_fields['M']==NULL) return;
- conf = strdup(msg->cm_fields['M']);
- if (conf == NULL) return;
-
- n = num_tokens(conf, '\n');
- for (i=0; i<n; ++i) {
- extract_token(buf, conf, i, '\n', sizeof buf);
- sptr = malloc(sizeof(struct spamstrings_t));
- sptr->string = strdup(buf);
- sptr->next = spamstrings;
- spamstrings = sptr;
- }
-
-}
-#endif
-
-
-/*
- * This handler detects changes being made to the system's Internet
- * configuration.
- */
-int inetcfg_aftersave(struct CtdlMessage *msg) {
- char *ptr;
- int linelen;
-
- /* If this isn't the configuration room, or if this isn't a MIME
- * message, don't bother.
- */
- if (strcasecmp(msg->cm_fields['O'], SYSCONFIGROOM)) return(0);
- if (msg->cm_format_type != 4) return(0);
-
- ptr = msg->cm_fields['M'];
- while (ptr != NULL) {
-
- linelen = strcspn(ptr, "\n");
- if (linelen == 0) return(0); /* end of headers */
-
- if (!strncasecmp(ptr, "Content-type: ", 14)) {
- if (!strncasecmp(&ptr[14], INTERNETCFG,
- strlen(INTERNETCFG))) {
- inetcfg_setTo(msg); /* changing configs */
- }
- }
-
- ptr = strchr((char *)ptr, '\n');
- if (ptr != NULL) ++ptr;
- }
-
- return(0);
-}
-
-
-void inetcfg_init_backend(long msgnum, void *userdata) {
- struct CtdlMessage *msg;
-
- msg = CtdlFetchMessage(msgnum, 1);
- if (msg != NULL) {
- inetcfg_setTo(msg);
- CtdlFreeMessage(msg);
- }
-}
-
-
-#ifdef ___NOT_CURRENTLY_IN_USE___
-void spamstrings_init_backend(long msgnum, void *userdata) {
- struct CtdlMessage *msg;
-
- msg = CtdlFetchMessage(msgnum, 1);
- if (msg != NULL) {
- spamstrings_setTo(msg);
- CtdlFreeMessage(msg);
- }
-}
-#endif
-
-
-void inetcfg_init(void) {
- if (getroom(&CC->room, SYSCONFIGROOM) != 0) return;
- CtdlForEachMessage(MSGS_LAST, 1, NULL, INTERNETCFG, NULL,
- inetcfg_init_backend, NULL);
-}
-
-
-
-
-/*****************************************************************************/
-/* MODULE INITIALIZATION STUFF */
-/*****************************************************************************/
-
-
-CTDL_MODULE_INIT(inetcfg)
-{
- CtdlRegisterMessageHook(inetcfg_aftersave, EVT_AFTERSAVE);
- inetcfg_init();
-
- /* return our Subversion id for the Log */
- return "$Id$";
-}
-
+++ /dev/null
-/*
- * $Id$
- *
- * A module which implements the LDAP connector for Citadel.
- *
- */
-
-#include "sysdep.h"
-#include <stdlib.h>
-#include <unistd.h>
-#include <stdio.h>
-#include <fcntl.h>
-#include <signal.h>
-#include <pwd.h>
-#include <errno.h>
-#include <sys/types.h>
-
-#if TIME_WITH_SYS_TIME
-# include <sys/time.h>
-# include <time.h>
-#else
-# if HAVE_SYS_TIME_H
-# include <sys/time.h>
-# else
-# include <time.h>
-# endif
-#endif
-
-#include <sys/wait.h>
-#include <string.h>
-#include <limits.h>
-#include "citadel.h"
-#include "server.h"
-#include "citserver.h"
-#include "support.h"
-#include "config.h"
-#include "room_ops.h"
-#include "policy.h"
-#include "database.h"
-#include "msgbase.h"
-#include "serv_ldap.h"
-#include "vcard.h"
-#include "tools.h"
-
-
-#include "ctdl_module.h"
-
-
-
-#ifdef HAVE_LDAP
-
-#include <ldap.h>
-
-LDAP *dirserver = NULL;
-
-/*
- * LDAP connector cleanup function
- */
-void serv_ldap_cleanup(void)
-{
- if (!dirserver) return;
-
- lprintf(CTDL_INFO, "Unbinding from directory server\n");
- ldap_unbind(dirserver);
- dirserver = NULL;
-}
-
-
-
-/*
- * Create the root node. If it's already there, so what?
- */
-void CtdlCreateLdapRoot(void) {
- char *dc_values[2];
- char *objectClass_values[3];
- LDAPMod dc, objectClass;
- LDAPMod *mods[3];
- char topdc[SIZ];
- int i;
-
- /* We just want the top-level dc, not the whole hierarchy */
- strcpy(topdc, config.c_ldap_base_dn);
- for (i=0; i<strlen(topdc); ++i) {
- if (topdc[i] == ',') topdc[i] = 0;
- }
- for (i=0; i<strlen(topdc); ++i) {
- if (topdc[i] == '=') strcpy(topdc, &topdc[i+1]);
- }
-
- /* Set up the transaction */
- dc.mod_op = LDAP_MOD_ADD;
- dc.mod_type = "dc";
- dc_values[0] = topdc;
- dc_values[1] = NULL;
- dc.mod_values = dc_values;
- objectClass.mod_op = LDAP_MOD_ADD;
- objectClass.mod_type = "objectClass";
- objectClass_values[0] = "top";
- objectClass_values[1] = "domain";
- objectClass_values[2] = NULL;
- objectClass.mod_values = objectClass_values;
- mods[0] = &dc;
- mods[1] = &objectClass;
- mods[2] = NULL;
-
- /* Perform the transaction */
- lprintf(CTDL_DEBUG, "Setting up Base DN node...\n");
- begin_critical_section(S_LDAP);
- i = ldap_add_s(dirserver, config.c_ldap_base_dn, mods);
- end_critical_section(S_LDAP);
-
- if (i == LDAP_ALREADY_EXISTS) {
- lprintf(CTDL_INFO, "Base DN is already present in the directory; no need to add it again.\n");
- }
- else if (i != LDAP_SUCCESS) {
- lprintf(CTDL_CRIT, "ldap_add_s() failed: %s (%d)\n",
- ldap_err2string(i), i);
- }
-}
-
-
-/*
- * Create an OU node representing a Citadel host.
- */
-void CtdlCreateHostOU(char *host) {
- char *dc_values[2];
- char *objectClass_values[3];
- LDAPMod dc, objectClass;
- LDAPMod *mods[3];
- int i;
- char dn[SIZ];
-
- /* The DN is this OU plus the base. */
- snprintf(dn, sizeof dn, "ou=%s,%s", host, config.c_ldap_base_dn);
-
- /* Set up the transaction */
- dc.mod_op = LDAP_MOD_ADD;
- dc.mod_type = "ou";
- dc_values[0] = host;
- dc_values[1] = NULL;
- dc.mod_values = dc_values;
- objectClass.mod_op = LDAP_MOD_ADD;
- objectClass.mod_type = "objectClass";
- objectClass_values[0] = "top";
- objectClass_values[1] = "organizationalUnit";
- objectClass_values[2] = NULL;
- objectClass.mod_values = objectClass_values;
- mods[0] = &dc;
- mods[1] = &objectClass;
- mods[2] = NULL;
-
- /* Perform the transaction */
- lprintf(CTDL_DEBUG, "Setting up Host OU node...\n");
- begin_critical_section(S_LDAP);
- i = ldap_add_s(dirserver, dn, mods);
- end_critical_section(S_LDAP);
-
- if (i == LDAP_ALREADY_EXISTS) {
- lprintf(CTDL_INFO, "Host OU is already present in the directory; no need to add it again.\n");
- }
- else if (i != LDAP_SUCCESS) {
- lprintf(CTDL_CRIT, "ldap_add_s() failed: %s (%d)\n",
- ldap_err2string(i), i);
- }
-}
-
-
-
-
-
-
-
-
-void CtdlConnectToLdap(void) {
- int i;
- int ldap_version = 3;
-
- lprintf(CTDL_INFO, "Connecting to LDAP server %s:%d...\n",
- config.c_ldap_host, config.c_ldap_port);
-
- dirserver = ldap_init(config.c_ldap_host, config.c_ldap_port);
- if (dirserver == NULL) {
- lprintf(CTDL_CRIT, "Could not connect to %s:%d : %s\n",
- config.c_ldap_host,
- config.c_ldap_port,
- strerror(errno));
- return;
- }
-
- ldap_set_option(dirserver, LDAP_OPT_PROTOCOL_VERSION, &ldap_version);
-
- lprintf(CTDL_INFO, "Binding to %s\n", config.c_ldap_bind_dn);
-
- i = ldap_simple_bind_s(dirserver,
- config.c_ldap_bind_dn,
- config.c_ldap_bind_pw
- );
- if (i != LDAP_SUCCESS) {
- lprintf(CTDL_CRIT, "Cannot bind: %s (%d)\n", ldap_err2string(i), i);
- dirserver = NULL; /* FIXME disconnect from ldap */
- return;
- }
-
- CtdlCreateLdapRoot();
-}
-
-
-/*
- * vCard-to-LDAP conversions.
- *
- * If 'op' is set to V2L_WRITE, then write
- * (add, or change if already exists) a directory entry to the
- * LDAP server, based on the information supplied in a vCard.
- *
- * If 'op' is set to V2L_DELETE, then delete the entry from LDAP.
- */
-void ctdl_vcard_to_ldap(struct CtdlMessage *msg, int op) {
- struct vCard *v = NULL;
- int i, j;
- char this_dn[SIZ];
- LDAPMod **attrs = NULL;
- int num_attrs = 0;
- int num_emails = 0;
- int alias_attr = (-1);
- int num_phones = 0;
- int phone_attr = (-1);
- int have_addr = 0;
- int have_cn = 0;
-
- char givenname[128];
- char sn[128];
- char uid[256];
- char street[256];
- char city[128];
- char state[3];
- char zipcode[10];
- char calFBURL[256];
-
- if (dirserver == NULL) return;
- if (msg == NULL) return;
- if (msg->cm_fields['M'] == NULL) return;
- if (msg->cm_fields['A'] == NULL) return;
- if (msg->cm_fields['N'] == NULL) return;
-
- /* Initialize variables */
- strcpy(givenname, "");
- strcpy(sn, "");
- strcpy(calFBURL, "");
-
- sprintf(this_dn, "cn=%s,ou=%s,%s",
- msg->cm_fields['A'],
- msg->cm_fields['N'],
- config.c_ldap_base_dn
- );
-
- sprintf(uid, "%s@%s",
- msg->cm_fields['A'],
- msg->cm_fields['N']
- );
-
- /* Are we just deleting? If so, it's simple... */
- if (op == V2L_DELETE) {
- lprintf(CTDL_DEBUG, "Calling ldap_delete_s()\n");
- begin_critical_section(S_LDAP);
- i = ldap_delete_s(dirserver, this_dn);
- end_critical_section(S_LDAP);
- if (i != LDAP_SUCCESS) {
- lprintf(CTDL_ERR, "ldap_delete_s() failed: %s (%d)\n",
- ldap_err2string(i), i);
- }
- return;
- }
-
- /*
- * If we get to this point then it must be a V2L_WRITE operation.
- */
-
- /* First make sure the OU for the user's home Citadel host is created */
- CtdlCreateHostOU(msg->cm_fields['N']);
-
- /* The first LDAP attribute will be an 'objectclass' list. Citadel
- * doesn't do anything with this. It's just there for compatibility
- * with Kolab.
- */
- num_attrs = 1;
- attrs = malloc( (sizeof(LDAPMod *) * num_attrs) );
- attrs[0] = malloc(sizeof(LDAPMod));
- memset(attrs[0], 0, sizeof(LDAPMod));
- attrs[0]->mod_op = LDAP_MOD_ADD;
- attrs[0]->mod_type = "objectclass";
- attrs[0]->mod_values = malloc(3 * sizeof(char *));
- attrs[0]->mod_values[0] = strdup("citadelInetOrgPerson");
- attrs[0]->mod_values[1] = NULL;
-
- /* Convert the vCard fields to LDAP properties */
- v = vcard_load(msg->cm_fields['M']);
- if (v->numprops) for (i=0; i<(v->numprops); ++i) if (striplt(v->prop[i].value), strlen(v->prop[i].value) > 0) {
-
- if (!strcasecmp(v->prop[i].name, "n")) {
- extract_token(sn, v->prop[i].value, 0, ';', sizeof sn);
- extract_token(givenname, v->prop[i].value, 1, ';', sizeof givenname);
- }
-
- if (!strcasecmp(v->prop[i].name, "fn")) {
- attrs = realloc(attrs, (sizeof(LDAPMod *) * ++num_attrs) );
- attrs[num_attrs-1] = malloc(sizeof(LDAPMod));
- memset(attrs[num_attrs-1], 0, sizeof(LDAPMod));
- attrs[num_attrs-1]->mod_op = LDAP_MOD_ADD;
- attrs[num_attrs-1]->mod_type = "cn";
- attrs[num_attrs-1]->mod_values = malloc(2 * sizeof(char *));
- attrs[num_attrs-1]->mod_values[0] = strdup(v->prop[i].value);
- attrs[num_attrs-1]->mod_values[1] = NULL;
- have_cn = 1;
- }
-
- if (!strcasecmp(v->prop[i].name, "title")) {
- attrs = realloc(attrs, (sizeof(LDAPMod *) * ++num_attrs) );
- attrs[num_attrs-1] = malloc(sizeof(LDAPMod));
- memset(attrs[num_attrs-1], 0, sizeof(LDAPMod));
- attrs[num_attrs-1]->mod_op = LDAP_MOD_ADD;
- attrs[num_attrs-1]->mod_type = "title";
- attrs[num_attrs-1]->mod_values = malloc(2 * sizeof(char *));
- attrs[num_attrs-1]->mod_values[0] = strdup(v->prop[i].value);
- attrs[num_attrs-1]->mod_values[1] = NULL;
- }
-
- if (!strcasecmp(v->prop[i].name, "org")) {
- attrs = realloc(attrs, (sizeof(LDAPMod *) * ++num_attrs) );
- attrs[num_attrs-1] = malloc(sizeof(LDAPMod));
- memset(attrs[num_attrs-1], 0, sizeof(LDAPMod));
- attrs[num_attrs-1]->mod_op = LDAP_MOD_ADD;
- attrs[num_attrs-1]->mod_type = "o";
- attrs[num_attrs-1]->mod_values = malloc(2 * sizeof(char *));
- attrs[num_attrs-1]->mod_values[0] = strdup(v->prop[i].value);
- attrs[num_attrs-1]->mod_values[1] = NULL;
- }
-
- if ( (!strcasecmp(v->prop[i].name, "adr"))
- ||(!strncasecmp(v->prop[i].name, "adr;", 4)) ) {
- /* Unfortunately, we can only do a single address */
- if (!have_addr) {
- have_addr = 1;
- strcpy(street, "");
- extract_token(&street[strlen(street)],
- v->prop[i].value, 0, ';', (sizeof street - strlen(street))); /* po box */
- strcat(street, " ");
- extract_token(&street[strlen(street)],
- v->prop[i].value, 1, ';', (sizeof street - strlen(street))); /* extend addr */
- strcat(street, " ");
- extract_token(&street[strlen(street)],
- v->prop[i].value, 2, ';', (sizeof street - strlen(street))); /* street */
- striplt(street);
- extract_token(city, v->prop[i].value, 3, ';', sizeof city);
- extract_token(state, v->prop[i].value, 4, ';', sizeof state);
- extract_token(zipcode, v->prop[i].value, 5, ';', sizeof zipcode);
-
- attrs = realloc(attrs, (sizeof(LDAPMod *) * ++num_attrs) );
- attrs[num_attrs-1] = malloc(sizeof(LDAPMod));
- memset(attrs[num_attrs-1], 0, sizeof(LDAPMod));
- attrs[num_attrs-1]->mod_op = LDAP_MOD_ADD;
- attrs[num_attrs-1]->mod_type = "street";
- attrs[num_attrs-1]->mod_values = malloc(2 * sizeof(char *));
- attrs[num_attrs-1]->mod_values[0] = strdup(street);
- attrs[num_attrs-1]->mod_values[1] = NULL;
-
- attrs = realloc(attrs, (sizeof(LDAPMod *) * ++num_attrs) );
- attrs[num_attrs-1] = malloc(sizeof(LDAPMod));
- memset(attrs[num_attrs-1], 0, sizeof(LDAPMod));
- attrs[num_attrs-1]->mod_op = LDAP_MOD_ADD;
- attrs[num_attrs-1]->mod_type = "l";
- attrs[num_attrs-1]->mod_values = malloc(2 * sizeof(char *));
- attrs[num_attrs-1]->mod_values[0] = strdup(city);
- attrs[num_attrs-1]->mod_values[1] = NULL;
-
- attrs = realloc(attrs, (sizeof(LDAPMod *) * ++num_attrs) );
- attrs[num_attrs-1] = malloc(sizeof(LDAPMod));
- memset(attrs[num_attrs-1], 0, sizeof(LDAPMod));
- attrs[num_attrs-1]->mod_op = LDAP_MOD_ADD;
- attrs[num_attrs-1]->mod_type = "st";
- attrs[num_attrs-1]->mod_values = malloc(2 * sizeof(char *));
- attrs[num_attrs-1]->mod_values[0] = strdup(state);
- attrs[num_attrs-1]->mod_values[1] = NULL;
-
- attrs = realloc(attrs, (sizeof(LDAPMod *) * ++num_attrs) );
- attrs[num_attrs-1] = malloc(sizeof(LDAPMod));
- memset(attrs[num_attrs-1], 0, sizeof(LDAPMod));
- attrs[num_attrs-1]->mod_op = LDAP_MOD_ADD;
- attrs[num_attrs-1]->mod_type = "postalcode";
- attrs[num_attrs-1]->mod_values = malloc(2 * sizeof(char *));
- attrs[num_attrs-1]->mod_values[0] = strdup(zipcode);
- attrs[num_attrs-1]->mod_values[1] = NULL;
- }
- }
-
- if ( (!strcasecmp(v->prop[i].name, "tel"))
- ||(!strncasecmp(v->prop[i].name, "tel;", 4)) ) {
- ++num_phones;
- /* The first 'tel' property creates the 'telephoneNumber' attribute */
- if (num_phones == 1) {
- attrs = realloc(attrs, (sizeof(LDAPMod *) * ++num_attrs) );
- phone_attr = num_attrs-1;
- attrs[phone_attr] = malloc(sizeof(LDAPMod));
- memset(attrs[phone_attr], 0, sizeof(LDAPMod));
- attrs[phone_attr]->mod_op = LDAP_MOD_ADD;
- attrs[phone_attr]->mod_type = "telephoneNumber";
- attrs[phone_attr]->mod_values = malloc(2 * sizeof(char *));
- attrs[phone_attr]->mod_values[0] = strdup(v->prop[i].value);
- attrs[phone_attr]->mod_values[1] = NULL;
- }
- /* Subsequent 'tel' properties *add to* the 'telephoneNumber' attribute */
- else {
- attrs[phone_attr]->mod_values = realloc(attrs[phone_attr]->mod_values,
- num_phones * sizeof(char *));
- attrs[phone_attr]->mod_values[num_phones-1]
- = strdup(v->prop[i].value);
- attrs[phone_attr]->mod_values[num_phones]
- = NULL;
- }
- }
-
-
- if ( (!strcasecmp(v->prop[i].name, "email"))
- ||(!strcasecmp(v->prop[i].name, "email;internet")) ) {
-
- ++num_emails;
- lprintf(CTDL_DEBUG, "email addr %d\n", num_emails);
-
- /* The first email address creates the 'mail' attribute */
- if (num_emails == 1) {
- attrs = realloc(attrs, (sizeof(LDAPMod *) * ++num_attrs) );
- attrs[num_attrs-1] = malloc(sizeof(LDAPMod));
- memset(attrs[num_attrs-1], 0, sizeof(LDAPMod));
- attrs[num_attrs-1]->mod_op = LDAP_MOD_ADD;
- attrs[num_attrs-1]->mod_type = "mail";
- attrs[num_attrs-1]->mod_values = malloc(2 * sizeof(char *));
- attrs[num_attrs-1]->mod_values[0] = strdup(v->prop[i].value);
- attrs[num_attrs-1]->mod_values[1] = NULL;
- }
- /* The second email address creates the 'alias' attribute */
- else if (num_emails == 2) {
- attrs = realloc(attrs, (sizeof(LDAPMod *) * ++num_attrs) );
- alias_attr = num_attrs-1;
- attrs[alias_attr] = malloc(sizeof(LDAPMod));
- memset(attrs[alias_attr], 0, sizeof(LDAPMod));
- attrs[alias_attr]->mod_op = LDAP_MOD_ADD;
- attrs[alias_attr]->mod_type = "alias";
- attrs[alias_attr]->mod_values = malloc(2 * sizeof(char *));
- attrs[alias_attr]->mod_values[0] = strdup(v->prop[i].value);
- attrs[alias_attr]->mod_values[1] = NULL;
- }
- /* Subsequent email addresses *add to* the 'alias' attribute */
- else if (num_emails > 2) {
- attrs[alias_attr]->mod_values = realloc(attrs[alias_attr]->mod_values,
- num_emails * sizeof(char *));
- attrs[alias_attr]->mod_values[num_emails-2]
- = strdup(v->prop[i].value);
- attrs[alias_attr]->mod_values[num_emails-1]
- = NULL;
- }
-
-
- }
-
- /* Calendar free/busy URL (take the first one we find, but if a subsequent
- * one contains the "pref" designation then we go with that instead.)
- */
- if ( (!strcasecmp(v->prop[i].name, "fburl"))
- ||(!strncasecmp(v->prop[i].name, "fburl;", 6)) ) {
- if ( (strlen(calFBURL) == 0)
- || (!strncasecmp(v->prop[i].name, "fburl;pref", 10)) ) {
- safestrncpy(calFBURL, v->prop[i].value, sizeof calFBURL);
- }
- }
-
- }
- vcard_free(v); /* Don't need this anymore. */
-
- /* "sn" (surname) based on info in vCard */
- attrs = realloc(attrs, (sizeof(LDAPMod *) * ++num_attrs) );
- attrs[num_attrs-1] = malloc(sizeof(LDAPMod));
- memset(attrs[num_attrs-1], 0, sizeof(LDAPMod));
- attrs[num_attrs-1]->mod_op = LDAP_MOD_ADD;
- attrs[num_attrs-1]->mod_type = "sn";
- attrs[num_attrs-1]->mod_values = malloc(2 * sizeof(char *));
- attrs[num_attrs-1]->mod_values[0] = strdup(sn);
- attrs[num_attrs-1]->mod_values[1] = NULL;
-
- /* "givenname" (first name) based on info in vCard */
- if (strlen(givenname) == 0) strcpy(givenname, "_");
- if (strlen(sn) == 0) strcpy(sn, "_");
- attrs = realloc(attrs, (sizeof(LDAPMod *) * ++num_attrs) );
- attrs[num_attrs-1] = malloc(sizeof(LDAPMod));
- memset(attrs[num_attrs-1], 0, sizeof(LDAPMod));
- attrs[num_attrs-1]->mod_op = LDAP_MOD_ADD;
- attrs[num_attrs-1]->mod_type = "givenname";
- attrs[num_attrs-1]->mod_values = malloc(2 * sizeof(char *));
- attrs[num_attrs-1]->mod_values[0] = strdup(givenname);
- attrs[num_attrs-1]->mod_values[1] = NULL;
-
- /* "uid" is a Kolab compatibility thing. We just do cituser@citnode */
- attrs = realloc(attrs, (sizeof(LDAPMod *) * ++num_attrs) );
- attrs[num_attrs-1] = malloc(sizeof(LDAPMod));
- memset(attrs[num_attrs-1], 0, sizeof(LDAPMod));
- attrs[num_attrs-1]->mod_op = LDAP_MOD_ADD;
- attrs[num_attrs-1]->mod_type = "uid";
- attrs[num_attrs-1]->mod_values = malloc(2 * sizeof(char *));
- attrs[num_attrs-1]->mod_values[0] = strdup(uid);
- attrs[num_attrs-1]->mod_values[1] = NULL;
-
- /* Add a "cn" (Common Name) attribute based on the user's screen name,
- * but only there was no 'fn' (full name) property in the vCard
- */
- if (!have_cn) {
- attrs = realloc(attrs, (sizeof(LDAPMod *) * ++num_attrs) );
- attrs[num_attrs-1] = malloc(sizeof(LDAPMod));
- memset(attrs[num_attrs-1], 0, sizeof(LDAPMod));
- attrs[num_attrs-1]->mod_op = LDAP_MOD_ADD;
- attrs[num_attrs-1]->mod_type = "cn";
- attrs[num_attrs-1]->mod_values = malloc(2 * sizeof(char *));
- attrs[num_attrs-1]->mod_values[0] = strdup(msg->cm_fields['A']);
- attrs[num_attrs-1]->mod_values[1] = NULL;
- }
-
- /* Add a "calFBURL" attribute if a calendar free/busy URL exists */
- if (strlen(calFBURL) > 0) {
- attrs = realloc(attrs, (sizeof(LDAPMod *) * ++num_attrs) );
- attrs[num_attrs-1] = malloc(sizeof(LDAPMod));
- memset(attrs[num_attrs-1], 0, sizeof(LDAPMod));
- attrs[num_attrs-1]->mod_op = LDAP_MOD_ADD;
- attrs[num_attrs-1]->mod_type = "calFBURL";
- attrs[num_attrs-1]->mod_values = malloc(2 * sizeof(char *));
- attrs[num_attrs-1]->mod_values[0] = strdup(calFBURL);
- attrs[num_attrs-1]->mod_values[1] = NULL;
- }
-
- /* The last attribute must be a NULL one. */
- attrs = realloc(attrs, (sizeof(LDAPMod *) * ++num_attrs) );
- attrs[num_attrs - 1] = NULL;
-
- lprintf(CTDL_DEBUG, "Calling ldap_add_s() for '%s'\n", this_dn);
- begin_critical_section(S_LDAP);
- i = ldap_add_s(dirserver, this_dn, attrs);
- end_critical_section(S_LDAP);
-
- /* If the entry already exists, repopulate it instead */
- if (i == LDAP_ALREADY_EXISTS) {
- for (j=0; j<(num_attrs-1); ++j) {
- attrs[j]->mod_op = LDAP_MOD_REPLACE;
- }
- lprintf(CTDL_DEBUG, "Calling ldap_modify_s() for '%s'\n", this_dn);
- begin_critical_section(S_LDAP);
- i = ldap_modify_s(dirserver, this_dn, attrs);
- end_critical_section(S_LDAP);
- }
-
- if (i != LDAP_SUCCESS) {
- lprintf(CTDL_ERR, "ldap_add_s() failed: %s (%d)\n",
- ldap_err2string(i), i);
- }
-
- lprintf(CTDL_DEBUG, "Freeing attributes\n");
- /* Free the attributes */
- for (i=0; i<num_attrs; ++i) {
- if (attrs[i] != NULL) {
-
- /* First, free the value strings */
- if (attrs[i]->mod_values != NULL) {
- for (j=0; attrs[i]->mod_values[j] != NULL; ++j) {
- free(attrs[i]->mod_values[j]);
- }
- }
-
- /* Free the value strings pointer list */
- if (attrs[i]->mod_values != NULL) {
- free(attrs[i]->mod_values);
- }
-
- /* Now free the LDAPMod struct itself. */
- free(attrs[i]);
- }
- }
- free(attrs);
- lprintf(CTDL_DEBUG, "LDAP write operation complete.\n");
-}
-
-
-#endif /* HAVE_LDAP */
-
-
-/*
- * Initialize the LDAP connector module ... or don't, if we don't have LDAP.
- */
-CTDL_MODULE_INIT(ldap)
-{
-#ifdef HAVE_LDAP
- CtdlRegisterCleanupHook(serv_ldap_cleanup);
-
- if (strlen(config.c_ldap_host) > 0) {
- CtdlConnectToLdap();
- }
-
-#endif /* HAVE_LDAP */
-
- /* return our Subversion id for the Log */
- return "$Id$";
-}
+++ /dev/null
-/*
- * $Id$
- *
- * This module handles self-service subscription/unsubscription to mail lists.
- *
- * Copyright (C) 2002-2005 by Art Cancro and others.
- * This code is released under the terms of the GNU General Public License.
- *
- */
-
-#include "sysdep.h"
-#include <stdlib.h>
-#include <unistd.h>
-#include <stdio.h>
-#include <fcntl.h>
-#include <ctype.h>
-#include <signal.h>
-#include <pwd.h>
-#include <errno.h>
-#include <sys/types.h>
-#include <dirent.h>
-#if TIME_WITH_SYS_TIME
-# include <sys/time.h>
-# include <time.h>
-#else
-# if HAVE_SYS_TIME_H
-# include <sys/time.h>
-# else
-# include <time.h>
-# endif
-#endif
-
-#include <sys/wait.h>
-#include <string.h>
-#include <limits.h>
-#include "citadel.h"
-#include "server.h"
-#include "citserver.h"
-#include "support.h"
-#include "config.h"
-#include "room_ops.h"
-#include "user_ops.h"
-#include "policy.h"
-#include "database.h"
-#include "msgbase.h"
-#include "tools.h"
-#include "internet_addressing.h"
-#include "serv_network.h"
-#include "clientsocket.h"
-#include "file_ops.h"
-
-#ifndef HAVE_SNPRINTF
-#include "snprintf.h"
-#endif
-
-
-
-#include "ctdl_module.h"
-
-
-/*
- * Generate a randomizationalisticized token to use for authentication of
- * a subscribe or unsubscribe request.
- */
-void listsub_generate_token(char *buf) {
- char sourcebuf[SIZ];
- static int seq = 0;
-
- /* Theo, please sit down and shut up. This key doesn't have to be
- * tinfoil-hat secure, it just needs to be reasonably unguessable
- * and unique.
- */
- sprintf(sourcebuf, "%lx",
- (long) (++seq + getpid() + time(NULL))
- );
-
- /* Convert it to base64 so it looks cool */
- CtdlEncodeBase64(buf, sourcebuf, strlen(sourcebuf));
-}
-
-
-/*
- * Enter a subscription request
- */
-void do_subscribe(char *room, char *email, char *subtype, char *webpage) {
- struct ctdlroom qrbuf;
- FILE *ncfp;
- char filename[256];
- char token[256];
- char confirmation_request[2048];
- char buf[512];
- char urlroom[ROOMNAMELEN];
- char scancmd[64];
- char scanemail[256];
- int found_sub = 0;
-
- if (getroom(&qrbuf, room) != 0) {
- cprintf("%d There is no list called '%s'\n", ERROR + ROOM_NOT_FOUND, room);
- return;
- }
-
- if ((qrbuf.QRflags2 & QR2_SELFLIST) == 0) {
- cprintf("%d '%s' "
- "does not accept subscribe/unsubscribe requests.\n",
- ERROR + HIGHER_ACCESS_REQUIRED, qrbuf.QRname);
- return;
- }
-
- listsub_generate_token(token);
-
- assoc_file_name(filename, sizeof filename, &qrbuf, ctdl_netcfg_dir);
-
- /*
- * Make sure the requested address isn't already subscribed
- */
- begin_critical_section(S_NETCONFIGS);
- ncfp = fopen(filename, "r");
- if (ncfp != NULL) {
- while (fgets(buf, sizeof buf, ncfp) != NULL) {
- buf[strlen(buf)-1] = 0;
- extract_token(scancmd, buf, 0, '|', sizeof scancmd);
- extract_token(scanemail, buf, 1, '|', sizeof scanemail);
- if ((!strcasecmp(scancmd, "listrecp"))
- || (!strcasecmp(scancmd, "digestrecp"))) {
- if (!strcasecmp(scanemail, email)) {
- ++found_sub;
- }
- }
- }
- fclose(ncfp);
- }
- end_critical_section(S_NETCONFIGS);
-
- if (found_sub != 0) {
- cprintf("%d '%s' is already subscribed to '%s'.\n",
- ERROR + ALREADY_EXISTS,
- email, qrbuf.QRname);
- return;
- }
-
- /*
- * Now add it to the file
- */
- begin_critical_section(S_NETCONFIGS);
- ncfp = fopen(filename, "a");
- if (ncfp != NULL) {
- fprintf(ncfp, "subpending|%s|%s|%s|%ld|%s\n",
- email,
- subtype,
- token,
- time(NULL),
- webpage
- );
- fclose(ncfp);
- }
- end_critical_section(S_NETCONFIGS);
-
- /* Generate and send the confirmation request */
-
- urlesc(urlroom, qrbuf.QRname);
-
- snprintf(confirmation_request, sizeof confirmation_request,
-
- "MIME-Version: 1.0\n"
- "Content-Type: multipart/alternative; boundary=\"__ctdlmultipart__\"\n"
- "\n"
- "This is a multipart message in MIME format.\n"
- "\n"
- "--__ctdlmultipart__\n"
- "Content-type: text/plain\n"
- "\n"
- "Someone (probably you) has submitted a request to subscribe\n"
- "<%s> to the '%s' mailing list.\n"
- "\n"
- "Please go here to confirm this request:\n"
- " %s?room=%s&token=%s&cmd=confirm \n"
- "\n"
- "If this request has been submitted in error and you do not\n"
- "wish to receive the '%s' mailing list, simply do nothing,\n"
- "and you will not receive any further mailings.\n"
- "\n"
- "--__ctdlmultipart__\n"
- "Content-type: text/html\n"
- "\n"
- "<HTML><BODY>\n"
- "Someone (probably you) has submitted a request to subscribe\n"
- "<%s> to the <B>%s</B> mailing list.<BR><BR>\n"
- "Please click here to confirm this request:<BR>\n"
- "<A HREF=\"%s?room=%s&token=%s&cmd=confirm\">"
- "%s?room=%s&token=%s&cmd=confirm</A><BR><BR>\n"
- "If this request has been submitted in error and you do not\n"
- "wish to receive the '%s' mailing list, simply do nothing,\n"
- "and you will not receive any further mailings.\n"
- "</BODY></HTML>\n"
- "\n"
- "--__ctdlmultipart__--\n",
-
- email, qrbuf.QRname,
- webpage, urlroom, token,
- qrbuf.QRname,
-
- email, qrbuf.QRname,
- webpage, urlroom, token,
- webpage, urlroom, token,
- qrbuf.QRname
- );
-
- quickie_message( /* This delivers the message */
- "Citadel",
- NULL,
- email,
- NULL,
- confirmation_request,
- FMT_RFC822,
- "Please confirm your list subscription"
- );
-
- cprintf("%d Subscription entered; confirmation request sent\n", CIT_OK);
-}
-
-
-/*
- * Enter an unsubscription request
- */
-void do_unsubscribe(char *room, char *email, char *webpage) {
- struct ctdlroom qrbuf;
- FILE *ncfp;
- char filename[256];
- char token[256];
- char buf[512];
- char confirmation_request[2048];
- char urlroom[ROOMNAMELEN];
- char scancmd[256];
- char scanemail[256];
- int found_sub = 0;
-
- if (getroom(&qrbuf, room) != 0) {
- cprintf("%d There is no list called '%s'\n",
- ERROR + ROOM_NOT_FOUND, room);
- return;
- }
-
- if ((qrbuf.QRflags2 & QR2_SELFLIST) == 0) {
- cprintf("%d '%s' "
- "does not accept subscribe/unsubscribe requests.\n",
- ERROR + HIGHER_ACCESS_REQUIRED, qrbuf.QRname);
- return;
- }
-
- listsub_generate_token(token);
-
- assoc_file_name(filename, sizeof filename, &qrbuf, ctdl_netcfg_dir);
-
- /*
- * Make sure there's actually a subscription there to remove
- */
- begin_critical_section(S_NETCONFIGS);
- ncfp = fopen(filename, "r");
- if (ncfp != NULL) {
- while (fgets(buf, sizeof buf, ncfp) != NULL) {
- buf[strlen(buf)-1] = 0;
- extract_token(scancmd, buf, 0, '|', sizeof scancmd);
- extract_token(scanemail, buf, 1, '|', sizeof scanemail);
- if ((!strcasecmp(scancmd, "listrecp"))
- || (!strcasecmp(scancmd, "digestrecp"))) {
- if (!strcasecmp(scanemail, email)) {
- ++found_sub;
- }
- }
- }
- fclose(ncfp);
- }
- end_critical_section(S_NETCONFIGS);
-
- if (found_sub == 0) {
- cprintf("%d '%s' is not subscribed to '%s'.\n",
- ERROR + NO_SUCH_USER,
- email, qrbuf.QRname);
- return;
- }
-
- /*
- * Ok, now enter the unsubscribe-pending entry.
- */
- begin_critical_section(S_NETCONFIGS);
- ncfp = fopen(filename, "a");
- if (ncfp != NULL) {
- fprintf(ncfp, "unsubpending|%s|%s|%ld|%s\n",
- email,
- token,
- time(NULL),
- webpage
- );
- fclose(ncfp);
- }
- end_critical_section(S_NETCONFIGS);
-
- /* Generate and send the confirmation request */
-
- urlesc(urlroom, qrbuf.QRname);
-
- snprintf(confirmation_request, sizeof confirmation_request,
-
- "MIME-Version: 1.0\n"
- "Content-Type: multipart/alternative; boundary=\"__ctdlmultipart__\"\n"
- "\n"
- "This is a multipart message in MIME format.\n"
- "\n"
- "--__ctdlmultipart__\n"
- "Content-type: text/plain\n"
- "\n"
- "Someone (probably you) has submitted a request to unsubscribe\n"
- "<%s> from the '%s' mailing list.\n"
- "\n"
- "Please go here to confirm this request:\n"
- " %s?room=%s&token=%s&cmd=confirm \n"
- "\n"
- "If this request has been submitted in error and you do not\n"
- "wish to unsubscribe from the '%s' mailing list, simply do nothing,\n"
- "and the request will not be processed.\n"
- "\n"
- "--__ctdlmultipart__\n"
- "Content-type: text/html\n"
- "\n"
- "<HTML><BODY>\n"
- "Someone (probably you) has submitted a request to unsubscribe\n"
- "<%s> from the <B>%s</B> mailing list.<BR><BR>\n"
- "Please click here to confirm this request:<BR>\n"
- "<A HREF=\"%s?room=%s&token=%s&cmd=confirm\">"
- "%s?room=%s&token=%s&cmd=confirm</A><BR><BR>\n"
- "If this request has been submitted in error and you do not\n"
- "wish to unsubscribe from the '%s' mailing list, simply do nothing,\n"
- "and the request will not be processed.\n"
- "</BODY></HTML>\n"
- "\n"
- "--__ctdlmultipart__--\n",
-
- email, qrbuf.QRname,
- webpage, urlroom, token,
- qrbuf.QRname,
-
- email, qrbuf.QRname,
- webpage, urlroom, token,
- webpage, urlroom, token,
- qrbuf.QRname
- );
-
- quickie_message( /* This delivers the message */
- "Citadel",
- NULL,
- email,
- NULL,
- confirmation_request,
- FMT_RFC822,
- "Please confirm your unsubscribe request"
- );
-
- cprintf("%d Unubscription noted; confirmation request sent\n", CIT_OK);
-}
-
-
-/*
- * Confirm a subscribe/unsubscribe request.
- */
-void do_confirm(char *room, char *token) {
- struct ctdlroom qrbuf;
- FILE *ncfp;
- char filename[256];
- char line_token[256];
- long line_offset;
- int line_length;
- char buf[512];
- char cmd[256];
- char email[256];
- char subtype[128];
- int success = 0;
- char address_to_unsubscribe[256];
- char scancmd[256];
- char scanemail[256];
- char *holdbuf = NULL;
- int linelen = 0;
- int buflen = 0;
-
- strcpy(address_to_unsubscribe, "");
-
- if (getroom(&qrbuf, room) != 0) {
- cprintf("%d There is no list called '%s'\n",
- ERROR + ROOM_NOT_FOUND, room);
- return;
- }
-
- if ((qrbuf.QRflags2 & QR2_SELFLIST) == 0) {
- cprintf("%d '%s' "
- "does not accept subscribe/unsubscribe requests.\n",
- ERROR + HIGHER_ACCESS_REQUIRED, qrbuf.QRname);
- return;
- }
-
- /*
- * Now start scanning this room's netconfig file for the
- * specified token.
- */
- assoc_file_name(filename, sizeof filename, &qrbuf, ctdl_netcfg_dir);
- begin_critical_section(S_NETCONFIGS);
- ncfp = fopen(filename, "r+");
- if (ncfp != NULL) {
- while (line_offset = ftell(ncfp),
- (fgets(buf, sizeof buf, ncfp) != NULL) ) {
- buf[strlen(buf)-1] = 0;
- line_length = strlen(buf);
- extract_token(cmd, buf, 0, '|', sizeof cmd);
- if (!strcasecmp(cmd, "subpending")) {
- extract_token(email, buf, 1, '|', sizeof email);
- extract_token(subtype, buf, 2, '|', sizeof subtype);
- extract_token(line_token, buf, 3, '|', sizeof line_token);
- if (!strcasecmp(token, line_token)) {
- if (!strcasecmp(subtype, "digest")) {
- safestrncpy(buf, "digestrecp|", sizeof buf);
- }
- else {
- safestrncpy(buf, "listrecp|", sizeof buf);
- }
- strcat(buf, email);
- strcat(buf, "|");
- /* SLEAZY HACK: pad the line out so
- * it's the same length as the line
- * we're replacing.
- */
- while (strlen(buf) < line_length) {
- strcat(buf, " ");
- }
- fseek(ncfp, line_offset, SEEK_SET);
- fprintf(ncfp, "%s\n", buf);
- ++success;
- }
- }
- if (!strcasecmp(cmd, "unsubpending")) {
- extract_token(line_token, buf, 2, '|', sizeof line_token);
- if (!strcasecmp(token, line_token)) {
- extract_token(address_to_unsubscribe, buf, 1, '|',
- sizeof address_to_unsubscribe);
- }
- }
- }
- fclose(ncfp);
- }
- end_critical_section(S_NETCONFIGS);
-
- /*
- * If "address_to_unsubscribe" contains something, then we have to
- * make another pass at the file, stripping out lines referring to
- * that address.
- */
- if (strlen(address_to_unsubscribe) > 0) {
- holdbuf = malloc(SIZ);
- begin_critical_section(S_NETCONFIGS);
- ncfp = fopen(filename, "r+");
- if (ncfp != NULL) {
- while (line_offset = ftell(ncfp),
- (fgets(buf, sizeof buf, ncfp) != NULL) ) {
- buf[strlen(buf)-1]=0;
- extract_token(scancmd, buf, 0, '|', sizeof scancmd);
- extract_token(scanemail, buf, 1, '|', sizeof scanemail);
- if ( (!strcasecmp(scancmd, "listrecp"))
- && (!strcasecmp(scanemail,
- address_to_unsubscribe)) ) {
- ++success;
- }
- else if ( (!strcasecmp(scancmd, "digestrecp"))
- && (!strcasecmp(scanemail,
- address_to_unsubscribe)) ) {
- ++success;
- }
- else if ( (!strcasecmp(scancmd, "subpending"))
- && (!strcasecmp(scanemail,
- address_to_unsubscribe)) ) {
- ++success;
- }
- else if ( (!strcasecmp(scancmd, "unsubpending"))
- && (!strcasecmp(scanemail,
- address_to_unsubscribe)) ) {
- ++success;
- }
- else { /* Not relevant, so *keep* it! */
- linelen = strlen(buf);
- holdbuf = realloc(holdbuf,
- (buflen + linelen + 2) );
- strcpy(&holdbuf[buflen], buf);
- buflen += linelen;
- strcpy(&holdbuf[buflen], "\n");
- buflen += 1;
- }
- }
- fclose(ncfp);
- }
- ncfp = fopen(filename, "w");
- if (ncfp != NULL) {
- fwrite(holdbuf, buflen+1, 1, ncfp);
- fclose(ncfp);
- }
- end_critical_section(S_NETCONFIGS);
- free(holdbuf);
- }
-
- /*
- * Did we do anything useful today?
- */
- if (success) {
- cprintf("%d %d operation(s) confirmed.\n", CIT_OK, success);
- lprintf(CTDL_NOTICE, "Mailing list: %s %ssubscribed to %s with token %s\n", email, (strlen(address_to_unsubscribe) > 0) ? "un" : "", room, token);
- }
- else {
- cprintf("%d Invalid token.\n", ERROR + ILLEGAL_VALUE);
- }
-
-}
-
-
-
-/*
- * process subscribe/unsubscribe requests and confirmations
- */
-void cmd_subs(char *cmdbuf) {
-
- char opr[256];
- char room[ROOMNAMELEN];
- char email[256];
- char subtype[256];
- char token[256];
- char webpage[256];
-
- extract_token(opr, cmdbuf, 0, '|', sizeof opr);
- if (!strcasecmp(opr, "subscribe")) {
- extract_token(subtype, cmdbuf, 3, '|', sizeof subtype);
- if ( (strcasecmp(subtype, "list"))
- && (strcasecmp(subtype, "digest")) ) {
- cprintf("%d Invalid subscription type '%s'\n",
- ERROR + ILLEGAL_VALUE, subtype);
- }
- else {
- extract_token(room, cmdbuf, 1, '|', sizeof room);
- extract_token(email, cmdbuf, 2, '|', sizeof email);
- extract_token(webpage, cmdbuf, 4, '|', sizeof webpage);
- do_subscribe(room, email, subtype, webpage);
- }
- }
- else if (!strcasecmp(opr, "unsubscribe")) {
- extract_token(room, cmdbuf, 1, '|', sizeof room);
- extract_token(email, cmdbuf, 2, '|', sizeof email);
- extract_token(webpage, cmdbuf, 3, '|', sizeof webpage);
- do_unsubscribe(room, email, webpage);
- }
- else if (!strcasecmp(opr, "confirm")) {
- extract_token(room, cmdbuf, 1, '|', sizeof room);
- extract_token(token, cmdbuf, 2, '|', sizeof token);
- do_confirm(room, token);
- }
- else {
- cprintf("%d Invalid command\n", ERROR + ILLEGAL_VALUE);
- }
-}
-
-
-/*
- * Module entry point
- */
-CTDL_MODULE_INIT(listsub)
-{
- CtdlRegisterProtoHook(cmd_subs, "SUBS", "List subscribe/unsubscribe");
-
- /* return our Subversion id for the Log */
- return "$Id$";
-}
+++ /dev/null
-/**
- * $Id$
- *
- * This module is an managesieve implementation for the Citadel system.
- * It is compliant with all of the following:
- *
- * http://tools.ietf.org/html/draft-martin-managesieve-06
- * as this draft expires with this writing, you might need to search for
- * the new one.
- */
-
-#include "sysdep.h"
-#include <stdlib.h>
-#include <unistd.h>
-#include <stdio.h>
-#include <fcntl.h>
-#include <signal.h>
-#include <pwd.h>
-#include <errno.h>
-#include <sys/types.h>
-#include <syslog.h>
-
-#if TIME_WITH_SYS_TIME
-# include <sys/time.h>
-# include <time.h>
-#else
-# if HAVE_SYS_TIME_H
-# include <sys/time.h>
-# else
-# include <time.h>
-# endif
-#endif
-
-#include <sys/wait.h>
-#include <ctype.h>
-#include <string.h>
-#include <limits.h>
-#include <sys/socket.h>
-#include <netinet/in.h>
-#include <arpa/inet.h>
-#include "citadel.h"
-#include "server.h"
-#include "citserver.h"
-#include "support.h"
-#include "config.h"
-#include "control.h"
-#include "room_ops.h"
-#include "user_ops.h"
-#include "policy.h"
-#include "database.h"
-#include "msgbase.h"
-#include "tools.h"
-#include "internet_addressing.h"
-#include "imap_tools.h"
-#include "genstamp.h"
-#include "domain.h"
-#include "clientsocket.h"
-#include "locate_host.h"
-#include "citadel_dirs.h"
-
-#ifdef HAVE_OPENSSL
-#include "serv_crypto.h"
-#endif
-
-#ifndef HAVE_SNPRINTF
-#include "snprintf.h"
-#endif
-
-
-#include "ctdl_module.h"
-
-
-
-#ifdef HAVE_LIBSIEVE
-
-#include "serv_sieve.h"
-
-
-/**
- * http://tools.ietf.org/html/draft-martin-managesieve-06
- *
- * this is the draft this code tries to implement.
- */
-
-
-struct citmgsve {
- int command_state; /**< Information about the current session */
- char *transmitted_message;
- size_t transmitted_length;
- char *imap_format_outstring;
- int imap_outstring_length;
-};
-
-enum { /** Command states for login authentication */
- mgsve_command,
- mgsve_tls,
- mgsve_user,
- mgsve_password,
- mgsve_plain
-};
-
-#define MGSVE CC->MGSVE
-
-/*****************************************************************************/
-/* MANAGESIEVE Server */
-/*****************************************************************************/
-
-void sieve_outbuf_append(char *str)
-{
- size_t newlen = strlen(str)+1;
- size_t oldlen = (MGSVE->imap_format_outstring==NULL)? 0 : strlen(MGSVE->imap_format_outstring)+2;
- char *buf = malloc ( newlen + oldlen + 10 );
- buf[0]='\0';
-
- if (oldlen!=0)
- sprintf(buf,"%s%s",MGSVE->imap_format_outstring, str);
- else
- memcpy(buf, str, newlen);
-
- if (oldlen != 0) free (MGSVE->imap_format_outstring);
- MGSVE->imap_format_outstring = buf;
-}
-
-
-/**
- * Capability listing. Printed as greeting or on "CAPABILITIES"
- * see Section 1.8 ; 2.4
- */
-void cmd_mgsve_caps(void)
-{
- cprintf("\"IMPLEMENTATION\" \"CITADEL Sieve v6.84\"\r\n" /* TODO: put citversion here. */
- "\"SASL\" \"PLAIN\"\r\n" /*DIGEST-MD5 GSSAPI SASL sucks.*/
-#ifdef HAVE_OPENSSL
-/* if TLS is already there, should we say that again? */
- "\"STARTTLS\"\r\n"
-#endif
- "\"SIEVE\" \"%s\"\r\n"
- "OK\r\n", msiv_extensions);
-}
-
-
-/*
- * Here's where our managesieve session begins its happy day.
- */
-void managesieve_greeting(void) {
-
- strcpy(CC->cs_clientname, "Managesieve session");
-
- CC->internal_pgm = 1;
- CC->cs_flags |= CS_STEALTH;
- MGSVE = malloc(sizeof(struct citmgsve));
- memset(MGSVE, 0, sizeof(struct citmgsve));
- cmd_mgsve_caps();
-}
-
-
-long GetSizeToken(char * token)
-{
- char *cursor = token;
- char *number;
-
- while ((*cursor != '\0') &&
- (*cursor != '{'))
- {
- cursor++;
- }
- if (*cursor == '\0')
- return -1;
- number = cursor + 1;
- while ((*cursor != '\0') &&
- (*cursor != '}'))
- {
- cursor++;
- }
-
- if (cursor[-1] == '+')
- cursor--;
-
- if (*cursor == '\0')
- return -1;
-
- return atol(number);
-}
-
-char *ReadString(long size, char *command)
-{
- long ret;
- if (size < 1) {
- cprintf("NO %s: %ld BAD Message length must be at least 1.\r\n",
- command, size);
- CC->kill_me = 1;
- return NULL;
- }
- MGSVE->transmitted_message = malloc(size + 2);
- if (MGSVE->transmitted_message == NULL) {
- cprintf("NO %s Cannot allocate memory.\r\n", command);
- CC->kill_me = 1;
- return NULL;
- }
- MGSVE->transmitted_length = size;
-
- ret = client_read(MGSVE->transmitted_message, size);
- MGSVE->transmitted_message[size] = '\0';
-
- if (ret != 1) {
- cprintf("%s NO Read failed.\r\n", command);
- return NULL;
- }
- return MGSVE->transmitted_message;
-
-}
-/* AUTHENTICATE command; 2.1 */
-void cmd_mgsve_auth(int num_parms, char **parms, struct sdm_userdata *u)
-{
- if ((num_parms == 3) && !strncasecmp(parms[1], "PLAIN", 5))
- /* todo, check length*/
- {
- char auth[SIZ];
- int retval;
- char *message = ReadString(GetSizeToken(parms[2]), parms[0]);
-
- if (message != NULL) {/**< do we have tokenized login? */
- retval = CtdlDecodeBase64(auth, MGSVE->transmitted_message, SIZ);
- }
- else
- retval = CtdlDecodeBase64(auth, parms[2], SIZ);
-
- if (login_ok == CtdlLoginExistingUser(NULL, auth))
- {
- char *pass;
- pass = &(auth[strlen(auth)+1]);
- /* for some reason the php script sends us the username twice. y? */
- pass = &(pass[strlen(pass)+1]);
-
- if (pass_ok == CtdlTryPassword(pass))
- {
- MGSVE->command_state = mgsve_password;
- cprintf("OK\r\n");
- return;
- }
- }
- }
-
- cprintf("NO \"Authentication Failure.\"\r\n");/* we just support auth plain. */
- CC->kill_me = 1;
-}
-
-
-#ifdef HAVE_OPENSSL
-/**
- * STARTTLS command chapter 2.2
- */
-void cmd_mgsve_starttls(void)
-{ /** answer with OK, and fire off tls session. */
- cprintf("OK\r\n");
- CtdlStartTLS(NULL, NULL, NULL);
- cmd_mgsve_caps();
-}
-#endif
-
-
-
-/**
- *LOGOUT command, see chapter 2.3
- */
-void cmd_mgsve_logout(struct sdm_userdata *u)
-{
- cprintf("OK\r\n");
- lprintf(CTDL_NOTICE, "MgSve bye.");
- CC->kill_me = 1;
-}
-
-
-/**
- * HAVESPACE command. see chapter 2.5
- */
-void cmd_mgsve_havespace(void)
-{
-/* as we don't have quotas in citadel we should always answer with OK;
- * pherhaps we should have a max-scriptsize.
- */
- if (MGSVE->command_state != mgsve_password)
- {
- cprintf("NO\r\n");
- CC->kill_me = 1;
- }
- else
- {
- cprintf("OK");
-/* citadel doesn't have quotas. in case of change, please add code here. */
-
- }
-}
-
-/**
- * PUTSCRIPT command, see chapter 2.6
- */
-void cmd_mgsve_putscript(int num_parms, char **parms, struct sdm_userdata *u)
-{
-/* "scriptname" {nnn+} */
-/* AFTER we have the whole script overwrite existing scripts */
-/* spellcheck the script before overwrite old ones, and reply with "no" */
- if (num_parms == 3)
- {
- char *ScriptName;
- char *Script;
- long slength;
-
- if (parms[1][0]=='"')
- ScriptName = &parms[1][1];
- else
- ScriptName = parms[1];
-
- slength = strlen (ScriptName);
-
- if (ScriptName[slength] == '"')
- ScriptName[slength] = '\0';
-
- Script = ReadString(GetSizeToken(parms[2]),parms[0]);
-
- if (Script == NULL) return;
-
- // TODO: do we spellcheck?
- msiv_putscript(u, ScriptName, Script);
- cprintf("OK\r\n");
- }
- else {
- cprintf("%s NO Read failed.\r\n", parms[0]);
- CC->kill_me = 1;
- return;
- }
-
-
-
-}
-
-
-
-
-/**
- * LISTSCRIPT command. see chapter 2.7
- */
-void cmd_mgsve_listscript(int num_parms, char **parms, struct sdm_userdata *u)
-{
-
- struct sdm_script *s;
- long nScripts = 0;
-
- MGSVE->imap_format_outstring = NULL;
- for (s=u->first_script; s!=NULL; s=s->next) {
- if (s->script_content != NULL) {
- cprintf("\"%s\"%s\r\n",
- s->script_name,
- (s->script_active)?" ACTIVE":"");
- nScripts++;
- }
- }
- cprintf("OK\r\n");
-}
-
-
-/**
- * \brief SETACTIVE command. see chapter 2.8
- */
-void cmd_mgsve_setactive(int num_parms, char **parms, struct sdm_userdata *u)
-{
- if (num_parms == 2)
- {
- if (msiv_setactive(u, parms[1]) == 0) {
- cprintf("OK\r\n");
- }
- else
- cprintf("No \"there is no script by that name %s \"\r\n", parms[1]);
- }
- else
- cprintf("NO \"unexpected parameters.\"\r\n");
-
-}
-
-
-/**
- * \brief GETSCRIPT command. see chapter 2.9
- */
-void cmd_mgsve_getscript(int num_parms, char **parms, struct sdm_userdata *u)
-{
- if (num_parms == 2){
- char *script_content;
- long slen;
-
- script_content = msiv_getscript(u, parms[1]);
- if (script_content != NULL){
- char *outbuf;
-
- slen = strlen(script_content);
- outbuf = malloc (slen + 64);
- snprintf(outbuf, slen + 64, "{%ld+}\r\n%s\r\nOK\r\n",slen, script_content);
- cprintf(outbuf);
- }
- else
- cprintf("No \"there is no script by that name %s \"\r\n", parms[1]);
- }
- else
- cprintf("NO \"unexpected parameters.\"\r\n");
-}
-
-
-/**
- * \brief DELETESCRIPT command. see chapter 2.10
- */
-void cmd_mgsve_deletescript(int num_parms, char **parms, struct sdm_userdata *u)
-{
- int i=-1;
-
- if (num_parms == 2)
- i = msiv_deletescript(u, parms[1]);
- switch (i){
- case 0:
- cprintf("OK\r\n");
- break;
- case 1:
- cprintf("NO \"no script by that name: %s\"\r\n", parms[1]);
- break;
- case 2:
- cprintf("NO \"can't delete active Script: %s\"\r\n", parms[1]);
- break;
- default:
- case -1:
- cprintf("NO \"unexpected parameters.\"\r\n");
- break;
- }
-}
-
-
-/**
- * \brief Attempt to perform authenticated managesieve
- */
-void mgsve_auth(char *argbuf) {
- char username_prompt[64];
- char method[64];
- char encoded_authstring[1024];
-
- if (CC->logged_in) {
- cprintf("NO \"Already logged in.\"\r\n");
- return;
- }
-
- extract_token(method, argbuf, 0, ' ', sizeof method);
-
- if (!strncasecmp(method, "login", 5) ) {
- if (strlen(argbuf) >= 7) {
- }
- else {
- CtdlEncodeBase64(username_prompt, "Username:", 9);
- cprintf("334 %s\r\n", username_prompt);
- }
- return;
- }
-
- if (!strncasecmp(method, "plain", 5) ) {
- if (num_tokens(argbuf, ' ') < 2) {
- cprintf("334 \r\n");
- return;
- }
- extract_token(encoded_authstring, argbuf, 1, ' ', sizeof encoded_authstring);
- return;
- }
-
- if (strncasecmp(method, "login", 5) ) {
- cprintf("NO \"Unknown authentication method.\"\r\n");
- return;
- }
-
-}
-
-
-
-/*
- * implements the STARTTLS command (Citadel API version)
- */
-#ifdef HAVE_OPENSSL
-void _mgsve_starttls(void)
-{
- char ok_response[SIZ];
- char nosup_response[SIZ];
- char error_response[SIZ];
-
- sprintf(ok_response,
- "200 2.0.0 Begin TLS negotiation now\r\n");
- sprintf(nosup_response,
- "554 5.7.3 TLS not supported here\r\n");
- sprintf(error_response,
- "554 5.7.3 Internal error\r\n");
- CtdlStartTLS(ok_response, nosup_response, error_response);
-}
-#endif
-
-
-/*
- * Main command loop for managesieve sessions.
- */
-void managesieve_command_loop(void) {
- char cmdbuf[SIZ];
- char *parms[SIZ];
- int length;
- int num_parms;
- struct sdm_userdata u;
- int changes_made = 0;
-
- memset(&u, 0, sizeof(struct sdm_userdata));
-
- time(&CC->lastcmd);
- memset(cmdbuf, 0, sizeof cmdbuf); /* Clear it, just in case */
- length = client_getln(cmdbuf, sizeof cmdbuf);
- if (length >= 1) {
- num_parms = imap_parameterize(parms, cmdbuf);
- if (num_parms == 0) return;
- length = strlen(parms[0]);
- }
- if (length < 1) {
- lprintf(CTDL_CRIT, "Client disconnected: ending session.\n");
- CC->kill_me = 1;
- return;
- }
- lprintf(CTDL_INFO, "MANAGESIEVE: %s\n", cmdbuf);
- if ((length>= 12) && (!strncasecmp(parms[0], "AUTHENTICATE", 12))){
- cmd_mgsve_auth(num_parms, parms, &u);
- }
-
-#ifdef HAVE_OPENSSL
- else if ((length>= 8) && (!strncasecmp(parms[0], "STARTTLS", 8))){
- cmd_mgsve_starttls();
- }
-#endif
- else if ((length>= 6) && (!strncasecmp(parms[0], "LOGOUT", 6))){
- cmd_mgsve_logout(&u);
- }
- else if ((length>= 6) && (!strncasecmp(parms[0], "CAPABILITY", 10))){
- cmd_mgsve_caps();
- }
- /** these commands need to be authenticated. throw it out if it tries. */
- else if (!CtdlAccessCheck(ac_logged_in))
- {
- msiv_load(&u);
- if ((length>= 9) && (!strncasecmp(parms[0], "HAVESPACE", 9))){
- cmd_mgsve_havespace();
- }
- else if ((length>= 6) && (!strncasecmp(parms[0], "PUTSCRIPT", 9))){
- cmd_mgsve_putscript(num_parms, parms, &u);
- changes_made = 1;
- }
- else if ((length>= 6) && (!strncasecmp(parms[0], "LISTSCRIPT", 10))){
- cmd_mgsve_listscript(num_parms, parms,&u);
- }
- else if ((length>= 6) && (!strncasecmp(parms[0], "SETACTIVE", 9))){
- cmd_mgsve_setactive(num_parms, parms,&u);
- changes_made = 1;
- }
- else if ((length>= 6) && (!strncasecmp(parms[0], "GETSCRIPT", 9))){
- cmd_mgsve_getscript(num_parms, parms, &u);
- }
- else if ((length>= 6) && (!strncasecmp(parms[0], "DELETESCRIPT", 11))){
- cmd_mgsve_deletescript(num_parms, parms, &u);
- changes_made = 1;
- }
- msiv_store(&u, changes_made);
- }
- else {
- cprintf("No\r\n");
- lprintf(CTDL_INFO, "illegal Managesieve command: %s", parms[0]);
- CC->kill_me = 1;
- }
-
-
-}
-
-
-#endif /* HAVE_LIBSIEVE */
-
-CTDL_MODULE_INIT(managesieve)
-{
-
-#ifdef HAVE_LIBSIEVE
-
- CtdlRegisterServiceHook(config.c_managesieve_port, /* MGSVE */
- NULL,
- managesieve_greeting,
- managesieve_command_loop,
- NULL);
-
-#else /* HAVE_LIBSIEVE */
-
- lprintf(CTDL_INFO, "This server is missing libsieve. Managesieve protocol is disabled..\n");
-
-#endif /* HAVE_LIBSIEVE */
-
- /* return our Subversion id for the Log */
- return "$Id$";
-}
-
-
+++ /dev/null
-/*
- * $Id$
- *
- * This module supplies statistics about the activity levels of your Citadel
- * system. We didn't bother writing a reporting module, because there is
- * already an excellent tool called MRTG (Multi Router Traffic Grapher) which
- * is available at http://www.mrtg.org that can fetch data using external
- * scripts. This module supplies data in the format expected by MRTG.
- *
- */
-
-#include "sysdep.h"
-#include <stdlib.h>
-#include <unistd.h>
-#include <stdio.h>
-#include <fcntl.h>
-#include <signal.h>
-#include <pwd.h>
-#include <errno.h>
-#include <sys/types.h>
-
-#if TIME_WITH_SYS_TIME
-# include <sys/time.h>
-# include <time.h>
-#else
-# if HAVE_SYS_TIME_H
-# include <sys/time.h>
-# else
-# include <time.h>
-# endif
-#endif
-
-#include <sys/wait.h>
-#include <string.h>
-#include <limits.h>
-#include "citadel.h"
-#include "server.h"
-#include "citserver.h"
-#include "support.h"
-#include "config.h"
-#include "control.h"
-#include "room_ops.h"
-#include "user_ops.h"
-#include "policy.h"
-#include "database.h"
-#include "msgbase.h"
-#include "tools.h"
-
-
-#include "ctdl_module.h"
-
-
-/*
- * Other functions call this one to output data in MRTG format
- */
-void mrtg_output(long value1, long value2) {
- time_t uptime_t;
- int uptime_days, uptime_hours, uptime_minutes;
-
- uptime_t = time(NULL) - server_startup_time;
- uptime_days = (int) (uptime_t / 86400L);
- uptime_hours = (int) ((uptime_t % 86400L) / 3600L);
- uptime_minutes = (int) ((uptime_t % 3600L) / 60L);
-
- cprintf("%d ok\n", LISTING_FOLLOWS);
- cprintf("%ld\n", value1);
- cprintf("%ld\n", value2);
- cprintf("%d days, %d hours, %d minutes\n",
- uptime_days, uptime_hours, uptime_minutes);
- cprintf("%s\n", config.c_humannode);
- cprintf("000\n");
-}
-
-
-
-
-/*
- * Tell us how many users are online
- */
-void mrtg_users(void) {
- long connected_users = 0;
- long active_users = 0;
-
- struct CitContext *cptr;
-
- for (cptr = ContextList; cptr != NULL; cptr = cptr->next) {
-
- if (cptr->internal_pgm == 0) {
- ++connected_users;
-
- if ( (time(NULL) - (cptr->lastidle)) < 900L) {
- ++active_users;
- }
- }
-
- }
-
- mrtg_output(connected_users, active_users);
-}
-
-
-/*
- * Volume of messages submitted
- */
-void mrtg_messages(void) {
- mrtg_output(CitControl.MMhighest, 0L);
-}
-
-
-/*
- * Fetch data for MRTG
- */
-void cmd_mrtg(char *argbuf) {
- char which[32];
-
- extract_token(which, argbuf, 0, '|', sizeof which);
-
- if (!strcasecmp(which, "users")) {
- mrtg_users();
- }
- else if (!strcasecmp(which, "messages")) {
- mrtg_messages();
- }
- else {
- cprintf("%d Unrecognized keyword '%s'\n",
- ERROR + ILLEGAL_VALUE, which);
- }
-}
-
-
-CTDL_MODULE_INIT(mrtg)
-{
- CtdlRegisterProtoHook(cmd_mrtg, "MRTG", "Supply stats to MRTG");
-
- /* return our Subversion id for the Log */
- return "$Id$";
-}
+++ /dev/null
-/*
- * $Id$
- *
- * A server-side module for Citadel designed to filter idiots off the network.
- *
- * Copyright (c) 2002 / released under the GNU General Public License
- */
-
-#include "sysdep.h"
-#include <stdlib.h>
-#include <unistd.h>
-#include <stdio.h>
-#include <fcntl.h>
-#include <signal.h>
-#include <pwd.h>
-#include <errno.h>
-#include <sys/types.h>
-
-#if TIME_WITH_SYS_TIME
-# include <sys/time.h>
-# include <time.h>
-#else
-# if HAVE_SYS_TIME_H
-# include <sys/time.h>
-# else
-# include <time.h>
-# endif
-#endif
-
-#include <sys/wait.h>
-#include <string.h>
-#include <limits.h>
-#include "citadel.h"
-#include "server.h"
-#include "citserver.h"
-#include "support.h"
-#include "config.h"
-#include "control.h"
-#include "room_ops.h"
-#include "user_ops.h"
-#include "policy.h"
-#include "database.h"
-#include "msgbase.h"
-#include "serv_network.h"
-#include "tools.h"
-
-
-#include "ctdl_module.h"
-
-
-/*
- * This handler detects whether an incoming network message is from some
- * moron user who the site operator has elected to filter out. If a match
- * is found, the message is rejected.
- */
-int filter_the_idiots(struct CtdlMessage *msg, char *target_room) {
- struct FilterList *fptr;
- int zap_user = 0;
- int zap_room = 0;
- int zap_node = 0;
-
- if ( (msg == NULL) || (filterlist == NULL) ) {
- return(0);
- }
-
- for (fptr = filterlist; fptr != NULL; fptr = fptr->next) {
-
- zap_user = 0;
- zap_room = 0;
- zap_node = 0;
-
- if (msg->cm_fields['A'] != NULL) {
- if ( (!strcasecmp(msg->cm_fields['A'], fptr->fl_user))
- || (fptr->fl_user[0] == 0) ) {
- zap_user = 1;
- }
- }
-
- if (msg->cm_fields['C'] != NULL) {
- if ( (!strcasecmp(msg->cm_fields['C'], fptr->fl_room))
- || (fptr->fl_room[0] == 0) ) {
- zap_room = 1;
- }
- }
-
- if (msg->cm_fields['O'] != NULL) {
- if ( (!strcasecmp(msg->cm_fields['O'], fptr->fl_room))
- || (fptr->fl_room[0] == 0) ) {
- zap_room = 1;
- }
- }
-
- if (msg->cm_fields['N'] != NULL) {
- if ( (!strcasecmp(msg->cm_fields['N'], fptr->fl_node))
- || (fptr->fl_node[0] == 0) ) {
- zap_node = 1;
- }
- }
-
- if (zap_user + zap_room + zap_node == 3) return(1);
-
- }
-
- return(0);
-}
-
-
-CTDL_MODULE_INIT(netfilter)
-{
- CtdlRegisterNetprocHook(filter_the_idiots);
-
- /* return our Subversion id for the Log */
- return "$Id$";
-}
+++ /dev/null
-/*
- * $Id$
- *
- * This module handles shared rooms, inter-Citadel mail, and outbound
- * mailing list processing.
- *
- * Copyright (C) 2000-2005 by Art Cancro and others.
- * This code is released under the terms of the GNU General Public License.
- *
- * ** NOTE ** A word on the S_NETCONFIGS semaphore:
- * This is a fairly high-level type of critical section. It ensures that no
- * two threads work on the netconfigs files at the same time. Since we do
- * so many things inside these, here are the rules:
- * 1. begin_critical_section(S_NETCONFIGS) *before* begin_ any others.
- * 2. Do *not* perform any I/O with the client during these sections.
- *
- */
-
-/*
- * Duration of time (in seconds) after which pending list subscribe/unsubscribe
- * requests that have not been confirmed will be deleted.
- */
-#define EXP 259200 /* three days */
-
-#include "sysdep.h"
-#include <stdlib.h>
-#include <unistd.h>
-#include <stdio.h>
-#include <fcntl.h>
-#include <ctype.h>
-#include <signal.h>
-#include <pwd.h>
-#include <errno.h>
-#include <sys/stat.h>
-#include <sys/types.h>
-#include <dirent.h>
-#if TIME_WITH_SYS_TIME
-# include <sys/time.h>
-# include <time.h>
-#else
-# if HAVE_SYS_TIME_H
-# include <sys/time.h>
-# else
-# include <time.h>
-# endif
-#endif
-
-#include <sys/wait.h>
-#include <string.h>
-#include <limits.h>
-#include "citadel.h"
-#include "server.h"
-#include "citserver.h"
-#include "support.h"
-#include "config.h"
-#include "room_ops.h"
-#include "user_ops.h"
-#include "policy.h"
-#include "database.h"
-#include "msgbase.h"
-#include "tools.h"
-#include "internet_addressing.h"
-#include "serv_network.h"
-#include "clientsocket.h"
-#include "file_ops.h"
-#include "citadel_dirs.h"
-
-#ifndef HAVE_SNPRINTF
-#include "snprintf.h"
-#endif
-
-
-#include "ctdl_module.h"
-
-
-
-/* Nonzero while we are doing network processing */
-static int doing_queue = 0;
-
-/*
- * When we do network processing, it's accomplished in two passes; one to
- * gather a list of rooms and one to actually do them. It's ok that rplist
- * is global; we have a mutex that keeps it safe.
- */
-struct RoomProcList *rplist = NULL;
-
-/*
- * We build a map of network nodes during processing.
- */
-struct NetMap *the_netmap = NULL;
-int netmap_changed = 0;
-char *working_ignetcfg = NULL;
-
-/*
- * Load or refresh the Citadel network (IGnet) configuration for this node.
- */
-void load_working_ignetcfg(void) {
- char *cfg;
- char *oldcfg;
-
- cfg = CtdlGetSysConfig(IGNETCFG);
- if (cfg == NULL) {
- cfg = strdup("");
- }
-
- oldcfg = working_ignetcfg;
- working_ignetcfg = cfg;
- if (oldcfg != NULL) {
- free(oldcfg);
- }
-}
-
-
-
-
-
-/*
- * Keep track of what messages to reject
- */
-struct FilterList *load_filter_list(void) {
- char *serialized_list = NULL;
- int i;
- char buf[SIZ];
- struct FilterList *newlist = NULL;
- struct FilterList *nptr;
-
- serialized_list = CtdlGetSysConfig(FILTERLIST);
- if (serialized_list == NULL) return(NULL); /* if null, no entries */
-
- /* Use the string tokenizer to grab one line at a time */
- for (i=0; i<num_tokens(serialized_list, '\n'); ++i) {
- extract_token(buf, serialized_list, i, '\n', sizeof buf);
- nptr = (struct FilterList *) malloc(sizeof(struct FilterList));
- extract_token(nptr->fl_user, buf, 0, '|', sizeof nptr->fl_user);
- striplt(nptr->fl_user);
- extract_token(nptr->fl_room, buf, 1, '|', sizeof nptr->fl_room);
- striplt(nptr->fl_room);
- extract_token(nptr->fl_node, buf, 2, '|', sizeof nptr->fl_node);
- striplt(nptr->fl_node);
-
- /* Cowardly refuse to add an any/any/any entry that would
- * end up filtering every single message.
- */
- if (strlen(nptr->fl_user) + strlen(nptr->fl_room)
- + strlen(nptr->fl_node) == 0) {
- free(nptr);
- }
- else {
- nptr->next = newlist;
- newlist = nptr;
- }
- }
-
- free(serialized_list);
- return newlist;
-}
-
-
-void free_filter_list(struct FilterList *fl) {
- if (fl == NULL) return;
- free_filter_list(fl->next);
- free(fl);
-}
-
-
-
-/*
- * Check the use table. This is a list of messages which have recently
- * arrived on the system. It is maintained and queried to prevent the same
- * message from being entered into the database multiple times if it happens
- * to arrive multiple times by accident.
- */
-int network_usetable(struct CtdlMessage *msg) {
-
- char msgid[SIZ];
- struct cdbdata *cdbut;
- struct UseTable ut;
-
- /* Bail out if we can't generate a message ID */
- if (msg == NULL) {
- return(0);
- }
- if (msg->cm_fields['I'] == NULL) {
- return(0);
- }
- if (strlen(msg->cm_fields['I']) == 0) {
- return(0);
- }
-
- /* Generate the message ID */
- strcpy(msgid, msg->cm_fields['I']);
- if (haschar(msgid, '@') == 0) {
- strcat(msgid, "@");
- if (msg->cm_fields['N'] != NULL) {
- strcat(msgid, msg->cm_fields['N']);
- }
- else {
- return(0);
- }
- }
-
- cdbut = cdb_fetch(CDB_USETABLE, msgid, strlen(msgid));
- if (cdbut != NULL) {
- cdb_free(cdbut);
- return(1);
- }
-
- /* If we got to this point, it's unique: add it. */
- strcpy(ut.ut_msgid, msgid);
- ut.ut_timestamp = time(NULL);
- cdb_store(CDB_USETABLE, msgid, strlen(msgid),
- &ut, sizeof(struct UseTable) );
- return(0);
-}
-
-
-/*
- * Read the network map from its configuration file into memory.
- */
-void read_network_map(void) {
- char *serialized_map = NULL;
- int i;
- char buf[SIZ];
- struct NetMap *nmptr;
-
- serialized_map = CtdlGetSysConfig(IGNETMAP);
- if (serialized_map == NULL) return; /* if null, no entries */
-
- /* Use the string tokenizer to grab one line at a time */
- for (i=0; i<num_tokens(serialized_map, '\n'); ++i) {
- extract_token(buf, serialized_map, i, '\n', sizeof buf);
- nmptr = (struct NetMap *) malloc(sizeof(struct NetMap));
- extract_token(nmptr->nodename, buf, 0, '|', sizeof nmptr->nodename);
- nmptr->lastcontact = extract_long(buf, 1);
- extract_token(nmptr->nexthop, buf, 2, '|', sizeof nmptr->nexthop);
- nmptr->next = the_netmap;
- the_netmap = nmptr;
- }
-
- free(serialized_map);
- netmap_changed = 0;
-}
-
-
-/*
- * Write the network map from memory back to the configuration file.
- */
-void write_network_map(void) {
- char *serialized_map = NULL;
- struct NetMap *nmptr;
-
-
- if (netmap_changed) {
- serialized_map = strdup("");
-
- if (the_netmap != NULL) {
- for (nmptr = the_netmap; nmptr != NULL; nmptr = nmptr->next) {
- serialized_map = realloc(serialized_map,
- (strlen(serialized_map)+SIZ) );
- if (strlen(nmptr->nodename) > 0) {
- snprintf(&serialized_map[strlen(serialized_map)],
- SIZ,
- "%s|%ld|%s\n",
- nmptr->nodename,
- (long)nmptr->lastcontact,
- nmptr->nexthop);
- }
- }
- }
-
- CtdlPutSysConfig(IGNETMAP, serialized_map);
- free(serialized_map);
- }
-
- /* Now free the list */
- while (the_netmap != NULL) {
- nmptr = the_netmap->next;
- free(the_netmap);
- the_netmap = nmptr;
- }
- netmap_changed = 0;
-}
-
-
-
-/*
- * Check the network map and determine whether the supplied node name is
- * valid. If it is not a neighbor node, supply the name of a neighbor node
- * which is the next hop. If it *is* a neighbor node, we also fill in the
- * shared secret.
- */
-int is_valid_node(char *nexthop, char *secret, char *node) {
- int i;
- char linebuf[SIZ];
- char buf[SIZ];
- int retval;
- struct NetMap *nmptr;
-
- if (node == NULL) {
- return(-1);
- }
-
- /*
- * First try the neighbor nodes
- */
- if (working_ignetcfg == NULL) {
- lprintf(CTDL_ERR, "working_ignetcfg is NULL!\n");
- if (nexthop != NULL) {
- strcpy(nexthop, "");
- }
- return(-1);
- }
-
- retval = (-1);
- if (nexthop != NULL) {
- strcpy(nexthop, "");
- }
-
- /* Use the string tokenizer to grab one line at a time */
- for (i=0; i<num_tokens(working_ignetcfg, '\n'); ++i) {
- extract_token(linebuf, working_ignetcfg, i, '\n', sizeof linebuf);
- extract_token(buf, linebuf, 0, '|', sizeof buf);
- if (!strcasecmp(buf, node)) {
- if (nexthop != NULL) {
- strcpy(nexthop, "");
- }
- if (secret != NULL) {
- extract_token(secret, linebuf, 1, '|', 256);
- }
- retval = 0;
- }
- }
-
- if (retval == 0) {
- return(retval); /* yup, it's a direct neighbor */
- }
-
- /*
- * If we get to this point we have to see if we know the next hop
- */
- if (the_netmap != NULL) {
- for (nmptr = the_netmap; nmptr != NULL; nmptr = nmptr->next) {
- if (!strcasecmp(nmptr->nodename, node)) {
- if (nexthop != NULL) {
- strcpy(nexthop, nmptr->nexthop);
- }
- return(0);
- }
- }
- }
-
- /*
- * If we get to this point, the supplied node name is bogus.
- */
- lprintf(CTDL_ERR, "Invalid node name <%s>\n", node);
- return(-1);
-}
-
-
-
-
-
-void cmd_gnet(char *argbuf) {
- char filename[SIZ];
- char buf[SIZ];
- FILE *fp;
-
- if (CtdlAccessCheck(ac_room_aide)) return;
- assoc_file_name(filename, sizeof filename, &CC->room, ctdl_netcfg_dir);
- cprintf("%d Network settings for room #%ld <%s>\n",
- LISTING_FOLLOWS,
- CC->room.QRnumber, CC->room.QRname);
-
- fp = fopen(filename, "r");
- if (fp != NULL) {
- while (fgets(buf, sizeof buf, fp) != NULL) {
- buf[strlen(buf)-1] = 0;
- cprintf("%s\n", buf);
- }
- fclose(fp);
- }
-
- cprintf("000\n");
-}
-
-
-void cmd_snet(char *argbuf) {
- char tempfilename[SIZ];
- char filename[SIZ];
- char buf[SIZ];
- FILE *fp;
-
- unbuffer_output();
-
- if (CtdlAccessCheck(ac_room_aide)) return;
- CtdlMakeTempFileName(tempfilename, sizeof tempfilename);
- assoc_file_name(filename, sizeof filename, &CC->room, ctdl_netcfg_dir);
-
- fp = fopen(tempfilename, "w");
- if (fp == NULL) {
- cprintf("%d Cannot open %s: %s\n",
- ERROR + INTERNAL_ERROR,
- tempfilename,
- strerror(errno));
- }
-
- cprintf("%d %s\n", SEND_LISTING, tempfilename);
- while (client_getln(buf, sizeof buf), strcmp(buf, "000")) {
- fprintf(fp, "%s\n", buf);
- }
- fclose(fp);
-
- /* Now copy the temp file to its permanent location
- * (We use /bin/mv instead of link() because they may be on
- * different filesystems)
- */
- unlink(filename);
- snprintf(buf, sizeof buf, "/bin/mv %s %s", tempfilename, filename);
- begin_critical_section(S_NETCONFIGS);
- system(buf);
- end_critical_section(S_NETCONFIGS);
-}
-
-
-/*
- * Deliver digest messages
- */
-void network_deliver_digest(struct SpoolControl *sc) {
- char buf[SIZ];
- int i;
- struct CtdlMessage *msg = NULL;
- long msglen;
- char *recps = NULL;
- size_t recps_len = SIZ;
- struct recptypes *valid;
- struct namelist *nptr;
-
- if (sc->num_msgs_spooled < 1) {
- fclose(sc->digestfp);
- sc->digestfp = NULL;
- return;
- }
-
- msg = malloc(sizeof(struct CtdlMessage));
- memset(msg, 0, sizeof(struct CtdlMessage));
- msg->cm_magic = CTDLMESSAGE_MAGIC;
- msg->cm_format_type = FMT_RFC822;
- msg->cm_anon_type = MES_NORMAL;
-
- sprintf(buf, "%ld", time(NULL));
- msg->cm_fields['T'] = strdup(buf);
- msg->cm_fields['A'] = strdup(CC->room.QRname);
- snprintf(buf, sizeof buf, "[%s]", CC->room.QRname);
- msg->cm_fields['U'] = strdup(buf);
- sprintf(buf, "room_%s@%s", CC->room.QRname, config.c_fqdn);
- for (i=0; i<strlen(buf); ++i) {
- if (isspace(buf[i])) buf[i]='_';
- buf[i] = tolower(buf[i]);
- }
- msg->cm_fields['F'] = strdup(buf);
- msg->cm_fields['R'] = strdup(buf);
-
- /*
- * Go fetch the contents of the digest
- */
- fseek(sc->digestfp, 0L, SEEK_END);
- msglen = ftell(sc->digestfp);
-
- msg->cm_fields['M'] = malloc(msglen + 1);
- fseek(sc->digestfp, 0L, SEEK_SET);
- fread(msg->cm_fields['M'], (size_t)msglen, 1, sc->digestfp);
- msg->cm_fields['M'][msglen] = 0;
-
- fclose(sc->digestfp);
- sc->digestfp = NULL;
-
- /* Now generate the delivery instructions */
-
- /*
- * Figure out how big a buffer we need to allocate
- */
- for (nptr = sc->digestrecps; nptr != NULL; nptr = nptr->next) {
- recps_len = recps_len + strlen(nptr->name) + 2;
- }
-
- recps = malloc(recps_len);
-
- if (recps == NULL) {
- lprintf(CTDL_EMERG, "Cannot allocate %ld bytes for recps...\n", (long)recps_len);
- abort();
- }
-
- strcpy(recps, "");
-
- /* Each recipient */
- for (nptr = sc->digestrecps; nptr != NULL; nptr = nptr->next) {
- if (nptr != sc->digestrecps) {
- strcat(recps, ",");
- }
- strcat(recps, nptr->name);
- }
-
- /* Now submit the message */
- valid = validate_recipients(recps);
- free(recps);
- CtdlSubmitMsg(msg, valid, NULL);
- CtdlFreeMessage(msg);
- free_recipients(valid);
-}
-
-
-/*
- * Deliver list messages to everyone on the list ... efficiently
- */
-void network_deliver_list(struct CtdlMessage *msg, struct SpoolControl *sc) {
- char *recps = NULL;
- size_t recps_len = SIZ;
- struct recptypes *valid;
- struct namelist *nptr;
-
- /* Don't do this if there were no recipients! */
- if (sc->listrecps == NULL) return;
-
- /* Now generate the delivery instructions */
-
- /*
- * Figure out how big a buffer we need to allocate
- */
- for (nptr = sc->listrecps; nptr != NULL; nptr = nptr->next) {
- recps_len = recps_len + strlen(nptr->name) + 2;
- }
-
- recps = malloc(recps_len);
-
- if (recps == NULL) {
- lprintf(CTDL_EMERG, "Cannot allocate %ld bytes for recps...\n", (long)recps_len);
- abort();
- }
-
- strcpy(recps, "");
-
- /* Each recipient */
- for (nptr = sc->listrecps; nptr != NULL; nptr = nptr->next) {
- if (nptr != sc->listrecps) {
- strcat(recps, ",");
- }
- strcat(recps, nptr->name);
- }
-
- /* Now submit the message */
- valid = validate_recipients(recps);
- free(recps);
- CtdlSubmitMsg(msg, valid, NULL);
- free_recipients(valid);
- /* Do not call CtdlFreeMessage(msg) here; the caller will free it. */
-}
-
-
-
-
-/*
- * Spools out one message from the list.
- */
-void network_spool_msg(long msgnum, void *userdata) {
- struct SpoolControl *sc;
- int i;
- char *newpath = NULL;
- size_t instr_len = SIZ;
- struct CtdlMessage *msg = NULL;
- struct namelist *nptr;
- struct maplist *mptr;
- struct ser_ret sermsg;
- FILE *fp;
- char filename[SIZ];
- char buf[SIZ];
- int bang = 0;
- int send = 1;
- int delete_after_send = 0; /* Set to 1 to delete after spooling */
- int ok_to_participate = 0;
- struct recptypes *valid;
-
- sc = (struct SpoolControl *)userdata;
-
- /*
- * Process mailing list recipients
- */
- instr_len = SIZ;
- if (sc->listrecps != NULL) {
- /* Fetch the message. We're going to need to modify it
- * in order to insert the [list name] in it, etc.
- */
- msg = CtdlFetchMessage(msgnum, 1);
- if (msg != NULL) {
-
- /* Prepend "[List name]" to the subject */
- if (msg->cm_fields['U'] == NULL) {
- msg->cm_fields['U'] = strdup("(no subject)");
- }
- snprintf(buf, sizeof buf, "[%s] %s", CC->room.QRname, msg->cm_fields['U']);
- free(msg->cm_fields['U']);
- msg->cm_fields['U'] = strdup(buf);
-
- /* Set the recipient of the list message to the
- * email address of the room itself.
- * FIXME ... I want to be able to pick any address
- */
- if (msg->cm_fields['R'] != NULL) {
- free(msg->cm_fields['R']);
- }
- msg->cm_fields['R'] = malloc(256);
- snprintf(msg->cm_fields['R'], 256,
- "room_%s@%s", CC->room.QRname,
- config.c_fqdn);
- for (i=0; i<strlen(msg->cm_fields['R']); ++i) {
- if (isspace(msg->cm_fields['R'][i])) {
- msg->cm_fields['R'][i] = '_';
- }
- }
-
- /* Handle delivery */
- network_deliver_list(msg, sc);
- CtdlFreeMessage(msg);
- }
- }
-
- /*
- * Process digest recipients
- */
- if ((sc->digestrecps != NULL) && (sc->digestfp != NULL)) {
- msg = CtdlFetchMessage(msgnum, 1);
- if (msg != NULL) {
- fprintf(sc->digestfp, " -----------------------------------"
- "------------------------------------"
- "-------\n");
- fprintf(sc->digestfp, "From: ");
- if (msg->cm_fields['A'] != NULL) {
- fprintf(sc->digestfp, "%s ", msg->cm_fields['A']);
- }
- if (msg->cm_fields['F'] != NULL) {
- fprintf(sc->digestfp, "<%s> ", msg->cm_fields['F']);
- }
- else if (msg->cm_fields['N'] != NULL) {
- fprintf(sc->digestfp, "@%s ", msg->cm_fields['N']);
- }
- fprintf(sc->digestfp, "\n");
- if (msg->cm_fields['U'] != NULL) {
- fprintf(sc->digestfp, "Subject: %s\n", msg->cm_fields['U']);
- }
-
- CC->redirect_buffer = malloc(SIZ);
- CC->redirect_len = 0;
- CC->redirect_alloc = SIZ;
-
- safestrncpy(CC->preferred_formats, "text/plain", sizeof CC->preferred_formats);
- CtdlOutputPreLoadedMsg(msg, MT_CITADEL, HEADERS_NONE, 0, 0);
-
- striplt(CC->redirect_buffer);
- fprintf(sc->digestfp, "\n%s\n", CC->redirect_buffer);
-
- free(CC->redirect_buffer);
- CC->redirect_buffer = NULL;
- CC->redirect_len = 0;
- CC->redirect_alloc = 0;
-
- sc->num_msgs_spooled += 1;
- free(msg);
- }
- }
-
- /*
- * Process client-side list participations for this room
- */
- instr_len = SIZ;
- if (sc->participates != NULL) {
- msg = CtdlFetchMessage(msgnum, 1);
- if (msg != NULL) {
-
- /* Only send messages which originated on our own Citadel
- * network, otherwise we'll end up sending the remote
- * mailing list's messages back to it, which is rude...
- */
- ok_to_participate = 0;
- if (msg->cm_fields['N'] != NULL) {
- if (!strcasecmp(msg->cm_fields['N'], config.c_nodename)) {
- ok_to_participate = 1;
- }
- if (is_valid_node(NULL, NULL, msg->cm_fields['N']) == 0) {
- ok_to_participate = 1;
- }
- }
- if (ok_to_participate) {
- if (msg->cm_fields['F'] != NULL) {
- free(msg->cm_fields['F']);
- }
- msg->cm_fields['F'] = malloc(SIZ);
- /* Replace the Internet email address of the actual
- * author with the email address of the room itself,
- * so the remote listserv doesn't reject us.
- * FIXME ... I want to be able to pick any address
- */
- snprintf(msg->cm_fields['F'], SIZ,
- "room_%s@%s", CC->room.QRname,
- config.c_fqdn);
- for (i=0; i<strlen(msg->cm_fields['F']); ++i) {
- if (isspace(msg->cm_fields['F'][i])) {
- msg->cm_fields['F'][i] = '_';
- }
- }
-
- /*
- * Figure out how big a buffer we need to allocate
- */
- for (nptr = sc->participates; nptr != NULL; nptr = nptr->next) {
-
- if (msg->cm_fields['R'] == NULL) {
- free(msg->cm_fields['R']);
- }
- msg->cm_fields['R'] = strdup(nptr->name);
-
- valid = validate_recipients(nptr->name);
- CtdlSubmitMsg(msg, valid, "");
- free_recipients(valid);
- }
-
- }
- CtdlFreeMessage(msg);
- }
- }
-
- /*
- * Process IGnet push shares
- */
- msg = CtdlFetchMessage(msgnum, 1);
- if (msg != NULL) {
- size_t newpath_len;
-
- /* Prepend our node name to the Path field whenever
- * sending a message to another IGnet node
- */
- if (msg->cm_fields['P'] == NULL) {
- msg->cm_fields['P'] = strdup("username");
- }
- newpath_len = strlen(msg->cm_fields['P']) +
- strlen(config.c_nodename) + 2;
- newpath = malloc(newpath_len);
- snprintf(newpath, newpath_len, "%s!%s",
- config.c_nodename, msg->cm_fields['P']);
- free(msg->cm_fields['P']);
- msg->cm_fields['P'] = newpath;
-
- /*
- * Determine if this message is set to be deleted
- * after sending out on the network
- */
- if (msg->cm_fields['S'] != NULL) {
- if (!strcasecmp(msg->cm_fields['S'], "CANCEL")) {
- delete_after_send = 1;
- }
- }
-
- /* Now send it to every node */
- if (sc->ignet_push_shares != NULL)
- for (mptr = sc->ignet_push_shares; mptr != NULL;
- mptr = mptr->next) {
-
- send = 1;
-
- /* Check for valid node name */
- if (is_valid_node(NULL, NULL, mptr->remote_nodename) != 0) {
- lprintf(CTDL_ERR, "Invalid node <%s>\n",
- mptr->remote_nodename);
- send = 0;
- }
-
- /* Check for split horizon */
- lprintf(CTDL_DEBUG, "Path is %s\n", msg->cm_fields['P']);
- bang = num_tokens(msg->cm_fields['P'], '!');
- if (bang > 1) for (i=0; i<(bang-1); ++i) {
- extract_token(buf, msg->cm_fields['P'],
- i, '!', sizeof buf);
- if (!strcasecmp(buf, mptr->remote_nodename)) {
- send = 0;
- }
- }
-
- /* Send the message */
- if (send == 1) {
-
- /*
- * Force the message to appear in the correct room
- * on the far end by setting the C field correctly
- */
- if (msg->cm_fields['C'] != NULL) {
- free(msg->cm_fields['C']);
- }
- if (strlen(mptr->remote_roomname) > 0) {
- msg->cm_fields['C'] = strdup(mptr->remote_roomname);
- }
- else {
- msg->cm_fields['C'] = strdup(CC->room.QRname);
- }
-
- /* serialize it for transmission */
- serialize_message(&sermsg, msg);
- if (sermsg.len > 0) {
-
- /* write it to the spool file */
- snprintf(filename, sizeof filename,"%s/%s",
- ctdl_netout_dir,
- mptr->remote_nodename);
- lprintf(CTDL_DEBUG, "Appending to %s\n", filename);
- fp = fopen(filename, "ab");
- if (fp != NULL) {
- fwrite(sermsg.ser,
- sermsg.len, 1, fp);
- fclose(fp);
- }
- else {
- lprintf(CTDL_ERR, "%s: %s\n", filename, strerror(errno));
- }
-
- /* free the serialized version */
- free(sermsg.ser);
- }
-
- }
- }
- CtdlFreeMessage(msg);
- }
-
- /* update lastsent */
- sc->lastsent = msgnum;
-
- /* Delete this message if delete-after-send is set */
- if (delete_after_send) {
- CtdlDeleteMessages(CC->room.QRname, &msgnum, 1, "");
- }
-
-}
-
-
-/*
- * Batch up and send all outbound traffic from the current room
- */
-void network_spoolout_room(char *room_to_spool) {
- char filename[SIZ];
- char buf[SIZ];
- char instr[SIZ];
- char nodename[256];
- char roomname[ROOMNAMELEN];
- char nexthop[256];
- FILE *fp;
- struct SpoolControl sc;
- struct namelist *nptr = NULL;
- struct maplist *mptr = NULL;
- size_t miscsize = 0;
- size_t linesize = 0;
- int skipthisline = 0;
- int i;
-
- /*
- * If the room doesn't exist, don't try to perform its networking tasks.
- * Normally this should never happen, but once in a while maybe a room gets
- * queued for networking and then deleted before it can happen.
- */
- if (getroom(&CC->room, room_to_spool) != 0) {
- lprintf(CTDL_CRIT, "ERROR: cannot load <%s>\n", room_to_spool);
- return;
- }
-
- memset(&sc, 0, sizeof(struct SpoolControl));
- assoc_file_name(filename, sizeof filename, &CC->room, ctdl_netcfg_dir);
-
- begin_critical_section(S_NETCONFIGS);
-
- /* Only do net processing for rooms that have netconfigs */
- fp = fopen(filename, "r");
- if (fp == NULL) {
- end_critical_section(S_NETCONFIGS);
- return;
- }
-
- lprintf(CTDL_INFO, "Networking started for <%s>\n", CC->room.QRname);
-
- while (fgets(buf, sizeof buf, fp) != NULL) {
- buf[strlen(buf)-1] = 0;
-
- extract_token(instr, buf, 0, '|', sizeof instr);
- if (!strcasecmp(instr, "lastsent")) {
- sc.lastsent = extract_long(buf, 1);
- }
- else if (!strcasecmp(instr, "listrecp")) {
- nptr = (struct namelist *)
- malloc(sizeof(struct namelist));
- nptr->next = sc.listrecps;
- extract_token(nptr->name, buf, 1, '|', sizeof nptr->name);
- sc.listrecps = nptr;
- }
- else if (!strcasecmp(instr, "participate")) {
- nptr = (struct namelist *)
- malloc(sizeof(struct namelist));
- nptr->next = sc.participates;
- extract_token(nptr->name, buf, 1, '|', sizeof nptr->name);
- sc.participates = nptr;
- }
- else if (!strcasecmp(instr, "digestrecp")) {
- nptr = (struct namelist *)
- malloc(sizeof(struct namelist));
- nptr->next = sc.digestrecps;
- extract_token(nptr->name, buf, 1, '|', sizeof nptr->name);
- sc.digestrecps = nptr;
- }
- else if (!strcasecmp(instr, "ignet_push_share")) {
- /* by checking each node's validity, we automatically
- * purge nodes which do not exist from room network
- * configurations at this time.
- */
- extract_token(nodename, buf, 1, '|', sizeof nodename);
- extract_token(roomname, buf, 2, '|', sizeof roomname);
- strcpy(nexthop, "xxx");
- if (is_valid_node(nexthop, NULL, nodename) == 0) {
- if (strlen(nexthop) == 0) {
- mptr = (struct maplist *)
- malloc(sizeof(struct maplist));
- mptr->next = sc.ignet_push_shares;
- strcpy(mptr->remote_nodename, nodename);
- strcpy(mptr->remote_roomname, roomname);
- sc.ignet_push_shares = mptr;
- }
- }
- }
- else {
- /* Preserve 'other' lines ... *unless* they happen to
- * be subscribe/unsubscribe pendings with expired
- * timestamps.
- */
- skipthisline = 0;
- if (!strncasecmp(buf, "subpending|", 11)) {
- if (time(NULL) - extract_long(buf, 4) > EXP) {
- skipthisline = 1;
- }
- }
- if (!strncasecmp(buf, "unsubpending|", 13)) {
- if (time(NULL) - extract_long(buf, 3) > EXP) {
- skipthisline = 1;
- }
- }
-
- if (skipthisline == 0) {
- linesize = strlen(buf);
- sc.misc = realloc(sc.misc,
- (miscsize + linesize + 2) );
- sprintf(&sc.misc[miscsize], "%s\n", buf);
- miscsize = miscsize + linesize + 1;
- }
- }
-
-
- }
- fclose(fp);
-
- /* If there are digest recipients, we have to build a digest */
- if (sc.digestrecps != NULL) {
- sc.digestfp = tmpfile();
- fprintf(sc.digestfp, "Content-type: text/plain\n\n");
- }
-
- /* Do something useful */
- CtdlForEachMessage(MSGS_GT, sc.lastsent, NULL, NULL, NULL,
- network_spool_msg, &sc);
-
- /* If we wrote a digest, deliver it and then close it */
- snprintf(buf, sizeof buf, "room_%s@%s",
- CC->room.QRname, config.c_fqdn);
- for (i=0; i<strlen(buf); ++i) {
- buf[i] = tolower(buf[i]);
- if (isspace(buf[i])) buf[i] = '_';
- }
- if (sc.digestfp != NULL) {
- fprintf(sc.digestfp, " -----------------------------------"
- "------------------------------------"
- "-------\n"
- "You are subscribed to the '%s' "
- "list.\n"
- "To post to the list: %s\n",
- CC->room.QRname, buf
- );
- network_deliver_digest(&sc); /* deliver and close */
- }
-
- /* Now rewrite the config file */
- fp = fopen(filename, "w");
- if (fp == NULL) {
- lprintf(CTDL_CRIT, "ERROR: cannot open %s: %s\n",
- filename, strerror(errno));
- }
- else {
- fprintf(fp, "lastsent|%ld\n", sc.lastsent);
-
- /* Write out the listrecps while freeing from memory at the
- * same time. Am I clever or what? :)
- */
- while (sc.listrecps != NULL) {
- fprintf(fp, "listrecp|%s\n", sc.listrecps->name);
- nptr = sc.listrecps->next;
- free(sc.listrecps);
- sc.listrecps = nptr;
- }
- /* Do the same for digestrecps */
- while (sc.digestrecps != NULL) {
- fprintf(fp, "digestrecp|%s\n", sc.digestrecps->name);
- nptr = sc.digestrecps->next;
- free(sc.digestrecps);
- sc.digestrecps = nptr;
- }
- /* Do the same for participates */
- while (sc.participates != NULL) {
- fprintf(fp, "participate|%s\n", sc.participates->name);
- nptr = sc.participates->next;
- free(sc.participates);
- sc.participates = nptr;
- }
- while (sc.ignet_push_shares != NULL) {
- /* by checking each node's validity, we automatically
- * purge nodes which do not exist from room network
- * configurations at this time.
- */
- if (is_valid_node(NULL, NULL, sc.ignet_push_shares->remote_nodename) == 0) {
- }
- fprintf(fp, "ignet_push_share|%s",
- sc.ignet_push_shares->remote_nodename);
- if (strlen(sc.ignet_push_shares->remote_roomname) > 0) {
- fprintf(fp, "|%s", sc.ignet_push_shares->remote_roomname);
- }
- fprintf(fp, "\n");
- mptr = sc.ignet_push_shares->next;
- free(sc.ignet_push_shares);
- sc.ignet_push_shares = mptr;
- }
- if (sc.misc != NULL) {
- fwrite(sc.misc, strlen(sc.misc), 1, fp);
- }
- free(sc.misc);
-
- fclose(fp);
- }
- end_critical_section(S_NETCONFIGS);
-}
-
-
-
-/*
- * Send the *entire* contents of the current room to one specific network node,
- * ignoring anything we know about which messages have already undergone
- * network processing. This can be used to bring a new node into sync.
- */
-int network_sync_to(char *target_node) {
- struct SpoolControl sc;
- int num_spooled = 0;
- int found_node = 0;
- char buf[256];
- char sc_type[256];
- char sc_node[256];
- char sc_room[256];
- char filename[256];
- FILE *fp;
-
- /* Grab the configuration line we're looking for */
- assoc_file_name(filename, sizeof filename, &CC->room, ctdl_netcfg_dir);
- begin_critical_section(S_NETCONFIGS);
- fp = fopen(filename, "r");
- if (fp == NULL) {
- end_critical_section(S_NETCONFIGS);
- return(-1);
- }
- while (fgets(buf, sizeof buf, fp) != NULL) {
- buf[strlen(buf)-1] = 0;
- extract_token(sc_type, buf, 0, '|', sizeof sc_type);
- extract_token(sc_node, buf, 1, '|', sizeof sc_node);
- extract_token(sc_room, buf, 2, '|', sizeof sc_room);
- if ( (!strcasecmp(sc_type, "ignet_push_share"))
- && (!strcasecmp(sc_node, target_node)) ) {
- found_node = 1;
-
- /* Concise syntax because we don't need a full linked-list */
- memset(&sc, 0, sizeof(struct SpoolControl));
- sc.ignet_push_shares = (struct maplist *)
- malloc(sizeof(struct maplist));
- sc.ignet_push_shares->next = NULL;
- safestrncpy(sc.ignet_push_shares->remote_nodename,
- sc_node,
- sizeof sc.ignet_push_shares->remote_nodename);
- safestrncpy(sc.ignet_push_shares->remote_roomname,
- sc_room,
- sizeof sc.ignet_push_shares->remote_roomname);
- }
- }
- fclose(fp);
- end_critical_section(S_NETCONFIGS);
-
- if (!found_node) return(-1);
-
- /* Send ALL messages */
- num_spooled = CtdlForEachMessage(MSGS_ALL, 0L, NULL, NULL, NULL,
- network_spool_msg, &sc);
-
- /* Concise cleanup because we know there's only one node in the sc */
- free(sc.ignet_push_shares);
-
- lprintf(CTDL_NOTICE, "Synchronized %d messages to <%s>\n",
- num_spooled, target_node);
- return(num_spooled);
-}
-
-
-/*
- * Implements the NSYN command
- */
-void cmd_nsyn(char *argbuf) {
- int num_spooled;
- char target_node[256];
-
- if (CtdlAccessCheck(ac_aide)) return;
-
- extract_token(target_node, argbuf, 0, '|', sizeof target_node);
- num_spooled = network_sync_to(target_node);
- if (num_spooled >= 0) {
- cprintf("%d Spooled %d messages.\n", CIT_OK, num_spooled);
- }
- else {
- cprintf("%d No such room/node share exists.\n",
- ERROR + ROOM_NOT_FOUND);
- }
-}
-
-
-
-/*
- * Batch up and send all outbound traffic from the current room
- */
-void network_queue_room(struct ctdlroom *qrbuf, void *data) {
- struct RoomProcList *ptr;
-
- ptr = (struct RoomProcList *) malloc(sizeof (struct RoomProcList));
- if (ptr == NULL) return;
-
- safestrncpy(ptr->name, qrbuf->QRname, sizeof ptr->name);
- begin_critical_section(S_RPLIST);
- ptr->next = rplist;
- rplist = ptr;
- end_critical_section(S_RPLIST);
-}
-
-void destroy_network_queue_room(void)
-{
- struct RoomProcList *cur, *p;
- struct NetMap *nmcur, *nmp;
-
- cur = rplist;
- begin_critical_section(S_RPLIST);
- while (cur != NULL)
- {
- p = cur->next;
- free (cur);
- cur = p;
- }
- rplist = NULL;
- end_critical_section(S_RPLIST);
-
- nmcur = the_netmap;
- while (nmcur != NULL)
- {
- nmp = nmcur->next;
- free (nmcur);
- nmcur = nmp;
- }
- the_netmap = NULL;
- if (working_ignetcfg != NULL)
- free (working_ignetcfg);
- working_ignetcfg = NULL;
-}
-
-
-/*
- * Learn topology from path fields
- */
-void network_learn_topology(char *node, char *path) {
- char nexthop[256];
- struct NetMap *nmptr;
-
- strcpy(nexthop, "");
-
- if (num_tokens(path, '!') < 3) return;
- for (nmptr = the_netmap; nmptr != NULL; nmptr = nmptr->next) {
- if (!strcasecmp(nmptr->nodename, node)) {
- extract_token(nmptr->nexthop, path, 0, '!', sizeof nmptr->nexthop);
- nmptr->lastcontact = time(NULL);
- ++netmap_changed;
- return;
- }
- }
-
- /* If we got here then it's not in the map, so add it. */
- nmptr = (struct NetMap *) malloc(sizeof (struct NetMap));
- strcpy(nmptr->nodename, node);
- nmptr->lastcontact = time(NULL);
- extract_token(nmptr->nexthop, path, 0, '!', sizeof nmptr->nexthop);
- nmptr->next = the_netmap;
- the_netmap = nmptr;
- ++netmap_changed;
-}
-
-
-
-
-/*
- * Bounce a message back to the sender
- */
-void network_bounce(struct CtdlMessage *msg, char *reason) {
- char *oldpath = NULL;
- char buf[SIZ];
- char bouncesource[SIZ];
- char recipient[SIZ];
- struct recptypes *valid = NULL;
- char force_room[ROOMNAMELEN];
- static int serialnum = 0;
- size_t size;
-
- lprintf(CTDL_DEBUG, "entering network_bounce()\n");
-
- if (msg == NULL) return;
-
- snprintf(bouncesource, sizeof bouncesource, "%s@%s", BOUNCESOURCE, config.c_nodename);
-
- /*
- * Give it a fresh message ID
- */
- if (msg->cm_fields['I'] != NULL) {
- free(msg->cm_fields['I']);
- }
- snprintf(buf, sizeof buf, "%ld.%04lx.%04x@%s",
- (long)time(NULL), (long)getpid(), ++serialnum, config.c_fqdn);
- msg->cm_fields['I'] = strdup(buf);
-
- /*
- * FIXME ... right now we're just sending a bounce; we really want to
- * include the text of the bounced message.
- */
- if (msg->cm_fields['M'] != NULL) {
- free(msg->cm_fields['M']);
- }
- msg->cm_fields['M'] = strdup(reason);
- msg->cm_format_type = 0;
-
- /*
- * Turn the message around
- */
- if (msg->cm_fields['R'] == NULL) {
- free(msg->cm_fields['R']);
- }
-
- if (msg->cm_fields['D'] == NULL) {
- free(msg->cm_fields['D']);
- }
-
- snprintf(recipient, sizeof recipient, "%s@%s",
- msg->cm_fields['A'], msg->cm_fields['N']);
-
- if (msg->cm_fields['A'] == NULL) {
- free(msg->cm_fields['A']);
- }
-
- if (msg->cm_fields['N'] == NULL) {
- free(msg->cm_fields['N']);
- }
-
- if (msg->cm_fields['U'] == NULL) {
- free(msg->cm_fields['U']);
- }
-
- msg->cm_fields['A'] = strdup(BOUNCESOURCE);
- msg->cm_fields['N'] = strdup(config.c_nodename);
- msg->cm_fields['U'] = strdup("Delivery Status Notification (Failure)");
-
- /* prepend our node to the path */
- if (msg->cm_fields['P'] != NULL) {
- oldpath = msg->cm_fields['P'];
- msg->cm_fields['P'] = NULL;
- }
- else {
- oldpath = strdup("unknown_user");
- }
- size = strlen(oldpath) + SIZ;
- msg->cm_fields['P'] = malloc(size);
- snprintf(msg->cm_fields['P'], size, "%s!%s", config.c_nodename, oldpath);
- free(oldpath);
-
- /* Now submit the message */
- valid = validate_recipients(recipient);
- if (valid != NULL) if (valid->num_error != 0) {
- free_recipients(valid);
- valid = NULL;
- }
- if ( (valid == NULL) || (!strcasecmp(recipient, bouncesource)) ) {
- strcpy(force_room, config.c_aideroom);
- }
- else {
- strcpy(force_room, "");
- }
- if ( (valid == NULL) && (strlen(force_room) == 0) ) {
- strcpy(force_room, config.c_aideroom);
- }
- CtdlSubmitMsg(msg, valid, force_room);
-
- /* Clean up */
- if (valid != NULL) free_recipients(valid);
- CtdlFreeMessage(msg);
- lprintf(CTDL_DEBUG, "leaving network_bounce()\n");
-}
-
-
-
-
-/*
- * Process a buffer containing a single message from a single file
- * from the inbound queue
- */
-void network_process_buffer(char *buffer, long size) {
- struct CtdlMessage *msg = NULL;
- long pos;
- int field;
- struct recptypes *recp = NULL;
- char target_room[ROOMNAMELEN];
- struct ser_ret sermsg;
- char *oldpath = NULL;
- char filename[SIZ];
- FILE *fp;
- char nexthop[SIZ];
- unsigned char firstbyte;
- unsigned char lastbyte;
-
- /* Validate just a little bit. First byte should be FF and
- * last byte should be 00.
- */
- memcpy(&firstbyte, &buffer[0], 1);
- memcpy(&lastbyte, &buffer[size-1], 1);
- if ( (firstbyte != 255) || (lastbyte != 0) ) {
- lprintf(CTDL_ERR, "Corrupt message! Ignoring.\n");
- return;
- }
-
- /* Set default target room to trash */
- strcpy(target_room, TWITROOM);
-
- /* Load the message into memory */
- msg = (struct CtdlMessage *) malloc(sizeof(struct CtdlMessage));
- memset(msg, 0, sizeof(struct CtdlMessage));
- msg->cm_magic = CTDLMESSAGE_MAGIC;
- msg->cm_anon_type = buffer[1];
- msg->cm_format_type = buffer[2];
-
- for (pos = 3; pos < size; ++pos) {
- field = buffer[pos];
- msg->cm_fields[field] = strdup(&buffer[pos+1]);
- pos = pos + strlen(&buffer[(int)pos]);
- }
-
- /* Check for message routing */
- if (msg->cm_fields['D'] != NULL) {
- if (strcasecmp(msg->cm_fields['D'], config.c_nodename)) {
-
- /* route the message */
- strcpy(nexthop, "");
- if (is_valid_node(nexthop, NULL,
- msg->cm_fields['D']) == 0) {
-
- /* prepend our node to the path */
- if (msg->cm_fields['P'] != NULL) {
- oldpath = msg->cm_fields['P'];
- msg->cm_fields['P'] = NULL;
- }
- else {
- oldpath = strdup("unknown_user");
- }
- size = strlen(oldpath) + SIZ;
- msg->cm_fields['P'] = malloc(size);
- snprintf(msg->cm_fields['P'], size, "%s!%s",
- config.c_nodename, oldpath);
- free(oldpath);
-
- /* serialize the message */
- serialize_message(&sermsg, msg);
-
- /* now send it */
- if (strlen(nexthop) == 0) {
- strcpy(nexthop, msg->cm_fields['D']);
- }
- snprintf(filename,
- sizeof filename,
- "%s/%s",
- ctdl_netout_dir,
- nexthop);
- lprintf(CTDL_DEBUG, "Appending to %s\n", filename);
- fp = fopen(filename, "ab");
- if (fp != NULL) {
- fwrite(sermsg.ser,
- sermsg.len, 1, fp);
- fclose(fp);
- }
- else {
- lprintf(CTDL_ERR, "%s: %s\n", filename, strerror(errno));
- }
- free(sermsg.ser);
- CtdlFreeMessage(msg);
- return;
- }
-
- else { /* invalid destination node name */
-
- network_bounce(msg,
-"A message you sent could not be delivered due to an invalid destination node"
-" name. Please check the address and try sending the message again.\n");
- msg = NULL;
- return;
-
- }
- }
- }
-
- /*
- * Check to see if we already have a copy of this message, and
- * abort its processing if so. (We used to post a warning to Aide>
- * every time this happened, but the network is now so densely
- * connected that it's inevitable.)
- */
- if (network_usetable(msg) != 0) {
- return;
- }
-
- /* Learn network topology from the path */
- if ((msg->cm_fields['N'] != NULL) && (msg->cm_fields['P'] != NULL)) {
- network_learn_topology(msg->cm_fields['N'],
- msg->cm_fields['P']);
- }
-
- /* Is the sending node giving us a very persuasive suggestion about
- * which room this message should be saved in? If so, go with that.
- */
- if (msg->cm_fields['C'] != NULL) {
- safestrncpy(target_room,
- msg->cm_fields['C'],
- sizeof target_room);
- }
-
- /* Otherwise, does it have a recipient? If so, validate it... */
- else if (msg->cm_fields['R'] != NULL) {
- recp = validate_recipients(msg->cm_fields['R']);
- if (recp != NULL) if (recp->num_error != 0) {
- network_bounce(msg,
- "A message you sent could not be delivered due to an invalid address.\n"
- "Please check the address and try sending the message again.\n");
- msg = NULL;
- free_recipients(recp);
- return;
- }
- strcpy(target_room, ""); /* no target room if mail */
- }
-
- /* Our last shot at finding a home for this message is to see if
- * it has the O field (Originating room) set.
- */
- else if (msg->cm_fields['O'] != NULL) {
- safestrncpy(target_room,
- msg->cm_fields['O'],
- sizeof target_room);
- }
-
- /* Strip out fields that are only relevant during transit */
- if (msg->cm_fields['D'] != NULL) {
- free(msg->cm_fields['D']);
- msg->cm_fields['D'] = NULL;
- }
- if (msg->cm_fields['C'] != NULL) {
- free(msg->cm_fields['C']);
- msg->cm_fields['C'] = NULL;
- }
-
- /* save the message into a room */
- if (PerformNetprocHooks(msg, target_room) == 0) {
- msg->cm_flags = CM_SKIP_HOOKS;
- CtdlSubmitMsg(msg, recp, target_room);
- }
- CtdlFreeMessage(msg);
- free_recipients(recp);
-}
-
-
-/*
- * Process a single message from a single file from the inbound queue
- */
-void network_process_message(FILE *fp, long msgstart, long msgend) {
- long hold_pos;
- long size;
- char *buffer;
-
- hold_pos = ftell(fp);
- size = msgend - msgstart + 1;
- buffer = malloc(size);
- if (buffer != NULL) {
- fseek(fp, msgstart, SEEK_SET);
- fread(buffer, size, 1, fp);
- network_process_buffer(buffer, size);
- free(buffer);
- }
-
- fseek(fp, hold_pos, SEEK_SET);
-}
-
-
-/*
- * Process a single file from the inbound queue
- */
-void network_process_file(char *filename) {
- FILE *fp;
- long msgstart = (-1L);
- long msgend = (-1L);
- long msgcur = 0L;
- int ch;
-
-
- fp = fopen(filename, "rb");
- if (fp == NULL) {
- lprintf(CTDL_CRIT, "Error opening %s: %s\n",
- filename, strerror(errno));
- return;
- }
-
- lprintf(CTDL_INFO, "network: processing <%s>\n", filename);
-
- /* Look for messages in the data stream and break them out */
- while (ch = getc(fp), ch >= 0) {
-
- if (ch == 255) {
- if (msgstart >= 0L) {
- msgend = msgcur - 1;
- network_process_message(fp, msgstart, msgend);
- }
- msgstart = msgcur;
- }
-
- ++msgcur;
- }
-
- msgend = msgcur - 1;
- if (msgstart >= 0L) {
- network_process_message(fp, msgstart, msgend);
- }
-
- fclose(fp);
- unlink(filename);
-}
-
-
-/*
- * Process anything in the inbound queue
- */
-void network_do_spoolin(void) {
- DIR *dp;
- struct dirent *d;
- struct stat statbuf;
- char filename[256];
- static time_t last_spoolin_mtime = 0L;
-
- /*
- * Check the spoolin directory's modification time. If it hasn't
- * been touched, we don't need to scan it.
- */
- if (stat(ctdl_netin_dir, &statbuf)) return;
- if (statbuf.st_mtime == last_spoolin_mtime) {
- lprintf(CTDL_DEBUG, "network: nothing in inbound queue\n");
- return;
- }
- last_spoolin_mtime = statbuf.st_mtime;
- lprintf(CTDL_DEBUG, "network: processing inbound queue\n");
-
- /*
- * Ok, there's something interesting in there, so scan it.
- */
- dp = opendir(ctdl_netin_dir);
- if (dp == NULL) return;
-
- while (d = readdir(dp), d != NULL) {
- if ((strcmp(d->d_name, ".")) && (strcmp(d->d_name, ".."))) {
- snprintf(filename,
- sizeof filename,
- "%s/%s",
- ctdl_netin_dir,
- d->d_name);
- network_process_file(filename);
- }
- }
-
- closedir(dp);
-}
-
-/*
- * Delete any files in the outbound queue that were intended
- * to be sent to nodes which no longer exist.
- */
-void network_purge_spoolout(void) {
- DIR *dp;
- struct dirent *d;
- char filename[256];
- char nexthop[256];
- int i;
-
- dp = opendir(ctdl_netout_dir);
- if (dp == NULL) return;
-
- while (d = readdir(dp), d != NULL) {
- if (!strcmp(d->d_name, ".") || !strcmp(d->d_name, ".."))
- continue;
- snprintf(filename,
- sizeof filename,
- "%s/%s",
- ctdl_netout_dir,
- d->d_name);
-
- strcpy(nexthop, "");
- i = is_valid_node(nexthop, NULL, d->d_name);
-
- if ( (i != 0) || (strlen(nexthop) > 0) ) {
- unlink(filename);
- }
- }
-
-
- closedir(dp);
-}
-
-
-/*
- * receive network spool from the remote system
- */
-void receive_spool(int sock, char *remote_nodename) {
- long download_len;
- long bytes_received;
- char buf[SIZ];
- static char pbuf[IGNET_PACKET_SIZE];
- char tempfilename[PATH_MAX];
- long plen;
- FILE *fp;
-
- CtdlMakeTempFileName(tempfilename, sizeof tempfilename);
- if (sock_puts(sock, "NDOP") < 0) return;
- if (sock_gets(sock, buf) < 0) return;
- lprintf(CTDL_DEBUG, "<%s\n", buf);
- if (buf[0] != '2') {
- return;
- }
- download_len = extract_long(&buf[4], 0);
-
- bytes_received = 0L;
- fp = fopen(tempfilename, "w");
- if (fp == NULL) {
- lprintf(CTDL_CRIT, "cannot open download file locally: %s\n",
- strerror(errno));
- return;
- }
-
- while (bytes_received < download_len) {
- snprintf(buf, sizeof buf, "READ %ld|%ld",
- bytes_received,
- ((download_len - bytes_received > IGNET_PACKET_SIZE)
- ? IGNET_PACKET_SIZE : (download_len - bytes_received)));
- if (sock_puts(sock, buf) < 0) {
- fclose(fp);
- unlink(tempfilename);
- return;
- }
- if (sock_gets(sock, buf) < 0) {
- fclose(fp);
- unlink(tempfilename);
- return;
- }
- if (buf[0] == '6') {
- plen = extract_long(&buf[4], 0);
- if (sock_read(sock, pbuf, plen) < 0) {
- fclose(fp);
- unlink(tempfilename);
- return;
- }
- fwrite((char *) pbuf, plen, 1, fp);
- bytes_received = bytes_received + plen;
- }
- }
-
- fclose(fp);
- if (sock_puts(sock, "CLOS") < 0) {
- unlink(tempfilename);
- return;
- }
- if (sock_gets(sock, buf) < 0) {
- unlink(tempfilename);
- return;
- }
- if (download_len > 0)
- lprintf(CTDL_NOTICE, "Received %ld octets from <%s>",
- download_len, remote_nodename);
- lprintf(CTDL_DEBUG, "%s", buf);
- /* TODO: make move inline. forking is verry expensive. */
- snprintf(buf,
- sizeof buf,
- "mv %s %s/%s.%ld",
- tempfilename,
- ctdl_netin_dir,
- remote_nodename,
- (long) getpid());
- system(buf);
-}
-
-
-
-/*
- * transmit network spool to the remote system
- */
-void transmit_spool(int sock, char *remote_nodename)
-{
- char buf[SIZ];
- char pbuf[4096];
- long plen;
- long bytes_to_write, thisblock, bytes_written;
- int fd;
- char sfname[128];
-
- if (sock_puts(sock, "NUOP") < 0) return;
- if (sock_gets(sock, buf) < 0) return;
- lprintf(CTDL_DEBUG, "<%s\n", buf);
- if (buf[0] != '2') {
- return;
- }
-
- snprintf(sfname, sizeof sfname,
- "%s/%s",
- ctdl_netout_dir,
- remote_nodename);
- fd = open(sfname, O_RDONLY);
- if (fd < 0) {
- if (errno != ENOENT) {
- lprintf(CTDL_CRIT, "cannot open upload file locally: %s\n",
- strerror(errno));
- }
- return;
- }
- bytes_written = 0;
- while (plen = (long) read(fd, pbuf, IGNET_PACKET_SIZE), plen > 0L) {
- bytes_to_write = plen;
- while (bytes_to_write > 0L) {
- snprintf(buf, sizeof buf, "WRIT %ld", bytes_to_write);
- if (sock_puts(sock, buf) < 0) {
- close(fd);
- return;
- }
- if (sock_gets(sock, buf) < 0) {
- close(fd);
- return;
- }
- thisblock = atol(&buf[4]);
- if (buf[0] == '7') {
- if (sock_write(sock, pbuf,
- (int) thisblock) < 0) {
- close(fd);
- return;
- }
- bytes_to_write -= thisblock;
- bytes_written += thisblock;
- } else {
- goto ABORTUPL;
- }
- }
- }
-
-ABORTUPL:
- close(fd);
- if (sock_puts(sock, "UCLS 1") < 0) return;
- if (sock_gets(sock, buf) < 0) return;
- lprintf(CTDL_NOTICE, "Sent %ld octets to <%s>\n",
- bytes_written, remote_nodename);
- lprintf(CTDL_DEBUG, "<%s\n", buf);
- if (buf[0] == '2') {
- lprintf(CTDL_DEBUG, "Removing <%s>\n", sfname);
- unlink(sfname);
- }
-}
-
-
-
-/*
- * Poll one Citadel node (called by network_poll_other_citadel_nodes() below)
- */
-void network_poll_node(char *node, char *secret, char *host, char *port) {
- int sock;
- char buf[SIZ];
-
- if (network_talking_to(node, NTT_CHECK)) return;
- network_talking_to(node, NTT_ADD);
- lprintf(CTDL_NOTICE, "Connecting to <%s> at %s:%s\n", node, host, port);
-
- sock = sock_connect(host, port, "tcp");
- if (sock < 0) {
- lprintf(CTDL_ERR, "Could not connect: %s\n", strerror(errno));
- network_talking_to(node, NTT_REMOVE);
- return;
- }
-
- lprintf(CTDL_DEBUG, "Connected!\n");
-
- /* Read the server greeting */
- if (sock_gets(sock, buf) < 0) goto bail;
- lprintf(CTDL_DEBUG, ">%s\n", buf);
-
- /* Identify ourselves */
- snprintf(buf, sizeof buf, "NETP %s|%s", config.c_nodename, secret);
- lprintf(CTDL_DEBUG, "<%s\n", buf);
- if (sock_puts(sock, buf) <0) goto bail;
- if (sock_gets(sock, buf) < 0) goto bail;
- lprintf(CTDL_DEBUG, ">%s\n", buf);
- if (buf[0] != '2') goto bail;
-
- /* At this point we are authenticated. */
- receive_spool(sock, node);
- transmit_spool(sock, node);
-
- sock_puts(sock, "QUIT");
-bail: sock_close(sock);
- network_talking_to(node, NTT_REMOVE);
-}
-
-
-
-/*
- * Poll other Citadel nodes and transfer inbound/outbound network data.
- * Set "full" to nonzero to force a poll of every node, or to zero to poll
- * only nodes to which we have data to send.
- */
-void network_poll_other_citadel_nodes(int full_poll) {
- int i;
- char linebuf[256];
- char node[SIZ];
- char host[256];
- char port[256];
- char secret[256];
- int poll = 0;
- char spoolfile[256];
-
- if (working_ignetcfg == NULL) {
- lprintf(CTDL_DEBUG, "No nodes defined - not polling\n");
- return;
- }
-
- /* Use the string tokenizer to grab one line at a time */
- for (i=0; i<num_tokens(working_ignetcfg, '\n'); ++i) {
- extract_token(linebuf, working_ignetcfg, i, '\n', sizeof linebuf);
- extract_token(node, linebuf, 0, '|', sizeof node);
- extract_token(secret, linebuf, 1, '|', sizeof secret);
- extract_token(host, linebuf, 2, '|', sizeof host);
- extract_token(port, linebuf, 3, '|', sizeof port);
- if ( (strlen(node) > 0) && (strlen(secret) > 0)
- && (strlen(host) > 0) && strlen(port) > 0) {
- poll = full_poll;
- if (poll == 0) {
- snprintf(spoolfile,
- sizeof spoolfile,
- "%s/%s",
- ctdl_netout_dir,
- node);
- if (access(spoolfile, R_OK) == 0) {
- poll = 1;
- }
- }
- if (poll) {
- network_poll_node(node, secret, host, port);
- }
- }
- }
-
-}
-
-
-
-
-/*
- * It's ok if these directories already exist. Just fail silently.
- */
-void create_spool_dirs(void) {
- mkdir(ctdl_spool_dir, 0700);
- chown(ctdl_spool_dir, CTDLUID, (-1));
- mkdir(ctdl_netin_dir, 0700);
- chown(ctdl_netin_dir, CTDLUID, (-1));
- mkdir(ctdl_netout_dir, 0700);
- chown(ctdl_netout_dir, CTDLUID, (-1));
-}
-
-
-
-
-
-/*
- * network_do_queue()
- *
- * Run through the rooms doing various types of network stuff.
- */
-void network_do_queue(void) {
- static time_t last_run = 0L;
- struct RoomProcList *ptr;
- int full_processing = 1;
-
- /*
- * Run the full set of processing tasks no more frequently
- * than once every n seconds
- */
- if ( (time(NULL) - last_run) < config.c_net_freq ) {
- full_processing = 0;
- }
-
- /*
- * This is a simple concurrency check to make sure only one queue run
- * is done at a time. We could do this with a mutex, but since we
- * don't really require extremely fine granularity here, we'll do it
- * with a static variable instead.
- */
- if (doing_queue) return;
- doing_queue = 1;
-
- /* Load the IGnet Configuration into memory */
- load_working_ignetcfg();
-
- /*
- * Poll other Citadel nodes. Maybe. If "full_processing" is set
- * then we poll everyone. Otherwise we only poll nodes we have stuff
- * to send to.
- */
- network_poll_other_citadel_nodes(full_processing);
-
- /*
- * Load the network map and filter list into memory.
- */
- read_network_map();
- filterlist = load_filter_list();
-
- /*
- * Go ahead and run the queue
- */
- if (full_processing) {
- lprintf(CTDL_DEBUG, "network: loading outbound queue\n");
- ForEachRoom(network_queue_room, NULL);
- }
-
- if (rplist != NULL) {
- lprintf(CTDL_DEBUG, "network: running outbound queue\n");
- while (rplist != NULL) {
- char spoolroomname[ROOMNAMELEN];
- safestrncpy(spoolroomname, rplist->name, sizeof spoolroomname);
- begin_critical_section(S_RPLIST);
-
- /* pop this record off the list */
- ptr = rplist;
- rplist = rplist->next;
- free(ptr);
-
- /* invalidate any duplicate entries to prevent double processing */
- for (ptr=rplist; ptr!=NULL; ptr=ptr->next) {
- if (!strcasecmp(ptr->name, spoolroomname)) {
- ptr->name[0] = 0;
- }
- }
-
- end_critical_section(S_RPLIST);
- if (spoolroomname[0] != 0) {
- network_spoolout_room(spoolroomname);
- }
- }
- }
-
- /* If there is anything in the inbound queue, process it */
- network_do_spoolin();
-
- /* Save the network map back to disk */
- write_network_map();
-
- /* Free the filter list in memory */
- free_filter_list(filterlist);
- filterlist = NULL;
-
- network_purge_spoolout();
-
- lprintf(CTDL_DEBUG, "network: queue run completed\n");
-
- if (full_processing) {
- last_run = time(NULL);
- }
-
- doing_queue = 0;
-}
-
-
-/*
- * cmd_netp() - authenticate to the server as another Citadel node polling
- * for network traffic
- */
-void cmd_netp(char *cmdbuf)
-{
- char node[256];
- char pass[256];
- int v;
-
- char secret[256];
- char nexthop[256];
-
- /* Authenticate */
- extract_token(node, cmdbuf, 0, '|', sizeof node);
- extract_token(pass, cmdbuf, 1, '|', sizeof pass);
-
- if (doing_queue) {
- lprintf(CTDL_WARNING, "Network node <%s> refused - spooling", node);
- cprintf("%d spooling - try again in a few minutes\n",
- ERROR + RESOURCE_BUSY);
- return;
- }
-
- /* load the IGnet Configuration to check node validity */
- load_working_ignetcfg();
- v = is_valid_node(nexthop, secret, node);
-
- if (v != 0) {
- lprintf(CTDL_WARNING, "Unknown node <%s>\n", node);
- cprintf("%d authentication failed\n",
- ERROR + PASSWORD_REQUIRED);
- return;
- }
-
- if (strcasecmp(pass, secret)) {
- lprintf(CTDL_WARNING, "Bad password for network node <%s>", node);
- cprintf("%d authentication failed\n", ERROR + PASSWORD_REQUIRED);
- return;
- }
-
- if (network_talking_to(node, NTT_CHECK)) {
- lprintf(CTDL_WARNING, "Duplicate session for network node <%s>", node);
- cprintf("%d Already talking to %s right now\n", ERROR + RESOURCE_BUSY, node);
- return;
- }
-
- safestrncpy(CC->net_node, node, sizeof CC->net_node);
- network_talking_to(node, NTT_ADD);
- lprintf(CTDL_NOTICE, "Network node <%s> logged in\n", CC->net_node);
- cprintf("%d authenticated as network node '%s'\n", CIT_OK,
- CC->net_node);
-}
-
-int network_room_handler (struct ctdlroom *room)
-{
- network_queue_room(room, NULL);
- return 0;
-}
-
-/*
- * Module entry point
- */
-CTDL_MODULE_INIT(network)
-{
- create_spool_dirs();
- CtdlRegisterProtoHook(cmd_gnet, "GNET", "Get network config");
- CtdlRegisterProtoHook(cmd_snet, "SNET", "Set network config");
- CtdlRegisterProtoHook(cmd_netp, "NETP", "Identify as network poller");
- CtdlRegisterProtoHook(cmd_nsyn, "NSYN", "Synchronize room to node");
- CtdlRegisterSessionHook(network_do_queue, EVT_TIMER);
- CtdlRegisterRoomHook(network_room_handler);
-
- /* return our Subversion id for the Log */
- return "$Id$";
-}
+++ /dev/null
-/*
- * $Id$
- *
- * Automaticalyl copies the contents of a "New User Greetings" room to the
- * inbox of any new user upon account creation.
- *
- */
-
-/*
- * Name of the New User Greetings room.
- */
-#define NEWUSERGREETINGS "New User Greetings"
-
-
-#include "sysdep.h"
-#include <stdlib.h>
-#include <unistd.h>
-#include <stdio.h>
-#include <fcntl.h>
-#include <signal.h>
-#include <pwd.h>
-#include <errno.h>
-#include <sys/types.h>
-
-#if TIME_WITH_SYS_TIME
-# include <sys/time.h>
-# include <time.h>
-#else
-# if HAVE_SYS_TIME_H
-# include <sys/time.h>
-# else
-# include <time.h>
-# endif
-#endif
-
-#include <sys/wait.h>
-#include <string.h>
-#include <limits.h>
-#include "citadel.h"
-#include "server.h"
-#include "citserver.h"
-#include "support.h"
-#include "config.h"
-#include "room_ops.h"
-#include "user_ops.h"
-#include "policy.h"
-#include "database.h"
-#include "msgbase.h"
-
-
-#include "ctdl_module.h"
-
-
-
-extern struct CitContext *ContextList;
-
-
-/*
- * Copy the contents of the New User Greetings> room to the user's Mail> room.
- */
-void CopyNewUserGreetings(void) {
- struct cdbdata *cdbfr;
- long *msglist = NULL;
- int num_msgs = 0;
- char mailboxname[ROOMNAMELEN];
-
-
- /* Only do this for new users. */
- if (CC->user.timescalled != 1) return;
-
- /* This user's mailbox. */
- MailboxName(mailboxname, sizeof mailboxname, &CC->user, MAILROOM);
-
- /* Go to the source room ... bail out silently if it's not there,
- * or if it's not private.
- */
- if (getroom(&CC->room, NEWUSERGREETINGS) != 0) return;
- if (! CC->room.QRflags & QR_PRIVATE ) return;
-
- cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long));
-
- if (cdbfr != NULL) {
- msglist = malloc(cdbfr->len);
- memcpy(msglist, cdbfr->ptr, cdbfr->len);
- num_msgs = cdbfr->len / sizeof(long);
- cdb_free(cdbfr);
- }
-
- if (num_msgs > 0) {
- CtdlCopyMsgsToRoom(msglist, num_msgs, mailboxname);
- }
-
- /* Now free the memory we used, and go away. */
- if (msglist != NULL) free(msglist);
-}
-
-
-CTDL_MODULE_INIT(newuser)
-{
- CtdlRegisterSessionHook(CopyNewUserGreetings, EVT_LOGIN);
-
- /* return our Subversion id for the Log */
- return "$Id$";
-}
+++ /dev/null
-/*
- * $Id$
- *
- * Handles functions related to yellow sticky notes.
- *
- */
-
-#include "sysdep.h"
-#include <stdlib.h>
-#include <unistd.h>
-#include <stdio.h>
-#include <fcntl.h>
-#include <signal.h>
-#include <pwd.h>
-#include <errno.h>
-#include <sys/types.h>
-
-#if TIME_WITH_SYS_TIME
-# include <sys/time.h>
-# include <time.h>
-#else
-# if HAVE_SYS_TIME_H
-# include <sys/time.h>
-# else
-# include <time.h>
-# endif
-#endif
-
-#include <sys/wait.h>
-#include <string.h>
-#include <limits.h>
-#include "citadel.h"
-#include "server.h"
-#include "citserver.h"
-#include "support.h"
-#include "config.h"
-#include "room_ops.h"
-#include "user_ops.h"
-#include "policy.h"
-#include "database.h"
-#include "msgbase.h"
-#include "tools.h"
-
-#include "ctdl_module.h"
-
-
-
-/*
- * If we are in a "notes" view room, and the client has sent an RFC822
- * message containing an X-KOrg-Note-Id: field (Aethera does this, as
- * do some Kolab clients) then set both the Subject and the Exclusive ID
- * of the message to that. It's going to be a UUID so we want to replace
- * any existing message containing that UUID.
- */
-int serv_notes_beforesave(struct CtdlMessage *msg)
-{
- char *p;
- int a, i;
- char uuid[SIZ];
-
- /* First determine if this room has the "notes" view set */
-
- if (CC->room.QRdefaultview != VIEW_NOTES) {
- return(0); /* not notes; do nothing */
- }
-
- /* It must be an RFC822 message! */
- if (msg->cm_format_type != 4) {
- return(0); /* You tried to save a non-RFC822 message! */
- }
-
- /* Find the X-KOrg-Note-Id: header */
- strcpy(uuid, "");
- p = msg->cm_fields['M'];
- a = strlen(p);
- while (--a > 0) {
- if (!strncasecmp(p, "X-KOrg-Note-Id: ", 16)) { /* Found it */
- safestrncpy(uuid, p + 16, sizeof(uuid));
- for (i = 0; i<strlen(uuid); ++i) {
- if ( (uuid[i] == '\r') || (uuid[i] == '\n') ) {
- uuid[i] = 0;
- }
- }
-
- lprintf(9, "UUID of note is: %s\n", uuid);
- if (strlen(uuid) > 0) {
-
- if (msg->cm_fields['E'] != NULL) {
- free(msg->cm_fields['E']);
- }
- msg->cm_fields['E'] = strdup(uuid);
-
- if (msg->cm_fields['U'] != NULL) {
- free(msg->cm_fields['U']);
- }
- msg->cm_fields['U'] = strdup(uuid);
- }
- }
- p++;
- }
-
- return(0);
-}
-
-
-CTDL_MODULE_INIT(notes)
-{
- CtdlRegisterMessageHook(serv_notes_beforesave, EVT_BEFORESAVE);
-
- /* return our Subversion id for the Log */
- return "$Id$";
-}
+++ /dev/null
-/*
- * cmd_pas2 - MD5 APOP style auth keyed off of the hash of the password
- * plus a nonce displayed at the login banner.
- */
-
-#include <stdlib.h>
-#include <unistd.h>
-#include <stdio.h>
-
-#if TIME_WITH_SYS_TIME
-# include <sys/time.h>
-# include <time.h>
-#else
-# if HAVE_SYS_TIME_H
-# include <sys/time.h>
-# else
-# include <time.h>
-# endif
-#endif
-
-#include <ctype.h>
-#include <string.h>
-#include <errno.h>
-#include "citadel.h"
-#include "server.h"
-#include "citserver.h"
-#include "support.h"
-#include "user_ops.h"
-#include "md5.h"
-#include "tools.h"
-
-
-#include "ctdl_module.h"
-
-
-void cmd_pas2(char *argbuf)
-{
- char pw[256];
- char hexstring[MD5_HEXSTRING_SIZE];
-
-
- if (!strcmp(CC->curr_user, NLI))
- {
- cprintf("%d You must enter a user with the USER command first.\n", ERROR + USERNAME_REQUIRED);
- return;
- }
-
- if (CC->logged_in)
- {
- cprintf("%d Already logged in.\n", ERROR + ALREADY_LOGGED_IN);
- return;
- }
-
- extract_token(pw, argbuf, 0, '|', sizeof pw);
-
- if (getuser(&CC->user, CC->curr_user))
- {
- cprintf("%d Unable to find user record for %s.\n", ERROR + NO_SUCH_USER, CC->curr_user);
- return;
- }
-
- strproc(pw);
- strproc(CC->user.password);
-
- if (strlen(pw) != (MD5_HEXSTRING_SIZE-1))
- {
- cprintf("%d Auth string of length %ld is the wrong length (should be %d).\n", ERROR + ILLEGAL_VALUE, (long)strlen(pw), MD5_HEXSTRING_SIZE-1);
- return;
- }
-
- make_apop_string(CC->user.password, CC->cs_nonce, hexstring, sizeof hexstring);
-
- if (!strcmp(hexstring, pw))
- {
- do_login();
- return;
- }
- else
- {
- cprintf("%d Wrong password.\n", ERROR + PASSWORD_REQUIRED);
- return;
- }
-}
-
-
-
-
-
-CTDL_MODULE_INIT(pas2)
-{
- CtdlRegisterProtoHook(cmd_pas2, "PAS2", "APOP-based login");
-
- /* return our Subversion id for the Log */
- return "$Id$";
-}
+++ /dev/null
-/*
- * $Id$
- *
- * POP3 service for the Citadel system
- * Copyright (C) 1998-2001 by Art Cancro and others.
- * This code is released under the terms of the GNU General Public License.
- *
- * Current status of standards conformance:
- *
- * -> All required POP3 commands described in RFC1939 are implemented.
- * -> All optional POP3 commands described in RFC1939 are also implemented.
- * -> The deprecated "LAST" command is included in this implementation, because
- * there exist mail clients which insist on using it (such as Bynari
- * TradeMail, and certain versions of Eudora).
- * -> Capability detection via the method described in RFC2449 is implemented.
- *
- */
-
-#include "sysdep.h"
-#include <stdlib.h>
-#include <unistd.h>
-#include <stdio.h>
-#include <fcntl.h>
-#include <signal.h>
-#include <pwd.h>
-#include <errno.h>
-#include <sys/types.h>
-
-#if TIME_WITH_SYS_TIME
-# include <sys/time.h>
-# include <time.h>
-#else
-# if HAVE_SYS_TIME_H
-# include <sys/time.h>
-# else
-# include <time.h>
-# endif
-#endif
-
-#include <sys/wait.h>
-#include <string.h>
-#include <limits.h>
-#include <ctype.h>
-#include "citadel.h"
-#include "server.h"
-#include "citserver.h"
-#include "support.h"
-#include "config.h"
-#include "room_ops.h"
-#include "user_ops.h"
-#include "policy.h"
-#include "database.h"
-#include "msgbase.h"
-#include "tools.h"
-#include "internet_addressing.h"
-#include "serv_pop3.h"
-#include "md5.h"
-
-#ifdef HAVE_OPENSSL
-#include "serv_crypto.h"
-#endif
-
-
-#include "ctdl_module.h"
-
-
-
-/*
- * This cleanup function blows away the temporary memory and files used by
- * the POP3 server.
- */
-void pop3_cleanup_function(void) {
-
- /* Don't do this stuff if this is not a POP3 session! */
- if (CC->h_command_function != pop3_command_loop) return;
-
- lprintf(CTDL_DEBUG, "Performing POP3 cleanup hook\n");
- if (POP3->msgs != NULL) free(POP3->msgs);
-
- free(POP3);
-}
-
-
-
-/*
- * Here's where our POP3 session begins its happy day.
- */
-void pop3_greeting(void) {
- strcpy(CC->cs_clientname, "POP3 session");
- CC->internal_pgm = 1;
- POP3 = malloc(sizeof(struct citpop3));
- memset(POP3, 0, sizeof(struct citpop3));
-
- cprintf("+OK Citadel POP3 server %s\r\n",
- CC->cs_nonce);
-}
-
-
-/*
- * POP3S is just like POP3, except it goes crypto right away.
- */
-#ifdef HAVE_OPENSSL
-void pop3s_greeting(void) {
- CtdlStartTLS(NULL, NULL, NULL);
- pop3_greeting();
-}
-#endif
-
-
-
-/*
- * Specify user name (implements POP3 "USER" command)
- */
-void pop3_user(char *argbuf) {
- char username[SIZ];
-
- if (CC->logged_in) {
- cprintf("-ERR You are already logged in.\r\n");
- return;
- }
-
- strcpy(username, argbuf);
- striplt(username);
-
- /* lprintf(CTDL_DEBUG, "Trying <%s>\n", username); */
- if (CtdlLoginExistingUser(NULL, username) == login_ok) {
- cprintf("+OK Password required for %s\r\n", username);
- }
- else {
- cprintf("-ERR No such user.\r\n");
- }
-}
-
-
-
-/*
- * Back end for pop3_grab_mailbox()
- */
-void pop3_add_message(long msgnum, void *userdata) {
- struct MetaData smi;
-
- ++POP3->num_msgs;
- if (POP3->num_msgs < 2) POP3->msgs = malloc(sizeof(struct pop3msg));
- else POP3->msgs = realloc(POP3->msgs,
- (POP3->num_msgs * sizeof(struct pop3msg)) ) ;
- POP3->msgs[POP3->num_msgs-1].msgnum = msgnum;
- POP3->msgs[POP3->num_msgs-1].deleted = 0;
-
- /* We need to know the length of this message when it is printed in
- * RFC822 format. Perhaps we have cached this length in the message's
- * metadata record. If so, great; if not, measure it and then cache
- * it for next time.
- */
- GetMetaData(&smi, msgnum);
- if (smi.meta_rfc822_length <= 0L) {
- CC->redirect_buffer = malloc(SIZ);
- CC->redirect_len = 0;
- CC->redirect_alloc = SIZ;
- CtdlOutputMsg(msgnum, MT_RFC822, HEADERS_ALL, 0, 1, NULL);
- smi.meta_rfc822_length = CC->redirect_len;
- free(CC->redirect_buffer);
- CC->redirect_buffer = NULL;
- CC->redirect_len = 0;
- CC->redirect_alloc = 0;
- PutMetaData(&smi);
- }
- POP3->msgs[POP3->num_msgs-1].rfc822_length = smi.meta_rfc822_length;
-}
-
-
-
-/*
- * Open the inbox and read its contents.
- * (This should be called only once, by pop3_pass(), and returns the number
- * of messages in the inbox, or -1 for error)
- */
-int pop3_grab_mailbox(void) {
- struct visit vbuf;
- int i;
-
- if (getroom(&CC->room, MAILROOM) != 0) return(-1);
-
- /* Load up the messages */
- CtdlForEachMessage(MSGS_ALL, 0L, NULL, NULL, NULL,
- pop3_add_message, NULL);
-
- /* Figure out which are old and which are new */
- CtdlGetRelationship(&vbuf, &CC->user, &CC->room);
- POP3->lastseen = (-1);
- if (POP3->num_msgs) for (i=0; i<POP3->num_msgs; ++i) {
- if (is_msg_in_sequence_set(vbuf.v_seen,
- (POP3->msgs[POP3->num_msgs-1].msgnum) )) {
- POP3->lastseen = i;
- }
- }
-
- return(POP3->num_msgs);
-}
-
-void pop3_login(void)
-{
- int msgs;
-
- msgs = pop3_grab_mailbox();
- if (msgs >= 0) {
- cprintf("+OK %s is logged in (%d messages)\r\n",
- CC->user.fullname, msgs);
- lprintf(CTDL_NOTICE, "POP3 authenticated %s\n", CC->user.fullname);
- }
- else {
- cprintf("-ERR Can't open your mailbox\r\n");
- }
-
-}
-
-void pop3_apop(char *argbuf)
-{
- char username[SIZ];
- char userdigest[MD5_HEXSTRING_SIZE];
- char realdigest[MD5_HEXSTRING_SIZE];
- char *sptr;
-
- if (CC->logged_in)
- {
- cprintf("-ERR You are already logged in; not in the AUTHORIZATION phase.\r\n");
- return;
- }
-
- if ((sptr = strchr(argbuf, ' ')) == NULL)
- {
- cprintf("-ERR Invalid APOP line.\r\n");
- return;
- }
-
- *sptr++ = '\0';
-
- while ((*sptr) && isspace(*sptr))
- sptr++;
-
- strncpy(username, argbuf, sizeof(username)-1);
- username[sizeof(username)-1] = '\0';
-
- memset(userdigest, MD5_HEXSTRING_SIZE, 0);
- strncpy(userdigest, sptr, MD5_HEXSTRING_SIZE-1);
-
- if (CtdlLoginExistingUser(NULL, username) != login_ok)
- {
- cprintf("-ERR No such user.\r\n");
- return;
- }
-
- if (getuser(&CC->user, CC->curr_user))
- {
- cprintf("-ERR No such user.\r\n");
- return;
- }
-
- make_apop_string(CC->user.password, CC->cs_nonce, realdigest, sizeof realdigest);
- if (!strncasecmp(realdigest, userdigest, MD5_HEXSTRING_SIZE-1))
- {
- do_login();
- pop3_login();
- }
- else
- {
- cprintf("-ERR That is NOT the password.\r\n");
- }
-}
-
-
-/*
- * Authorize with password (implements POP3 "PASS" command)
- */
-void pop3_pass(char *argbuf) {
- char password[SIZ];
-
- strcpy(password, argbuf);
- striplt(password);
-
- /* lprintf(CTDL_DEBUG, "Trying <%s>\n", password); */
- if (CtdlTryPassword(password) == pass_ok) {
- pop3_login();
- }
- else {
- cprintf("-ERR That is NOT the password.\r\n");
- }
-}
-
-
-
-/*
- * list available msgs
- */
-void pop3_list(char *argbuf) {
- int i;
- int which_one;
-
- which_one = atoi(argbuf);
-
- /* "list one" mode */
- if (which_one > 0) {
- if (which_one > POP3->num_msgs) {
- cprintf("-ERR no such message, only %d are here\r\n",
- POP3->num_msgs);
- return;
- }
- else if (POP3->msgs[which_one-1].deleted) {
- cprintf("-ERR Sorry, you deleted that message.\r\n");
- return;
- }
- else {
- cprintf("+OK %d %ld\r\n",
- which_one,
- (long)POP3->msgs[which_one-1].rfc822_length
- );
- return;
- }
- }
-
- /* "list all" (scan listing) mode */
- else {
- cprintf("+OK Here's your mail:\r\n");
- if (POP3->num_msgs > 0) for (i=0; i<POP3->num_msgs; ++i) {
- if (! POP3->msgs[i].deleted) {
- cprintf("%d %ld\r\n",
- i+1,
- (long)POP3->msgs[i].rfc822_length);
- }
- }
- cprintf(".\r\n");
- }
-}
-
-
-/*
- * STAT (tally up the total message count and byte count) command
- */
-void pop3_stat(char *argbuf) {
- int total_msgs = 0;
- size_t total_octets = 0;
- int i;
-
- if (POP3->num_msgs > 0) for (i=0; i<POP3->num_msgs; ++i) {
- if (! POP3->msgs[i].deleted) {
- ++total_msgs;
- total_octets += POP3->msgs[i].rfc822_length;
- }
- }
-
- cprintf("+OK %d %ld\r\n", total_msgs, (long)total_octets);
-}
-
-
-
-/*
- * RETR command (fetch a message)
- */
-void pop3_retr(char *argbuf) {
- int which_one;
-
- which_one = atoi(argbuf);
- if ( (which_one < 1) || (which_one > POP3->num_msgs) ) {
- cprintf("-ERR No such message.\r\n");
- return;
- }
-
- if (POP3->msgs[which_one - 1].deleted) {
- cprintf("-ERR Sorry, you deleted that message.\r\n");
- return;
- }
-
- cprintf("+OK Message %d:\r\n", which_one);
- CtdlOutputMsg(POP3->msgs[which_one - 1].msgnum, MT_RFC822, HEADERS_ALL, 0, 1, NULL);
- cprintf(".\r\n");
-}
-
-
-/*
- * TOP command (dumb way of fetching a partial message or headers-only)
- */
-void pop3_top(char *argbuf) {
- int which_one;
- int lines_requested = 0;
- int lines_dumped = 0;
- char buf[1024];
- char *msgtext;
- char *ptr;
- int in_body = 0;
- int done = 0;
-
- sscanf(argbuf, "%d %d", &which_one, &lines_requested);
- if ( (which_one < 1) || (which_one > POP3->num_msgs) ) {
- cprintf("-ERR No such message.\r\n");
- return;
- }
-
- if (POP3->msgs[which_one - 1].deleted) {
- cprintf("-ERR Sorry, you deleted that message.\r\n");
- return;
- }
-
- CC->redirect_buffer = malloc(SIZ);
- CC->redirect_len = 0;
- CC->redirect_alloc = SIZ;
- CtdlOutputMsg(POP3->msgs[which_one - 1].msgnum,
- MT_RFC822, HEADERS_ALL, 0, 1, NULL);
- msgtext = CC->redirect_buffer;
- CC->redirect_buffer = NULL;
- CC->redirect_len = 0;
- CC->redirect_alloc = 0;
-
- cprintf("+OK Message %d:\r\n", which_one);
-
- ptr = msgtext;
-
- while (ptr = memreadline(ptr, buf, (sizeof buf - 2)),
- ( (*ptr != 0) && (done == 0))) {
- strcat(buf, "\r\n");
- if (in_body == 1) {
- if (lines_dumped >= lines_requested) {
- done = 1;
- }
- }
- if ((in_body == 0) || (done == 0)) {
- client_write(buf, strlen(buf));
- }
- if (in_body) {
- ++lines_dumped;
- }
- if ((buf[0]==13)||(buf[0]==10)) in_body = 1;
- }
-
- if (buf[strlen(buf)-1] != 10) cprintf("\n");
- free(msgtext);
-
- cprintf(".\r\n");
-}
-
-
-/*
- * DELE (delete message from mailbox)
- */
-void pop3_dele(char *argbuf) {
- int which_one;
-
- which_one = atoi(argbuf);
- if ( (which_one < 1) || (which_one > POP3->num_msgs) ) {
- cprintf("-ERR No such message.\r\n");
- return;
- }
-
- if (POP3->msgs[which_one - 1].deleted) {
- cprintf("-ERR You already deleted that message.\r\n");
- return;
- }
-
- /* Flag the message as deleted. Will expunge during QUIT command. */
- POP3->msgs[which_one - 1].deleted = 1;
- cprintf("+OK Message %d deleted.\r\n",
- which_one);
-}
-
-
-/* Perform "UPDATE state" stuff
- */
-void pop3_update(void) {
- int i;
- struct visit vbuf;
-
- long *deletemsgs = NULL;
- int num_deletemsgs = 0;
-
- /* Remove messages marked for deletion */
- if (POP3->num_msgs > 0) {
- deletemsgs = malloc(POP3->num_msgs * sizeof(long));
- for (i=0; i<POP3->num_msgs; ++i) {
- if (POP3->msgs[i].deleted) {
- deletemsgs[num_deletemsgs++] = POP3->msgs[i].msgnum;
- }
- }
- if (num_deletemsgs > 0) {
- CtdlDeleteMessages(MAILROOM, deletemsgs, num_deletemsgs, "");
- }
- free(deletemsgs);
- }
-
- /* Set last read pointer */
- if (POP3->num_msgs > 0) {
- lgetuser(&CC->user, CC->curr_user);
-
- CtdlGetRelationship(&vbuf, &CC->user, &CC->room);
- snprintf(vbuf.v_seen, sizeof vbuf.v_seen, "*:%ld",
- POP3->msgs[POP3->num_msgs-1].msgnum);
- CtdlSetRelationship(&vbuf, &CC->user, &CC->room);
-
- lputuser(&CC->user);
- }
-
-}
-
-
-/*
- * RSET (reset, i.e. undelete any deleted messages) command
- */
-void pop3_rset(char *argbuf) {
- int i;
-
- if (POP3->num_msgs > 0) for (i=0; i<POP3->num_msgs; ++i) {
- if (POP3->msgs[i].deleted) {
- POP3->msgs[i].deleted = 0;
- }
- }
- cprintf("+OK Reset completed.\r\n");
-}
-
-
-
-/*
- * LAST (Determine which message is the last unread message)
- */
-void pop3_last(char *argbuf) {
- cprintf("+OK %d\r\n", POP3->lastseen + 1);
-}
-
-
-/*
- * CAPA is a command which tells the client which POP3 extensions
- * are supported.
- */
-void pop3_capa(void) {
- cprintf("+OK Capability list follows\r\n"
- "TOP\r\n"
- "USER\r\n"
- "UIDL\r\n"
- "IMPLEMENTATION %s\r\n"
- ".\r\n"
- ,
- CITADEL
- );
-}
-
-
-
-/*
- * UIDL (Universal IDentifier Listing) is easy. Our 'unique' message
- * identifiers are simply the Citadel message numbers in the database.
- */
-void pop3_uidl(char *argbuf) {
- int i;
- int which_one;
-
- which_one = atoi(argbuf);
-
- /* "list one" mode */
- if (which_one > 0) {
- if (which_one > POP3->num_msgs) {
- cprintf("-ERR no such message, only %d are here\r\n",
- POP3->num_msgs);
- return;
- }
- else if (POP3->msgs[which_one-1].deleted) {
- cprintf("-ERR Sorry, you deleted that message.\r\n");
- return;
- }
- else {
- cprintf("+OK %d %ld\r\n",
- which_one,
- POP3->msgs[which_one-1].msgnum
- );
- return;
- }
- }
-
- /* "list all" (scan listing) mode */
- else {
- cprintf("+OK Here's your mail:\r\n");
- if (POP3->num_msgs > 0) for (i=0; i<POP3->num_msgs; ++i) {
- if (! POP3->msgs[i].deleted) {
- cprintf("%d %ld\r\n",
- i+1,
- POP3->msgs[i].msgnum);
- }
- }
- cprintf(".\r\n");
- }
-}
-
-
-/*
- * implements the STLS command (Citadel API version)
- */
-#ifdef HAVE_OPENSSL
-void pop3_stls(void)
-{
- char ok_response[SIZ];
- char nosup_response[SIZ];
- char error_response[SIZ];
-
- sprintf(ok_response,
- "+OK Begin TLS negotiation now\r\n");
- sprintf(nosup_response,
- "-ERR TLS not supported here\r\n");
- sprintf(error_response,
- "-ERR Internal error\r\n");
- CtdlStartTLS(ok_response, nosup_response, error_response);
-}
-#endif
-
-
-
-
-
-
-
-/*
- * Main command loop for POP3 sessions.
- */
-void pop3_command_loop(void) {
- char cmdbuf[SIZ];
-
- time(&CC->lastcmd);
- memset(cmdbuf, 0, sizeof cmdbuf); /* Clear it, just in case */
- if (client_getln(cmdbuf, sizeof cmdbuf) < 1) {
- lprintf(CTDL_ERR, "Client disconnected: ending session.\r\n");
- CC->kill_me = 1;
- return;
- }
- if (!strncasecmp(cmdbuf, "PASS", 4)) {
- lprintf(CTDL_INFO, "POP3: PASS...\r\n");
- }
- else {
- lprintf(CTDL_INFO, "POP3: %s\r\n", cmdbuf);
- }
- while (strlen(cmdbuf) < 5) strcat(cmdbuf, " ");
-
- if (!strncasecmp(cmdbuf, "NOOP", 4)) {
- cprintf("+OK No operation.\r\n");
- }
-
- else if (!strncasecmp(cmdbuf, "CAPA", 4)) {
- pop3_capa();
- }
-
- else if (!strncasecmp(cmdbuf, "QUIT", 4)) {
- cprintf("+OK Goodbye...\r\n");
- pop3_update();
- CC->kill_me = 1;
- return;
- }
-
- else if (!strncasecmp(cmdbuf, "USER", 4)) {
- pop3_user(&cmdbuf[5]);
- }
-
- else if (!strncasecmp(cmdbuf, "PASS", 4)) {
- pop3_pass(&cmdbuf[5]);
- }
-
- else if (!strncasecmp(cmdbuf, "APOP", 4))
- {
- pop3_apop(&cmdbuf[5]);
- }
-
-#ifdef HAVE_OPENSSL
- else if (!strncasecmp(cmdbuf, "STLS", 4)) {
- pop3_stls();
- }
-#endif
-
- else if (!CC->logged_in) {
- cprintf("-ERR Not logged in.\r\n");
- }
-
- else if (!strncasecmp(cmdbuf, "LIST", 4)) {
- pop3_list(&cmdbuf[5]);
- }
-
- else if (!strncasecmp(cmdbuf, "STAT", 4)) {
- pop3_stat(&cmdbuf[5]);
- }
-
- else if (!strncasecmp(cmdbuf, "RETR", 4)) {
- pop3_retr(&cmdbuf[5]);
- }
-
- else if (!strncasecmp(cmdbuf, "DELE", 4)) {
- pop3_dele(&cmdbuf[5]);
- }
-
- else if (!strncasecmp(cmdbuf, "RSET", 4)) {
- pop3_rset(&cmdbuf[5]);
- }
-
- else if (!strncasecmp(cmdbuf, "UIDL", 4)) {
- pop3_uidl(&cmdbuf[5]);
- }
-
- else if (!strncasecmp(cmdbuf, "TOP", 3)) {
- pop3_top(&cmdbuf[4]);
- }
-
- else if (!strncasecmp(cmdbuf, "LAST", 4)) {
- pop3_last(&cmdbuf[4]);
- }
-
- else {
- cprintf("-ERR I'm afraid I can't do that.\r\n");
- }
-
-}
-
-
-
-CTDL_MODULE_INIT(pop3)
-{
- CtdlRegisterServiceHook(config.c_pop3_port,
- NULL,
- pop3_greeting,
- pop3_command_loop,
- NULL);
-#ifdef HAVE_OPENSSL
- CtdlRegisterServiceHook(config.c_pop3s_port,
- NULL,
- pop3s_greeting,
- pop3_command_loop,
- NULL);
-#endif
- CtdlRegisterSessionHook(pop3_cleanup_function, EVT_STOP);
-
- /* return our Subversion id for the Log */
- return "$Id$";
-}
+++ /dev/null
-/*
- * $Id$
- *
- */
-
-struct pop3msg {
- long msgnum;
- size_t rfc822_length;
- int deleted;
-};
-
-struct citpop3 { /* Information about the current session */
- struct pop3msg *msgs; /* Array of message pointers */
- int num_msgs; /* Number of messages in array */
- int lastseen; /* Offset of last-read message in array */
-};
- /* Note: the "lastseen" is represented as the
- * offset in this array (zero-based), so when
- * displaying it to a POP3 client, it must be
- * incremented by one.
- */
-
-#define POP3 CC->POP3
-
-void pop3_cleanup_function(void);
-void pop3_greeting(void);
-void pop3_user(char *argbuf);
-void pop3_pass(char *argbuf);
-void pop3_list(char *argbuf);
-void pop3_command_loop(void);
-void pop3_login(void);
-
+++ /dev/null
-/*
- * $Id$
- *
- * This module implementsserver commands related to the display and
- * manipulation of the "Who's online" list.
- *
- */
-
-#include "sysdep.h"
-#include <stdlib.h>
-#include <unistd.h>
-#include <stdio.h>
-#include <fcntl.h>
-#include <signal.h>
-#include <pwd.h>
-#include <errno.h>
-#include <sys/types.h>
-
-#if TIME_WITH_SYS_TIME
-# include <sys/time.h>
-# include <time.h>
-#else
-# if HAVE_SYS_TIME_H
-# include <sys/time.h>
-# else
-# include <time.h>
-# endif
-#endif
-
-#include <sys/wait.h>
-#include <string.h>
-#include <limits.h>
-#include "citadel.h"
-#include "server.h"
-#include "citserver.h"
-#include "support.h"
-#include "config.h"
-#include "control.h"
-#include "room_ops.h"
-#include "user_ops.h"
-#include "policy.h"
-#include "database.h"
-#include "msgbase.h"
-#include "tools.h"
-
-
-#include "ctdl_module.h"
-
-
-/*
- * display who's online
- */
-void cmd_rwho(char *argbuf) {
- struct CitContext *cptr;
- int spoofed = 0;
- int user_spoofed = 0;
- int room_spoofed = 0;
- int host_spoofed = 0;
- int aide;
- char un[40];
- char real_room[ROOMNAMELEN], room[ROOMNAMELEN];
- char host[64], flags[5];
-
- aide = CC->user.axlevel >= 6;
- cprintf("%d%c \n", LISTING_FOLLOWS, CtdlCheckExpress() );
-
- for (cptr = ContextList; cptr != NULL; cptr = cptr->next)
- {
- flags[0] = '\0';
- spoofed = 0;
- user_spoofed = 0;
- room_spoofed = 0;
- host_spoofed = 0;
-
- if (cptr->cs_flags & CS_POSTING)
- strcat(flags, "*");
- else
- strcat(flags, ".");
-
- if (cptr->fake_username[0])
- {
- strcpy(un, cptr->fake_username);
- spoofed = 1;
- user_spoofed = 1;
- }
- else
- strcpy(un, cptr->curr_user);
-
- if (cptr->fake_hostname[0])
- {
- strcpy(host, cptr->fake_hostname);
- spoofed = 1;
- host_spoofed = 1;
- }
- else
- strcpy(host, cptr->cs_host);
-
- GenerateRoomDisplay(real_room, cptr, CC);
-
- if (cptr->fake_roomname[0]) {
- strcpy(room, cptr->fake_roomname);
- spoofed = 1;
- room_spoofed = 1;
- }
- else {
- strcpy(room, real_room);
- }
-
- if ((aide) && (spoofed)) {
- strcat(flags, "+");
- }
-
- if ((cptr->cs_flags & CS_STEALTH) && (aide)) {
- strcat(flags, "-");
- }
-
- if (((cptr->cs_flags&CS_STEALTH)==0) || (aide))
- {
- cprintf("%d|%s|%s|%s|%s|%ld|%s|%s|",
- cptr->cs_pid, un, room,
- host, cptr->cs_clientname,
- (long)(cptr->lastidle),
- cptr->lastcmdname, flags
- );
-
- if ((user_spoofed) && (aide)) {
- cprintf("%s|", cptr->curr_user);
- }
- else {
- cprintf("|");
- }
-
- if ((room_spoofed) && (aide)) {
- cprintf("%s|", real_room);
- }
- else {
- cprintf("|");
- }
-
- if ((host_spoofed) && (aide)) {
- cprintf("%s|", cptr->cs_host);
- }
- else {
- cprintf("|");
- }
-
- cprintf("%d\n", cptr->logged_in);
- }
- }
-
- /* Now it's magic time. Before we finish, call any EVT_RWHO hooks
- * so that external paging modules such as serv_icq can add more
- * content to the Wholist.
- */
- PerformSessionHooks(EVT_RWHO);
- cprintf("000\n");
- }
-
-
-/*
- * Masquerade roomname
- */
-void cmd_rchg(char *argbuf)
-{
- char newroomname[ROOMNAMELEN];
-
- extract_token(newroomname, argbuf, 0, '|', sizeof newroomname);
- newroomname[ROOMNAMELEN-1] = 0;
- if (strlen(newroomname) > 0) {
- safestrncpy(CC->fake_roomname, newroomname,
- sizeof(CC->fake_roomname) );
- }
- else {
- safestrncpy(CC->fake_roomname, "", sizeof CC->fake_roomname);
- }
- cprintf("%d OK\n", CIT_OK);
-}
-
-/*
- * Masquerade hostname
- */
-void cmd_hchg(char *argbuf)
-{
- char newhostname[64];
-
- extract_token(newhostname, argbuf, 0, '|', sizeof newhostname);
- if (strlen(newhostname) > 0) {
- safestrncpy(CC->fake_hostname, newhostname,
- sizeof(CC->fake_hostname) );
- }
- else {
- safestrncpy(CC->fake_hostname, "", sizeof CC->fake_hostname);
- }
- cprintf("%d OK\n", CIT_OK);
-}
-
-
-/*
- * Masquerade username (aides only)
- */
-void cmd_uchg(char *argbuf)
-{
-
- char newusername[USERNAME_SIZE];
-
- extract_token(newusername, argbuf, 0, '|', sizeof newusername);
-
- if (CtdlAccessCheck(ac_aide)) return;
-
- if (strlen(newusername) > 0) {
- CC->cs_flags &= ~CS_STEALTH;
- memset(CC->fake_username, 0, 32);
- if (strncasecmp(newusername, CC->curr_user,
- strlen(CC->curr_user)))
- safestrncpy(CC->fake_username, newusername,
- sizeof(CC->fake_username));
- }
- else {
- CC->fake_username[0] = '\0';
- CC->cs_flags |= CS_STEALTH;
- }
- cprintf("%d\n",CIT_OK);
-}
-
-
-
-
-/*
- * enter or exit "stealth mode"
- */
-void cmd_stel(char *cmdbuf)
-{
- int requested_mode;
-
- requested_mode = extract_int(cmdbuf,0);
-
- if (CtdlAccessCheck(ac_logged_in)) return;
-
- if (requested_mode == 1) {
- CC->cs_flags = CC->cs_flags | CS_STEALTH;
- }
- if (requested_mode == 0) {
- CC->cs_flags = CC->cs_flags & ~CS_STEALTH;
- }
-
- cprintf("%d %d\n", CIT_OK,
- ((CC->cs_flags & CS_STEALTH) ? 1 : 0) );
-}
-
-
-
-
-
-
-
-CTDL_MODULE_INIT(rwho)
-{
- CtdlRegisterProtoHook(cmd_rwho, "RWHO", "Display who is online");
- CtdlRegisterProtoHook(cmd_hchg, "HCHG", "Masquerade hostname");
- CtdlRegisterProtoHook(cmd_rchg, "RCHG", "Masquerade roomname");
- CtdlRegisterProtoHook(cmd_uchg, "UCHG", "Masquerade username");
- CtdlRegisterProtoHook(cmd_stel, "STEL", "Enter/exit stealth mode");
-
- /* return our Subversion id for the Log */
- return "$Id$";
-}
+++ /dev/null
-/*
- * $Id$
- *
- * This module glues libSieve to the Citadel server in order to implement
- * the Sieve mailbox filtering language (RFC 3028).
- *
- * This code is released under the terms of the GNU General Public License.
- */
-
-#include "sysdep.h"
-#include <stdlib.h>
-#include <unistd.h>
-#include <stdio.h>
-#include <fcntl.h>
-#include <ctype.h>
-#include <pwd.h>
-#include <errno.h>
-#include <sys/types.h>
-
-#if TIME_WITH_SYS_TIME
-# include <sys/time.h>
-# include <time.h>
-#else
-# if HAVE_SYS_TIME_H
-# include <sys/time.h>
-# else
-# include <time.h>
-# endif
-#endif
-
-#include <sys/wait.h>
-#include <string.h>
-#include <limits.h>
-#include "citadel.h"
-#include "server.h"
-#include "citserver.h"
-#include "support.h"
-#include "config.h"
-#include "room_ops.h"
-#include "policy.h"
-#include "database.h"
-#include "msgbase.h"
-#include "internet_addressing.h"
-#include "tools.h"
-
-
-#include "ctdl_module.h"
-
-
-#ifdef HAVE_LIBSIEVE
-
-#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)
-{
- lprintf(CTDL_DEBUG, "Sieve: %s\n", sieve2_getvalue_string(s, "message"));
- return SIEVE2_OK;
-}
-
-
-/*
- * Callback function to log script parsing errors
- */
-int ctdl_errparse(sieve2_context_t *s, void *my)
-{
- lprintf(CTDL_WARNING, "Error in script, line %d: %s\n",
- 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)
-{
- lprintf(CTDL_WARNING, "Error executing script: %s\n",
- sieve2_getvalue_string(s, "message")
- );
- return SIEVE2_OK;
-}
-
-
-/*
- * Callback function to redirect a message to a different folder
- */
-int ctdl_redirect(sieve2_context_t *s, void *my)
-{
- struct ctdl_sieve *cs = (struct ctdl_sieve *)my;
- struct CtdlMessage *msg = NULL;
- struct recptypes *valid = NULL;
- char recp[256];
-
- safestrncpy(recp, sieve2_getvalue_string(s, "address"), sizeof recp);
-
- lprintf(CTDL_DEBUG, "Action is REDIRECT, recipient <%s>\n", recp);
-
- valid = validate_recipients(recp);
- if (valid == NULL) {
- lprintf(CTDL_WARNING, "REDIRECT failed: bad recipient <%s>\n", recp);
- return SIEVE2_ERROR_BADARGS;
- }
- if (valid->num_error > 0) {
- lprintf(CTDL_WARNING, "REDIRECT failed: bad recipient <%s>\n", recp);
- free_recipients(valid);
- return SIEVE2_ERROR_BADARGS;
- }
-
- msg = CtdlFetchMessage(cs->msgnum, 1);
- if (msg == NULL) {
- lprintf(CTDL_WARNING, "REDIRECT failed: unable to fetch msg %ld\n", cs->msgnum);
- free_recipients(valid);
- return SIEVE2_ERROR_BADARGS;
- }
-
- CtdlSubmitMsg(msg, valid, NULL);
- cs->cancel_implicit_keep = 1;
- free_recipients(valid);
- CtdlFreeMessage(msg);
- return SIEVE2_OK;
-}
-
-
-/*
- * Callback function to indicate that a message *will* be kept in the inbox
- */
-int ctdl_keep(sieve2_context_t *s, void *my)
-{
- struct ctdl_sieve *cs = (struct ctdl_sieve *)my;
-
- lprintf(CTDL_DEBUG, "Action is KEEP\n");
-
- cs->keep = 1;
- cs->cancel_implicit_keep = 1;
- return SIEVE2_OK;
-}
-
-
-/*
- * Callback function to file a message into a different mailbox
- */
-int ctdl_fileinto(sieve2_context_t *s, void *my)
-{
- struct ctdl_sieve *cs = (struct ctdl_sieve *)my;
- const char *dest_folder = sieve2_getvalue_string(s, "mailbox");
- int c;
- char foldername[256];
- char original_room_name[ROOMNAMELEN];
-
- lprintf(CTDL_DEBUG, "Action is FILEINTO, destination is <%s>\n", dest_folder);
-
- /* FILEINTO 'INBOX' is the same thing as KEEP */
- if ( (!strcasecmp(dest_folder, "INBOX")) || (!strcasecmp(dest_folder, MAILROOM)) ) {
- cs->keep = 1;
- cs->cancel_implicit_keep = 1;
- return SIEVE2_OK;
- }
-
- /* Remember what room we came from */
- safestrncpy(original_room_name, CC->room.QRname, sizeof original_room_name);
-
- /* First try a mailbox name match (check personal mail folders first) */
- snprintf(foldername, sizeof foldername, "%010ld.%s", cs->usernum, dest_folder);
- c = getroom(&CC->room, foldername);
-
- /* Then a regular room name match (public and private rooms) */
- if (c != 0) {
- safestrncpy(foldername, dest_folder, sizeof foldername);
- c = getroom(&CC->room, foldername);
- }
-
- if (c != 0) {
- lprintf(CTDL_WARNING, "FILEINTO failed: target <%s> does not exist\n", dest_folder);
- return SIEVE2_ERROR_BADARGS;
- }
-
- /* Yes, we actually have to go there */
- usergoto(NULL, 0, 0, NULL, NULL);
-
- c = CtdlSaveMsgPointersInRoom(NULL, &cs->msgnum, 1, 0, NULL);
-
- /* Go back to the room we came from */
- if (strcasecmp(original_room_name, CC->room.QRname)) {
- usergoto(original_room_name, 0, 0, NULL, NULL);
- }
-
- if (c == 0) {
- cs->cancel_implicit_keep = 1;
- return SIEVE2_OK;
- }
- else {
- return SIEVE2_ERROR_BADARGS;
- }
-}
-
-
-/*
- * Callback function to indicate that a message should be discarded.
- */
-int ctdl_discard(sieve2_context_t *s, void *my)
-{
- struct ctdl_sieve *cs = (struct ctdl_sieve *)my;
-
- lprintf(CTDL_DEBUG, "Action is DISCARD\n");
-
- /* Cancel the implicit keep. That's all there is to it. */
- cs->cancel_implicit_keep = 1;
- return SIEVE2_OK;
-}
-
-
-
-/*
- * Callback function to indicate that a message should be rejected
- */
-int ctdl_reject(sieve2_context_t *s, void *my)
-{
- struct ctdl_sieve *cs = (struct ctdl_sieve *)my;
- char *reject_text = NULL;
-
- lprintf(CTDL_DEBUG, "Action is REJECT\n");
-
- /* If we don't know who sent the message, do a DISCARD instead. */
- if (strlen(cs->sender) == 0) {
- lprintf(CTDL_INFO, "Unknown sender. Doing DISCARD instead of REJECT.\n");
- return ctdl_discard(s, my);
- }
-
- /* Assemble the reject message. */
- reject_text = malloc(strlen(sieve2_getvalue_string(s, "message")) + 1024);
- if (reject_text == NULL) {
- return SIEVE2_ERROR_FAIL;
- }
-
- sprintf(reject_text,
- "Content-type: text/plain\n"
- "\n"
- "The message was refused by the recipient's mail filtering program.\n"
- "The reason given was as follows:\n"
- "\n"
- "%s\n"
- "\n"
- ,
- sieve2_getvalue_string(s, "message")
- );
-
- quickie_message( /* This delivers the message */
- NULL,
- cs->envelope_to,
- cs->sender,
- NULL,
- reject_text,
- FMT_RFC822,
- "Delivery status notification"
- );
-
- free(reject_text);
- cs->cancel_implicit_keep = 1;
- return SIEVE2_OK;
-}
-
-
-
-/*
- * Callback function to indicate that a vacation message should be generated
- */
-int ctdl_vacation(sieve2_context_t *s, void *my)
-{
- struct ctdl_sieve *cs = (struct ctdl_sieve *)my;
- struct sdm_vacation *vptr;
- int days = 1;
- const char *message;
- char *vacamsg_text = NULL;
- char vacamsg_subject[1024];
-
- lprintf(CTDL_DEBUG, "Action is VACATION\n");
-
- message = sieve2_getvalue_string(s, "message");
- if (message == NULL) return SIEVE2_ERROR_BADARGS;
-
- if (sieve2_getvalue_string(s, "subject") != NULL) {
- safestrncpy(vacamsg_subject, sieve2_getvalue_string(s, "subject"), sizeof vacamsg_subject);
- }
- else {
- snprintf(vacamsg_subject, sizeof vacamsg_subject, "Re: %s", cs->subject);
- }
-
- days = sieve2_getvalue_int(s, "days");
- if (days < 1) days = 1;
- if (days > MAX_VACATION) days = MAX_VACATION;
-
- /* Check to see whether we've already alerted this sender that we're on vacation. */
- for (vptr = cs->u->first_vacation; vptr != NULL; vptr = vptr->next) {
- if (!strcasecmp(vptr->fromaddr, cs->sender)) {
- if ( (time(NULL) - vptr->timestamp) < (days * 86400) ) {
- lprintf(CTDL_DEBUG, "Already alerted <%s> recently.\n", cs->sender);
- return SIEVE2_OK;
- }
- }
- }
-
- /* Assemble the reject message. */
- vacamsg_text = malloc(strlen(message) + 1024);
- if (vacamsg_text == NULL) {
- return SIEVE2_ERROR_FAIL;
- }
-
- sprintf(vacamsg_text,
- "Content-type: text/plain\n"
- "\n"
- "%s\n"
- "\n"
- ,
- message
- );
-
- quickie_message( /* This delivers the message */
- NULL,
- cs->envelope_to,
- cs->sender,
- NULL,
- vacamsg_text,
- FMT_RFC822,
- vacamsg_subject
- );
-
- free(vacamsg_text);
-
- /* Now update the list to reflect the fact that we've alerted this sender.
- * If they're already in the list, just update the timestamp.
- */
- for (vptr = cs->u->first_vacation; vptr != NULL; vptr = vptr->next) {
- if (!strcasecmp(vptr->fromaddr, cs->sender)) {
- vptr->timestamp = time(NULL);
- return SIEVE2_OK;
- }
- }
-
- /* If we get to this point, create a new record.
- */
- vptr = malloc(sizeof(struct sdm_vacation));
- vptr->timestamp = time(NULL);
- safestrncpy(vptr->fromaddr, cs->sender, sizeof vptr->fromaddr);
- vptr->next = cs->u->first_vacation;
- cs->u->first_vacation = vptr;
-
- return SIEVE2_OK;
-}
-
-
-/*
- * Callback function to parse addresses per local system convention
- * It is disabled because we don't support subaddresses.
- */
-#if 0
-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
- */
-int ctdl_getenvelope(sieve2_context_t *s, void *my)
-{
- struct ctdl_sieve *cs = (struct ctdl_sieve *)my;
-
- lprintf(CTDL_DEBUG, "Action is GETENVELOPE\n");
- sieve2_setvalue_string(s, "to", cs->envelope_to);
- sieve2_setvalue_string(s, "from", cs->envelope_from);
- return SIEVE2_OK;
-}
-
-
-/*
- * 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;
-}
- *
- */
-
-
-/*
- * Callback function to fetch message size
- */
-int ctdl_getsize(sieve2_context_t *s, void *my)
-{
- struct ctdl_sieve *cs = (struct ctdl_sieve *)my;
- struct MetaData smi;
-
- GetMetaData(&smi, cs->msgnum);
-
- if (smi.meta_rfc822_length > 0L) {
- sieve2_setvalue_int(s, "size", (int)smi.meta_rfc822_length);
- return SIEVE2_OK;
- }
-
- return SIEVE2_ERROR_UNSUPPORTED;
-}
-
-
-/*
- * Callback function to retrieve the sieve script
- */
-int ctdl_getscript(sieve2_context_t *s, void *my) {
- struct sdm_script *sptr;
- struct ctdl_sieve *cs = (struct ctdl_sieve *)my;
-
- for (sptr=cs->u->first_script; sptr!=NULL; sptr=sptr->next) {
- if (sptr->script_active > 0) {
- lprintf(CTDL_DEBUG, "ctdl_getscript() is using script '%s'\n", sptr->script_name);
- sieve2_setvalue_string(s, "script", sptr->script_content);
- return SIEVE2_OK;
- }
- }
-
- lprintf(CTDL_DEBUG, "ctdl_getscript() found no active script\n");
- return SIEVE2_ERROR_GETSCRIPT;
-}
-
-/*
- * Callback function to retrieve message headers
- */
-int ctdl_getheaders(sieve2_context_t *s, void *my) {
-
- struct ctdl_sieve *cs = (struct ctdl_sieve *)my;
-
- lprintf(CTDL_DEBUG, "ctdl_getheaders() was called\n");
- sieve2_setvalue_string(s, "allheaders", cs->rfc822headers);
- return SIEVE2_OK;
-}
-
-
-
-/*
- * 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);
- lprintf(CTDL_DEBUG, "<%s> queued for Sieve processing\n", which_room->QRname);
-}
-
-
-
-/*
- * Perform sieve processing for one message (called by sieve_do_room() for each message)
- */
-void sieve_do_msg(long msgnum, void *userdata) {
- struct sdm_userdata *u = (struct sdm_userdata *) userdata;
- sieve2_context_t *sieve2_context = u->sieve2_context;
- struct ctdl_sieve my;
- int res;
- struct CtdlMessage *msg;
- int i;
- size_t headers_len = 0;
- int len = 0;
-
- lprintf(CTDL_DEBUG, "Performing sieve processing on msg <%ld>\n", msgnum);
-
- msg = CtdlFetchMessage(msgnum, 0);
- if (msg == NULL) return;
-
- /*
- * Grab the message headers so we can feed them to libSieve.
- */
- CC->redirect_buffer = malloc(SIZ);
- CC->redirect_len = 0;
- CC->redirect_alloc = SIZ;
- CtdlOutputPreLoadedMsg(msg, MT_RFC822, HEADERS_ONLY, 0, 1);
- my.rfc822headers = CC->redirect_buffer;
- headers_len = CC->redirect_len;
- CC->redirect_buffer = NULL;
- CC->redirect_len = 0;
- CC->redirect_alloc = 0;
-
- /*
- * libSieve clobbers the stack if it encounters badly formed
- * headers. Sanitize our headers by stripping nonprintable
- * characters.
- */
- for (i=0; i<headers_len; ++i) {
- if (!isascii(my.rfc822headers[i])) {
- my.rfc822headers[i] = '_';
- }
- }
-
- my.keep = 0; /* Set to 1 to declare an *explicit* keep */
- my.cancel_implicit_keep = 0; /* Some actions will cancel the implicit keep */
- my.usernum = atol(CC->room.QRname); /* Keep track of the owner of the room's namespace */
- my.msgnum = msgnum; /* Keep track of the message number in our local store */
- my.u = u; /* Hand off a pointer to the rest of this info */
-
- /* Keep track of the recipient so we can do handling based on it later */
- process_rfc822_addr(msg->cm_fields['R'], my.recp_user, my.recp_node, my.recp_name);
-
- /* Keep track of the sender so we can use it for REJECT and VACATION responses */
- if (msg->cm_fields['F'] != NULL) {
- safestrncpy(my.sender, msg->cm_fields['F'], sizeof my.sender);
- }
- else if ( (msg->cm_fields['A'] != NULL) && (msg->cm_fields['N'] != NULL) ) {
- snprintf(my.sender, sizeof my.sender, "%s@%s", msg->cm_fields['A'], msg->cm_fields['N']);
- }
- else if (msg->cm_fields['A'] != NULL) {
- safestrncpy(my.sender, msg->cm_fields['A'], sizeof my.sender);
- }
- else {
- strcpy(my.sender, "");
- }
-
- /* Keep track of the subject so we can use it for VACATION responses */
- if (msg->cm_fields['U'] != NULL) {
- safestrncpy(my.subject, msg->cm_fields['U'], sizeof my.subject);
- }
- else {
- strcpy(my.subject, "");
- }
-
- /* Keep track of the envelope-from address (use body-from if not found) */
- if (msg->cm_fields['P'] != NULL) {
- safestrncpy(my.envelope_from, msg->cm_fields['P'], sizeof my.envelope_from);
- }
- else if (msg->cm_fields['F'] != NULL) {
- safestrncpy(my.envelope_from, msg->cm_fields['F'], sizeof my.envelope_from);
- }
- else {
- strcpy(my.envelope_from, "");
- }
-
- len = strlen(my.envelope_from);
- for (i=0; i<len; ++i) {
- if (isspace(my.envelope_from[i])) my.envelope_from[i] = '_';
- }
- if (haschar(my.envelope_from, '@') == 0) {
- strcat(my.envelope_from, "@");
- strcat(my.envelope_from, config.c_fqdn);
- }
-
- /* Keep track of the envelope-to address (use body-to if not found) */
- if (msg->cm_fields['V'] != NULL) {
- safestrncpy(my.envelope_to, msg->cm_fields['V'], sizeof my.envelope_to);
- }
- else if (msg->cm_fields['R'] != NULL) {
- safestrncpy(my.envelope_to, msg->cm_fields['R'], sizeof my.envelope_to);
- if (msg->cm_fields['D'] != NULL) {
- strcat(my.envelope_to, "@");
- strcat(my.envelope_to, msg->cm_fields['D']);
- }
- }
- else {
- strcpy(my.envelope_to, "");
- }
-
- len = strlen(my.envelope_to);
- for (i=0; i<len; ++i) {
- if (isspace(my.envelope_to[i])) my.envelope_to[i] = '_';
- }
- if (haschar(my.envelope_to, '@') == 0) {
- strcat(my.envelope_to, "@");
- strcat(my.envelope_to, config.c_fqdn);
- }
-
- CtdlFreeMessage(msg);
-
- sieve2_setvalue_string(sieve2_context, "allheaders", my.rfc822headers);
-
- lprintf(CTDL_DEBUG, "Calling sieve2_execute()\n");
- res = sieve2_execute(sieve2_context, &my);
- if (res != SIEVE2_OK) {
- lprintf(CTDL_CRIT, "sieve2_execute() returned %d: %s\n", res, sieve2_errstr(res));
- }
-
- free(my.rfc822headers);
- my.rfc822headers = NULL;
-
- /*
- * Delete the message from the inbox unless either we were told not to, or
- * if no other action was successfully taken.
- */
- if ( (!my.keep) && (my.cancel_implicit_keep) ) {
- lprintf(CTDL_DEBUG, "keep is 0 -- deleting message from inbox\n");
- CtdlDeleteMessages(CC->room.QRname, &msgnum, 1, "");
- }
-
- lprintf(CTDL_DEBUG, "Completed sieve processing on msg <%ld>\n", msgnum);
- u->lastproc = msgnum;
-
- return;
-}
-
-
-
-/*
- * Given the on-disk representation of our Sieve config, load
- * it into an in-memory data structure.
- */
-void parse_sieve_config(char *conf, struct sdm_userdata *u) {
- char *ptr;
- char *c, *vacrec;
- char keyword[256];
- struct sdm_script *sptr;
- struct sdm_vacation *vptr;
-
- ptr = conf;
- while (c = ptr, ptr = bmstrcasestr(ptr, CTDLSIEVECONFIGSEPARATOR), ptr != NULL) {
- *ptr = 0;
- ptr += strlen(CTDLSIEVECONFIGSEPARATOR);
-
- extract_token(keyword, c, 0, '|', sizeof keyword);
-
- if (!strcasecmp(keyword, "lastproc")) {
- u->lastproc = extract_long(c, 1);
- }
-
- else if (!strcasecmp(keyword, "script")) {
- sptr = malloc(sizeof(struct sdm_script));
- extract_token(sptr->script_name, c, 1, '|', sizeof sptr->script_name);
- sptr->script_active = extract_int(c, 2);
- remove_token(c, 0, '|');
- remove_token(c, 0, '|');
- remove_token(c, 0, '|');
- sptr->script_content = strdup(c);
- sptr->next = u->first_script;
- u->first_script = sptr;
- }
-
- else if (!strcasecmp(keyword, "vacation")) {
-
- if (c != NULL) while (vacrec=c, c=strchr(c, '\n'), (c != NULL)) {
-
- *c = 0;
- ++c;
-
- if (strncasecmp(vacrec, "vacation|", 9)) {
- vptr = malloc(sizeof(struct sdm_vacation));
- extract_token(vptr->fromaddr, vacrec, 0, '|', sizeof vptr->fromaddr);
- vptr->timestamp = extract_long(vacrec, 1);
- vptr->next = u->first_vacation;
- u->first_vacation = vptr;
- }
- }
- }
-
- /* ignore unknown keywords */
- }
-}
-
-/*
- * We found the Sieve configuration for this user.
- * Now do something with it.
- */
-void get_sieve_config_backend(long msgnum, void *userdata) {
- struct sdm_userdata *u = (struct sdm_userdata *) userdata;
- struct CtdlMessage *msg;
- char *conf;
-
- u->config_msgnum = msgnum;
- msg = CtdlFetchMessage(msgnum, 1);
- if (msg == NULL) {
- u->config_msgnum = (-1) ;
- return;
- }
-
- conf = msg->cm_fields['M'];
- msg->cm_fields['M'] = NULL;
- CtdlFreeMessage(msg);
-
- if (conf != NULL) {
- parse_sieve_config(conf, u);
- free(conf);
- }
-
-}
-
-
-/*
- * Write our citadel sieve config back to disk
- *
- * (Set yes_write_to_disk to nonzero to make it actually write the config;
- * otherwise it just frees the data structures.)
- */
-void rewrite_ctdl_sieve_config(struct sdm_userdata *u, int yes_write_to_disk) {
- char *text;
- struct sdm_script *sptr;
- struct sdm_vacation *vptr;
- size_t tsize;
-
- text = malloc(1024);
- tsize = 1024;
- snprintf(text, 1024,
- "Content-type: application/x-citadel-sieve-config\n"
- "\n"
- CTDLSIEVECONFIGSEPARATOR
- "lastproc|%ld"
- CTDLSIEVECONFIGSEPARATOR
- ,
- u->lastproc
- );
-
- while (u->first_script != NULL) {
- size_t tlen;
- tlen = strlen(text);
- tsize = tlen + strlen(u->first_script->script_content) +256;
- text = realloc(text, tsize);
- sprintf(&text[strlen(text)], "script|%s|%d|%s" CTDLSIEVECONFIGSEPARATOR,
- u->first_script->script_name,
- u->first_script->script_active,
- u->first_script->script_content
- );
- sptr = u->first_script;
- u->first_script = u->first_script->next;
- free(sptr->script_content);
- free(sptr);
- }
-
- if (u->first_vacation != NULL) {
-
- tsize = strlen(text) + 256;
- for (vptr = u->first_vacation; vptr != NULL; vptr = vptr->next) {
- tsize += strlen(vptr->fromaddr + 32);
- }
- text = realloc(text, tsize);
-
- sprintf(&text[strlen(text)], "vacation|\n");
- while (u->first_vacation != NULL) {
- if ( (time(NULL) - u->first_vacation->timestamp) < (MAX_VACATION * 86400)) {
- sprintf(&text[strlen(text)], "%s|%ld\n",
- u->first_vacation->fromaddr,
- u->first_vacation->timestamp
- );
- }
- vptr = u->first_vacation;
- u->first_vacation = u->first_vacation->next;
- free(vptr);
- }
- sprintf(&text[strlen(text)], CTDLSIEVECONFIGSEPARATOR);
- }
-
- /* Save the config */
- quickie_message("Citadel", NULL, NULL, u->config_roomname,
- text,
- 4,
- "Sieve configuration"
- );
-
- free (text);
- /* And delete the old one */
- if (u->config_msgnum > 0) {
- CtdlDeleteMessages(u->config_roomname, &u->config_msgnum, 1, "");
- }
-
-}
-
-
-/*
- * This is our callback registration table for libSieve.
- */
-sieve2_callback_t ctdl_sieve_callbacks[] = {
- { SIEVE2_ACTION_REJECT, ctdl_reject },
- { SIEVE2_ACTION_VACATION, ctdl_vacation },
- { SIEVE2_ERRCALL_PARSE, ctdl_errparse },
- { SIEVE2_ERRCALL_RUNTIME, ctdl_errexec },
- { SIEVE2_ACTION_FILEINTO, ctdl_fileinto },
- { SIEVE2_ACTION_REDIRECT, ctdl_redirect },
- { SIEVE2_ACTION_DISCARD, ctdl_discard },
- { SIEVE2_ACTION_KEEP, ctdl_keep },
- { SIEVE2_SCRIPT_GETSCRIPT, ctdl_getscript },
- { SIEVE2_DEBUG_TRACE, ctdl_debug },
- { 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 }
-};
-
-
-/*
- * Perform sieve processing for a single room
- */
-void sieve_do_room(char *roomname) {
-
- struct sdm_userdata u;
- sieve2_context_t *sieve2_context = NULL; /* Context for sieve parser */
- int res; /* Return code from libsieve calls */
- long orig_lastproc = 0;
-
- memset(&u, 0, sizeof u);
-
- /* See if the user who owns this 'mailbox' has any Sieve scripts that
- * require execution.
- */
- snprintf(u.config_roomname, sizeof u.config_roomname, "%010ld.%s", atol(roomname), USERCONFIGROOM);
- if (getroom(&CC->room, u.config_roomname) != 0) {
- lprintf(CTDL_DEBUG, "<%s> does not exist. No processing is required.\n", u.config_roomname);
- return;
- }
-
- /*
- * 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 );
-
- if (u.config_msgnum < 0) {
- lprintf(CTDL_DEBUG, "No Sieve rules exist. No processing is required.\n");
- return;
- }
-
- lprintf(CTDL_DEBUG, "Rules found. Performing Sieve processing for <%s>\n", roomname);
-
- if (getroom(&CC->room, roomname) != 0) {
- lprintf(CTDL_CRIT, "ERROR: cannot load <%s>\n", roomname);
- return;
- }
-
- /* Initialize the Sieve parser */
-
- res = sieve2_alloc(&sieve2_context);
- if (res != SIEVE2_OK) {
- lprintf(CTDL_CRIT, "sieve2_alloc() returned %d: %s\n", res, sieve2_errstr(res));
- return;
- }
-
- res = sieve2_callbacks(sieve2_context, ctdl_sieve_callbacks);
- if (res != SIEVE2_OK) {
- lprintf(CTDL_CRIT, "sieve2_callbacks() returned %d: %s\n", res, sieve2_errstr(res));
- goto BAIL;
- }
-
- /* Validate the script */
-
- struct ctdl_sieve my; /* dummy ctdl_sieve struct just to pass "u" slong */
- memset(&my, 0, sizeof my);
- my.u = &u;
- res = sieve2_validate(sieve2_context, &my);
- if (res != SIEVE2_OK) {
- lprintf(CTDL_CRIT, "sieve2_validate() returned %d: %s\n", res, sieve2_errstr(res));
- goto BAIL;
- }
-
- /* 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
- );
-
-BAIL:
- res = sieve2_free(&sieve2_context);
- if (res != SIEVE2_OK) {
- lprintf(CTDL_CRIT, "sieve2_free() returned %d: %s\n", res, sieve2_errstr(res));
- }
-
- /* Rewrite the config if we have to */
- 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) {
- lprintf(CTDL_DEBUG, "Begin Sieve processing\n");
- 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 (getroom(&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)) {
- getroom(&CC->room, hold_rm); /* return to saved room */
- }
-}
-
-void msiv_store(struct sdm_userdata *u, int yes_write_to_disk) {
- 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 (strlen(script_name) == 0) {
- 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;
-
- 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.
- *
- * 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.
- */
-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);
- }
- }
- }
-
- if (script_to_delete == NULL) return(1);
-
- 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;
- }
- }
-
- free(script_to_delete->script_content);
- free(script_to_delete);
- return(0);
-}
-
-
-/*
- * 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);
- }
- s->script_content = script_content;
- replaced = 1;
- }
- }
-
- 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;
- }
-}
-
-
-
-/*
- * Citadel protocol to manage sieve scripts.
- * This is basically a simplified (read: doesn't resemble IMAP) version
- * of the 'managesieve' protocol.
- */
-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));
-
- 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 (strlen(script_name) > 0) {
- cprintf("%d Transmit script now\n", SEND_LISTING);
- script_content = CtdlReadMessageBody("000", config.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");
- }
- cprintf("000\n");
- }
- else {
- cprintf("%d Invalid script name.\n", ERROR + ILLEGAL_VALUE);
- }
- }
-
- 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], "...");
- }
-
- lprintf(CTDL_INFO, "%s\n",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) {
- lprintf(CTDL_CRIT, "sieve2_alloc() returned %d: %s\n", res, sieve2_errstr(res));
- return;
- }
-
- res = sieve2_callbacks(sieve2_context, ctdl_sieve_callbacks);
- if (res != SIEVE2_OK) {
- lprintf(CTDL_CRIT, "sieve2_callbacks() returned %d: %s\n", res, sieve2_errstr(res));
- goto BAIL;
- }
-
- msiv_extensions = strdup(sieve2_listextensions(sieve2_context));
- lprintf(CTDL_INFO, "Extensions: %s\n", msiv_extensions);
-
-BAIL: res = sieve2_free(&sieve2_context);
- if (res != SIEVE2_OK) {
- lprintf(CTDL_CRIT, "sieve2_free() returned %d: %s\n", res, sieve2_errstr(res));
- }
-
-}
-
-int serv_sieve_room(struct ctdlroom *room)
-{
- if (!strcasecmp(&room->QRname[11], MAILROOM)) {
- sieve_queue_room(room);
- }
- return 0;
-}
-
-#endif /* HAVE_LIBSIEVE */
-
-CTDL_MODULE_INIT(sieve)
-{
-
-#ifdef HAVE_LIBSIEVE
-
- ctdl_sieve_init();
- CtdlRegisterProtoHook(cmd_msiv, "MSIV", "Manage Sieve scripts");
-
- CtdlRegisterRoomHook(serv_sieve_room);
-
- CtdlRegisterSessionHook(perform_sieve_processing, EVT_HOUSE);
-
-#else /* HAVE_LIBSIEVE */
-
- lprintf(CTDL_INFO, "This server is missing libsieve. Mailbox filtering will be disabled.\n");
-
-#endif /* HAVE_LIBSIEVE */
-
- /* return our Subversion id for the Log */
- return "$Id$";
-}
-
+++ /dev/null
-/*
- * $Id$
- *
- * This module is an SMTP and ESMTP implementation for the Citadel system.
- * It is compliant with all of the following:
- *
- * RFC 821 - Simple Mail Transfer Protocol
- * RFC 876 - Survey of SMTP Implementations
- * RFC 1047 - Duplicate messages and SMTP
- * RFC 1854 - command pipelining
- * RFC 1869 - Extended Simple Mail Transfer Protocol
- * RFC 1870 - SMTP Service Extension for Message Size Declaration
- * RFC 1893 - Enhanced Mail System Status Codes
- * RFC 2033 - Local Mail Transfer Protocol
- * RFC 2034 - SMTP Service Extension for Returning Enhanced Error Codes
- * RFC 2197 - SMTP Service Extension for Command Pipelining
- * RFC 2476 - Message Submission
- * RFC 2487 - SMTP Service Extension for Secure SMTP over TLS
- * RFC 2554 - SMTP Service Extension for Authentication
- * RFC 2821 - Simple Mail Transfer Protocol
- * RFC 2822 - Internet Message Format
- * RFC 2920 - SMTP Service Extension for Command Pipelining
- *
- * The VRFY and EXPN commands have been removed from this implementation
- * because nobody uses these commands anymore, except for spammers.
- *
- */
-
-#include "sysdep.h"
-#include <stdlib.h>
-#include <unistd.h>
-#include <stdio.h>
-#include <fcntl.h>
-#include <signal.h>
-#include <pwd.h>
-#include <errno.h>
-#include <sys/types.h>
-#include <syslog.h>
-
-#if TIME_WITH_SYS_TIME
-# include <sys/time.h>
-# include <time.h>
-#else
-# if HAVE_SYS_TIME_H
-# include <sys/time.h>
-# else
-# include <time.h>
-# endif
-#endif
-
-#include <sys/wait.h>
-#include <ctype.h>
-#include <string.h>
-#include <limits.h>
-#include <sys/socket.h>
-#include <netinet/in.h>
-#include <arpa/inet.h>
-#include "citadel.h"
-#include "server.h"
-#include "citserver.h"
-#include "support.h"
-#include "config.h"
-#include "control.h"
-#include "room_ops.h"
-#include "user_ops.h"
-#include "policy.h"
-#include "database.h"
-#include "msgbase.h"
-#include "tools.h"
-#include "internet_addressing.h"
-#include "genstamp.h"
-#include "domain.h"
-#include "clientsocket.h"
-#include "locate_host.h"
-#include "citadel_dirs.h"
-
-#ifdef HAVE_OPENSSL
-#include "serv_crypto.h"
-#endif
-
-
-
-#ifndef HAVE_SNPRINTF
-#include "snprintf.h"
-#endif
-
-
-#include "ctdl_module.h"
-
-
-
-struct citsmtp { /* Information about the current session */
- int command_state;
- char helo_node[SIZ];
- char from[SIZ];
- char recipients[SIZ];
- int number_of_recipients;
- int delivery_mode;
- int message_originated_locally;
- int is_lmtp;
- int is_unfiltered;
- int is_msa;
-};
-
-enum { /* Command states for login authentication */
- smtp_command,
- smtp_user,
- smtp_password,
- smtp_plain
-};
-
-#define SMTP CC->SMTP
-
-
-int run_queue_now = 0; /* Set to 1 to ignore SMTP send retry times */
-
-
-
-/*****************************************************************************/
-/* SMTP SERVER (INBOUND) STUFF */
-/*****************************************************************************/
-
-
-/*
- * Here's where our SMTP session begins its happy day.
- */
-void smtp_greeting(int is_msa)
-{
- char message_to_spammer[1024];
-
- strcpy(CC->cs_clientname, "SMTP session");
- CC->internal_pgm = 1;
- CC->cs_flags |= CS_STEALTH;
- SMTP = malloc(sizeof(struct citsmtp));
- memset(SMTP, 0, sizeof(struct citsmtp));
- SMTP->is_msa = is_msa;
-
- /* If this config option is set, reject connections from problem
- * addresses immediately instead of after they execute a RCPT
- */
- if ( (config.c_rbl_at_greeting) && (SMTP->is_msa == 0) ) {
- if (rbl_check(message_to_spammer)) {
- cprintf("550 %s\r\n", message_to_spammer);
- CC->kill_me = 1;
- /* no need to free_recipients(valid), it's not allocated yet */
- return;
- }
- }
-
- /* Otherwise we're either clean or we check later. */
-
- if (CC->nologin==1) {
- cprintf("500 Too many users are already online (maximum is %d)\r\n",
- config.c_maxsessions
- );
- CC->kill_me = 1;
- /* no need to free_recipients(valid), it's not allocated yet */
- return;
- }
-
- /* Note: the FQDN *must* appear as the first thing after the 220 code.
- * Some clients (including citmail.c) depend on it being there.
- */
- cprintf("220 %s ESMTP Citadel server ready.\r\n", config.c_fqdn);
-}
-
-
-/*
- * SMTPS is just like SMTP, except it goes crypto right away.
- */
-#ifdef HAVE_OPENSSL
-void smtps_greeting(void) {
- CtdlStartTLS(NULL, NULL, NULL);
- smtp_greeting(0);
-}
-#endif
-
-
-/*
- * SMTP MSA port requires authentication.
- */
-void smtp_msa_greeting(void) {
- smtp_greeting(1);
-}
-
-
-/*
- * LMTP is like SMTP but with some extra bonus footage added.
- */
-void lmtp_greeting(void) {
- smtp_greeting(0);
- SMTP->is_lmtp = 1;
-}
-
-
-/*
- * Generic SMTP MTA greeting
- */
-void smtp_mta_greeting(void) {
- smtp_greeting(0);
-}
-
-
-/*
- * We also have an unfiltered LMTP socket that bypasses spam filters.
- */
-void lmtp_unfiltered_greeting(void) {
- smtp_greeting(0);
- SMTP->is_lmtp = 1;
- SMTP->is_unfiltered = 1;
-}
-
-
-/*
- * Login greeting common to all auth methods
- */
-void smtp_auth_greeting(void) {
- cprintf("235 2.0.0 Hello, %s\r\n", CC->user.fullname);
- lprintf(CTDL_NOTICE, "SMTP authenticated %s\n", CC->user.fullname);
- CC->internal_pgm = 0;
- CC->cs_flags &= ~CS_STEALTH;
-}
-
-
-/*
- * Implement HELO and EHLO commands.
- *
- * which_command: 0=HELO, 1=EHLO, 2=LHLO
- */
-void smtp_hello(char *argbuf, int which_command) {
-
- safestrncpy(SMTP->helo_node, argbuf, sizeof SMTP->helo_node);
-
- if ( (which_command != 2) && (SMTP->is_lmtp) ) {
- cprintf("500 Only LHLO is allowed when running LMTP\r\n");
- return;
- }
-
- if ( (which_command == 2) && (SMTP->is_lmtp == 0) ) {
- cprintf("500 LHLO is only allowed when running LMTP\r\n");
- return;
- }
-
- if (which_command == 0) {
- cprintf("250 Hello %s (%s [%s])\r\n",
- SMTP->helo_node,
- CC->cs_host,
- CC->cs_addr
- );
- }
- else {
- if (which_command == 1) {
- cprintf("250-Hello %s (%s [%s])\r\n",
- SMTP->helo_node,
- CC->cs_host,
- CC->cs_addr
- );
- }
- else {
- cprintf("250-Greetings and joyous salutations.\r\n");
- }
- cprintf("250-HELP\r\n");
- cprintf("250-SIZE %ld\r\n", config.c_maxmsglen);
-
-#ifdef HAVE_OPENSSL
-
- /* Only offer the PIPELINING command if TLS is inactive,
- * because of flow control issues. Also, avoid offering TLS
- * if TLS is already active. Finally, we only offer TLS on
- * the SMTP-MSA port, not on the SMTP-MTA port, due to
- * questionable reliability of TLS in certain sending MTA's.
- */
- if ( (!CC->redirect_ssl) && (SMTP->is_msa) ) {
- cprintf("250-PIPELINING\r\n");
- cprintf("250-STARTTLS\r\n");
- }
-
-#else /* HAVE_OPENSSL */
-
- /* Non SSL enabled server, so always offer PIPELINING. */
- cprintf("250-PIPELINING\r\n");
-
-#endif /* HAVE_OPENSSL */
-
- cprintf("250-AUTH LOGIN PLAIN\r\n");
- cprintf("250-AUTH=LOGIN PLAIN\r\n");
-
- cprintf("250 ENHANCEDSTATUSCODES\r\n");
- }
-}
-
-
-
-/*
- * Implement HELP command.
- */
-void smtp_help(void) {
- cprintf("214-Commands accepted:\r\n");
- cprintf("214- DATA\r\n");
- cprintf("214- EHLO\r\n");
- cprintf("214- HELO\r\n");
- cprintf("214- HELP\r\n");
- cprintf("214- MAIL\r\n");
- cprintf("214- NOOP\r\n");
- cprintf("214- QUIT\r\n");
- cprintf("214- RCPT\r\n");
- cprintf("214- RSET\r\n");
- cprintf("214 \r\n");
-}
-
-
-/*
- *
- */
-void smtp_get_user(char *argbuf) {
- char buf[SIZ];
- char username[SIZ];
-
- CtdlDecodeBase64(username, argbuf, SIZ);
- /* lprintf(CTDL_DEBUG, "Trying <%s>\n", username); */
- if (CtdlLoginExistingUser(NULL, username) == login_ok) {
- CtdlEncodeBase64(buf, "Password:", 9);
- cprintf("334 %s\r\n", buf);
- SMTP->command_state = smtp_password;
- }
- else {
- cprintf("500 5.7.0 No such user.\r\n");
- SMTP->command_state = smtp_command;
- }
-}
-
-
-/*
- *
- */
-void smtp_get_pass(char *argbuf) {
- char password[SIZ];
-
- CtdlDecodeBase64(password, argbuf, SIZ);
- /* lprintf(CTDL_DEBUG, "Trying <%s>\n", password); */
- if (CtdlTryPassword(password) == pass_ok) {
- smtp_auth_greeting();
- }
- else {
- cprintf("535 5.7.0 Authentication failed.\r\n");
- }
- SMTP->command_state = smtp_command;
-}
-
-
-/*
- * Back end for PLAIN auth method (either inline or multistate)
- */
-void smtp_try_plain(char *encoded_authstring) {
- char decoded_authstring[1024];
- char ident[256];
- char user[256];
- char pass[256];
- int result;
-
- CtdlDecodeBase64(decoded_authstring, encoded_authstring, strlen(encoded_authstring) );
- safestrncpy(ident, decoded_authstring, sizeof ident);
- safestrncpy(user, &decoded_authstring[strlen(ident) + 1], sizeof user);
- safestrncpy(pass, &decoded_authstring[strlen(ident) + strlen(user) + 2], sizeof pass);
-
- SMTP->command_state = smtp_command;
-
- if (strlen(ident) > 0) {
- result = CtdlLoginExistingUser(user, ident);
- }
- else {
- result = CtdlLoginExistingUser(NULL, user);
- }
-
- if (result == login_ok) {
- if (CtdlTryPassword(pass) == pass_ok) {
- smtp_auth_greeting();
- return;
- }
- }
- cprintf("504 5.7.4 Authentication failed.\r\n");
-}
-
-
-/*
- * Attempt to perform authenticated SMTP
- */
-void smtp_auth(char *argbuf) {
- char username_prompt[64];
- char method[64];
- char encoded_authstring[1024];
-
- if (CC->logged_in) {
- cprintf("504 5.7.4 Already logged in.\r\n");
- return;
- }
-
- extract_token(method, argbuf, 0, ' ', sizeof method);
-
- if (!strncasecmp(method, "login", 5) ) {
- if (strlen(argbuf) >= 7) {
- smtp_get_user(&argbuf[6]);
- }
- else {
- CtdlEncodeBase64(username_prompt, "Username:", 9);
- cprintf("334 %s\r\n", username_prompt);
- SMTP->command_state = smtp_user;
- }
- return;
- }
-
- if (!strncasecmp(method, "plain", 5) ) {
- if (num_tokens(argbuf, ' ') < 2) {
- cprintf("334 \r\n");
- SMTP->command_state = smtp_plain;
- return;
- }
-
- extract_token(encoded_authstring, argbuf, 1, ' ', sizeof encoded_authstring);
-
- smtp_try_plain(encoded_authstring);
- return;
- }
-
- if (strncasecmp(method, "login", 5) ) {
- cprintf("504 5.7.4 Unknown authentication method.\r\n");
- return;
- }
-
-}
-
-
-/*
- * Implements the RSET (reset state) command.
- * Currently this just zeroes out the state buffer. If pointers to data
- * allocated with malloc() are ever placed in the state buffer, we have to
- * be sure to free() them first!
- *
- * Set do_response to nonzero to output the SMTP RSET response code.
- */
-void smtp_rset(int do_response) {
- int is_lmtp;
- int is_unfiltered;
-
- /*
- * Our entire SMTP state is discarded when a RSET command is issued,
- * but we need to preserve this one little piece of information, so
- * we save it for later.
- */
- is_lmtp = SMTP->is_lmtp;
- is_unfiltered = SMTP->is_unfiltered;
-
- memset(SMTP, 0, sizeof(struct citsmtp));
-
- /*
- * It is somewhat ambiguous whether we want to log out when a RSET
- * command is issued. Here's the code to do it. It is commented out
- * because some clients (such as Pine) issue RSET commands before
- * each message, but still expect to be logged in.
- *
- * if (CC->logged_in) {
- * logout(CC);
- * }
- */
-
- /*
- * Reinstate this little piece of information we saved (see above).
- */
- SMTP->is_lmtp = is_lmtp;
- SMTP->is_unfiltered = is_unfiltered;
-
- if (do_response) {
- cprintf("250 2.0.0 Zap!\r\n");
- }
-}
-
-/*
- * Clear out the portions of the state buffer that need to be cleared out
- * after the DATA command finishes.
- */
-void smtp_data_clear(void) {
- strcpy(SMTP->from, "");
- strcpy(SMTP->recipients, "");
- SMTP->number_of_recipients = 0;
- SMTP->delivery_mode = 0;
- SMTP->message_originated_locally = 0;
-}
-
-
-
-/*
- * Implements the "MAIL From:" command
- */
-void smtp_mail(char *argbuf) {
- char user[SIZ];
- char node[SIZ];
- char name[SIZ];
-
- if (strlen(SMTP->from) != 0) {
- cprintf("503 5.1.0 Only one sender permitted\r\n");
- return;
- }
-
- if (strncasecmp(argbuf, "From:", 5)) {
- cprintf("501 5.1.7 Syntax error\r\n");
- return;
- }
-
- strcpy(SMTP->from, &argbuf[5]);
- striplt(SMTP->from);
- if (haschar(SMTP->from, '<') > 0) {
- stripallbut(SMTP->from, '<', '>');
- }
-
- /* We used to reject empty sender names, until it was brought to our
- * attention that RFC1123 5.2.9 requires that this be allowed. So now
- * we allow it, but replace the empty string with a fake
- * address so we don't have to contend with the empty string causing
- * other code to fail when it's expecting something there.
- */
- if (strlen(SMTP->from) == 0) {
- strcpy(SMTP->from, "someone@somewhere.org");
- }
-
- /* If this SMTP connection is from a logged-in user, force the 'from'
- * to be the user's Internet e-mail address as Citadel knows it.
- */
- if (CC->logged_in) {
- safestrncpy(SMTP->from, CC->cs_inet_email, sizeof SMTP->from);
- cprintf("250 2.1.0 Sender ok <%s>\r\n", SMTP->from);
- SMTP->message_originated_locally = 1;
- return;
- }
-
- else if (SMTP->is_lmtp) {
- /* Bypass forgery checking for LMTP */
- }
-
- /* Otherwise, make sure outsiders aren't trying to forge mail from
- * this system (unless, of course, c_allow_spoofing is enabled)
- */
- else if (config.c_allow_spoofing == 0) {
- process_rfc822_addr(SMTP->from, user, node, name);
- if (CtdlHostAlias(node) != hostalias_nomatch) {
- cprintf("550 5.7.1 "
- "You must log in to send mail from %s\r\n",
- node);
- strcpy(SMTP->from, "");
- return;
- }
- }
-
- cprintf("250 2.0.0 Sender ok\r\n");
-}
-
-
-
-/*
- * Implements the "RCPT To:" command
- */
-void smtp_rcpt(char *argbuf) {
- char recp[1024];
- char message_to_spammer[SIZ];
- struct recptypes *valid = NULL;
-
- if (strlen(SMTP->from) == 0) {
- cprintf("503 5.5.1 Need MAIL before RCPT\r\n");
- return;
- }
-
- if (strncasecmp(argbuf, "To:", 3)) {
- cprintf("501 5.1.7 Syntax error\r\n");
- return;
- }
-
- if ( (SMTP->is_msa) && (!CC->logged_in) ) {
- cprintf("550 5.1.8 "
- "You must log in to send mail on this port.\r\n");
- strcpy(SMTP->from, "");
- return;
- }
-
- safestrncpy(recp, &argbuf[3], sizeof recp);
- striplt(recp);
- stripallbut(recp, '<', '>');
-
- if ( (strlen(recp) + strlen(SMTP->recipients) + 1 ) >= SIZ) {
- cprintf("452 4.5.3 Too many recipients\r\n");
- return;
- }
-
- /* RBL check */
- if ( (!CC->logged_in) /* Don't RBL authenticated users */
- && (!SMTP->is_lmtp) ) { /* Don't RBL LMTP clients */
- if (config.c_rbl_at_greeting == 0) { /* Don't RBL again if we already did it */
- if (rbl_check(message_to_spammer)) {
- cprintf("550 %s\r\n", message_to_spammer);
- /* no need to free_recipients(valid), it's not allocated yet */
- return;
- }
- }
- }
-
- valid = validate_recipients(recp);
- if (valid->num_error != 0) {
- cprintf("599 5.1.1 Error: %s\r\n", valid->errormsg);
- free_recipients(valid);
- return;
- }
-
- if (valid->num_internet > 0) {
- if (CC->logged_in) {
- if (CtdlCheckInternetMailPermission(&CC->user)==0) {
- cprintf("551 5.7.1 <%s> - you do not have permission to send Internet mail\r\n", recp);
- free_recipients(valid);
- return;
- }
- }
- }
-
- if (valid->num_internet > 0) {
- if ( (SMTP->message_originated_locally == 0)
- && (SMTP->is_lmtp == 0) ) {
- cprintf("551 5.7.1 <%s> - relaying denied\r\n", recp);
- free_recipients(valid);
- return;
- }
- }
-
- cprintf("250 2.1.5 RCPT ok <%s>\r\n", recp);
- if (strlen(SMTP->recipients) > 0) {
- strcat(SMTP->recipients, ",");
- }
- strcat(SMTP->recipients, recp);
- SMTP->number_of_recipients += 1;
- if (valid != NULL) {
- free_recipients(valid);
- }
-}
-
-
-
-
-/*
- * Implements the DATA command
- */
-void smtp_data(void) {
- char *body;
- struct CtdlMessage *msg = NULL;
- long msgnum = (-1L);
- char nowstamp[SIZ];
- struct recptypes *valid;
- int scan_errors;
- int i;
- char result[SIZ];
-
- if (strlen(SMTP->from) == 0) {
- cprintf("503 5.5.1 Need MAIL command first.\r\n");
- return;
- }
-
- if (SMTP->number_of_recipients < 1) {
- cprintf("503 5.5.1 Need RCPT command first.\r\n");
- return;
- }
-
- cprintf("354 Transmit message now - terminate with '.' by itself\r\n");
-
- datestring(nowstamp, sizeof nowstamp, time(NULL), DATESTRING_RFC822);
- body = malloc(4096);
-
- if (body != NULL) snprintf(body, 4096,
- "Received: from %s (%s [%s])\n"
- " by %s; %s\n",
- SMTP->helo_node,
- CC->cs_host,
- CC->cs_addr,
- config.c_fqdn,
- nowstamp);
-
- body = CtdlReadMessageBody(".", config.c_maxmsglen, body, 1);
- if (body == NULL) {
- cprintf("550 5.6.5 "
- "Unable to save message: internal error.\r\n");
- return;
- }
-
- lprintf(CTDL_DEBUG, "Converting message...\n");
- msg = convert_internet_message(body);
-
- /* If the user is locally authenticated, FORCE the From: header to
- * show up as the real sender. Yes, this violates the RFC standard,
- * but IT MAKES SENSE. If you prefer strict RFC adherence over
- * common sense, you can disable this in the configuration.
- *
- * We also set the "message room name" ('O' field) to MAILROOM
- * (which is Mail> on most systems) to prevent it from getting set
- * to something ugly like "0000058008.Sent Items>" when the message
- * is read with a Citadel client.
- */
- if ( (CC->logged_in) && (config.c_rfc822_strict_from == 0) ) {
- if (msg->cm_fields['A'] != NULL) free(msg->cm_fields['A']);
- if (msg->cm_fields['N'] != NULL) free(msg->cm_fields['N']);
- if (msg->cm_fields['H'] != NULL) free(msg->cm_fields['H']);
- if (msg->cm_fields['F'] != NULL) free(msg->cm_fields['F']);
- if (msg->cm_fields['O'] != NULL) free(msg->cm_fields['O']);
- msg->cm_fields['A'] = strdup(CC->user.fullname);
- msg->cm_fields['N'] = strdup(config.c_nodename);
- msg->cm_fields['H'] = strdup(config.c_humannode);
- msg->cm_fields['F'] = strdup(CC->cs_inet_email);
- msg->cm_fields['O'] = strdup(MAILROOM);
- }
-
- /* Set the "envelope from" address */
- if (msg->cm_fields['P'] != NULL) {
- free(msg->cm_fields['P']);
- }
- msg->cm_fields['P'] = strdup(SMTP->from);
-
- /* Set the "envelope to" address */
- if (msg->cm_fields['V'] != NULL) {
- free(msg->cm_fields['V']);
- }
- msg->cm_fields['V'] = strdup(SMTP->recipients);
-
- /* Submit the message into the Citadel system. */
- valid = validate_recipients(SMTP->recipients);
-
- /* If there are modules that want to scan this message before final
- * submission (such as virus checkers or spam filters), call them now
- * and give them an opportunity to reject the message.
- */
- if (SMTP->is_unfiltered) {
- scan_errors = 0;
- }
- else {
- scan_errors = PerformMessageHooks(msg, EVT_SMTPSCAN);
- }
-
- if (scan_errors > 0) { /* We don't want this message! */
-
- if (msg->cm_fields['0'] == NULL) {
- msg->cm_fields['0'] = strdup(
- "5.7.1 Message rejected by filter");
- }
-
- sprintf(result, "550 %s\r\n", msg->cm_fields['0']);
- }
-
- else { /* Ok, we'll accept this message. */
- msgnum = CtdlSubmitMsg(msg, valid, "");
- if (msgnum > 0L) {
- sprintf(result, "250 2.0.0 Message accepted.\r\n");
- }
- else {
- sprintf(result, "550 5.5.0 Internal delivery error\r\n");
- }
- }
-
- /* For SMTP and ESTMP, just print the result message. For LMTP, we
- * have to print one result message for each recipient. Since there
- * is nothing in Citadel which would cause different recipients to
- * have different results, we can get away with just spitting out the
- * same message once for each recipient.
- */
- if (SMTP->is_lmtp) {
- for (i=0; i<SMTP->number_of_recipients; ++i) {
- cprintf("%s", result);
- }
- }
- else {
- cprintf("%s", result);
- }
-
- /* Write something to the syslog (which may or may not be where the
- * rest of the Citadel logs are going; some sysadmins want LOG_MAIL).
- */
- if (enable_syslog) {
- syslog((LOG_MAIL | LOG_INFO),
- "%ld: from=<%s>, nrcpts=%d, relay=%s [%s], stat=%s",
- msgnum,
- SMTP->from,
- SMTP->number_of_recipients,
- CC->cs_host,
- CC->cs_addr,
- result
- );
- }
-
- /* Clean up */
- CtdlFreeMessage(msg);
- free_recipients(valid);
- smtp_data_clear(); /* clear out the buffers now */
-}
-
-
-/*
- * implements the STARTTLS command (Citadel API version)
- */
-#ifdef HAVE_OPENSSL
-void smtp_starttls(void)
-{
- char ok_response[SIZ];
- char nosup_response[SIZ];
- char error_response[SIZ];
-
- sprintf(ok_response,
- "200 2.0.0 Begin TLS negotiation now\r\n");
- sprintf(nosup_response,
- "554 5.7.3 TLS not supported here\r\n");
- sprintf(error_response,
- "554 5.7.3 Internal error\r\n");
- CtdlStartTLS(ok_response, nosup_response, error_response);
- smtp_rset(0);
-}
-#endif
-
-
-
-/*
- * Main command loop for SMTP sessions.
- */
-void smtp_command_loop(void) {
- char cmdbuf[SIZ];
-
- time(&CC->lastcmd);
- memset(cmdbuf, 0, sizeof cmdbuf); /* Clear it, just in case */
- if (client_getln(cmdbuf, sizeof cmdbuf) < 1) {
- lprintf(CTDL_CRIT, "Client disconnected: ending session.\n");
- CC->kill_me = 1;
- return;
- }
- lprintf(CTDL_INFO, "SMTP: %s\n", cmdbuf);
- while (strlen(cmdbuf) < 5) strcat(cmdbuf, " ");
-
- if (SMTP->command_state == smtp_user) {
- smtp_get_user(cmdbuf);
- }
-
- else if (SMTP->command_state == smtp_password) {
- smtp_get_pass(cmdbuf);
- }
-
- else if (SMTP->command_state == smtp_plain) {
- smtp_try_plain(cmdbuf);
- }
-
- else if (!strncasecmp(cmdbuf, "AUTH", 4)) {
- smtp_auth(&cmdbuf[5]);
- }
-
- else if (!strncasecmp(cmdbuf, "DATA", 4)) {
- smtp_data();
- }
-
- else if (!strncasecmp(cmdbuf, "HELO", 4)) {
- smtp_hello(&cmdbuf[5], 0);
- }
-
- else if (!strncasecmp(cmdbuf, "EHLO", 4)) {
- smtp_hello(&cmdbuf[5], 1);
- }
-
- else if (!strncasecmp(cmdbuf, "LHLO", 4)) {
- smtp_hello(&cmdbuf[5], 2);
- }
-
- else if (!strncasecmp(cmdbuf, "HELP", 4)) {
- smtp_help();
- }
-
- else if (!strncasecmp(cmdbuf, "MAIL", 4)) {
- smtp_mail(&cmdbuf[5]);
- }
-
- else if (!strncasecmp(cmdbuf, "NOOP", 4)) {
- cprintf("250 NOOP\r\n");
- }
-
- else if (!strncasecmp(cmdbuf, "QUIT", 4)) {
- cprintf("221 Goodbye...\r\n");
- CC->kill_me = 1;
- return;
- }
-
- else if (!strncasecmp(cmdbuf, "RCPT", 4)) {
- smtp_rcpt(&cmdbuf[5]);
- }
-
- else if (!strncasecmp(cmdbuf, "RSET", 4)) {
- smtp_rset(1);
- }
-#ifdef HAVE_OPENSSL
- else if (!strcasecmp(cmdbuf, "STARTTLS")) {
- smtp_starttls();
- }
-#endif
- else {
- cprintf("502 5.0.0 I'm afraid I can't do that.\r\n");
- }
-
-
-}
-
-
-
-
-/*****************************************************************************/
-/* SMTP CLIENT (OUTBOUND PROCESSING) STUFF */
-/*****************************************************************************/
-
-
-
-/*
- * smtp_try()
- *
- * Called by smtp_do_procmsg() to attempt delivery to one SMTP host
- *
- */
-void smtp_try(const char *key, const char *addr, int *status,
- char *dsn, size_t n, long msgnum)
-{
- int sock = (-1);
- char mxhosts[1024];
- int num_mxhosts;
- int mx;
- int i;
- char user[1024], node[1024], name[1024];
- char buf[1024];
- char mailfrom[1024];
- char mx_user[256];
- char mx_pass[256];
- char mx_host[256];
- char mx_port[256];
- int lp, rp;
- char *msgtext;
- char *ptr;
- size_t msg_size;
- int scan_done;
-
- /* Parse out the host portion of the recipient address */
- process_rfc822_addr(addr, user, node, name);
-
- lprintf(CTDL_DEBUG, "Attempting SMTP delivery to <%s> @ <%s> (%s)\n",
- user, node, name);
-
- /* Load the message out of the database */
- CC->redirect_buffer = malloc(SIZ);
- CC->redirect_len = 0;
- CC->redirect_alloc = SIZ;
- CtdlOutputMsg(msgnum, MT_RFC822, HEADERS_ALL, 0, 1, NULL);
- msgtext = CC->redirect_buffer;
- msg_size = CC->redirect_len;
- CC->redirect_buffer = NULL;
- CC->redirect_len = 0;
- CC->redirect_alloc = 0;
-
- /* Extract something to send later in the 'MAIL From:' command */
- strcpy(mailfrom, "");
- scan_done = 0;
- ptr = msgtext;
- do {
- if (ptr = memreadline(ptr, buf, sizeof buf), *ptr == 0) {
- scan_done = 1;
- }
- if (!strncasecmp(buf, "From:", 5)) {
- safestrncpy(mailfrom, &buf[5], sizeof mailfrom);
- striplt(mailfrom);
- for (i=0; i<strlen(mailfrom); ++i) {
- if (!isprint(mailfrom[i])) {
- strcpy(&mailfrom[i], &mailfrom[i+1]);
- i=0;
- }
- }
-
- /* Strip out parenthesized names */
- lp = (-1);
- rp = (-1);
- for (i=0; i<strlen(mailfrom); ++i) {
- if (mailfrom[i] == '(') lp = i;
- if (mailfrom[i] == ')') rp = i;
- }
- if ((lp>0)&&(rp>lp)) {
- strcpy(&mailfrom[lp-1], &mailfrom[rp+1]);
- }
-
- /* Prefer brokketized names */
- lp = (-1);
- rp = (-1);
- for (i=0; i<strlen(mailfrom); ++i) {
- if (mailfrom[i] == '<') lp = i;
- if (mailfrom[i] == '>') rp = i;
- }
- if ( (lp>=0) && (rp>lp) ) {
- mailfrom[rp] = 0;
- strcpy(mailfrom, &mailfrom[lp]);
- }
-
- scan_done = 1;
- }
- } while (scan_done == 0);
- if (strlen(mailfrom)==0) strcpy(mailfrom, "someone@somewhere.org");
- stripallbut(mailfrom, '<', '>');
-
- /* Figure out what mail exchanger host we have to connect to */
- num_mxhosts = getmx(mxhosts, node);
- lprintf(CTDL_DEBUG, "Number of MX hosts for <%s> is %d\n", node, num_mxhosts);
- if (num_mxhosts < 1) {
- *status = 5;
- snprintf(dsn, SIZ, "No MX hosts found for <%s>", node);
- return;
- }
-
- sock = (-1);
- for (mx=0; (mx<num_mxhosts && sock < 0); ++mx) {
- char *endpart;
- extract_token(buf, mxhosts, mx, '|', sizeof buf);
- strcpy(mx_user, "");
- strcpy(mx_pass, "");
- if (num_tokens(buf, '@') > 1) {
- strcpy (mx_user, buf);
- endpart = strrchr(mx_user, '@');
- *endpart = '\0';
- strcpy (mx_host, endpart + 1);
- endpart = strrchr(mx_user, ':');
- if (endpart != NULL) {
- strcpy(mx_pass, endpart+1);
- *endpart = '\0';
- }
- }
- else
- strcpy (mx_host, buf);
- endpart = strrchr(mx_host, ':');
- if (endpart != 0){
- *endpart = '\0';
- strcpy(mx_port, endpart + 1);
- }
- else {
- strcpy(mx_port, "25");
- }
- lprintf(CTDL_DEBUG, "Trying %s : %s ...\n", mx_host, mx_port);
- sock = sock_connect(mx_host, mx_port, "tcp");
- snprintf(dsn, SIZ, "Could not connect: %s", strerror(errno));
- if (sock >= 0) lprintf(CTDL_DEBUG, "Connected!\n");
- if (sock < 0) {
- if (errno > 0) {
- snprintf(dsn, SIZ, "%s", strerror(errno));
- }
- else {
- snprintf(dsn, SIZ, "Unable to connect to %s : %s\n", mx_host, mx_port);
- }
- }
- }
-
- if (sock < 0) {
- *status = 4; /* dsn is already filled in */
- return;
- }
-
- /* Process the SMTP greeting from the server */
- if (ml_sock_gets(sock, buf) < 0) {
- *status = 4;
- strcpy(dsn, "Connection broken during SMTP conversation");
- goto bail;
- }
- lprintf(CTDL_DEBUG, "<%s\n", buf);
- if (buf[0] != '2') {
- if (buf[0] == '4') {
- *status = 4;
- safestrncpy(dsn, &buf[4], 1023);
- goto bail;
- }
- else {
- *status = 5;
- safestrncpy(dsn, &buf[4], 1023);
- goto bail;
- }
- }
-
- /* At this point we know we are talking to a real SMTP server */
-
- /* Do a EHLO command. If it fails, try the HELO command. */
- snprintf(buf, sizeof buf, "EHLO %s\r\n", config.c_fqdn);
- lprintf(CTDL_DEBUG, ">%s", buf);
- sock_write(sock, buf, strlen(buf));
- if (ml_sock_gets(sock, buf) < 0) {
- *status = 4;
- strcpy(dsn, "Connection broken during SMTP HELO");
- goto bail;
- }
- lprintf(CTDL_DEBUG, "<%s\n", buf);
- if (buf[0] != '2') {
- snprintf(buf, sizeof buf, "HELO %s\r\n", config.c_fqdn);
- lprintf(CTDL_DEBUG, ">%s", buf);
- sock_write(sock, buf, strlen(buf));
- if (ml_sock_gets(sock, buf) < 0) {
- *status = 4;
- strcpy(dsn, "Connection broken during SMTP HELO");
- goto bail;
- }
- }
- if (buf[0] != '2') {
- if (buf[0] == '4') {
- *status = 4;
- safestrncpy(dsn, &buf[4], 1023);
- goto bail;
- }
- else {
- *status = 5;
- safestrncpy(dsn, &buf[4], 1023);
- goto bail;
- }
- }
-
- /* Do an AUTH command if necessary */
- if (strlen(mx_user) > 0) {
- char encoded[1024];
- sprintf(buf, "%s%c%s%c%s", mx_user, '\0', mx_user, '\0', mx_pass);
- CtdlEncodeBase64(encoded, buf, strlen(mx_user) + strlen(mx_user) + strlen(mx_pass) + 2);
- snprintf(buf, sizeof buf, "AUTH PLAIN %s\r\n", encoded);
- lprintf(CTDL_DEBUG, ">%s", buf);
- sock_write(sock, buf, strlen(buf));
- if (ml_sock_gets(sock, buf) < 0) {
- *status = 4;
- strcpy(dsn, "Connection broken during SMTP AUTH");
- goto bail;
- }
- lprintf(CTDL_DEBUG, "<%s\n", buf);
- if (buf[0] != '2') {
- if (buf[0] == '4') {
- *status = 4;
- safestrncpy(dsn, &buf[4], 1023);
- goto bail;
- }
- else {
- *status = 5;
- safestrncpy(dsn, &buf[4], 1023);
- goto bail;
- }
- }
- }
-
- /* previous command succeeded, now try the MAIL From: command */
- snprintf(buf, sizeof buf, "MAIL From: <%s>\r\n", mailfrom);
- lprintf(CTDL_DEBUG, ">%s", buf);
- sock_write(sock, buf, strlen(buf));
- if (ml_sock_gets(sock, buf) < 0) {
- *status = 4;
- strcpy(dsn, "Connection broken during SMTP MAIL");
- goto bail;
- }
- lprintf(CTDL_DEBUG, "<%s\n", buf);
- if (buf[0] != '2') {
- if (buf[0] == '4') {
- *status = 4;
- safestrncpy(dsn, &buf[4], 1023);
- goto bail;
- }
- else {
- *status = 5;
- safestrncpy(dsn, &buf[4], 1023);
- goto bail;
- }
- }
-
- /* MAIL succeeded, now try the RCPT To: command */
- snprintf(buf, sizeof buf, "RCPT To: <%s@%s>\r\n", user, node);
- lprintf(CTDL_DEBUG, ">%s", buf);
- sock_write(sock, buf, strlen(buf));
- if (ml_sock_gets(sock, buf) < 0) {
- *status = 4;
- strcpy(dsn, "Connection broken during SMTP RCPT");
- goto bail;
- }
- lprintf(CTDL_DEBUG, "<%s\n", buf);
- if (buf[0] != '2') {
- if (buf[0] == '4') {
- *status = 4;
- safestrncpy(dsn, &buf[4], 1023);
- goto bail;
- }
- else {
- *status = 5;
- safestrncpy(dsn, &buf[4], 1023);
- goto bail;
- }
- }
-
- /* RCPT succeeded, now try the DATA command */
- lprintf(CTDL_DEBUG, ">DATA\n");
- sock_write(sock, "DATA\r\n", 6);
- if (ml_sock_gets(sock, buf) < 0) {
- *status = 4;
- strcpy(dsn, "Connection broken during SMTP DATA");
- goto bail;
- }
- lprintf(CTDL_DEBUG, "<%s\n", buf);
- if (buf[0] != '3') {
- if (buf[0] == '4') {
- *status = 3;
- safestrncpy(dsn, &buf[4], 1023);
- goto bail;
- }
- else {
- *status = 5;
- safestrncpy(dsn, &buf[4], 1023);
- goto bail;
- }
- }
-
- /* If we reach this point, the server is expecting data */
- sock_write(sock, msgtext, msg_size);
- if (msgtext[msg_size-1] != 10) {
- lprintf(CTDL_WARNING, "Possible problem: message did not "
- "correctly terminate. (expecting 0x10, got 0x%02x)\n",
- buf[msg_size-1]);
- }
-
- sock_write(sock, ".\r\n", 3);
- if (ml_sock_gets(sock, buf) < 0) {
- *status = 4;
- strcpy(dsn, "Connection broken during SMTP message transmit");
- goto bail;
- }
- lprintf(CTDL_DEBUG, "%s\n", buf);
- if (buf[0] != '2') {
- if (buf[0] == '4') {
- *status = 4;
- safestrncpy(dsn, &buf[4], 1023);
- goto bail;
- }
- else {
- *status = 5;
- safestrncpy(dsn, &buf[4], 1023);
- goto bail;
- }
- }
-
- /* We did it! */
- safestrncpy(dsn, &buf[4], 1023);
- *status = 2;
-
- lprintf(CTDL_DEBUG, ">QUIT\n");
- sock_write(sock, "QUIT\r\n", 6);
- ml_sock_gets(sock, buf);
- lprintf(CTDL_DEBUG, "<%s\n", buf);
- lprintf(CTDL_INFO, "SMTP delivery to <%s> @ <%s> (%s) succeeded\n",
- user, node, name);
-
-bail: free(msgtext);
- sock_close(sock);
-
- /* Write something to the syslog (which may or may not be where the
- * rest of the Citadel logs are going; some sysadmins want LOG_MAIL).
- */
- if (enable_syslog) {
- syslog((LOG_MAIL | LOG_INFO),
- "%ld: to=<%s>, relay=%s, stat=%s",
- msgnum,
- addr,
- mx_host,
- dsn
- );
- }
-
- return;
-}
-
-
-
-/*
- * smtp_do_bounce() is caled by smtp_do_procmsg() to scan a set of delivery
- * instructions for "5" codes (permanent fatal errors) and produce/deliver
- * a "bounce" message (delivery status notification).
- */
-void smtp_do_bounce(char *instr) {
- int i;
- int lines;
- int status;
- char buf[1024];
- char key[1024];
- char addr[1024];
- char dsn[1024];
- char bounceto[1024];
- char boundary[64];
- int num_bounces = 0;
- int bounce_this = 0;
- long bounce_msgid = (-1);
- time_t submitted = 0L;
- struct CtdlMessage *bmsg = NULL;
- int give_up = 0;
- struct recptypes *valid;
- int successful_bounce = 0;
- static int seq = 0;
- char *omsgtext;
- size_t omsgsize;
- long omsgid = (-1);
-
- lprintf(CTDL_DEBUG, "smtp_do_bounce() called\n");
- strcpy(bounceto, "");
- sprintf(boundary, "=_Citadel_Multipart_%s_%04x%04x", config.c_fqdn, getpid(), ++seq);
- lines = num_tokens(instr, '\n');
-
- /* See if it's time to give up on delivery of this message */
- for (i=0; i<lines; ++i) {
- extract_token(buf, instr, i, '\n', sizeof buf);
- extract_token(key, buf, 0, '|', sizeof key);
- extract_token(addr, buf, 1, '|', sizeof addr);
- if (!strcasecmp(key, "submitted")) {
- submitted = atol(addr);
- }
- }
-
- if ( (time(NULL) - submitted) > SMTP_GIVE_UP ) {
- give_up = 1;
- }
-
- /* Start building our bounce message */
-
- bmsg = (struct CtdlMessage *) malloc(sizeof(struct CtdlMessage));
- if (bmsg == NULL) return;
- memset(bmsg, 0, sizeof(struct CtdlMessage));
-
- bmsg->cm_magic = CTDLMESSAGE_MAGIC;
- bmsg->cm_anon_type = MES_NORMAL;
- bmsg->cm_format_type = FMT_RFC822;
- bmsg->cm_fields['A'] = strdup("Citadel");
- bmsg->cm_fields['O'] = strdup(MAILROOM);
- bmsg->cm_fields['N'] = strdup(config.c_nodename);
- bmsg->cm_fields['U'] = strdup("Delivery Status Notification (Failure)");
- bmsg->cm_fields['M'] = malloc(1024);
-
- strcpy(bmsg->cm_fields['M'], "Content-type: multipart/mixed; boundary=\"");
- strcat(bmsg->cm_fields['M'], boundary);
- strcat(bmsg->cm_fields['M'], "\"\r\n");
- strcat(bmsg->cm_fields['M'], "MIME-Version: 1.0\r\n");
- strcat(bmsg->cm_fields['M'], "X-Mailer: " CITADEL "\r\n");
- strcat(bmsg->cm_fields['M'], "\r\nThis is a multipart message in MIME format.\r\n\r\n");
- strcat(bmsg->cm_fields['M'], "--");
- strcat(bmsg->cm_fields['M'], boundary);
- strcat(bmsg->cm_fields['M'], "\r\n");
- strcat(bmsg->cm_fields['M'], "Content-type: text/plain\r\n\r\n");
-
- if (give_up) strcat(bmsg->cm_fields['M'],
-"A message you sent could not be delivered to some or all of its recipients\n"
-"due to prolonged unavailability of its destination(s).\n"
-"Giving up on the following addresses:\n\n"
-);
-
- else strcat(bmsg->cm_fields['M'],
-"A message you sent could not be delivered to some or all of its recipients.\n"
-"The following addresses were undeliverable:\n\n"
-);
-
- /*
- * Now go through the instructions checking for stuff.
- */
- for (i=0; i<lines; ++i) {
- extract_token(buf, instr, i, '\n', sizeof buf);
- extract_token(key, buf, 0, '|', sizeof key);
- extract_token(addr, buf, 1, '|', sizeof addr);
- status = extract_int(buf, 2);
- extract_token(dsn, buf, 3, '|', sizeof dsn);
- bounce_this = 0;
-
- lprintf(CTDL_DEBUG, "key=<%s> addr=<%s> status=%d dsn=<%s>\n",
- key, addr, status, dsn);
-
- if (!strcasecmp(key, "bounceto")) {
- strcpy(bounceto, addr);
- }
-
- if (!strcasecmp(key, "msgid")) {
- omsgid = atol(addr);
- }
-
- if (!strcasecmp(key, "remote")) {
- if (status == 5) bounce_this = 1;
- if (give_up) bounce_this = 1;
- }
-
- if (bounce_this) {
- ++num_bounces;
-
- if (bmsg->cm_fields['M'] == NULL) {
- lprintf(CTDL_ERR, "ERROR ... M field is null "
- "(%s:%d)\n", __FILE__, __LINE__);
- }
-
- bmsg->cm_fields['M'] = realloc(bmsg->cm_fields['M'],
- strlen(bmsg->cm_fields['M']) + 1024 );
- strcat(bmsg->cm_fields['M'], addr);
- strcat(bmsg->cm_fields['M'], ": ");
- strcat(bmsg->cm_fields['M'], dsn);
- strcat(bmsg->cm_fields['M'], "\r\n");
-
- remove_token(instr, i, '\n');
- --i;
- --lines;
- }
- }
-
- /* Attach the original message */
- if (omsgid >= 0) {
- strcat(bmsg->cm_fields['M'], "--");
- strcat(bmsg->cm_fields['M'], boundary);
- strcat(bmsg->cm_fields['M'], "\r\n");
- strcat(bmsg->cm_fields['M'], "Content-type: message/rfc822\r\n");
- strcat(bmsg->cm_fields['M'], "Content-Transfer-Encoding: 7bit\r\n");
- strcat(bmsg->cm_fields['M'], "Content-Disposition: inline\r\n");
- strcat(bmsg->cm_fields['M'], "\r\n");
-
- CC->redirect_buffer = malloc(SIZ);
- CC->redirect_len = 0;
- CC->redirect_alloc = SIZ;
- CtdlOutputMsg(omsgid, MT_RFC822, HEADERS_ALL, 0, 1, NULL);
- omsgtext = CC->redirect_buffer;
- omsgsize = CC->redirect_len;
- CC->redirect_buffer = NULL;
- CC->redirect_len = 0;
- CC->redirect_alloc = 0;
- bmsg->cm_fields['M'] = realloc(bmsg->cm_fields['M'],
- (strlen(bmsg->cm_fields['M']) + omsgsize + 1024) );
- strcat(bmsg->cm_fields['M'], omsgtext);
- free(omsgtext);
- }
-
- /* Close the multipart MIME scope */
- strcat(bmsg->cm_fields['M'], "--");
- strcat(bmsg->cm_fields['M'], boundary);
- strcat(bmsg->cm_fields['M'], "--\r\n");
-
- /* Deliver the bounce if there's anything worth mentioning */
- lprintf(CTDL_DEBUG, "num_bounces = %d\n", num_bounces);
- if (num_bounces > 0) {
-
- /* First try the user who sent the message */
- lprintf(CTDL_DEBUG, "bounce to user? <%s>\n", bounceto);
- if (strlen(bounceto) == 0) {
- lprintf(CTDL_ERR, "No bounce address specified\n");
- bounce_msgid = (-1L);
- }
-
- /* Can we deliver the bounce to the original sender? */
- valid = validate_recipients(bounceto);
- if (valid != NULL) {
- if (valid->num_error == 0) {
- CtdlSubmitMsg(bmsg, valid, "");
- successful_bounce = 1;
- }
- }
-
- /* If not, post it in the Aide> room */
- if (successful_bounce == 0) {
- CtdlSubmitMsg(bmsg, NULL, config.c_aideroom);
- }
-
- /* Free up the memory we used */
- if (valid != NULL) {
- free_recipients(valid);
- }
- }
-
- CtdlFreeMessage(bmsg);
- lprintf(CTDL_DEBUG, "Done processing bounces\n");
-}
-
-
-/*
- * smtp_purge_completed_deliveries() is caled by smtp_do_procmsg() to scan a
- * set of delivery instructions for completed deliveries and remove them.
- *
- * It returns the number of incomplete deliveries remaining.
- */
-int smtp_purge_completed_deliveries(char *instr) {
- int i;
- int lines;
- int status;
- char buf[1024];
- char key[1024];
- char addr[1024];
- char dsn[1024];
- int completed;
- int incomplete = 0;
-
- lines = num_tokens(instr, '\n');
- for (i=0; i<lines; ++i) {
- extract_token(buf, instr, i, '\n', sizeof buf);
- extract_token(key, buf, 0, '|', sizeof key);
- extract_token(addr, buf, 1, '|', sizeof addr);
- status = extract_int(buf, 2);
- extract_token(dsn, buf, 3, '|', sizeof dsn);
-
- completed = 0;
-
- if (!strcasecmp(key, "remote")) {
- if (status == 2) completed = 1;
- else ++incomplete;
- }
-
- if (completed) {
- remove_token(instr, i, '\n');
- --i;
- --lines;
- }
- }
-
- return(incomplete);
-}
-
-
-/*
- * smtp_do_procmsg()
- *
- * Called by smtp_do_queue() to handle an individual message.
- */
-void smtp_do_procmsg(long msgnum, void *userdata) {
- struct CtdlMessage *msg = NULL;
- char *instr = NULL;
- char *results = NULL;
- int i;
- int lines;
- int status;
- char buf[1024];
- char key[1024];
- char addr[1024];
- char dsn[1024];
- long text_msgid = (-1);
- int incomplete_deliveries_remaining;
- time_t attempted = 0L;
- time_t last_attempted = 0L;
- time_t retry = SMTP_RETRY_INTERVAL;
-
- lprintf(CTDL_DEBUG, "smtp_do_procmsg(%ld)\n", msgnum);
-
- msg = CtdlFetchMessage(msgnum, 1);
- if (msg == NULL) {
- lprintf(CTDL_ERR, "SMTP: tried %ld but no such message!\n", msgnum);
- return;
- }
-
- instr = strdup(msg->cm_fields['M']);
- CtdlFreeMessage(msg);
-
- /* Strip out the headers amd any other non-instruction line */
- lines = num_tokens(instr, '\n');
- for (i=0; i<lines; ++i) {
- extract_token(buf, instr, i, '\n', sizeof buf);
- if (num_tokens(buf, '|') < 2) {
- remove_token(instr, i, '\n');
- --lines;
- --i;
- }
- }
-
- /* Learn the message ID and find out about recent delivery attempts */
- lines = num_tokens(instr, '\n');
- for (i=0; i<lines; ++i) {
- extract_token(buf, instr, i, '\n', sizeof buf);
- extract_token(key, buf, 0, '|', sizeof key);
- if (!strcasecmp(key, "msgid")) {
- text_msgid = extract_long(buf, 1);
- }
- if (!strcasecmp(key, "retry")) {
- /* double the retry interval after each attempt */
- retry = extract_long(buf, 1) * 2L;
- if (retry > SMTP_RETRY_MAX) {
- retry = SMTP_RETRY_MAX;
- }
- remove_token(instr, i, '\n');
- }
- if (!strcasecmp(key, "attempted")) {
- attempted = extract_long(buf, 1);
- if (attempted > last_attempted)
- last_attempted = attempted;
- }
- }
-
- /*
- * Postpone delivery if we've already tried recently.
- */
- if (((time(NULL) - last_attempted) < retry) && (run_queue_now == 0)) {
- lprintf(CTDL_DEBUG, "Retry time not yet reached.\n");
- free(instr);
- return;
- }
-
-
- /*
- * Bail out if there's no actual message associated with this
- */
- if (text_msgid < 0L) {
- lprintf(CTDL_ERR, "SMTP: no 'msgid' directive found!\n");
- free(instr);
- return;
- }
-
- /* Plow through the instructions looking for 'remote' directives and
- * a status of 0 (no delivery yet attempted) or 3/4 (transient errors
- * were experienced and it's time to try again)
- */
- lines = num_tokens(instr, '\n');
- for (i=0; i<lines; ++i) {
- extract_token(buf, instr, i, '\n', sizeof buf);
- extract_token(key, buf, 0, '|', sizeof key);
- extract_token(addr, buf, 1, '|', sizeof addr);
- status = extract_int(buf, 2);
- extract_token(dsn, buf, 3, '|', sizeof dsn);
- if ( (!strcasecmp(key, "remote"))
- && ((status==0)||(status==3)||(status==4)) ) {
-
- /* Remove this "remote" instruction from the set,
- * but replace the set's final newline if
- * remove_token() stripped it. It has to be there.
- */
- remove_token(instr, i, '\n');
- if (instr[strlen(instr)-1] != '\n') {
- strcat(instr, "\n");
- }
-
- --i;
- --lines;
- lprintf(CTDL_DEBUG, "SMTP: Trying <%s>\n", addr);
- smtp_try(key, addr, &status, dsn, sizeof dsn, text_msgid);
- if (status != 2) {
- if (results == NULL) {
- results = malloc(1024);
- memset(results, 0, 1024);
- }
- else {
- results = realloc(results,
- strlen(results) + 1024);
- }
- snprintf(&results[strlen(results)], 1024,
- "%s|%s|%d|%s\n",
- key, addr, status, dsn);
- }
- }
- }
-
- if (results != NULL) {
- instr = realloc(instr, strlen(instr) + strlen(results) + 2);
- strcat(instr, results);
- free(results);
- }
-
-
- /* Generate 'bounce' messages */
- smtp_do_bounce(instr);
-
- /* Go through the delivery list, deleting completed deliveries */
- incomplete_deliveries_remaining =
- smtp_purge_completed_deliveries(instr);
-
-
- /*
- * No delivery instructions remain, so delete both the instructions
- * message and the message message.
- */
- if (incomplete_deliveries_remaining <= 0) {
- long delmsgs[2];
- delmsgs[0] = msgnum;
- delmsgs[1] = text_msgid;
- CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, delmsgs, 2, "");
- }
-
- /*
- * Uncompleted delivery instructions remain, so delete the old
- * instructions and replace with the updated ones.
- */
- if (incomplete_deliveries_remaining > 0) {
- CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, &msgnum, 1, "");
- msg = malloc(sizeof(struct CtdlMessage));
- memset(msg, 0, sizeof(struct CtdlMessage));
- msg->cm_magic = CTDLMESSAGE_MAGIC;
- msg->cm_anon_type = MES_NORMAL;
- msg->cm_format_type = FMT_RFC822;
- msg->cm_fields['M'] = malloc(strlen(instr)+SIZ);
- snprintf(msg->cm_fields['M'],
- strlen(instr)+SIZ,
- "Content-type: %s\n\n%s\n"
- "attempted|%ld\n"
- "retry|%ld\n",
- SPOOLMIME, instr, (long)time(NULL), (long)retry );
- CtdlSubmitMsg(msg, NULL, SMTP_SPOOLOUT_ROOM);
- CtdlFreeMessage(msg);
- }
-
- free(instr);
-}
-
-
-
-/*
- * smtp_do_queue()
- *
- * Run through the queue sending out messages.
- */
-void smtp_do_queue(void) {
- static int doing_queue = 0;
-
- /*
- * This is a simple concurrency check to make sure only one queue run
- * is done at a time. We could do this with a mutex, but since we
- * don't really require extremely fine granularity here, we'll do it
- * with a static variable instead.
- */
- if (doing_queue) return;
- doing_queue = 1;
-
- /*
- * Go ahead and run the queue
- */
- lprintf(CTDL_INFO, "SMTP: processing outbound queue\n");
-
- if (getroom(&CC->room, SMTP_SPOOLOUT_ROOM) != 0) {
- lprintf(CTDL_ERR, "Cannot find room <%s>\n", SMTP_SPOOLOUT_ROOM);
- return;
- }
- CtdlForEachMessage(MSGS_ALL, 0L, NULL,
- SPOOLMIME, NULL, smtp_do_procmsg, NULL);
-
- lprintf(CTDL_INFO, "SMTP: queue run completed\n");
- run_queue_now = 0;
- doing_queue = 0;
-}
-
-
-
-/*****************************************************************************/
-/* SMTP UTILITY COMMANDS */
-/*****************************************************************************/
-
-void cmd_smtp(char *argbuf) {
- char cmd[64];
- char node[256];
- char buf[1024];
- int i;
- int num_mxhosts;
-
- if (CtdlAccessCheck(ac_aide)) return;
-
- extract_token(cmd, argbuf, 0, '|', sizeof cmd);
-
- if (!strcasecmp(cmd, "mx")) {
- extract_token(node, argbuf, 1, '|', sizeof node);
- num_mxhosts = getmx(buf, node);
- cprintf("%d %d MX hosts listed for %s\n",
- LISTING_FOLLOWS, num_mxhosts, node);
- for (i=0; i<num_mxhosts; ++i) {
- extract_token(node, buf, i, '|', sizeof node);
- cprintf("%s\n", node);
- }
- cprintf("000\n");
- return;
- }
-
- else if (!strcasecmp(cmd, "runqueue")) {
- run_queue_now = 1;
- cprintf("%d All outbound SMTP will be retried now.\n", CIT_OK);
- return;
- }
-
- else {
- cprintf("%d Invalid command.\n", ERROR + ILLEGAL_VALUE);
- }
-
-}
-
-
-/*
- * Initialize the SMTP outbound queue
- */
-void smtp_init_spoolout(void) {
- struct ctdlroom qrbuf;
-
- /*
- * Create the room. This will silently fail if the room already
- * exists, and that's perfectly ok, because we want it to exist.
- */
- create_room(SMTP_SPOOLOUT_ROOM, 3, "", 0, 1, 0, VIEW_MAILBOX);
-
- /*
- * Make sure it's set to be a "system room" so it doesn't show up
- * in the <K>nown rooms list for Aides.
- */
- if (lgetroom(&qrbuf, SMTP_SPOOLOUT_ROOM) == 0) {
- qrbuf.QRflags2 |= QR2_SYSTEM;
- lputroom(&qrbuf);
- }
-}
-
-
-
-
-/*****************************************************************************/
-/* MODULE INITIALIZATION STUFF */
-/*****************************************************************************/
-/*
- * This cleanup function blows away the temporary memory used by
- * the SMTP server.
- */
-void smtp_cleanup_function(void) {
-
- /* Don't do this stuff if this is not an SMTP session! */
- if (CC->h_command_function != smtp_command_loop) return;
-
- lprintf(CTDL_DEBUG, "Performing SMTP cleanup hook\n");
- free(SMTP);
-}
-
-
-
-
-
-CTDL_MODULE_INIT(smtp)
-{
- CtdlRegisterServiceHook(config.c_smtp_port, /* SMTP MTA */
- NULL,
- smtp_mta_greeting,
- smtp_command_loop,
- NULL);
-
-#ifdef HAVE_OPENSSL
- CtdlRegisterServiceHook(config.c_smtps_port,
- NULL,
- smtps_greeting,
- smtp_command_loop,
- NULL);
-#endif
-
- CtdlRegisterServiceHook(config.c_msa_port, /* SMTP MSA */
- NULL,
- smtp_msa_greeting,
- smtp_command_loop,
- NULL);
-
- CtdlRegisterServiceHook(0, /* local LMTP */
- file_lmtp_socket,
- lmtp_greeting,
- smtp_command_loop,
- NULL);
-
- CtdlRegisterServiceHook(0, /* local LMTP */
- file_lmtp_unfiltered_socket,
- lmtp_unfiltered_greeting,
- smtp_command_loop,
- NULL);
-
- smtp_init_spoolout();
- CtdlRegisterSessionHook(smtp_do_queue, EVT_TIMER);
- CtdlRegisterSessionHook(smtp_cleanup_function, EVT_STOP);
- CtdlRegisterProtoHook(cmd_smtp, "SMTP", "SMTP utility commands");
-
- /* return our Subversion id for the Log */
- return "$Id$";
-}
+++ /dev/null
-/*
- * $Id$
- *
- * This module allows Citadel to use SpamAssassin to filter incoming messages
- * arriving via SMTP. For more information on SpamAssassin, visit
- * http://www.spamassassin.org (the SpamAssassin project is not in any way
- * affiliated with the Citadel project).
- */
-
-#define SPAMASSASSIN_PORT "783"
-
-#include "sysdep.h"
-#include <stdlib.h>
-#include <unistd.h>
-#include <stdio.h>
-#include <fcntl.h>
-#include <signal.h>
-#include <pwd.h>
-#include <errno.h>
-#include <sys/types.h>
-
-#if TIME_WITH_SYS_TIME
-# include <sys/time.h>
-# include <time.h>
-#else
-# if HAVE_SYS_TIME_H
-# include <sys/time.h>
-# else
-# include <time.h>
-# endif
-#endif
-
-#include <sys/wait.h>
-#include <string.h>
-#include <limits.h>
-#include <sys/socket.h>
-#include "citadel.h"
-#include "server.h"
-#include "citserver.h"
-#include "support.h"
-#include "config.h"
-#include "control.h"
-#include "room_ops.h"
-#include "user_ops.h"
-#include "policy.h"
-#include "database.h"
-#include "msgbase.h"
-#include "tools.h"
-#include "internet_addressing.h"
-#include "domain.h"
-#include "clientsocket.h"
-
-
-#include "ctdl_module.h"
-
-
-
-/*
- * Connect to the SpamAssassin server and scan a message.
- */
-int spam_assassin(struct CtdlMessage *msg) {
- int sock = (-1);
- char sahosts[SIZ];
- int num_sahosts;
- char buf[SIZ];
- int is_spam = 0;
- int sa;
- char *msgtext;
- size_t msglen;
-
- /* For users who have authenticated to this server we never want to
- * apply spam filtering, because presumably they're trustworthy.
- */
- if (CC->logged_in) return(0);
-
- /* See if we have any SpamAssassin hosts configured */
- num_sahosts = get_hosts(sahosts, "spamassassin");
- if (num_sahosts < 1) return(0);
-
- /* Try them one by one until we get a working one */
- for (sa=0; sa<num_sahosts; ++sa) {
- extract_token(buf, sahosts, sa, '|', sizeof buf);
- lprintf(CTDL_INFO, "Connecting to SpamAssassin at <%s>\n", buf);
- sock = sock_connect(buf, SPAMASSASSIN_PORT, "tcp");
- if (sock >= 0) lprintf(CTDL_DEBUG, "Connected!\n");
- }
-
- if (sock < 0) {
- /* If the service isn't running, just pass the mail
- * through. Potentially throwing away mails isn't good.
- */
- return(0);
- }
-
- /* Command */
- lprintf(CTDL_DEBUG, "Transmitting command\n");
- sprintf(buf, "CHECK SPAMC/1.2\r\n\r\n");
- sock_write(sock, buf, strlen(buf));
-
- /* Message */
- CC->redirect_buffer = malloc(SIZ);
- CC->redirect_len = 0;
- CC->redirect_alloc = SIZ;
- CtdlOutputPreLoadedMsg(msg, MT_RFC822, HEADERS_ALL, 0, 1);
- msgtext = CC->redirect_buffer;
- msglen = CC->redirect_len;
- CC->redirect_buffer = NULL;
- CC->redirect_len = 0;
- CC->redirect_alloc = 0;
-
- sock_write(sock, msgtext, msglen);
- free(msgtext);
-
- /* Close one end of the socket connection; this tells SpamAssassin
- * that we're done.
- */
- sock_shutdown(sock, SHUT_WR);
-
- /* Response */
- lprintf(CTDL_DEBUG, "Awaiting response\n");
- if (sock_gets(sock, buf) < 0) {
- goto bail;
- }
- lprintf(CTDL_DEBUG, "<%s\n", buf);
- if (strncasecmp(buf, "SPAMD", 5)) {
- goto bail;
- }
- if (sock_gets(sock, buf) < 0) {
- goto bail;
- }
- lprintf(CTDL_DEBUG, "<%s\n", buf);
- if (!strncasecmp(buf, "Spam: True", 10)) {
- is_spam = 1;
- }
-
- if (is_spam) {
- if (msg->cm_fields['0'] != NULL) {
- free(msg->cm_fields['0']);
- }
- msg->cm_fields['0'] = strdup("5.7.1 message rejected by spam filter");
- }
-
-bail: close(sock);
- return(is_spam);
-}
-
-
-
-CTDL_MODULE_INIT(spam)
-{
- CtdlRegisterMessageHook(spam_assassin, EVT_SMTPSCAN);
-
- /* return our Subversion id for the Log */
- return "$Id$";
-}
+++ /dev/null
-/*
- * $Id$
- *
- * Transparently handle the upgrading of server data formats.
- *
- */
-
-#include "sysdep.h"
-#include <stdlib.h>
-#include <unistd.h>
-#include <stdio.h>
-#include <fcntl.h>
-#include <signal.h>
-#include <pwd.h>
-#include <errno.h>
-#include <sys/types.h>
-
-#if TIME_WITH_SYS_TIME
-# include <sys/time.h>
-# include <time.h>
-#else
-# if HAVE_SYS_TIME_H
-# include <sys/time.h>
-# else
-# include <time.h>
-# endif
-#endif
-
-#include <sys/wait.h>
-#include <string.h>
-#include <limits.h>
-#include "citadel.h"
-#include "server.h"
-#include "citserver.h"
-#include "support.h"
-#include "config.h"
-#include "control.h"
-#include "database.h"
-#include "room_ops.h"
-#include "user_ops.h"
-#include "msgbase.h"
-#include "tools.h"
-#include "serv_upgrade.h"
-#include "euidindex.h"
-
-
-#include "ctdl_module.h"
-
-
-/*
- * Back end processing function for cmd_bmbx
- */
-void cmd_bmbx_backend(struct ctdlroom *qrbuf, void *data) {
- static struct RoomProcList *rplist = NULL;
- struct RoomProcList *ptr;
- struct ctdlroom qr;
-
- /* Lazy programming here. Call this function as a ForEachRoom backend
- * in order to queue up the room names, or call it with a null room
- * to make it do the processing.
- */
- if (qrbuf != NULL) {
- ptr = (struct RoomProcList *)
- malloc(sizeof (struct RoomProcList));
- if (ptr == NULL) return;
-
- safestrncpy(ptr->name, qrbuf->QRname, sizeof ptr->name);
- ptr->next = rplist;
- rplist = ptr;
- return;
- }
-
- while (rplist != NULL) {
-
- if (lgetroom(&qr, rplist->name) == 0) {
- lprintf(CTDL_DEBUG, "Processing <%s>...\n", rplist->name);
- if ( (qr.QRflags & QR_MAILBOX) == 0) {
- lprintf(CTDL_DEBUG, " -- not a mailbox\n");
- }
- else {
-
- qr.QRgen = time(NULL);
- lprintf(CTDL_DEBUG, " -- fixed!\n");
- }
- lputroom(&qr);
- }
-
- ptr = rplist;
- rplist = rplist->next;
- free(ptr);
- }
-}
-
-/*
- * quick fix to bump mailbox generation numbers
- */
-void bump_mailbox_generation_numbers(void) {
- lprintf(CTDL_WARNING, "Applying security fix to mailbox rooms\n");
- ForEachRoom(cmd_bmbx_backend, NULL);
- cmd_bmbx_backend(NULL, NULL);
- return;
-}
-
-
-/*
- * Back end processing function for convert_ctdluid_to_minusone()
- */
-void cbtm_backend(struct ctdluser *usbuf, void *data) {
- static struct UserProcList *uplist = NULL;
- struct UserProcList *ptr;
- struct ctdluser us;
-
- /* Lazy programming here. Call this function as a ForEachUser backend
- * in order to queue up the room names, or call it with a null user
- * to make it do the processing.
- */
- if (usbuf != NULL) {
- ptr = (struct UserProcList *)
- malloc(sizeof (struct UserProcList));
- if (ptr == NULL) return;
-
- safestrncpy(ptr->user, usbuf->fullname, sizeof ptr->user);
- ptr->next = uplist;
- uplist = ptr;
- return;
- }
-
- while (uplist != NULL) {
-
- if (lgetuser(&us, uplist->user) == 0) {
- lprintf(CTDL_DEBUG, "Processing <%s>...\n", uplist->user);
- if (us.uid == CTDLUID) {
- us.uid = (-1);
- }
- lputuser(&us);
- }
-
- ptr = uplist;
- uplist = uplist->next;
- free(ptr);
- }
-}
-
-/*
- * quick fix to change all CTDLUID users to (-1)
- */
-void convert_ctdluid_to_minusone(void) {
- lprintf(CTDL_WARNING, "Applying uid changes\n");
- ForEachUser(cbtm_backend, NULL);
- cbtm_backend(NULL, NULL);
- return;
-}
-
-/*
- * Do various things to our configuration file
- */
-void update_config(void) {
- get_config();
-
- if (CitControl.version < 606) {
- config.c_rfc822_strict_from = 0;
- }
-
- if (CitControl.version < 609) {
- config.c_purge_hour = 3;
- }
-
- if (CitControl.version < 615) {
- config.c_ldap_port = 389;
- }
-
- if (CitControl.version < 623) {
- strcpy(config.c_ip_addr, "0.0.0.0");
- }
-
- if (CitControl.version < 650) {
- config.c_enable_fulltext = 0;
- }
-
- if (CitControl.version < 652) {
- config.c_auto_cull = 1;
- }
-
- put_config();
-}
-
-
-
-
-void check_server_upgrades(void) {
-
- get_control();
- lprintf(CTDL_INFO, "Server-hosted upgrade level is %d.%02d\n",
- (CitControl.version / 100),
- (CitControl.version % 100) );
-
- if (CitControl.version < REV_LEVEL) {
- lprintf(CTDL_WARNING,
- "Server hosted updates need to be processed at "
- "this time. Please wait...\n");
- }
- else {
- return;
- }
-
- update_config();
-
- if ((CitControl.version > 000) && (CitControl.version < 555)) {
- lprintf(CTDL_EMERG,
- "Your data files are from a version of Citadel\n"
- "that is too old to be upgraded. Sorry.\n");
- exit(EXIT_FAILURE);
- }
- if ((CitControl.version > 000) && (CitControl.version < 591)) {
- bump_mailbox_generation_numbers();
- }
- if ((CitControl.version > 000) && (CitControl.version < 608)) {
- convert_ctdluid_to_minusone();
- }
- if ((CitControl.version > 000) && (CitControl.version < 659)) {
- rebuild_euid_index();
- }
-
- CitControl.version = REV_LEVEL;
- put_control();
-}
-
-
-CTDL_MODULE_INIT(upgrade)
-{
- check_server_upgrades();
-
- /* return our Subversion id for the Log */
- return "$Id$";
-}
+++ /dev/null
-/*
- * $Id$
- *
- */
-
+++ /dev/null
-/*
- * $Id$
- *
- * This is the "Art Vandelay" module. It is an importer/exporter.
- *
- */
-
-#include "sysdep.h"
-#include <stdlib.h>
-#include <unistd.h>
-#include <stdio.h>
-#include <fcntl.h>
-#include <signal.h>
-#include <pwd.h>
-#include <errno.h>
-#include <sys/types.h>
-
-#if TIME_WITH_SYS_TIME
-# include <sys/time.h>
-# include <time.h>
-#else
-# if HAVE_SYS_TIME_H
-# include <sys/time.h>
-# else
-# include <time.h>
-# endif
-#endif
-
-#include <sys/wait.h>
-#include <string.h>
-#include <ctype.h>
-#include <limits.h>
-#include "citadel.h"
-#include "server.h"
-#include "citserver.h"
-#include "support.h"
-#include "config.h"
-#include "database.h"
-#include "msgbase.h"
-#include "tools.h"
-#include "user_ops.h"
-#include "room_ops.h"
-#include "control.h"
-#include "euidindex.h"
-
-
-#include "ctdl_module.h"
-
-
-
-
-#define END_OF_MESSAGE "---eom---dbd---"
-
-char artv_tempfilename1[PATH_MAX];
-char artv_tempfilename2[PATH_MAX];
-FILE *artv_global_message_list;
-
-void artv_export_users_backend(struct ctdluser *buf, void *data) {
- cprintf("user\n");
-/*
-#include "artv_serialize.h"
-#include "dtds/user-defs.h"
-#include "undef_data.h"
-*/
- cprintf("%d\n", buf->version);
- cprintf("%ld\n", (long)buf->uid);
- cprintf("%s\n", buf->password);
- cprintf("%u\n", buf->flags);
- cprintf("%ld\n", buf->timescalled);
- cprintf("%ld\n", buf->posted);
- cprintf("%d\n", buf->axlevel);
- cprintf("%ld\n", buf->usernum);
- cprintf("%ld\n", (long)buf->lastcall);
- cprintf("%d\n", buf->USuserpurge);
- cprintf("%s\n", buf->fullname);
- cprintf("%d\n", buf->USscreenwidth);
- cprintf("%d\n", buf->USscreenheight);
-}
-
-
-void artv_export_users(void) {
- ForEachUser(artv_export_users_backend, NULL);
-}
-
-
-void artv_export_room_msg(long msgnum, void *userdata) {
- cprintf("%ld\n", msgnum);
- fprintf(artv_global_message_list, "%ld\n", msgnum);
-}
-
-
-void artv_export_rooms_backend(struct ctdlroom *buf, void *data) {
- cprintf("room\n");
-/*
-#include "artv_serialize.h"
-#include "dtds/room-defs.h"
-#include "undef_data.h"
-*/
- cprintf("%s\n", buf->QRname);
- cprintf("%s\n", buf->QRpasswd);
- cprintf("%ld\n", buf->QRroomaide);
- cprintf("%ld\n", buf->QRhighest);
- cprintf("%ld\n", (long)buf->QRgen);
- cprintf("%u\n", buf->QRflags);
- cprintf("%s\n", buf->QRdirname);
- cprintf("%ld\n", buf->QRinfo);
- cprintf("%d\n", buf->QRfloor);
- cprintf("%ld\n", (long)buf->QRmtime);
- cprintf("%d\n", buf->QRep.expire_mode);
- cprintf("%d\n", buf->QRep.expire_value);
- cprintf("%ld\n", buf->QRnumber);
- cprintf("%d\n", buf->QRorder);
- cprintf("%u\n", buf->QRflags2);
- cprintf("%d\n", buf->QRdefaultview);
-
- getroom(&CC->room, buf->QRname);
- /* format of message list export is all message numbers output
- * one per line terminated by a 0.
- */
-//*/
- getroom(&CC->room, buf->QRname);
- /* format of message list export is all message numbers output
- * one per line terminated by a 0.
- */
- CtdlForEachMessage(MSGS_ALL, 0L, NULL, NULL, NULL,
- artv_export_room_msg, NULL);
- cprintf("0\n");
-
-}
-
-
-
-void artv_export_rooms(void) {
- char cmd[SIZ];
- artv_global_message_list = fopen(artv_tempfilename1, "w");
- if (artv_global_message_list != NULL) {
- ForEachRoom(artv_export_rooms_backend, NULL);
- fclose(artv_global_message_list);
- }
-
- /*
- * Process the 'global' message list. (Sort it and remove dups.
- * Dups are ok because a message may be in more than one room, but
- * this will be handled by exporting the reference count, not by
- * exporting the message multiple times.)
- */
- snprintf(cmd, sizeof cmd, "sort <%s >%s", artv_tempfilename1, artv_tempfilename2);
- system(cmd);
- snprintf(cmd, sizeof cmd, "uniq <%s >%s", artv_tempfilename2, artv_tempfilename1);
- system(cmd);
-}
-
-
-void artv_export_floors(void) {
- struct floor qfbuf, *buf;
- int i;
-
- for (i=0; i < MAXFLOORS; ++i) {
- cprintf("floor\n");
- cprintf("%d\n", i);
- getfloor(&qfbuf, i);
- buf = &qfbuf;
-/*
-#include "artv_serialize.h"
-#include "dtds/floor-defs.h"
-#include "undef_data.h"
-/*/
- cprintf("%u\n", buf->f_flags);
- cprintf("%s\n", buf->f_name);
- cprintf("%d\n", buf->f_ref_count);
- cprintf("%d\n", buf->f_ep.expire_mode);
- cprintf("%d\n", buf->f_ep.expire_value);
-//*/
- }
-}
-
-
-
-
-
-/*
- * Traverse the visits file...
- */
-void artv_export_visits(void) {
- struct visit vbuf;
- struct cdbdata *cdbv;
-
- cdb_rewind(CDB_VISIT);
-
- while (cdbv = cdb_next_item(CDB_VISIT), cdbv != NULL) {
- memset(&vbuf, 0, sizeof(struct visit));
- memcpy(&vbuf, cdbv->ptr,
- ((cdbv->len > sizeof(struct visit)) ?
- sizeof(struct visit) : cdbv->len));
- cdb_free(cdbv);
-
- cprintf("visit\n");
- cprintf("%ld\n", vbuf.v_roomnum);
- cprintf("%ld\n", vbuf.v_roomgen);
- cprintf("%ld\n", vbuf.v_usernum);
-
- if (strlen(vbuf.v_seen) > 0) {
- cprintf("%s\n", vbuf.v_seen);
- }
- else {
- cprintf("%ld\n", vbuf.v_lastseen);
- }
-
- cprintf("%s\n", vbuf.v_answered);
- cprintf("%u\n", vbuf.v_flags);
- cprintf("%d\n", vbuf.v_view);
- }
-}
-
-
-void artv_export_message(long msgnum) {
- struct MetaData smi;
- struct CtdlMessage *msg;
- struct ser_ret smr;
- FILE *fp;
- char buf[SIZ];
- char tempfile[PATH_MAX];
-
- msg = CtdlFetchMessage(msgnum, 1);
- if (msg == NULL) return; /* fail silently */
-
- cprintf("message\n");
- GetMetaData(&smi, msgnum);
- cprintf("%ld\n", msgnum);
- cprintf("%d\n", smi.meta_refcount);
- cprintf("%s\n", smi.meta_content_type);
-
- serialize_message(&smr, msg);
- CtdlFreeMessage(msg);
-
- /* write it in base64 */
- CtdlMakeTempFileName(tempfile, sizeof tempfile);
- snprintf(buf, sizeof buf, "%s -e >%s", file_base64, tempfile);
- fp = popen(buf, "w");
- fwrite(smr.ser, smr.len, 1, fp);
- pclose(fp);
-
- free(smr.ser);
-
- fp = fopen(tempfile, "r");
- unlink(tempfile);
- if (fp != NULL) {
- while (fgets(buf, sizeof(buf), fp) != NULL) {
- buf[strlen(buf)-1] = 0;
- cprintf("%s\n", buf);
- }
- fclose(fp);
- }
- cprintf("%s\n", END_OF_MESSAGE);
-}
-
-
-
-void artv_export_messages(void) {
- char buf[SIZ];
- long msgnum;
- int count = 0;
-
- artv_global_message_list = fopen(artv_tempfilename1, "r");
- if (artv_global_message_list != NULL) {
- lprintf(CTDL_INFO, "Opened %s\n", artv_tempfilename1);
- while (fgets(buf, sizeof(buf),
- artv_global_message_list) != NULL) {
- msgnum = atol(buf);
- if (msgnum > 0L) {
- artv_export_message(msgnum);
- ++count;
- }
- }
- fclose(artv_global_message_list);
- }
- lprintf(CTDL_INFO, "Exported %d messages.\n", count);
-}
-
-
-
-
-void artv_do_export(void) {
- struct config *buf;
- buf = &config;
- cprintf("%d Exporting all Citadel databases.\n", LISTING_FOLLOWS);
-
- cprintf("version\n%d\n", REV_LEVEL);
-
- /* export the config file */
- cprintf("config\n");
-
-#include "artv_serialize.h"
-#include "dtds/config-defs.h"
-#include "undef_data.h"
-
-/*
- cprintf("%s\n", config.c_nodename);
- cprintf("%s\n", config.c_fqdn);
- cprintf("%s\n", config.c_humannode);
- cprintf("%s\n", config.c_phonenum);
- cprintf("%ld\n", (long)config.c_ctdluid);
- cprintf("%d\n", config.c_creataide);
- cprintf("%d\n", config.c_sleeping);
- cprintf("%d\n", config.c_initax);
- cprintf("%d\n", config.c_regiscall);
- cprintf("%d\n", config.c_twitdetect);
- cprintf("%s\n", config.c_twitroom);
- cprintf("%s\n", config.c_moreprompt);
- cprintf("%d\n", config.c_restrict);
- cprintf("%s\n", config.c_site_location);
- cprintf("%s\n", config.c_sysadm);
- cprintf("%d\n", config.c_setup_level);
- cprintf("%d\n", config.c_maxsessions);
- cprintf("%d\n", config.c_port_number);
- cprintf("%d\n", config.c_ep.expire_mode);
- cprintf("%d\n", config.c_ep.expire_value);
- cprintf("%d\n", config.c_userpurge);
- cprintf("%d\n", config.c_roompurge);
- cprintf("%s\n", config.c_logpages);
- cprintf("%d\n", config.c_createax);
- cprintf("%ld\n", config.c_maxmsglen);
- cprintf("%d\n", config.c_min_workers);
- cprintf("%d\n", config.c_max_workers);
- cprintf("%d\n", config.c_pop3_port);
- cprintf("%d\n", config.c_smtp_port);
- cprintf("%d\n", config.c_purge_hour);
- cprintf("%d\n", config.c_mbxep.expire_mode);
- cprintf("%d\n", config.c_mbxep.expire_value);
- cprintf("%s\n", config.c_ldap_host);
- cprintf("%d\n", config.c_ldap_port);
- cprintf("%s\n", config.c_ldap_base_dn);
- cprintf("%s\n", config.c_ldap_bind_dn);
- cprintf("%s\n", config.c_ldap_bind_pw);
- cprintf("%s\n", config.c_ip_addr);
- cprintf("%d\n", config.c_msa_port);
- cprintf("%d\n", config.c_imaps_port);
- cprintf("%d\n", config.c_pop3s_port);
- cprintf("%d\n", config.c_smtps_port);
- cprintf("%d\n", config.c_rfc822_strict_from);
- cprintf("%d\n", config.c_aide_zap);
- cprintf("%d\n", config.c_imap_port);
- cprintf("%ld\n", config.c_net_freq);
- cprintf("%d\n", config.c_disable_newu);
- cprintf("%s\n", config.c_baseroom);
- cprintf("%s\n", config.c_aideroom);
- cprintf("%d\n", config.c_auto_cull);
- cprintf("%d\n", config.c_instant_expunge);
- cprintf("%d\n", config.c_allow_spoofing);
- cprintf("%d\n", config.c_journal_email);
- cprintf("%d\n", config.c_journal_pubmsgs);
- cprintf("%s\n", config.c_journal_dest);
- cprintf("%s\n", config.c_default_cal_zone);
- cprintf("%d\n", config.c_pftcpdict_port);
- cprintf("%d\n", config.c_managesieve_port);
- cprintf("%d\n", config.c_auth_mode);
- cprintf("%s\n", config.c_funambol_host);
- cprintf("%d\n", config.c_funambol_port);
- cprintf("%s\n", config.c_funambol_source);
- cprintf("%s\n", config.c_funambol_auth);
- cprintf("%d\n", config.c_rbl_at_greeting);
-*/
- /* Export the control file */
- get_control();
- cprintf("control\n");
- cprintf("%ld\n", CitControl.MMhighest);
- cprintf("%u\n", CitControl.MMflags);
- cprintf("%ld\n", CitControl.MMnextuser);
- cprintf("%ld\n", CitControl.MMnextroom);
- cprintf("%d\n", CitControl.version);
-
- artv_export_users();
- artv_export_rooms();
- artv_export_floors();
- artv_export_visits();
- artv_export_messages();
-
- cprintf("000\n");
-}
-
-
-
-void artv_import_config(void) {
- char cbuf[SIZ];
- struct config *buf;
- buf = &config;
-
- lprintf(CTDL_DEBUG, "Importing config file\n");
-
-#include "artv_deserialize.h"
-#include "dtds/config-defs.h"
-#include "undef_data.h"
-
-/*
- client_getln(config.c_nodename, sizeof config.c_nodename);
- client_getln(config.c_fqdn, sizeof config.c_fqdn);
- client_getln(config.c_humannode, sizeof config.c_humannode);
- client_getln(config.c_phonenum, sizeof config.c_phonenum);
- client_getln(buf, sizeof buf); config.c_ctdluid = atoi(buf);
- client_getln(buf, sizeof buf); config.c_creataide = atoi(buf);
- client_getln(buf, sizeof buf); config.c_sleeping = atoi(buf);
- client_getln(buf, sizeof buf); config.c_initax = atoi(buf);
- client_getln(buf, sizeof buf); config.c_regiscall = atoi(buf);
- client_getln(buf, sizeof buf); config.c_twitdetect = atoi(buf);
- client_getln(config.c_twitroom, sizeof config.c_twitroom);
- client_getln(config.c_moreprompt, sizeof config.c_moreprompt);
- client_getln(buf, sizeof buf); config.c_restrict = atoi(buf);
- client_getln(config.c_site_location, sizeof config.c_site_location);
- client_getln(config.c_sysadm, sizeof config.c_sysadm);
- client_getln(buf, sizeof buf); config.c_setup_level = atoi(buf);
- client_getln(buf, sizeof buf); config.c_maxsessions = atoi(buf);
- client_getln(buf, sizeof buf); config.c_port_number = atoi(buf);
- client_getln(buf, sizeof buf); config.c_ep.expire_mode = atoi(buf);
- client_getln(buf, sizeof buf); config.c_ep.expire_value = atoi(buf);
- client_getln(buf, sizeof buf); config.c_userpurge = atoi(buf);
- client_getln(buf, sizeof buf); config.c_roompurge = atoi(buf);
- client_getln(config.c_logpages, sizeof config.c_logpages);
- client_getln(buf, sizeof buf); config.c_createax = atoi(buf);
- client_getln(buf, sizeof buf); config.c_maxmsglen = atol(buf);
- client_getln(buf, sizeof buf); config.c_min_workers = atoi(buf);
- client_getln(buf, sizeof buf); config.c_max_workers = atoi(buf);
- client_getln(buf, sizeof buf); config.c_pop3_port = atoi(buf);
- client_getln(buf, sizeof buf); config.c_smtp_port = atoi(buf);
- client_getln(buf, sizeof buf); config.c_purge_hour = atoi(buf);
- client_getln(buf, sizeof buf); config.c_mbxep.expire_mode = atoi(buf);
- client_getln(buf, sizeof buf); config.c_mbxep.expire_value = atoi(buf);
- client_getln(config.c_ldap_host, sizeof config.c_ldap_host);
- client_getln(buf, sizeof buf); config.c_ldap_port = atoi(buf);
- client_getln(config.c_ldap_base_dn, sizeof config.c_ldap_base_dn);
- client_getln(config.c_ldap_bind_dn, sizeof config.c_ldap_bind_dn);
- client_getln(config.c_ldap_bind_pw, sizeof config.c_ldap_bind_pw);
- client_getln(config.c_ip_addr, sizeof config.c_ip_addr);
- client_getln(buf, sizeof buf); config.c_msa_port = atoi(buf);
- client_getln(buf, sizeof buf); config.c_imaps_port = atoi(buf);
- client_getln(buf, sizeof buf); config.c_pop3s_port = atoi(buf);
- client_getln(buf, sizeof buf); config.c_smtps_port = atoi(buf);
- client_getln(buf, sizeof buf); config.c_rfc822_strict_from = atoi(buf);
- client_getln(buf, sizeof buf); config.c_aide_zap = atoi(buf);
- client_getln(buf, sizeof buf); config.c_imap_port = atoi(buf);
- client_getln(buf, sizeof buf); config.c_net_freq = atol(buf);
- client_getln(buf, sizeof buf); config.c_disable_newu = atoi(buf);
- client_getln(config.c_baseroom, sizeof config.c_baseroom);
- client_getln(config.c_aideroom, sizeof config.c_aideroom);
- client_getln(buf, sizeof buf); config.c_auto_cull = atoi(buf);
- client_getln(buf, sizeof buf); config.c_instant_expunge = atoi(buf);
- client_getln(buf, sizeof buf); config.c_allow_spoofing = atoi(buf);
- client_getln(buf, sizeof buf); config.c_journal_email = atoi(buf);
- client_getln(buf, sizeof buf); config.c_journal_pubmsgs = atoi(buf);
- client_getln(config.c_journal_dest, sizeof config.c_journal_dest);
- client_getln(config.c_default_cal_zone, sizeof config.c_default_cal_zone);
- client_getln(buf, sizeof buf); config.c_pftcpdict_port = atoi(buf);
- client_getln(buf, sizeof buf); config.c_managesieve_port = atoi(buf);
- client_getln(buf, sizeof buf); config.c_auth_mode = atoi(buf);
- client_getln(config.c_funambol_host, sizeof config.c_funambol_host);
- client_getln(buf, sizeof buf); config.c_funambol_port = atoi(buf);
- client_getln(config.c_funambol_source, sizeof config.c_funambol_source);
- client_getln(config.c_funambol_auth, sizeof config.c_funambol_auth);
- client_getln(buf, sizeof buf); config.c_rbl_at_greeting = atoi(buf);
-*/
- config.c_enable_fulltext = 0; /* always disable */
- put_config();
- lprintf(CTDL_INFO, "Imported config file\n");
-}
-
-
-void artv_import_control(void) {
- char buf[SIZ];
-
- lprintf(CTDL_DEBUG, "Importing control file\n");
- client_getln(buf, sizeof buf); CitControl.MMhighest = atol(buf);
- client_getln(buf, sizeof buf); CitControl.MMflags = atoi(buf);
- client_getln(buf, sizeof buf); CitControl.MMnextuser = atol(buf);
- client_getln(buf, sizeof buf); CitControl.MMnextroom = atol(buf);
- client_getln(buf, sizeof buf); CitControl.version = atoi(buf);
- CitControl.MMfulltext = (-1L); /* always flush */
- put_control();
- lprintf(CTDL_INFO, "Imported control file\n");
-}
-
-
-void artv_import_user(void) {
- char cbuf[SIZ];
- struct ctdluser usbuf, *buf;
- buf = &usbuf;
-/*
-#include "artv_deserialize.h"
-#include "dtds/user-defs.h"
-#include "undef_data.h"
-
-/*/
- client_getln(cbuf, sizeof cbuf); buf->version = atoi(cbuf);
- client_getln(cbuf, sizeof cbuf); buf->uid = atoi(cbuf);
- client_getln(buf->password, sizeof buf->password);
- client_getln(cbuf, sizeof cbuf); buf->flags = atoi(cbuf);
- client_getln(cbuf, sizeof cbuf); buf->timescalled = atol(cbuf);
- client_getln(cbuf, sizeof cbuf); buf->posted = atol(cbuf);
- client_getln(cbuf, sizeof cbuf); buf->axlevel = atoi(cbuf);
- client_getln(cbuf, sizeof cbuf); buf->usernum = atol(cbuf);
- client_getln(cbuf, sizeof cbuf); buf->lastcall = atol(cbuf);
- client_getln(cbuf, sizeof cbuf); buf->USuserpurge = atoi(cbuf);
- client_getln(buf->fullname, sizeof buf->fullname);
- client_getln(cbuf, sizeof cbuf); buf->USscreenwidth = atoi(cbuf);
- client_getln(cbuf, sizeof cbuf); buf->USscreenheight = atoi(cbuf);
-//*/
- putuser(buf);
-}
-
-
-void artv_import_room(void) {
- char cbuf[SIZ];
- struct ctdlroom qrbuf, *buf;
- long msgnum;
- int msgcount = 0;
-
- buf = &qrbuf;
-/*
-#include "artv_deserialize.h"
-#include "dtds/room-defs.h"
-#include "undef_data.h"
-
-/*/
- client_getln(buf->QRname, sizeof buf->QRname);
- client_getln(buf->QRpasswd, sizeof buf->QRpasswd);
- client_getln(cbuf, sizeof cbuf); buf->QRroomaide = atol(cbuf);
- client_getln(cbuf, sizeof cbuf); buf->QRhighest = atol(cbuf);
- client_getln(cbuf, sizeof cbuf); buf->QRgen = atol(cbuf);
- client_getln(cbuf, sizeof cbuf); buf->QRflags = atoi(cbuf);
- client_getln(buf->QRdirname, sizeof buf->QRdirname);
- client_getln(cbuf, sizeof cbuf); buf->QRinfo = atol(cbuf);
- client_getln(cbuf, sizeof cbuf); buf->QRfloor = atoi(cbuf);
- client_getln(cbuf, sizeof cbuf); buf->QRmtime = atol(cbuf);
- client_getln(cbuf, sizeof cbuf); buf->QRep.expire_mode = atoi(cbuf);
- client_getln(cbuf, sizeof cbuf); buf->QRep.expire_value = atoi(cbuf);
- client_getln(cbuf, sizeof cbuf); buf->QRnumber = atol(cbuf);
- client_getln(cbuf, sizeof cbuf); buf->QRorder = atoi(cbuf);
- client_getln(cbuf, sizeof cbuf); buf->QRflags2 = atoi(cbuf);
- client_getln(cbuf, sizeof cbuf); buf->QRdefaultview = atoi(cbuf);
-//*/
- putroom(buf);
- lprintf(CTDL_INFO, "Imported room <%s>\n", qrbuf.QRname);
- /* format of message list export is all message numbers output
- * one per line terminated by a 0.
- */
- while (client_getln(cbuf, sizeof cbuf), msgnum = atol(cbuf), msgnum > 0) {
- CtdlSaveMsgPointerInRoom(qrbuf.QRname, msgnum, 0, NULL);
- ++msgcount;
- }
- lprintf(CTDL_INFO, "(%d messages)\n", msgcount);
-}
-
-
-void artv_import_floor(void) {
- struct floor flbuf, *buf;
- int i;
- char cbuf[SIZ];
-
- buf = & flbuf;
- memset(buf, 0, sizeof(buf));
- client_getln(cbuf, sizeof cbuf); i = atoi(cbuf);
-/*
-#include "artv_deserialize.h"
-#include "dtds/floor-defs.h"
-#include "undef_data.h"
-/*/
- client_getln(cbuf, sizeof cbuf); buf->f_flags = atoi(cbuf);
- client_getln(buf->f_name, sizeof buf->f_name);
- client_getln(cbuf, sizeof cbuf); buf->f_ref_count = atoi(cbuf);
- client_getln(cbuf, sizeof cbuf); buf->f_ep.expire_mode = atoi(cbuf);
- client_getln(cbuf, sizeof cbuf); buf->f_ep.expire_value = atoi(cbuf);
-//*/
- putfloor(buf, i);
- lprintf(CTDL_INFO, "Imported floor #%d (%s)\n", i, flbuf.f_name);
-}
-
-
-/*
- */
-void artv_import_visit(void) {
- struct visit vbuf;
- char buf[SIZ];
- int i;
- int is_textual_seen = 0;
-
- client_getln(buf, sizeof buf); vbuf.v_roomnum = atol(buf);
- client_getln(buf, sizeof buf); vbuf.v_roomgen = atol(buf);
- client_getln(buf, sizeof buf); vbuf.v_usernum = atol(buf);
-
- client_getln(buf, sizeof buf);
- vbuf.v_lastseen = atol(buf);
- for (i=0; i<strlen(buf); ++i) if (!isdigit(buf[i])) is_textual_seen = 1;
- if (is_textual_seen) strcpy(vbuf.v_seen, buf);
-
- client_getln(vbuf.v_answered, sizeof vbuf.v_answered);
- client_getln(buf, sizeof buf); vbuf.v_flags = atoi(buf);
- client_getln(buf, sizeof buf); vbuf.v_view = atoi(buf);
- put_visit(&vbuf);
- lprintf(CTDL_INFO, "Imported visit %ld/%ld/%ld\n",
- vbuf.v_roomnum, vbuf.v_roomgen, vbuf.v_usernum);
-}
-
-
-
-void artv_import_message(void) {
- struct MetaData smi;
- long msgnum;
- long msglen;
- FILE *fp;
- char buf[SIZ];
- char tempfile[PATH_MAX];
- char *mbuf;
-
- memset(&smi, 0, sizeof(struct MetaData));
- client_getln(buf, sizeof buf); msgnum = atol(buf);
- smi.meta_msgnum = msgnum;
- client_getln(buf, sizeof buf); smi.meta_refcount = atoi(buf);
- client_getln(smi.meta_content_type, sizeof smi.meta_content_type);
-
- lprintf(CTDL_INFO, "message #%ld\n", msgnum);
-
- /* decode base64 message text */
- CtdlMakeTempFileName(tempfile, sizeof tempfile);
- snprintf(buf, sizeof buf, "%s -d >%s", file_base64, tempfile);
- fp = popen(buf, "w");
- while (client_getln(buf, sizeof buf), strcasecmp(buf, END_OF_MESSAGE)) {
- fprintf(fp, "%s\n", buf);
- }
- pclose(fp);
- fp = fopen(tempfile, "rb");
- fseek(fp, 0L, SEEK_END);
- msglen = ftell(fp);
- fclose(fp);
- lprintf(CTDL_DEBUG, "msglen = %ld\n", msglen);
-
- mbuf = malloc(msglen);
- fp = fopen(tempfile, "rb");
- fread(mbuf, msglen, 1, fp);
- fclose(fp);
-
- cdb_store(CDB_MSGMAIN, &msgnum, sizeof(long), mbuf, msglen);
-
- free(mbuf);
- unlink(tempfile);
-
- PutMetaData(&smi);
- lprintf(CTDL_INFO, "Imported message %ld\n", msgnum);
-}
-
-
-
-
-void artv_do_import(void) {
- char buf[SIZ];
- char abuf[SIZ];
- char s_version[SIZ];
- int version;
- long iterations;
-
- unbuffer_output();
-
- cprintf("%d sock it to me\n", SEND_LISTING);
- abuf[0] = '\0';
- unbuffer_output();
- iterations = 0;
- while (client_getln(buf, sizeof buf), strcmp(buf, "000")) {
-
- lprintf(CTDL_DEBUG, "import keyword: <%s>\n", buf);
- if ((abuf[0] == '\0') || (strcasecmp(buf, abuf))) {
- cprintf ("\n\nImporting datatype %s\n", buf);
- strncpy (abuf, buf, SIZ);
- iterations = 0;
- }
- else {
- cprintf(".");
- if (iterations % 64 == 0)
- cprintf("\n");
-
- }
-
- if (!strcasecmp(buf, "version")) {
- client_getln(s_version, sizeof s_version);
- version = atoi(s_version);
- if ((version<EXPORT_REV_MIN) || (version>REV_LEVEL)) {
- lprintf(CTDL_ERR, "Version mismatch in ARTV import; aborting\n");
- break;
- }
- }
- else if (!strcasecmp(buf, "config")) artv_import_config();
- else if (!strcasecmp(buf, "control")) artv_import_control();
- else if (!strcasecmp(buf, "user")) artv_import_user();
- else if (!strcasecmp(buf, "room")) artv_import_room();
- else if (!strcasecmp(buf, "floor")) artv_import_floor();
- else if (!strcasecmp(buf, "visit")) artv_import_visit();
- else if (!strcasecmp(buf, "message")) artv_import_message();
- else break;
- iterations ++;
- }
- lprintf(CTDL_INFO, "Invalid keyword <%s>. Flushing input.\n", buf);
- while (client_getln(buf, sizeof buf), strcmp(buf, "000")) ;;
- rebuild_euid_index();
-}
-
-
-
-void cmd_artv(char *cmdbuf) {
- char cmd[32];
- static int is_running = 0;
-
- if (CtdlAccessCheck(ac_internal)) return;
- if (is_running) {
- cprintf("%d The importer/exporter is already running.\n",
- ERROR + RESOURCE_BUSY);
- return;
- }
- is_running = 1;
-
- CtdlMakeTempFileName(artv_tempfilename1, sizeof artv_tempfilename1);
- CtdlMakeTempFileName(artv_tempfilename2, sizeof artv_tempfilename2);
-
- extract_token(cmd, cmdbuf, 0, '|', sizeof cmd);
- if (!strcasecmp(cmd, "export")) artv_do_export();
- else if (!strcasecmp(cmd, "import")) artv_do_import();
- else cprintf("%d illegal command\n", ERROR + ILLEGAL_VALUE);
-
- unlink(artv_tempfilename1);
- unlink(artv_tempfilename2);
-
- is_running = 0;
-}
-
-
-
-
-CTDL_MODULE_INIT(vandelay)
-{
- CtdlRegisterProtoHook(cmd_artv, "ARTV", "import/export data store");
-
- /* return our Subversion id for the Log */
- return "$Id$";
-}
+++ /dev/null
-/*
- * $Id$
- *
- * A server-side module for Citadel which supports address book information
- * using the standard vCard format.
- *
- * Copyright (c) 1999-2007 / released under the GNU General Public License
- */
-
-/*
- * Format of the "Exclusive ID" field of the message containing a user's
- * vCard. Doesn't matter what it really looks like as long as it's both
- * unique and consistent (because we use it for replication checking to
- * delete the old vCard network-wide when the user enters a new one).
- */
-#define VCARD_EXT_FORMAT "Citadel vCard: personal card for %s at %s"
-
-/*
- * Citadel will accept either text/vcard or text/x-vcard as the MIME type
- * for a vCard. The following definition determines which one it *generates*
- * when serializing.
- */
-#define VCARD_MIME_TYPE "text/x-vcard"
-
-#include "sysdep.h"
-#include <stdlib.h>
-#include <unistd.h>
-#include <stdio.h>
-#include <fcntl.h>
-#include <signal.h>
-#include <pwd.h>
-#include <errno.h>
-#include <ctype.h>
-#include <sys/types.h>
-
-#if TIME_WITH_SYS_TIME
-# include <sys/time.h>
-# include <time.h>
-#else
-# if HAVE_SYS_TIME_H
-# include <sys/time.h>
-# else
-# include <time.h>
-# endif
-#endif
-
-#include <sys/wait.h>
-#include <string.h>
-#include <limits.h>
-#include "citadel.h"
-#include "server.h"
-#include "citserver.h"
-#include "support.h"
-#include "config.h"
-#include "control.h"
-#include "room_ops.h"
-#include "user_ops.h"
-#include "policy.h"
-#include "database.h"
-#include "msgbase.h"
-#include "internet_addressing.h"
-#include "tools.h"
-#include "mime_parser.h"
-#include "vcard.h"
-#include "serv_ldap.h"
-#include "serv_vcard.h"
-
-
-#include "ctdl_module.h"
-
-
-
-/*
- * set global flag calling for an aide to validate new users
- */
-void set_mm_valid(void) {
- begin_critical_section(S_CONTROL);
- get_control();
- CitControl.MMflags = CitControl.MMflags | MM_VALID ;
- put_control();
- end_critical_section(S_CONTROL);
-}
-
-
-
-/*
- * Extract Internet e-mail addresses from a message containing a vCard, and
- * perform a callback for any found.
- */
-void vcard_extract_internet_addresses(struct CtdlMessage *msg,
- void (*callback)(char *, char *) ) {
- struct vCard *v;
- char *s;
- char *addr;
- char citadel_address[SIZ];
- int instance = 0;
- int found_something = 0;
-
- if (msg->cm_fields['A'] == NULL) return;
- if (msg->cm_fields['N'] == NULL) return;
- snprintf(citadel_address, sizeof citadel_address, "%s @ %s",
- msg->cm_fields['A'], msg->cm_fields['N']);
-
- v = vcard_load(msg->cm_fields['M']);
- if (v == NULL) return;
-
- /* Go through the vCard searching for *all* instances of
- * the "email;internet" key
- */
- do {
- s = vcard_get_prop(v, "email;internet", 0, instance++, 0);
- if (s != NULL) {
- addr = strdup(s);
- striplt(addr);
- if (strlen(addr) > 0) {
- if (callback != NULL) {
- callback(addr, citadel_address);
- }
- }
- free(addr);
- found_something = 1;
- }
- else {
- found_something = 0;
- }
- } while(found_something);
-
- vcard_free(v);
-}
-
-
-
-/*
- * Callback for vcard_add_to_directory()
- * (Lotsa ugly nested callbacks. Oh well.)
- */
-void vcard_directory_add_user(char *internet_addr, char *citadel_addr) {
- char buf[SIZ];
-
- /* We have to validate that we're not stepping on someone else's
- * email address ... but only if we're logged in. Otherwise it's
- * probably just the networker or something.
- */
- if (CC->logged_in) {
- lprintf(CTDL_DEBUG, "Checking for <%s>...\n", internet_addr);
- if (CtdlDirectoryLookup(buf, internet_addr, sizeof buf) == 0) {
- if (strcasecmp(buf, citadel_addr)) {
- /* This address belongs to someone else.
- * Bail out silently without saving.
- */
- lprintf(CTDL_DEBUG, "DOOP!\n");
- return;
- }
- }
- }
- lprintf(CTDL_INFO, "Adding %s (%s) to directory\n",
- citadel_addr, internet_addr);
- CtdlDirectoryAddUser(internet_addr, citadel_addr);
-}
-
-
-/*
- * Back end function for cmd_igab()
- */
-void vcard_add_to_directory(long msgnum, void *data) {
- struct CtdlMessage *msg;
-
- msg = CtdlFetchMessage(msgnum, 1);
- if (msg != NULL) {
- vcard_extract_internet_addresses(msg, vcard_directory_add_user);
- }
-
-#ifdef HAVE_LDAP
- ctdl_vcard_to_ldap(msg, V2L_WRITE);
-#endif
-
- CtdlFreeMessage(msg);
-}
-
-
-/*
- * Initialize Global Adress Book
- */
-void cmd_igab(char *argbuf) {
- char hold_rm[ROOMNAMELEN];
-
- if (CtdlAccessCheck(ac_aide)) return;
-
- strcpy(hold_rm, CC->room.QRname); /* save current room */
-
- if (getroom(&CC->room, ADDRESS_BOOK_ROOM) != 0) {
- getroom(&CC->room, hold_rm);
- cprintf("%d cannot get address book room\n", ERROR + ROOM_NOT_FOUND);
- return;
- }
-
- /* Empty the existing database first.
- */
- CtdlDirectoryInit();
-
- /* We want *all* vCards in this room */
- CtdlForEachMessage(MSGS_ALL, 0, NULL, "^[Tt][Ee][Xx][Tt]/.*[Vv][Cc][Aa][Rr][Dd]$",
- NULL, vcard_add_to_directory, NULL);
-
- getroom(&CC->room, hold_rm); /* return to saved room */
- cprintf("%d Directory has been rebuilt.\n", CIT_OK);
-}
-
-
-
-
-/*
- * See if there is a valid Internet address in a vCard to use for outbound
- * Internet messages. If there is, stick it in the buffer.
- */
-void extract_inet_email_addrs(char *emailaddrbuf, size_t emailaddrbuf_len,
- char *secemailaddrbuf, size_t secemailaddrbuf_len,
- struct vCard *v, int local_addrs_only) {
- char *s, *addr;
- int instance = 0;
- int saved_instance = 0;
-
- /* Go through the vCard searching for *all* instances of
- * the "email;internet" key
- */
- while (s = vcard_get_prop(v, "email;internet", 0, instance++, 0), s != NULL) {
- addr = strdup(s);
- striplt(addr);
- if (strlen(addr) > 0) {
- if ( (IsDirectory(addr, 1)) ||
- (!local_addrs_only) ) {
- ++saved_instance;
- if ((saved_instance == 1) && (emailaddrbuf != NULL)) {
- safestrncpy(emailaddrbuf, addr, emailaddrbuf_len);
- }
- else if ((saved_instance == 2) && (secemailaddrbuf != NULL)) {
- safestrncpy(secemailaddrbuf, addr, secemailaddrbuf_len);
- }
- else if ((saved_instance > 2) && (secemailaddrbuf != NULL)) {
- if ( (strlen(addr) + strlen(secemailaddrbuf) + 2)
- < secemailaddrbuf_len ) {
- strcat(secemailaddrbuf, "|");
- strcat(secemailaddrbuf, addr);
- }
- }
- }
- }
- free(addr);
- }
-}
-
-
-
-/*
- * See if there is a name / screen name / friendly name in a vCard to use for outbound
- * Internet messages. If there is, stick it in the buffer.
- */
-void extract_friendly_name(char *namebuf, size_t namebuf_len, struct vCard *v)
-{
- char *s;
-
- s = vcard_get_prop(v, "fn", 0, 0, 0);
- if (s == NULL) {
- s = vcard_get_prop(v, "n", 0, 0, 0);
- }
-
- if (s != NULL) {
- safestrncpy(namebuf, s, namebuf_len);
- }
-}
-
-
-/*
- * Callback function for vcard_upload_beforesave() hunts for the real vcard in the MIME structure
- */
-void vcard_extract_vcard(char *name, char *filename, char *partnum, char *disp,
- void *content, char *cbtype, char *cbcharset, size_t length,
- char *encoding, void *cbuserdata)
-{
- struct vCard **v = (struct vCard **) cbuserdata;
-
- if ( (!strcasecmp(cbtype, "text/x-vcard"))
- || (!strcasecmp(cbtype, "text/vcard")) ) {
-
- lprintf(CTDL_DEBUG, "Part %s contains a vCard! Loading...\n", partnum);
- if (*v != NULL) {
- vcard_free(*v);
- }
- *v = vcard_load(content);
- }
-}
-
-
-/*
- * This handler detects whether the user is attempting to save a new
- * vCard as part of his/her personal configuration, and handles the replace
- * function accordingly (delete the user's existing vCard in the config room
- * and in the global address book).
- */
-int vcard_upload_beforesave(struct CtdlMessage *msg) {
- char *ptr;
- char *s;
- char buf[SIZ];
- struct ctdluser usbuf;
- long what_user;
- struct vCard *v = NULL;
- char *ser = NULL;
- int i = 0;
- int yes_my_citadel_config = 0;
- int yes_any_vcard_room = 0;
-
- if (!CC->logged_in) return(0); /* Only do this if logged in. */
-
- /* Is this some user's "My Citadel Config" room? */
- if ( (CC->room.QRflags && QR_MAILBOX)
- && (!strcasecmp(&CC->room.QRname[11], USERCONFIGROOM)) ) {
- /* Yes, we want to do this */
- yes_my_citadel_config = 1;
-
-#ifdef VCARD_SAVES_BY_AIDES_ONLY
- /* Prevent non-aides from performing registration changes */
- if (CC->user.axlevel < 6) {
- return(1);
- }
-#endif
-
- }
-
- /* Is this a room with an address book in it? */
- if (CC->room.QRdefaultview == VIEW_ADDRESSBOOK) {
- yes_any_vcard_room = 1;
- }
-
- /* If neither condition exists, don't run this hook. */
- if ( (!yes_my_citadel_config) && (!yes_any_vcard_room) ) {
- return(0);
- }
-
- /* If this isn't a MIME message, don't bother. */
- if (msg->cm_format_type != 4) return(0);
-
- /* Ok, if we got this far, look into the situation further... */
-
- ptr = msg->cm_fields['M'];
- if (ptr == NULL) return(0);
-
- mime_parser(msg->cm_fields['M'],
- NULL,
- *vcard_extract_vcard,
- NULL, NULL,
- &v, /* user data ptr - put the vcard here */
- 0
- );
-
- if (v == NULL) return(0); /* no vCards were found in this message */
-
- /* If users cannot create their own accounts, they cannot re-register either. */
- if ( (yes_my_citadel_config) && (config.c_disable_newu) && (CC->user.axlevel < 6) ) {
- return(1);
- }
-
- s = vcard_get_prop(v, "FN", 0, 0, 0);
- if (s) lprintf(CTDL_DEBUG, "vCard beforesave hook running for <%s>\n", s);
-
- if (yes_my_citadel_config) {
- /* Bingo! The user is uploading a new vCard, so
- * delete the old one. First, figure out which user
- * is being re-registered...
- */
- what_user = atol(CC->room.QRname);
-
- if (what_user == CC->user.usernum) {
- /* It's the logged in user. That was easy. */
- memcpy(&usbuf, &CC->user, sizeof(struct ctdluser));
- }
-
- else if (getuserbynumber(&usbuf, what_user) == 0) {
- /* We fetched a valid user record */
- }
-
- else {
- /* somebody set up us the bomb! */
- yes_my_citadel_config = 0;
- }
- }
-
- if (yes_my_citadel_config) {
- /* Delete the user's old vCard. This would probably
- * get taken care of by the replication check, but we
- * want to make sure there is absolutely only one
- * vCard in the user's config room at all times.
- *
- */
- CtdlDeleteMessages(CC->room.QRname, NULL, 0, "^[Tt][Ee][Xx][Tt]/.*[Vv][Cc][Aa][Rr][Dd]$");
-
- /* Make the author of the message the name of the user. */
- if (msg->cm_fields['A'] != NULL) {
- free(msg->cm_fields['A']);
- }
- msg->cm_fields['A'] = strdup(usbuf.fullname);
- }
-
- /* Insert or replace RFC2739-compliant free/busy URL */
- if (yes_my_citadel_config) {
- sprintf(buf, "http://%s/%s.vfb",
- config.c_fqdn,
- usbuf.fullname);
- for (i=0; i<strlen(buf); ++i) {
- if (buf[i] == ' ') buf[i] = '_';
- }
- vcard_set_prop(v, "FBURL;PREF", buf, 0);
- }
-
- /* If the vCard has no UID, then give it one. */
- s = vcard_get_prop(v, "UID", 0, 0, 0);
- if (s == NULL) {
- generate_uuid(buf);
- vcard_set_prop(v, "UID", buf, 0);
- }
-
- /* Enforce local UID policy if applicable */
- if (yes_my_citadel_config) {
- snprintf(buf, sizeof buf, VCARD_EXT_FORMAT, msg->cm_fields['A'], NODENAME);
- vcard_set_prop(v, "UID", buf, 0);
- }
-
- /*
- * Set the EUID of the message to the UID of the vCard.
- */
- if (msg->cm_fields['E'] != NULL) free(msg->cm_fields['E']);
- s = vcard_get_prop(v, "UID", 0, 0, 0);
- if (s != NULL) {
- msg->cm_fields['E'] = strdup(s);
- if (msg->cm_fields['U'] == NULL) {
- msg->cm_fields['U'] = strdup(s);
- }
- }
-
- /*
- * Set the Subject to the name in the vCard.
- */
- s = vcard_get_prop(v, "FN", 0, 0, 0);
- if (s == NULL) {
- s = vcard_get_prop(v, "N", 0, 0, 0);
- }
- if (s != NULL) {
- if (msg->cm_fields['U'] != NULL) {
- free(msg->cm_fields['U']);
- }
- msg->cm_fields['U'] = strdup(s);
- }
-
- /* Re-serialize it back into the msg body */
- ser = vcard_serialize(v);
- if (ser != NULL) {
- msg->cm_fields['M'] = realloc(msg->cm_fields['M'], strlen(ser) + 1024);
- sprintf(msg->cm_fields['M'],
- "Content-type: " VCARD_MIME_TYPE
- "\r\n\r\n%s\r\n", ser);
- free(ser);
- }
-
- /* Now allow the save to complete. */
- vcard_free(v);
- return(0);
-}
-
-
-
-/*
- * This handler detects whether the user is attempting to save a new
- * vCard as part of his/her personal configuration, and handles the replace
- * function accordingly (copy the vCard from the config room to the global
- * address book).
- */
-int vcard_upload_aftersave(struct CtdlMessage *msg) {
- char *ptr;
- int linelen;
- long I;
- struct vCard *v;
-
- if (!CC->logged_in) return(0); /* Only do this if logged in. */
-
- /* If this isn't the configuration room, or if this isn't a MIME
- * message, don't bother.
- */
- if (msg->cm_fields['O'] == NULL) return(0);
- if (strcasecmp(msg->cm_fields['O'], USERCONFIGROOM)) return(0);
- if (msg->cm_format_type != 4) return(0);
-
- ptr = msg->cm_fields['M'];
- if (ptr == NULL) return(0);
- while (ptr != NULL) {
-
- linelen = strcspn(ptr, "\n");
- if (linelen == 0) return(0); /* end of headers */
-
- if ( (!strncasecmp(ptr, "Content-type: text/x-vcard", 26))
- || (!strncasecmp(ptr, "Content-type: text/vcard", 24)) ) {
- /*
- * Bingo! The user is uploading a new vCard, so
- * copy it to the Global Address Book room.
- */
-
- I = atol(msg->cm_fields['I']);
- if (I < 0L) return(0);
-
- /* Store our Internet return address in memory */
- v = vcard_load(msg->cm_fields['M']);
- extract_inet_email_addrs(CC->cs_inet_email, sizeof CC->cs_inet_email,
- CC->cs_inet_other_emails, sizeof CC->cs_inet_other_emails,
- v, 1);
- extract_friendly_name(CC->cs_inet_fn, sizeof CC->cs_inet_fn, v);
- vcard_free(v);
-
- /* Put it in the Global Address Book room... */
- CtdlSaveMsgPointerInRoom(ADDRESS_BOOK_ROOM, I, 1, msg);
-
- /* ...and also in the directory database. */
- vcard_add_to_directory(I, NULL);
-
- /* Some sites want an Aide to be notified when a
- * user registers or re-registers...
- */
- set_mm_valid();
-
- /* ...which also means we need to flag the user */
- lgetuser(&CC->user, CC->curr_user);
- CC->user.flags |= (US_REGIS|US_NEEDVALID);
- lputuser(&CC->user);
-
- return(0);
- }
-
- ptr = strchr((char *)ptr, '\n');
- if (ptr != NULL) ++ptr;
- }
-
- return(0);
-}
-
-
-
-/*
- * back end function used for callbacks
- */
-void vcard_gu_backend(long supplied_msgnum, void *userdata) {
- long *msgnum;
-
- msgnum = (long *) userdata;
- *msgnum = supplied_msgnum;
-}
-
-
-/*
- * If this user has a vcard on disk, read it into memory, otherwise allocate
- * and return an empty vCard.
- */
-struct vCard *vcard_get_user(struct ctdluser *u) {
- char hold_rm[ROOMNAMELEN];
- char config_rm[ROOMNAMELEN];
- struct CtdlMessage *msg = NULL;
- struct vCard *v;
- long VCmsgnum;
-
- strcpy(hold_rm, CC->room.QRname); /* save current room */
- MailboxName(config_rm, sizeof config_rm, u, USERCONFIGROOM);
-
- if (getroom(&CC->room, config_rm) != 0) {
- getroom(&CC->room, hold_rm);
- return vcard_new();
- }
-
- /* We want the last (and probably only) vcard in this room */
- VCmsgnum = (-1);
- CtdlForEachMessage(MSGS_LAST, 1, NULL, "^[Tt][Ee][Xx][Tt]/.*[Vv][Cc][Aa][Rr][Dd]$",
- NULL, vcard_gu_backend, (void *)&VCmsgnum );
- getroom(&CC->room, hold_rm); /* return to saved room */
-
- if (VCmsgnum < 0L) return vcard_new();
-
- msg = CtdlFetchMessage(VCmsgnum, 1);
- if (msg == NULL) return vcard_new();
-
- v = vcard_load(msg->cm_fields['M']);
- CtdlFreeMessage(msg);
- return v;
-}
-
-
-/*
- * Store this user's vCard in the appropriate place
- */
-/*
- * Write our config to disk
- */
-void vcard_write_user(struct ctdluser *u, struct vCard *v) {
- char temp[PATH_MAX];
- FILE *fp;
- char *ser;
-
- CtdlMakeTempFileName(temp, sizeof temp);
- ser = vcard_serialize(v);
-
- fp = fopen(temp, "w");
- if (fp == NULL) return;
- if (ser == NULL) {
- fprintf(fp, "begin:vcard\r\nend:vcard\r\n");
- } else {
- fwrite(ser, strlen(ser), 1, fp);
- free(ser);
- }
- fclose(fp);
-
- /* This handy API function does all the work for us.
- * NOTE: normally we would want to set that last argument to 1, to
- * force the system to delete the user's old vCard. But it doesn't
- * have to, because the vcard_upload_beforesave() hook above
- * is going to notice what we're trying to do, and delete the old vCard.
- */
- CtdlWriteObject(USERCONFIGROOM, /* which room */
- VCARD_MIME_TYPE,/* MIME type */
- temp, /* temp file */
- u, /* which user */
- 0, /* not binary */
- 0, /* don't delete others of this type */
- 0); /* no flags */
-
- unlink(temp);
-}
-
-
-
-/*
- * Old style "enter registration info" command. This function simply honors
- * the REGI protocol command, translates the entered parameters into a vCard,
- * and enters the vCard into the user's configuration.
- */
-void cmd_regi(char *argbuf) {
- int a,b,c;
- char buf[SIZ];
- struct vCard *my_vcard;
-
- char tmpaddr[SIZ];
- char tmpcity[SIZ];
- char tmpstate[SIZ];
- char tmpzip[SIZ];
- char tmpaddress[SIZ];
- char tmpcountry[SIZ];
-
- unbuffer_output();
-
- if (!(CC->logged_in)) {
- cprintf("%d Not logged in.\n",ERROR + NOT_LOGGED_IN);
- return;
- }
-
- /* If users cannot create their own accounts, they cannot re-register either. */
- if ( (config.c_disable_newu) && (CC->user.axlevel < 6) ) {
- cprintf("%d Self-service registration is not allowed here.\n",
- ERROR + HIGHER_ACCESS_REQUIRED);
- }
-
- my_vcard = vcard_get_user(&CC->user);
- strcpy(tmpaddr, "");
- strcpy(tmpcity, "");
- strcpy(tmpstate, "");
- strcpy(tmpzip, "");
- strcpy(tmpcountry, "USA");
-
- cprintf("%d Send registration...\n", SEND_LISTING);
- a=0;
- while (client_getln(buf, sizeof buf), strcmp(buf,"000")) {
- if (a==0) vcard_set_prop(my_vcard, "n", buf, 0);
- if (a==1) strcpy(tmpaddr, buf);
- if (a==2) strcpy(tmpcity, buf);
- if (a==3) strcpy(tmpstate, buf);
- if (a==4) {
- for (c=0; c<strlen(buf); ++c) {
- if ((buf[c]>='0') && (buf[c]<='9')) {
- b = strlen(tmpzip);
- tmpzip[b] = buf[c];
- tmpzip[b+1] = 0;
- }
- }
- }
- if (a==5) vcard_set_prop(my_vcard, "tel;home", buf, 0);
- if (a==6) vcard_set_prop(my_vcard, "email;internet", buf, 0);
- if (a==7) strcpy(tmpcountry, buf);
- ++a;
- }
-
- snprintf(tmpaddress, sizeof tmpaddress, ";;%s;%s;%s;%s;%s",
- tmpaddr, tmpcity, tmpstate, tmpzip, tmpcountry);
- vcard_set_prop(my_vcard, "adr", tmpaddress, 0);
- vcard_write_user(&CC->user, my_vcard);
- vcard_free(my_vcard);
-}
-
-
-/*
- * Protocol command to fetch registration info for a user
- */
-void cmd_greg(char *argbuf)
-{
- struct ctdluser usbuf;
- struct vCard *v;
- char *s;
- char who[USERNAME_SIZE];
- char adr[256];
- char buf[256];
-
- extract_token(who, argbuf, 0, '|', sizeof who);
-
- if (!(CC->logged_in)) {
- cprintf("%d Not logged in.\n", ERROR + NOT_LOGGED_IN);
- return;
- }
-
- if (!strcasecmp(who,"_SELF_")) strcpy(who,CC->curr_user);
-
- if ((CC->user.axlevel < 6) && (strcasecmp(who,CC->curr_user))) {
- cprintf("%d Higher access required.\n",
- ERROR + HIGHER_ACCESS_REQUIRED);
- return;
- }
-
- if (getuser(&usbuf, who) != 0) {
- cprintf("%d '%s' not found.\n", ERROR + NO_SUCH_USER, who);
- return;
- }
-
- v = vcard_get_user(&usbuf);
-
- cprintf("%d %s\n", LISTING_FOLLOWS, usbuf.fullname);
- cprintf("%ld\n", usbuf.usernum);
- cprintf("%s\n", usbuf.password);
- s = vcard_get_prop(v, "n", 0, 0, 0);
- cprintf("%s\n", s ? s : " "); /* name */
-
- s = vcard_get_prop(v, "adr", 0, 0, 0);
- snprintf(adr, sizeof adr, "%s", s ? s : " ");/* address... */
-
- extract_token(buf, adr, 2, ';', sizeof buf);
- cprintf("%s\n", buf); /* street */
- extract_token(buf, adr, 3, ';', sizeof buf);
- cprintf("%s\n", buf); /* city */
- extract_token(buf, adr, 4, ';', sizeof buf);
- cprintf("%s\n", buf); /* state */
- extract_token(buf, adr, 5, ';', sizeof buf);
- cprintf("%s\n", buf); /* zip */
-
- s = vcard_get_prop(v, "tel;home", 0, 0, 0);
- if (s == NULL) s = vcard_get_prop(v, "tel", 1, 0, 0);
- if (s != NULL) {
- cprintf("%s\n", s);
- }
- else {
- cprintf(" \n");
- }
-
- cprintf("%d\n", usbuf.axlevel);
-
- s = vcard_get_prop(v, "email;internet", 0, 0, 0);
- cprintf("%s\n", s ? s : " ");
- s = vcard_get_prop(v, "adr", 0, 0, 0);
- snprintf(adr, sizeof adr, "%s", s ? s : " ");/* address... */
-
- extract_token(buf, adr, 6, ';', sizeof buf);
- cprintf("%s\n", buf); /* country */
- cprintf("000\n");
- vcard_free(v);
-}
-
-
-
-/*
- * When a user is being created, create his/her vCard.
- */
-void vcard_newuser(struct ctdluser *usbuf) {
- char vname[256];
- char buf[256];
- int i;
- struct vCard *v;
-
- vcard_fn_to_n(vname, usbuf->fullname, sizeof vname);
- lprintf(CTDL_DEBUG, "Converted <%s> to <%s>\n", usbuf->fullname, vname);
-
- /* Create and save the vCard */
- v = vcard_new();
- if (v == NULL) return;
- sprintf(buf, "%s@%s", usbuf->fullname, config.c_fqdn);
- for (i=0; i<strlen(buf); ++i) {
- if (buf[i] == ' ') buf[i] = '_';
- }
- vcard_add_prop(v, "fn", usbuf->fullname);
- vcard_add_prop(v, "n", vname);
- vcard_add_prop(v, "adr", "adr:;;_;_;_;00000;__");
- vcard_add_prop(v, "email;internet", buf);
- vcard_write_user(usbuf, v);
- vcard_free(v);
-}
-
-
-/*
- * When a user is being deleted, we have to remove his/her vCard.
- * This is accomplished by issuing a message with 'CANCEL' in the S (special)
- * field, and the same Exclusive ID as the existing card.
- */
-void vcard_purge(struct ctdluser *usbuf) {
- struct CtdlMessage *msg;
- char buf[SIZ];
-
- msg = (struct CtdlMessage *) malloc(sizeof(struct CtdlMessage));
- if (msg == NULL) return;
- memset(msg, 0, sizeof(struct CtdlMessage));
-
- msg->cm_magic = CTDLMESSAGE_MAGIC;
- msg->cm_anon_type = MES_NORMAL;
- msg->cm_format_type = 0;
- msg->cm_fields['A'] = strdup(usbuf->fullname);
- msg->cm_fields['O'] = strdup(ADDRESS_BOOK_ROOM);
- msg->cm_fields['N'] = strdup(NODENAME);
- msg->cm_fields['M'] = strdup("Purge this vCard\n");
-
- snprintf(buf, sizeof buf, VCARD_EXT_FORMAT,
- msg->cm_fields['A'], NODENAME);
- msg->cm_fields['E'] = strdup(buf);
-
- msg->cm_fields['S'] = strdup("CANCEL");
-
- CtdlSubmitMsg(msg, NULL, ADDRESS_BOOK_ROOM);
- CtdlFreeMessage(msg);
-}
-
-
-/*
- * Grab vCard directory stuff out of incoming network messages
- */
-int vcard_extract_from_network(struct CtdlMessage *msg, char *target_room) {
- char *ptr;
- int linelen;
-
- if (msg == NULL) return(0);
-
- if (strcasecmp(target_room, ADDRESS_BOOK_ROOM)) {
- return(0);
- }
-
- if (msg->cm_format_type != 4) return(0);
-
- ptr = msg->cm_fields['M'];
- if (ptr == NULL) return(0);
- while (ptr != NULL) {
-
- linelen = strcspn(ptr, "\n");
- if (linelen == 0) return(0); /* end of headers */
-
- if ( (!strncasecmp(ptr, "Content-type: text/x-vcard", 26))
- || (!strncasecmp(ptr, "Content-type: text/vcard", 24)) ) {
- /* It's a vCard. Add it to the directory. */
- vcard_extract_internet_addresses(msg, CtdlDirectoryAddUser);
- return(0);
- }
-
- ptr = strchr((char *)ptr, '\n');
- if (ptr != NULL) ++ptr;
- }
-
- return(0);
-}
-
-
-
-/*
- * When a vCard is being removed from the Global Address Book room, remove it
- * from the directory as well.
- */
-void vcard_delete_remove(char *room, long msgnum) {
- struct CtdlMessage *msg;
- char *ptr;
- int linelen;
-
- if (msgnum <= 0L) return;
-
- if (strcasecmp(room, ADDRESS_BOOK_ROOM)) {
- return;
- }
-
- msg = CtdlFetchMessage(msgnum, 1);
- if (msg == NULL) return;
-
- ptr = msg->cm_fields['M'];
- if (ptr == NULL) goto EOH;
- while (ptr != NULL) {
- linelen = strcspn(ptr, "\n");
- if (linelen == 0) goto EOH;
-
- if ( (!strncasecmp(ptr, "Content-type: text/x-vcard", 26))
- || (!strncasecmp(ptr, "Content-type: text/vcard", 24)) ) {
- /* Bingo! A vCard is being deleted. */
- vcard_extract_internet_addresses(msg, CtdlDirectoryDelUser);
-#ifdef HAVE_LDAP
- ctdl_vcard_to_ldap(msg, V2L_DELETE);
-#endif
- }
- ptr = strchr((char *)ptr, '\n');
- if (ptr != NULL) ++ptr;
- }
-
-EOH: CtdlFreeMessage(msg);
-}
-
-
-
-/*
- * Get Valid Screen Names
- */
-void cmd_gvsn(char *argbuf)
-{
- if (CtdlAccessCheck(ac_logged_in)) return;
-
- cprintf("%d valid screen names:\n", LISTING_FOLLOWS);
- cprintf("%s\n", CC->user.fullname);
- if ( (strlen(CC->cs_inet_fn) > 0) && (strcasecmp(CC->user.fullname, CC->cs_inet_fn)) ) {
- cprintf("%s\n", CC->cs_inet_fn);
- }
- cprintf("000\n");
-}
-
-
-/*
- * Get Valid Email Addresses
- */
-void cmd_gvea(char *argbuf)
-{
- int num_secondary_emails = 0;
- int i;
- char buf[256];
-
- if (CtdlAccessCheck(ac_logged_in)) return;
-
- cprintf("%d valid email addresses:\n", LISTING_FOLLOWS);
- if (strlen(CC->cs_inet_email) > 0) {
- cprintf("%s\n", CC->cs_inet_email);
- }
- if (strlen(CC->cs_inet_other_emails) > 0) {
- num_secondary_emails = num_tokens(CC->cs_inet_other_emails, '|');
- for (i=0; i<num_secondary_emails; ++i) {
- extract_token(buf, CC->cs_inet_other_emails,i,'|',sizeof CC->cs_inet_other_emails);
- cprintf("%s\n", buf);
- }
- }
- cprintf("000\n");
-}
-
-
-
-
-/*
- * Callback function for cmd_dvca() that hunts for vCard content types
- * and outputs any email addresses found within.
- */
-void dvca_mime_callback(char *name, char *filename, char *partnum, char *disp,
- void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
- void *cbuserdata) {
-
- struct vCard *v;
- char displayname[256];
- int displayname_len;
- char emailaddr[256];
- int i;
- int has_commas = 0;
-
- if ( (strcasecmp(cbtype, "text/vcard")) && (strcasecmp(cbtype, "text/x-vcard")) ) {
- return;
- }
-
- v = vcard_load(content);
- if (v == NULL) return;
-
- extract_friendly_name(displayname, sizeof displayname, v);
- extract_inet_email_addrs(emailaddr, sizeof emailaddr, NULL, 0, v, 0);
-
- displayname_len = strlen(displayname);
- for (i=0; i<displayname_len; ++i) {
- if (displayname[i] == '\"') displayname[i] = ' ';
- if (displayname[i] == ';') displayname[i] = ',';
- if (displayname[i] == ',') has_commas = 1;
- }
- striplt(displayname);
-
- cprintf("%s%s%s <%s>\n",
- (has_commas ? "\"" : ""),
- displayname,
- (has_commas ? "\"" : ""),
- emailaddr
- );
-
- vcard_free(v);
-}
-
-
-/*
- * Back end callback function for cmd_dvca()
- *
- * It's basically just passed a list of message numbers, which we're going
- * to fetch off the disk and then pass along to the MIME parser via another
- * layer of callback...
- */
-void dvca_callback(long msgnum, void *userdata) {
- struct CtdlMessage *msg = NULL;
-
- msg = CtdlFetchMessage(msgnum, 1);
- if (msg == NULL) return;
- mime_parser(msg->cm_fields['M'],
- NULL,
- *dvca_mime_callback, /* callback function */
- NULL, NULL,
- NULL, /* user data */
- 0
- );
- CtdlFreeMessage(msg);
-}
-
-
-/*
- * Dump VCard Addresses
- */
-void cmd_dvca(char *argbuf)
-{
- if (CtdlAccessCheck(ac_logged_in)) return;
-
- cprintf("%d addresses:\n", LISTING_FOLLOWS);
- CtdlForEachMessage(MSGS_ALL, 0, NULL, NULL, NULL, dvca_callback, NULL);
- cprintf("000\n");
-}
-
-
-/*
- * Query Directory
- */
-void cmd_qdir(char *argbuf) {
- char citadel_addr[256];
- char internet_addr[256];
-
- if (CtdlAccessCheck(ac_logged_in)) return;
-
- extract_token(internet_addr, argbuf, 0, '|', sizeof internet_addr);
-
- if (CtdlDirectoryLookup(citadel_addr, internet_addr, sizeof citadel_addr) != 0) {
- cprintf("%d %s was not found.\n",
- ERROR + NO_SUCH_USER, internet_addr);
- return;
- }
-
- cprintf("%d %s\n", CIT_OK, citadel_addr);
-}
-
-/*
- * Query Directory, in fact an alias to match postfix tcp auth.
- */
-void check_get(void) {
- char internet_addr[256];
-
- char cmdbuf[SIZ];
-
- time(&CC->lastcmd);
- memset(cmdbuf, 0, sizeof cmdbuf); /* Clear it, just in case */
- if (client_getln(cmdbuf, sizeof cmdbuf) < 1) {
- lprintf(CTDL_CRIT, "Client disconnected: ending session.\n");
- CC->kill_me = 1;
- return;
- }
- lprintf(CTDL_INFO, ": %s\n", cmdbuf);
- while (strlen(cmdbuf) < 3) strcat(cmdbuf, " ");
-
- if (strcasecmp(cmdbuf, "GET "));
- {
- struct recptypes *rcpt;
- char *argbuf = &cmdbuf[4];
-
- extract_token(internet_addr, argbuf, 0, '|', sizeof internet_addr);
- rcpt = validate_recipients(internet_addr);
- if ((rcpt != NULL)&&
- (
- (*rcpt->recp_local != '\0')||
- (*rcpt->recp_room != '\0')||
- (*rcpt->recp_ignet != '\0')))
- {
-
- cprintf("200 OK %s\n", internet_addr);
- lprintf(CTDL_INFO, "sending 200 OK for the room %s\n", rcpt->display_recp);
- }
- else
- {
- cprintf("500 REJECT noone here by that name.\n");
-
- lprintf(CTDL_INFO, "sending 500 REJECT noone here by that name: %s\n", internet_addr);
- }
- if (rcpt != NULL) free_recipients(rcpt);
- }
-}
-
-void check_get_greeting(void) {
-/* dummy function, we have no greeting in this verry simple protocol. */
-}
-
-
-/*
- * We don't know if the Contacts room exists so we just create it at login
- */
-void vcard_create_room(void)
-{
- struct ctdlroom qr;
- struct visit vbuf;
-
- /* Create the calendar room if it doesn't already exist */
- create_room(USERCONTACTSROOM, 4, "", 0, 1, 0, VIEW_ADDRESSBOOK);
-
- /* Set expiration policy to manual; otherwise objects will be lost! */
- if (lgetroom(&qr, USERCONTACTSROOM)) {
- lprintf(CTDL_ERR, "Couldn't get the user CONTACTS room!\n");
- return;
- }
- qr.QRep.expire_mode = EXPIRE_MANUAL;
- qr.QRdefaultview = VIEW_ADDRESSBOOK; /* 2 = address book view */
- lputroom(&qr);
-
- /* Set the view to a calendar view */
- CtdlGetRelationship(&vbuf, &CC->user, &qr);
- vbuf.v_view = 2; /* 2 = address book view */
- CtdlSetRelationship(&vbuf, &CC->user, &qr);
-
- return;
-}
-
-
-
-
-/*
- * When a user logs in...
- */
-void vcard_session_login_hook(void) {
- struct vCard *v = NULL;
-
- v = vcard_get_user(&CC->user);
- extract_inet_email_addrs(CC->cs_inet_email, sizeof CC->cs_inet_email,
- CC->cs_inet_other_emails, sizeof CC->cs_inet_other_emails,
- v, 1);
- extract_friendly_name(CC->cs_inet_fn, sizeof CC->cs_inet_fn, v);
- vcard_free(v);
-
- vcard_create_room();
-}
-
-
-/*
- * Turn an arbitrary RFC822 address into a struct vCard for possible
- * inclusion into an address book.
- */
-struct vCard *vcard_new_from_rfc822_addr(char *addr) {
- struct vCard *v;
- char user[256], node[256], name[256], email[256], n[256], uid[256];
- int i;
-
- v = vcard_new();
- if (v == NULL) return(NULL);
-
- process_rfc822_addr(addr, user, node, name);
- vcard_set_prop(v, "fn", name, 0);
-
- vcard_fn_to_n(n, name, sizeof n);
- vcard_set_prop(v, "n", n, 0);
-
- snprintf(email, sizeof email, "%s@%s", user, node);
- vcard_set_prop(v, "email;internet", email, 0);
-
- snprintf(uid, sizeof uid, "collected: %s %s@%s", name, user, node);
- for (i=0; i<strlen(uid); ++i) {
- if (isspace(uid[i])) uid[i] = '_';
- uid[i] = tolower(uid[i]);
- }
- vcard_set_prop(v, "UID", uid, 0);
-
- return(v);
-}
-
-
-
-/*
- * This is called by store_harvested_addresses() to remove from the
- * list any addresses we already have in our address book.
- */
-void strip_addresses_already_have(long msgnum, void *userdata) {
- char *collected_addresses;
- struct CtdlMessage *msg = NULL;
- struct vCard *v;
- char *value = NULL;
- int i, j;
- char addr[256], user[256], node[256], name[256];
-
- collected_addresses = (char *)userdata;
-
- msg = CtdlFetchMessage(msgnum, 1);
- if (msg == NULL) return;
- v = vcard_load(msg->cm_fields['M']);
- CtdlFreeMessage(msg);
-
- i = 0;
- while (value = vcard_get_prop(v, "email", 1, i++, 0), value != NULL) {
-
- for (j=0; j<num_tokens(collected_addresses, ','); ++j) {
- extract_token(addr, collected_addresses, j, ',', sizeof addr);
-
- /* Remove the address if we already have it! */
- process_rfc822_addr(addr, user, node, name);
- snprintf(addr, sizeof addr, "%s@%s", user, node);
- if (!strcasecmp(value, addr)) {
- remove_token(collected_addresses, j, ',');
- }
- }
-
- }
-
- vcard_free(v);
-}
-
-
-
-/*
- * Back end function for store_harvested_addresses()
- */
-void store_this_ha(struct addresses_to_be_filed *aptr) {
- struct CtdlMessage *vmsg = NULL;
- long vmsgnum = (-1L);
- char *ser = NULL;
- struct vCard *v = NULL;
- char recipient[256];
- int i;
-
- /* First remove any addresses we already have in the address book */
- usergoto(aptr->roomname, 0, 0, NULL, NULL);
- CtdlForEachMessage(MSGS_ALL, 0, NULL, "^[Tt][Ee][Xx][Tt]/.*[Vv][Cc][Aa][Rr][Dd]$", NULL,
- strip_addresses_already_have, aptr->collected_addresses);
-
- if (strlen(aptr->collected_addresses) > 0)
- for (i=0; i<num_tokens(aptr->collected_addresses, ','); ++i) {
-
- /* Make a vCard out of each address */
- extract_token(recipient, aptr->collected_addresses, i, ',', sizeof recipient);
- striplt(recipient);
- v = vcard_new_from_rfc822_addr(recipient);
- if (v != NULL) {
- vmsg = malloc(sizeof(struct CtdlMessage));
- memset(vmsg, 0, sizeof(struct CtdlMessage));
- vmsg->cm_magic = CTDLMESSAGE_MAGIC;
- vmsg->cm_anon_type = MES_NORMAL;
- vmsg->cm_format_type = FMT_RFC822;
- vmsg->cm_fields['A'] = strdup("Citadel");
- vmsg->cm_fields['E'] = strdup(vcard_get_prop(v, "UID", 0, 0, 0));
- ser = vcard_serialize(v);
- if (ser != NULL) {
- vmsg->cm_fields['M'] = malloc(strlen(ser) + 1024);
- sprintf(vmsg->cm_fields['M'],
- "Content-type: " VCARD_MIME_TYPE
- "\r\n\r\n%s\r\n", ser);
- free(ser);
- }
- vcard_free(v);
-
- lprintf(CTDL_DEBUG, "Adding contact: %s\n", recipient);
- vmsgnum = CtdlSubmitMsg(vmsg, NULL, aptr->roomname);
- CtdlFreeMessage(vmsg);
- }
- }
-
- free(aptr->roomname);
- free(aptr->collected_addresses);
- free(aptr);
-}
-
-
-/*
- * When a user sends a message, we may harvest one or more email addresses
- * from the recipient list to be added to the user's address book. But we
- * want to do this asynchronously so it doesn't keep the user waiting.
- */
-void store_harvested_addresses(void) {
-
- struct addresses_to_be_filed *aptr = NULL;
-
- if (atbf == NULL) return;
-
- begin_critical_section(S_ATBF);
- while (atbf != NULL) {
- aptr = atbf;
- atbf = atbf->next;
- end_critical_section(S_ATBF);
- store_this_ha(aptr);
- begin_critical_section(S_ATBF);
- }
- end_critical_section(S_ATBF);
-}
-
-
-/*
- * Function to output vCard data as plain text. Nobody uses MSG0 anymore, so
- * really this is just so we expose the vCard data to the full text indexer.
- */
-void vcard_fixed_output(char *ptr, int len) {
- char *serialized_vcard;
- struct vCard *v;
- char *key, *value;
- int i = 0;
-
- serialized_vcard = malloc(len + 1);
- safestrncpy(serialized_vcard, ptr, len+1);
- v = vcard_load(serialized_vcard);
- free(serialized_vcard);
-
- i = 0;
- while (key = vcard_get_prop(v, "", 0, i, 1), key != NULL) {
- value = vcard_get_prop(v, "", 0, i++, 0);
- cprintf("%s\n", value);
- }
-
- vcard_free(v);
-}
-
-
-
-
-CTDL_MODULE_INIT(vcard)
-{
- struct ctdlroom qr;
- char filename[256];
- FILE *fp;
-
- CtdlRegisterSessionHook(vcard_session_login_hook, EVT_LOGIN);
- CtdlRegisterMessageHook(vcard_upload_beforesave, EVT_BEFORESAVE);
- CtdlRegisterMessageHook(vcard_upload_aftersave, EVT_AFTERSAVE);
- CtdlRegisterDeleteHook(vcard_delete_remove);
- CtdlRegisterProtoHook(cmd_regi, "REGI", "Enter registration info");
- CtdlRegisterProtoHook(cmd_greg, "GREG", "Get registration info");
- CtdlRegisterProtoHook(cmd_igab, "IGAB",
- "Initialize Global Address Book");
- CtdlRegisterProtoHook(cmd_qdir, "QDIR", "Query Directory");
- CtdlRegisterProtoHook(cmd_gvsn, "GVSN", "Get Valid Screen Names");
- CtdlRegisterProtoHook(cmd_gvea, "GVEA", "Get Valid Email Addresses");
- CtdlRegisterProtoHook(cmd_dvca, "DVCA", "Dump VCard Addresses");
- CtdlRegisterUserHook(vcard_newuser, EVT_NEWUSER);
- CtdlRegisterUserHook(vcard_purge, EVT_PURGEUSER);
- CtdlRegisterNetprocHook(vcard_extract_from_network);
- CtdlRegisterSessionHook(store_harvested_addresses, EVT_TIMER);
- CtdlRegisterFixedOutputHook("text/x-vcard", vcard_fixed_output);
- CtdlRegisterFixedOutputHook("text/vcard", vcard_fixed_output);
-
- /* Create the Global ADdress Book room if necessary */
- create_room(ADDRESS_BOOK_ROOM, 3, "", 0, 1, 0, VIEW_ADDRESSBOOK);
-
- /* Set expiration policy to manual; otherwise objects will be lost! */
- if (!lgetroom(&qr, ADDRESS_BOOK_ROOM)) {
- qr.QRep.expire_mode = EXPIRE_MANUAL;
- qr.QRdefaultview = VIEW_ADDRESSBOOK; /* 2 = address book view */
- lputroom(&qr);
-
- /*
- * Also make sure it has a netconfig file, so the networker runs
- * on this room even if we don't share it with any other nodes.
- * This allows the CANCEL messages (i.e. "Purge this vCard") to be
- * purged.
- */
- assoc_file_name(filename, sizeof filename, &qr, ctdl_netcfg_dir);
- fp = fopen(filename, "a");
- if (fp != NULL) fclose(fp);
- chown(filename, CTDLUID, (-1));
- }
-
- /* for postfix tcpdict */
- CtdlRegisterServiceHook(config.c_pftcpdict_port, /* Postfix */
- NULL,
- check_get_greeting,
- check_get,
- NULL);
-
- /* return our Subversion id for the Log */
- return "$Id$";
-}