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"
58 struct addresses_to_be_filed *atbf = NULL;
60 /* This temp file holds the queue of operations for AdjRefCount() */
61 static FILE *arcfp = NULL;
64 * This really belongs in serv_network.c, but I don't know how to export
65 * symbols between modules.
67 struct FilterList *filterlist = NULL;
71 * These are the four-character field headers we use when outputting
72 * messages in Citadel format (as opposed to RFC822 format).
75 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
76 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
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,
111 * This function is self explanatory.
112 * (What can I say, I'm in a weird mood today...)
114 void remove_any_whitespace_to_the_left_or_right_of_at_symbol(char *name)
118 for (i = 0; i < strlen(name); ++i) {
119 if (name[i] == '@') {
120 while (isspace(name[i - 1]) && i > 0) {
121 strcpy(&name[i - 1], &name[i]);
124 while (isspace(name[i + 1])) {
125 strcpy(&name[i + 1], &name[i + 2]);
133 * Aliasing for network mail.
134 * (Error messages have been commented out, because this is a server.)
136 int alias(char *name)
137 { /* process alias and routing info for mail */
140 char aaa[SIZ], bbb[SIZ];
141 char *ignetcfg = NULL;
142 char *ignetmap = NULL;
148 char original_name[256];
149 safestrncpy(original_name, name, sizeof original_name);
152 remove_any_whitespace_to_the_left_or_right_of_at_symbol(name);
153 stripallbut(name, '<', '>');
155 fp = fopen(file_mail_aliases, "r");
157 fp = fopen("/dev/null", "r");
164 while (fgets(aaa, sizeof aaa, fp) != NULL) {
165 while (isspace(name[0]))
166 strcpy(name, &name[1]);
167 aaa[strlen(aaa) - 1] = 0;
169 for (a = 0; a < strlen(aaa); ++a) {
171 strcpy(bbb, &aaa[a + 1]);
175 if (!strcasecmp(name, aaa))
180 /* Hit the Global Address Book */
181 if (CtdlDirectoryLookup(aaa, name, sizeof aaa) == 0) {
185 if (strcasecmp(original_name, name)) {
186 CtdlLogPrintf(CTDL_INFO, "%s is being forwarded to %s\n", original_name, name);
189 /* Change "user @ xxx" to "user" if xxx is an alias for this host */
190 for (a=0; a<strlen(name); ++a) {
191 if (name[a] == '@') {
192 if (CtdlHostAlias(&name[a+1]) == hostalias_localhost) {
194 CtdlLogPrintf(CTDL_INFO, "Changed to <%s>\n", name);
199 /* determine local or remote type, see citadel.h */
200 at = haschar(name, '@');
201 if (at == 0) return(MES_LOCAL); /* no @'s - local address */
202 if (at > 1) return(MES_ERROR); /* >1 @'s - invalid address */
203 remove_any_whitespace_to_the_left_or_right_of_at_symbol(name);
205 /* figure out the delivery mode */
206 extract_token(node, name, 1, '@', sizeof node);
208 /* If there are one or more dots in the nodename, we assume that it
209 * is an FQDN and will attempt SMTP delivery to the Internet.
211 if (haschar(node, '.') > 0) {
212 return(MES_INTERNET);
215 /* Otherwise we look in the IGnet maps for a valid Citadel node.
216 * Try directly-connected nodes first...
218 ignetcfg = CtdlGetSysConfig(IGNETCFG);
219 for (i=0; i<num_tokens(ignetcfg, '\n'); ++i) {
220 extract_token(buf, ignetcfg, i, '\n', sizeof buf);
221 extract_token(testnode, buf, 0, '|', sizeof testnode);
222 if (!strcasecmp(node, testnode)) {
230 * Then try nodes that are two or more hops away.
232 ignetmap = CtdlGetSysConfig(IGNETMAP);
233 for (i=0; i<num_tokens(ignetmap, '\n'); ++i) {
234 extract_token(buf, ignetmap, i, '\n', sizeof buf);
235 extract_token(testnode, buf, 0, '|', sizeof testnode);
236 if (!strcasecmp(node, testnode)) {
243 /* If we get to this point it's an invalid node name */
249 * Back end for the MSGS command: output message number only.
251 void simple_listing(long msgnum, void *userdata)
253 cprintf("%ld\n", msgnum);
259 * Back end for the MSGS command: output header summary.
261 void headers_listing(long msgnum, void *userdata)
263 struct CtdlMessage *msg;
265 msg = CtdlFetchMessage(msgnum, 0);
267 cprintf("%ld|0|||||\n", msgnum);
271 cprintf("%ld|%s|%s|%s|%s|%s|\n",
273 (msg->cm_fields['T'] ? msg->cm_fields['T'] : "0"),
274 (msg->cm_fields['A'] ? msg->cm_fields['A'] : ""),
275 (msg->cm_fields['N'] ? msg->cm_fields['N'] : ""),
276 (msg->cm_fields['F'] ? msg->cm_fields['F'] : ""),
277 (msg->cm_fields['U'] ? msg->cm_fields['U'] : "")
279 CtdlFreeMessage(msg);
284 /* Determine if a given message matches the fields in a message template.
285 * Return 0 for a successful match.
287 int CtdlMsgCmp(struct CtdlMessage *msg, struct CtdlMessage *template) {
290 /* If there aren't any fields in the template, all messages will
293 if (template == NULL) return(0);
295 /* Null messages are bogus. */
296 if (msg == NULL) return(1);
298 for (i='A'; i<='Z'; ++i) {
299 if (template->cm_fields[i] != NULL) {
300 if (msg->cm_fields[i] == NULL) {
303 if (strcasecmp(msg->cm_fields[i],
304 template->cm_fields[i])) return 1;
308 /* All compares succeeded: we have a match! */
315 * Retrieve the "seen" message list for the current room.
317 void CtdlGetSeen(char *buf, int which_set) {
320 /* Learn about the user and room in question */
321 CtdlGetRelationship(&vbuf, &CC->user, &CC->room);
323 if (which_set == ctdlsetseen_seen)
324 safestrncpy(buf, vbuf.v_seen, SIZ);
325 if (which_set == ctdlsetseen_answered)
326 safestrncpy(buf, vbuf.v_answered, SIZ);
332 * Manipulate the "seen msgs" string (or other message set strings)
334 void CtdlSetSeen(long *target_msgnums, int num_target_msgnums,
335 int target_setting, int which_set,
336 struct ctdluser *which_user, struct ctdlroom *which_room) {
337 struct cdbdata *cdbfr;
347 char *is_set; /* actually an array of booleans */
351 char setstr[SIZ], lostr[SIZ], histr[SIZ];
353 /* Don't bother doing *anything* if we were passed a list of zero messages */
354 if (num_target_msgnums < 1) {
358 /* If no room was specified, we go with the current room. */
360 which_room = &CC->room;
363 /* If no user was specified, we go with the current user. */
365 which_user = &CC->user;
368 CtdlLogPrintf(CTDL_DEBUG, "CtdlSetSeen(%d msgs starting with %ld, %s, %d) in <%s>\n",
369 num_target_msgnums, target_msgnums[0],
370 (target_setting ? "SET" : "CLEAR"),
374 /* Learn about the user and room in question */
375 CtdlGetRelationship(&vbuf, which_user, which_room);
377 /* Load the message list */
378 cdbfr = cdb_fetch(CDB_MSGLISTS, &which_room->QRnumber, sizeof(long));
380 msglist = (long *) cdbfr->ptr;
381 cdbfr->ptr = NULL; /* CtdlSetSeen() now owns this memory */
382 num_msgs = cdbfr->len / sizeof(long);
385 return; /* No messages at all? No further action. */
388 is_set = malloc(num_msgs * sizeof(char));
389 memset(is_set, 0, (num_msgs * sizeof(char)) );
391 /* Decide which message set we're manipulating */
393 case ctdlsetseen_seen:
394 safestrncpy(vset, vbuf.v_seen, sizeof vset);
396 case ctdlsetseen_answered:
397 safestrncpy(vset, vbuf.v_answered, sizeof vset);
402 #if 0 /* This is a special diagnostic section. Do not allow it to run during normal operation. */
403 CtdlLogPrintf(CTDL_DEBUG, "There are %d messages in the room.\n", num_msgs);
404 for (i=0; i<num_msgs; ++i) {
405 if ((i > 0) && (msglist[i] <= msglist[i-1])) abort();
407 CtdlLogPrintf(CTDL_DEBUG, "We are twiddling %d of them.\n", num_target_msgnums);
408 for (k=0; k<num_target_msgnums; ++k) {
409 if ((k > 0) && (target_msgnums[k] <= target_msgnums[k-1])) abort();
413 CtdlLogPrintf(CTDL_DEBUG, "before update: %s\n", vset);
415 /* Translate the existing sequence set into an array of booleans */
416 num_sets = num_tokens(vset, ',');
417 for (s=0; s<num_sets; ++s) {
418 extract_token(setstr, vset, s, ',', sizeof setstr);
420 extract_token(lostr, setstr, 0, ':', sizeof lostr);
421 if (num_tokens(setstr, ':') >= 2) {
422 extract_token(histr, setstr, 1, ':', sizeof histr);
425 strcpy(histr, lostr);
428 if (!strcmp(histr, "*")) {
435 for (i = 0; i < num_msgs; ++i) {
436 if ((msglist[i] >= lo) && (msglist[i] <= hi)) {
443 /* Now translate the array of booleans back into a sequence set */
449 for (i=0; i<num_msgs; ++i) {
453 for (k=0; k<num_target_msgnums; ++k) {
454 if (msglist[i] == target_msgnums[k]) {
455 is_seen = target_setting;
459 w = 0; /* set to 1 if we write something to the string */
461 if ((was_seen == 0) && (is_seen == 1)) {
464 else if ((was_seen == 1) && (is_seen == 0)) {
468 if (!IsEmptyStr(vset)) {
472 sprintf(&vset[strlen(vset)], "%ld", hi);
475 sprintf(&vset[strlen(vset)], "%ld:%ld", lo, hi);
478 else if ((is_seen) && (i == num_msgs - 1)) {
480 if (!IsEmptyStr(vset)) {
483 if ((i==0) || (was_seen == 0)) {
484 sprintf(&vset[strlen(vset)], "%ld", msglist[i]);
487 sprintf(&vset[strlen(vset)], "%ld:%ld", lo, msglist[i]);
491 /* If the string is getting too long, truncate it at the beginning; repeat up to 9 times */
492 if (w) for (j=0; j<9; ++j) {
493 if ((strlen(vset) + 20) > sizeof vset) {
494 remove_token(vset, 0, ',');
495 if (which_set == ctdlsetseen_seen) {
497 sprintf(temp, "1:%ld,", atol(vset)-1L);
507 CtdlLogPrintf(CTDL_DEBUG, " after update: %s\n", vset);
509 /* Decide which message set we're manipulating */
511 case ctdlsetseen_seen:
512 safestrncpy(vbuf.v_seen, vset, sizeof vbuf.v_seen);
514 case ctdlsetseen_answered:
515 safestrncpy(vbuf.v_answered, vset, sizeof vbuf.v_answered);
521 CtdlSetRelationship(&vbuf, which_user, which_room);
526 * API function to perform an operation for each qualifying message in the
527 * current room. (Returns the number of messages processed.)
529 int CtdlForEachMessage(int mode, long ref, char *search_string,
531 struct CtdlMessage *compare,
532 void (*CallBack) (long, void *),
538 struct cdbdata *cdbfr;
539 long *msglist = NULL;
541 int num_processed = 0;
544 struct CtdlMessage *msg = NULL;
547 int printed_lastold = 0;
548 int num_search_msgs = 0;
549 long *search_msgs = NULL;
551 int need_to_free_re = 0;
554 if ((content_type) && (!IsEmptyStr(content_type))) {
555 regcomp(&re, content_type, 0);
559 /* Learn about the user and room in question */
560 getuser(&CC->user, CC->curr_user);
561 CtdlGetRelationship(&vbuf, &CC->user, &CC->room);
563 /* Load the message list */
564 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long));
566 msglist = (long *) cdbfr->ptr;
567 num_msgs = cdbfr->len / sizeof(long);
569 if (need_to_free_re) regfree(&re);
570 return 0; /* No messages at all? No further action. */
575 * Now begin the traversal.
577 if (num_msgs > 0) for (a = 0; a < num_msgs; ++a) {
579 /* If the caller is looking for a specific MIME type, filter
580 * out all messages which are not of the type requested.
582 if ((content_type != NULL) && (!IsEmptyStr(content_type))) {
584 /* This call to GetMetaData() sits inside this loop
585 * so that we only do the extra database read per msg
586 * if we need to. Doing the extra read all the time
587 * really kills the server. If we ever need to use
588 * metadata for another search criterion, we need to
589 * move the read somewhere else -- but still be smart
590 * enough to only do the read if the caller has
591 * specified something that will need it.
593 GetMetaData(&smi, msglist[a]);
595 /* if (strcasecmp(smi.meta_content_type, content_type)) { old non-regex way */
596 if (regexec(&re, smi.meta_content_type, 1, &pm, 0) != 0) {
602 num_msgs = sort_msglist(msglist, num_msgs);
604 /* If a template was supplied, filter out the messages which
605 * don't match. (This could induce some delays!)
608 if (compare != NULL) {
609 for (a = 0; a < num_msgs; ++a) {
610 msg = CtdlFetchMessage(msglist[a], 1);
612 if (CtdlMsgCmp(msg, compare)) {
615 CtdlFreeMessage(msg);
621 /* If a search string was specified, get a message list from
622 * the full text index and remove messages which aren't on both
626 * Since the lists are sorted and strictly ascending, and the
627 * output list is guaranteed to be shorter than or equal to the
628 * input list, we overwrite the bottom of the input list. This
629 * eliminates the need to memmove big chunks of the list over and
632 if ( (num_msgs > 0) && (mode == MSGS_SEARCH) && (search_string) ) {
634 /* Call search module via hook mechanism.
635 * NULL means use any search function available.
636 * otherwise replace with a char * to name of search routine
638 CtdlModuleDoSearch(&num_search_msgs, &search_msgs, search_string, "fulltext");
640 if (num_search_msgs > 0) {
644 orig_num_msgs = num_msgs;
646 for (i=0; i<orig_num_msgs; ++i) {
647 for (j=0; j<num_search_msgs; ++j) {
648 if (msglist[i] == search_msgs[j]) {
649 msglist[num_msgs++] = msglist[i];
655 num_msgs = 0; /* No messages qualify */
657 if (search_msgs != NULL) free(search_msgs);
659 /* Now that we've purged messages which don't contain the search
660 * string, treat a MSGS_SEARCH just like a MSGS_ALL from this
667 * Now iterate through the message list, according to the
668 * criteria supplied by the caller.
671 for (a = 0; a < num_msgs; ++a) {
672 thismsg = msglist[a];
673 if (mode == MSGS_ALL) {
677 is_seen = is_msg_in_sequence_set(
678 vbuf.v_seen, thismsg);
679 if (is_seen) lastold = thismsg;
685 || ((mode == MSGS_OLD) && (is_seen))
686 || ((mode == MSGS_NEW) && (!is_seen))
687 || ((mode == MSGS_LAST) && (a >= (num_msgs - ref)))
688 || ((mode == MSGS_FIRST) && (a < ref))
689 || ((mode == MSGS_GT) && (thismsg > ref))
690 || ((mode == MSGS_EQ) && (thismsg == ref))
693 if ((mode == MSGS_NEW) && (CC->user.flags & US_LASTOLD) && (lastold > 0L) && (printed_lastold == 0) && (!is_seen)) {
695 CallBack(lastold, userdata);
699 if (CallBack) CallBack(thismsg, userdata);
703 cdb_free(cdbfr); /* Clean up */
704 if (need_to_free_re) regfree(&re);
705 return num_processed;
711 * cmd_msgs() - get list of message #'s in this room
712 * implements the MSGS server command using CtdlForEachMessage()
714 void cmd_msgs(char *cmdbuf)
723 int with_template = 0;
724 struct CtdlMessage *template = NULL;
725 int with_headers = 0;
726 char search_string[1024];
728 extract_token(which, cmdbuf, 0, '|', sizeof which);
729 cm_ref = extract_int(cmdbuf, 1);
730 extract_token(search_string, cmdbuf, 1, '|', sizeof search_string);
731 with_template = extract_int(cmdbuf, 2);
732 with_headers = extract_int(cmdbuf, 3);
735 if (!strncasecmp(which, "OLD", 3))
737 else if (!strncasecmp(which, "NEW", 3))
739 else if (!strncasecmp(which, "FIRST", 5))
741 else if (!strncasecmp(which, "LAST", 4))
743 else if (!strncasecmp(which, "GT", 2))
745 else if (!strncasecmp(which, "SEARCH", 6))
750 if ((!(CC->logged_in)) && (!(CC->internal_pgm))) {
751 cprintf("%d not logged in\n", ERROR + NOT_LOGGED_IN);
755 if ( (mode == MSGS_SEARCH) && (!config.c_enable_fulltext) ) {
756 cprintf("%d Full text index is not enabled on this server.\n",
757 ERROR + CMD_NOT_SUPPORTED);
763 cprintf("%d Send template then receive message list\n",
765 template = (struct CtdlMessage *)
766 malloc(sizeof(struct CtdlMessage));
767 memset(template, 0, sizeof(struct CtdlMessage));
768 template->cm_magic = CTDLMESSAGE_MAGIC;
769 template->cm_anon_type = MES_NORMAL;
771 while(client_getln(buf, sizeof buf) >= 0 && strcmp(buf,"000")) {
772 extract_token(tfield, buf, 0, '|', sizeof tfield);
773 extract_token(tvalue, buf, 1, '|', sizeof tvalue);
774 for (i='A'; i<='Z'; ++i) if (msgkeys[i]!=NULL) {
775 if (!strcasecmp(tfield, msgkeys[i])) {
776 template->cm_fields[i] =
784 cprintf("%d \n", LISTING_FOLLOWS);
787 CtdlForEachMessage(mode,
788 ( (mode == MSGS_SEARCH) ? 0 : cm_ref ),
789 ( (mode == MSGS_SEARCH) ? search_string : NULL ),
792 (with_headers ? headers_listing : simple_listing),
795 if (template != NULL) CtdlFreeMessage(template);
803 * help_subst() - support routine for help file viewer
805 void help_subst(char *strbuf, char *source, char *dest)
810 while (p = pattern2(strbuf, source), (p >= 0)) {
811 strcpy(workbuf, &strbuf[p + strlen(source)]);
812 strcpy(&strbuf[p], dest);
813 strcat(strbuf, workbuf);
818 void do_help_subst(char *buffer)
822 help_subst(buffer, "^nodename", config.c_nodename);
823 help_subst(buffer, "^humannode", config.c_humannode);
824 help_subst(buffer, "^fqdn", config.c_fqdn);
825 help_subst(buffer, "^username", CC->user.fullname);
826 snprintf(buf2, sizeof buf2, "%ld", CC->user.usernum);
827 help_subst(buffer, "^usernum", buf2);
828 help_subst(buffer, "^sysadm", config.c_sysadm);
829 help_subst(buffer, "^variantname", CITADEL);
830 snprintf(buf2, sizeof buf2, "%d", config.c_maxsessions);
831 help_subst(buffer, "^maxsessions", buf2);
832 help_subst(buffer, "^bbsdir", ctdl_message_dir);
838 * memfmout() - Citadel text formatter and paginator.
839 * Although the original purpose of this routine was to format
840 * text to the reader's screen width, all we're really using it
841 * for here is to format text out to 80 columns before sending it
842 * to the client. The client software may reformat it again.
845 char *mptr, /* where are we going to get our text from? */
846 char subst, /* nonzero if we should do substitutions */
847 char *nl) /* string to terminate lines with */
855 static int width = 80;
860 c = 1; /* c is the current pos */
864 while (ch = *mptr, ((ch != 0) && (strlen(buffer) < 126))) {
866 buffer[strlen(buffer) + 1] = 0;
867 buffer[strlen(buffer)] = ch;
870 if (buffer[0] == '^')
871 do_help_subst(buffer);
873 buffer[strlen(buffer) + 1] = 0;
875 strcpy(buffer, &buffer[1]);
883 if (((ch == 13) || (ch == 10)) && (old != 13) && (old != 10)) {
886 if (((old == 13) || (old == 10)) && (isspace(real))) {
891 if (((strlen(aaa) + c) > (width - 5)) && (strlen(aaa) > (width - 5))) {
892 cprintf("%s%s", nl, aaa);
901 if ((strlen(aaa) + c) > (width - 5)) {
910 if ((ch == 13) || (ch == 10)) {
911 cprintf("%s%s", aaa, nl);
918 cprintf("%s%s", aaa, nl);
924 * Callback function for mime parser that simply lists the part
926 void list_this_part(char *name, char *filename, char *partnum, char *disp,
927 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
928 char *cbid, void *cbuserdata)
932 ma = (struct ma_info *)cbuserdata;
933 if (ma->is_ma == 0) {
934 cprintf("part=%s|%s|%s|%s|%s|%ld|%s\n",
935 name, filename, partnum, disp, cbtype, (long)length, cbid);
940 * Callback function for multipart prefix
942 void list_this_pref(char *name, char *filename, char *partnum, char *disp,
943 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
944 char *cbid, void *cbuserdata)
948 ma = (struct ma_info *)cbuserdata;
949 if (!strcasecmp(cbtype, "multipart/alternative")) {
953 if (ma->is_ma == 0) {
954 cprintf("pref=%s|%s\n", partnum, cbtype);
959 * Callback function for multipart sufffix
961 void list_this_suff(char *name, char *filename, char *partnum, char *disp,
962 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
963 char *cbid, void *cbuserdata)
967 ma = (struct ma_info *)cbuserdata;
968 if (ma->is_ma == 0) {
969 cprintf("suff=%s|%s\n", partnum, cbtype);
971 if (!strcasecmp(cbtype, "multipart/alternative")) {
978 * Callback function for mime parser that opens a section for downloading
980 void mime_download(char *name, char *filename, char *partnum, char *disp,
981 void *content, char *cbtype, char *cbcharset, size_t length,
982 char *encoding, char *cbid, void *cbuserdata)
985 /* Silently go away if there's already a download open. */
986 if (CC->download_fp != NULL)
990 (!IsEmptyStr(partnum) && (!strcasecmp(CC->download_desired_section, partnum)))
991 || (!IsEmptyStr(cbid) && (!strcasecmp(CC->download_desired_section, cbid)))
993 CC->download_fp = tmpfile();
994 if (CC->download_fp == NULL)
997 fwrite(content, length, 1, CC->download_fp);
998 fflush(CC->download_fp);
999 rewind(CC->download_fp);
1001 OpenCmdResult(filename, cbtype);
1008 * Callback function for mime parser that outputs a section all at once.
1009 * We can specify the desired section by part number *or* content-id.
1011 void mime_spew_section(char *name, char *filename, char *partnum, char *disp,
1012 void *content, char *cbtype, char *cbcharset, size_t length,
1013 char *encoding, char *cbid, void *cbuserdata)
1015 int *found_it = (int *)cbuserdata;
1018 (!IsEmptyStr(partnum) && (!strcasecmp(CC->download_desired_section, partnum)))
1019 || (!IsEmptyStr(cbid) && (!strcasecmp(CC->download_desired_section, cbid)))
1022 cprintf("%d %d\n", BINARY_FOLLOWS, (int)length);
1023 client_write(content, length);
1030 * Load a message from disk into memory.
1031 * This is used by CtdlOutputMsg() and other fetch functions.
1033 * NOTE: Caller is responsible for freeing the returned CtdlMessage struct
1034 * using the CtdlMessageFree() function.
1036 struct CtdlMessage *CtdlFetchMessage(long msgnum, int with_body)
1038 struct cdbdata *dmsgtext;
1039 struct CtdlMessage *ret = NULL;
1043 cit_uint8_t field_header;
1045 CtdlLogPrintf(CTDL_DEBUG, "CtdlFetchMessage(%ld, %d)\n", msgnum, with_body);
1047 dmsgtext = cdb_fetch(CDB_MSGMAIN, &msgnum, sizeof(long));
1048 if (dmsgtext == NULL) {
1051 mptr = dmsgtext->ptr;
1052 upper_bound = mptr + dmsgtext->len;
1054 /* Parse the three bytes that begin EVERY message on disk.
1055 * The first is always 0xFF, the on-disk magic number.
1056 * The second is the anonymous/public type byte.
1057 * The third is the format type byte (vari, fixed, or MIME).
1061 CtdlLogPrintf(CTDL_ERR, "Message %ld appears to be corrupted.\n", msgnum);
1065 ret = (struct CtdlMessage *) malloc(sizeof(struct CtdlMessage));
1066 memset(ret, 0, sizeof(struct CtdlMessage));
1068 ret->cm_magic = CTDLMESSAGE_MAGIC;
1069 ret->cm_anon_type = *mptr++; /* Anon type byte */
1070 ret->cm_format_type = *mptr++; /* Format type byte */
1073 * The rest is zero or more arbitrary fields. Load them in.
1074 * We're done when we encounter either a zero-length field or
1075 * have just processed the 'M' (message text) field.
1078 if (mptr >= upper_bound) {
1081 field_header = *mptr++;
1082 ret->cm_fields[field_header] = strdup(mptr);
1084 while (*mptr++ != 0); /* advance to next field */
1086 } while ((mptr < upper_bound) && (field_header != 'M'));
1090 /* Always make sure there's something in the msg text field. If
1091 * it's NULL, the message text is most likely stored separately,
1092 * so go ahead and fetch that. Failing that, just set a dummy
1093 * body so other code doesn't barf.
1095 if ( (ret->cm_fields['M'] == NULL) && (with_body) ) {
1096 dmsgtext = cdb_fetch(CDB_BIGMSGS, &msgnum, sizeof(long));
1097 if (dmsgtext != NULL) {
1098 ret->cm_fields['M'] = strdup(dmsgtext->ptr);
1102 if (ret->cm_fields['M'] == NULL) {
1103 ret->cm_fields['M'] = strdup("\r\n\r\n (no text)\r\n");
1106 /* Perform "before read" hooks (aborting if any return nonzero) */
1107 if (PerformMessageHooks(ret, EVT_BEFOREREAD) > 0) {
1108 CtdlFreeMessage(ret);
1117 * Returns 1 if the supplied pointer points to a valid Citadel message.
1118 * If the pointer is NULL or the magic number check fails, returns 0.
1120 int is_valid_message(struct CtdlMessage *msg) {
1123 if ((msg->cm_magic) != CTDLMESSAGE_MAGIC) {
1124 CtdlLogPrintf(CTDL_WARNING, "is_valid_message() -- self-check failed\n");
1132 * 'Destructor' for struct CtdlMessage
1134 void CtdlFreeMessage(struct CtdlMessage *msg)
1138 if (is_valid_message(msg) == 0)
1140 if (msg != NULL) free (msg);
1144 for (i = 0; i < 256; ++i)
1145 if (msg->cm_fields[i] != NULL) {
1146 free(msg->cm_fields[i]);
1149 msg->cm_magic = 0; /* just in case */
1155 * Pre callback function for multipart/alternative
1157 * NOTE: this differs from the standard behavior for a reason. Normally when
1158 * displaying multipart/alternative you want to show the _last_ usable
1159 * format in the message. Here we show the _first_ one, because it's
1160 * usually text/plain. Since this set of functions is designed for text
1161 * output to non-MIME-aware clients, this is the desired behavior.
1164 void fixed_output_pre(char *name, char *filename, char *partnum, char *disp,
1165 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
1166 char *cbid, void *cbuserdata)
1170 ma = (struct ma_info *)cbuserdata;
1171 CtdlLogPrintf(CTDL_DEBUG, "fixed_output_pre() type=<%s>\n", cbtype);
1172 if (!strcasecmp(cbtype, "multipart/alternative")) {
1176 if (!strcasecmp(cbtype, "message/rfc822")) {
1182 * Post callback function for multipart/alternative
1184 void fixed_output_post(char *name, char *filename, char *partnum, char *disp,
1185 void *content, char *cbtype, char *cbcharset, size_t length,
1186 char *encoding, char *cbid, void *cbuserdata)
1190 ma = (struct ma_info *)cbuserdata;
1191 CtdlLogPrintf(CTDL_DEBUG, "fixed_output_post() type=<%s>\n", cbtype);
1192 if (!strcasecmp(cbtype, "multipart/alternative")) {
1196 if (!strcasecmp(cbtype, "message/rfc822")) {
1202 * Inline callback function for mime parser that wants to display text
1204 void fixed_output(char *name, char *filename, char *partnum, char *disp,
1205 void *content, char *cbtype, char *cbcharset, size_t length,
1206 char *encoding, char *cbid, void *cbuserdata)
1213 ma = (struct ma_info *)cbuserdata;
1215 CtdlLogPrintf(CTDL_DEBUG,
1216 "fixed_output() part %s: %s (%s) (%ld bytes)\n",
1217 partnum, filename, cbtype, (long)length);
1220 * If we're in the middle of a multipart/alternative scope and
1221 * we've already printed another section, skip this one.
1223 if ( (ma->is_ma) && (ma->did_print) ) {
1224 CtdlLogPrintf(CTDL_DEBUG, "Skipping part %s (%s)\n", partnum, cbtype);
1229 if ( (!strcasecmp(cbtype, "text/plain"))
1230 || (IsEmptyStr(cbtype)) ) {
1233 client_write(wptr, length);
1234 if (wptr[length-1] != '\n') {
1241 if (!strcasecmp(cbtype, "text/html")) {
1242 ptr = html_to_ascii(content, length, 80, 0);
1244 client_write(ptr, wlen);
1245 if (ptr[wlen-1] != '\n') {
1252 if (ma->use_fo_hooks) {
1253 if (PerformFixedOutputHooks(cbtype, content, length)) {
1254 /* above function returns nonzero if it handled the part */
1259 if (strncasecmp(cbtype, "multipart/", 10)) {
1260 cprintf("Part %s: %s (%s) (%ld bytes)\r\n",
1261 partnum, filename, cbtype, (long)length);
1267 * The client is elegant and sophisticated and wants to be choosy about
1268 * MIME content types, so figure out which multipart/alternative part
1269 * we're going to send.
1271 * We use a system of weights. When we find a part that matches one of the
1272 * MIME types we've declared as preferential, we can store it in ma->chosen_part
1273 * and then set ma->chosen_pref to that MIME type's position in our preference
1274 * list. If we then hit another match, we only replace the first match if
1275 * the preference value is lower.
1277 void choose_preferred(char *name, char *filename, char *partnum, char *disp,
1278 void *content, char *cbtype, char *cbcharset, size_t length,
1279 char *encoding, char *cbid, void *cbuserdata)
1285 ma = (struct ma_info *)cbuserdata;
1287 // NOTE: REMOVING THIS CONDITIONAL FIXES BUG 220
1288 // http://bugzilla.citadel.org/show_bug.cgi?id=220
1289 // I don't know if there are any side effects! Please TEST TEST TEST
1290 //if (ma->is_ma > 0) {
1292 for (i=0; i<num_tokens(CC->preferred_formats, '|'); ++i) {
1293 extract_token(buf, CC->preferred_formats, i, '|', sizeof buf);
1294 if ( (!strcasecmp(buf, cbtype)) && (!ma->freeze) ) {
1295 if (i < ma->chosen_pref) {
1296 CtdlLogPrintf(CTDL_DEBUG, "Setting chosen part: <%s>\n", partnum);
1297 safestrncpy(ma->chosen_part, partnum, sizeof ma->chosen_part);
1298 ma->chosen_pref = i;
1305 * Now that we've chosen our preferred part, output it.
1307 void output_preferred(char *name, char *filename, char *partnum, char *disp,
1308 void *content, char *cbtype, char *cbcharset, size_t length,
1309 char *encoding, char *cbid, void *cbuserdata)
1313 int add_newline = 0;
1317 ma = (struct ma_info *)cbuserdata;
1319 /* This is not the MIME part you're looking for... */
1320 if (strcasecmp(partnum, ma->chosen_part)) return;
1322 /* If the content-type of this part is in our preferred formats
1323 * list, we can simply output it verbatim.
1325 for (i=0; i<num_tokens(CC->preferred_formats, '|'); ++i) {
1326 extract_token(buf, CC->preferred_formats, i, '|', sizeof buf);
1327 if (!strcasecmp(buf, cbtype)) {
1328 /* Yeah! Go! W00t!! */
1330 text_content = (char *)content;
1331 if (text_content[length-1] != '\n') {
1334 cprintf("Content-type: %s", cbtype);
1335 if (!IsEmptyStr(cbcharset)) {
1336 cprintf("; charset=%s", cbcharset);
1338 cprintf("\nContent-length: %d\n",
1339 (int)(length + add_newline) );
1340 if (!IsEmptyStr(encoding)) {
1341 cprintf("Content-transfer-encoding: %s\n", encoding);
1344 cprintf("Content-transfer-encoding: 7bit\n");
1346 cprintf("X-Citadel-MSG4-Partnum: %s\n", partnum);
1348 client_write(content, length);
1349 if (add_newline) cprintf("\n");
1354 /* No translations required or possible: output as text/plain */
1355 cprintf("Content-type: text/plain\n\n");
1356 fixed_output(name, filename, partnum, disp, content, cbtype, cbcharset,
1357 length, encoding, cbid, cbuserdata);
1362 char desired_section[64];
1369 * Callback function for
1371 void extract_encapsulated_message(char *name, char *filename, char *partnum, char *disp,
1372 void *content, char *cbtype, char *cbcharset, size_t length,
1373 char *encoding, char *cbid, void *cbuserdata)
1375 struct encapmsg *encap;
1377 encap = (struct encapmsg *)cbuserdata;
1379 /* Only proceed if this is the desired section... */
1380 if (!strcasecmp(encap->desired_section, partnum)) {
1381 encap->msglen = length;
1382 encap->msg = malloc(length + 2);
1383 memcpy(encap->msg, content, length);
1393 * Get a message off disk. (returns om_* values found in msgbase.h)
1396 int CtdlOutputMsg(long msg_num, /* message number (local) to fetch */
1397 int mode, /* how would you like that message? */
1398 int headers_only, /* eschew the message body? */
1399 int do_proto, /* do Citadel protocol responses? */
1400 int crlf, /* Use CRLF newlines instead of LF? */
1401 char *section, /* NULL or a message/rfc822 section */
1402 int flags /* should the bessage be exported clean? */
1404 struct CtdlMessage *TheMessage = NULL;
1405 int retcode = om_no_such_msg;
1406 struct encapmsg encap;
1408 CtdlLogPrintf(CTDL_DEBUG, "CtdlOutputMsg() msgnum=%ld, mode=%d, section=%s\n",
1410 (section ? section : "<>")
1413 if ((!(CC->logged_in)) && (!(CC->internal_pgm))) {
1414 if (do_proto) cprintf("%d Not logged in.\n",
1415 ERROR + NOT_LOGGED_IN);
1416 return(om_not_logged_in);
1419 /* FIXME: check message id against msglist for this room */
1422 * Fetch the message from disk. If we're in HEADERS_FAST mode,
1423 * request that we don't even bother loading the body into memory.
1425 if (headers_only == HEADERS_FAST) {
1426 TheMessage = CtdlFetchMessage(msg_num, 0);
1429 TheMessage = CtdlFetchMessage(msg_num, 1);
1432 if (TheMessage == NULL) {
1433 if (do_proto) cprintf("%d Can't locate msg %ld on disk\n",
1434 ERROR + MESSAGE_NOT_FOUND, msg_num);
1435 return(om_no_such_msg);
1438 /* Here is the weird form of this command, to process only an
1439 * encapsulated message/rfc822 section.
1441 if (section) if (!IsEmptyStr(section)) if (strcmp(section, "0")) {
1442 memset(&encap, 0, sizeof encap);
1443 safestrncpy(encap.desired_section, section, sizeof encap.desired_section);
1444 mime_parser(TheMessage->cm_fields['M'],
1446 *extract_encapsulated_message,
1447 NULL, NULL, (void *)&encap, 0
1449 CtdlFreeMessage(TheMessage);
1453 encap.msg[encap.msglen] = 0;
1454 TheMessage = convert_internet_message(encap.msg);
1455 encap.msg = NULL; /* no free() here, TheMessage owns it now */
1457 /* Now we let it fall through to the bottom of this
1458 * function, because TheMessage now contains the
1459 * encapsulated message instead of the top-level
1460 * message. Isn't that neat?
1465 if (do_proto) cprintf("%d msg %ld has no part %s\n",
1466 ERROR + MESSAGE_NOT_FOUND, msg_num, section);
1467 retcode = om_no_such_msg;
1472 /* Ok, output the message now */
1473 retcode = CtdlOutputPreLoadedMsg(TheMessage, mode, headers_only, do_proto, crlf, flags);
1474 CtdlFreeMessage(TheMessage);
1480 char *qp_encode_email_addrs(char *source)
1482 char user[256], node[256], name[256];
1483 const char headerStr[] = "=?UTF-8?Q?";
1487 int need_to_encode = 0;
1493 long nAddrPtrMax = 50;
1498 if (source == NULL) return source;
1499 if (IsEmptyStr(source)) return source;
1501 AddrPtr = malloc (sizeof (long) * nAddrPtrMax);
1502 AddrUtf8 = malloc (sizeof (long) * nAddrPtrMax);
1503 memset(AddrUtf8, 0, sizeof (long) * nAddrPtrMax);
1506 while (!IsEmptyStr (&source[i])) {
1507 if (nColons >= nAddrPtrMax){
1510 ptr = (long *) malloc(sizeof (long) * nAddrPtrMax * 2);
1511 memcpy (ptr, AddrPtr, sizeof (long) * nAddrPtrMax);
1512 free (AddrPtr), AddrPtr = ptr;
1514 ptr = (long *) malloc(sizeof (long) * nAddrPtrMax * 2);
1515 memset(&ptr[nAddrPtrMax], 0,
1516 sizeof (long) * nAddrPtrMax);
1518 memcpy (ptr, AddrUtf8, sizeof (long) * nAddrPtrMax);
1519 free (AddrUtf8), AddrUtf8 = ptr;
1522 if (((unsigned char) source[i] < 32) ||
1523 ((unsigned char) source[i] > 126)) {
1525 AddrUtf8[nColons] = 1;
1527 if (source[i] == '"')
1528 InQuotes = !InQuotes;
1529 if (!InQuotes && source[i] == ',') {
1530 AddrPtr[nColons] = i;
1535 if (need_to_encode == 0) {
1542 EncodedMaxLen = nColons * (sizeof(headerStr) + 3) + SourceLen * 3;
1543 Encoded = (char*) malloc (EncodedMaxLen);
1545 for (i = 0; i < nColons; i++)
1546 source[AddrPtr[i]++] = '\0';
1550 for (i = 0; i < nColons && nPtr != NULL; i++) {
1551 nmax = EncodedMaxLen - (nPtr - Encoded);
1553 process_rfc822_addr(&source[AddrPtr[i]],
1557 /* TODO: libIDN here ! */
1558 if (IsEmptyStr(name)) {
1559 n = snprintf(nPtr, nmax,
1560 (i==0)?"%s@%s" : ",%s@%s",
1564 EncodedName = rfc2047encode(name, strlen(name));
1565 n = snprintf(nPtr, nmax,
1566 (i==0)?"%s <%s@%s>" : ",%s <%s@%s>",
1567 EncodedName, user, node);
1572 n = snprintf(nPtr, nmax,
1573 (i==0)?"%s" : ",%s",
1574 &source[AddrPtr[i]]);
1580 ptr = (char*) malloc(EncodedMaxLen * 2);
1581 memcpy(ptr, Encoded, EncodedMaxLen);
1582 nnPtr = ptr + (nPtr - Encoded), nPtr = nnPtr;
1583 free(Encoded), Encoded = ptr;
1585 i--; /* do it once more with properly lengthened buffer */
1588 for (i = 0; i < nColons; i++)
1589 source[--AddrPtr[i]] = ',';
1596 /* If the last item in a list of recipients was truncated to a partial address,
1597 * remove it completely in order to avoid choking libSieve
1599 void sanitize_truncated_recipient(char *str)
1602 if (num_tokens(str, ',') < 2) return;
1604 int len = strlen(str);
1605 if (len < 900) return;
1606 if (len > 998) str[998] = 0;
1608 char *cptr = strrchr(str, ',');
1611 char *lptr = strchr(cptr, '<');
1612 char *rptr = strchr(cptr, '>');
1614 if ( (lptr) && (rptr) && (rptr > lptr) ) return;
1622 * Get a message off disk. (returns om_* values found in msgbase.h)
1624 int CtdlOutputPreLoadedMsg(
1625 struct CtdlMessage *TheMessage,
1626 int mode, /* how would you like that message? */
1627 int headers_only, /* eschew the message body? */
1628 int do_proto, /* do Citadel protocol responses? */
1629 int crlf, /* Use CRLF newlines instead of LF? */
1630 int flags /* should the bessage be exported clean? */
1634 cit_uint8_t ch, prev_ch;
1636 char display_name[256];
1638 char *nl; /* newline string */
1640 int subject_found = 0;
1643 /* Buffers needed for RFC822 translation. These are all filled
1644 * using functions that are bounds-checked, and therefore we can
1645 * make them substantially smaller than SIZ.
1652 char datestamp[100];
1654 CtdlLogPrintf(CTDL_DEBUG, "CtdlOutputPreLoadedMsg(TheMessage=%s, %d, %d, %d, %d\n",
1655 ((TheMessage == NULL) ? "NULL" : "not null"),
1656 mode, headers_only, do_proto, crlf);
1658 strcpy(mid, "unknown");
1659 nl = (crlf ? "\r\n" : "\n");
1661 if (!is_valid_message(TheMessage)) {
1662 CtdlLogPrintf(CTDL_ERR,
1663 "ERROR: invalid preloaded message for output\n");
1665 return(om_no_such_msg);
1668 /* Are we downloading a MIME component? */
1669 if (mode == MT_DOWNLOAD) {
1670 if (TheMessage->cm_format_type != FMT_RFC822) {
1672 cprintf("%d This is not a MIME message.\n",
1673 ERROR + ILLEGAL_VALUE);
1674 } else if (CC->download_fp != NULL) {
1675 if (do_proto) cprintf(
1676 "%d You already have a download open.\n",
1677 ERROR + RESOURCE_BUSY);
1679 /* Parse the message text component */
1680 mptr = TheMessage->cm_fields['M'];
1681 mime_parser(mptr, NULL, *mime_download, NULL, NULL, NULL, 0);
1682 /* If there's no file open by this time, the requested
1683 * section wasn't found, so print an error
1685 if (CC->download_fp == NULL) {
1686 if (do_proto) cprintf(
1687 "%d Section %s not found.\n",
1688 ERROR + FILE_NOT_FOUND,
1689 CC->download_desired_section);
1692 return((CC->download_fp != NULL) ? om_ok : om_mime_error);
1695 /* MT_SPEW_SECTION is like MT_DOWNLOAD except it outputs the whole MIME part
1696 * in a single server operation instead of opening a download file.
1698 if (mode == MT_SPEW_SECTION) {
1699 if (TheMessage->cm_format_type != FMT_RFC822) {
1701 cprintf("%d This is not a MIME message.\n",
1702 ERROR + ILLEGAL_VALUE);
1704 /* Parse the message text component */
1707 mptr = TheMessage->cm_fields['M'];
1708 mime_parser(mptr, NULL, *mime_spew_section, NULL, NULL, (void *)&found_it, 0);
1709 /* If section wasn't found, print an error
1712 if (do_proto) cprintf(
1713 "%d Section %s not found.\n",
1714 ERROR + FILE_NOT_FOUND,
1715 CC->download_desired_section);
1718 return((CC->download_fp != NULL) ? om_ok : om_mime_error);
1721 /* now for the user-mode message reading loops */
1722 if (do_proto) cprintf("%d msg:\n", LISTING_FOLLOWS);
1724 /* Does the caller want to skip the headers? */
1725 if (headers_only == HEADERS_NONE) goto START_TEXT;
1727 /* Tell the client which format type we're using. */
1728 if ( (mode == MT_CITADEL) && (do_proto) ) {
1729 cprintf("type=%d\n", TheMessage->cm_format_type);
1732 /* nhdr=yes means that we're only displaying headers, no body */
1733 if ( (TheMessage->cm_anon_type == MES_ANONONLY)
1734 && ((mode == MT_CITADEL) || (mode == MT_MIME))
1737 cprintf("nhdr=yes\n");
1740 /* begin header processing loop for Citadel message format */
1742 if ((mode == MT_CITADEL) || (mode == MT_MIME)) {
1744 safestrncpy(display_name, "<unknown>", sizeof display_name);
1745 if (TheMessage->cm_fields['A']) {
1746 strcpy(buf, TheMessage->cm_fields['A']);
1747 if (TheMessage->cm_anon_type == MES_ANONONLY) {
1748 safestrncpy(display_name, "****", sizeof display_name);
1750 else if (TheMessage->cm_anon_type == MES_ANONOPT) {
1751 safestrncpy(display_name, "anonymous", sizeof display_name);
1754 safestrncpy(display_name, buf, sizeof display_name);
1756 if ((is_room_aide())
1757 && ((TheMessage->cm_anon_type == MES_ANONONLY)
1758 || (TheMessage->cm_anon_type == MES_ANONOPT))) {
1759 size_t tmp = strlen(display_name);
1760 snprintf(&display_name[tmp],
1761 sizeof display_name - tmp,
1766 /* Don't show Internet address for users on the
1767 * local Citadel network.
1770 if (TheMessage->cm_fields['N'] != NULL)
1771 if (!IsEmptyStr(TheMessage->cm_fields['N']))
1772 if (haschar(TheMessage->cm_fields['N'], '.') == 0) {
1776 /* Now spew the header fields in the order we like them. */
1777 safestrncpy(allkeys, FORDER, sizeof allkeys);
1778 for (i=0; i<strlen(allkeys); ++i) {
1779 k = (int) allkeys[i];
1781 if ( (TheMessage->cm_fields[k] != NULL)
1782 && (msgkeys[k] != NULL) ) {
1783 if ((k == 'V') || (k == 'R') || (k == 'Y')) {
1784 sanitize_truncated_recipient(TheMessage->cm_fields[k]);
1787 if (do_proto) cprintf("%s=%s\n",
1791 else if ((k == 'F') && (suppress_f)) {
1794 /* Masquerade display name if needed */
1796 if (do_proto) cprintf("%s=%s\n",
1798 TheMessage->cm_fields[k]
1807 /* begin header processing loop for RFC822 transfer format */
1812 strcpy(snode, NODENAME);
1813 if (mode == MT_RFC822) {
1814 for (i = 0; i < 256; ++i) {
1815 if (TheMessage->cm_fields[i]) {
1816 mptr = mpptr = TheMessage->cm_fields[i];
1819 safestrncpy(luser, mptr, sizeof luser);
1820 safestrncpy(suser, mptr, sizeof suser);
1822 else if (i == 'Y') {
1823 if ((flags & QP_EADDR) != 0) {
1824 mptr = qp_encode_email_addrs(mptr);
1826 sanitize_truncated_recipient(mptr);
1827 cprintf("CC: %s%s", mptr, nl);
1829 else if (i == 'P') {
1830 cprintf("Return-Path: %s%s", mptr, nl);
1832 else if (i == 'L') {
1833 cprintf("List-ID: %s%s", mptr, nl);
1835 else if (i == 'V') {
1836 if ((flags & QP_EADDR) != 0)
1837 mptr = qp_encode_email_addrs(mptr);
1838 cprintf("Envelope-To: %s%s", mptr, nl);
1840 else if (i == 'U') {
1841 cprintf("Subject: %s%s", mptr, nl);
1845 safestrncpy(mid, mptr, sizeof mid);
1847 safestrncpy(fuser, mptr, sizeof fuser);
1848 /* else if (i == 'O')
1849 cprintf("X-Citadel-Room: %s%s",
1852 safestrncpy(snode, mptr, sizeof snode);
1855 if (haschar(mptr, '@') == 0)
1857 sanitize_truncated_recipient(mptr);
1858 cprintf("To: %s@%s", mptr, config.c_fqdn);
1863 if ((flags & QP_EADDR) != 0) {
1864 mptr = qp_encode_email_addrs(mptr);
1866 sanitize_truncated_recipient(mptr);
1867 cprintf("To: %s", mptr);
1871 else if (i == 'T') {
1872 datestring(datestamp, sizeof datestamp,
1873 atol(mptr), DATESTRING_RFC822);
1874 cprintf("Date: %s%s", datestamp, nl);
1876 else if (i == 'W') {
1877 cprintf("References: ");
1878 k = num_tokens(mptr, '|');
1879 for (j=0; j<k; ++j) {
1880 extract_token(buf, mptr, j, '|', sizeof buf);
1881 cprintf("<%s>", buf);
1894 if (subject_found == 0) {
1895 cprintf("Subject: (no subject)%s", nl);
1899 for (i=0; !IsEmptyStr(&suser[i]); ++i) {
1900 suser[i] = tolower(suser[i]);
1901 if (!isalnum(suser[i])) suser[i]='_';
1904 if (mode == MT_RFC822) {
1905 if (!strcasecmp(snode, NODENAME)) {
1906 safestrncpy(snode, FQDN, sizeof snode);
1909 /* Construct a fun message id */
1910 cprintf("Message-ID: <%s", mid);/// todo: this possibly breaks threadding mails.
1911 if (strchr(mid, '@')==NULL) {
1912 cprintf("@%s", snode);
1916 if (!is_room_aide() && (TheMessage->cm_anon_type == MES_ANONONLY)) {
1917 cprintf("From: \"----\" <x@x.org>%s", nl);
1919 else if (!is_room_aide() && (TheMessage->cm_anon_type == MES_ANONOPT)) {
1920 cprintf("From: \"anonymous\" <x@x.org>%s", nl);
1922 else if (!IsEmptyStr(fuser)) {
1923 cprintf("From: \"%s\" <%s>%s", luser, fuser, nl);
1926 cprintf("From: \"%s\" <%s@%s>%s", luser, suser, snode, nl);
1929 /* Blank line signifying RFC822 end-of-headers */
1930 if (TheMessage->cm_format_type != FMT_RFC822) {
1935 /* end header processing loop ... at this point, we're in the text */
1937 if (headers_only == HEADERS_FAST) goto DONE;
1938 mptr = TheMessage->cm_fields['M'];
1940 /* Tell the client about the MIME parts in this message */
1941 if (TheMessage->cm_format_type == FMT_RFC822) {
1942 if ( (mode == MT_CITADEL) || (mode == MT_MIME) ) {
1943 memset(&ma, 0, sizeof(struct ma_info));
1944 mime_parser(mptr, NULL,
1945 (do_proto ? *list_this_part : NULL),
1946 (do_proto ? *list_this_pref : NULL),
1947 (do_proto ? *list_this_suff : NULL),
1950 else if (mode == MT_RFC822) { /* unparsed RFC822 dump */
1951 char *start_of_text = NULL;
1952 start_of_text = strstr(mptr, "\n\r\n");
1953 if (start_of_text == NULL) start_of_text = strstr(mptr, "\n\n");
1954 if (start_of_text == NULL) start_of_text = mptr;
1956 start_of_text = strstr(start_of_text, "\n");
1961 int nllen = strlen(nl);
1963 while (ch=*mptr, ch!=0) {
1969 ((headers_only == HEADERS_NONE) && (mptr >= start_of_text))
1970 || ((headers_only == HEADERS_ONLY) && (mptr < start_of_text))
1971 || ((headers_only != HEADERS_NONE) && (headers_only != HEADERS_ONLY))
1974 sprintf(&outbuf[outlen], "%s", nl);
1978 outbuf[outlen++] = ch;
1982 if (flags & ESC_DOT)
1984 if ((prev_ch == 10) && (ch == '.') && ((*(mptr+1) == 13) || (*(mptr+1) == 10)))
1986 outbuf[outlen++] = '.';
1991 if (outlen > 1000) {
1992 client_write(outbuf, outlen);
1997 client_write(outbuf, outlen);
2005 if (headers_only == HEADERS_ONLY) {
2009 /* signify start of msg text */
2010 if ( (mode == MT_CITADEL) || (mode == MT_MIME) ) {
2011 if (do_proto) cprintf("text\n");
2014 /* If the format type on disk is 1 (fixed-format), then we want
2015 * everything to be output completely literally ... regardless of
2016 * what message transfer format is in use.
2018 if (TheMessage->cm_format_type == FMT_FIXED) {
2020 if (mode == MT_MIME) {
2021 cprintf("Content-type: text/plain\n\n");
2025 while (ch = *mptr++, ch > 0) {
2028 if ((ch == 10) || (buflen > 250)) {
2030 cprintf("%s%s", buf, nl);
2039 if (!IsEmptyStr(buf))
2040 cprintf("%s%s", buf, nl);
2043 /* If the message on disk is format 0 (Citadel vari-format), we
2044 * output using the formatter at 80 columns. This is the final output
2045 * form if the transfer format is RFC822, but if the transfer format
2046 * is Citadel proprietary, it'll still work, because the indentation
2047 * for new paragraphs is correct and the client will reformat the
2048 * message to the reader's screen width.
2050 if (TheMessage->cm_format_type == FMT_CITADEL) {
2051 if (mode == MT_MIME) {
2052 cprintf("Content-type: text/x-citadel-variformat\n\n");
2054 memfmout(mptr, 0, nl);
2057 /* If the message on disk is format 4 (MIME), we've gotta hand it
2058 * off to the MIME parser. The client has already been told that
2059 * this message is format 1 (fixed format), so the callback function
2060 * we use will display those parts as-is.
2062 if (TheMessage->cm_format_type == FMT_RFC822) {
2063 memset(&ma, 0, sizeof(struct ma_info));
2065 if (mode == MT_MIME) {
2066 ma.use_fo_hooks = 0;
2067 strcpy(ma.chosen_part, "1");
2068 ma.chosen_pref = 9999;
2069 mime_parser(mptr, NULL,
2070 *choose_preferred, *fixed_output_pre,
2071 *fixed_output_post, (void *)&ma, 0);
2072 mime_parser(mptr, NULL,
2073 *output_preferred, NULL, NULL, (void *)&ma, CC->msg4_dont_decode);
2076 ma.use_fo_hooks = 1;
2077 mime_parser(mptr, NULL,
2078 *fixed_output, *fixed_output_pre,
2079 *fixed_output_post, (void *)&ma, 0);
2084 DONE: /* now we're done */
2085 if (do_proto) cprintf("000\n");
2092 * display a message (mode 0 - Citadel proprietary)
2094 void cmd_msg0(char *cmdbuf)
2097 int headers_only = HEADERS_ALL;
2099 msgid = extract_long(cmdbuf, 0);
2100 headers_only = extract_int(cmdbuf, 1);
2102 CtdlOutputMsg(msgid, MT_CITADEL, headers_only, 1, 0, NULL, 0);
2108 * display a message (mode 2 - RFC822)
2110 void cmd_msg2(char *cmdbuf)
2113 int headers_only = HEADERS_ALL;
2115 msgid = extract_long(cmdbuf, 0);
2116 headers_only = extract_int(cmdbuf, 1);
2118 CtdlOutputMsg(msgid, MT_RFC822, headers_only, 1, 1, NULL, 0);
2124 * display a message (mode 3 - IGnet raw format - internal programs only)
2126 void cmd_msg3(char *cmdbuf)
2129 struct CtdlMessage *msg = NULL;
2132 if (CC->internal_pgm == 0) {
2133 cprintf("%d This command is for internal programs only.\n",
2134 ERROR + HIGHER_ACCESS_REQUIRED);
2138 msgnum = extract_long(cmdbuf, 0);
2139 msg = CtdlFetchMessage(msgnum, 1);
2141 cprintf("%d Message %ld not found.\n",
2142 ERROR + MESSAGE_NOT_FOUND, msgnum);
2146 serialize_message(&smr, msg);
2147 CtdlFreeMessage(msg);
2150 cprintf("%d Unable to serialize message\n",
2151 ERROR + INTERNAL_ERROR);
2155 cprintf("%d %ld\n", BINARY_FOLLOWS, (long)smr.len);
2156 client_write((char *)smr.ser, (int)smr.len);
2163 * Display a message using MIME content types
2165 void cmd_msg4(char *cmdbuf)
2170 msgid = extract_long(cmdbuf, 0);
2171 extract_token(section, cmdbuf, 1, '|', sizeof section);
2172 CtdlOutputMsg(msgid, MT_MIME, 0, 1, 0, (section[0] ? section : NULL) , 0);
2178 * Client tells us its preferred message format(s)
2180 void cmd_msgp(char *cmdbuf)
2182 if (!strcasecmp(cmdbuf, "dont_decode")) {
2183 CC->msg4_dont_decode = 1;
2184 cprintf("%d MSG4 will not pre-decode messages.\n", CIT_OK);
2187 safestrncpy(CC->preferred_formats, cmdbuf, sizeof(CC->preferred_formats));
2188 cprintf("%d Preferred MIME formats have been set.\n", CIT_OK);
2194 * Open a component of a MIME message as a download file
2196 void cmd_opna(char *cmdbuf)
2199 char desired_section[128];
2201 msgid = extract_long(cmdbuf, 0);
2202 extract_token(desired_section, cmdbuf, 1, '|', sizeof desired_section);
2203 safestrncpy(CC->download_desired_section, desired_section,
2204 sizeof CC->download_desired_section);
2205 CtdlOutputMsg(msgid, MT_DOWNLOAD, 0, 1, 1, NULL, 0);
2210 * Open a component of a MIME message and transmit it all at once
2212 void cmd_dlat(char *cmdbuf)
2215 char desired_section[128];
2217 msgid = extract_long(cmdbuf, 0);
2218 extract_token(desired_section, cmdbuf, 1, '|', sizeof desired_section);
2219 safestrncpy(CC->download_desired_section, desired_section,
2220 sizeof CC->download_desired_section);
2221 CtdlOutputMsg(msgid, MT_SPEW_SECTION, 0, 1, 1, NULL, 0);
2226 * Save one or more message pointers into a specified room
2227 * (Returns 0 for success, nonzero for failure)
2228 * roomname may be NULL to use the current room
2230 * Note that the 'supplied_msg' field may be set to NULL, in which case
2231 * the message will be fetched from disk, by number, if we need to perform
2232 * replication checks. This adds an additional database read, so if the
2233 * caller already has the message in memory then it should be supplied. (Obviously
2234 * this mode of operation only works if we're saving a single message.)
2236 int CtdlSaveMsgPointersInRoom(char *roomname, long newmsgidlist[], int num_newmsgs,
2237 int do_repl_check, struct CtdlMessage *supplied_msg)
2240 char hold_rm[ROOMNAMELEN];
2241 struct cdbdata *cdbfr;
2244 long highest_msg = 0L;
2247 struct CtdlMessage *msg = NULL;
2249 long *msgs_to_be_merged = NULL;
2250 int num_msgs_to_be_merged = 0;
2252 CtdlLogPrintf(CTDL_DEBUG,
2253 "CtdlSaveMsgPointersInRoom(room=%s, num_msgs=%d, repl=%d)\n",
2254 roomname, num_newmsgs, do_repl_check);
2256 strcpy(hold_rm, CC->room.QRname);
2259 if (newmsgidlist == NULL) return(ERROR + INTERNAL_ERROR);
2260 if (num_newmsgs < 1) return(ERROR + INTERNAL_ERROR);
2261 if (num_newmsgs > 1) supplied_msg = NULL;
2263 /* Now the regular stuff */
2264 if (lgetroom(&CC->room,
2265 ((roomname != NULL) ? roomname : CC->room.QRname) )
2267 CtdlLogPrintf(CTDL_ERR, "No such room <%s>\n", roomname);
2268 return(ERROR + ROOM_NOT_FOUND);
2272 msgs_to_be_merged = malloc(sizeof(long) * num_newmsgs);
2273 num_msgs_to_be_merged = 0;
2276 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long));
2277 if (cdbfr == NULL) {
2281 msglist = (long *) cdbfr->ptr;
2282 cdbfr->ptr = NULL; /* CtdlSaveMsgPointerInRoom() now owns this memory */
2283 num_msgs = cdbfr->len / sizeof(long);
2288 /* Create a list of msgid's which were supplied by the caller, but do
2289 * not already exist in the target room. It is absolutely taboo to
2290 * have more than one reference to the same message in a room.
2292 for (i=0; i<num_newmsgs; ++i) {
2294 if (num_msgs > 0) for (j=0; j<num_msgs; ++j) {
2295 if (msglist[j] == newmsgidlist[i]) {
2300 msgs_to_be_merged[num_msgs_to_be_merged++] = newmsgidlist[i];
2304 CtdlLogPrintf(9, "%d unique messages to be merged\n", num_msgs_to_be_merged);
2307 * Now merge the new messages
2309 msglist = realloc(msglist, (sizeof(long) * (num_msgs + num_msgs_to_be_merged)) );
2310 if (msglist == NULL) {
2311 CtdlLogPrintf(CTDL_ALERT, "ERROR: can't realloc message list!\n");
2313 memcpy(&msglist[num_msgs], msgs_to_be_merged, (sizeof(long) * num_msgs_to_be_merged) );
2314 num_msgs += num_msgs_to_be_merged;
2316 /* Sort the message list, so all the msgid's are in order */
2317 num_msgs = sort_msglist(msglist, num_msgs);
2319 /* Determine the highest message number */
2320 highest_msg = msglist[num_msgs - 1];
2322 /* Write it back to disk. */
2323 cdb_store(CDB_MSGLISTS, &CC->room.QRnumber, (int)sizeof(long),
2324 msglist, (int)(num_msgs * sizeof(long)));
2326 /* Free up the memory we used. */
2329 /* Update the highest-message pointer and unlock the room. */
2330 CC->room.QRhighest = highest_msg;
2331 lputroom(&CC->room);
2333 /* Perform replication checks if necessary */
2334 if ( (DoesThisRoomNeedEuidIndexing(&CC->room)) && (do_repl_check) ) {
2335 CtdlLogPrintf(CTDL_DEBUG, "CtdlSaveMsgPointerInRoom() doing repl checks\n");
2337 for (i=0; i<num_msgs_to_be_merged; ++i) {
2338 msgid = msgs_to_be_merged[i];
2340 if (supplied_msg != NULL) {
2344 msg = CtdlFetchMessage(msgid, 0);
2348 ReplicationChecks(msg);
2350 /* If the message has an Exclusive ID, index that... */
2351 if (msg->cm_fields['E'] != NULL) {
2352 index_message_by_euid(msg->cm_fields['E'], &CC->room, msgid);
2355 /* Free up the memory we may have allocated */
2356 if (msg != supplied_msg) {
2357 CtdlFreeMessage(msg);
2365 CtdlLogPrintf(CTDL_DEBUG, "CtdlSaveMsgPointerInRoom() skips repl checks\n");
2368 /* Submit this room for processing by hooks */
2369 PerformRoomHooks(&CC->room);
2371 /* Go back to the room we were in before we wandered here... */
2372 getroom(&CC->room, hold_rm);
2374 /* Bump the reference count for all messages which were merged */
2375 for (i=0; i<num_msgs_to_be_merged; ++i) {
2376 AdjRefCount(msgs_to_be_merged[i], +1);
2379 /* Free up memory... */
2380 if (msgs_to_be_merged != NULL) {
2381 free(msgs_to_be_merged);
2384 /* Return success. */
2390 * This is the same as CtdlSaveMsgPointersInRoom() but it only accepts
2393 int CtdlSaveMsgPointerInRoom(char *roomname, long msgid,
2394 int do_repl_check, struct CtdlMessage *supplied_msg)
2396 return CtdlSaveMsgPointersInRoom(roomname, &msgid, 1, do_repl_check, supplied_msg);
2403 * Message base operation to save a new message to the message store
2404 * (returns new message number)
2406 * This is the back end for CtdlSubmitMsg() and should not be directly
2407 * called by server-side modules.
2410 long send_message(struct CtdlMessage *msg) {
2418 /* Get a new message number */
2419 newmsgid = get_new_message_number();
2420 snprintf(msgidbuf, sizeof msgidbuf, "%010ld@%s", newmsgid, config.c_fqdn);
2422 /* Generate an ID if we don't have one already */
2423 if (msg->cm_fields['I']==NULL) {
2424 msg->cm_fields['I'] = strdup(msgidbuf);
2427 /* If the message is big, set its body aside for storage elsewhere */
2428 if (msg->cm_fields['M'] != NULL) {
2429 if (strlen(msg->cm_fields['M']) > BIGMSG) {
2431 holdM = msg->cm_fields['M'];
2432 msg->cm_fields['M'] = NULL;
2436 /* Serialize our data structure for storage in the database */
2437 serialize_message(&smr, msg);
2440 msg->cm_fields['M'] = holdM;
2444 cprintf("%d Unable to serialize message\n",
2445 ERROR + INTERNAL_ERROR);
2449 /* Write our little bundle of joy into the message base */
2450 if (cdb_store(CDB_MSGMAIN, &newmsgid, (int)sizeof(long),
2451 smr.ser, smr.len) < 0) {
2452 CtdlLogPrintf(CTDL_ERR, "Can't store message\n");
2456 cdb_store(CDB_BIGMSGS,
2466 /* Free the memory we used for the serialized message */
2469 /* Return the *local* message ID to the caller
2470 * (even if we're storing an incoming network message)
2478 * Serialize a struct CtdlMessage into the format used on disk and network.
2480 * This function loads up a "struct ser_ret" (defined in server.h) which
2481 * contains the length of the serialized message and a pointer to the
2482 * serialized message in memory. THE LATTER MUST BE FREED BY THE CALLER.
2484 void serialize_message(struct ser_ret *ret, /* return values */
2485 struct CtdlMessage *msg) /* unserialized msg */
2487 size_t wlen, fieldlen;
2489 static char *forder = FORDER;
2492 * Check for valid message format
2494 if (is_valid_message(msg) == 0) {
2495 CtdlLogPrintf(CTDL_ERR, "serialize_message() aborting due to invalid message\n");
2502 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL)
2503 ret->len = ret->len +
2504 strlen(msg->cm_fields[(int)forder[i]]) + 2;
2506 ret->ser = malloc(ret->len);
2507 if (ret->ser == NULL) {
2508 CtdlLogPrintf(CTDL_ERR, "serialize_message() malloc(%ld) failed: %s\n",
2509 (long)ret->len, strerror(errno));
2516 ret->ser[1] = msg->cm_anon_type;
2517 ret->ser[2] = msg->cm_format_type;
2520 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL) {
2521 fieldlen = strlen(msg->cm_fields[(int)forder[i]]);
2522 ret->ser[wlen++] = (char)forder[i];
2523 safestrncpy((char *)&ret->ser[wlen], msg->cm_fields[(int)forder[i]], fieldlen+1);
2524 wlen = wlen + fieldlen + 1;
2526 if (ret->len != wlen) CtdlLogPrintf(CTDL_ERR, "ERROR: len=%ld wlen=%ld\n",
2527 (long)ret->len, (long)wlen);
2534 * Serialize a struct CtdlMessage into the format used on disk and network.
2536 * This function loads up a "struct ser_ret" (defined in server.h) which
2537 * contains the length of the serialized message and a pointer to the
2538 * serialized message in memory. THE LATTER MUST BE FREED BY THE CALLER.
2540 void dump_message(struct CtdlMessage *msg, /* unserialized msg */
2541 long Siz) /* how many chars ? */
2545 static char *forder = FORDER;
2549 * Check for valid message format
2551 if (is_valid_message(msg) == 0) {
2552 CtdlLogPrintf(CTDL_ERR, "dump_message() aborting due to invalid message\n");
2556 buf = (char*) malloc (Siz + 1);
2560 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL) {
2561 snprintf (buf, Siz, " msg[%c] = %s ...\n", (char) forder[i],
2562 msg->cm_fields[(int)forder[i]]);
2563 client_write (buf, strlen(buf));
2572 * Check to see if any messages already exist in the current room which
2573 * carry the same Exclusive ID as this one. If any are found, delete them.
2575 void ReplicationChecks(struct CtdlMessage *msg) {
2576 long old_msgnum = (-1L);
2578 if (DoesThisRoomNeedEuidIndexing(&CC->room) == 0) return;
2580 CtdlLogPrintf(CTDL_DEBUG, "Performing replication checks in <%s>\n",
2583 /* No exclusive id? Don't do anything. */
2584 if (msg == NULL) return;
2585 if (msg->cm_fields['E'] == NULL) return;
2586 if (IsEmptyStr(msg->cm_fields['E'])) return;
2587 /*CtdlLogPrintf(CTDL_DEBUG, "Exclusive ID: <%s> for room <%s>\n",
2588 msg->cm_fields['E'], CC->room.QRname);*/
2590 old_msgnum = locate_message_by_euid(msg->cm_fields['E'], &CC->room);
2591 if (old_msgnum > 0L) {
2592 CtdlLogPrintf(CTDL_DEBUG, "ReplicationChecks() replacing message %ld\n", old_msgnum);
2593 CtdlDeleteMessages(CC->room.QRname, &old_msgnum, 1, "");
2600 * Save a message to disk and submit it into the delivery system.
2602 long CtdlSubmitMsg(struct CtdlMessage *msg, /* message to save */
2603 struct recptypes *recps, /* recipients (if mail) */
2604 char *force, /* force a particular room? */
2605 int flags /* should the bessage be exported clean? */
2607 char submit_filename[128];
2608 char generated_timestamp[32];
2609 char hold_rm[ROOMNAMELEN];
2610 char actual_rm[ROOMNAMELEN];
2611 char force_room[ROOMNAMELEN];
2612 char content_type[SIZ]; /* We have to learn this */
2613 char recipient[SIZ];
2616 struct ctdluser userbuf;
2618 struct MetaData smi;
2619 FILE *network_fp = NULL;
2620 static int seqnum = 1;
2621 struct CtdlMessage *imsg = NULL;
2623 size_t instr_alloc = 0;
2625 char *hold_R, *hold_D;
2626 char *collected_addresses = NULL;
2627 struct addresses_to_be_filed *aptr = NULL;
2628 char *saved_rfc822_version = NULL;
2629 int qualified_for_journaling = 0;
2630 struct CitContext *CCC = CC; /* CachedCitContext - performance boost */
2631 char bounce_to[1024] = "";
2633 CtdlLogPrintf(CTDL_DEBUG, "CtdlSubmitMsg() called\n");
2634 if (is_valid_message(msg) == 0) return(-1); /* self check */
2636 /* If this message has no timestamp, we take the liberty of
2637 * giving it one, right now.
2639 if (msg->cm_fields['T'] == NULL) {
2640 snprintf(generated_timestamp, sizeof generated_timestamp, "%ld", (long)time(NULL));
2641 msg->cm_fields['T'] = strdup(generated_timestamp);
2644 /* If this message has no path, we generate one.
2646 if (msg->cm_fields['P'] == NULL) {
2647 if (msg->cm_fields['A'] != NULL) {
2648 msg->cm_fields['P'] = strdup(msg->cm_fields['A']);
2649 for (a=0; !IsEmptyStr(&msg->cm_fields['P'][a]); ++a) {
2650 if (isspace(msg->cm_fields['P'][a])) {
2651 msg->cm_fields['P'][a] = ' ';
2656 msg->cm_fields['P'] = strdup("unknown");
2660 if (force == NULL) {
2661 strcpy(force_room, "");
2664 strcpy(force_room, force);
2667 /* Learn about what's inside, because it's what's inside that counts */
2668 if (msg->cm_fields['M'] == NULL) {
2669 CtdlLogPrintf(CTDL_ERR, "ERROR: attempt to save message with NULL body\n");
2673 switch (msg->cm_format_type) {
2675 strcpy(content_type, "text/x-citadel-variformat");
2678 strcpy(content_type, "text/plain");
2681 strcpy(content_type, "text/plain");
2682 mptr = bmstrcasestr(msg->cm_fields['M'], "Content-type:");
2685 safestrncpy(content_type, &mptr[13], sizeof content_type);
2686 striplt(content_type);
2687 aptr = content_type;
2688 while (!IsEmptyStr(aptr)) {
2700 /* Goto the correct room */
2701 CtdlLogPrintf(CTDL_DEBUG, "Selected room %s\n", (recps) ? CCC->room.QRname : SENTITEMS);
2702 strcpy(hold_rm, CCC->room.QRname);
2703 strcpy(actual_rm, CCC->room.QRname);
2704 if (recps != NULL) {
2705 strcpy(actual_rm, SENTITEMS);
2708 /* If the user is a twit, move to the twit room for posting */
2710 if (CCC->user.axlevel == 2) {
2711 strcpy(hold_rm, actual_rm);
2712 strcpy(actual_rm, config.c_twitroom);
2713 CtdlLogPrintf(CTDL_DEBUG, "Diverting to twit room\n");
2717 /* ...or if this message is destined for Aide> then go there. */
2718 if (!IsEmptyStr(force_room)) {
2719 strcpy(actual_rm, force_room);
2722 CtdlLogPrintf(CTDL_DEBUG, "Final selection: %s\n", actual_rm);
2723 if (strcasecmp(actual_rm, CCC->room.QRname)) {
2724 /* getroom(&CCC->room, actual_rm); */
2725 usergoto(actual_rm, 0, 1, NULL, NULL);
2729 * If this message has no O (room) field, generate one.
2731 if (msg->cm_fields['O'] == NULL) {
2732 msg->cm_fields['O'] = strdup(CCC->room.QRname);
2735 /* Perform "before save" hooks (aborting if any return nonzero) */
2736 CtdlLogPrintf(CTDL_DEBUG, "Performing before-save hooks\n");
2737 if (PerformMessageHooks(msg, EVT_BEFORESAVE) > 0) return(-3);
2740 * If this message has an Exclusive ID, and the room is replication
2741 * checking enabled, then do replication checks.
2743 if (DoesThisRoomNeedEuidIndexing(&CCC->room)) {
2744 ReplicationChecks(msg);
2747 /* Save it to disk */
2748 CtdlLogPrintf(CTDL_DEBUG, "Saving to disk\n");
2749 newmsgid = send_message(msg);
2750 if (newmsgid <= 0L) return(-5);
2752 /* Write a supplemental message info record. This doesn't have to
2753 * be a critical section because nobody else knows about this message
2756 CtdlLogPrintf(CTDL_DEBUG, "Creating MetaData record\n");
2757 memset(&smi, 0, sizeof(struct MetaData));
2758 smi.meta_msgnum = newmsgid;
2759 smi.meta_refcount = 0;
2760 safestrncpy(smi.meta_content_type, content_type,
2761 sizeof smi.meta_content_type);
2764 * Measure how big this message will be when rendered as RFC822.
2765 * We do this for two reasons:
2766 * 1. We need the RFC822 length for the new metadata record, so the
2767 * POP and IMAP services don't have to calculate message lengths
2768 * while the user is waiting (multiplied by potentially hundreds
2769 * or thousands of messages).
2770 * 2. If journaling is enabled, we will need an RFC822 version of the
2771 * message to attach to the journalized copy.
2773 if (CCC->redirect_buffer != NULL) {
2774 CtdlLogPrintf(CTDL_ALERT, "CCC->redirect_buffer is not NULL during message submission!\n");
2777 CCC->redirect_buffer = malloc(SIZ);
2778 CCC->redirect_len = 0;
2779 CCC->redirect_alloc = SIZ;
2780 CtdlOutputPreLoadedMsg(msg, MT_RFC822, HEADERS_ALL, 0, 1, QP_EADDR);
2781 smi.meta_rfc822_length = CCC->redirect_len;
2782 saved_rfc822_version = CCC->redirect_buffer;
2783 CCC->redirect_buffer = NULL;
2784 CCC->redirect_len = 0;
2785 CCC->redirect_alloc = 0;
2789 /* Now figure out where to store the pointers */
2790 CtdlLogPrintf(CTDL_DEBUG, "Storing pointers\n");
2792 /* If this is being done by the networker delivering a private
2793 * message, we want to BYPASS saving the sender's copy (because there
2794 * is no local sender; it would otherwise go to the Trashcan).
2796 if ((!CCC->internal_pgm) || (recps == NULL)) {
2797 if (CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 1, msg) != 0) {
2798 CtdlLogPrintf(CTDL_ERR, "ERROR saving message pointer!\n");
2799 CtdlSaveMsgPointerInRoom(config.c_aideroom, newmsgid, 0, msg);
2803 /* For internet mail, drop a copy in the outbound queue room */
2805 if (recps->num_internet > 0) {
2806 CtdlSaveMsgPointerInRoom(SMTP_SPOOLOUT_ROOM, newmsgid, 0, msg);
2809 /* If other rooms are specified, drop them there too. */
2811 if (recps->num_room > 0)
2812 for (i=0; i<num_tokens(recps->recp_room, '|'); ++i) {
2813 extract_token(recipient, recps->recp_room, i,
2814 '|', sizeof recipient);
2815 CtdlLogPrintf(CTDL_DEBUG, "Delivering to room <%s>\n", recipient);
2816 CtdlSaveMsgPointerInRoom(recipient, newmsgid, 0, msg);
2819 /* Bump this user's messages posted counter. */
2820 CtdlLogPrintf(CTDL_DEBUG, "Updating user\n");
2821 lgetuser(&CCC->user, CCC->curr_user);
2822 CCC->user.posted = CCC->user.posted + 1;
2823 lputuser(&CCC->user);
2825 /* Decide where bounces need to be delivered */
2826 if (CCC->logged_in) {
2827 snprintf(bounce_to, sizeof bounce_to, "%s@%s", CCC->user.fullname, config.c_nodename);
2830 snprintf(bounce_to, sizeof bounce_to, "%s@%s", msg->cm_fields['A'], msg->cm_fields['N']);
2833 /* If this is private, local mail, make a copy in the
2834 * recipient's mailbox and bump the reference count.
2837 if (recps->num_local > 0)
2838 for (i=0; i<num_tokens(recps->recp_local, '|'); ++i) {
2839 extract_token(recipient, recps->recp_local, i,
2840 '|', sizeof recipient);
2841 CtdlLogPrintf(CTDL_DEBUG, "Delivering private local mail to <%s>\n",
2843 if (getuser(&userbuf, recipient) == 0) {
2844 // Add a flag so the Funambol module knows its mail
2845 msg->cm_fields['W'] = strdup(recipient);
2846 MailboxName(actual_rm, sizeof actual_rm, &userbuf, MAILROOM);
2847 CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 0, msg);
2848 BumpNewMailCounter(userbuf.usernum);
2849 if (!IsEmptyStr(config.c_funambol_host) || !IsEmptyStr(config.c_pager_program)) {
2850 /* Generate a instruction message for the Funambol notification
2851 * server, in the same style as the SMTP queue
2854 instr = malloc(instr_alloc);
2855 snprintf(instr, instr_alloc,
2856 "Content-type: %s\n\nmsgid|%ld\nsubmitted|%ld\n"
2858 SPOOLMIME, newmsgid, (long)time(NULL),
2862 imsg = malloc(sizeof(struct CtdlMessage));
2863 memset(imsg, 0, sizeof(struct CtdlMessage));
2864 imsg->cm_magic = CTDLMESSAGE_MAGIC;
2865 imsg->cm_anon_type = MES_NORMAL;
2866 imsg->cm_format_type = FMT_RFC822;
2867 imsg->cm_fields['A'] = strdup("Citadel");
2868 imsg->cm_fields['J'] = strdup("do not journal");
2869 imsg->cm_fields['M'] = instr; /* imsg owns this memory now */
2870 imsg->cm_fields['W'] = strdup(recipient);
2871 CtdlSubmitMsg(imsg, NULL, FNBL_QUEUE_ROOM, 0);
2872 CtdlFreeMessage(imsg);
2876 CtdlLogPrintf(CTDL_DEBUG, "No user <%s>\n", recipient);
2877 CtdlSaveMsgPointerInRoom(config.c_aideroom,
2882 /* Perform "after save" hooks */
2883 CtdlLogPrintf(CTDL_DEBUG, "Performing after-save hooks\n");
2884 PerformMessageHooks(msg, EVT_AFTERSAVE);
2886 /* For IGnet mail, we have to save a new copy into the spooler for
2887 * each recipient, with the R and D fields set to the recipient and
2888 * destination-node. This has two ugly side effects: all other
2889 * recipients end up being unlisted in this recipient's copy of the
2890 * message, and it has to deliver multiple messages to the same
2891 * node. We'll revisit this again in a year or so when everyone has
2892 * a network spool receiver that can handle the new style messages.
2895 if (recps->num_ignet > 0)
2896 for (i=0; i<num_tokens(recps->recp_ignet, '|'); ++i) {
2897 extract_token(recipient, recps->recp_ignet, i,
2898 '|', sizeof recipient);
2900 hold_R = msg->cm_fields['R'];
2901 hold_D = msg->cm_fields['D'];
2902 msg->cm_fields['R'] = malloc(SIZ);
2903 msg->cm_fields['D'] = malloc(128);
2904 extract_token(msg->cm_fields['R'], recipient, 0, '@', SIZ);
2905 extract_token(msg->cm_fields['D'], recipient, 1, '@', 128);
2907 serialize_message(&smr, msg);
2909 snprintf(submit_filename, sizeof submit_filename,
2910 "%s/netmail.%04lx.%04x.%04x",
2912 (long) getpid(), CCC->cs_pid, ++seqnum);
2913 network_fp = fopen(submit_filename, "wb+");
2914 if (network_fp != NULL) {
2915 fwrite(smr.ser, smr.len, 1, network_fp);
2921 free(msg->cm_fields['R']);
2922 free(msg->cm_fields['D']);
2923 msg->cm_fields['R'] = hold_R;
2924 msg->cm_fields['D'] = hold_D;
2927 /* Go back to the room we started from */
2928 CtdlLogPrintf(CTDL_DEBUG, "Returning to original room %s\n", hold_rm);
2929 if (strcasecmp(hold_rm, CCC->room.QRname))
2930 usergoto(hold_rm, 0, 1, NULL, NULL);
2932 /* For internet mail, generate delivery instructions.
2933 * Yes, this is recursive. Deal with it. Infinite recursion does
2934 * not happen because the delivery instructions message does not
2935 * contain a recipient.
2938 if (recps->num_internet > 0) {
2939 CtdlLogPrintf(CTDL_DEBUG, "Generating delivery instructions\n");
2941 instr = malloc(instr_alloc);
2942 snprintf(instr, instr_alloc,
2943 "Content-type: %s\n\nmsgid|%ld\nsubmitted|%ld\n"
2945 SPOOLMIME, newmsgid, (long)time(NULL),
2949 for (i=0; i<num_tokens(recps->recp_internet, '|'); ++i) {
2950 size_t tmp = strlen(instr);
2951 extract_token(recipient, recps->recp_internet, i, '|', sizeof recipient);
2952 if ((tmp + strlen(recipient) + 32) > instr_alloc) {
2953 instr_alloc = instr_alloc * 2;
2954 instr = realloc(instr, instr_alloc);
2956 snprintf(&instr[tmp], instr_alloc - tmp, "remote|%s|0||\n", recipient);
2959 imsg = malloc(sizeof(struct CtdlMessage));
2960 memset(imsg, 0, sizeof(struct CtdlMessage));
2961 imsg->cm_magic = CTDLMESSAGE_MAGIC;
2962 imsg->cm_anon_type = MES_NORMAL;
2963 imsg->cm_format_type = FMT_RFC822;
2964 imsg->cm_fields['A'] = strdup("Citadel");
2965 imsg->cm_fields['J'] = strdup("do not journal");
2966 imsg->cm_fields['M'] = instr; /* imsg owns this memory now */
2967 CtdlSubmitMsg(imsg, NULL, SMTP_SPOOLOUT_ROOM, QP_EADDR);
2968 CtdlFreeMessage(imsg);
2972 * Any addresses to harvest for someone's address book?
2974 if ( (CCC->logged_in) && (recps != NULL) ) {
2975 collected_addresses = harvest_collected_addresses(msg);
2978 if (collected_addresses != NULL) {
2979 aptr = (struct addresses_to_be_filed *)
2980 malloc(sizeof(struct addresses_to_be_filed));
2981 MailboxName(actual_rm, sizeof actual_rm,
2982 &CCC->user, USERCONTACTSROOM);
2983 aptr->roomname = strdup(actual_rm);
2984 aptr->collected_addresses = collected_addresses;
2985 begin_critical_section(S_ATBF);
2988 end_critical_section(S_ATBF);
2992 * Determine whether this message qualifies for journaling.
2994 if (msg->cm_fields['J'] != NULL) {
2995 qualified_for_journaling = 0;
2998 if (recps == NULL) {
2999 qualified_for_journaling = config.c_journal_pubmsgs;
3001 else if (recps->num_local + recps->num_ignet + recps->num_internet > 0) {
3002 qualified_for_journaling = config.c_journal_email;
3005 qualified_for_journaling = config.c_journal_pubmsgs;
3010 * Do we have to perform journaling? If so, hand off the saved
3011 * RFC822 version will be handed off to the journaler for background
3012 * submit. Otherwise, we have to free the memory ourselves.
3014 if (saved_rfc822_version != NULL) {
3015 if (qualified_for_journaling) {
3016 JournalBackgroundSubmit(msg, saved_rfc822_version, recps);
3019 free(saved_rfc822_version);
3032 * Convenience function for generating small administrative messages.
3034 void quickie_message(char *from, char *fromaddr, char *to, char *room, char *text,
3035 int format_type, char *subject)
3037 struct CtdlMessage *msg;
3038 struct recptypes *recp = NULL;
3040 msg = malloc(sizeof(struct CtdlMessage));
3041 memset(msg, 0, sizeof(struct CtdlMessage));
3042 msg->cm_magic = CTDLMESSAGE_MAGIC;
3043 msg->cm_anon_type = MES_NORMAL;
3044 msg->cm_format_type = format_type;
3047 msg->cm_fields['A'] = strdup(from);
3049 else if (fromaddr != NULL) {
3050 msg->cm_fields['A'] = strdup(fromaddr);
3051 if (strchr(msg->cm_fields['A'], '@')) {
3052 *strchr(msg->cm_fields['A'], '@') = 0;
3056 msg->cm_fields['A'] = strdup("Citadel");
3059 if (fromaddr != NULL) msg->cm_fields['F'] = strdup(fromaddr);
3060 if (room != NULL) msg->cm_fields['O'] = strdup(room);
3061 msg->cm_fields['N'] = strdup(NODENAME);
3063 msg->cm_fields['R'] = strdup(to);
3064 recp = validate_recipients(to, NULL, 0);
3066 if (subject != NULL) {
3067 msg->cm_fields['U'] = strdup(subject);
3069 msg->cm_fields['M'] = strdup(text);
3071 CtdlSubmitMsg(msg, recp, room, 0);
3072 CtdlFreeMessage(msg);
3073 if (recp != NULL) free_recipients(recp);
3079 * Back end function used by CtdlMakeMessage() and similar functions
3081 char *CtdlReadMessageBody(char *terminator, /* token signalling EOT */
3082 size_t maxlen, /* maximum message length */
3083 char *exist, /* if non-null, append to it;
3084 exist is ALWAYS freed */
3085 int crlf, /* CRLF newlines instead of LF */
3086 int sock /* socket handle or 0 for this session's client socket */
3090 size_t message_len = 0;
3091 size_t buffer_len = 0;
3098 if (exist == NULL) {
3105 message_len = strlen(exist);
3106 buffer_len = message_len + 4096;
3107 m = realloc(exist, buffer_len);
3114 /* Do we need to change leading ".." to "." for SMTP escaping? */
3115 if (!strcmp(terminator, ".")) {
3119 /* flush the input if we have nowhere to store it */
3124 /* read in the lines of message text one by one */
3127 if (sock_getln(sock, buf, (sizeof buf - 3)) < 0) finished = 1;
3130 if (client_getln(buf, (sizeof buf - 3)) < 1) finished = 1;
3132 if (!strcmp(buf, terminator)) finished = 1;
3134 strcat(buf, "\r\n");
3140 /* Unescape SMTP-style input of two dots at the beginning of the line */
3142 if (!strncmp(buf, "..", 2)) {
3143 strcpy(buf, &buf[1]);
3147 if ( (!flushing) && (!finished) ) {
3148 /* Measure the line */
3149 linelen = strlen(buf);
3151 /* augment the buffer if we have to */
3152 if ((message_len + linelen) >= buffer_len) {
3153 ptr = realloc(m, (buffer_len * 2) );
3154 if (ptr == NULL) { /* flush if can't allocate */
3157 buffer_len = (buffer_len * 2);
3159 CtdlLogPrintf(CTDL_DEBUG, "buffer_len is now %ld\n", (long)buffer_len);
3163 /* Add the new line to the buffer. NOTE: this loop must avoid
3164 * using functions like strcat() and strlen() because they
3165 * traverse the entire buffer upon every call, and doing that
3166 * for a multi-megabyte message slows it down beyond usability.
3168 strcpy(&m[message_len], buf);
3169 message_len += linelen;
3172 /* if we've hit the max msg length, flush the rest */
3173 if (message_len >= maxlen) flushing = 1;
3175 } while (!finished);
3183 * Build a binary message to be saved on disk.
3184 * (NOTE: if you supply 'preformatted_text', the buffer you give it
3185 * will become part of the message. This means you are no longer
3186 * responsible for managing that memory -- it will be freed along with
3187 * the rest of the fields when CtdlFreeMessage() is called.)
3190 struct CtdlMessage *CtdlMakeMessage(
3191 struct ctdluser *author, /* author's user structure */
3192 char *recipient, /* NULL if it's not mail */
3193 char *recp_cc, /* NULL if it's not mail */
3194 char *room, /* room where it's going */
3195 int type, /* see MES_ types in header file */
3196 int format_type, /* variformat, plain text, MIME... */
3197 char *fake_name, /* who we're masquerading as */
3198 char *my_email, /* which of my email addresses to use (empty is ok) */
3199 char *subject, /* Subject (optional) */
3200 char *supplied_euid, /* ...or NULL if this is irrelevant */
3201 char *preformatted_text, /* ...or NULL to read text from client */
3202 char *references /* Thread references */
3204 char dest_node[256];
3206 struct CtdlMessage *msg;
3208 msg = malloc(sizeof(struct CtdlMessage));
3209 memset(msg, 0, sizeof(struct CtdlMessage));
3210 msg->cm_magic = CTDLMESSAGE_MAGIC;
3211 msg->cm_anon_type = type;
3212 msg->cm_format_type = format_type;
3214 /* Don't confuse the poor folks if it's not routed mail. */
3215 strcpy(dest_node, "");
3220 /* Path or Return-Path */
3221 if (my_email == NULL) my_email = "";
3223 if (!IsEmptyStr(my_email)) {
3224 msg->cm_fields['P'] = strdup(my_email);
3227 snprintf(buf, sizeof buf, "%s", author->fullname);
3228 msg->cm_fields['P'] = strdup(buf);
3230 convert_spaces_to_underscores(msg->cm_fields['P']);
3232 snprintf(buf, sizeof buf, "%ld", (long)time(NULL)); /* timestamp */
3233 msg->cm_fields['T'] = strdup(buf);
3235 if (fake_name[0]) /* author */
3236 msg->cm_fields['A'] = strdup(fake_name);
3238 msg->cm_fields['A'] = strdup(author->fullname);
3240 if (CC->room.QRflags & QR_MAILBOX) { /* room */
3241 msg->cm_fields['O'] = strdup(&CC->room.QRname[11]);
3244 msg->cm_fields['O'] = strdup(CC->room.QRname);
3247 msg->cm_fields['N'] = strdup(NODENAME); /* nodename */
3248 msg->cm_fields['H'] = strdup(HUMANNODE); /* hnodename */
3250 if (recipient[0] != 0) {
3251 msg->cm_fields['R'] = strdup(recipient);
3253 if (recp_cc[0] != 0) {
3254 msg->cm_fields['Y'] = strdup(recp_cc);
3256 if (dest_node[0] != 0) {
3257 msg->cm_fields['D'] = strdup(dest_node);
3260 if (!IsEmptyStr(my_email)) {
3261 msg->cm_fields['F'] = strdup(my_email);
3263 else if ( (author == &CC->user) && (!IsEmptyStr(CC->cs_inet_email)) ) {
3264 msg->cm_fields['F'] = strdup(CC->cs_inet_email);
3267 if (subject != NULL) {
3270 length = strlen(subject);
3276 while ((subject[i] != '\0') &&
3277 (IsAscii = isascii(subject[i]) != 0 ))
3280 msg->cm_fields['U'] = strdup(subject);
3281 else /* ok, we've got utf8 in the string. */
3283 msg->cm_fields['U'] = rfc2047encode(subject, length);
3289 if (supplied_euid != NULL) {
3290 msg->cm_fields['E'] = strdup(supplied_euid);
3293 if (references != NULL) {
3294 if (!IsEmptyStr(references)) {
3295 msg->cm_fields['W'] = strdup(references);
3299 if (preformatted_text != NULL) {
3300 msg->cm_fields['M'] = preformatted_text;
3303 msg->cm_fields['M'] = CtdlReadMessageBody("000", config.c_maxmsglen, NULL, 0, 0);
3311 * Check to see whether we have permission to post a message in the current
3312 * room. Returns a *CITADEL ERROR CODE* and puts a message in errmsgbuf, or
3313 * returns 0 on success.
3315 int CtdlDoIHavePermissionToPostInThisRoom(char *errmsgbuf,
3317 const char* RemoteIdentifier,
3321 if (!(CC->logged_in) &&
3322 (PostPublic == POST_LOGGED_IN)) {
3323 snprintf(errmsgbuf, n, "Not logged in.");
3324 return (ERROR + NOT_LOGGED_IN);
3326 else if (PostPublic == CHECK_EXISTANCE) {
3327 return (0); // We're Evaling whether a recipient exists
3329 else if (!(CC->logged_in)) {
3331 if ((CC->room.QRflags & QR_READONLY)) {
3332 snprintf(errmsgbuf, n, "Not logged in.");
3333 return (ERROR + NOT_LOGGED_IN);
3335 if (CC->room.QRflags2 & QR2_MODERATED) {
3336 snprintf(errmsgbuf, n, "Not logged in Moderation feature not yet implemented!");
3337 return (ERROR + NOT_LOGGED_IN);
3339 if ((PostPublic!=POST_LMTP) &&(CC->room.QRflags2 & QR2_SMTP_PUBLIC) == 0) {
3344 if (RemoteIdentifier == NULL)
3346 snprintf(errmsgbuf, n, "Need sender to permit access.");
3347 return (ERROR + USERNAME_REQUIRED);
3350 assoc_file_name(filename, sizeof filename, &CC->room, ctdl_netcfg_dir);
3351 begin_critical_section(S_NETCONFIGS);
3352 if (!read_spoolcontrol_file(&sc, filename))
3354 end_critical_section(S_NETCONFIGS);
3355 snprintf(errmsgbuf, n,
3356 "This mailing list only accepts posts from subscribers.");
3357 return (ERROR + NO_SUCH_USER);
3359 end_critical_section(S_NETCONFIGS);
3360 found = is_recipient (sc, RemoteIdentifier);
3361 free_spoolcontrol_struct(&sc);
3366 snprintf(errmsgbuf, n,
3367 "This mailing list only accepts posts from subscribers.");
3368 return (ERROR + NO_SUCH_USER);
3375 if ((CC->user.axlevel < 2)
3376 && ((CC->room.QRflags & QR_MAILBOX) == 0)) {
3377 snprintf(errmsgbuf, n, "Need to be validated to enter "
3378 "(except in %s> to sysop)", MAILROOM);
3379 return (ERROR + HIGHER_ACCESS_REQUIRED);
3382 CtdlRoomAccess(&CC->room, &CC->user, &ra, NULL);
3383 if (!(ra & UA_POSTALLOWED)) {
3384 snprintf(errmsgbuf, n, "Higher access is required to post in this room.");
3385 return (ERROR + HIGHER_ACCESS_REQUIRED);
3388 strcpy(errmsgbuf, "Ok");
3394 * Check to see if the specified user has Internet mail permission
3395 * (returns nonzero if permission is granted)
3397 int CtdlCheckInternetMailPermission(struct ctdluser *who) {
3399 /* Do not allow twits to send Internet mail */
3400 if (who->axlevel <= 2) return(0);
3402 /* Globally enabled? */
3403 if (config.c_restrict == 0) return(1);
3405 /* User flagged ok? */
3406 if (who->flags & US_INTERNET) return(2);
3408 /* Aide level access? */
3409 if (who->axlevel >= 6) return(3);
3411 /* No mail for you! */
3417 * Validate recipients, count delivery types and errors, and handle aliasing
3418 * FIXME check for dupes!!!!!
3420 * Returns 0 if all addresses are ok, ret->num_error = -1 if no addresses
3421 * were specified, or the number of addresses found invalid.
3423 * Caller needs to free the result using free_recipients()
3425 struct recptypes *validate_recipients(char *supplied_recipients,
3426 const char *RemoteIdentifier,
3428 struct recptypes *ret;
3429 char *recipients = NULL;
3430 char this_recp[256];
3431 char this_recp_cooked[256];
3437 struct ctdluser tempUS;
3438 struct ctdlroom tempQR;
3439 struct ctdlroom tempQR2;
3445 ret = (struct recptypes *) malloc(sizeof(struct recptypes));
3446 if (ret == NULL) return(NULL);
3448 /* Set all strings to null and numeric values to zero */
3449 memset(ret, 0, sizeof(struct recptypes));
3451 if (supplied_recipients == NULL) {
3452 recipients = strdup("");
3455 recipients = strdup(supplied_recipients);
3458 /* Allocate some memory. Yes, this allocates 500% more memory than we will
3459 * actually need, but it's healthier for the heap than doing lots of tiny
3460 * realloc() calls instead.
3463 ret->errormsg = malloc(strlen(recipients) + 1024);
3464 ret->recp_local = malloc(strlen(recipients) + 1024);
3465 ret->recp_internet = malloc(strlen(recipients) + 1024);
3466 ret->recp_ignet = malloc(strlen(recipients) + 1024);
3467 ret->recp_room = malloc(strlen(recipients) + 1024);
3468 ret->display_recp = malloc(strlen(recipients) + 1024);
3470 ret->errormsg[0] = 0;
3471 ret->recp_local[0] = 0;
3472 ret->recp_internet[0] = 0;
3473 ret->recp_ignet[0] = 0;
3474 ret->recp_room[0] = 0;
3475 ret->display_recp[0] = 0;
3477 ret->recptypes_magic = RECPTYPES_MAGIC;
3479 /* Change all valid separator characters to commas */
3480 for (i=0; !IsEmptyStr(&recipients[i]); ++i) {
3481 if ((recipients[i] == ';') || (recipients[i] == '|')) {
3482 recipients[i] = ',';
3486 /* Now start extracting recipients... */
3488 while (!IsEmptyStr(recipients)) {
3490 for (i=0; i<=strlen(recipients); ++i) {
3491 if (recipients[i] == '\"') in_quotes = 1 - in_quotes;
3492 if ( ( (recipients[i] == ',') && (!in_quotes) ) || (recipients[i] == 0) ) {
3493 safestrncpy(this_recp, recipients, i+1);
3495 if (recipients[i] == ',') {
3496 strcpy(recipients, &recipients[i+1]);
3499 strcpy(recipients, "");
3506 if (IsEmptyStr(this_recp))
3508 CtdlLogPrintf(CTDL_DEBUG, "Evaluating recipient #%d: %s\n", num_recps, this_recp);
3510 mailtype = alias(this_recp);
3511 mailtype = alias(this_recp);
3512 mailtype = alias(this_recp);
3514 for (j=0; !IsEmptyStr(&this_recp[j]); ++j) {
3515 if (this_recp[j]=='_') {
3516 this_recp_cooked[j] = ' ';
3519 this_recp_cooked[j] = this_recp[j];
3522 this_recp_cooked[j] = '\0';
3527 if (!strcasecmp(this_recp, "sysop")) {
3529 strcpy(this_recp, config.c_aideroom);
3530 if (!IsEmptyStr(ret->recp_room)) {
3531 strcat(ret->recp_room, "|");
3533 strcat(ret->recp_room, this_recp);
3535 else if ( (!strncasecmp(this_recp, "room_", 5))
3536 && (!getroom(&tempQR, &this_recp_cooked[5])) ) {
3538 /* Save room so we can restore it later */
3542 /* Check permissions to send mail to this room */
3543 err = CtdlDoIHavePermissionToPostInThisRoom(errmsg,
3555 if (!IsEmptyStr(ret->recp_room)) {
3556 strcat(ret->recp_room, "|");
3558 strcat(ret->recp_room, &this_recp_cooked[5]);
3561 /* Restore room in case something needs it */
3565 else if (getuser(&tempUS, this_recp) == 0) {
3567 strcpy(this_recp, tempUS.fullname);
3568 if (!IsEmptyStr(ret->recp_local)) {
3569 strcat(ret->recp_local, "|");
3571 strcat(ret->recp_local, this_recp);
3573 else if (getuser(&tempUS, this_recp_cooked) == 0) {
3575 strcpy(this_recp, tempUS.fullname);
3576 if (!IsEmptyStr(ret->recp_local)) {
3577 strcat(ret->recp_local, "|");
3579 strcat(ret->recp_local, this_recp);
3587 /* Yes, you're reading this correctly: if the target
3588 * domain points back to the local system or an attached
3589 * Citadel directory, the address is invalid. That's
3590 * because if the address were valid, we would have
3591 * already translated it to a local address by now.
3593 if (IsDirectory(this_recp, 0)) {
3598 ++ret->num_internet;
3599 if (!IsEmptyStr(ret->recp_internet)) {
3600 strcat(ret->recp_internet, "|");
3602 strcat(ret->recp_internet, this_recp);
3607 if (!IsEmptyStr(ret->recp_ignet)) {
3608 strcat(ret->recp_ignet, "|");
3610 strcat(ret->recp_ignet, this_recp);
3618 if (IsEmptyStr(errmsg)) {
3619 snprintf(append, sizeof append, "Invalid recipient: %s", this_recp);
3622 snprintf(append, sizeof append, "%s", errmsg);
3624 if ( (strlen(ret->errormsg) + strlen(append) + 3) < SIZ) {
3625 if (!IsEmptyStr(ret->errormsg)) {
3626 strcat(ret->errormsg, "; ");
3628 strcat(ret->errormsg, append);
3632 if (IsEmptyStr(ret->display_recp)) {
3633 strcpy(append, this_recp);
3636 snprintf(append, sizeof append, ", %s", this_recp);
3638 if ( (strlen(ret->display_recp)+strlen(append)) < SIZ) {
3639 strcat(ret->display_recp, append);
3644 if ((ret->num_local + ret->num_internet + ret->num_ignet +
3645 ret->num_room + ret->num_error) == 0) {
3646 ret->num_error = (-1);
3647 strcpy(ret->errormsg, "No recipients specified.");
3650 CtdlLogPrintf(CTDL_DEBUG, "validate_recipients()\n");
3651 CtdlLogPrintf(CTDL_DEBUG, " local: %d <%s>\n", ret->num_local, ret->recp_local);
3652 CtdlLogPrintf(CTDL_DEBUG, " room: %d <%s>\n", ret->num_room, ret->recp_room);
3653 CtdlLogPrintf(CTDL_DEBUG, " inet: %d <%s>\n", ret->num_internet, ret->recp_internet);
3654 CtdlLogPrintf(CTDL_DEBUG, " ignet: %d <%s>\n", ret->num_ignet, ret->recp_ignet);
3655 CtdlLogPrintf(CTDL_DEBUG, " error: %d <%s>\n", ret->num_error, ret->errormsg);
3663 * Destructor for struct recptypes
3665 void free_recipients(struct recptypes *valid) {
3667 if (valid == NULL) {
3671 if (valid->recptypes_magic != RECPTYPES_MAGIC) {
3672 CtdlLogPrintf(CTDL_EMERG, "Attempt to call free_recipients() on some other data type!\n");
3676 if (valid->errormsg != NULL) free(valid->errormsg);
3677 if (valid->recp_local != NULL) free(valid->recp_local);
3678 if (valid->recp_internet != NULL) free(valid->recp_internet);
3679 if (valid->recp_ignet != NULL) free(valid->recp_ignet);
3680 if (valid->recp_room != NULL) free(valid->recp_room);
3681 if (valid->display_recp != NULL) free(valid->display_recp);
3688 * message entry - mode 0 (normal)
3690 void cmd_ent0(char *entargs)
3696 char supplied_euid[128];
3698 int format_type = 0;
3699 char newusername[256];
3700 char newuseremail[256];
3701 struct CtdlMessage *msg;
3705 struct recptypes *valid = NULL;
3706 struct recptypes *valid_to = NULL;
3707 struct recptypes *valid_cc = NULL;
3708 struct recptypes *valid_bcc = NULL;
3710 int subject_required = 0;
3715 int newuseremail_ok = 0;
3716 char references[SIZ];
3721 post = extract_int(entargs, 0);
3722 extract_token(recp, entargs, 1, '|', sizeof recp);
3723 anon_flag = extract_int(entargs, 2);
3724 format_type = extract_int(entargs, 3);
3725 extract_token(subject, entargs, 4, '|', sizeof subject);
3726 extract_token(newusername, entargs, 5, '|', sizeof newusername);
3727 do_confirm = extract_int(entargs, 6);
3728 extract_token(cc, entargs, 7, '|', sizeof cc);
3729 extract_token(bcc, entargs, 8, '|', sizeof bcc);
3730 switch(CC->room.QRdefaultview) {
3733 extract_token(supplied_euid, entargs, 9, '|', sizeof supplied_euid);
3736 supplied_euid[0] = 0;
3739 extract_token(newuseremail, entargs, 10, '|', sizeof newuseremail);
3740 extract_token(references, entargs, 11, '|', sizeof references);
3741 for (ptr=references; *ptr != 0; ++ptr) {
3742 if (*ptr == '!') *ptr = '|';
3745 /* first check to make sure the request is valid. */
3747 err = CtdlDoIHavePermissionToPostInThisRoom(errmsg, sizeof errmsg, NULL, POST_LOGGED_IN);
3750 cprintf("%d %s\n", err, errmsg);
3754 /* Check some other permission type things. */
3756 if (IsEmptyStr(newusername)) {
3757 strcpy(newusername, CC->user.fullname);
3759 if ( (CC->user.axlevel < 6)
3760 && (strcasecmp(newusername, CC->user.fullname))
3761 && (strcasecmp(newusername, CC->cs_inet_fn))
3763 cprintf("%d You don't have permission to author messages as '%s'.\n",
3764 ERROR + HIGHER_ACCESS_REQUIRED,
3771 if (IsEmptyStr(newuseremail)) {
3772 newuseremail_ok = 1;
3775 if (!IsEmptyStr(newuseremail)) {
3776 if (!strcasecmp(newuseremail, CC->cs_inet_email)) {
3777 newuseremail_ok = 1;
3779 else if (!IsEmptyStr(CC->cs_inet_other_emails)) {
3780 j = num_tokens(CC->cs_inet_other_emails, '|');
3781 for (i=0; i<j; ++i) {
3782 extract_token(buf, CC->cs_inet_other_emails, i, '|', sizeof buf);
3783 if (!strcasecmp(newuseremail, buf)) {
3784 newuseremail_ok = 1;
3790 if (!newuseremail_ok) {
3791 cprintf("%d You don't have permission to author messages as '%s'.\n",
3792 ERROR + HIGHER_ACCESS_REQUIRED,
3798 CC->cs_flags |= CS_POSTING;
3800 /* In mailbox rooms we have to behave a little differently --
3801 * make sure the user has specified at least one recipient. Then
3802 * validate the recipient(s). We do this for the Mail> room, as
3803 * well as any room which has the "Mailbox" view set.
3806 if ( ( (CC->room.QRflags & QR_MAILBOX) && (!strcasecmp(&CC->room.QRname[11], MAILROOM)) )
3807 || ( (CC->room.QRflags & QR_MAILBOX) && (CC->curr_view == VIEW_MAILBOX) )
3809 if (CC->user.axlevel < 2) {
3810 strcpy(recp, "sysop");
3815 valid_to = validate_recipients(recp, NULL, 0);
3816 if (valid_to->num_error > 0) {
3817 cprintf("%d %s\n", ERROR + NO_SUCH_USER, valid_to->errormsg);
3818 free_recipients(valid_to);
3822 valid_cc = validate_recipients(cc, NULL, 0);
3823 if (valid_cc->num_error > 0) {
3824 cprintf("%d %s\n", ERROR + NO_SUCH_USER, valid_cc->errormsg);
3825 free_recipients(valid_to);
3826 free_recipients(valid_cc);
3830 valid_bcc = validate_recipients(bcc, NULL, 0);
3831 if (valid_bcc->num_error > 0) {
3832 cprintf("%d %s\n", ERROR + NO_SUCH_USER, valid_bcc->errormsg);
3833 free_recipients(valid_to);
3834 free_recipients(valid_cc);
3835 free_recipients(valid_bcc);
3839 /* Recipient required, but none were specified */
3840 if ( (valid_to->num_error < 0) && (valid_cc->num_error < 0) && (valid_bcc->num_error < 0) ) {
3841 free_recipients(valid_to);
3842 free_recipients(valid_cc);
3843 free_recipients(valid_bcc);
3844 cprintf("%d At least one recipient is required.\n", ERROR + NO_SUCH_USER);
3848 if (valid_to->num_internet + valid_cc->num_internet + valid_bcc->num_internet > 0) {
3849 if (CtdlCheckInternetMailPermission(&CC->user)==0) {
3850 cprintf("%d You do not have permission "
3851 "to send Internet mail.\n",
3852 ERROR + HIGHER_ACCESS_REQUIRED);
3853 free_recipients(valid_to);
3854 free_recipients(valid_cc);
3855 free_recipients(valid_bcc);
3860 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)
3861 && (CC->user.axlevel < 4) ) {
3862 cprintf("%d Higher access required for network mail.\n",
3863 ERROR + HIGHER_ACCESS_REQUIRED);
3864 free_recipients(valid_to);
3865 free_recipients(valid_cc);
3866 free_recipients(valid_bcc);
3870 if ((RESTRICT_INTERNET == 1)
3871 && (valid_to->num_internet + valid_cc->num_internet + valid_bcc->num_internet > 0)
3872 && ((CC->user.flags & US_INTERNET) == 0)
3873 && (!CC->internal_pgm)) {
3874 cprintf("%d You don't have access to Internet mail.\n",
3875 ERROR + HIGHER_ACCESS_REQUIRED);
3876 free_recipients(valid_to);
3877 free_recipients(valid_cc);
3878 free_recipients(valid_bcc);
3884 /* Is this a room which has anonymous-only or anonymous-option? */
3885 anonymous = MES_NORMAL;
3886 if (CC->room.QRflags & QR_ANONONLY) {
3887 anonymous = MES_ANONONLY;
3889 if (CC->room.QRflags & QR_ANONOPT) {
3890 if (anon_flag == 1) { /* only if the user requested it */
3891 anonymous = MES_ANONOPT;
3895 if ((CC->room.QRflags & QR_MAILBOX) == 0) {
3899 /* Recommend to the client that the use of a message subject is
3900 * strongly recommended in this room, if either the SUBJECTREQ flag
3901 * is set, or if there is one or more Internet email recipients.
3903 if (CC->room.QRflags2 & QR2_SUBJECTREQ) subject_required = 1;
3904 if ((valid_to) && (valid_to->num_internet > 0)) subject_required = 1;
3905 if ((valid_cc) && (valid_cc->num_internet > 0)) subject_required = 1;
3906 if ((valid_bcc) && (valid_bcc->num_internet > 0)) subject_required = 1;
3908 /* If we're only checking the validity of the request, return
3909 * success without creating the message.
3912 cprintf("%d %s|%d\n", CIT_OK,
3913 ((valid_to != NULL) ? valid_to->display_recp : ""),
3915 free_recipients(valid_to);
3916 free_recipients(valid_cc);
3917 free_recipients(valid_bcc);
3921 /* We don't need these anymore because we'll do it differently below */
3922 free_recipients(valid_to);
3923 free_recipients(valid_cc);
3924 free_recipients(valid_bcc);
3926 /* Read in the message from the client. */
3928 cprintf("%d send message\n", START_CHAT_MODE);
3930 cprintf("%d send message\n", SEND_LISTING);
3933 msg = CtdlMakeMessage(&CC->user, recp, cc,
3934 CC->room.QRname, anonymous, format_type,
3935 newusername, newuseremail, subject,
3936 ((!IsEmptyStr(supplied_euid)) ? supplied_euid : NULL),
3939 /* Put together one big recipients struct containing to/cc/bcc all in
3940 * one. This is for the envelope.
3942 char *all_recps = malloc(SIZ * 3);
3943 strcpy(all_recps, recp);
3944 if (!IsEmptyStr(cc)) {
3945 if (!IsEmptyStr(all_recps)) {
3946 strcat(all_recps, ",");
3948 strcat(all_recps, cc);
3950 if (!IsEmptyStr(bcc)) {
3951 if (!IsEmptyStr(all_recps)) {
3952 strcat(all_recps, ",");
3954 strcat(all_recps, bcc);
3956 if (!IsEmptyStr(all_recps)) {
3957 valid = validate_recipients(all_recps, NULL, 0);
3965 msgnum = CtdlSubmitMsg(msg, valid, "", QP_EADDR);
3968 cprintf("%ld\n", msgnum);
3970 cprintf("Message accepted.\n");
3973 cprintf("Internal error.\n");
3975 if (msg->cm_fields['E'] != NULL) {
3976 cprintf("%s\n", msg->cm_fields['E']);
3983 CtdlFreeMessage(msg);
3985 if (valid != NULL) {
3986 free_recipients(valid);
3994 * API function to delete messages which match a set of criteria
3995 * (returns the actual number of messages deleted)
3997 int CtdlDeleteMessages(char *room_name, /* which room */
3998 long *dmsgnums, /* array of msg numbers to be deleted */
3999 int num_dmsgnums, /* number of msgs to be deleted, or 0 for "any" */
4000 char *content_type /* or "" for any. regular expressions expected. */
4003 struct ctdlroom qrbuf;
4004 struct cdbdata *cdbfr;
4005 long *msglist = NULL;
4006 long *dellist = NULL;
4009 int num_deleted = 0;
4011 struct MetaData smi;
4014 int need_to_free_re = 0;
4016 if (content_type) if (!IsEmptyStr(content_type)) {
4017 regcomp(&re, content_type, 0);
4018 need_to_free_re = 1;
4020 CtdlLogPrintf(CTDL_DEBUG, "CtdlDeleteMessages(%s, %d msgs, %s)\n",
4021 room_name, num_dmsgnums, content_type);
4023 /* get room record, obtaining a lock... */
4024 if (lgetroom(&qrbuf, room_name) != 0) {
4025 CtdlLogPrintf(CTDL_ERR, "CtdlDeleteMessages(): Room <%s> not found\n",
4027 if (need_to_free_re) regfree(&re);
4028 return (0); /* room not found */
4030 cdbfr = cdb_fetch(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long));
4032 if (cdbfr != NULL) {
4033 dellist = malloc(cdbfr->len);
4034 msglist = (long *) cdbfr->ptr;
4035 cdbfr->ptr = NULL; /* CtdlDeleteMessages() now owns this memory */
4036 num_msgs = cdbfr->len / sizeof(long);
4040 for (i = 0; i < num_msgs; ++i) {
4043 /* Set/clear a bit for each criterion */
4045 /* 0 messages in the list or a null list means that we are
4046 * interested in deleting any messages which meet the other criteria.
4048 if ((num_dmsgnums == 0) || (dmsgnums == NULL)) {
4049 delete_this |= 0x01;
4052 for (j=0; j<num_dmsgnums; ++j) {
4053 if (msglist[i] == dmsgnums[j]) {
4054 delete_this |= 0x01;
4059 if (IsEmptyStr(content_type)) {
4060 delete_this |= 0x02;
4062 GetMetaData(&smi, msglist[i]);
4063 if (regexec(&re, smi.meta_content_type, 1, &pm, 0) == 0) {
4064 delete_this |= 0x02;
4068 /* Delete message only if all bits are set */
4069 if (delete_this == 0x03) {
4070 dellist[num_deleted++] = msglist[i];
4075 num_msgs = sort_msglist(msglist, num_msgs);
4076 cdb_store(CDB_MSGLISTS, &qrbuf.QRnumber, (int)sizeof(long),
4077 msglist, (int)(num_msgs * sizeof(long)));
4079 qrbuf.QRhighest = msglist[num_msgs - 1];
4083 /* Go through the messages we pulled out of the index, and decrement
4084 * their reference counts by 1. If this is the only room the message
4085 * was in, the reference count will reach zero and the message will
4086 * automatically be deleted from the database. We do this in a
4087 * separate pass because there might be plug-in hooks getting called,
4088 * and we don't want that happening during an S_ROOMS critical
4091 if (num_deleted) for (i=0; i<num_deleted; ++i) {
4092 PerformDeleteHooks(qrbuf.QRname, dellist[i]);
4093 AdjRefCount(dellist[i], -1);
4096 /* Now free the memory we used, and go away. */
4097 if (msglist != NULL) free(msglist);
4098 if (dellist != NULL) free(dellist);
4099 CtdlLogPrintf(CTDL_DEBUG, "%d message(s) deleted.\n", num_deleted);
4100 if (need_to_free_re) regfree(&re);
4101 return (num_deleted);
4107 * Check whether the current user has permission to delete messages from
4108 * the current room (returns 1 for yes, 0 for no)
4110 int CtdlDoIHavePermissionToDeleteMessagesFromThisRoom(void) {
4112 CtdlRoomAccess(&CC->room, &CC->user, &ra, NULL);
4113 if (ra & UA_DELETEALLOWED) return(1);
4121 * Delete message from current room
4123 void cmd_dele(char *args)
4132 extract_token(msgset, args, 0, '|', sizeof msgset);
4133 num_msgs = num_tokens(msgset, ',');
4135 cprintf("%d Nothing to do.\n", CIT_OK);
4139 if (CtdlDoIHavePermissionToDeleteMessagesFromThisRoom() == 0) {
4140 cprintf("%d Higher access required.\n",
4141 ERROR + HIGHER_ACCESS_REQUIRED);
4146 * Build our message set to be moved/copied
4148 msgs = malloc(num_msgs * sizeof(long));
4149 for (i=0; i<num_msgs; ++i) {
4150 extract_token(msgtok, msgset, i, ',', sizeof msgtok);
4151 msgs[i] = atol(msgtok);
4154 num_deleted = CtdlDeleteMessages(CC->room.QRname, msgs, num_msgs, "");
4158 cprintf("%d %d message%s deleted.\n", CIT_OK,
4159 num_deleted, ((num_deleted != 1) ? "s" : ""));
4161 cprintf("%d Message not found.\n", ERROR + MESSAGE_NOT_FOUND);
4167 * Back end API function for moves and deletes (multiple messages)
4169 int CtdlCopyMsgsToRoom(long *msgnums, int num_msgs, char *dest) {
4172 err = CtdlSaveMsgPointersInRoom(dest, msgnums, num_msgs, 1, NULL);
4173 if (err != 0) return(err);
4182 * move or copy a message to another room
4184 void cmd_move(char *args)
4191 char targ[ROOMNAMELEN];
4192 struct ctdlroom qtemp;
4199 extract_token(msgset, args, 0, '|', sizeof msgset);
4200 num_msgs = num_tokens(msgset, ',');
4202 cprintf("%d Nothing to do.\n", CIT_OK);
4206 extract_token(targ, args, 1, '|', sizeof targ);
4207 convert_room_name_macros(targ, sizeof targ);
4208 targ[ROOMNAMELEN - 1] = 0;
4209 is_copy = extract_int(args, 2);
4211 if (getroom(&qtemp, targ) != 0) {
4212 cprintf("%d '%s' does not exist.\n",
4213 ERROR + ROOM_NOT_FOUND, targ);
4217 getuser(&CC->user, CC->curr_user);
4218 CtdlRoomAccess(&qtemp, &CC->user, &ra, NULL);
4220 /* Check for permission to perform this operation.
4221 * Remember: "CC->room" is source, "qtemp" is target.
4225 /* Aides can move/copy */
4226 if (CC->user.axlevel >= 6) permit = 1;
4228 /* Room aides can move/copy */
4229 if (CC->user.usernum == CC->room.QRroomaide) permit = 1;
4231 /* Permit move/copy from personal rooms */
4232 if ((CC->room.QRflags & QR_MAILBOX)
4233 && (qtemp.QRflags & QR_MAILBOX)) permit = 1;
4235 /* Permit only copy from public to personal room */
4237 && (!(CC->room.QRflags & QR_MAILBOX))
4238 && (qtemp.QRflags & QR_MAILBOX)) permit = 1;
4240 /* Permit message removal from collaborative delete rooms */
4241 if (CC->room.QRflags2 & QR2_COLLABDEL) permit = 1;
4243 /* Users allowed to post into the target room may move into it too. */
4244 if ((CC->room.QRflags & QR_MAILBOX) &&
4245 (qtemp.QRflags & UA_POSTALLOWED)) permit = 1;
4247 /* User must have access to target room */
4248 if (!(ra & UA_KNOWN)) permit = 0;
4251 cprintf("%d Higher access required.\n",
4252 ERROR + HIGHER_ACCESS_REQUIRED);
4257 * Build our message set to be moved/copied
4259 msgs = malloc(num_msgs * sizeof(long));
4260 for (i=0; i<num_msgs; ++i) {
4261 extract_token(msgtok, msgset, i, ',', sizeof msgtok);
4262 msgs[i] = atol(msgtok);
4268 err = CtdlCopyMsgsToRoom(msgs, num_msgs, targ);
4270 cprintf("%d Cannot store message(s) in %s: error %d\n",
4276 /* Now delete the message from the source room,
4277 * if this is a 'move' rather than a 'copy' operation.
4280 CtdlDeleteMessages(CC->room.QRname, msgs, num_msgs, "");
4284 cprintf("%d Message(s) %s.\n", CIT_OK, (is_copy ? "copied" : "moved") );
4290 * GetMetaData() - Get the supplementary record for a message
4292 void GetMetaData(struct MetaData *smibuf, long msgnum)
4295 struct cdbdata *cdbsmi;
4298 memset(smibuf, 0, sizeof(struct MetaData));
4299 smibuf->meta_msgnum = msgnum;
4300 smibuf->meta_refcount = 1; /* Default reference count is 1 */
4302 /* Use the negative of the message number for its supp record index */
4303 TheIndex = (0L - msgnum);
4305 cdbsmi = cdb_fetch(CDB_MSGMAIN, &TheIndex, sizeof(long));
4306 if (cdbsmi == NULL) {
4307 return; /* record not found; go with defaults */
4309 memcpy(smibuf, cdbsmi->ptr,
4310 ((cdbsmi->len > sizeof(struct MetaData)) ?
4311 sizeof(struct MetaData) : cdbsmi->len));
4318 * PutMetaData() - (re)write supplementary record for a message
4320 void PutMetaData(struct MetaData *smibuf)
4324 /* Use the negative of the message number for the metadata db index */
4325 TheIndex = (0L - smibuf->meta_msgnum);
4327 cdb_store(CDB_MSGMAIN,
4328 &TheIndex, (int)sizeof(long),
4329 smibuf, (int)sizeof(struct MetaData));
4334 * AdjRefCount - submit an adjustment to the reference count for a message.
4335 * (These are just queued -- we actually process them later.)
4337 void AdjRefCount(long msgnum, int incr)
4339 struct arcq new_arcq;
4341 begin_critical_section(S_SUPPMSGMAIN);
4342 if (arcfp == NULL) {
4343 arcfp = fopen(file_arcq, "ab+");
4345 end_critical_section(S_SUPPMSGMAIN);
4347 /* msgnum < 0 means that we're trying to close the file */
4349 CtdlLogPrintf(CTDL_DEBUG, "Closing the AdjRefCount queue file\n");
4350 begin_critical_section(S_SUPPMSGMAIN);
4351 if (arcfp != NULL) {
4355 end_critical_section(S_SUPPMSGMAIN);
4360 * If we can't open the queue, perform the operation synchronously.
4362 if (arcfp == NULL) {
4363 TDAP_AdjRefCount(msgnum, incr);
4367 new_arcq.arcq_msgnum = msgnum;
4368 new_arcq.arcq_delta = incr;
4369 fwrite(&new_arcq, sizeof(struct arcq), 1, arcfp);
4377 * TDAP_ProcessAdjRefCountQueue()
4379 * Process the queue of message count adjustments that was created by calls
4380 * to AdjRefCount() ... by reading the queue and calling TDAP_AdjRefCount()
4381 * for each one. This should be an "off hours" operation.
4383 int TDAP_ProcessAdjRefCountQueue(void)
4385 char file_arcq_temp[PATH_MAX];
4388 struct arcq arcq_rec;
4389 int num_records_processed = 0;
4391 snprintf(file_arcq_temp, sizeof file_arcq_temp, "%s.%04x", file_arcq, rand());
4393 begin_critical_section(S_SUPPMSGMAIN);
4394 if (arcfp != NULL) {
4399 r = link(file_arcq, file_arcq_temp);
4401 CtdlLogPrintf(CTDL_CRIT, "%s: %s\n", file_arcq_temp, strerror(errno));
4402 end_critical_section(S_SUPPMSGMAIN);
4403 return(num_records_processed);
4407 end_critical_section(S_SUPPMSGMAIN);
4409 fp = fopen(file_arcq_temp, "rb");
4411 CtdlLogPrintf(CTDL_CRIT, "%s: %s\n", file_arcq_temp, strerror(errno));
4412 return(num_records_processed);
4415 while (fread(&arcq_rec, sizeof(struct arcq), 1, fp) == 1) {
4416 TDAP_AdjRefCount(arcq_rec.arcq_msgnum, arcq_rec.arcq_delta);
4417 ++num_records_processed;
4421 r = unlink(file_arcq_temp);
4423 CtdlLogPrintf(CTDL_CRIT, "%s: %s\n", file_arcq_temp, strerror(errno));
4426 return(num_records_processed);
4432 * TDAP_AdjRefCount - adjust the reference count for a message.
4433 * This one does it "for real" because it's called by
4434 * the autopurger function that processes the queue
4435 * created by AdjRefCount(). If a message's reference
4436 * count becomes zero, we also delete the message from
4437 * disk and de-index it.
4439 void TDAP_AdjRefCount(long msgnum, int incr)
4442 struct MetaData smi;
4445 /* This is a *tight* critical section; please keep it that way, as
4446 * it may get called while nested in other critical sections.
4447 * Complicating this any further will surely cause deadlock!
4449 begin_critical_section(S_SUPPMSGMAIN);
4450 GetMetaData(&smi, msgnum);
4451 smi.meta_refcount += incr;
4453 end_critical_section(S_SUPPMSGMAIN);
4454 CtdlLogPrintf(CTDL_DEBUG, "msg %ld ref count delta %+d, is now %d\n",
4455 msgnum, incr, smi.meta_refcount);
4457 /* If the reference count is now zero, delete the message
4458 * (and its supplementary record as well).
4460 if (smi.meta_refcount == 0) {
4461 CtdlLogPrintf(CTDL_DEBUG, "Deleting message <%ld>\n", msgnum);
4463 /* Call delete hooks with NULL room to show it has gone altogether */
4464 PerformDeleteHooks(NULL, msgnum);
4466 /* Remove from message base */
4468 cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long));
4469 cdb_delete(CDB_BIGMSGS, &delnum, (int)sizeof(long));
4471 /* Remove metadata record */
4472 delnum = (0L - msgnum);
4473 cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long));
4479 * Write a generic object to this room
4481 * Note: this could be much more efficient. Right now we use two temporary
4482 * files, and still pull the message into memory as with all others.
4484 void CtdlWriteObject(char *req_room, /* Room to stuff it in */
4485 char *content_type, /* MIME type of this object */
4486 char *raw_message, /* Data to be written */
4487 off_t raw_length, /* Size of raw_message */
4488 struct ctdluser *is_mailbox, /* Mailbox room? */
4489 int is_binary, /* Is encoding necessary? */
4490 int is_unique, /* Del others of this type? */
4491 unsigned int flags /* Internal save flags */
4495 struct ctdlroom qrbuf;
4496 char roomname[ROOMNAMELEN];
4497 struct CtdlMessage *msg;
4498 char *encoded_message = NULL;
4500 if (is_mailbox != NULL) {
4501 MailboxName(roomname, sizeof roomname, is_mailbox, req_room);
4504 safestrncpy(roomname, req_room, sizeof(roomname));
4507 CtdlLogPrintf(CTDL_DEBUG, "Raw length is %ld\n", (long)raw_length);
4510 encoded_message = malloc((size_t) (((raw_length * 134) / 100) + 4096 ) );
4513 encoded_message = malloc((size_t)(raw_length + 4096));
4516 sprintf(encoded_message, "Content-type: %s\n", content_type);
4519 sprintf(&encoded_message[strlen(encoded_message)],
4520 "Content-transfer-encoding: base64\n\n"
4524 sprintf(&encoded_message[strlen(encoded_message)],
4525 "Content-transfer-encoding: 7bit\n\n"
4531 &encoded_message[strlen(encoded_message)],
4539 &encoded_message[strlen(encoded_message)],
4545 CtdlLogPrintf(CTDL_DEBUG, "Allocating\n");
4546 msg = malloc(sizeof(struct CtdlMessage));
4547 memset(msg, 0, sizeof(struct CtdlMessage));
4548 msg->cm_magic = CTDLMESSAGE_MAGIC;
4549 msg->cm_anon_type = MES_NORMAL;
4550 msg->cm_format_type = 4;
4551 msg->cm_fields['A'] = strdup(CC->user.fullname);
4552 msg->cm_fields['O'] = strdup(req_room);
4553 msg->cm_fields['N'] = strdup(config.c_nodename);
4554 msg->cm_fields['H'] = strdup(config.c_humannode);
4555 msg->cm_flags = flags;
4557 msg->cm_fields['M'] = encoded_message;
4559 /* Create the requested room if we have to. */
4560 if (getroom(&qrbuf, roomname) != 0) {
4561 create_room(roomname,
4562 ( (is_mailbox != NULL) ? 5 : 3 ),
4563 "", 0, 1, 0, VIEW_BBS);
4565 /* If the caller specified this object as unique, delete all
4566 * other objects of this type that are currently in the room.
4569 CtdlLogPrintf(CTDL_DEBUG, "Deleted %d other msgs of this type\n",
4570 CtdlDeleteMessages(roomname, NULL, 0, content_type)
4573 /* Now write the data */
4574 CtdlSubmitMsg(msg, NULL, roomname, 0);
4575 CtdlFreeMessage(msg);
4583 void CtdlGetSysConfigBackend(long msgnum, void *userdata) {
4584 config_msgnum = msgnum;
4588 char *CtdlGetSysConfig(char *sysconfname) {
4589 char hold_rm[ROOMNAMELEN];
4592 struct CtdlMessage *msg;
4595 strcpy(hold_rm, CC->room.QRname);
4596 if (getroom(&CC->room, SYSCONFIGROOM) != 0) {
4597 getroom(&CC->room, hold_rm);
4602 /* We want the last (and probably only) config in this room */
4603 begin_critical_section(S_CONFIG);
4604 config_msgnum = (-1L);
4605 CtdlForEachMessage(MSGS_LAST, 1, NULL, sysconfname, NULL,
4606 CtdlGetSysConfigBackend, NULL);
4607 msgnum = config_msgnum;
4608 end_critical_section(S_CONFIG);
4614 msg = CtdlFetchMessage(msgnum, 1);
4616 conf = strdup(msg->cm_fields['M']);
4617 CtdlFreeMessage(msg);
4624 getroom(&CC->room, hold_rm);
4626 if (conf != NULL) do {
4627 extract_token(buf, conf, 0, '\n', sizeof buf);
4628 strcpy(conf, &conf[strlen(buf)+1]);
4629 } while ( (!IsEmptyStr(conf)) && (!IsEmptyStr(buf)) );
4635 void CtdlPutSysConfig(char *sysconfname, char *sysconfdata) {
4636 CtdlWriteObject(SYSCONFIGROOM, sysconfname, sysconfdata, (strlen(sysconfdata)+1), NULL, 0, 1, 0);
4641 * Determine whether a given Internet address belongs to the current user
4643 int CtdlIsMe(char *addr, int addr_buf_len)
4645 struct recptypes *recp;
4648 recp = validate_recipients(addr, NULL, 0);
4649 if (recp == NULL) return(0);
4651 if (recp->num_local == 0) {
4652 free_recipients(recp);
4656 for (i=0; i<recp->num_local; ++i) {
4657 extract_token(addr, recp->recp_local, i, '|', addr_buf_len);
4658 if (!strcasecmp(addr, CC->user.fullname)) {
4659 free_recipients(recp);
4664 free_recipients(recp);
4670 * Citadel protocol command to do the same
4672 void cmd_isme(char *argbuf) {
4675 if (CtdlAccessCheck(ac_logged_in)) return;
4676 extract_token(addr, argbuf, 0, '|', sizeof addr);
4678 if (CtdlIsMe(addr, sizeof addr)) {
4679 cprintf("%d %s\n", CIT_OK, addr);
4682 cprintf("%d Not you.\n", ERROR + ILLEGAL_VALUE);