4 * Implements the message store.
14 #if TIME_WITH_SYS_TIME
15 # include <sys/time.h>
19 # include <sys/time.h>
32 #include <sys/types.h>
34 #include <libcitadel.h>
37 #include "serv_extensions.h"
41 #include "sysdep_decls.h"
42 #include "citserver.h"
49 #include "internet_addressing.h"
50 #include "euidindex.h"
51 #include "journaling.h"
52 #include "citadel_dirs.h"
53 #include "clientsocket.h"
54 #include "serv_network.h"
57 #include "ctdl_module.h"
60 struct addresses_to_be_filed *atbf = NULL;
62 /* This temp file holds the queue of operations for AdjRefCount() */
63 static FILE *arcfp = NULL;
66 * This really belongs in serv_network.c, but I don't know how to export
67 * symbols between modules.
69 struct FilterList *filterlist = NULL;
73 * These are the four-character field headers we use when outputting
74 * messages in Citadel format (as opposed to RFC822 format).
77 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
78 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
79 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
80 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
81 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
82 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
83 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
84 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
113 * This function is self explanatory.
114 * (What can I say, I'm in a weird mood today...)
116 void remove_any_whitespace_to_the_left_or_right_of_at_symbol(char *name)
120 for (i = 0; i < strlen(name); ++i) {
121 if (name[i] == '@') {
122 while (isspace(name[i - 1]) && i > 0) {
123 strcpy(&name[i - 1], &name[i]);
126 while (isspace(name[i + 1])) {
127 strcpy(&name[i + 1], &name[i + 2]);
135 * Aliasing for network mail.
136 * (Error messages have been commented out, because this is a server.)
138 int alias(char *name)
139 { /* process alias and routing info for mail */
142 char aaa[SIZ], bbb[SIZ];
143 char *ignetcfg = NULL;
144 char *ignetmap = NULL;
150 char original_name[256];
151 safestrncpy(original_name, name, sizeof original_name);
154 remove_any_whitespace_to_the_left_or_right_of_at_symbol(name);
155 stripallbut(name, '<', '>');
157 fp = fopen(file_mail_aliases, "r");
159 fp = fopen("/dev/null", "r");
166 while (fgets(aaa, sizeof aaa, fp) != NULL) {
167 while (isspace(name[0]))
168 strcpy(name, &name[1]);
169 aaa[strlen(aaa) - 1] = 0;
171 for (a = 0; a < strlen(aaa); ++a) {
173 strcpy(bbb, &aaa[a + 1]);
177 if (!strcasecmp(name, aaa))
182 /* Hit the Global Address Book */
183 if (CtdlDirectoryLookup(aaa, name, sizeof aaa) == 0) {
187 if (strcasecmp(original_name, name)) {
188 CtdlLogPrintf(CTDL_INFO, "%s is being forwarded to %s\n", original_name, name);
191 /* Change "user @ xxx" to "user" if xxx is an alias for this host */
192 for (a=0; a<strlen(name); ++a) {
193 if (name[a] == '@') {
194 if (CtdlHostAlias(&name[a+1]) == hostalias_localhost) {
196 CtdlLogPrintf(CTDL_INFO, "Changed to <%s>\n", name);
201 /* determine local or remote type, see citadel.h */
202 at = haschar(name, '@');
203 if (at == 0) return(MES_LOCAL); /* no @'s - local address */
204 if (at > 1) return(MES_ERROR); /* >1 @'s - invalid address */
205 remove_any_whitespace_to_the_left_or_right_of_at_symbol(name);
207 /* figure out the delivery mode */
208 extract_token(node, name, 1, '@', sizeof node);
210 /* If there are one or more dots in the nodename, we assume that it
211 * is an FQDN and will attempt SMTP delivery to the Internet.
213 if (haschar(node, '.') > 0) {
214 return(MES_INTERNET);
217 /* Otherwise we look in the IGnet maps for a valid Citadel node.
218 * Try directly-connected nodes first...
220 ignetcfg = CtdlGetSysConfig(IGNETCFG);
221 for (i=0; i<num_tokens(ignetcfg, '\n'); ++i) {
222 extract_token(buf, ignetcfg, i, '\n', sizeof buf);
223 extract_token(testnode, buf, 0, '|', sizeof testnode);
224 if (!strcasecmp(node, testnode)) {
232 * Then try nodes that are two or more hops away.
234 ignetmap = CtdlGetSysConfig(IGNETMAP);
235 for (i=0; i<num_tokens(ignetmap, '\n'); ++i) {
236 extract_token(buf, ignetmap, i, '\n', sizeof buf);
237 extract_token(testnode, buf, 0, '|', sizeof testnode);
238 if (!strcasecmp(node, testnode)) {
245 /* If we get to this point it's an invalid node name */
251 * Back end for the MSGS command: output message number only.
253 void simple_listing(long msgnum, void *userdata)
255 cprintf("%ld\n", msgnum);
261 * Back end for the MSGS command: output header summary.
263 void headers_listing(long msgnum, void *userdata)
265 struct CtdlMessage *msg;
267 msg = CtdlFetchMessage(msgnum, 0);
269 cprintf("%ld|0|||||\n", msgnum);
273 cprintf("%ld|%s|%s|%s|%s|%s|\n",
275 (msg->cm_fields['T'] ? msg->cm_fields['T'] : "0"),
276 (msg->cm_fields['A'] ? msg->cm_fields['A'] : ""),
277 (msg->cm_fields['N'] ? msg->cm_fields['N'] : ""),
278 (msg->cm_fields['F'] ? msg->cm_fields['F'] : ""),
279 (msg->cm_fields['U'] ? msg->cm_fields['U'] : "")
281 CtdlFreeMessage(msg);
286 /* Determine if a given message matches the fields in a message template.
287 * Return 0 for a successful match.
289 int CtdlMsgCmp(struct CtdlMessage *msg, struct CtdlMessage *template) {
292 /* If there aren't any fields in the template, all messages will
295 if (template == NULL) return(0);
297 /* Null messages are bogus. */
298 if (msg == NULL) return(1);
300 for (i='A'; i<='Z'; ++i) {
301 if (template->cm_fields[i] != NULL) {
302 if (msg->cm_fields[i] == NULL) {
303 /* Considered equal if temmplate is empty string */
304 if (IsEmptyStr(template->cm_fields[i])) continue;
307 if (strcasecmp(msg->cm_fields[i],
308 template->cm_fields[i])) return 1;
312 /* All compares succeeded: we have a match! */
319 * Retrieve the "seen" message list for the current room.
321 void CtdlGetSeen(char *buf, int which_set) {
324 /* Learn about the user and room in question */
325 CtdlGetRelationship(&vbuf, &CC->user, &CC->room);
327 if (which_set == ctdlsetseen_seen)
328 safestrncpy(buf, vbuf.v_seen, SIZ);
329 if (which_set == ctdlsetseen_answered)
330 safestrncpy(buf, vbuf.v_answered, SIZ);
336 * Manipulate the "seen msgs" string (or other message set strings)
338 void CtdlSetSeen(long *target_msgnums, int num_target_msgnums,
339 int target_setting, int which_set,
340 struct ctdluser *which_user, struct ctdlroom *which_room) {
341 struct cdbdata *cdbfr;
355 char *is_set; /* actually an array of booleans */
357 /* Don't bother doing *anything* if we were passed a list of zero messages */
358 if (num_target_msgnums < 1) {
362 /* If no room was specified, we go with the current room. */
364 which_room = &CC->room;
367 /* If no user was specified, we go with the current user. */
369 which_user = &CC->user;
372 CtdlLogPrintf(CTDL_DEBUG, "CtdlSetSeen(%d msgs starting with %ld, %s, %d) in <%s>\n",
373 num_target_msgnums, target_msgnums[0],
374 (target_setting ? "SET" : "CLEAR"),
378 /* Learn about the user and room in question */
379 CtdlGetRelationship(&vbuf, which_user, which_room);
381 /* Load the message list */
382 cdbfr = cdb_fetch(CDB_MSGLISTS, &which_room->QRnumber, sizeof(long));
384 msglist = (long *) cdbfr->ptr;
385 cdbfr->ptr = NULL; /* CtdlSetSeen() now owns this memory */
386 num_msgs = cdbfr->len / sizeof(long);
389 return; /* No messages at all? No further action. */
392 is_set = malloc(num_msgs * sizeof(char));
393 memset(is_set, 0, (num_msgs * sizeof(char)) );
395 /* Decide which message set we're manipulating */
397 case ctdlsetseen_seen:
398 vset = NewStrBufPlain(vbuf.v_seen, -1);
400 case ctdlsetseen_answered:
401 vset = NewStrBufPlain(vbuf.v_answered, -1);
408 #if 0 /* This is a special diagnostic section. Do not allow it to run during normal operation. */
409 CtdlLogPrintf(CTDL_DEBUG, "There are %d messages in the room.\n", num_msgs);
410 for (i=0; i<num_msgs; ++i) {
411 if ((i > 0) && (msglist[i] <= msglist[i-1])) abort();
413 CtdlLogPrintf(CTDL_DEBUG, "We are twiddling %d of them.\n", num_target_msgnums);
414 for (k=0; k<num_target_msgnums; ++k) {
415 if ((k > 0) && (target_msgnums[k] <= target_msgnums[k-1])) abort();
419 CtdlLogPrintf(CTDL_DEBUG, "before update: %s\n", ChrPtr(vset));
421 /* Translate the existing sequence set into an array of booleans */
422 setstr = NewStrBuf();
426 while (StrBufExtract_NextToken(setstr, vset, &pvset, ',') >= 0) {
428 StrBufExtract_token(lostr, setstr, 0, ':');
429 if (StrBufNum_tokens(setstr, ':') >= 2) {
430 StrBufExtract_token(histr, setstr, 1, ':');
434 StrBufAppendBuf(histr, lostr, 0);
437 if (!strcmp(ChrPtr(histr), "*")) {
444 for (i = 0; i < num_msgs; ++i) {
445 if ((msglist[i] >= lo) && (msglist[i] <= hi)) {
455 /* Now translate the array of booleans back into a sequence set */
461 for (i=0; i<num_msgs; ++i) {
465 for (k=0; k<num_target_msgnums; ++k) {
466 if (msglist[i] == target_msgnums[k]) {
467 is_seen = target_setting;
471 if ((was_seen == 0) && (is_seen == 1)) {
474 else if ((was_seen == 1) && (is_seen == 0)) {
477 if (StrLength(vset) > 0) {
478 StrBufAppendBufPlain(vset, HKEY(","), 0);
481 StrBufAppendPrintf(vset, "%ld", hi);
484 StrBufAppendPrintf(vset, "%ld:%ld", lo, hi);
488 if ((is_seen) && (i == num_msgs - 1)) {
489 if (StrLength(vset) > 0) {
490 StrBufAppendBufPlain(vset, HKEY(","), 0);
492 if ((i==0) || (was_seen == 0)) {
493 StrBufAppendPrintf(vset, "%ld", msglist[i]);
496 StrBufAppendPrintf(vset, "%ld:%ld", lo, msglist[i]);
504 * We will have to stuff this string back into a 4096 byte buffer, so if it's
505 * larger than that now, truncate it by removing tokens from the beginning.
506 * The limit of 100 iterations is there to prevent an infinite loop in case
507 * something unexpected happens.
509 int number_of_truncations = 0;
510 while ( (StrLength(vset) > SIZ) && (number_of_truncations < 100) ) {
511 StrBufRemove_token(vset, 0, ',');
512 ++number_of_truncations;
516 * If we're truncating the sequence set of messages marked with the 'seen' flag,
517 * we want the earliest messages (the truncated ones) to be marked, not unmarked.
518 * Otherwise messages at the beginning will suddenly appear to be 'unseen'.
520 if ( (which_set == ctdlsetseen_seen) && (number_of_truncations > 0) ) {
522 first_tok = NewStrBuf();
523 StrBufExtract_token(first_tok, vset, 0, ',');
524 StrBufRemove_token(vset, 0, ',');
526 if (StrBufNum_tokens(first_tok, ':') > 1) {
527 StrBufRemove_token(first_tok, 0, ':');
531 new_set = NewStrBuf();
532 StrBufAppendBufPlain(new_set, HKEY("1:"), 0);
533 StrBufAppendBuf(new_set, first_tok, 0);
534 StrBufAppendBufPlain(new_set, HKEY(":"), 0);
535 StrBufAppendBuf(new_set, vset, 0);
538 FreeStrBuf(&first_tok);
542 CtdlLogPrintf(CTDL_DEBUG, " after update: %s\n", ChrPtr(vset));
544 /* Decide which message set we're manipulating */
546 case ctdlsetseen_seen:
547 safestrncpy(vbuf.v_seen, ChrPtr(vset), sizeof vbuf.v_seen);
549 case ctdlsetseen_answered:
550 safestrncpy(vbuf.v_answered, ChrPtr(vset), sizeof vbuf.v_answered);
556 CtdlSetRelationship(&vbuf, which_user, which_room);
562 * API function to perform an operation for each qualifying message in the
563 * current room. (Returns the number of messages processed.)
565 int CtdlForEachMessage(int mode,
569 struct CtdlMessage *compare,
570 void (*CallBack) (long, void *),
576 struct cdbdata *cdbfr;
577 long *msglist = NULL;
579 int num_processed = 0;
582 struct CtdlMessage *msg = NULL;
585 int printed_lastold = 0;
586 int num_search_msgs = 0;
587 long *search_msgs = NULL;
589 int need_to_free_re = 0;
592 if ((content_type) && (!IsEmptyStr(content_type))) {
593 regcomp(&re, content_type, 0);
597 /* Learn about the user and room in question */
598 getuser(&CC->user, CC->curr_user);
599 CtdlGetRelationship(&vbuf, &CC->user, &CC->room);
601 /* Load the message list */
602 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long));
604 msglist = (long *) cdbfr->ptr;
605 num_msgs = cdbfr->len / sizeof(long);
607 if (need_to_free_re) regfree(&re);
608 return 0; /* No messages at all? No further action. */
613 * Now begin the traversal.
615 if (num_msgs > 0) for (a = 0; a < num_msgs; ++a) {
617 /* If the caller is looking for a specific MIME type, filter
618 * out all messages which are not of the type requested.
620 if ((content_type != NULL) && (!IsEmptyStr(content_type))) {
622 /* This call to GetMetaData() sits inside this loop
623 * so that we only do the extra database read per msg
624 * if we need to. Doing the extra read all the time
625 * really kills the server. If we ever need to use
626 * metadata for another search criterion, we need to
627 * move the read somewhere else -- but still be smart
628 * enough to only do the read if the caller has
629 * specified something that will need it.
631 GetMetaData(&smi, msglist[a]);
633 /* if (strcasecmp(smi.meta_content_type, content_type)) { old non-regex way */
634 if (regexec(&re, smi.meta_content_type, 1, &pm, 0) != 0) {
640 num_msgs = sort_msglist(msglist, num_msgs);
642 /* If a template was supplied, filter out the messages which
643 * don't match. (This could induce some delays!)
646 if (compare != NULL) {
647 for (a = 0; a < num_msgs; ++a) {
648 msg = CtdlFetchMessage(msglist[a], 1);
650 if (CtdlMsgCmp(msg, compare)) {
653 CtdlFreeMessage(msg);
659 /* If an EUID was specified, throw away all messages except the correct one. */
660 if (mode == MSGS_EUID) {
664 if ((num_msgs > 0) && (search_string) ) {
665 correct_msgnum = locate_message_by_euid(search_string, &CC->room);
666 if ( (num_msgs > 0) && (correct_msgnum >= 0L) ) {
667 for (i=0; i<num_msgs; ++i) {
668 if (msglist[i] == correct_msgnum) {
675 msglist[0] = correct_msgnum;
678 num_msgs = 0; /* didn't find the right one ... dump the rest */
680 mode = MSGS_ALL; /* treat it like 'read all' from now on */
683 /* If a search string was specified, get a message list from
684 * the full text index and remove messages which aren't on both
688 * Since the lists are sorted and strictly ascending, and the
689 * output list is guaranteed to be shorter than or equal to the
690 * input list, we overwrite the bottom of the input list. This
691 * eliminates the need to memmove big chunks of the list over and
694 if ( (num_msgs > 0) && (mode == MSGS_SEARCH) && (search_string) ) {
696 /* Call search module via hook mechanism.
697 * NULL means use any search function available.
698 * otherwise replace with a char * to name of search routine
700 CtdlModuleDoSearch(&num_search_msgs, &search_msgs, search_string, "fulltext");
702 if (num_search_msgs > 0) {
706 orig_num_msgs = num_msgs;
708 for (i=0; i<orig_num_msgs; ++i) {
709 for (j=0; j<num_search_msgs; ++j) {
710 if (msglist[i] == search_msgs[j]) {
711 msglist[num_msgs++] = msglist[i];
717 num_msgs = 0; /* No messages qualify */
719 if (search_msgs != NULL) free(search_msgs);
721 /* Now that we've purged messages which don't contain the search
722 * string, treat a MSGS_SEARCH just like a MSGS_ALL from this
729 * Now iterate through the message list, according to the
730 * criteria supplied by the caller.
733 for (a = 0; a < num_msgs; ++a) {
734 thismsg = msglist[a];
735 if (mode == MSGS_ALL) {
739 is_seen = is_msg_in_sequence_set(
740 vbuf.v_seen, thismsg);
741 if (is_seen) lastold = thismsg;
747 || ((mode == MSGS_OLD) && (is_seen))
748 || ((mode == MSGS_NEW) && (!is_seen))
749 || ((mode == MSGS_LAST) && (a >= (num_msgs - ref)))
750 || ((mode == MSGS_FIRST) && (a < ref))
751 || ((mode == MSGS_GT) && (thismsg > ref))
752 || ((mode == MSGS_EQ) && (thismsg == ref))
755 if ((mode == MSGS_NEW) && (CC->user.flags & US_LASTOLD) && (lastold > 0L) && (printed_lastold == 0) && (!is_seen)) {
757 CallBack(lastold, userdata);
761 if (CallBack) CallBack(thismsg, userdata);
765 cdb_free(cdbfr); /* Clean up */
766 if (need_to_free_re) regfree(&re);
767 return num_processed;
773 * cmd_msgs() - get list of message #'s in this room
774 * implements the MSGS server command using CtdlForEachMessage()
776 void cmd_msgs(char *cmdbuf)
785 int with_template = 0;
786 struct CtdlMessage *template = NULL;
787 int with_headers = 0;
788 char search_string[1024];
790 extract_token(which, cmdbuf, 0, '|', sizeof which);
791 cm_ref = extract_int(cmdbuf, 1);
792 extract_token(search_string, cmdbuf, 1, '|', sizeof search_string);
793 with_template = extract_int(cmdbuf, 2);
794 with_headers = extract_int(cmdbuf, 3);
797 if (!strncasecmp(which, "OLD", 3))
799 else if (!strncasecmp(which, "NEW", 3))
801 else if (!strncasecmp(which, "FIRST", 5))
803 else if (!strncasecmp(which, "LAST", 4))
805 else if (!strncasecmp(which, "GT", 2))
807 else if (!strncasecmp(which, "SEARCH", 6))
809 else if (!strncasecmp(which, "EUID", 4))
814 if ((!(CC->logged_in)) && (!(CC->internal_pgm))) {
815 cprintf("%d not logged in\n", ERROR + NOT_LOGGED_IN);
819 if ( (mode == MSGS_SEARCH) && (!config.c_enable_fulltext) ) {
820 cprintf("%d Full text index is not enabled on this server.\n",
821 ERROR + CMD_NOT_SUPPORTED);
827 cprintf("%d Send template then receive message list\n",
829 template = (struct CtdlMessage *)
830 malloc(sizeof(struct CtdlMessage));
831 memset(template, 0, sizeof(struct CtdlMessage));
832 template->cm_magic = CTDLMESSAGE_MAGIC;
833 template->cm_anon_type = MES_NORMAL;
835 while(client_getln(buf, sizeof buf) >= 0 && strcmp(buf,"000")) {
836 extract_token(tfield, buf, 0, '|', sizeof tfield);
837 extract_token(tvalue, buf, 1, '|', sizeof tvalue);
838 for (i='A'; i<='Z'; ++i) if (msgkeys[i]!=NULL) {
839 if (!strcasecmp(tfield, msgkeys[i])) {
840 template->cm_fields[i] =
848 cprintf("%d \n", LISTING_FOLLOWS);
851 CtdlForEachMessage(mode,
852 ( (mode == MSGS_SEARCH) ? 0 : cm_ref ),
853 ( ((mode == MSGS_SEARCH)||(mode == MSGS_EUID)) ? search_string : NULL ),
856 (with_headers ? headers_listing : simple_listing),
859 if (template != NULL) CtdlFreeMessage(template);
867 * help_subst() - support routine for help file viewer
869 void help_subst(char *strbuf, char *source, char *dest)
874 while (p = pattern2(strbuf, source), (p >= 0)) {
875 strcpy(workbuf, &strbuf[p + strlen(source)]);
876 strcpy(&strbuf[p], dest);
877 strcat(strbuf, workbuf);
882 void do_help_subst(char *buffer)
886 help_subst(buffer, "^nodename", config.c_nodename);
887 help_subst(buffer, "^humannode", config.c_humannode);
888 help_subst(buffer, "^fqdn", config.c_fqdn);
889 help_subst(buffer, "^username", CC->user.fullname);
890 snprintf(buf2, sizeof buf2, "%ld", CC->user.usernum);
891 help_subst(buffer, "^usernum", buf2);
892 help_subst(buffer, "^sysadm", config.c_sysadm);
893 help_subst(buffer, "^variantname", CITADEL);
894 snprintf(buf2, sizeof buf2, "%d", config.c_maxsessions);
895 help_subst(buffer, "^maxsessions", buf2);
896 help_subst(buffer, "^bbsdir", ctdl_message_dir);
902 * memfmout() - Citadel text formatter and paginator.
903 * Although the original purpose of this routine was to format
904 * text to the reader's screen width, all we're really using it
905 * for here is to format text out to 80 columns before sending it
906 * to the client. The client software may reformat it again.
909 char *mptr, /* where are we going to get our text from? */
910 char subst, /* nonzero if we should do substitutions */
911 char *nl) /* string to terminate lines with */
919 static int width = 80;
924 c = 1; /* c is the current pos */
928 while (ch = *mptr, ((ch != 0) && (strlen(buffer) < 126))) {
930 buffer[strlen(buffer) + 1] = 0;
931 buffer[strlen(buffer)] = ch;
934 if (buffer[0] == '^')
935 do_help_subst(buffer);
937 buffer[strlen(buffer) + 1] = 0;
939 strcpy(buffer, &buffer[1]);
947 if (((ch == 13) || (ch == 10)) && (old != 13) && (old != 10)) {
950 if (((old == 13) || (old == 10)) && (isspace(real))) {
955 if (((strlen(aaa) + c) > (width - 5)) && (strlen(aaa) > (width - 5))) {
956 cprintf("%s%s", nl, aaa);
965 if ((strlen(aaa) + c) > (width - 5)) {
974 if ((ch == 13) || (ch == 10)) {
975 cprintf("%s%s", aaa, nl);
982 cprintf("%s%s", aaa, nl);
988 * Callback function for mime parser that simply lists the part
990 void list_this_part(char *name, char *filename, char *partnum, char *disp,
991 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
992 char *cbid, void *cbuserdata)
996 ma = (struct ma_info *)cbuserdata;
997 if (ma->is_ma == 0) {
998 cprintf("part=%s|%s|%s|%s|%s|%ld|%s\n",
999 name, filename, partnum, disp, cbtype, (long)length, cbid);
1004 * Callback function for multipart prefix
1006 void list_this_pref(char *name, char *filename, char *partnum, char *disp,
1007 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
1008 char *cbid, void *cbuserdata)
1012 ma = (struct ma_info *)cbuserdata;
1013 if (!strcasecmp(cbtype, "multipart/alternative")) {
1017 if (ma->is_ma == 0) {
1018 cprintf("pref=%s|%s\n", partnum, cbtype);
1023 * Callback function for multipart sufffix
1025 void list_this_suff(char *name, char *filename, char *partnum, char *disp,
1026 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
1027 char *cbid, void *cbuserdata)
1031 ma = (struct ma_info *)cbuserdata;
1032 if (ma->is_ma == 0) {
1033 cprintf("suff=%s|%s\n", partnum, cbtype);
1035 if (!strcasecmp(cbtype, "multipart/alternative")) {
1042 * Callback function for mime parser that opens a section for downloading
1044 void mime_download(char *name, char *filename, char *partnum, char *disp,
1045 void *content, char *cbtype, char *cbcharset, size_t length,
1046 char *encoding, char *cbid, void *cbuserdata)
1050 /* Silently go away if there's already a download open. */
1051 if (CC->download_fp != NULL)
1055 (!IsEmptyStr(partnum) && (!strcasecmp(CC->download_desired_section, partnum)))
1056 || (!IsEmptyStr(cbid) && (!strcasecmp(CC->download_desired_section, cbid)))
1058 CC->download_fp = tmpfile();
1059 if (CC->download_fp == NULL)
1062 rv = fwrite(content, length, 1, CC->download_fp);
1063 fflush(CC->download_fp);
1064 rewind(CC->download_fp);
1066 OpenCmdResult(filename, cbtype);
1073 * Callback function for mime parser that outputs a section all at once.
1074 * We can specify the desired section by part number *or* content-id.
1076 void mime_spew_section(char *name, char *filename, char *partnum, char *disp,
1077 void *content, char *cbtype, char *cbcharset, size_t length,
1078 char *encoding, char *cbid, void *cbuserdata)
1080 int *found_it = (int *)cbuserdata;
1083 (!IsEmptyStr(partnum) && (!strcasecmp(CC->download_desired_section, partnum)))
1084 || (!IsEmptyStr(cbid) && (!strcasecmp(CC->download_desired_section, cbid)))
1087 cprintf("%d %d|-1|%s|%s\n",
1093 client_write(content, length);
1100 * Load a message from disk into memory.
1101 * This is used by CtdlOutputMsg() and other fetch functions.
1103 * NOTE: Caller is responsible for freeing the returned CtdlMessage struct
1104 * using the CtdlMessageFree() function.
1106 struct CtdlMessage *CtdlFetchMessage(long msgnum, int with_body)
1108 struct cdbdata *dmsgtext;
1109 struct CtdlMessage *ret = NULL;
1113 cit_uint8_t field_header;
1115 CtdlLogPrintf(CTDL_DEBUG, "CtdlFetchMessage(%ld, %d)\n", msgnum, with_body);
1117 dmsgtext = cdb_fetch(CDB_MSGMAIN, &msgnum, sizeof(long));
1118 if (dmsgtext == NULL) {
1121 mptr = dmsgtext->ptr;
1122 upper_bound = mptr + dmsgtext->len;
1124 /* Parse the three bytes that begin EVERY message on disk.
1125 * The first is always 0xFF, the on-disk magic number.
1126 * The second is the anonymous/public type byte.
1127 * The third is the format type byte (vari, fixed, or MIME).
1131 CtdlLogPrintf(CTDL_ERR, "Message %ld appears to be corrupted.\n", msgnum);
1135 ret = (struct CtdlMessage *) malloc(sizeof(struct CtdlMessage));
1136 memset(ret, 0, sizeof(struct CtdlMessage));
1138 ret->cm_magic = CTDLMESSAGE_MAGIC;
1139 ret->cm_anon_type = *mptr++; /* Anon type byte */
1140 ret->cm_format_type = *mptr++; /* Format type byte */
1143 * The rest is zero or more arbitrary fields. Load them in.
1144 * We're done when we encounter either a zero-length field or
1145 * have just processed the 'M' (message text) field.
1148 if (mptr >= upper_bound) {
1151 field_header = *mptr++;
1152 ret->cm_fields[field_header] = strdup(mptr);
1154 while (*mptr++ != 0); /* advance to next field */
1156 } while ((mptr < upper_bound) && (field_header != 'M'));
1160 /* Always make sure there's something in the msg text field. If
1161 * it's NULL, the message text is most likely stored separately,
1162 * so go ahead and fetch that. Failing that, just set a dummy
1163 * body so other code doesn't barf.
1165 if ( (ret->cm_fields['M'] == NULL) && (with_body) ) {
1166 dmsgtext = cdb_fetch(CDB_BIGMSGS, &msgnum, sizeof(long));
1167 if (dmsgtext != NULL) {
1168 ret->cm_fields['M'] = strdup(dmsgtext->ptr);
1172 if (ret->cm_fields['M'] == NULL) {
1173 ret->cm_fields['M'] = strdup("\r\n\r\n (no text)\r\n");
1176 /* Perform "before read" hooks (aborting if any return nonzero) */
1177 if (PerformMessageHooks(ret, EVT_BEFOREREAD) > 0) {
1178 CtdlFreeMessage(ret);
1187 * Returns 1 if the supplied pointer points to a valid Citadel message.
1188 * If the pointer is NULL or the magic number check fails, returns 0.
1190 int is_valid_message(struct CtdlMessage *msg) {
1193 if ((msg->cm_magic) != CTDLMESSAGE_MAGIC) {
1194 CtdlLogPrintf(CTDL_WARNING, "is_valid_message() -- self-check failed\n");
1202 * 'Destructor' for struct CtdlMessage
1204 void CtdlFreeMessage(struct CtdlMessage *msg)
1208 if (is_valid_message(msg) == 0)
1210 if (msg != NULL) free (msg);
1214 for (i = 0; i < 256; ++i)
1215 if (msg->cm_fields[i] != NULL) {
1216 free(msg->cm_fields[i]);
1219 msg->cm_magic = 0; /* just in case */
1225 * Pre callback function for multipart/alternative
1227 * NOTE: this differs from the standard behavior for a reason. Normally when
1228 * displaying multipart/alternative you want to show the _last_ usable
1229 * format in the message. Here we show the _first_ one, because it's
1230 * usually text/plain. Since this set of functions is designed for text
1231 * output to non-MIME-aware clients, this is the desired behavior.
1234 void fixed_output_pre(char *name, char *filename, char *partnum, char *disp,
1235 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
1236 char *cbid, void *cbuserdata)
1240 ma = (struct ma_info *)cbuserdata;
1241 CtdlLogPrintf(CTDL_DEBUG, "fixed_output_pre() type=<%s>\n", cbtype);
1242 if (!strcasecmp(cbtype, "multipart/alternative")) {
1246 if (!strcasecmp(cbtype, "message/rfc822")) {
1252 * Post callback function for multipart/alternative
1254 void fixed_output_post(char *name, char *filename, char *partnum, char *disp,
1255 void *content, char *cbtype, char *cbcharset, size_t length,
1256 char *encoding, char *cbid, void *cbuserdata)
1260 ma = (struct ma_info *)cbuserdata;
1261 CtdlLogPrintf(CTDL_DEBUG, "fixed_output_post() type=<%s>\n", cbtype);
1262 if (!strcasecmp(cbtype, "multipart/alternative")) {
1266 if (!strcasecmp(cbtype, "message/rfc822")) {
1272 * Inline callback function for mime parser that wants to display text
1274 void fixed_output(char *name, char *filename, char *partnum, char *disp,
1275 void *content, char *cbtype, char *cbcharset, size_t length,
1276 char *encoding, char *cbid, void *cbuserdata)
1283 ma = (struct ma_info *)cbuserdata;
1285 CtdlLogPrintf(CTDL_DEBUG,
1286 "fixed_output() part %s: %s (%s) (%ld bytes)\n",
1287 partnum, filename, cbtype, (long)length);
1290 * If we're in the middle of a multipart/alternative scope and
1291 * we've already printed another section, skip this one.
1293 if ( (ma->is_ma) && (ma->did_print) ) {
1294 CtdlLogPrintf(CTDL_DEBUG, "Skipping part %s (%s)\n", partnum, cbtype);
1299 if ( (!strcasecmp(cbtype, "text/plain"))
1300 || (IsEmptyStr(cbtype)) ) {
1303 client_write(wptr, length);
1304 if (wptr[length-1] != '\n') {
1311 if (!strcasecmp(cbtype, "text/html")) {
1312 ptr = html_to_ascii(content, length, 80, 0);
1314 client_write(ptr, wlen);
1315 if (ptr[wlen-1] != '\n') {
1322 if (ma->use_fo_hooks) {
1323 if (PerformFixedOutputHooks(cbtype, content, length)) {
1324 /* above function returns nonzero if it handled the part */
1329 if (strncasecmp(cbtype, "multipart/", 10)) {
1330 cprintf("Part %s: %s (%s) (%ld bytes)\r\n",
1331 partnum, filename, cbtype, (long)length);
1337 * The client is elegant and sophisticated and wants to be choosy about
1338 * MIME content types, so figure out which multipart/alternative part
1339 * we're going to send.
1341 * We use a system of weights. When we find a part that matches one of the
1342 * MIME types we've declared as preferential, we can store it in ma->chosen_part
1343 * and then set ma->chosen_pref to that MIME type's position in our preference
1344 * list. If we then hit another match, we only replace the first match if
1345 * the preference value is lower.
1347 void choose_preferred(char *name, char *filename, char *partnum, char *disp,
1348 void *content, char *cbtype, char *cbcharset, size_t length,
1349 char *encoding, char *cbid, void *cbuserdata)
1355 ma = (struct ma_info *)cbuserdata;
1357 // NOTE: REMOVING THIS CONDITIONAL FIXES BUG 220
1358 // http://bugzilla.citadel.org/show_bug.cgi?id=220
1359 // I don't know if there are any side effects! Please TEST TEST TEST
1360 //if (ma->is_ma > 0) {
1362 for (i=0; i<num_tokens(CC->preferred_formats, '|'); ++i) {
1363 extract_token(buf, CC->preferred_formats, i, '|', sizeof buf);
1364 if ( (!strcasecmp(buf, cbtype)) && (!ma->freeze) ) {
1365 if (i < ma->chosen_pref) {
1366 CtdlLogPrintf(CTDL_DEBUG, "Setting chosen part: <%s>\n", partnum);
1367 safestrncpy(ma->chosen_part, partnum, sizeof ma->chosen_part);
1368 ma->chosen_pref = i;
1375 * Now that we've chosen our preferred part, output it.
1377 void output_preferred(char *name, char *filename, char *partnum, char *disp,
1378 void *content, char *cbtype, char *cbcharset, size_t length,
1379 char *encoding, char *cbid, void *cbuserdata)
1383 int add_newline = 0;
1387 ma = (struct ma_info *)cbuserdata;
1389 /* This is not the MIME part you're looking for... */
1390 if (strcasecmp(partnum, ma->chosen_part)) return;
1392 /* If the content-type of this part is in our preferred formats
1393 * list, we can simply output it verbatim.
1395 for (i=0; i<num_tokens(CC->preferred_formats, '|'); ++i) {
1396 extract_token(buf, CC->preferred_formats, i, '|', sizeof buf);
1397 if (!strcasecmp(buf, cbtype)) {
1398 /* Yeah! Go! W00t!! */
1400 text_content = (char *)content;
1401 if (text_content[length-1] != '\n') {
1404 cprintf("Content-type: %s", cbtype);
1405 if (!IsEmptyStr(cbcharset)) {
1406 cprintf("; charset=%s", cbcharset);
1408 cprintf("\nContent-length: %d\n",
1409 (int)(length + add_newline) );
1410 if (!IsEmptyStr(encoding)) {
1411 cprintf("Content-transfer-encoding: %s\n", encoding);
1414 cprintf("Content-transfer-encoding: 7bit\n");
1416 cprintf("X-Citadel-MSG4-Partnum: %s\n", partnum);
1418 client_write(content, length);
1419 if (add_newline) cprintf("\n");
1424 /* No translations required or possible: output as text/plain */
1425 cprintf("Content-type: text/plain\n\n");
1426 fixed_output(name, filename, partnum, disp, content, cbtype, cbcharset,
1427 length, encoding, cbid, cbuserdata);
1432 char desired_section[64];
1439 * Callback function for
1441 void extract_encapsulated_message(char *name, char *filename, char *partnum, char *disp,
1442 void *content, char *cbtype, char *cbcharset, size_t length,
1443 char *encoding, char *cbid, void *cbuserdata)
1445 struct encapmsg *encap;
1447 encap = (struct encapmsg *)cbuserdata;
1449 /* Only proceed if this is the desired section... */
1450 if (!strcasecmp(encap->desired_section, partnum)) {
1451 encap->msglen = length;
1452 encap->msg = malloc(length + 2);
1453 memcpy(encap->msg, content, length);
1463 * Get a message off disk. (returns om_* values found in msgbase.h)
1466 int CtdlOutputMsg(long msg_num, /* message number (local) to fetch */
1467 int mode, /* how would you like that message? */
1468 int headers_only, /* eschew the message body? */
1469 int do_proto, /* do Citadel protocol responses? */
1470 int crlf, /* Use CRLF newlines instead of LF? */
1471 char *section, /* NULL or a message/rfc822 section */
1472 int flags /* should the bessage be exported clean? */
1474 struct CtdlMessage *TheMessage = NULL;
1475 int retcode = om_no_such_msg;
1476 struct encapmsg encap;
1478 CtdlLogPrintf(CTDL_DEBUG, "CtdlOutputMsg() msgnum=%ld, mode=%d, section=%s\n",
1480 (section ? section : "<>")
1483 if ((!(CC->logged_in)) && (!(CC->internal_pgm))) {
1484 if (do_proto) cprintf("%d Not logged in.\n",
1485 ERROR + NOT_LOGGED_IN);
1486 return(om_not_logged_in);
1489 /* FIXME: check message id against msglist for this room */
1492 * Fetch the message from disk. If we're in HEADERS_FAST mode,
1493 * request that we don't even bother loading the body into memory.
1495 if (headers_only == HEADERS_FAST) {
1496 TheMessage = CtdlFetchMessage(msg_num, 0);
1499 TheMessage = CtdlFetchMessage(msg_num, 1);
1502 if (TheMessage == NULL) {
1503 if (do_proto) cprintf("%d Can't locate msg %ld on disk\n",
1504 ERROR + MESSAGE_NOT_FOUND, msg_num);
1505 return(om_no_such_msg);
1508 /* Here is the weird form of this command, to process only an
1509 * encapsulated message/rfc822 section.
1511 if (section) if (!IsEmptyStr(section)) if (strcmp(section, "0")) {
1512 memset(&encap, 0, sizeof encap);
1513 safestrncpy(encap.desired_section, section, sizeof encap.desired_section);
1514 mime_parser(TheMessage->cm_fields['M'],
1516 *extract_encapsulated_message,
1517 NULL, NULL, (void *)&encap, 0
1519 CtdlFreeMessage(TheMessage);
1523 encap.msg[encap.msglen] = 0;
1524 TheMessage = convert_internet_message(encap.msg);
1525 encap.msg = NULL; /* no free() here, TheMessage owns it now */
1527 /* Now we let it fall through to the bottom of this
1528 * function, because TheMessage now contains the
1529 * encapsulated message instead of the top-level
1530 * message. Isn't that neat?
1535 if (do_proto) cprintf("%d msg %ld has no part %s\n",
1536 ERROR + MESSAGE_NOT_FOUND, msg_num, section);
1537 retcode = om_no_such_msg;
1542 /* Ok, output the message now */
1543 retcode = CtdlOutputPreLoadedMsg(TheMessage, mode, headers_only, do_proto, crlf, flags);
1544 CtdlFreeMessage(TheMessage);
1550 char *qp_encode_email_addrs(char *source)
1552 char user[256], node[256], name[256];
1553 const char headerStr[] = "=?UTF-8?Q?";
1557 int need_to_encode = 0;
1563 long nAddrPtrMax = 50;
1568 if (source == NULL) return source;
1569 if (IsEmptyStr(source)) return source;
1571 AddrPtr = malloc (sizeof (long) * nAddrPtrMax);
1572 AddrUtf8 = malloc (sizeof (long) * nAddrPtrMax);
1573 memset(AddrUtf8, 0, sizeof (long) * nAddrPtrMax);
1576 while (!IsEmptyStr (&source[i])) {
1577 if (nColons >= nAddrPtrMax){
1580 ptr = (long *) malloc(sizeof (long) * nAddrPtrMax * 2);
1581 memcpy (ptr, AddrPtr, sizeof (long) * nAddrPtrMax);
1582 free (AddrPtr), AddrPtr = ptr;
1584 ptr = (long *) malloc(sizeof (long) * nAddrPtrMax * 2);
1585 memset(&ptr[nAddrPtrMax], 0,
1586 sizeof (long) * nAddrPtrMax);
1588 memcpy (ptr, AddrUtf8, sizeof (long) * nAddrPtrMax);
1589 free (AddrUtf8), AddrUtf8 = ptr;
1592 if (((unsigned char) source[i] < 32) ||
1593 ((unsigned char) source[i] > 126)) {
1595 AddrUtf8[nColons] = 1;
1597 if (source[i] == '"')
1598 InQuotes = !InQuotes;
1599 if (!InQuotes && source[i] == ',') {
1600 AddrPtr[nColons] = i;
1605 if (need_to_encode == 0) {
1612 EncodedMaxLen = nColons * (sizeof(headerStr) + 3) + SourceLen * 3;
1613 Encoded = (char*) malloc (EncodedMaxLen);
1615 for (i = 0; i < nColons; i++)
1616 source[AddrPtr[i]++] = '\0';
1620 for (i = 0; i < nColons && nPtr != NULL; i++) {
1621 nmax = EncodedMaxLen - (nPtr - Encoded);
1623 process_rfc822_addr(&source[AddrPtr[i]],
1627 /* TODO: libIDN here ! */
1628 if (IsEmptyStr(name)) {
1629 n = snprintf(nPtr, nmax,
1630 (i==0)?"%s@%s" : ",%s@%s",
1634 EncodedName = rfc2047encode(name, strlen(name));
1635 n = snprintf(nPtr, nmax,
1636 (i==0)?"%s <%s@%s>" : ",%s <%s@%s>",
1637 EncodedName, user, node);
1642 n = snprintf(nPtr, nmax,
1643 (i==0)?"%s" : ",%s",
1644 &source[AddrPtr[i]]);
1650 ptr = (char*) malloc(EncodedMaxLen * 2);
1651 memcpy(ptr, Encoded, EncodedMaxLen);
1652 nnPtr = ptr + (nPtr - Encoded), nPtr = nnPtr;
1653 free(Encoded), Encoded = ptr;
1655 i--; /* do it once more with properly lengthened buffer */
1658 for (i = 0; i < nColons; i++)
1659 source[--AddrPtr[i]] = ',';
1666 /* If the last item in a list of recipients was truncated to a partial address,
1667 * remove it completely in order to avoid choking libSieve
1669 void sanitize_truncated_recipient(char *str)
1672 if (num_tokens(str, ',') < 2) return;
1674 int len = strlen(str);
1675 if (len < 900) return;
1676 if (len > 998) str[998] = 0;
1678 char *cptr = strrchr(str, ',');
1681 char *lptr = strchr(cptr, '<');
1682 char *rptr = strchr(cptr, '>');
1684 if ( (lptr) && (rptr) && (rptr > lptr) ) return;
1692 * Get a message off disk. (returns om_* values found in msgbase.h)
1694 int CtdlOutputPreLoadedMsg(
1695 struct CtdlMessage *TheMessage,
1696 int mode, /* how would you like that message? */
1697 int headers_only, /* eschew the message body? */
1698 int do_proto, /* do Citadel protocol responses? */
1699 int crlf, /* Use CRLF newlines instead of LF? */
1700 int flags /* should the bessage be exported clean? */
1704 cit_uint8_t ch, prev_ch;
1706 char display_name[256];
1708 char *nl; /* newline string */
1710 int subject_found = 0;
1713 /* Buffers needed for RFC822 translation. These are all filled
1714 * using functions that are bounds-checked, and therefore we can
1715 * make them substantially smaller than SIZ.
1722 char datestamp[100];
1724 CtdlLogPrintf(CTDL_DEBUG, "CtdlOutputPreLoadedMsg(TheMessage=%s, %d, %d, %d, %d\n",
1725 ((TheMessage == NULL) ? "NULL" : "not null"),
1726 mode, headers_only, do_proto, crlf);
1728 strcpy(mid, "unknown");
1729 nl = (crlf ? "\r\n" : "\n");
1731 if (!is_valid_message(TheMessage)) {
1732 CtdlLogPrintf(CTDL_ERR,
1733 "ERROR: invalid preloaded message for output\n");
1735 return(om_no_such_msg);
1738 /* Are we downloading a MIME component? */
1739 if (mode == MT_DOWNLOAD) {
1740 if (TheMessage->cm_format_type != FMT_RFC822) {
1742 cprintf("%d This is not a MIME message.\n",
1743 ERROR + ILLEGAL_VALUE);
1744 } else if (CC->download_fp != NULL) {
1745 if (do_proto) cprintf(
1746 "%d You already have a download open.\n",
1747 ERROR + RESOURCE_BUSY);
1749 /* Parse the message text component */
1750 mptr = TheMessage->cm_fields['M'];
1751 mime_parser(mptr, NULL, *mime_download, NULL, NULL, NULL, 0);
1752 /* If there's no file open by this time, the requested
1753 * section wasn't found, so print an error
1755 if (CC->download_fp == NULL) {
1756 if (do_proto) cprintf(
1757 "%d Section %s not found.\n",
1758 ERROR + FILE_NOT_FOUND,
1759 CC->download_desired_section);
1762 return((CC->download_fp != NULL) ? om_ok : om_mime_error);
1765 /* MT_SPEW_SECTION is like MT_DOWNLOAD except it outputs the whole MIME part
1766 * in a single server operation instead of opening a download file.
1768 if (mode == MT_SPEW_SECTION) {
1769 if (TheMessage->cm_format_type != FMT_RFC822) {
1771 cprintf("%d This is not a MIME message.\n",
1772 ERROR + ILLEGAL_VALUE);
1774 /* Parse the message text component */
1777 mptr = TheMessage->cm_fields['M'];
1778 mime_parser(mptr, NULL, *mime_spew_section, NULL, NULL, (void *)&found_it, 0);
1779 /* If section wasn't found, print an error
1782 if (do_proto) cprintf(
1783 "%d Section %s not found.\n",
1784 ERROR + FILE_NOT_FOUND,
1785 CC->download_desired_section);
1788 return((CC->download_fp != NULL) ? om_ok : om_mime_error);
1791 /* now for the user-mode message reading loops */
1792 if (do_proto) cprintf("%d msg:\n", LISTING_FOLLOWS);
1794 /* Does the caller want to skip the headers? */
1795 if (headers_only == HEADERS_NONE) goto START_TEXT;
1797 /* Tell the client which format type we're using. */
1798 if ( (mode == MT_CITADEL) && (do_proto) ) {
1799 cprintf("type=%d\n", TheMessage->cm_format_type);
1802 /* nhdr=yes means that we're only displaying headers, no body */
1803 if ( (TheMessage->cm_anon_type == MES_ANONONLY)
1804 && ((mode == MT_CITADEL) || (mode == MT_MIME))
1807 cprintf("nhdr=yes\n");
1810 /* begin header processing loop for Citadel message format */
1812 if ((mode == MT_CITADEL) || (mode == MT_MIME)) {
1814 safestrncpy(display_name, "<unknown>", sizeof display_name);
1815 if (TheMessage->cm_fields['A']) {
1816 strcpy(buf, TheMessage->cm_fields['A']);
1817 if (TheMessage->cm_anon_type == MES_ANONONLY) {
1818 safestrncpy(display_name, "****", sizeof display_name);
1820 else if (TheMessage->cm_anon_type == MES_ANONOPT) {
1821 safestrncpy(display_name, "anonymous", sizeof display_name);
1824 safestrncpy(display_name, buf, sizeof display_name);
1826 if ((is_room_aide())
1827 && ((TheMessage->cm_anon_type == MES_ANONONLY)
1828 || (TheMessage->cm_anon_type == MES_ANONOPT))) {
1829 size_t tmp = strlen(display_name);
1830 snprintf(&display_name[tmp],
1831 sizeof display_name - tmp,
1836 /* Don't show Internet address for users on the
1837 * local Citadel network.
1840 if (TheMessage->cm_fields['N'] != NULL)
1841 if (!IsEmptyStr(TheMessage->cm_fields['N']))
1842 if (haschar(TheMessage->cm_fields['N'], '.') == 0) {
1846 /* Now spew the header fields in the order we like them. */
1847 safestrncpy(allkeys, FORDER, sizeof allkeys);
1848 for (i=0; i<strlen(allkeys); ++i) {
1849 k = (int) allkeys[i];
1851 if ( (TheMessage->cm_fields[k] != NULL)
1852 && (msgkeys[k] != NULL) ) {
1853 if ((k == 'V') || (k == 'R') || (k == 'Y')) {
1854 sanitize_truncated_recipient(TheMessage->cm_fields[k]);
1857 if (do_proto) cprintf("%s=%s\n",
1861 else if ((k == 'F') && (suppress_f)) {
1864 /* Masquerade display name if needed */
1866 if (do_proto) cprintf("%s=%s\n",
1868 TheMessage->cm_fields[k]
1877 /* begin header processing loop for RFC822 transfer format */
1882 strcpy(snode, NODENAME);
1883 if (mode == MT_RFC822) {
1884 for (i = 0; i < 256; ++i) {
1885 if (TheMessage->cm_fields[i]) {
1886 mptr = mpptr = TheMessage->cm_fields[i];
1889 safestrncpy(luser, mptr, sizeof luser);
1890 safestrncpy(suser, mptr, sizeof suser);
1892 else if (i == 'Y') {
1893 if ((flags & QP_EADDR) != 0) {
1894 mptr = qp_encode_email_addrs(mptr);
1896 sanitize_truncated_recipient(mptr);
1897 cprintf("CC: %s%s", mptr, nl);
1899 else if (i == 'P') {
1900 cprintf("Return-Path: %s%s", mptr, nl);
1902 else if (i == 'L') {
1903 cprintf("List-ID: %s%s", mptr, nl);
1905 else if (i == 'V') {
1906 if ((flags & QP_EADDR) != 0)
1907 mptr = qp_encode_email_addrs(mptr);
1908 cprintf("Envelope-To: %s%s", mptr, nl);
1910 else if (i == 'U') {
1911 cprintf("Subject: %s%s", mptr, nl);
1915 safestrncpy(mid, mptr, sizeof mid);
1917 safestrncpy(fuser, mptr, sizeof fuser);
1918 /* else if (i == 'O')
1919 cprintf("X-Citadel-Room: %s%s",
1922 safestrncpy(snode, mptr, sizeof snode);
1925 if (haschar(mptr, '@') == 0)
1927 sanitize_truncated_recipient(mptr);
1928 cprintf("To: %s@%s", mptr, config.c_fqdn);
1933 if ((flags & QP_EADDR) != 0) {
1934 mptr = qp_encode_email_addrs(mptr);
1936 sanitize_truncated_recipient(mptr);
1937 cprintf("To: %s", mptr);
1941 else if (i == 'T') {
1942 datestring(datestamp, sizeof datestamp,
1943 atol(mptr), DATESTRING_RFC822);
1944 cprintf("Date: %s%s", datestamp, nl);
1946 else if (i == 'W') {
1947 cprintf("References: ");
1948 k = num_tokens(mptr, '|');
1949 for (j=0; j<k; ++j) {
1950 extract_token(buf, mptr, j, '|', sizeof buf);
1951 cprintf("<%s>", buf);
1964 if (subject_found == 0) {
1965 cprintf("Subject: (no subject)%s", nl);
1969 for (i=0; !IsEmptyStr(&suser[i]); ++i) {
1970 suser[i] = tolower(suser[i]);
1971 if (!isalnum(suser[i])) suser[i]='_';
1974 if (mode == MT_RFC822) {
1975 if (!strcasecmp(snode, NODENAME)) {
1976 safestrncpy(snode, FQDN, sizeof snode);
1979 /* Construct a fun message id */
1980 cprintf("Message-ID: <%s", mid);/// todo: this possibly breaks threadding mails.
1981 if (strchr(mid, '@')==NULL) {
1982 cprintf("@%s", snode);
1986 if (!is_room_aide() && (TheMessage->cm_anon_type == MES_ANONONLY)) {
1987 cprintf("From: \"----\" <x@x.org>%s", nl);
1989 else if (!is_room_aide() && (TheMessage->cm_anon_type == MES_ANONOPT)) {
1990 cprintf("From: \"anonymous\" <x@x.org>%s", nl);
1992 else if (!IsEmptyStr(fuser)) {
1993 cprintf("From: \"%s\" <%s>%s", luser, fuser, nl);
1996 cprintf("From: \"%s\" <%s@%s>%s", luser, suser, snode, nl);
1999 /* Blank line signifying RFC822 end-of-headers */
2000 if (TheMessage->cm_format_type != FMT_RFC822) {
2005 /* end header processing loop ... at this point, we're in the text */
2007 if (headers_only == HEADERS_FAST) goto DONE;
2008 mptr = TheMessage->cm_fields['M'];
2010 /* Tell the client about the MIME parts in this message */
2011 if (TheMessage->cm_format_type == FMT_RFC822) {
2012 if ( (mode == MT_CITADEL) || (mode == MT_MIME) ) {
2013 memset(&ma, 0, sizeof(struct ma_info));
2014 mime_parser(mptr, NULL,
2015 (do_proto ? *list_this_part : NULL),
2016 (do_proto ? *list_this_pref : NULL),
2017 (do_proto ? *list_this_suff : NULL),
2020 else if (mode == MT_RFC822) { /* unparsed RFC822 dump */
2021 char *start_of_text = NULL;
2022 start_of_text = strstr(mptr, "\n\r\n");
2023 if (start_of_text == NULL) start_of_text = strstr(mptr, "\n\n");
2024 if (start_of_text == NULL) start_of_text = mptr;
2026 start_of_text = strstr(start_of_text, "\n");
2031 int nllen = strlen(nl);
2033 while (ch=*mptr, ch!=0) {
2039 ((headers_only == HEADERS_NONE) && (mptr >= start_of_text))
2040 || ((headers_only == HEADERS_ONLY) && (mptr < start_of_text))
2041 || ((headers_only != HEADERS_NONE) && (headers_only != HEADERS_ONLY))
2044 sprintf(&outbuf[outlen], "%s", nl);
2048 outbuf[outlen++] = ch;
2052 if (flags & ESC_DOT)
2054 if ((prev_ch == 10) && (ch == '.') && ((*(mptr+1) == 13) || (*(mptr+1) == 10)))
2056 outbuf[outlen++] = '.';
2061 if (outlen > 1000) {
2062 client_write(outbuf, outlen);
2067 client_write(outbuf, outlen);
2075 if (headers_only == HEADERS_ONLY) {
2079 /* signify start of msg text */
2080 if ( (mode == MT_CITADEL) || (mode == MT_MIME) ) {
2081 if (do_proto) cprintf("text\n");
2084 /* If the format type on disk is 1 (fixed-format), then we want
2085 * everything to be output completely literally ... regardless of
2086 * what message transfer format is in use.
2088 if (TheMessage->cm_format_type == FMT_FIXED) {
2090 if (mode == MT_MIME) {
2091 cprintf("Content-type: text/plain\n\n");
2095 while (ch = *mptr++, ch > 0) {
2098 if ((ch == 10) || (buflen > 250)) {
2100 cprintf("%s%s", buf, nl);
2109 if (!IsEmptyStr(buf))
2110 cprintf("%s%s", buf, nl);
2113 /* If the message on disk is format 0 (Citadel vari-format), we
2114 * output using the formatter at 80 columns. This is the final output
2115 * form if the transfer format is RFC822, but if the transfer format
2116 * is Citadel proprietary, it'll still work, because the indentation
2117 * for new paragraphs is correct and the client will reformat the
2118 * message to the reader's screen width.
2120 if (TheMessage->cm_format_type == FMT_CITADEL) {
2121 if (mode == MT_MIME) {
2122 cprintf("Content-type: text/x-citadel-variformat\n\n");
2124 memfmout(mptr, 0, nl);
2127 /* If the message on disk is format 4 (MIME), we've gotta hand it
2128 * off to the MIME parser. The client has already been told that
2129 * this message is format 1 (fixed format), so the callback function
2130 * we use will display those parts as-is.
2132 if (TheMessage->cm_format_type == FMT_RFC822) {
2133 memset(&ma, 0, sizeof(struct ma_info));
2135 if (mode == MT_MIME) {
2136 ma.use_fo_hooks = 0;
2137 strcpy(ma.chosen_part, "1");
2138 ma.chosen_pref = 9999;
2139 mime_parser(mptr, NULL,
2140 *choose_preferred, *fixed_output_pre,
2141 *fixed_output_post, (void *)&ma, 0);
2142 mime_parser(mptr, NULL,
2143 *output_preferred, NULL, NULL, (void *)&ma, CC->msg4_dont_decode);
2146 ma.use_fo_hooks = 1;
2147 mime_parser(mptr, NULL,
2148 *fixed_output, *fixed_output_pre,
2149 *fixed_output_post, (void *)&ma, 0);
2154 DONE: /* now we're done */
2155 if (do_proto) cprintf("000\n");
2162 * display a message (mode 0 - Citadel proprietary)
2164 void cmd_msg0(char *cmdbuf)
2167 int headers_only = HEADERS_ALL;
2169 msgid = extract_long(cmdbuf, 0);
2170 headers_only = extract_int(cmdbuf, 1);
2172 CtdlOutputMsg(msgid, MT_CITADEL, headers_only, 1, 0, NULL, 0);
2178 * display a message (mode 2 - RFC822)
2180 void cmd_msg2(char *cmdbuf)
2183 int headers_only = HEADERS_ALL;
2185 msgid = extract_long(cmdbuf, 0);
2186 headers_only = extract_int(cmdbuf, 1);
2188 CtdlOutputMsg(msgid, MT_RFC822, headers_only, 1, 1, NULL, 0);
2194 * display a message (mode 3 - IGnet raw format - internal programs only)
2196 void cmd_msg3(char *cmdbuf)
2199 struct CtdlMessage *msg = NULL;
2202 if (CC->internal_pgm == 0) {
2203 cprintf("%d This command is for internal programs only.\n",
2204 ERROR + HIGHER_ACCESS_REQUIRED);
2208 msgnum = extract_long(cmdbuf, 0);
2209 msg = CtdlFetchMessage(msgnum, 1);
2211 cprintf("%d Message %ld not found.\n",
2212 ERROR + MESSAGE_NOT_FOUND, msgnum);
2216 serialize_message(&smr, msg);
2217 CtdlFreeMessage(msg);
2220 cprintf("%d Unable to serialize message\n",
2221 ERROR + INTERNAL_ERROR);
2225 cprintf("%d %ld\n", BINARY_FOLLOWS, (long)smr.len);
2226 client_write((char *)smr.ser, (int)smr.len);
2233 * Display a message using MIME content types
2235 void cmd_msg4(char *cmdbuf)
2240 msgid = extract_long(cmdbuf, 0);
2241 extract_token(section, cmdbuf, 1, '|', sizeof section);
2242 CtdlOutputMsg(msgid, MT_MIME, 0, 1, 0, (section[0] ? section : NULL) , 0);
2248 * Client tells us its preferred message format(s)
2250 void cmd_msgp(char *cmdbuf)
2252 if (!strcasecmp(cmdbuf, "dont_decode")) {
2253 CC->msg4_dont_decode = 1;
2254 cprintf("%d MSG4 will not pre-decode messages.\n", CIT_OK);
2257 safestrncpy(CC->preferred_formats, cmdbuf, sizeof(CC->preferred_formats));
2258 cprintf("%d Preferred MIME formats have been set.\n", CIT_OK);
2264 * Open a component of a MIME message as a download file
2266 void cmd_opna(char *cmdbuf)
2269 char desired_section[128];
2271 msgid = extract_long(cmdbuf, 0);
2272 extract_token(desired_section, cmdbuf, 1, '|', sizeof desired_section);
2273 safestrncpy(CC->download_desired_section, desired_section,
2274 sizeof CC->download_desired_section);
2275 CtdlOutputMsg(msgid, MT_DOWNLOAD, 0, 1, 1, NULL, 0);
2280 * Open a component of a MIME message and transmit it all at once
2282 void cmd_dlat(char *cmdbuf)
2285 char desired_section[128];
2287 msgid = extract_long(cmdbuf, 0);
2288 extract_token(desired_section, cmdbuf, 1, '|', sizeof desired_section);
2289 safestrncpy(CC->download_desired_section, desired_section,
2290 sizeof CC->download_desired_section);
2291 CtdlOutputMsg(msgid, MT_SPEW_SECTION, 0, 1, 1, NULL, 0);
2296 * Save one or more message pointers into a specified room
2297 * (Returns 0 for success, nonzero for failure)
2298 * roomname may be NULL to use the current room
2300 * Note that the 'supplied_msg' field may be set to NULL, in which case
2301 * the message will be fetched from disk, by number, if we need to perform
2302 * replication checks. This adds an additional database read, so if the
2303 * caller already has the message in memory then it should be supplied. (Obviously
2304 * this mode of operation only works if we're saving a single message.)
2306 int CtdlSaveMsgPointersInRoom(char *roomname, long newmsgidlist[], int num_newmsgs,
2307 int do_repl_check, struct CtdlMessage *supplied_msg)
2310 char hold_rm[ROOMNAMELEN];
2311 struct cdbdata *cdbfr;
2314 long highest_msg = 0L;
2317 struct CtdlMessage *msg = NULL;
2319 long *msgs_to_be_merged = NULL;
2320 int num_msgs_to_be_merged = 0;
2322 CtdlLogPrintf(CTDL_DEBUG,
2323 "CtdlSaveMsgPointersInRoom(room=%s, num_msgs=%d, repl=%d)\n",
2324 roomname, num_newmsgs, do_repl_check);
2326 strcpy(hold_rm, CC->room.QRname);
2329 if (newmsgidlist == NULL) return(ERROR + INTERNAL_ERROR);
2330 if (num_newmsgs < 1) return(ERROR + INTERNAL_ERROR);
2331 if (num_newmsgs > 1) supplied_msg = NULL;
2333 /* Now the regular stuff */
2334 if (lgetroom(&CC->room,
2335 ((roomname != NULL) ? roomname : CC->room.QRname) )
2337 CtdlLogPrintf(CTDL_ERR, "No such room <%s>\n", roomname);
2338 return(ERROR + ROOM_NOT_FOUND);
2342 msgs_to_be_merged = malloc(sizeof(long) * num_newmsgs);
2343 num_msgs_to_be_merged = 0;
2346 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long));
2347 if (cdbfr == NULL) {
2351 msglist = (long *) cdbfr->ptr;
2352 cdbfr->ptr = NULL; /* CtdlSaveMsgPointerInRoom() now owns this memory */
2353 num_msgs = cdbfr->len / sizeof(long);
2358 /* Create a list of msgid's which were supplied by the caller, but do
2359 * not already exist in the target room. It is absolutely taboo to
2360 * have more than one reference to the same message in a room.
2362 for (i=0; i<num_newmsgs; ++i) {
2364 if (num_msgs > 0) for (j=0; j<num_msgs; ++j) {
2365 if (msglist[j] == newmsgidlist[i]) {
2370 msgs_to_be_merged[num_msgs_to_be_merged++] = newmsgidlist[i];
2374 CtdlLogPrintf(9, "%d unique messages to be merged\n", num_msgs_to_be_merged);
2377 * Now merge the new messages
2379 msglist = realloc(msglist, (sizeof(long) * (num_msgs + num_msgs_to_be_merged)) );
2380 if (msglist == NULL) {
2381 CtdlLogPrintf(CTDL_ALERT, "ERROR: can't realloc message list!\n");
2383 memcpy(&msglist[num_msgs], msgs_to_be_merged, (sizeof(long) * num_msgs_to_be_merged) );
2384 num_msgs += num_msgs_to_be_merged;
2386 /* Sort the message list, so all the msgid's are in order */
2387 num_msgs = sort_msglist(msglist, num_msgs);
2389 /* Determine the highest message number */
2390 highest_msg = msglist[num_msgs - 1];
2392 /* Write it back to disk. */
2393 cdb_store(CDB_MSGLISTS, &CC->room.QRnumber, (int)sizeof(long),
2394 msglist, (int)(num_msgs * sizeof(long)));
2396 /* Free up the memory we used. */
2399 /* Update the highest-message pointer and unlock the room. */
2400 CC->room.QRhighest = highest_msg;
2401 lputroom(&CC->room);
2403 /* Perform replication checks if necessary */
2404 if ( (DoesThisRoomNeedEuidIndexing(&CC->room)) && (do_repl_check) ) {
2405 CtdlLogPrintf(CTDL_DEBUG, "CtdlSaveMsgPointerInRoom() doing repl checks\n");
2407 for (i=0; i<num_msgs_to_be_merged; ++i) {
2408 msgid = msgs_to_be_merged[i];
2410 if (supplied_msg != NULL) {
2414 msg = CtdlFetchMessage(msgid, 0);
2418 ReplicationChecks(msg);
2420 /* If the message has an Exclusive ID, index that... */
2421 if (msg->cm_fields['E'] != NULL) {
2422 index_message_by_euid(msg->cm_fields['E'], &CC->room, msgid);
2425 /* Free up the memory we may have allocated */
2426 if (msg != supplied_msg) {
2427 CtdlFreeMessage(msg);
2435 CtdlLogPrintf(CTDL_DEBUG, "CtdlSaveMsgPointerInRoom() skips repl checks\n");
2438 /* Submit this room for processing by hooks */
2439 PerformRoomHooks(&CC->room);
2441 /* Go back to the room we were in before we wandered here... */
2442 getroom(&CC->room, hold_rm);
2444 /* Bump the reference count for all messages which were merged */
2445 for (i=0; i<num_msgs_to_be_merged; ++i) {
2446 AdjRefCount(msgs_to_be_merged[i], +1);
2449 /* Free up memory... */
2450 if (msgs_to_be_merged != NULL) {
2451 free(msgs_to_be_merged);
2454 /* Return success. */
2460 * This is the same as CtdlSaveMsgPointersInRoom() but it only accepts
2463 int CtdlSaveMsgPointerInRoom(char *roomname, long msgid,
2464 int do_repl_check, struct CtdlMessage *supplied_msg)
2466 return CtdlSaveMsgPointersInRoom(roomname, &msgid, 1, do_repl_check, supplied_msg);
2473 * Message base operation to save a new message to the message store
2474 * (returns new message number)
2476 * This is the back end for CtdlSubmitMsg() and should not be directly
2477 * called by server-side modules.
2480 long send_message(struct CtdlMessage *msg) {
2488 /* Get a new message number */
2489 newmsgid = get_new_message_number();
2490 snprintf(msgidbuf, sizeof msgidbuf, "%010ld@%s", newmsgid, config.c_fqdn);
2492 /* Generate an ID if we don't have one already */
2493 if (msg->cm_fields['I']==NULL) {
2494 msg->cm_fields['I'] = strdup(msgidbuf);
2497 /* If the message is big, set its body aside for storage elsewhere */
2498 if (msg->cm_fields['M'] != NULL) {
2499 if (strlen(msg->cm_fields['M']) > BIGMSG) {
2501 holdM = msg->cm_fields['M'];
2502 msg->cm_fields['M'] = NULL;
2506 /* Serialize our data structure for storage in the database */
2507 serialize_message(&smr, msg);
2510 msg->cm_fields['M'] = holdM;
2514 cprintf("%d Unable to serialize message\n",
2515 ERROR + INTERNAL_ERROR);
2519 /* Write our little bundle of joy into the message base */
2520 if (cdb_store(CDB_MSGMAIN, &newmsgid, (int)sizeof(long),
2521 smr.ser, smr.len) < 0) {
2522 CtdlLogPrintf(CTDL_ERR, "Can't store message\n");
2526 cdb_store(CDB_BIGMSGS,
2536 /* Free the memory we used for the serialized message */
2539 /* Return the *local* message ID to the caller
2540 * (even if we're storing an incoming network message)
2548 * Serialize a struct CtdlMessage into the format used on disk and network.
2550 * This function loads up a "struct ser_ret" (defined in server.h) which
2551 * contains the length of the serialized message and a pointer to the
2552 * serialized message in memory. THE LATTER MUST BE FREED BY THE CALLER.
2554 void serialize_message(struct ser_ret *ret, /* return values */
2555 struct CtdlMessage *msg) /* unserialized msg */
2557 size_t wlen, fieldlen;
2559 static char *forder = FORDER;
2562 * Check for valid message format
2564 if (is_valid_message(msg) == 0) {
2565 CtdlLogPrintf(CTDL_ERR, "serialize_message() aborting due to invalid message\n");
2572 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL)
2573 ret->len = ret->len +
2574 strlen(msg->cm_fields[(int)forder[i]]) + 2;
2576 ret->ser = malloc(ret->len);
2577 if (ret->ser == NULL) {
2578 CtdlLogPrintf(CTDL_ERR, "serialize_message() malloc(%ld) failed: %s\n",
2579 (long)ret->len, strerror(errno));
2586 ret->ser[1] = msg->cm_anon_type;
2587 ret->ser[2] = msg->cm_format_type;
2590 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL) {
2591 fieldlen = strlen(msg->cm_fields[(int)forder[i]]);
2592 ret->ser[wlen++] = (char)forder[i];
2593 safestrncpy((char *)&ret->ser[wlen], msg->cm_fields[(int)forder[i]], fieldlen+1);
2594 wlen = wlen + fieldlen + 1;
2596 if (ret->len != wlen) CtdlLogPrintf(CTDL_ERR, "ERROR: len=%ld wlen=%ld\n",
2597 (long)ret->len, (long)wlen);
2604 * Serialize a struct CtdlMessage into the format used on disk and network.
2606 * This function loads up a "struct ser_ret" (defined in server.h) which
2607 * contains the length of the serialized message and a pointer to the
2608 * serialized message in memory. THE LATTER MUST BE FREED BY THE CALLER.
2610 void dump_message(struct CtdlMessage *msg, /* unserialized msg */
2611 long Siz) /* how many chars ? */
2615 static char *forder = FORDER;
2619 * Check for valid message format
2621 if (is_valid_message(msg) == 0) {
2622 CtdlLogPrintf(CTDL_ERR, "dump_message() aborting due to invalid message\n");
2626 buf = (char*) malloc (Siz + 1);
2630 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL) {
2631 snprintf (buf, Siz, " msg[%c] = %s ...\n", (char) forder[i],
2632 msg->cm_fields[(int)forder[i]]);
2633 client_write (buf, strlen(buf));
2642 * Check to see if any messages already exist in the current room which
2643 * carry the same Exclusive ID as this one. If any are found, delete them.
2645 void ReplicationChecks(struct CtdlMessage *msg) {
2646 long old_msgnum = (-1L);
2648 if (DoesThisRoomNeedEuidIndexing(&CC->room) == 0) return;
2650 CtdlLogPrintf(CTDL_DEBUG, "Performing replication checks in <%s>\n",
2653 /* No exclusive id? Don't do anything. */
2654 if (msg == NULL) return;
2655 if (msg->cm_fields['E'] == NULL) return;
2656 if (IsEmptyStr(msg->cm_fields['E'])) return;
2657 /*CtdlLogPrintf(CTDL_DEBUG, "Exclusive ID: <%s> for room <%s>\n",
2658 msg->cm_fields['E'], CC->room.QRname);*/
2660 old_msgnum = locate_message_by_euid(msg->cm_fields['E'], &CC->room);
2661 if (old_msgnum > 0L) {
2662 CtdlLogPrintf(CTDL_DEBUG, "ReplicationChecks() replacing message %ld\n", old_msgnum);
2663 CtdlDeleteMessages(CC->room.QRname, &old_msgnum, 1, "");
2670 * Save a message to disk and submit it into the delivery system.
2672 long CtdlSubmitMsg(struct CtdlMessage *msg, /* message to save */
2673 struct recptypes *recps, /* recipients (if mail) */
2674 char *force, /* force a particular room? */
2675 int flags /* should the bessage be exported clean? */
2677 char submit_filename[128];
2678 char generated_timestamp[32];
2679 char hold_rm[ROOMNAMELEN];
2680 char actual_rm[ROOMNAMELEN];
2681 char force_room[ROOMNAMELEN];
2682 char content_type[SIZ]; /* We have to learn this */
2683 char recipient[SIZ];
2686 struct ctdluser userbuf;
2688 struct MetaData smi;
2689 FILE *network_fp = NULL;
2690 static int seqnum = 1;
2691 struct CtdlMessage *imsg = NULL;
2693 size_t instr_alloc = 0;
2695 char *hold_R, *hold_D;
2696 char *collected_addresses = NULL;
2697 struct addresses_to_be_filed *aptr = NULL;
2698 char *saved_rfc822_version = NULL;
2699 int qualified_for_journaling = 0;
2700 struct CitContext *CCC = CC; /* CachedCitContext - performance boost */
2701 char bounce_to[1024] = "";
2705 CtdlLogPrintf(CTDL_DEBUG, "CtdlSubmitMsg() called\n");
2706 if (is_valid_message(msg) == 0) return(-1); /* self check */
2708 /* If this message has no timestamp, we take the liberty of
2709 * giving it one, right now.
2711 if (msg->cm_fields['T'] == NULL) {
2712 snprintf(generated_timestamp, sizeof generated_timestamp, "%ld", (long)time(NULL));
2713 msg->cm_fields['T'] = strdup(generated_timestamp);
2716 /* If this message has no path, we generate one.
2718 if (msg->cm_fields['P'] == NULL) {
2719 if (msg->cm_fields['A'] != NULL) {
2720 msg->cm_fields['P'] = strdup(msg->cm_fields['A']);
2721 for (a=0; !IsEmptyStr(&msg->cm_fields['P'][a]); ++a) {
2722 if (isspace(msg->cm_fields['P'][a])) {
2723 msg->cm_fields['P'][a] = ' ';
2728 msg->cm_fields['P'] = strdup("unknown");
2732 if (force == NULL) {
2733 strcpy(force_room, "");
2736 strcpy(force_room, force);
2739 /* Learn about what's inside, because it's what's inside that counts */
2740 if (msg->cm_fields['M'] == NULL) {
2741 CtdlLogPrintf(CTDL_ERR, "ERROR: attempt to save message with NULL body\n");
2745 switch (msg->cm_format_type) {
2747 strcpy(content_type, "text/x-citadel-variformat");
2750 strcpy(content_type, "text/plain");
2753 strcpy(content_type, "text/plain");
2754 mptr = bmstrcasestr(msg->cm_fields['M'], "Content-type:");
2757 safestrncpy(content_type, &mptr[13], sizeof content_type);
2758 striplt(content_type);
2759 aptr = content_type;
2760 while (!IsEmptyStr(aptr)) {
2772 /* Goto the correct room */
2773 CtdlLogPrintf(CTDL_DEBUG, "Selected room %s\n", (recps) ? CCC->room.QRname : SENTITEMS);
2774 strcpy(hold_rm, CCC->room.QRname);
2775 strcpy(actual_rm, CCC->room.QRname);
2776 if (recps != NULL) {
2777 strcpy(actual_rm, SENTITEMS);
2780 /* If the user is a twit, move to the twit room for posting */
2782 if (CCC->user.axlevel == 2) {
2783 strcpy(hold_rm, actual_rm);
2784 strcpy(actual_rm, config.c_twitroom);
2785 CtdlLogPrintf(CTDL_DEBUG, "Diverting to twit room\n");
2789 /* ...or if this message is destined for Aide> then go there. */
2790 if (!IsEmptyStr(force_room)) {
2791 strcpy(actual_rm, force_room);
2794 CtdlLogPrintf(CTDL_DEBUG, "Final selection: %s\n", actual_rm);
2795 if (strcasecmp(actual_rm, CCC->room.QRname)) {
2796 /* getroom(&CCC->room, actual_rm); */
2797 usergoto(actual_rm, 0, 1, NULL, NULL);
2801 * If this message has no O (room) field, generate one.
2803 if (msg->cm_fields['O'] == NULL) {
2804 msg->cm_fields['O'] = strdup(CCC->room.QRname);
2807 /* Perform "before save" hooks (aborting if any return nonzero) */
2808 CtdlLogPrintf(CTDL_DEBUG, "Performing before-save hooks\n");
2809 if (PerformMessageHooks(msg, EVT_BEFORESAVE) > 0) return(-3);
2812 * If this message has an Exclusive ID, and the room is replication
2813 * checking enabled, then do replication checks.
2815 if (DoesThisRoomNeedEuidIndexing(&CCC->room)) {
2816 ReplicationChecks(msg);
2819 /* Save it to disk */
2820 CtdlLogPrintf(CTDL_DEBUG, "Saving to disk\n");
2821 newmsgid = send_message(msg);
2822 if (newmsgid <= 0L) return(-5);
2824 /* Write a supplemental message info record. This doesn't have to
2825 * be a critical section because nobody else knows about this message
2828 CtdlLogPrintf(CTDL_DEBUG, "Creating MetaData record\n");
2829 memset(&smi, 0, sizeof(struct MetaData));
2830 smi.meta_msgnum = newmsgid;
2831 smi.meta_refcount = 0;
2832 safestrncpy(smi.meta_content_type, content_type,
2833 sizeof smi.meta_content_type);
2836 * Measure how big this message will be when rendered as RFC822.
2837 * We do this for two reasons:
2838 * 1. We need the RFC822 length for the new metadata record, so the
2839 * POP and IMAP services don't have to calculate message lengths
2840 * while the user is waiting (multiplied by potentially hundreds
2841 * or thousands of messages).
2842 * 2. If journaling is enabled, we will need an RFC822 version of the
2843 * message to attach to the journalized copy.
2845 if (CCC->redirect_buffer != NULL) {
2846 CtdlLogPrintf(CTDL_ALERT, "CCC->redirect_buffer is not NULL during message submission!\n");
2849 CCC->redirect_buffer = malloc(SIZ);
2850 CCC->redirect_len = 0;
2851 CCC->redirect_alloc = SIZ;
2852 CtdlOutputPreLoadedMsg(msg, MT_RFC822, HEADERS_ALL, 0, 1, QP_EADDR);
2853 smi.meta_rfc822_length = CCC->redirect_len;
2854 saved_rfc822_version = CCC->redirect_buffer;
2855 CCC->redirect_buffer = NULL;
2856 CCC->redirect_len = 0;
2857 CCC->redirect_alloc = 0;
2861 /* Now figure out where to store the pointers */
2862 CtdlLogPrintf(CTDL_DEBUG, "Storing pointers\n");
2864 /* If this is being done by the networker delivering a private
2865 * message, we want to BYPASS saving the sender's copy (because there
2866 * is no local sender; it would otherwise go to the Trashcan).
2868 if ((!CCC->internal_pgm) || (recps == NULL)) {
2869 if (CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 1, msg) != 0) {
2870 CtdlLogPrintf(CTDL_ERR, "ERROR saving message pointer!\n");
2871 CtdlSaveMsgPointerInRoom(config.c_aideroom, newmsgid, 0, msg);
2875 /* For internet mail, drop a copy in the outbound queue room */
2876 if ((recps != NULL) && (recps->num_internet > 0)) {
2877 CtdlSaveMsgPointerInRoom(SMTP_SPOOLOUT_ROOM, newmsgid, 0, msg);
2880 /* If other rooms are specified, drop them there too. */
2881 if ((recps != NULL) && (recps->num_room > 0))
2882 for (i=0; i<num_tokens(recps->recp_room, '|'); ++i) {
2883 extract_token(recipient, recps->recp_room, i,
2884 '|', sizeof recipient);
2885 CtdlLogPrintf(CTDL_DEBUG, "Delivering to room <%s>\n", recipient);
2886 CtdlSaveMsgPointerInRoom(recipient, newmsgid, 0, msg);
2889 /* Bump this user's messages posted counter. */
2890 CtdlLogPrintf(CTDL_DEBUG, "Updating user\n");
2891 lgetuser(&CCC->user, CCC->curr_user);
2892 CCC->user.posted = CCC->user.posted + 1;
2893 lputuser(&CCC->user);
2895 /* Decide where bounces need to be delivered */
2896 if ((recps != NULL) && (recps->bounce_to != NULL)) {
2897 safestrncpy(bounce_to, recps->bounce_to, sizeof bounce_to);
2899 else if (CCC->logged_in) {
2900 snprintf(bounce_to, sizeof bounce_to, "%s@%s", CCC->user.fullname, config.c_nodename);
2903 snprintf(bounce_to, sizeof bounce_to, "%s@%s", msg->cm_fields['A'], msg->cm_fields['N']);
2906 /* If this is private, local mail, make a copy in the
2907 * recipient's mailbox and bump the reference count.
2909 if ((recps != NULL) && (recps->num_local > 0))
2910 for (i=0; i<num_tokens(recps->recp_local, '|'); ++i) {
2911 extract_token(recipient, recps->recp_local, i,
2912 '|', sizeof recipient);
2913 CtdlLogPrintf(CTDL_DEBUG, "Delivering private local mail to <%s>\n",
2915 if (getuser(&userbuf, recipient) == 0) {
2916 // Add a flag so the Funambol module knows its mail
2917 msg->cm_fields['W'] = strdup(recipient);
2918 MailboxName(actual_rm, sizeof actual_rm, &userbuf, MAILROOM);
2919 CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 0, msg);
2920 BumpNewMailCounter(userbuf.usernum);
2921 if (!IsEmptyStr(config.c_funambol_host) || !IsEmptyStr(config.c_pager_program)) {
2922 /* Generate a instruction message for the Funambol notification
2923 * server, in the same style as the SMTP queue
2926 instr = malloc(instr_alloc);
2927 snprintf(instr, instr_alloc,
2928 "Content-type: %s\n\nmsgid|%ld\nsubmitted|%ld\n"
2930 SPOOLMIME, newmsgid, (long)time(NULL),
2934 imsg = malloc(sizeof(struct CtdlMessage));
2935 memset(imsg, 0, sizeof(struct CtdlMessage));
2936 imsg->cm_magic = CTDLMESSAGE_MAGIC;
2937 imsg->cm_anon_type = MES_NORMAL;
2938 imsg->cm_format_type = FMT_RFC822;
2939 imsg->cm_fields['A'] = strdup("Citadel");
2940 imsg->cm_fields['J'] = strdup("do not journal");
2941 imsg->cm_fields['M'] = instr; /* imsg owns this memory now */
2942 imsg->cm_fields['W'] = strdup(recipient);
2943 CtdlSubmitMsg(imsg, NULL, FNBL_QUEUE_ROOM, 0);
2944 CtdlFreeMessage(imsg);
2948 CtdlLogPrintf(CTDL_DEBUG, "No user <%s>\n", recipient);
2949 CtdlSaveMsgPointerInRoom(config.c_aideroom,
2954 /* Perform "after save" hooks */
2955 CtdlLogPrintf(CTDL_DEBUG, "Performing after-save hooks\n");
2956 PerformMessageHooks(msg, EVT_AFTERSAVE);
2958 /* For IGnet mail, we have to save a new copy into the spooler for
2959 * each recipient, with the R and D fields set to the recipient and
2960 * destination-node. This has two ugly side effects: all other
2961 * recipients end up being unlisted in this recipient's copy of the
2962 * message, and it has to deliver multiple messages to the same
2963 * node. We'll revisit this again in a year or so when everyone has
2964 * a network spool receiver that can handle the new style messages.
2966 if ((recps != NULL) && (recps->num_ignet > 0))
2967 for (i=0; i<num_tokens(recps->recp_ignet, '|'); ++i) {
2968 extract_token(recipient, recps->recp_ignet, i,
2969 '|', sizeof recipient);
2971 hold_R = msg->cm_fields['R'];
2972 hold_D = msg->cm_fields['D'];
2973 msg->cm_fields['R'] = malloc(SIZ);
2974 msg->cm_fields['D'] = malloc(128);
2975 extract_token(msg->cm_fields['R'], recipient, 0, '@', SIZ);
2976 extract_token(msg->cm_fields['D'], recipient, 1, '@', 128);
2978 serialize_message(&smr, msg);
2980 snprintf(submit_filename, sizeof submit_filename,
2981 "%s/netmail.%04lx.%04x.%04x",
2983 (long) getpid(), CCC->cs_pid, ++seqnum);
2984 network_fp = fopen(submit_filename, "wb+");
2985 if (network_fp != NULL) {
2986 rv = fwrite(smr.ser, smr.len, 1, network_fp);
2992 free(msg->cm_fields['R']);
2993 free(msg->cm_fields['D']);
2994 msg->cm_fields['R'] = hold_R;
2995 msg->cm_fields['D'] = hold_D;
2998 /* Go back to the room we started from */
2999 CtdlLogPrintf(CTDL_DEBUG, "Returning to original room %s\n", hold_rm);
3000 if (strcasecmp(hold_rm, CCC->room.QRname))
3001 usergoto(hold_rm, 0, 1, NULL, NULL);
3003 /* For internet mail, generate delivery instructions.
3004 * Yes, this is recursive. Deal with it. Infinite recursion does
3005 * not happen because the delivery instructions message does not
3006 * contain a recipient.
3008 if ((recps != NULL) && (recps->num_internet > 0)) {
3009 CtdlLogPrintf(CTDL_DEBUG, "Generating delivery instructions\n");
3011 instr = malloc(instr_alloc);
3012 snprintf(instr, instr_alloc,
3013 "Content-type: %s\n\nmsgid|%ld\nsubmitted|%ld\n"
3015 SPOOLMIME, newmsgid, (long)time(NULL),
3019 if (recps->envelope_from != NULL) {
3020 tmp = strlen(instr);
3021 snprintf(&instr[tmp], instr_alloc-tmp, "envelope_from|%s\n", recps->envelope_from);
3024 for (i=0; i<num_tokens(recps->recp_internet, '|'); ++i) {
3025 tmp = strlen(instr);
3026 extract_token(recipient, recps->recp_internet, i, '|', sizeof recipient);
3027 if ((tmp + strlen(recipient) + 32) > instr_alloc) {
3028 instr_alloc = instr_alloc * 2;
3029 instr = realloc(instr, instr_alloc);
3031 snprintf(&instr[tmp], instr_alloc - tmp, "remote|%s|0||\n", recipient);
3034 imsg = malloc(sizeof(struct CtdlMessage));
3035 memset(imsg, 0, sizeof(struct CtdlMessage));
3036 imsg->cm_magic = CTDLMESSAGE_MAGIC;
3037 imsg->cm_anon_type = MES_NORMAL;
3038 imsg->cm_format_type = FMT_RFC822;
3039 imsg->cm_fields['A'] = strdup("Citadel");
3040 imsg->cm_fields['J'] = strdup("do not journal");
3041 imsg->cm_fields['M'] = instr; /* imsg owns this memory now */
3042 CtdlSubmitMsg(imsg, NULL, SMTP_SPOOLOUT_ROOM, QP_EADDR);
3043 CtdlFreeMessage(imsg);
3047 * Any addresses to harvest for someone's address book?
3049 if ( (CCC->logged_in) && (recps != NULL) ) {
3050 collected_addresses = harvest_collected_addresses(msg);
3053 if (collected_addresses != NULL) {
3054 aptr = (struct addresses_to_be_filed *)
3055 malloc(sizeof(struct addresses_to_be_filed));
3056 MailboxName(actual_rm, sizeof actual_rm,
3057 &CCC->user, USERCONTACTSROOM);
3058 aptr->roomname = strdup(actual_rm);
3059 aptr->collected_addresses = collected_addresses;
3060 begin_critical_section(S_ATBF);
3063 end_critical_section(S_ATBF);
3067 * Determine whether this message qualifies for journaling.
3069 if (msg->cm_fields['J'] != NULL) {
3070 qualified_for_journaling = 0;
3073 if (recps == NULL) {
3074 qualified_for_journaling = config.c_journal_pubmsgs;
3076 else if (recps->num_local + recps->num_ignet + recps->num_internet > 0) {
3077 qualified_for_journaling = config.c_journal_email;
3080 qualified_for_journaling = config.c_journal_pubmsgs;
3085 * Do we have to perform journaling? If so, hand off the saved
3086 * RFC822 version will be handed off to the journaler for background
3087 * submit. Otherwise, we have to free the memory ourselves.
3089 if (saved_rfc822_version != NULL) {
3090 if (qualified_for_journaling) {
3091 JournalBackgroundSubmit(msg, saved_rfc822_version, recps);
3094 free(saved_rfc822_version);
3107 * Convenience function for generating small administrative messages.
3109 void quickie_message(const char *from, const char *fromaddr, char *to, char *room, const char *text,
3110 int format_type, const char *subject)
3112 struct CtdlMessage *msg;
3113 struct recptypes *recp = NULL;
3115 msg = malloc(sizeof(struct CtdlMessage));
3116 memset(msg, 0, sizeof(struct CtdlMessage));
3117 msg->cm_magic = CTDLMESSAGE_MAGIC;
3118 msg->cm_anon_type = MES_NORMAL;
3119 msg->cm_format_type = format_type;
3122 msg->cm_fields['A'] = strdup(from);
3124 else if (fromaddr != NULL) {
3125 msg->cm_fields['A'] = strdup(fromaddr);
3126 if (strchr(msg->cm_fields['A'], '@')) {
3127 *strchr(msg->cm_fields['A'], '@') = 0;
3131 msg->cm_fields['A'] = strdup("Citadel");
3134 if (fromaddr != NULL) msg->cm_fields['F'] = strdup(fromaddr);
3135 if (room != NULL) msg->cm_fields['O'] = strdup(room);
3136 msg->cm_fields['N'] = strdup(NODENAME);
3138 msg->cm_fields['R'] = strdup(to);
3139 recp = validate_recipients(to, NULL, 0);
3141 if (subject != NULL) {
3142 msg->cm_fields['U'] = strdup(subject);
3144 msg->cm_fields['M'] = strdup(text);
3146 CtdlSubmitMsg(msg, recp, room, 0);
3147 CtdlFreeMessage(msg);
3148 if (recp != NULL) free_recipients(recp);
3154 * Back end function used by CtdlMakeMessage() and similar functions
3156 char *CtdlReadMessageBody(char *terminator, /* token signalling EOT */
3157 size_t maxlen, /* maximum message length */
3158 char *exist, /* if non-null, append to it;
3159 exist is ALWAYS freed */
3160 int crlf, /* CRLF newlines instead of LF */
3161 int sock /* socket handle or 0 for this session's client socket */
3165 size_t message_len = 0;
3166 size_t buffer_len = 0;
3173 if (exist == NULL) {
3180 message_len = strlen(exist);
3181 buffer_len = message_len + 4096;
3182 m = realloc(exist, buffer_len);
3189 /* Do we need to change leading ".." to "." for SMTP escaping? */
3190 if (!strcmp(terminator, ".")) {
3194 /* flush the input if we have nowhere to store it */
3199 /* read in the lines of message text one by one */
3202 if (sock_getln(sock, buf, (sizeof buf - 3)) < 0) finished = 1;
3205 if (client_getln(buf, (sizeof buf - 3)) < 1) finished = 1;
3207 if (!strcmp(buf, terminator)) finished = 1;
3209 strcat(buf, "\r\n");
3215 /* Unescape SMTP-style input of two dots at the beginning of the line */
3217 if (!strncmp(buf, "..", 2)) {
3218 strcpy(buf, &buf[1]);
3222 if ( (!flushing) && (!finished) ) {
3223 /* Measure the line */
3224 linelen = strlen(buf);
3226 /* augment the buffer if we have to */
3227 if ((message_len + linelen) >= buffer_len) {
3228 ptr = realloc(m, (buffer_len * 2) );
3229 if (ptr == NULL) { /* flush if can't allocate */
3232 buffer_len = (buffer_len * 2);
3234 CtdlLogPrintf(CTDL_DEBUG, "buffer_len is now %ld\n", (long)buffer_len);
3238 /* Add the new line to the buffer. NOTE: this loop must avoid
3239 * using functions like strcat() and strlen() because they
3240 * traverse the entire buffer upon every call, and doing that
3241 * for a multi-megabyte message slows it down beyond usability.
3243 strcpy(&m[message_len], buf);
3244 message_len += linelen;
3247 /* if we've hit the max msg length, flush the rest */
3248 if (message_len >= maxlen) flushing = 1;
3250 } while (!finished);
3258 * Build a binary message to be saved on disk.
3259 * (NOTE: if you supply 'preformatted_text', the buffer you give it
3260 * will become part of the message. This means you are no longer
3261 * responsible for managing that memory -- it will be freed along with
3262 * the rest of the fields when CtdlFreeMessage() is called.)
3265 struct CtdlMessage *CtdlMakeMessage(
3266 struct ctdluser *author, /* author's user structure */
3267 char *recipient, /* NULL if it's not mail */
3268 char *recp_cc, /* NULL if it's not mail */
3269 char *room, /* room where it's going */
3270 int type, /* see MES_ types in header file */
3271 int format_type, /* variformat, plain text, MIME... */
3272 char *fake_name, /* who we're masquerading as */
3273 char *my_email, /* which of my email addresses to use (empty is ok) */
3274 char *subject, /* Subject (optional) */
3275 char *supplied_euid, /* ...or NULL if this is irrelevant */
3276 char *preformatted_text, /* ...or NULL to read text from client */
3277 char *references /* Thread references */
3279 char dest_node[256];
3281 struct CtdlMessage *msg;
3283 msg = malloc(sizeof(struct CtdlMessage));
3284 memset(msg, 0, sizeof(struct CtdlMessage));
3285 msg->cm_magic = CTDLMESSAGE_MAGIC;
3286 msg->cm_anon_type = type;
3287 msg->cm_format_type = format_type;
3289 /* Don't confuse the poor folks if it's not routed mail. */
3290 strcpy(dest_node, "");
3292 if (recipient != NULL) striplt(recipient);
3293 if (recp_cc != NULL) striplt(recp_cc);
3295 /* Path or Return-Path */
3296 if (my_email == NULL) my_email = "";
3298 if (!IsEmptyStr(my_email)) {
3299 msg->cm_fields['P'] = strdup(my_email);
3302 snprintf(buf, sizeof buf, "%s", author->fullname);
3303 msg->cm_fields['P'] = strdup(buf);
3305 convert_spaces_to_underscores(msg->cm_fields['P']);
3307 snprintf(buf, sizeof buf, "%ld", (long)time(NULL)); /* timestamp */
3308 msg->cm_fields['T'] = strdup(buf);
3310 if ((fake_name != NULL) && (fake_name[0])) { /* author */
3311 msg->cm_fields['A'] = strdup(fake_name);
3314 msg->cm_fields['A'] = strdup(author->fullname);
3317 if (CC->room.QRflags & QR_MAILBOX) { /* room */
3318 msg->cm_fields['O'] = strdup(&CC->room.QRname[11]);
3321 msg->cm_fields['O'] = strdup(CC->room.QRname);
3324 msg->cm_fields['N'] = strdup(NODENAME); /* nodename */
3325 msg->cm_fields['H'] = strdup(HUMANNODE); /* hnodename */
3327 if ((recipient != NULL) && (recipient[0] != 0)) {
3328 msg->cm_fields['R'] = strdup(recipient);
3330 if ((recp_cc != NULL) && (recp_cc[0] != 0)) {
3331 msg->cm_fields['Y'] = strdup(recp_cc);
3333 if (dest_node[0] != 0) {
3334 msg->cm_fields['D'] = strdup(dest_node);
3337 if (!IsEmptyStr(my_email)) {
3338 msg->cm_fields['F'] = strdup(my_email);
3340 else if ( (author == &CC->user) && (!IsEmptyStr(CC->cs_inet_email)) ) {
3341 msg->cm_fields['F'] = strdup(CC->cs_inet_email);
3344 if (subject != NULL) {
3347 length = strlen(subject);
3353 while ((subject[i] != '\0') &&
3354 (IsAscii = isascii(subject[i]) != 0 ))
3357 msg->cm_fields['U'] = strdup(subject);
3358 else /* ok, we've got utf8 in the string. */
3360 msg->cm_fields['U'] = rfc2047encode(subject, length);
3366 if (supplied_euid != NULL) {
3367 msg->cm_fields['E'] = strdup(supplied_euid);
3370 if (references != NULL) {
3371 if (!IsEmptyStr(references)) {
3372 msg->cm_fields['W'] = strdup(references);
3376 if (preformatted_text != NULL) {
3377 msg->cm_fields['M'] = preformatted_text;
3380 msg->cm_fields['M'] = CtdlReadMessageBody("000", config.c_maxmsglen, NULL, 0, 0);
3388 * Check to see whether we have permission to post a message in the current
3389 * room. Returns a *CITADEL ERROR CODE* and puts a message in errmsgbuf, or
3390 * returns 0 on success.
3392 int CtdlDoIHavePermissionToPostInThisRoom(char *errmsgbuf,
3394 const char* RemoteIdentifier,
3398 if (!(CC->logged_in) &&
3399 (PostPublic == POST_LOGGED_IN)) {
3400 snprintf(errmsgbuf, n, "Not logged in.");
3401 return (ERROR + NOT_LOGGED_IN);
3403 else if (PostPublic == CHECK_EXISTANCE) {
3404 return (0); // We're Evaling whether a recipient exists
3406 else if (!(CC->logged_in)) {
3408 if ((CC->room.QRflags & QR_READONLY)) {
3409 snprintf(errmsgbuf, n, "Not logged in.");
3410 return (ERROR + NOT_LOGGED_IN);
3412 if (CC->room.QRflags2 & QR2_MODERATED) {
3413 snprintf(errmsgbuf, n, "Not logged in Moderation feature not yet implemented!");
3414 return (ERROR + NOT_LOGGED_IN);
3416 if ((PostPublic!=POST_LMTP) &&(CC->room.QRflags2 & QR2_SMTP_PUBLIC) == 0) {
3421 if (RemoteIdentifier == NULL)
3423 snprintf(errmsgbuf, n, "Need sender to permit access.");
3424 return (ERROR + USERNAME_REQUIRED);
3427 assoc_file_name(filename, sizeof filename, &CC->room, ctdl_netcfg_dir);
3428 begin_critical_section(S_NETCONFIGS);
3429 if (!read_spoolcontrol_file(&sc, filename))
3431 end_critical_section(S_NETCONFIGS);
3432 snprintf(errmsgbuf, n,
3433 "This mailing list only accepts posts from subscribers.");
3434 return (ERROR + NO_SUCH_USER);
3436 end_critical_section(S_NETCONFIGS);
3437 found = is_recipient (sc, RemoteIdentifier);
3438 free_spoolcontrol_struct(&sc);
3443 snprintf(errmsgbuf, n,
3444 "This mailing list only accepts posts from subscribers.");
3445 return (ERROR + NO_SUCH_USER);
3452 if ((CC->user.axlevel < 2)
3453 && ((CC->room.QRflags & QR_MAILBOX) == 0)) {
3454 snprintf(errmsgbuf, n, "Need to be validated to enter "
3455 "(except in %s> to sysop)", MAILROOM);
3456 return (ERROR + HIGHER_ACCESS_REQUIRED);
3459 CtdlRoomAccess(&CC->room, &CC->user, &ra, NULL);
3460 if (!(ra & UA_POSTALLOWED)) {
3461 snprintf(errmsgbuf, n, "Higher access is required to post in this room.");
3462 return (ERROR + HIGHER_ACCESS_REQUIRED);
3465 strcpy(errmsgbuf, "Ok");
3471 * Check to see if the specified user has Internet mail permission
3472 * (returns nonzero if permission is granted)
3474 int CtdlCheckInternetMailPermission(struct ctdluser *who) {
3476 /* Do not allow twits to send Internet mail */
3477 if (who->axlevel <= 2) return(0);
3479 /* Globally enabled? */
3480 if (config.c_restrict == 0) return(1);
3482 /* User flagged ok? */
3483 if (who->flags & US_INTERNET) return(2);
3485 /* Aide level access? */
3486 if (who->axlevel >= 6) return(3);
3488 /* No mail for you! */
3494 * Validate recipients, count delivery types and errors, and handle aliasing
3495 * FIXME check for dupes!!!!!
3497 * Returns 0 if all addresses are ok, ret->num_error = -1 if no addresses
3498 * were specified, or the number of addresses found invalid.
3500 * Caller needs to free the result using free_recipients()
3502 struct recptypes *validate_recipients(char *supplied_recipients,
3503 const char *RemoteIdentifier,
3505 struct recptypes *ret;
3506 char *recipients = NULL;
3507 char this_recp[256];
3508 char this_recp_cooked[256];
3514 struct ctdluser tempUS;
3515 struct ctdlroom tempQR;
3516 struct ctdlroom tempQR2;
3522 ret = (struct recptypes *) malloc(sizeof(struct recptypes));
3523 if (ret == NULL) return(NULL);
3525 /* Set all strings to null and numeric values to zero */
3526 memset(ret, 0, sizeof(struct recptypes));
3528 if (supplied_recipients == NULL) {
3529 recipients = strdup("");
3532 recipients = strdup(supplied_recipients);
3535 /* Allocate some memory. Yes, this allocates 500% more memory than we will
3536 * actually need, but it's healthier for the heap than doing lots of tiny
3537 * realloc() calls instead.
3540 ret->errormsg = malloc(strlen(recipients) + 1024);
3541 ret->recp_local = malloc(strlen(recipients) + 1024);
3542 ret->recp_internet = malloc(strlen(recipients) + 1024);
3543 ret->recp_ignet = malloc(strlen(recipients) + 1024);
3544 ret->recp_room = malloc(strlen(recipients) + 1024);
3545 ret->display_recp = malloc(strlen(recipients) + 1024);
3547 ret->errormsg[0] = 0;
3548 ret->recp_local[0] = 0;
3549 ret->recp_internet[0] = 0;
3550 ret->recp_ignet[0] = 0;
3551 ret->recp_room[0] = 0;
3552 ret->display_recp[0] = 0;
3554 ret->recptypes_magic = RECPTYPES_MAGIC;
3556 /* Change all valid separator characters to commas */
3557 for (i=0; !IsEmptyStr(&recipients[i]); ++i) {
3558 if ((recipients[i] == ';') || (recipients[i] == '|')) {
3559 recipients[i] = ',';
3563 /* Now start extracting recipients... */
3565 while (!IsEmptyStr(recipients)) {
3567 for (i=0; i<=strlen(recipients); ++i) {
3568 if (recipients[i] == '\"') in_quotes = 1 - in_quotes;
3569 if ( ( (recipients[i] == ',') && (!in_quotes) ) || (recipients[i] == 0) ) {
3570 safestrncpy(this_recp, recipients, i+1);
3572 if (recipients[i] == ',') {
3573 strcpy(recipients, &recipients[i+1]);
3576 strcpy(recipients, "");
3583 if (IsEmptyStr(this_recp))
3585 CtdlLogPrintf(CTDL_DEBUG, "Evaluating recipient #%d: %s\n", num_recps, this_recp);
3587 mailtype = alias(this_recp);
3588 mailtype = alias(this_recp);
3589 mailtype = alias(this_recp);
3591 for (j=0; !IsEmptyStr(&this_recp[j]); ++j) {
3592 if (this_recp[j]=='_') {
3593 this_recp_cooked[j] = ' ';
3596 this_recp_cooked[j] = this_recp[j];
3599 this_recp_cooked[j] = '\0';
3604 if (!strcasecmp(this_recp, "sysop")) {
3606 strcpy(this_recp, config.c_aideroom);
3607 if (!IsEmptyStr(ret->recp_room)) {
3608 strcat(ret->recp_room, "|");
3610 strcat(ret->recp_room, this_recp);
3612 else if ( (!strncasecmp(this_recp, "room_", 5))
3613 && (!getroom(&tempQR, &this_recp_cooked[5])) ) {
3615 /* Save room so we can restore it later */
3619 /* Check permissions to send mail to this room */
3620 err = CtdlDoIHavePermissionToPostInThisRoom(errmsg,
3632 if (!IsEmptyStr(ret->recp_room)) {
3633 strcat(ret->recp_room, "|");
3635 strcat(ret->recp_room, &this_recp_cooked[5]);
3638 /* Restore room in case something needs it */
3642 else if (getuser(&tempUS, this_recp) == 0) {
3644 strcpy(this_recp, tempUS.fullname);
3645 if (!IsEmptyStr(ret->recp_local)) {
3646 strcat(ret->recp_local, "|");
3648 strcat(ret->recp_local, this_recp);
3650 else if (getuser(&tempUS, this_recp_cooked) == 0) {
3652 strcpy(this_recp, tempUS.fullname);
3653 if (!IsEmptyStr(ret->recp_local)) {
3654 strcat(ret->recp_local, "|");
3656 strcat(ret->recp_local, this_recp);
3664 /* Yes, you're reading this correctly: if the target
3665 * domain points back to the local system or an attached
3666 * Citadel directory, the address is invalid. That's
3667 * because if the address were valid, we would have
3668 * already translated it to a local address by now.
3670 if (IsDirectory(this_recp, 0)) {
3675 ++ret->num_internet;
3676 if (!IsEmptyStr(ret->recp_internet)) {
3677 strcat(ret->recp_internet, "|");
3679 strcat(ret->recp_internet, this_recp);
3684 if (!IsEmptyStr(ret->recp_ignet)) {
3685 strcat(ret->recp_ignet, "|");
3687 strcat(ret->recp_ignet, this_recp);
3695 if (IsEmptyStr(errmsg)) {
3696 snprintf(append, sizeof append, "Invalid recipient: %s", this_recp);
3699 snprintf(append, sizeof append, "%s", errmsg);
3701 if ( (strlen(ret->errormsg) + strlen(append) + 3) < SIZ) {
3702 if (!IsEmptyStr(ret->errormsg)) {
3703 strcat(ret->errormsg, "; ");
3705 strcat(ret->errormsg, append);
3709 if (IsEmptyStr(ret->display_recp)) {
3710 strcpy(append, this_recp);
3713 snprintf(append, sizeof append, ", %s", this_recp);
3715 if ( (strlen(ret->display_recp)+strlen(append)) < SIZ) {
3716 strcat(ret->display_recp, append);
3721 if ((ret->num_local + ret->num_internet + ret->num_ignet +
3722 ret->num_room + ret->num_error) == 0) {
3723 ret->num_error = (-1);
3724 strcpy(ret->errormsg, "No recipients specified.");
3727 CtdlLogPrintf(CTDL_DEBUG, "validate_recipients()\n");
3728 CtdlLogPrintf(CTDL_DEBUG, " local: %d <%s>\n", ret->num_local, ret->recp_local);
3729 CtdlLogPrintf(CTDL_DEBUG, " room: %d <%s>\n", ret->num_room, ret->recp_room);
3730 CtdlLogPrintf(CTDL_DEBUG, " inet: %d <%s>\n", ret->num_internet, ret->recp_internet);
3731 CtdlLogPrintf(CTDL_DEBUG, " ignet: %d <%s>\n", ret->num_ignet, ret->recp_ignet);
3732 CtdlLogPrintf(CTDL_DEBUG, " error: %d <%s>\n", ret->num_error, ret->errormsg);
3740 * Destructor for struct recptypes
3742 void free_recipients(struct recptypes *valid) {
3744 if (valid == NULL) {
3748 if (valid->recptypes_magic != RECPTYPES_MAGIC) {
3749 CtdlLogPrintf(CTDL_EMERG, "Attempt to call free_recipients() on some other data type!\n");
3753 if (valid->errormsg != NULL) free(valid->errormsg);
3754 if (valid->recp_local != NULL) free(valid->recp_local);
3755 if (valid->recp_internet != NULL) free(valid->recp_internet);
3756 if (valid->recp_ignet != NULL) free(valid->recp_ignet);
3757 if (valid->recp_room != NULL) free(valid->recp_room);
3758 if (valid->display_recp != NULL) free(valid->display_recp);
3759 if (valid->bounce_to != NULL) free(valid->bounce_to);
3760 if (valid->envelope_from != NULL) free(valid->envelope_from);
3767 * message entry - mode 0 (normal)
3769 void cmd_ent0(char *entargs)
3775 char supplied_euid[128];
3777 int format_type = 0;
3778 char newusername[256];
3779 char newuseremail[256];
3780 struct CtdlMessage *msg;
3784 struct recptypes *valid = NULL;
3785 struct recptypes *valid_to = NULL;
3786 struct recptypes *valid_cc = NULL;
3787 struct recptypes *valid_bcc = NULL;
3789 int subject_required = 0;
3794 int newuseremail_ok = 0;
3795 char references[SIZ];
3800 post = extract_int(entargs, 0);
3801 extract_token(recp, entargs, 1, '|', sizeof recp);
3802 anon_flag = extract_int(entargs, 2);
3803 format_type = extract_int(entargs, 3);
3804 extract_token(subject, entargs, 4, '|', sizeof subject);
3805 extract_token(newusername, entargs, 5, '|', sizeof newusername);
3806 do_confirm = extract_int(entargs, 6);
3807 extract_token(cc, entargs, 7, '|', sizeof cc);
3808 extract_token(bcc, entargs, 8, '|', sizeof bcc);
3809 switch(CC->room.QRdefaultview) {
3812 extract_token(supplied_euid, entargs, 9, '|', sizeof supplied_euid);
3815 supplied_euid[0] = 0;
3818 extract_token(newuseremail, entargs, 10, '|', sizeof newuseremail);
3819 extract_token(references, entargs, 11, '|', sizeof references);
3820 for (ptr=references; *ptr != 0; ++ptr) {
3821 if (*ptr == '!') *ptr = '|';
3824 /* first check to make sure the request is valid. */
3826 err = CtdlDoIHavePermissionToPostInThisRoom(errmsg, sizeof errmsg, NULL, POST_LOGGED_IN);
3829 cprintf("%d %s\n", err, errmsg);
3833 /* Check some other permission type things. */
3835 if (IsEmptyStr(newusername)) {
3836 strcpy(newusername, CC->user.fullname);
3838 if ( (CC->user.axlevel < 6)
3839 && (strcasecmp(newusername, CC->user.fullname))
3840 && (strcasecmp(newusername, CC->cs_inet_fn))
3842 cprintf("%d You don't have permission to author messages as '%s'.\n",
3843 ERROR + HIGHER_ACCESS_REQUIRED,
3850 if (IsEmptyStr(newuseremail)) {
3851 newuseremail_ok = 1;
3854 if (!IsEmptyStr(newuseremail)) {
3855 if (!strcasecmp(newuseremail, CC->cs_inet_email)) {
3856 newuseremail_ok = 1;
3858 else if (!IsEmptyStr(CC->cs_inet_other_emails)) {
3859 j = num_tokens(CC->cs_inet_other_emails, '|');
3860 for (i=0; i<j; ++i) {
3861 extract_token(buf, CC->cs_inet_other_emails, i, '|', sizeof buf);
3862 if (!strcasecmp(newuseremail, buf)) {
3863 newuseremail_ok = 1;
3869 if (!newuseremail_ok) {
3870 cprintf("%d You don't have permission to author messages as '%s'.\n",
3871 ERROR + HIGHER_ACCESS_REQUIRED,
3877 CC->cs_flags |= CS_POSTING;
3879 /* In mailbox rooms we have to behave a little differently --
3880 * make sure the user has specified at least one recipient. Then
3881 * validate the recipient(s). We do this for the Mail> room, as
3882 * well as any room which has the "Mailbox" view set.
3885 if ( ( (CC->room.QRflags & QR_MAILBOX) && (!strcasecmp(&CC->room.QRname[11], MAILROOM)) )
3886 || ( (CC->room.QRflags & QR_MAILBOX) && (CC->curr_view == VIEW_MAILBOX) )
3888 if (CC->user.axlevel < 2) {
3889 strcpy(recp, "sysop");
3894 valid_to = validate_recipients(recp, NULL, 0);
3895 if (valid_to->num_error > 0) {
3896 cprintf("%d %s\n", ERROR + NO_SUCH_USER, valid_to->errormsg);
3897 free_recipients(valid_to);
3901 valid_cc = validate_recipients(cc, NULL, 0);
3902 if (valid_cc->num_error > 0) {
3903 cprintf("%d %s\n", ERROR + NO_SUCH_USER, valid_cc->errormsg);
3904 free_recipients(valid_to);
3905 free_recipients(valid_cc);
3909 valid_bcc = validate_recipients(bcc, NULL, 0);
3910 if (valid_bcc->num_error > 0) {
3911 cprintf("%d %s\n", ERROR + NO_SUCH_USER, valid_bcc->errormsg);
3912 free_recipients(valid_to);
3913 free_recipients(valid_cc);
3914 free_recipients(valid_bcc);
3918 /* Recipient required, but none were specified */
3919 if ( (valid_to->num_error < 0) && (valid_cc->num_error < 0) && (valid_bcc->num_error < 0) ) {
3920 free_recipients(valid_to);
3921 free_recipients(valid_cc);
3922 free_recipients(valid_bcc);
3923 cprintf("%d At least one recipient is required.\n", ERROR + NO_SUCH_USER);
3927 if (valid_to->num_internet + valid_cc->num_internet + valid_bcc->num_internet > 0) {
3928 if (CtdlCheckInternetMailPermission(&CC->user)==0) {
3929 cprintf("%d You do not have permission "
3930 "to send Internet mail.\n",
3931 ERROR + HIGHER_ACCESS_REQUIRED);
3932 free_recipients(valid_to);
3933 free_recipients(valid_cc);
3934 free_recipients(valid_bcc);
3939 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)
3940 && (CC->user.axlevel < 4) ) {
3941 cprintf("%d Higher access required for network mail.\n",
3942 ERROR + HIGHER_ACCESS_REQUIRED);
3943 free_recipients(valid_to);
3944 free_recipients(valid_cc);
3945 free_recipients(valid_bcc);
3949 if ((RESTRICT_INTERNET == 1)
3950 && (valid_to->num_internet + valid_cc->num_internet + valid_bcc->num_internet > 0)
3951 && ((CC->user.flags & US_INTERNET) == 0)
3952 && (!CC->internal_pgm)) {
3953 cprintf("%d You don't have access to Internet mail.\n",
3954 ERROR + HIGHER_ACCESS_REQUIRED);
3955 free_recipients(valid_to);
3956 free_recipients(valid_cc);
3957 free_recipients(valid_bcc);
3963 /* Is this a room which has anonymous-only or anonymous-option? */
3964 anonymous = MES_NORMAL;
3965 if (CC->room.QRflags & QR_ANONONLY) {
3966 anonymous = MES_ANONONLY;
3968 if (CC->room.QRflags & QR_ANONOPT) {
3969 if (anon_flag == 1) { /* only if the user requested it */
3970 anonymous = MES_ANONOPT;
3974 if ((CC->room.QRflags & QR_MAILBOX) == 0) {
3978 /* Recommend to the client that the use of a message subject is
3979 * strongly recommended in this room, if either the SUBJECTREQ flag
3980 * is set, or if there is one or more Internet email recipients.
3982 if (CC->room.QRflags2 & QR2_SUBJECTREQ) subject_required = 1;
3983 if ((valid_to) && (valid_to->num_internet > 0)) subject_required = 1;
3984 if ((valid_cc) && (valid_cc->num_internet > 0)) subject_required = 1;
3985 if ((valid_bcc) && (valid_bcc->num_internet > 0)) subject_required = 1;
3987 /* If we're only checking the validity of the request, return
3988 * success without creating the message.
3991 cprintf("%d %s|%d\n", CIT_OK,
3992 ((valid_to != NULL) ? valid_to->display_recp : ""),
3994 free_recipients(valid_to);
3995 free_recipients(valid_cc);
3996 free_recipients(valid_bcc);
4000 /* We don't need these anymore because we'll do it differently below */
4001 free_recipients(valid_to);
4002 free_recipients(valid_cc);
4003 free_recipients(valid_bcc);
4005 /* Read in the message from the client. */
4007 cprintf("%d send message\n", START_CHAT_MODE);
4009 cprintf("%d send message\n", SEND_LISTING);
4012 msg = CtdlMakeMessage(&CC->user, recp, cc,
4013 CC->room.QRname, anonymous, format_type,
4014 newusername, newuseremail, subject,
4015 ((!IsEmptyStr(supplied_euid)) ? supplied_euid : NULL),
4018 /* Put together one big recipients struct containing to/cc/bcc all in
4019 * one. This is for the envelope.
4021 char *all_recps = malloc(SIZ * 3);
4022 strcpy(all_recps, recp);
4023 if (!IsEmptyStr(cc)) {
4024 if (!IsEmptyStr(all_recps)) {
4025 strcat(all_recps, ",");
4027 strcat(all_recps, cc);
4029 if (!IsEmptyStr(bcc)) {
4030 if (!IsEmptyStr(all_recps)) {
4031 strcat(all_recps, ",");
4033 strcat(all_recps, bcc);
4035 if (!IsEmptyStr(all_recps)) {
4036 valid = validate_recipients(all_recps, NULL, 0);
4044 msgnum = CtdlSubmitMsg(msg, valid, "", QP_EADDR);
4047 cprintf("%ld\n", msgnum);
4049 cprintf("Message accepted.\n");
4052 cprintf("Internal error.\n");
4054 if (msg->cm_fields['E'] != NULL) {
4055 cprintf("%s\n", msg->cm_fields['E']);
4062 CtdlFreeMessage(msg);
4064 if (valid != NULL) {
4065 free_recipients(valid);
4073 * API function to delete messages which match a set of criteria
4074 * (returns the actual number of messages deleted)
4076 int CtdlDeleteMessages(char *room_name, /* which room */
4077 long *dmsgnums, /* array of msg numbers to be deleted */
4078 int num_dmsgnums, /* number of msgs to be deleted, or 0 for "any" */
4079 char *content_type /* or "" for any. regular expressions expected. */
4082 struct ctdlroom qrbuf;
4083 struct cdbdata *cdbfr;
4084 long *msglist = NULL;
4085 long *dellist = NULL;
4088 int num_deleted = 0;
4090 struct MetaData smi;
4093 int need_to_free_re = 0;
4095 if (content_type) if (!IsEmptyStr(content_type)) {
4096 regcomp(&re, content_type, 0);
4097 need_to_free_re = 1;
4099 CtdlLogPrintf(CTDL_DEBUG, "CtdlDeleteMessages(%s, %d msgs, %s)\n",
4100 room_name, num_dmsgnums, content_type);
4102 /* get room record, obtaining a lock... */
4103 if (lgetroom(&qrbuf, room_name) != 0) {
4104 CtdlLogPrintf(CTDL_ERR, "CtdlDeleteMessages(): Room <%s> not found\n",
4106 if (need_to_free_re) regfree(&re);
4107 return (0); /* room not found */
4109 cdbfr = cdb_fetch(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long));
4111 if (cdbfr != NULL) {
4112 dellist = malloc(cdbfr->len);
4113 msglist = (long *) cdbfr->ptr;
4114 cdbfr->ptr = NULL; /* CtdlDeleteMessages() now owns this memory */
4115 num_msgs = cdbfr->len / sizeof(long);
4119 for (i = 0; i < num_msgs; ++i) {
4122 /* Set/clear a bit for each criterion */
4124 /* 0 messages in the list or a null list means that we are
4125 * interested in deleting any messages which meet the other criteria.
4127 if ((num_dmsgnums == 0) || (dmsgnums == NULL)) {
4128 delete_this |= 0x01;
4131 for (j=0; j<num_dmsgnums; ++j) {
4132 if (msglist[i] == dmsgnums[j]) {
4133 delete_this |= 0x01;
4138 if (IsEmptyStr(content_type)) {
4139 delete_this |= 0x02;
4141 GetMetaData(&smi, msglist[i]);
4142 if (regexec(&re, smi.meta_content_type, 1, &pm, 0) == 0) {
4143 delete_this |= 0x02;
4147 /* Delete message only if all bits are set */
4148 if (delete_this == 0x03) {
4149 dellist[num_deleted++] = msglist[i];
4154 num_msgs = sort_msglist(msglist, num_msgs);
4155 cdb_store(CDB_MSGLISTS, &qrbuf.QRnumber, (int)sizeof(long),
4156 msglist, (int)(num_msgs * sizeof(long)));
4158 qrbuf.QRhighest = msglist[num_msgs - 1];
4162 /* Go through the messages we pulled out of the index, and decrement
4163 * their reference counts by 1. If this is the only room the message
4164 * was in, the reference count will reach zero and the message will
4165 * automatically be deleted from the database. We do this in a
4166 * separate pass because there might be plug-in hooks getting called,
4167 * and we don't want that happening during an S_ROOMS critical
4170 if (num_deleted) for (i=0; i<num_deleted; ++i) {
4171 PerformDeleteHooks(qrbuf.QRname, dellist[i]);
4172 AdjRefCount(dellist[i], -1);
4175 /* Now free the memory we used, and go away. */
4176 if (msglist != NULL) free(msglist);
4177 if (dellist != NULL) free(dellist);
4178 CtdlLogPrintf(CTDL_DEBUG, "%d message(s) deleted.\n", num_deleted);
4179 if (need_to_free_re) regfree(&re);
4180 return (num_deleted);
4186 * Check whether the current user has permission to delete messages from
4187 * the current room (returns 1 for yes, 0 for no)
4189 int CtdlDoIHavePermissionToDeleteMessagesFromThisRoom(void) {
4191 CtdlRoomAccess(&CC->room, &CC->user, &ra, NULL);
4192 if (ra & UA_DELETEALLOWED) return(1);
4200 * Delete message from current room
4202 void cmd_dele(char *args)
4211 extract_token(msgset, args, 0, '|', sizeof msgset);
4212 num_msgs = num_tokens(msgset, ',');
4214 cprintf("%d Nothing to do.\n", CIT_OK);
4218 if (CtdlDoIHavePermissionToDeleteMessagesFromThisRoom() == 0) {
4219 cprintf("%d Higher access required.\n",
4220 ERROR + HIGHER_ACCESS_REQUIRED);
4225 * Build our message set to be moved/copied
4227 msgs = malloc(num_msgs * sizeof(long));
4228 for (i=0; i<num_msgs; ++i) {
4229 extract_token(msgtok, msgset, i, ',', sizeof msgtok);
4230 msgs[i] = atol(msgtok);
4233 num_deleted = CtdlDeleteMessages(CC->room.QRname, msgs, num_msgs, "");
4237 cprintf("%d %d message%s deleted.\n", CIT_OK,
4238 num_deleted, ((num_deleted != 1) ? "s" : ""));
4240 cprintf("%d Message not found.\n", ERROR + MESSAGE_NOT_FOUND);
4248 * move or copy a message to another room
4250 void cmd_move(char *args)
4257 char targ[ROOMNAMELEN];
4258 struct ctdlroom qtemp;
4265 extract_token(msgset, args, 0, '|', sizeof msgset);
4266 num_msgs = num_tokens(msgset, ',');
4268 cprintf("%d Nothing to do.\n", CIT_OK);
4272 extract_token(targ, args, 1, '|', sizeof targ);
4273 convert_room_name_macros(targ, sizeof targ);
4274 targ[ROOMNAMELEN - 1] = 0;
4275 is_copy = extract_int(args, 2);
4277 if (getroom(&qtemp, targ) != 0) {
4278 cprintf("%d '%s' does not exist.\n", ERROR + ROOM_NOT_FOUND, targ);
4282 if (!strcasecmp(qtemp.QRname, CC->room.QRname)) {
4283 cprintf("%d Source and target rooms are the same.\n", ERROR + ALREADY_EXISTS);
4287 getuser(&CC->user, CC->curr_user);
4288 CtdlRoomAccess(&qtemp, &CC->user, &ra, NULL);
4290 /* Check for permission to perform this operation.
4291 * Remember: "CC->room" is source, "qtemp" is target.
4295 /* Aides can move/copy */
4296 if (CC->user.axlevel >= 6) permit = 1;
4298 /* Room aides can move/copy */
4299 if (CC->user.usernum == CC->room.QRroomaide) permit = 1;
4301 /* Permit move/copy from personal rooms */
4302 if ((CC->room.QRflags & QR_MAILBOX)
4303 && (qtemp.QRflags & QR_MAILBOX)) permit = 1;
4305 /* Permit only copy from public to personal room */
4307 && (!(CC->room.QRflags & QR_MAILBOX))
4308 && (qtemp.QRflags & QR_MAILBOX)) permit = 1;
4310 /* Permit message removal from collaborative delete rooms */
4311 if (CC->room.QRflags2 & QR2_COLLABDEL) permit = 1;
4313 /* Users allowed to post into the target room may move into it too. */
4314 if ((CC->room.QRflags & QR_MAILBOX) &&
4315 (qtemp.QRflags & UA_POSTALLOWED)) permit = 1;
4317 /* User must have access to target room */
4318 if (!(ra & UA_KNOWN)) permit = 0;
4321 cprintf("%d Higher access required.\n",
4322 ERROR + HIGHER_ACCESS_REQUIRED);
4327 * Build our message set to be moved/copied
4329 msgs = malloc(num_msgs * sizeof(long));
4330 for (i=0; i<num_msgs; ++i) {
4331 extract_token(msgtok, msgset, i, ',', sizeof msgtok);
4332 msgs[i] = atol(msgtok);
4338 err = CtdlSaveMsgPointersInRoom(targ, msgs, num_msgs, 1, NULL);
4340 cprintf("%d Cannot store message(s) in %s: error %d\n",
4346 /* Now delete the message from the source room,
4347 * if this is a 'move' rather than a 'copy' operation.
4350 CtdlDeleteMessages(CC->room.QRname, msgs, num_msgs, "");
4354 cprintf("%d Message(s) %s.\n", CIT_OK, (is_copy ? "copied" : "moved") );
4360 * GetMetaData() - Get the supplementary record for a message
4362 void GetMetaData(struct MetaData *smibuf, long msgnum)
4365 struct cdbdata *cdbsmi;
4368 memset(smibuf, 0, sizeof(struct MetaData));
4369 smibuf->meta_msgnum = msgnum;
4370 smibuf->meta_refcount = 1; /* Default reference count is 1 */
4372 /* Use the negative of the message number for its supp record index */
4373 TheIndex = (0L - msgnum);
4375 cdbsmi = cdb_fetch(CDB_MSGMAIN, &TheIndex, sizeof(long));
4376 if (cdbsmi == NULL) {
4377 return; /* record not found; go with defaults */
4379 memcpy(smibuf, cdbsmi->ptr,
4380 ((cdbsmi->len > sizeof(struct MetaData)) ?
4381 sizeof(struct MetaData) : cdbsmi->len));
4388 * PutMetaData() - (re)write supplementary record for a message
4390 void PutMetaData(struct MetaData *smibuf)
4394 /* Use the negative of the message number for the metadata db index */
4395 TheIndex = (0L - smibuf->meta_msgnum);
4397 cdb_store(CDB_MSGMAIN,
4398 &TheIndex, (int)sizeof(long),
4399 smibuf, (int)sizeof(struct MetaData));
4404 * AdjRefCount - submit an adjustment to the reference count for a message.
4405 * (These are just queued -- we actually process them later.)
4407 void AdjRefCount(long msgnum, int incr)
4409 struct arcq new_arcq;
4412 begin_critical_section(S_SUPPMSGMAIN);
4413 if (arcfp == NULL) {
4414 arcfp = fopen(file_arcq, "ab+");
4416 end_critical_section(S_SUPPMSGMAIN);
4418 /* msgnum < 0 means that we're trying to close the file */
4420 CtdlLogPrintf(CTDL_DEBUG, "Closing the AdjRefCount queue file\n");
4421 begin_critical_section(S_SUPPMSGMAIN);
4422 if (arcfp != NULL) {
4426 end_critical_section(S_SUPPMSGMAIN);
4431 * If we can't open the queue, perform the operation synchronously.
4433 if (arcfp == NULL) {
4434 TDAP_AdjRefCount(msgnum, incr);
4438 new_arcq.arcq_msgnum = msgnum;
4439 new_arcq.arcq_delta = incr;
4440 rv = fwrite(&new_arcq, sizeof(struct arcq), 1, arcfp);
4448 * TDAP_ProcessAdjRefCountQueue()
4450 * Process the queue of message count adjustments that was created by calls
4451 * to AdjRefCount() ... by reading the queue and calling TDAP_AdjRefCount()
4452 * for each one. This should be an "off hours" operation.
4454 int TDAP_ProcessAdjRefCountQueue(void)
4456 char file_arcq_temp[PATH_MAX];
4459 struct arcq arcq_rec;
4460 int num_records_processed = 0;
4462 snprintf(file_arcq_temp, sizeof file_arcq_temp, "%s.%04x", file_arcq, rand());
4464 begin_critical_section(S_SUPPMSGMAIN);
4465 if (arcfp != NULL) {
4470 r = link(file_arcq, file_arcq_temp);
4472 CtdlLogPrintf(CTDL_CRIT, "%s: %s\n", file_arcq_temp, strerror(errno));
4473 end_critical_section(S_SUPPMSGMAIN);
4474 return(num_records_processed);
4478 end_critical_section(S_SUPPMSGMAIN);
4480 fp = fopen(file_arcq_temp, "rb");
4482 CtdlLogPrintf(CTDL_CRIT, "%s: %s\n", file_arcq_temp, strerror(errno));
4483 return(num_records_processed);
4486 while (fread(&arcq_rec, sizeof(struct arcq), 1, fp) == 1) {
4487 TDAP_AdjRefCount(arcq_rec.arcq_msgnum, arcq_rec.arcq_delta);
4488 ++num_records_processed;
4492 r = unlink(file_arcq_temp);
4494 CtdlLogPrintf(CTDL_CRIT, "%s: %s\n", file_arcq_temp, strerror(errno));
4497 return(num_records_processed);
4503 * TDAP_AdjRefCount - adjust the reference count for a message.
4504 * This one does it "for real" because it's called by
4505 * the autopurger function that processes the queue
4506 * created by AdjRefCount(). If a message's reference
4507 * count becomes zero, we also delete the message from
4508 * disk and de-index it.
4510 void TDAP_AdjRefCount(long msgnum, int incr)
4513 struct MetaData smi;
4516 /* This is a *tight* critical section; please keep it that way, as
4517 * it may get called while nested in other critical sections.
4518 * Complicating this any further will surely cause deadlock!
4520 begin_critical_section(S_SUPPMSGMAIN);
4521 GetMetaData(&smi, msgnum);
4522 smi.meta_refcount += incr;
4524 end_critical_section(S_SUPPMSGMAIN);
4525 CtdlLogPrintf(CTDL_DEBUG, "msg %ld ref count delta %+d, is now %d\n",
4526 msgnum, incr, smi.meta_refcount);
4528 /* If the reference count is now zero, delete the message
4529 * (and its supplementary record as well).
4531 if (smi.meta_refcount == 0) {
4532 CtdlLogPrintf(CTDL_DEBUG, "Deleting message <%ld>\n", msgnum);
4534 /* Call delete hooks with NULL room to show it has gone altogether */
4535 PerformDeleteHooks(NULL, msgnum);
4537 /* Remove from message base */
4539 cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long));
4540 cdb_delete(CDB_BIGMSGS, &delnum, (int)sizeof(long));
4542 /* Remove metadata record */
4543 delnum = (0L - msgnum);
4544 cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long));
4550 * Write a generic object to this room
4552 * Note: this could be much more efficient. Right now we use two temporary
4553 * files, and still pull the message into memory as with all others.
4555 void CtdlWriteObject(char *req_room, /* Room to stuff it in */
4556 char *content_type, /* MIME type of this object */
4557 char *raw_message, /* Data to be written */
4558 off_t raw_length, /* Size of raw_message */
4559 struct ctdluser *is_mailbox, /* Mailbox room? */
4560 int is_binary, /* Is encoding necessary? */
4561 int is_unique, /* Del others of this type? */
4562 unsigned int flags /* Internal save flags */
4566 struct ctdlroom qrbuf;
4567 char roomname[ROOMNAMELEN];
4568 struct CtdlMessage *msg;
4569 char *encoded_message = NULL;
4571 if (is_mailbox != NULL) {
4572 MailboxName(roomname, sizeof roomname, is_mailbox, req_room);
4575 safestrncpy(roomname, req_room, sizeof(roomname));
4578 CtdlLogPrintf(CTDL_DEBUG, "Raw length is %ld\n", (long)raw_length);
4581 encoded_message = malloc((size_t) (((raw_length * 134) / 100) + 4096 ) );
4584 encoded_message = malloc((size_t)(raw_length + 4096));
4587 sprintf(encoded_message, "Content-type: %s\n", content_type);
4590 sprintf(&encoded_message[strlen(encoded_message)],
4591 "Content-transfer-encoding: base64\n\n"
4595 sprintf(&encoded_message[strlen(encoded_message)],
4596 "Content-transfer-encoding: 7bit\n\n"
4602 &encoded_message[strlen(encoded_message)],
4610 &encoded_message[strlen(encoded_message)],
4616 CtdlLogPrintf(CTDL_DEBUG, "Allocating\n");
4617 msg = malloc(sizeof(struct CtdlMessage));
4618 memset(msg, 0, sizeof(struct CtdlMessage));
4619 msg->cm_magic = CTDLMESSAGE_MAGIC;
4620 msg->cm_anon_type = MES_NORMAL;
4621 msg->cm_format_type = 4;
4622 msg->cm_fields['A'] = strdup(CC->user.fullname);
4623 msg->cm_fields['O'] = strdup(req_room);
4624 msg->cm_fields['N'] = strdup(config.c_nodename);
4625 msg->cm_fields['H'] = strdup(config.c_humannode);
4626 msg->cm_flags = flags;
4628 msg->cm_fields['M'] = encoded_message;
4630 /* Create the requested room if we have to. */
4631 if (getroom(&qrbuf, roomname) != 0) {
4632 create_room(roomname,
4633 ( (is_mailbox != NULL) ? 5 : 3 ),
4634 "", 0, 1, 0, VIEW_BBS);
4636 /* If the caller specified this object as unique, delete all
4637 * other objects of this type that are currently in the room.
4640 CtdlLogPrintf(CTDL_DEBUG, "Deleted %d other msgs of this type\n",
4641 CtdlDeleteMessages(roomname, NULL, 0, content_type)
4644 /* Now write the data */
4645 CtdlSubmitMsg(msg, NULL, roomname, 0);
4646 CtdlFreeMessage(msg);
4654 void CtdlGetSysConfigBackend(long msgnum, void *userdata) {
4655 config_msgnum = msgnum;
4659 char *CtdlGetSysConfig(char *sysconfname) {
4660 char hold_rm[ROOMNAMELEN];
4663 struct CtdlMessage *msg;
4666 strcpy(hold_rm, CC->room.QRname);
4667 if (getroom(&CC->room, SYSCONFIGROOM) != 0) {
4668 getroom(&CC->room, hold_rm);
4673 /* We want the last (and probably only) config in this room */
4674 begin_critical_section(S_CONFIG);
4675 config_msgnum = (-1L);
4676 CtdlForEachMessage(MSGS_LAST, 1, NULL, sysconfname, NULL,
4677 CtdlGetSysConfigBackend, NULL);
4678 msgnum = config_msgnum;
4679 end_critical_section(S_CONFIG);
4685 msg = CtdlFetchMessage(msgnum, 1);
4687 conf = strdup(msg->cm_fields['M']);
4688 CtdlFreeMessage(msg);
4695 getroom(&CC->room, hold_rm);
4697 if (conf != NULL) do {
4698 extract_token(buf, conf, 0, '\n', sizeof buf);
4699 strcpy(conf, &conf[strlen(buf)+1]);
4700 } while ( (!IsEmptyStr(conf)) && (!IsEmptyStr(buf)) );
4706 void CtdlPutSysConfig(char *sysconfname, char *sysconfdata) {
4707 CtdlWriteObject(SYSCONFIGROOM, sysconfname, sysconfdata, (strlen(sysconfdata)+1), NULL, 0, 1, 0);
4712 * Determine whether a given Internet address belongs to the current user
4714 int CtdlIsMe(char *addr, int addr_buf_len)
4716 struct recptypes *recp;
4719 recp = validate_recipients(addr, NULL, 0);
4720 if (recp == NULL) return(0);
4722 if (recp->num_local == 0) {
4723 free_recipients(recp);
4727 for (i=0; i<recp->num_local; ++i) {
4728 extract_token(addr, recp->recp_local, i, '|', addr_buf_len);
4729 if (!strcasecmp(addr, CC->user.fullname)) {
4730 free_recipients(recp);
4735 free_recipients(recp);
4741 * Citadel protocol command to do the same
4743 void cmd_isme(char *argbuf) {
4746 if (CtdlAccessCheck(ac_logged_in)) return;
4747 extract_token(addr, argbuf, 0, '|', sizeof addr);
4749 if (CtdlIsMe(addr, sizeof addr)) {
4750 cprintf("%d %s\n", CIT_OK, addr);
4753 cprintf("%d Not you.\n", ERROR + ILLEGAL_VALUE);
4759 /*****************************************************************************/
4760 /* MODULE INITIALIZATION STUFF */
4761 /*****************************************************************************/
4763 CTDL_MODULE_INIT(msgbase)
4765 CtdlRegisterProtoHook(cmd_msgs, "MSGS", "Output a list of messages in the current room");
4766 CtdlRegisterProtoHook(cmd_msg0, "MSG0", "Output a message in plain text format");
4767 CtdlRegisterProtoHook(cmd_msg2, "MSG2", "Output a message in RFC822 format");
4768 CtdlRegisterProtoHook(cmd_msg3, "MSG3", "Output a message in raw format (deprecated)");
4769 CtdlRegisterProtoHook(cmd_msg4, "MSG4", "Output a message in the client's preferred format");
4770 CtdlRegisterProtoHook(cmd_msgp, "MSGP", "Select preferred format for MSG4 output");
4771 CtdlRegisterProtoHook(cmd_opna, "OPNA", "Open an attachment for download");
4772 CtdlRegisterProtoHook(cmd_dlat, "DLAT", "Download an attachment");
4773 CtdlRegisterProtoHook(cmd_ent0, "ENT0", "Enter a message");
4774 CtdlRegisterProtoHook(cmd_dele, "DELE", "Delete a message");
4775 CtdlRegisterProtoHook(cmd_move, "MOVE", "Move or copy a message to another room");
4776 CtdlRegisterProtoHook(cmd_isme, "ISME", "Determine whether an email address belongs to a user");
4778 /* return our Subversion id for the Log */