From: Dave West Date: Tue, 31 Jul 2007 21:36:03 +0000 (+0000) Subject: Continue phase 2 of modules stuff. X-Git-Tag: v7.86~3191 X-Git-Url: https://code.citadel.org/?p=citadel.git;a=commitdiff_plain;h=84aa84fdd0a02f703c5e836f258e33f950c66355 Continue phase 2 of modules stuff. 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. --- diff --git a/citadel/Makefile.in b/citadel/Makefile.in index 5d6837fc1..f2c63032e 100644 --- a/citadel/Makefile.in +++ b/citadel/Makefile.in @@ -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 index 6b9fb2d24..000000000 --- a/citadel/ft_wordbreaker.c +++ /dev/null @@ -1,272 +0,0 @@ -/* - * $Id$ - * - * Default wordbreaker module for full text indexing. - * - */ - - -#include "sysdep.h" -#include -#include -#include -#include -#include -#include -#include -#include - -#if TIME_WITH_SYS_TIME -# include -# include -#else -# if HAVE_SYS_TIME_H -# include -# else -# include -# endif -#endif - -#include -#include -#include -#include -#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 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 index 5f1fb99fe..000000000 --- a/citadel/ft_wordbreaker.h +++ /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 index 000000000..ddea4f0dc --- /dev/null +++ b/citadel/modules/autocompletion/serv_autocompletion.c @@ -0,0 +1,258 @@ +/* + * $Id$ + * + * Autocompletion of email recipients, etc. + */ + +#include "sysdep.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if TIME_WITH_SYS_TIME +# include +# include +#else +# if HAVE_SYS_TIME_H +# include +# else +# include +# endif +#endif + +#include +#include +#include +#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; icm_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 + */ + 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 + */ + 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 0) for (i=0; iroom.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 index 000000000..62f550317 --- /dev/null +++ b/citadel/modules/autocompletion/serv_autocompletion.h @@ -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 index 000000000..9f5589b3b --- /dev/null +++ b/citadel/modules/bio/serv_bio.c @@ -0,0 +1,145 @@ +/* + * $Id$ + * + * This module implementsserver commands related to the display and + * manipulation of user "bio" files. + * + */ + +#include "sysdep.h" +#include +#include +#include +#include +#include +#include +#include +#include + +#if TIME_WITH_SYS_TIME +# include +# include +#else +# if HAVE_SYS_TIME_H +# include +# else +# include +# endif +#endif + +#include +#include +#include +#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 index 000000000..de3504441 --- /dev/null +++ b/citadel/modules/calendar/serv_calendar.c @@ -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 +#include +#include +#include +#include +#include +#ifdef HAVE_STRINGS_H +#include +#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 +#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; iroom, 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 index 000000000..c58a6e40d --- /dev/null +++ b/citadel/modules/calendar/serv_calendar.h @@ -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 index 000000000..a34f7efc3 --- /dev/null +++ b/citadel/modules/chat/serv_chat.c @@ -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 +#include +#include +#include +#include +#include +#include +#include + +#if TIME_WITH_SYS_TIME +# include +# include +#else +# if HAVE_SYS_TIME_H +# include +# else +# include +# endif +#endif + +#include +#include +#include +#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("", 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("", 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("", 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("", 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 index 000000000..f16f83a64 --- /dev/null +++ b/citadel/modules/chat/serv_chat.h @@ -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 index 000000000..a9dd5934b --- /dev/null +++ b/citadel/modules/crypto/serv_crypto.c @@ -0,0 +1,606 @@ +/* $Id$ */ + +#include +#include +#include +#include +#include "sysdep.h" + +#ifdef HAVE_OPENSSL +#include +#include +#include +#endif + +#if TIME_WITH_SYS_TIME +# include +# include +#else +# if HAVE_SYS_TIME_H +# include +# else +# include +# endif +#endif + +#ifdef HAVE_PTHREAD_H +#include +#endif + +#ifdef HAVE_SYS_SELECT_H +#include +#endif + +#include +#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 index 000000000..66b46570d --- /dev/null +++ b/citadel/modules/expire/serv_expire.c @@ -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 +#include +#include +#include +#include +#include +#include +#include + +#if TIME_WITH_SYS_TIME +# include +# include +#else +# if HAVE_SYS_TIME_H +# include +# else +# include +# endif +#endif + +#include +#include +#include +#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; acm_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 index 000000000..6b9fb2d24 --- /dev/null +++ b/citadel/modules/fulltext/ft_wordbreaker.c @@ -0,0 +1,272 @@ +/* + * $Id$ + * + * Default wordbreaker module for full text indexing. + * + */ + + +#include "sysdep.h" +#include +#include +#include +#include +#include +#include +#include +#include + +#if TIME_WITH_SYS_TIME +# include +# include +#else +# if HAVE_SYS_TIME_H +# include +# else +# include +# endif +#endif + +#include +#include +#include +#include +#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 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 index 000000000..5f1fb99fe --- /dev/null +++ b/citadel/modules/fulltext/ft_wordbreaker.h @@ -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 index 000000000..f6e2c672a --- /dev/null +++ b/citadel/modules/fulltext/serv_fulltext.c @@ -0,0 +1,479 @@ +/* + * $Id$ + * + * This module handles fulltext indexing of the message base. + * + */ + + +#include "sysdep.h" +#include +#include +#include +#include +#include +#include +#include +#include + +#if TIME_WITH_SYS_TIME +# include +# include +#else +# if HAVE_SYS_TIME_H +# include +# else +# include +# endif +#endif + +#include +#include +#include +#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= 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 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_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; ilen / 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 +#include +#include +#include +#include +#include +#include +#include + +#if TIME_WITH_SYS_TIME +# include +# include +#else +# if HAVE_SYS_TIME_H +# include +# else +# include +# endif +#endif + +#include +#include +#include +#include +#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 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, ""); + strcat(SOAPData, ""); + strcat(SOAPData, ""); + strcat(SOAPData, msg->cm_fields['W']); + strcat(SOAPData, ""); + strcat(SOAPData, "<?xml version="1.0" encoding="UTF-8"?>\r\n"); + strcat(SOAPData, "<java version="1.5.0_10" class="java.beans.XMLDecoder"> \r\n"); + strcat(SOAPData, " <array class="com.funambol.framework.core.Alert" length="1">\r\n"); + strcat(SOAPData, " <void index="0">\r\n"); + strcat(SOAPData, " <object class="com.funambol.framework.core.Alert">\r\n"); + strcat(SOAPData, " <void property="cmdID">\r\n"); + strcat(SOAPData, " <object class="com.funambol.framework.core.CmdID"/>\r\n"); + strcat(SOAPData, " </void>"); + strcat(SOAPData, " <void property="data">\r\n"); + strcat(SOAPData, " <int>210</int>\r\n"); + strcat(SOAPData, " </void>\r\n"); + strcat(SOAPData, " <void property="items">\r\n"); + strcat(SOAPData, " <void method="add">\r\n"); + strcat(SOAPData, " <object class="com.funambol.framework.core.Item">\r\n"); + strcat(SOAPData, " <void property="meta">\r\n"); + strcat(SOAPData, " <object class="com.funambol.framework.core.Meta">\r\n"); + strcat(SOAPData, " <void property="metInf">\r\n"); + strcat(SOAPData, " <void property="type">\r\n"); + strcat(SOAPData, " <string>application/vnd.omads-email+xml</string>\r\n"); + strcat(SOAPData, " </void>\r\n"); + strcat(SOAPData, " </void>\r\n"); + strcat(SOAPData, " </object>\r\n"); + strcat(SOAPData, " </void>\r\n"); + strcat(SOAPData, " <void property="target">\r\n"); + strcat(SOAPData, " <object class="com.funambol.framework.core.Target">\r\n"); + strcat(SOAPData, " <void property="locURI">\r\n"); + strcat(SOAPData, " <string>"); + strcat(SOAPData, config.c_funambol_source); + strcat(SOAPData, "</string>\r\n"); + strcat(SOAPData, " </void>\r\n"); + strcat(SOAPData, " </object>\r\n"); + strcat(SOAPData, " </void>\r\n"); + strcat(SOAPData, " </object>\r\n"); + strcat(SOAPData, " </void>\r\n"); + strcat(SOAPData, " </void>\r\n"); + strcat(SOAPData, " </object>\r\n"); + strcat(SOAPData, " </void>\r\n"); + strcat(SOAPData, " </array>\r\n"); + strcat(SOAPData, "</java>"); + strcat(SOAPData,"1"); + + /* 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 index 000000000..209fb9b39 --- /dev/null +++ b/citadel/modules/funambol/serv_funambol.h @@ -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 index 000000000..eaab32d37 --- /dev/null +++ b/citadel/modules/inetcfg/serv_inetcfg.c @@ -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 +#include +#include +#include +#include +#include +#include +#include + +#if TIME_WITH_SYS_TIME +# include +# include +#else +# if HAVE_SYS_TIME_H +# include +# else +# include +# endif +#endif + +#include +#include +#include +#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; istring = 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 index 000000000..f3e5a774a --- /dev/null +++ b/citadel/modules/ldap/serv_ldap.c @@ -0,0 +1,606 @@ +/* + * $Id$ + * + * A module which implements the LDAP connector for Citadel. + * + */ + +#include "sysdep.h" +#include +#include +#include +#include +#include +#include +#include +#include + +#if TIME_WITH_SYS_TIME +# include +# include +#else +# if HAVE_SYS_TIME_H +# include +# else +# include +# endif +#endif + +#include +#include +#include +#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 *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; icm_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; imod_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 index 000000000..93e93553b --- /dev/null +++ b/citadel/modules/listsub/serv_listsub.c @@ -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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#if TIME_WITH_SYS_TIME +# include +# include +#else +# if HAVE_SYS_TIME_H +# include +# else +# include +# endif +#endif + +#include +#include +#include +#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" + "\n" + "Someone (probably you) has submitted a request to subscribe\n" + "<%s> to the %s mailing list.

\n" + "Please click here to confirm this request:
\n" + "" + "%s?room=%s&token=%s&cmd=confirm

\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" + "\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" + "\n" + "Someone (probably you) has submitted a request to unsubscribe\n" + "<%s> from the %s mailing list.

\n" + "Please click here to confirm this request:
\n" + "" + "%s?room=%s&token=%s&cmd=confirm

\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" + "\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 index 000000000..c43d8aa19 --- /dev/null +++ b/citadel/modules/managesieve/serv_managesieve.c @@ -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 +#include +#include +#include +#include +#include +#include +#include +#include + +#if TIME_WITH_SYS_TIME +# include +# include +#else +# if HAVE_SYS_TIME_H +# include +# else +# include +# endif +#endif + +#include +#include +#include +#include +#include +#include +#include +#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 index 000000000..12e09f0f0 --- /dev/null +++ b/citadel/modules/mrtg/serv_mrtg.c @@ -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 +#include +#include +#include +#include +#include +#include +#include + +#if TIME_WITH_SYS_TIME +# include +# include +#else +# if HAVE_SYS_TIME_H +# include +# else +# include +# endif +#endif + +#include +#include +#include +#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 index 000000000..31fced57b --- /dev/null +++ b/citadel/modules/netfilter/serv_netfilter.c @@ -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 +#include +#include +#include +#include +#include +#include +#include + +#if TIME_WITH_SYS_TIME +# include +# include +#else +# if HAVE_SYS_TIME_H +# include +# else +# include +# endif +#endif + +#include +#include +#include +#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 index 000000000..1c8260b9b --- /dev/null +++ b/citadel/modules/network/serv_network.c @@ -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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#if TIME_WITH_SYS_TIME +# include +# include +#else +# if HAVE_SYS_TIME_H +# include +# else +# include +# endif +#endif + +#include +#include +#include +#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; ifl_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; inodename, 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; inext) { + 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; icm_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; icm_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; icm_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; iroom.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 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 index 000000000..ac47feb10 --- /dev/null +++ b/citadel/modules/newuser/serv_newuser.c @@ -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 +#include +#include +#include +#include +#include +#include +#include + +#if TIME_WITH_SYS_TIME +# include +# include +#else +# if HAVE_SYS_TIME_H +# include +# else +# include +# endif +#endif + +#include +#include +#include +#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 index 000000000..47e968b03 --- /dev/null +++ b/citadel/modules/notes/serv_notes.c @@ -0,0 +1,112 @@ +/* + * $Id$ + * + * Handles functions related to yellow sticky notes. + * + */ + +#include "sysdep.h" +#include +#include +#include +#include +#include +#include +#include +#include + +#if TIME_WITH_SYS_TIME +# include +# include +#else +# if HAVE_SYS_TIME_H +# include +# else +# include +# endif +#endif + +#include +#include +#include +#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 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 index 000000000..fd7fbf246 --- /dev/null +++ b/citadel/modules/pas2/serv_pas2.c @@ -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 +#include +#include + +#if TIME_WITH_SYS_TIME +# include +# include +#else +# if HAVE_SYS_TIME_H +# include +# else +# include +# endif +#endif + +#include +#include +#include +#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 index 000000000..c49c5cad9 --- /dev/null +++ b/citadel/modules/pop3/serv_pop3.c @@ -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 +#include +#include +#include +#include +#include +#include +#include + +#if TIME_WITH_SYS_TIME +# include +# include +#else +# if HAVE_SYS_TIME_H +# include +# else +# include +# endif +#endif + +#include +#include +#include +#include +#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; inum_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; inum_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; inum_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; inum_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; inum_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; inum_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 index 000000000..8c73f689d --- /dev/null +++ b/citadel/modules/pop3/serv_pop3.h @@ -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 index 000000000..553049c9b --- /dev/null +++ b/citadel/modules/rwho/serv_rwho.c @@ -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 +#include +#include +#include +#include +#include +#include +#include + +#if TIME_WITH_SYS_TIME +# include +# include +#else +# if HAVE_SYS_TIME_H +# include +# else +# include +# endif +#endif + +#include +#include +#include +#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 index 000000000..508f8d218 --- /dev/null +++ b/citadel/modules/sieve/serv_sieve.c @@ -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 +#include +#include +#include +#include +#include +#include +#include + +#if TIME_WITH_SYS_TIME +# include +# include +#else +# if HAVE_SYS_TIME_H +# include +# else +# include +# endif +#endif + +#include +#include +#include +#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; iroom.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; icm_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; iroom.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 index 000000000..c2329ba91 --- /dev/null +++ b/citadel/modules/smtp/serv_smtp.c @@ -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 +#include +#include +#include +#include +#include +#include +#include +#include + +#if TIME_WITH_SYS_TIME +# include +# include +#else +# if HAVE_SYS_TIME_H +# include +# else +# include +# endif +#endif + +#include +#include +#include +#include +#include +#include +#include +#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; inumber_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; i0)&&(rp>lp)) { + strcpy(&mailfrom[lp-1], &mailfrom[rp+1]); + } + + /* Prefer brokketized names */ + lp = (-1); + rp = (-1); + for (i=0; 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 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 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 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; icm_fields['M']); + CtdlFreeMessage(msg); + + /* Strip out the headers amd any other non-instruction line */ + lines = num_tokens(instr, '\n'); + for (i=0; i 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\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; inown 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 index 000000000..64ad1f5cb --- /dev/null +++ b/citadel/modules/spam/serv_spam.c @@ -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 +#include +#include +#include +#include +#include +#include +#include + +#if TIME_WITH_SYS_TIME +# include +# include +#else +# if HAVE_SYS_TIME_H +# include +# else +# include +# endif +#endif + +#include +#include +#include +#include +#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\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 index 000000000..2956e79a0 --- /dev/null +++ b/citadel/modules/upgrade/serv_upgrade.c @@ -0,0 +1,235 @@ +/* + * $Id$ + * + * Transparently handle the upgrading of server data formats. + * + */ + +#include "sysdep.h" +#include +#include +#include +#include +#include +#include +#include +#include + +#if TIME_WITH_SYS_TIME +# include +# include +#else +# if HAVE_SYS_TIME_H +# include +# else +# include +# endif +#endif + +#include +#include +#include +#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 index 000000000..8d8bc3202 --- /dev/null +++ b/citadel/modules/upgrade/serv_upgrade.h @@ -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 index 000000000..b1c3630c0 --- /dev/null +++ b/citadel/modules/vandelay/serv_vandelay.c @@ -0,0 +1,739 @@ +/* + * $Id$ + * + * This is the "Art Vandelay" module. It is an importer/exporter. + * + */ + +#include "sysdep.h" +#include +#include +#include +#include +#include +#include +#include +#include + +#if TIME_WITH_SYS_TIME +# include +# include +#else +# if HAVE_SYS_TIME_H +# include +# else +# include +# endif +#endif + +#include +#include +#include +#include +#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%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 ((versionREV_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 index 000000000..ba292dfeb --- /dev/null +++ b/citadel/modules/vcard/serv_vcard.c @@ -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 +#include +#include +#include +#include +#include +#include +#include +#include + +#if TIME_WITH_SYS_TIME +# include +# include +#else +# if HAVE_SYS_TIME_H +# include +# else +# include +# endif +#endif + +#include +#include +#include +#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; icm_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='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; ifullname); + 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; ics_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\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; icm_fields['M']); + CtdlFreeMessage(msg); + + i = 0; + while (value = vcard_get_prop(v, "email", 1, i++, 0), value != NULL) { + + for (j=0; jroomname, 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; icollected_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 index ddea4f0dc..000000000 --- a/citadel/serv_autocompletion.c +++ /dev/null @@ -1,258 +0,0 @@ -/* - * $Id$ - * - * Autocompletion of email recipients, etc. - */ - -#include "sysdep.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#if TIME_WITH_SYS_TIME -# include -# include -#else -# if HAVE_SYS_TIME_H -# include -# else -# include -# endif -#endif - -#include -#include -#include -#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; icm_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 - */ - 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 - */ - 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 0) for (i=0; iroom.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 index 62f550317..000000000 --- a/citadel/serv_autocompletion.h +++ /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 index 9f5589b3b..000000000 --- a/citadel/serv_bio.c +++ /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 -#include -#include -#include -#include -#include -#include -#include - -#if TIME_WITH_SYS_TIME -# include -# include -#else -# if HAVE_SYS_TIME_H -# include -# else -# include -# endif -#endif - -#include -#include -#include -#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 index de3504441..000000000 --- a/citadel/serv_calendar.c +++ /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 -#include -#include -#include -#include -#include -#ifdef HAVE_STRINGS_H -#include -#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 -#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; iroom, 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 index c58a6e40d..000000000 --- a/citadel/serv_calendar.h +++ /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 index a34f7efc3..000000000 --- a/citadel/serv_chat.c +++ /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 -#include -#include -#include -#include -#include -#include -#include - -#if TIME_WITH_SYS_TIME -# include -# include -#else -# if HAVE_SYS_TIME_H -# include -# else -# include -# endif -#endif - -#include -#include -#include -#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("", 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("", 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("", 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("", 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 index f16f83a64..000000000 --- a/citadel/serv_chat.h +++ /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 index a9dd5934b..000000000 --- a/citadel/serv_crypto.c +++ /dev/null @@ -1,606 +0,0 @@ -/* $Id$ */ - -#include -#include -#include -#include -#include "sysdep.h" - -#ifdef HAVE_OPENSSL -#include -#include -#include -#endif - -#if TIME_WITH_SYS_TIME -# include -# include -#else -# if HAVE_SYS_TIME_H -# include -# else -# include -# endif -#endif - -#ifdef HAVE_PTHREAD_H -#include -#endif - -#ifdef HAVE_SYS_SELECT_H -#include -#endif - -#include -#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 index 66b46570d..000000000 --- a/citadel/serv_expire.c +++ /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 -#include -#include -#include -#include -#include -#include -#include - -#if TIME_WITH_SYS_TIME -# include -# include -#else -# if HAVE_SYS_TIME_H -# include -# else -# include -# endif -#endif - -#include -#include -#include -#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; acm_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 index f6e2c672a..000000000 --- a/citadel/serv_fulltext.c +++ /dev/null @@ -1,479 +0,0 @@ -/* - * $Id$ - * - * This module handles fulltext indexing of the message base. - * - */ - - -#include "sysdep.h" -#include -#include -#include -#include -#include -#include -#include -#include - -#if TIME_WITH_SYS_TIME -# include -# include -#else -# if HAVE_SYS_TIME_H -# include -# else -# include -# endif -#endif - -#include -#include -#include -#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= 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 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_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; ilen / 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 -#include -#include -#include -#include -#include -#include -#include - -#if TIME_WITH_SYS_TIME -# include -# include -#else -# if HAVE_SYS_TIME_H -# include -# else -# include -# endif -#endif - -#include -#include -#include -#include -#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 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, ""); - strcat(SOAPData, ""); - strcat(SOAPData, ""); - strcat(SOAPData, msg->cm_fields['W']); - strcat(SOAPData, ""); - strcat(SOAPData, "<?xml version="1.0" encoding="UTF-8"?>\r\n"); - strcat(SOAPData, "<java version="1.5.0_10" class="java.beans.XMLDecoder"> \r\n"); - strcat(SOAPData, " <array class="com.funambol.framework.core.Alert" length="1">\r\n"); - strcat(SOAPData, " <void index="0">\r\n"); - strcat(SOAPData, " <object class="com.funambol.framework.core.Alert">\r\n"); - strcat(SOAPData, " <void property="cmdID">\r\n"); - strcat(SOAPData, " <object class="com.funambol.framework.core.CmdID"/>\r\n"); - strcat(SOAPData, " </void>"); - strcat(SOAPData, " <void property="data">\r\n"); - strcat(SOAPData, " <int>210</int>\r\n"); - strcat(SOAPData, " </void>\r\n"); - strcat(SOAPData, " <void property="items">\r\n"); - strcat(SOAPData, " <void method="add">\r\n"); - strcat(SOAPData, " <object class="com.funambol.framework.core.Item">\r\n"); - strcat(SOAPData, " <void property="meta">\r\n"); - strcat(SOAPData, " <object class="com.funambol.framework.core.Meta">\r\n"); - strcat(SOAPData, " <void property="metInf">\r\n"); - strcat(SOAPData, " <void property="type">\r\n"); - strcat(SOAPData, " <string>application/vnd.omads-email+xml</string>\r\n"); - strcat(SOAPData, " </void>\r\n"); - strcat(SOAPData, " </void>\r\n"); - strcat(SOAPData, " </object>\r\n"); - strcat(SOAPData, " </void>\r\n"); - strcat(SOAPData, " <void property="target">\r\n"); - strcat(SOAPData, " <object class="com.funambol.framework.core.Target">\r\n"); - strcat(SOAPData, " <void property="locURI">\r\n"); - strcat(SOAPData, " <string>"); - strcat(SOAPData, config.c_funambol_source); - strcat(SOAPData, "</string>\r\n"); - strcat(SOAPData, " </void>\r\n"); - strcat(SOAPData, " </object>\r\n"); - strcat(SOAPData, " </void>\r\n"); - strcat(SOAPData, " </object>\r\n"); - strcat(SOAPData, " </void>\r\n"); - strcat(SOAPData, " </void>\r\n"); - strcat(SOAPData, " </object>\r\n"); - strcat(SOAPData, " </void>\r\n"); - strcat(SOAPData, " </array>\r\n"); - strcat(SOAPData, "</java>"); - strcat(SOAPData,"1"); - - /* 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 index 209fb9b39..000000000 --- a/citadel/serv_funambol.h +++ /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 index eaab32d37..000000000 --- a/citadel/serv_inetcfg.c +++ /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 -#include -#include -#include -#include -#include -#include -#include - -#if TIME_WITH_SYS_TIME -# include -# include -#else -# if HAVE_SYS_TIME_H -# include -# else -# include -# endif -#endif - -#include -#include -#include -#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; istring = 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 index f3e5a774a..000000000 --- a/citadel/serv_ldap.c +++ /dev/null @@ -1,606 +0,0 @@ -/* - * $Id$ - * - * A module which implements the LDAP connector for Citadel. - * - */ - -#include "sysdep.h" -#include -#include -#include -#include -#include -#include -#include -#include - -#if TIME_WITH_SYS_TIME -# include -# include -#else -# if HAVE_SYS_TIME_H -# include -# else -# include -# endif -#endif - -#include -#include -#include -#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 *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; icm_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; imod_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 index 93e93553b..000000000 --- a/citadel/serv_listsub.c +++ /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 -#include -#include -#include -#include -#include -#include -#include -#include -#include -#if TIME_WITH_SYS_TIME -# include -# include -#else -# if HAVE_SYS_TIME_H -# include -# else -# include -# endif -#endif - -#include -#include -#include -#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" - "\n" - "Someone (probably you) has submitted a request to subscribe\n" - "<%s> to the %s mailing list.

\n" - "Please click here to confirm this request:
\n" - "" - "%s?room=%s&token=%s&cmd=confirm

\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" - "\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" - "\n" - "Someone (probably you) has submitted a request to unsubscribe\n" - "<%s> from the %s mailing list.

\n" - "Please click here to confirm this request:
\n" - "" - "%s?room=%s&token=%s&cmd=confirm

\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" - "\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 index c43d8aa19..000000000 --- a/citadel/serv_managesieve.c +++ /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 -#include -#include -#include -#include -#include -#include -#include -#include - -#if TIME_WITH_SYS_TIME -# include -# include -#else -# if HAVE_SYS_TIME_H -# include -# else -# include -# endif -#endif - -#include -#include -#include -#include -#include -#include -#include -#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 index 12e09f0f0..000000000 --- a/citadel/serv_mrtg.c +++ /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 -#include -#include -#include -#include -#include -#include -#include - -#if TIME_WITH_SYS_TIME -# include -# include -#else -# if HAVE_SYS_TIME_H -# include -# else -# include -# endif -#endif - -#include -#include -#include -#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 index 31fced57b..000000000 --- a/citadel/serv_netfilter.c +++ /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 -#include -#include -#include -#include -#include -#include -#include - -#if TIME_WITH_SYS_TIME -# include -# include -#else -# if HAVE_SYS_TIME_H -# include -# else -# include -# endif -#endif - -#include -#include -#include -#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 index 1c8260b9b..000000000 --- a/citadel/serv_network.c +++ /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 -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#if TIME_WITH_SYS_TIME -# include -# include -#else -# if HAVE_SYS_TIME_H -# include -# else -# include -# endif -#endif - -#include -#include -#include -#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; ifl_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; inodename, 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; inext) { - 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; icm_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; icm_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; icm_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; iroom.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 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 index ac47feb10..000000000 --- a/citadel/serv_newuser.c +++ /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 -#include -#include -#include -#include -#include -#include -#include - -#if TIME_WITH_SYS_TIME -# include -# include -#else -# if HAVE_SYS_TIME_H -# include -# else -# include -# endif -#endif - -#include -#include -#include -#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 index 47e968b03..000000000 --- a/citadel/serv_notes.c +++ /dev/null @@ -1,112 +0,0 @@ -/* - * $Id$ - * - * Handles functions related to yellow sticky notes. - * - */ - -#include "sysdep.h" -#include -#include -#include -#include -#include -#include -#include -#include - -#if TIME_WITH_SYS_TIME -# include -# include -#else -# if HAVE_SYS_TIME_H -# include -# else -# include -# endif -#endif - -#include -#include -#include -#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 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 index fd7fbf246..000000000 --- a/citadel/serv_pas2.c +++ /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 -#include -#include - -#if TIME_WITH_SYS_TIME -# include -# include -#else -# if HAVE_SYS_TIME_H -# include -# else -# include -# endif -#endif - -#include -#include -#include -#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 index c49c5cad9..000000000 --- a/citadel/serv_pop3.c +++ /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 -#include -#include -#include -#include -#include -#include -#include - -#if TIME_WITH_SYS_TIME -# include -# include -#else -# if HAVE_SYS_TIME_H -# include -# else -# include -# endif -#endif - -#include -#include -#include -#include -#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; inum_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; inum_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; inum_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; inum_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; inum_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; inum_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 index 8c73f689d..000000000 --- a/citadel/serv_pop3.h +++ /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 index 553049c9b..000000000 --- a/citadel/serv_rwho.c +++ /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 -#include -#include -#include -#include -#include -#include -#include - -#if TIME_WITH_SYS_TIME -# include -# include -#else -# if HAVE_SYS_TIME_H -# include -# else -# include -# endif -#endif - -#include -#include -#include -#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 index 508f8d218..000000000 --- a/citadel/serv_sieve.c +++ /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 -#include -#include -#include -#include -#include -#include -#include - -#if TIME_WITH_SYS_TIME -# include -# include -#else -# if HAVE_SYS_TIME_H -# include -# else -# include -# endif -#endif - -#include -#include -#include -#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; iroom.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; icm_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; iroom.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 index c2329ba91..000000000 --- a/citadel/serv_smtp.c +++ /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 -#include -#include -#include -#include -#include -#include -#include -#include - -#if TIME_WITH_SYS_TIME -# include -# include -#else -# if HAVE_SYS_TIME_H -# include -# else -# include -# endif -#endif - -#include -#include -#include -#include -#include -#include -#include -#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; inumber_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; i0)&&(rp>lp)) { - strcpy(&mailfrom[lp-1], &mailfrom[rp+1]); - } - - /* Prefer brokketized names */ - lp = (-1); - rp = (-1); - for (i=0; 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 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 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 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; icm_fields['M']); - CtdlFreeMessage(msg); - - /* Strip out the headers amd any other non-instruction line */ - lines = num_tokens(instr, '\n'); - for (i=0; i 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\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; inown 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 index 64ad1f5cb..000000000 --- a/citadel/serv_spam.c +++ /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 -#include -#include -#include -#include -#include -#include -#include - -#if TIME_WITH_SYS_TIME -# include -# include -#else -# if HAVE_SYS_TIME_H -# include -# else -# include -# endif -#endif - -#include -#include -#include -#include -#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\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 index 2956e79a0..000000000 --- a/citadel/serv_upgrade.c +++ /dev/null @@ -1,235 +0,0 @@ -/* - * $Id$ - * - * Transparently handle the upgrading of server data formats. - * - */ - -#include "sysdep.h" -#include -#include -#include -#include -#include -#include -#include -#include - -#if TIME_WITH_SYS_TIME -# include -# include -#else -# if HAVE_SYS_TIME_H -# include -# else -# include -# endif -#endif - -#include -#include -#include -#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 index 8d8bc3202..000000000 --- a/citadel/serv_upgrade.h +++ /dev/null @@ -1,5 +0,0 @@ -/* - * $Id$ - * - */ - diff --git a/citadel/serv_vandelay.c b/citadel/serv_vandelay.c deleted file mode 100644 index b1c3630c0..000000000 --- a/citadel/serv_vandelay.c +++ /dev/null @@ -1,739 +0,0 @@ -/* - * $Id$ - * - * This is the "Art Vandelay" module. It is an importer/exporter. - * - */ - -#include "sysdep.h" -#include -#include -#include -#include -#include -#include -#include -#include - -#if TIME_WITH_SYS_TIME -# include -# include -#else -# if HAVE_SYS_TIME_H -# include -# else -# include -# endif -#endif - -#include -#include -#include -#include -#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%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 ((versionREV_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 index ba292dfeb..000000000 --- a/citadel/serv_vcard.c +++ /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 -#include -#include -#include -#include -#include -#include -#include -#include - -#if TIME_WITH_SYS_TIME -# include -# include -#else -# if HAVE_SYS_TIME_H -# include -# else -# include -# endif -#endif - -#include -#include -#include -#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; icm_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='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; ifullname); - 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; ics_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\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; icm_fields['M']); - CtdlFreeMessage(msg); - - i = 0; - while (value = vcard_get_prop(v, "email", 1, i++, 0), value != NULL) { - - for (j=0; jroomname, 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; icollected_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$"; -}