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 */
358 /* Don't bother doing *anything* if we were passed a list of zero messages */
359 if (num_target_msgnums < 1) {
363 /* If no room was specified, we go with the current room. */
365 which_room = &CC->room;
368 /* If no user was specified, we go with the current user. */
370 which_user = &CC->user;
373 CtdlLogPrintf(CTDL_DEBUG, "CtdlSetSeen(%d msgs starting with %ld, %s, %d) in <%s>\n",
374 num_target_msgnums, target_msgnums[0],
375 (target_setting ? "SET" : "CLEAR"),
379 /* Learn about the user and room in question */
380 CtdlGetRelationship(&vbuf, which_user, which_room);
382 /* Load the message list */
383 cdbfr = cdb_fetch(CDB_MSGLISTS, &which_room->QRnumber, sizeof(long));
385 msglist = (long *) cdbfr->ptr;
386 cdbfr->ptr = NULL; /* CtdlSetSeen() now owns this memory */
387 num_msgs = cdbfr->len / sizeof(long);
390 return; /* No messages at all? No further action. */
393 is_set = malloc(num_msgs * sizeof(char));
394 memset(is_set, 0, (num_msgs * sizeof(char)) );
396 /* Decide which message set we're manipulating */
398 case ctdlsetseen_seen:
399 vset = NewStrBufPlain(vbuf.v_seen, -1);
401 case ctdlsetseen_answered:
402 vset = NewStrBufPlain(vbuf.v_answered, -1);
409 #if 0 /* This is a special diagnostic section. Do not allow it to run during normal operation. */
410 CtdlLogPrintf(CTDL_DEBUG, "There are %d messages in the room.\n", num_msgs);
411 for (i=0; i<num_msgs; ++i) {
412 if ((i > 0) && (msglist[i] <= msglist[i-1])) abort();
414 CtdlLogPrintf(CTDL_DEBUG, "We are twiddling %d of them.\n", num_target_msgnums);
415 for (k=0; k<num_target_msgnums; ++k) {
416 if ((k > 0) && (target_msgnums[k] <= target_msgnums[k-1])) abort();
420 CtdlLogPrintf(CTDL_DEBUG, "before update: %s\n", ChrPtr(vset));
422 /* Translate the existing sequence set into an array of booleans */
423 setstr = NewStrBuf();
427 while (StrBufExtract_NextToken(setstr, vset, &pvset, ',') >= 0) {
429 StrBufExtract_token(lostr, setstr, 0, ':');
430 if (StrBufNum_tokens(setstr, ':') >= 2) {
431 StrBufExtract_token(histr, setstr, 1, ':');
435 StrBufAppendBuf(histr, lostr, 0);
438 if (!strcmp(ChrPtr(histr), "*")) {
445 for (i = 0; i < num_msgs; ++i) {
446 if ((msglist[i] >= lo) && (msglist[i] <= hi)) {
456 /* Now translate the array of booleans back into a sequence set */
462 for (i=0; i<num_msgs; ++i) {
466 for (k=0; k<num_target_msgnums; ++k) {
467 if (msglist[i] == target_msgnums[k]) {
468 is_seen = target_setting;
472 w = 0; /* set to 1 if we write something to the string */
474 if ((was_seen == 0) && (is_seen == 1)) {
477 else if ((was_seen == 1) && (is_seen == 0)) {
481 if (StrLength(vset) > 0) {
482 StrBufAppendBufPlain(vset, HKEY(","), 0);
485 StrBufAppendPrintf(vset, "%ld", hi);
488 StrBufAppendPrintf(vset, "%ld:%ld", lo, hi);
491 else if ((is_seen) && (i == num_msgs - 1)) {
493 if (StrLength(vset) > 0) {
494 StrBufAppendBufPlain(vset, HKEY(","), 0);
496 if ((i==0) || (was_seen == 0)) {
497 StrBufAppendPrintf(vset, "%ld", msglist[i]);
500 StrBufAppendPrintf(vset, "%ld:%ld", lo, msglist[i]);
507 while (StrLength(vset) > SIZ) {
508 StrBufRemove_token(vset, 0, ',');
512 * If we're truncating the sequence set of messages marked with the 'seen' flag,
513 * we want the earliest messages (the truncated ones) to be marked, not unmarked.
514 * Otherwise messages at the beginning will suddenly appear to be 'unseen'.
516 if (which_set == ctdlsetseen_seen) {
518 first_tok = NewStrBuf();
519 StrBufExtract_token(first_tok, vset, 0, ',');
520 StrBufRemove_token(vset, 0, ',');
522 if (StrBufNum_tokens(first_tok, ':') > 1) {
523 StrBufRemove_token(first_tok, 0, ':');
527 new_set = NewStrBuf();
528 StrBufAppendBufPlain(new_set, HKEY("1:"), 0);
529 StrBufAppendBuf(new_set, first_tok, 0);
530 StrBufAppendBufPlain(new_set, HKEY(":"), 0);
531 StrBufAppendBuf(new_set, vset, 0);
534 FreeStrBuf(&first_tok);
539 CtdlLogPrintf(CTDL_DEBUG, " after update: %s\n", ChrPtr(vset));
541 /* Decide which message set we're manipulating */
543 case ctdlsetseen_seen:
544 safestrncpy(vbuf.v_seen, ChrPtr(vset), sizeof vbuf.v_seen);
546 case ctdlsetseen_answered:
547 safestrncpy(vbuf.v_answered, ChrPtr(vset), sizeof vbuf.v_answered);
554 CtdlSetRelationship(&vbuf, which_user, which_room);
561 * API function to perform an operation for each qualifying message in the
562 * current room. (Returns the number of messages processed.)
564 int CtdlForEachMessage(int mode, long ref, char *search_string,
566 struct CtdlMessage *compare,
567 void (*CallBack) (long, void *),
573 struct cdbdata *cdbfr;
574 long *msglist = NULL;
576 int num_processed = 0;
579 struct CtdlMessage *msg = NULL;
582 int printed_lastold = 0;
583 int num_search_msgs = 0;
584 long *search_msgs = NULL;
586 int need_to_free_re = 0;
589 if ((content_type) && (!IsEmptyStr(content_type))) {
590 regcomp(&re, content_type, 0);
594 /* Learn about the user and room in question */
595 getuser(&CC->user, CC->curr_user);
596 CtdlGetRelationship(&vbuf, &CC->user, &CC->room);
598 /* Load the message list */
599 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long));
601 msglist = (long *) cdbfr->ptr;
602 num_msgs = cdbfr->len / sizeof(long);
604 if (need_to_free_re) regfree(&re);
605 return 0; /* No messages at all? No further action. */
610 * Now begin the traversal.
612 if (num_msgs > 0) for (a = 0; a < num_msgs; ++a) {
614 /* If the caller is looking for a specific MIME type, filter
615 * out all messages which are not of the type requested.
617 if ((content_type != NULL) && (!IsEmptyStr(content_type))) {
619 /* This call to GetMetaData() sits inside this loop
620 * so that we only do the extra database read per msg
621 * if we need to. Doing the extra read all the time
622 * really kills the server. If we ever need to use
623 * metadata for another search criterion, we need to
624 * move the read somewhere else -- but still be smart
625 * enough to only do the read if the caller has
626 * specified something that will need it.
628 GetMetaData(&smi, msglist[a]);
630 /* if (strcasecmp(smi.meta_content_type, content_type)) { old non-regex way */
631 if (regexec(&re, smi.meta_content_type, 1, &pm, 0) != 0) {
637 num_msgs = sort_msglist(msglist, num_msgs);
639 /* If a template was supplied, filter out the messages which
640 * don't match. (This could induce some delays!)
643 if (compare != NULL) {
644 for (a = 0; a < num_msgs; ++a) {
645 msg = CtdlFetchMessage(msglist[a], 1);
647 if (CtdlMsgCmp(msg, compare)) {
650 CtdlFreeMessage(msg);
656 /* If a search string was specified, get a message list from
657 * the full text index and remove messages which aren't on both
661 * Since the lists are sorted and strictly ascending, and the
662 * output list is guaranteed to be shorter than or equal to the
663 * input list, we overwrite the bottom of the input list. This
664 * eliminates the need to memmove big chunks of the list over and
667 if ( (num_msgs > 0) && (mode == MSGS_SEARCH) && (search_string) ) {
669 /* Call search module via hook mechanism.
670 * NULL means use any search function available.
671 * otherwise replace with a char * to name of search routine
673 CtdlModuleDoSearch(&num_search_msgs, &search_msgs, search_string, "fulltext");
675 if (num_search_msgs > 0) {
679 orig_num_msgs = num_msgs;
681 for (i=0; i<orig_num_msgs; ++i) {
682 for (j=0; j<num_search_msgs; ++j) {
683 if (msglist[i] == search_msgs[j]) {
684 msglist[num_msgs++] = msglist[i];
690 num_msgs = 0; /* No messages qualify */
692 if (search_msgs != NULL) free(search_msgs);
694 /* Now that we've purged messages which don't contain the search
695 * string, treat a MSGS_SEARCH just like a MSGS_ALL from this
702 * Now iterate through the message list, according to the
703 * criteria supplied by the caller.
706 for (a = 0; a < num_msgs; ++a) {
707 thismsg = msglist[a];
708 if (mode == MSGS_ALL) {
712 is_seen = is_msg_in_sequence_set(
713 vbuf.v_seen, thismsg);
714 if (is_seen) lastold = thismsg;
720 || ((mode == MSGS_OLD) && (is_seen))
721 || ((mode == MSGS_NEW) && (!is_seen))
722 || ((mode == MSGS_LAST) && (a >= (num_msgs - ref)))
723 || ((mode == MSGS_FIRST) && (a < ref))
724 || ((mode == MSGS_GT) && (thismsg > ref))
725 || ((mode == MSGS_EQ) && (thismsg == ref))
728 if ((mode == MSGS_NEW) && (CC->user.flags & US_LASTOLD) && (lastold > 0L) && (printed_lastold == 0) && (!is_seen)) {
730 CallBack(lastold, userdata);
734 if (CallBack) CallBack(thismsg, userdata);
738 cdb_free(cdbfr); /* Clean up */
739 if (need_to_free_re) regfree(&re);
740 return num_processed;
746 * cmd_msgs() - get list of message #'s in this room
747 * implements the MSGS server command using CtdlForEachMessage()
749 void cmd_msgs(char *cmdbuf)
758 int with_template = 0;
759 struct CtdlMessage *template = NULL;
760 int with_headers = 0;
761 char search_string[1024];
763 extract_token(which, cmdbuf, 0, '|', sizeof which);
764 cm_ref = extract_int(cmdbuf, 1);
765 extract_token(search_string, cmdbuf, 1, '|', sizeof search_string);
766 with_template = extract_int(cmdbuf, 2);
767 with_headers = extract_int(cmdbuf, 3);
770 if (!strncasecmp(which, "OLD", 3))
772 else if (!strncasecmp(which, "NEW", 3))
774 else if (!strncasecmp(which, "FIRST", 5))
776 else if (!strncasecmp(which, "LAST", 4))
778 else if (!strncasecmp(which, "GT", 2))
780 else if (!strncasecmp(which, "SEARCH", 6))
785 if ((!(CC->logged_in)) && (!(CC->internal_pgm))) {
786 cprintf("%d not logged in\n", ERROR + NOT_LOGGED_IN);
790 if ( (mode == MSGS_SEARCH) && (!config.c_enable_fulltext) ) {
791 cprintf("%d Full text index is not enabled on this server.\n",
792 ERROR + CMD_NOT_SUPPORTED);
798 cprintf("%d Send template then receive message list\n",
800 template = (struct CtdlMessage *)
801 malloc(sizeof(struct CtdlMessage));
802 memset(template, 0, sizeof(struct CtdlMessage));
803 template->cm_magic = CTDLMESSAGE_MAGIC;
804 template->cm_anon_type = MES_NORMAL;
806 while(client_getln(buf, sizeof buf) >= 0 && strcmp(buf,"000")) {
807 extract_token(tfield, buf, 0, '|', sizeof tfield);
808 extract_token(tvalue, buf, 1, '|', sizeof tvalue);
809 for (i='A'; i<='Z'; ++i) if (msgkeys[i]!=NULL) {
810 if (!strcasecmp(tfield, msgkeys[i])) {
811 template->cm_fields[i] =
819 cprintf("%d \n", LISTING_FOLLOWS);
822 CtdlForEachMessage(mode,
823 ( (mode == MSGS_SEARCH) ? 0 : cm_ref ),
824 ( (mode == MSGS_SEARCH) ? search_string : NULL ),
827 (with_headers ? headers_listing : simple_listing),
830 if (template != NULL) CtdlFreeMessage(template);
838 * help_subst() - support routine for help file viewer
840 void help_subst(char *strbuf, char *source, char *dest)
845 while (p = pattern2(strbuf, source), (p >= 0)) {
846 strcpy(workbuf, &strbuf[p + strlen(source)]);
847 strcpy(&strbuf[p], dest);
848 strcat(strbuf, workbuf);
853 void do_help_subst(char *buffer)
857 help_subst(buffer, "^nodename", config.c_nodename);
858 help_subst(buffer, "^humannode", config.c_humannode);
859 help_subst(buffer, "^fqdn", config.c_fqdn);
860 help_subst(buffer, "^username", CC->user.fullname);
861 snprintf(buf2, sizeof buf2, "%ld", CC->user.usernum);
862 help_subst(buffer, "^usernum", buf2);
863 help_subst(buffer, "^sysadm", config.c_sysadm);
864 help_subst(buffer, "^variantname", CITADEL);
865 snprintf(buf2, sizeof buf2, "%d", config.c_maxsessions);
866 help_subst(buffer, "^maxsessions", buf2);
867 help_subst(buffer, "^bbsdir", ctdl_message_dir);
873 * memfmout() - Citadel text formatter and paginator.
874 * Although the original purpose of this routine was to format
875 * text to the reader's screen width, all we're really using it
876 * for here is to format text out to 80 columns before sending it
877 * to the client. The client software may reformat it again.
880 char *mptr, /* where are we going to get our text from? */
881 char subst, /* nonzero if we should do substitutions */
882 char *nl) /* string to terminate lines with */
890 static int width = 80;
895 c = 1; /* c is the current pos */
899 while (ch = *mptr, ((ch != 0) && (strlen(buffer) < 126))) {
901 buffer[strlen(buffer) + 1] = 0;
902 buffer[strlen(buffer)] = ch;
905 if (buffer[0] == '^')
906 do_help_subst(buffer);
908 buffer[strlen(buffer) + 1] = 0;
910 strcpy(buffer, &buffer[1]);
918 if (((ch == 13) || (ch == 10)) && (old != 13) && (old != 10)) {
921 if (((old == 13) || (old == 10)) && (isspace(real))) {
926 if (((strlen(aaa) + c) > (width - 5)) && (strlen(aaa) > (width - 5))) {
927 cprintf("%s%s", nl, aaa);
936 if ((strlen(aaa) + c) > (width - 5)) {
945 if ((ch == 13) || (ch == 10)) {
946 cprintf("%s%s", aaa, nl);
953 cprintf("%s%s", aaa, nl);
959 * Callback function for mime parser that simply lists the part
961 void list_this_part(char *name, char *filename, char *partnum, char *disp,
962 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
963 char *cbid, void *cbuserdata)
967 ma = (struct ma_info *)cbuserdata;
968 if (ma->is_ma == 0) {
969 cprintf("part=%s|%s|%s|%s|%s|%ld|%s\n",
970 name, filename, partnum, disp, cbtype, (long)length, cbid);
975 * Callback function for multipart prefix
977 void list_this_pref(char *name, char *filename, char *partnum, char *disp,
978 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
979 char *cbid, void *cbuserdata)
983 ma = (struct ma_info *)cbuserdata;
984 if (!strcasecmp(cbtype, "multipart/alternative")) {
988 if (ma->is_ma == 0) {
989 cprintf("pref=%s|%s\n", partnum, cbtype);
994 * Callback function for multipart sufffix
996 void list_this_suff(char *name, char *filename, char *partnum, char *disp,
997 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
998 char *cbid, void *cbuserdata)
1002 ma = (struct ma_info *)cbuserdata;
1003 if (ma->is_ma == 0) {
1004 cprintf("suff=%s|%s\n", partnum, cbtype);
1006 if (!strcasecmp(cbtype, "multipart/alternative")) {
1013 * Callback function for mime parser that opens a section for downloading
1015 void mime_download(char *name, char *filename, char *partnum, char *disp,
1016 void *content, char *cbtype, char *cbcharset, size_t length,
1017 char *encoding, char *cbid, void *cbuserdata)
1021 /* Silently go away if there's already a download open. */
1022 if (CC->download_fp != NULL)
1026 (!IsEmptyStr(partnum) && (!strcasecmp(CC->download_desired_section, partnum)))
1027 || (!IsEmptyStr(cbid) && (!strcasecmp(CC->download_desired_section, cbid)))
1029 CC->download_fp = tmpfile();
1030 if (CC->download_fp == NULL)
1033 rv = fwrite(content, length, 1, CC->download_fp);
1034 fflush(CC->download_fp);
1035 rewind(CC->download_fp);
1037 OpenCmdResult(filename, cbtype);
1044 * Callback function for mime parser that outputs a section all at once.
1045 * We can specify the desired section by part number *or* content-id.
1047 void mime_spew_section(char *name, char *filename, char *partnum, char *disp,
1048 void *content, char *cbtype, char *cbcharset, size_t length,
1049 char *encoding, char *cbid, void *cbuserdata)
1051 int *found_it = (int *)cbuserdata;
1054 (!IsEmptyStr(partnum) && (!strcasecmp(CC->download_desired_section, partnum)))
1055 || (!IsEmptyStr(cbid) && (!strcasecmp(CC->download_desired_section, cbid)))
1058 cprintf("%d %d|-1|%s|%s\n",
1064 client_write(content, length);
1071 * Load a message from disk into memory.
1072 * This is used by CtdlOutputMsg() and other fetch functions.
1074 * NOTE: Caller is responsible for freeing the returned CtdlMessage struct
1075 * using the CtdlMessageFree() function.
1077 struct CtdlMessage *CtdlFetchMessage(long msgnum, int with_body)
1079 struct cdbdata *dmsgtext;
1080 struct CtdlMessage *ret = NULL;
1084 cit_uint8_t field_header;
1086 CtdlLogPrintf(CTDL_DEBUG, "CtdlFetchMessage(%ld, %d)\n", msgnum, with_body);
1088 dmsgtext = cdb_fetch(CDB_MSGMAIN, &msgnum, sizeof(long));
1089 if (dmsgtext == NULL) {
1092 mptr = dmsgtext->ptr;
1093 upper_bound = mptr + dmsgtext->len;
1095 /* Parse the three bytes that begin EVERY message on disk.
1096 * The first is always 0xFF, the on-disk magic number.
1097 * The second is the anonymous/public type byte.
1098 * The third is the format type byte (vari, fixed, or MIME).
1102 CtdlLogPrintf(CTDL_ERR, "Message %ld appears to be corrupted.\n", msgnum);
1106 ret = (struct CtdlMessage *) malloc(sizeof(struct CtdlMessage));
1107 memset(ret, 0, sizeof(struct CtdlMessage));
1109 ret->cm_magic = CTDLMESSAGE_MAGIC;
1110 ret->cm_anon_type = *mptr++; /* Anon type byte */
1111 ret->cm_format_type = *mptr++; /* Format type byte */
1114 * The rest is zero or more arbitrary fields. Load them in.
1115 * We're done when we encounter either a zero-length field or
1116 * have just processed the 'M' (message text) field.
1119 if (mptr >= upper_bound) {
1122 field_header = *mptr++;
1123 ret->cm_fields[field_header] = strdup(mptr);
1125 while (*mptr++ != 0); /* advance to next field */
1127 } while ((mptr < upper_bound) && (field_header != 'M'));
1131 /* Always make sure there's something in the msg text field. If
1132 * it's NULL, the message text is most likely stored separately,
1133 * so go ahead and fetch that. Failing that, just set a dummy
1134 * body so other code doesn't barf.
1136 if ( (ret->cm_fields['M'] == NULL) && (with_body) ) {
1137 dmsgtext = cdb_fetch(CDB_BIGMSGS, &msgnum, sizeof(long));
1138 if (dmsgtext != NULL) {
1139 ret->cm_fields['M'] = strdup(dmsgtext->ptr);
1143 if (ret->cm_fields['M'] == NULL) {
1144 ret->cm_fields['M'] = strdup("\r\n\r\n (no text)\r\n");
1147 /* Perform "before read" hooks (aborting if any return nonzero) */
1148 if (PerformMessageHooks(ret, EVT_BEFOREREAD) > 0) {
1149 CtdlFreeMessage(ret);
1158 * Returns 1 if the supplied pointer points to a valid Citadel message.
1159 * If the pointer is NULL or the magic number check fails, returns 0.
1161 int is_valid_message(struct CtdlMessage *msg) {
1164 if ((msg->cm_magic) != CTDLMESSAGE_MAGIC) {
1165 CtdlLogPrintf(CTDL_WARNING, "is_valid_message() -- self-check failed\n");
1173 * 'Destructor' for struct CtdlMessage
1175 void CtdlFreeMessage(struct CtdlMessage *msg)
1179 if (is_valid_message(msg) == 0)
1181 if (msg != NULL) free (msg);
1185 for (i = 0; i < 256; ++i)
1186 if (msg->cm_fields[i] != NULL) {
1187 free(msg->cm_fields[i]);
1190 msg->cm_magic = 0; /* just in case */
1196 * Pre callback function for multipart/alternative
1198 * NOTE: this differs from the standard behavior for a reason. Normally when
1199 * displaying multipart/alternative you want to show the _last_ usable
1200 * format in the message. Here we show the _first_ one, because it's
1201 * usually text/plain. Since this set of functions is designed for text
1202 * output to non-MIME-aware clients, this is the desired behavior.
1205 void fixed_output_pre(char *name, char *filename, char *partnum, char *disp,
1206 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
1207 char *cbid, void *cbuserdata)
1211 ma = (struct ma_info *)cbuserdata;
1212 CtdlLogPrintf(CTDL_DEBUG, "fixed_output_pre() type=<%s>\n", cbtype);
1213 if (!strcasecmp(cbtype, "multipart/alternative")) {
1217 if (!strcasecmp(cbtype, "message/rfc822")) {
1223 * Post callback function for multipart/alternative
1225 void fixed_output_post(char *name, char *filename, char *partnum, char *disp,
1226 void *content, char *cbtype, char *cbcharset, size_t length,
1227 char *encoding, char *cbid, void *cbuserdata)
1231 ma = (struct ma_info *)cbuserdata;
1232 CtdlLogPrintf(CTDL_DEBUG, "fixed_output_post() type=<%s>\n", cbtype);
1233 if (!strcasecmp(cbtype, "multipart/alternative")) {
1237 if (!strcasecmp(cbtype, "message/rfc822")) {
1243 * Inline callback function for mime parser that wants to display text
1245 void fixed_output(char *name, char *filename, char *partnum, char *disp,
1246 void *content, char *cbtype, char *cbcharset, size_t length,
1247 char *encoding, char *cbid, void *cbuserdata)
1254 ma = (struct ma_info *)cbuserdata;
1256 CtdlLogPrintf(CTDL_DEBUG,
1257 "fixed_output() part %s: %s (%s) (%ld bytes)\n",
1258 partnum, filename, cbtype, (long)length);
1261 * If we're in the middle of a multipart/alternative scope and
1262 * we've already printed another section, skip this one.
1264 if ( (ma->is_ma) && (ma->did_print) ) {
1265 CtdlLogPrintf(CTDL_DEBUG, "Skipping part %s (%s)\n", partnum, cbtype);
1270 if ( (!strcasecmp(cbtype, "text/plain"))
1271 || (IsEmptyStr(cbtype)) ) {
1274 client_write(wptr, length);
1275 if (wptr[length-1] != '\n') {
1282 if (!strcasecmp(cbtype, "text/html")) {
1283 ptr = html_to_ascii(content, length, 80, 0);
1285 client_write(ptr, wlen);
1286 if (ptr[wlen-1] != '\n') {
1293 if (ma->use_fo_hooks) {
1294 if (PerformFixedOutputHooks(cbtype, content, length)) {
1295 /* above function returns nonzero if it handled the part */
1300 if (strncasecmp(cbtype, "multipart/", 10)) {
1301 cprintf("Part %s: %s (%s) (%ld bytes)\r\n",
1302 partnum, filename, cbtype, (long)length);
1308 * The client is elegant and sophisticated and wants to be choosy about
1309 * MIME content types, so figure out which multipart/alternative part
1310 * we're going to send.
1312 * We use a system of weights. When we find a part that matches one of the
1313 * MIME types we've declared as preferential, we can store it in ma->chosen_part
1314 * and then set ma->chosen_pref to that MIME type's position in our preference
1315 * list. If we then hit another match, we only replace the first match if
1316 * the preference value is lower.
1318 void choose_preferred(char *name, char *filename, char *partnum, char *disp,
1319 void *content, char *cbtype, char *cbcharset, size_t length,
1320 char *encoding, char *cbid, void *cbuserdata)
1326 ma = (struct ma_info *)cbuserdata;
1328 // NOTE: REMOVING THIS CONDITIONAL FIXES BUG 220
1329 // http://bugzilla.citadel.org/show_bug.cgi?id=220
1330 // I don't know if there are any side effects! Please TEST TEST TEST
1331 //if (ma->is_ma > 0) {
1333 for (i=0; i<num_tokens(CC->preferred_formats, '|'); ++i) {
1334 extract_token(buf, CC->preferred_formats, i, '|', sizeof buf);
1335 if ( (!strcasecmp(buf, cbtype)) && (!ma->freeze) ) {
1336 if (i < ma->chosen_pref) {
1337 CtdlLogPrintf(CTDL_DEBUG, "Setting chosen part: <%s>\n", partnum);
1338 safestrncpy(ma->chosen_part, partnum, sizeof ma->chosen_part);
1339 ma->chosen_pref = i;
1346 * Now that we've chosen our preferred part, output it.
1348 void output_preferred(char *name, char *filename, char *partnum, char *disp,
1349 void *content, char *cbtype, char *cbcharset, size_t length,
1350 char *encoding, char *cbid, void *cbuserdata)
1354 int add_newline = 0;
1358 ma = (struct ma_info *)cbuserdata;
1360 /* This is not the MIME part you're looking for... */
1361 if (strcasecmp(partnum, ma->chosen_part)) return;
1363 /* If the content-type of this part is in our preferred formats
1364 * list, we can simply output it verbatim.
1366 for (i=0; i<num_tokens(CC->preferred_formats, '|'); ++i) {
1367 extract_token(buf, CC->preferred_formats, i, '|', sizeof buf);
1368 if (!strcasecmp(buf, cbtype)) {
1369 /* Yeah! Go! W00t!! */
1371 text_content = (char *)content;
1372 if (text_content[length-1] != '\n') {
1375 cprintf("Content-type: %s", cbtype);
1376 if (!IsEmptyStr(cbcharset)) {
1377 cprintf("; charset=%s", cbcharset);
1379 cprintf("\nContent-length: %d\n",
1380 (int)(length + add_newline) );
1381 if (!IsEmptyStr(encoding)) {
1382 cprintf("Content-transfer-encoding: %s\n", encoding);
1385 cprintf("Content-transfer-encoding: 7bit\n");
1387 cprintf("X-Citadel-MSG4-Partnum: %s\n", partnum);
1389 client_write(content, length);
1390 if (add_newline) cprintf("\n");
1395 /* No translations required or possible: output as text/plain */
1396 cprintf("Content-type: text/plain\n\n");
1397 fixed_output(name, filename, partnum, disp, content, cbtype, cbcharset,
1398 length, encoding, cbid, cbuserdata);
1403 char desired_section[64];
1410 * Callback function for
1412 void extract_encapsulated_message(char *name, char *filename, char *partnum, char *disp,
1413 void *content, char *cbtype, char *cbcharset, size_t length,
1414 char *encoding, char *cbid, void *cbuserdata)
1416 struct encapmsg *encap;
1418 encap = (struct encapmsg *)cbuserdata;
1420 /* Only proceed if this is the desired section... */
1421 if (!strcasecmp(encap->desired_section, partnum)) {
1422 encap->msglen = length;
1423 encap->msg = malloc(length + 2);
1424 memcpy(encap->msg, content, length);
1434 * Get a message off disk. (returns om_* values found in msgbase.h)
1437 int CtdlOutputMsg(long msg_num, /* message number (local) to fetch */
1438 int mode, /* how would you like that message? */
1439 int headers_only, /* eschew the message body? */
1440 int do_proto, /* do Citadel protocol responses? */
1441 int crlf, /* Use CRLF newlines instead of LF? */
1442 char *section, /* NULL or a message/rfc822 section */
1443 int flags /* should the bessage be exported clean? */
1445 struct CtdlMessage *TheMessage = NULL;
1446 int retcode = om_no_such_msg;
1447 struct encapmsg encap;
1449 CtdlLogPrintf(CTDL_DEBUG, "CtdlOutputMsg() msgnum=%ld, mode=%d, section=%s\n",
1451 (section ? section : "<>")
1454 if ((!(CC->logged_in)) && (!(CC->internal_pgm))) {
1455 if (do_proto) cprintf("%d Not logged in.\n",
1456 ERROR + NOT_LOGGED_IN);
1457 return(om_not_logged_in);
1460 /* FIXME: check message id against msglist for this room */
1463 * Fetch the message from disk. If we're in HEADERS_FAST mode,
1464 * request that we don't even bother loading the body into memory.
1466 if (headers_only == HEADERS_FAST) {
1467 TheMessage = CtdlFetchMessage(msg_num, 0);
1470 TheMessage = CtdlFetchMessage(msg_num, 1);
1473 if (TheMessage == NULL) {
1474 if (do_proto) cprintf("%d Can't locate msg %ld on disk\n",
1475 ERROR + MESSAGE_NOT_FOUND, msg_num);
1476 return(om_no_such_msg);
1479 /* Here is the weird form of this command, to process only an
1480 * encapsulated message/rfc822 section.
1482 if (section) if (!IsEmptyStr(section)) if (strcmp(section, "0")) {
1483 memset(&encap, 0, sizeof encap);
1484 safestrncpy(encap.desired_section, section, sizeof encap.desired_section);
1485 mime_parser(TheMessage->cm_fields['M'],
1487 *extract_encapsulated_message,
1488 NULL, NULL, (void *)&encap, 0
1490 CtdlFreeMessage(TheMessage);
1494 encap.msg[encap.msglen] = 0;
1495 TheMessage = convert_internet_message(encap.msg);
1496 encap.msg = NULL; /* no free() here, TheMessage owns it now */
1498 /* Now we let it fall through to the bottom of this
1499 * function, because TheMessage now contains the
1500 * encapsulated message instead of the top-level
1501 * message. Isn't that neat?
1506 if (do_proto) cprintf("%d msg %ld has no part %s\n",
1507 ERROR + MESSAGE_NOT_FOUND, msg_num, section);
1508 retcode = om_no_such_msg;
1513 /* Ok, output the message now */
1514 retcode = CtdlOutputPreLoadedMsg(TheMessage, mode, headers_only, do_proto, crlf, flags);
1515 CtdlFreeMessage(TheMessage);
1521 char *qp_encode_email_addrs(char *source)
1523 char user[256], node[256], name[256];
1524 const char headerStr[] = "=?UTF-8?Q?";
1528 int need_to_encode = 0;
1534 long nAddrPtrMax = 50;
1539 if (source == NULL) return source;
1540 if (IsEmptyStr(source)) return source;
1542 AddrPtr = malloc (sizeof (long) * nAddrPtrMax);
1543 AddrUtf8 = malloc (sizeof (long) * nAddrPtrMax);
1544 memset(AddrUtf8, 0, sizeof (long) * nAddrPtrMax);
1547 while (!IsEmptyStr (&source[i])) {
1548 if (nColons >= nAddrPtrMax){
1551 ptr = (long *) malloc(sizeof (long) * nAddrPtrMax * 2);
1552 memcpy (ptr, AddrPtr, sizeof (long) * nAddrPtrMax);
1553 free (AddrPtr), AddrPtr = ptr;
1555 ptr = (long *) malloc(sizeof (long) * nAddrPtrMax * 2);
1556 memset(&ptr[nAddrPtrMax], 0,
1557 sizeof (long) * nAddrPtrMax);
1559 memcpy (ptr, AddrUtf8, sizeof (long) * nAddrPtrMax);
1560 free (AddrUtf8), AddrUtf8 = ptr;
1563 if (((unsigned char) source[i] < 32) ||
1564 ((unsigned char) source[i] > 126)) {
1566 AddrUtf8[nColons] = 1;
1568 if (source[i] == '"')
1569 InQuotes = !InQuotes;
1570 if (!InQuotes && source[i] == ',') {
1571 AddrPtr[nColons] = i;
1576 if (need_to_encode == 0) {
1583 EncodedMaxLen = nColons * (sizeof(headerStr) + 3) + SourceLen * 3;
1584 Encoded = (char*) malloc (EncodedMaxLen);
1586 for (i = 0; i < nColons; i++)
1587 source[AddrPtr[i]++] = '\0';
1591 for (i = 0; i < nColons && nPtr != NULL; i++) {
1592 nmax = EncodedMaxLen - (nPtr - Encoded);
1594 process_rfc822_addr(&source[AddrPtr[i]],
1598 /* TODO: libIDN here ! */
1599 if (IsEmptyStr(name)) {
1600 n = snprintf(nPtr, nmax,
1601 (i==0)?"%s@%s" : ",%s@%s",
1605 EncodedName = rfc2047encode(name, strlen(name));
1606 n = snprintf(nPtr, nmax,
1607 (i==0)?"%s <%s@%s>" : ",%s <%s@%s>",
1608 EncodedName, user, node);
1613 n = snprintf(nPtr, nmax,
1614 (i==0)?"%s" : ",%s",
1615 &source[AddrPtr[i]]);
1621 ptr = (char*) malloc(EncodedMaxLen * 2);
1622 memcpy(ptr, Encoded, EncodedMaxLen);
1623 nnPtr = ptr + (nPtr - Encoded), nPtr = nnPtr;
1624 free(Encoded), Encoded = ptr;
1626 i--; /* do it once more with properly lengthened buffer */
1629 for (i = 0; i < nColons; i++)
1630 source[--AddrPtr[i]] = ',';
1637 /* If the last item in a list of recipients was truncated to a partial address,
1638 * remove it completely in order to avoid choking libSieve
1640 void sanitize_truncated_recipient(char *str)
1643 if (num_tokens(str, ',') < 2) return;
1645 int len = strlen(str);
1646 if (len < 900) return;
1647 if (len > 998) str[998] = 0;
1649 char *cptr = strrchr(str, ',');
1652 char *lptr = strchr(cptr, '<');
1653 char *rptr = strchr(cptr, '>');
1655 if ( (lptr) && (rptr) && (rptr > lptr) ) return;
1663 * Get a message off disk. (returns om_* values found in msgbase.h)
1665 int CtdlOutputPreLoadedMsg(
1666 struct CtdlMessage *TheMessage,
1667 int mode, /* how would you like that message? */
1668 int headers_only, /* eschew the message body? */
1669 int do_proto, /* do Citadel protocol responses? */
1670 int crlf, /* Use CRLF newlines instead of LF? */
1671 int flags /* should the bessage be exported clean? */
1675 cit_uint8_t ch, prev_ch;
1677 char display_name[256];
1679 char *nl; /* newline string */
1681 int subject_found = 0;
1684 /* Buffers needed for RFC822 translation. These are all filled
1685 * using functions that are bounds-checked, and therefore we can
1686 * make them substantially smaller than SIZ.
1693 char datestamp[100];
1695 CtdlLogPrintf(CTDL_DEBUG, "CtdlOutputPreLoadedMsg(TheMessage=%s, %d, %d, %d, %d\n",
1696 ((TheMessage == NULL) ? "NULL" : "not null"),
1697 mode, headers_only, do_proto, crlf);
1699 strcpy(mid, "unknown");
1700 nl = (crlf ? "\r\n" : "\n");
1702 if (!is_valid_message(TheMessage)) {
1703 CtdlLogPrintf(CTDL_ERR,
1704 "ERROR: invalid preloaded message for output\n");
1706 return(om_no_such_msg);
1709 /* Are we downloading a MIME component? */
1710 if (mode == MT_DOWNLOAD) {
1711 if (TheMessage->cm_format_type != FMT_RFC822) {
1713 cprintf("%d This is not a MIME message.\n",
1714 ERROR + ILLEGAL_VALUE);
1715 } else if (CC->download_fp != NULL) {
1716 if (do_proto) cprintf(
1717 "%d You already have a download open.\n",
1718 ERROR + RESOURCE_BUSY);
1720 /* Parse the message text component */
1721 mptr = TheMessage->cm_fields['M'];
1722 mime_parser(mptr, NULL, *mime_download, NULL, NULL, NULL, 0);
1723 /* If there's no file open by this time, the requested
1724 * section wasn't found, so print an error
1726 if (CC->download_fp == NULL) {
1727 if (do_proto) cprintf(
1728 "%d Section %s not found.\n",
1729 ERROR + FILE_NOT_FOUND,
1730 CC->download_desired_section);
1733 return((CC->download_fp != NULL) ? om_ok : om_mime_error);
1736 /* MT_SPEW_SECTION is like MT_DOWNLOAD except it outputs the whole MIME part
1737 * in a single server operation instead of opening a download file.
1739 if (mode == MT_SPEW_SECTION) {
1740 if (TheMessage->cm_format_type != FMT_RFC822) {
1742 cprintf("%d This is not a MIME message.\n",
1743 ERROR + ILLEGAL_VALUE);
1745 /* Parse the message text component */
1748 mptr = TheMessage->cm_fields['M'];
1749 mime_parser(mptr, NULL, *mime_spew_section, NULL, NULL, (void *)&found_it, 0);
1750 /* If section wasn't found, print an error
1753 if (do_proto) cprintf(
1754 "%d Section %s not found.\n",
1755 ERROR + FILE_NOT_FOUND,
1756 CC->download_desired_section);
1759 return((CC->download_fp != NULL) ? om_ok : om_mime_error);
1762 /* now for the user-mode message reading loops */
1763 if (do_proto) cprintf("%d msg:\n", LISTING_FOLLOWS);
1765 /* Does the caller want to skip the headers? */
1766 if (headers_only == HEADERS_NONE) goto START_TEXT;
1768 /* Tell the client which format type we're using. */
1769 if ( (mode == MT_CITADEL) && (do_proto) ) {
1770 cprintf("type=%d\n", TheMessage->cm_format_type);
1773 /* nhdr=yes means that we're only displaying headers, no body */
1774 if ( (TheMessage->cm_anon_type == MES_ANONONLY)
1775 && ((mode == MT_CITADEL) || (mode == MT_MIME))
1778 cprintf("nhdr=yes\n");
1781 /* begin header processing loop for Citadel message format */
1783 if ((mode == MT_CITADEL) || (mode == MT_MIME)) {
1785 safestrncpy(display_name, "<unknown>", sizeof display_name);
1786 if (TheMessage->cm_fields['A']) {
1787 strcpy(buf, TheMessage->cm_fields['A']);
1788 if (TheMessage->cm_anon_type == MES_ANONONLY) {
1789 safestrncpy(display_name, "****", sizeof display_name);
1791 else if (TheMessage->cm_anon_type == MES_ANONOPT) {
1792 safestrncpy(display_name, "anonymous", sizeof display_name);
1795 safestrncpy(display_name, buf, sizeof display_name);
1797 if ((is_room_aide())
1798 && ((TheMessage->cm_anon_type == MES_ANONONLY)
1799 || (TheMessage->cm_anon_type == MES_ANONOPT))) {
1800 size_t tmp = strlen(display_name);
1801 snprintf(&display_name[tmp],
1802 sizeof display_name - tmp,
1807 /* Don't show Internet address for users on the
1808 * local Citadel network.
1811 if (TheMessage->cm_fields['N'] != NULL)
1812 if (!IsEmptyStr(TheMessage->cm_fields['N']))
1813 if (haschar(TheMessage->cm_fields['N'], '.') == 0) {
1817 /* Now spew the header fields in the order we like them. */
1818 safestrncpy(allkeys, FORDER, sizeof allkeys);
1819 for (i=0; i<strlen(allkeys); ++i) {
1820 k = (int) allkeys[i];
1822 if ( (TheMessage->cm_fields[k] != NULL)
1823 && (msgkeys[k] != NULL) ) {
1824 if ((k == 'V') || (k == 'R') || (k == 'Y')) {
1825 sanitize_truncated_recipient(TheMessage->cm_fields[k]);
1828 if (do_proto) cprintf("%s=%s\n",
1832 else if ((k == 'F') && (suppress_f)) {
1835 /* Masquerade display name if needed */
1837 if (do_proto) cprintf("%s=%s\n",
1839 TheMessage->cm_fields[k]
1848 /* begin header processing loop for RFC822 transfer format */
1853 strcpy(snode, NODENAME);
1854 if (mode == MT_RFC822) {
1855 for (i = 0; i < 256; ++i) {
1856 if (TheMessage->cm_fields[i]) {
1857 mptr = mpptr = TheMessage->cm_fields[i];
1860 safestrncpy(luser, mptr, sizeof luser);
1861 safestrncpy(suser, mptr, sizeof suser);
1863 else if (i == 'Y') {
1864 if ((flags & QP_EADDR) != 0) {
1865 mptr = qp_encode_email_addrs(mptr);
1867 sanitize_truncated_recipient(mptr);
1868 cprintf("CC: %s%s", mptr, nl);
1870 else if (i == 'P') {
1871 cprintf("Return-Path: %s%s", mptr, nl);
1873 else if (i == 'L') {
1874 cprintf("List-ID: %s%s", mptr, nl);
1876 else if (i == 'V') {
1877 if ((flags & QP_EADDR) != 0)
1878 mptr = qp_encode_email_addrs(mptr);
1879 cprintf("Envelope-To: %s%s", mptr, nl);
1881 else if (i == 'U') {
1882 cprintf("Subject: %s%s", mptr, nl);
1886 safestrncpy(mid, mptr, sizeof mid);
1888 safestrncpy(fuser, mptr, sizeof fuser);
1889 /* else if (i == 'O')
1890 cprintf("X-Citadel-Room: %s%s",
1893 safestrncpy(snode, mptr, sizeof snode);
1896 if (haschar(mptr, '@') == 0)
1898 sanitize_truncated_recipient(mptr);
1899 cprintf("To: %s@%s", mptr, config.c_fqdn);
1904 if ((flags & QP_EADDR) != 0) {
1905 mptr = qp_encode_email_addrs(mptr);
1907 sanitize_truncated_recipient(mptr);
1908 cprintf("To: %s", mptr);
1912 else if (i == 'T') {
1913 datestring(datestamp, sizeof datestamp,
1914 atol(mptr), DATESTRING_RFC822);
1915 cprintf("Date: %s%s", datestamp, nl);
1917 else if (i == 'W') {
1918 cprintf("References: ");
1919 k = num_tokens(mptr, '|');
1920 for (j=0; j<k; ++j) {
1921 extract_token(buf, mptr, j, '|', sizeof buf);
1922 cprintf("<%s>", buf);
1935 if (subject_found == 0) {
1936 cprintf("Subject: (no subject)%s", nl);
1940 for (i=0; !IsEmptyStr(&suser[i]); ++i) {
1941 suser[i] = tolower(suser[i]);
1942 if (!isalnum(suser[i])) suser[i]='_';
1945 if (mode == MT_RFC822) {
1946 if (!strcasecmp(snode, NODENAME)) {
1947 safestrncpy(snode, FQDN, sizeof snode);
1950 /* Construct a fun message id */
1951 cprintf("Message-ID: <%s", mid);/// todo: this possibly breaks threadding mails.
1952 if (strchr(mid, '@')==NULL) {
1953 cprintf("@%s", snode);
1957 if (!is_room_aide() && (TheMessage->cm_anon_type == MES_ANONONLY)) {
1958 cprintf("From: \"----\" <x@x.org>%s", nl);
1960 else if (!is_room_aide() && (TheMessage->cm_anon_type == MES_ANONOPT)) {
1961 cprintf("From: \"anonymous\" <x@x.org>%s", nl);
1963 else if (!IsEmptyStr(fuser)) {
1964 cprintf("From: \"%s\" <%s>%s", luser, fuser, nl);
1967 cprintf("From: \"%s\" <%s@%s>%s", luser, suser, snode, nl);
1970 /* Blank line signifying RFC822 end-of-headers */
1971 if (TheMessage->cm_format_type != FMT_RFC822) {
1976 /* end header processing loop ... at this point, we're in the text */
1978 if (headers_only == HEADERS_FAST) goto DONE;
1979 mptr = TheMessage->cm_fields['M'];
1981 /* Tell the client about the MIME parts in this message */
1982 if (TheMessage->cm_format_type == FMT_RFC822) {
1983 if ( (mode == MT_CITADEL) || (mode == MT_MIME) ) {
1984 memset(&ma, 0, sizeof(struct ma_info));
1985 mime_parser(mptr, NULL,
1986 (do_proto ? *list_this_part : NULL),
1987 (do_proto ? *list_this_pref : NULL),
1988 (do_proto ? *list_this_suff : NULL),
1991 else if (mode == MT_RFC822) { /* unparsed RFC822 dump */
1992 char *start_of_text = NULL;
1993 start_of_text = strstr(mptr, "\n\r\n");
1994 if (start_of_text == NULL) start_of_text = strstr(mptr, "\n\n");
1995 if (start_of_text == NULL) start_of_text = mptr;
1997 start_of_text = strstr(start_of_text, "\n");
2002 int nllen = strlen(nl);
2004 while (ch=*mptr, ch!=0) {
2010 ((headers_only == HEADERS_NONE) && (mptr >= start_of_text))
2011 || ((headers_only == HEADERS_ONLY) && (mptr < start_of_text))
2012 || ((headers_only != HEADERS_NONE) && (headers_only != HEADERS_ONLY))
2015 sprintf(&outbuf[outlen], "%s", nl);
2019 outbuf[outlen++] = ch;
2023 if (flags & ESC_DOT)
2025 if ((prev_ch == 10) && (ch == '.') && ((*(mptr+1) == 13) || (*(mptr+1) == 10)))
2027 outbuf[outlen++] = '.';
2032 if (outlen > 1000) {
2033 client_write(outbuf, outlen);
2038 client_write(outbuf, outlen);
2046 if (headers_only == HEADERS_ONLY) {
2050 /* signify start of msg text */
2051 if ( (mode == MT_CITADEL) || (mode == MT_MIME) ) {
2052 if (do_proto) cprintf("text\n");
2055 /* If the format type on disk is 1 (fixed-format), then we want
2056 * everything to be output completely literally ... regardless of
2057 * what message transfer format is in use.
2059 if (TheMessage->cm_format_type == FMT_FIXED) {
2061 if (mode == MT_MIME) {
2062 cprintf("Content-type: text/plain\n\n");
2066 while (ch = *mptr++, ch > 0) {
2069 if ((ch == 10) || (buflen > 250)) {
2071 cprintf("%s%s", buf, nl);
2080 if (!IsEmptyStr(buf))
2081 cprintf("%s%s", buf, nl);
2084 /* If the message on disk is format 0 (Citadel vari-format), we
2085 * output using the formatter at 80 columns. This is the final output
2086 * form if the transfer format is RFC822, but if the transfer format
2087 * is Citadel proprietary, it'll still work, because the indentation
2088 * for new paragraphs is correct and the client will reformat the
2089 * message to the reader's screen width.
2091 if (TheMessage->cm_format_type == FMT_CITADEL) {
2092 if (mode == MT_MIME) {
2093 cprintf("Content-type: text/x-citadel-variformat\n\n");
2095 memfmout(mptr, 0, nl);
2098 /* If the message on disk is format 4 (MIME), we've gotta hand it
2099 * off to the MIME parser. The client has already been told that
2100 * this message is format 1 (fixed format), so the callback function
2101 * we use will display those parts as-is.
2103 if (TheMessage->cm_format_type == FMT_RFC822) {
2104 memset(&ma, 0, sizeof(struct ma_info));
2106 if (mode == MT_MIME) {
2107 ma.use_fo_hooks = 0;
2108 strcpy(ma.chosen_part, "1");
2109 ma.chosen_pref = 9999;
2110 mime_parser(mptr, NULL,
2111 *choose_preferred, *fixed_output_pre,
2112 *fixed_output_post, (void *)&ma, 0);
2113 mime_parser(mptr, NULL,
2114 *output_preferred, NULL, NULL, (void *)&ma, CC->msg4_dont_decode);
2117 ma.use_fo_hooks = 1;
2118 mime_parser(mptr, NULL,
2119 *fixed_output, *fixed_output_pre,
2120 *fixed_output_post, (void *)&ma, 0);
2125 DONE: /* now we're done */
2126 if (do_proto) cprintf("000\n");
2133 * display a message (mode 0 - Citadel proprietary)
2135 void cmd_msg0(char *cmdbuf)
2138 int headers_only = HEADERS_ALL;
2140 msgid = extract_long(cmdbuf, 0);
2141 headers_only = extract_int(cmdbuf, 1);
2143 CtdlOutputMsg(msgid, MT_CITADEL, headers_only, 1, 0, NULL, 0);
2149 * display a message (mode 2 - RFC822)
2151 void cmd_msg2(char *cmdbuf)
2154 int headers_only = HEADERS_ALL;
2156 msgid = extract_long(cmdbuf, 0);
2157 headers_only = extract_int(cmdbuf, 1);
2159 CtdlOutputMsg(msgid, MT_RFC822, headers_only, 1, 1, NULL, 0);
2165 * display a message (mode 3 - IGnet raw format - internal programs only)
2167 void cmd_msg3(char *cmdbuf)
2170 struct CtdlMessage *msg = NULL;
2173 if (CC->internal_pgm == 0) {
2174 cprintf("%d This command is for internal programs only.\n",
2175 ERROR + HIGHER_ACCESS_REQUIRED);
2179 msgnum = extract_long(cmdbuf, 0);
2180 msg = CtdlFetchMessage(msgnum, 1);
2182 cprintf("%d Message %ld not found.\n",
2183 ERROR + MESSAGE_NOT_FOUND, msgnum);
2187 serialize_message(&smr, msg);
2188 CtdlFreeMessage(msg);
2191 cprintf("%d Unable to serialize message\n",
2192 ERROR + INTERNAL_ERROR);
2196 cprintf("%d %ld\n", BINARY_FOLLOWS, (long)smr.len);
2197 client_write((char *)smr.ser, (int)smr.len);
2204 * Display a message using MIME content types
2206 void cmd_msg4(char *cmdbuf)
2211 msgid = extract_long(cmdbuf, 0);
2212 extract_token(section, cmdbuf, 1, '|', sizeof section);
2213 CtdlOutputMsg(msgid, MT_MIME, 0, 1, 0, (section[0] ? section : NULL) , 0);
2219 * Client tells us its preferred message format(s)
2221 void cmd_msgp(char *cmdbuf)
2223 if (!strcasecmp(cmdbuf, "dont_decode")) {
2224 CC->msg4_dont_decode = 1;
2225 cprintf("%d MSG4 will not pre-decode messages.\n", CIT_OK);
2228 safestrncpy(CC->preferred_formats, cmdbuf, sizeof(CC->preferred_formats));
2229 cprintf("%d Preferred MIME formats have been set.\n", CIT_OK);
2235 * Open a component of a MIME message as a download file
2237 void cmd_opna(char *cmdbuf)
2240 char desired_section[128];
2242 msgid = extract_long(cmdbuf, 0);
2243 extract_token(desired_section, cmdbuf, 1, '|', sizeof desired_section);
2244 safestrncpy(CC->download_desired_section, desired_section,
2245 sizeof CC->download_desired_section);
2246 CtdlOutputMsg(msgid, MT_DOWNLOAD, 0, 1, 1, NULL, 0);
2251 * Open a component of a MIME message and transmit it all at once
2253 void cmd_dlat(char *cmdbuf)
2256 char desired_section[128];
2258 msgid = extract_long(cmdbuf, 0);
2259 extract_token(desired_section, cmdbuf, 1, '|', sizeof desired_section);
2260 safestrncpy(CC->download_desired_section, desired_section,
2261 sizeof CC->download_desired_section);
2262 CtdlOutputMsg(msgid, MT_SPEW_SECTION, 0, 1, 1, NULL, 0);
2267 * Save one or more message pointers into a specified room
2268 * (Returns 0 for success, nonzero for failure)
2269 * roomname may be NULL to use the current room
2271 * Note that the 'supplied_msg' field may be set to NULL, in which case
2272 * the message will be fetched from disk, by number, if we need to perform
2273 * replication checks. This adds an additional database read, so if the
2274 * caller already has the message in memory then it should be supplied. (Obviously
2275 * this mode of operation only works if we're saving a single message.)
2277 int CtdlSaveMsgPointersInRoom(char *roomname, long newmsgidlist[], int num_newmsgs,
2278 int do_repl_check, struct CtdlMessage *supplied_msg)
2281 char hold_rm[ROOMNAMELEN];
2282 struct cdbdata *cdbfr;
2285 long highest_msg = 0L;
2288 struct CtdlMessage *msg = NULL;
2290 long *msgs_to_be_merged = NULL;
2291 int num_msgs_to_be_merged = 0;
2293 CtdlLogPrintf(CTDL_DEBUG,
2294 "CtdlSaveMsgPointersInRoom(room=%s, num_msgs=%d, repl=%d)\n",
2295 roomname, num_newmsgs, do_repl_check);
2297 strcpy(hold_rm, CC->room.QRname);
2300 if (newmsgidlist == NULL) return(ERROR + INTERNAL_ERROR);
2301 if (num_newmsgs < 1) return(ERROR + INTERNAL_ERROR);
2302 if (num_newmsgs > 1) supplied_msg = NULL;
2304 /* Now the regular stuff */
2305 if (lgetroom(&CC->room,
2306 ((roomname != NULL) ? roomname : CC->room.QRname) )
2308 CtdlLogPrintf(CTDL_ERR, "No such room <%s>\n", roomname);
2309 return(ERROR + ROOM_NOT_FOUND);
2313 msgs_to_be_merged = malloc(sizeof(long) * num_newmsgs);
2314 num_msgs_to_be_merged = 0;
2317 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long));
2318 if (cdbfr == NULL) {
2322 msglist = (long *) cdbfr->ptr;
2323 cdbfr->ptr = NULL; /* CtdlSaveMsgPointerInRoom() now owns this memory */
2324 num_msgs = cdbfr->len / sizeof(long);
2329 /* Create a list of msgid's which were supplied by the caller, but do
2330 * not already exist in the target room. It is absolutely taboo to
2331 * have more than one reference to the same message in a room.
2333 for (i=0; i<num_newmsgs; ++i) {
2335 if (num_msgs > 0) for (j=0; j<num_msgs; ++j) {
2336 if (msglist[j] == newmsgidlist[i]) {
2341 msgs_to_be_merged[num_msgs_to_be_merged++] = newmsgidlist[i];
2345 CtdlLogPrintf(9, "%d unique messages to be merged\n", num_msgs_to_be_merged);
2348 * Now merge the new messages
2350 msglist = realloc(msglist, (sizeof(long) * (num_msgs + num_msgs_to_be_merged)) );
2351 if (msglist == NULL) {
2352 CtdlLogPrintf(CTDL_ALERT, "ERROR: can't realloc message list!\n");
2354 memcpy(&msglist[num_msgs], msgs_to_be_merged, (sizeof(long) * num_msgs_to_be_merged) );
2355 num_msgs += num_msgs_to_be_merged;
2357 /* Sort the message list, so all the msgid's are in order */
2358 num_msgs = sort_msglist(msglist, num_msgs);
2360 /* Determine the highest message number */
2361 highest_msg = msglist[num_msgs - 1];
2363 /* Write it back to disk. */
2364 cdb_store(CDB_MSGLISTS, &CC->room.QRnumber, (int)sizeof(long),
2365 msglist, (int)(num_msgs * sizeof(long)));
2367 /* Free up the memory we used. */
2370 /* Update the highest-message pointer and unlock the room. */
2371 CC->room.QRhighest = highest_msg;
2372 lputroom(&CC->room);
2374 /* Perform replication checks if necessary */
2375 if ( (DoesThisRoomNeedEuidIndexing(&CC->room)) && (do_repl_check) ) {
2376 CtdlLogPrintf(CTDL_DEBUG, "CtdlSaveMsgPointerInRoom() doing repl checks\n");
2378 for (i=0; i<num_msgs_to_be_merged; ++i) {
2379 msgid = msgs_to_be_merged[i];
2381 if (supplied_msg != NULL) {
2385 msg = CtdlFetchMessage(msgid, 0);
2389 ReplicationChecks(msg);
2391 /* If the message has an Exclusive ID, index that... */
2392 if (msg->cm_fields['E'] != NULL) {
2393 index_message_by_euid(msg->cm_fields['E'], &CC->room, msgid);
2396 /* Free up the memory we may have allocated */
2397 if (msg != supplied_msg) {
2398 CtdlFreeMessage(msg);
2406 CtdlLogPrintf(CTDL_DEBUG, "CtdlSaveMsgPointerInRoom() skips repl checks\n");
2409 /* Submit this room for processing by hooks */
2410 PerformRoomHooks(&CC->room);
2412 /* Go back to the room we were in before we wandered here... */
2413 getroom(&CC->room, hold_rm);
2415 /* Bump the reference count for all messages which were merged */
2416 for (i=0; i<num_msgs_to_be_merged; ++i) {
2417 AdjRefCount(msgs_to_be_merged[i], +1);
2420 /* Free up memory... */
2421 if (msgs_to_be_merged != NULL) {
2422 free(msgs_to_be_merged);
2425 /* Return success. */
2431 * This is the same as CtdlSaveMsgPointersInRoom() but it only accepts
2434 int CtdlSaveMsgPointerInRoom(char *roomname, long msgid,
2435 int do_repl_check, struct CtdlMessage *supplied_msg)
2437 return CtdlSaveMsgPointersInRoom(roomname, &msgid, 1, do_repl_check, supplied_msg);
2444 * Message base operation to save a new message to the message store
2445 * (returns new message number)
2447 * This is the back end for CtdlSubmitMsg() and should not be directly
2448 * called by server-side modules.
2451 long send_message(struct CtdlMessage *msg) {
2459 /* Get a new message number */
2460 newmsgid = get_new_message_number();
2461 snprintf(msgidbuf, sizeof msgidbuf, "%010ld@%s", newmsgid, config.c_fqdn);
2463 /* Generate an ID if we don't have one already */
2464 if (msg->cm_fields['I']==NULL) {
2465 msg->cm_fields['I'] = strdup(msgidbuf);
2468 /* If the message is big, set its body aside for storage elsewhere */
2469 if (msg->cm_fields['M'] != NULL) {
2470 if (strlen(msg->cm_fields['M']) > BIGMSG) {
2472 holdM = msg->cm_fields['M'];
2473 msg->cm_fields['M'] = NULL;
2477 /* Serialize our data structure for storage in the database */
2478 serialize_message(&smr, msg);
2481 msg->cm_fields['M'] = holdM;
2485 cprintf("%d Unable to serialize message\n",
2486 ERROR + INTERNAL_ERROR);
2490 /* Write our little bundle of joy into the message base */
2491 if (cdb_store(CDB_MSGMAIN, &newmsgid, (int)sizeof(long),
2492 smr.ser, smr.len) < 0) {
2493 CtdlLogPrintf(CTDL_ERR, "Can't store message\n");
2497 cdb_store(CDB_BIGMSGS,
2507 /* Free the memory we used for the serialized message */
2510 /* Return the *local* message ID to the caller
2511 * (even if we're storing an incoming network message)
2519 * Serialize a struct CtdlMessage into the format used on disk and network.
2521 * This function loads up a "struct ser_ret" (defined in server.h) which
2522 * contains the length of the serialized message and a pointer to the
2523 * serialized message in memory. THE LATTER MUST BE FREED BY THE CALLER.
2525 void serialize_message(struct ser_ret *ret, /* return values */
2526 struct CtdlMessage *msg) /* unserialized msg */
2528 size_t wlen, fieldlen;
2530 static char *forder = FORDER;
2533 * Check for valid message format
2535 if (is_valid_message(msg) == 0) {
2536 CtdlLogPrintf(CTDL_ERR, "serialize_message() aborting due to invalid message\n");
2543 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL)
2544 ret->len = ret->len +
2545 strlen(msg->cm_fields[(int)forder[i]]) + 2;
2547 ret->ser = malloc(ret->len);
2548 if (ret->ser == NULL) {
2549 CtdlLogPrintf(CTDL_ERR, "serialize_message() malloc(%ld) failed: %s\n",
2550 (long)ret->len, strerror(errno));
2557 ret->ser[1] = msg->cm_anon_type;
2558 ret->ser[2] = msg->cm_format_type;
2561 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL) {
2562 fieldlen = strlen(msg->cm_fields[(int)forder[i]]);
2563 ret->ser[wlen++] = (char)forder[i];
2564 safestrncpy((char *)&ret->ser[wlen], msg->cm_fields[(int)forder[i]], fieldlen+1);
2565 wlen = wlen + fieldlen + 1;
2567 if (ret->len != wlen) CtdlLogPrintf(CTDL_ERR, "ERROR: len=%ld wlen=%ld\n",
2568 (long)ret->len, (long)wlen);
2575 * Serialize a struct CtdlMessage into the format used on disk and network.
2577 * This function loads up a "struct ser_ret" (defined in server.h) which
2578 * contains the length of the serialized message and a pointer to the
2579 * serialized message in memory. THE LATTER MUST BE FREED BY THE CALLER.
2581 void dump_message(struct CtdlMessage *msg, /* unserialized msg */
2582 long Siz) /* how many chars ? */
2586 static char *forder = FORDER;
2590 * Check for valid message format
2592 if (is_valid_message(msg) == 0) {
2593 CtdlLogPrintf(CTDL_ERR, "dump_message() aborting due to invalid message\n");
2597 buf = (char*) malloc (Siz + 1);
2601 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL) {
2602 snprintf (buf, Siz, " msg[%c] = %s ...\n", (char) forder[i],
2603 msg->cm_fields[(int)forder[i]]);
2604 client_write (buf, strlen(buf));
2613 * Check to see if any messages already exist in the current room which
2614 * carry the same Exclusive ID as this one. If any are found, delete them.
2616 void ReplicationChecks(struct CtdlMessage *msg) {
2617 long old_msgnum = (-1L);
2619 if (DoesThisRoomNeedEuidIndexing(&CC->room) == 0) return;
2621 CtdlLogPrintf(CTDL_DEBUG, "Performing replication checks in <%s>\n",
2624 /* No exclusive id? Don't do anything. */
2625 if (msg == NULL) return;
2626 if (msg->cm_fields['E'] == NULL) return;
2627 if (IsEmptyStr(msg->cm_fields['E'])) return;
2628 /*CtdlLogPrintf(CTDL_DEBUG, "Exclusive ID: <%s> for room <%s>\n",
2629 msg->cm_fields['E'], CC->room.QRname);*/
2631 old_msgnum = locate_message_by_euid(msg->cm_fields['E'], &CC->room);
2632 if (old_msgnum > 0L) {
2633 CtdlLogPrintf(CTDL_DEBUG, "ReplicationChecks() replacing message %ld\n", old_msgnum);
2634 CtdlDeleteMessages(CC->room.QRname, &old_msgnum, 1, "");
2641 * Save a message to disk and submit it into the delivery system.
2643 long CtdlSubmitMsg(struct CtdlMessage *msg, /* message to save */
2644 struct recptypes *recps, /* recipients (if mail) */
2645 char *force, /* force a particular room? */
2646 int flags /* should the bessage be exported clean? */
2648 char submit_filename[128];
2649 char generated_timestamp[32];
2650 char hold_rm[ROOMNAMELEN];
2651 char actual_rm[ROOMNAMELEN];
2652 char force_room[ROOMNAMELEN];
2653 char content_type[SIZ]; /* We have to learn this */
2654 char recipient[SIZ];
2657 struct ctdluser userbuf;
2659 struct MetaData smi;
2660 FILE *network_fp = NULL;
2661 static int seqnum = 1;
2662 struct CtdlMessage *imsg = NULL;
2664 size_t instr_alloc = 0;
2666 char *hold_R, *hold_D;
2667 char *collected_addresses = NULL;
2668 struct addresses_to_be_filed *aptr = NULL;
2669 char *saved_rfc822_version = NULL;
2670 int qualified_for_journaling = 0;
2671 struct CitContext *CCC = CC; /* CachedCitContext - performance boost */
2672 char bounce_to[1024] = "";
2676 CtdlLogPrintf(CTDL_DEBUG, "CtdlSubmitMsg() called\n");
2677 if (is_valid_message(msg) == 0) return(-1); /* self check */
2679 /* If this message has no timestamp, we take the liberty of
2680 * giving it one, right now.
2682 if (msg->cm_fields['T'] == NULL) {
2683 snprintf(generated_timestamp, sizeof generated_timestamp, "%ld", (long)time(NULL));
2684 msg->cm_fields['T'] = strdup(generated_timestamp);
2687 /* If this message has no path, we generate one.
2689 if (msg->cm_fields['P'] == NULL) {
2690 if (msg->cm_fields['A'] != NULL) {
2691 msg->cm_fields['P'] = strdup(msg->cm_fields['A']);
2692 for (a=0; !IsEmptyStr(&msg->cm_fields['P'][a]); ++a) {
2693 if (isspace(msg->cm_fields['P'][a])) {
2694 msg->cm_fields['P'][a] = ' ';
2699 msg->cm_fields['P'] = strdup("unknown");
2703 if (force == NULL) {
2704 strcpy(force_room, "");
2707 strcpy(force_room, force);
2710 /* Learn about what's inside, because it's what's inside that counts */
2711 if (msg->cm_fields['M'] == NULL) {
2712 CtdlLogPrintf(CTDL_ERR, "ERROR: attempt to save message with NULL body\n");
2716 switch (msg->cm_format_type) {
2718 strcpy(content_type, "text/x-citadel-variformat");
2721 strcpy(content_type, "text/plain");
2724 strcpy(content_type, "text/plain");
2725 mptr = bmstrcasestr(msg->cm_fields['M'], "Content-type:");
2728 safestrncpy(content_type, &mptr[13], sizeof content_type);
2729 striplt(content_type);
2730 aptr = content_type;
2731 while (!IsEmptyStr(aptr)) {
2743 /* Goto the correct room */
2744 CtdlLogPrintf(CTDL_DEBUG, "Selected room %s\n", (recps) ? CCC->room.QRname : SENTITEMS);
2745 strcpy(hold_rm, CCC->room.QRname);
2746 strcpy(actual_rm, CCC->room.QRname);
2747 if (recps != NULL) {
2748 strcpy(actual_rm, SENTITEMS);
2751 /* If the user is a twit, move to the twit room for posting */
2753 if (CCC->user.axlevel == 2) {
2754 strcpy(hold_rm, actual_rm);
2755 strcpy(actual_rm, config.c_twitroom);
2756 CtdlLogPrintf(CTDL_DEBUG, "Diverting to twit room\n");
2760 /* ...or if this message is destined for Aide> then go there. */
2761 if (!IsEmptyStr(force_room)) {
2762 strcpy(actual_rm, force_room);
2765 CtdlLogPrintf(CTDL_DEBUG, "Final selection: %s\n", actual_rm);
2766 if (strcasecmp(actual_rm, CCC->room.QRname)) {
2767 /* getroom(&CCC->room, actual_rm); */
2768 usergoto(actual_rm, 0, 1, NULL, NULL);
2772 * If this message has no O (room) field, generate one.
2774 if (msg->cm_fields['O'] == NULL) {
2775 msg->cm_fields['O'] = strdup(CCC->room.QRname);
2778 /* Perform "before save" hooks (aborting if any return nonzero) */
2779 CtdlLogPrintf(CTDL_DEBUG, "Performing before-save hooks\n");
2780 if (PerformMessageHooks(msg, EVT_BEFORESAVE) > 0) return(-3);
2783 * If this message has an Exclusive ID, and the room is replication
2784 * checking enabled, then do replication checks.
2786 if (DoesThisRoomNeedEuidIndexing(&CCC->room)) {
2787 ReplicationChecks(msg);
2790 /* Save it to disk */
2791 CtdlLogPrintf(CTDL_DEBUG, "Saving to disk\n");
2792 newmsgid = send_message(msg);
2793 if (newmsgid <= 0L) return(-5);
2795 /* Write a supplemental message info record. This doesn't have to
2796 * be a critical section because nobody else knows about this message
2799 CtdlLogPrintf(CTDL_DEBUG, "Creating MetaData record\n");
2800 memset(&smi, 0, sizeof(struct MetaData));
2801 smi.meta_msgnum = newmsgid;
2802 smi.meta_refcount = 0;
2803 safestrncpy(smi.meta_content_type, content_type,
2804 sizeof smi.meta_content_type);
2807 * Measure how big this message will be when rendered as RFC822.
2808 * We do this for two reasons:
2809 * 1. We need the RFC822 length for the new metadata record, so the
2810 * POP and IMAP services don't have to calculate message lengths
2811 * while the user is waiting (multiplied by potentially hundreds
2812 * or thousands of messages).
2813 * 2. If journaling is enabled, we will need an RFC822 version of the
2814 * message to attach to the journalized copy.
2816 if (CCC->redirect_buffer != NULL) {
2817 CtdlLogPrintf(CTDL_ALERT, "CCC->redirect_buffer is not NULL during message submission!\n");
2820 CCC->redirect_buffer = malloc(SIZ);
2821 CCC->redirect_len = 0;
2822 CCC->redirect_alloc = SIZ;
2823 CtdlOutputPreLoadedMsg(msg, MT_RFC822, HEADERS_ALL, 0, 1, QP_EADDR);
2824 smi.meta_rfc822_length = CCC->redirect_len;
2825 saved_rfc822_version = CCC->redirect_buffer;
2826 CCC->redirect_buffer = NULL;
2827 CCC->redirect_len = 0;
2828 CCC->redirect_alloc = 0;
2832 /* Now figure out where to store the pointers */
2833 CtdlLogPrintf(CTDL_DEBUG, "Storing pointers\n");
2835 /* If this is being done by the networker delivering a private
2836 * message, we want to BYPASS saving the sender's copy (because there
2837 * is no local sender; it would otherwise go to the Trashcan).
2839 if ((!CCC->internal_pgm) || (recps == NULL)) {
2840 if (CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 1, msg) != 0) {
2841 CtdlLogPrintf(CTDL_ERR, "ERROR saving message pointer!\n");
2842 CtdlSaveMsgPointerInRoom(config.c_aideroom, newmsgid, 0, msg);
2846 /* For internet mail, drop a copy in the outbound queue room */
2847 if ((recps != NULL) && (recps->num_internet > 0)) {
2848 CtdlSaveMsgPointerInRoom(SMTP_SPOOLOUT_ROOM, newmsgid, 0, msg);
2851 /* If other rooms are specified, drop them there too. */
2852 if ((recps != NULL) && (recps->num_room > 0))
2853 for (i=0; i<num_tokens(recps->recp_room, '|'); ++i) {
2854 extract_token(recipient, recps->recp_room, i,
2855 '|', sizeof recipient);
2856 CtdlLogPrintf(CTDL_DEBUG, "Delivering to room <%s>\n", recipient);
2857 CtdlSaveMsgPointerInRoom(recipient, newmsgid, 0, msg);
2860 /* Bump this user's messages posted counter. */
2861 CtdlLogPrintf(CTDL_DEBUG, "Updating user\n");
2862 lgetuser(&CCC->user, CCC->curr_user);
2863 CCC->user.posted = CCC->user.posted + 1;
2864 lputuser(&CCC->user);
2866 /* Decide where bounces need to be delivered */
2867 if ((recps != NULL) && (recps->bounce_to != NULL)) {
2868 safestrncpy(bounce_to, recps->bounce_to, sizeof bounce_to);
2870 else if (CCC->logged_in) {
2871 snprintf(bounce_to, sizeof bounce_to, "%s@%s", CCC->user.fullname, config.c_nodename);
2874 snprintf(bounce_to, sizeof bounce_to, "%s@%s", msg->cm_fields['A'], msg->cm_fields['N']);
2877 /* If this is private, local mail, make a copy in the
2878 * recipient's mailbox and bump the reference count.
2880 if ((recps != NULL) && (recps->num_local > 0))
2881 for (i=0; i<num_tokens(recps->recp_local, '|'); ++i) {
2882 extract_token(recipient, recps->recp_local, i,
2883 '|', sizeof recipient);
2884 CtdlLogPrintf(CTDL_DEBUG, "Delivering private local mail to <%s>\n",
2886 if (getuser(&userbuf, recipient) == 0) {
2887 // Add a flag so the Funambol module knows its mail
2888 msg->cm_fields['W'] = strdup(recipient);
2889 MailboxName(actual_rm, sizeof actual_rm, &userbuf, MAILROOM);
2890 CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 0, msg);
2891 BumpNewMailCounter(userbuf.usernum);
2892 if (!IsEmptyStr(config.c_funambol_host) || !IsEmptyStr(config.c_pager_program)) {
2893 /* Generate a instruction message for the Funambol notification
2894 * server, in the same style as the SMTP queue
2897 instr = malloc(instr_alloc);
2898 snprintf(instr, instr_alloc,
2899 "Content-type: %s\n\nmsgid|%ld\nsubmitted|%ld\n"
2901 SPOOLMIME, newmsgid, (long)time(NULL),
2905 imsg = malloc(sizeof(struct CtdlMessage));
2906 memset(imsg, 0, sizeof(struct CtdlMessage));
2907 imsg->cm_magic = CTDLMESSAGE_MAGIC;
2908 imsg->cm_anon_type = MES_NORMAL;
2909 imsg->cm_format_type = FMT_RFC822;
2910 imsg->cm_fields['A'] = strdup("Citadel");
2911 imsg->cm_fields['J'] = strdup("do not journal");
2912 imsg->cm_fields['M'] = instr; /* imsg owns this memory now */
2913 imsg->cm_fields['W'] = strdup(recipient);
2914 CtdlSubmitMsg(imsg, NULL, FNBL_QUEUE_ROOM, 0);
2915 CtdlFreeMessage(imsg);
2919 CtdlLogPrintf(CTDL_DEBUG, "No user <%s>\n", recipient);
2920 CtdlSaveMsgPointerInRoom(config.c_aideroom,
2925 /* Perform "after save" hooks */
2926 CtdlLogPrintf(CTDL_DEBUG, "Performing after-save hooks\n");
2927 PerformMessageHooks(msg, EVT_AFTERSAVE);
2929 /* For IGnet mail, we have to save a new copy into the spooler for
2930 * each recipient, with the R and D fields set to the recipient and
2931 * destination-node. This has two ugly side effects: all other
2932 * recipients end up being unlisted in this recipient's copy of the
2933 * message, and it has to deliver multiple messages to the same
2934 * node. We'll revisit this again in a year or so when everyone has
2935 * a network spool receiver that can handle the new style messages.
2937 if ((recps != NULL) && (recps->num_ignet > 0))
2938 for (i=0; i<num_tokens(recps->recp_ignet, '|'); ++i) {
2939 extract_token(recipient, recps->recp_ignet, i,
2940 '|', sizeof recipient);
2942 hold_R = msg->cm_fields['R'];
2943 hold_D = msg->cm_fields['D'];
2944 msg->cm_fields['R'] = malloc(SIZ);
2945 msg->cm_fields['D'] = malloc(128);
2946 extract_token(msg->cm_fields['R'], recipient, 0, '@', SIZ);
2947 extract_token(msg->cm_fields['D'], recipient, 1, '@', 128);
2949 serialize_message(&smr, msg);
2951 snprintf(submit_filename, sizeof submit_filename,
2952 "%s/netmail.%04lx.%04x.%04x",
2954 (long) getpid(), CCC->cs_pid, ++seqnum);
2955 network_fp = fopen(submit_filename, "wb+");
2956 if (network_fp != NULL) {
2957 rv = fwrite(smr.ser, smr.len, 1, network_fp);
2963 free(msg->cm_fields['R']);
2964 free(msg->cm_fields['D']);
2965 msg->cm_fields['R'] = hold_R;
2966 msg->cm_fields['D'] = hold_D;
2969 /* Go back to the room we started from */
2970 CtdlLogPrintf(CTDL_DEBUG, "Returning to original room %s\n", hold_rm);
2971 if (strcasecmp(hold_rm, CCC->room.QRname))
2972 usergoto(hold_rm, 0, 1, NULL, NULL);
2974 /* For internet mail, generate delivery instructions.
2975 * Yes, this is recursive. Deal with it. Infinite recursion does
2976 * not happen because the delivery instructions message does not
2977 * contain a recipient.
2979 if ((recps != NULL) && (recps->num_internet > 0)) {
2980 CtdlLogPrintf(CTDL_DEBUG, "Generating delivery instructions\n");
2982 instr = malloc(instr_alloc);
2983 snprintf(instr, instr_alloc,
2984 "Content-type: %s\n\nmsgid|%ld\nsubmitted|%ld\n"
2986 SPOOLMIME, newmsgid, (long)time(NULL),
2990 if (recps->envelope_from != NULL) {
2991 tmp = strlen(instr);
2992 snprintf(&instr[tmp], instr_alloc-tmp, "envelope_from|%s\n", recps->envelope_from);
2995 for (i=0; i<num_tokens(recps->recp_internet, '|'); ++i) {
2996 tmp = strlen(instr);
2997 extract_token(recipient, recps->recp_internet, i, '|', sizeof recipient);
2998 if ((tmp + strlen(recipient) + 32) > instr_alloc) {
2999 instr_alloc = instr_alloc * 2;
3000 instr = realloc(instr, instr_alloc);
3002 snprintf(&instr[tmp], instr_alloc - tmp, "remote|%s|0||\n", recipient);
3005 imsg = malloc(sizeof(struct CtdlMessage));
3006 memset(imsg, 0, sizeof(struct CtdlMessage));
3007 imsg->cm_magic = CTDLMESSAGE_MAGIC;
3008 imsg->cm_anon_type = MES_NORMAL;
3009 imsg->cm_format_type = FMT_RFC822;
3010 imsg->cm_fields['A'] = strdup("Citadel");
3011 imsg->cm_fields['J'] = strdup("do not journal");
3012 imsg->cm_fields['M'] = instr; /* imsg owns this memory now */
3013 CtdlSubmitMsg(imsg, NULL, SMTP_SPOOLOUT_ROOM, QP_EADDR);
3014 CtdlFreeMessage(imsg);
3018 * Any addresses to harvest for someone's address book?
3020 if ( (CCC->logged_in) && (recps != NULL) ) {
3021 collected_addresses = harvest_collected_addresses(msg);
3024 if (collected_addresses != NULL) {
3025 aptr = (struct addresses_to_be_filed *)
3026 malloc(sizeof(struct addresses_to_be_filed));
3027 MailboxName(actual_rm, sizeof actual_rm,
3028 &CCC->user, USERCONTACTSROOM);
3029 aptr->roomname = strdup(actual_rm);
3030 aptr->collected_addresses = collected_addresses;
3031 begin_critical_section(S_ATBF);
3034 end_critical_section(S_ATBF);
3038 * Determine whether this message qualifies for journaling.
3040 if (msg->cm_fields['J'] != NULL) {
3041 qualified_for_journaling = 0;
3044 if (recps == NULL) {
3045 qualified_for_journaling = config.c_journal_pubmsgs;
3047 else if (recps->num_local + recps->num_ignet + recps->num_internet > 0) {
3048 qualified_for_journaling = config.c_journal_email;
3051 qualified_for_journaling = config.c_journal_pubmsgs;
3056 * Do we have to perform journaling? If so, hand off the saved
3057 * RFC822 version will be handed off to the journaler for background
3058 * submit. Otherwise, we have to free the memory ourselves.
3060 if (saved_rfc822_version != NULL) {
3061 if (qualified_for_journaling) {
3062 JournalBackgroundSubmit(msg, saved_rfc822_version, recps);
3065 free(saved_rfc822_version);
3078 * Convenience function for generating small administrative messages.
3080 void quickie_message(const char *from, const char *fromaddr, char *to, char *room, const char *text,
3081 int format_type, const char *subject)
3083 struct CtdlMessage *msg;
3084 struct recptypes *recp = NULL;
3086 msg = malloc(sizeof(struct CtdlMessage));
3087 memset(msg, 0, sizeof(struct CtdlMessage));
3088 msg->cm_magic = CTDLMESSAGE_MAGIC;
3089 msg->cm_anon_type = MES_NORMAL;
3090 msg->cm_format_type = format_type;
3093 msg->cm_fields['A'] = strdup(from);
3095 else if (fromaddr != NULL) {
3096 msg->cm_fields['A'] = strdup(fromaddr);
3097 if (strchr(msg->cm_fields['A'], '@')) {
3098 *strchr(msg->cm_fields['A'], '@') = 0;
3102 msg->cm_fields['A'] = strdup("Citadel");
3105 if (fromaddr != NULL) msg->cm_fields['F'] = strdup(fromaddr);
3106 if (room != NULL) msg->cm_fields['O'] = strdup(room);
3107 msg->cm_fields['N'] = strdup(NODENAME);
3109 msg->cm_fields['R'] = strdup(to);
3110 recp = validate_recipients(to, NULL, 0);
3112 if (subject != NULL) {
3113 msg->cm_fields['U'] = strdup(subject);
3115 msg->cm_fields['M'] = strdup(text);
3117 CtdlSubmitMsg(msg, recp, room, 0);
3118 CtdlFreeMessage(msg);
3119 if (recp != NULL) free_recipients(recp);
3125 * Back end function used by CtdlMakeMessage() and similar functions
3127 char *CtdlReadMessageBody(char *terminator, /* token signalling EOT */
3128 size_t maxlen, /* maximum message length */
3129 char *exist, /* if non-null, append to it;
3130 exist is ALWAYS freed */
3131 int crlf, /* CRLF newlines instead of LF */
3132 int sock /* socket handle or 0 for this session's client socket */
3136 size_t message_len = 0;
3137 size_t buffer_len = 0;
3144 if (exist == NULL) {
3151 message_len = strlen(exist);
3152 buffer_len = message_len + 4096;
3153 m = realloc(exist, buffer_len);
3160 /* Do we need to change leading ".." to "." for SMTP escaping? */
3161 if (!strcmp(terminator, ".")) {
3165 /* flush the input if we have nowhere to store it */
3170 /* read in the lines of message text one by one */
3173 if (sock_getln(sock, buf, (sizeof buf - 3)) < 0) finished = 1;
3176 if (client_getln(buf, (sizeof buf - 3)) < 1) finished = 1;
3178 if (!strcmp(buf, terminator)) finished = 1;
3180 strcat(buf, "\r\n");
3186 /* Unescape SMTP-style input of two dots at the beginning of the line */
3188 if (!strncmp(buf, "..", 2)) {
3189 strcpy(buf, &buf[1]);
3193 if ( (!flushing) && (!finished) ) {
3194 /* Measure the line */
3195 linelen = strlen(buf);
3197 /* augment the buffer if we have to */
3198 if ((message_len + linelen) >= buffer_len) {
3199 ptr = realloc(m, (buffer_len * 2) );
3200 if (ptr == NULL) { /* flush if can't allocate */
3203 buffer_len = (buffer_len * 2);
3205 CtdlLogPrintf(CTDL_DEBUG, "buffer_len is now %ld\n", (long)buffer_len);
3209 /* Add the new line to the buffer. NOTE: this loop must avoid
3210 * using functions like strcat() and strlen() because they
3211 * traverse the entire buffer upon every call, and doing that
3212 * for a multi-megabyte message slows it down beyond usability.
3214 strcpy(&m[message_len], buf);
3215 message_len += linelen;
3218 /* if we've hit the max msg length, flush the rest */
3219 if (message_len >= maxlen) flushing = 1;
3221 } while (!finished);
3229 * Build a binary message to be saved on disk.
3230 * (NOTE: if you supply 'preformatted_text', the buffer you give it
3231 * will become part of the message. This means you are no longer
3232 * responsible for managing that memory -- it will be freed along with
3233 * the rest of the fields when CtdlFreeMessage() is called.)
3236 struct CtdlMessage *CtdlMakeMessage(
3237 struct ctdluser *author, /* author's user structure */
3238 char *recipient, /* NULL if it's not mail */
3239 char *recp_cc, /* NULL if it's not mail */
3240 char *room, /* room where it's going */
3241 int type, /* see MES_ types in header file */
3242 int format_type, /* variformat, plain text, MIME... */
3243 char *fake_name, /* who we're masquerading as */
3244 char *my_email, /* which of my email addresses to use (empty is ok) */
3245 char *subject, /* Subject (optional) */
3246 char *supplied_euid, /* ...or NULL if this is irrelevant */
3247 char *preformatted_text, /* ...or NULL to read text from client */
3248 char *references /* Thread references */
3250 char dest_node[256];
3252 struct CtdlMessage *msg;
3254 msg = malloc(sizeof(struct CtdlMessage));
3255 memset(msg, 0, sizeof(struct CtdlMessage));
3256 msg->cm_magic = CTDLMESSAGE_MAGIC;
3257 msg->cm_anon_type = type;
3258 msg->cm_format_type = format_type;
3260 /* Don't confuse the poor folks if it's not routed mail. */
3261 strcpy(dest_node, "");
3263 if (recipient != NULL) striplt(recipient);
3264 if (recp_cc != NULL) striplt(recp_cc);
3266 /* Path or Return-Path */
3267 if (my_email == NULL) my_email = "";
3269 if (!IsEmptyStr(my_email)) {
3270 msg->cm_fields['P'] = strdup(my_email);
3273 snprintf(buf, sizeof buf, "%s", author->fullname);
3274 msg->cm_fields['P'] = strdup(buf);
3276 convert_spaces_to_underscores(msg->cm_fields['P']);
3278 snprintf(buf, sizeof buf, "%ld", (long)time(NULL)); /* timestamp */
3279 msg->cm_fields['T'] = strdup(buf);
3281 if ((fake_name != NULL) && (fake_name[0])) { /* author */
3282 msg->cm_fields['A'] = strdup(fake_name);
3285 msg->cm_fields['A'] = strdup(author->fullname);
3288 if (CC->room.QRflags & QR_MAILBOX) { /* room */
3289 msg->cm_fields['O'] = strdup(&CC->room.QRname[11]);
3292 msg->cm_fields['O'] = strdup(CC->room.QRname);
3295 msg->cm_fields['N'] = strdup(NODENAME); /* nodename */
3296 msg->cm_fields['H'] = strdup(HUMANNODE); /* hnodename */
3298 if ((recipient != NULL) && (recipient[0] != 0)) {
3299 msg->cm_fields['R'] = strdup(recipient);
3301 if ((recp_cc != NULL) && (recp_cc[0] != 0)) {
3302 msg->cm_fields['Y'] = strdup(recp_cc);
3304 if (dest_node[0] != 0) {
3305 msg->cm_fields['D'] = strdup(dest_node);
3308 if (!IsEmptyStr(my_email)) {
3309 msg->cm_fields['F'] = strdup(my_email);
3311 else if ( (author == &CC->user) && (!IsEmptyStr(CC->cs_inet_email)) ) {
3312 msg->cm_fields['F'] = strdup(CC->cs_inet_email);
3315 if (subject != NULL) {
3318 length = strlen(subject);
3324 while ((subject[i] != '\0') &&
3325 (IsAscii = isascii(subject[i]) != 0 ))
3328 msg->cm_fields['U'] = strdup(subject);
3329 else /* ok, we've got utf8 in the string. */
3331 msg->cm_fields['U'] = rfc2047encode(subject, length);
3337 if (supplied_euid != NULL) {
3338 msg->cm_fields['E'] = strdup(supplied_euid);
3341 if (references != NULL) {
3342 if (!IsEmptyStr(references)) {
3343 msg->cm_fields['W'] = strdup(references);
3347 if (preformatted_text != NULL) {
3348 msg->cm_fields['M'] = preformatted_text;
3351 msg->cm_fields['M'] = CtdlReadMessageBody("000", config.c_maxmsglen, NULL, 0, 0);
3359 * Check to see whether we have permission to post a message in the current
3360 * room. Returns a *CITADEL ERROR CODE* and puts a message in errmsgbuf, or
3361 * returns 0 on success.
3363 int CtdlDoIHavePermissionToPostInThisRoom(char *errmsgbuf,
3365 const char* RemoteIdentifier,
3369 if (!(CC->logged_in) &&
3370 (PostPublic == POST_LOGGED_IN)) {
3371 snprintf(errmsgbuf, n, "Not logged in.");
3372 return (ERROR + NOT_LOGGED_IN);
3374 else if (PostPublic == CHECK_EXISTANCE) {
3375 return (0); // We're Evaling whether a recipient exists
3377 else if (!(CC->logged_in)) {
3379 if ((CC->room.QRflags & QR_READONLY)) {
3380 snprintf(errmsgbuf, n, "Not logged in.");
3381 return (ERROR + NOT_LOGGED_IN);
3383 if (CC->room.QRflags2 & QR2_MODERATED) {
3384 snprintf(errmsgbuf, n, "Not logged in Moderation feature not yet implemented!");
3385 return (ERROR + NOT_LOGGED_IN);
3387 if ((PostPublic!=POST_LMTP) &&(CC->room.QRflags2 & QR2_SMTP_PUBLIC) == 0) {
3392 if (RemoteIdentifier == NULL)
3394 snprintf(errmsgbuf, n, "Need sender to permit access.");
3395 return (ERROR + USERNAME_REQUIRED);
3398 assoc_file_name(filename, sizeof filename, &CC->room, ctdl_netcfg_dir);
3399 begin_critical_section(S_NETCONFIGS);
3400 if (!read_spoolcontrol_file(&sc, filename))
3402 end_critical_section(S_NETCONFIGS);
3403 snprintf(errmsgbuf, n,
3404 "This mailing list only accepts posts from subscribers.");
3405 return (ERROR + NO_SUCH_USER);
3407 end_critical_section(S_NETCONFIGS);
3408 found = is_recipient (sc, RemoteIdentifier);
3409 free_spoolcontrol_struct(&sc);
3414 snprintf(errmsgbuf, n,
3415 "This mailing list only accepts posts from subscribers.");
3416 return (ERROR + NO_SUCH_USER);
3423 if ((CC->user.axlevel < 2)
3424 && ((CC->room.QRflags & QR_MAILBOX) == 0)) {
3425 snprintf(errmsgbuf, n, "Need to be validated to enter "
3426 "(except in %s> to sysop)", MAILROOM);
3427 return (ERROR + HIGHER_ACCESS_REQUIRED);
3430 CtdlRoomAccess(&CC->room, &CC->user, &ra, NULL);
3431 if (!(ra & UA_POSTALLOWED)) {
3432 snprintf(errmsgbuf, n, "Higher access is required to post in this room.");
3433 return (ERROR + HIGHER_ACCESS_REQUIRED);
3436 strcpy(errmsgbuf, "Ok");
3442 * Check to see if the specified user has Internet mail permission
3443 * (returns nonzero if permission is granted)
3445 int CtdlCheckInternetMailPermission(struct ctdluser *who) {
3447 /* Do not allow twits to send Internet mail */
3448 if (who->axlevel <= 2) return(0);
3450 /* Globally enabled? */
3451 if (config.c_restrict == 0) return(1);
3453 /* User flagged ok? */
3454 if (who->flags & US_INTERNET) return(2);
3456 /* Aide level access? */
3457 if (who->axlevel >= 6) return(3);
3459 /* No mail for you! */
3465 * Validate recipients, count delivery types and errors, and handle aliasing
3466 * FIXME check for dupes!!!!!
3468 * Returns 0 if all addresses are ok, ret->num_error = -1 if no addresses
3469 * were specified, or the number of addresses found invalid.
3471 * Caller needs to free the result using free_recipients()
3473 struct recptypes *validate_recipients(char *supplied_recipients,
3474 const char *RemoteIdentifier,
3476 struct recptypes *ret;
3477 char *recipients = NULL;
3478 char this_recp[256];
3479 char this_recp_cooked[256];
3485 struct ctdluser tempUS;
3486 struct ctdlroom tempQR;
3487 struct ctdlroom tempQR2;
3493 ret = (struct recptypes *) malloc(sizeof(struct recptypes));
3494 if (ret == NULL) return(NULL);
3496 /* Set all strings to null and numeric values to zero */
3497 memset(ret, 0, sizeof(struct recptypes));
3499 if (supplied_recipients == NULL) {
3500 recipients = strdup("");
3503 recipients = strdup(supplied_recipients);
3506 /* Allocate some memory. Yes, this allocates 500% more memory than we will
3507 * actually need, but it's healthier for the heap than doing lots of tiny
3508 * realloc() calls instead.
3511 ret->errormsg = malloc(strlen(recipients) + 1024);
3512 ret->recp_local = malloc(strlen(recipients) + 1024);
3513 ret->recp_internet = malloc(strlen(recipients) + 1024);
3514 ret->recp_ignet = malloc(strlen(recipients) + 1024);
3515 ret->recp_room = malloc(strlen(recipients) + 1024);
3516 ret->display_recp = malloc(strlen(recipients) + 1024);
3518 ret->errormsg[0] = 0;
3519 ret->recp_local[0] = 0;
3520 ret->recp_internet[0] = 0;
3521 ret->recp_ignet[0] = 0;
3522 ret->recp_room[0] = 0;
3523 ret->display_recp[0] = 0;
3525 ret->recptypes_magic = RECPTYPES_MAGIC;
3527 /* Change all valid separator characters to commas */
3528 for (i=0; !IsEmptyStr(&recipients[i]); ++i) {
3529 if ((recipients[i] == ';') || (recipients[i] == '|')) {
3530 recipients[i] = ',';
3534 /* Now start extracting recipients... */
3536 while (!IsEmptyStr(recipients)) {
3538 for (i=0; i<=strlen(recipients); ++i) {
3539 if (recipients[i] == '\"') in_quotes = 1 - in_quotes;
3540 if ( ( (recipients[i] == ',') && (!in_quotes) ) || (recipients[i] == 0) ) {
3541 safestrncpy(this_recp, recipients, i+1);
3543 if (recipients[i] == ',') {
3544 strcpy(recipients, &recipients[i+1]);
3547 strcpy(recipients, "");
3554 if (IsEmptyStr(this_recp))
3556 CtdlLogPrintf(CTDL_DEBUG, "Evaluating recipient #%d: %s\n", num_recps, this_recp);
3558 mailtype = alias(this_recp);
3559 mailtype = alias(this_recp);
3560 mailtype = alias(this_recp);
3562 for (j=0; !IsEmptyStr(&this_recp[j]); ++j) {
3563 if (this_recp[j]=='_') {
3564 this_recp_cooked[j] = ' ';
3567 this_recp_cooked[j] = this_recp[j];
3570 this_recp_cooked[j] = '\0';
3575 if (!strcasecmp(this_recp, "sysop")) {
3577 strcpy(this_recp, config.c_aideroom);
3578 if (!IsEmptyStr(ret->recp_room)) {
3579 strcat(ret->recp_room, "|");
3581 strcat(ret->recp_room, this_recp);
3583 else if ( (!strncasecmp(this_recp, "room_", 5))
3584 && (!getroom(&tempQR, &this_recp_cooked[5])) ) {
3586 /* Save room so we can restore it later */
3590 /* Check permissions to send mail to this room */
3591 err = CtdlDoIHavePermissionToPostInThisRoom(errmsg,
3603 if (!IsEmptyStr(ret->recp_room)) {
3604 strcat(ret->recp_room, "|");
3606 strcat(ret->recp_room, &this_recp_cooked[5]);
3609 /* Restore room in case something needs it */
3613 else if (getuser(&tempUS, this_recp) == 0) {
3615 strcpy(this_recp, tempUS.fullname);
3616 if (!IsEmptyStr(ret->recp_local)) {
3617 strcat(ret->recp_local, "|");
3619 strcat(ret->recp_local, this_recp);
3621 else if (getuser(&tempUS, this_recp_cooked) == 0) {
3623 strcpy(this_recp, tempUS.fullname);
3624 if (!IsEmptyStr(ret->recp_local)) {
3625 strcat(ret->recp_local, "|");
3627 strcat(ret->recp_local, this_recp);
3635 /* Yes, you're reading this correctly: if the target
3636 * domain points back to the local system or an attached
3637 * Citadel directory, the address is invalid. That's
3638 * because if the address were valid, we would have
3639 * already translated it to a local address by now.
3641 if (IsDirectory(this_recp, 0)) {
3646 ++ret->num_internet;
3647 if (!IsEmptyStr(ret->recp_internet)) {
3648 strcat(ret->recp_internet, "|");
3650 strcat(ret->recp_internet, this_recp);
3655 if (!IsEmptyStr(ret->recp_ignet)) {
3656 strcat(ret->recp_ignet, "|");
3658 strcat(ret->recp_ignet, this_recp);
3666 if (IsEmptyStr(errmsg)) {
3667 snprintf(append, sizeof append, "Invalid recipient: %s", this_recp);
3670 snprintf(append, sizeof append, "%s", errmsg);
3672 if ( (strlen(ret->errormsg) + strlen(append) + 3) < SIZ) {
3673 if (!IsEmptyStr(ret->errormsg)) {
3674 strcat(ret->errormsg, "; ");
3676 strcat(ret->errormsg, append);
3680 if (IsEmptyStr(ret->display_recp)) {
3681 strcpy(append, this_recp);
3684 snprintf(append, sizeof append, ", %s", this_recp);
3686 if ( (strlen(ret->display_recp)+strlen(append)) < SIZ) {
3687 strcat(ret->display_recp, append);
3692 if ((ret->num_local + ret->num_internet + ret->num_ignet +
3693 ret->num_room + ret->num_error) == 0) {
3694 ret->num_error = (-1);
3695 strcpy(ret->errormsg, "No recipients specified.");
3698 CtdlLogPrintf(CTDL_DEBUG, "validate_recipients()\n");
3699 CtdlLogPrintf(CTDL_DEBUG, " local: %d <%s>\n", ret->num_local, ret->recp_local);
3700 CtdlLogPrintf(CTDL_DEBUG, " room: %d <%s>\n", ret->num_room, ret->recp_room);
3701 CtdlLogPrintf(CTDL_DEBUG, " inet: %d <%s>\n", ret->num_internet, ret->recp_internet);
3702 CtdlLogPrintf(CTDL_DEBUG, " ignet: %d <%s>\n", ret->num_ignet, ret->recp_ignet);
3703 CtdlLogPrintf(CTDL_DEBUG, " error: %d <%s>\n", ret->num_error, ret->errormsg);
3711 * Destructor for struct recptypes
3713 void free_recipients(struct recptypes *valid) {
3715 if (valid == NULL) {
3719 if (valid->recptypes_magic != RECPTYPES_MAGIC) {
3720 CtdlLogPrintf(CTDL_EMERG, "Attempt to call free_recipients() on some other data type!\n");
3724 if (valid->errormsg != NULL) free(valid->errormsg);
3725 if (valid->recp_local != NULL) free(valid->recp_local);
3726 if (valid->recp_internet != NULL) free(valid->recp_internet);
3727 if (valid->recp_ignet != NULL) free(valid->recp_ignet);
3728 if (valid->recp_room != NULL) free(valid->recp_room);
3729 if (valid->display_recp != NULL) free(valid->display_recp);
3730 if (valid->bounce_to != NULL) free(valid->bounce_to);
3731 if (valid->envelope_from != NULL) free(valid->envelope_from);
3738 * message entry - mode 0 (normal)
3740 void cmd_ent0(char *entargs)
3746 char supplied_euid[128];
3748 int format_type = 0;
3749 char newusername[256];
3750 char newuseremail[256];
3751 struct CtdlMessage *msg;
3755 struct recptypes *valid = NULL;
3756 struct recptypes *valid_to = NULL;
3757 struct recptypes *valid_cc = NULL;
3758 struct recptypes *valid_bcc = NULL;
3760 int subject_required = 0;
3765 int newuseremail_ok = 0;
3766 char references[SIZ];
3771 post = extract_int(entargs, 0);
3772 extract_token(recp, entargs, 1, '|', sizeof recp);
3773 anon_flag = extract_int(entargs, 2);
3774 format_type = extract_int(entargs, 3);
3775 extract_token(subject, entargs, 4, '|', sizeof subject);
3776 extract_token(newusername, entargs, 5, '|', sizeof newusername);
3777 do_confirm = extract_int(entargs, 6);
3778 extract_token(cc, entargs, 7, '|', sizeof cc);
3779 extract_token(bcc, entargs, 8, '|', sizeof bcc);
3780 switch(CC->room.QRdefaultview) {
3783 extract_token(supplied_euid, entargs, 9, '|', sizeof supplied_euid);
3786 supplied_euid[0] = 0;
3789 extract_token(newuseremail, entargs, 10, '|', sizeof newuseremail);
3790 extract_token(references, entargs, 11, '|', sizeof references);
3791 for (ptr=references; *ptr != 0; ++ptr) {
3792 if (*ptr == '!') *ptr = '|';
3795 /* first check to make sure the request is valid. */
3797 err = CtdlDoIHavePermissionToPostInThisRoom(errmsg, sizeof errmsg, NULL, POST_LOGGED_IN);
3800 cprintf("%d %s\n", err, errmsg);
3804 /* Check some other permission type things. */
3806 if (IsEmptyStr(newusername)) {
3807 strcpy(newusername, CC->user.fullname);
3809 if ( (CC->user.axlevel < 6)
3810 && (strcasecmp(newusername, CC->user.fullname))
3811 && (strcasecmp(newusername, CC->cs_inet_fn))
3813 cprintf("%d You don't have permission to author messages as '%s'.\n",
3814 ERROR + HIGHER_ACCESS_REQUIRED,
3821 if (IsEmptyStr(newuseremail)) {
3822 newuseremail_ok = 1;
3825 if (!IsEmptyStr(newuseremail)) {
3826 if (!strcasecmp(newuseremail, CC->cs_inet_email)) {
3827 newuseremail_ok = 1;
3829 else if (!IsEmptyStr(CC->cs_inet_other_emails)) {
3830 j = num_tokens(CC->cs_inet_other_emails, '|');
3831 for (i=0; i<j; ++i) {
3832 extract_token(buf, CC->cs_inet_other_emails, i, '|', sizeof buf);
3833 if (!strcasecmp(newuseremail, buf)) {
3834 newuseremail_ok = 1;
3840 if (!newuseremail_ok) {
3841 cprintf("%d You don't have permission to author messages as '%s'.\n",
3842 ERROR + HIGHER_ACCESS_REQUIRED,
3848 CC->cs_flags |= CS_POSTING;
3850 /* In mailbox rooms we have to behave a little differently --
3851 * make sure the user has specified at least one recipient. Then
3852 * validate the recipient(s). We do this for the Mail> room, as
3853 * well as any room which has the "Mailbox" view set.
3856 if ( ( (CC->room.QRflags & QR_MAILBOX) && (!strcasecmp(&CC->room.QRname[11], MAILROOM)) )
3857 || ( (CC->room.QRflags & QR_MAILBOX) && (CC->curr_view == VIEW_MAILBOX) )
3859 if (CC->user.axlevel < 2) {
3860 strcpy(recp, "sysop");
3865 valid_to = validate_recipients(recp, NULL, 0);
3866 if (valid_to->num_error > 0) {
3867 cprintf("%d %s\n", ERROR + NO_SUCH_USER, valid_to->errormsg);
3868 free_recipients(valid_to);
3872 valid_cc = validate_recipients(cc, NULL, 0);
3873 if (valid_cc->num_error > 0) {
3874 cprintf("%d %s\n", ERROR + NO_SUCH_USER, valid_cc->errormsg);
3875 free_recipients(valid_to);
3876 free_recipients(valid_cc);
3880 valid_bcc = validate_recipients(bcc, NULL, 0);
3881 if (valid_bcc->num_error > 0) {
3882 cprintf("%d %s\n", ERROR + NO_SUCH_USER, valid_bcc->errormsg);
3883 free_recipients(valid_to);
3884 free_recipients(valid_cc);
3885 free_recipients(valid_bcc);
3889 /* Recipient required, but none were specified */
3890 if ( (valid_to->num_error < 0) && (valid_cc->num_error < 0) && (valid_bcc->num_error < 0) ) {
3891 free_recipients(valid_to);
3892 free_recipients(valid_cc);
3893 free_recipients(valid_bcc);
3894 cprintf("%d At least one recipient is required.\n", ERROR + NO_SUCH_USER);
3898 if (valid_to->num_internet + valid_cc->num_internet + valid_bcc->num_internet > 0) {
3899 if (CtdlCheckInternetMailPermission(&CC->user)==0) {
3900 cprintf("%d You do not have permission "
3901 "to send Internet mail.\n",
3902 ERROR + HIGHER_ACCESS_REQUIRED);
3903 free_recipients(valid_to);
3904 free_recipients(valid_cc);
3905 free_recipients(valid_bcc);
3910 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)
3911 && (CC->user.axlevel < 4) ) {
3912 cprintf("%d Higher access required for network mail.\n",
3913 ERROR + HIGHER_ACCESS_REQUIRED);
3914 free_recipients(valid_to);
3915 free_recipients(valid_cc);
3916 free_recipients(valid_bcc);
3920 if ((RESTRICT_INTERNET == 1)
3921 && (valid_to->num_internet + valid_cc->num_internet + valid_bcc->num_internet > 0)
3922 && ((CC->user.flags & US_INTERNET) == 0)
3923 && (!CC->internal_pgm)) {
3924 cprintf("%d You don't have access to Internet mail.\n",
3925 ERROR + HIGHER_ACCESS_REQUIRED);
3926 free_recipients(valid_to);
3927 free_recipients(valid_cc);
3928 free_recipients(valid_bcc);
3934 /* Is this a room which has anonymous-only or anonymous-option? */
3935 anonymous = MES_NORMAL;
3936 if (CC->room.QRflags & QR_ANONONLY) {
3937 anonymous = MES_ANONONLY;
3939 if (CC->room.QRflags & QR_ANONOPT) {
3940 if (anon_flag == 1) { /* only if the user requested it */
3941 anonymous = MES_ANONOPT;
3945 if ((CC->room.QRflags & QR_MAILBOX) == 0) {
3949 /* Recommend to the client that the use of a message subject is
3950 * strongly recommended in this room, if either the SUBJECTREQ flag
3951 * is set, or if there is one or more Internet email recipients.
3953 if (CC->room.QRflags2 & QR2_SUBJECTREQ) subject_required = 1;
3954 if ((valid_to) && (valid_to->num_internet > 0)) subject_required = 1;
3955 if ((valid_cc) && (valid_cc->num_internet > 0)) subject_required = 1;
3956 if ((valid_bcc) && (valid_bcc->num_internet > 0)) subject_required = 1;
3958 /* If we're only checking the validity of the request, return
3959 * success without creating the message.
3962 cprintf("%d %s|%d\n", CIT_OK,
3963 ((valid_to != NULL) ? valid_to->display_recp : ""),
3965 free_recipients(valid_to);
3966 free_recipients(valid_cc);
3967 free_recipients(valid_bcc);
3971 /* We don't need these anymore because we'll do it differently below */
3972 free_recipients(valid_to);
3973 free_recipients(valid_cc);
3974 free_recipients(valid_bcc);
3976 /* Read in the message from the client. */
3978 cprintf("%d send message\n", START_CHAT_MODE);
3980 cprintf("%d send message\n", SEND_LISTING);
3983 msg = CtdlMakeMessage(&CC->user, recp, cc,
3984 CC->room.QRname, anonymous, format_type,
3985 newusername, newuseremail, subject,
3986 ((!IsEmptyStr(supplied_euid)) ? supplied_euid : NULL),
3989 /* Put together one big recipients struct containing to/cc/bcc all in
3990 * one. This is for the envelope.
3992 char *all_recps = malloc(SIZ * 3);
3993 strcpy(all_recps, recp);
3994 if (!IsEmptyStr(cc)) {
3995 if (!IsEmptyStr(all_recps)) {
3996 strcat(all_recps, ",");
3998 strcat(all_recps, cc);
4000 if (!IsEmptyStr(bcc)) {
4001 if (!IsEmptyStr(all_recps)) {
4002 strcat(all_recps, ",");
4004 strcat(all_recps, bcc);
4006 if (!IsEmptyStr(all_recps)) {
4007 valid = validate_recipients(all_recps, NULL, 0);
4015 msgnum = CtdlSubmitMsg(msg, valid, "", QP_EADDR);
4018 cprintf("%ld\n", msgnum);
4020 cprintf("Message accepted.\n");
4023 cprintf("Internal error.\n");
4025 if (msg->cm_fields['E'] != NULL) {
4026 cprintf("%s\n", msg->cm_fields['E']);
4033 CtdlFreeMessage(msg);
4035 if (valid != NULL) {
4036 free_recipients(valid);
4044 * API function to delete messages which match a set of criteria
4045 * (returns the actual number of messages deleted)
4047 int CtdlDeleteMessages(char *room_name, /* which room */
4048 long *dmsgnums, /* array of msg numbers to be deleted */
4049 int num_dmsgnums, /* number of msgs to be deleted, or 0 for "any" */
4050 char *content_type /* or "" for any. regular expressions expected. */
4053 struct ctdlroom qrbuf;
4054 struct cdbdata *cdbfr;
4055 long *msglist = NULL;
4056 long *dellist = NULL;
4059 int num_deleted = 0;
4061 struct MetaData smi;
4064 int need_to_free_re = 0;
4066 if (content_type) if (!IsEmptyStr(content_type)) {
4067 regcomp(&re, content_type, 0);
4068 need_to_free_re = 1;
4070 CtdlLogPrintf(CTDL_DEBUG, "CtdlDeleteMessages(%s, %d msgs, %s)\n",
4071 room_name, num_dmsgnums, content_type);
4073 /* get room record, obtaining a lock... */
4074 if (lgetroom(&qrbuf, room_name) != 0) {
4075 CtdlLogPrintf(CTDL_ERR, "CtdlDeleteMessages(): Room <%s> not found\n",
4077 if (need_to_free_re) regfree(&re);
4078 return (0); /* room not found */
4080 cdbfr = cdb_fetch(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long));
4082 if (cdbfr != NULL) {
4083 dellist = malloc(cdbfr->len);
4084 msglist = (long *) cdbfr->ptr;
4085 cdbfr->ptr = NULL; /* CtdlDeleteMessages() now owns this memory */
4086 num_msgs = cdbfr->len / sizeof(long);
4090 for (i = 0; i < num_msgs; ++i) {
4093 /* Set/clear a bit for each criterion */
4095 /* 0 messages in the list or a null list means that we are
4096 * interested in deleting any messages which meet the other criteria.
4098 if ((num_dmsgnums == 0) || (dmsgnums == NULL)) {
4099 delete_this |= 0x01;
4102 for (j=0; j<num_dmsgnums; ++j) {
4103 if (msglist[i] == dmsgnums[j]) {
4104 delete_this |= 0x01;
4109 if (IsEmptyStr(content_type)) {
4110 delete_this |= 0x02;
4112 GetMetaData(&smi, msglist[i]);
4113 if (regexec(&re, smi.meta_content_type, 1, &pm, 0) == 0) {
4114 delete_this |= 0x02;
4118 /* Delete message only if all bits are set */
4119 if (delete_this == 0x03) {
4120 dellist[num_deleted++] = msglist[i];
4125 num_msgs = sort_msglist(msglist, num_msgs);
4126 cdb_store(CDB_MSGLISTS, &qrbuf.QRnumber, (int)sizeof(long),
4127 msglist, (int)(num_msgs * sizeof(long)));
4129 qrbuf.QRhighest = msglist[num_msgs - 1];
4133 /* Go through the messages we pulled out of the index, and decrement
4134 * their reference counts by 1. If this is the only room the message
4135 * was in, the reference count will reach zero and the message will
4136 * automatically be deleted from the database. We do this in a
4137 * separate pass because there might be plug-in hooks getting called,
4138 * and we don't want that happening during an S_ROOMS critical
4141 if (num_deleted) for (i=0; i<num_deleted; ++i) {
4142 PerformDeleteHooks(qrbuf.QRname, dellist[i]);
4143 AdjRefCount(dellist[i], -1);
4146 /* Now free the memory we used, and go away. */
4147 if (msglist != NULL) free(msglist);
4148 if (dellist != NULL) free(dellist);
4149 CtdlLogPrintf(CTDL_DEBUG, "%d message(s) deleted.\n", num_deleted);
4150 if (need_to_free_re) regfree(&re);
4151 return (num_deleted);
4157 * Check whether the current user has permission to delete messages from
4158 * the current room (returns 1 for yes, 0 for no)
4160 int CtdlDoIHavePermissionToDeleteMessagesFromThisRoom(void) {
4162 CtdlRoomAccess(&CC->room, &CC->user, &ra, NULL);
4163 if (ra & UA_DELETEALLOWED) return(1);
4171 * Delete message from current room
4173 void cmd_dele(char *args)
4182 extract_token(msgset, args, 0, '|', sizeof msgset);
4183 num_msgs = num_tokens(msgset, ',');
4185 cprintf("%d Nothing to do.\n", CIT_OK);
4189 if (CtdlDoIHavePermissionToDeleteMessagesFromThisRoom() == 0) {
4190 cprintf("%d Higher access required.\n",
4191 ERROR + HIGHER_ACCESS_REQUIRED);
4196 * Build our message set to be moved/copied
4198 msgs = malloc(num_msgs * sizeof(long));
4199 for (i=0; i<num_msgs; ++i) {
4200 extract_token(msgtok, msgset, i, ',', sizeof msgtok);
4201 msgs[i] = atol(msgtok);
4204 num_deleted = CtdlDeleteMessages(CC->room.QRname, msgs, num_msgs, "");
4208 cprintf("%d %d message%s deleted.\n", CIT_OK,
4209 num_deleted, ((num_deleted != 1) ? "s" : ""));
4211 cprintf("%d Message not found.\n", ERROR + MESSAGE_NOT_FOUND);
4219 * move or copy a message to another room
4221 void cmd_move(char *args)
4228 char targ[ROOMNAMELEN];
4229 struct ctdlroom qtemp;
4236 extract_token(msgset, args, 0, '|', sizeof msgset);
4237 num_msgs = num_tokens(msgset, ',');
4239 cprintf("%d Nothing to do.\n", CIT_OK);
4243 extract_token(targ, args, 1, '|', sizeof targ);
4244 convert_room_name_macros(targ, sizeof targ);
4245 targ[ROOMNAMELEN - 1] = 0;
4246 is_copy = extract_int(args, 2);
4248 if (getroom(&qtemp, targ) != 0) {
4249 cprintf("%d '%s' does not exist.\n", ERROR + ROOM_NOT_FOUND, targ);
4253 if (!strcasecmp(qtemp.QRname, CC->room.QRname)) {
4254 cprintf("%d Source and target rooms are the same.\n", ERROR + ALREADY_EXISTS);
4258 getuser(&CC->user, CC->curr_user);
4259 CtdlRoomAccess(&qtemp, &CC->user, &ra, NULL);
4261 /* Check for permission to perform this operation.
4262 * Remember: "CC->room" is source, "qtemp" is target.
4266 /* Aides can move/copy */
4267 if (CC->user.axlevel >= 6) permit = 1;
4269 /* Room aides can move/copy */
4270 if (CC->user.usernum == CC->room.QRroomaide) permit = 1;
4272 /* Permit move/copy from personal rooms */
4273 if ((CC->room.QRflags & QR_MAILBOX)
4274 && (qtemp.QRflags & QR_MAILBOX)) permit = 1;
4276 /* Permit only copy from public to personal room */
4278 && (!(CC->room.QRflags & QR_MAILBOX))
4279 && (qtemp.QRflags & QR_MAILBOX)) permit = 1;
4281 /* Permit message removal from collaborative delete rooms */
4282 if (CC->room.QRflags2 & QR2_COLLABDEL) permit = 1;
4284 /* Users allowed to post into the target room may move into it too. */
4285 if ((CC->room.QRflags & QR_MAILBOX) &&
4286 (qtemp.QRflags & UA_POSTALLOWED)) permit = 1;
4288 /* User must have access to target room */
4289 if (!(ra & UA_KNOWN)) permit = 0;
4292 cprintf("%d Higher access required.\n",
4293 ERROR + HIGHER_ACCESS_REQUIRED);
4298 * Build our message set to be moved/copied
4300 msgs = malloc(num_msgs * sizeof(long));
4301 for (i=0; i<num_msgs; ++i) {
4302 extract_token(msgtok, msgset, i, ',', sizeof msgtok);
4303 msgs[i] = atol(msgtok);
4309 err = CtdlSaveMsgPointersInRoom(targ, msgs, num_msgs, 1, NULL);
4311 cprintf("%d Cannot store message(s) in %s: error %d\n",
4317 /* Now delete the message from the source room,
4318 * if this is a 'move' rather than a 'copy' operation.
4321 CtdlDeleteMessages(CC->room.QRname, msgs, num_msgs, "");
4325 cprintf("%d Message(s) %s.\n", CIT_OK, (is_copy ? "copied" : "moved") );
4331 * GetMetaData() - Get the supplementary record for a message
4333 void GetMetaData(struct MetaData *smibuf, long msgnum)
4336 struct cdbdata *cdbsmi;
4339 memset(smibuf, 0, sizeof(struct MetaData));
4340 smibuf->meta_msgnum = msgnum;
4341 smibuf->meta_refcount = 1; /* Default reference count is 1 */
4343 /* Use the negative of the message number for its supp record index */
4344 TheIndex = (0L - msgnum);
4346 cdbsmi = cdb_fetch(CDB_MSGMAIN, &TheIndex, sizeof(long));
4347 if (cdbsmi == NULL) {
4348 return; /* record not found; go with defaults */
4350 memcpy(smibuf, cdbsmi->ptr,
4351 ((cdbsmi->len > sizeof(struct MetaData)) ?
4352 sizeof(struct MetaData) : cdbsmi->len));
4359 * PutMetaData() - (re)write supplementary record for a message
4361 void PutMetaData(struct MetaData *smibuf)
4365 /* Use the negative of the message number for the metadata db index */
4366 TheIndex = (0L - smibuf->meta_msgnum);
4368 cdb_store(CDB_MSGMAIN,
4369 &TheIndex, (int)sizeof(long),
4370 smibuf, (int)sizeof(struct MetaData));
4375 * AdjRefCount - submit an adjustment to the reference count for a message.
4376 * (These are just queued -- we actually process them later.)
4378 void AdjRefCount(long msgnum, int incr)
4380 struct arcq new_arcq;
4383 begin_critical_section(S_SUPPMSGMAIN);
4384 if (arcfp == NULL) {
4385 arcfp = fopen(file_arcq, "ab+");
4387 end_critical_section(S_SUPPMSGMAIN);
4389 /* msgnum < 0 means that we're trying to close the file */
4391 CtdlLogPrintf(CTDL_DEBUG, "Closing the AdjRefCount queue file\n");
4392 begin_critical_section(S_SUPPMSGMAIN);
4393 if (arcfp != NULL) {
4397 end_critical_section(S_SUPPMSGMAIN);
4402 * If we can't open the queue, perform the operation synchronously.
4404 if (arcfp == NULL) {
4405 TDAP_AdjRefCount(msgnum, incr);
4409 new_arcq.arcq_msgnum = msgnum;
4410 new_arcq.arcq_delta = incr;
4411 rv = fwrite(&new_arcq, sizeof(struct arcq), 1, arcfp);
4419 * TDAP_ProcessAdjRefCountQueue()
4421 * Process the queue of message count adjustments that was created by calls
4422 * to AdjRefCount() ... by reading the queue and calling TDAP_AdjRefCount()
4423 * for each one. This should be an "off hours" operation.
4425 int TDAP_ProcessAdjRefCountQueue(void)
4427 char file_arcq_temp[PATH_MAX];
4430 struct arcq arcq_rec;
4431 int num_records_processed = 0;
4433 snprintf(file_arcq_temp, sizeof file_arcq_temp, "%s.%04x", file_arcq, rand());
4435 begin_critical_section(S_SUPPMSGMAIN);
4436 if (arcfp != NULL) {
4441 r = link(file_arcq, file_arcq_temp);
4443 CtdlLogPrintf(CTDL_CRIT, "%s: %s\n", file_arcq_temp, strerror(errno));
4444 end_critical_section(S_SUPPMSGMAIN);
4445 return(num_records_processed);
4449 end_critical_section(S_SUPPMSGMAIN);
4451 fp = fopen(file_arcq_temp, "rb");
4453 CtdlLogPrintf(CTDL_CRIT, "%s: %s\n", file_arcq_temp, strerror(errno));
4454 return(num_records_processed);
4457 while (fread(&arcq_rec, sizeof(struct arcq), 1, fp) == 1) {
4458 TDAP_AdjRefCount(arcq_rec.arcq_msgnum, arcq_rec.arcq_delta);
4459 ++num_records_processed;
4463 r = unlink(file_arcq_temp);
4465 CtdlLogPrintf(CTDL_CRIT, "%s: %s\n", file_arcq_temp, strerror(errno));
4468 return(num_records_processed);
4474 * TDAP_AdjRefCount - adjust the reference count for a message.
4475 * This one does it "for real" because it's called by
4476 * the autopurger function that processes the queue
4477 * created by AdjRefCount(). If a message's reference
4478 * count becomes zero, we also delete the message from
4479 * disk and de-index it.
4481 void TDAP_AdjRefCount(long msgnum, int incr)
4484 struct MetaData smi;
4487 /* This is a *tight* critical section; please keep it that way, as
4488 * it may get called while nested in other critical sections.
4489 * Complicating this any further will surely cause deadlock!
4491 begin_critical_section(S_SUPPMSGMAIN);
4492 GetMetaData(&smi, msgnum);
4493 smi.meta_refcount += incr;
4495 end_critical_section(S_SUPPMSGMAIN);
4496 CtdlLogPrintf(CTDL_DEBUG, "msg %ld ref count delta %+d, is now %d\n",
4497 msgnum, incr, smi.meta_refcount);
4499 /* If the reference count is now zero, delete the message
4500 * (and its supplementary record as well).
4502 if (smi.meta_refcount == 0) {
4503 CtdlLogPrintf(CTDL_DEBUG, "Deleting message <%ld>\n", msgnum);
4505 /* Call delete hooks with NULL room to show it has gone altogether */
4506 PerformDeleteHooks(NULL, msgnum);
4508 /* Remove from message base */
4510 cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long));
4511 cdb_delete(CDB_BIGMSGS, &delnum, (int)sizeof(long));
4513 /* Remove metadata record */
4514 delnum = (0L - msgnum);
4515 cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long));
4521 * Write a generic object to this room
4523 * Note: this could be much more efficient. Right now we use two temporary
4524 * files, and still pull the message into memory as with all others.
4526 void CtdlWriteObject(char *req_room, /* Room to stuff it in */
4527 char *content_type, /* MIME type of this object */
4528 char *raw_message, /* Data to be written */
4529 off_t raw_length, /* Size of raw_message */
4530 struct ctdluser *is_mailbox, /* Mailbox room? */
4531 int is_binary, /* Is encoding necessary? */
4532 int is_unique, /* Del others of this type? */
4533 unsigned int flags /* Internal save flags */
4537 struct ctdlroom qrbuf;
4538 char roomname[ROOMNAMELEN];
4539 struct CtdlMessage *msg;
4540 char *encoded_message = NULL;
4542 if (is_mailbox != NULL) {
4543 MailboxName(roomname, sizeof roomname, is_mailbox, req_room);
4546 safestrncpy(roomname, req_room, sizeof(roomname));
4549 CtdlLogPrintf(CTDL_DEBUG, "Raw length is %ld\n", (long)raw_length);
4552 encoded_message = malloc((size_t) (((raw_length * 134) / 100) + 4096 ) );
4555 encoded_message = malloc((size_t)(raw_length + 4096));
4558 sprintf(encoded_message, "Content-type: %s\n", content_type);
4561 sprintf(&encoded_message[strlen(encoded_message)],
4562 "Content-transfer-encoding: base64\n\n"
4566 sprintf(&encoded_message[strlen(encoded_message)],
4567 "Content-transfer-encoding: 7bit\n\n"
4573 &encoded_message[strlen(encoded_message)],
4581 &encoded_message[strlen(encoded_message)],
4587 CtdlLogPrintf(CTDL_DEBUG, "Allocating\n");
4588 msg = malloc(sizeof(struct CtdlMessage));
4589 memset(msg, 0, sizeof(struct CtdlMessage));
4590 msg->cm_magic = CTDLMESSAGE_MAGIC;
4591 msg->cm_anon_type = MES_NORMAL;
4592 msg->cm_format_type = 4;
4593 msg->cm_fields['A'] = strdup(CC->user.fullname);
4594 msg->cm_fields['O'] = strdup(req_room);
4595 msg->cm_fields['N'] = strdup(config.c_nodename);
4596 msg->cm_fields['H'] = strdup(config.c_humannode);
4597 msg->cm_flags = flags;
4599 msg->cm_fields['M'] = encoded_message;
4601 /* Create the requested room if we have to. */
4602 if (getroom(&qrbuf, roomname) != 0) {
4603 create_room(roomname,
4604 ( (is_mailbox != NULL) ? 5 : 3 ),
4605 "", 0, 1, 0, VIEW_BBS);
4607 /* If the caller specified this object as unique, delete all
4608 * other objects of this type that are currently in the room.
4611 CtdlLogPrintf(CTDL_DEBUG, "Deleted %d other msgs of this type\n",
4612 CtdlDeleteMessages(roomname, NULL, 0, content_type)
4615 /* Now write the data */
4616 CtdlSubmitMsg(msg, NULL, roomname, 0);
4617 CtdlFreeMessage(msg);
4625 void CtdlGetSysConfigBackend(long msgnum, void *userdata) {
4626 config_msgnum = msgnum;
4630 char *CtdlGetSysConfig(char *sysconfname) {
4631 char hold_rm[ROOMNAMELEN];
4634 struct CtdlMessage *msg;
4637 strcpy(hold_rm, CC->room.QRname);
4638 if (getroom(&CC->room, SYSCONFIGROOM) != 0) {
4639 getroom(&CC->room, hold_rm);
4644 /* We want the last (and probably only) config in this room */
4645 begin_critical_section(S_CONFIG);
4646 config_msgnum = (-1L);
4647 CtdlForEachMessage(MSGS_LAST, 1, NULL, sysconfname, NULL,
4648 CtdlGetSysConfigBackend, NULL);
4649 msgnum = config_msgnum;
4650 end_critical_section(S_CONFIG);
4656 msg = CtdlFetchMessage(msgnum, 1);
4658 conf = strdup(msg->cm_fields['M']);
4659 CtdlFreeMessage(msg);
4666 getroom(&CC->room, hold_rm);
4668 if (conf != NULL) do {
4669 extract_token(buf, conf, 0, '\n', sizeof buf);
4670 strcpy(conf, &conf[strlen(buf)+1]);
4671 } while ( (!IsEmptyStr(conf)) && (!IsEmptyStr(buf)) );
4677 void CtdlPutSysConfig(char *sysconfname, char *sysconfdata) {
4678 CtdlWriteObject(SYSCONFIGROOM, sysconfname, sysconfdata, (strlen(sysconfdata)+1), NULL, 0, 1, 0);
4683 * Determine whether a given Internet address belongs to the current user
4685 int CtdlIsMe(char *addr, int addr_buf_len)
4687 struct recptypes *recp;
4690 recp = validate_recipients(addr, NULL, 0);
4691 if (recp == NULL) return(0);
4693 if (recp->num_local == 0) {
4694 free_recipients(recp);
4698 for (i=0; i<recp->num_local; ++i) {
4699 extract_token(addr, recp->recp_local, i, '|', addr_buf_len);
4700 if (!strcasecmp(addr, CC->user.fullname)) {
4701 free_recipients(recp);
4706 free_recipients(recp);
4712 * Citadel protocol command to do the same
4714 void cmd_isme(char *argbuf) {
4717 if (CtdlAccessCheck(ac_logged_in)) return;
4718 extract_token(addr, argbuf, 0, '|', sizeof addr);
4720 if (CtdlIsMe(addr, sizeof addr)) {
4721 cprintf("%d %s\n", CIT_OK, addr);
4724 cprintf("%d Not you.\n", ERROR + ILLEGAL_VALUE);
4730 /*****************************************************************************/
4731 /* MODULE INITIALIZATION STUFF */
4732 /*****************************************************************************/
4734 CTDL_MODULE_INIT(msgbase)
4736 CtdlRegisterProtoHook(cmd_msgs, "MSGS", "Output a list of messages in the current room");
4737 CtdlRegisterProtoHook(cmd_msg0, "MSG0", "Output a message in plain text format");
4738 CtdlRegisterProtoHook(cmd_msg2, "MSG2", "Output a message in RFC822 format");
4739 CtdlRegisterProtoHook(cmd_msg3, "MSG3", "Output a message in raw format (deprecated)");
4740 CtdlRegisterProtoHook(cmd_msg4, "MSG4", "Output a message in the client's preferred format");
4741 CtdlRegisterProtoHook(cmd_msgp, "MSGP", "Select preferred format for MSG4 output");
4742 CtdlRegisterProtoHook(cmd_opna, "OPNA", "Open an attachment for download");
4743 CtdlRegisterProtoHook(cmd_dlat, "DLAT", "Download an attachment");
4744 CtdlRegisterProtoHook(cmd_ent0, "ENT0", "Enter a message");
4745 CtdlRegisterProtoHook(cmd_dele, "DELE", "Delete a message");
4746 CtdlRegisterProtoHook(cmd_move, "MOVE", "Move or copy a message to another room");
4747 CtdlRegisterProtoHook(cmd_isme, "ISME", "Determine whether an email address belongs to a user");
4749 /* return our Subversion id for the Log */