4 * Implements the message store.
14 #if TIME_WITH_SYS_TIME
15 # include <sys/time.h>
19 # include <sys/time.h>
32 #include <sys/types.h>
34 #include <libcitadel.h>
37 #include "serv_extensions.h"
41 #include "sysdep_decls.h"
42 #include "citserver.h"
49 #include "internet_addressing.h"
50 #include "euidindex.h"
51 #include "journaling.h"
52 #include "citadel_dirs.h"
53 #include "clientsocket.h"
54 #include "serv_network.h"
57 #include "ctdl_module.h"
60 struct addresses_to_be_filed *atbf = NULL;
62 /* This temp file holds the queue of operations for AdjRefCount() */
63 static FILE *arcfp = NULL;
66 * This really belongs in serv_network.c, but I don't know how to export
67 * symbols between modules.
69 struct FilterList *filterlist = NULL;
73 * These are the four-character field headers we use when outputting
74 * messages in Citadel format (as opposed to RFC822 format).
77 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
78 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
79 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
80 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
81 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
82 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
83 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
84 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
113 * This function is self explanatory.
114 * (What can I say, I'm in a weird mood today...)
116 void remove_any_whitespace_to_the_left_or_right_of_at_symbol(char *name)
120 for (i = 0; i < strlen(name); ++i) {
121 if (name[i] == '@') {
122 while (isspace(name[i - 1]) && i > 0) {
123 strcpy(&name[i - 1], &name[i]);
126 while (isspace(name[i + 1])) {
127 strcpy(&name[i + 1], &name[i + 2]);
135 * Aliasing for network mail.
136 * (Error messages have been commented out, because this is a server.)
138 int alias(char *name)
139 { /* process alias and routing info for mail */
142 char aaa[SIZ], bbb[SIZ];
143 char *ignetcfg = NULL;
144 char *ignetmap = NULL;
150 char original_name[256];
151 safestrncpy(original_name, name, sizeof original_name);
154 remove_any_whitespace_to_the_left_or_right_of_at_symbol(name);
155 stripallbut(name, '<', '>');
157 fp = fopen(file_mail_aliases, "r");
159 fp = fopen("/dev/null", "r");
166 while (fgets(aaa, sizeof aaa, fp) != NULL) {
167 while (isspace(name[0]))
168 strcpy(name, &name[1]);
169 aaa[strlen(aaa) - 1] = 0;
171 for (a = 0; a < strlen(aaa); ++a) {
173 strcpy(bbb, &aaa[a + 1]);
177 if (!strcasecmp(name, aaa))
182 /* Hit the Global Address Book */
183 if (CtdlDirectoryLookup(aaa, name, sizeof aaa) == 0) {
187 if (strcasecmp(original_name, name)) {
188 CtdlLogPrintf(CTDL_INFO, "%s is being forwarded to %s\n", original_name, name);
191 /* Change "user @ xxx" to "user" if xxx is an alias for this host */
192 for (a=0; a<strlen(name); ++a) {
193 if (name[a] == '@') {
194 if (CtdlHostAlias(&name[a+1]) == hostalias_localhost) {
196 CtdlLogPrintf(CTDL_INFO, "Changed to <%s>\n", name);
201 /* determine local or remote type, see citadel.h */
202 at = haschar(name, '@');
203 if (at == 0) return(MES_LOCAL); /* no @'s - local address */
204 if (at > 1) return(MES_ERROR); /* >1 @'s - invalid address */
205 remove_any_whitespace_to_the_left_or_right_of_at_symbol(name);
207 /* figure out the delivery mode */
208 extract_token(node, name, 1, '@', sizeof node);
210 /* If there are one or more dots in the nodename, we assume that it
211 * is an FQDN and will attempt SMTP delivery to the Internet.
213 if (haschar(node, '.') > 0) {
214 return(MES_INTERNET);
217 /* Otherwise we look in the IGnet maps for a valid Citadel node.
218 * Try directly-connected nodes first...
220 ignetcfg = CtdlGetSysConfig(IGNETCFG);
221 for (i=0; i<num_tokens(ignetcfg, '\n'); ++i) {
222 extract_token(buf, ignetcfg, i, '\n', sizeof buf);
223 extract_token(testnode, buf, 0, '|', sizeof testnode);
224 if (!strcasecmp(node, testnode)) {
232 * Then try nodes that are two or more hops away.
234 ignetmap = CtdlGetSysConfig(IGNETMAP);
235 for (i=0; i<num_tokens(ignetmap, '\n'); ++i) {
236 extract_token(buf, ignetmap, i, '\n', sizeof buf);
237 extract_token(testnode, buf, 0, '|', sizeof testnode);
238 if (!strcasecmp(node, testnode)) {
245 /* If we get to this point it's an invalid node name */
251 * Back end for the MSGS command: output message number only.
253 void simple_listing(long msgnum, void *userdata)
255 cprintf("%ld\n", msgnum);
261 * Back end for the MSGS command: output header summary.
263 void headers_listing(long msgnum, void *userdata)
265 struct CtdlMessage *msg;
267 msg = CtdlFetchMessage(msgnum, 0);
269 cprintf("%ld|0|||||\n", msgnum);
273 cprintf("%ld|%s|%s|%s|%s|%s|\n",
275 (msg->cm_fields['T'] ? msg->cm_fields['T'] : "0"),
276 (msg->cm_fields['A'] ? msg->cm_fields['A'] : ""),
277 (msg->cm_fields['N'] ? msg->cm_fields['N'] : ""),
278 (msg->cm_fields['F'] ? msg->cm_fields['F'] : ""),
279 (msg->cm_fields['U'] ? msg->cm_fields['U'] : "")
281 CtdlFreeMessage(msg);
286 /* Determine if a given message matches the fields in a message template.
287 * Return 0 for a successful match.
289 int CtdlMsgCmp(struct CtdlMessage *msg, struct CtdlMessage *template) {
292 /* If there aren't any fields in the template, all messages will
295 if (template == NULL) return(0);
297 /* Null messages are bogus. */
298 if (msg == NULL) return(1);
300 for (i='A'; i<='Z'; ++i) {
301 if (template->cm_fields[i] != NULL) {
302 if (msg->cm_fields[i] == NULL) {
303 /* Considered equal if temmplate is empty string */
304 if (IsEmptyStr(template->cm_fields[i])) continue;
307 if (strcasecmp(msg->cm_fields[i],
308 template->cm_fields[i])) return 1;
312 /* All compares succeeded: we have a match! */
319 * Retrieve the "seen" message list for the current room.
321 void CtdlGetSeen(char *buf, int which_set) {
324 /* Learn about the user and room in question */
325 CtdlGetRelationship(&vbuf, &CC->user, &CC->room);
327 if (which_set == ctdlsetseen_seen)
328 safestrncpy(buf, vbuf.v_seen, SIZ);
329 if (which_set == ctdlsetseen_answered)
330 safestrncpy(buf, vbuf.v_answered, SIZ);
336 * Manipulate the "seen msgs" string (or other message set strings)
338 void CtdlSetSeen(long *target_msgnums, int num_target_msgnums,
339 int target_setting, int which_set,
340 struct ctdluser *which_user, struct ctdlroom *which_room) {
341 struct cdbdata *cdbfr;
355 char *is_set; /* actually an array of booleans */
357 /* Don't bother doing *anything* if we were passed a list of zero messages */
358 if (num_target_msgnums < 1) {
362 /* If no room was specified, we go with the current room. */
364 which_room = &CC->room;
367 /* If no user was specified, we go with the current user. */
369 which_user = &CC->user;
372 CtdlLogPrintf(CTDL_DEBUG, "CtdlSetSeen(%d msgs starting with %ld, %s, %d) in <%s>\n",
373 num_target_msgnums, target_msgnums[0],
374 (target_setting ? "SET" : "CLEAR"),
378 /* Learn about the user and room in question */
379 CtdlGetRelationship(&vbuf, which_user, which_room);
381 /* Load the message list */
382 cdbfr = cdb_fetch(CDB_MSGLISTS, &which_room->QRnumber, sizeof(long));
384 msglist = (long *) cdbfr->ptr;
385 cdbfr->ptr = NULL; /* CtdlSetSeen() now owns this memory */
386 num_msgs = cdbfr->len / sizeof(long);
389 return; /* No messages at all? No further action. */
392 is_set = malloc(num_msgs * sizeof(char));
393 memset(is_set, 0, (num_msgs * sizeof(char)) );
395 /* Decide which message set we're manipulating */
397 case ctdlsetseen_seen:
398 vset = NewStrBufPlain(vbuf.v_seen, -1);
400 case ctdlsetseen_answered:
401 vset = NewStrBufPlain(vbuf.v_answered, -1);
408 #if 0 /* This is a special diagnostic section. Do not allow it to run during normal operation. */
409 CtdlLogPrintf(CTDL_DEBUG, "There are %d messages in the room.\n", num_msgs);
410 for (i=0; i<num_msgs; ++i) {
411 if ((i > 0) && (msglist[i] <= msglist[i-1])) abort();
413 CtdlLogPrintf(CTDL_DEBUG, "We are twiddling %d of them.\n", num_target_msgnums);
414 for (k=0; k<num_target_msgnums; ++k) {
415 if ((k > 0) && (target_msgnums[k] <= target_msgnums[k-1])) abort();
419 CtdlLogPrintf(CTDL_DEBUG, "before update: %s\n", ChrPtr(vset));
421 /* Translate the existing sequence set into an array of booleans */
422 setstr = NewStrBuf();
426 while (StrBufExtract_NextToken(setstr, vset, &pvset, ',') >= 0) {
428 StrBufExtract_token(lostr, setstr, 0, ':');
429 if (StrBufNum_tokens(setstr, ':') >= 2) {
430 StrBufExtract_token(histr, setstr, 1, ':');
434 StrBufAppendBuf(histr, lostr, 0);
437 if (!strcmp(ChrPtr(histr), "*")) {
444 for (i = 0; i < num_msgs; ++i) {
445 if ((msglist[i] >= lo) && (msglist[i] <= hi)) {
455 /* Now translate the array of booleans back into a sequence set */
461 for (i=0; i<num_msgs; ++i) {
465 for (k=0; k<num_target_msgnums; ++k) {
466 if (msglist[i] == target_msgnums[k]) {
467 is_seen = target_setting;
471 if ((was_seen == 0) && (is_seen == 1)) {
474 else if ((was_seen == 1) && (is_seen == 0)) {
477 if (StrLength(vset) > 0) {
478 StrBufAppendBufPlain(vset, HKEY(","), 0);
481 StrBufAppendPrintf(vset, "%ld", hi);
484 StrBufAppendPrintf(vset, "%ld:%ld", lo, hi);
488 if ((is_seen) && (i == num_msgs - 1)) {
489 if (StrLength(vset) > 0) {
490 StrBufAppendBufPlain(vset, HKEY(","), 0);
492 if ((i==0) || (was_seen == 0)) {
493 StrBufAppendPrintf(vset, "%ld", msglist[i]);
496 StrBufAppendPrintf(vset, "%ld:%ld", lo, msglist[i]);
504 * We will have to stuff this string back into a 4096 byte buffer, so if it's
505 * larger than that now, truncate it by removing tokens from the beginning.
506 * The limit of 100 iterations is there to prevent an infinite loop in case
507 * something unexpected happens.
509 int number_of_truncations = 0;
510 while ( (StrLength(vset) > SIZ) && (number_of_truncations < 100) ) {
511 StrBufRemove_token(vset, 0, ',');
512 ++number_of_truncations;
516 * If we're truncating the sequence set of messages marked with the 'seen' flag,
517 * we want the earliest messages (the truncated ones) to be marked, not unmarked.
518 * Otherwise messages at the beginning will suddenly appear to be 'unseen'.
520 if ( (which_set == ctdlsetseen_seen) && (number_of_truncations > 0) ) {
522 first_tok = NewStrBuf();
523 StrBufExtract_token(first_tok, vset, 0, ',');
524 StrBufRemove_token(vset, 0, ',');
526 if (StrBufNum_tokens(first_tok, ':') > 1) {
527 StrBufRemove_token(first_tok, 0, ':');
531 new_set = NewStrBuf();
532 StrBufAppendBufPlain(new_set, HKEY("1:"), 0);
533 StrBufAppendBuf(new_set, first_tok, 0);
534 StrBufAppendBufPlain(new_set, HKEY(":"), 0);
535 StrBufAppendBuf(new_set, vset, 0);
538 FreeStrBuf(&first_tok);
542 CtdlLogPrintf(CTDL_DEBUG, " after update: %s\n", ChrPtr(vset));
544 /* Decide which message set we're manipulating */
546 case ctdlsetseen_seen:
547 safestrncpy(vbuf.v_seen, ChrPtr(vset), sizeof vbuf.v_seen);
549 case ctdlsetseen_answered:
550 safestrncpy(vbuf.v_answered, ChrPtr(vset), sizeof vbuf.v_answered);
556 CtdlSetRelationship(&vbuf, which_user, which_room);
562 * API function to perform an operation for each qualifying message in the
563 * current room. (Returns the number of messages processed.)
565 int CtdlForEachMessage(int mode, long ref, char *search_string,
567 struct CtdlMessage *compare,
568 void (*CallBack) (long, void *),
574 struct cdbdata *cdbfr;
575 long *msglist = NULL;
577 int num_processed = 0;
580 struct CtdlMessage *msg = NULL;
583 int printed_lastold = 0;
584 int num_search_msgs = 0;
585 long *search_msgs = NULL;
587 int need_to_free_re = 0;
590 if ((content_type) && (!IsEmptyStr(content_type))) {
591 regcomp(&re, content_type, 0);
595 /* Learn about the user and room in question */
596 getuser(&CC->user, CC->curr_user);
597 CtdlGetRelationship(&vbuf, &CC->user, &CC->room);
599 /* Load the message list */
600 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long));
602 msglist = (long *) cdbfr->ptr;
603 num_msgs = cdbfr->len / sizeof(long);
605 if (need_to_free_re) regfree(&re);
606 return 0; /* No messages at all? No further action. */
611 * Now begin the traversal.
613 if (num_msgs > 0) for (a = 0; a < num_msgs; ++a) {
615 /* If the caller is looking for a specific MIME type, filter
616 * out all messages which are not of the type requested.
618 if ((content_type != NULL) && (!IsEmptyStr(content_type))) {
620 /* This call to GetMetaData() sits inside this loop
621 * so that we only do the extra database read per msg
622 * if we need to. Doing the extra read all the time
623 * really kills the server. If we ever need to use
624 * metadata for another search criterion, we need to
625 * move the read somewhere else -- but still be smart
626 * enough to only do the read if the caller has
627 * specified something that will need it.
629 GetMetaData(&smi, msglist[a]);
631 /* if (strcasecmp(smi.meta_content_type, content_type)) { old non-regex way */
632 if (regexec(&re, smi.meta_content_type, 1, &pm, 0) != 0) {
638 num_msgs = sort_msglist(msglist, num_msgs);
640 /* If a template was supplied, filter out the messages which
641 * don't match. (This could induce some delays!)
644 if (compare != NULL) {
645 for (a = 0; a < num_msgs; ++a) {
646 msg = CtdlFetchMessage(msglist[a], 1);
648 if (CtdlMsgCmp(msg, compare)) {
651 CtdlFreeMessage(msg);
657 /* If a search string was specified, get a message list from
658 * the full text index and remove messages which aren't on both
662 * Since the lists are sorted and strictly ascending, and the
663 * output list is guaranteed to be shorter than or equal to the
664 * input list, we overwrite the bottom of the input list. This
665 * eliminates the need to memmove big chunks of the list over and
668 if ( (num_msgs > 0) && (mode == MSGS_SEARCH) && (search_string) ) {
670 /* Call search module via hook mechanism.
671 * NULL means use any search function available.
672 * otherwise replace with a char * to name of search routine
674 CtdlModuleDoSearch(&num_search_msgs, &search_msgs, search_string, "fulltext");
676 if (num_search_msgs > 0) {
680 orig_num_msgs = num_msgs;
682 for (i=0; i<orig_num_msgs; ++i) {
683 for (j=0; j<num_search_msgs; ++j) {
684 if (msglist[i] == search_msgs[j]) {
685 msglist[num_msgs++] = msglist[i];
691 num_msgs = 0; /* No messages qualify */
693 if (search_msgs != NULL) free(search_msgs);
695 /* Now that we've purged messages which don't contain the search
696 * string, treat a MSGS_SEARCH just like a MSGS_ALL from this
703 * Now iterate through the message list, according to the
704 * criteria supplied by the caller.
707 for (a = 0; a < num_msgs; ++a) {
708 thismsg = msglist[a];
709 if (mode == MSGS_ALL) {
713 is_seen = is_msg_in_sequence_set(
714 vbuf.v_seen, thismsg);
715 if (is_seen) lastold = thismsg;
721 || ((mode == MSGS_OLD) && (is_seen))
722 || ((mode == MSGS_NEW) && (!is_seen))
723 || ((mode == MSGS_LAST) && (a >= (num_msgs - ref)))
724 || ((mode == MSGS_FIRST) && (a < ref))
725 || ((mode == MSGS_GT) && (thismsg > ref))
726 || ((mode == MSGS_EQ) && (thismsg == ref))
729 if ((mode == MSGS_NEW) && (CC->user.flags & US_LASTOLD) && (lastold > 0L) && (printed_lastold == 0) && (!is_seen)) {
731 CallBack(lastold, userdata);
735 if (CallBack) CallBack(thismsg, userdata);
739 cdb_free(cdbfr); /* Clean up */
740 if (need_to_free_re) regfree(&re);
741 return num_processed;
747 * cmd_msgs() - get list of message #'s in this room
748 * implements the MSGS server command using CtdlForEachMessage()
750 void cmd_msgs(char *cmdbuf)
759 int with_template = 0;
760 struct CtdlMessage *template = NULL;
761 int with_headers = 0;
762 char search_string[1024];
764 extract_token(which, cmdbuf, 0, '|', sizeof which);
765 cm_ref = extract_int(cmdbuf, 1);
766 extract_token(search_string, cmdbuf, 1, '|', sizeof search_string);
767 with_template = extract_int(cmdbuf, 2);
768 with_headers = extract_int(cmdbuf, 3);
771 if (!strncasecmp(which, "OLD", 3))
773 else if (!strncasecmp(which, "NEW", 3))
775 else if (!strncasecmp(which, "FIRST", 5))
777 else if (!strncasecmp(which, "LAST", 4))
779 else if (!strncasecmp(which, "GT", 2))
781 else if (!strncasecmp(which, "SEARCH", 6))
786 if ((!(CC->logged_in)) && (!(CC->internal_pgm))) {
787 cprintf("%d not logged in\n", ERROR + NOT_LOGGED_IN);
791 if ( (mode == MSGS_SEARCH) && (!config.c_enable_fulltext) ) {
792 cprintf("%d Full text index is not enabled on this server.\n",
793 ERROR + CMD_NOT_SUPPORTED);
799 cprintf("%d Send template then receive message list\n",
801 template = (struct CtdlMessage *)
802 malloc(sizeof(struct CtdlMessage));
803 memset(template, 0, sizeof(struct CtdlMessage));
804 template->cm_magic = CTDLMESSAGE_MAGIC;
805 template->cm_anon_type = MES_NORMAL;
807 while(client_getln(buf, sizeof buf) >= 0 && strcmp(buf,"000")) {
808 extract_token(tfield, buf, 0, '|', sizeof tfield);
809 extract_token(tvalue, buf, 1, '|', sizeof tvalue);
810 for (i='A'; i<='Z'; ++i) if (msgkeys[i]!=NULL) {
811 if (!strcasecmp(tfield, msgkeys[i])) {
812 template->cm_fields[i] =
820 cprintf("%d \n", LISTING_FOLLOWS);
823 CtdlForEachMessage(mode,
824 ( (mode == MSGS_SEARCH) ? 0 : cm_ref ),
825 ( (mode == MSGS_SEARCH) ? search_string : NULL ),
828 (with_headers ? headers_listing : simple_listing),
831 if (template != NULL) CtdlFreeMessage(template);
839 * help_subst() - support routine for help file viewer
841 void help_subst(char *strbuf, char *source, char *dest)
846 while (p = pattern2(strbuf, source), (p >= 0)) {
847 strcpy(workbuf, &strbuf[p + strlen(source)]);
848 strcpy(&strbuf[p], dest);
849 strcat(strbuf, workbuf);
854 void do_help_subst(char *buffer)
858 help_subst(buffer, "^nodename", config.c_nodename);
859 help_subst(buffer, "^humannode", config.c_humannode);
860 help_subst(buffer, "^fqdn", config.c_fqdn);
861 help_subst(buffer, "^username", CC->user.fullname);
862 snprintf(buf2, sizeof buf2, "%ld", CC->user.usernum);
863 help_subst(buffer, "^usernum", buf2);
864 help_subst(buffer, "^sysadm", config.c_sysadm);
865 help_subst(buffer, "^variantname", CITADEL);
866 snprintf(buf2, sizeof buf2, "%d", config.c_maxsessions);
867 help_subst(buffer, "^maxsessions", buf2);
868 help_subst(buffer, "^bbsdir", ctdl_message_dir);
874 * memfmout() - Citadel text formatter and paginator.
875 * Although the original purpose of this routine was to format
876 * text to the reader's screen width, all we're really using it
877 * for here is to format text out to 80 columns before sending it
878 * to the client. The client software may reformat it again.
881 char *mptr, /* where are we going to get our text from? */
882 char subst, /* nonzero if we should do substitutions */
883 char *nl) /* string to terminate lines with */
891 static int width = 80;
896 c = 1; /* c is the current pos */
900 while (ch = *mptr, ((ch != 0) && (strlen(buffer) < 126))) {
902 buffer[strlen(buffer) + 1] = 0;
903 buffer[strlen(buffer)] = ch;
906 if (buffer[0] == '^')
907 do_help_subst(buffer);
909 buffer[strlen(buffer) + 1] = 0;
911 strcpy(buffer, &buffer[1]);
919 if (((ch == 13) || (ch == 10)) && (old != 13) && (old != 10)) {
922 if (((old == 13) || (old == 10)) && (isspace(real))) {
927 if (((strlen(aaa) + c) > (width - 5)) && (strlen(aaa) > (width - 5))) {
928 cprintf("%s%s", nl, aaa);
937 if ((strlen(aaa) + c) > (width - 5)) {
946 if ((ch == 13) || (ch == 10)) {
947 cprintf("%s%s", aaa, nl);
954 cprintf("%s%s", aaa, nl);
960 * Callback function for mime parser that simply lists the part
962 void list_this_part(char *name, char *filename, char *partnum, char *disp,
963 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
964 char *cbid, void *cbuserdata)
968 ma = (struct ma_info *)cbuserdata;
969 if (ma->is_ma == 0) {
970 cprintf("part=%s|%s|%s|%s|%s|%ld|%s\n",
971 name, filename, partnum, disp, cbtype, (long)length, cbid);
976 * Callback function for multipart prefix
978 void list_this_pref(char *name, char *filename, char *partnum, char *disp,
979 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
980 char *cbid, void *cbuserdata)
984 ma = (struct ma_info *)cbuserdata;
985 if (!strcasecmp(cbtype, "multipart/alternative")) {
989 if (ma->is_ma == 0) {
990 cprintf("pref=%s|%s\n", partnum, cbtype);
995 * Callback function for multipart sufffix
997 void list_this_suff(char *name, char *filename, char *partnum, char *disp,
998 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
999 char *cbid, void *cbuserdata)
1003 ma = (struct ma_info *)cbuserdata;
1004 if (ma->is_ma == 0) {
1005 cprintf("suff=%s|%s\n", partnum, cbtype);
1007 if (!strcasecmp(cbtype, "multipart/alternative")) {
1014 * Callback function for mime parser that opens a section for downloading
1016 void mime_download(char *name, char *filename, char *partnum, char *disp,
1017 void *content, char *cbtype, char *cbcharset, size_t length,
1018 char *encoding, char *cbid, void *cbuserdata)
1022 /* Silently go away if there's already a download open. */
1023 if (CC->download_fp != NULL)
1027 (!IsEmptyStr(partnum) && (!strcasecmp(CC->download_desired_section, partnum)))
1028 || (!IsEmptyStr(cbid) && (!strcasecmp(CC->download_desired_section, cbid)))
1030 CC->download_fp = tmpfile();
1031 if (CC->download_fp == NULL)
1034 rv = fwrite(content, length, 1, CC->download_fp);
1035 fflush(CC->download_fp);
1036 rewind(CC->download_fp);
1038 OpenCmdResult(filename, cbtype);
1045 * Callback function for mime parser that outputs a section all at once.
1046 * We can specify the desired section by part number *or* content-id.
1048 void mime_spew_section(char *name, char *filename, char *partnum, char *disp,
1049 void *content, char *cbtype, char *cbcharset, size_t length,
1050 char *encoding, char *cbid, void *cbuserdata)
1052 int *found_it = (int *)cbuserdata;
1055 (!IsEmptyStr(partnum) && (!strcasecmp(CC->download_desired_section, partnum)))
1056 || (!IsEmptyStr(cbid) && (!strcasecmp(CC->download_desired_section, cbid)))
1059 cprintf("%d %d|-1|%s|%s\n",
1065 client_write(content, length);
1072 * Load a message from disk into memory.
1073 * This is used by CtdlOutputMsg() and other fetch functions.
1075 * NOTE: Caller is responsible for freeing the returned CtdlMessage struct
1076 * using the CtdlMessageFree() function.
1078 struct CtdlMessage *CtdlFetchMessage(long msgnum, int with_body)
1080 struct cdbdata *dmsgtext;
1081 struct CtdlMessage *ret = NULL;
1085 cit_uint8_t field_header;
1087 CtdlLogPrintf(CTDL_DEBUG, "CtdlFetchMessage(%ld, %d)\n", msgnum, with_body);
1089 dmsgtext = cdb_fetch(CDB_MSGMAIN, &msgnum, sizeof(long));
1090 if (dmsgtext == NULL) {
1093 mptr = dmsgtext->ptr;
1094 upper_bound = mptr + dmsgtext->len;
1096 /* Parse the three bytes that begin EVERY message on disk.
1097 * The first is always 0xFF, the on-disk magic number.
1098 * The second is the anonymous/public type byte.
1099 * The third is the format type byte (vari, fixed, or MIME).
1103 CtdlLogPrintf(CTDL_ERR, "Message %ld appears to be corrupted.\n", msgnum);
1107 ret = (struct CtdlMessage *) malloc(sizeof(struct CtdlMessage));
1108 memset(ret, 0, sizeof(struct CtdlMessage));
1110 ret->cm_magic = CTDLMESSAGE_MAGIC;
1111 ret->cm_anon_type = *mptr++; /* Anon type byte */
1112 ret->cm_format_type = *mptr++; /* Format type byte */
1115 * The rest is zero or more arbitrary fields. Load them in.
1116 * We're done when we encounter either a zero-length field or
1117 * have just processed the 'M' (message text) field.
1120 if (mptr >= upper_bound) {
1123 field_header = *mptr++;
1124 ret->cm_fields[field_header] = strdup(mptr);
1126 while (*mptr++ != 0); /* advance to next field */
1128 } while ((mptr < upper_bound) && (field_header != 'M'));
1132 /* Always make sure there's something in the msg text field. If
1133 * it's NULL, the message text is most likely stored separately,
1134 * so go ahead and fetch that. Failing that, just set a dummy
1135 * body so other code doesn't barf.
1137 if ( (ret->cm_fields['M'] == NULL) && (with_body) ) {
1138 dmsgtext = cdb_fetch(CDB_BIGMSGS, &msgnum, sizeof(long));
1139 if (dmsgtext != NULL) {
1140 ret->cm_fields['M'] = strdup(dmsgtext->ptr);
1144 if (ret->cm_fields['M'] == NULL) {
1145 ret->cm_fields['M'] = strdup("\r\n\r\n (no text)\r\n");
1148 /* Perform "before read" hooks (aborting if any return nonzero) */
1149 if (PerformMessageHooks(ret, EVT_BEFOREREAD) > 0) {
1150 CtdlFreeMessage(ret);
1159 * Returns 1 if the supplied pointer points to a valid Citadel message.
1160 * If the pointer is NULL or the magic number check fails, returns 0.
1162 int is_valid_message(struct CtdlMessage *msg) {
1165 if ((msg->cm_magic) != CTDLMESSAGE_MAGIC) {
1166 CtdlLogPrintf(CTDL_WARNING, "is_valid_message() -- self-check failed\n");
1174 * 'Destructor' for struct CtdlMessage
1176 void CtdlFreeMessage(struct CtdlMessage *msg)
1180 if (is_valid_message(msg) == 0)
1182 if (msg != NULL) free (msg);
1186 for (i = 0; i < 256; ++i)
1187 if (msg->cm_fields[i] != NULL) {
1188 free(msg->cm_fields[i]);
1191 msg->cm_magic = 0; /* just in case */
1197 * Pre callback function for multipart/alternative
1199 * NOTE: this differs from the standard behavior for a reason. Normally when
1200 * displaying multipart/alternative you want to show the _last_ usable
1201 * format in the message. Here we show the _first_ one, because it's
1202 * usually text/plain. Since this set of functions is designed for text
1203 * output to non-MIME-aware clients, this is the desired behavior.
1206 void fixed_output_pre(char *name, char *filename, char *partnum, char *disp,
1207 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
1208 char *cbid, void *cbuserdata)
1212 ma = (struct ma_info *)cbuserdata;
1213 CtdlLogPrintf(CTDL_DEBUG, "fixed_output_pre() type=<%s>\n", cbtype);
1214 if (!strcasecmp(cbtype, "multipart/alternative")) {
1218 if (!strcasecmp(cbtype, "message/rfc822")) {
1224 * Post callback function for multipart/alternative
1226 void fixed_output_post(char *name, char *filename, char *partnum, char *disp,
1227 void *content, char *cbtype, char *cbcharset, size_t length,
1228 char *encoding, char *cbid, void *cbuserdata)
1232 ma = (struct ma_info *)cbuserdata;
1233 CtdlLogPrintf(CTDL_DEBUG, "fixed_output_post() type=<%s>\n", cbtype);
1234 if (!strcasecmp(cbtype, "multipart/alternative")) {
1238 if (!strcasecmp(cbtype, "message/rfc822")) {
1244 * Inline callback function for mime parser that wants to display text
1246 void fixed_output(char *name, char *filename, char *partnum, char *disp,
1247 void *content, char *cbtype, char *cbcharset, size_t length,
1248 char *encoding, char *cbid, void *cbuserdata)
1255 ma = (struct ma_info *)cbuserdata;
1257 CtdlLogPrintf(CTDL_DEBUG,
1258 "fixed_output() part %s: %s (%s) (%ld bytes)\n",
1259 partnum, filename, cbtype, (long)length);
1262 * If we're in the middle of a multipart/alternative scope and
1263 * we've already printed another section, skip this one.
1265 if ( (ma->is_ma) && (ma->did_print) ) {
1266 CtdlLogPrintf(CTDL_DEBUG, "Skipping part %s (%s)\n", partnum, cbtype);
1271 if ( (!strcasecmp(cbtype, "text/plain"))
1272 || (IsEmptyStr(cbtype)) ) {
1275 client_write(wptr, length);
1276 if (wptr[length-1] != '\n') {
1283 if (!strcasecmp(cbtype, "text/html")) {
1284 ptr = html_to_ascii(content, length, 80, 0);
1286 client_write(ptr, wlen);
1287 if (ptr[wlen-1] != '\n') {
1294 if (ma->use_fo_hooks) {
1295 if (PerformFixedOutputHooks(cbtype, content, length)) {
1296 /* above function returns nonzero if it handled the part */
1301 if (strncasecmp(cbtype, "multipart/", 10)) {
1302 cprintf("Part %s: %s (%s) (%ld bytes)\r\n",
1303 partnum, filename, cbtype, (long)length);
1309 * The client is elegant and sophisticated and wants to be choosy about
1310 * MIME content types, so figure out which multipart/alternative part
1311 * we're going to send.
1313 * We use a system of weights. When we find a part that matches one of the
1314 * MIME types we've declared as preferential, we can store it in ma->chosen_part
1315 * and then set ma->chosen_pref to that MIME type's position in our preference
1316 * list. If we then hit another match, we only replace the first match if
1317 * the preference value is lower.
1319 void choose_preferred(char *name, char *filename, char *partnum, char *disp,
1320 void *content, char *cbtype, char *cbcharset, size_t length,
1321 char *encoding, char *cbid, void *cbuserdata)
1327 ma = (struct ma_info *)cbuserdata;
1329 // NOTE: REMOVING THIS CONDITIONAL FIXES BUG 220
1330 // http://bugzilla.citadel.org/show_bug.cgi?id=220
1331 // I don't know if there are any side effects! Please TEST TEST TEST
1332 //if (ma->is_ma > 0) {
1334 for (i=0; i<num_tokens(CC->preferred_formats, '|'); ++i) {
1335 extract_token(buf, CC->preferred_formats, i, '|', sizeof buf);
1336 if ( (!strcasecmp(buf, cbtype)) && (!ma->freeze) ) {
1337 if (i < ma->chosen_pref) {
1338 CtdlLogPrintf(CTDL_DEBUG, "Setting chosen part: <%s>\n", partnum);
1339 safestrncpy(ma->chosen_part, partnum, sizeof ma->chosen_part);
1340 ma->chosen_pref = i;
1347 * Now that we've chosen our preferred part, output it.
1349 void output_preferred(char *name, char *filename, char *partnum, char *disp,
1350 void *content, char *cbtype, char *cbcharset, size_t length,
1351 char *encoding, char *cbid, void *cbuserdata)
1355 int add_newline = 0;
1359 ma = (struct ma_info *)cbuserdata;
1361 /* This is not the MIME part you're looking for... */
1362 if (strcasecmp(partnum, ma->chosen_part)) return;
1364 /* If the content-type of this part is in our preferred formats
1365 * list, we can simply output it verbatim.
1367 for (i=0; i<num_tokens(CC->preferred_formats, '|'); ++i) {
1368 extract_token(buf, CC->preferred_formats, i, '|', sizeof buf);
1369 if (!strcasecmp(buf, cbtype)) {
1370 /* Yeah! Go! W00t!! */
1372 text_content = (char *)content;
1373 if (text_content[length-1] != '\n') {
1376 cprintf("Content-type: %s", cbtype);
1377 if (!IsEmptyStr(cbcharset)) {
1378 cprintf("; charset=%s", cbcharset);
1380 cprintf("\nContent-length: %d\n",
1381 (int)(length + add_newline) );
1382 if (!IsEmptyStr(encoding)) {
1383 cprintf("Content-transfer-encoding: %s\n", encoding);
1386 cprintf("Content-transfer-encoding: 7bit\n");
1388 cprintf("X-Citadel-MSG4-Partnum: %s\n", partnum);
1390 client_write(content, length);
1391 if (add_newline) cprintf("\n");
1396 /* No translations required or possible: output as text/plain */
1397 cprintf("Content-type: text/plain\n\n");
1398 fixed_output(name, filename, partnum, disp, content, cbtype, cbcharset,
1399 length, encoding, cbid, cbuserdata);
1404 char desired_section[64];
1411 * Callback function for
1413 void extract_encapsulated_message(char *name, char *filename, char *partnum, char *disp,
1414 void *content, char *cbtype, char *cbcharset, size_t length,
1415 char *encoding, char *cbid, void *cbuserdata)
1417 struct encapmsg *encap;
1419 encap = (struct encapmsg *)cbuserdata;
1421 /* Only proceed if this is the desired section... */
1422 if (!strcasecmp(encap->desired_section, partnum)) {
1423 encap->msglen = length;
1424 encap->msg = malloc(length + 2);
1425 memcpy(encap->msg, content, length);
1435 * Get a message off disk. (returns om_* values found in msgbase.h)
1438 int CtdlOutputMsg(long msg_num, /* message number (local) to fetch */
1439 int mode, /* how would you like that message? */
1440 int headers_only, /* eschew the message body? */
1441 int do_proto, /* do Citadel protocol responses? */
1442 int crlf, /* Use CRLF newlines instead of LF? */
1443 char *section, /* NULL or a message/rfc822 section */
1444 int flags /* should the bessage be exported clean? */
1446 struct CtdlMessage *TheMessage = NULL;
1447 int retcode = om_no_such_msg;
1448 struct encapmsg encap;
1450 CtdlLogPrintf(CTDL_DEBUG, "CtdlOutputMsg() msgnum=%ld, mode=%d, section=%s\n",
1452 (section ? section : "<>")
1455 if ((!(CC->logged_in)) && (!(CC->internal_pgm))) {
1456 if (do_proto) cprintf("%d Not logged in.\n",
1457 ERROR + NOT_LOGGED_IN);
1458 return(om_not_logged_in);
1461 /* FIXME: check message id against msglist for this room */
1464 * Fetch the message from disk. If we're in HEADERS_FAST mode,
1465 * request that we don't even bother loading the body into memory.
1467 if (headers_only == HEADERS_FAST) {
1468 TheMessage = CtdlFetchMessage(msg_num, 0);
1471 TheMessage = CtdlFetchMessage(msg_num, 1);
1474 if (TheMessage == NULL) {
1475 if (do_proto) cprintf("%d Can't locate msg %ld on disk\n",
1476 ERROR + MESSAGE_NOT_FOUND, msg_num);
1477 return(om_no_such_msg);
1480 /* Here is the weird form of this command, to process only an
1481 * encapsulated message/rfc822 section.
1483 if (section) if (!IsEmptyStr(section)) if (strcmp(section, "0")) {
1484 memset(&encap, 0, sizeof encap);
1485 safestrncpy(encap.desired_section, section, sizeof encap.desired_section);
1486 mime_parser(TheMessage->cm_fields['M'],
1488 *extract_encapsulated_message,
1489 NULL, NULL, (void *)&encap, 0
1491 CtdlFreeMessage(TheMessage);
1495 encap.msg[encap.msglen] = 0;
1496 TheMessage = convert_internet_message(encap.msg);
1497 encap.msg = NULL; /* no free() here, TheMessage owns it now */
1499 /* Now we let it fall through to the bottom of this
1500 * function, because TheMessage now contains the
1501 * encapsulated message instead of the top-level
1502 * message. Isn't that neat?
1507 if (do_proto) cprintf("%d msg %ld has no part %s\n",
1508 ERROR + MESSAGE_NOT_FOUND, msg_num, section);
1509 retcode = om_no_such_msg;
1514 /* Ok, output the message now */
1515 retcode = CtdlOutputPreLoadedMsg(TheMessage, mode, headers_only, do_proto, crlf, flags);
1516 CtdlFreeMessage(TheMessage);
1522 char *qp_encode_email_addrs(char *source)
1524 char user[256], node[256], name[256];
1525 const char headerStr[] = "=?UTF-8?Q?";
1529 int need_to_encode = 0;
1535 long nAddrPtrMax = 50;
1540 if (source == NULL) return source;
1541 if (IsEmptyStr(source)) return source;
1543 AddrPtr = malloc (sizeof (long) * nAddrPtrMax);
1544 AddrUtf8 = malloc (sizeof (long) * nAddrPtrMax);
1545 memset(AddrUtf8, 0, sizeof (long) * nAddrPtrMax);
1548 while (!IsEmptyStr (&source[i])) {
1549 if (nColons >= nAddrPtrMax){
1552 ptr = (long *) malloc(sizeof (long) * nAddrPtrMax * 2);
1553 memcpy (ptr, AddrPtr, sizeof (long) * nAddrPtrMax);
1554 free (AddrPtr), AddrPtr = ptr;
1556 ptr = (long *) malloc(sizeof (long) * nAddrPtrMax * 2);
1557 memset(&ptr[nAddrPtrMax], 0,
1558 sizeof (long) * nAddrPtrMax);
1560 memcpy (ptr, AddrUtf8, sizeof (long) * nAddrPtrMax);
1561 free (AddrUtf8), AddrUtf8 = ptr;
1564 if (((unsigned char) source[i] < 32) ||
1565 ((unsigned char) source[i] > 126)) {
1567 AddrUtf8[nColons] = 1;
1569 if (source[i] == '"')
1570 InQuotes = !InQuotes;
1571 if (!InQuotes && source[i] == ',') {
1572 AddrPtr[nColons] = i;
1577 if (need_to_encode == 0) {
1584 EncodedMaxLen = nColons * (sizeof(headerStr) + 3) + SourceLen * 3;
1585 Encoded = (char*) malloc (EncodedMaxLen);
1587 for (i = 0; i < nColons; i++)
1588 source[AddrPtr[i]++] = '\0';
1592 for (i = 0; i < nColons && nPtr != NULL; i++) {
1593 nmax = EncodedMaxLen - (nPtr - Encoded);
1595 process_rfc822_addr(&source[AddrPtr[i]],
1599 /* TODO: libIDN here ! */
1600 if (IsEmptyStr(name)) {
1601 n = snprintf(nPtr, nmax,
1602 (i==0)?"%s@%s" : ",%s@%s",
1606 EncodedName = rfc2047encode(name, strlen(name));
1607 n = snprintf(nPtr, nmax,
1608 (i==0)?"%s <%s@%s>" : ",%s <%s@%s>",
1609 EncodedName, user, node);
1614 n = snprintf(nPtr, nmax,
1615 (i==0)?"%s" : ",%s",
1616 &source[AddrPtr[i]]);
1622 ptr = (char*) malloc(EncodedMaxLen * 2);
1623 memcpy(ptr, Encoded, EncodedMaxLen);
1624 nnPtr = ptr + (nPtr - Encoded), nPtr = nnPtr;
1625 free(Encoded), Encoded = ptr;
1627 i--; /* do it once more with properly lengthened buffer */
1630 for (i = 0; i < nColons; i++)
1631 source[--AddrPtr[i]] = ',';
1638 /* If the last item in a list of recipients was truncated to a partial address,
1639 * remove it completely in order to avoid choking libSieve
1641 void sanitize_truncated_recipient(char *str)
1644 if (num_tokens(str, ',') < 2) return;
1646 int len = strlen(str);
1647 if (len < 900) return;
1648 if (len > 998) str[998] = 0;
1650 char *cptr = strrchr(str, ',');
1653 char *lptr = strchr(cptr, '<');
1654 char *rptr = strchr(cptr, '>');
1656 if ( (lptr) && (rptr) && (rptr > lptr) ) return;
1664 * Get a message off disk. (returns om_* values found in msgbase.h)
1666 int CtdlOutputPreLoadedMsg(
1667 struct CtdlMessage *TheMessage,
1668 int mode, /* how would you like that message? */
1669 int headers_only, /* eschew the message body? */
1670 int do_proto, /* do Citadel protocol responses? */
1671 int crlf, /* Use CRLF newlines instead of LF? */
1672 int flags /* should the bessage be exported clean? */
1676 cit_uint8_t ch, prev_ch;
1678 char display_name[256];
1680 char *nl; /* newline string */
1682 int subject_found = 0;
1685 /* Buffers needed for RFC822 translation. These are all filled
1686 * using functions that are bounds-checked, and therefore we can
1687 * make them substantially smaller than SIZ.
1694 char datestamp[100];
1696 CtdlLogPrintf(CTDL_DEBUG, "CtdlOutputPreLoadedMsg(TheMessage=%s, %d, %d, %d, %d\n",
1697 ((TheMessage == NULL) ? "NULL" : "not null"),
1698 mode, headers_only, do_proto, crlf);
1700 strcpy(mid, "unknown");
1701 nl = (crlf ? "\r\n" : "\n");
1703 if (!is_valid_message(TheMessage)) {
1704 CtdlLogPrintf(CTDL_ERR,
1705 "ERROR: invalid preloaded message for output\n");
1707 return(om_no_such_msg);
1710 /* Are we downloading a MIME component? */
1711 if (mode == MT_DOWNLOAD) {
1712 if (TheMessage->cm_format_type != FMT_RFC822) {
1714 cprintf("%d This is not a MIME message.\n",
1715 ERROR + ILLEGAL_VALUE);
1716 } else if (CC->download_fp != NULL) {
1717 if (do_proto) cprintf(
1718 "%d You already have a download open.\n",
1719 ERROR + RESOURCE_BUSY);
1721 /* Parse the message text component */
1722 mptr = TheMessage->cm_fields['M'];
1723 mime_parser(mptr, NULL, *mime_download, NULL, NULL, NULL, 0);
1724 /* If there's no file open by this time, the requested
1725 * section wasn't found, so print an error
1727 if (CC->download_fp == NULL) {
1728 if (do_proto) cprintf(
1729 "%d Section %s not found.\n",
1730 ERROR + FILE_NOT_FOUND,
1731 CC->download_desired_section);
1734 return((CC->download_fp != NULL) ? om_ok : om_mime_error);
1737 /* MT_SPEW_SECTION is like MT_DOWNLOAD except it outputs the whole MIME part
1738 * in a single server operation instead of opening a download file.
1740 if (mode == MT_SPEW_SECTION) {
1741 if (TheMessage->cm_format_type != FMT_RFC822) {
1743 cprintf("%d This is not a MIME message.\n",
1744 ERROR + ILLEGAL_VALUE);
1746 /* Parse the message text component */
1749 mptr = TheMessage->cm_fields['M'];
1750 mime_parser(mptr, NULL, *mime_spew_section, NULL, NULL, (void *)&found_it, 0);
1751 /* If section wasn't found, print an error
1754 if (do_proto) cprintf(
1755 "%d Section %s not found.\n",
1756 ERROR + FILE_NOT_FOUND,
1757 CC->download_desired_section);
1760 return((CC->download_fp != NULL) ? om_ok : om_mime_error);
1763 /* now for the user-mode message reading loops */
1764 if (do_proto) cprintf("%d msg:\n", LISTING_FOLLOWS);
1766 /* Does the caller want to skip the headers? */
1767 if (headers_only == HEADERS_NONE) goto START_TEXT;
1769 /* Tell the client which format type we're using. */
1770 if ( (mode == MT_CITADEL) && (do_proto) ) {
1771 cprintf("type=%d\n", TheMessage->cm_format_type);
1774 /* nhdr=yes means that we're only displaying headers, no body */
1775 if ( (TheMessage->cm_anon_type == MES_ANONONLY)
1776 && ((mode == MT_CITADEL) || (mode == MT_MIME))
1779 cprintf("nhdr=yes\n");
1782 /* begin header processing loop for Citadel message format */
1784 if ((mode == MT_CITADEL) || (mode == MT_MIME)) {
1786 safestrncpy(display_name, "<unknown>", sizeof display_name);
1787 if (TheMessage->cm_fields['A']) {
1788 strcpy(buf, TheMessage->cm_fields['A']);
1789 if (TheMessage->cm_anon_type == MES_ANONONLY) {
1790 safestrncpy(display_name, "****", sizeof display_name);
1792 else if (TheMessage->cm_anon_type == MES_ANONOPT) {
1793 safestrncpy(display_name, "anonymous", sizeof display_name);
1796 safestrncpy(display_name, buf, sizeof display_name);
1798 if ((is_room_aide())
1799 && ((TheMessage->cm_anon_type == MES_ANONONLY)
1800 || (TheMessage->cm_anon_type == MES_ANONOPT))) {
1801 size_t tmp = strlen(display_name);
1802 snprintf(&display_name[tmp],
1803 sizeof display_name - tmp,
1808 /* Don't show Internet address for users on the
1809 * local Citadel network.
1812 if (TheMessage->cm_fields['N'] != NULL)
1813 if (!IsEmptyStr(TheMessage->cm_fields['N']))
1814 if (haschar(TheMessage->cm_fields['N'], '.') == 0) {
1818 /* Now spew the header fields in the order we like them. */
1819 safestrncpy(allkeys, FORDER, sizeof allkeys);
1820 for (i=0; i<strlen(allkeys); ++i) {
1821 k = (int) allkeys[i];
1823 if ( (TheMessage->cm_fields[k] != NULL)
1824 && (msgkeys[k] != NULL) ) {
1825 if ((k == 'V') || (k == 'R') || (k == 'Y')) {
1826 sanitize_truncated_recipient(TheMessage->cm_fields[k]);
1829 if (do_proto) cprintf("%s=%s\n",
1833 else if ((k == 'F') && (suppress_f)) {
1836 /* Masquerade display name if needed */
1838 if (do_proto) cprintf("%s=%s\n",
1840 TheMessage->cm_fields[k]
1849 /* begin header processing loop for RFC822 transfer format */
1854 strcpy(snode, NODENAME);
1855 if (mode == MT_RFC822) {
1856 for (i = 0; i < 256; ++i) {
1857 if (TheMessage->cm_fields[i]) {
1858 mptr = mpptr = TheMessage->cm_fields[i];
1861 safestrncpy(luser, mptr, sizeof luser);
1862 safestrncpy(suser, mptr, sizeof suser);
1864 else if (i == 'Y') {
1865 if ((flags & QP_EADDR) != 0) {
1866 mptr = qp_encode_email_addrs(mptr);
1868 sanitize_truncated_recipient(mptr);
1869 cprintf("CC: %s%s", mptr, nl);
1871 else if (i == 'P') {
1872 cprintf("Return-Path: %s%s", mptr, nl);
1874 else if (i == 'L') {
1875 cprintf("List-ID: %s%s", mptr, nl);
1877 else if (i == 'V') {
1878 if ((flags & QP_EADDR) != 0)
1879 mptr = qp_encode_email_addrs(mptr);
1880 cprintf("Envelope-To: %s%s", mptr, nl);
1882 else if (i == 'U') {
1883 cprintf("Subject: %s%s", mptr, nl);
1887 safestrncpy(mid, mptr, sizeof mid);
1889 safestrncpy(fuser, mptr, sizeof fuser);
1890 /* else if (i == 'O')
1891 cprintf("X-Citadel-Room: %s%s",
1894 safestrncpy(snode, mptr, sizeof snode);
1897 if (haschar(mptr, '@') == 0)
1899 sanitize_truncated_recipient(mptr);
1900 cprintf("To: %s@%s", mptr, config.c_fqdn);
1905 if ((flags & QP_EADDR) != 0) {
1906 mptr = qp_encode_email_addrs(mptr);
1908 sanitize_truncated_recipient(mptr);
1909 cprintf("To: %s", mptr);
1913 else if (i == 'T') {
1914 datestring(datestamp, sizeof datestamp,
1915 atol(mptr), DATESTRING_RFC822);
1916 cprintf("Date: %s%s", datestamp, nl);
1918 else if (i == 'W') {
1919 cprintf("References: ");
1920 k = num_tokens(mptr, '|');
1921 for (j=0; j<k; ++j) {
1922 extract_token(buf, mptr, j, '|', sizeof buf);
1923 cprintf("<%s>", buf);
1936 if (subject_found == 0) {
1937 cprintf("Subject: (no subject)%s", nl);
1941 for (i=0; !IsEmptyStr(&suser[i]); ++i) {
1942 suser[i] = tolower(suser[i]);
1943 if (!isalnum(suser[i])) suser[i]='_';
1946 if (mode == MT_RFC822) {
1947 if (!strcasecmp(snode, NODENAME)) {
1948 safestrncpy(snode, FQDN, sizeof snode);
1951 /* Construct a fun message id */
1952 cprintf("Message-ID: <%s", mid);/// todo: this possibly breaks threadding mails.
1953 if (strchr(mid, '@')==NULL) {
1954 cprintf("@%s", snode);
1958 if (!is_room_aide() && (TheMessage->cm_anon_type == MES_ANONONLY)) {
1959 cprintf("From: \"----\" <x@x.org>%s", nl);
1961 else if (!is_room_aide() && (TheMessage->cm_anon_type == MES_ANONOPT)) {
1962 cprintf("From: \"anonymous\" <x@x.org>%s", nl);
1964 else if (!IsEmptyStr(fuser)) {
1965 cprintf("From: \"%s\" <%s>%s", luser, fuser, nl);
1968 cprintf("From: \"%s\" <%s@%s>%s", luser, suser, snode, nl);
1971 /* Blank line signifying RFC822 end-of-headers */
1972 if (TheMessage->cm_format_type != FMT_RFC822) {
1977 /* end header processing loop ... at this point, we're in the text */
1979 if (headers_only == HEADERS_FAST) goto DONE;
1980 mptr = TheMessage->cm_fields['M'];
1982 /* Tell the client about the MIME parts in this message */
1983 if (TheMessage->cm_format_type == FMT_RFC822) {
1984 if ( (mode == MT_CITADEL) || (mode == MT_MIME) ) {
1985 memset(&ma, 0, sizeof(struct ma_info));
1986 mime_parser(mptr, NULL,
1987 (do_proto ? *list_this_part : NULL),
1988 (do_proto ? *list_this_pref : NULL),
1989 (do_proto ? *list_this_suff : NULL),
1992 else if (mode == MT_RFC822) { /* unparsed RFC822 dump */
1993 char *start_of_text = NULL;
1994 start_of_text = strstr(mptr, "\n\r\n");
1995 if (start_of_text == NULL) start_of_text = strstr(mptr, "\n\n");
1996 if (start_of_text == NULL) start_of_text = mptr;
1998 start_of_text = strstr(start_of_text, "\n");
2003 int nllen = strlen(nl);
2005 while (ch=*mptr, ch!=0) {
2011 ((headers_only == HEADERS_NONE) && (mptr >= start_of_text))
2012 || ((headers_only == HEADERS_ONLY) && (mptr < start_of_text))
2013 || ((headers_only != HEADERS_NONE) && (headers_only != HEADERS_ONLY))
2016 sprintf(&outbuf[outlen], "%s", nl);
2020 outbuf[outlen++] = ch;
2024 if (flags & ESC_DOT)
2026 if ((prev_ch == 10) && (ch == '.') && ((*(mptr+1) == 13) || (*(mptr+1) == 10)))
2028 outbuf[outlen++] = '.';
2033 if (outlen > 1000) {
2034 client_write(outbuf, outlen);
2039 client_write(outbuf, outlen);
2047 if (headers_only == HEADERS_ONLY) {
2051 /* signify start of msg text */
2052 if ( (mode == MT_CITADEL) || (mode == MT_MIME) ) {
2053 if (do_proto) cprintf("text\n");
2056 /* If the format type on disk is 1 (fixed-format), then we want
2057 * everything to be output completely literally ... regardless of
2058 * what message transfer format is in use.
2060 if (TheMessage->cm_format_type == FMT_FIXED) {
2062 if (mode == MT_MIME) {
2063 cprintf("Content-type: text/plain\n\n");
2067 while (ch = *mptr++, ch > 0) {
2070 if ((ch == 10) || (buflen > 250)) {
2072 cprintf("%s%s", buf, nl);
2081 if (!IsEmptyStr(buf))
2082 cprintf("%s%s", buf, nl);
2085 /* If the message on disk is format 0 (Citadel vari-format), we
2086 * output using the formatter at 80 columns. This is the final output
2087 * form if the transfer format is RFC822, but if the transfer format
2088 * is Citadel proprietary, it'll still work, because the indentation
2089 * for new paragraphs is correct and the client will reformat the
2090 * message to the reader's screen width.
2092 if (TheMessage->cm_format_type == FMT_CITADEL) {
2093 if (mode == MT_MIME) {
2094 cprintf("Content-type: text/x-citadel-variformat\n\n");
2096 memfmout(mptr, 0, nl);
2099 /* If the message on disk is format 4 (MIME), we've gotta hand it
2100 * off to the MIME parser. The client has already been told that
2101 * this message is format 1 (fixed format), so the callback function
2102 * we use will display those parts as-is.
2104 if (TheMessage->cm_format_type == FMT_RFC822) {
2105 memset(&ma, 0, sizeof(struct ma_info));
2107 if (mode == MT_MIME) {
2108 ma.use_fo_hooks = 0;
2109 strcpy(ma.chosen_part, "1");
2110 ma.chosen_pref = 9999;
2111 mime_parser(mptr, NULL,
2112 *choose_preferred, *fixed_output_pre,
2113 *fixed_output_post, (void *)&ma, 0);
2114 mime_parser(mptr, NULL,
2115 *output_preferred, NULL, NULL, (void *)&ma, CC->msg4_dont_decode);
2118 ma.use_fo_hooks = 1;
2119 mime_parser(mptr, NULL,
2120 *fixed_output, *fixed_output_pre,
2121 *fixed_output_post, (void *)&ma, 0);
2126 DONE: /* now we're done */
2127 if (do_proto) cprintf("000\n");
2134 * display a message (mode 0 - Citadel proprietary)
2136 void cmd_msg0(char *cmdbuf)
2139 int headers_only = HEADERS_ALL;
2141 msgid = extract_long(cmdbuf, 0);
2142 headers_only = extract_int(cmdbuf, 1);
2144 CtdlOutputMsg(msgid, MT_CITADEL, headers_only, 1, 0, NULL, 0);
2150 * display a message (mode 2 - RFC822)
2152 void cmd_msg2(char *cmdbuf)
2155 int headers_only = HEADERS_ALL;
2157 msgid = extract_long(cmdbuf, 0);
2158 headers_only = extract_int(cmdbuf, 1);
2160 CtdlOutputMsg(msgid, MT_RFC822, headers_only, 1, 1, NULL, 0);
2166 * display a message (mode 3 - IGnet raw format - internal programs only)
2168 void cmd_msg3(char *cmdbuf)
2171 struct CtdlMessage *msg = NULL;
2174 if (CC->internal_pgm == 0) {
2175 cprintf("%d This command is for internal programs only.\n",
2176 ERROR + HIGHER_ACCESS_REQUIRED);
2180 msgnum = extract_long(cmdbuf, 0);
2181 msg = CtdlFetchMessage(msgnum, 1);
2183 cprintf("%d Message %ld not found.\n",
2184 ERROR + MESSAGE_NOT_FOUND, msgnum);
2188 serialize_message(&smr, msg);
2189 CtdlFreeMessage(msg);
2192 cprintf("%d Unable to serialize message\n",
2193 ERROR + INTERNAL_ERROR);
2197 cprintf("%d %ld\n", BINARY_FOLLOWS, (long)smr.len);
2198 client_write((char *)smr.ser, (int)smr.len);
2205 * Display a message using MIME content types
2207 void cmd_msg4(char *cmdbuf)
2212 msgid = extract_long(cmdbuf, 0);
2213 extract_token(section, cmdbuf, 1, '|', sizeof section);
2214 CtdlOutputMsg(msgid, MT_MIME, 0, 1, 0, (section[0] ? section : NULL) , 0);
2220 * Client tells us its preferred message format(s)
2222 void cmd_msgp(char *cmdbuf)
2224 if (!strcasecmp(cmdbuf, "dont_decode")) {
2225 CC->msg4_dont_decode = 1;
2226 cprintf("%d MSG4 will not pre-decode messages.\n", CIT_OK);
2229 safestrncpy(CC->preferred_formats, cmdbuf, sizeof(CC->preferred_formats));
2230 cprintf("%d Preferred MIME formats have been set.\n", CIT_OK);
2236 * Open a component of a MIME message as a download file
2238 void cmd_opna(char *cmdbuf)
2241 char desired_section[128];
2243 msgid = extract_long(cmdbuf, 0);
2244 extract_token(desired_section, cmdbuf, 1, '|', sizeof desired_section);
2245 safestrncpy(CC->download_desired_section, desired_section,
2246 sizeof CC->download_desired_section);
2247 CtdlOutputMsg(msgid, MT_DOWNLOAD, 0, 1, 1, NULL, 0);
2252 * Open a component of a MIME message and transmit it all at once
2254 void cmd_dlat(char *cmdbuf)
2257 char desired_section[128];
2259 msgid = extract_long(cmdbuf, 0);
2260 extract_token(desired_section, cmdbuf, 1, '|', sizeof desired_section);
2261 safestrncpy(CC->download_desired_section, desired_section,
2262 sizeof CC->download_desired_section);
2263 CtdlOutputMsg(msgid, MT_SPEW_SECTION, 0, 1, 1, NULL, 0);
2268 * Save one or more message pointers into a specified room
2269 * (Returns 0 for success, nonzero for failure)
2270 * roomname may be NULL to use the current room
2272 * Note that the 'supplied_msg' field may be set to NULL, in which case
2273 * the message will be fetched from disk, by number, if we need to perform
2274 * replication checks. This adds an additional database read, so if the
2275 * caller already has the message in memory then it should be supplied. (Obviously
2276 * this mode of operation only works if we're saving a single message.)
2278 int CtdlSaveMsgPointersInRoom(char *roomname, long newmsgidlist[], int num_newmsgs,
2279 int do_repl_check, struct CtdlMessage *supplied_msg)
2282 char hold_rm[ROOMNAMELEN];
2283 struct cdbdata *cdbfr;
2286 long highest_msg = 0L;
2289 struct CtdlMessage *msg = NULL;
2291 long *msgs_to_be_merged = NULL;
2292 int num_msgs_to_be_merged = 0;
2294 CtdlLogPrintf(CTDL_DEBUG,
2295 "CtdlSaveMsgPointersInRoom(room=%s, num_msgs=%d, repl=%d)\n",
2296 roomname, num_newmsgs, do_repl_check);
2298 strcpy(hold_rm, CC->room.QRname);
2301 if (newmsgidlist == NULL) return(ERROR + INTERNAL_ERROR);
2302 if (num_newmsgs < 1) return(ERROR + INTERNAL_ERROR);
2303 if (num_newmsgs > 1) supplied_msg = NULL;
2305 /* Now the regular stuff */
2306 if (lgetroom(&CC->room,
2307 ((roomname != NULL) ? roomname : CC->room.QRname) )
2309 CtdlLogPrintf(CTDL_ERR, "No such room <%s>\n", roomname);
2310 return(ERROR + ROOM_NOT_FOUND);
2314 msgs_to_be_merged = malloc(sizeof(long) * num_newmsgs);
2315 num_msgs_to_be_merged = 0;
2318 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long));
2319 if (cdbfr == NULL) {
2323 msglist = (long *) cdbfr->ptr;
2324 cdbfr->ptr = NULL; /* CtdlSaveMsgPointerInRoom() now owns this memory */
2325 num_msgs = cdbfr->len / sizeof(long);
2330 /* Create a list of msgid's which were supplied by the caller, but do
2331 * not already exist in the target room. It is absolutely taboo to
2332 * have more than one reference to the same message in a room.
2334 for (i=0; i<num_newmsgs; ++i) {
2336 if (num_msgs > 0) for (j=0; j<num_msgs; ++j) {
2337 if (msglist[j] == newmsgidlist[i]) {
2342 msgs_to_be_merged[num_msgs_to_be_merged++] = newmsgidlist[i];
2346 CtdlLogPrintf(9, "%d unique messages to be merged\n", num_msgs_to_be_merged);
2349 * Now merge the new messages
2351 msglist = realloc(msglist, (sizeof(long) * (num_msgs + num_msgs_to_be_merged)) );
2352 if (msglist == NULL) {
2353 CtdlLogPrintf(CTDL_ALERT, "ERROR: can't realloc message list!\n");
2355 memcpy(&msglist[num_msgs], msgs_to_be_merged, (sizeof(long) * num_msgs_to_be_merged) );
2356 num_msgs += num_msgs_to_be_merged;
2358 /* Sort the message list, so all the msgid's are in order */
2359 num_msgs = sort_msglist(msglist, num_msgs);
2361 /* Determine the highest message number */
2362 highest_msg = msglist[num_msgs - 1];
2364 /* Write it back to disk. */
2365 cdb_store(CDB_MSGLISTS, &CC->room.QRnumber, (int)sizeof(long),
2366 msglist, (int)(num_msgs * sizeof(long)));
2368 /* Free up the memory we used. */
2371 /* Update the highest-message pointer and unlock the room. */
2372 CC->room.QRhighest = highest_msg;
2373 lputroom(&CC->room);
2375 /* Perform replication checks if necessary */
2376 if ( (DoesThisRoomNeedEuidIndexing(&CC->room)) && (do_repl_check) ) {
2377 CtdlLogPrintf(CTDL_DEBUG, "CtdlSaveMsgPointerInRoom() doing repl checks\n");
2379 for (i=0; i<num_msgs_to_be_merged; ++i) {
2380 msgid = msgs_to_be_merged[i];
2382 if (supplied_msg != NULL) {
2386 msg = CtdlFetchMessage(msgid, 0);
2390 ReplicationChecks(msg);
2392 /* If the message has an Exclusive ID, index that... */
2393 if (msg->cm_fields['E'] != NULL) {
2394 index_message_by_euid(msg->cm_fields['E'], &CC->room, msgid);
2397 /* Free up the memory we may have allocated */
2398 if (msg != supplied_msg) {
2399 CtdlFreeMessage(msg);
2407 CtdlLogPrintf(CTDL_DEBUG, "CtdlSaveMsgPointerInRoom() skips repl checks\n");
2410 /* Submit this room for processing by hooks */
2411 PerformRoomHooks(&CC->room);
2413 /* Go back to the room we were in before we wandered here... */
2414 getroom(&CC->room, hold_rm);
2416 /* Bump the reference count for all messages which were merged */
2417 for (i=0; i<num_msgs_to_be_merged; ++i) {
2418 AdjRefCount(msgs_to_be_merged[i], +1);
2421 /* Free up memory... */
2422 if (msgs_to_be_merged != NULL) {
2423 free(msgs_to_be_merged);
2426 /* Return success. */
2432 * This is the same as CtdlSaveMsgPointersInRoom() but it only accepts
2435 int CtdlSaveMsgPointerInRoom(char *roomname, long msgid,
2436 int do_repl_check, struct CtdlMessage *supplied_msg)
2438 return CtdlSaveMsgPointersInRoom(roomname, &msgid, 1, do_repl_check, supplied_msg);
2445 * Message base operation to save a new message to the message store
2446 * (returns new message number)
2448 * This is the back end for CtdlSubmitMsg() and should not be directly
2449 * called by server-side modules.
2452 long send_message(struct CtdlMessage *msg) {
2460 /* Get a new message number */
2461 newmsgid = get_new_message_number();
2462 snprintf(msgidbuf, sizeof msgidbuf, "%010ld@%s", newmsgid, config.c_fqdn);
2464 /* Generate an ID if we don't have one already */
2465 if (msg->cm_fields['I']==NULL) {
2466 msg->cm_fields['I'] = strdup(msgidbuf);
2469 /* If the message is big, set its body aside for storage elsewhere */
2470 if (msg->cm_fields['M'] != NULL) {
2471 if (strlen(msg->cm_fields['M']) > BIGMSG) {
2473 holdM = msg->cm_fields['M'];
2474 msg->cm_fields['M'] = NULL;
2478 /* Serialize our data structure for storage in the database */
2479 serialize_message(&smr, msg);
2482 msg->cm_fields['M'] = holdM;
2486 cprintf("%d Unable to serialize message\n",
2487 ERROR + INTERNAL_ERROR);
2491 /* Write our little bundle of joy into the message base */
2492 if (cdb_store(CDB_MSGMAIN, &newmsgid, (int)sizeof(long),
2493 smr.ser, smr.len) < 0) {
2494 CtdlLogPrintf(CTDL_ERR, "Can't store message\n");
2498 cdb_store(CDB_BIGMSGS,
2508 /* Free the memory we used for the serialized message */
2511 /* Return the *local* message ID to the caller
2512 * (even if we're storing an incoming network message)
2520 * Serialize a struct CtdlMessage into the format used on disk and network.
2522 * This function loads up a "struct ser_ret" (defined in server.h) which
2523 * contains the length of the serialized message and a pointer to the
2524 * serialized message in memory. THE LATTER MUST BE FREED BY THE CALLER.
2526 void serialize_message(struct ser_ret *ret, /* return values */
2527 struct CtdlMessage *msg) /* unserialized msg */
2529 size_t wlen, fieldlen;
2531 static char *forder = FORDER;
2534 * Check for valid message format
2536 if (is_valid_message(msg) == 0) {
2537 CtdlLogPrintf(CTDL_ERR, "serialize_message() aborting due to invalid message\n");
2544 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL)
2545 ret->len = ret->len +
2546 strlen(msg->cm_fields[(int)forder[i]]) + 2;
2548 ret->ser = malloc(ret->len);
2549 if (ret->ser == NULL) {
2550 CtdlLogPrintf(CTDL_ERR, "serialize_message() malloc(%ld) failed: %s\n",
2551 (long)ret->len, strerror(errno));
2558 ret->ser[1] = msg->cm_anon_type;
2559 ret->ser[2] = msg->cm_format_type;
2562 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL) {
2563 fieldlen = strlen(msg->cm_fields[(int)forder[i]]);
2564 ret->ser[wlen++] = (char)forder[i];
2565 safestrncpy((char *)&ret->ser[wlen], msg->cm_fields[(int)forder[i]], fieldlen+1);
2566 wlen = wlen + fieldlen + 1;
2568 if (ret->len != wlen) CtdlLogPrintf(CTDL_ERR, "ERROR: len=%ld wlen=%ld\n",
2569 (long)ret->len, (long)wlen);
2576 * Serialize a struct CtdlMessage into the format used on disk and network.
2578 * This function loads up a "struct ser_ret" (defined in server.h) which
2579 * contains the length of the serialized message and a pointer to the
2580 * serialized message in memory. THE LATTER MUST BE FREED BY THE CALLER.
2582 void dump_message(struct CtdlMessage *msg, /* unserialized msg */
2583 long Siz) /* how many chars ? */
2587 static char *forder = FORDER;
2591 * Check for valid message format
2593 if (is_valid_message(msg) == 0) {
2594 CtdlLogPrintf(CTDL_ERR, "dump_message() aborting due to invalid message\n");
2598 buf = (char*) malloc (Siz + 1);
2602 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL) {
2603 snprintf (buf, Siz, " msg[%c] = %s ...\n", (char) forder[i],
2604 msg->cm_fields[(int)forder[i]]);
2605 client_write (buf, strlen(buf));
2614 * Check to see if any messages already exist in the current room which
2615 * carry the same Exclusive ID as this one. If any are found, delete them.
2617 void ReplicationChecks(struct CtdlMessage *msg) {
2618 long old_msgnum = (-1L);
2620 if (DoesThisRoomNeedEuidIndexing(&CC->room) == 0) return;
2622 CtdlLogPrintf(CTDL_DEBUG, "Performing replication checks in <%s>\n",
2625 /* No exclusive id? Don't do anything. */
2626 if (msg == NULL) return;
2627 if (msg->cm_fields['E'] == NULL) return;
2628 if (IsEmptyStr(msg->cm_fields['E'])) return;
2629 /*CtdlLogPrintf(CTDL_DEBUG, "Exclusive ID: <%s> for room <%s>\n",
2630 msg->cm_fields['E'], CC->room.QRname);*/
2632 old_msgnum = locate_message_by_euid(msg->cm_fields['E'], &CC->room);
2633 if (old_msgnum > 0L) {
2634 CtdlLogPrintf(CTDL_DEBUG, "ReplicationChecks() replacing message %ld\n", old_msgnum);
2635 CtdlDeleteMessages(CC->room.QRname, &old_msgnum, 1, "");
2642 * Save a message to disk and submit it into the delivery system.
2644 long CtdlSubmitMsg(struct CtdlMessage *msg, /* message to save */
2645 struct recptypes *recps, /* recipients (if mail) */
2646 char *force, /* force a particular room? */
2647 int flags /* should the bessage be exported clean? */
2649 char submit_filename[128];
2650 char generated_timestamp[32];
2651 char hold_rm[ROOMNAMELEN];
2652 char actual_rm[ROOMNAMELEN];
2653 char force_room[ROOMNAMELEN];
2654 char content_type[SIZ]; /* We have to learn this */
2655 char recipient[SIZ];
2658 struct ctdluser userbuf;
2660 struct MetaData smi;
2661 FILE *network_fp = NULL;
2662 static int seqnum = 1;
2663 struct CtdlMessage *imsg = NULL;
2665 size_t instr_alloc = 0;
2667 char *hold_R, *hold_D;
2668 char *collected_addresses = NULL;
2669 struct addresses_to_be_filed *aptr = NULL;
2670 char *saved_rfc822_version = NULL;
2671 int qualified_for_journaling = 0;
2672 struct CitContext *CCC = CC; /* CachedCitContext - performance boost */
2673 char bounce_to[1024] = "";
2677 CtdlLogPrintf(CTDL_DEBUG, "CtdlSubmitMsg() called\n");
2678 if (is_valid_message(msg) == 0) return(-1); /* self check */
2680 /* If this message has no timestamp, we take the liberty of
2681 * giving it one, right now.
2683 if (msg->cm_fields['T'] == NULL) {
2684 snprintf(generated_timestamp, sizeof generated_timestamp, "%ld", (long)time(NULL));
2685 msg->cm_fields['T'] = strdup(generated_timestamp);
2688 /* If this message has no path, we generate one.
2690 if (msg->cm_fields['P'] == NULL) {
2691 if (msg->cm_fields['A'] != NULL) {
2692 msg->cm_fields['P'] = strdup(msg->cm_fields['A']);
2693 for (a=0; !IsEmptyStr(&msg->cm_fields['P'][a]); ++a) {
2694 if (isspace(msg->cm_fields['P'][a])) {
2695 msg->cm_fields['P'][a] = ' ';
2700 msg->cm_fields['P'] = strdup("unknown");
2704 if (force == NULL) {
2705 strcpy(force_room, "");
2708 strcpy(force_room, force);
2711 /* Learn about what's inside, because it's what's inside that counts */
2712 if (msg->cm_fields['M'] == NULL) {
2713 CtdlLogPrintf(CTDL_ERR, "ERROR: attempt to save message with NULL body\n");
2717 switch (msg->cm_format_type) {
2719 strcpy(content_type, "text/x-citadel-variformat");
2722 strcpy(content_type, "text/plain");
2725 strcpy(content_type, "text/plain");
2726 mptr = bmstrcasestr(msg->cm_fields['M'], "Content-type:");
2729 safestrncpy(content_type, &mptr[13], sizeof content_type);
2730 striplt(content_type);
2731 aptr = content_type;
2732 while (!IsEmptyStr(aptr)) {
2744 /* Goto the correct room */
2745 CtdlLogPrintf(CTDL_DEBUG, "Selected room %s\n", (recps) ? CCC->room.QRname : SENTITEMS);
2746 strcpy(hold_rm, CCC->room.QRname);
2747 strcpy(actual_rm, CCC->room.QRname);
2748 if (recps != NULL) {
2749 strcpy(actual_rm, SENTITEMS);
2752 /* If the user is a twit, move to the twit room for posting */
2754 if (CCC->user.axlevel == 2) {
2755 strcpy(hold_rm, actual_rm);
2756 strcpy(actual_rm, config.c_twitroom);
2757 CtdlLogPrintf(CTDL_DEBUG, "Diverting to twit room\n");
2761 /* ...or if this message is destined for Aide> then go there. */
2762 if (!IsEmptyStr(force_room)) {
2763 strcpy(actual_rm, force_room);
2766 CtdlLogPrintf(CTDL_DEBUG, "Final selection: %s\n", actual_rm);
2767 if (strcasecmp(actual_rm, CCC->room.QRname)) {
2768 /* getroom(&CCC->room, actual_rm); */
2769 usergoto(actual_rm, 0, 1, NULL, NULL);
2773 * If this message has no O (room) field, generate one.
2775 if (msg->cm_fields['O'] == NULL) {
2776 msg->cm_fields['O'] = strdup(CCC->room.QRname);
2779 /* Perform "before save" hooks (aborting if any return nonzero) */
2780 CtdlLogPrintf(CTDL_DEBUG, "Performing before-save hooks\n");
2781 if (PerformMessageHooks(msg, EVT_BEFORESAVE) > 0) return(-3);
2784 * If this message has an Exclusive ID, and the room is replication
2785 * checking enabled, then do replication checks.
2787 if (DoesThisRoomNeedEuidIndexing(&CCC->room)) {
2788 ReplicationChecks(msg);
2791 /* Save it to disk */
2792 CtdlLogPrintf(CTDL_DEBUG, "Saving to disk\n");
2793 newmsgid = send_message(msg);
2794 if (newmsgid <= 0L) return(-5);
2796 /* Write a supplemental message info record. This doesn't have to
2797 * be a critical section because nobody else knows about this message
2800 CtdlLogPrintf(CTDL_DEBUG, "Creating MetaData record\n");
2801 memset(&smi, 0, sizeof(struct MetaData));
2802 smi.meta_msgnum = newmsgid;
2803 smi.meta_refcount = 0;
2804 safestrncpy(smi.meta_content_type, content_type,
2805 sizeof smi.meta_content_type);
2808 * Measure how big this message will be when rendered as RFC822.
2809 * We do this for two reasons:
2810 * 1. We need the RFC822 length for the new metadata record, so the
2811 * POP and IMAP services don't have to calculate message lengths
2812 * while the user is waiting (multiplied by potentially hundreds
2813 * or thousands of messages).
2814 * 2. If journaling is enabled, we will need an RFC822 version of the
2815 * message to attach to the journalized copy.
2817 if (CCC->redirect_buffer != NULL) {
2818 CtdlLogPrintf(CTDL_ALERT, "CCC->redirect_buffer is not NULL during message submission!\n");
2821 CCC->redirect_buffer = malloc(SIZ);
2822 CCC->redirect_len = 0;
2823 CCC->redirect_alloc = SIZ;
2824 CtdlOutputPreLoadedMsg(msg, MT_RFC822, HEADERS_ALL, 0, 1, QP_EADDR);
2825 smi.meta_rfc822_length = CCC->redirect_len;
2826 saved_rfc822_version = CCC->redirect_buffer;
2827 CCC->redirect_buffer = NULL;
2828 CCC->redirect_len = 0;
2829 CCC->redirect_alloc = 0;
2833 /* Now figure out where to store the pointers */
2834 CtdlLogPrintf(CTDL_DEBUG, "Storing pointers\n");
2836 /* If this is being done by the networker delivering a private
2837 * message, we want to BYPASS saving the sender's copy (because there
2838 * is no local sender; it would otherwise go to the Trashcan).
2840 if ((!CCC->internal_pgm) || (recps == NULL)) {
2841 if (CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 1, msg) != 0) {
2842 CtdlLogPrintf(CTDL_ERR, "ERROR saving message pointer!\n");
2843 CtdlSaveMsgPointerInRoom(config.c_aideroom, newmsgid, 0, msg);
2847 /* For internet mail, drop a copy in the outbound queue room */
2848 if ((recps != NULL) && (recps->num_internet > 0)) {
2849 CtdlSaveMsgPointerInRoom(SMTP_SPOOLOUT_ROOM, newmsgid, 0, msg);
2852 /* If other rooms are specified, drop them there too. */
2853 if ((recps != NULL) && (recps->num_room > 0))
2854 for (i=0; i<num_tokens(recps->recp_room, '|'); ++i) {
2855 extract_token(recipient, recps->recp_room, i,
2856 '|', sizeof recipient);
2857 CtdlLogPrintf(CTDL_DEBUG, "Delivering to room <%s>\n", recipient);
2858 CtdlSaveMsgPointerInRoom(recipient, newmsgid, 0, msg);
2861 /* Bump this user's messages posted counter. */
2862 CtdlLogPrintf(CTDL_DEBUG, "Updating user\n");
2863 lgetuser(&CCC->user, CCC->curr_user);
2864 CCC->user.posted = CCC->user.posted + 1;
2865 lputuser(&CCC->user);
2867 /* Decide where bounces need to be delivered */
2868 if ((recps != NULL) && (recps->bounce_to != NULL)) {
2869 safestrncpy(bounce_to, recps->bounce_to, sizeof bounce_to);
2871 else if (CCC->logged_in) {
2872 snprintf(bounce_to, sizeof bounce_to, "%s@%s", CCC->user.fullname, config.c_nodename);
2875 snprintf(bounce_to, sizeof bounce_to, "%s@%s", msg->cm_fields['A'], msg->cm_fields['N']);
2878 /* If this is private, local mail, make a copy in the
2879 * recipient's mailbox and bump the reference count.
2881 if ((recps != NULL) && (recps->num_local > 0))
2882 for (i=0; i<num_tokens(recps->recp_local, '|'); ++i) {
2883 extract_token(recipient, recps->recp_local, i,
2884 '|', sizeof recipient);
2885 CtdlLogPrintf(CTDL_DEBUG, "Delivering private local mail to <%s>\n",
2887 if (getuser(&userbuf, recipient) == 0) {
2888 // Add a flag so the Funambol module knows its mail
2889 msg->cm_fields['W'] = strdup(recipient);
2890 MailboxName(actual_rm, sizeof actual_rm, &userbuf, MAILROOM);
2891 CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 0, msg);
2892 BumpNewMailCounter(userbuf.usernum);
2893 if (!IsEmptyStr(config.c_funambol_host) || !IsEmptyStr(config.c_pager_program)) {
2894 /* Generate a instruction message for the Funambol notification
2895 * server, in the same style as the SMTP queue
2898 instr = malloc(instr_alloc);
2899 snprintf(instr, instr_alloc,
2900 "Content-type: %s\n\nmsgid|%ld\nsubmitted|%ld\n"
2902 SPOOLMIME, newmsgid, (long)time(NULL),
2906 imsg = malloc(sizeof(struct CtdlMessage));
2907 memset(imsg, 0, sizeof(struct CtdlMessage));
2908 imsg->cm_magic = CTDLMESSAGE_MAGIC;
2909 imsg->cm_anon_type = MES_NORMAL;
2910 imsg->cm_format_type = FMT_RFC822;
2911 imsg->cm_fields['A'] = strdup("Citadel");
2912 imsg->cm_fields['J'] = strdup("do not journal");
2913 imsg->cm_fields['M'] = instr; /* imsg owns this memory now */
2914 imsg->cm_fields['W'] = strdup(recipient);
2915 CtdlSubmitMsg(imsg, NULL, FNBL_QUEUE_ROOM, 0);
2916 CtdlFreeMessage(imsg);
2920 CtdlLogPrintf(CTDL_DEBUG, "No user <%s>\n", recipient);
2921 CtdlSaveMsgPointerInRoom(config.c_aideroom,
2926 /* Perform "after save" hooks */
2927 CtdlLogPrintf(CTDL_DEBUG, "Performing after-save hooks\n");
2928 PerformMessageHooks(msg, EVT_AFTERSAVE);
2930 /* For IGnet mail, we have to save a new copy into the spooler for
2931 * each recipient, with the R and D fields set to the recipient and
2932 * destination-node. This has two ugly side effects: all other
2933 * recipients end up being unlisted in this recipient's copy of the
2934 * message, and it has to deliver multiple messages to the same
2935 * node. We'll revisit this again in a year or so when everyone has
2936 * a network spool receiver that can handle the new style messages.
2938 if ((recps != NULL) && (recps->num_ignet > 0))
2939 for (i=0; i<num_tokens(recps->recp_ignet, '|'); ++i) {
2940 extract_token(recipient, recps->recp_ignet, i,
2941 '|', sizeof recipient);
2943 hold_R = msg->cm_fields['R'];
2944 hold_D = msg->cm_fields['D'];
2945 msg->cm_fields['R'] = malloc(SIZ);
2946 msg->cm_fields['D'] = malloc(128);
2947 extract_token(msg->cm_fields['R'], recipient, 0, '@', SIZ);
2948 extract_token(msg->cm_fields['D'], recipient, 1, '@', 128);
2950 serialize_message(&smr, msg);
2952 snprintf(submit_filename, sizeof submit_filename,
2953 "%s/netmail.%04lx.%04x.%04x",
2955 (long) getpid(), CCC->cs_pid, ++seqnum);
2956 network_fp = fopen(submit_filename, "wb+");
2957 if (network_fp != NULL) {
2958 rv = fwrite(smr.ser, smr.len, 1, network_fp);
2964 free(msg->cm_fields['R']);
2965 free(msg->cm_fields['D']);
2966 msg->cm_fields['R'] = hold_R;
2967 msg->cm_fields['D'] = hold_D;
2970 /* Go back to the room we started from */
2971 CtdlLogPrintf(CTDL_DEBUG, "Returning to original room %s\n", hold_rm);
2972 if (strcasecmp(hold_rm, CCC->room.QRname))
2973 usergoto(hold_rm, 0, 1, NULL, NULL);
2975 /* For internet mail, generate delivery instructions.
2976 * Yes, this is recursive. Deal with it. Infinite recursion does
2977 * not happen because the delivery instructions message does not
2978 * contain a recipient.
2980 if ((recps != NULL) && (recps->num_internet > 0)) {
2981 CtdlLogPrintf(CTDL_DEBUG, "Generating delivery instructions\n");
2983 instr = malloc(instr_alloc);
2984 snprintf(instr, instr_alloc,
2985 "Content-type: %s\n\nmsgid|%ld\nsubmitted|%ld\n"
2987 SPOOLMIME, newmsgid, (long)time(NULL),
2991 if (recps->envelope_from != NULL) {
2992 tmp = strlen(instr);
2993 snprintf(&instr[tmp], instr_alloc-tmp, "envelope_from|%s\n", recps->envelope_from);
2996 for (i=0; i<num_tokens(recps->recp_internet, '|'); ++i) {
2997 tmp = strlen(instr);
2998 extract_token(recipient, recps->recp_internet, i, '|', sizeof recipient);
2999 if ((tmp + strlen(recipient) + 32) > instr_alloc) {
3000 instr_alloc = instr_alloc * 2;
3001 instr = realloc(instr, instr_alloc);
3003 snprintf(&instr[tmp], instr_alloc - tmp, "remote|%s|0||\n", recipient);
3006 imsg = malloc(sizeof(struct CtdlMessage));
3007 memset(imsg, 0, sizeof(struct CtdlMessage));
3008 imsg->cm_magic = CTDLMESSAGE_MAGIC;
3009 imsg->cm_anon_type = MES_NORMAL;
3010 imsg->cm_format_type = FMT_RFC822;
3011 imsg->cm_fields['A'] = strdup("Citadel");
3012 imsg->cm_fields['J'] = strdup("do not journal");
3013 imsg->cm_fields['M'] = instr; /* imsg owns this memory now */
3014 CtdlSubmitMsg(imsg, NULL, SMTP_SPOOLOUT_ROOM, QP_EADDR);
3015 CtdlFreeMessage(imsg);
3019 * Any addresses to harvest for someone's address book?
3021 if ( (CCC->logged_in) && (recps != NULL) ) {
3022 collected_addresses = harvest_collected_addresses(msg);
3025 if (collected_addresses != NULL) {
3026 aptr = (struct addresses_to_be_filed *)
3027 malloc(sizeof(struct addresses_to_be_filed));
3028 MailboxName(actual_rm, sizeof actual_rm,
3029 &CCC->user, USERCONTACTSROOM);
3030 aptr->roomname = strdup(actual_rm);
3031 aptr->collected_addresses = collected_addresses;
3032 begin_critical_section(S_ATBF);
3035 end_critical_section(S_ATBF);
3039 * Determine whether this message qualifies for journaling.
3041 if (msg->cm_fields['J'] != NULL) {
3042 qualified_for_journaling = 0;
3045 if (recps == NULL) {
3046 qualified_for_journaling = config.c_journal_pubmsgs;
3048 else if (recps->num_local + recps->num_ignet + recps->num_internet > 0) {
3049 qualified_for_journaling = config.c_journal_email;
3052 qualified_for_journaling = config.c_journal_pubmsgs;
3057 * Do we have to perform journaling? If so, hand off the saved
3058 * RFC822 version will be handed off to the journaler for background
3059 * submit. Otherwise, we have to free the memory ourselves.
3061 if (saved_rfc822_version != NULL) {
3062 if (qualified_for_journaling) {
3063 JournalBackgroundSubmit(msg, saved_rfc822_version, recps);
3066 free(saved_rfc822_version);
3079 * Convenience function for generating small administrative messages.
3081 void quickie_message(const char *from, const char *fromaddr, char *to, char *room, const char *text,
3082 int format_type, const char *subject)
3084 struct CtdlMessage *msg;
3085 struct recptypes *recp = NULL;
3087 msg = malloc(sizeof(struct CtdlMessage));
3088 memset(msg, 0, sizeof(struct CtdlMessage));
3089 msg->cm_magic = CTDLMESSAGE_MAGIC;
3090 msg->cm_anon_type = MES_NORMAL;
3091 msg->cm_format_type = format_type;
3094 msg->cm_fields['A'] = strdup(from);
3096 else if (fromaddr != NULL) {
3097 msg->cm_fields['A'] = strdup(fromaddr);
3098 if (strchr(msg->cm_fields['A'], '@')) {
3099 *strchr(msg->cm_fields['A'], '@') = 0;
3103 msg->cm_fields['A'] = strdup("Citadel");
3106 if (fromaddr != NULL) msg->cm_fields['F'] = strdup(fromaddr);
3107 if (room != NULL) msg->cm_fields['O'] = strdup(room);
3108 msg->cm_fields['N'] = strdup(NODENAME);
3110 msg->cm_fields['R'] = strdup(to);
3111 recp = validate_recipients(to, NULL, 0);
3113 if (subject != NULL) {
3114 msg->cm_fields['U'] = strdup(subject);
3116 msg->cm_fields['M'] = strdup(text);
3118 CtdlSubmitMsg(msg, recp, room, 0);
3119 CtdlFreeMessage(msg);
3120 if (recp != NULL) free_recipients(recp);
3126 * Back end function used by CtdlMakeMessage() and similar functions
3128 char *CtdlReadMessageBody(char *terminator, /* token signalling EOT */
3129 size_t maxlen, /* maximum message length */
3130 char *exist, /* if non-null, append to it;
3131 exist is ALWAYS freed */
3132 int crlf, /* CRLF newlines instead of LF */
3133 int sock /* socket handle or 0 for this session's client socket */
3137 size_t message_len = 0;
3138 size_t buffer_len = 0;
3145 if (exist == NULL) {
3152 message_len = strlen(exist);
3153 buffer_len = message_len + 4096;
3154 m = realloc(exist, buffer_len);
3161 /* Do we need to change leading ".." to "." for SMTP escaping? */
3162 if (!strcmp(terminator, ".")) {
3166 /* flush the input if we have nowhere to store it */
3171 /* read in the lines of message text one by one */
3174 if (sock_getln(sock, buf, (sizeof buf - 3)) < 0) finished = 1;
3177 if (client_getln(buf, (sizeof buf - 3)) < 1) finished = 1;
3179 if (!strcmp(buf, terminator)) finished = 1;
3181 strcat(buf, "\r\n");
3187 /* Unescape SMTP-style input of two dots at the beginning of the line */
3189 if (!strncmp(buf, "..", 2)) {
3190 strcpy(buf, &buf[1]);
3194 if ( (!flushing) && (!finished) ) {
3195 /* Measure the line */
3196 linelen = strlen(buf);
3198 /* augment the buffer if we have to */
3199 if ((message_len + linelen) >= buffer_len) {
3200 ptr = realloc(m, (buffer_len * 2) );
3201 if (ptr == NULL) { /* flush if can't allocate */
3204 buffer_len = (buffer_len * 2);
3206 CtdlLogPrintf(CTDL_DEBUG, "buffer_len is now %ld\n", (long)buffer_len);
3210 /* Add the new line to the buffer. NOTE: this loop must avoid
3211 * using functions like strcat() and strlen() because they
3212 * traverse the entire buffer upon every call, and doing that
3213 * for a multi-megabyte message slows it down beyond usability.
3215 strcpy(&m[message_len], buf);
3216 message_len += linelen;
3219 /* if we've hit the max msg length, flush the rest */
3220 if (message_len >= maxlen) flushing = 1;
3222 } while (!finished);
3230 * Build a binary message to be saved on disk.
3231 * (NOTE: if you supply 'preformatted_text', the buffer you give it
3232 * will become part of the message. This means you are no longer
3233 * responsible for managing that memory -- it will be freed along with
3234 * the rest of the fields when CtdlFreeMessage() is called.)
3237 struct CtdlMessage *CtdlMakeMessage(
3238 struct ctdluser *author, /* author's user structure */
3239 char *recipient, /* NULL if it's not mail */
3240 char *recp_cc, /* NULL if it's not mail */
3241 char *room, /* room where it's going */
3242 int type, /* see MES_ types in header file */
3243 int format_type, /* variformat, plain text, MIME... */
3244 char *fake_name, /* who we're masquerading as */
3245 char *my_email, /* which of my email addresses to use (empty is ok) */
3246 char *subject, /* Subject (optional) */
3247 char *supplied_euid, /* ...or NULL if this is irrelevant */
3248 char *preformatted_text, /* ...or NULL to read text from client */
3249 char *references /* Thread references */
3251 char dest_node[256];
3253 struct CtdlMessage *msg;
3255 msg = malloc(sizeof(struct CtdlMessage));
3256 memset(msg, 0, sizeof(struct CtdlMessage));
3257 msg->cm_magic = CTDLMESSAGE_MAGIC;
3258 msg->cm_anon_type = type;
3259 msg->cm_format_type = format_type;
3261 /* Don't confuse the poor folks if it's not routed mail. */
3262 strcpy(dest_node, "");
3264 if (recipient != NULL) striplt(recipient);
3265 if (recp_cc != NULL) striplt(recp_cc);
3267 /* Path or Return-Path */
3268 if (my_email == NULL) my_email = "";
3270 if (!IsEmptyStr(my_email)) {
3271 msg->cm_fields['P'] = strdup(my_email);
3274 snprintf(buf, sizeof buf, "%s", author->fullname);
3275 msg->cm_fields['P'] = strdup(buf);
3277 convert_spaces_to_underscores(msg->cm_fields['P']);
3279 snprintf(buf, sizeof buf, "%ld", (long)time(NULL)); /* timestamp */
3280 msg->cm_fields['T'] = strdup(buf);
3282 if ((fake_name != NULL) && (fake_name[0])) { /* author */
3283 msg->cm_fields['A'] = strdup(fake_name);
3286 msg->cm_fields['A'] = strdup(author->fullname);
3289 if (CC->room.QRflags & QR_MAILBOX) { /* room */
3290 msg->cm_fields['O'] = strdup(&CC->room.QRname[11]);
3293 msg->cm_fields['O'] = strdup(CC->room.QRname);
3296 msg->cm_fields['N'] = strdup(NODENAME); /* nodename */
3297 msg->cm_fields['H'] = strdup(HUMANNODE); /* hnodename */
3299 if ((recipient != NULL) && (recipient[0] != 0)) {
3300 msg->cm_fields['R'] = strdup(recipient);
3302 if ((recp_cc != NULL) && (recp_cc[0] != 0)) {
3303 msg->cm_fields['Y'] = strdup(recp_cc);
3305 if (dest_node[0] != 0) {
3306 msg->cm_fields['D'] = strdup(dest_node);
3309 if (!IsEmptyStr(my_email)) {
3310 msg->cm_fields['F'] = strdup(my_email);
3312 else if ( (author == &CC->user) && (!IsEmptyStr(CC->cs_inet_email)) ) {
3313 msg->cm_fields['F'] = strdup(CC->cs_inet_email);
3316 if (subject != NULL) {
3319 length = strlen(subject);
3325 while ((subject[i] != '\0') &&
3326 (IsAscii = isascii(subject[i]) != 0 ))
3329 msg->cm_fields['U'] = strdup(subject);
3330 else /* ok, we've got utf8 in the string. */
3332 msg->cm_fields['U'] = rfc2047encode(subject, length);
3338 if (supplied_euid != NULL) {
3339 msg->cm_fields['E'] = strdup(supplied_euid);
3342 if (references != NULL) {
3343 if (!IsEmptyStr(references)) {
3344 msg->cm_fields['W'] = strdup(references);
3348 if (preformatted_text != NULL) {
3349 msg->cm_fields['M'] = preformatted_text;
3352 msg->cm_fields['M'] = CtdlReadMessageBody("000", config.c_maxmsglen, NULL, 0, 0);
3360 * Check to see whether we have permission to post a message in the current
3361 * room. Returns a *CITADEL ERROR CODE* and puts a message in errmsgbuf, or
3362 * returns 0 on success.
3364 int CtdlDoIHavePermissionToPostInThisRoom(char *errmsgbuf,
3366 const char* RemoteIdentifier,
3370 if (!(CC->logged_in) &&
3371 (PostPublic == POST_LOGGED_IN)) {
3372 snprintf(errmsgbuf, n, "Not logged in.");
3373 return (ERROR + NOT_LOGGED_IN);
3375 else if (PostPublic == CHECK_EXISTANCE) {
3376 return (0); // We're Evaling whether a recipient exists
3378 else if (!(CC->logged_in)) {
3380 if ((CC->room.QRflags & QR_READONLY)) {
3381 snprintf(errmsgbuf, n, "Not logged in.");
3382 return (ERROR + NOT_LOGGED_IN);
3384 if (CC->room.QRflags2 & QR2_MODERATED) {
3385 snprintf(errmsgbuf, n, "Not logged in Moderation feature not yet implemented!");
3386 return (ERROR + NOT_LOGGED_IN);
3388 if ((PostPublic!=POST_LMTP) &&(CC->room.QRflags2 & QR2_SMTP_PUBLIC) == 0) {
3393 if (RemoteIdentifier == NULL)
3395 snprintf(errmsgbuf, n, "Need sender to permit access.");
3396 return (ERROR + USERNAME_REQUIRED);
3399 assoc_file_name(filename, sizeof filename, &CC->room, ctdl_netcfg_dir);
3400 begin_critical_section(S_NETCONFIGS);
3401 if (!read_spoolcontrol_file(&sc, filename))
3403 end_critical_section(S_NETCONFIGS);
3404 snprintf(errmsgbuf, n,
3405 "This mailing list only accepts posts from subscribers.");
3406 return (ERROR + NO_SUCH_USER);
3408 end_critical_section(S_NETCONFIGS);
3409 found = is_recipient (sc, RemoteIdentifier);
3410 free_spoolcontrol_struct(&sc);
3415 snprintf(errmsgbuf, n,
3416 "This mailing list only accepts posts from subscribers.");
3417 return (ERROR + NO_SUCH_USER);
3424 if ((CC->user.axlevel < 2)
3425 && ((CC->room.QRflags & QR_MAILBOX) == 0)) {
3426 snprintf(errmsgbuf, n, "Need to be validated to enter "
3427 "(except in %s> to sysop)", MAILROOM);
3428 return (ERROR + HIGHER_ACCESS_REQUIRED);
3431 CtdlRoomAccess(&CC->room, &CC->user, &ra, NULL);
3432 if (!(ra & UA_POSTALLOWED)) {
3433 snprintf(errmsgbuf, n, "Higher access is required to post in this room.");
3434 return (ERROR + HIGHER_ACCESS_REQUIRED);
3437 strcpy(errmsgbuf, "Ok");
3443 * Check to see if the specified user has Internet mail permission
3444 * (returns nonzero if permission is granted)
3446 int CtdlCheckInternetMailPermission(struct ctdluser *who) {
3448 /* Do not allow twits to send Internet mail */
3449 if (who->axlevel <= 2) return(0);
3451 /* Globally enabled? */
3452 if (config.c_restrict == 0) return(1);
3454 /* User flagged ok? */
3455 if (who->flags & US_INTERNET) return(2);
3457 /* Aide level access? */
3458 if (who->axlevel >= 6) return(3);
3460 /* No mail for you! */
3466 * Validate recipients, count delivery types and errors, and handle aliasing
3467 * FIXME check for dupes!!!!!
3469 * Returns 0 if all addresses are ok, ret->num_error = -1 if no addresses
3470 * were specified, or the number of addresses found invalid.
3472 * Caller needs to free the result using free_recipients()
3474 struct recptypes *validate_recipients(char *supplied_recipients,
3475 const char *RemoteIdentifier,
3477 struct recptypes *ret;
3478 char *recipients = NULL;
3479 char this_recp[256];
3480 char this_recp_cooked[256];
3486 struct ctdluser tempUS;
3487 struct ctdlroom tempQR;
3488 struct ctdlroom tempQR2;
3494 ret = (struct recptypes *) malloc(sizeof(struct recptypes));
3495 if (ret == NULL) return(NULL);
3497 /* Set all strings to null and numeric values to zero */
3498 memset(ret, 0, sizeof(struct recptypes));
3500 if (supplied_recipients == NULL) {
3501 recipients = strdup("");
3504 recipients = strdup(supplied_recipients);
3507 /* Allocate some memory. Yes, this allocates 500% more memory than we will
3508 * actually need, but it's healthier for the heap than doing lots of tiny
3509 * realloc() calls instead.
3512 ret->errormsg = malloc(strlen(recipients) + 1024);
3513 ret->recp_local = malloc(strlen(recipients) + 1024);
3514 ret->recp_internet = malloc(strlen(recipients) + 1024);
3515 ret->recp_ignet = malloc(strlen(recipients) + 1024);
3516 ret->recp_room = malloc(strlen(recipients) + 1024);
3517 ret->display_recp = malloc(strlen(recipients) + 1024);
3519 ret->errormsg[0] = 0;
3520 ret->recp_local[0] = 0;
3521 ret->recp_internet[0] = 0;
3522 ret->recp_ignet[0] = 0;
3523 ret->recp_room[0] = 0;
3524 ret->display_recp[0] = 0;
3526 ret->recptypes_magic = RECPTYPES_MAGIC;
3528 /* Change all valid separator characters to commas */
3529 for (i=0; !IsEmptyStr(&recipients[i]); ++i) {
3530 if ((recipients[i] == ';') || (recipients[i] == '|')) {
3531 recipients[i] = ',';
3535 /* Now start extracting recipients... */
3537 while (!IsEmptyStr(recipients)) {
3539 for (i=0; i<=strlen(recipients); ++i) {
3540 if (recipients[i] == '\"') in_quotes = 1 - in_quotes;
3541 if ( ( (recipients[i] == ',') && (!in_quotes) ) || (recipients[i] == 0) ) {
3542 safestrncpy(this_recp, recipients, i+1);
3544 if (recipients[i] == ',') {
3545 strcpy(recipients, &recipients[i+1]);
3548 strcpy(recipients, "");
3555 if (IsEmptyStr(this_recp))
3557 CtdlLogPrintf(CTDL_DEBUG, "Evaluating recipient #%d: %s\n", num_recps, this_recp);
3559 mailtype = alias(this_recp);
3560 mailtype = alias(this_recp);
3561 mailtype = alias(this_recp);
3563 for (j=0; !IsEmptyStr(&this_recp[j]); ++j) {
3564 if (this_recp[j]=='_') {
3565 this_recp_cooked[j] = ' ';
3568 this_recp_cooked[j] = this_recp[j];
3571 this_recp_cooked[j] = '\0';
3576 if (!strcasecmp(this_recp, "sysop")) {
3578 strcpy(this_recp, config.c_aideroom);
3579 if (!IsEmptyStr(ret->recp_room)) {
3580 strcat(ret->recp_room, "|");
3582 strcat(ret->recp_room, this_recp);
3584 else if ( (!strncasecmp(this_recp, "room_", 5))
3585 && (!getroom(&tempQR, &this_recp_cooked[5])) ) {
3587 /* Save room so we can restore it later */
3591 /* Check permissions to send mail to this room */
3592 err = CtdlDoIHavePermissionToPostInThisRoom(errmsg,
3604 if (!IsEmptyStr(ret->recp_room)) {
3605 strcat(ret->recp_room, "|");
3607 strcat(ret->recp_room, &this_recp_cooked[5]);
3610 /* Restore room in case something needs it */
3614 else if (getuser(&tempUS, this_recp) == 0) {
3616 strcpy(this_recp, tempUS.fullname);
3617 if (!IsEmptyStr(ret->recp_local)) {
3618 strcat(ret->recp_local, "|");
3620 strcat(ret->recp_local, this_recp);
3622 else if (getuser(&tempUS, this_recp_cooked) == 0) {
3624 strcpy(this_recp, tempUS.fullname);
3625 if (!IsEmptyStr(ret->recp_local)) {
3626 strcat(ret->recp_local, "|");
3628 strcat(ret->recp_local, this_recp);
3636 /* Yes, you're reading this correctly: if the target
3637 * domain points back to the local system or an attached
3638 * Citadel directory, the address is invalid. That's
3639 * because if the address were valid, we would have
3640 * already translated it to a local address by now.
3642 if (IsDirectory(this_recp, 0)) {
3647 ++ret->num_internet;
3648 if (!IsEmptyStr(ret->recp_internet)) {
3649 strcat(ret->recp_internet, "|");
3651 strcat(ret->recp_internet, this_recp);
3656 if (!IsEmptyStr(ret->recp_ignet)) {
3657 strcat(ret->recp_ignet, "|");
3659 strcat(ret->recp_ignet, this_recp);
3667 if (IsEmptyStr(errmsg)) {
3668 snprintf(append, sizeof append, "Invalid recipient: %s", this_recp);
3671 snprintf(append, sizeof append, "%s", errmsg);
3673 if ( (strlen(ret->errormsg) + strlen(append) + 3) < SIZ) {
3674 if (!IsEmptyStr(ret->errormsg)) {
3675 strcat(ret->errormsg, "; ");
3677 strcat(ret->errormsg, append);
3681 if (IsEmptyStr(ret->display_recp)) {
3682 strcpy(append, this_recp);
3685 snprintf(append, sizeof append, ", %s", this_recp);
3687 if ( (strlen(ret->display_recp)+strlen(append)) < SIZ) {
3688 strcat(ret->display_recp, append);
3693 if ((ret->num_local + ret->num_internet + ret->num_ignet +
3694 ret->num_room + ret->num_error) == 0) {
3695 ret->num_error = (-1);
3696 strcpy(ret->errormsg, "No recipients specified.");
3699 CtdlLogPrintf(CTDL_DEBUG, "validate_recipients()\n");
3700 CtdlLogPrintf(CTDL_DEBUG, " local: %d <%s>\n", ret->num_local, ret->recp_local);
3701 CtdlLogPrintf(CTDL_DEBUG, " room: %d <%s>\n", ret->num_room, ret->recp_room);
3702 CtdlLogPrintf(CTDL_DEBUG, " inet: %d <%s>\n", ret->num_internet, ret->recp_internet);
3703 CtdlLogPrintf(CTDL_DEBUG, " ignet: %d <%s>\n", ret->num_ignet, ret->recp_ignet);
3704 CtdlLogPrintf(CTDL_DEBUG, " error: %d <%s>\n", ret->num_error, ret->errormsg);
3712 * Destructor for struct recptypes
3714 void free_recipients(struct recptypes *valid) {
3716 if (valid == NULL) {
3720 if (valid->recptypes_magic != RECPTYPES_MAGIC) {
3721 CtdlLogPrintf(CTDL_EMERG, "Attempt to call free_recipients() on some other data type!\n");
3725 if (valid->errormsg != NULL) free(valid->errormsg);
3726 if (valid->recp_local != NULL) free(valid->recp_local);
3727 if (valid->recp_internet != NULL) free(valid->recp_internet);
3728 if (valid->recp_ignet != NULL) free(valid->recp_ignet);
3729 if (valid->recp_room != NULL) free(valid->recp_room);
3730 if (valid->display_recp != NULL) free(valid->display_recp);
3731 if (valid->bounce_to != NULL) free(valid->bounce_to);
3732 if (valid->envelope_from != NULL) free(valid->envelope_from);
3739 * message entry - mode 0 (normal)
3741 void cmd_ent0(char *entargs)
3747 char supplied_euid[128];
3749 int format_type = 0;
3750 char newusername[256];
3751 char newuseremail[256];
3752 struct CtdlMessage *msg;
3756 struct recptypes *valid = NULL;
3757 struct recptypes *valid_to = NULL;
3758 struct recptypes *valid_cc = NULL;
3759 struct recptypes *valid_bcc = NULL;
3761 int subject_required = 0;
3766 int newuseremail_ok = 0;
3767 char references[SIZ];
3772 post = extract_int(entargs, 0);
3773 extract_token(recp, entargs, 1, '|', sizeof recp);
3774 anon_flag = extract_int(entargs, 2);
3775 format_type = extract_int(entargs, 3);
3776 extract_token(subject, entargs, 4, '|', sizeof subject);
3777 extract_token(newusername, entargs, 5, '|', sizeof newusername);
3778 do_confirm = extract_int(entargs, 6);
3779 extract_token(cc, entargs, 7, '|', sizeof cc);
3780 extract_token(bcc, entargs, 8, '|', sizeof bcc);
3781 switch(CC->room.QRdefaultview) {
3784 extract_token(supplied_euid, entargs, 9, '|', sizeof supplied_euid);
3787 supplied_euid[0] = 0;
3790 extract_token(newuseremail, entargs, 10, '|', sizeof newuseremail);
3791 extract_token(references, entargs, 11, '|', sizeof references);
3792 for (ptr=references; *ptr != 0; ++ptr) {
3793 if (*ptr == '!') *ptr = '|';
3796 /* first check to make sure the request is valid. */
3798 err = CtdlDoIHavePermissionToPostInThisRoom(errmsg, sizeof errmsg, NULL, POST_LOGGED_IN);
3801 cprintf("%d %s\n", err, errmsg);
3805 /* Check some other permission type things. */
3807 if (IsEmptyStr(newusername)) {
3808 strcpy(newusername, CC->user.fullname);
3810 if ( (CC->user.axlevel < 6)
3811 && (strcasecmp(newusername, CC->user.fullname))
3812 && (strcasecmp(newusername, CC->cs_inet_fn))
3814 cprintf("%d You don't have permission to author messages as '%s'.\n",
3815 ERROR + HIGHER_ACCESS_REQUIRED,
3822 if (IsEmptyStr(newuseremail)) {
3823 newuseremail_ok = 1;
3826 if (!IsEmptyStr(newuseremail)) {
3827 if (!strcasecmp(newuseremail, CC->cs_inet_email)) {
3828 newuseremail_ok = 1;
3830 else if (!IsEmptyStr(CC->cs_inet_other_emails)) {
3831 j = num_tokens(CC->cs_inet_other_emails, '|');
3832 for (i=0; i<j; ++i) {
3833 extract_token(buf, CC->cs_inet_other_emails, i, '|', sizeof buf);
3834 if (!strcasecmp(newuseremail, buf)) {
3835 newuseremail_ok = 1;
3841 if (!newuseremail_ok) {
3842 cprintf("%d You don't have permission to author messages as '%s'.\n",
3843 ERROR + HIGHER_ACCESS_REQUIRED,
3849 CC->cs_flags |= CS_POSTING;
3851 /* In mailbox rooms we have to behave a little differently --
3852 * make sure the user has specified at least one recipient. Then
3853 * validate the recipient(s). We do this for the Mail> room, as
3854 * well as any room which has the "Mailbox" view set.
3857 if ( ( (CC->room.QRflags & QR_MAILBOX) && (!strcasecmp(&CC->room.QRname[11], MAILROOM)) )
3858 || ( (CC->room.QRflags & QR_MAILBOX) && (CC->curr_view == VIEW_MAILBOX) )
3860 if (CC->user.axlevel < 2) {
3861 strcpy(recp, "sysop");
3866 valid_to = validate_recipients(recp, NULL, 0);
3867 if (valid_to->num_error > 0) {
3868 cprintf("%d %s\n", ERROR + NO_SUCH_USER, valid_to->errormsg);
3869 free_recipients(valid_to);
3873 valid_cc = validate_recipients(cc, NULL, 0);
3874 if (valid_cc->num_error > 0) {
3875 cprintf("%d %s\n", ERROR + NO_SUCH_USER, valid_cc->errormsg);
3876 free_recipients(valid_to);
3877 free_recipients(valid_cc);
3881 valid_bcc = validate_recipients(bcc, NULL, 0);
3882 if (valid_bcc->num_error > 0) {
3883 cprintf("%d %s\n", ERROR + NO_SUCH_USER, valid_bcc->errormsg);
3884 free_recipients(valid_to);
3885 free_recipients(valid_cc);
3886 free_recipients(valid_bcc);
3890 /* Recipient required, but none were specified */
3891 if ( (valid_to->num_error < 0) && (valid_cc->num_error < 0) && (valid_bcc->num_error < 0) ) {
3892 free_recipients(valid_to);
3893 free_recipients(valid_cc);
3894 free_recipients(valid_bcc);
3895 cprintf("%d At least one recipient is required.\n", ERROR + NO_SUCH_USER);
3899 if (valid_to->num_internet + valid_cc->num_internet + valid_bcc->num_internet > 0) {
3900 if (CtdlCheckInternetMailPermission(&CC->user)==0) {
3901 cprintf("%d You do not have permission "
3902 "to send Internet mail.\n",
3903 ERROR + HIGHER_ACCESS_REQUIRED);
3904 free_recipients(valid_to);
3905 free_recipients(valid_cc);
3906 free_recipients(valid_bcc);
3911 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)
3912 && (CC->user.axlevel < 4) ) {
3913 cprintf("%d Higher access required for network mail.\n",
3914 ERROR + HIGHER_ACCESS_REQUIRED);
3915 free_recipients(valid_to);
3916 free_recipients(valid_cc);
3917 free_recipients(valid_bcc);
3921 if ((RESTRICT_INTERNET == 1)
3922 && (valid_to->num_internet + valid_cc->num_internet + valid_bcc->num_internet > 0)
3923 && ((CC->user.flags & US_INTERNET) == 0)
3924 && (!CC->internal_pgm)) {
3925 cprintf("%d You don't have access to Internet mail.\n",
3926 ERROR + HIGHER_ACCESS_REQUIRED);
3927 free_recipients(valid_to);
3928 free_recipients(valid_cc);
3929 free_recipients(valid_bcc);
3935 /* Is this a room which has anonymous-only or anonymous-option? */
3936 anonymous = MES_NORMAL;
3937 if (CC->room.QRflags & QR_ANONONLY) {
3938 anonymous = MES_ANONONLY;
3940 if (CC->room.QRflags & QR_ANONOPT) {
3941 if (anon_flag == 1) { /* only if the user requested it */
3942 anonymous = MES_ANONOPT;
3946 if ((CC->room.QRflags & QR_MAILBOX) == 0) {
3950 /* Recommend to the client that the use of a message subject is
3951 * strongly recommended in this room, if either the SUBJECTREQ flag
3952 * is set, or if there is one or more Internet email recipients.
3954 if (CC->room.QRflags2 & QR2_SUBJECTREQ) subject_required = 1;
3955 if ((valid_to) && (valid_to->num_internet > 0)) subject_required = 1;
3956 if ((valid_cc) && (valid_cc->num_internet > 0)) subject_required = 1;
3957 if ((valid_bcc) && (valid_bcc->num_internet > 0)) subject_required = 1;
3959 /* If we're only checking the validity of the request, return
3960 * success without creating the message.
3963 cprintf("%d %s|%d\n", CIT_OK,
3964 ((valid_to != NULL) ? valid_to->display_recp : ""),
3966 free_recipients(valid_to);
3967 free_recipients(valid_cc);
3968 free_recipients(valid_bcc);
3972 /* We don't need these anymore because we'll do it differently below */
3973 free_recipients(valid_to);
3974 free_recipients(valid_cc);
3975 free_recipients(valid_bcc);
3977 /* Read in the message from the client. */
3979 cprintf("%d send message\n", START_CHAT_MODE);
3981 cprintf("%d send message\n", SEND_LISTING);
3984 msg = CtdlMakeMessage(&CC->user, recp, cc,
3985 CC->room.QRname, anonymous, format_type,
3986 newusername, newuseremail, subject,
3987 ((!IsEmptyStr(supplied_euid)) ? supplied_euid : NULL),
3990 /* Put together one big recipients struct containing to/cc/bcc all in
3991 * one. This is for the envelope.
3993 char *all_recps = malloc(SIZ * 3);
3994 strcpy(all_recps, recp);
3995 if (!IsEmptyStr(cc)) {
3996 if (!IsEmptyStr(all_recps)) {
3997 strcat(all_recps, ",");
3999 strcat(all_recps, cc);
4001 if (!IsEmptyStr(bcc)) {
4002 if (!IsEmptyStr(all_recps)) {
4003 strcat(all_recps, ",");
4005 strcat(all_recps, bcc);
4007 if (!IsEmptyStr(all_recps)) {
4008 valid = validate_recipients(all_recps, NULL, 0);
4016 msgnum = CtdlSubmitMsg(msg, valid, "", QP_EADDR);
4019 cprintf("%ld\n", msgnum);
4021 cprintf("Message accepted.\n");
4024 cprintf("Internal error.\n");
4026 if (msg->cm_fields['E'] != NULL) {
4027 cprintf("%s\n", msg->cm_fields['E']);
4034 CtdlFreeMessage(msg);
4036 if (valid != NULL) {
4037 free_recipients(valid);
4045 * API function to delete messages which match a set of criteria
4046 * (returns the actual number of messages deleted)
4048 int CtdlDeleteMessages(char *room_name, /* which room */
4049 long *dmsgnums, /* array of msg numbers to be deleted */
4050 int num_dmsgnums, /* number of msgs to be deleted, or 0 for "any" */
4051 char *content_type /* or "" for any. regular expressions expected. */
4054 struct ctdlroom qrbuf;
4055 struct cdbdata *cdbfr;
4056 long *msglist = NULL;
4057 long *dellist = NULL;
4060 int num_deleted = 0;
4062 struct MetaData smi;
4065 int need_to_free_re = 0;
4067 if (content_type) if (!IsEmptyStr(content_type)) {
4068 regcomp(&re, content_type, 0);
4069 need_to_free_re = 1;
4071 CtdlLogPrintf(CTDL_DEBUG, "CtdlDeleteMessages(%s, %d msgs, %s)\n",
4072 room_name, num_dmsgnums, content_type);
4074 /* get room record, obtaining a lock... */
4075 if (lgetroom(&qrbuf, room_name) != 0) {
4076 CtdlLogPrintf(CTDL_ERR, "CtdlDeleteMessages(): Room <%s> not found\n",
4078 if (need_to_free_re) regfree(&re);
4079 return (0); /* room not found */
4081 cdbfr = cdb_fetch(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long));
4083 if (cdbfr != NULL) {
4084 dellist = malloc(cdbfr->len);
4085 msglist = (long *) cdbfr->ptr;
4086 cdbfr->ptr = NULL; /* CtdlDeleteMessages() now owns this memory */
4087 num_msgs = cdbfr->len / sizeof(long);
4091 for (i = 0; i < num_msgs; ++i) {
4094 /* Set/clear a bit for each criterion */
4096 /* 0 messages in the list or a null list means that we are
4097 * interested in deleting any messages which meet the other criteria.
4099 if ((num_dmsgnums == 0) || (dmsgnums == NULL)) {
4100 delete_this |= 0x01;
4103 for (j=0; j<num_dmsgnums; ++j) {
4104 if (msglist[i] == dmsgnums[j]) {
4105 delete_this |= 0x01;
4110 if (IsEmptyStr(content_type)) {
4111 delete_this |= 0x02;
4113 GetMetaData(&smi, msglist[i]);
4114 if (regexec(&re, smi.meta_content_type, 1, &pm, 0) == 0) {
4115 delete_this |= 0x02;
4119 /* Delete message only if all bits are set */
4120 if (delete_this == 0x03) {
4121 dellist[num_deleted++] = msglist[i];
4126 num_msgs = sort_msglist(msglist, num_msgs);
4127 cdb_store(CDB_MSGLISTS, &qrbuf.QRnumber, (int)sizeof(long),
4128 msglist, (int)(num_msgs * sizeof(long)));
4130 qrbuf.QRhighest = msglist[num_msgs - 1];
4134 /* Go through the messages we pulled out of the index, and decrement
4135 * their reference counts by 1. If this is the only room the message
4136 * was in, the reference count will reach zero and the message will
4137 * automatically be deleted from the database. We do this in a
4138 * separate pass because there might be plug-in hooks getting called,
4139 * and we don't want that happening during an S_ROOMS critical
4142 if (num_deleted) for (i=0; i<num_deleted; ++i) {
4143 PerformDeleteHooks(qrbuf.QRname, dellist[i]);
4144 AdjRefCount(dellist[i], -1);
4147 /* Now free the memory we used, and go away. */
4148 if (msglist != NULL) free(msglist);
4149 if (dellist != NULL) free(dellist);
4150 CtdlLogPrintf(CTDL_DEBUG, "%d message(s) deleted.\n", num_deleted);
4151 if (need_to_free_re) regfree(&re);
4152 return (num_deleted);
4158 * Check whether the current user has permission to delete messages from
4159 * the current room (returns 1 for yes, 0 for no)
4161 int CtdlDoIHavePermissionToDeleteMessagesFromThisRoom(void) {
4163 CtdlRoomAccess(&CC->room, &CC->user, &ra, NULL);
4164 if (ra & UA_DELETEALLOWED) return(1);
4172 * Delete message from current room
4174 void cmd_dele(char *args)
4183 extract_token(msgset, args, 0, '|', sizeof msgset);
4184 num_msgs = num_tokens(msgset, ',');
4186 cprintf("%d Nothing to do.\n", CIT_OK);
4190 if (CtdlDoIHavePermissionToDeleteMessagesFromThisRoom() == 0) {
4191 cprintf("%d Higher access required.\n",
4192 ERROR + HIGHER_ACCESS_REQUIRED);
4197 * Build our message set to be moved/copied
4199 msgs = malloc(num_msgs * sizeof(long));
4200 for (i=0; i<num_msgs; ++i) {
4201 extract_token(msgtok, msgset, i, ',', sizeof msgtok);
4202 msgs[i] = atol(msgtok);
4205 num_deleted = CtdlDeleteMessages(CC->room.QRname, msgs, num_msgs, "");
4209 cprintf("%d %d message%s deleted.\n", CIT_OK,
4210 num_deleted, ((num_deleted != 1) ? "s" : ""));
4212 cprintf("%d Message not found.\n", ERROR + MESSAGE_NOT_FOUND);
4220 * move or copy a message to another room
4222 void cmd_move(char *args)
4229 char targ[ROOMNAMELEN];
4230 struct ctdlroom qtemp;
4237 extract_token(msgset, args, 0, '|', sizeof msgset);
4238 num_msgs = num_tokens(msgset, ',');
4240 cprintf("%d Nothing to do.\n", CIT_OK);
4244 extract_token(targ, args, 1, '|', sizeof targ);
4245 convert_room_name_macros(targ, sizeof targ);
4246 targ[ROOMNAMELEN - 1] = 0;
4247 is_copy = extract_int(args, 2);
4249 if (getroom(&qtemp, targ) != 0) {
4250 cprintf("%d '%s' does not exist.\n", ERROR + ROOM_NOT_FOUND, targ);
4254 if (!strcasecmp(qtemp.QRname, CC->room.QRname)) {
4255 cprintf("%d Source and target rooms are the same.\n", ERROR + ALREADY_EXISTS);
4259 getuser(&CC->user, CC->curr_user);
4260 CtdlRoomAccess(&qtemp, &CC->user, &ra, NULL);
4262 /* Check for permission to perform this operation.
4263 * Remember: "CC->room" is source, "qtemp" is target.
4267 /* Aides can move/copy */
4268 if (CC->user.axlevel >= 6) permit = 1;
4270 /* Room aides can move/copy */
4271 if (CC->user.usernum == CC->room.QRroomaide) permit = 1;
4273 /* Permit move/copy from personal rooms */
4274 if ((CC->room.QRflags & QR_MAILBOX)
4275 && (qtemp.QRflags & QR_MAILBOX)) permit = 1;
4277 /* Permit only copy from public to personal room */
4279 && (!(CC->room.QRflags & QR_MAILBOX))
4280 && (qtemp.QRflags & QR_MAILBOX)) permit = 1;
4282 /* Permit message removal from collaborative delete rooms */
4283 if (CC->room.QRflags2 & QR2_COLLABDEL) permit = 1;
4285 /* Users allowed to post into the target room may move into it too. */
4286 if ((CC->room.QRflags & QR_MAILBOX) &&
4287 (qtemp.QRflags & UA_POSTALLOWED)) permit = 1;
4289 /* User must have access to target room */
4290 if (!(ra & UA_KNOWN)) permit = 0;
4293 cprintf("%d Higher access required.\n",
4294 ERROR + HIGHER_ACCESS_REQUIRED);
4299 * Build our message set to be moved/copied
4301 msgs = malloc(num_msgs * sizeof(long));
4302 for (i=0; i<num_msgs; ++i) {
4303 extract_token(msgtok, msgset, i, ',', sizeof msgtok);
4304 msgs[i] = atol(msgtok);
4310 err = CtdlSaveMsgPointersInRoom(targ, msgs, num_msgs, 1, NULL);
4312 cprintf("%d Cannot store message(s) in %s: error %d\n",
4318 /* Now delete the message from the source room,
4319 * if this is a 'move' rather than a 'copy' operation.
4322 CtdlDeleteMessages(CC->room.QRname, msgs, num_msgs, "");
4326 cprintf("%d Message(s) %s.\n", CIT_OK, (is_copy ? "copied" : "moved") );
4332 * GetMetaData() - Get the supplementary record for a message
4334 void GetMetaData(struct MetaData *smibuf, long msgnum)
4337 struct cdbdata *cdbsmi;
4340 memset(smibuf, 0, sizeof(struct MetaData));
4341 smibuf->meta_msgnum = msgnum;
4342 smibuf->meta_refcount = 1; /* Default reference count is 1 */
4344 /* Use the negative of the message number for its supp record index */
4345 TheIndex = (0L - msgnum);
4347 cdbsmi = cdb_fetch(CDB_MSGMAIN, &TheIndex, sizeof(long));
4348 if (cdbsmi == NULL) {
4349 return; /* record not found; go with defaults */
4351 memcpy(smibuf, cdbsmi->ptr,
4352 ((cdbsmi->len > sizeof(struct MetaData)) ?
4353 sizeof(struct MetaData) : cdbsmi->len));
4360 * PutMetaData() - (re)write supplementary record for a message
4362 void PutMetaData(struct MetaData *smibuf)
4366 /* Use the negative of the message number for the metadata db index */
4367 TheIndex = (0L - smibuf->meta_msgnum);
4369 cdb_store(CDB_MSGMAIN,
4370 &TheIndex, (int)sizeof(long),
4371 smibuf, (int)sizeof(struct MetaData));
4376 * AdjRefCount - submit an adjustment to the reference count for a message.
4377 * (These are just queued -- we actually process them later.)
4379 void AdjRefCount(long msgnum, int incr)
4381 struct arcq new_arcq;
4384 begin_critical_section(S_SUPPMSGMAIN);
4385 if (arcfp == NULL) {
4386 arcfp = fopen(file_arcq, "ab+");
4388 end_critical_section(S_SUPPMSGMAIN);
4390 /* msgnum < 0 means that we're trying to close the file */
4392 CtdlLogPrintf(CTDL_DEBUG, "Closing the AdjRefCount queue file\n");
4393 begin_critical_section(S_SUPPMSGMAIN);
4394 if (arcfp != NULL) {
4398 end_critical_section(S_SUPPMSGMAIN);
4403 * If we can't open the queue, perform the operation synchronously.
4405 if (arcfp == NULL) {
4406 TDAP_AdjRefCount(msgnum, incr);
4410 new_arcq.arcq_msgnum = msgnum;
4411 new_arcq.arcq_delta = incr;
4412 rv = fwrite(&new_arcq, sizeof(struct arcq), 1, arcfp);
4420 * TDAP_ProcessAdjRefCountQueue()
4422 * Process the queue of message count adjustments that was created by calls
4423 * to AdjRefCount() ... by reading the queue and calling TDAP_AdjRefCount()
4424 * for each one. This should be an "off hours" operation.
4426 int TDAP_ProcessAdjRefCountQueue(void)
4428 char file_arcq_temp[PATH_MAX];
4431 struct arcq arcq_rec;
4432 int num_records_processed = 0;
4434 snprintf(file_arcq_temp, sizeof file_arcq_temp, "%s.%04x", file_arcq, rand());
4436 begin_critical_section(S_SUPPMSGMAIN);
4437 if (arcfp != NULL) {
4442 r = link(file_arcq, file_arcq_temp);
4444 CtdlLogPrintf(CTDL_CRIT, "%s: %s\n", file_arcq_temp, strerror(errno));
4445 end_critical_section(S_SUPPMSGMAIN);
4446 return(num_records_processed);
4450 end_critical_section(S_SUPPMSGMAIN);
4452 fp = fopen(file_arcq_temp, "rb");
4454 CtdlLogPrintf(CTDL_CRIT, "%s: %s\n", file_arcq_temp, strerror(errno));
4455 return(num_records_processed);
4458 while (fread(&arcq_rec, sizeof(struct arcq), 1, fp) == 1) {
4459 TDAP_AdjRefCount(arcq_rec.arcq_msgnum, arcq_rec.arcq_delta);
4460 ++num_records_processed;
4464 r = unlink(file_arcq_temp);
4466 CtdlLogPrintf(CTDL_CRIT, "%s: %s\n", file_arcq_temp, strerror(errno));
4469 return(num_records_processed);
4475 * TDAP_AdjRefCount - adjust the reference count for a message.
4476 * This one does it "for real" because it's called by
4477 * the autopurger function that processes the queue
4478 * created by AdjRefCount(). If a message's reference
4479 * count becomes zero, we also delete the message from
4480 * disk and de-index it.
4482 void TDAP_AdjRefCount(long msgnum, int incr)
4485 struct MetaData smi;
4488 /* This is a *tight* critical section; please keep it that way, as
4489 * it may get called while nested in other critical sections.
4490 * Complicating this any further will surely cause deadlock!
4492 begin_critical_section(S_SUPPMSGMAIN);
4493 GetMetaData(&smi, msgnum);
4494 smi.meta_refcount += incr;
4496 end_critical_section(S_SUPPMSGMAIN);
4497 CtdlLogPrintf(CTDL_DEBUG, "msg %ld ref count delta %+d, is now %d\n",
4498 msgnum, incr, smi.meta_refcount);
4500 /* If the reference count is now zero, delete the message
4501 * (and its supplementary record as well).
4503 if (smi.meta_refcount == 0) {
4504 CtdlLogPrintf(CTDL_DEBUG, "Deleting message <%ld>\n", msgnum);
4506 /* Call delete hooks with NULL room to show it has gone altogether */
4507 PerformDeleteHooks(NULL, msgnum);
4509 /* Remove from message base */
4511 cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long));
4512 cdb_delete(CDB_BIGMSGS, &delnum, (int)sizeof(long));
4514 /* Remove metadata record */
4515 delnum = (0L - msgnum);
4516 cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long));
4522 * Write a generic object to this room
4524 * Note: this could be much more efficient. Right now we use two temporary
4525 * files, and still pull the message into memory as with all others.
4527 void CtdlWriteObject(char *req_room, /* Room to stuff it in */
4528 char *content_type, /* MIME type of this object */
4529 char *raw_message, /* Data to be written */
4530 off_t raw_length, /* Size of raw_message */
4531 struct ctdluser *is_mailbox, /* Mailbox room? */
4532 int is_binary, /* Is encoding necessary? */
4533 int is_unique, /* Del others of this type? */
4534 unsigned int flags /* Internal save flags */
4538 struct ctdlroom qrbuf;
4539 char roomname[ROOMNAMELEN];
4540 struct CtdlMessage *msg;
4541 char *encoded_message = NULL;
4543 if (is_mailbox != NULL) {
4544 MailboxName(roomname, sizeof roomname, is_mailbox, req_room);
4547 safestrncpy(roomname, req_room, sizeof(roomname));
4550 CtdlLogPrintf(CTDL_DEBUG, "Raw length is %ld\n", (long)raw_length);
4553 encoded_message = malloc((size_t) (((raw_length * 134) / 100) + 4096 ) );
4556 encoded_message = malloc((size_t)(raw_length + 4096));
4559 sprintf(encoded_message, "Content-type: %s\n", content_type);
4562 sprintf(&encoded_message[strlen(encoded_message)],
4563 "Content-transfer-encoding: base64\n\n"
4567 sprintf(&encoded_message[strlen(encoded_message)],
4568 "Content-transfer-encoding: 7bit\n\n"
4574 &encoded_message[strlen(encoded_message)],
4582 &encoded_message[strlen(encoded_message)],
4588 CtdlLogPrintf(CTDL_DEBUG, "Allocating\n");
4589 msg = malloc(sizeof(struct CtdlMessage));
4590 memset(msg, 0, sizeof(struct CtdlMessage));
4591 msg->cm_magic = CTDLMESSAGE_MAGIC;
4592 msg->cm_anon_type = MES_NORMAL;
4593 msg->cm_format_type = 4;
4594 msg->cm_fields['A'] = strdup(CC->user.fullname);
4595 msg->cm_fields['O'] = strdup(req_room);
4596 msg->cm_fields['N'] = strdup(config.c_nodename);
4597 msg->cm_fields['H'] = strdup(config.c_humannode);
4598 msg->cm_flags = flags;
4600 msg->cm_fields['M'] = encoded_message;
4602 /* Create the requested room if we have to. */
4603 if (getroom(&qrbuf, roomname) != 0) {
4604 create_room(roomname,
4605 ( (is_mailbox != NULL) ? 5 : 3 ),
4606 "", 0, 1, 0, VIEW_BBS);
4608 /* If the caller specified this object as unique, delete all
4609 * other objects of this type that are currently in the room.
4612 CtdlLogPrintf(CTDL_DEBUG, "Deleted %d other msgs of this type\n",
4613 CtdlDeleteMessages(roomname, NULL, 0, content_type)
4616 /* Now write the data */
4617 CtdlSubmitMsg(msg, NULL, roomname, 0);
4618 CtdlFreeMessage(msg);
4626 void CtdlGetSysConfigBackend(long msgnum, void *userdata) {
4627 config_msgnum = msgnum;
4631 char *CtdlGetSysConfig(char *sysconfname) {
4632 char hold_rm[ROOMNAMELEN];
4635 struct CtdlMessage *msg;
4638 strcpy(hold_rm, CC->room.QRname);
4639 if (getroom(&CC->room, SYSCONFIGROOM) != 0) {
4640 getroom(&CC->room, hold_rm);
4645 /* We want the last (and probably only) config in this room */
4646 begin_critical_section(S_CONFIG);
4647 config_msgnum = (-1L);
4648 CtdlForEachMessage(MSGS_LAST, 1, NULL, sysconfname, NULL,
4649 CtdlGetSysConfigBackend, NULL);
4650 msgnum = config_msgnum;
4651 end_critical_section(S_CONFIG);
4657 msg = CtdlFetchMessage(msgnum, 1);
4659 conf = strdup(msg->cm_fields['M']);
4660 CtdlFreeMessage(msg);
4667 getroom(&CC->room, hold_rm);
4669 if (conf != NULL) do {
4670 extract_token(buf, conf, 0, '\n', sizeof buf);
4671 strcpy(conf, &conf[strlen(buf)+1]);
4672 } while ( (!IsEmptyStr(conf)) && (!IsEmptyStr(buf)) );
4678 void CtdlPutSysConfig(char *sysconfname, char *sysconfdata) {
4679 CtdlWriteObject(SYSCONFIGROOM, sysconfname, sysconfdata, (strlen(sysconfdata)+1), NULL, 0, 1, 0);
4684 * Determine whether a given Internet address belongs to the current user
4686 int CtdlIsMe(char *addr, int addr_buf_len)
4688 struct recptypes *recp;
4691 recp = validate_recipients(addr, NULL, 0);
4692 if (recp == NULL) return(0);
4694 if (recp->num_local == 0) {
4695 free_recipients(recp);
4699 for (i=0; i<recp->num_local; ++i) {
4700 extract_token(addr, recp->recp_local, i, '|', addr_buf_len);
4701 if (!strcasecmp(addr, CC->user.fullname)) {
4702 free_recipients(recp);
4707 free_recipients(recp);
4713 * Citadel protocol command to do the same
4715 void cmd_isme(char *argbuf) {
4718 if (CtdlAccessCheck(ac_logged_in)) return;
4719 extract_token(addr, argbuf, 0, '|', sizeof addr);
4721 if (CtdlIsMe(addr, sizeof addr)) {
4722 cprintf("%d %s\n", CIT_OK, addr);
4725 cprintf("%d Not you.\n", ERROR + ILLEGAL_VALUE);
4731 /*****************************************************************************/
4732 /* MODULE INITIALIZATION STUFF */
4733 /*****************************************************************************/
4735 CTDL_MODULE_INIT(msgbase)
4737 CtdlRegisterProtoHook(cmd_msgs, "MSGS", "Output a list of messages in the current room");
4738 CtdlRegisterProtoHook(cmd_msg0, "MSG0", "Output a message in plain text format");
4739 CtdlRegisterProtoHook(cmd_msg2, "MSG2", "Output a message in RFC822 format");
4740 CtdlRegisterProtoHook(cmd_msg3, "MSG3", "Output a message in raw format (deprecated)");
4741 CtdlRegisterProtoHook(cmd_msg4, "MSG4", "Output a message in the client's preferred format");
4742 CtdlRegisterProtoHook(cmd_msgp, "MSGP", "Select preferred format for MSG4 output");
4743 CtdlRegisterProtoHook(cmd_opna, "OPNA", "Open an attachment for download");
4744 CtdlRegisterProtoHook(cmd_dlat, "DLAT", "Download an attachment");
4745 CtdlRegisterProtoHook(cmd_ent0, "ENT0", "Enter a message");
4746 CtdlRegisterProtoHook(cmd_dele, "DELE", "Delete a message");
4747 CtdlRegisterProtoHook(cmd_move, "MOVE", "Move or copy a message to another room");
4748 CtdlRegisterProtoHook(cmd_isme, "ISME", "Determine whether an email address belongs to a user");
4750 /* return our Subversion id for the Log */