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);
1436 int CtdlDoIHavePermissionToReadMessagesInThisRoom(void) {
1437 if ((!(CC->logged_in)) && (!(CC->internal_pgm))) {
1438 return(om_not_logged_in);
1445 * Get a message off disk. (returns om_* values found in msgbase.h)
1448 int CtdlOutputMsg(long msg_num, /* message number (local) to fetch */
1449 int mode, /* how would you like that message? */
1450 int headers_only, /* eschew the message body? */
1451 int do_proto, /* do Citadel protocol responses? */
1452 int crlf, /* Use CRLF newlines instead of LF? */
1453 char *section, /* NULL or a message/rfc822 section */
1454 int flags /* various flags; see msgbase.h */
1456 struct CtdlMessage *TheMessage = NULL;
1457 int retcode = om_no_such_msg;
1458 struct encapmsg encap;
1461 CtdlLogPrintf(CTDL_DEBUG, "CtdlOutputMsg(msgnum=%ld, mode=%d, section=%s)\n",
1463 (section ? section : "<>")
1466 r = CtdlDoIHavePermissionToReadMessagesInThisRoom();
1469 if (r == om_not_logged_in) {
1470 cprintf("%d Not logged in.\n", ERROR + NOT_LOGGED_IN);
1473 cprintf("%d An unknown error has occurred.\n", ERROR);
1479 /* FIXME: check message id against msglist for this room */
1482 * Fetch the message from disk. If we're in HEADERS_FAST mode,
1483 * request that we don't even bother loading the body into memory.
1485 if (headers_only == HEADERS_FAST) {
1486 TheMessage = CtdlFetchMessage(msg_num, 0);
1489 TheMessage = CtdlFetchMessage(msg_num, 1);
1492 if (TheMessage == NULL) {
1493 if (do_proto) cprintf("%d Can't locate msg %ld on disk\n",
1494 ERROR + MESSAGE_NOT_FOUND, msg_num);
1495 return(om_no_such_msg);
1498 /* Here is the weird form of this command, to process only an
1499 * encapsulated message/rfc822 section.
1501 if (section) if (!IsEmptyStr(section)) if (strcmp(section, "0")) {
1502 memset(&encap, 0, sizeof encap);
1503 safestrncpy(encap.desired_section, section, sizeof encap.desired_section);
1504 mime_parser(TheMessage->cm_fields['M'],
1506 *extract_encapsulated_message,
1507 NULL, NULL, (void *)&encap, 0
1509 CtdlFreeMessage(TheMessage);
1513 encap.msg[encap.msglen] = 0;
1514 TheMessage = convert_internet_message(encap.msg);
1515 encap.msg = NULL; /* no free() here, TheMessage owns it now */
1517 /* Now we let it fall through to the bottom of this
1518 * function, because TheMessage now contains the
1519 * encapsulated message instead of the top-level
1520 * message. Isn't that neat?
1525 if (do_proto) cprintf("%d msg %ld has no part %s\n",
1526 ERROR + MESSAGE_NOT_FOUND, msg_num, section);
1527 retcode = om_no_such_msg;
1532 /* Ok, output the message now */
1533 retcode = CtdlOutputPreLoadedMsg(TheMessage, mode, headers_only, do_proto, crlf, flags);
1534 CtdlFreeMessage(TheMessage);
1540 char *qp_encode_email_addrs(char *source)
1542 char user[256], node[256], name[256];
1543 const char headerStr[] = "=?UTF-8?Q?";
1547 int need_to_encode = 0;
1553 long nAddrPtrMax = 50;
1558 if (source == NULL) return source;
1559 if (IsEmptyStr(source)) return source;
1561 AddrPtr = malloc (sizeof (long) * nAddrPtrMax);
1562 AddrUtf8 = malloc (sizeof (long) * nAddrPtrMax);
1563 memset(AddrUtf8, 0, sizeof (long) * nAddrPtrMax);
1566 while (!IsEmptyStr (&source[i])) {
1567 if (nColons >= nAddrPtrMax){
1570 ptr = (long *) malloc(sizeof (long) * nAddrPtrMax * 2);
1571 memcpy (ptr, AddrPtr, sizeof (long) * nAddrPtrMax);
1572 free (AddrPtr), AddrPtr = ptr;
1574 ptr = (long *) malloc(sizeof (long) * nAddrPtrMax * 2);
1575 memset(&ptr[nAddrPtrMax], 0,
1576 sizeof (long) * nAddrPtrMax);
1578 memcpy (ptr, AddrUtf8, sizeof (long) * nAddrPtrMax);
1579 free (AddrUtf8), AddrUtf8 = ptr;
1582 if (((unsigned char) source[i] < 32) ||
1583 ((unsigned char) source[i] > 126)) {
1585 AddrUtf8[nColons] = 1;
1587 if (source[i] == '"')
1588 InQuotes = !InQuotes;
1589 if (!InQuotes && source[i] == ',') {
1590 AddrPtr[nColons] = i;
1595 if (need_to_encode == 0) {
1602 EncodedMaxLen = nColons * (sizeof(headerStr) + 3) + SourceLen * 3;
1603 Encoded = (char*) malloc (EncodedMaxLen);
1605 for (i = 0; i < nColons; i++)
1606 source[AddrPtr[i]++] = '\0';
1610 for (i = 0; i < nColons && nPtr != NULL; i++) {
1611 nmax = EncodedMaxLen - (nPtr - Encoded);
1613 process_rfc822_addr(&source[AddrPtr[i]],
1617 /* TODO: libIDN here ! */
1618 if (IsEmptyStr(name)) {
1619 n = snprintf(nPtr, nmax,
1620 (i==0)?"%s@%s" : ",%s@%s",
1624 EncodedName = rfc2047encode(name, strlen(name));
1625 n = snprintf(nPtr, nmax,
1626 (i==0)?"%s <%s@%s>" : ",%s <%s@%s>",
1627 EncodedName, user, node);
1632 n = snprintf(nPtr, nmax,
1633 (i==0)?"%s" : ",%s",
1634 &source[AddrPtr[i]]);
1640 ptr = (char*) malloc(EncodedMaxLen * 2);
1641 memcpy(ptr, Encoded, EncodedMaxLen);
1642 nnPtr = ptr + (nPtr - Encoded), nPtr = nnPtr;
1643 free(Encoded), Encoded = ptr;
1645 i--; /* do it once more with properly lengthened buffer */
1648 for (i = 0; i < nColons; i++)
1649 source[--AddrPtr[i]] = ',';
1656 /* If the last item in a list of recipients was truncated to a partial address,
1657 * remove it completely in order to avoid choking libSieve
1659 void sanitize_truncated_recipient(char *str)
1662 if (num_tokens(str, ',') < 2) return;
1664 int len = strlen(str);
1665 if (len < 900) return;
1666 if (len > 998) str[998] = 0;
1668 char *cptr = strrchr(str, ',');
1671 char *lptr = strchr(cptr, '<');
1672 char *rptr = strchr(cptr, '>');
1674 if ( (lptr) && (rptr) && (rptr > lptr) ) return;
1682 * Get a message off disk. (returns om_* values found in msgbase.h)
1684 int CtdlOutputPreLoadedMsg(
1685 struct CtdlMessage *TheMessage,
1686 int mode, /* how would you like that message? */
1687 int headers_only, /* eschew the message body? */
1688 int do_proto, /* do Citadel protocol responses? */
1689 int crlf, /* Use CRLF newlines instead of LF? */
1690 int flags /* should the bessage be exported clean? */
1694 cit_uint8_t ch, prev_ch;
1696 char display_name[256];
1698 char *nl; /* newline string */
1700 int subject_found = 0;
1703 /* Buffers needed for RFC822 translation. These are all filled
1704 * using functions that are bounds-checked, and therefore we can
1705 * make them substantially smaller than SIZ.
1712 char datestamp[100];
1714 CtdlLogPrintf(CTDL_DEBUG, "CtdlOutputPreLoadedMsg(TheMessage=%s, %d, %d, %d, %d\n",
1715 ((TheMessage == NULL) ? "NULL" : "not null"),
1716 mode, headers_only, do_proto, crlf);
1718 strcpy(mid, "unknown");
1719 nl = (crlf ? "\r\n" : "\n");
1721 if (!is_valid_message(TheMessage)) {
1722 CtdlLogPrintf(CTDL_ERR,
1723 "ERROR: invalid preloaded message for output\n");
1725 return(om_no_such_msg);
1728 /* Suppress envelope recipients if required to avoid disclosing BCC addresses.
1729 * Pad it with spaces in order to avoid changing the RFC822 length of the message.
1731 if ( (flags & SUPPRESS_ENV_TO) && (TheMessage->cm_fields['V'] != NULL) ) {
1732 memset(TheMessage->cm_fields['V'], ' ', strlen(TheMessage->cm_fields['V']));
1735 /* Are we downloading a MIME component? */
1736 if (mode == MT_DOWNLOAD) {
1737 if (TheMessage->cm_format_type != FMT_RFC822) {
1739 cprintf("%d This is not a MIME message.\n",
1740 ERROR + ILLEGAL_VALUE);
1741 } else if (CC->download_fp != NULL) {
1742 if (do_proto) cprintf(
1743 "%d You already have a download open.\n",
1744 ERROR + RESOURCE_BUSY);
1746 /* Parse the message text component */
1747 mptr = TheMessage->cm_fields['M'];
1748 mime_parser(mptr, NULL, *mime_download, NULL, NULL, NULL, 0);
1749 /* If there's no file open by this time, the requested
1750 * section wasn't found, so print an error
1752 if (CC->download_fp == NULL) {
1753 if (do_proto) cprintf(
1754 "%d Section %s not found.\n",
1755 ERROR + FILE_NOT_FOUND,
1756 CC->download_desired_section);
1759 return((CC->download_fp != NULL) ? om_ok : om_mime_error);
1762 /* MT_SPEW_SECTION is like MT_DOWNLOAD except it outputs the whole MIME part
1763 * in a single server operation instead of opening a download file.
1765 if (mode == MT_SPEW_SECTION) {
1766 if (TheMessage->cm_format_type != FMT_RFC822) {
1768 cprintf("%d This is not a MIME message.\n",
1769 ERROR + ILLEGAL_VALUE);
1771 /* Parse the message text component */
1774 mptr = TheMessage->cm_fields['M'];
1775 mime_parser(mptr, NULL, *mime_spew_section, NULL, NULL, (void *)&found_it, 0);
1776 /* If section wasn't found, print an error
1779 if (do_proto) cprintf(
1780 "%d Section %s not found.\n",
1781 ERROR + FILE_NOT_FOUND,
1782 CC->download_desired_section);
1785 return((CC->download_fp != NULL) ? om_ok : om_mime_error);
1788 /* now for the user-mode message reading loops */
1789 if (do_proto) cprintf("%d msg:\n", LISTING_FOLLOWS);
1791 /* Does the caller want to skip the headers? */
1792 if (headers_only == HEADERS_NONE) goto START_TEXT;
1794 /* Tell the client which format type we're using. */
1795 if ( (mode == MT_CITADEL) && (do_proto) ) {
1796 cprintf("type=%d\n", TheMessage->cm_format_type);
1799 /* nhdr=yes means that we're only displaying headers, no body */
1800 if ( (TheMessage->cm_anon_type == MES_ANONONLY)
1801 && ((mode == MT_CITADEL) || (mode == MT_MIME))
1804 cprintf("nhdr=yes\n");
1807 /* begin header processing loop for Citadel message format */
1809 if ((mode == MT_CITADEL) || (mode == MT_MIME)) {
1811 safestrncpy(display_name, "<unknown>", sizeof display_name);
1812 if (TheMessage->cm_fields['A']) {
1813 strcpy(buf, TheMessage->cm_fields['A']);
1814 if (TheMessage->cm_anon_type == MES_ANONONLY) {
1815 safestrncpy(display_name, "****", sizeof display_name);
1817 else if (TheMessage->cm_anon_type == MES_ANONOPT) {
1818 safestrncpy(display_name, "anonymous", sizeof display_name);
1821 safestrncpy(display_name, buf, sizeof display_name);
1823 if ((is_room_aide())
1824 && ((TheMessage->cm_anon_type == MES_ANONONLY)
1825 || (TheMessage->cm_anon_type == MES_ANONOPT))) {
1826 size_t tmp = strlen(display_name);
1827 snprintf(&display_name[tmp],
1828 sizeof display_name - tmp,
1833 /* Don't show Internet address for users on the
1834 * local Citadel network.
1837 if (TheMessage->cm_fields['N'] != NULL)
1838 if (!IsEmptyStr(TheMessage->cm_fields['N']))
1839 if (haschar(TheMessage->cm_fields['N'], '.') == 0) {
1843 /* Now spew the header fields in the order we like them. */
1844 safestrncpy(allkeys, FORDER, sizeof allkeys);
1845 for (i=0; i<strlen(allkeys); ++i) {
1846 k = (int) allkeys[i];
1848 if ( (TheMessage->cm_fields[k] != NULL)
1849 && (msgkeys[k] != NULL) ) {
1850 if ((k == 'V') || (k == 'R') || (k == 'Y')) {
1851 sanitize_truncated_recipient(TheMessage->cm_fields[k]);
1854 if (do_proto) cprintf("%s=%s\n",
1858 else if ((k == 'F') && (suppress_f)) {
1861 /* Masquerade display name if needed */
1863 if (do_proto) cprintf("%s=%s\n",
1865 TheMessage->cm_fields[k]
1874 /* begin header processing loop for RFC822 transfer format */
1879 strcpy(snode, NODENAME);
1880 if (mode == MT_RFC822) {
1881 for (i = 0; i < 256; ++i) {
1882 if (TheMessage->cm_fields[i]) {
1883 mptr = mpptr = TheMessage->cm_fields[i];
1886 safestrncpy(luser, mptr, sizeof luser);
1887 safestrncpy(suser, mptr, sizeof suser);
1889 else if (i == 'Y') {
1890 if ((flags & QP_EADDR) != 0) {
1891 mptr = qp_encode_email_addrs(mptr);
1893 sanitize_truncated_recipient(mptr);
1894 cprintf("CC: %s%s", mptr, nl);
1896 else if (i == 'P') {
1897 cprintf("Return-Path: %s%s", mptr, nl);
1899 else if (i == 'L') {
1900 cprintf("List-ID: %s%s", mptr, nl);
1902 else if (i == 'V') {
1903 if ((flags & QP_EADDR) != 0)
1904 mptr = qp_encode_email_addrs(mptr);
1905 cprintf("Envelope-To: %s%s", mptr, nl);
1907 else if (i == 'U') {
1908 cprintf("Subject: %s%s", mptr, nl);
1912 safestrncpy(mid, mptr, sizeof mid);
1914 safestrncpy(fuser, mptr, sizeof fuser);
1915 /* else if (i == 'O')
1916 cprintf("X-Citadel-Room: %s%s",
1919 safestrncpy(snode, mptr, sizeof snode);
1922 if (haschar(mptr, '@') == 0)
1924 sanitize_truncated_recipient(mptr);
1925 cprintf("To: %s@%s", mptr, config.c_fqdn);
1930 if ((flags & QP_EADDR) != 0) {
1931 mptr = qp_encode_email_addrs(mptr);
1933 sanitize_truncated_recipient(mptr);
1934 cprintf("To: %s", mptr);
1938 else if (i == 'T') {
1939 datestring(datestamp, sizeof datestamp,
1940 atol(mptr), DATESTRING_RFC822);
1941 cprintf("Date: %s%s", datestamp, nl);
1943 else if (i == 'W') {
1944 cprintf("References: ");
1945 k = num_tokens(mptr, '|');
1946 for (j=0; j<k; ++j) {
1947 extract_token(buf, mptr, j, '|', sizeof buf);
1948 cprintf("<%s>", buf);
1961 if (subject_found == 0) {
1962 cprintf("Subject: (no subject)%s", nl);
1966 for (i=0; !IsEmptyStr(&suser[i]); ++i) {
1967 suser[i] = tolower(suser[i]);
1968 if (!isalnum(suser[i])) suser[i]='_';
1971 if (mode == MT_RFC822) {
1972 if (!strcasecmp(snode, NODENAME)) {
1973 safestrncpy(snode, FQDN, sizeof snode);
1976 /* Construct a fun message id */
1977 cprintf("Message-ID: <%s", mid);/// todo: this possibly breaks threadding mails.
1978 if (strchr(mid, '@')==NULL) {
1979 cprintf("@%s", snode);
1983 if (!is_room_aide() && (TheMessage->cm_anon_type == MES_ANONONLY)) {
1984 cprintf("From: \"----\" <x@x.org>%s", nl);
1986 else if (!is_room_aide() && (TheMessage->cm_anon_type == MES_ANONOPT)) {
1987 cprintf("From: \"anonymous\" <x@x.org>%s", nl);
1989 else if (!IsEmptyStr(fuser)) {
1990 cprintf("From: \"%s\" <%s>%s", luser, fuser, nl);
1993 cprintf("From: \"%s\" <%s@%s>%s", luser, suser, snode, nl);
1996 /* Blank line signifying RFC822 end-of-headers */
1997 if (TheMessage->cm_format_type != FMT_RFC822) {
2002 /* end header processing loop ... at this point, we're in the text */
2004 if (headers_only == HEADERS_FAST) goto DONE;
2005 mptr = TheMessage->cm_fields['M'];
2007 /* Tell the client about the MIME parts in this message */
2008 if (TheMessage->cm_format_type == FMT_RFC822) {
2009 if ( (mode == MT_CITADEL) || (mode == MT_MIME) ) {
2010 memset(&ma, 0, sizeof(struct ma_info));
2011 mime_parser(mptr, NULL,
2012 (do_proto ? *list_this_part : NULL),
2013 (do_proto ? *list_this_pref : NULL),
2014 (do_proto ? *list_this_suff : NULL),
2017 else if (mode == MT_RFC822) { /* unparsed RFC822 dump */
2018 char *start_of_text = NULL;
2019 start_of_text = strstr(mptr, "\n\r\n");
2020 if (start_of_text == NULL) start_of_text = strstr(mptr, "\n\n");
2021 if (start_of_text == NULL) start_of_text = mptr;
2023 start_of_text = strstr(start_of_text, "\n");
2028 int nllen = strlen(nl);
2030 while (ch=*mptr, ch!=0) {
2036 ((headers_only == HEADERS_NONE) && (mptr >= start_of_text))
2037 || ((headers_only == HEADERS_ONLY) && (mptr < start_of_text))
2038 || ((headers_only != HEADERS_NONE) && (headers_only != HEADERS_ONLY))
2041 sprintf(&outbuf[outlen], "%s", nl);
2045 outbuf[outlen++] = ch;
2049 if (flags & ESC_DOT)
2051 if ((prev_ch == 10) && (ch == '.') && ((*(mptr+1) == 13) || (*(mptr+1) == 10)))
2053 outbuf[outlen++] = '.';
2058 if (outlen > 1000) {
2059 client_write(outbuf, outlen);
2064 client_write(outbuf, outlen);
2072 if (headers_only == HEADERS_ONLY) {
2076 /* signify start of msg text */
2077 if ( (mode == MT_CITADEL) || (mode == MT_MIME) ) {
2078 if (do_proto) cprintf("text\n");
2081 /* If the format type on disk is 1 (fixed-format), then we want
2082 * everything to be output completely literally ... regardless of
2083 * what message transfer format is in use.
2085 if (TheMessage->cm_format_type == FMT_FIXED) {
2087 if (mode == MT_MIME) {
2088 cprintf("Content-type: text/plain\n\n");
2092 while (ch = *mptr++, ch > 0) {
2095 if ((ch == 10) || (buflen > 250)) {
2097 cprintf("%s%s", buf, nl);
2106 if (!IsEmptyStr(buf))
2107 cprintf("%s%s", buf, nl);
2110 /* If the message on disk is format 0 (Citadel vari-format), we
2111 * output using the formatter at 80 columns. This is the final output
2112 * form if the transfer format is RFC822, but if the transfer format
2113 * is Citadel proprietary, it'll still work, because the indentation
2114 * for new paragraphs is correct and the client will reformat the
2115 * message to the reader's screen width.
2117 if (TheMessage->cm_format_type == FMT_CITADEL) {
2118 if (mode == MT_MIME) {
2119 cprintf("Content-type: text/x-citadel-variformat\n\n");
2121 memfmout(mptr, 0, nl);
2124 /* If the message on disk is format 4 (MIME), we've gotta hand it
2125 * off to the MIME parser. The client has already been told that
2126 * this message is format 1 (fixed format), so the callback function
2127 * we use will display those parts as-is.
2129 if (TheMessage->cm_format_type == FMT_RFC822) {
2130 memset(&ma, 0, sizeof(struct ma_info));
2132 if (mode == MT_MIME) {
2133 ma.use_fo_hooks = 0;
2134 strcpy(ma.chosen_part, "1");
2135 ma.chosen_pref = 9999;
2136 mime_parser(mptr, NULL,
2137 *choose_preferred, *fixed_output_pre,
2138 *fixed_output_post, (void *)&ma, 0);
2139 mime_parser(mptr, NULL,
2140 *output_preferred, NULL, NULL, (void *)&ma, CC->msg4_dont_decode);
2143 ma.use_fo_hooks = 1;
2144 mime_parser(mptr, NULL,
2145 *fixed_output, *fixed_output_pre,
2146 *fixed_output_post, (void *)&ma, 0);
2151 DONE: /* now we're done */
2152 if (do_proto) cprintf("000\n");
2159 * display a message (mode 0 - Citadel proprietary)
2161 void cmd_msg0(char *cmdbuf)
2164 int headers_only = HEADERS_ALL;
2166 msgid = extract_long(cmdbuf, 0);
2167 headers_only = extract_int(cmdbuf, 1);
2169 CtdlOutputMsg(msgid, MT_CITADEL, headers_only, 1, 0, NULL, 0);
2175 * display a message (mode 2 - RFC822)
2177 void cmd_msg2(char *cmdbuf)
2180 int headers_only = HEADERS_ALL;
2182 msgid = extract_long(cmdbuf, 0);
2183 headers_only = extract_int(cmdbuf, 1);
2185 CtdlOutputMsg(msgid, MT_RFC822, headers_only, 1, 1, NULL, 0);
2191 * display a message (mode 3 - IGnet raw format - internal programs only)
2193 void cmd_msg3(char *cmdbuf)
2196 struct CtdlMessage *msg = NULL;
2199 if (CC->internal_pgm == 0) {
2200 cprintf("%d This command is for internal programs only.\n",
2201 ERROR + HIGHER_ACCESS_REQUIRED);
2205 msgnum = extract_long(cmdbuf, 0);
2206 msg = CtdlFetchMessage(msgnum, 1);
2208 cprintf("%d Message %ld not found.\n",
2209 ERROR + MESSAGE_NOT_FOUND, msgnum);
2213 serialize_message(&smr, msg);
2214 CtdlFreeMessage(msg);
2217 cprintf("%d Unable to serialize message\n",
2218 ERROR + INTERNAL_ERROR);
2222 cprintf("%d %ld\n", BINARY_FOLLOWS, (long)smr.len);
2223 client_write((char *)smr.ser, (int)smr.len);
2230 * Display a message using MIME content types
2232 void cmd_msg4(char *cmdbuf)
2237 msgid = extract_long(cmdbuf, 0);
2238 extract_token(section, cmdbuf, 1, '|', sizeof section);
2239 CtdlOutputMsg(msgid, MT_MIME, 0, 1, 0, (section[0] ? section : NULL) , 0);
2245 * Client tells us its preferred message format(s)
2247 void cmd_msgp(char *cmdbuf)
2249 if (!strcasecmp(cmdbuf, "dont_decode")) {
2250 CC->msg4_dont_decode = 1;
2251 cprintf("%d MSG4 will not pre-decode messages.\n", CIT_OK);
2254 safestrncpy(CC->preferred_formats, cmdbuf, sizeof(CC->preferred_formats));
2255 cprintf("%d Preferred MIME formats have been set.\n", CIT_OK);
2261 * Open a component of a MIME message as a download file
2263 void cmd_opna(char *cmdbuf)
2266 char desired_section[128];
2268 msgid = extract_long(cmdbuf, 0);
2269 extract_token(desired_section, cmdbuf, 1, '|', sizeof desired_section);
2270 safestrncpy(CC->download_desired_section, desired_section,
2271 sizeof CC->download_desired_section);
2272 CtdlOutputMsg(msgid, MT_DOWNLOAD, 0, 1, 1, NULL, 0);
2277 * Open a component of a MIME message and transmit it all at once
2279 void cmd_dlat(char *cmdbuf)
2282 char desired_section[128];
2284 msgid = extract_long(cmdbuf, 0);
2285 extract_token(desired_section, cmdbuf, 1, '|', sizeof desired_section);
2286 safestrncpy(CC->download_desired_section, desired_section,
2287 sizeof CC->download_desired_section);
2288 CtdlOutputMsg(msgid, MT_SPEW_SECTION, 0, 1, 1, NULL, 0);
2293 * Save one or more message pointers into a specified room
2294 * (Returns 0 for success, nonzero for failure)
2295 * roomname may be NULL to use the current room
2297 * Note that the 'supplied_msg' field may be set to NULL, in which case
2298 * the message will be fetched from disk, by number, if we need to perform
2299 * replication checks. This adds an additional database read, so if the
2300 * caller already has the message in memory then it should be supplied. (Obviously
2301 * this mode of operation only works if we're saving a single message.)
2303 int CtdlSaveMsgPointersInRoom(char *roomname, long newmsgidlist[], int num_newmsgs,
2304 int do_repl_check, struct CtdlMessage *supplied_msg)
2307 char hold_rm[ROOMNAMELEN];
2308 struct cdbdata *cdbfr;
2311 long highest_msg = 0L;
2314 struct CtdlMessage *msg = NULL;
2316 long *msgs_to_be_merged = NULL;
2317 int num_msgs_to_be_merged = 0;
2319 CtdlLogPrintf(CTDL_DEBUG,
2320 "CtdlSaveMsgPointersInRoom(room=%s, num_msgs=%d, repl=%d)\n",
2321 roomname, num_newmsgs, do_repl_check);
2323 strcpy(hold_rm, CC->room.QRname);
2326 if (newmsgidlist == NULL) return(ERROR + INTERNAL_ERROR);
2327 if (num_newmsgs < 1) return(ERROR + INTERNAL_ERROR);
2328 if (num_newmsgs > 1) supplied_msg = NULL;
2330 /* Now the regular stuff */
2331 if (CtdlGetRoomLock(&CC->room,
2332 ((roomname != NULL) ? roomname : CC->room.QRname) )
2334 CtdlLogPrintf(CTDL_ERR, "No such room <%s>\n", roomname);
2335 return(ERROR + ROOM_NOT_FOUND);
2339 msgs_to_be_merged = malloc(sizeof(long) * num_newmsgs);
2340 num_msgs_to_be_merged = 0;
2343 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long));
2344 if (cdbfr == NULL) {
2348 msglist = (long *) cdbfr->ptr;
2349 cdbfr->ptr = NULL; /* CtdlSaveMsgPointerInRoom() now owns this memory */
2350 num_msgs = cdbfr->len / sizeof(long);
2355 /* Create a list of msgid's which were supplied by the caller, but do
2356 * not already exist in the target room. It is absolutely taboo to
2357 * have more than one reference to the same message in a room.
2359 for (i=0; i<num_newmsgs; ++i) {
2361 if (num_msgs > 0) for (j=0; j<num_msgs; ++j) {
2362 if (msglist[j] == newmsgidlist[i]) {
2367 msgs_to_be_merged[num_msgs_to_be_merged++] = newmsgidlist[i];
2371 CtdlLogPrintf(9, "%d unique messages to be merged\n", num_msgs_to_be_merged);
2374 * Now merge the new messages
2376 msglist = realloc(msglist, (sizeof(long) * (num_msgs + num_msgs_to_be_merged)) );
2377 if (msglist == NULL) {
2378 CtdlLogPrintf(CTDL_ALERT, "ERROR: can't realloc message list!\n");
2380 memcpy(&msglist[num_msgs], msgs_to_be_merged, (sizeof(long) * num_msgs_to_be_merged) );
2381 num_msgs += num_msgs_to_be_merged;
2383 /* Sort the message list, so all the msgid's are in order */
2384 num_msgs = sort_msglist(msglist, num_msgs);
2386 /* Determine the highest message number */
2387 highest_msg = msglist[num_msgs - 1];
2389 /* Write it back to disk. */
2390 cdb_store(CDB_MSGLISTS, &CC->room.QRnumber, (int)sizeof(long),
2391 msglist, (int)(num_msgs * sizeof(long)));
2393 /* Free up the memory we used. */
2396 /* Update the highest-message pointer and unlock the room. */
2397 CC->room.QRhighest = highest_msg;
2398 CtdlPutRoomLock(&CC->room);
2400 /* Perform replication checks if necessary */
2401 if ( (DoesThisRoomNeedEuidIndexing(&CC->room)) && (do_repl_check) ) {
2402 CtdlLogPrintf(CTDL_DEBUG, "CtdlSaveMsgPointerInRoom() doing repl checks\n");
2404 for (i=0; i<num_msgs_to_be_merged; ++i) {
2405 msgid = msgs_to_be_merged[i];
2407 if (supplied_msg != NULL) {
2411 msg = CtdlFetchMessage(msgid, 0);
2415 ReplicationChecks(msg);
2417 /* If the message has an Exclusive ID, index that... */
2418 if (msg->cm_fields['E'] != NULL) {
2419 index_message_by_euid(msg->cm_fields['E'], &CC->room, msgid);
2422 /* Free up the memory we may have allocated */
2423 if (msg != supplied_msg) {
2424 CtdlFreeMessage(msg);
2432 CtdlLogPrintf(CTDL_DEBUG, "CtdlSaveMsgPointerInRoom() skips repl checks\n");
2435 /* Submit this room for processing by hooks */
2436 PerformRoomHooks(&CC->room);
2438 /* Go back to the room we were in before we wandered here... */
2439 CtdlGetRoom(&CC->room, hold_rm);
2441 /* Bump the reference count for all messages which were merged */
2442 for (i=0; i<num_msgs_to_be_merged; ++i) {
2443 AdjRefCount(msgs_to_be_merged[i], +1);
2446 /* Free up memory... */
2447 if (msgs_to_be_merged != NULL) {
2448 free(msgs_to_be_merged);
2451 /* Return success. */
2457 * This is the same as CtdlSaveMsgPointersInRoom() but it only accepts
2460 int CtdlSaveMsgPointerInRoom(char *roomname, long msgid,
2461 int do_repl_check, struct CtdlMessage *supplied_msg)
2463 return CtdlSaveMsgPointersInRoom(roomname, &msgid, 1, do_repl_check, supplied_msg);
2470 * Message base operation to save a new message to the message store
2471 * (returns new message number)
2473 * This is the back end for CtdlSubmitMsg() and should not be directly
2474 * called by server-side modules.
2477 long send_message(struct CtdlMessage *msg) {
2485 /* Get a new message number */
2486 newmsgid = get_new_message_number();
2487 snprintf(msgidbuf, sizeof msgidbuf, "%010ld@%s", newmsgid, config.c_fqdn);
2489 /* Generate an ID if we don't have one already */
2490 if (msg->cm_fields['I']==NULL) {
2491 msg->cm_fields['I'] = strdup(msgidbuf);
2494 /* If the message is big, set its body aside for storage elsewhere */
2495 if (msg->cm_fields['M'] != NULL) {
2496 if (strlen(msg->cm_fields['M']) > BIGMSG) {
2498 holdM = msg->cm_fields['M'];
2499 msg->cm_fields['M'] = NULL;
2503 /* Serialize our data structure for storage in the database */
2504 serialize_message(&smr, msg);
2507 msg->cm_fields['M'] = holdM;
2511 cprintf("%d Unable to serialize message\n",
2512 ERROR + INTERNAL_ERROR);
2516 /* Write our little bundle of joy into the message base */
2517 if (cdb_store(CDB_MSGMAIN, &newmsgid, (int)sizeof(long),
2518 smr.ser, smr.len) < 0) {
2519 CtdlLogPrintf(CTDL_ERR, "Can't store message\n");
2523 cdb_store(CDB_BIGMSGS,
2533 /* Free the memory we used for the serialized message */
2536 /* Return the *local* message ID to the caller
2537 * (even if we're storing an incoming network message)
2545 * Serialize a struct CtdlMessage into the format used on disk and network.
2547 * This function loads up a "struct ser_ret" (defined in server.h) which
2548 * contains the length of the serialized message and a pointer to the
2549 * serialized message in memory. THE LATTER MUST BE FREED BY THE CALLER.
2551 void serialize_message(struct ser_ret *ret, /* return values */
2552 struct CtdlMessage *msg) /* unserialized msg */
2554 size_t wlen, fieldlen;
2556 static char *forder = FORDER;
2559 * Check for valid message format
2561 if (is_valid_message(msg) == 0) {
2562 CtdlLogPrintf(CTDL_ERR, "serialize_message() aborting due to invalid message\n");
2569 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL)
2570 ret->len = ret->len +
2571 strlen(msg->cm_fields[(int)forder[i]]) + 2;
2573 ret->ser = malloc(ret->len);
2574 if (ret->ser == NULL) {
2575 CtdlLogPrintf(CTDL_ERR, "serialize_message() malloc(%ld) failed: %s\n",
2576 (long)ret->len, strerror(errno));
2583 ret->ser[1] = msg->cm_anon_type;
2584 ret->ser[2] = msg->cm_format_type;
2587 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL) {
2588 fieldlen = strlen(msg->cm_fields[(int)forder[i]]);
2589 ret->ser[wlen++] = (char)forder[i];
2590 safestrncpy((char *)&ret->ser[wlen], msg->cm_fields[(int)forder[i]], fieldlen+1);
2591 wlen = wlen + fieldlen + 1;
2593 if (ret->len != wlen) CtdlLogPrintf(CTDL_ERR, "ERROR: len=%ld wlen=%ld\n",
2594 (long)ret->len, (long)wlen);
2601 * Serialize a struct CtdlMessage into the format used on disk and network.
2603 * This function loads up a "struct ser_ret" (defined in server.h) which
2604 * contains the length of the serialized message and a pointer to the
2605 * serialized message in memory. THE LATTER MUST BE FREED BY THE CALLER.
2607 void dump_message(struct CtdlMessage *msg, /* unserialized msg */
2608 long Siz) /* how many chars ? */
2612 static char *forder = FORDER;
2616 * Check for valid message format
2618 if (is_valid_message(msg) == 0) {
2619 CtdlLogPrintf(CTDL_ERR, "dump_message() aborting due to invalid message\n");
2623 buf = (char*) malloc (Siz + 1);
2627 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL) {
2628 snprintf (buf, Siz, " msg[%c] = %s ...\n", (char) forder[i],
2629 msg->cm_fields[(int)forder[i]]);
2630 client_write (buf, strlen(buf));
2639 * Check to see if any messages already exist in the current room which
2640 * carry the same Exclusive ID as this one. If any are found, delete them.
2642 void ReplicationChecks(struct CtdlMessage *msg) {
2643 long old_msgnum = (-1L);
2645 if (DoesThisRoomNeedEuidIndexing(&CC->room) == 0) return;
2647 CtdlLogPrintf(CTDL_DEBUG, "Performing replication checks in <%s>\n",
2650 /* No exclusive id? Don't do anything. */
2651 if (msg == NULL) return;
2652 if (msg->cm_fields['E'] == NULL) return;
2653 if (IsEmptyStr(msg->cm_fields['E'])) return;
2654 /*CtdlLogPrintf(CTDL_DEBUG, "Exclusive ID: <%s> for room <%s>\n",
2655 msg->cm_fields['E'], CC->room.QRname);*/
2657 old_msgnum = locate_message_by_euid(msg->cm_fields['E'], &CC->room);
2658 if (old_msgnum > 0L) {
2659 CtdlLogPrintf(CTDL_DEBUG, "ReplicationChecks() replacing message %ld\n", old_msgnum);
2660 CtdlDeleteMessages(CC->room.QRname, &old_msgnum, 1, "");
2667 * Save a message to disk and submit it into the delivery system.
2669 long CtdlSubmitMsg(struct CtdlMessage *msg, /* message to save */
2670 struct recptypes *recps, /* recipients (if mail) */
2671 char *force, /* force a particular room? */
2672 int flags /* should the bessage be exported clean? */
2674 char submit_filename[128];
2675 char generated_timestamp[32];
2676 char hold_rm[ROOMNAMELEN];
2677 char actual_rm[ROOMNAMELEN];
2678 char force_room[ROOMNAMELEN];
2679 char content_type[SIZ]; /* We have to learn this */
2680 char recipient[SIZ];
2683 struct ctdluser userbuf;
2685 struct MetaData smi;
2686 FILE *network_fp = NULL;
2687 static int seqnum = 1;
2688 struct CtdlMessage *imsg = NULL;
2690 size_t instr_alloc = 0;
2692 char *hold_R, *hold_D;
2693 char *collected_addresses = NULL;
2694 struct addresses_to_be_filed *aptr = NULL;
2695 char *saved_rfc822_version = NULL;
2696 int qualified_for_journaling = 0;
2697 struct CitContext *CCC = CC; /* CachedCitContext - performance boost */
2698 char bounce_to[1024] = "";
2702 CtdlLogPrintf(CTDL_DEBUG, "CtdlSubmitMsg() called\n");
2703 if (is_valid_message(msg) == 0) return(-1); /* self check */
2705 /* If this message has no timestamp, we take the liberty of
2706 * giving it one, right now.
2708 if (msg->cm_fields['T'] == NULL) {
2709 snprintf(generated_timestamp, sizeof generated_timestamp, "%ld", (long)time(NULL));
2710 msg->cm_fields['T'] = strdup(generated_timestamp);
2713 /* If this message has no path, we generate one.
2715 if (msg->cm_fields['P'] == NULL) {
2716 if (msg->cm_fields['A'] != NULL) {
2717 msg->cm_fields['P'] = strdup(msg->cm_fields['A']);
2718 for (a=0; !IsEmptyStr(&msg->cm_fields['P'][a]); ++a) {
2719 if (isspace(msg->cm_fields['P'][a])) {
2720 msg->cm_fields['P'][a] = ' ';
2725 msg->cm_fields['P'] = strdup("unknown");
2729 if (force == NULL) {
2730 strcpy(force_room, "");
2733 strcpy(force_room, force);
2736 /* Learn about what's inside, because it's what's inside that counts */
2737 if (msg->cm_fields['M'] == NULL) {
2738 CtdlLogPrintf(CTDL_ERR, "ERROR: attempt to save message with NULL body\n");
2742 switch (msg->cm_format_type) {
2744 strcpy(content_type, "text/x-citadel-variformat");
2747 strcpy(content_type, "text/plain");
2750 strcpy(content_type, "text/plain");
2751 mptr = bmstrcasestr(msg->cm_fields['M'], "Content-type:");
2754 safestrncpy(content_type, &mptr[13], sizeof content_type);
2755 striplt(content_type);
2756 aptr = content_type;
2757 while (!IsEmptyStr(aptr)) {
2769 /* Goto the correct room */
2770 CtdlLogPrintf(CTDL_DEBUG, "Selected room %s\n", (recps) ? CCC->room.QRname : SENTITEMS);
2771 strcpy(hold_rm, CCC->room.QRname);
2772 strcpy(actual_rm, CCC->room.QRname);
2773 if (recps != NULL) {
2774 strcpy(actual_rm, SENTITEMS);
2777 /* If the user is a twit, move to the twit room for posting */
2779 if (CCC->user.axlevel == 2) {
2780 strcpy(hold_rm, actual_rm);
2781 strcpy(actual_rm, config.c_twitroom);
2782 CtdlLogPrintf(CTDL_DEBUG, "Diverting to twit room\n");
2786 /* ...or if this message is destined for Aide> then go there. */
2787 if (!IsEmptyStr(force_room)) {
2788 strcpy(actual_rm, force_room);
2791 CtdlLogPrintf(CTDL_DEBUG, "Final selection: %s\n", actual_rm);
2792 if (strcasecmp(actual_rm, CCC->room.QRname)) {
2793 /* CtdlGetRoom(&CCC->room, actual_rm); */
2794 CtdlUserGoto(actual_rm, 0, 1, NULL, NULL);
2798 * If this message has no O (room) field, generate one.
2800 if (msg->cm_fields['O'] == NULL) {
2801 msg->cm_fields['O'] = strdup(CCC->room.QRname);
2804 /* Perform "before save" hooks (aborting if any return nonzero) */
2805 CtdlLogPrintf(CTDL_DEBUG, "Performing before-save hooks\n");
2806 if (PerformMessageHooks(msg, EVT_BEFORESAVE) > 0) return(-3);
2809 * If this message has an Exclusive ID, and the room is replication
2810 * checking enabled, then do replication checks.
2812 if (DoesThisRoomNeedEuidIndexing(&CCC->room)) {
2813 ReplicationChecks(msg);
2816 /* Save it to disk */
2817 CtdlLogPrintf(CTDL_DEBUG, "Saving to disk\n");
2818 newmsgid = send_message(msg);
2819 if (newmsgid <= 0L) return(-5);
2821 /* Write a supplemental message info record. This doesn't have to
2822 * be a critical section because nobody else knows about this message
2825 CtdlLogPrintf(CTDL_DEBUG, "Creating MetaData record\n");
2826 memset(&smi, 0, sizeof(struct MetaData));
2827 smi.meta_msgnum = newmsgid;
2828 smi.meta_refcount = 0;
2829 safestrncpy(smi.meta_content_type, content_type,
2830 sizeof smi.meta_content_type);
2833 * Measure how big this message will be when rendered as RFC822.
2834 * We do this for two reasons:
2835 * 1. We need the RFC822 length for the new metadata record, so the
2836 * POP and IMAP services don't have to calculate message lengths
2837 * while the user is waiting (multiplied by potentially hundreds
2838 * or thousands of messages).
2839 * 2. If journaling is enabled, we will need an RFC822 version of the
2840 * message to attach to the journalized copy.
2842 if (CCC->redirect_buffer != NULL) {
2843 CtdlLogPrintf(CTDL_ALERT, "CCC->redirect_buffer is not NULL during message submission!\n");
2846 CCC->redirect_buffer = malloc(SIZ);
2847 CCC->redirect_len = 0;
2848 CCC->redirect_alloc = SIZ;
2849 CtdlOutputPreLoadedMsg(msg, MT_RFC822, HEADERS_ALL, 0, 1, QP_EADDR);
2850 smi.meta_rfc822_length = CCC->redirect_len;
2851 saved_rfc822_version = CCC->redirect_buffer;
2852 CCC->redirect_buffer = NULL;
2853 CCC->redirect_len = 0;
2854 CCC->redirect_alloc = 0;
2858 /* Now figure out where to store the pointers */
2859 CtdlLogPrintf(CTDL_DEBUG, "Storing pointers\n");
2861 /* If this is being done by the networker delivering a private
2862 * message, we want to BYPASS saving the sender's copy (because there
2863 * is no local sender; it would otherwise go to the Trashcan).
2865 if ((!CCC->internal_pgm) || (recps == NULL)) {
2866 if (CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 1, msg) != 0) {
2867 CtdlLogPrintf(CTDL_ERR, "ERROR saving message pointer!\n");
2868 CtdlSaveMsgPointerInRoom(config.c_aideroom, newmsgid, 0, msg);
2872 /* For internet mail, drop a copy in the outbound queue room */
2873 if ((recps != NULL) && (recps->num_internet > 0)) {
2874 CtdlSaveMsgPointerInRoom(SMTP_SPOOLOUT_ROOM, newmsgid, 0, msg);
2877 /* If other rooms are specified, drop them there too. */
2878 if ((recps != NULL) && (recps->num_room > 0))
2879 for (i=0; i<num_tokens(recps->recp_room, '|'); ++i) {
2880 extract_token(recipient, recps->recp_room, i,
2881 '|', sizeof recipient);
2882 CtdlLogPrintf(CTDL_DEBUG, "Delivering to room <%s>\n", recipient);
2883 CtdlSaveMsgPointerInRoom(recipient, newmsgid, 0, msg);
2886 /* Bump this user's messages posted counter. */
2887 CtdlLogPrintf(CTDL_DEBUG, "Updating user\n");
2888 lgetuser(&CCC->user, CCC->curr_user);
2889 CCC->user.posted = CCC->user.posted + 1;
2890 lputuser(&CCC->user);
2892 /* Decide where bounces need to be delivered */
2893 if ((recps != NULL) && (recps->bounce_to != NULL)) {
2894 safestrncpy(bounce_to, recps->bounce_to, sizeof bounce_to);
2896 else if (CCC->logged_in) {
2897 snprintf(bounce_to, sizeof bounce_to, "%s@%s", CCC->user.fullname, config.c_nodename);
2900 snprintf(bounce_to, sizeof bounce_to, "%s@%s", msg->cm_fields['A'], msg->cm_fields['N']);
2903 /* If this is private, local mail, make a copy in the
2904 * recipient's mailbox and bump the reference count.
2906 if ((recps != NULL) && (recps->num_local > 0))
2907 for (i=0; i<num_tokens(recps->recp_local, '|'); ++i) {
2908 extract_token(recipient, recps->recp_local, i,
2909 '|', sizeof recipient);
2910 CtdlLogPrintf(CTDL_DEBUG, "Delivering private local mail to <%s>\n",
2912 if (getuser(&userbuf, recipient) == 0) {
2913 // Add a flag so the Funambol module knows its mail
2914 msg->cm_fields['W'] = strdup(recipient);
2915 MailboxName(actual_rm, sizeof actual_rm, &userbuf, MAILROOM);
2916 CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 0, msg);
2917 BumpNewMailCounter(userbuf.usernum);
2918 if (!IsEmptyStr(config.c_funambol_host) || !IsEmptyStr(config.c_pager_program)) {
2919 /* Generate a instruction message for the Funambol notification
2920 * server, in the same style as the SMTP queue
2923 instr = malloc(instr_alloc);
2924 snprintf(instr, instr_alloc,
2925 "Content-type: %s\n\nmsgid|%ld\nsubmitted|%ld\n"
2927 SPOOLMIME, newmsgid, (long)time(NULL),
2931 imsg = malloc(sizeof(struct CtdlMessage));
2932 memset(imsg, 0, sizeof(struct CtdlMessage));
2933 imsg->cm_magic = CTDLMESSAGE_MAGIC;
2934 imsg->cm_anon_type = MES_NORMAL;
2935 imsg->cm_format_type = FMT_RFC822;
2936 imsg->cm_fields['A'] = strdup("Citadel");
2937 imsg->cm_fields['J'] = strdup("do not journal");
2938 imsg->cm_fields['M'] = instr; /* imsg owns this memory now */
2939 imsg->cm_fields['W'] = strdup(recipient);
2940 CtdlSubmitMsg(imsg, NULL, FNBL_QUEUE_ROOM, 0);
2941 CtdlFreeMessage(imsg);
2945 CtdlLogPrintf(CTDL_DEBUG, "No user <%s>\n", recipient);
2946 CtdlSaveMsgPointerInRoom(config.c_aideroom,
2951 /* Perform "after save" hooks */
2952 CtdlLogPrintf(CTDL_DEBUG, "Performing after-save hooks\n");
2953 PerformMessageHooks(msg, EVT_AFTERSAVE);
2955 /* For IGnet mail, we have to save a new copy into the spooler for
2956 * each recipient, with the R and D fields set to the recipient and
2957 * destination-node. This has two ugly side effects: all other
2958 * recipients end up being unlisted in this recipient's copy of the
2959 * message, and it has to deliver multiple messages to the same
2960 * node. We'll revisit this again in a year or so when everyone has
2961 * a network spool receiver that can handle the new style messages.
2963 if ((recps != NULL) && (recps->num_ignet > 0))
2964 for (i=0; i<num_tokens(recps->recp_ignet, '|'); ++i) {
2965 extract_token(recipient, recps->recp_ignet, i,
2966 '|', sizeof recipient);
2968 hold_R = msg->cm_fields['R'];
2969 hold_D = msg->cm_fields['D'];
2970 msg->cm_fields['R'] = malloc(SIZ);
2971 msg->cm_fields['D'] = malloc(128);
2972 extract_token(msg->cm_fields['R'], recipient, 0, '@', SIZ);
2973 extract_token(msg->cm_fields['D'], recipient, 1, '@', 128);
2975 serialize_message(&smr, msg);
2977 snprintf(submit_filename, sizeof submit_filename,
2978 "%s/netmail.%04lx.%04x.%04x",
2980 (long) getpid(), CCC->cs_pid, ++seqnum);
2981 network_fp = fopen(submit_filename, "wb+");
2982 if (network_fp != NULL) {
2983 rv = fwrite(smr.ser, smr.len, 1, network_fp);
2989 free(msg->cm_fields['R']);
2990 free(msg->cm_fields['D']);
2991 msg->cm_fields['R'] = hold_R;
2992 msg->cm_fields['D'] = hold_D;
2995 /* Go back to the room we started from */
2996 CtdlLogPrintf(CTDL_DEBUG, "Returning to original room %s\n", hold_rm);
2997 if (strcasecmp(hold_rm, CCC->room.QRname))
2998 CtdlUserGoto(hold_rm, 0, 1, NULL, NULL);
3000 /* For internet mail, generate delivery instructions.
3001 * Yes, this is recursive. Deal with it. Infinite recursion does
3002 * not happen because the delivery instructions message does not
3003 * contain a recipient.
3005 if ((recps != NULL) && (recps->num_internet > 0)) {
3006 CtdlLogPrintf(CTDL_DEBUG, "Generating delivery instructions\n");
3008 instr = malloc(instr_alloc);
3009 snprintf(instr, instr_alloc,
3010 "Content-type: %s\n\nmsgid|%ld\nsubmitted|%ld\n"
3012 SPOOLMIME, newmsgid, (long)time(NULL),
3016 if (recps->envelope_from != NULL) {
3017 tmp = strlen(instr);
3018 snprintf(&instr[tmp], instr_alloc-tmp, "envelope_from|%s\n", recps->envelope_from);
3021 for (i=0; i<num_tokens(recps->recp_internet, '|'); ++i) {
3022 tmp = strlen(instr);
3023 extract_token(recipient, recps->recp_internet, i, '|', sizeof recipient);
3024 if ((tmp + strlen(recipient) + 32) > instr_alloc) {
3025 instr_alloc = instr_alloc * 2;
3026 instr = realloc(instr, instr_alloc);
3028 snprintf(&instr[tmp], instr_alloc - tmp, "remote|%s|0||\n", recipient);
3031 imsg = malloc(sizeof(struct CtdlMessage));
3032 memset(imsg, 0, sizeof(struct CtdlMessage));
3033 imsg->cm_magic = CTDLMESSAGE_MAGIC;
3034 imsg->cm_anon_type = MES_NORMAL;
3035 imsg->cm_format_type = FMT_RFC822;
3036 imsg->cm_fields['A'] = strdup("Citadel");
3037 imsg->cm_fields['J'] = strdup("do not journal");
3038 imsg->cm_fields['M'] = instr; /* imsg owns this memory now */
3039 CtdlSubmitMsg(imsg, NULL, SMTP_SPOOLOUT_ROOM, QP_EADDR);
3040 CtdlFreeMessage(imsg);
3044 * Any addresses to harvest for someone's address book?
3046 if ( (CCC->logged_in) && (recps != NULL) ) {
3047 collected_addresses = harvest_collected_addresses(msg);
3050 if (collected_addresses != NULL) {
3051 aptr = (struct addresses_to_be_filed *)
3052 malloc(sizeof(struct addresses_to_be_filed));
3053 MailboxName(actual_rm, sizeof actual_rm,
3054 &CCC->user, USERCONTACTSROOM);
3055 aptr->roomname = strdup(actual_rm);
3056 aptr->collected_addresses = collected_addresses;
3057 begin_critical_section(S_ATBF);
3060 end_critical_section(S_ATBF);
3064 * Determine whether this message qualifies for journaling.
3066 if (msg->cm_fields['J'] != NULL) {
3067 qualified_for_journaling = 0;
3070 if (recps == NULL) {
3071 qualified_for_journaling = config.c_journal_pubmsgs;
3073 else if (recps->num_local + recps->num_ignet + recps->num_internet > 0) {
3074 qualified_for_journaling = config.c_journal_email;
3077 qualified_for_journaling = config.c_journal_pubmsgs;
3082 * Do we have to perform journaling? If so, hand off the saved
3083 * RFC822 version will be handed off to the journaler for background
3084 * submit. Otherwise, we have to free the memory ourselves.
3086 if (saved_rfc822_version != NULL) {
3087 if (qualified_for_journaling) {
3088 JournalBackgroundSubmit(msg, saved_rfc822_version, recps);
3091 free(saved_rfc822_version);
3104 * Convenience function for generating small administrative messages.
3106 void quickie_message(const char *from, const char *fromaddr, char *to, char *room, const char *text,
3107 int format_type, const char *subject)
3109 struct CtdlMessage *msg;
3110 struct recptypes *recp = NULL;
3112 msg = malloc(sizeof(struct CtdlMessage));
3113 memset(msg, 0, sizeof(struct CtdlMessage));
3114 msg->cm_magic = CTDLMESSAGE_MAGIC;
3115 msg->cm_anon_type = MES_NORMAL;
3116 msg->cm_format_type = format_type;
3119 msg->cm_fields['A'] = strdup(from);
3121 else if (fromaddr != NULL) {
3122 msg->cm_fields['A'] = strdup(fromaddr);
3123 if (strchr(msg->cm_fields['A'], '@')) {
3124 *strchr(msg->cm_fields['A'], '@') = 0;
3128 msg->cm_fields['A'] = strdup("Citadel");
3131 if (fromaddr != NULL) msg->cm_fields['F'] = strdup(fromaddr);
3132 if (room != NULL) msg->cm_fields['O'] = strdup(room);
3133 msg->cm_fields['N'] = strdup(NODENAME);
3135 msg->cm_fields['R'] = strdup(to);
3136 recp = validate_recipients(to, NULL, 0);
3138 if (subject != NULL) {
3139 msg->cm_fields['U'] = strdup(subject);
3141 msg->cm_fields['M'] = strdup(text);
3143 CtdlSubmitMsg(msg, recp, room, 0);
3144 CtdlFreeMessage(msg);
3145 if (recp != NULL) free_recipients(recp);
3151 * Back end function used by CtdlMakeMessage() and similar functions
3153 char *CtdlReadMessageBody(char *terminator, /* token signalling EOT */
3154 size_t maxlen, /* maximum message length */
3155 char *exist, /* if non-null, append to it;
3156 exist is ALWAYS freed */
3157 int crlf, /* CRLF newlines instead of LF */
3158 int sock /* socket handle or 0 for this session's client socket */
3162 size_t message_len = 0;
3163 size_t buffer_len = 0;
3170 if (exist == NULL) {
3177 message_len = strlen(exist);
3178 buffer_len = message_len + 4096;
3179 m = realloc(exist, buffer_len);
3186 /* Do we need to change leading ".." to "." for SMTP escaping? */
3187 if (!strcmp(terminator, ".")) {
3191 /* flush the input if we have nowhere to store it */
3196 /* read in the lines of message text one by one */
3199 if (sock_getln(sock, buf, (sizeof buf - 3)) < 0) finished = 1;
3202 if (client_getln(buf, (sizeof buf - 3)) < 1) finished = 1;
3204 if (!strcmp(buf, terminator)) finished = 1;
3206 strcat(buf, "\r\n");
3212 /* Unescape SMTP-style input of two dots at the beginning of the line */
3214 if (!strncmp(buf, "..", 2)) {
3215 strcpy(buf, &buf[1]);
3219 if ( (!flushing) && (!finished) ) {
3220 /* Measure the line */
3221 linelen = strlen(buf);
3223 /* augment the buffer if we have to */
3224 if ((message_len + linelen) >= buffer_len) {
3225 ptr = realloc(m, (buffer_len * 2) );
3226 if (ptr == NULL) { /* flush if can't allocate */
3229 buffer_len = (buffer_len * 2);
3231 CtdlLogPrintf(CTDL_DEBUG, "buffer_len is now %ld\n", (long)buffer_len);
3235 /* Add the new line to the buffer. NOTE: this loop must avoid
3236 * using functions like strcat() and strlen() because they
3237 * traverse the entire buffer upon every call, and doing that
3238 * for a multi-megabyte message slows it down beyond usability.
3240 strcpy(&m[message_len], buf);
3241 message_len += linelen;
3244 /* if we've hit the max msg length, flush the rest */
3245 if (message_len >= maxlen) flushing = 1;
3247 } while (!finished);
3255 * Build a binary message to be saved on disk.
3256 * (NOTE: if you supply 'preformatted_text', the buffer you give it
3257 * will become part of the message. This means you are no longer
3258 * responsible for managing that memory -- it will be freed along with
3259 * the rest of the fields when CtdlFreeMessage() is called.)
3262 struct CtdlMessage *CtdlMakeMessage(
3263 struct ctdluser *author, /* author's user structure */
3264 char *recipient, /* NULL if it's not mail */
3265 char *recp_cc, /* NULL if it's not mail */
3266 char *room, /* room where it's going */
3267 int type, /* see MES_ types in header file */
3268 int format_type, /* variformat, plain text, MIME... */
3269 char *fake_name, /* who we're masquerading as */
3270 char *my_email, /* which of my email addresses to use (empty is ok) */
3271 char *subject, /* Subject (optional) */
3272 char *supplied_euid, /* ...or NULL if this is irrelevant */
3273 char *preformatted_text, /* ...or NULL to read text from client */
3274 char *references /* Thread references */
3276 char dest_node[256];
3278 struct CtdlMessage *msg;
3280 msg = malloc(sizeof(struct CtdlMessage));
3281 memset(msg, 0, sizeof(struct CtdlMessage));
3282 msg->cm_magic = CTDLMESSAGE_MAGIC;
3283 msg->cm_anon_type = type;
3284 msg->cm_format_type = format_type;
3286 /* Don't confuse the poor folks if it's not routed mail. */
3287 strcpy(dest_node, "");
3289 if (recipient != NULL) striplt(recipient);
3290 if (recp_cc != NULL) striplt(recp_cc);
3292 /* Path or Return-Path */
3293 if (my_email == NULL) my_email = "";
3295 if (!IsEmptyStr(my_email)) {
3296 msg->cm_fields['P'] = strdup(my_email);
3299 snprintf(buf, sizeof buf, "%s", author->fullname);
3300 msg->cm_fields['P'] = strdup(buf);
3302 convert_spaces_to_underscores(msg->cm_fields['P']);
3304 snprintf(buf, sizeof buf, "%ld", (long)time(NULL)); /* timestamp */
3305 msg->cm_fields['T'] = strdup(buf);
3307 if ((fake_name != NULL) && (fake_name[0])) { /* author */
3308 msg->cm_fields['A'] = strdup(fake_name);
3311 msg->cm_fields['A'] = strdup(author->fullname);
3314 if (CC->room.QRflags & QR_MAILBOX) { /* room */
3315 msg->cm_fields['O'] = strdup(&CC->room.QRname[11]);
3318 msg->cm_fields['O'] = strdup(CC->room.QRname);
3321 msg->cm_fields['N'] = strdup(NODENAME); /* nodename */
3322 msg->cm_fields['H'] = strdup(HUMANNODE); /* hnodename */
3324 if ((recipient != NULL) && (recipient[0] != 0)) {
3325 msg->cm_fields['R'] = strdup(recipient);
3327 if ((recp_cc != NULL) && (recp_cc[0] != 0)) {
3328 msg->cm_fields['Y'] = strdup(recp_cc);
3330 if (dest_node[0] != 0) {
3331 msg->cm_fields['D'] = strdup(dest_node);
3334 if (!IsEmptyStr(my_email)) {
3335 msg->cm_fields['F'] = strdup(my_email);
3337 else if ( (author == &CC->user) && (!IsEmptyStr(CC->cs_inet_email)) ) {
3338 msg->cm_fields['F'] = strdup(CC->cs_inet_email);
3341 if (subject != NULL) {
3344 length = strlen(subject);
3350 while ((subject[i] != '\0') &&
3351 (IsAscii = isascii(subject[i]) != 0 ))
3354 msg->cm_fields['U'] = strdup(subject);
3355 else /* ok, we've got utf8 in the string. */
3357 msg->cm_fields['U'] = rfc2047encode(subject, length);
3363 if (supplied_euid != NULL) {
3364 msg->cm_fields['E'] = strdup(supplied_euid);
3367 if (references != NULL) {
3368 if (!IsEmptyStr(references)) {
3369 msg->cm_fields['W'] = strdup(references);
3373 if (preformatted_text != NULL) {
3374 msg->cm_fields['M'] = preformatted_text;
3377 msg->cm_fields['M'] = CtdlReadMessageBody("000", config.c_maxmsglen, NULL, 0, 0);
3385 * Check to see whether we have permission to post a message in the current
3386 * room. Returns a *CITADEL ERROR CODE* and puts a message in errmsgbuf, or
3387 * returns 0 on success.
3389 int CtdlDoIHavePermissionToPostInThisRoom(char *errmsgbuf,
3391 const char* RemoteIdentifier,
3395 if (!(CC->logged_in) &&
3396 (PostPublic == POST_LOGGED_IN)) {
3397 snprintf(errmsgbuf, n, "Not logged in.");
3398 return (ERROR + NOT_LOGGED_IN);
3400 else if (PostPublic == CHECK_EXISTANCE) {
3401 return (0); // We're Evaling whether a recipient exists
3403 else if (!(CC->logged_in)) {
3405 if ((CC->room.QRflags & QR_READONLY)) {
3406 snprintf(errmsgbuf, n, "Not logged in.");
3407 return (ERROR + NOT_LOGGED_IN);
3409 if (CC->room.QRflags2 & QR2_MODERATED) {
3410 snprintf(errmsgbuf, n, "Not logged in Moderation feature not yet implemented!");
3411 return (ERROR + NOT_LOGGED_IN);
3413 if ((PostPublic!=POST_LMTP) &&(CC->room.QRflags2 & QR2_SMTP_PUBLIC) == 0) {
3418 if (RemoteIdentifier == NULL)
3420 snprintf(errmsgbuf, n, "Need sender to permit access.");
3421 return (ERROR + USERNAME_REQUIRED);
3424 assoc_file_name(filename, sizeof filename, &CC->room, ctdl_netcfg_dir);
3425 begin_critical_section(S_NETCONFIGS);
3426 if (!read_spoolcontrol_file(&sc, filename))
3428 end_critical_section(S_NETCONFIGS);
3429 snprintf(errmsgbuf, n,
3430 "This mailing list only accepts posts from subscribers.");
3431 return (ERROR + NO_SUCH_USER);
3433 end_critical_section(S_NETCONFIGS);
3434 found = is_recipient (sc, RemoteIdentifier);
3435 free_spoolcontrol_struct(&sc);
3440 snprintf(errmsgbuf, n,
3441 "This mailing list only accepts posts from subscribers.");
3442 return (ERROR + NO_SUCH_USER);
3449 if ((CC->user.axlevel < 2)
3450 && ((CC->room.QRflags & QR_MAILBOX) == 0)) {
3451 snprintf(errmsgbuf, n, "Need to be validated to enter "
3452 "(except in %s> to sysop)", MAILROOM);
3453 return (ERROR + HIGHER_ACCESS_REQUIRED);
3456 CtdlRoomAccess(&CC->room, &CC->user, &ra, NULL);
3457 if (!(ra & UA_POSTALLOWED)) {
3458 snprintf(errmsgbuf, n, "Higher access is required to post in this room.");
3459 return (ERROR + HIGHER_ACCESS_REQUIRED);
3462 strcpy(errmsgbuf, "Ok");
3468 * Check to see if the specified user has Internet mail permission
3469 * (returns nonzero if permission is granted)
3471 int CtdlCheckInternetMailPermission(struct ctdluser *who) {
3473 /* Do not allow twits to send Internet mail */
3474 if (who->axlevel <= 2) return(0);
3476 /* Globally enabled? */
3477 if (config.c_restrict == 0) return(1);
3479 /* User flagged ok? */
3480 if (who->flags & US_INTERNET) return(2);
3482 /* Aide level access? */
3483 if (who->axlevel >= 6) return(3);
3485 /* No mail for you! */
3491 * Validate recipients, count delivery types and errors, and handle aliasing
3492 * FIXME check for dupes!!!!!
3494 * Returns 0 if all addresses are ok, ret->num_error = -1 if no addresses
3495 * were specified, or the number of addresses found invalid.
3497 * Caller needs to free the result using free_recipients()
3499 struct recptypes *validate_recipients(char *supplied_recipients,
3500 const char *RemoteIdentifier,
3502 struct recptypes *ret;
3503 char *recipients = NULL;
3504 char this_recp[256];
3505 char this_recp_cooked[256];
3511 struct ctdluser tempUS;
3512 struct ctdlroom tempQR;
3513 struct ctdlroom tempQR2;
3519 ret = (struct recptypes *) malloc(sizeof(struct recptypes));
3520 if (ret == NULL) return(NULL);
3522 /* Set all strings to null and numeric values to zero */
3523 memset(ret, 0, sizeof(struct recptypes));
3525 if (supplied_recipients == NULL) {
3526 recipients = strdup("");
3529 recipients = strdup(supplied_recipients);
3532 /* Allocate some memory. Yes, this allocates 500% more memory than we will
3533 * actually need, but it's healthier for the heap than doing lots of tiny
3534 * realloc() calls instead.
3537 ret->errormsg = malloc(strlen(recipients) + 1024);
3538 ret->recp_local = malloc(strlen(recipients) + 1024);
3539 ret->recp_internet = malloc(strlen(recipients) + 1024);
3540 ret->recp_ignet = malloc(strlen(recipients) + 1024);
3541 ret->recp_room = malloc(strlen(recipients) + 1024);
3542 ret->display_recp = malloc(strlen(recipients) + 1024);
3544 ret->errormsg[0] = 0;
3545 ret->recp_local[0] = 0;
3546 ret->recp_internet[0] = 0;
3547 ret->recp_ignet[0] = 0;
3548 ret->recp_room[0] = 0;
3549 ret->display_recp[0] = 0;
3551 ret->recptypes_magic = RECPTYPES_MAGIC;
3553 /* Change all valid separator characters to commas */
3554 for (i=0; !IsEmptyStr(&recipients[i]); ++i) {
3555 if ((recipients[i] == ';') || (recipients[i] == '|')) {
3556 recipients[i] = ',';
3560 /* Now start extracting recipients... */
3562 while (!IsEmptyStr(recipients)) {
3564 for (i=0; i<=strlen(recipients); ++i) {
3565 if (recipients[i] == '\"') in_quotes = 1 - in_quotes;
3566 if ( ( (recipients[i] == ',') && (!in_quotes) ) || (recipients[i] == 0) ) {
3567 safestrncpy(this_recp, recipients, i+1);
3569 if (recipients[i] == ',') {
3570 strcpy(recipients, &recipients[i+1]);
3573 strcpy(recipients, "");
3580 if (IsEmptyStr(this_recp))
3582 CtdlLogPrintf(CTDL_DEBUG, "Evaluating recipient #%d: %s\n", num_recps, this_recp);
3584 mailtype = alias(this_recp);
3585 mailtype = alias(this_recp);
3586 mailtype = alias(this_recp);
3588 for (j=0; !IsEmptyStr(&this_recp[j]); ++j) {
3589 if (this_recp[j]=='_') {
3590 this_recp_cooked[j] = ' ';
3593 this_recp_cooked[j] = this_recp[j];
3596 this_recp_cooked[j] = '\0';
3601 if (!strcasecmp(this_recp, "sysop")) {
3603 strcpy(this_recp, config.c_aideroom);
3604 if (!IsEmptyStr(ret->recp_room)) {
3605 strcat(ret->recp_room, "|");
3607 strcat(ret->recp_room, this_recp);
3609 else if ( (!strncasecmp(this_recp, "room_", 5))
3610 && (!CtdlGetRoom(&tempQR, &this_recp_cooked[5])) ) {
3612 /* Save room so we can restore it later */
3616 /* Check permissions to send mail to this room */
3617 err = CtdlDoIHavePermissionToPostInThisRoom(errmsg,
3629 if (!IsEmptyStr(ret->recp_room)) {
3630 strcat(ret->recp_room, "|");
3632 strcat(ret->recp_room, &this_recp_cooked[5]);
3635 /* Restore room in case something needs it */
3639 else if (getuser(&tempUS, this_recp) == 0) {
3641 strcpy(this_recp, tempUS.fullname);
3642 if (!IsEmptyStr(ret->recp_local)) {
3643 strcat(ret->recp_local, "|");
3645 strcat(ret->recp_local, this_recp);
3647 else if (getuser(&tempUS, this_recp_cooked) == 0) {
3649 strcpy(this_recp, tempUS.fullname);
3650 if (!IsEmptyStr(ret->recp_local)) {
3651 strcat(ret->recp_local, "|");
3653 strcat(ret->recp_local, this_recp);
3661 /* Yes, you're reading this correctly: if the target
3662 * domain points back to the local system or an attached
3663 * Citadel directory, the address is invalid. That's
3664 * because if the address were valid, we would have
3665 * already translated it to a local address by now.
3667 if (IsDirectory(this_recp, 0)) {
3672 ++ret->num_internet;
3673 if (!IsEmptyStr(ret->recp_internet)) {
3674 strcat(ret->recp_internet, "|");
3676 strcat(ret->recp_internet, this_recp);
3681 if (!IsEmptyStr(ret->recp_ignet)) {
3682 strcat(ret->recp_ignet, "|");
3684 strcat(ret->recp_ignet, this_recp);
3692 if (IsEmptyStr(errmsg)) {
3693 snprintf(append, sizeof append, "Invalid recipient: %s", this_recp);
3696 snprintf(append, sizeof append, "%s", errmsg);
3698 if ( (strlen(ret->errormsg) + strlen(append) + 3) < SIZ) {
3699 if (!IsEmptyStr(ret->errormsg)) {
3700 strcat(ret->errormsg, "; ");
3702 strcat(ret->errormsg, append);
3706 if (IsEmptyStr(ret->display_recp)) {
3707 strcpy(append, this_recp);
3710 snprintf(append, sizeof append, ", %s", this_recp);
3712 if ( (strlen(ret->display_recp)+strlen(append)) < SIZ) {
3713 strcat(ret->display_recp, append);
3718 if ((ret->num_local + ret->num_internet + ret->num_ignet +
3719 ret->num_room + ret->num_error) == 0) {
3720 ret->num_error = (-1);
3721 strcpy(ret->errormsg, "No recipients specified.");
3724 CtdlLogPrintf(CTDL_DEBUG, "validate_recipients()\n");
3725 CtdlLogPrintf(CTDL_DEBUG, " local: %d <%s>\n", ret->num_local, ret->recp_local);
3726 CtdlLogPrintf(CTDL_DEBUG, " room: %d <%s>\n", ret->num_room, ret->recp_room);
3727 CtdlLogPrintf(CTDL_DEBUG, " inet: %d <%s>\n", ret->num_internet, ret->recp_internet);
3728 CtdlLogPrintf(CTDL_DEBUG, " ignet: %d <%s>\n", ret->num_ignet, ret->recp_ignet);
3729 CtdlLogPrintf(CTDL_DEBUG, " error: %d <%s>\n", ret->num_error, ret->errormsg);
3737 * Destructor for struct recptypes
3739 void free_recipients(struct recptypes *valid) {
3741 if (valid == NULL) {
3745 if (valid->recptypes_magic != RECPTYPES_MAGIC) {
3746 CtdlLogPrintf(CTDL_EMERG, "Attempt to call free_recipients() on some other data type!\n");
3750 if (valid->errormsg != NULL) free(valid->errormsg);
3751 if (valid->recp_local != NULL) free(valid->recp_local);
3752 if (valid->recp_internet != NULL) free(valid->recp_internet);
3753 if (valid->recp_ignet != NULL) free(valid->recp_ignet);
3754 if (valid->recp_room != NULL) free(valid->recp_room);
3755 if (valid->display_recp != NULL) free(valid->display_recp);
3756 if (valid->bounce_to != NULL) free(valid->bounce_to);
3757 if (valid->envelope_from != NULL) free(valid->envelope_from);
3764 * message entry - mode 0 (normal)
3766 void cmd_ent0(char *entargs)
3772 char supplied_euid[128];
3774 int format_type = 0;
3775 char newusername[256];
3776 char newuseremail[256];
3777 struct CtdlMessage *msg;
3781 struct recptypes *valid = NULL;
3782 struct recptypes *valid_to = NULL;
3783 struct recptypes *valid_cc = NULL;
3784 struct recptypes *valid_bcc = NULL;
3786 int subject_required = 0;
3791 int newuseremail_ok = 0;
3792 char references[SIZ];
3797 post = extract_int(entargs, 0);
3798 extract_token(recp, entargs, 1, '|', sizeof recp);
3799 anon_flag = extract_int(entargs, 2);
3800 format_type = extract_int(entargs, 3);
3801 extract_token(subject, entargs, 4, '|', sizeof subject);
3802 extract_token(newusername, entargs, 5, '|', sizeof newusername);
3803 do_confirm = extract_int(entargs, 6);
3804 extract_token(cc, entargs, 7, '|', sizeof cc);
3805 extract_token(bcc, entargs, 8, '|', sizeof bcc);
3806 switch(CC->room.QRdefaultview) {
3809 extract_token(supplied_euid, entargs, 9, '|', sizeof supplied_euid);
3812 supplied_euid[0] = 0;
3815 extract_token(newuseremail, entargs, 10, '|', sizeof newuseremail);
3816 extract_token(references, entargs, 11, '|', sizeof references);
3817 for (ptr=references; *ptr != 0; ++ptr) {
3818 if (*ptr == '!') *ptr = '|';
3821 /* first check to make sure the request is valid. */
3823 err = CtdlDoIHavePermissionToPostInThisRoom(errmsg, sizeof errmsg, NULL, POST_LOGGED_IN);
3826 cprintf("%d %s\n", err, errmsg);
3830 /* Check some other permission type things. */
3832 if (IsEmptyStr(newusername)) {
3833 strcpy(newusername, CC->user.fullname);
3835 if ( (CC->user.axlevel < 6)
3836 && (strcasecmp(newusername, CC->user.fullname))
3837 && (strcasecmp(newusername, CC->cs_inet_fn))
3839 cprintf("%d You don't have permission to author messages as '%s'.\n",
3840 ERROR + HIGHER_ACCESS_REQUIRED,
3847 if (IsEmptyStr(newuseremail)) {
3848 newuseremail_ok = 1;
3851 if (!IsEmptyStr(newuseremail)) {
3852 if (!strcasecmp(newuseremail, CC->cs_inet_email)) {
3853 newuseremail_ok = 1;
3855 else if (!IsEmptyStr(CC->cs_inet_other_emails)) {
3856 j = num_tokens(CC->cs_inet_other_emails, '|');
3857 for (i=0; i<j; ++i) {
3858 extract_token(buf, CC->cs_inet_other_emails, i, '|', sizeof buf);
3859 if (!strcasecmp(newuseremail, buf)) {
3860 newuseremail_ok = 1;
3866 if (!newuseremail_ok) {
3867 cprintf("%d You don't have permission to author messages as '%s'.\n",
3868 ERROR + HIGHER_ACCESS_REQUIRED,
3874 CC->cs_flags |= CS_POSTING;
3876 /* In mailbox rooms we have to behave a little differently --
3877 * make sure the user has specified at least one recipient. Then
3878 * validate the recipient(s). We do this for the Mail> room, as
3879 * well as any room which has the "Mailbox" view set.
3882 if ( ( (CC->room.QRflags & QR_MAILBOX) && (!strcasecmp(&CC->room.QRname[11], MAILROOM)) )
3883 || ( (CC->room.QRflags & QR_MAILBOX) && (CC->curr_view == VIEW_MAILBOX) )
3885 if (CC->user.axlevel < 2) {
3886 strcpy(recp, "sysop");
3891 valid_to = validate_recipients(recp, NULL, 0);
3892 if (valid_to->num_error > 0) {
3893 cprintf("%d %s\n", ERROR + NO_SUCH_USER, valid_to->errormsg);
3894 free_recipients(valid_to);
3898 valid_cc = validate_recipients(cc, NULL, 0);
3899 if (valid_cc->num_error > 0) {
3900 cprintf("%d %s\n", ERROR + NO_SUCH_USER, valid_cc->errormsg);
3901 free_recipients(valid_to);
3902 free_recipients(valid_cc);
3906 valid_bcc = validate_recipients(bcc, NULL, 0);
3907 if (valid_bcc->num_error > 0) {
3908 cprintf("%d %s\n", ERROR + NO_SUCH_USER, valid_bcc->errormsg);
3909 free_recipients(valid_to);
3910 free_recipients(valid_cc);
3911 free_recipients(valid_bcc);
3915 /* Recipient required, but none were specified */
3916 if ( (valid_to->num_error < 0) && (valid_cc->num_error < 0) && (valid_bcc->num_error < 0) ) {
3917 free_recipients(valid_to);
3918 free_recipients(valid_cc);
3919 free_recipients(valid_bcc);
3920 cprintf("%d At least one recipient is required.\n", ERROR + NO_SUCH_USER);
3924 if (valid_to->num_internet + valid_cc->num_internet + valid_bcc->num_internet > 0) {
3925 if (CtdlCheckInternetMailPermission(&CC->user)==0) {
3926 cprintf("%d You do not have permission "
3927 "to send Internet mail.\n",
3928 ERROR + HIGHER_ACCESS_REQUIRED);
3929 free_recipients(valid_to);
3930 free_recipients(valid_cc);
3931 free_recipients(valid_bcc);
3936 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)
3937 && (CC->user.axlevel < 4) ) {
3938 cprintf("%d Higher access required for network mail.\n",
3939 ERROR + HIGHER_ACCESS_REQUIRED);
3940 free_recipients(valid_to);
3941 free_recipients(valid_cc);
3942 free_recipients(valid_bcc);
3946 if ((RESTRICT_INTERNET == 1)
3947 && (valid_to->num_internet + valid_cc->num_internet + valid_bcc->num_internet > 0)
3948 && ((CC->user.flags & US_INTERNET) == 0)
3949 && (!CC->internal_pgm)) {
3950 cprintf("%d You don't have access to Internet mail.\n",
3951 ERROR + HIGHER_ACCESS_REQUIRED);
3952 free_recipients(valid_to);
3953 free_recipients(valid_cc);
3954 free_recipients(valid_bcc);
3960 /* Is this a room which has anonymous-only or anonymous-option? */
3961 anonymous = MES_NORMAL;
3962 if (CC->room.QRflags & QR_ANONONLY) {
3963 anonymous = MES_ANONONLY;
3965 if (CC->room.QRflags & QR_ANONOPT) {
3966 if (anon_flag == 1) { /* only if the user requested it */
3967 anonymous = MES_ANONOPT;
3971 if ((CC->room.QRflags & QR_MAILBOX) == 0) {
3975 /* Recommend to the client that the use of a message subject is
3976 * strongly recommended in this room, if either the SUBJECTREQ flag
3977 * is set, or if there is one or more Internet email recipients.
3979 if (CC->room.QRflags2 & QR2_SUBJECTREQ) subject_required = 1;
3980 if ((valid_to) && (valid_to->num_internet > 0)) subject_required = 1;
3981 if ((valid_cc) && (valid_cc->num_internet > 0)) subject_required = 1;
3982 if ((valid_bcc) && (valid_bcc->num_internet > 0)) subject_required = 1;
3984 /* If we're only checking the validity of the request, return
3985 * success without creating the message.
3988 cprintf("%d %s|%d\n", CIT_OK,
3989 ((valid_to != NULL) ? valid_to->display_recp : ""),
3991 free_recipients(valid_to);
3992 free_recipients(valid_cc);
3993 free_recipients(valid_bcc);
3997 /* We don't need these anymore because we'll do it differently below */
3998 free_recipients(valid_to);
3999 free_recipients(valid_cc);
4000 free_recipients(valid_bcc);
4002 /* Read in the message from the client. */
4004 cprintf("%d send message\n", START_CHAT_MODE);
4006 cprintf("%d send message\n", SEND_LISTING);
4009 msg = CtdlMakeMessage(&CC->user, recp, cc,
4010 CC->room.QRname, anonymous, format_type,
4011 newusername, newuseremail, subject,
4012 ((!IsEmptyStr(supplied_euid)) ? supplied_euid : NULL),
4015 /* Put together one big recipients struct containing to/cc/bcc all in
4016 * one. This is for the envelope.
4018 char *all_recps = malloc(SIZ * 3);
4019 strcpy(all_recps, recp);
4020 if (!IsEmptyStr(cc)) {
4021 if (!IsEmptyStr(all_recps)) {
4022 strcat(all_recps, ",");
4024 strcat(all_recps, cc);
4026 if (!IsEmptyStr(bcc)) {
4027 if (!IsEmptyStr(all_recps)) {
4028 strcat(all_recps, ",");
4030 strcat(all_recps, bcc);
4032 if (!IsEmptyStr(all_recps)) {
4033 valid = validate_recipients(all_recps, NULL, 0);
4041 msgnum = CtdlSubmitMsg(msg, valid, "", QP_EADDR);
4044 cprintf("%ld\n", msgnum);
4046 cprintf("Message accepted.\n");
4049 cprintf("Internal error.\n");
4051 if (msg->cm_fields['E'] != NULL) {
4052 cprintf("%s\n", msg->cm_fields['E']);
4059 CtdlFreeMessage(msg);
4061 if (valid != NULL) {
4062 free_recipients(valid);
4070 * API function to delete messages which match a set of criteria
4071 * (returns the actual number of messages deleted)
4073 int CtdlDeleteMessages(char *room_name, /* which room */
4074 long *dmsgnums, /* array of msg numbers to be deleted */
4075 int num_dmsgnums, /* number of msgs to be deleted, or 0 for "any" */
4076 char *content_type /* or "" for any. regular expressions expected. */
4079 struct ctdlroom qrbuf;
4080 struct cdbdata *cdbfr;
4081 long *msglist = NULL;
4082 long *dellist = NULL;
4085 int num_deleted = 0;
4087 struct MetaData smi;
4090 int need_to_free_re = 0;
4092 if (content_type) if (!IsEmptyStr(content_type)) {
4093 regcomp(&re, content_type, 0);
4094 need_to_free_re = 1;
4096 CtdlLogPrintf(CTDL_DEBUG, "CtdlDeleteMessages(%s, %d msgs, %s)\n",
4097 room_name, num_dmsgnums, content_type);
4099 /* get room record, obtaining a lock... */
4100 if (CtdlGetRoomLock(&qrbuf, room_name) != 0) {
4101 CtdlLogPrintf(CTDL_ERR, "CtdlDeleteMessages(): Room <%s> not found\n",
4103 if (need_to_free_re) regfree(&re);
4104 return (0); /* room not found */
4106 cdbfr = cdb_fetch(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long));
4108 if (cdbfr != NULL) {
4109 dellist = malloc(cdbfr->len);
4110 msglist = (long *) cdbfr->ptr;
4111 cdbfr->ptr = NULL; /* CtdlDeleteMessages() now owns this memory */
4112 num_msgs = cdbfr->len / sizeof(long);
4116 for (i = 0; i < num_msgs; ++i) {
4119 /* Set/clear a bit for each criterion */
4121 /* 0 messages in the list or a null list means that we are
4122 * interested in deleting any messages which meet the other criteria.
4124 if ((num_dmsgnums == 0) || (dmsgnums == NULL)) {
4125 delete_this |= 0x01;
4128 for (j=0; j<num_dmsgnums; ++j) {
4129 if (msglist[i] == dmsgnums[j]) {
4130 delete_this |= 0x01;
4135 if (IsEmptyStr(content_type)) {
4136 delete_this |= 0x02;
4138 GetMetaData(&smi, msglist[i]);
4139 if (regexec(&re, smi.meta_content_type, 1, &pm, 0) == 0) {
4140 delete_this |= 0x02;
4144 /* Delete message only if all bits are set */
4145 if (delete_this == 0x03) {
4146 dellist[num_deleted++] = msglist[i];
4151 num_msgs = sort_msglist(msglist, num_msgs);
4152 cdb_store(CDB_MSGLISTS, &qrbuf.QRnumber, (int)sizeof(long),
4153 msglist, (int)(num_msgs * sizeof(long)));
4155 qrbuf.QRhighest = msglist[num_msgs - 1];
4157 CtdlPutRoomLock(&qrbuf);
4159 /* Go through the messages we pulled out of the index, and decrement
4160 * their reference counts by 1. If this is the only room the message
4161 * was in, the reference count will reach zero and the message will
4162 * automatically be deleted from the database. We do this in a
4163 * separate pass because there might be plug-in hooks getting called,
4164 * and we don't want that happening during an S_ROOMS critical
4167 if (num_deleted) for (i=0; i<num_deleted; ++i) {
4168 PerformDeleteHooks(qrbuf.QRname, dellist[i]);
4169 AdjRefCount(dellist[i], -1);
4172 /* Now free the memory we used, and go away. */
4173 if (msglist != NULL) free(msglist);
4174 if (dellist != NULL) free(dellist);
4175 CtdlLogPrintf(CTDL_DEBUG, "%d message(s) deleted.\n", num_deleted);
4176 if (need_to_free_re) regfree(&re);
4177 return (num_deleted);
4183 * Check whether the current user has permission to delete messages from
4184 * the current room (returns 1 for yes, 0 for no)
4186 int CtdlDoIHavePermissionToDeleteMessagesFromThisRoom(void) {
4188 CtdlRoomAccess(&CC->room, &CC->user, &ra, NULL);
4189 if (ra & UA_DELETEALLOWED) return(1);
4197 * Delete message from current room
4199 void cmd_dele(char *args)
4208 extract_token(msgset, args, 0, '|', sizeof msgset);
4209 num_msgs = num_tokens(msgset, ',');
4211 cprintf("%d Nothing to do.\n", CIT_OK);
4215 if (CtdlDoIHavePermissionToDeleteMessagesFromThisRoom() == 0) {
4216 cprintf("%d Higher access required.\n",
4217 ERROR + HIGHER_ACCESS_REQUIRED);
4222 * Build our message set to be moved/copied
4224 msgs = malloc(num_msgs * sizeof(long));
4225 for (i=0; i<num_msgs; ++i) {
4226 extract_token(msgtok, msgset, i, ',', sizeof msgtok);
4227 msgs[i] = atol(msgtok);
4230 num_deleted = CtdlDeleteMessages(CC->room.QRname, msgs, num_msgs, "");
4234 cprintf("%d %d message%s deleted.\n", CIT_OK,
4235 num_deleted, ((num_deleted != 1) ? "s" : ""));
4237 cprintf("%d Message not found.\n", ERROR + MESSAGE_NOT_FOUND);
4245 * move or copy a message to another room
4247 void cmd_move(char *args)
4254 char targ[ROOMNAMELEN];
4255 struct ctdlroom qtemp;
4262 extract_token(msgset, args, 0, '|', sizeof msgset);
4263 num_msgs = num_tokens(msgset, ',');
4265 cprintf("%d Nothing to do.\n", CIT_OK);
4269 extract_token(targ, args, 1, '|', sizeof targ);
4270 convert_room_name_macros(targ, sizeof targ);
4271 targ[ROOMNAMELEN - 1] = 0;
4272 is_copy = extract_int(args, 2);
4274 if (CtdlGetRoom(&qtemp, targ) != 0) {
4275 cprintf("%d '%s' does not exist.\n", ERROR + ROOM_NOT_FOUND, targ);
4279 if (!strcasecmp(qtemp.QRname, CC->room.QRname)) {
4280 cprintf("%d Source and target rooms are the same.\n", ERROR + ALREADY_EXISTS);
4284 getuser(&CC->user, CC->curr_user);
4285 CtdlRoomAccess(&qtemp, &CC->user, &ra, NULL);
4287 /* Check for permission to perform this operation.
4288 * Remember: "CC->room" is source, "qtemp" is target.
4292 /* Aides can move/copy */
4293 if (CC->user.axlevel >= 6) permit = 1;
4295 /* Room aides can move/copy */
4296 if (CC->user.usernum == CC->room.QRroomaide) permit = 1;
4298 /* Permit move/copy from personal rooms */
4299 if ((CC->room.QRflags & QR_MAILBOX)
4300 && (qtemp.QRflags & QR_MAILBOX)) permit = 1;
4302 /* Permit only copy from public to personal room */
4304 && (!(CC->room.QRflags & QR_MAILBOX))
4305 && (qtemp.QRflags & QR_MAILBOX)) permit = 1;
4307 /* Permit message removal from collaborative delete rooms */
4308 if (CC->room.QRflags2 & QR2_COLLABDEL) permit = 1;
4310 /* Users allowed to post into the target room may move into it too. */
4311 if ((CC->room.QRflags & QR_MAILBOX) &&
4312 (qtemp.QRflags & UA_POSTALLOWED)) permit = 1;
4314 /* User must have access to target room */
4315 if (!(ra & UA_KNOWN)) permit = 0;
4318 cprintf("%d Higher access required.\n",
4319 ERROR + HIGHER_ACCESS_REQUIRED);
4324 * Build our message set to be moved/copied
4326 msgs = malloc(num_msgs * sizeof(long));
4327 for (i=0; i<num_msgs; ++i) {
4328 extract_token(msgtok, msgset, i, ',', sizeof msgtok);
4329 msgs[i] = atol(msgtok);
4335 err = CtdlSaveMsgPointersInRoom(targ, msgs, num_msgs, 1, NULL);
4337 cprintf("%d Cannot store message(s) in %s: error %d\n",
4343 /* Now delete the message from the source room,
4344 * if this is a 'move' rather than a 'copy' operation.
4347 CtdlDeleteMessages(CC->room.QRname, msgs, num_msgs, "");
4351 cprintf("%d Message(s) %s.\n", CIT_OK, (is_copy ? "copied" : "moved") );
4357 * GetMetaData() - Get the supplementary record for a message
4359 void GetMetaData(struct MetaData *smibuf, long msgnum)
4362 struct cdbdata *cdbsmi;
4365 memset(smibuf, 0, sizeof(struct MetaData));
4366 smibuf->meta_msgnum = msgnum;
4367 smibuf->meta_refcount = 1; /* Default reference count is 1 */
4369 /* Use the negative of the message number for its supp record index */
4370 TheIndex = (0L - msgnum);
4372 cdbsmi = cdb_fetch(CDB_MSGMAIN, &TheIndex, sizeof(long));
4373 if (cdbsmi == NULL) {
4374 return; /* record not found; go with defaults */
4376 memcpy(smibuf, cdbsmi->ptr,
4377 ((cdbsmi->len > sizeof(struct MetaData)) ?
4378 sizeof(struct MetaData) : cdbsmi->len));
4385 * PutMetaData() - (re)write supplementary record for a message
4387 void PutMetaData(struct MetaData *smibuf)
4391 /* Use the negative of the message number for the metadata db index */
4392 TheIndex = (0L - smibuf->meta_msgnum);
4394 cdb_store(CDB_MSGMAIN,
4395 &TheIndex, (int)sizeof(long),
4396 smibuf, (int)sizeof(struct MetaData));
4401 * AdjRefCount - submit an adjustment to the reference count for a message.
4402 * (These are just queued -- we actually process them later.)
4404 void AdjRefCount(long msgnum, int incr)
4406 struct arcq new_arcq;
4409 begin_critical_section(S_SUPPMSGMAIN);
4410 if (arcfp == NULL) {
4411 arcfp = fopen(file_arcq, "ab+");
4413 end_critical_section(S_SUPPMSGMAIN);
4415 /* msgnum < 0 means that we're trying to close the file */
4417 CtdlLogPrintf(CTDL_DEBUG, "Closing the AdjRefCount queue file\n");
4418 begin_critical_section(S_SUPPMSGMAIN);
4419 if (arcfp != NULL) {
4423 end_critical_section(S_SUPPMSGMAIN);
4428 * If we can't open the queue, perform the operation synchronously.
4430 if (arcfp == NULL) {
4431 TDAP_AdjRefCount(msgnum, incr);
4435 new_arcq.arcq_msgnum = msgnum;
4436 new_arcq.arcq_delta = incr;
4437 rv = fwrite(&new_arcq, sizeof(struct arcq), 1, arcfp);
4445 * TDAP_ProcessAdjRefCountQueue()
4447 * Process the queue of message count adjustments that was created by calls
4448 * to AdjRefCount() ... by reading the queue and calling TDAP_AdjRefCount()
4449 * for each one. This should be an "off hours" operation.
4451 int TDAP_ProcessAdjRefCountQueue(void)
4453 char file_arcq_temp[PATH_MAX];
4456 struct arcq arcq_rec;
4457 int num_records_processed = 0;
4459 snprintf(file_arcq_temp, sizeof file_arcq_temp, "%s.%04x", file_arcq, rand());
4461 begin_critical_section(S_SUPPMSGMAIN);
4462 if (arcfp != NULL) {
4467 r = link(file_arcq, file_arcq_temp);
4469 CtdlLogPrintf(CTDL_CRIT, "%s: %s\n", file_arcq_temp, strerror(errno));
4470 end_critical_section(S_SUPPMSGMAIN);
4471 return(num_records_processed);
4475 end_critical_section(S_SUPPMSGMAIN);
4477 fp = fopen(file_arcq_temp, "rb");
4479 CtdlLogPrintf(CTDL_CRIT, "%s: %s\n", file_arcq_temp, strerror(errno));
4480 return(num_records_processed);
4483 while (fread(&arcq_rec, sizeof(struct arcq), 1, fp) == 1) {
4484 TDAP_AdjRefCount(arcq_rec.arcq_msgnum, arcq_rec.arcq_delta);
4485 ++num_records_processed;
4489 r = unlink(file_arcq_temp);
4491 CtdlLogPrintf(CTDL_CRIT, "%s: %s\n", file_arcq_temp, strerror(errno));
4494 return(num_records_processed);
4500 * TDAP_AdjRefCount - adjust the reference count for a message.
4501 * This one does it "for real" because it's called by
4502 * the autopurger function that processes the queue
4503 * created by AdjRefCount(). If a message's reference
4504 * count becomes zero, we also delete the message from
4505 * disk and de-index it.
4507 void TDAP_AdjRefCount(long msgnum, int incr)
4510 struct MetaData smi;
4513 /* This is a *tight* critical section; please keep it that way, as
4514 * it may get called while nested in other critical sections.
4515 * Complicating this any further will surely cause deadlock!
4517 begin_critical_section(S_SUPPMSGMAIN);
4518 GetMetaData(&smi, msgnum);
4519 smi.meta_refcount += incr;
4521 end_critical_section(S_SUPPMSGMAIN);
4522 CtdlLogPrintf(CTDL_DEBUG, "msg %ld ref count delta %+d, is now %d\n",
4523 msgnum, incr, smi.meta_refcount);
4525 /* If the reference count is now zero, delete the message
4526 * (and its supplementary record as well).
4528 if (smi.meta_refcount == 0) {
4529 CtdlLogPrintf(CTDL_DEBUG, "Deleting message <%ld>\n", msgnum);
4531 /* Call delete hooks with NULL room to show it has gone altogether */
4532 PerformDeleteHooks(NULL, msgnum);
4534 /* Remove from message base */
4536 cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long));
4537 cdb_delete(CDB_BIGMSGS, &delnum, (int)sizeof(long));
4539 /* Remove metadata record */
4540 delnum = (0L - msgnum);
4541 cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long));
4547 * Write a generic object to this room
4549 * Note: this could be much more efficient. Right now we use two temporary
4550 * files, and still pull the message into memory as with all others.
4552 void CtdlWriteObject(char *req_room, /* Room to stuff it in */
4553 char *content_type, /* MIME type of this object */
4554 char *raw_message, /* Data to be written */
4555 off_t raw_length, /* Size of raw_message */
4556 struct ctdluser *is_mailbox, /* Mailbox room? */
4557 int is_binary, /* Is encoding necessary? */
4558 int is_unique, /* Del others of this type? */
4559 unsigned int flags /* Internal save flags */
4563 struct ctdlroom qrbuf;
4564 char roomname[ROOMNAMELEN];
4565 struct CtdlMessage *msg;
4566 char *encoded_message = NULL;
4568 if (is_mailbox != NULL) {
4569 MailboxName(roomname, sizeof roomname, is_mailbox, req_room);
4572 safestrncpy(roomname, req_room, sizeof(roomname));
4575 CtdlLogPrintf(CTDL_DEBUG, "Raw length is %ld\n", (long)raw_length);
4578 encoded_message = malloc((size_t) (((raw_length * 134) / 100) + 4096 ) );
4581 encoded_message = malloc((size_t)(raw_length + 4096));
4584 sprintf(encoded_message, "Content-type: %s\n", content_type);
4587 sprintf(&encoded_message[strlen(encoded_message)],
4588 "Content-transfer-encoding: base64\n\n"
4592 sprintf(&encoded_message[strlen(encoded_message)],
4593 "Content-transfer-encoding: 7bit\n\n"
4599 &encoded_message[strlen(encoded_message)],
4607 &encoded_message[strlen(encoded_message)],
4613 CtdlLogPrintf(CTDL_DEBUG, "Allocating\n");
4614 msg = malloc(sizeof(struct CtdlMessage));
4615 memset(msg, 0, sizeof(struct CtdlMessage));
4616 msg->cm_magic = CTDLMESSAGE_MAGIC;
4617 msg->cm_anon_type = MES_NORMAL;
4618 msg->cm_format_type = 4;
4619 msg->cm_fields['A'] = strdup(CC->user.fullname);
4620 msg->cm_fields['O'] = strdup(req_room);
4621 msg->cm_fields['N'] = strdup(config.c_nodename);
4622 msg->cm_fields['H'] = strdup(config.c_humannode);
4623 msg->cm_flags = flags;
4625 msg->cm_fields['M'] = encoded_message;
4627 /* Create the requested room if we have to. */
4628 if (CtdlGetRoom(&qrbuf, roomname) != 0) {
4629 CtdlCreateRoom(roomname,
4630 ( (is_mailbox != NULL) ? 5 : 3 ),
4631 "", 0, 1, 0, VIEW_BBS);
4633 /* If the caller specified this object as unique, delete all
4634 * other objects of this type that are currently in the room.
4637 CtdlLogPrintf(CTDL_DEBUG, "Deleted %d other msgs of this type\n",
4638 CtdlDeleteMessages(roomname, NULL, 0, content_type)
4641 /* Now write the data */
4642 CtdlSubmitMsg(msg, NULL, roomname, 0);
4643 CtdlFreeMessage(msg);
4651 void CtdlGetSysConfigBackend(long msgnum, void *userdata) {
4652 config_msgnum = msgnum;
4656 char *CtdlGetSysConfig(char *sysconfname) {
4657 char hold_rm[ROOMNAMELEN];
4660 struct CtdlMessage *msg;
4663 strcpy(hold_rm, CC->room.QRname);
4664 if (CtdlGetRoom(&CC->room, SYSCONFIGROOM) != 0) {
4665 CtdlGetRoom(&CC->room, hold_rm);
4670 /* We want the last (and probably only) config in this room */
4671 begin_critical_section(S_CONFIG);
4672 config_msgnum = (-1L);
4673 CtdlForEachMessage(MSGS_LAST, 1, NULL, sysconfname, NULL,
4674 CtdlGetSysConfigBackend, NULL);
4675 msgnum = config_msgnum;
4676 end_critical_section(S_CONFIG);
4682 msg = CtdlFetchMessage(msgnum, 1);
4684 conf = strdup(msg->cm_fields['M']);
4685 CtdlFreeMessage(msg);
4692 CtdlGetRoom(&CC->room, hold_rm);
4694 if (conf != NULL) do {
4695 extract_token(buf, conf, 0, '\n', sizeof buf);
4696 strcpy(conf, &conf[strlen(buf)+1]);
4697 } while ( (!IsEmptyStr(conf)) && (!IsEmptyStr(buf)) );
4703 void CtdlPutSysConfig(char *sysconfname, char *sysconfdata) {
4704 CtdlWriteObject(SYSCONFIGROOM, sysconfname, sysconfdata, (strlen(sysconfdata)+1), NULL, 0, 1, 0);
4709 * Determine whether a given Internet address belongs to the current user
4711 int CtdlIsMe(char *addr, int addr_buf_len)
4713 struct recptypes *recp;
4716 recp = validate_recipients(addr, NULL, 0);
4717 if (recp == NULL) return(0);
4719 if (recp->num_local == 0) {
4720 free_recipients(recp);
4724 for (i=0; i<recp->num_local; ++i) {
4725 extract_token(addr, recp->recp_local, i, '|', addr_buf_len);
4726 if (!strcasecmp(addr, CC->user.fullname)) {
4727 free_recipients(recp);
4732 free_recipients(recp);
4738 * Citadel protocol command to do the same
4740 void cmd_isme(char *argbuf) {
4743 if (CtdlAccessCheck(ac_logged_in)) return;
4744 extract_token(addr, argbuf, 0, '|', sizeof addr);
4746 if (CtdlIsMe(addr, sizeof addr)) {
4747 cprintf("%d %s\n", CIT_OK, addr);
4750 cprintf("%d Not you.\n", ERROR + ILLEGAL_VALUE);
4756 /*****************************************************************************/
4757 /* MODULE INITIALIZATION STUFF */
4758 /*****************************************************************************/
4760 CTDL_MODULE_INIT(msgbase)
4762 CtdlRegisterProtoHook(cmd_msgs, "MSGS", "Output a list of messages in the current room");
4763 CtdlRegisterProtoHook(cmd_msg0, "MSG0", "Output a message in plain text format");
4764 CtdlRegisterProtoHook(cmd_msg2, "MSG2", "Output a message in RFC822 format");
4765 CtdlRegisterProtoHook(cmd_msg3, "MSG3", "Output a message in raw format (deprecated)");
4766 CtdlRegisterProtoHook(cmd_msg4, "MSG4", "Output a message in the client's preferred format");
4767 CtdlRegisterProtoHook(cmd_msgp, "MSGP", "Select preferred format for MSG4 output");
4768 CtdlRegisterProtoHook(cmd_opna, "OPNA", "Open an attachment for download");
4769 CtdlRegisterProtoHook(cmd_dlat, "DLAT", "Download an attachment");
4770 CtdlRegisterProtoHook(cmd_ent0, "ENT0", "Enter a message");
4771 CtdlRegisterProtoHook(cmd_dele, "DELE", "Delete a message");
4772 CtdlRegisterProtoHook(cmd_move, "MOVE", "Move or copy a message to another room");
4773 CtdlRegisterProtoHook(cmd_isme, "ISME", "Determine whether an email address belongs to a user");
4775 /* return our Subversion id for the Log */