4 * Implements the message store.
14 #if TIME_WITH_SYS_TIME
15 # include <sys/time.h>
19 # include <sys/time.h>
32 #include <sys/types.h>
34 #include <libcitadel.h>
37 #include "serv_extensions.h"
41 #include "sysdep_decls.h"
42 #include "citserver.h"
49 #include "internet_addressing.h"
50 #include "euidindex.h"
51 #include "journaling.h"
52 #include "citadel_dirs.h"
53 #include "clientsocket.h"
54 #include "serv_network.h"
57 #include "ctdl_module.h"
60 struct addresses_to_be_filed *atbf = NULL;
62 /* This temp file holds the queue of operations for AdjRefCount() */
63 static FILE *arcfp = NULL;
66 * This really belongs in serv_network.c, but I don't know how to export
67 * symbols between modules.
69 struct FilterList *filterlist = NULL;
73 * These are the four-character field headers we use when outputting
74 * messages in Citadel format (as opposed to RFC822 format).
77 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
78 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
79 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
80 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
81 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
82 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
83 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
84 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
113 * This function is self explanatory.
114 * (What can I say, I'm in a weird mood today...)
116 void remove_any_whitespace_to_the_left_or_right_of_at_symbol(char *name)
120 for (i = 0; i < strlen(name); ++i) {
121 if (name[i] == '@') {
122 while (isspace(name[i - 1]) && i > 0) {
123 strcpy(&name[i - 1], &name[i]);
126 while (isspace(name[i + 1])) {
127 strcpy(&name[i + 1], &name[i + 2]);
135 * Aliasing for network mail.
136 * (Error messages have been commented out, because this is a server.)
138 int alias(char *name)
139 { /* process alias and routing info for mail */
142 char aaa[SIZ], bbb[SIZ];
143 char *ignetcfg = NULL;
144 char *ignetmap = NULL;
150 char original_name[256];
151 safestrncpy(original_name, name, sizeof original_name);
154 remove_any_whitespace_to_the_left_or_right_of_at_symbol(name);
155 stripallbut(name, '<', '>');
157 fp = fopen(file_mail_aliases, "r");
159 fp = fopen("/dev/null", "r");
166 while (fgets(aaa, sizeof aaa, fp) != NULL) {
167 while (isspace(name[0]))
168 strcpy(name, &name[1]);
169 aaa[strlen(aaa) - 1] = 0;
171 for (a = 0; a < strlen(aaa); ++a) {
173 strcpy(bbb, &aaa[a + 1]);
177 if (!strcasecmp(name, aaa))
182 /* Hit the Global Address Book */
183 if (CtdlDirectoryLookup(aaa, name, sizeof aaa) == 0) {
187 if (strcasecmp(original_name, name)) {
188 CtdlLogPrintf(CTDL_INFO, "%s is being forwarded to %s\n", original_name, name);
191 /* Change "user @ xxx" to "user" if xxx is an alias for this host */
192 for (a=0; a<strlen(name); ++a) {
193 if (name[a] == '@') {
194 if (CtdlHostAlias(&name[a+1]) == hostalias_localhost) {
196 CtdlLogPrintf(CTDL_INFO, "Changed to <%s>\n", name);
201 /* determine local or remote type, see citadel.h */
202 at = haschar(name, '@');
203 if (at == 0) return(MES_LOCAL); /* no @'s - local address */
204 if (at > 1) return(MES_ERROR); /* >1 @'s - invalid address */
205 remove_any_whitespace_to_the_left_or_right_of_at_symbol(name);
207 /* figure out the delivery mode */
208 extract_token(node, name, 1, '@', sizeof node);
210 /* If there are one or more dots in the nodename, we assume that it
211 * is an FQDN and will attempt SMTP delivery to the Internet.
213 if (haschar(node, '.') > 0) {
214 return(MES_INTERNET);
217 /* Otherwise we look in the IGnet maps for a valid Citadel node.
218 * Try directly-connected nodes first...
220 ignetcfg = CtdlGetSysConfig(IGNETCFG);
221 for (i=0; i<num_tokens(ignetcfg, '\n'); ++i) {
222 extract_token(buf, ignetcfg, i, '\n', sizeof buf);
223 extract_token(testnode, buf, 0, '|', sizeof testnode);
224 if (!strcasecmp(node, testnode)) {
232 * Then try nodes that are two or more hops away.
234 ignetmap = CtdlGetSysConfig(IGNETMAP);
235 for (i=0; i<num_tokens(ignetmap, '\n'); ++i) {
236 extract_token(buf, ignetmap, i, '\n', sizeof buf);
237 extract_token(testnode, buf, 0, '|', sizeof testnode);
238 if (!strcasecmp(node, testnode)) {
245 /* If we get to this point it's an invalid node name */
251 * Back end for the MSGS command: output message number only.
253 void simple_listing(long msgnum, void *userdata)
255 cprintf("%ld\n", msgnum);
261 * Back end for the MSGS command: output header summary.
263 void headers_listing(long msgnum, void *userdata)
265 struct CtdlMessage *msg;
267 msg = CtdlFetchMessage(msgnum, 0);
269 cprintf("%ld|0|||||\n", msgnum);
273 cprintf("%ld|%s|%s|%s|%s|%s|\n",
275 (msg->cm_fields['T'] ? msg->cm_fields['T'] : "0"),
276 (msg->cm_fields['A'] ? msg->cm_fields['A'] : ""),
277 (msg->cm_fields['N'] ? msg->cm_fields['N'] : ""),
278 (msg->cm_fields['F'] ? msg->cm_fields['F'] : ""),
279 (msg->cm_fields['U'] ? msg->cm_fields['U'] : "")
281 CtdlFreeMessage(msg);
286 /* Determine if a given message matches the fields in a message template.
287 * Return 0 for a successful match.
289 int CtdlMsgCmp(struct CtdlMessage *msg, struct CtdlMessage *template) {
292 /* If there aren't any fields in the template, all messages will
295 if (template == NULL) return(0);
297 /* Null messages are bogus. */
298 if (msg == NULL) return(1);
300 for (i='A'; i<='Z'; ++i) {
301 if (template->cm_fields[i] != NULL) {
302 if (msg->cm_fields[i] == NULL) {
303 /* Considered equal if temmplate is empty string */
304 if (IsEmptyStr(template->cm_fields[i])) continue;
307 if (strcasecmp(msg->cm_fields[i],
308 template->cm_fields[i])) return 1;
312 /* All compares succeeded: we have a match! */
319 * Retrieve the "seen" message list for the current room.
321 void CtdlGetSeen(char *buf, int which_set) {
324 /* Learn about the user and room in question */
325 CtdlGetRelationship(&vbuf, &CC->user, &CC->room);
327 if (which_set == ctdlsetseen_seen)
328 safestrncpy(buf, vbuf.v_seen, SIZ);
329 if (which_set == ctdlsetseen_answered)
330 safestrncpy(buf, vbuf.v_answered, SIZ);
336 * Manipulate the "seen msgs" string (or other message set strings)
338 void CtdlSetSeen(long *target_msgnums, int num_target_msgnums,
339 int target_setting, int which_set,
340 struct ctdluser *which_user, struct ctdlroom *which_room) {
341 struct cdbdata *cdbfr;
355 char *is_set; /* actually an array of booleans */
357 /* Don't bother doing *anything* if we were passed a list of zero messages */
358 if (num_target_msgnums < 1) {
362 /* If no room was specified, we go with the current room. */
364 which_room = &CC->room;
367 /* If no user was specified, we go with the current user. */
369 which_user = &CC->user;
372 CtdlLogPrintf(CTDL_DEBUG, "CtdlSetSeen(%d msgs starting with %ld, %s, %d) in <%s>\n",
373 num_target_msgnums, target_msgnums[0],
374 (target_setting ? "SET" : "CLEAR"),
378 /* Learn about the user and room in question */
379 CtdlGetRelationship(&vbuf, which_user, which_room);
381 /* Load the message list */
382 cdbfr = cdb_fetch(CDB_MSGLISTS, &which_room->QRnumber, sizeof(long));
384 msglist = (long *) cdbfr->ptr;
385 cdbfr->ptr = NULL; /* CtdlSetSeen() now owns this memory */
386 num_msgs = cdbfr->len / sizeof(long);
389 return; /* No messages at all? No further action. */
392 is_set = malloc(num_msgs * sizeof(char));
393 memset(is_set, 0, (num_msgs * sizeof(char)) );
395 /* Decide which message set we're manipulating */
397 case ctdlsetseen_seen:
398 vset = NewStrBufPlain(vbuf.v_seen, -1);
400 case ctdlsetseen_answered:
401 vset = NewStrBufPlain(vbuf.v_answered, -1);
408 #if 0 /* This is a special diagnostic section. Do not allow it to run during normal operation. */
409 CtdlLogPrintf(CTDL_DEBUG, "There are %d messages in the room.\n", num_msgs);
410 for (i=0; i<num_msgs; ++i) {
411 if ((i > 0) && (msglist[i] <= msglist[i-1])) abort();
413 CtdlLogPrintf(CTDL_DEBUG, "We are twiddling %d of them.\n", num_target_msgnums);
414 for (k=0; k<num_target_msgnums; ++k) {
415 if ((k > 0) && (target_msgnums[k] <= target_msgnums[k-1])) abort();
419 CtdlLogPrintf(CTDL_DEBUG, "before update: %s\n", ChrPtr(vset));
421 /* Translate the existing sequence set into an array of booleans */
422 setstr = NewStrBuf();
426 while (StrBufExtract_NextToken(setstr, vset, &pvset, ',') >= 0) {
428 StrBufExtract_token(lostr, setstr, 0, ':');
429 if (StrBufNum_tokens(setstr, ':') >= 2) {
430 StrBufExtract_token(histr, setstr, 1, ':');
434 StrBufAppendBuf(histr, lostr, 0);
437 if (!strcmp(ChrPtr(histr), "*")) {
444 for (i = 0; i < num_msgs; ++i) {
445 if ((msglist[i] >= lo) && (msglist[i] <= hi)) {
455 /* Now translate the array of booleans back into a sequence set */
461 for (i=0; i<num_msgs; ++i) {
465 for (k=0; k<num_target_msgnums; ++k) {
466 if (msglist[i] == target_msgnums[k]) {
467 is_seen = target_setting;
471 if ((was_seen == 0) && (is_seen == 1)) {
474 else if ((was_seen == 1) && (is_seen == 0)) {
477 if (StrLength(vset) > 0) {
478 StrBufAppendBufPlain(vset, HKEY(","), 0);
481 StrBufAppendPrintf(vset, "%ld", hi);
484 StrBufAppendPrintf(vset, "%ld:%ld", lo, hi);
488 if ((is_seen) && (i == num_msgs - 1)) {
489 if (StrLength(vset) > 0) {
490 StrBufAppendBufPlain(vset, HKEY(","), 0);
492 if ((i==0) || (was_seen == 0)) {
493 StrBufAppendPrintf(vset, "%ld", msglist[i]);
496 StrBufAppendPrintf(vset, "%ld:%ld", lo, msglist[i]);
504 * We will have to stuff this string back into a 4096 byte buffer, so if it's
505 * larger than that now, truncate it by removing tokens from the beginning.
506 * The limit of 100 iterations is there to prevent an infinite loop in case
507 * something unexpected happens.
509 int number_of_truncations = 0;
510 while ( (StrLength(vset) > SIZ) && (number_of_truncations < 100) ) {
511 StrBufRemove_token(vset, 0, ',');
512 ++number_of_truncations;
516 * If we're truncating the sequence set of messages marked with the 'seen' flag,
517 * we want the earliest messages (the truncated ones) to be marked, not unmarked.
518 * Otherwise messages at the beginning will suddenly appear to be 'unseen'.
520 if ( (which_set == ctdlsetseen_seen) && (number_of_truncations > 0) ) {
522 first_tok = NewStrBuf();
523 StrBufExtract_token(first_tok, vset, 0, ',');
524 StrBufRemove_token(vset, 0, ',');
526 if (StrBufNum_tokens(first_tok, ':') > 1) {
527 StrBufRemove_token(first_tok, 0, ':');
531 new_set = NewStrBuf();
532 StrBufAppendBufPlain(new_set, HKEY("1:"), 0);
533 StrBufAppendBuf(new_set, first_tok, 0);
534 StrBufAppendBufPlain(new_set, HKEY(":"), 0);
535 StrBufAppendBuf(new_set, vset, 0);
538 FreeStrBuf(&first_tok);
542 CtdlLogPrintf(CTDL_DEBUG, " after update: %s\n", ChrPtr(vset));
544 /* Decide which message set we're manipulating */
546 case ctdlsetseen_seen:
547 safestrncpy(vbuf.v_seen, ChrPtr(vset), sizeof vbuf.v_seen);
549 case ctdlsetseen_answered:
550 safestrncpy(vbuf.v_answered, ChrPtr(vset), sizeof vbuf.v_answered);
556 CtdlSetRelationship(&vbuf, which_user, which_room);
562 * API function to perform an operation for each qualifying message in the
563 * current room. (Returns the number of messages processed.)
565 int CtdlForEachMessage(int mode, long ref, char *search_string,
567 struct CtdlMessage *compare,
568 void (*CallBack) (long, void *),
574 struct cdbdata *cdbfr;
575 long *msglist = NULL;
577 int num_processed = 0;
580 struct CtdlMessage *msg = NULL;
583 int printed_lastold = 0;
584 int num_search_msgs = 0;
585 long *search_msgs = NULL;
587 int need_to_free_re = 0;
590 if ((content_type) && (!IsEmptyStr(content_type))) {
591 regcomp(&re, content_type, 0);
595 /* Learn about the user and room in question */
596 getuser(&CC->user, CC->curr_user);
597 CtdlGetRelationship(&vbuf, &CC->user, &CC->room);
599 /* Load the message list */
600 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long));
602 msglist = (long *) cdbfr->ptr;
603 num_msgs = cdbfr->len / sizeof(long);
605 if (need_to_free_re) regfree(&re);
606 return 0; /* No messages at all? No further action. */
611 * Now begin the traversal.
613 if (num_msgs > 0) for (a = 0; a < num_msgs; ++a) {
615 /* If the caller is looking for a specific MIME type, filter
616 * out all messages which are not of the type requested.
618 if ((content_type != NULL) && (!IsEmptyStr(content_type))) {
620 /* This call to GetMetaData() sits inside this loop
621 * so that we only do the extra database read per msg
622 * if we need to. Doing the extra read all the time
623 * really kills the server. If we ever need to use
624 * metadata for another search criterion, we need to
625 * move the read somewhere else -- but still be smart
626 * enough to only do the read if the caller has
627 * specified something that will need it.
629 GetMetaData(&smi, msglist[a]);
631 /* if (strcasecmp(smi.meta_content_type, content_type)) { old non-regex way */
632 if (regexec(&re, smi.meta_content_type, 1, &pm, 0) != 0) {
638 num_msgs = sort_msglist(msglist, num_msgs);
640 /* If a template was supplied, filter out the messages which
641 * don't match. (This could induce some delays!)
644 if (compare != NULL) {
645 for (a = 0; a < num_msgs; ++a) {
646 msg = CtdlFetchMessage(msglist[a], 1);
648 if (CtdlMsgCmp(msg, compare)) {
651 CtdlFreeMessage(msg);
657 /* If a search string was specified, get a message list from
658 * the full text index and remove messages which aren't on both
662 * Since the lists are sorted and strictly ascending, and the
663 * output list is guaranteed to be shorter than or equal to the
664 * input list, we overwrite the bottom of the input list. This
665 * eliminates the need to memmove big chunks of the list over and
668 if ( (num_msgs > 0) && (mode == MSGS_SEARCH) && (search_string) ) {
670 /* Call search module via hook mechanism.
671 * NULL means use any search function available.
672 * otherwise replace with a char * to name of search routine
674 CtdlModuleDoSearch(&num_search_msgs, &search_msgs, search_string, "fulltext");
676 if (num_search_msgs > 0) {
680 orig_num_msgs = num_msgs;
682 for (i=0; i<orig_num_msgs; ++i) {
683 for (j=0; j<num_search_msgs; ++j) {
684 if (msglist[i] == search_msgs[j]) {
685 msglist[num_msgs++] = msglist[i];
691 num_msgs = 0; /* No messages qualify */
693 if (search_msgs != NULL) free(search_msgs);
695 /* Now that we've purged messages which don't contain the search
696 * string, treat a MSGS_SEARCH just like a MSGS_ALL from this
703 * Now iterate through the message list, according to the
704 * criteria supplied by the caller.
707 for (a = 0; a < num_msgs; ++a) {
708 thismsg = msglist[a];
709 if (mode == MSGS_ALL) {
713 is_seen = is_msg_in_sequence_set(
714 vbuf.v_seen, thismsg);
715 if (is_seen) lastold = thismsg;
721 || ((mode == MSGS_OLD) && (is_seen))
722 || ((mode == MSGS_NEW) && (!is_seen))
723 || ((mode == MSGS_LAST) && (a >= (num_msgs - ref)))
724 || ((mode == MSGS_FIRST) && (a < ref))
725 || ((mode == MSGS_GT) && (thismsg > ref))
726 || ((mode == MSGS_EQ) && (thismsg == ref))
729 if ((mode == MSGS_NEW) && (CC->user.flags & US_LASTOLD) && (lastold > 0L) && (printed_lastold == 0) && (!is_seen)) {
731 CallBack(lastold, userdata);
735 if (CallBack) CallBack(thismsg, userdata);
739 cdb_free(cdbfr); /* Clean up */
740 if (need_to_free_re) regfree(&re);
741 return num_processed;
747 * cmd_msgs() - get list of message #'s in this room
748 * implements the MSGS server command using CtdlForEachMessage()
750 void cmd_msgs(char *cmdbuf)
759 int with_template = 0;
760 struct CtdlMessage *template = NULL;
761 int with_headers = 0;
762 char search_string[1024];
764 extract_token(which, cmdbuf, 0, '|', sizeof which);
765 cm_ref = extract_int(cmdbuf, 1);
766 extract_token(search_string, cmdbuf, 1, '|', sizeof search_string);
767 with_template = extract_int(cmdbuf, 2);
768 with_headers = extract_int(cmdbuf, 3);
771 if (!strncasecmp(which, "OLD", 3))
773 else if (!strncasecmp(which, "NEW", 3))
775 else if (!strncasecmp(which, "FIRST", 5))
777 else if (!strncasecmp(which, "LAST", 4))
779 else if (!strncasecmp(which, "GT", 2))
781 else if (!strncasecmp(which, "SEARCH", 6))
786 if ((!(CC->logged_in)) && (!(CC->internal_pgm))) {
787 cprintf("%d not logged in\n", ERROR + NOT_LOGGED_IN);
791 if ( (mode == MSGS_SEARCH) && (!config.c_enable_fulltext) ) {
792 cprintf("%d Full text index is not enabled on this server.\n",
793 ERROR + CMD_NOT_SUPPORTED);
799 cprintf("%d Send template then receive message list\n",
801 template = (struct CtdlMessage *)
802 malloc(sizeof(struct CtdlMessage));
803 memset(template, 0, sizeof(struct CtdlMessage));
804 template->cm_magic = CTDLMESSAGE_MAGIC;
805 template->cm_anon_type = MES_NORMAL;
807 while(client_getln(buf, sizeof buf) >= 0 && strcmp(buf,"000")) {
808 extract_token(tfield, buf, 0, '|', sizeof tfield);
809 extract_token(tvalue, buf, 1, '|', sizeof tvalue);
810 for (i='A'; i<='Z'; ++i) if (msgkeys[i]!=NULL) {
811 if (!strcasecmp(tfield, msgkeys[i])) {
812 template->cm_fields[i] =
820 cprintf("%d \n", LISTING_FOLLOWS);
823 CtdlForEachMessage(mode,
824 ( (mode == MSGS_SEARCH) ? 0 : cm_ref ),
825 ( (mode == MSGS_SEARCH) ? search_string : NULL ),
828 (with_headers ? headers_listing : simple_listing),
831 if (template != NULL) CtdlFreeMessage(template);
839 * help_subst() - support routine for help file viewer
841 void help_subst(char *strbuf, char *source, char *dest)
846 while (p = pattern2(strbuf, source), (p >= 0)) {
847 strcpy(workbuf, &strbuf[p + strlen(source)]);
848 strcpy(&strbuf[p], dest);
849 strcat(strbuf, workbuf);
854 void do_help_subst(char *buffer)
858 help_subst(buffer, "^nodename", config.c_nodename);
859 help_subst(buffer, "^humannode", config.c_humannode);
860 help_subst(buffer, "^fqdn", config.c_fqdn);
861 help_subst(buffer, "^username", CC->user.fullname);
862 snprintf(buf2, sizeof buf2, "%ld", CC->user.usernum);
863 help_subst(buffer, "^usernum", buf2);
864 help_subst(buffer, "^sysadm", config.c_sysadm);
865 help_subst(buffer, "^variantname", CITADEL);
866 snprintf(buf2, sizeof buf2, "%d", config.c_maxsessions);
867 help_subst(buffer, "^maxsessions", buf2);
868 help_subst(buffer, "^bbsdir", ctdl_message_dir);
874 * memfmout() - Citadel text formatter and paginator.
875 * Although the original purpose of this routine was to format
876 * text to the reader's screen width, all we're really using it
877 * for here is to format text out to 80 columns before sending it
878 * to the client. The client software may reformat it again.
881 char *mptr, /* where are we going to get our text from? */
882 char subst, /* nonzero if we should do substitutions */
883 char *nl) /* string to terminate lines with */
891 static int width = 80;
896 c = 1; /* c is the current pos */
900 while (ch = *mptr, ((ch != 0) && (strlen(buffer) < 126))) {
902 buffer[strlen(buffer) + 1] = 0;
903 buffer[strlen(buffer)] = ch;
906 if (buffer[0] == '^')
907 do_help_subst(buffer);
909 buffer[strlen(buffer) + 1] = 0;
911 strcpy(buffer, &buffer[1]);
919 if (((ch == 13) || (ch == 10)) && (old != 13) && (old != 10)) {
922 if (((old == 13) || (old == 10)) && (isspace(real))) {
927 if (((strlen(aaa) + c) > (width - 5)) && (strlen(aaa) > (width - 5))) {
928 cprintf("%s%s", nl, aaa);
937 if ((strlen(aaa) + c) > (width - 5)) {
946 if ((ch == 13) || (ch == 10)) {
947 cprintf("%s%s", aaa, nl);
954 cprintf("%s%s", aaa, nl);
960 * Callback function for mime parser that simply lists the part
962 void list_this_part(char *name, char *filename, char *partnum, char *disp,
963 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
964 char *cbid, void *cbuserdata)
968 ma = (struct ma_info *)cbuserdata;
969 if (ma->is_ma == 0) {
970 cprintf("part=%s|%s|%s|%s|%s|%ld|%s\n",
971 name, filename, partnum, disp, cbtype, (long)length, cbid);
976 * Callback function for multipart prefix
978 void list_this_pref(char *name, char *filename, char *partnum, char *disp,
979 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
980 char *cbid, void *cbuserdata)
984 ma = (struct ma_info *)cbuserdata;
985 if (!strcasecmp(cbtype, "multipart/alternative")) {
989 if (ma->is_ma == 0) {
990 cprintf("pref=%s|%s\n", partnum, cbtype);
995 * Callback function for multipart sufffix
997 void list_this_suff(char *name, char *filename, char *partnum, char *disp,
998 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
999 char *cbid, void *cbuserdata)
1003 ma = (struct ma_info *)cbuserdata;
1004 if (ma->is_ma == 0) {
1005 cprintf("suff=%s|%s\n", partnum, cbtype);
1007 if (!strcasecmp(cbtype, "multipart/alternative")) {
1014 * Callback function for mime parser that opens a section for downloading
1016 void mime_download(char *name, char *filename, char *partnum, char *disp,
1017 void *content, char *cbtype, char *cbcharset, size_t length,
1018 char *encoding, char *cbid, void *cbuserdata)
1022 /* Silently go away if there's already a download open. */
1023 if (CC->download_fp != NULL)
1027 (!IsEmptyStr(partnum) && (!strcasecmp(CC->download_desired_section, partnum)))
1028 || (!IsEmptyStr(cbid) && (!strcasecmp(CC->download_desired_section, cbid)))
1030 CC->download_fp = tmpfile();
1031 if (CC->download_fp == NULL)
1034 rv = fwrite(content, length, 1, CC->download_fp);
1035 fflush(CC->download_fp);
1036 rewind(CC->download_fp);
1038 OpenCmdResult(filename, cbtype);
1045 * Callback function for mime parser that outputs a section all at once.
1046 * We can specify the desired section by part number *or* content-id.
1048 void mime_spew_section(char *name, char *filename, char *partnum, char *disp,
1049 void *content, char *cbtype, char *cbcharset, size_t length,
1050 char *encoding, char *cbid, void *cbuserdata)
1052 int *found_it = (int *)cbuserdata;
1055 (!IsEmptyStr(partnum) && (!strcasecmp(CC->download_desired_section, partnum)))
1056 || (!IsEmptyStr(cbid) && (!strcasecmp(CC->download_desired_section, cbid)))
1059 cprintf("%d %d|-1|%s|%s\n",
1065 client_write(content, length);
1072 * Load a message from disk into memory.
1073 * This is used by CtdlOutputMsg() and other fetch functions.
1075 * NOTE: Caller is responsible for freeing the returned CtdlMessage struct
1076 * using the CtdlMessageFree() function.
1078 struct CtdlMessage *CtdlFetchMessage(long msgnum, int with_body)
1080 struct cdbdata *dmsgtext;
1081 struct CtdlMessage *ret = NULL;
1085 cit_uint8_t field_header;
1087 CtdlLogPrintf(CTDL_DEBUG, "CtdlFetchMessage(%ld, %d)\n", msgnum, with_body);
1089 dmsgtext = cdb_fetch(CDB_MSGMAIN, &msgnum, sizeof(long));
1090 if (dmsgtext == NULL) {
1093 mptr = dmsgtext->ptr;
1094 upper_bound = mptr + dmsgtext->len;
1096 /* Parse the three bytes that begin EVERY message on disk.
1097 * The first is always 0xFF, the on-disk magic number.
1098 * The second is the anonymous/public type byte.
1099 * The third is the format type byte (vari, fixed, or MIME).
1103 CtdlLogPrintf(CTDL_ERR, "Message %ld appears to be corrupted.\n", msgnum);
1107 ret = (struct CtdlMessage *) malloc(sizeof(struct CtdlMessage));
1108 memset(ret, 0, sizeof(struct CtdlMessage));
1110 ret->cm_magic = CTDLMESSAGE_MAGIC;
1111 ret->cm_anon_type = *mptr++; /* Anon type byte */
1112 ret->cm_format_type = *mptr++; /* Format type byte */
1115 * The rest is zero or more arbitrary fields. Load them in.
1116 * We're done when we encounter either a zero-length field or
1117 * have just processed the 'M' (message text) field.
1120 if (mptr >= upper_bound) {
1123 field_header = *mptr++;
1124 ret->cm_fields[field_header] = strdup(mptr);
1126 while (*mptr++ != 0); /* advance to next field */
1128 } while ((mptr < upper_bound) && (field_header != 'M'));
1132 /* Always make sure there's something in the msg text field. If
1133 * it's NULL, the message text is most likely stored separately,
1134 * so go ahead and fetch that. Failing that, just set a dummy
1135 * body so other code doesn't barf.
1137 if ( (ret->cm_fields['M'] == NULL) && (with_body) ) {
1138 dmsgtext = cdb_fetch(CDB_BIGMSGS, &msgnum, sizeof(long));
1139 if (dmsgtext != NULL) {
1140 ret->cm_fields['M'] = strdup(dmsgtext->ptr);
1144 if (ret->cm_fields['M'] == NULL) {
1145 ret->cm_fields['M'] = strdup("\r\n\r\n (no text)\r\n");
1148 /* Perform "before read" hooks (aborting if any return nonzero) */
1149 if (PerformMessageHooks(ret, EVT_BEFOREREAD) > 0) {
1150 CtdlFreeMessage(ret);
1159 * Returns 1 if the supplied pointer points to a valid Citadel message.
1160 * If the pointer is NULL or the magic number check fails, returns 0.
1162 int is_valid_message(struct CtdlMessage *msg) {
1165 if ((msg->cm_magic) != CTDLMESSAGE_MAGIC) {
1166 CtdlLogPrintf(CTDL_WARNING, "is_valid_message() -- self-check failed\n");
1174 * 'Destructor' for struct CtdlMessage
1176 void CtdlFreeMessage(struct CtdlMessage *msg)
1180 if (is_valid_message(msg) == 0)
1182 if (msg != NULL) free (msg);
1186 for (i = 0; i < 256; ++i)
1187 if (msg->cm_fields[i] != NULL) {
1188 free(msg->cm_fields[i]);
1191 msg->cm_magic = 0; /* just in case */
1197 * Pre callback function for multipart/alternative
1199 * NOTE: this differs from the standard behavior for a reason. Normally when
1200 * displaying multipart/alternative you want to show the _last_ usable
1201 * format in the message. Here we show the _first_ one, because it's
1202 * usually text/plain. Since this set of functions is designed for text
1203 * output to non-MIME-aware clients, this is the desired behavior.
1206 void fixed_output_pre(char *name, char *filename, char *partnum, char *disp,
1207 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
1208 char *cbid, void *cbuserdata)
1212 ma = (struct ma_info *)cbuserdata;
1213 CtdlLogPrintf(CTDL_DEBUG, "fixed_output_pre() type=<%s>\n", cbtype);
1214 if (!strcasecmp(cbtype, "multipart/alternative")) {
1218 if (!strcasecmp(cbtype, "message/rfc822")) {
1224 * Post callback function for multipart/alternative
1226 void fixed_output_post(char *name, char *filename, char *partnum, char *disp,
1227 void *content, char *cbtype, char *cbcharset, size_t length,
1228 char *encoding, char *cbid, void *cbuserdata)
1232 ma = (struct ma_info *)cbuserdata;
1233 CtdlLogPrintf(CTDL_DEBUG, "fixed_output_post() type=<%s>\n", cbtype);
1234 if (!strcasecmp(cbtype, "multipart/alternative")) {
1238 if (!strcasecmp(cbtype, "message/rfc822")) {
1244 * Inline callback function for mime parser that wants to display text
1246 void fixed_output(char *name, char *filename, char *partnum, char *disp,
1247 void *content, char *cbtype, char *cbcharset, size_t length,
1248 char *encoding, char *cbid, void *cbuserdata)
1255 ma = (struct ma_info *)cbuserdata;
1257 CtdlLogPrintf(CTDL_DEBUG,
1258 "fixed_output() part %s: %s (%s) (%ld bytes)\n",
1259 partnum, filename, cbtype, (long)length);
1262 * If we're in the middle of a multipart/alternative scope and
1263 * we've already printed another section, skip this one.
1265 if ( (ma->is_ma) && (ma->did_print) ) {
1266 CtdlLogPrintf(CTDL_DEBUG, "Skipping part %s (%s)\n", partnum, cbtype);
1271 if ( (!strcasecmp(cbtype, "text/plain"))
1272 || (IsEmptyStr(cbtype)) ) {
1275 client_write(wptr, length);
1276 if (wptr[length-1] != '\n') {
1283 if (!strcasecmp(cbtype, "text/html")) {
1284 ptr = html_to_ascii(content, length, 80, 0);
1286 client_write(ptr, wlen);
1287 if (ptr[wlen-1] != '\n') {
1294 if (ma->use_fo_hooks) {
1295 if (PerformFixedOutputHooks(cbtype, content, length)) {
1296 /* above function returns nonzero if it handled the part */
1301 if (strncasecmp(cbtype, "multipart/", 10)) {
1302 cprintf("Part %s: %s (%s) (%ld bytes)\r\n",
1303 partnum, filename, cbtype, (long)length);
1309 * The client is elegant and sophisticated and wants to be choosy about
1310 * MIME content types, so figure out which multipart/alternative part
1311 * we're going to send.
1313 * We use a system of weights. When we find a part that matches one of the
1314 * MIME types we've declared as preferential, we can store it in ma->chosen_part
1315 * and then set ma->chosen_pref to that MIME type's position in our preference
1316 * list. If we then hit another match, we only replace the first match if
1317 * the preference value is lower.
1319 void choose_preferred(char *name, char *filename, char *partnum, char *disp,
1320 void *content, char *cbtype, char *cbcharset, size_t length,
1321 char *encoding, char *cbid, void *cbuserdata)
1327 ma = (struct ma_info *)cbuserdata;
1329 // NOTE: REMOVING THIS CONDITIONAL FIXES BUG 220
1330 // http://bugzilla.citadel.org/show_bug.cgi?id=220
1331 // I don't know if there are any side effects! Please TEST TEST TEST
1332 //if (ma->is_ma > 0) {
1334 for (i=0; i<num_tokens(CC->preferred_formats, '|'); ++i) {
1335 extract_token(buf, CC->preferred_formats, i, '|', sizeof buf);
1336 if ( (!strcasecmp(buf, cbtype)) && (!ma->freeze) ) {
1337 if (i < ma->chosen_pref) {
1338 CtdlLogPrintf(CTDL_DEBUG, "Setting chosen part: <%s>\n", partnum);
1339 safestrncpy(ma->chosen_part, partnum, sizeof ma->chosen_part);
1340 ma->chosen_pref = i;
1347 * Now that we've chosen our preferred part, output it.
1349 void output_preferred(char *name, char *filename, char *partnum, char *disp,
1350 void *content, char *cbtype, char *cbcharset, size_t length,
1351 char *encoding, char *cbid, void *cbuserdata)
1355 int add_newline = 0;
1359 ma = (struct ma_info *)cbuserdata;
1361 /* This is not the MIME part you're looking for... */
1362 if (strcasecmp(partnum, ma->chosen_part)) return;
1364 /* If the content-type of this part is in our preferred formats
1365 * list, we can simply output it verbatim.
1367 for (i=0; i<num_tokens(CC->preferred_formats, '|'); ++i) {
1368 extract_token(buf, CC->preferred_formats, i, '|', sizeof buf);
1369 if (!strcasecmp(buf, cbtype)) {
1370 /* Yeah! Go! W00t!! */
1372 text_content = (char *)content;
1373 if (text_content[length-1] != '\n') {
1376 cprintf("Content-type: %s", cbtype);
1377 if (!IsEmptyStr(cbcharset)) {
1378 cprintf("; charset=%s", cbcharset);
1380 cprintf("\nContent-length: %d\n",
1381 (int)(length + add_newline) );
1382 if (!IsEmptyStr(encoding)) {
1383 cprintf("Content-transfer-encoding: %s\n", encoding);
1386 cprintf("Content-transfer-encoding: 7bit\n");
1388 cprintf("X-Citadel-MSG4-Partnum: %s\n", partnum);
1390 client_write(content, length);
1391 if (add_newline) cprintf("\n");
1396 /* No translations required or possible: output as text/plain */
1397 cprintf("Content-type: text/plain\n\n");
1398 fixed_output(name, filename, partnum, disp, content, cbtype, cbcharset,
1399 length, encoding, cbid, cbuserdata);
1404 char desired_section[64];
1411 * Callback function for
1413 void extract_encapsulated_message(char *name, char *filename, char *partnum, char *disp,
1414 void *content, char *cbtype, char *cbcharset, size_t length,
1415 char *encoding, char *cbid, void *cbuserdata)
1417 struct encapmsg *encap;
1419 encap = (struct encapmsg *)cbuserdata;
1421 /* Only proceed if this is the desired section... */
1422 if (!strcasecmp(encap->desired_section, partnum)) {
1423 encap->msglen = length;
1424 encap->msg = malloc(length + 2);
1425 memcpy(encap->msg, content, length);
1435 * Get a message off disk. (returns om_* values found in msgbase.h)
1438 int CtdlOutputMsg(long msg_num, /* message number (local) to fetch */
1439 int mode, /* how would you like that message? */
1440 int headers_only, /* eschew the message body? */
1441 int do_proto, /* do Citadel protocol responses? */
1442 int crlf, /* Use CRLF newlines instead of LF? */
1443 char *section, /* NULL or a message/rfc822 section */
1444 int flags /* various flags; see msgbase.h */
1446 struct CtdlMessage *TheMessage = NULL;
1447 int retcode = om_no_such_msg;
1448 struct encapmsg encap;
1450 CtdlLogPrintf(CTDL_DEBUG, "CtdlOutputMsg() msgnum=%ld, mode=%d, section=%s\n",
1452 (section ? section : "<>")
1455 if ((!(CC->logged_in)) && (!(CC->internal_pgm))) {
1456 if (do_proto) cprintf("%d Not logged in.\n",
1457 ERROR + NOT_LOGGED_IN);
1458 return(om_not_logged_in);
1461 /* FIXME: check message id against msglist for this room */
1464 * Fetch the message from disk. If we're in HEADERS_FAST mode,
1465 * request that we don't even bother loading the body into memory.
1467 if (headers_only == HEADERS_FAST) {
1468 TheMessage = CtdlFetchMessage(msg_num, 0);
1471 TheMessage = CtdlFetchMessage(msg_num, 1);
1474 if (TheMessage == NULL) {
1475 if (do_proto) cprintf("%d Can't locate msg %ld on disk\n",
1476 ERROR + MESSAGE_NOT_FOUND, msg_num);
1477 return(om_no_such_msg);
1480 /* Here is the weird form of this command, to process only an
1481 * encapsulated message/rfc822 section.
1483 if (section) if (!IsEmptyStr(section)) if (strcmp(section, "0")) {
1484 memset(&encap, 0, sizeof encap);
1485 safestrncpy(encap.desired_section, section, sizeof encap.desired_section);
1486 mime_parser(TheMessage->cm_fields['M'],
1488 *extract_encapsulated_message,
1489 NULL, NULL, (void *)&encap, 0
1491 CtdlFreeMessage(TheMessage);
1495 encap.msg[encap.msglen] = 0;
1496 TheMessage = convert_internet_message(encap.msg);
1497 encap.msg = NULL; /* no free() here, TheMessage owns it now */
1499 /* Now we let it fall through to the bottom of this
1500 * function, because TheMessage now contains the
1501 * encapsulated message instead of the top-level
1502 * message. Isn't that neat?
1507 if (do_proto) cprintf("%d msg %ld has no part %s\n",
1508 ERROR + MESSAGE_NOT_FOUND, msg_num, section);
1509 retcode = om_no_such_msg;
1514 /* Ok, output the message now */
1515 retcode = CtdlOutputPreLoadedMsg(TheMessage, mode, headers_only, do_proto, crlf, flags);
1516 CtdlFreeMessage(TheMessage);
1522 char *qp_encode_email_addrs(char *source)
1524 char user[256], node[256], name[256];
1525 const char headerStr[] = "=?UTF-8?Q?";
1529 int need_to_encode = 0;
1535 long nAddrPtrMax = 50;
1540 if (source == NULL) return source;
1541 if (IsEmptyStr(source)) return source;
1543 AddrPtr = malloc (sizeof (long) * nAddrPtrMax);
1544 AddrUtf8 = malloc (sizeof (long) * nAddrPtrMax);
1545 memset(AddrUtf8, 0, sizeof (long) * nAddrPtrMax);
1548 while (!IsEmptyStr (&source[i])) {
1549 if (nColons >= nAddrPtrMax){
1552 ptr = (long *) malloc(sizeof (long) * nAddrPtrMax * 2);
1553 memcpy (ptr, AddrPtr, sizeof (long) * nAddrPtrMax);
1554 free (AddrPtr), AddrPtr = ptr;
1556 ptr = (long *) malloc(sizeof (long) * nAddrPtrMax * 2);
1557 memset(&ptr[nAddrPtrMax], 0,
1558 sizeof (long) * nAddrPtrMax);
1560 memcpy (ptr, AddrUtf8, sizeof (long) * nAddrPtrMax);
1561 free (AddrUtf8), AddrUtf8 = ptr;
1564 if (((unsigned char) source[i] < 32) ||
1565 ((unsigned char) source[i] > 126)) {
1567 AddrUtf8[nColons] = 1;
1569 if (source[i] == '"')
1570 InQuotes = !InQuotes;
1571 if (!InQuotes && source[i] == ',') {
1572 AddrPtr[nColons] = i;
1577 if (need_to_encode == 0) {
1584 EncodedMaxLen = nColons * (sizeof(headerStr) + 3) + SourceLen * 3;
1585 Encoded = (char*) malloc (EncodedMaxLen);
1587 for (i = 0; i < nColons; i++)
1588 source[AddrPtr[i]++] = '\0';
1592 for (i = 0; i < nColons && nPtr != NULL; i++) {
1593 nmax = EncodedMaxLen - (nPtr - Encoded);
1595 process_rfc822_addr(&source[AddrPtr[i]],
1599 /* TODO: libIDN here ! */
1600 if (IsEmptyStr(name)) {
1601 n = snprintf(nPtr, nmax,
1602 (i==0)?"%s@%s" : ",%s@%s",
1606 EncodedName = rfc2047encode(name, strlen(name));
1607 n = snprintf(nPtr, nmax,
1608 (i==0)?"%s <%s@%s>" : ",%s <%s@%s>",
1609 EncodedName, user, node);
1614 n = snprintf(nPtr, nmax,
1615 (i==0)?"%s" : ",%s",
1616 &source[AddrPtr[i]]);
1622 ptr = (char*) malloc(EncodedMaxLen * 2);
1623 memcpy(ptr, Encoded, EncodedMaxLen);
1624 nnPtr = ptr + (nPtr - Encoded), nPtr = nnPtr;
1625 free(Encoded), Encoded = ptr;
1627 i--; /* do it once more with properly lengthened buffer */
1630 for (i = 0; i < nColons; i++)
1631 source[--AddrPtr[i]] = ',';
1638 /* If the last item in a list of recipients was truncated to a partial address,
1639 * remove it completely in order to avoid choking libSieve
1641 void sanitize_truncated_recipient(char *str)
1644 if (num_tokens(str, ',') < 2) return;
1646 int len = strlen(str);
1647 if (len < 900) return;
1648 if (len > 998) str[998] = 0;
1650 char *cptr = strrchr(str, ',');
1653 char *lptr = strchr(cptr, '<');
1654 char *rptr = strchr(cptr, '>');
1656 if ( (lptr) && (rptr) && (rptr > lptr) ) return;
1664 * Get a message off disk. (returns om_* values found in msgbase.h)
1666 int CtdlOutputPreLoadedMsg(
1667 struct CtdlMessage *TheMessage,
1668 int mode, /* how would you like that message? */
1669 int headers_only, /* eschew the message body? */
1670 int do_proto, /* do Citadel protocol responses? */
1671 int crlf, /* Use CRLF newlines instead of LF? */
1672 int flags /* should the bessage be exported clean? */
1676 cit_uint8_t ch, prev_ch;
1678 char display_name[256];
1680 char *nl; /* newline string */
1682 int subject_found = 0;
1685 /* Buffers needed for RFC822 translation. These are all filled
1686 * using functions that are bounds-checked, and therefore we can
1687 * make them substantially smaller than SIZ.
1694 char datestamp[100];
1696 CtdlLogPrintf(CTDL_DEBUG, "CtdlOutputPreLoadedMsg(TheMessage=%s, %d, %d, %d, %d\n",
1697 ((TheMessage == NULL) ? "NULL" : "not null"),
1698 mode, headers_only, do_proto, crlf);
1700 strcpy(mid, "unknown");
1701 nl = (crlf ? "\r\n" : "\n");
1703 if (!is_valid_message(TheMessage)) {
1704 CtdlLogPrintf(CTDL_ERR,
1705 "ERROR: invalid preloaded message for output\n");
1707 return(om_no_such_msg);
1710 /* Suppress envelope recipients if required to avoid disclosing BCC addresses.
1711 * Pad it with spaces in order to avoid changing the RFC822 length of the message.
1713 if ( (flags & SUPPRESS_ENV_TO) && (TheMessage->cm_fields['V'] != NULL) ) {
1714 memset(TheMessage->cm_fields['V'], ' ', strlen(TheMessage->cm_fields['V']));
1717 /* Are we downloading a MIME component? */
1718 if (mode == MT_DOWNLOAD) {
1719 if (TheMessage->cm_format_type != FMT_RFC822) {
1721 cprintf("%d This is not a MIME message.\n",
1722 ERROR + ILLEGAL_VALUE);
1723 } else if (CC->download_fp != NULL) {
1724 if (do_proto) cprintf(
1725 "%d You already have a download open.\n",
1726 ERROR + RESOURCE_BUSY);
1728 /* Parse the message text component */
1729 mptr = TheMessage->cm_fields['M'];
1730 mime_parser(mptr, NULL, *mime_download, NULL, NULL, NULL, 0);
1731 /* If there's no file open by this time, the requested
1732 * section wasn't found, so print an error
1734 if (CC->download_fp == NULL) {
1735 if (do_proto) cprintf(
1736 "%d Section %s not found.\n",
1737 ERROR + FILE_NOT_FOUND,
1738 CC->download_desired_section);
1741 return((CC->download_fp != NULL) ? om_ok : om_mime_error);
1744 /* MT_SPEW_SECTION is like MT_DOWNLOAD except it outputs the whole MIME part
1745 * in a single server operation instead of opening a download file.
1747 if (mode == MT_SPEW_SECTION) {
1748 if (TheMessage->cm_format_type != FMT_RFC822) {
1750 cprintf("%d This is not a MIME message.\n",
1751 ERROR + ILLEGAL_VALUE);
1753 /* Parse the message text component */
1756 mptr = TheMessage->cm_fields['M'];
1757 mime_parser(mptr, NULL, *mime_spew_section, NULL, NULL, (void *)&found_it, 0);
1758 /* If section wasn't found, print an error
1761 if (do_proto) cprintf(
1762 "%d Section %s not found.\n",
1763 ERROR + FILE_NOT_FOUND,
1764 CC->download_desired_section);
1767 return((CC->download_fp != NULL) ? om_ok : om_mime_error);
1770 /* now for the user-mode message reading loops */
1771 if (do_proto) cprintf("%d msg:\n", LISTING_FOLLOWS);
1773 /* Does the caller want to skip the headers? */
1774 if (headers_only == HEADERS_NONE) goto START_TEXT;
1776 /* Tell the client which format type we're using. */
1777 if ( (mode == MT_CITADEL) && (do_proto) ) {
1778 cprintf("type=%d\n", TheMessage->cm_format_type);
1781 /* nhdr=yes means that we're only displaying headers, no body */
1782 if ( (TheMessage->cm_anon_type == MES_ANONONLY)
1783 && ((mode == MT_CITADEL) || (mode == MT_MIME))
1786 cprintf("nhdr=yes\n");
1789 /* begin header processing loop for Citadel message format */
1791 if ((mode == MT_CITADEL) || (mode == MT_MIME)) {
1793 safestrncpy(display_name, "<unknown>", sizeof display_name);
1794 if (TheMessage->cm_fields['A']) {
1795 strcpy(buf, TheMessage->cm_fields['A']);
1796 if (TheMessage->cm_anon_type == MES_ANONONLY) {
1797 safestrncpy(display_name, "****", sizeof display_name);
1799 else if (TheMessage->cm_anon_type == MES_ANONOPT) {
1800 safestrncpy(display_name, "anonymous", sizeof display_name);
1803 safestrncpy(display_name, buf, sizeof display_name);
1805 if ((is_room_aide())
1806 && ((TheMessage->cm_anon_type == MES_ANONONLY)
1807 || (TheMessage->cm_anon_type == MES_ANONOPT))) {
1808 size_t tmp = strlen(display_name);
1809 snprintf(&display_name[tmp],
1810 sizeof display_name - tmp,
1815 /* Don't show Internet address for users on the
1816 * local Citadel network.
1819 if (TheMessage->cm_fields['N'] != NULL)
1820 if (!IsEmptyStr(TheMessage->cm_fields['N']))
1821 if (haschar(TheMessage->cm_fields['N'], '.') == 0) {
1825 /* Now spew the header fields in the order we like them. */
1826 safestrncpy(allkeys, FORDER, sizeof allkeys);
1827 for (i=0; i<strlen(allkeys); ++i) {
1828 k = (int) allkeys[i];
1830 if ( (TheMessage->cm_fields[k] != NULL)
1831 && (msgkeys[k] != NULL) ) {
1832 if ((k == 'V') || (k == 'R') || (k == 'Y')) {
1833 sanitize_truncated_recipient(TheMessage->cm_fields[k]);
1836 if (do_proto) cprintf("%s=%s\n",
1840 else if ((k == 'F') && (suppress_f)) {
1843 /* Masquerade display name if needed */
1845 if (do_proto) cprintf("%s=%s\n",
1847 TheMessage->cm_fields[k]
1856 /* begin header processing loop for RFC822 transfer format */
1861 strcpy(snode, NODENAME);
1862 if (mode == MT_RFC822) {
1863 for (i = 0; i < 256; ++i) {
1864 if (TheMessage->cm_fields[i]) {
1865 mptr = mpptr = TheMessage->cm_fields[i];
1868 safestrncpy(luser, mptr, sizeof luser);
1869 safestrncpy(suser, mptr, sizeof suser);
1871 else if (i == 'Y') {
1872 if ((flags & QP_EADDR) != 0) {
1873 mptr = qp_encode_email_addrs(mptr);
1875 sanitize_truncated_recipient(mptr);
1876 cprintf("CC: %s%s", mptr, nl);
1878 else if (i == 'P') {
1879 cprintf("Return-Path: %s%s", mptr, nl);
1881 else if (i == 'L') {
1882 cprintf("List-ID: %s%s", mptr, nl);
1884 else if (i == 'V') {
1885 if ((flags & QP_EADDR) != 0)
1886 mptr = qp_encode_email_addrs(mptr);
1887 cprintf("Envelope-To: %s%s", mptr, nl);
1889 else if (i == 'U') {
1890 cprintf("Subject: %s%s", mptr, nl);
1894 safestrncpy(mid, mptr, sizeof mid);
1896 safestrncpy(fuser, mptr, sizeof fuser);
1897 /* else if (i == 'O')
1898 cprintf("X-Citadel-Room: %s%s",
1901 safestrncpy(snode, mptr, sizeof snode);
1904 if (haschar(mptr, '@') == 0)
1906 sanitize_truncated_recipient(mptr);
1907 cprintf("To: %s@%s", mptr, config.c_fqdn);
1912 if ((flags & QP_EADDR) != 0) {
1913 mptr = qp_encode_email_addrs(mptr);
1915 sanitize_truncated_recipient(mptr);
1916 cprintf("To: %s", mptr);
1920 else if (i == 'T') {
1921 datestring(datestamp, sizeof datestamp,
1922 atol(mptr), DATESTRING_RFC822);
1923 cprintf("Date: %s%s", datestamp, nl);
1925 else if (i == 'W') {
1926 cprintf("References: ");
1927 k = num_tokens(mptr, '|');
1928 for (j=0; j<k; ++j) {
1929 extract_token(buf, mptr, j, '|', sizeof buf);
1930 cprintf("<%s>", buf);
1943 if (subject_found == 0) {
1944 cprintf("Subject: (no subject)%s", nl);
1948 for (i=0; !IsEmptyStr(&suser[i]); ++i) {
1949 suser[i] = tolower(suser[i]);
1950 if (!isalnum(suser[i])) suser[i]='_';
1953 if (mode == MT_RFC822) {
1954 if (!strcasecmp(snode, NODENAME)) {
1955 safestrncpy(snode, FQDN, sizeof snode);
1958 /* Construct a fun message id */
1959 cprintf("Message-ID: <%s", mid);/// todo: this possibly breaks threadding mails.
1960 if (strchr(mid, '@')==NULL) {
1961 cprintf("@%s", snode);
1965 if (!is_room_aide() && (TheMessage->cm_anon_type == MES_ANONONLY)) {
1966 cprintf("From: \"----\" <x@x.org>%s", nl);
1968 else if (!is_room_aide() && (TheMessage->cm_anon_type == MES_ANONOPT)) {
1969 cprintf("From: \"anonymous\" <x@x.org>%s", nl);
1971 else if (!IsEmptyStr(fuser)) {
1972 cprintf("From: \"%s\" <%s>%s", luser, fuser, nl);
1975 cprintf("From: \"%s\" <%s@%s>%s", luser, suser, snode, nl);
1978 /* Blank line signifying RFC822 end-of-headers */
1979 if (TheMessage->cm_format_type != FMT_RFC822) {
1984 /* end header processing loop ... at this point, we're in the text */
1986 if (headers_only == HEADERS_FAST) goto DONE;
1987 mptr = TheMessage->cm_fields['M'];
1989 /* Tell the client about the MIME parts in this message */
1990 if (TheMessage->cm_format_type == FMT_RFC822) {
1991 if ( (mode == MT_CITADEL) || (mode == MT_MIME) ) {
1992 memset(&ma, 0, sizeof(struct ma_info));
1993 mime_parser(mptr, NULL,
1994 (do_proto ? *list_this_part : NULL),
1995 (do_proto ? *list_this_pref : NULL),
1996 (do_proto ? *list_this_suff : NULL),
1999 else if (mode == MT_RFC822) { /* unparsed RFC822 dump */
2000 char *start_of_text = NULL;
2001 start_of_text = strstr(mptr, "\n\r\n");
2002 if (start_of_text == NULL) start_of_text = strstr(mptr, "\n\n");
2003 if (start_of_text == NULL) start_of_text = mptr;
2005 start_of_text = strstr(start_of_text, "\n");
2010 int nllen = strlen(nl);
2012 while (ch=*mptr, ch!=0) {
2018 ((headers_only == HEADERS_NONE) && (mptr >= start_of_text))
2019 || ((headers_only == HEADERS_ONLY) && (mptr < start_of_text))
2020 || ((headers_only != HEADERS_NONE) && (headers_only != HEADERS_ONLY))
2023 sprintf(&outbuf[outlen], "%s", nl);
2027 outbuf[outlen++] = ch;
2031 if (flags & ESC_DOT)
2033 if ((prev_ch == 10) && (ch == '.') && ((*(mptr+1) == 13) || (*(mptr+1) == 10)))
2035 outbuf[outlen++] = '.';
2040 if (outlen > 1000) {
2041 client_write(outbuf, outlen);
2046 client_write(outbuf, outlen);
2054 if (headers_only == HEADERS_ONLY) {
2058 /* signify start of msg text */
2059 if ( (mode == MT_CITADEL) || (mode == MT_MIME) ) {
2060 if (do_proto) cprintf("text\n");
2063 /* If the format type on disk is 1 (fixed-format), then we want
2064 * everything to be output completely literally ... regardless of
2065 * what message transfer format is in use.
2067 if (TheMessage->cm_format_type == FMT_FIXED) {
2069 if (mode == MT_MIME) {
2070 cprintf("Content-type: text/plain\n\n");
2074 while (ch = *mptr++, ch > 0) {
2077 if ((ch == 10) || (buflen > 250)) {
2079 cprintf("%s%s", buf, nl);
2088 if (!IsEmptyStr(buf))
2089 cprintf("%s%s", buf, nl);
2092 /* If the message on disk is format 0 (Citadel vari-format), we
2093 * output using the formatter at 80 columns. This is the final output
2094 * form if the transfer format is RFC822, but if the transfer format
2095 * is Citadel proprietary, it'll still work, because the indentation
2096 * for new paragraphs is correct and the client will reformat the
2097 * message to the reader's screen width.
2099 if (TheMessage->cm_format_type == FMT_CITADEL) {
2100 if (mode == MT_MIME) {
2101 cprintf("Content-type: text/x-citadel-variformat\n\n");
2103 memfmout(mptr, 0, nl);
2106 /* If the message on disk is format 4 (MIME), we've gotta hand it
2107 * off to the MIME parser. The client has already been told that
2108 * this message is format 1 (fixed format), so the callback function
2109 * we use will display those parts as-is.
2111 if (TheMessage->cm_format_type == FMT_RFC822) {
2112 memset(&ma, 0, sizeof(struct ma_info));
2114 if (mode == MT_MIME) {
2115 ma.use_fo_hooks = 0;
2116 strcpy(ma.chosen_part, "1");
2117 ma.chosen_pref = 9999;
2118 mime_parser(mptr, NULL,
2119 *choose_preferred, *fixed_output_pre,
2120 *fixed_output_post, (void *)&ma, 0);
2121 mime_parser(mptr, NULL,
2122 *output_preferred, NULL, NULL, (void *)&ma, CC->msg4_dont_decode);
2125 ma.use_fo_hooks = 1;
2126 mime_parser(mptr, NULL,
2127 *fixed_output, *fixed_output_pre,
2128 *fixed_output_post, (void *)&ma, 0);
2133 DONE: /* now we're done */
2134 if (do_proto) cprintf("000\n");
2141 * display a message (mode 0 - Citadel proprietary)
2143 void cmd_msg0(char *cmdbuf)
2146 int headers_only = HEADERS_ALL;
2148 msgid = extract_long(cmdbuf, 0);
2149 headers_only = extract_int(cmdbuf, 1);
2151 CtdlOutputMsg(msgid, MT_CITADEL, headers_only, 1, 0, NULL, 0);
2157 * display a message (mode 2 - RFC822)
2159 void cmd_msg2(char *cmdbuf)
2162 int headers_only = HEADERS_ALL;
2164 msgid = extract_long(cmdbuf, 0);
2165 headers_only = extract_int(cmdbuf, 1);
2167 CtdlOutputMsg(msgid, MT_RFC822, headers_only, 1, 1, NULL, 0);
2173 * display a message (mode 3 - IGnet raw format - internal programs only)
2175 void cmd_msg3(char *cmdbuf)
2178 struct CtdlMessage *msg = NULL;
2181 if (CC->internal_pgm == 0) {
2182 cprintf("%d This command is for internal programs only.\n",
2183 ERROR + HIGHER_ACCESS_REQUIRED);
2187 msgnum = extract_long(cmdbuf, 0);
2188 msg = CtdlFetchMessage(msgnum, 1);
2190 cprintf("%d Message %ld not found.\n",
2191 ERROR + MESSAGE_NOT_FOUND, msgnum);
2195 serialize_message(&smr, msg);
2196 CtdlFreeMessage(msg);
2199 cprintf("%d Unable to serialize message\n",
2200 ERROR + INTERNAL_ERROR);
2204 cprintf("%d %ld\n", BINARY_FOLLOWS, (long)smr.len);
2205 client_write((char *)smr.ser, (int)smr.len);
2212 * Display a message using MIME content types
2214 void cmd_msg4(char *cmdbuf)
2219 msgid = extract_long(cmdbuf, 0);
2220 extract_token(section, cmdbuf, 1, '|', sizeof section);
2221 CtdlOutputMsg(msgid, MT_MIME, 0, 1, 0, (section[0] ? section : NULL) , 0);
2227 * Client tells us its preferred message format(s)
2229 void cmd_msgp(char *cmdbuf)
2231 if (!strcasecmp(cmdbuf, "dont_decode")) {
2232 CC->msg4_dont_decode = 1;
2233 cprintf("%d MSG4 will not pre-decode messages.\n", CIT_OK);
2236 safestrncpy(CC->preferred_formats, cmdbuf, sizeof(CC->preferred_formats));
2237 cprintf("%d Preferred MIME formats have been set.\n", CIT_OK);
2243 * Open a component of a MIME message as a download file
2245 void cmd_opna(char *cmdbuf)
2248 char desired_section[128];
2250 msgid = extract_long(cmdbuf, 0);
2251 extract_token(desired_section, cmdbuf, 1, '|', sizeof desired_section);
2252 safestrncpy(CC->download_desired_section, desired_section,
2253 sizeof CC->download_desired_section);
2254 CtdlOutputMsg(msgid, MT_DOWNLOAD, 0, 1, 1, NULL, 0);
2259 * Open a component of a MIME message and transmit it all at once
2261 void cmd_dlat(char *cmdbuf)
2264 char desired_section[128];
2266 msgid = extract_long(cmdbuf, 0);
2267 extract_token(desired_section, cmdbuf, 1, '|', sizeof desired_section);
2268 safestrncpy(CC->download_desired_section, desired_section,
2269 sizeof CC->download_desired_section);
2270 CtdlOutputMsg(msgid, MT_SPEW_SECTION, 0, 1, 1, NULL, 0);
2275 * Save one or more message pointers into a specified room
2276 * (Returns 0 for success, nonzero for failure)
2277 * roomname may be NULL to use the current room
2279 * Note that the 'supplied_msg' field may be set to NULL, in which case
2280 * the message will be fetched from disk, by number, if we need to perform
2281 * replication checks. This adds an additional database read, so if the
2282 * caller already has the message in memory then it should be supplied. (Obviously
2283 * this mode of operation only works if we're saving a single message.)
2285 int CtdlSaveMsgPointersInRoom(char *roomname, long newmsgidlist[], int num_newmsgs,
2286 int do_repl_check, struct CtdlMessage *supplied_msg)
2289 char hold_rm[ROOMNAMELEN];
2290 struct cdbdata *cdbfr;
2293 long highest_msg = 0L;
2296 struct CtdlMessage *msg = NULL;
2298 long *msgs_to_be_merged = NULL;
2299 int num_msgs_to_be_merged = 0;
2301 CtdlLogPrintf(CTDL_DEBUG,
2302 "CtdlSaveMsgPointersInRoom(room=%s, num_msgs=%d, repl=%d)\n",
2303 roomname, num_newmsgs, do_repl_check);
2305 strcpy(hold_rm, CC->room.QRname);
2308 if (newmsgidlist == NULL) return(ERROR + INTERNAL_ERROR);
2309 if (num_newmsgs < 1) return(ERROR + INTERNAL_ERROR);
2310 if (num_newmsgs > 1) supplied_msg = NULL;
2312 /* Now the regular stuff */
2313 if (CtdlGetRoomLock(&CC->room,
2314 ((roomname != NULL) ? roomname : CC->room.QRname) )
2316 CtdlLogPrintf(CTDL_ERR, "No such room <%s>\n", roomname);
2317 return(ERROR + ROOM_NOT_FOUND);
2321 msgs_to_be_merged = malloc(sizeof(long) * num_newmsgs);
2322 num_msgs_to_be_merged = 0;
2325 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long));
2326 if (cdbfr == NULL) {
2330 msglist = (long *) cdbfr->ptr;
2331 cdbfr->ptr = NULL; /* CtdlSaveMsgPointerInRoom() now owns this memory */
2332 num_msgs = cdbfr->len / sizeof(long);
2337 /* Create a list of msgid's which were supplied by the caller, but do
2338 * not already exist in the target room. It is absolutely taboo to
2339 * have more than one reference to the same message in a room.
2341 for (i=0; i<num_newmsgs; ++i) {
2343 if (num_msgs > 0) for (j=0; j<num_msgs; ++j) {
2344 if (msglist[j] == newmsgidlist[i]) {
2349 msgs_to_be_merged[num_msgs_to_be_merged++] = newmsgidlist[i];
2353 CtdlLogPrintf(9, "%d unique messages to be merged\n", num_msgs_to_be_merged);
2356 * Now merge the new messages
2358 msglist = realloc(msglist, (sizeof(long) * (num_msgs + num_msgs_to_be_merged)) );
2359 if (msglist == NULL) {
2360 CtdlLogPrintf(CTDL_ALERT, "ERROR: can't realloc message list!\n");
2362 memcpy(&msglist[num_msgs], msgs_to_be_merged, (sizeof(long) * num_msgs_to_be_merged) );
2363 num_msgs += num_msgs_to_be_merged;
2365 /* Sort the message list, so all the msgid's are in order */
2366 num_msgs = sort_msglist(msglist, num_msgs);
2368 /* Determine the highest message number */
2369 highest_msg = msglist[num_msgs - 1];
2371 /* Write it back to disk. */
2372 cdb_store(CDB_MSGLISTS, &CC->room.QRnumber, (int)sizeof(long),
2373 msglist, (int)(num_msgs * sizeof(long)));
2375 /* Free up the memory we used. */
2378 /* Update the highest-message pointer and unlock the room. */
2379 CC->room.QRhighest = highest_msg;
2380 CtdlPutRoomLock(&CC->room);
2382 /* Perform replication checks if necessary */
2383 if ( (DoesThisRoomNeedEuidIndexing(&CC->room)) && (do_repl_check) ) {
2384 CtdlLogPrintf(CTDL_DEBUG, "CtdlSaveMsgPointerInRoom() doing repl checks\n");
2386 for (i=0; i<num_msgs_to_be_merged; ++i) {
2387 msgid = msgs_to_be_merged[i];
2389 if (supplied_msg != NULL) {
2393 msg = CtdlFetchMessage(msgid, 0);
2397 ReplicationChecks(msg);
2399 /* If the message has an Exclusive ID, index that... */
2400 if (msg->cm_fields['E'] != NULL) {
2401 index_message_by_euid(msg->cm_fields['E'], &CC->room, msgid);
2404 /* Free up the memory we may have allocated */
2405 if (msg != supplied_msg) {
2406 CtdlFreeMessage(msg);
2414 CtdlLogPrintf(CTDL_DEBUG, "CtdlSaveMsgPointerInRoom() skips repl checks\n");
2417 /* Submit this room for processing by hooks */
2418 PerformRoomHooks(&CC->room);
2420 /* Go back to the room we were in before we wandered here... */
2421 CtdlGetRoom(&CC->room, hold_rm);
2423 /* Bump the reference count for all messages which were merged */
2424 for (i=0; i<num_msgs_to_be_merged; ++i) {
2425 AdjRefCount(msgs_to_be_merged[i], +1);
2428 /* Free up memory... */
2429 if (msgs_to_be_merged != NULL) {
2430 free(msgs_to_be_merged);
2433 /* Return success. */
2439 * This is the same as CtdlSaveMsgPointersInRoom() but it only accepts
2442 int CtdlSaveMsgPointerInRoom(char *roomname, long msgid,
2443 int do_repl_check, struct CtdlMessage *supplied_msg)
2445 return CtdlSaveMsgPointersInRoom(roomname, &msgid, 1, do_repl_check, supplied_msg);
2452 * Message base operation to save a new message to the message store
2453 * (returns new message number)
2455 * This is the back end for CtdlSubmitMsg() and should not be directly
2456 * called by server-side modules.
2459 long send_message(struct CtdlMessage *msg) {
2467 /* Get a new message number */
2468 newmsgid = get_new_message_number();
2469 snprintf(msgidbuf, sizeof msgidbuf, "%010ld@%s", newmsgid, config.c_fqdn);
2471 /* Generate an ID if we don't have one already */
2472 if (msg->cm_fields['I']==NULL) {
2473 msg->cm_fields['I'] = strdup(msgidbuf);
2476 /* If the message is big, set its body aside for storage elsewhere */
2477 if (msg->cm_fields['M'] != NULL) {
2478 if (strlen(msg->cm_fields['M']) > BIGMSG) {
2480 holdM = msg->cm_fields['M'];
2481 msg->cm_fields['M'] = NULL;
2485 /* Serialize our data structure for storage in the database */
2486 serialize_message(&smr, msg);
2489 msg->cm_fields['M'] = holdM;
2493 cprintf("%d Unable to serialize message\n",
2494 ERROR + INTERNAL_ERROR);
2498 /* Write our little bundle of joy into the message base */
2499 if (cdb_store(CDB_MSGMAIN, &newmsgid, (int)sizeof(long),
2500 smr.ser, smr.len) < 0) {
2501 CtdlLogPrintf(CTDL_ERR, "Can't store message\n");
2505 cdb_store(CDB_BIGMSGS,
2515 /* Free the memory we used for the serialized message */
2518 /* Return the *local* message ID to the caller
2519 * (even if we're storing an incoming network message)
2527 * Serialize a struct CtdlMessage into the format used on disk and network.
2529 * This function loads up a "struct ser_ret" (defined in server.h) which
2530 * contains the length of the serialized message and a pointer to the
2531 * serialized message in memory. THE LATTER MUST BE FREED BY THE CALLER.
2533 void serialize_message(struct ser_ret *ret, /* return values */
2534 struct CtdlMessage *msg) /* unserialized msg */
2536 size_t wlen, fieldlen;
2538 static char *forder = FORDER;
2541 * Check for valid message format
2543 if (is_valid_message(msg) == 0) {
2544 CtdlLogPrintf(CTDL_ERR, "serialize_message() aborting due to invalid message\n");
2551 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL)
2552 ret->len = ret->len +
2553 strlen(msg->cm_fields[(int)forder[i]]) + 2;
2555 ret->ser = malloc(ret->len);
2556 if (ret->ser == NULL) {
2557 CtdlLogPrintf(CTDL_ERR, "serialize_message() malloc(%ld) failed: %s\n",
2558 (long)ret->len, strerror(errno));
2565 ret->ser[1] = msg->cm_anon_type;
2566 ret->ser[2] = msg->cm_format_type;
2569 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL) {
2570 fieldlen = strlen(msg->cm_fields[(int)forder[i]]);
2571 ret->ser[wlen++] = (char)forder[i];
2572 safestrncpy((char *)&ret->ser[wlen], msg->cm_fields[(int)forder[i]], fieldlen+1);
2573 wlen = wlen + fieldlen + 1;
2575 if (ret->len != wlen) CtdlLogPrintf(CTDL_ERR, "ERROR: len=%ld wlen=%ld\n",
2576 (long)ret->len, (long)wlen);
2583 * Serialize a struct CtdlMessage into the format used on disk and network.
2585 * This function loads up a "struct ser_ret" (defined in server.h) which
2586 * contains the length of the serialized message and a pointer to the
2587 * serialized message in memory. THE LATTER MUST BE FREED BY THE CALLER.
2589 void dump_message(struct CtdlMessage *msg, /* unserialized msg */
2590 long Siz) /* how many chars ? */
2594 static char *forder = FORDER;
2598 * Check for valid message format
2600 if (is_valid_message(msg) == 0) {
2601 CtdlLogPrintf(CTDL_ERR, "dump_message() aborting due to invalid message\n");
2605 buf = (char*) malloc (Siz + 1);
2609 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL) {
2610 snprintf (buf, Siz, " msg[%c] = %s ...\n", (char) forder[i],
2611 msg->cm_fields[(int)forder[i]]);
2612 client_write (buf, strlen(buf));
2621 * Check to see if any messages already exist in the current room which
2622 * carry the same Exclusive ID as this one. If any are found, delete them.
2624 void ReplicationChecks(struct CtdlMessage *msg) {
2625 long old_msgnum = (-1L);
2627 if (DoesThisRoomNeedEuidIndexing(&CC->room) == 0) return;
2629 CtdlLogPrintf(CTDL_DEBUG, "Performing replication checks in <%s>\n",
2632 /* No exclusive id? Don't do anything. */
2633 if (msg == NULL) return;
2634 if (msg->cm_fields['E'] == NULL) return;
2635 if (IsEmptyStr(msg->cm_fields['E'])) return;
2636 /*CtdlLogPrintf(CTDL_DEBUG, "Exclusive ID: <%s> for room <%s>\n",
2637 msg->cm_fields['E'], CC->room.QRname);*/
2639 old_msgnum = locate_message_by_euid(msg->cm_fields['E'], &CC->room);
2640 if (old_msgnum > 0L) {
2641 CtdlLogPrintf(CTDL_DEBUG, "ReplicationChecks() replacing message %ld\n", old_msgnum);
2642 CtdlDeleteMessages(CC->room.QRname, &old_msgnum, 1, "");
2649 * Save a message to disk and submit it into the delivery system.
2651 long CtdlSubmitMsg(struct CtdlMessage *msg, /* message to save */
2652 struct recptypes *recps, /* recipients (if mail) */
2653 char *force, /* force a particular room? */
2654 int flags /* should the bessage be exported clean? */
2656 char submit_filename[128];
2657 char generated_timestamp[32];
2658 char hold_rm[ROOMNAMELEN];
2659 char actual_rm[ROOMNAMELEN];
2660 char force_room[ROOMNAMELEN];
2661 char content_type[SIZ]; /* We have to learn this */
2662 char recipient[SIZ];
2665 struct ctdluser userbuf;
2667 struct MetaData smi;
2668 FILE *network_fp = NULL;
2669 static int seqnum = 1;
2670 struct CtdlMessage *imsg = NULL;
2672 size_t instr_alloc = 0;
2674 char *hold_R, *hold_D;
2675 char *collected_addresses = NULL;
2676 struct addresses_to_be_filed *aptr = NULL;
2677 char *saved_rfc822_version = NULL;
2678 int qualified_for_journaling = 0;
2679 struct CitContext *CCC = CC; /* CachedCitContext - performance boost */
2680 char bounce_to[1024] = "";
2684 CtdlLogPrintf(CTDL_DEBUG, "CtdlSubmitMsg() called\n");
2685 if (is_valid_message(msg) == 0) return(-1); /* self check */
2687 /* If this message has no timestamp, we take the liberty of
2688 * giving it one, right now.
2690 if (msg->cm_fields['T'] == NULL) {
2691 snprintf(generated_timestamp, sizeof generated_timestamp, "%ld", (long)time(NULL));
2692 msg->cm_fields['T'] = strdup(generated_timestamp);
2695 /* If this message has no path, we generate one.
2697 if (msg->cm_fields['P'] == NULL) {
2698 if (msg->cm_fields['A'] != NULL) {
2699 msg->cm_fields['P'] = strdup(msg->cm_fields['A']);
2700 for (a=0; !IsEmptyStr(&msg->cm_fields['P'][a]); ++a) {
2701 if (isspace(msg->cm_fields['P'][a])) {
2702 msg->cm_fields['P'][a] = ' ';
2707 msg->cm_fields['P'] = strdup("unknown");
2711 if (force == NULL) {
2712 strcpy(force_room, "");
2715 strcpy(force_room, force);
2718 /* Learn about what's inside, because it's what's inside that counts */
2719 if (msg->cm_fields['M'] == NULL) {
2720 CtdlLogPrintf(CTDL_ERR, "ERROR: attempt to save message with NULL body\n");
2724 switch (msg->cm_format_type) {
2726 strcpy(content_type, "text/x-citadel-variformat");
2729 strcpy(content_type, "text/plain");
2732 strcpy(content_type, "text/plain");
2733 mptr = bmstrcasestr(msg->cm_fields['M'], "Content-type:");
2736 safestrncpy(content_type, &mptr[13], sizeof content_type);
2737 striplt(content_type);
2738 aptr = content_type;
2739 while (!IsEmptyStr(aptr)) {
2751 /* Goto the correct room */
2752 CtdlLogPrintf(CTDL_DEBUG, "Selected room %s\n", (recps) ? CCC->room.QRname : SENTITEMS);
2753 strcpy(hold_rm, CCC->room.QRname);
2754 strcpy(actual_rm, CCC->room.QRname);
2755 if (recps != NULL) {
2756 strcpy(actual_rm, SENTITEMS);
2759 /* If the user is a twit, move to the twit room for posting */
2761 if (CCC->user.axlevel == 2) {
2762 strcpy(hold_rm, actual_rm);
2763 strcpy(actual_rm, config.c_twitroom);
2764 CtdlLogPrintf(CTDL_DEBUG, "Diverting to twit room\n");
2768 /* ...or if this message is destined for Aide> then go there. */
2769 if (!IsEmptyStr(force_room)) {
2770 strcpy(actual_rm, force_room);
2773 CtdlLogPrintf(CTDL_DEBUG, "Final selection: %s\n", actual_rm);
2774 if (strcasecmp(actual_rm, CCC->room.QRname)) {
2775 /* CtdlGetRoom(&CCC->room, actual_rm); */
2776 CtdlUserGoto(actual_rm, 0, 1, NULL, NULL);
2780 * If this message has no O (room) field, generate one.
2782 if (msg->cm_fields['O'] == NULL) {
2783 msg->cm_fields['O'] = strdup(CCC->room.QRname);
2786 /* Perform "before save" hooks (aborting if any return nonzero) */
2787 CtdlLogPrintf(CTDL_DEBUG, "Performing before-save hooks\n");
2788 if (PerformMessageHooks(msg, EVT_BEFORESAVE) > 0) return(-3);
2791 * If this message has an Exclusive ID, and the room is replication
2792 * checking enabled, then do replication checks.
2794 if (DoesThisRoomNeedEuidIndexing(&CCC->room)) {
2795 ReplicationChecks(msg);
2798 /* Save it to disk */
2799 CtdlLogPrintf(CTDL_DEBUG, "Saving to disk\n");
2800 newmsgid = send_message(msg);
2801 if (newmsgid <= 0L) return(-5);
2803 /* Write a supplemental message info record. This doesn't have to
2804 * be a critical section because nobody else knows about this message
2807 CtdlLogPrintf(CTDL_DEBUG, "Creating MetaData record\n");
2808 memset(&smi, 0, sizeof(struct MetaData));
2809 smi.meta_msgnum = newmsgid;
2810 smi.meta_refcount = 0;
2811 safestrncpy(smi.meta_content_type, content_type,
2812 sizeof smi.meta_content_type);
2815 * Measure how big this message will be when rendered as RFC822.
2816 * We do this for two reasons:
2817 * 1. We need the RFC822 length for the new metadata record, so the
2818 * POP and IMAP services don't have to calculate message lengths
2819 * while the user is waiting (multiplied by potentially hundreds
2820 * or thousands of messages).
2821 * 2. If journaling is enabled, we will need an RFC822 version of the
2822 * message to attach to the journalized copy.
2824 if (CCC->redirect_buffer != NULL) {
2825 CtdlLogPrintf(CTDL_ALERT, "CCC->redirect_buffer is not NULL during message submission!\n");
2828 CCC->redirect_buffer = malloc(SIZ);
2829 CCC->redirect_len = 0;
2830 CCC->redirect_alloc = SIZ;
2831 CtdlOutputPreLoadedMsg(msg, MT_RFC822, HEADERS_ALL, 0, 1, QP_EADDR);
2832 smi.meta_rfc822_length = CCC->redirect_len;
2833 saved_rfc822_version = CCC->redirect_buffer;
2834 CCC->redirect_buffer = NULL;
2835 CCC->redirect_len = 0;
2836 CCC->redirect_alloc = 0;
2840 /* Now figure out where to store the pointers */
2841 CtdlLogPrintf(CTDL_DEBUG, "Storing pointers\n");
2843 /* If this is being done by the networker delivering a private
2844 * message, we want to BYPASS saving the sender's copy (because there
2845 * is no local sender; it would otherwise go to the Trashcan).
2847 if ((!CCC->internal_pgm) || (recps == NULL)) {
2848 if (CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 1, msg) != 0) {
2849 CtdlLogPrintf(CTDL_ERR, "ERROR saving message pointer!\n");
2850 CtdlSaveMsgPointerInRoom(config.c_aideroom, newmsgid, 0, msg);
2854 /* For internet mail, drop a copy in the outbound queue room */
2855 if ((recps != NULL) && (recps->num_internet > 0)) {
2856 CtdlSaveMsgPointerInRoom(SMTP_SPOOLOUT_ROOM, newmsgid, 0, msg);
2859 /* If other rooms are specified, drop them there too. */
2860 if ((recps != NULL) && (recps->num_room > 0))
2861 for (i=0; i<num_tokens(recps->recp_room, '|'); ++i) {
2862 extract_token(recipient, recps->recp_room, i,
2863 '|', sizeof recipient);
2864 CtdlLogPrintf(CTDL_DEBUG, "Delivering to room <%s>\n", recipient);
2865 CtdlSaveMsgPointerInRoom(recipient, newmsgid, 0, msg);
2868 /* Bump this user's messages posted counter. */
2869 CtdlLogPrintf(CTDL_DEBUG, "Updating user\n");
2870 lgetuser(&CCC->user, CCC->curr_user);
2871 CCC->user.posted = CCC->user.posted + 1;
2872 lputuser(&CCC->user);
2874 /* Decide where bounces need to be delivered */
2875 if ((recps != NULL) && (recps->bounce_to != NULL)) {
2876 safestrncpy(bounce_to, recps->bounce_to, sizeof bounce_to);
2878 else if (CCC->logged_in) {
2879 snprintf(bounce_to, sizeof bounce_to, "%s@%s", CCC->user.fullname, config.c_nodename);
2882 snprintf(bounce_to, sizeof bounce_to, "%s@%s", msg->cm_fields['A'], msg->cm_fields['N']);
2885 /* If this is private, local mail, make a copy in the
2886 * recipient's mailbox and bump the reference count.
2888 if ((recps != NULL) && (recps->num_local > 0))
2889 for (i=0; i<num_tokens(recps->recp_local, '|'); ++i) {
2890 extract_token(recipient, recps->recp_local, i,
2891 '|', sizeof recipient);
2892 CtdlLogPrintf(CTDL_DEBUG, "Delivering private local mail to <%s>\n",
2894 if (getuser(&userbuf, recipient) == 0) {
2895 // Add a flag so the Funambol module knows its mail
2896 msg->cm_fields['W'] = strdup(recipient);
2897 MailboxName(actual_rm, sizeof actual_rm, &userbuf, MAILROOM);
2898 CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 0, msg);
2899 BumpNewMailCounter(userbuf.usernum);
2900 if (!IsEmptyStr(config.c_funambol_host) || !IsEmptyStr(config.c_pager_program)) {
2901 /* Generate a instruction message for the Funambol notification
2902 * server, in the same style as the SMTP queue
2905 instr = malloc(instr_alloc);
2906 snprintf(instr, instr_alloc,
2907 "Content-type: %s\n\nmsgid|%ld\nsubmitted|%ld\n"
2909 SPOOLMIME, newmsgid, (long)time(NULL),
2913 imsg = malloc(sizeof(struct CtdlMessage));
2914 memset(imsg, 0, sizeof(struct CtdlMessage));
2915 imsg->cm_magic = CTDLMESSAGE_MAGIC;
2916 imsg->cm_anon_type = MES_NORMAL;
2917 imsg->cm_format_type = FMT_RFC822;
2918 imsg->cm_fields['A'] = strdup("Citadel");
2919 imsg->cm_fields['J'] = strdup("do not journal");
2920 imsg->cm_fields['M'] = instr; /* imsg owns this memory now */
2921 imsg->cm_fields['W'] = strdup(recipient);
2922 CtdlSubmitMsg(imsg, NULL, FNBL_QUEUE_ROOM, 0);
2923 CtdlFreeMessage(imsg);
2927 CtdlLogPrintf(CTDL_DEBUG, "No user <%s>\n", recipient);
2928 CtdlSaveMsgPointerInRoom(config.c_aideroom,
2933 /* Perform "after save" hooks */
2934 CtdlLogPrintf(CTDL_DEBUG, "Performing after-save hooks\n");
2935 PerformMessageHooks(msg, EVT_AFTERSAVE);
2937 /* For IGnet mail, we have to save a new copy into the spooler for
2938 * each recipient, with the R and D fields set to the recipient and
2939 * destination-node. This has two ugly side effects: all other
2940 * recipients end up being unlisted in this recipient's copy of the
2941 * message, and it has to deliver multiple messages to the same
2942 * node. We'll revisit this again in a year or so when everyone has
2943 * a network spool receiver that can handle the new style messages.
2945 if ((recps != NULL) && (recps->num_ignet > 0))
2946 for (i=0; i<num_tokens(recps->recp_ignet, '|'); ++i) {
2947 extract_token(recipient, recps->recp_ignet, i,
2948 '|', sizeof recipient);
2950 hold_R = msg->cm_fields['R'];
2951 hold_D = msg->cm_fields['D'];
2952 msg->cm_fields['R'] = malloc(SIZ);
2953 msg->cm_fields['D'] = malloc(128);
2954 extract_token(msg->cm_fields['R'], recipient, 0, '@', SIZ);
2955 extract_token(msg->cm_fields['D'], recipient, 1, '@', 128);
2957 serialize_message(&smr, msg);
2959 snprintf(submit_filename, sizeof submit_filename,
2960 "%s/netmail.%04lx.%04x.%04x",
2962 (long) getpid(), CCC->cs_pid, ++seqnum);
2963 network_fp = fopen(submit_filename, "wb+");
2964 if (network_fp != NULL) {
2965 rv = fwrite(smr.ser, smr.len, 1, network_fp);
2971 free(msg->cm_fields['R']);
2972 free(msg->cm_fields['D']);
2973 msg->cm_fields['R'] = hold_R;
2974 msg->cm_fields['D'] = hold_D;
2977 /* Go back to the room we started from */
2978 CtdlLogPrintf(CTDL_DEBUG, "Returning to original room %s\n", hold_rm);
2979 if (strcasecmp(hold_rm, CCC->room.QRname))
2980 CtdlUserGoto(hold_rm, 0, 1, NULL, NULL);
2982 /* For internet mail, generate delivery instructions.
2983 * Yes, this is recursive. Deal with it. Infinite recursion does
2984 * not happen because the delivery instructions message does not
2985 * contain a recipient.
2987 if ((recps != NULL) && (recps->num_internet > 0)) {
2988 CtdlLogPrintf(CTDL_DEBUG, "Generating delivery instructions\n");
2990 instr = malloc(instr_alloc);
2991 snprintf(instr, instr_alloc,
2992 "Content-type: %s\n\nmsgid|%ld\nsubmitted|%ld\n"
2994 SPOOLMIME, newmsgid, (long)time(NULL),
2998 if (recps->envelope_from != NULL) {
2999 tmp = strlen(instr);
3000 snprintf(&instr[tmp], instr_alloc-tmp, "envelope_from|%s\n", recps->envelope_from);
3003 for (i=0; i<num_tokens(recps->recp_internet, '|'); ++i) {
3004 tmp = strlen(instr);
3005 extract_token(recipient, recps->recp_internet, i, '|', sizeof recipient);
3006 if ((tmp + strlen(recipient) + 32) > instr_alloc) {
3007 instr_alloc = instr_alloc * 2;
3008 instr = realloc(instr, instr_alloc);
3010 snprintf(&instr[tmp], instr_alloc - tmp, "remote|%s|0||\n", recipient);
3013 imsg = malloc(sizeof(struct CtdlMessage));
3014 memset(imsg, 0, sizeof(struct CtdlMessage));
3015 imsg->cm_magic = CTDLMESSAGE_MAGIC;
3016 imsg->cm_anon_type = MES_NORMAL;
3017 imsg->cm_format_type = FMT_RFC822;
3018 imsg->cm_fields['A'] = strdup("Citadel");
3019 imsg->cm_fields['J'] = strdup("do not journal");
3020 imsg->cm_fields['M'] = instr; /* imsg owns this memory now */
3021 CtdlSubmitMsg(imsg, NULL, SMTP_SPOOLOUT_ROOM, QP_EADDR);
3022 CtdlFreeMessage(imsg);
3026 * Any addresses to harvest for someone's address book?
3028 if ( (CCC->logged_in) && (recps != NULL) ) {
3029 collected_addresses = harvest_collected_addresses(msg);
3032 if (collected_addresses != NULL) {
3033 aptr = (struct addresses_to_be_filed *)
3034 malloc(sizeof(struct addresses_to_be_filed));
3035 MailboxName(actual_rm, sizeof actual_rm,
3036 &CCC->user, USERCONTACTSROOM);
3037 aptr->roomname = strdup(actual_rm);
3038 aptr->collected_addresses = collected_addresses;
3039 begin_critical_section(S_ATBF);
3042 end_critical_section(S_ATBF);
3046 * Determine whether this message qualifies for journaling.
3048 if (msg->cm_fields['J'] != NULL) {
3049 qualified_for_journaling = 0;
3052 if (recps == NULL) {
3053 qualified_for_journaling = config.c_journal_pubmsgs;
3055 else if (recps->num_local + recps->num_ignet + recps->num_internet > 0) {
3056 qualified_for_journaling = config.c_journal_email;
3059 qualified_for_journaling = config.c_journal_pubmsgs;
3064 * Do we have to perform journaling? If so, hand off the saved
3065 * RFC822 version will be handed off to the journaler for background
3066 * submit. Otherwise, we have to free the memory ourselves.
3068 if (saved_rfc822_version != NULL) {
3069 if (qualified_for_journaling) {
3070 JournalBackgroundSubmit(msg, saved_rfc822_version, recps);
3073 free(saved_rfc822_version);
3086 * Convenience function for generating small administrative messages.
3088 void quickie_message(const char *from, const char *fromaddr, char *to, char *room, const char *text,
3089 int format_type, const char *subject)
3091 struct CtdlMessage *msg;
3092 struct recptypes *recp = NULL;
3094 msg = malloc(sizeof(struct CtdlMessage));
3095 memset(msg, 0, sizeof(struct CtdlMessage));
3096 msg->cm_magic = CTDLMESSAGE_MAGIC;
3097 msg->cm_anon_type = MES_NORMAL;
3098 msg->cm_format_type = format_type;
3101 msg->cm_fields['A'] = strdup(from);
3103 else if (fromaddr != NULL) {
3104 msg->cm_fields['A'] = strdup(fromaddr);
3105 if (strchr(msg->cm_fields['A'], '@')) {
3106 *strchr(msg->cm_fields['A'], '@') = 0;
3110 msg->cm_fields['A'] = strdup("Citadel");
3113 if (fromaddr != NULL) msg->cm_fields['F'] = strdup(fromaddr);
3114 if (room != NULL) msg->cm_fields['O'] = strdup(room);
3115 msg->cm_fields['N'] = strdup(NODENAME);
3117 msg->cm_fields['R'] = strdup(to);
3118 recp = validate_recipients(to, NULL, 0);
3120 if (subject != NULL) {
3121 msg->cm_fields['U'] = strdup(subject);
3123 msg->cm_fields['M'] = strdup(text);
3125 CtdlSubmitMsg(msg, recp, room, 0);
3126 CtdlFreeMessage(msg);
3127 if (recp != NULL) free_recipients(recp);
3133 * Back end function used by CtdlMakeMessage() and similar functions
3135 char *CtdlReadMessageBody(char *terminator, /* token signalling EOT */
3136 size_t maxlen, /* maximum message length */
3137 char *exist, /* if non-null, append to it;
3138 exist is ALWAYS freed */
3139 int crlf, /* CRLF newlines instead of LF */
3140 int sock /* socket handle or 0 for this session's client socket */
3144 size_t message_len = 0;
3145 size_t buffer_len = 0;
3152 if (exist == NULL) {
3159 message_len = strlen(exist);
3160 buffer_len = message_len + 4096;
3161 m = realloc(exist, buffer_len);
3168 /* Do we need to change leading ".." to "." for SMTP escaping? */
3169 if (!strcmp(terminator, ".")) {
3173 /* flush the input if we have nowhere to store it */
3178 /* read in the lines of message text one by one */
3181 if (sock_getln(sock, buf, (sizeof buf - 3)) < 0) finished = 1;
3184 if (client_getln(buf, (sizeof buf - 3)) < 1) finished = 1;
3186 if (!strcmp(buf, terminator)) finished = 1;
3188 strcat(buf, "\r\n");
3194 /* Unescape SMTP-style input of two dots at the beginning of the line */
3196 if (!strncmp(buf, "..", 2)) {
3197 strcpy(buf, &buf[1]);
3201 if ( (!flushing) && (!finished) ) {
3202 /* Measure the line */
3203 linelen = strlen(buf);
3205 /* augment the buffer if we have to */
3206 if ((message_len + linelen) >= buffer_len) {
3207 ptr = realloc(m, (buffer_len * 2) );
3208 if (ptr == NULL) { /* flush if can't allocate */
3211 buffer_len = (buffer_len * 2);
3213 CtdlLogPrintf(CTDL_DEBUG, "buffer_len is now %ld\n", (long)buffer_len);
3217 /* Add the new line to the buffer. NOTE: this loop must avoid
3218 * using functions like strcat() and strlen() because they
3219 * traverse the entire buffer upon every call, and doing that
3220 * for a multi-megabyte message slows it down beyond usability.
3222 strcpy(&m[message_len], buf);
3223 message_len += linelen;
3226 /* if we've hit the max msg length, flush the rest */
3227 if (message_len >= maxlen) flushing = 1;
3229 } while (!finished);
3237 * Build a binary message to be saved on disk.
3238 * (NOTE: if you supply 'preformatted_text', the buffer you give it
3239 * will become part of the message. This means you are no longer
3240 * responsible for managing that memory -- it will be freed along with
3241 * the rest of the fields when CtdlFreeMessage() is called.)
3244 struct CtdlMessage *CtdlMakeMessage(
3245 struct ctdluser *author, /* author's user structure */
3246 char *recipient, /* NULL if it's not mail */
3247 char *recp_cc, /* NULL if it's not mail */
3248 char *room, /* room where it's going */
3249 int type, /* see MES_ types in header file */
3250 int format_type, /* variformat, plain text, MIME... */
3251 char *fake_name, /* who we're masquerading as */
3252 char *my_email, /* which of my email addresses to use (empty is ok) */
3253 char *subject, /* Subject (optional) */
3254 char *supplied_euid, /* ...or NULL if this is irrelevant */
3255 char *preformatted_text, /* ...or NULL to read text from client */
3256 char *references /* Thread references */
3258 char dest_node[256];
3260 struct CtdlMessage *msg;
3262 msg = malloc(sizeof(struct CtdlMessage));
3263 memset(msg, 0, sizeof(struct CtdlMessage));
3264 msg->cm_magic = CTDLMESSAGE_MAGIC;
3265 msg->cm_anon_type = type;
3266 msg->cm_format_type = format_type;
3268 /* Don't confuse the poor folks if it's not routed mail. */
3269 strcpy(dest_node, "");
3271 if (recipient != NULL) striplt(recipient);
3272 if (recp_cc != NULL) striplt(recp_cc);
3274 /* Path or Return-Path */
3275 if (my_email == NULL) my_email = "";
3277 if (!IsEmptyStr(my_email)) {
3278 msg->cm_fields['P'] = strdup(my_email);
3281 snprintf(buf, sizeof buf, "%s", author->fullname);
3282 msg->cm_fields['P'] = strdup(buf);
3284 convert_spaces_to_underscores(msg->cm_fields['P']);
3286 snprintf(buf, sizeof buf, "%ld", (long)time(NULL)); /* timestamp */
3287 msg->cm_fields['T'] = strdup(buf);
3289 if ((fake_name != NULL) && (fake_name[0])) { /* author */
3290 msg->cm_fields['A'] = strdup(fake_name);
3293 msg->cm_fields['A'] = strdup(author->fullname);
3296 if (CC->room.QRflags & QR_MAILBOX) { /* room */
3297 msg->cm_fields['O'] = strdup(&CC->room.QRname[11]);
3300 msg->cm_fields['O'] = strdup(CC->room.QRname);
3303 msg->cm_fields['N'] = strdup(NODENAME); /* nodename */
3304 msg->cm_fields['H'] = strdup(HUMANNODE); /* hnodename */
3306 if ((recipient != NULL) && (recipient[0] != 0)) {
3307 msg->cm_fields['R'] = strdup(recipient);
3309 if ((recp_cc != NULL) && (recp_cc[0] != 0)) {
3310 msg->cm_fields['Y'] = strdup(recp_cc);
3312 if (dest_node[0] != 0) {
3313 msg->cm_fields['D'] = strdup(dest_node);
3316 if (!IsEmptyStr(my_email)) {
3317 msg->cm_fields['F'] = strdup(my_email);
3319 else if ( (author == &CC->user) && (!IsEmptyStr(CC->cs_inet_email)) ) {
3320 msg->cm_fields['F'] = strdup(CC->cs_inet_email);
3323 if (subject != NULL) {
3326 length = strlen(subject);
3332 while ((subject[i] != '\0') &&
3333 (IsAscii = isascii(subject[i]) != 0 ))
3336 msg->cm_fields['U'] = strdup(subject);
3337 else /* ok, we've got utf8 in the string. */
3339 msg->cm_fields['U'] = rfc2047encode(subject, length);
3345 if (supplied_euid != NULL) {
3346 msg->cm_fields['E'] = strdup(supplied_euid);
3349 if (references != NULL) {
3350 if (!IsEmptyStr(references)) {
3351 msg->cm_fields['W'] = strdup(references);
3355 if (preformatted_text != NULL) {
3356 msg->cm_fields['M'] = preformatted_text;
3359 msg->cm_fields['M'] = CtdlReadMessageBody("000", config.c_maxmsglen, NULL, 0, 0);
3367 * Check to see whether we have permission to post a message in the current
3368 * room. Returns a *CITADEL ERROR CODE* and puts a message in errmsgbuf, or
3369 * returns 0 on success.
3371 int CtdlDoIHavePermissionToPostInThisRoom(char *errmsgbuf,
3373 const char* RemoteIdentifier,
3377 if (!(CC->logged_in) &&
3378 (PostPublic == POST_LOGGED_IN)) {
3379 snprintf(errmsgbuf, n, "Not logged in.");
3380 return (ERROR + NOT_LOGGED_IN);
3382 else if (PostPublic == CHECK_EXISTANCE) {
3383 return (0); // We're Evaling whether a recipient exists
3385 else if (!(CC->logged_in)) {
3387 if ((CC->room.QRflags & QR_READONLY)) {
3388 snprintf(errmsgbuf, n, "Not logged in.");
3389 return (ERROR + NOT_LOGGED_IN);
3391 if (CC->room.QRflags2 & QR2_MODERATED) {
3392 snprintf(errmsgbuf, n, "Not logged in Moderation feature not yet implemented!");
3393 return (ERROR + NOT_LOGGED_IN);
3395 if ((PostPublic!=POST_LMTP) &&(CC->room.QRflags2 & QR2_SMTP_PUBLIC) == 0) {
3400 if (RemoteIdentifier == NULL)
3402 snprintf(errmsgbuf, n, "Need sender to permit access.");
3403 return (ERROR + USERNAME_REQUIRED);
3406 assoc_file_name(filename, sizeof filename, &CC->room, ctdl_netcfg_dir);
3407 begin_critical_section(S_NETCONFIGS);
3408 if (!read_spoolcontrol_file(&sc, filename))
3410 end_critical_section(S_NETCONFIGS);
3411 snprintf(errmsgbuf, n,
3412 "This mailing list only accepts posts from subscribers.");
3413 return (ERROR + NO_SUCH_USER);
3415 end_critical_section(S_NETCONFIGS);
3416 found = is_recipient (sc, RemoteIdentifier);
3417 free_spoolcontrol_struct(&sc);
3422 snprintf(errmsgbuf, n,
3423 "This mailing list only accepts posts from subscribers.");
3424 return (ERROR + NO_SUCH_USER);
3431 if ((CC->user.axlevel < 2)
3432 && ((CC->room.QRflags & QR_MAILBOX) == 0)) {
3433 snprintf(errmsgbuf, n, "Need to be validated to enter "
3434 "(except in %s> to sysop)", MAILROOM);
3435 return (ERROR + HIGHER_ACCESS_REQUIRED);
3438 CtdlRoomAccess(&CC->room, &CC->user, &ra, NULL);
3439 if (!(ra & UA_POSTALLOWED)) {
3440 snprintf(errmsgbuf, n, "Higher access is required to post in this room.");
3441 return (ERROR + HIGHER_ACCESS_REQUIRED);
3444 strcpy(errmsgbuf, "Ok");
3450 * Check to see if the specified user has Internet mail permission
3451 * (returns nonzero if permission is granted)
3453 int CtdlCheckInternetMailPermission(struct ctdluser *who) {
3455 /* Do not allow twits to send Internet mail */
3456 if (who->axlevel <= 2) return(0);
3458 /* Globally enabled? */
3459 if (config.c_restrict == 0) return(1);
3461 /* User flagged ok? */
3462 if (who->flags & US_INTERNET) return(2);
3464 /* Aide level access? */
3465 if (who->axlevel >= 6) return(3);
3467 /* No mail for you! */
3473 * Validate recipients, count delivery types and errors, and handle aliasing
3474 * FIXME check for dupes!!!!!
3476 * Returns 0 if all addresses are ok, ret->num_error = -1 if no addresses
3477 * were specified, or the number of addresses found invalid.
3479 * Caller needs to free the result using free_recipients()
3481 struct recptypes *validate_recipients(char *supplied_recipients,
3482 const char *RemoteIdentifier,
3484 struct recptypes *ret;
3485 char *recipients = NULL;
3486 char this_recp[256];
3487 char this_recp_cooked[256];
3493 struct ctdluser tempUS;
3494 struct ctdlroom tempQR;
3495 struct ctdlroom tempQR2;
3501 ret = (struct recptypes *) malloc(sizeof(struct recptypes));
3502 if (ret == NULL) return(NULL);
3504 /* Set all strings to null and numeric values to zero */
3505 memset(ret, 0, sizeof(struct recptypes));
3507 if (supplied_recipients == NULL) {
3508 recipients = strdup("");
3511 recipients = strdup(supplied_recipients);
3514 /* Allocate some memory. Yes, this allocates 500% more memory than we will
3515 * actually need, but it's healthier for the heap than doing lots of tiny
3516 * realloc() calls instead.
3519 ret->errormsg = malloc(strlen(recipients) + 1024);
3520 ret->recp_local = malloc(strlen(recipients) + 1024);
3521 ret->recp_internet = malloc(strlen(recipients) + 1024);
3522 ret->recp_ignet = malloc(strlen(recipients) + 1024);
3523 ret->recp_room = malloc(strlen(recipients) + 1024);
3524 ret->display_recp = malloc(strlen(recipients) + 1024);
3526 ret->errormsg[0] = 0;
3527 ret->recp_local[0] = 0;
3528 ret->recp_internet[0] = 0;
3529 ret->recp_ignet[0] = 0;
3530 ret->recp_room[0] = 0;
3531 ret->display_recp[0] = 0;
3533 ret->recptypes_magic = RECPTYPES_MAGIC;
3535 /* Change all valid separator characters to commas */
3536 for (i=0; !IsEmptyStr(&recipients[i]); ++i) {
3537 if ((recipients[i] == ';') || (recipients[i] == '|')) {
3538 recipients[i] = ',';
3542 /* Now start extracting recipients... */
3544 while (!IsEmptyStr(recipients)) {
3546 for (i=0; i<=strlen(recipients); ++i) {
3547 if (recipients[i] == '\"') in_quotes = 1 - in_quotes;
3548 if ( ( (recipients[i] == ',') && (!in_quotes) ) || (recipients[i] == 0) ) {
3549 safestrncpy(this_recp, recipients, i+1);
3551 if (recipients[i] == ',') {
3552 strcpy(recipients, &recipients[i+1]);
3555 strcpy(recipients, "");
3562 if (IsEmptyStr(this_recp))
3564 CtdlLogPrintf(CTDL_DEBUG, "Evaluating recipient #%d: %s\n", num_recps, this_recp);
3566 mailtype = alias(this_recp);
3567 mailtype = alias(this_recp);
3568 mailtype = alias(this_recp);
3570 for (j=0; !IsEmptyStr(&this_recp[j]); ++j) {
3571 if (this_recp[j]=='_') {
3572 this_recp_cooked[j] = ' ';
3575 this_recp_cooked[j] = this_recp[j];
3578 this_recp_cooked[j] = '\0';
3583 if (!strcasecmp(this_recp, "sysop")) {
3585 strcpy(this_recp, config.c_aideroom);
3586 if (!IsEmptyStr(ret->recp_room)) {
3587 strcat(ret->recp_room, "|");
3589 strcat(ret->recp_room, this_recp);
3591 else if ( (!strncasecmp(this_recp, "room_", 5))
3592 && (!CtdlGetRoom(&tempQR, &this_recp_cooked[5])) ) {
3594 /* Save room so we can restore it later */
3598 /* Check permissions to send mail to this room */
3599 err = CtdlDoIHavePermissionToPostInThisRoom(errmsg,
3611 if (!IsEmptyStr(ret->recp_room)) {
3612 strcat(ret->recp_room, "|");
3614 strcat(ret->recp_room, &this_recp_cooked[5]);
3617 /* Restore room in case something needs it */
3621 else if (getuser(&tempUS, this_recp) == 0) {
3623 strcpy(this_recp, tempUS.fullname);
3624 if (!IsEmptyStr(ret->recp_local)) {
3625 strcat(ret->recp_local, "|");
3627 strcat(ret->recp_local, this_recp);
3629 else if (getuser(&tempUS, this_recp_cooked) == 0) {
3631 strcpy(this_recp, tempUS.fullname);
3632 if (!IsEmptyStr(ret->recp_local)) {
3633 strcat(ret->recp_local, "|");
3635 strcat(ret->recp_local, this_recp);
3643 /* Yes, you're reading this correctly: if the target
3644 * domain points back to the local system or an attached
3645 * Citadel directory, the address is invalid. That's
3646 * because if the address were valid, we would have
3647 * already translated it to a local address by now.
3649 if (IsDirectory(this_recp, 0)) {
3654 ++ret->num_internet;
3655 if (!IsEmptyStr(ret->recp_internet)) {
3656 strcat(ret->recp_internet, "|");
3658 strcat(ret->recp_internet, this_recp);
3663 if (!IsEmptyStr(ret->recp_ignet)) {
3664 strcat(ret->recp_ignet, "|");
3666 strcat(ret->recp_ignet, this_recp);
3674 if (IsEmptyStr(errmsg)) {
3675 snprintf(append, sizeof append, "Invalid recipient: %s", this_recp);
3678 snprintf(append, sizeof append, "%s", errmsg);
3680 if ( (strlen(ret->errormsg) + strlen(append) + 3) < SIZ) {
3681 if (!IsEmptyStr(ret->errormsg)) {
3682 strcat(ret->errormsg, "; ");
3684 strcat(ret->errormsg, append);
3688 if (IsEmptyStr(ret->display_recp)) {
3689 strcpy(append, this_recp);
3692 snprintf(append, sizeof append, ", %s", this_recp);
3694 if ( (strlen(ret->display_recp)+strlen(append)) < SIZ) {
3695 strcat(ret->display_recp, append);
3700 if ((ret->num_local + ret->num_internet + ret->num_ignet +
3701 ret->num_room + ret->num_error) == 0) {
3702 ret->num_error = (-1);
3703 strcpy(ret->errormsg, "No recipients specified.");
3706 CtdlLogPrintf(CTDL_DEBUG, "validate_recipients()\n");
3707 CtdlLogPrintf(CTDL_DEBUG, " local: %d <%s>\n", ret->num_local, ret->recp_local);
3708 CtdlLogPrintf(CTDL_DEBUG, " room: %d <%s>\n", ret->num_room, ret->recp_room);
3709 CtdlLogPrintf(CTDL_DEBUG, " inet: %d <%s>\n", ret->num_internet, ret->recp_internet);
3710 CtdlLogPrintf(CTDL_DEBUG, " ignet: %d <%s>\n", ret->num_ignet, ret->recp_ignet);
3711 CtdlLogPrintf(CTDL_DEBUG, " error: %d <%s>\n", ret->num_error, ret->errormsg);
3719 * Destructor for struct recptypes
3721 void free_recipients(struct recptypes *valid) {
3723 if (valid == NULL) {
3727 if (valid->recptypes_magic != RECPTYPES_MAGIC) {
3728 CtdlLogPrintf(CTDL_EMERG, "Attempt to call free_recipients() on some other data type!\n");
3732 if (valid->errormsg != NULL) free(valid->errormsg);
3733 if (valid->recp_local != NULL) free(valid->recp_local);
3734 if (valid->recp_internet != NULL) free(valid->recp_internet);
3735 if (valid->recp_ignet != NULL) free(valid->recp_ignet);
3736 if (valid->recp_room != NULL) free(valid->recp_room);
3737 if (valid->display_recp != NULL) free(valid->display_recp);
3738 if (valid->bounce_to != NULL) free(valid->bounce_to);
3739 if (valid->envelope_from != NULL) free(valid->envelope_from);
3746 * message entry - mode 0 (normal)
3748 void cmd_ent0(char *entargs)
3754 char supplied_euid[128];
3756 int format_type = 0;
3757 char newusername[256];
3758 char newuseremail[256];
3759 struct CtdlMessage *msg;
3763 struct recptypes *valid = NULL;
3764 struct recptypes *valid_to = NULL;
3765 struct recptypes *valid_cc = NULL;
3766 struct recptypes *valid_bcc = NULL;
3768 int subject_required = 0;
3773 int newuseremail_ok = 0;
3774 char references[SIZ];
3779 post = extract_int(entargs, 0);
3780 extract_token(recp, entargs, 1, '|', sizeof recp);
3781 anon_flag = extract_int(entargs, 2);
3782 format_type = extract_int(entargs, 3);
3783 extract_token(subject, entargs, 4, '|', sizeof subject);
3784 extract_token(newusername, entargs, 5, '|', sizeof newusername);
3785 do_confirm = extract_int(entargs, 6);
3786 extract_token(cc, entargs, 7, '|', sizeof cc);
3787 extract_token(bcc, entargs, 8, '|', sizeof bcc);
3788 switch(CC->room.QRdefaultview) {
3791 extract_token(supplied_euid, entargs, 9, '|', sizeof supplied_euid);
3794 supplied_euid[0] = 0;
3797 extract_token(newuseremail, entargs, 10, '|', sizeof newuseremail);
3798 extract_token(references, entargs, 11, '|', sizeof references);
3799 for (ptr=references; *ptr != 0; ++ptr) {
3800 if (*ptr == '!') *ptr = '|';
3803 /* first check to make sure the request is valid. */
3805 err = CtdlDoIHavePermissionToPostInThisRoom(errmsg, sizeof errmsg, NULL, POST_LOGGED_IN);
3808 cprintf("%d %s\n", err, errmsg);
3812 /* Check some other permission type things. */
3814 if (IsEmptyStr(newusername)) {
3815 strcpy(newusername, CC->user.fullname);
3817 if ( (CC->user.axlevel < 6)
3818 && (strcasecmp(newusername, CC->user.fullname))
3819 && (strcasecmp(newusername, CC->cs_inet_fn))
3821 cprintf("%d You don't have permission to author messages as '%s'.\n",
3822 ERROR + HIGHER_ACCESS_REQUIRED,
3829 if (IsEmptyStr(newuseremail)) {
3830 newuseremail_ok = 1;
3833 if (!IsEmptyStr(newuseremail)) {
3834 if (!strcasecmp(newuseremail, CC->cs_inet_email)) {
3835 newuseremail_ok = 1;
3837 else if (!IsEmptyStr(CC->cs_inet_other_emails)) {
3838 j = num_tokens(CC->cs_inet_other_emails, '|');
3839 for (i=0; i<j; ++i) {
3840 extract_token(buf, CC->cs_inet_other_emails, i, '|', sizeof buf);
3841 if (!strcasecmp(newuseremail, buf)) {
3842 newuseremail_ok = 1;
3848 if (!newuseremail_ok) {
3849 cprintf("%d You don't have permission to author messages as '%s'.\n",
3850 ERROR + HIGHER_ACCESS_REQUIRED,
3856 CC->cs_flags |= CS_POSTING;
3858 /* In mailbox rooms we have to behave a little differently --
3859 * make sure the user has specified at least one recipient. Then
3860 * validate the recipient(s). We do this for the Mail> room, as
3861 * well as any room which has the "Mailbox" view set.
3864 if ( ( (CC->room.QRflags & QR_MAILBOX) && (!strcasecmp(&CC->room.QRname[11], MAILROOM)) )
3865 || ( (CC->room.QRflags & QR_MAILBOX) && (CC->curr_view == VIEW_MAILBOX) )
3867 if (CC->user.axlevel < 2) {
3868 strcpy(recp, "sysop");
3873 valid_to = validate_recipients(recp, NULL, 0);
3874 if (valid_to->num_error > 0) {
3875 cprintf("%d %s\n", ERROR + NO_SUCH_USER, valid_to->errormsg);
3876 free_recipients(valid_to);
3880 valid_cc = validate_recipients(cc, NULL, 0);
3881 if (valid_cc->num_error > 0) {
3882 cprintf("%d %s\n", ERROR + NO_SUCH_USER, valid_cc->errormsg);
3883 free_recipients(valid_to);
3884 free_recipients(valid_cc);
3888 valid_bcc = validate_recipients(bcc, NULL, 0);
3889 if (valid_bcc->num_error > 0) {
3890 cprintf("%d %s\n", ERROR + NO_SUCH_USER, valid_bcc->errormsg);
3891 free_recipients(valid_to);
3892 free_recipients(valid_cc);
3893 free_recipients(valid_bcc);
3897 /* Recipient required, but none were specified */
3898 if ( (valid_to->num_error < 0) && (valid_cc->num_error < 0) && (valid_bcc->num_error < 0) ) {
3899 free_recipients(valid_to);
3900 free_recipients(valid_cc);
3901 free_recipients(valid_bcc);
3902 cprintf("%d At least one recipient is required.\n", ERROR + NO_SUCH_USER);
3906 if (valid_to->num_internet + valid_cc->num_internet + valid_bcc->num_internet > 0) {
3907 if (CtdlCheckInternetMailPermission(&CC->user)==0) {
3908 cprintf("%d You do not have permission "
3909 "to send Internet mail.\n",
3910 ERROR + HIGHER_ACCESS_REQUIRED);
3911 free_recipients(valid_to);
3912 free_recipients(valid_cc);
3913 free_recipients(valid_bcc);
3918 if ( ( (valid_to->num_internet + valid_to->num_ignet + valid_cc->num_internet + valid_cc->num_ignet + valid_bcc->num_internet + valid_bcc->num_ignet) > 0)
3919 && (CC->user.axlevel < 4) ) {
3920 cprintf("%d Higher access required for network mail.\n",
3921 ERROR + HIGHER_ACCESS_REQUIRED);
3922 free_recipients(valid_to);
3923 free_recipients(valid_cc);
3924 free_recipients(valid_bcc);
3928 if ((RESTRICT_INTERNET == 1)
3929 && (valid_to->num_internet + valid_cc->num_internet + valid_bcc->num_internet > 0)
3930 && ((CC->user.flags & US_INTERNET) == 0)
3931 && (!CC->internal_pgm)) {
3932 cprintf("%d You don't have access to Internet mail.\n",
3933 ERROR + HIGHER_ACCESS_REQUIRED);
3934 free_recipients(valid_to);
3935 free_recipients(valid_cc);
3936 free_recipients(valid_bcc);
3942 /* Is this a room which has anonymous-only or anonymous-option? */
3943 anonymous = MES_NORMAL;
3944 if (CC->room.QRflags & QR_ANONONLY) {
3945 anonymous = MES_ANONONLY;
3947 if (CC->room.QRflags & QR_ANONOPT) {
3948 if (anon_flag == 1) { /* only if the user requested it */
3949 anonymous = MES_ANONOPT;
3953 if ((CC->room.QRflags & QR_MAILBOX) == 0) {
3957 /* Recommend to the client that the use of a message subject is
3958 * strongly recommended in this room, if either the SUBJECTREQ flag
3959 * is set, or if there is one or more Internet email recipients.
3961 if (CC->room.QRflags2 & QR2_SUBJECTREQ) subject_required = 1;
3962 if ((valid_to) && (valid_to->num_internet > 0)) subject_required = 1;
3963 if ((valid_cc) && (valid_cc->num_internet > 0)) subject_required = 1;
3964 if ((valid_bcc) && (valid_bcc->num_internet > 0)) subject_required = 1;
3966 /* If we're only checking the validity of the request, return
3967 * success without creating the message.
3970 cprintf("%d %s|%d\n", CIT_OK,
3971 ((valid_to != NULL) ? valid_to->display_recp : ""),
3973 free_recipients(valid_to);
3974 free_recipients(valid_cc);
3975 free_recipients(valid_bcc);
3979 /* We don't need these anymore because we'll do it differently below */
3980 free_recipients(valid_to);
3981 free_recipients(valid_cc);
3982 free_recipients(valid_bcc);
3984 /* Read in the message from the client. */
3986 cprintf("%d send message\n", START_CHAT_MODE);
3988 cprintf("%d send message\n", SEND_LISTING);
3991 msg = CtdlMakeMessage(&CC->user, recp, cc,
3992 CC->room.QRname, anonymous, format_type,
3993 newusername, newuseremail, subject,
3994 ((!IsEmptyStr(supplied_euid)) ? supplied_euid : NULL),
3997 /* Put together one big recipients struct containing to/cc/bcc all in
3998 * one. This is for the envelope.
4000 char *all_recps = malloc(SIZ * 3);
4001 strcpy(all_recps, recp);
4002 if (!IsEmptyStr(cc)) {
4003 if (!IsEmptyStr(all_recps)) {
4004 strcat(all_recps, ",");
4006 strcat(all_recps, cc);
4008 if (!IsEmptyStr(bcc)) {
4009 if (!IsEmptyStr(all_recps)) {
4010 strcat(all_recps, ",");
4012 strcat(all_recps, bcc);
4014 if (!IsEmptyStr(all_recps)) {
4015 valid = validate_recipients(all_recps, NULL, 0);
4023 msgnum = CtdlSubmitMsg(msg, valid, "", QP_EADDR);
4026 cprintf("%ld\n", msgnum);
4028 cprintf("Message accepted.\n");
4031 cprintf("Internal error.\n");
4033 if (msg->cm_fields['E'] != NULL) {
4034 cprintf("%s\n", msg->cm_fields['E']);
4041 CtdlFreeMessage(msg);
4043 if (valid != NULL) {
4044 free_recipients(valid);
4052 * API function to delete messages which match a set of criteria
4053 * (returns the actual number of messages deleted)
4055 int CtdlDeleteMessages(char *room_name, /* which room */
4056 long *dmsgnums, /* array of msg numbers to be deleted */
4057 int num_dmsgnums, /* number of msgs to be deleted, or 0 for "any" */
4058 char *content_type /* or "" for any. regular expressions expected. */
4061 struct ctdlroom qrbuf;
4062 struct cdbdata *cdbfr;
4063 long *msglist = NULL;
4064 long *dellist = NULL;
4067 int num_deleted = 0;
4069 struct MetaData smi;
4072 int need_to_free_re = 0;
4074 if (content_type) if (!IsEmptyStr(content_type)) {
4075 regcomp(&re, content_type, 0);
4076 need_to_free_re = 1;
4078 CtdlLogPrintf(CTDL_DEBUG, "CtdlDeleteMessages(%s, %d msgs, %s)\n",
4079 room_name, num_dmsgnums, content_type);
4081 /* get room record, obtaining a lock... */
4082 if (CtdlGetRoomLock(&qrbuf, room_name) != 0) {
4083 CtdlLogPrintf(CTDL_ERR, "CtdlDeleteMessages(): Room <%s> not found\n",
4085 if (need_to_free_re) regfree(&re);
4086 return (0); /* room not found */
4088 cdbfr = cdb_fetch(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long));
4090 if (cdbfr != NULL) {
4091 dellist = malloc(cdbfr->len);
4092 msglist = (long *) cdbfr->ptr;
4093 cdbfr->ptr = NULL; /* CtdlDeleteMessages() now owns this memory */
4094 num_msgs = cdbfr->len / sizeof(long);
4098 for (i = 0; i < num_msgs; ++i) {
4101 /* Set/clear a bit for each criterion */
4103 /* 0 messages in the list or a null list means that we are
4104 * interested in deleting any messages which meet the other criteria.
4106 if ((num_dmsgnums == 0) || (dmsgnums == NULL)) {
4107 delete_this |= 0x01;
4110 for (j=0; j<num_dmsgnums; ++j) {
4111 if (msglist[i] == dmsgnums[j]) {
4112 delete_this |= 0x01;
4117 if (IsEmptyStr(content_type)) {
4118 delete_this |= 0x02;
4120 GetMetaData(&smi, msglist[i]);
4121 if (regexec(&re, smi.meta_content_type, 1, &pm, 0) == 0) {
4122 delete_this |= 0x02;
4126 /* Delete message only if all bits are set */
4127 if (delete_this == 0x03) {
4128 dellist[num_deleted++] = msglist[i];
4133 num_msgs = sort_msglist(msglist, num_msgs);
4134 cdb_store(CDB_MSGLISTS, &qrbuf.QRnumber, (int)sizeof(long),
4135 msglist, (int)(num_msgs * sizeof(long)));
4137 qrbuf.QRhighest = msglist[num_msgs - 1];
4139 CtdlPutRoomLock(&qrbuf);
4141 /* Go through the messages we pulled out of the index, and decrement
4142 * their reference counts by 1. If this is the only room the message
4143 * was in, the reference count will reach zero and the message will
4144 * automatically be deleted from the database. We do this in a
4145 * separate pass because there might be plug-in hooks getting called,
4146 * and we don't want that happening during an S_ROOMS critical
4149 if (num_deleted) for (i=0; i<num_deleted; ++i) {
4150 PerformDeleteHooks(qrbuf.QRname, dellist[i]);
4151 AdjRefCount(dellist[i], -1);
4154 /* Now free the memory we used, and go away. */
4155 if (msglist != NULL) free(msglist);
4156 if (dellist != NULL) free(dellist);
4157 CtdlLogPrintf(CTDL_DEBUG, "%d message(s) deleted.\n", num_deleted);
4158 if (need_to_free_re) regfree(&re);
4159 return (num_deleted);
4165 * Check whether the current user has permission to delete messages from
4166 * the current room (returns 1 for yes, 0 for no)
4168 int CtdlDoIHavePermissionToDeleteMessagesFromThisRoom(void) {
4170 CtdlRoomAccess(&CC->room, &CC->user, &ra, NULL);
4171 if (ra & UA_DELETEALLOWED) return(1);
4179 * Delete message from current room
4181 void cmd_dele(char *args)
4190 extract_token(msgset, args, 0, '|', sizeof msgset);
4191 num_msgs = num_tokens(msgset, ',');
4193 cprintf("%d Nothing to do.\n", CIT_OK);
4197 if (CtdlDoIHavePermissionToDeleteMessagesFromThisRoom() == 0) {
4198 cprintf("%d Higher access required.\n",
4199 ERROR + HIGHER_ACCESS_REQUIRED);
4204 * Build our message set to be moved/copied
4206 msgs = malloc(num_msgs * sizeof(long));
4207 for (i=0; i<num_msgs; ++i) {
4208 extract_token(msgtok, msgset, i, ',', sizeof msgtok);
4209 msgs[i] = atol(msgtok);
4212 num_deleted = CtdlDeleteMessages(CC->room.QRname, msgs, num_msgs, "");
4216 cprintf("%d %d message%s deleted.\n", CIT_OK,
4217 num_deleted, ((num_deleted != 1) ? "s" : ""));
4219 cprintf("%d Message not found.\n", ERROR + MESSAGE_NOT_FOUND);
4227 * move or copy a message to another room
4229 void cmd_move(char *args)
4236 char targ[ROOMNAMELEN];
4237 struct ctdlroom qtemp;
4244 extract_token(msgset, args, 0, '|', sizeof msgset);
4245 num_msgs = num_tokens(msgset, ',');
4247 cprintf("%d Nothing to do.\n", CIT_OK);
4251 extract_token(targ, args, 1, '|', sizeof targ);
4252 convert_room_name_macros(targ, sizeof targ);
4253 targ[ROOMNAMELEN - 1] = 0;
4254 is_copy = extract_int(args, 2);
4256 if (CtdlGetRoom(&qtemp, targ) != 0) {
4257 cprintf("%d '%s' does not exist.\n", ERROR + ROOM_NOT_FOUND, targ);
4261 if (!strcasecmp(qtemp.QRname, CC->room.QRname)) {
4262 cprintf("%d Source and target rooms are the same.\n", ERROR + ALREADY_EXISTS);
4266 getuser(&CC->user, CC->curr_user);
4267 CtdlRoomAccess(&qtemp, &CC->user, &ra, NULL);
4269 /* Check for permission to perform this operation.
4270 * Remember: "CC->room" is source, "qtemp" is target.
4274 /* Aides can move/copy */
4275 if (CC->user.axlevel >= 6) permit = 1;
4277 /* Room aides can move/copy */
4278 if (CC->user.usernum == CC->room.QRroomaide) permit = 1;
4280 /* Permit move/copy from personal rooms */
4281 if ((CC->room.QRflags & QR_MAILBOX)
4282 && (qtemp.QRflags & QR_MAILBOX)) permit = 1;
4284 /* Permit only copy from public to personal room */
4286 && (!(CC->room.QRflags & QR_MAILBOX))
4287 && (qtemp.QRflags & QR_MAILBOX)) permit = 1;
4289 /* Permit message removal from collaborative delete rooms */
4290 if (CC->room.QRflags2 & QR2_COLLABDEL) permit = 1;
4292 /* Users allowed to post into the target room may move into it too. */
4293 if ((CC->room.QRflags & QR_MAILBOX) &&
4294 (qtemp.QRflags & UA_POSTALLOWED)) permit = 1;
4296 /* User must have access to target room */
4297 if (!(ra & UA_KNOWN)) permit = 0;
4300 cprintf("%d Higher access required.\n",
4301 ERROR + HIGHER_ACCESS_REQUIRED);
4306 * Build our message set to be moved/copied
4308 msgs = malloc(num_msgs * sizeof(long));
4309 for (i=0; i<num_msgs; ++i) {
4310 extract_token(msgtok, msgset, i, ',', sizeof msgtok);
4311 msgs[i] = atol(msgtok);
4317 err = CtdlSaveMsgPointersInRoom(targ, msgs, num_msgs, 1, NULL);
4319 cprintf("%d Cannot store message(s) in %s: error %d\n",
4325 /* Now delete the message from the source room,
4326 * if this is a 'move' rather than a 'copy' operation.
4329 CtdlDeleteMessages(CC->room.QRname, msgs, num_msgs, "");
4333 cprintf("%d Message(s) %s.\n", CIT_OK, (is_copy ? "copied" : "moved") );
4339 * GetMetaData() - Get the supplementary record for a message
4341 void GetMetaData(struct MetaData *smibuf, long msgnum)
4344 struct cdbdata *cdbsmi;
4347 memset(smibuf, 0, sizeof(struct MetaData));
4348 smibuf->meta_msgnum = msgnum;
4349 smibuf->meta_refcount = 1; /* Default reference count is 1 */
4351 /* Use the negative of the message number for its supp record index */
4352 TheIndex = (0L - msgnum);
4354 cdbsmi = cdb_fetch(CDB_MSGMAIN, &TheIndex, sizeof(long));
4355 if (cdbsmi == NULL) {
4356 return; /* record not found; go with defaults */
4358 memcpy(smibuf, cdbsmi->ptr,
4359 ((cdbsmi->len > sizeof(struct MetaData)) ?
4360 sizeof(struct MetaData) : cdbsmi->len));
4367 * PutMetaData() - (re)write supplementary record for a message
4369 void PutMetaData(struct MetaData *smibuf)
4373 /* Use the negative of the message number for the metadata db index */
4374 TheIndex = (0L - smibuf->meta_msgnum);
4376 cdb_store(CDB_MSGMAIN,
4377 &TheIndex, (int)sizeof(long),
4378 smibuf, (int)sizeof(struct MetaData));
4383 * AdjRefCount - submit an adjustment to the reference count for a message.
4384 * (These are just queued -- we actually process them later.)
4386 void AdjRefCount(long msgnum, int incr)
4388 struct arcq new_arcq;
4391 begin_critical_section(S_SUPPMSGMAIN);
4392 if (arcfp == NULL) {
4393 arcfp = fopen(file_arcq, "ab+");
4395 end_critical_section(S_SUPPMSGMAIN);
4397 /* msgnum < 0 means that we're trying to close the file */
4399 CtdlLogPrintf(CTDL_DEBUG, "Closing the AdjRefCount queue file\n");
4400 begin_critical_section(S_SUPPMSGMAIN);
4401 if (arcfp != NULL) {
4405 end_critical_section(S_SUPPMSGMAIN);
4410 * If we can't open the queue, perform the operation synchronously.
4412 if (arcfp == NULL) {
4413 TDAP_AdjRefCount(msgnum, incr);
4417 new_arcq.arcq_msgnum = msgnum;
4418 new_arcq.arcq_delta = incr;
4419 rv = fwrite(&new_arcq, sizeof(struct arcq), 1, arcfp);
4427 * TDAP_ProcessAdjRefCountQueue()
4429 * Process the queue of message count adjustments that was created by calls
4430 * to AdjRefCount() ... by reading the queue and calling TDAP_AdjRefCount()
4431 * for each one. This should be an "off hours" operation.
4433 int TDAP_ProcessAdjRefCountQueue(void)
4435 char file_arcq_temp[PATH_MAX];
4438 struct arcq arcq_rec;
4439 int num_records_processed = 0;
4441 snprintf(file_arcq_temp, sizeof file_arcq_temp, "%s.%04x", file_arcq, rand());
4443 begin_critical_section(S_SUPPMSGMAIN);
4444 if (arcfp != NULL) {
4449 r = link(file_arcq, file_arcq_temp);
4451 CtdlLogPrintf(CTDL_CRIT, "%s: %s\n", file_arcq_temp, strerror(errno));
4452 end_critical_section(S_SUPPMSGMAIN);
4453 return(num_records_processed);
4457 end_critical_section(S_SUPPMSGMAIN);
4459 fp = fopen(file_arcq_temp, "rb");
4461 CtdlLogPrintf(CTDL_CRIT, "%s: %s\n", file_arcq_temp, strerror(errno));
4462 return(num_records_processed);
4465 while (fread(&arcq_rec, sizeof(struct arcq), 1, fp) == 1) {
4466 TDAP_AdjRefCount(arcq_rec.arcq_msgnum, arcq_rec.arcq_delta);
4467 ++num_records_processed;
4471 r = unlink(file_arcq_temp);
4473 CtdlLogPrintf(CTDL_CRIT, "%s: %s\n", file_arcq_temp, strerror(errno));
4476 return(num_records_processed);
4482 * TDAP_AdjRefCount - adjust the reference count for a message.
4483 * This one does it "for real" because it's called by
4484 * the autopurger function that processes the queue
4485 * created by AdjRefCount(). If a message's reference
4486 * count becomes zero, we also delete the message from
4487 * disk and de-index it.
4489 void TDAP_AdjRefCount(long msgnum, int incr)
4492 struct MetaData smi;
4495 /* This is a *tight* critical section; please keep it that way, as
4496 * it may get called while nested in other critical sections.
4497 * Complicating this any further will surely cause deadlock!
4499 begin_critical_section(S_SUPPMSGMAIN);
4500 GetMetaData(&smi, msgnum);
4501 smi.meta_refcount += incr;
4503 end_critical_section(S_SUPPMSGMAIN);
4504 CtdlLogPrintf(CTDL_DEBUG, "msg %ld ref count delta %+d, is now %d\n",
4505 msgnum, incr, smi.meta_refcount);
4507 /* If the reference count is now zero, delete the message
4508 * (and its supplementary record as well).
4510 if (smi.meta_refcount == 0) {
4511 CtdlLogPrintf(CTDL_DEBUG, "Deleting message <%ld>\n", msgnum);
4513 /* Call delete hooks with NULL room to show it has gone altogether */
4514 PerformDeleteHooks(NULL, msgnum);
4516 /* Remove from message base */
4518 cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long));
4519 cdb_delete(CDB_BIGMSGS, &delnum, (int)sizeof(long));
4521 /* Remove metadata record */
4522 delnum = (0L - msgnum);
4523 cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long));
4529 * Write a generic object to this room
4531 * Note: this could be much more efficient. Right now we use two temporary
4532 * files, and still pull the message into memory as with all others.
4534 void CtdlWriteObject(char *req_room, /* Room to stuff it in */
4535 char *content_type, /* MIME type of this object */
4536 char *raw_message, /* Data to be written */
4537 off_t raw_length, /* Size of raw_message */
4538 struct ctdluser *is_mailbox, /* Mailbox room? */
4539 int is_binary, /* Is encoding necessary? */
4540 int is_unique, /* Del others of this type? */
4541 unsigned int flags /* Internal save flags */
4545 struct ctdlroom qrbuf;
4546 char roomname[ROOMNAMELEN];
4547 struct CtdlMessage *msg;
4548 char *encoded_message = NULL;
4550 if (is_mailbox != NULL) {
4551 MailboxName(roomname, sizeof roomname, is_mailbox, req_room);
4554 safestrncpy(roomname, req_room, sizeof(roomname));
4557 CtdlLogPrintf(CTDL_DEBUG, "Raw length is %ld\n", (long)raw_length);
4560 encoded_message = malloc((size_t) (((raw_length * 134) / 100) + 4096 ) );
4563 encoded_message = malloc((size_t)(raw_length + 4096));
4566 sprintf(encoded_message, "Content-type: %s\n", content_type);
4569 sprintf(&encoded_message[strlen(encoded_message)],
4570 "Content-transfer-encoding: base64\n\n"
4574 sprintf(&encoded_message[strlen(encoded_message)],
4575 "Content-transfer-encoding: 7bit\n\n"
4581 &encoded_message[strlen(encoded_message)],
4589 &encoded_message[strlen(encoded_message)],
4595 CtdlLogPrintf(CTDL_DEBUG, "Allocating\n");
4596 msg = malloc(sizeof(struct CtdlMessage));
4597 memset(msg, 0, sizeof(struct CtdlMessage));
4598 msg->cm_magic = CTDLMESSAGE_MAGIC;
4599 msg->cm_anon_type = MES_NORMAL;
4600 msg->cm_format_type = 4;
4601 msg->cm_fields['A'] = strdup(CC->user.fullname);
4602 msg->cm_fields['O'] = strdup(req_room);
4603 msg->cm_fields['N'] = strdup(config.c_nodename);
4604 msg->cm_fields['H'] = strdup(config.c_humannode);
4605 msg->cm_flags = flags;
4607 msg->cm_fields['M'] = encoded_message;
4609 /* Create the requested room if we have to. */
4610 if (CtdlGetRoom(&qrbuf, roomname) != 0) {
4611 CtdlCreateRoom(roomname,
4612 ( (is_mailbox != NULL) ? 5 : 3 ),
4613 "", 0, 1, 0, VIEW_BBS);
4615 /* If the caller specified this object as unique, delete all
4616 * other objects of this type that are currently in the room.
4619 CtdlLogPrintf(CTDL_DEBUG, "Deleted %d other msgs of this type\n",
4620 CtdlDeleteMessages(roomname, NULL, 0, content_type)
4623 /* Now write the data */
4624 CtdlSubmitMsg(msg, NULL, roomname, 0);
4625 CtdlFreeMessage(msg);
4633 void CtdlGetSysConfigBackend(long msgnum, void *userdata) {
4634 config_msgnum = msgnum;
4638 char *CtdlGetSysConfig(char *sysconfname) {
4639 char hold_rm[ROOMNAMELEN];
4642 struct CtdlMessage *msg;
4645 strcpy(hold_rm, CC->room.QRname);
4646 if (CtdlGetRoom(&CC->room, SYSCONFIGROOM) != 0) {
4647 CtdlGetRoom(&CC->room, hold_rm);
4652 /* We want the last (and probably only) config in this room */
4653 begin_critical_section(S_CONFIG);
4654 config_msgnum = (-1L);
4655 CtdlForEachMessage(MSGS_LAST, 1, NULL, sysconfname, NULL,
4656 CtdlGetSysConfigBackend, NULL);
4657 msgnum = config_msgnum;
4658 end_critical_section(S_CONFIG);
4664 msg = CtdlFetchMessage(msgnum, 1);
4666 conf = strdup(msg->cm_fields['M']);
4667 CtdlFreeMessage(msg);
4674 CtdlGetRoom(&CC->room, hold_rm);
4676 if (conf != NULL) do {
4677 extract_token(buf, conf, 0, '\n', sizeof buf);
4678 strcpy(conf, &conf[strlen(buf)+1]);
4679 } while ( (!IsEmptyStr(conf)) && (!IsEmptyStr(buf)) );
4685 void CtdlPutSysConfig(char *sysconfname, char *sysconfdata) {
4686 CtdlWriteObject(SYSCONFIGROOM, sysconfname, sysconfdata, (strlen(sysconfdata)+1), NULL, 0, 1, 0);
4691 * Determine whether a given Internet address belongs to the current user
4693 int CtdlIsMe(char *addr, int addr_buf_len)
4695 struct recptypes *recp;
4698 recp = validate_recipients(addr, NULL, 0);
4699 if (recp == NULL) return(0);
4701 if (recp->num_local == 0) {
4702 free_recipients(recp);
4706 for (i=0; i<recp->num_local; ++i) {
4707 extract_token(addr, recp->recp_local, i, '|', addr_buf_len);
4708 if (!strcasecmp(addr, CC->user.fullname)) {
4709 free_recipients(recp);
4714 free_recipients(recp);
4720 * Citadel protocol command to do the same
4722 void cmd_isme(char *argbuf) {
4725 if (CtdlAccessCheck(ac_logged_in)) return;
4726 extract_token(addr, argbuf, 0, '|', sizeof addr);
4728 if (CtdlIsMe(addr, sizeof addr)) {
4729 cprintf("%d %s\n", CIT_OK, addr);
4732 cprintf("%d Not you.\n", ERROR + ILLEGAL_VALUE);
4738 /*****************************************************************************/
4739 /* MODULE INITIALIZATION STUFF */
4740 /*****************************************************************************/
4742 CTDL_MODULE_INIT(msgbase)
4744 CtdlRegisterProtoHook(cmd_msgs, "MSGS", "Output a list of messages in the current room");
4745 CtdlRegisterProtoHook(cmd_msg0, "MSG0", "Output a message in plain text format");
4746 CtdlRegisterProtoHook(cmd_msg2, "MSG2", "Output a message in RFC822 format");
4747 CtdlRegisterProtoHook(cmd_msg3, "MSG3", "Output a message in raw format (deprecated)");
4748 CtdlRegisterProtoHook(cmd_msg4, "MSG4", "Output a message in the client's preferred format");
4749 CtdlRegisterProtoHook(cmd_msgp, "MSGP", "Select preferred format for MSG4 output");
4750 CtdlRegisterProtoHook(cmd_opna, "OPNA", "Open an attachment for download");
4751 CtdlRegisterProtoHook(cmd_dlat, "DLAT", "Download an attachment");
4752 CtdlRegisterProtoHook(cmd_ent0, "ENT0", "Enter a message");
4753 CtdlRegisterProtoHook(cmd_dele, "DELE", "Delete a message");
4754 CtdlRegisterProtoHook(cmd_move, "MOVE", "Move or copy a message to another room");
4755 CtdlRegisterProtoHook(cmd_isme, "ISME", "Determine whether an email address belongs to a user");
4757 /* return our Subversion id for the Log */