Continue phase 2 of modules stuff.
authorDave West <davew@uncensored.citadel.org>
Tue, 31 Jul 2007 21:36:03 +0000 (21:36 +0000)
committerDave West <davew@uncensored.citadel.org>
Tue, 31 Jul 2007 21:36:03 +0000 (21:36 +0000)
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.

69 files changed:
citadel/Makefile.in
citadel/ft_wordbreaker.c [deleted file]
citadel/ft_wordbreaker.h [deleted file]
citadel/modules/autocompletion/serv_autocompletion.c [new file with mode: 0644]
citadel/modules/autocompletion/serv_autocompletion.h [new file with mode: 0644]
citadel/modules/bio/serv_bio.c [new file with mode: 0644]
citadel/modules/calendar/serv_calendar.c [new file with mode: 0644]
citadel/modules/calendar/serv_calendar.h [new file with mode: 0644]
citadel/modules/chat/serv_chat.c [new file with mode: 0644]
citadel/modules/chat/serv_chat.h [new file with mode: 0644]
citadel/modules/crypto/serv_crypto.c [new file with mode: 0644]
citadel/modules/expire/serv_expire.c [new file with mode: 0644]
citadel/modules/fulltext/ft_wordbreaker.c [new file with mode: 0644]
citadel/modules/fulltext/ft_wordbreaker.h [new file with mode: 0644]
citadel/modules/fulltext/serv_fulltext.c [new file with mode: 0644]
citadel/modules/funambol/serv_funambol.c [new file with mode: 0644]
citadel/modules/funambol/serv_funambol.h [new file with mode: 0644]
citadel/modules/inetcfg/serv_inetcfg.c [new file with mode: 0644]
citadel/modules/ldap/serv_ldap.c [new file with mode: 0644]
citadel/modules/listsub/serv_listsub.c [new file with mode: 0644]
citadel/modules/managesieve/serv_managesieve.c [new file with mode: 0644]
citadel/modules/mrtg/serv_mrtg.c [new file with mode: 0644]
citadel/modules/netfilter/serv_netfilter.c [new file with mode: 0644]
citadel/modules/network/serv_network.c [new file with mode: 0644]
citadel/modules/newuser/serv_newuser.c [new file with mode: 0644]
citadel/modules/notes/serv_notes.c [new file with mode: 0644]
citadel/modules/pas2/serv_pas2.c [new file with mode: 0644]
citadel/modules/pop3/serv_pop3.c [new file with mode: 0644]
citadel/modules/pop3/serv_pop3.h [new file with mode: 0644]
citadel/modules/rwho/serv_rwho.c [new file with mode: 0644]
citadel/modules/sieve/serv_sieve.c [new file with mode: 0644]
citadel/modules/smtp/serv_smtp.c [new file with mode: 0644]
citadel/modules/spam/serv_spam.c [new file with mode: 0644]
citadel/modules/upgrade/serv_upgrade.c [new file with mode: 0644]
citadel/modules/upgrade/serv_upgrade.h [new file with mode: 0644]
citadel/modules/vandelay/serv_vandelay.c [new file with mode: 0644]
citadel/modules/vcard/serv_vcard.c [new file with mode: 0644]
citadel/serv_autocompletion.c [deleted file]
citadel/serv_autocompletion.h [deleted file]
citadel/serv_bio.c [deleted file]
citadel/serv_calendar.c [deleted file]
citadel/serv_calendar.h [deleted file]
citadel/serv_chat.c [deleted file]
citadel/serv_chat.h [deleted file]
citadel/serv_crypto.c [deleted file]
citadel/serv_expire.c [deleted file]
citadel/serv_fulltext.c [deleted file]
citadel/serv_funambol.c [deleted file]
citadel/serv_funambol.h [deleted file]
citadel/serv_inetcfg.c [deleted file]
citadel/serv_ldap.c [deleted file]
citadel/serv_listsub.c [deleted file]
citadel/serv_managesieve.c [deleted file]
citadel/serv_mrtg.c [deleted file]
citadel/serv_netfilter.c [deleted file]
citadel/serv_network.c [deleted file]
citadel/serv_newuser.c [deleted file]
citadel/serv_notes.c [deleted file]
citadel/serv_pas2.c [deleted file]
citadel/serv_pop3.c [deleted file]
citadel/serv_pop3.h [deleted file]
citadel/serv_rwho.c [deleted file]
citadel/serv_sieve.c [deleted file]
citadel/serv_smtp.c [deleted file]
citadel/serv_spam.c [deleted file]
citadel/serv_upgrade.c [deleted file]
citadel/serv_upgrade.h [deleted file]
citadel/serv_vandelay.c [deleted file]
citadel/serv_vcard.c [deleted file]

index 5d6837fc147ac13c441583e872dc08029509e400..f2c63032e8d5447954a18457356cf75d0210bfc8 100644 (file)
@@ -28,13 +28,14 @@ EXEEXT=@EXEEXT@
 
 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 \
@@ -44,27 +45,29 @@ SERV_MODULES=serv_chat.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 \
@@ -106,18 +109,42 @@ SOURCES=aidepost.c auth.c base64.c chkpwd.c chkpw.c citadel.c citadel_ipc.c \
        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
 
@@ -156,7 +183,7 @@ SERV_OBJS = server_main.o \
        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)
diff --git a/citadel/ft_wordbreaker.c b/citadel/ft_wordbreaker.c
deleted file mode 100644 (file)
index 6b9fb2d..0000000
+++ /dev/null
@@ -1,272 +0,0 @@
-/*
- * $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;
-}
-
diff --git a/citadel/ft_wordbreaker.h b/citadel/ft_wordbreaker.h
deleted file mode 100644 (file)
index 5f1fb99..0000000
+++ /dev/null
@@ -1,21 +0,0 @@
-/*
- * $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);
-
diff --git a/citadel/modules/autocompletion/serv_autocompletion.c b/citadel/modules/autocompletion/serv_autocompletion.c
new file mode 100644 (file)
index 0000000..ddea4f0
--- /dev/null
@@ -0,0 +1,258 @@
+/*
+ * $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$";
+}
diff --git a/citadel/modules/autocompletion/serv_autocompletion.h b/citadel/modules/autocompletion/serv_autocompletion.h
new file mode 100644 (file)
index 0000000..62f5503
--- /dev/null
@@ -0,0 +1,7 @@
+/* 
+ * $Id$
+ *
+ */
+
+
+char *serv_autocompletion_init(void);
diff --git a/citadel/modules/bio/serv_bio.c b/citadel/modules/bio/serv_bio.c
new file mode 100644 (file)
index 0000000..9f5589b
--- /dev/null
@@ -0,0 +1,145 @@
+/*
+ * $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$";
+}
+
+
diff --git a/citadel/modules/calendar/serv_calendar.c b/citadel/modules/calendar/serv_calendar.c
new file mode 100644 (file)
index 0000000..de35044
--- /dev/null
@@ -0,0 +1,2163 @@
+/* 
+ * $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
+}
diff --git a/citadel/modules/calendar/serv_calendar.h b/citadel/modules/calendar/serv_calendar.h
new file mode 100644 (file)
index 0000000..c58a6e4
--- /dev/null
@@ -0,0 +1,33 @@
+/*
+ * $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;
+};
diff --git a/citadel/modules/chat/serv_chat.c b/citadel/modules/chat/serv_chat.c
new file mode 100644 (file)
index 0000000..a34f7ef
--- /dev/null
@@ -0,0 +1,831 @@
+/*
+ * $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$";
+}
+
diff --git a/citadel/modules/chat/serv_chat.h b/citadel/modules/chat/serv_chat.h
new file mode 100644 (file)
index 0000000..f16f83a
--- /dev/null
@@ -0,0 +1,16 @@
+/* $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];
+};
diff --git a/citadel/modules/crypto/serv_crypto.c b/citadel/modules/crypto/serv_crypto.c
new file mode 100644 (file)
index 0000000..a9dd593
--- /dev/null
@@ -0,0 +1,606 @@
+/* $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 */
diff --git a/citadel/modules/expire/serv_expire.c b/citadel/modules/expire/serv_expire.c
new file mode 100644 (file)
index 0000000..66b4657
--- /dev/null
@@ -0,0 +1,847 @@
+/*
+ * $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$";
+}
diff --git a/citadel/modules/fulltext/ft_wordbreaker.c b/citadel/modules/fulltext/ft_wordbreaker.c
new file mode 100644 (file)
index 0000000..6b9fb2d
--- /dev/null
@@ -0,0 +1,272 @@
+/*
+ * $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;
+}
+
diff --git a/citadel/modules/fulltext/ft_wordbreaker.h b/citadel/modules/fulltext/ft_wordbreaker.h
new file mode 100644 (file)
index 0000000..5f1fb99
--- /dev/null
@@ -0,0 +1,21 @@
+/*
+ * $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);
+
diff --git a/citadel/modules/fulltext/serv_fulltext.c b/citadel/modules/fulltext/serv_fulltext.c
new file mode 100644 (file)
index 0000000..f6e2c67
--- /dev/null
@@ -0,0 +1,479 @@
+/*
+ * $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$";
+}
diff --git a/citadel/modules/funambol/serv_funambol.c b/citadel/modules/funambol/serv_funambol.c
new file mode 100644 (file)
index 0000000..561be09
--- /dev/null
@@ -0,0 +1,233 @@
+/*
+ * 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/\">&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt;\r\n");
+       strcat(SOAPData, "&lt;java version=&quot;1.5.0_10&quot; class=&quot;java.beans.XMLDecoder&quot;&gt; \r\n");
+       strcat(SOAPData, " &lt;array class=&quot;com.funambol.framework.core.Alert&quot; length=&quot;1&quot;&gt;\r\n");
+       strcat(SOAPData, "  &lt;void index=&quot;0&quot;&gt;\r\n");
+       strcat(SOAPData, "   &lt;object class=&quot;com.funambol.framework.core.Alert&quot;&gt;\r\n");
+       strcat(SOAPData, "    &lt;void property=&quot;cmdID&quot;>\r\n");
+       strcat(SOAPData, "     &lt;object class=&quot;com.funambol.framework.core.CmdID&quot;/&gt;\r\n");
+       strcat(SOAPData, "    &lt;/void&gt;");
+       strcat(SOAPData, "    &lt;void property=&quot;data&quot;&gt;\r\n");
+       strcat(SOAPData, "     &lt;int&gt;210&lt;/int&gt;\r\n");
+       strcat(SOAPData, "    &lt;/void&gt;\r\n");
+       strcat(SOAPData, "    &lt;void property=&quot;items&quot;&gt;\r\n");
+        strcat(SOAPData, "     &lt;void method=&quot;add&quot;&gt;\r\n"); 
+       strcat(SOAPData, "      &lt;object class=&quot;com.funambol.framework.core.Item&quot;&gt;\r\n"); 
+       strcat(SOAPData, "       &lt;void property=&quot;meta&quot;&gt;\r\n"); 
+       strcat(SOAPData, "        &lt;object class=&quot;com.funambol.framework.core.Meta&quot;&gt;\r\n"); 
+       strcat(SOAPData, "         &lt;void property=&quot;metInf&quot;&gt;\r\n");
+       strcat(SOAPData, "          &lt;void property=&quot;type&quot;&gt;\r\n");
+       strcat(SOAPData, "           &lt;string&gt;application/vnd.omads-email+xml&lt;/string&gt;\r\n");
+       strcat(SOAPData, "          &lt;/void&gt;\r\n"); 
+       strcat(SOAPData, "         &lt;/void&gt;\r\n"); 
+       strcat(SOAPData, "        &lt;/object&gt;\r\n"); 
+       strcat(SOAPData, "       &lt;/void&gt;\r\n"); 
+       strcat(SOAPData, "       &lt;void property=&quot;target&quot;&gt;\r\n"); 
+       strcat(SOAPData, "        &lt;object class=&quot;com.funambol.framework.core.Target&quot;&gt;\r\n");
+       strcat(SOAPData, "         &lt;void property=&quot;locURI&quot;&gt;\r\n");
+       strcat(SOAPData, "          &lt;string&gt;");
+       strcat(SOAPData, config.c_funambol_source);
+       strcat(SOAPData, "&lt;/string&gt;\r\n");
+       strcat(SOAPData, "         &lt;/void&gt;\r\n");
+       strcat(SOAPData, "        &lt;/object&gt;\r\n");
+       strcat(SOAPData, "       &lt;/void&gt;\r\n");
+       strcat(SOAPData, "      &lt;/object&gt;\r\n");
+       strcat(SOAPData, "     &lt;/void&gt;\r\n");
+       strcat(SOAPData, "    &lt;/void&gt;\r\n");
+       strcat(SOAPData, "   &lt;/object&gt;\r\n");
+       strcat(SOAPData, "  &lt;/void&gt;\r\n");
+       strcat(SOAPData, " &lt;/array&gt;\r\n");
+       strcat(SOAPData, "&lt;/java&gt;");
+       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$";
+}
diff --git a/citadel/modules/funambol/serv_funambol.h b/citadel/modules/funambol/serv_funambol.h
new file mode 100644 (file)
index 0000000..209fb9b
--- /dev/null
@@ -0,0 +1 @@
+void notify_funambol(long msgnum, void *userdata);
diff --git a/citadel/modules/inetcfg/serv_inetcfg.c b/citadel/modules/inetcfg/serv_inetcfg.c
new file mode 100644 (file)
index 0000000..eaab32d
--- /dev/null
@@ -0,0 +1,187 @@
+/*
+ * $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$";
+}
+
diff --git a/citadel/modules/ldap/serv_ldap.c b/citadel/modules/ldap/serv_ldap.c
new file mode 100644 (file)
index 0000000..f3e5a77
--- /dev/null
@@ -0,0 +1,606 @@
+/*
+ * $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$";
+}
diff --git a/citadel/modules/listsub/serv_listsub.c b/citadel/modules/listsub/serv_listsub.c
new file mode 100644 (file)
index 0000000..93e9355
--- /dev/null
@@ -0,0 +1,573 @@
+/*
+ * $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"
+               "&lt;%s&gt; 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"
+               "&lt;%s&gt; 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$";
+}
diff --git a/citadel/modules/managesieve/serv_managesieve.c b/citadel/modules/managesieve/serv_managesieve.c
new file mode 100644 (file)
index 0000000..c43d8aa
--- /dev/null
@@ -0,0 +1,600 @@
+/**
+ * $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$";
+}
+
+
diff --git a/citadel/modules/mrtg/serv_mrtg.c b/citadel/modules/mrtg/serv_mrtg.c
new file mode 100644 (file)
index 0000000..12e09f0
--- /dev/null
@@ -0,0 +1,137 @@
+/*
+ * $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$";
+}
diff --git a/citadel/modules/netfilter/serv_netfilter.c b/citadel/modules/netfilter/serv_netfilter.c
new file mode 100644 (file)
index 0000000..31fced5
--- /dev/null
@@ -0,0 +1,114 @@
+/*
+ * $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$";
+}
diff --git a/citadel/modules/network/serv_network.c b/citadel/modules/network/serv_network.c
new file mode 100644 (file)
index 0000000..1c8260b
--- /dev/null
@@ -0,0 +1,2082 @@
+/*
+ * $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$";
+}
diff --git a/citadel/modules/newuser/serv_newuser.c b/citadel/modules/newuser/serv_newuser.c
new file mode 100644 (file)
index 0000000..ac47feb
--- /dev/null
@@ -0,0 +1,104 @@
+/*
+ * $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$";
+}
diff --git a/citadel/modules/notes/serv_notes.c b/citadel/modules/notes/serv_notes.c
new file mode 100644 (file)
index 0000000..47e968b
--- /dev/null
@@ -0,0 +1,112 @@
+/*
+ * $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$";
+}
diff --git a/citadel/modules/pas2/serv_pas2.c b/citadel/modules/pas2/serv_pas2.c
new file mode 100644 (file)
index 0000000..fd7fbf2
--- /dev/null
@@ -0,0 +1,95 @@
+/*
+ * 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$";
+}
diff --git a/citadel/modules/pop3/serv_pop3.c b/citadel/modules/pop3/serv_pop3.c
new file mode 100644 (file)
index 0000000..c49c5ca
--- /dev/null
@@ -0,0 +1,732 @@
+/*
+ * $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$";
+}
diff --git a/citadel/modules/pop3/serv_pop3.h b/citadel/modules/pop3/serv_pop3.h
new file mode 100644 (file)
index 0000000..8c73f68
--- /dev/null
@@ -0,0 +1,32 @@
+/*
+ * $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);
+
diff --git a/citadel/modules/rwho/serv_rwho.c b/citadel/modules/rwho/serv_rwho.c
new file mode 100644 (file)
index 0000000..553049c
--- /dev/null
@@ -0,0 +1,266 @@
+/*
+ * $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$";
+}
diff --git a/citadel/modules/sieve/serv_sieve.c b/citadel/modules/sieve/serv_sieve.c
new file mode 100644 (file)
index 0000000..508f8d2
--- /dev/null
@@ -0,0 +1,1275 @@
+/*
+ * $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$";
+}
+
diff --git a/citadel/modules/smtp/serv_smtp.c b/citadel/modules/smtp/serv_smtp.c
new file mode 100644 (file)
index 0000000..c2329ba
--- /dev/null
@@ -0,0 +1,1857 @@
+/*
+ * $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$";
+}
diff --git a/citadel/modules/spam/serv_spam.c b/citadel/modules/spam/serv_spam.c
new file mode 100644 (file)
index 0000000..64ad1f5
--- /dev/null
@@ -0,0 +1,155 @@
+/*
+ * $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$";
+}
diff --git a/citadel/modules/upgrade/serv_upgrade.c b/citadel/modules/upgrade/serv_upgrade.c
new file mode 100644 (file)
index 0000000..2956e79
--- /dev/null
@@ -0,0 +1,235 @@
+/*
+ * $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$";
+}
diff --git a/citadel/modules/upgrade/serv_upgrade.h b/citadel/modules/upgrade/serv_upgrade.h
new file mode 100644 (file)
index 0000000..8d8bc32
--- /dev/null
@@ -0,0 +1,5 @@
+/*
+ * $Id$
+ *
+ */
+
diff --git a/citadel/modules/vandelay/serv_vandelay.c b/citadel/modules/vandelay/serv_vandelay.c
new file mode 100644 (file)
index 0000000..b1c3630
--- /dev/null
@@ -0,0 +1,739 @@
+/*
+ * $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$";
+}
diff --git a/citadel/modules/vcard/serv_vcard.c b/citadel/modules/vcard/serv_vcard.c
new file mode 100644 (file)
index 0000000..ba292df
--- /dev/null
@@ -0,0 +1,1389 @@
+/*
+ * $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$";
+}
diff --git a/citadel/serv_autocompletion.c b/citadel/serv_autocompletion.c
deleted file mode 100644 (file)
index ddea4f0..0000000
+++ /dev/null
@@ -1,258 +0,0 @@
-/*
- * $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$";
-}
diff --git a/citadel/serv_autocompletion.h b/citadel/serv_autocompletion.h
deleted file mode 100644 (file)
index 62f5503..0000000
+++ /dev/null
@@ -1,7 +0,0 @@
-/* 
- * $Id$
- *
- */
-
-
-char *serv_autocompletion_init(void);
diff --git a/citadel/serv_bio.c b/citadel/serv_bio.c
deleted file mode 100644 (file)
index 9f5589b..0000000
+++ /dev/null
@@ -1,145 +0,0 @@
-/*
- * $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$";
-}
-
-
diff --git a/citadel/serv_calendar.c b/citadel/serv_calendar.c
deleted file mode 100644 (file)
index de35044..0000000
+++ /dev/null
@@ -1,2163 +0,0 @@
-/* 
- * $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
-}
diff --git a/citadel/serv_calendar.h b/citadel/serv_calendar.h
deleted file mode 100644 (file)
index c58a6e4..0000000
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- * $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;
-};
diff --git a/citadel/serv_chat.c b/citadel/serv_chat.c
deleted file mode 100644 (file)
index a34f7ef..0000000
+++ /dev/null
@@ -1,831 +0,0 @@
-/*
- * $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$";
-}
-
diff --git a/citadel/serv_chat.h b/citadel/serv_chat.h
deleted file mode 100644 (file)
index f16f83a..0000000
+++ /dev/null
@@ -1,16 +0,0 @@
-/* $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];
-};
diff --git a/citadel/serv_crypto.c b/citadel/serv_crypto.c
deleted file mode 100644 (file)
index a9dd593..0000000
+++ /dev/null
@@ -1,606 +0,0 @@
-/* $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 */
diff --git a/citadel/serv_expire.c b/citadel/serv_expire.c
deleted file mode 100644 (file)
index 66b4657..0000000
+++ /dev/null
@@ -1,847 +0,0 @@
-/*
- * $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$";
-}
diff --git a/citadel/serv_fulltext.c b/citadel/serv_fulltext.c
deleted file mode 100644 (file)
index f6e2c67..0000000
+++ /dev/null
@@ -1,479 +0,0 @@
-/*
- * $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$";
-}
diff --git a/citadel/serv_funambol.c b/citadel/serv_funambol.c
deleted file mode 100644 (file)
index 561be09..0000000
+++ /dev/null
@@ -1,233 +0,0 @@
-/*
- * 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/\">&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt;\r\n");
-       strcat(SOAPData, "&lt;java version=&quot;1.5.0_10&quot; class=&quot;java.beans.XMLDecoder&quot;&gt; \r\n");
-       strcat(SOAPData, " &lt;array class=&quot;com.funambol.framework.core.Alert&quot; length=&quot;1&quot;&gt;\r\n");
-       strcat(SOAPData, "  &lt;void index=&quot;0&quot;&gt;\r\n");
-       strcat(SOAPData, "   &lt;object class=&quot;com.funambol.framework.core.Alert&quot;&gt;\r\n");
-       strcat(SOAPData, "    &lt;void property=&quot;cmdID&quot;>\r\n");
-       strcat(SOAPData, "     &lt;object class=&quot;com.funambol.framework.core.CmdID&quot;/&gt;\r\n");
-       strcat(SOAPData, "    &lt;/void&gt;");
-       strcat(SOAPData, "    &lt;void property=&quot;data&quot;&gt;\r\n");
-       strcat(SOAPData, "     &lt;int&gt;210&lt;/int&gt;\r\n");
-       strcat(SOAPData, "    &lt;/void&gt;\r\n");
-       strcat(SOAPData, "    &lt;void property=&quot;items&quot;&gt;\r\n");
-        strcat(SOAPData, "     &lt;void method=&quot;add&quot;&gt;\r\n"); 
-       strcat(SOAPData, "      &lt;object class=&quot;com.funambol.framework.core.Item&quot;&gt;\r\n"); 
-       strcat(SOAPData, "       &lt;void property=&quot;meta&quot;&gt;\r\n"); 
-       strcat(SOAPData, "        &lt;object class=&quot;com.funambol.framework.core.Meta&quot;&gt;\r\n"); 
-       strcat(SOAPData, "         &lt;void property=&quot;metInf&quot;&gt;\r\n");
-       strcat(SOAPData, "          &lt;void property=&quot;type&quot;&gt;\r\n");
-       strcat(SOAPData, "           &lt;string&gt;application/vnd.omads-email+xml&lt;/string&gt;\r\n");
-       strcat(SOAPData, "          &lt;/void&gt;\r\n"); 
-       strcat(SOAPData, "         &lt;/void&gt;\r\n"); 
-       strcat(SOAPData, "        &lt;/object&gt;\r\n"); 
-       strcat(SOAPData, "       &lt;/void&gt;\r\n"); 
-       strcat(SOAPData, "       &lt;void property=&quot;target&quot;&gt;\r\n"); 
-       strcat(SOAPData, "        &lt;object class=&quot;com.funambol.framework.core.Target&quot;&gt;\r\n");
-       strcat(SOAPData, "         &lt;void property=&quot;locURI&quot;&gt;\r\n");
-       strcat(SOAPData, "          &lt;string&gt;");
-       strcat(SOAPData, config.c_funambol_source);
-       strcat(SOAPData, "&lt;/string&gt;\r\n");
-       strcat(SOAPData, "         &lt;/void&gt;\r\n");
-       strcat(SOAPData, "        &lt;/object&gt;\r\n");
-       strcat(SOAPData, "       &lt;/void&gt;\r\n");
-       strcat(SOAPData, "      &lt;/object&gt;\r\n");
-       strcat(SOAPData, "     &lt;/void&gt;\r\n");
-       strcat(SOAPData, "    &lt;/void&gt;\r\n");
-       strcat(SOAPData, "   &lt;/object&gt;\r\n");
-       strcat(SOAPData, "  &lt;/void&gt;\r\n");
-       strcat(SOAPData, " &lt;/array&gt;\r\n");
-       strcat(SOAPData, "&lt;/java&gt;");
-       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$";
-}
diff --git a/citadel/serv_funambol.h b/citadel/serv_funambol.h
deleted file mode 100644 (file)
index 209fb9b..0000000
+++ /dev/null
@@ -1 +0,0 @@
-void notify_funambol(long msgnum, void *userdata);
diff --git a/citadel/serv_inetcfg.c b/citadel/serv_inetcfg.c
deleted file mode 100644 (file)
index eaab32d..0000000
+++ /dev/null
@@ -1,187 +0,0 @@
-/*
- * $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$";
-}
-
diff --git a/citadel/serv_ldap.c b/citadel/serv_ldap.c
deleted file mode 100644 (file)
index f3e5a77..0000000
+++ /dev/null
@@ -1,606 +0,0 @@
-/*
- * $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$";
-}
diff --git a/citadel/serv_listsub.c b/citadel/serv_listsub.c
deleted file mode 100644 (file)
index 93e9355..0000000
+++ /dev/null
@@ -1,573 +0,0 @@
-/*
- * $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"
-               "&lt;%s&gt; 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"
-               "&lt;%s&gt; 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$";
-}
diff --git a/citadel/serv_managesieve.c b/citadel/serv_managesieve.c
deleted file mode 100644 (file)
index c43d8aa..0000000
+++ /dev/null
@@ -1,600 +0,0 @@
-/**
- * $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$";
-}
-
-
diff --git a/citadel/serv_mrtg.c b/citadel/serv_mrtg.c
deleted file mode 100644 (file)
index 12e09f0..0000000
+++ /dev/null
@@ -1,137 +0,0 @@
-/*
- * $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$";
-}
diff --git a/citadel/serv_netfilter.c b/citadel/serv_netfilter.c
deleted file mode 100644 (file)
index 31fced5..0000000
+++ /dev/null
@@ -1,114 +0,0 @@
-/*
- * $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$";
-}
diff --git a/citadel/serv_network.c b/citadel/serv_network.c
deleted file mode 100644 (file)
index 1c8260b..0000000
+++ /dev/null
@@ -1,2082 +0,0 @@
-/*
- * $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$";
-}
diff --git a/citadel/serv_newuser.c b/citadel/serv_newuser.c
deleted file mode 100644 (file)
index ac47feb..0000000
+++ /dev/null
@@ -1,104 +0,0 @@
-/*
- * $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$";
-}
diff --git a/citadel/serv_notes.c b/citadel/serv_notes.c
deleted file mode 100644 (file)
index 47e968b..0000000
+++ /dev/null
@@ -1,112 +0,0 @@
-/*
- * $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$";
-}
diff --git a/citadel/serv_pas2.c b/citadel/serv_pas2.c
deleted file mode 100644 (file)
index fd7fbf2..0000000
+++ /dev/null
@@ -1,95 +0,0 @@
-/*
- * 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$";
-}
diff --git a/citadel/serv_pop3.c b/citadel/serv_pop3.c
deleted file mode 100644 (file)
index c49c5ca..0000000
+++ /dev/null
@@ -1,732 +0,0 @@
-/*
- * $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$";
-}
diff --git a/citadel/serv_pop3.h b/citadel/serv_pop3.h
deleted file mode 100644 (file)
index 8c73f68..0000000
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- * $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);
-
diff --git a/citadel/serv_rwho.c b/citadel/serv_rwho.c
deleted file mode 100644 (file)
index 553049c..0000000
+++ /dev/null
@@ -1,266 +0,0 @@
-/*
- * $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$";
-}
diff --git a/citadel/serv_sieve.c b/citadel/serv_sieve.c
deleted file mode 100644 (file)
index 508f8d2..0000000
+++ /dev/null
@@ -1,1275 +0,0 @@
-/*
- * $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$";
-}
-
diff --git a/citadel/serv_smtp.c b/citadel/serv_smtp.c
deleted file mode 100644 (file)
index c2329ba..0000000
+++ /dev/null
@@ -1,1857 +0,0 @@
-/*
- * $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$";
-}
diff --git a/citadel/serv_spam.c b/citadel/serv_spam.c
deleted file mode 100644 (file)
index 64ad1f5..0000000
+++ /dev/null
@@ -1,155 +0,0 @@
-/*
- * $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$";
-}
diff --git a/citadel/serv_upgrade.c b/citadel/serv_upgrade.c
deleted file mode 100644 (file)
index 2956e79..0000000
+++ /dev/null
@@ -1,235 +0,0 @@
-/*
- * $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$";
-}
diff --git a/citadel/serv_upgrade.h b/citadel/serv_upgrade.h
deleted file mode 100644 (file)
index 8d8bc32..0000000
+++ /dev/null
@@ -1,5 +0,0 @@
-/*
- * $Id$
- *
- */
-
diff --git a/citadel/serv_vandelay.c b/citadel/serv_vandelay.c
deleted file mode 100644 (file)
index b1c3630..0000000
+++ /dev/null
@@ -1,739 +0,0 @@
-/*
- * $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$";
-}
diff --git a/citadel/serv_vcard.c b/citadel/serv_vcard.c
deleted file mode 100644 (file)
index ba292df..0000000
+++ /dev/null
@@ -1,1389 +0,0 @@
-/*
- * $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$";
-}