4 * Implements the message store.
14 #if TIME_WITH_SYS_TIME
15 # include <sys/time.h>
19 # include <sys/time.h>
32 #include <sys/types.h>
34 #include <libcitadel.h>
37 #include "serv_extensions.h"
41 #include "sysdep_decls.h"
42 #include "citserver.h"
49 #include "internet_addressing.h"
50 #include "euidindex.h"
51 #include "journaling.h"
52 #include "citadel_dirs.h"
53 #include "clientsocket.h"
54 #include "serv_network.h"
57 #include "ctdl_module.h"
60 struct addresses_to_be_filed *atbf = NULL;
62 /* This temp file holds the queue of operations for AdjRefCount() */
63 static FILE *arcfp = NULL;
66 * This really belongs in serv_network.c, but I don't know how to export
67 * symbols between modules.
69 struct FilterList *filterlist = NULL;
73 * These are the four-character field headers we use when outputting
74 * messages in Citadel format (as opposed to RFC822 format).
77 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
78 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
79 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
80 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
81 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
82 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
83 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
84 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
113 * This function is self explanatory.
114 * (What can I say, I'm in a weird mood today...)
116 void remove_any_whitespace_to_the_left_or_right_of_at_symbol(char *name)
120 for (i = 0; i < strlen(name); ++i) {
121 if (name[i] == '@') {
122 while (isspace(name[i - 1]) && i > 0) {
123 strcpy(&name[i - 1], &name[i]);
126 while (isspace(name[i + 1])) {
127 strcpy(&name[i + 1], &name[i + 2]);
135 * Aliasing for network mail.
136 * (Error messages have been commented out, because this is a server.)
138 int alias(char *name)
139 { /* process alias and routing info for mail */
142 char aaa[SIZ], bbb[SIZ];
143 char *ignetcfg = NULL;
144 char *ignetmap = NULL;
150 char original_name[256];
151 safestrncpy(original_name, name, sizeof original_name);
154 remove_any_whitespace_to_the_left_or_right_of_at_symbol(name);
155 stripallbut(name, '<', '>');
157 fp = fopen(file_mail_aliases, "r");
159 fp = fopen("/dev/null", "r");
166 while (fgets(aaa, sizeof aaa, fp) != NULL) {
167 while (isspace(name[0]))
168 strcpy(name, &name[1]);
169 aaa[strlen(aaa) - 1] = 0;
171 for (a = 0; a < strlen(aaa); ++a) {
173 strcpy(bbb, &aaa[a + 1]);
177 if (!strcasecmp(name, aaa))
182 /* Hit the Global Address Book */
183 if (CtdlDirectoryLookup(aaa, name, sizeof aaa) == 0) {
187 if (strcasecmp(original_name, name)) {
188 CtdlLogPrintf(CTDL_INFO, "%s is being forwarded to %s\n", original_name, name);
191 /* Change "user @ xxx" to "user" if xxx is an alias for this host */
192 for (a=0; a<strlen(name); ++a) {
193 if (name[a] == '@') {
194 if (CtdlHostAlias(&name[a+1]) == hostalias_localhost) {
196 CtdlLogPrintf(CTDL_INFO, "Changed to <%s>\n", name);
201 /* determine local or remote type, see citadel.h */
202 at = haschar(name, '@');
203 if (at == 0) return(MES_LOCAL); /* no @'s - local address */
204 if (at > 1) return(MES_ERROR); /* >1 @'s - invalid address */
205 remove_any_whitespace_to_the_left_or_right_of_at_symbol(name);
207 /* figure out the delivery mode */
208 extract_token(node, name, 1, '@', sizeof node);
210 /* If there are one or more dots in the nodename, we assume that it
211 * is an FQDN and will attempt SMTP delivery to the Internet.
213 if (haschar(node, '.') > 0) {
214 return(MES_INTERNET);
217 /* Otherwise we look in the IGnet maps for a valid Citadel node.
218 * Try directly-connected nodes first...
220 ignetcfg = CtdlGetSysConfig(IGNETCFG);
221 for (i=0; i<num_tokens(ignetcfg, '\n'); ++i) {
222 extract_token(buf, ignetcfg, i, '\n', sizeof buf);
223 extract_token(testnode, buf, 0, '|', sizeof testnode);
224 if (!strcasecmp(node, testnode)) {
232 * Then try nodes that are two or more hops away.
234 ignetmap = CtdlGetSysConfig(IGNETMAP);
235 for (i=0; i<num_tokens(ignetmap, '\n'); ++i) {
236 extract_token(buf, ignetmap, i, '\n', sizeof buf);
237 extract_token(testnode, buf, 0, '|', sizeof testnode);
238 if (!strcasecmp(node, testnode)) {
245 /* If we get to this point it's an invalid node name */
251 * Back end for the MSGS command: output message number only.
253 void simple_listing(long msgnum, void *userdata)
255 cprintf("%ld\n", msgnum);
261 * Back end for the MSGS command: output header summary.
263 void headers_listing(long msgnum, void *userdata)
265 struct CtdlMessage *msg;
267 msg = CtdlFetchMessage(msgnum, 0);
269 cprintf("%ld|0|||||\n", msgnum);
273 cprintf("%ld|%s|%s|%s|%s|%s|\n",
275 (msg->cm_fields['T'] ? msg->cm_fields['T'] : "0"),
276 (msg->cm_fields['A'] ? msg->cm_fields['A'] : ""),
277 (msg->cm_fields['N'] ? msg->cm_fields['N'] : ""),
278 (msg->cm_fields['F'] ? msg->cm_fields['F'] : ""),
279 (msg->cm_fields['U'] ? msg->cm_fields['U'] : "")
281 CtdlFreeMessage(msg);
286 /* Determine if a given message matches the fields in a message template.
287 * Return 0 for a successful match.
289 int CtdlMsgCmp(struct CtdlMessage *msg, struct CtdlMessage *template) {
292 /* If there aren't any fields in the template, all messages will
295 if (template == NULL) return(0);
297 /* Null messages are bogus. */
298 if (msg == NULL) return(1);
300 for (i='A'; i<='Z'; ++i) {
301 if (template->cm_fields[i] != NULL) {
302 if (msg->cm_fields[i] == NULL) {
303 /* Considered equal if temmplate is empty string */
304 if (IsEmptyStr(template->cm_fields[i])) continue;
307 if (strcasecmp(msg->cm_fields[i],
308 template->cm_fields[i])) return 1;
312 /* All compares succeeded: we have a match! */
319 * Retrieve the "seen" message list for the current room.
321 void CtdlGetSeen(char *buf, int which_set) {
324 /* Learn about the user and room in question */
325 CtdlGetRelationship(&vbuf, &CC->user, &CC->room);
327 if (which_set == ctdlsetseen_seen)
328 safestrncpy(buf, vbuf.v_seen, SIZ);
329 if (which_set == ctdlsetseen_answered)
330 safestrncpy(buf, vbuf.v_answered, SIZ);
336 * Manipulate the "seen msgs" string (or other message set strings)
338 void CtdlSetSeen(long *target_msgnums, int num_target_msgnums,
339 int target_setting, int which_set,
340 struct ctdluser *which_user, struct ctdlroom *which_room) {
341 struct cdbdata *cdbfr;
351 char *is_set; /* actually an array of booleans */
355 char setstr[SIZ], lostr[SIZ], histr[SIZ];
357 /* Don't bother doing *anything* if we were passed a list of zero messages */
358 if (num_target_msgnums < 1) {
362 /* If no room was specified, we go with the current room. */
364 which_room = &CC->room;
367 /* If no user was specified, we go with the current user. */
369 which_user = &CC->user;
372 CtdlLogPrintf(CTDL_DEBUG, "CtdlSetSeen(%d msgs starting with %ld, %s, %d) in <%s>\n",
373 num_target_msgnums, target_msgnums[0],
374 (target_setting ? "SET" : "CLEAR"),
378 /* Learn about the user and room in question */
379 CtdlGetRelationship(&vbuf, which_user, which_room);
381 /* Load the message list */
382 cdbfr = cdb_fetch(CDB_MSGLISTS, &which_room->QRnumber, sizeof(long));
384 msglist = (long *) cdbfr->ptr;
385 cdbfr->ptr = NULL; /* CtdlSetSeen() now owns this memory */
386 num_msgs = cdbfr->len / sizeof(long);
389 return; /* No messages at all? No further action. */
392 is_set = malloc(num_msgs * sizeof(char));
393 memset(is_set, 0, (num_msgs * sizeof(char)) );
395 /* Decide which message set we're manipulating */
397 case ctdlsetseen_seen:
398 safestrncpy(vset, vbuf.v_seen, sizeof vset);
400 case ctdlsetseen_answered:
401 safestrncpy(vset, vbuf.v_answered, sizeof vset);
406 #if 0 /* This is a special diagnostic section. Do not allow it to run during normal operation. */
407 CtdlLogPrintf(CTDL_DEBUG, "There are %d messages in the room.\n", num_msgs);
408 for (i=0; i<num_msgs; ++i) {
409 if ((i > 0) && (msglist[i] <= msglist[i-1])) abort();
411 CtdlLogPrintf(CTDL_DEBUG, "We are twiddling %d of them.\n", num_target_msgnums);
412 for (k=0; k<num_target_msgnums; ++k) {
413 if ((k > 0) && (target_msgnums[k] <= target_msgnums[k-1])) abort();
417 CtdlLogPrintf(CTDL_DEBUG, "before update: %s\n", vset);
419 /* Translate the existing sequence set into an array of booleans */
420 num_sets = num_tokens(vset, ',');
421 for (s=0; s<num_sets; ++s) {
422 extract_token(setstr, vset, s, ',', sizeof setstr);
424 extract_token(lostr, setstr, 0, ':', sizeof lostr);
425 if (num_tokens(setstr, ':') >= 2) {
426 extract_token(histr, setstr, 1, ':', sizeof histr);
429 strcpy(histr, lostr);
432 if (!strcmp(histr, "*")) {
439 for (i = 0; i < num_msgs; ++i) {
440 if ((msglist[i] >= lo) && (msglist[i] <= hi)) {
447 /* Now translate the array of booleans back into a sequence set */
453 for (i=0; i<num_msgs; ++i) {
457 for (k=0; k<num_target_msgnums; ++k) {
458 if (msglist[i] == target_msgnums[k]) {
459 is_seen = target_setting;
463 w = 0; /* set to 1 if we write something to the string */
465 if ((was_seen == 0) && (is_seen == 1)) {
468 else if ((was_seen == 1) && (is_seen == 0)) {
472 if (!IsEmptyStr(vset)) {
476 sprintf(&vset[strlen(vset)], "%ld", hi);
479 sprintf(&vset[strlen(vset)], "%ld:%ld", lo, hi);
482 else if ((is_seen) && (i == num_msgs - 1)) {
484 if (!IsEmptyStr(vset)) {
487 if ((i==0) || (was_seen == 0)) {
488 sprintf(&vset[strlen(vset)], "%ld", msglist[i]);
491 sprintf(&vset[strlen(vset)], "%ld:%ld", lo, msglist[i]);
495 /* If the string is getting too long, truncate it at the beginning; repeat up to 9 times */
496 if (w) for (j=0; j<9; ++j) {
497 if ((strlen(vset) + 20) > sizeof vset) {
498 remove_token(vset, 0, ',');
499 if (which_set == ctdlsetseen_seen) {
501 sprintf(temp, "1:%ld,", atol(vset)-1L);
511 CtdlLogPrintf(CTDL_DEBUG, " after update: %s\n", vset);
513 /* Decide which message set we're manipulating */
515 case ctdlsetseen_seen:
516 safestrncpy(vbuf.v_seen, vset, sizeof vbuf.v_seen);
518 case ctdlsetseen_answered:
519 safestrncpy(vbuf.v_answered, vset, sizeof vbuf.v_answered);
525 CtdlSetRelationship(&vbuf, which_user, which_room);
530 * API function to perform an operation for each qualifying message in the
531 * current room. (Returns the number of messages processed.)
533 int CtdlForEachMessage(int mode, long ref, char *search_string,
535 struct CtdlMessage *compare,
536 void (*CallBack) (long, void *),
542 struct cdbdata *cdbfr;
543 long *msglist = NULL;
545 int num_processed = 0;
548 struct CtdlMessage *msg = NULL;
551 int printed_lastold = 0;
552 int num_search_msgs = 0;
553 long *search_msgs = NULL;
555 int need_to_free_re = 0;
558 if ((content_type) && (!IsEmptyStr(content_type))) {
559 regcomp(&re, content_type, 0);
563 /* Learn about the user and room in question */
564 getuser(&CC->user, CC->curr_user);
565 CtdlGetRelationship(&vbuf, &CC->user, &CC->room);
567 /* Load the message list */
568 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long));
570 msglist = (long *) cdbfr->ptr;
571 num_msgs = cdbfr->len / sizeof(long);
573 if (need_to_free_re) regfree(&re);
574 return 0; /* No messages at all? No further action. */
579 * Now begin the traversal.
581 if (num_msgs > 0) for (a = 0; a < num_msgs; ++a) {
583 /* If the caller is looking for a specific MIME type, filter
584 * out all messages which are not of the type requested.
586 if ((content_type != NULL) && (!IsEmptyStr(content_type))) {
588 /* This call to GetMetaData() sits inside this loop
589 * so that we only do the extra database read per msg
590 * if we need to. Doing the extra read all the time
591 * really kills the server. If we ever need to use
592 * metadata for another search criterion, we need to
593 * move the read somewhere else -- but still be smart
594 * enough to only do the read if the caller has
595 * specified something that will need it.
597 GetMetaData(&smi, msglist[a]);
599 /* if (strcasecmp(smi.meta_content_type, content_type)) { old non-regex way */
600 if (regexec(&re, smi.meta_content_type, 1, &pm, 0) != 0) {
606 num_msgs = sort_msglist(msglist, num_msgs);
608 /* If a template was supplied, filter out the messages which
609 * don't match. (This could induce some delays!)
612 if (compare != NULL) {
613 for (a = 0; a < num_msgs; ++a) {
614 msg = CtdlFetchMessage(msglist[a], 1);
616 if (CtdlMsgCmp(msg, compare)) {
619 CtdlFreeMessage(msg);
625 /* If a search string was specified, get a message list from
626 * the full text index and remove messages which aren't on both
630 * Since the lists are sorted and strictly ascending, and the
631 * output list is guaranteed to be shorter than or equal to the
632 * input list, we overwrite the bottom of the input list. This
633 * eliminates the need to memmove big chunks of the list over and
636 if ( (num_msgs > 0) && (mode == MSGS_SEARCH) && (search_string) ) {
638 /* Call search module via hook mechanism.
639 * NULL means use any search function available.
640 * otherwise replace with a char * to name of search routine
642 CtdlModuleDoSearch(&num_search_msgs, &search_msgs, search_string, "fulltext");
644 if (num_search_msgs > 0) {
648 orig_num_msgs = num_msgs;
650 for (i=0; i<orig_num_msgs; ++i) {
651 for (j=0; j<num_search_msgs; ++j) {
652 if (msglist[i] == search_msgs[j]) {
653 msglist[num_msgs++] = msglist[i];
659 num_msgs = 0; /* No messages qualify */
661 if (search_msgs != NULL) free(search_msgs);
663 /* Now that we've purged messages which don't contain the search
664 * string, treat a MSGS_SEARCH just like a MSGS_ALL from this
671 * Now iterate through the message list, according to the
672 * criteria supplied by the caller.
675 for (a = 0; a < num_msgs; ++a) {
676 thismsg = msglist[a];
677 if (mode == MSGS_ALL) {
681 is_seen = is_msg_in_sequence_set(
682 vbuf.v_seen, thismsg);
683 if (is_seen) lastold = thismsg;
689 || ((mode == MSGS_OLD) && (is_seen))
690 || ((mode == MSGS_NEW) && (!is_seen))
691 || ((mode == MSGS_LAST) && (a >= (num_msgs - ref)))
692 || ((mode == MSGS_FIRST) && (a < ref))
693 || ((mode == MSGS_GT) && (thismsg > ref))
694 || ((mode == MSGS_EQ) && (thismsg == ref))
697 if ((mode == MSGS_NEW) && (CC->user.flags & US_LASTOLD) && (lastold > 0L) && (printed_lastold == 0) && (!is_seen)) {
699 CallBack(lastold, userdata);
703 if (CallBack) CallBack(thismsg, userdata);
707 cdb_free(cdbfr); /* Clean up */
708 if (need_to_free_re) regfree(&re);
709 return num_processed;
715 * cmd_msgs() - get list of message #'s in this room
716 * implements the MSGS server command using CtdlForEachMessage()
718 void cmd_msgs(char *cmdbuf)
727 int with_template = 0;
728 struct CtdlMessage *template = NULL;
729 int with_headers = 0;
730 char search_string[1024];
732 extract_token(which, cmdbuf, 0, '|', sizeof which);
733 cm_ref = extract_int(cmdbuf, 1);
734 extract_token(search_string, cmdbuf, 1, '|', sizeof search_string);
735 with_template = extract_int(cmdbuf, 2);
736 with_headers = extract_int(cmdbuf, 3);
739 if (!strncasecmp(which, "OLD", 3))
741 else if (!strncasecmp(which, "NEW", 3))
743 else if (!strncasecmp(which, "FIRST", 5))
745 else if (!strncasecmp(which, "LAST", 4))
747 else if (!strncasecmp(which, "GT", 2))
749 else if (!strncasecmp(which, "SEARCH", 6))
754 if ((!(CC->logged_in)) && (!(CC->internal_pgm))) {
755 cprintf("%d not logged in\n", ERROR + NOT_LOGGED_IN);
759 if ( (mode == MSGS_SEARCH) && (!config.c_enable_fulltext) ) {
760 cprintf("%d Full text index is not enabled on this server.\n",
761 ERROR + CMD_NOT_SUPPORTED);
767 cprintf("%d Send template then receive message list\n",
769 template = (struct CtdlMessage *)
770 malloc(sizeof(struct CtdlMessage));
771 memset(template, 0, sizeof(struct CtdlMessage));
772 template->cm_magic = CTDLMESSAGE_MAGIC;
773 template->cm_anon_type = MES_NORMAL;
775 while(client_getln(buf, sizeof buf) >= 0 && strcmp(buf,"000")) {
776 extract_token(tfield, buf, 0, '|', sizeof tfield);
777 extract_token(tvalue, buf, 1, '|', sizeof tvalue);
778 for (i='A'; i<='Z'; ++i) if (msgkeys[i]!=NULL) {
779 if (!strcasecmp(tfield, msgkeys[i])) {
780 template->cm_fields[i] =
788 cprintf("%d \n", LISTING_FOLLOWS);
791 CtdlForEachMessage(mode,
792 ( (mode == MSGS_SEARCH) ? 0 : cm_ref ),
793 ( (mode == MSGS_SEARCH) ? search_string : NULL ),
796 (with_headers ? headers_listing : simple_listing),
799 if (template != NULL) CtdlFreeMessage(template);
807 * help_subst() - support routine for help file viewer
809 void help_subst(char *strbuf, char *source, char *dest)
814 while (p = pattern2(strbuf, source), (p >= 0)) {
815 strcpy(workbuf, &strbuf[p + strlen(source)]);
816 strcpy(&strbuf[p], dest);
817 strcat(strbuf, workbuf);
822 void do_help_subst(char *buffer)
826 help_subst(buffer, "^nodename", config.c_nodename);
827 help_subst(buffer, "^humannode", config.c_humannode);
828 help_subst(buffer, "^fqdn", config.c_fqdn);
829 help_subst(buffer, "^username", CC->user.fullname);
830 snprintf(buf2, sizeof buf2, "%ld", CC->user.usernum);
831 help_subst(buffer, "^usernum", buf2);
832 help_subst(buffer, "^sysadm", config.c_sysadm);
833 help_subst(buffer, "^variantname", CITADEL);
834 snprintf(buf2, sizeof buf2, "%d", config.c_maxsessions);
835 help_subst(buffer, "^maxsessions", buf2);
836 help_subst(buffer, "^bbsdir", ctdl_message_dir);
842 * memfmout() - Citadel text formatter and paginator.
843 * Although the original purpose of this routine was to format
844 * text to the reader's screen width, all we're really using it
845 * for here is to format text out to 80 columns before sending it
846 * to the client. The client software may reformat it again.
849 char *mptr, /* where are we going to get our text from? */
850 char subst, /* nonzero if we should do substitutions */
851 char *nl) /* string to terminate lines with */
859 static int width = 80;
864 c = 1; /* c is the current pos */
868 while (ch = *mptr, ((ch != 0) && (strlen(buffer) < 126))) {
870 buffer[strlen(buffer) + 1] = 0;
871 buffer[strlen(buffer)] = ch;
874 if (buffer[0] == '^')
875 do_help_subst(buffer);
877 buffer[strlen(buffer) + 1] = 0;
879 strcpy(buffer, &buffer[1]);
887 if (((ch == 13) || (ch == 10)) && (old != 13) && (old != 10)) {
890 if (((old == 13) || (old == 10)) && (isspace(real))) {
895 if (((strlen(aaa) + c) > (width - 5)) && (strlen(aaa) > (width - 5))) {
896 cprintf("%s%s", nl, aaa);
905 if ((strlen(aaa) + c) > (width - 5)) {
914 if ((ch == 13) || (ch == 10)) {
915 cprintf("%s%s", aaa, nl);
922 cprintf("%s%s", aaa, nl);
928 * Callback function for mime parser that simply lists the part
930 void list_this_part(char *name, char *filename, char *partnum, char *disp,
931 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
932 char *cbid, void *cbuserdata)
936 ma = (struct ma_info *)cbuserdata;
937 if (ma->is_ma == 0) {
938 cprintf("part=%s|%s|%s|%s|%s|%ld|%s\n",
939 name, filename, partnum, disp, cbtype, (long)length, cbid);
944 * Callback function for multipart prefix
946 void list_this_pref(char *name, char *filename, char *partnum, char *disp,
947 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
948 char *cbid, void *cbuserdata)
952 ma = (struct ma_info *)cbuserdata;
953 if (!strcasecmp(cbtype, "multipart/alternative")) {
957 if (ma->is_ma == 0) {
958 cprintf("pref=%s|%s\n", partnum, cbtype);
963 * Callback function for multipart sufffix
965 void list_this_suff(char *name, char *filename, char *partnum, char *disp,
966 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
967 char *cbid, void *cbuserdata)
971 ma = (struct ma_info *)cbuserdata;
972 if (ma->is_ma == 0) {
973 cprintf("suff=%s|%s\n", partnum, cbtype);
975 if (!strcasecmp(cbtype, "multipart/alternative")) {
982 * Callback function for mime parser that opens a section for downloading
984 void mime_download(char *name, char *filename, char *partnum, char *disp,
985 void *content, char *cbtype, char *cbcharset, size_t length,
986 char *encoding, char *cbid, void *cbuserdata)
989 /* Silently go away if there's already a download open. */
990 if (CC->download_fp != NULL)
994 (!IsEmptyStr(partnum) && (!strcasecmp(CC->download_desired_section, partnum)))
995 || (!IsEmptyStr(cbid) && (!strcasecmp(CC->download_desired_section, cbid)))
997 CC->download_fp = tmpfile();
998 if (CC->download_fp == NULL)
1001 fwrite(content, length, 1, CC->download_fp);
1002 fflush(CC->download_fp);
1003 rewind(CC->download_fp);
1005 OpenCmdResult(filename, cbtype);
1012 * Callback function for mime parser that outputs a section all at once.
1013 * We can specify the desired section by part number *or* content-id.
1015 void mime_spew_section(char *name, char *filename, char *partnum, char *disp,
1016 void *content, char *cbtype, char *cbcharset, size_t length,
1017 char *encoding, char *cbid, void *cbuserdata)
1019 int *found_it = (int *)cbuserdata;
1022 (!IsEmptyStr(partnum) && (!strcasecmp(CC->download_desired_section, partnum)))
1023 || (!IsEmptyStr(cbid) && (!strcasecmp(CC->download_desired_section, cbid)))
1026 cprintf("%d %d|-1|%s|%s\n",
1032 client_write(content, length);
1039 * Load a message from disk into memory.
1040 * This is used by CtdlOutputMsg() and other fetch functions.
1042 * NOTE: Caller is responsible for freeing the returned CtdlMessage struct
1043 * using the CtdlMessageFree() function.
1045 struct CtdlMessage *CtdlFetchMessage(long msgnum, int with_body)
1047 struct cdbdata *dmsgtext;
1048 struct CtdlMessage *ret = NULL;
1052 cit_uint8_t field_header;
1054 CtdlLogPrintf(CTDL_DEBUG, "CtdlFetchMessage(%ld, %d)\n", msgnum, with_body);
1056 dmsgtext = cdb_fetch(CDB_MSGMAIN, &msgnum, sizeof(long));
1057 if (dmsgtext == NULL) {
1060 mptr = dmsgtext->ptr;
1061 upper_bound = mptr + dmsgtext->len;
1063 /* Parse the three bytes that begin EVERY message on disk.
1064 * The first is always 0xFF, the on-disk magic number.
1065 * The second is the anonymous/public type byte.
1066 * The third is the format type byte (vari, fixed, or MIME).
1070 CtdlLogPrintf(CTDL_ERR, "Message %ld appears to be corrupted.\n", msgnum);
1074 ret = (struct CtdlMessage *) malloc(sizeof(struct CtdlMessage));
1075 memset(ret, 0, sizeof(struct CtdlMessage));
1077 ret->cm_magic = CTDLMESSAGE_MAGIC;
1078 ret->cm_anon_type = *mptr++; /* Anon type byte */
1079 ret->cm_format_type = *mptr++; /* Format type byte */
1082 * The rest is zero or more arbitrary fields. Load them in.
1083 * We're done when we encounter either a zero-length field or
1084 * have just processed the 'M' (message text) field.
1087 if (mptr >= upper_bound) {
1090 field_header = *mptr++;
1091 ret->cm_fields[field_header] = strdup(mptr);
1093 while (*mptr++ != 0); /* advance to next field */
1095 } while ((mptr < upper_bound) && (field_header != 'M'));
1099 /* Always make sure there's something in the msg text field. If
1100 * it's NULL, the message text is most likely stored separately,
1101 * so go ahead and fetch that. Failing that, just set a dummy
1102 * body so other code doesn't barf.
1104 if ( (ret->cm_fields['M'] == NULL) && (with_body) ) {
1105 dmsgtext = cdb_fetch(CDB_BIGMSGS, &msgnum, sizeof(long));
1106 if (dmsgtext != NULL) {
1107 ret->cm_fields['M'] = strdup(dmsgtext->ptr);
1111 if (ret->cm_fields['M'] == NULL) {
1112 ret->cm_fields['M'] = strdup("\r\n\r\n (no text)\r\n");
1115 /* Perform "before read" hooks (aborting if any return nonzero) */
1116 if (PerformMessageHooks(ret, EVT_BEFOREREAD) > 0) {
1117 CtdlFreeMessage(ret);
1126 * Returns 1 if the supplied pointer points to a valid Citadel message.
1127 * If the pointer is NULL or the magic number check fails, returns 0.
1129 int is_valid_message(struct CtdlMessage *msg) {
1132 if ((msg->cm_magic) != CTDLMESSAGE_MAGIC) {
1133 CtdlLogPrintf(CTDL_WARNING, "is_valid_message() -- self-check failed\n");
1141 * 'Destructor' for struct CtdlMessage
1143 void CtdlFreeMessage(struct CtdlMessage *msg)
1147 if (is_valid_message(msg) == 0)
1149 if (msg != NULL) free (msg);
1153 for (i = 0; i < 256; ++i)
1154 if (msg->cm_fields[i] != NULL) {
1155 free(msg->cm_fields[i]);
1158 msg->cm_magic = 0; /* just in case */
1164 * Pre callback function for multipart/alternative
1166 * NOTE: this differs from the standard behavior for a reason. Normally when
1167 * displaying multipart/alternative you want to show the _last_ usable
1168 * format in the message. Here we show the _first_ one, because it's
1169 * usually text/plain. Since this set of functions is designed for text
1170 * output to non-MIME-aware clients, this is the desired behavior.
1173 void fixed_output_pre(char *name, char *filename, char *partnum, char *disp,
1174 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
1175 char *cbid, void *cbuserdata)
1179 ma = (struct ma_info *)cbuserdata;
1180 CtdlLogPrintf(CTDL_DEBUG, "fixed_output_pre() type=<%s>\n", cbtype);
1181 if (!strcasecmp(cbtype, "multipart/alternative")) {
1185 if (!strcasecmp(cbtype, "message/rfc822")) {
1191 * Post callback function for multipart/alternative
1193 void fixed_output_post(char *name, char *filename, char *partnum, char *disp,
1194 void *content, char *cbtype, char *cbcharset, size_t length,
1195 char *encoding, char *cbid, void *cbuserdata)
1199 ma = (struct ma_info *)cbuserdata;
1200 CtdlLogPrintf(CTDL_DEBUG, "fixed_output_post() type=<%s>\n", cbtype);
1201 if (!strcasecmp(cbtype, "multipart/alternative")) {
1205 if (!strcasecmp(cbtype, "message/rfc822")) {
1211 * Inline callback function for mime parser that wants to display text
1213 void fixed_output(char *name, char *filename, char *partnum, char *disp,
1214 void *content, char *cbtype, char *cbcharset, size_t length,
1215 char *encoding, char *cbid, void *cbuserdata)
1222 ma = (struct ma_info *)cbuserdata;
1224 CtdlLogPrintf(CTDL_DEBUG,
1225 "fixed_output() part %s: %s (%s) (%ld bytes)\n",
1226 partnum, filename, cbtype, (long)length);
1229 * If we're in the middle of a multipart/alternative scope and
1230 * we've already printed another section, skip this one.
1232 if ( (ma->is_ma) && (ma->did_print) ) {
1233 CtdlLogPrintf(CTDL_DEBUG, "Skipping part %s (%s)\n", partnum, cbtype);
1238 if ( (!strcasecmp(cbtype, "text/plain"))
1239 || (IsEmptyStr(cbtype)) ) {
1242 client_write(wptr, length);
1243 if (wptr[length-1] != '\n') {
1250 if (!strcasecmp(cbtype, "text/html")) {
1251 ptr = html_to_ascii(content, length, 80, 0);
1253 client_write(ptr, wlen);
1254 if (ptr[wlen-1] != '\n') {
1261 if (ma->use_fo_hooks) {
1262 if (PerformFixedOutputHooks(cbtype, content, length)) {
1263 /* above function returns nonzero if it handled the part */
1268 if (strncasecmp(cbtype, "multipart/", 10)) {
1269 cprintf("Part %s: %s (%s) (%ld bytes)\r\n",
1270 partnum, filename, cbtype, (long)length);
1276 * The client is elegant and sophisticated and wants to be choosy about
1277 * MIME content types, so figure out which multipart/alternative part
1278 * we're going to send.
1280 * We use a system of weights. When we find a part that matches one of the
1281 * MIME types we've declared as preferential, we can store it in ma->chosen_part
1282 * and then set ma->chosen_pref to that MIME type's position in our preference
1283 * list. If we then hit another match, we only replace the first match if
1284 * the preference value is lower.
1286 void choose_preferred(char *name, char *filename, char *partnum, char *disp,
1287 void *content, char *cbtype, char *cbcharset, size_t length,
1288 char *encoding, char *cbid, void *cbuserdata)
1294 ma = (struct ma_info *)cbuserdata;
1296 // NOTE: REMOVING THIS CONDITIONAL FIXES BUG 220
1297 // http://bugzilla.citadel.org/show_bug.cgi?id=220
1298 // I don't know if there are any side effects! Please TEST TEST TEST
1299 //if (ma->is_ma > 0) {
1301 for (i=0; i<num_tokens(CC->preferred_formats, '|'); ++i) {
1302 extract_token(buf, CC->preferred_formats, i, '|', sizeof buf);
1303 if ( (!strcasecmp(buf, cbtype)) && (!ma->freeze) ) {
1304 if (i < ma->chosen_pref) {
1305 CtdlLogPrintf(CTDL_DEBUG, "Setting chosen part: <%s>\n", partnum);
1306 safestrncpy(ma->chosen_part, partnum, sizeof ma->chosen_part);
1307 ma->chosen_pref = i;
1314 * Now that we've chosen our preferred part, output it.
1316 void output_preferred(char *name, char *filename, char *partnum, char *disp,
1317 void *content, char *cbtype, char *cbcharset, size_t length,
1318 char *encoding, char *cbid, void *cbuserdata)
1322 int add_newline = 0;
1326 ma = (struct ma_info *)cbuserdata;
1328 /* This is not the MIME part you're looking for... */
1329 if (strcasecmp(partnum, ma->chosen_part)) return;
1331 /* If the content-type of this part is in our preferred formats
1332 * list, we can simply output it verbatim.
1334 for (i=0; i<num_tokens(CC->preferred_formats, '|'); ++i) {
1335 extract_token(buf, CC->preferred_formats, i, '|', sizeof buf);
1336 if (!strcasecmp(buf, cbtype)) {
1337 /* Yeah! Go! W00t!! */
1339 text_content = (char *)content;
1340 if (text_content[length-1] != '\n') {
1343 cprintf("Content-type: %s", cbtype);
1344 if (!IsEmptyStr(cbcharset)) {
1345 cprintf("; charset=%s", cbcharset);
1347 cprintf("\nContent-length: %d\n",
1348 (int)(length + add_newline) );
1349 if (!IsEmptyStr(encoding)) {
1350 cprintf("Content-transfer-encoding: %s\n", encoding);
1353 cprintf("Content-transfer-encoding: 7bit\n");
1355 cprintf("X-Citadel-MSG4-Partnum: %s\n", partnum);
1357 client_write(content, length);
1358 if (add_newline) cprintf("\n");
1363 /* No translations required or possible: output as text/plain */
1364 cprintf("Content-type: text/plain\n\n");
1365 fixed_output(name, filename, partnum, disp, content, cbtype, cbcharset,
1366 length, encoding, cbid, cbuserdata);
1371 char desired_section[64];
1378 * Callback function for
1380 void extract_encapsulated_message(char *name, char *filename, char *partnum, char *disp,
1381 void *content, char *cbtype, char *cbcharset, size_t length,
1382 char *encoding, char *cbid, void *cbuserdata)
1384 struct encapmsg *encap;
1386 encap = (struct encapmsg *)cbuserdata;
1388 /* Only proceed if this is the desired section... */
1389 if (!strcasecmp(encap->desired_section, partnum)) {
1390 encap->msglen = length;
1391 encap->msg = malloc(length + 2);
1392 memcpy(encap->msg, content, length);
1402 * Get a message off disk. (returns om_* values found in msgbase.h)
1405 int CtdlOutputMsg(long msg_num, /* message number (local) to fetch */
1406 int mode, /* how would you like that message? */
1407 int headers_only, /* eschew the message body? */
1408 int do_proto, /* do Citadel protocol responses? */
1409 int crlf, /* Use CRLF newlines instead of LF? */
1410 char *section, /* NULL or a message/rfc822 section */
1411 int flags /* should the bessage be exported clean? */
1413 struct CtdlMessage *TheMessage = NULL;
1414 int retcode = om_no_such_msg;
1415 struct encapmsg encap;
1417 CtdlLogPrintf(CTDL_DEBUG, "CtdlOutputMsg() msgnum=%ld, mode=%d, section=%s\n",
1419 (section ? section : "<>")
1422 if ((!(CC->logged_in)) && (!(CC->internal_pgm))) {
1423 if (do_proto) cprintf("%d Not logged in.\n",
1424 ERROR + NOT_LOGGED_IN);
1425 return(om_not_logged_in);
1428 /* FIXME: check message id against msglist for this room */
1431 * Fetch the message from disk. If we're in HEADERS_FAST mode,
1432 * request that we don't even bother loading the body into memory.
1434 if (headers_only == HEADERS_FAST) {
1435 TheMessage = CtdlFetchMessage(msg_num, 0);
1438 TheMessage = CtdlFetchMessage(msg_num, 1);
1441 if (TheMessage == NULL) {
1442 if (do_proto) cprintf("%d Can't locate msg %ld on disk\n",
1443 ERROR + MESSAGE_NOT_FOUND, msg_num);
1444 return(om_no_such_msg);
1447 /* Here is the weird form of this command, to process only an
1448 * encapsulated message/rfc822 section.
1450 if (section) if (!IsEmptyStr(section)) if (strcmp(section, "0")) {
1451 memset(&encap, 0, sizeof encap);
1452 safestrncpy(encap.desired_section, section, sizeof encap.desired_section);
1453 mime_parser(TheMessage->cm_fields['M'],
1455 *extract_encapsulated_message,
1456 NULL, NULL, (void *)&encap, 0
1458 CtdlFreeMessage(TheMessage);
1462 encap.msg[encap.msglen] = 0;
1463 TheMessage = convert_internet_message(encap.msg);
1464 encap.msg = NULL; /* no free() here, TheMessage owns it now */
1466 /* Now we let it fall through to the bottom of this
1467 * function, because TheMessage now contains the
1468 * encapsulated message instead of the top-level
1469 * message. Isn't that neat?
1474 if (do_proto) cprintf("%d msg %ld has no part %s\n",
1475 ERROR + MESSAGE_NOT_FOUND, msg_num, section);
1476 retcode = om_no_such_msg;
1481 /* Ok, output the message now */
1482 retcode = CtdlOutputPreLoadedMsg(TheMessage, mode, headers_only, do_proto, crlf, flags);
1483 CtdlFreeMessage(TheMessage);
1489 char *qp_encode_email_addrs(char *source)
1491 char user[256], node[256], name[256];
1492 const char headerStr[] = "=?UTF-8?Q?";
1496 int need_to_encode = 0;
1502 long nAddrPtrMax = 50;
1507 if (source == NULL) return source;
1508 if (IsEmptyStr(source)) return source;
1510 AddrPtr = malloc (sizeof (long) * nAddrPtrMax);
1511 AddrUtf8 = malloc (sizeof (long) * nAddrPtrMax);
1512 memset(AddrUtf8, 0, sizeof (long) * nAddrPtrMax);
1515 while (!IsEmptyStr (&source[i])) {
1516 if (nColons >= nAddrPtrMax){
1519 ptr = (long *) malloc(sizeof (long) * nAddrPtrMax * 2);
1520 memcpy (ptr, AddrPtr, sizeof (long) * nAddrPtrMax);
1521 free (AddrPtr), AddrPtr = ptr;
1523 ptr = (long *) malloc(sizeof (long) * nAddrPtrMax * 2);
1524 memset(&ptr[nAddrPtrMax], 0,
1525 sizeof (long) * nAddrPtrMax);
1527 memcpy (ptr, AddrUtf8, sizeof (long) * nAddrPtrMax);
1528 free (AddrUtf8), AddrUtf8 = ptr;
1531 if (((unsigned char) source[i] < 32) ||
1532 ((unsigned char) source[i] > 126)) {
1534 AddrUtf8[nColons] = 1;
1536 if (source[i] == '"')
1537 InQuotes = !InQuotes;
1538 if (!InQuotes && source[i] == ',') {
1539 AddrPtr[nColons] = i;
1544 if (need_to_encode == 0) {
1551 EncodedMaxLen = nColons * (sizeof(headerStr) + 3) + SourceLen * 3;
1552 Encoded = (char*) malloc (EncodedMaxLen);
1554 for (i = 0; i < nColons; i++)
1555 source[AddrPtr[i]++] = '\0';
1559 for (i = 0; i < nColons && nPtr != NULL; i++) {
1560 nmax = EncodedMaxLen - (nPtr - Encoded);
1562 process_rfc822_addr(&source[AddrPtr[i]],
1566 /* TODO: libIDN here ! */
1567 if (IsEmptyStr(name)) {
1568 n = snprintf(nPtr, nmax,
1569 (i==0)?"%s@%s" : ",%s@%s",
1573 EncodedName = rfc2047encode(name, strlen(name));
1574 n = snprintf(nPtr, nmax,
1575 (i==0)?"%s <%s@%s>" : ",%s <%s@%s>",
1576 EncodedName, user, node);
1581 n = snprintf(nPtr, nmax,
1582 (i==0)?"%s" : ",%s",
1583 &source[AddrPtr[i]]);
1589 ptr = (char*) malloc(EncodedMaxLen * 2);
1590 memcpy(ptr, Encoded, EncodedMaxLen);
1591 nnPtr = ptr + (nPtr - Encoded), nPtr = nnPtr;
1592 free(Encoded), Encoded = ptr;
1594 i--; /* do it once more with properly lengthened buffer */
1597 for (i = 0; i < nColons; i++)
1598 source[--AddrPtr[i]] = ',';
1605 /* If the last item in a list of recipients was truncated to a partial address,
1606 * remove it completely in order to avoid choking libSieve
1608 void sanitize_truncated_recipient(char *str)
1611 if (num_tokens(str, ',') < 2) return;
1613 int len = strlen(str);
1614 if (len < 900) return;
1615 if (len > 998) str[998] = 0;
1617 char *cptr = strrchr(str, ',');
1620 char *lptr = strchr(cptr, '<');
1621 char *rptr = strchr(cptr, '>');
1623 if ( (lptr) && (rptr) && (rptr > lptr) ) return;
1631 * Get a message off disk. (returns om_* values found in msgbase.h)
1633 int CtdlOutputPreLoadedMsg(
1634 struct CtdlMessage *TheMessage,
1635 int mode, /* how would you like that message? */
1636 int headers_only, /* eschew the message body? */
1637 int do_proto, /* do Citadel protocol responses? */
1638 int crlf, /* Use CRLF newlines instead of LF? */
1639 int flags /* should the bessage be exported clean? */
1643 cit_uint8_t ch, prev_ch;
1645 char display_name[256];
1647 char *nl; /* newline string */
1649 int subject_found = 0;
1652 /* Buffers needed for RFC822 translation. These are all filled
1653 * using functions that are bounds-checked, and therefore we can
1654 * make them substantially smaller than SIZ.
1661 char datestamp[100];
1663 CtdlLogPrintf(CTDL_DEBUG, "CtdlOutputPreLoadedMsg(TheMessage=%s, %d, %d, %d, %d\n",
1664 ((TheMessage == NULL) ? "NULL" : "not null"),
1665 mode, headers_only, do_proto, crlf);
1667 strcpy(mid, "unknown");
1668 nl = (crlf ? "\r\n" : "\n");
1670 if (!is_valid_message(TheMessage)) {
1671 CtdlLogPrintf(CTDL_ERR,
1672 "ERROR: invalid preloaded message for output\n");
1674 return(om_no_such_msg);
1677 /* Are we downloading a MIME component? */
1678 if (mode == MT_DOWNLOAD) {
1679 if (TheMessage->cm_format_type != FMT_RFC822) {
1681 cprintf("%d This is not a MIME message.\n",
1682 ERROR + ILLEGAL_VALUE);
1683 } else if (CC->download_fp != NULL) {
1684 if (do_proto) cprintf(
1685 "%d You already have a download open.\n",
1686 ERROR + RESOURCE_BUSY);
1688 /* Parse the message text component */
1689 mptr = TheMessage->cm_fields['M'];
1690 mime_parser(mptr, NULL, *mime_download, NULL, NULL, NULL, 0);
1691 /* If there's no file open by this time, the requested
1692 * section wasn't found, so print an error
1694 if (CC->download_fp == NULL) {
1695 if (do_proto) cprintf(
1696 "%d Section %s not found.\n",
1697 ERROR + FILE_NOT_FOUND,
1698 CC->download_desired_section);
1701 return((CC->download_fp != NULL) ? om_ok : om_mime_error);
1704 /* MT_SPEW_SECTION is like MT_DOWNLOAD except it outputs the whole MIME part
1705 * in a single server operation instead of opening a download file.
1707 if (mode == MT_SPEW_SECTION) {
1708 if (TheMessage->cm_format_type != FMT_RFC822) {
1710 cprintf("%d This is not a MIME message.\n",
1711 ERROR + ILLEGAL_VALUE);
1713 /* Parse the message text component */
1716 mptr = TheMessage->cm_fields['M'];
1717 mime_parser(mptr, NULL, *mime_spew_section, NULL, NULL, (void *)&found_it, 0);
1718 /* If section wasn't found, print an error
1721 if (do_proto) cprintf(
1722 "%d Section %s not found.\n",
1723 ERROR + FILE_NOT_FOUND,
1724 CC->download_desired_section);
1727 return((CC->download_fp != NULL) ? om_ok : om_mime_error);
1730 /* now for the user-mode message reading loops */
1731 if (do_proto) cprintf("%d msg:\n", LISTING_FOLLOWS);
1733 /* Does the caller want to skip the headers? */
1734 if (headers_only == HEADERS_NONE) goto START_TEXT;
1736 /* Tell the client which format type we're using. */
1737 if ( (mode == MT_CITADEL) && (do_proto) ) {
1738 cprintf("type=%d\n", TheMessage->cm_format_type);
1741 /* nhdr=yes means that we're only displaying headers, no body */
1742 if ( (TheMessage->cm_anon_type == MES_ANONONLY)
1743 && ((mode == MT_CITADEL) || (mode == MT_MIME))
1746 cprintf("nhdr=yes\n");
1749 /* begin header processing loop for Citadel message format */
1751 if ((mode == MT_CITADEL) || (mode == MT_MIME)) {
1753 safestrncpy(display_name, "<unknown>", sizeof display_name);
1754 if (TheMessage->cm_fields['A']) {
1755 strcpy(buf, TheMessage->cm_fields['A']);
1756 if (TheMessage->cm_anon_type == MES_ANONONLY) {
1757 safestrncpy(display_name, "****", sizeof display_name);
1759 else if (TheMessage->cm_anon_type == MES_ANONOPT) {
1760 safestrncpy(display_name, "anonymous", sizeof display_name);
1763 safestrncpy(display_name, buf, sizeof display_name);
1765 if ((is_room_aide())
1766 && ((TheMessage->cm_anon_type == MES_ANONONLY)
1767 || (TheMessage->cm_anon_type == MES_ANONOPT))) {
1768 size_t tmp = strlen(display_name);
1769 snprintf(&display_name[tmp],
1770 sizeof display_name - tmp,
1775 /* Don't show Internet address for users on the
1776 * local Citadel network.
1779 if (TheMessage->cm_fields['N'] != NULL)
1780 if (!IsEmptyStr(TheMessage->cm_fields['N']))
1781 if (haschar(TheMessage->cm_fields['N'], '.') == 0) {
1785 /* Now spew the header fields in the order we like them. */
1786 safestrncpy(allkeys, FORDER, sizeof allkeys);
1787 for (i=0; i<strlen(allkeys); ++i) {
1788 k = (int) allkeys[i];
1790 if ( (TheMessage->cm_fields[k] != NULL)
1791 && (msgkeys[k] != NULL) ) {
1792 if ((k == 'V') || (k == 'R') || (k == 'Y')) {
1793 sanitize_truncated_recipient(TheMessage->cm_fields[k]);
1796 if (do_proto) cprintf("%s=%s\n",
1800 else if ((k == 'F') && (suppress_f)) {
1803 /* Masquerade display name if needed */
1805 if (do_proto) cprintf("%s=%s\n",
1807 TheMessage->cm_fields[k]
1816 /* begin header processing loop for RFC822 transfer format */
1821 strcpy(snode, NODENAME);
1822 if (mode == MT_RFC822) {
1823 for (i = 0; i < 256; ++i) {
1824 if (TheMessage->cm_fields[i]) {
1825 mptr = mpptr = TheMessage->cm_fields[i];
1828 safestrncpy(luser, mptr, sizeof luser);
1829 safestrncpy(suser, mptr, sizeof suser);
1831 else if (i == 'Y') {
1832 if ((flags & QP_EADDR) != 0) {
1833 mptr = qp_encode_email_addrs(mptr);
1835 sanitize_truncated_recipient(mptr);
1836 cprintf("CC: %s%s", mptr, nl);
1838 else if (i == 'P') {
1839 cprintf("Return-Path: %s%s", mptr, nl);
1841 else if (i == 'L') {
1842 cprintf("List-ID: %s%s", mptr, nl);
1844 else if (i == 'V') {
1845 if ((flags & QP_EADDR) != 0)
1846 mptr = qp_encode_email_addrs(mptr);
1847 cprintf("Envelope-To: %s%s", mptr, nl);
1849 else if (i == 'U') {
1850 cprintf("Subject: %s%s", mptr, nl);
1854 safestrncpy(mid, mptr, sizeof mid);
1856 safestrncpy(fuser, mptr, sizeof fuser);
1857 /* else if (i == 'O')
1858 cprintf("X-Citadel-Room: %s%s",
1861 safestrncpy(snode, mptr, sizeof snode);
1864 if (haschar(mptr, '@') == 0)
1866 sanitize_truncated_recipient(mptr);
1867 cprintf("To: %s@%s", mptr, config.c_fqdn);
1872 if ((flags & QP_EADDR) != 0) {
1873 mptr = qp_encode_email_addrs(mptr);
1875 sanitize_truncated_recipient(mptr);
1876 cprintf("To: %s", mptr);
1880 else if (i == 'T') {
1881 datestring(datestamp, sizeof datestamp,
1882 atol(mptr), DATESTRING_RFC822);
1883 cprintf("Date: %s%s", datestamp, nl);
1885 else if (i == 'W') {
1886 cprintf("References: ");
1887 k = num_tokens(mptr, '|');
1888 for (j=0; j<k; ++j) {
1889 extract_token(buf, mptr, j, '|', sizeof buf);
1890 cprintf("<%s>", buf);
1903 if (subject_found == 0) {
1904 cprintf("Subject: (no subject)%s", nl);
1908 for (i=0; !IsEmptyStr(&suser[i]); ++i) {
1909 suser[i] = tolower(suser[i]);
1910 if (!isalnum(suser[i])) suser[i]='_';
1913 if (mode == MT_RFC822) {
1914 if (!strcasecmp(snode, NODENAME)) {
1915 safestrncpy(snode, FQDN, sizeof snode);
1918 /* Construct a fun message id */
1919 cprintf("Message-ID: <%s", mid);/// todo: this possibly breaks threadding mails.
1920 if (strchr(mid, '@')==NULL) {
1921 cprintf("@%s", snode);
1925 if (!is_room_aide() && (TheMessage->cm_anon_type == MES_ANONONLY)) {
1926 cprintf("From: \"----\" <x@x.org>%s", nl);
1928 else if (!is_room_aide() && (TheMessage->cm_anon_type == MES_ANONOPT)) {
1929 cprintf("From: \"anonymous\" <x@x.org>%s", nl);
1931 else if (!IsEmptyStr(fuser)) {
1932 cprintf("From: \"%s\" <%s>%s", luser, fuser, nl);
1935 cprintf("From: \"%s\" <%s@%s>%s", luser, suser, snode, nl);
1938 /* Blank line signifying RFC822 end-of-headers */
1939 if (TheMessage->cm_format_type != FMT_RFC822) {
1944 /* end header processing loop ... at this point, we're in the text */
1946 if (headers_only == HEADERS_FAST) goto DONE;
1947 mptr = TheMessage->cm_fields['M'];
1949 /* Tell the client about the MIME parts in this message */
1950 if (TheMessage->cm_format_type == FMT_RFC822) {
1951 if ( (mode == MT_CITADEL) || (mode == MT_MIME) ) {
1952 memset(&ma, 0, sizeof(struct ma_info));
1953 mime_parser(mptr, NULL,
1954 (do_proto ? *list_this_part : NULL),
1955 (do_proto ? *list_this_pref : NULL),
1956 (do_proto ? *list_this_suff : NULL),
1959 else if (mode == MT_RFC822) { /* unparsed RFC822 dump */
1960 char *start_of_text = NULL;
1961 start_of_text = strstr(mptr, "\n\r\n");
1962 if (start_of_text == NULL) start_of_text = strstr(mptr, "\n\n");
1963 if (start_of_text == NULL) start_of_text = mptr;
1965 start_of_text = strstr(start_of_text, "\n");
1970 int nllen = strlen(nl);
1972 while (ch=*mptr, ch!=0) {
1978 ((headers_only == HEADERS_NONE) && (mptr >= start_of_text))
1979 || ((headers_only == HEADERS_ONLY) && (mptr < start_of_text))
1980 || ((headers_only != HEADERS_NONE) && (headers_only != HEADERS_ONLY))
1983 sprintf(&outbuf[outlen], "%s", nl);
1987 outbuf[outlen++] = ch;
1991 if (flags & ESC_DOT)
1993 if ((prev_ch == 10) && (ch == '.') && ((*(mptr+1) == 13) || (*(mptr+1) == 10)))
1995 outbuf[outlen++] = '.';
2000 if (outlen > 1000) {
2001 client_write(outbuf, outlen);
2006 client_write(outbuf, outlen);
2014 if (headers_only == HEADERS_ONLY) {
2018 /* signify start of msg text */
2019 if ( (mode == MT_CITADEL) || (mode == MT_MIME) ) {
2020 if (do_proto) cprintf("text\n");
2023 /* If the format type on disk is 1 (fixed-format), then we want
2024 * everything to be output completely literally ... regardless of
2025 * what message transfer format is in use.
2027 if (TheMessage->cm_format_type == FMT_FIXED) {
2029 if (mode == MT_MIME) {
2030 cprintf("Content-type: text/plain\n\n");
2034 while (ch = *mptr++, ch > 0) {
2037 if ((ch == 10) || (buflen > 250)) {
2039 cprintf("%s%s", buf, nl);
2048 if (!IsEmptyStr(buf))
2049 cprintf("%s%s", buf, nl);
2052 /* If the message on disk is format 0 (Citadel vari-format), we
2053 * output using the formatter at 80 columns. This is the final output
2054 * form if the transfer format is RFC822, but if the transfer format
2055 * is Citadel proprietary, it'll still work, because the indentation
2056 * for new paragraphs is correct and the client will reformat the
2057 * message to the reader's screen width.
2059 if (TheMessage->cm_format_type == FMT_CITADEL) {
2060 if (mode == MT_MIME) {
2061 cprintf("Content-type: text/x-citadel-variformat\n\n");
2063 memfmout(mptr, 0, nl);
2066 /* If the message on disk is format 4 (MIME), we've gotta hand it
2067 * off to the MIME parser. The client has already been told that
2068 * this message is format 1 (fixed format), so the callback function
2069 * we use will display those parts as-is.
2071 if (TheMessage->cm_format_type == FMT_RFC822) {
2072 memset(&ma, 0, sizeof(struct ma_info));
2074 if (mode == MT_MIME) {
2075 ma.use_fo_hooks = 0;
2076 strcpy(ma.chosen_part, "1");
2077 ma.chosen_pref = 9999;
2078 mime_parser(mptr, NULL,
2079 *choose_preferred, *fixed_output_pre,
2080 *fixed_output_post, (void *)&ma, 0);
2081 mime_parser(mptr, NULL,
2082 *output_preferred, NULL, NULL, (void *)&ma, CC->msg4_dont_decode);
2085 ma.use_fo_hooks = 1;
2086 mime_parser(mptr, NULL,
2087 *fixed_output, *fixed_output_pre,
2088 *fixed_output_post, (void *)&ma, 0);
2093 DONE: /* now we're done */
2094 if (do_proto) cprintf("000\n");
2101 * display a message (mode 0 - Citadel proprietary)
2103 void cmd_msg0(char *cmdbuf)
2106 int headers_only = HEADERS_ALL;
2108 msgid = extract_long(cmdbuf, 0);
2109 headers_only = extract_int(cmdbuf, 1);
2111 CtdlOutputMsg(msgid, MT_CITADEL, headers_only, 1, 0, NULL, 0);
2117 * display a message (mode 2 - RFC822)
2119 void cmd_msg2(char *cmdbuf)
2122 int headers_only = HEADERS_ALL;
2124 msgid = extract_long(cmdbuf, 0);
2125 headers_only = extract_int(cmdbuf, 1);
2127 CtdlOutputMsg(msgid, MT_RFC822, headers_only, 1, 1, NULL, 0);
2133 * display a message (mode 3 - IGnet raw format - internal programs only)
2135 void cmd_msg3(char *cmdbuf)
2138 struct CtdlMessage *msg = NULL;
2141 if (CC->internal_pgm == 0) {
2142 cprintf("%d This command is for internal programs only.\n",
2143 ERROR + HIGHER_ACCESS_REQUIRED);
2147 msgnum = extract_long(cmdbuf, 0);
2148 msg = CtdlFetchMessage(msgnum, 1);
2150 cprintf("%d Message %ld not found.\n",
2151 ERROR + MESSAGE_NOT_FOUND, msgnum);
2155 serialize_message(&smr, msg);
2156 CtdlFreeMessage(msg);
2159 cprintf("%d Unable to serialize message\n",
2160 ERROR + INTERNAL_ERROR);
2164 cprintf("%d %ld\n", BINARY_FOLLOWS, (long)smr.len);
2165 client_write((char *)smr.ser, (int)smr.len);
2172 * Display a message using MIME content types
2174 void cmd_msg4(char *cmdbuf)
2179 msgid = extract_long(cmdbuf, 0);
2180 extract_token(section, cmdbuf, 1, '|', sizeof section);
2181 CtdlOutputMsg(msgid, MT_MIME, 0, 1, 0, (section[0] ? section : NULL) , 0);
2187 * Client tells us its preferred message format(s)
2189 void cmd_msgp(char *cmdbuf)
2191 if (!strcasecmp(cmdbuf, "dont_decode")) {
2192 CC->msg4_dont_decode = 1;
2193 cprintf("%d MSG4 will not pre-decode messages.\n", CIT_OK);
2196 safestrncpy(CC->preferred_formats, cmdbuf, sizeof(CC->preferred_formats));
2197 cprintf("%d Preferred MIME formats have been set.\n", CIT_OK);
2203 * Open a component of a MIME message as a download file
2205 void cmd_opna(char *cmdbuf)
2208 char desired_section[128];
2210 msgid = extract_long(cmdbuf, 0);
2211 extract_token(desired_section, cmdbuf, 1, '|', sizeof desired_section);
2212 safestrncpy(CC->download_desired_section, desired_section,
2213 sizeof CC->download_desired_section);
2214 CtdlOutputMsg(msgid, MT_DOWNLOAD, 0, 1, 1, NULL, 0);
2219 * Open a component of a MIME message and transmit it all at once
2221 void cmd_dlat(char *cmdbuf)
2224 char desired_section[128];
2226 msgid = extract_long(cmdbuf, 0);
2227 extract_token(desired_section, cmdbuf, 1, '|', sizeof desired_section);
2228 safestrncpy(CC->download_desired_section, desired_section,
2229 sizeof CC->download_desired_section);
2230 CtdlOutputMsg(msgid, MT_SPEW_SECTION, 0, 1, 1, NULL, 0);
2235 * Save one or more message pointers into a specified room
2236 * (Returns 0 for success, nonzero for failure)
2237 * roomname may be NULL to use the current room
2239 * Note that the 'supplied_msg' field may be set to NULL, in which case
2240 * the message will be fetched from disk, by number, if we need to perform
2241 * replication checks. This adds an additional database read, so if the
2242 * caller already has the message in memory then it should be supplied. (Obviously
2243 * this mode of operation only works if we're saving a single message.)
2245 int CtdlSaveMsgPointersInRoom(char *roomname, long newmsgidlist[], int num_newmsgs,
2246 int do_repl_check, struct CtdlMessage *supplied_msg)
2249 char hold_rm[ROOMNAMELEN];
2250 struct cdbdata *cdbfr;
2253 long highest_msg = 0L;
2256 struct CtdlMessage *msg = NULL;
2258 long *msgs_to_be_merged = NULL;
2259 int num_msgs_to_be_merged = 0;
2261 CtdlLogPrintf(CTDL_DEBUG,
2262 "CtdlSaveMsgPointersInRoom(room=%s, num_msgs=%d, repl=%d)\n",
2263 roomname, num_newmsgs, do_repl_check);
2265 strcpy(hold_rm, CC->room.QRname);
2268 if (newmsgidlist == NULL) return(ERROR + INTERNAL_ERROR);
2269 if (num_newmsgs < 1) return(ERROR + INTERNAL_ERROR);
2270 if (num_newmsgs > 1) supplied_msg = NULL;
2272 /* Now the regular stuff */
2273 if (lgetroom(&CC->room,
2274 ((roomname != NULL) ? roomname : CC->room.QRname) )
2276 CtdlLogPrintf(CTDL_ERR, "No such room <%s>\n", roomname);
2277 return(ERROR + ROOM_NOT_FOUND);
2281 msgs_to_be_merged = malloc(sizeof(long) * num_newmsgs);
2282 num_msgs_to_be_merged = 0;
2285 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long));
2286 if (cdbfr == NULL) {
2290 msglist = (long *) cdbfr->ptr;
2291 cdbfr->ptr = NULL; /* CtdlSaveMsgPointerInRoom() now owns this memory */
2292 num_msgs = cdbfr->len / sizeof(long);
2297 /* Create a list of msgid's which were supplied by the caller, but do
2298 * not already exist in the target room. It is absolutely taboo to
2299 * have more than one reference to the same message in a room.
2301 for (i=0; i<num_newmsgs; ++i) {
2303 if (num_msgs > 0) for (j=0; j<num_msgs; ++j) {
2304 if (msglist[j] == newmsgidlist[i]) {
2309 msgs_to_be_merged[num_msgs_to_be_merged++] = newmsgidlist[i];
2313 CtdlLogPrintf(9, "%d unique messages to be merged\n", num_msgs_to_be_merged);
2316 * Now merge the new messages
2318 msglist = realloc(msglist, (sizeof(long) * (num_msgs + num_msgs_to_be_merged)) );
2319 if (msglist == NULL) {
2320 CtdlLogPrintf(CTDL_ALERT, "ERROR: can't realloc message list!\n");
2322 memcpy(&msglist[num_msgs], msgs_to_be_merged, (sizeof(long) * num_msgs_to_be_merged) );
2323 num_msgs += num_msgs_to_be_merged;
2325 /* Sort the message list, so all the msgid's are in order */
2326 num_msgs = sort_msglist(msglist, num_msgs);
2328 /* Determine the highest message number */
2329 highest_msg = msglist[num_msgs - 1];
2331 /* Write it back to disk. */
2332 cdb_store(CDB_MSGLISTS, &CC->room.QRnumber, (int)sizeof(long),
2333 msglist, (int)(num_msgs * sizeof(long)));
2335 /* Free up the memory we used. */
2338 /* Update the highest-message pointer and unlock the room. */
2339 CC->room.QRhighest = highest_msg;
2340 lputroom(&CC->room);
2342 /* Perform replication checks if necessary */
2343 if ( (DoesThisRoomNeedEuidIndexing(&CC->room)) && (do_repl_check) ) {
2344 CtdlLogPrintf(CTDL_DEBUG, "CtdlSaveMsgPointerInRoom() doing repl checks\n");
2346 for (i=0; i<num_msgs_to_be_merged; ++i) {
2347 msgid = msgs_to_be_merged[i];
2349 if (supplied_msg != NULL) {
2353 msg = CtdlFetchMessage(msgid, 0);
2357 ReplicationChecks(msg);
2359 /* If the message has an Exclusive ID, index that... */
2360 if (msg->cm_fields['E'] != NULL) {
2361 index_message_by_euid(msg->cm_fields['E'], &CC->room, msgid);
2364 /* Free up the memory we may have allocated */
2365 if (msg != supplied_msg) {
2366 CtdlFreeMessage(msg);
2374 CtdlLogPrintf(CTDL_DEBUG, "CtdlSaveMsgPointerInRoom() skips repl checks\n");
2377 /* Submit this room for processing by hooks */
2378 PerformRoomHooks(&CC->room);
2380 /* Go back to the room we were in before we wandered here... */
2381 getroom(&CC->room, hold_rm);
2383 /* Bump the reference count for all messages which were merged */
2384 for (i=0; i<num_msgs_to_be_merged; ++i) {
2385 AdjRefCount(msgs_to_be_merged[i], +1);
2388 /* Free up memory... */
2389 if (msgs_to_be_merged != NULL) {
2390 free(msgs_to_be_merged);
2393 /* Return success. */
2399 * This is the same as CtdlSaveMsgPointersInRoom() but it only accepts
2402 int CtdlSaveMsgPointerInRoom(char *roomname, long msgid,
2403 int do_repl_check, struct CtdlMessage *supplied_msg)
2405 return CtdlSaveMsgPointersInRoom(roomname, &msgid, 1, do_repl_check, supplied_msg);
2412 * Message base operation to save a new message to the message store
2413 * (returns new message number)
2415 * This is the back end for CtdlSubmitMsg() and should not be directly
2416 * called by server-side modules.
2419 long send_message(struct CtdlMessage *msg) {
2427 /* Get a new message number */
2428 newmsgid = get_new_message_number();
2429 snprintf(msgidbuf, sizeof msgidbuf, "%010ld@%s", newmsgid, config.c_fqdn);
2431 /* Generate an ID if we don't have one already */
2432 if (msg->cm_fields['I']==NULL) {
2433 msg->cm_fields['I'] = strdup(msgidbuf);
2436 /* If the message is big, set its body aside for storage elsewhere */
2437 if (msg->cm_fields['M'] != NULL) {
2438 if (strlen(msg->cm_fields['M']) > BIGMSG) {
2440 holdM = msg->cm_fields['M'];
2441 msg->cm_fields['M'] = NULL;
2445 /* Serialize our data structure for storage in the database */
2446 serialize_message(&smr, msg);
2449 msg->cm_fields['M'] = holdM;
2453 cprintf("%d Unable to serialize message\n",
2454 ERROR + INTERNAL_ERROR);
2458 /* Write our little bundle of joy into the message base */
2459 if (cdb_store(CDB_MSGMAIN, &newmsgid, (int)sizeof(long),
2460 smr.ser, smr.len) < 0) {
2461 CtdlLogPrintf(CTDL_ERR, "Can't store message\n");
2465 cdb_store(CDB_BIGMSGS,
2475 /* Free the memory we used for the serialized message */
2478 /* Return the *local* message ID to the caller
2479 * (even if we're storing an incoming network message)
2487 * Serialize a struct CtdlMessage into the format used on disk and network.
2489 * This function loads up a "struct ser_ret" (defined in server.h) which
2490 * contains the length of the serialized message and a pointer to the
2491 * serialized message in memory. THE LATTER MUST BE FREED BY THE CALLER.
2493 void serialize_message(struct ser_ret *ret, /* return values */
2494 struct CtdlMessage *msg) /* unserialized msg */
2496 size_t wlen, fieldlen;
2498 static char *forder = FORDER;
2501 * Check for valid message format
2503 if (is_valid_message(msg) == 0) {
2504 CtdlLogPrintf(CTDL_ERR, "serialize_message() aborting due to invalid message\n");
2511 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL)
2512 ret->len = ret->len +
2513 strlen(msg->cm_fields[(int)forder[i]]) + 2;
2515 ret->ser = malloc(ret->len);
2516 if (ret->ser == NULL) {
2517 CtdlLogPrintf(CTDL_ERR, "serialize_message() malloc(%ld) failed: %s\n",
2518 (long)ret->len, strerror(errno));
2525 ret->ser[1] = msg->cm_anon_type;
2526 ret->ser[2] = msg->cm_format_type;
2529 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL) {
2530 fieldlen = strlen(msg->cm_fields[(int)forder[i]]);
2531 ret->ser[wlen++] = (char)forder[i];
2532 safestrncpy((char *)&ret->ser[wlen], msg->cm_fields[(int)forder[i]], fieldlen+1);
2533 wlen = wlen + fieldlen + 1;
2535 if (ret->len != wlen) CtdlLogPrintf(CTDL_ERR, "ERROR: len=%ld wlen=%ld\n",
2536 (long)ret->len, (long)wlen);
2543 * Serialize a struct CtdlMessage into the format used on disk and network.
2545 * This function loads up a "struct ser_ret" (defined in server.h) which
2546 * contains the length of the serialized message and a pointer to the
2547 * serialized message in memory. THE LATTER MUST BE FREED BY THE CALLER.
2549 void dump_message(struct CtdlMessage *msg, /* unserialized msg */
2550 long Siz) /* how many chars ? */
2554 static char *forder = FORDER;
2558 * Check for valid message format
2560 if (is_valid_message(msg) == 0) {
2561 CtdlLogPrintf(CTDL_ERR, "dump_message() aborting due to invalid message\n");
2565 buf = (char*) malloc (Siz + 1);
2569 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL) {
2570 snprintf (buf, Siz, " msg[%c] = %s ...\n", (char) forder[i],
2571 msg->cm_fields[(int)forder[i]]);
2572 client_write (buf, strlen(buf));
2581 * Check to see if any messages already exist in the current room which
2582 * carry the same Exclusive ID as this one. If any are found, delete them.
2584 void ReplicationChecks(struct CtdlMessage *msg) {
2585 long old_msgnum = (-1L);
2587 if (DoesThisRoomNeedEuidIndexing(&CC->room) == 0) return;
2589 CtdlLogPrintf(CTDL_DEBUG, "Performing replication checks in <%s>\n",
2592 /* No exclusive id? Don't do anything. */
2593 if (msg == NULL) return;
2594 if (msg->cm_fields['E'] == NULL) return;
2595 if (IsEmptyStr(msg->cm_fields['E'])) return;
2596 /*CtdlLogPrintf(CTDL_DEBUG, "Exclusive ID: <%s> for room <%s>\n",
2597 msg->cm_fields['E'], CC->room.QRname);*/
2599 old_msgnum = locate_message_by_euid(msg->cm_fields['E'], &CC->room);
2600 if (old_msgnum > 0L) {
2601 CtdlLogPrintf(CTDL_DEBUG, "ReplicationChecks() replacing message %ld\n", old_msgnum);
2602 CtdlDeleteMessages(CC->room.QRname, &old_msgnum, 1, "");
2609 * Save a message to disk and submit it into the delivery system.
2611 long CtdlSubmitMsg(struct CtdlMessage *msg, /* message to save */
2612 struct recptypes *recps, /* recipients (if mail) */
2613 char *force, /* force a particular room? */
2614 int flags /* should the bessage be exported clean? */
2616 char submit_filename[128];
2617 char generated_timestamp[32];
2618 char hold_rm[ROOMNAMELEN];
2619 char actual_rm[ROOMNAMELEN];
2620 char force_room[ROOMNAMELEN];
2621 char content_type[SIZ]; /* We have to learn this */
2622 char recipient[SIZ];
2625 struct ctdluser userbuf;
2627 struct MetaData smi;
2628 FILE *network_fp = NULL;
2629 static int seqnum = 1;
2630 struct CtdlMessage *imsg = NULL;
2632 size_t instr_alloc = 0;
2634 char *hold_R, *hold_D;
2635 char *collected_addresses = NULL;
2636 struct addresses_to_be_filed *aptr = NULL;
2637 char *saved_rfc822_version = NULL;
2638 int qualified_for_journaling = 0;
2639 struct CitContext *CCC = CC; /* CachedCitContext - performance boost */
2640 char bounce_to[1024] = "";
2643 CtdlLogPrintf(CTDL_DEBUG, "CtdlSubmitMsg() called\n");
2644 if (is_valid_message(msg) == 0) return(-1); /* self check */
2646 /* If this message has no timestamp, we take the liberty of
2647 * giving it one, right now.
2649 if (msg->cm_fields['T'] == NULL) {
2650 snprintf(generated_timestamp, sizeof generated_timestamp, "%ld", (long)time(NULL));
2651 msg->cm_fields['T'] = strdup(generated_timestamp);
2654 /* If this message has no path, we generate one.
2656 if (msg->cm_fields['P'] == NULL) {
2657 if (msg->cm_fields['A'] != NULL) {
2658 msg->cm_fields['P'] = strdup(msg->cm_fields['A']);
2659 for (a=0; !IsEmptyStr(&msg->cm_fields['P'][a]); ++a) {
2660 if (isspace(msg->cm_fields['P'][a])) {
2661 msg->cm_fields['P'][a] = ' ';
2666 msg->cm_fields['P'] = strdup("unknown");
2670 if (force == NULL) {
2671 strcpy(force_room, "");
2674 strcpy(force_room, force);
2677 /* Learn about what's inside, because it's what's inside that counts */
2678 if (msg->cm_fields['M'] == NULL) {
2679 CtdlLogPrintf(CTDL_ERR, "ERROR: attempt to save message with NULL body\n");
2683 switch (msg->cm_format_type) {
2685 strcpy(content_type, "text/x-citadel-variformat");
2688 strcpy(content_type, "text/plain");
2691 strcpy(content_type, "text/plain");
2692 mptr = bmstrcasestr(msg->cm_fields['M'], "Content-type:");
2695 safestrncpy(content_type, &mptr[13], sizeof content_type);
2696 striplt(content_type);
2697 aptr = content_type;
2698 while (!IsEmptyStr(aptr)) {
2710 /* Goto the correct room */
2711 CtdlLogPrintf(CTDL_DEBUG, "Selected room %s\n", (recps) ? CCC->room.QRname : SENTITEMS);
2712 strcpy(hold_rm, CCC->room.QRname);
2713 strcpy(actual_rm, CCC->room.QRname);
2714 if (recps != NULL) {
2715 strcpy(actual_rm, SENTITEMS);
2718 /* If the user is a twit, move to the twit room for posting */
2720 if (CCC->user.axlevel == 2) {
2721 strcpy(hold_rm, actual_rm);
2722 strcpy(actual_rm, config.c_twitroom);
2723 CtdlLogPrintf(CTDL_DEBUG, "Diverting to twit room\n");
2727 /* ...or if this message is destined for Aide> then go there. */
2728 if (!IsEmptyStr(force_room)) {
2729 strcpy(actual_rm, force_room);
2732 CtdlLogPrintf(CTDL_DEBUG, "Final selection: %s\n", actual_rm);
2733 if (strcasecmp(actual_rm, CCC->room.QRname)) {
2734 /* getroom(&CCC->room, actual_rm); */
2735 usergoto(actual_rm, 0, 1, NULL, NULL);
2739 * If this message has no O (room) field, generate one.
2741 if (msg->cm_fields['O'] == NULL) {
2742 msg->cm_fields['O'] = strdup(CCC->room.QRname);
2745 /* Perform "before save" hooks (aborting if any return nonzero) */
2746 CtdlLogPrintf(CTDL_DEBUG, "Performing before-save hooks\n");
2747 if (PerformMessageHooks(msg, EVT_BEFORESAVE) > 0) return(-3);
2750 * If this message has an Exclusive ID, and the room is replication
2751 * checking enabled, then do replication checks.
2753 if (DoesThisRoomNeedEuidIndexing(&CCC->room)) {
2754 ReplicationChecks(msg);
2757 /* Save it to disk */
2758 CtdlLogPrintf(CTDL_DEBUG, "Saving to disk\n");
2759 newmsgid = send_message(msg);
2760 if (newmsgid <= 0L) return(-5);
2762 /* Write a supplemental message info record. This doesn't have to
2763 * be a critical section because nobody else knows about this message
2766 CtdlLogPrintf(CTDL_DEBUG, "Creating MetaData record\n");
2767 memset(&smi, 0, sizeof(struct MetaData));
2768 smi.meta_msgnum = newmsgid;
2769 smi.meta_refcount = 0;
2770 safestrncpy(smi.meta_content_type, content_type,
2771 sizeof smi.meta_content_type);
2774 * Measure how big this message will be when rendered as RFC822.
2775 * We do this for two reasons:
2776 * 1. We need the RFC822 length for the new metadata record, so the
2777 * POP and IMAP services don't have to calculate message lengths
2778 * while the user is waiting (multiplied by potentially hundreds
2779 * or thousands of messages).
2780 * 2. If journaling is enabled, we will need an RFC822 version of the
2781 * message to attach to the journalized copy.
2783 if (CCC->redirect_buffer != NULL) {
2784 CtdlLogPrintf(CTDL_ALERT, "CCC->redirect_buffer is not NULL during message submission!\n");
2787 CCC->redirect_buffer = malloc(SIZ);
2788 CCC->redirect_len = 0;
2789 CCC->redirect_alloc = SIZ;
2790 CtdlOutputPreLoadedMsg(msg, MT_RFC822, HEADERS_ALL, 0, 1, QP_EADDR);
2791 smi.meta_rfc822_length = CCC->redirect_len;
2792 saved_rfc822_version = CCC->redirect_buffer;
2793 CCC->redirect_buffer = NULL;
2794 CCC->redirect_len = 0;
2795 CCC->redirect_alloc = 0;
2799 /* Now figure out where to store the pointers */
2800 CtdlLogPrintf(CTDL_DEBUG, "Storing pointers\n");
2802 /* If this is being done by the networker delivering a private
2803 * message, we want to BYPASS saving the sender's copy (because there
2804 * is no local sender; it would otherwise go to the Trashcan).
2806 if ((!CCC->internal_pgm) || (recps == NULL)) {
2807 if (CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 1, msg) != 0) {
2808 CtdlLogPrintf(CTDL_ERR, "ERROR saving message pointer!\n");
2809 CtdlSaveMsgPointerInRoom(config.c_aideroom, newmsgid, 0, msg);
2813 /* For internet mail, drop a copy in the outbound queue room */
2814 if ((recps != NULL) && (recps->num_internet > 0)) {
2815 CtdlSaveMsgPointerInRoom(SMTP_SPOOLOUT_ROOM, newmsgid, 0, msg);
2818 /* If other rooms are specified, drop them there too. */
2819 if ((recps != NULL) && (recps->num_room > 0))
2820 for (i=0; i<num_tokens(recps->recp_room, '|'); ++i) {
2821 extract_token(recipient, recps->recp_room, i,
2822 '|', sizeof recipient);
2823 CtdlLogPrintf(CTDL_DEBUG, "Delivering to room <%s>\n", recipient);
2824 CtdlSaveMsgPointerInRoom(recipient, newmsgid, 0, msg);
2827 /* Bump this user's messages posted counter. */
2828 CtdlLogPrintf(CTDL_DEBUG, "Updating user\n");
2829 lgetuser(&CCC->user, CCC->curr_user);
2830 CCC->user.posted = CCC->user.posted + 1;
2831 lputuser(&CCC->user);
2833 /* Decide where bounces need to be delivered */
2834 if ((recps != NULL) && (recps->bounce_to != NULL)) {
2835 safestrncpy(bounce_to, recps->bounce_to, sizeof bounce_to);
2837 else if (CCC->logged_in) {
2838 snprintf(bounce_to, sizeof bounce_to, "%s@%s", CCC->user.fullname, config.c_nodename);
2841 snprintf(bounce_to, sizeof bounce_to, "%s@%s", msg->cm_fields['A'], msg->cm_fields['N']);
2844 /* If this is private, local mail, make a copy in the
2845 * recipient's mailbox and bump the reference count.
2847 if ((recps != NULL) && (recps->num_local > 0))
2848 for (i=0; i<num_tokens(recps->recp_local, '|'); ++i) {
2849 extract_token(recipient, recps->recp_local, i,
2850 '|', sizeof recipient);
2851 CtdlLogPrintf(CTDL_DEBUG, "Delivering private local mail to <%s>\n",
2853 if (getuser(&userbuf, recipient) == 0) {
2854 // Add a flag so the Funambol module knows its mail
2855 msg->cm_fields['W'] = strdup(recipient);
2856 MailboxName(actual_rm, sizeof actual_rm, &userbuf, MAILROOM);
2857 CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 0, msg);
2858 BumpNewMailCounter(userbuf.usernum);
2859 if (!IsEmptyStr(config.c_funambol_host) || !IsEmptyStr(config.c_pager_program)) {
2860 /* Generate a instruction message for the Funambol notification
2861 * server, in the same style as the SMTP queue
2864 instr = malloc(instr_alloc);
2865 snprintf(instr, instr_alloc,
2866 "Content-type: %s\n\nmsgid|%ld\nsubmitted|%ld\n"
2868 SPOOLMIME, newmsgid, (long)time(NULL),
2872 imsg = malloc(sizeof(struct CtdlMessage));
2873 memset(imsg, 0, sizeof(struct CtdlMessage));
2874 imsg->cm_magic = CTDLMESSAGE_MAGIC;
2875 imsg->cm_anon_type = MES_NORMAL;
2876 imsg->cm_format_type = FMT_RFC822;
2877 imsg->cm_fields['A'] = strdup("Citadel");
2878 imsg->cm_fields['J'] = strdup("do not journal");
2879 imsg->cm_fields['M'] = instr; /* imsg owns this memory now */
2880 imsg->cm_fields['W'] = strdup(recipient);
2881 CtdlSubmitMsg(imsg, NULL, FNBL_QUEUE_ROOM, 0);
2882 CtdlFreeMessage(imsg);
2886 CtdlLogPrintf(CTDL_DEBUG, "No user <%s>\n", recipient);
2887 CtdlSaveMsgPointerInRoom(config.c_aideroom,
2892 /* Perform "after save" hooks */
2893 CtdlLogPrintf(CTDL_DEBUG, "Performing after-save hooks\n");
2894 PerformMessageHooks(msg, EVT_AFTERSAVE);
2896 /* For IGnet mail, we have to save a new copy into the spooler for
2897 * each recipient, with the R and D fields set to the recipient and
2898 * destination-node. This has two ugly side effects: all other
2899 * recipients end up being unlisted in this recipient's copy of the
2900 * message, and it has to deliver multiple messages to the same
2901 * node. We'll revisit this again in a year or so when everyone has
2902 * a network spool receiver that can handle the new style messages.
2904 if ((recps != NULL) && (recps->num_ignet > 0))
2905 for (i=0; i<num_tokens(recps->recp_ignet, '|'); ++i) {
2906 extract_token(recipient, recps->recp_ignet, i,
2907 '|', sizeof recipient);
2909 hold_R = msg->cm_fields['R'];
2910 hold_D = msg->cm_fields['D'];
2911 msg->cm_fields['R'] = malloc(SIZ);
2912 msg->cm_fields['D'] = malloc(128);
2913 extract_token(msg->cm_fields['R'], recipient, 0, '@', SIZ);
2914 extract_token(msg->cm_fields['D'], recipient, 1, '@', 128);
2916 serialize_message(&smr, msg);
2918 snprintf(submit_filename, sizeof submit_filename,
2919 "%s/netmail.%04lx.%04x.%04x",
2921 (long) getpid(), CCC->cs_pid, ++seqnum);
2922 network_fp = fopen(submit_filename, "wb+");
2923 if (network_fp != NULL) {
2924 fwrite(smr.ser, smr.len, 1, network_fp);
2930 free(msg->cm_fields['R']);
2931 free(msg->cm_fields['D']);
2932 msg->cm_fields['R'] = hold_R;
2933 msg->cm_fields['D'] = hold_D;
2936 /* Go back to the room we started from */
2937 CtdlLogPrintf(CTDL_DEBUG, "Returning to original room %s\n", hold_rm);
2938 if (strcasecmp(hold_rm, CCC->room.QRname))
2939 usergoto(hold_rm, 0, 1, NULL, NULL);
2941 /* For internet mail, generate delivery instructions.
2942 * Yes, this is recursive. Deal with it. Infinite recursion does
2943 * not happen because the delivery instructions message does not
2944 * contain a recipient.
2946 if ((recps != NULL) && (recps->num_internet > 0)) {
2947 CtdlLogPrintf(CTDL_DEBUG, "Generating delivery instructions\n");
2949 instr = malloc(instr_alloc);
2950 snprintf(instr, instr_alloc,
2951 "Content-type: %s\n\nmsgid|%ld\nsubmitted|%ld\n"
2953 SPOOLMIME, newmsgid, (long)time(NULL),
2957 if (recps->envelope_from != NULL) {
2958 tmp = strlen(instr);
2959 snprintf(&instr[tmp], instr_alloc-tmp, "envelope_from|%s\n", recps->envelope_from);
2962 for (i=0; i<num_tokens(recps->recp_internet, '|'); ++i) {
2963 tmp = strlen(instr);
2964 extract_token(recipient, recps->recp_internet, i, '|', sizeof recipient);
2965 if ((tmp + strlen(recipient) + 32) > instr_alloc) {
2966 instr_alloc = instr_alloc * 2;
2967 instr = realloc(instr, instr_alloc);
2969 snprintf(&instr[tmp], instr_alloc - tmp, "remote|%s|0||\n", recipient);
2972 imsg = malloc(sizeof(struct CtdlMessage));
2973 memset(imsg, 0, sizeof(struct CtdlMessage));
2974 imsg->cm_magic = CTDLMESSAGE_MAGIC;
2975 imsg->cm_anon_type = MES_NORMAL;
2976 imsg->cm_format_type = FMT_RFC822;
2977 imsg->cm_fields['A'] = strdup("Citadel");
2978 imsg->cm_fields['J'] = strdup("do not journal");
2979 imsg->cm_fields['M'] = instr; /* imsg owns this memory now */
2980 CtdlSubmitMsg(imsg, NULL, SMTP_SPOOLOUT_ROOM, QP_EADDR);
2981 CtdlFreeMessage(imsg);
2985 * Any addresses to harvest for someone's address book?
2987 if ( (CCC->logged_in) && (recps != NULL) ) {
2988 collected_addresses = harvest_collected_addresses(msg);
2991 if (collected_addresses != NULL) {
2992 aptr = (struct addresses_to_be_filed *)
2993 malloc(sizeof(struct addresses_to_be_filed));
2994 MailboxName(actual_rm, sizeof actual_rm,
2995 &CCC->user, USERCONTACTSROOM);
2996 aptr->roomname = strdup(actual_rm);
2997 aptr->collected_addresses = collected_addresses;
2998 begin_critical_section(S_ATBF);
3001 end_critical_section(S_ATBF);
3005 * Determine whether this message qualifies for journaling.
3007 if (msg->cm_fields['J'] != NULL) {
3008 qualified_for_journaling = 0;
3011 if (recps == NULL) {
3012 qualified_for_journaling = config.c_journal_pubmsgs;
3014 else if (recps->num_local + recps->num_ignet + recps->num_internet > 0) {
3015 qualified_for_journaling = config.c_journal_email;
3018 qualified_for_journaling = config.c_journal_pubmsgs;
3023 * Do we have to perform journaling? If so, hand off the saved
3024 * RFC822 version will be handed off to the journaler for background
3025 * submit. Otherwise, we have to free the memory ourselves.
3027 if (saved_rfc822_version != NULL) {
3028 if (qualified_for_journaling) {
3029 JournalBackgroundSubmit(msg, saved_rfc822_version, recps);
3032 free(saved_rfc822_version);
3045 * Convenience function for generating small administrative messages.
3047 void quickie_message(const char *from, const char *fromaddr, char *to, char *room, const char *text,
3048 int format_type, const char *subject)
3050 struct CtdlMessage *msg;
3051 struct recptypes *recp = NULL;
3053 msg = malloc(sizeof(struct CtdlMessage));
3054 memset(msg, 0, sizeof(struct CtdlMessage));
3055 msg->cm_magic = CTDLMESSAGE_MAGIC;
3056 msg->cm_anon_type = MES_NORMAL;
3057 msg->cm_format_type = format_type;
3060 msg->cm_fields['A'] = strdup(from);
3062 else if (fromaddr != NULL) {
3063 msg->cm_fields['A'] = strdup(fromaddr);
3064 if (strchr(msg->cm_fields['A'], '@')) {
3065 *strchr(msg->cm_fields['A'], '@') = 0;
3069 msg->cm_fields['A'] = strdup("Citadel");
3072 if (fromaddr != NULL) msg->cm_fields['F'] = strdup(fromaddr);
3073 if (room != NULL) msg->cm_fields['O'] = strdup(room);
3074 msg->cm_fields['N'] = strdup(NODENAME);
3076 msg->cm_fields['R'] = strdup(to);
3077 recp = validate_recipients(to, NULL, 0);
3079 if (subject != NULL) {
3080 msg->cm_fields['U'] = strdup(subject);
3082 msg->cm_fields['M'] = strdup(text);
3084 CtdlSubmitMsg(msg, recp, room, 0);
3085 CtdlFreeMessage(msg);
3086 if (recp != NULL) free_recipients(recp);
3092 * Back end function used by CtdlMakeMessage() and similar functions
3094 char *CtdlReadMessageBody(char *terminator, /* token signalling EOT */
3095 size_t maxlen, /* maximum message length */
3096 char *exist, /* if non-null, append to it;
3097 exist is ALWAYS freed */
3098 int crlf, /* CRLF newlines instead of LF */
3099 int sock /* socket handle or 0 for this session's client socket */
3103 size_t message_len = 0;
3104 size_t buffer_len = 0;
3111 if (exist == NULL) {
3118 message_len = strlen(exist);
3119 buffer_len = message_len + 4096;
3120 m = realloc(exist, buffer_len);
3127 /* Do we need to change leading ".." to "." for SMTP escaping? */
3128 if (!strcmp(terminator, ".")) {
3132 /* flush the input if we have nowhere to store it */
3137 /* read in the lines of message text one by one */
3140 if (sock_getln(sock, buf, (sizeof buf - 3)) < 0) finished = 1;
3143 if (client_getln(buf, (sizeof buf - 3)) < 1) finished = 1;
3145 if (!strcmp(buf, terminator)) finished = 1;
3147 strcat(buf, "\r\n");
3153 /* Unescape SMTP-style input of two dots at the beginning of the line */
3155 if (!strncmp(buf, "..", 2)) {
3156 strcpy(buf, &buf[1]);
3160 if ( (!flushing) && (!finished) ) {
3161 /* Measure the line */
3162 linelen = strlen(buf);
3164 /* augment the buffer if we have to */
3165 if ((message_len + linelen) >= buffer_len) {
3166 ptr = realloc(m, (buffer_len * 2) );
3167 if (ptr == NULL) { /* flush if can't allocate */
3170 buffer_len = (buffer_len * 2);
3172 CtdlLogPrintf(CTDL_DEBUG, "buffer_len is now %ld\n", (long)buffer_len);
3176 /* Add the new line to the buffer. NOTE: this loop must avoid
3177 * using functions like strcat() and strlen() because they
3178 * traverse the entire buffer upon every call, and doing that
3179 * for a multi-megabyte message slows it down beyond usability.
3181 strcpy(&m[message_len], buf);
3182 message_len += linelen;
3185 /* if we've hit the max msg length, flush the rest */
3186 if (message_len >= maxlen) flushing = 1;
3188 } while (!finished);
3196 * Build a binary message to be saved on disk.
3197 * (NOTE: if you supply 'preformatted_text', the buffer you give it
3198 * will become part of the message. This means you are no longer
3199 * responsible for managing that memory -- it will be freed along with
3200 * the rest of the fields when CtdlFreeMessage() is called.)
3203 struct CtdlMessage *CtdlMakeMessage(
3204 struct ctdluser *author, /* author's user structure */
3205 char *recipient, /* NULL if it's not mail */
3206 char *recp_cc, /* NULL if it's not mail */
3207 char *room, /* room where it's going */
3208 int type, /* see MES_ types in header file */
3209 int format_type, /* variformat, plain text, MIME... */
3210 char *fake_name, /* who we're masquerading as */
3211 char *my_email, /* which of my email addresses to use (empty is ok) */
3212 char *subject, /* Subject (optional) */
3213 char *supplied_euid, /* ...or NULL if this is irrelevant */
3214 char *preformatted_text, /* ...or NULL to read text from client */
3215 char *references /* Thread references */
3217 char dest_node[256];
3219 struct CtdlMessage *msg;
3221 msg = malloc(sizeof(struct CtdlMessage));
3222 memset(msg, 0, sizeof(struct CtdlMessage));
3223 msg->cm_magic = CTDLMESSAGE_MAGIC;
3224 msg->cm_anon_type = type;
3225 msg->cm_format_type = format_type;
3227 /* Don't confuse the poor folks if it's not routed mail. */
3228 strcpy(dest_node, "");
3230 if (recipient != NULL) striplt(recipient);
3231 if (recp_cc != NULL) striplt(recp_cc);
3233 /* Path or Return-Path */
3234 if (my_email == NULL) my_email = "";
3236 if (!IsEmptyStr(my_email)) {
3237 msg->cm_fields['P'] = strdup(my_email);
3240 snprintf(buf, sizeof buf, "%s", author->fullname);
3241 msg->cm_fields['P'] = strdup(buf);
3243 convert_spaces_to_underscores(msg->cm_fields['P']);
3245 snprintf(buf, sizeof buf, "%ld", (long)time(NULL)); /* timestamp */
3246 msg->cm_fields['T'] = strdup(buf);
3248 if ((fake_name != NULL) && (fake_name[0])) { /* author */
3249 msg->cm_fields['A'] = strdup(fake_name);
3252 msg->cm_fields['A'] = strdup(author->fullname);
3255 if (CC->room.QRflags & QR_MAILBOX) { /* room */
3256 msg->cm_fields['O'] = strdup(&CC->room.QRname[11]);
3259 msg->cm_fields['O'] = strdup(CC->room.QRname);
3262 msg->cm_fields['N'] = strdup(NODENAME); /* nodename */
3263 msg->cm_fields['H'] = strdup(HUMANNODE); /* hnodename */
3265 if ((recipient != NULL) && (recipient[0] != 0)) {
3266 msg->cm_fields['R'] = strdup(recipient);
3268 if ((recp_cc != NULL) && (recp_cc[0] != 0)) {
3269 msg->cm_fields['Y'] = strdup(recp_cc);
3271 if (dest_node[0] != 0) {
3272 msg->cm_fields['D'] = strdup(dest_node);
3275 if (!IsEmptyStr(my_email)) {
3276 msg->cm_fields['F'] = strdup(my_email);
3278 else if ( (author == &CC->user) && (!IsEmptyStr(CC->cs_inet_email)) ) {
3279 msg->cm_fields['F'] = strdup(CC->cs_inet_email);
3282 if (subject != NULL) {
3285 length = strlen(subject);
3291 while ((subject[i] != '\0') &&
3292 (IsAscii = isascii(subject[i]) != 0 ))
3295 msg->cm_fields['U'] = strdup(subject);
3296 else /* ok, we've got utf8 in the string. */
3298 msg->cm_fields['U'] = rfc2047encode(subject, length);
3304 if (supplied_euid != NULL) {
3305 msg->cm_fields['E'] = strdup(supplied_euid);
3308 if (references != NULL) {
3309 if (!IsEmptyStr(references)) {
3310 msg->cm_fields['W'] = strdup(references);
3314 if (preformatted_text != NULL) {
3315 msg->cm_fields['M'] = preformatted_text;
3318 msg->cm_fields['M'] = CtdlReadMessageBody("000", config.c_maxmsglen, NULL, 0, 0);
3326 * Check to see whether we have permission to post a message in the current
3327 * room. Returns a *CITADEL ERROR CODE* and puts a message in errmsgbuf, or
3328 * returns 0 on success.
3330 int CtdlDoIHavePermissionToPostInThisRoom(char *errmsgbuf,
3332 const char* RemoteIdentifier,
3336 if (!(CC->logged_in) &&
3337 (PostPublic == POST_LOGGED_IN)) {
3338 snprintf(errmsgbuf, n, "Not logged in.");
3339 return (ERROR + NOT_LOGGED_IN);
3341 else if (PostPublic == CHECK_EXISTANCE) {
3342 return (0); // We're Evaling whether a recipient exists
3344 else if (!(CC->logged_in)) {
3346 if ((CC->room.QRflags & QR_READONLY)) {
3347 snprintf(errmsgbuf, n, "Not logged in.");
3348 return (ERROR + NOT_LOGGED_IN);
3350 if (CC->room.QRflags2 & QR2_MODERATED) {
3351 snprintf(errmsgbuf, n, "Not logged in Moderation feature not yet implemented!");
3352 return (ERROR + NOT_LOGGED_IN);
3354 if ((PostPublic!=POST_LMTP) &&(CC->room.QRflags2 & QR2_SMTP_PUBLIC) == 0) {
3359 if (RemoteIdentifier == NULL)
3361 snprintf(errmsgbuf, n, "Need sender to permit access.");
3362 return (ERROR + USERNAME_REQUIRED);
3365 assoc_file_name(filename, sizeof filename, &CC->room, ctdl_netcfg_dir);
3366 begin_critical_section(S_NETCONFIGS);
3367 if (!read_spoolcontrol_file(&sc, filename))
3369 end_critical_section(S_NETCONFIGS);
3370 snprintf(errmsgbuf, n,
3371 "This mailing list only accepts posts from subscribers.");
3372 return (ERROR + NO_SUCH_USER);
3374 end_critical_section(S_NETCONFIGS);
3375 found = is_recipient (sc, RemoteIdentifier);
3376 free_spoolcontrol_struct(&sc);
3381 snprintf(errmsgbuf, n,
3382 "This mailing list only accepts posts from subscribers.");
3383 return (ERROR + NO_SUCH_USER);
3390 if ((CC->user.axlevel < 2)
3391 && ((CC->room.QRflags & QR_MAILBOX) == 0)) {
3392 snprintf(errmsgbuf, n, "Need to be validated to enter "
3393 "(except in %s> to sysop)", MAILROOM);
3394 return (ERROR + HIGHER_ACCESS_REQUIRED);
3397 CtdlRoomAccess(&CC->room, &CC->user, &ra, NULL);
3398 if (!(ra & UA_POSTALLOWED)) {
3399 snprintf(errmsgbuf, n, "Higher access is required to post in this room.");
3400 return (ERROR + HIGHER_ACCESS_REQUIRED);
3403 strcpy(errmsgbuf, "Ok");
3409 * Check to see if the specified user has Internet mail permission
3410 * (returns nonzero if permission is granted)
3412 int CtdlCheckInternetMailPermission(struct ctdluser *who) {
3414 /* Do not allow twits to send Internet mail */
3415 if (who->axlevel <= 2) return(0);
3417 /* Globally enabled? */
3418 if (config.c_restrict == 0) return(1);
3420 /* User flagged ok? */
3421 if (who->flags & US_INTERNET) return(2);
3423 /* Aide level access? */
3424 if (who->axlevel >= 6) return(3);
3426 /* No mail for you! */
3432 * Validate recipients, count delivery types and errors, and handle aliasing
3433 * FIXME check for dupes!!!!!
3435 * Returns 0 if all addresses are ok, ret->num_error = -1 if no addresses
3436 * were specified, or the number of addresses found invalid.
3438 * Caller needs to free the result using free_recipients()
3440 struct recptypes *validate_recipients(char *supplied_recipients,
3441 const char *RemoteIdentifier,
3443 struct recptypes *ret;
3444 char *recipients = NULL;
3445 char this_recp[256];
3446 char this_recp_cooked[256];
3452 struct ctdluser tempUS;
3453 struct ctdlroom tempQR;
3454 struct ctdlroom tempQR2;
3460 ret = (struct recptypes *) malloc(sizeof(struct recptypes));
3461 if (ret == NULL) return(NULL);
3463 /* Set all strings to null and numeric values to zero */
3464 memset(ret, 0, sizeof(struct recptypes));
3466 if (supplied_recipients == NULL) {
3467 recipients = strdup("");
3470 recipients = strdup(supplied_recipients);
3473 /* Allocate some memory. Yes, this allocates 500% more memory than we will
3474 * actually need, but it's healthier for the heap than doing lots of tiny
3475 * realloc() calls instead.
3478 ret->errormsg = malloc(strlen(recipients) + 1024);
3479 ret->recp_local = malloc(strlen(recipients) + 1024);
3480 ret->recp_internet = malloc(strlen(recipients) + 1024);
3481 ret->recp_ignet = malloc(strlen(recipients) + 1024);
3482 ret->recp_room = malloc(strlen(recipients) + 1024);
3483 ret->display_recp = malloc(strlen(recipients) + 1024);
3485 ret->errormsg[0] = 0;
3486 ret->recp_local[0] = 0;
3487 ret->recp_internet[0] = 0;
3488 ret->recp_ignet[0] = 0;
3489 ret->recp_room[0] = 0;
3490 ret->display_recp[0] = 0;
3492 ret->recptypes_magic = RECPTYPES_MAGIC;
3494 /* Change all valid separator characters to commas */
3495 for (i=0; !IsEmptyStr(&recipients[i]); ++i) {
3496 if ((recipients[i] == ';') || (recipients[i] == '|')) {
3497 recipients[i] = ',';
3501 /* Now start extracting recipients... */
3503 while (!IsEmptyStr(recipients)) {
3505 for (i=0; i<=strlen(recipients); ++i) {
3506 if (recipients[i] == '\"') in_quotes = 1 - in_quotes;
3507 if ( ( (recipients[i] == ',') && (!in_quotes) ) || (recipients[i] == 0) ) {
3508 safestrncpy(this_recp, recipients, i+1);
3510 if (recipients[i] == ',') {
3511 strcpy(recipients, &recipients[i+1]);
3514 strcpy(recipients, "");
3521 if (IsEmptyStr(this_recp))
3523 CtdlLogPrintf(CTDL_DEBUG, "Evaluating recipient #%d: %s\n", num_recps, this_recp);
3525 mailtype = alias(this_recp);
3526 mailtype = alias(this_recp);
3527 mailtype = alias(this_recp);
3529 for (j=0; !IsEmptyStr(&this_recp[j]); ++j) {
3530 if (this_recp[j]=='_') {
3531 this_recp_cooked[j] = ' ';
3534 this_recp_cooked[j] = this_recp[j];
3537 this_recp_cooked[j] = '\0';
3542 if (!strcasecmp(this_recp, "sysop")) {
3544 strcpy(this_recp, config.c_aideroom);
3545 if (!IsEmptyStr(ret->recp_room)) {
3546 strcat(ret->recp_room, "|");
3548 strcat(ret->recp_room, this_recp);
3550 else if ( (!strncasecmp(this_recp, "room_", 5))
3551 && (!getroom(&tempQR, &this_recp_cooked[5])) ) {
3553 /* Save room so we can restore it later */
3557 /* Check permissions to send mail to this room */
3558 err = CtdlDoIHavePermissionToPostInThisRoom(errmsg,
3570 if (!IsEmptyStr(ret->recp_room)) {
3571 strcat(ret->recp_room, "|");
3573 strcat(ret->recp_room, &this_recp_cooked[5]);
3576 /* Restore room in case something needs it */
3580 else if (getuser(&tempUS, this_recp) == 0) {
3582 strcpy(this_recp, tempUS.fullname);
3583 if (!IsEmptyStr(ret->recp_local)) {
3584 strcat(ret->recp_local, "|");
3586 strcat(ret->recp_local, this_recp);
3588 else if (getuser(&tempUS, this_recp_cooked) == 0) {
3590 strcpy(this_recp, tempUS.fullname);
3591 if (!IsEmptyStr(ret->recp_local)) {
3592 strcat(ret->recp_local, "|");
3594 strcat(ret->recp_local, this_recp);
3602 /* Yes, you're reading this correctly: if the target
3603 * domain points back to the local system or an attached
3604 * Citadel directory, the address is invalid. That's
3605 * because if the address were valid, we would have
3606 * already translated it to a local address by now.
3608 if (IsDirectory(this_recp, 0)) {
3613 ++ret->num_internet;
3614 if (!IsEmptyStr(ret->recp_internet)) {
3615 strcat(ret->recp_internet, "|");
3617 strcat(ret->recp_internet, this_recp);
3622 if (!IsEmptyStr(ret->recp_ignet)) {
3623 strcat(ret->recp_ignet, "|");
3625 strcat(ret->recp_ignet, this_recp);
3633 if (IsEmptyStr(errmsg)) {
3634 snprintf(append, sizeof append, "Invalid recipient: %s", this_recp);
3637 snprintf(append, sizeof append, "%s", errmsg);
3639 if ( (strlen(ret->errormsg) + strlen(append) + 3) < SIZ) {
3640 if (!IsEmptyStr(ret->errormsg)) {
3641 strcat(ret->errormsg, "; ");
3643 strcat(ret->errormsg, append);
3647 if (IsEmptyStr(ret->display_recp)) {
3648 strcpy(append, this_recp);
3651 snprintf(append, sizeof append, ", %s", this_recp);
3653 if ( (strlen(ret->display_recp)+strlen(append)) < SIZ) {
3654 strcat(ret->display_recp, append);
3659 if ((ret->num_local + ret->num_internet + ret->num_ignet +
3660 ret->num_room + ret->num_error) == 0) {
3661 ret->num_error = (-1);
3662 strcpy(ret->errormsg, "No recipients specified.");
3665 CtdlLogPrintf(CTDL_DEBUG, "validate_recipients()\n");
3666 CtdlLogPrintf(CTDL_DEBUG, " local: %d <%s>\n", ret->num_local, ret->recp_local);
3667 CtdlLogPrintf(CTDL_DEBUG, " room: %d <%s>\n", ret->num_room, ret->recp_room);
3668 CtdlLogPrintf(CTDL_DEBUG, " inet: %d <%s>\n", ret->num_internet, ret->recp_internet);
3669 CtdlLogPrintf(CTDL_DEBUG, " ignet: %d <%s>\n", ret->num_ignet, ret->recp_ignet);
3670 CtdlLogPrintf(CTDL_DEBUG, " error: %d <%s>\n", ret->num_error, ret->errormsg);
3678 * Destructor for struct recptypes
3680 void free_recipients(struct recptypes *valid) {
3682 if (valid == NULL) {
3686 if (valid->recptypes_magic != RECPTYPES_MAGIC) {
3687 CtdlLogPrintf(CTDL_EMERG, "Attempt to call free_recipients() on some other data type!\n");
3691 if (valid->errormsg != NULL) free(valid->errormsg);
3692 if (valid->recp_local != NULL) free(valid->recp_local);
3693 if (valid->recp_internet != NULL) free(valid->recp_internet);
3694 if (valid->recp_ignet != NULL) free(valid->recp_ignet);
3695 if (valid->recp_room != NULL) free(valid->recp_room);
3696 if (valid->display_recp != NULL) free(valid->display_recp);
3697 if (valid->bounce_to != NULL) free(valid->bounce_to);
3698 if (valid->envelope_from != NULL) free(valid->envelope_from);
3705 * message entry - mode 0 (normal)
3707 void cmd_ent0(char *entargs)
3713 char supplied_euid[128];
3715 int format_type = 0;
3716 char newusername[256];
3717 char newuseremail[256];
3718 struct CtdlMessage *msg;
3722 struct recptypes *valid = NULL;
3723 struct recptypes *valid_to = NULL;
3724 struct recptypes *valid_cc = NULL;
3725 struct recptypes *valid_bcc = NULL;
3727 int subject_required = 0;
3732 int newuseremail_ok = 0;
3733 char references[SIZ];
3738 post = extract_int(entargs, 0);
3739 extract_token(recp, entargs, 1, '|', sizeof recp);
3740 anon_flag = extract_int(entargs, 2);
3741 format_type = extract_int(entargs, 3);
3742 extract_token(subject, entargs, 4, '|', sizeof subject);
3743 extract_token(newusername, entargs, 5, '|', sizeof newusername);
3744 do_confirm = extract_int(entargs, 6);
3745 extract_token(cc, entargs, 7, '|', sizeof cc);
3746 extract_token(bcc, entargs, 8, '|', sizeof bcc);
3747 switch(CC->room.QRdefaultview) {
3750 extract_token(supplied_euid, entargs, 9, '|', sizeof supplied_euid);
3753 supplied_euid[0] = 0;
3756 extract_token(newuseremail, entargs, 10, '|', sizeof newuseremail);
3757 extract_token(references, entargs, 11, '|', sizeof references);
3758 for (ptr=references; *ptr != 0; ++ptr) {
3759 if (*ptr == '!') *ptr = '|';
3762 /* first check to make sure the request is valid. */
3764 err = CtdlDoIHavePermissionToPostInThisRoom(errmsg, sizeof errmsg, NULL, POST_LOGGED_IN);
3767 cprintf("%d %s\n", err, errmsg);
3771 /* Check some other permission type things. */
3773 if (IsEmptyStr(newusername)) {
3774 strcpy(newusername, CC->user.fullname);
3776 if ( (CC->user.axlevel < 6)
3777 && (strcasecmp(newusername, CC->user.fullname))
3778 && (strcasecmp(newusername, CC->cs_inet_fn))
3780 cprintf("%d You don't have permission to author messages as '%s'.\n",
3781 ERROR + HIGHER_ACCESS_REQUIRED,
3788 if (IsEmptyStr(newuseremail)) {
3789 newuseremail_ok = 1;
3792 if (!IsEmptyStr(newuseremail)) {
3793 if (!strcasecmp(newuseremail, CC->cs_inet_email)) {
3794 newuseremail_ok = 1;
3796 else if (!IsEmptyStr(CC->cs_inet_other_emails)) {
3797 j = num_tokens(CC->cs_inet_other_emails, '|');
3798 for (i=0; i<j; ++i) {
3799 extract_token(buf, CC->cs_inet_other_emails, i, '|', sizeof buf);
3800 if (!strcasecmp(newuseremail, buf)) {
3801 newuseremail_ok = 1;
3807 if (!newuseremail_ok) {
3808 cprintf("%d You don't have permission to author messages as '%s'.\n",
3809 ERROR + HIGHER_ACCESS_REQUIRED,
3815 CC->cs_flags |= CS_POSTING;
3817 /* In mailbox rooms we have to behave a little differently --
3818 * make sure the user has specified at least one recipient. Then
3819 * validate the recipient(s). We do this for the Mail> room, as
3820 * well as any room which has the "Mailbox" view set.
3823 if ( ( (CC->room.QRflags & QR_MAILBOX) && (!strcasecmp(&CC->room.QRname[11], MAILROOM)) )
3824 || ( (CC->room.QRflags & QR_MAILBOX) && (CC->curr_view == VIEW_MAILBOX) )
3826 if (CC->user.axlevel < 2) {
3827 strcpy(recp, "sysop");
3832 valid_to = validate_recipients(recp, NULL, 0);
3833 if (valid_to->num_error > 0) {
3834 cprintf("%d %s\n", ERROR + NO_SUCH_USER, valid_to->errormsg);
3835 free_recipients(valid_to);
3839 valid_cc = validate_recipients(cc, NULL, 0);
3840 if (valid_cc->num_error > 0) {
3841 cprintf("%d %s\n", ERROR + NO_SUCH_USER, valid_cc->errormsg);
3842 free_recipients(valid_to);
3843 free_recipients(valid_cc);
3847 valid_bcc = validate_recipients(bcc, NULL, 0);
3848 if (valid_bcc->num_error > 0) {
3849 cprintf("%d %s\n", ERROR + NO_SUCH_USER, valid_bcc->errormsg);
3850 free_recipients(valid_to);
3851 free_recipients(valid_cc);
3852 free_recipients(valid_bcc);
3856 /* Recipient required, but none were specified */
3857 if ( (valid_to->num_error < 0) && (valid_cc->num_error < 0) && (valid_bcc->num_error < 0) ) {
3858 free_recipients(valid_to);
3859 free_recipients(valid_cc);
3860 free_recipients(valid_bcc);
3861 cprintf("%d At least one recipient is required.\n", ERROR + NO_SUCH_USER);
3865 if (valid_to->num_internet + valid_cc->num_internet + valid_bcc->num_internet > 0) {
3866 if (CtdlCheckInternetMailPermission(&CC->user)==0) {
3867 cprintf("%d You do not have permission "
3868 "to send Internet mail.\n",
3869 ERROR + HIGHER_ACCESS_REQUIRED);
3870 free_recipients(valid_to);
3871 free_recipients(valid_cc);
3872 free_recipients(valid_bcc);
3877 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)
3878 && (CC->user.axlevel < 4) ) {
3879 cprintf("%d Higher access required for network mail.\n",
3880 ERROR + HIGHER_ACCESS_REQUIRED);
3881 free_recipients(valid_to);
3882 free_recipients(valid_cc);
3883 free_recipients(valid_bcc);
3887 if ((RESTRICT_INTERNET == 1)
3888 && (valid_to->num_internet + valid_cc->num_internet + valid_bcc->num_internet > 0)
3889 && ((CC->user.flags & US_INTERNET) == 0)
3890 && (!CC->internal_pgm)) {
3891 cprintf("%d You don't have access to Internet mail.\n",
3892 ERROR + HIGHER_ACCESS_REQUIRED);
3893 free_recipients(valid_to);
3894 free_recipients(valid_cc);
3895 free_recipients(valid_bcc);
3901 /* Is this a room which has anonymous-only or anonymous-option? */
3902 anonymous = MES_NORMAL;
3903 if (CC->room.QRflags & QR_ANONONLY) {
3904 anonymous = MES_ANONONLY;
3906 if (CC->room.QRflags & QR_ANONOPT) {
3907 if (anon_flag == 1) { /* only if the user requested it */
3908 anonymous = MES_ANONOPT;
3912 if ((CC->room.QRflags & QR_MAILBOX) == 0) {
3916 /* Recommend to the client that the use of a message subject is
3917 * strongly recommended in this room, if either the SUBJECTREQ flag
3918 * is set, or if there is one or more Internet email recipients.
3920 if (CC->room.QRflags2 & QR2_SUBJECTREQ) subject_required = 1;
3921 if ((valid_to) && (valid_to->num_internet > 0)) subject_required = 1;
3922 if ((valid_cc) && (valid_cc->num_internet > 0)) subject_required = 1;
3923 if ((valid_bcc) && (valid_bcc->num_internet > 0)) subject_required = 1;
3925 /* If we're only checking the validity of the request, return
3926 * success without creating the message.
3929 cprintf("%d %s|%d\n", CIT_OK,
3930 ((valid_to != NULL) ? valid_to->display_recp : ""),
3932 free_recipients(valid_to);
3933 free_recipients(valid_cc);
3934 free_recipients(valid_bcc);
3938 /* We don't need these anymore because we'll do it differently below */
3939 free_recipients(valid_to);
3940 free_recipients(valid_cc);
3941 free_recipients(valid_bcc);
3943 /* Read in the message from the client. */
3945 cprintf("%d send message\n", START_CHAT_MODE);
3947 cprintf("%d send message\n", SEND_LISTING);
3950 msg = CtdlMakeMessage(&CC->user, recp, cc,
3951 CC->room.QRname, anonymous, format_type,
3952 newusername, newuseremail, subject,
3953 ((!IsEmptyStr(supplied_euid)) ? supplied_euid : NULL),
3956 /* Put together one big recipients struct containing to/cc/bcc all in
3957 * one. This is for the envelope.
3959 char *all_recps = malloc(SIZ * 3);
3960 strcpy(all_recps, recp);
3961 if (!IsEmptyStr(cc)) {
3962 if (!IsEmptyStr(all_recps)) {
3963 strcat(all_recps, ",");
3965 strcat(all_recps, cc);
3967 if (!IsEmptyStr(bcc)) {
3968 if (!IsEmptyStr(all_recps)) {
3969 strcat(all_recps, ",");
3971 strcat(all_recps, bcc);
3973 if (!IsEmptyStr(all_recps)) {
3974 valid = validate_recipients(all_recps, NULL, 0);
3982 msgnum = CtdlSubmitMsg(msg, valid, "", QP_EADDR);
3985 cprintf("%ld\n", msgnum);
3987 cprintf("Message accepted.\n");
3990 cprintf("Internal error.\n");
3992 if (msg->cm_fields['E'] != NULL) {
3993 cprintf("%s\n", msg->cm_fields['E']);
4000 CtdlFreeMessage(msg);
4002 if (valid != NULL) {
4003 free_recipients(valid);
4011 * API function to delete messages which match a set of criteria
4012 * (returns the actual number of messages deleted)
4014 int CtdlDeleteMessages(char *room_name, /* which room */
4015 long *dmsgnums, /* array of msg numbers to be deleted */
4016 int num_dmsgnums, /* number of msgs to be deleted, or 0 for "any" */
4017 char *content_type /* or "" for any. regular expressions expected. */
4020 struct ctdlroom qrbuf;
4021 struct cdbdata *cdbfr;
4022 long *msglist = NULL;
4023 long *dellist = NULL;
4026 int num_deleted = 0;
4028 struct MetaData smi;
4031 int need_to_free_re = 0;
4033 if (content_type) if (!IsEmptyStr(content_type)) {
4034 regcomp(&re, content_type, 0);
4035 need_to_free_re = 1;
4037 CtdlLogPrintf(CTDL_DEBUG, "CtdlDeleteMessages(%s, %d msgs, %s)\n",
4038 room_name, num_dmsgnums, content_type);
4040 /* get room record, obtaining a lock... */
4041 if (lgetroom(&qrbuf, room_name) != 0) {
4042 CtdlLogPrintf(CTDL_ERR, "CtdlDeleteMessages(): Room <%s> not found\n",
4044 if (need_to_free_re) regfree(&re);
4045 return (0); /* room not found */
4047 cdbfr = cdb_fetch(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long));
4049 if (cdbfr != NULL) {
4050 dellist = malloc(cdbfr->len);
4051 msglist = (long *) cdbfr->ptr;
4052 cdbfr->ptr = NULL; /* CtdlDeleteMessages() now owns this memory */
4053 num_msgs = cdbfr->len / sizeof(long);
4057 for (i = 0; i < num_msgs; ++i) {
4060 /* Set/clear a bit for each criterion */
4062 /* 0 messages in the list or a null list means that we are
4063 * interested in deleting any messages which meet the other criteria.
4065 if ((num_dmsgnums == 0) || (dmsgnums == NULL)) {
4066 delete_this |= 0x01;
4069 for (j=0; j<num_dmsgnums; ++j) {
4070 if (msglist[i] == dmsgnums[j]) {
4071 delete_this |= 0x01;
4076 if (IsEmptyStr(content_type)) {
4077 delete_this |= 0x02;
4079 GetMetaData(&smi, msglist[i]);
4080 if (regexec(&re, smi.meta_content_type, 1, &pm, 0) == 0) {
4081 delete_this |= 0x02;
4085 /* Delete message only if all bits are set */
4086 if (delete_this == 0x03) {
4087 dellist[num_deleted++] = msglist[i];
4092 num_msgs = sort_msglist(msglist, num_msgs);
4093 cdb_store(CDB_MSGLISTS, &qrbuf.QRnumber, (int)sizeof(long),
4094 msglist, (int)(num_msgs * sizeof(long)));
4096 qrbuf.QRhighest = msglist[num_msgs - 1];
4100 /* Go through the messages we pulled out of the index, and decrement
4101 * their reference counts by 1. If this is the only room the message
4102 * was in, the reference count will reach zero and the message will
4103 * automatically be deleted from the database. We do this in a
4104 * separate pass because there might be plug-in hooks getting called,
4105 * and we don't want that happening during an S_ROOMS critical
4108 if (num_deleted) for (i=0; i<num_deleted; ++i) {
4109 PerformDeleteHooks(qrbuf.QRname, dellist[i]);
4110 AdjRefCount(dellist[i], -1);
4113 /* Now free the memory we used, and go away. */
4114 if (msglist != NULL) free(msglist);
4115 if (dellist != NULL) free(dellist);
4116 CtdlLogPrintf(CTDL_DEBUG, "%d message(s) deleted.\n", num_deleted);
4117 if (need_to_free_re) regfree(&re);
4118 return (num_deleted);
4124 * Check whether the current user has permission to delete messages from
4125 * the current room (returns 1 for yes, 0 for no)
4127 int CtdlDoIHavePermissionToDeleteMessagesFromThisRoom(void) {
4129 CtdlRoomAccess(&CC->room, &CC->user, &ra, NULL);
4130 if (ra & UA_DELETEALLOWED) return(1);
4138 * Delete message from current room
4140 void cmd_dele(char *args)
4149 extract_token(msgset, args, 0, '|', sizeof msgset);
4150 num_msgs = num_tokens(msgset, ',');
4152 cprintf("%d Nothing to do.\n", CIT_OK);
4156 if (CtdlDoIHavePermissionToDeleteMessagesFromThisRoom() == 0) {
4157 cprintf("%d Higher access required.\n",
4158 ERROR + HIGHER_ACCESS_REQUIRED);
4163 * Build our message set to be moved/copied
4165 msgs = malloc(num_msgs * sizeof(long));
4166 for (i=0; i<num_msgs; ++i) {
4167 extract_token(msgtok, msgset, i, ',', sizeof msgtok);
4168 msgs[i] = atol(msgtok);
4171 num_deleted = CtdlDeleteMessages(CC->room.QRname, msgs, num_msgs, "");
4175 cprintf("%d %d message%s deleted.\n", CIT_OK,
4176 num_deleted, ((num_deleted != 1) ? "s" : ""));
4178 cprintf("%d Message not found.\n", ERROR + MESSAGE_NOT_FOUND);
4186 * move or copy a message to another room
4188 void cmd_move(char *args)
4195 char targ[ROOMNAMELEN];
4196 struct ctdlroom qtemp;
4203 extract_token(msgset, args, 0, '|', sizeof msgset);
4204 num_msgs = num_tokens(msgset, ',');
4206 cprintf("%d Nothing to do.\n", CIT_OK);
4210 extract_token(targ, args, 1, '|', sizeof targ);
4211 convert_room_name_macros(targ, sizeof targ);
4212 targ[ROOMNAMELEN - 1] = 0;
4213 is_copy = extract_int(args, 2);
4215 if (getroom(&qtemp, targ) != 0) {
4216 cprintf("%d '%s' does not exist.\n", ERROR + ROOM_NOT_FOUND, targ);
4220 if (!strcasecmp(qtemp.QRname, CC->room.QRname)) {
4221 cprintf("%d Source and target rooms are the same.\n", ERROR + ALREADY_EXISTS);
4225 getuser(&CC->user, CC->curr_user);
4226 CtdlRoomAccess(&qtemp, &CC->user, &ra, NULL);
4228 /* Check for permission to perform this operation.
4229 * Remember: "CC->room" is source, "qtemp" is target.
4233 /* Aides can move/copy */
4234 if (CC->user.axlevel >= 6) permit = 1;
4236 /* Room aides can move/copy */
4237 if (CC->user.usernum == CC->room.QRroomaide) permit = 1;
4239 /* Permit move/copy from personal rooms */
4240 if ((CC->room.QRflags & QR_MAILBOX)
4241 && (qtemp.QRflags & QR_MAILBOX)) permit = 1;
4243 /* Permit only copy from public to personal room */
4245 && (!(CC->room.QRflags & QR_MAILBOX))
4246 && (qtemp.QRflags & QR_MAILBOX)) permit = 1;
4248 /* Permit message removal from collaborative delete rooms */
4249 if (CC->room.QRflags2 & QR2_COLLABDEL) permit = 1;
4251 /* Users allowed to post into the target room may move into it too. */
4252 if ((CC->room.QRflags & QR_MAILBOX) &&
4253 (qtemp.QRflags & UA_POSTALLOWED)) permit = 1;
4255 /* User must have access to target room */
4256 if (!(ra & UA_KNOWN)) permit = 0;
4259 cprintf("%d Higher access required.\n",
4260 ERROR + HIGHER_ACCESS_REQUIRED);
4265 * Build our message set to be moved/copied
4267 msgs = malloc(num_msgs * sizeof(long));
4268 for (i=0; i<num_msgs; ++i) {
4269 extract_token(msgtok, msgset, i, ',', sizeof msgtok);
4270 msgs[i] = atol(msgtok);
4276 err = CtdlSaveMsgPointersInRoom(targ, msgs, num_msgs, 1, NULL);
4278 cprintf("%d Cannot store message(s) in %s: error %d\n",
4284 /* Now delete the message from the source room,
4285 * if this is a 'move' rather than a 'copy' operation.
4288 CtdlDeleteMessages(CC->room.QRname, msgs, num_msgs, "");
4292 cprintf("%d Message(s) %s.\n", CIT_OK, (is_copy ? "copied" : "moved") );
4298 * GetMetaData() - Get the supplementary record for a message
4300 void GetMetaData(struct MetaData *smibuf, long msgnum)
4303 struct cdbdata *cdbsmi;
4306 memset(smibuf, 0, sizeof(struct MetaData));
4307 smibuf->meta_msgnum = msgnum;
4308 smibuf->meta_refcount = 1; /* Default reference count is 1 */
4310 /* Use the negative of the message number for its supp record index */
4311 TheIndex = (0L - msgnum);
4313 cdbsmi = cdb_fetch(CDB_MSGMAIN, &TheIndex, sizeof(long));
4314 if (cdbsmi == NULL) {
4315 return; /* record not found; go with defaults */
4317 memcpy(smibuf, cdbsmi->ptr,
4318 ((cdbsmi->len > sizeof(struct MetaData)) ?
4319 sizeof(struct MetaData) : cdbsmi->len));
4326 * PutMetaData() - (re)write supplementary record for a message
4328 void PutMetaData(struct MetaData *smibuf)
4332 /* Use the negative of the message number for the metadata db index */
4333 TheIndex = (0L - smibuf->meta_msgnum);
4335 cdb_store(CDB_MSGMAIN,
4336 &TheIndex, (int)sizeof(long),
4337 smibuf, (int)sizeof(struct MetaData));
4342 * AdjRefCount - submit an adjustment to the reference count for a message.
4343 * (These are just queued -- we actually process them later.)
4345 void AdjRefCount(long msgnum, int incr)
4347 struct arcq new_arcq;
4349 begin_critical_section(S_SUPPMSGMAIN);
4350 if (arcfp == NULL) {
4351 arcfp = fopen(file_arcq, "ab+");
4353 end_critical_section(S_SUPPMSGMAIN);
4355 /* msgnum < 0 means that we're trying to close the file */
4357 CtdlLogPrintf(CTDL_DEBUG, "Closing the AdjRefCount queue file\n");
4358 begin_critical_section(S_SUPPMSGMAIN);
4359 if (arcfp != NULL) {
4363 end_critical_section(S_SUPPMSGMAIN);
4368 * If we can't open the queue, perform the operation synchronously.
4370 if (arcfp == NULL) {
4371 TDAP_AdjRefCount(msgnum, incr);
4375 new_arcq.arcq_msgnum = msgnum;
4376 new_arcq.arcq_delta = incr;
4377 fwrite(&new_arcq, sizeof(struct arcq), 1, arcfp);
4385 * TDAP_ProcessAdjRefCountQueue()
4387 * Process the queue of message count adjustments that was created by calls
4388 * to AdjRefCount() ... by reading the queue and calling TDAP_AdjRefCount()
4389 * for each one. This should be an "off hours" operation.
4391 int TDAP_ProcessAdjRefCountQueue(void)
4393 char file_arcq_temp[PATH_MAX];
4396 struct arcq arcq_rec;
4397 int num_records_processed = 0;
4399 snprintf(file_arcq_temp, sizeof file_arcq_temp, "%s.%04x", file_arcq, rand());
4401 begin_critical_section(S_SUPPMSGMAIN);
4402 if (arcfp != NULL) {
4407 r = link(file_arcq, file_arcq_temp);
4409 CtdlLogPrintf(CTDL_CRIT, "%s: %s\n", file_arcq_temp, strerror(errno));
4410 end_critical_section(S_SUPPMSGMAIN);
4411 return(num_records_processed);
4415 end_critical_section(S_SUPPMSGMAIN);
4417 fp = fopen(file_arcq_temp, "rb");
4419 CtdlLogPrintf(CTDL_CRIT, "%s: %s\n", file_arcq_temp, strerror(errno));
4420 return(num_records_processed);
4423 while (fread(&arcq_rec, sizeof(struct arcq), 1, fp) == 1) {
4424 TDAP_AdjRefCount(arcq_rec.arcq_msgnum, arcq_rec.arcq_delta);
4425 ++num_records_processed;
4429 r = unlink(file_arcq_temp);
4431 CtdlLogPrintf(CTDL_CRIT, "%s: %s\n", file_arcq_temp, strerror(errno));
4434 return(num_records_processed);
4440 * TDAP_AdjRefCount - adjust the reference count for a message.
4441 * This one does it "for real" because it's called by
4442 * the autopurger function that processes the queue
4443 * created by AdjRefCount(). If a message's reference
4444 * count becomes zero, we also delete the message from
4445 * disk and de-index it.
4447 void TDAP_AdjRefCount(long msgnum, int incr)
4450 struct MetaData smi;
4453 /* This is a *tight* critical section; please keep it that way, as
4454 * it may get called while nested in other critical sections.
4455 * Complicating this any further will surely cause deadlock!
4457 begin_critical_section(S_SUPPMSGMAIN);
4458 GetMetaData(&smi, msgnum);
4459 smi.meta_refcount += incr;
4461 end_critical_section(S_SUPPMSGMAIN);
4462 CtdlLogPrintf(CTDL_DEBUG, "msg %ld ref count delta %+d, is now %d\n",
4463 msgnum, incr, smi.meta_refcount);
4465 /* If the reference count is now zero, delete the message
4466 * (and its supplementary record as well).
4468 if (smi.meta_refcount == 0) {
4469 CtdlLogPrintf(CTDL_DEBUG, "Deleting message <%ld>\n", msgnum);
4471 /* Call delete hooks with NULL room to show it has gone altogether */
4472 PerformDeleteHooks(NULL, msgnum);
4474 /* Remove from message base */
4476 cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long));
4477 cdb_delete(CDB_BIGMSGS, &delnum, (int)sizeof(long));
4479 /* Remove metadata record */
4480 delnum = (0L - msgnum);
4481 cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long));
4487 * Write a generic object to this room
4489 * Note: this could be much more efficient. Right now we use two temporary
4490 * files, and still pull the message into memory as with all others.
4492 void CtdlWriteObject(char *req_room, /* Room to stuff it in */
4493 char *content_type, /* MIME type of this object */
4494 char *raw_message, /* Data to be written */
4495 off_t raw_length, /* Size of raw_message */
4496 struct ctdluser *is_mailbox, /* Mailbox room? */
4497 int is_binary, /* Is encoding necessary? */
4498 int is_unique, /* Del others of this type? */
4499 unsigned int flags /* Internal save flags */
4503 struct ctdlroom qrbuf;
4504 char roomname[ROOMNAMELEN];
4505 struct CtdlMessage *msg;
4506 char *encoded_message = NULL;
4508 if (is_mailbox != NULL) {
4509 MailboxName(roomname, sizeof roomname, is_mailbox, req_room);
4512 safestrncpy(roomname, req_room, sizeof(roomname));
4515 CtdlLogPrintf(CTDL_DEBUG, "Raw length is %ld\n", (long)raw_length);
4518 encoded_message = malloc((size_t) (((raw_length * 134) / 100) + 4096 ) );
4521 encoded_message = malloc((size_t)(raw_length + 4096));
4524 sprintf(encoded_message, "Content-type: %s\n", content_type);
4527 sprintf(&encoded_message[strlen(encoded_message)],
4528 "Content-transfer-encoding: base64\n\n"
4532 sprintf(&encoded_message[strlen(encoded_message)],
4533 "Content-transfer-encoding: 7bit\n\n"
4539 &encoded_message[strlen(encoded_message)],
4547 &encoded_message[strlen(encoded_message)],
4553 CtdlLogPrintf(CTDL_DEBUG, "Allocating\n");
4554 msg = malloc(sizeof(struct CtdlMessage));
4555 memset(msg, 0, sizeof(struct CtdlMessage));
4556 msg->cm_magic = CTDLMESSAGE_MAGIC;
4557 msg->cm_anon_type = MES_NORMAL;
4558 msg->cm_format_type = 4;
4559 msg->cm_fields['A'] = strdup(CC->user.fullname);
4560 msg->cm_fields['O'] = strdup(req_room);
4561 msg->cm_fields['N'] = strdup(config.c_nodename);
4562 msg->cm_fields['H'] = strdup(config.c_humannode);
4563 msg->cm_flags = flags;
4565 msg->cm_fields['M'] = encoded_message;
4567 /* Create the requested room if we have to. */
4568 if (getroom(&qrbuf, roomname) != 0) {
4569 create_room(roomname,
4570 ( (is_mailbox != NULL) ? 5 : 3 ),
4571 "", 0, 1, 0, VIEW_BBS);
4573 /* If the caller specified this object as unique, delete all
4574 * other objects of this type that are currently in the room.
4577 CtdlLogPrintf(CTDL_DEBUG, "Deleted %d other msgs of this type\n",
4578 CtdlDeleteMessages(roomname, NULL, 0, content_type)
4581 /* Now write the data */
4582 CtdlSubmitMsg(msg, NULL, roomname, 0);
4583 CtdlFreeMessage(msg);
4591 void CtdlGetSysConfigBackend(long msgnum, void *userdata) {
4592 config_msgnum = msgnum;
4596 char *CtdlGetSysConfig(char *sysconfname) {
4597 char hold_rm[ROOMNAMELEN];
4600 struct CtdlMessage *msg;
4603 strcpy(hold_rm, CC->room.QRname);
4604 if (getroom(&CC->room, SYSCONFIGROOM) != 0) {
4605 getroom(&CC->room, hold_rm);
4610 /* We want the last (and probably only) config in this room */
4611 begin_critical_section(S_CONFIG);
4612 config_msgnum = (-1L);
4613 CtdlForEachMessage(MSGS_LAST, 1, NULL, sysconfname, NULL,
4614 CtdlGetSysConfigBackend, NULL);
4615 msgnum = config_msgnum;
4616 end_critical_section(S_CONFIG);
4622 msg = CtdlFetchMessage(msgnum, 1);
4624 conf = strdup(msg->cm_fields['M']);
4625 CtdlFreeMessage(msg);
4632 getroom(&CC->room, hold_rm);
4634 if (conf != NULL) do {
4635 extract_token(buf, conf, 0, '\n', sizeof buf);
4636 strcpy(conf, &conf[strlen(buf)+1]);
4637 } while ( (!IsEmptyStr(conf)) && (!IsEmptyStr(buf)) );
4643 void CtdlPutSysConfig(char *sysconfname, char *sysconfdata) {
4644 CtdlWriteObject(SYSCONFIGROOM, sysconfname, sysconfdata, (strlen(sysconfdata)+1), NULL, 0, 1, 0);
4649 * Determine whether a given Internet address belongs to the current user
4651 int CtdlIsMe(char *addr, int addr_buf_len)
4653 struct recptypes *recp;
4656 recp = validate_recipients(addr, NULL, 0);
4657 if (recp == NULL) return(0);
4659 if (recp->num_local == 0) {
4660 free_recipients(recp);
4664 for (i=0; i<recp->num_local; ++i) {
4665 extract_token(addr, recp->recp_local, i, '|', addr_buf_len);
4666 if (!strcasecmp(addr, CC->user.fullname)) {
4667 free_recipients(recp);
4672 free_recipients(recp);
4678 * Citadel protocol command to do the same
4680 void cmd_isme(char *argbuf) {
4683 if (CtdlAccessCheck(ac_logged_in)) return;
4684 extract_token(addr, argbuf, 0, '|', sizeof addr);
4686 if (CtdlIsMe(addr, sizeof addr)) {
4687 cprintf("%d %s\n", CIT_OK, addr);
4690 cprintf("%d Not you.\n", ERROR + ILLEGAL_VALUE);
4696 /*****************************************************************************/
4697 /* MODULE INITIALIZATION STUFF */
4698 /*****************************************************************************/
4700 CTDL_MODULE_INIT(msgbase)
4702 CtdlRegisterProtoHook(cmd_msgs, "MSGS", "Output a list of messages in the current room");
4703 CtdlRegisterProtoHook(cmd_msg0, "MSG0", "Output a message in plain text format");
4704 CtdlRegisterProtoHook(cmd_msg2, "MSG2", "Output a message in RFC822 format");
4705 CtdlRegisterProtoHook(cmd_msg3, "MSG3", "Output a message in raw format (deprecated)");
4706 CtdlRegisterProtoHook(cmd_msg4, "MSG4", "Output a message in the client's preferred format");
4707 CtdlRegisterProtoHook(cmd_msgp, "MSGP", "Select preferred format for MSG4 output");
4708 CtdlRegisterProtoHook(cmd_opna, "OPNA", "Open an attachment for download");
4709 CtdlRegisterProtoHook(cmd_dlat, "DLAT", "Download an attachment");
4710 CtdlRegisterProtoHook(cmd_ent0, "ENT0", "Enter a message");
4711 CtdlRegisterProtoHook(cmd_dele, "DELE", "Delete a message");
4712 CtdlRegisterProtoHook(cmd_move, "MOVE", "Move or copy a message to another room");
4713 CtdlRegisterProtoHook(cmd_isme, "ISME", "Determine whether an email address belongs to a user");
4715 /* return our Subversion id for the Log */