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, ',')) {
428 StrBufExtract_token(lostr, setstr, 0, ':');
429 if (StrBufNum_tokens(setstr, ':') >= 2) {
430 StrBufExtract_token(histr, setstr, 1, ':');
434 StrBufAppendBuf(histr, lostr, 0);
437 if (!strcmp(ChrPtr(histr), "*")) {
444 for (i = 0; i < num_msgs; ++i) {
445 if ((msglist[i] >= lo) && (msglist[i] <= hi)) {
455 /* Now translate the array of booleans back into a sequence set */
461 for (i=0; i<num_msgs; ++i) {
465 for (k=0; k<num_target_msgnums; ++k) {
466 if (msglist[i] == target_msgnums[k]) {
467 is_seen = target_setting;
471 w = 0; /* set to 1 if we write something to the string */
473 if ((was_seen == 0) && (is_seen == 1)) {
476 else if ((was_seen == 1) && (is_seen == 0)) {
480 if (StrLength(vset) > 0) {
481 StrBufAppendBufPlain(vset, HKEY(","), 0);
484 StrBufAppendPrintf(vset, "%ld", hi);
487 StrBufAppendPrintf(vset, "%ld:%ld", lo, hi);
490 else if ((is_seen) && (i == num_msgs - 1)) {
492 if (StrLength(vset) > 0) {
493 StrBufAppendBufPlain(vset, HKEY(","), 0);
495 if ((i==0) || (was_seen == 0)) {
496 StrBufAppendPrintf(vset, "%ld", msglist[i]);
499 StrBufAppendPrintf(vset, "%ld:%ld", lo, msglist[i]);
503 /* If the string is getting too long, truncate it at the beginning; repeat up to 9 times * /
504 if (w) for (j=0; j<9; ++j) {
505 if ((StrLength(vset) + 20) > sizeof vset) {
506 remove_token(vset, 0, ',');
507 if (which_set == ctdlsetseen_seen) {
509 sprintf(temp, "1:%ld,", atol(vset)-1L);
515 we don't get to long anymore.
521 while (StrLength(vset) > SIZ)
522 StrBufRemove_token(vset, 0, ',');
524 CtdlLogPrintf(CTDL_DEBUG, " after update: %s\n", ChrPtr(vset));
526 /* Decide which message set we're manipulating */
528 case ctdlsetseen_seen:
529 safestrncpy(vbuf.v_seen, ChrPtr(vset), sizeof vbuf.v_seen);
531 case ctdlsetseen_answered:
532 safestrncpy(vbuf.v_answered, ChrPtr(vset), sizeof vbuf.v_answered);
538 CtdlSetRelationship(&vbuf, which_user, which_room);
544 * API function to perform an operation for each qualifying message in the
545 * current room. (Returns the number of messages processed.)
547 int CtdlForEachMessage(int mode, long ref, char *search_string,
549 struct CtdlMessage *compare,
550 void (*CallBack) (long, void *),
556 struct cdbdata *cdbfr;
557 long *msglist = NULL;
559 int num_processed = 0;
562 struct CtdlMessage *msg = NULL;
565 int printed_lastold = 0;
566 int num_search_msgs = 0;
567 long *search_msgs = NULL;
569 int need_to_free_re = 0;
572 if ((content_type) && (!IsEmptyStr(content_type))) {
573 regcomp(&re, content_type, 0);
577 /* Learn about the user and room in question */
578 getuser(&CC->user, CC->curr_user);
579 CtdlGetRelationship(&vbuf, &CC->user, &CC->room);
581 /* Load the message list */
582 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long));
584 msglist = (long *) cdbfr->ptr;
585 num_msgs = cdbfr->len / sizeof(long);
587 if (need_to_free_re) regfree(&re);
588 return 0; /* No messages at all? No further action. */
593 * Now begin the traversal.
595 if (num_msgs > 0) for (a = 0; a < num_msgs; ++a) {
597 /* If the caller is looking for a specific MIME type, filter
598 * out all messages which are not of the type requested.
600 if ((content_type != NULL) && (!IsEmptyStr(content_type))) {
602 /* This call to GetMetaData() sits inside this loop
603 * so that we only do the extra database read per msg
604 * if we need to. Doing the extra read all the time
605 * really kills the server. If we ever need to use
606 * metadata for another search criterion, we need to
607 * move the read somewhere else -- but still be smart
608 * enough to only do the read if the caller has
609 * specified something that will need it.
611 GetMetaData(&smi, msglist[a]);
613 /* if (strcasecmp(smi.meta_content_type, content_type)) { old non-regex way */
614 if (regexec(&re, smi.meta_content_type, 1, &pm, 0) != 0) {
620 num_msgs = sort_msglist(msglist, num_msgs);
622 /* If a template was supplied, filter out the messages which
623 * don't match. (This could induce some delays!)
626 if (compare != NULL) {
627 for (a = 0; a < num_msgs; ++a) {
628 msg = CtdlFetchMessage(msglist[a], 1);
630 if (CtdlMsgCmp(msg, compare)) {
633 CtdlFreeMessage(msg);
639 /* If a search string was specified, get a message list from
640 * the full text index and remove messages which aren't on both
644 * Since the lists are sorted and strictly ascending, and the
645 * output list is guaranteed to be shorter than or equal to the
646 * input list, we overwrite the bottom of the input list. This
647 * eliminates the need to memmove big chunks of the list over and
650 if ( (num_msgs > 0) && (mode == MSGS_SEARCH) && (search_string) ) {
652 /* Call search module via hook mechanism.
653 * NULL means use any search function available.
654 * otherwise replace with a char * to name of search routine
656 CtdlModuleDoSearch(&num_search_msgs, &search_msgs, search_string, "fulltext");
658 if (num_search_msgs > 0) {
662 orig_num_msgs = num_msgs;
664 for (i=0; i<orig_num_msgs; ++i) {
665 for (j=0; j<num_search_msgs; ++j) {
666 if (msglist[i] == search_msgs[j]) {
667 msglist[num_msgs++] = msglist[i];
673 num_msgs = 0; /* No messages qualify */
675 if (search_msgs != NULL) free(search_msgs);
677 /* Now that we've purged messages which don't contain the search
678 * string, treat a MSGS_SEARCH just like a MSGS_ALL from this
685 * Now iterate through the message list, according to the
686 * criteria supplied by the caller.
689 for (a = 0; a < num_msgs; ++a) {
690 thismsg = msglist[a];
691 if (mode == MSGS_ALL) {
695 is_seen = is_msg_in_sequence_set(
696 vbuf.v_seen, thismsg);
697 if (is_seen) lastold = thismsg;
703 || ((mode == MSGS_OLD) && (is_seen))
704 || ((mode == MSGS_NEW) && (!is_seen))
705 || ((mode == MSGS_LAST) && (a >= (num_msgs - ref)))
706 || ((mode == MSGS_FIRST) && (a < ref))
707 || ((mode == MSGS_GT) && (thismsg > ref))
708 || ((mode == MSGS_EQ) && (thismsg == ref))
711 if ((mode == MSGS_NEW) && (CC->user.flags & US_LASTOLD) && (lastold > 0L) && (printed_lastold == 0) && (!is_seen)) {
713 CallBack(lastold, userdata);
717 if (CallBack) CallBack(thismsg, userdata);
721 cdb_free(cdbfr); /* Clean up */
722 if (need_to_free_re) regfree(&re);
723 return num_processed;
729 * cmd_msgs() - get list of message #'s in this room
730 * implements the MSGS server command using CtdlForEachMessage()
732 void cmd_msgs(char *cmdbuf)
741 int with_template = 0;
742 struct CtdlMessage *template = NULL;
743 int with_headers = 0;
744 char search_string[1024];
746 extract_token(which, cmdbuf, 0, '|', sizeof which);
747 cm_ref = extract_int(cmdbuf, 1);
748 extract_token(search_string, cmdbuf, 1, '|', sizeof search_string);
749 with_template = extract_int(cmdbuf, 2);
750 with_headers = extract_int(cmdbuf, 3);
753 if (!strncasecmp(which, "OLD", 3))
755 else if (!strncasecmp(which, "NEW", 3))
757 else if (!strncasecmp(which, "FIRST", 5))
759 else if (!strncasecmp(which, "LAST", 4))
761 else if (!strncasecmp(which, "GT", 2))
763 else if (!strncasecmp(which, "SEARCH", 6))
768 if ((!(CC->logged_in)) && (!(CC->internal_pgm))) {
769 cprintf("%d not logged in\n", ERROR + NOT_LOGGED_IN);
773 if ( (mode == MSGS_SEARCH) && (!config.c_enable_fulltext) ) {
774 cprintf("%d Full text index is not enabled on this server.\n",
775 ERROR + CMD_NOT_SUPPORTED);
781 cprintf("%d Send template then receive message list\n",
783 template = (struct CtdlMessage *)
784 malloc(sizeof(struct CtdlMessage));
785 memset(template, 0, sizeof(struct CtdlMessage));
786 template->cm_magic = CTDLMESSAGE_MAGIC;
787 template->cm_anon_type = MES_NORMAL;
789 while(client_getln(buf, sizeof buf) >= 0 && strcmp(buf,"000")) {
790 extract_token(tfield, buf, 0, '|', sizeof tfield);
791 extract_token(tvalue, buf, 1, '|', sizeof tvalue);
792 for (i='A'; i<='Z'; ++i) if (msgkeys[i]!=NULL) {
793 if (!strcasecmp(tfield, msgkeys[i])) {
794 template->cm_fields[i] =
802 cprintf("%d \n", LISTING_FOLLOWS);
805 CtdlForEachMessage(mode,
806 ( (mode == MSGS_SEARCH) ? 0 : cm_ref ),
807 ( (mode == MSGS_SEARCH) ? search_string : NULL ),
810 (with_headers ? headers_listing : simple_listing),
813 if (template != NULL) CtdlFreeMessage(template);
821 * help_subst() - support routine for help file viewer
823 void help_subst(char *strbuf, char *source, char *dest)
828 while (p = pattern2(strbuf, source), (p >= 0)) {
829 strcpy(workbuf, &strbuf[p + strlen(source)]);
830 strcpy(&strbuf[p], dest);
831 strcat(strbuf, workbuf);
836 void do_help_subst(char *buffer)
840 help_subst(buffer, "^nodename", config.c_nodename);
841 help_subst(buffer, "^humannode", config.c_humannode);
842 help_subst(buffer, "^fqdn", config.c_fqdn);
843 help_subst(buffer, "^username", CC->user.fullname);
844 snprintf(buf2, sizeof buf2, "%ld", CC->user.usernum);
845 help_subst(buffer, "^usernum", buf2);
846 help_subst(buffer, "^sysadm", config.c_sysadm);
847 help_subst(buffer, "^variantname", CITADEL);
848 snprintf(buf2, sizeof buf2, "%d", config.c_maxsessions);
849 help_subst(buffer, "^maxsessions", buf2);
850 help_subst(buffer, "^bbsdir", ctdl_message_dir);
856 * memfmout() - Citadel text formatter and paginator.
857 * Although the original purpose of this routine was to format
858 * text to the reader's screen width, all we're really using it
859 * for here is to format text out to 80 columns before sending it
860 * to the client. The client software may reformat it again.
863 char *mptr, /* where are we going to get our text from? */
864 char subst, /* nonzero if we should do substitutions */
865 char *nl) /* string to terminate lines with */
873 static int width = 80;
878 c = 1; /* c is the current pos */
882 while (ch = *mptr, ((ch != 0) && (strlen(buffer) < 126))) {
884 buffer[strlen(buffer) + 1] = 0;
885 buffer[strlen(buffer)] = ch;
888 if (buffer[0] == '^')
889 do_help_subst(buffer);
891 buffer[strlen(buffer) + 1] = 0;
893 strcpy(buffer, &buffer[1]);
901 if (((ch == 13) || (ch == 10)) && (old != 13) && (old != 10)) {
904 if (((old == 13) || (old == 10)) && (isspace(real))) {
909 if (((strlen(aaa) + c) > (width - 5)) && (strlen(aaa) > (width - 5))) {
910 cprintf("%s%s", nl, aaa);
919 if ((strlen(aaa) + c) > (width - 5)) {
928 if ((ch == 13) || (ch == 10)) {
929 cprintf("%s%s", aaa, nl);
936 cprintf("%s%s", aaa, nl);
942 * Callback function for mime parser that simply lists the part
944 void list_this_part(char *name, char *filename, char *partnum, char *disp,
945 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
946 char *cbid, void *cbuserdata)
950 ma = (struct ma_info *)cbuserdata;
951 if (ma->is_ma == 0) {
952 cprintf("part=%s|%s|%s|%s|%s|%ld|%s\n",
953 name, filename, partnum, disp, cbtype, (long)length, cbid);
958 * Callback function for multipart prefix
960 void list_this_pref(char *name, char *filename, char *partnum, char *disp,
961 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
962 char *cbid, void *cbuserdata)
966 ma = (struct ma_info *)cbuserdata;
967 if (!strcasecmp(cbtype, "multipart/alternative")) {
971 if (ma->is_ma == 0) {
972 cprintf("pref=%s|%s\n", partnum, cbtype);
977 * Callback function for multipart sufffix
979 void list_this_suff(char *name, char *filename, char *partnum, char *disp,
980 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
981 char *cbid, void *cbuserdata)
985 ma = (struct ma_info *)cbuserdata;
986 if (ma->is_ma == 0) {
987 cprintf("suff=%s|%s\n", partnum, cbtype);
989 if (!strcasecmp(cbtype, "multipart/alternative")) {
996 * Callback function for mime parser that opens a section for downloading
998 void mime_download(char *name, char *filename, char *partnum, char *disp,
999 void *content, char *cbtype, char *cbcharset, size_t length,
1000 char *encoding, char *cbid, void *cbuserdata)
1003 /* Silently go away if there's already a download open. */
1004 if (CC->download_fp != NULL)
1008 (!IsEmptyStr(partnum) && (!strcasecmp(CC->download_desired_section, partnum)))
1009 || (!IsEmptyStr(cbid) && (!strcasecmp(CC->download_desired_section, cbid)))
1011 CC->download_fp = tmpfile();
1012 if (CC->download_fp == NULL)
1015 fwrite(content, length, 1, CC->download_fp);
1016 fflush(CC->download_fp);
1017 rewind(CC->download_fp);
1019 OpenCmdResult(filename, cbtype);
1026 * Callback function for mime parser that outputs a section all at once.
1027 * We can specify the desired section by part number *or* content-id.
1029 void mime_spew_section(char *name, char *filename, char *partnum, char *disp,
1030 void *content, char *cbtype, char *cbcharset, size_t length,
1031 char *encoding, char *cbid, void *cbuserdata)
1033 int *found_it = (int *)cbuserdata;
1036 (!IsEmptyStr(partnum) && (!strcasecmp(CC->download_desired_section, partnum)))
1037 || (!IsEmptyStr(cbid) && (!strcasecmp(CC->download_desired_section, cbid)))
1040 cprintf("%d %d|-1|%s|%s\n",
1046 client_write(content, length);
1053 * Load a message from disk into memory.
1054 * This is used by CtdlOutputMsg() and other fetch functions.
1056 * NOTE: Caller is responsible for freeing the returned CtdlMessage struct
1057 * using the CtdlMessageFree() function.
1059 struct CtdlMessage *CtdlFetchMessage(long msgnum, int with_body)
1061 struct cdbdata *dmsgtext;
1062 struct CtdlMessage *ret = NULL;
1066 cit_uint8_t field_header;
1068 CtdlLogPrintf(CTDL_DEBUG, "CtdlFetchMessage(%ld, %d)\n", msgnum, with_body);
1070 dmsgtext = cdb_fetch(CDB_MSGMAIN, &msgnum, sizeof(long));
1071 if (dmsgtext == NULL) {
1074 mptr = dmsgtext->ptr;
1075 upper_bound = mptr + dmsgtext->len;
1077 /* Parse the three bytes that begin EVERY message on disk.
1078 * The first is always 0xFF, the on-disk magic number.
1079 * The second is the anonymous/public type byte.
1080 * The third is the format type byte (vari, fixed, or MIME).
1084 CtdlLogPrintf(CTDL_ERR, "Message %ld appears to be corrupted.\n", msgnum);
1088 ret = (struct CtdlMessage *) malloc(sizeof(struct CtdlMessage));
1089 memset(ret, 0, sizeof(struct CtdlMessage));
1091 ret->cm_magic = CTDLMESSAGE_MAGIC;
1092 ret->cm_anon_type = *mptr++; /* Anon type byte */
1093 ret->cm_format_type = *mptr++; /* Format type byte */
1096 * The rest is zero or more arbitrary fields. Load them in.
1097 * We're done when we encounter either a zero-length field or
1098 * have just processed the 'M' (message text) field.
1101 if (mptr >= upper_bound) {
1104 field_header = *mptr++;
1105 ret->cm_fields[field_header] = strdup(mptr);
1107 while (*mptr++ != 0); /* advance to next field */
1109 } while ((mptr < upper_bound) && (field_header != 'M'));
1113 /* Always make sure there's something in the msg text field. If
1114 * it's NULL, the message text is most likely stored separately,
1115 * so go ahead and fetch that. Failing that, just set a dummy
1116 * body so other code doesn't barf.
1118 if ( (ret->cm_fields['M'] == NULL) && (with_body) ) {
1119 dmsgtext = cdb_fetch(CDB_BIGMSGS, &msgnum, sizeof(long));
1120 if (dmsgtext != NULL) {
1121 ret->cm_fields['M'] = strdup(dmsgtext->ptr);
1125 if (ret->cm_fields['M'] == NULL) {
1126 ret->cm_fields['M'] = strdup("\r\n\r\n (no text)\r\n");
1129 /* Perform "before read" hooks (aborting if any return nonzero) */
1130 if (PerformMessageHooks(ret, EVT_BEFOREREAD) > 0) {
1131 CtdlFreeMessage(ret);
1140 * Returns 1 if the supplied pointer points to a valid Citadel message.
1141 * If the pointer is NULL or the magic number check fails, returns 0.
1143 int is_valid_message(struct CtdlMessage *msg) {
1146 if ((msg->cm_magic) != CTDLMESSAGE_MAGIC) {
1147 CtdlLogPrintf(CTDL_WARNING, "is_valid_message() -- self-check failed\n");
1155 * 'Destructor' for struct CtdlMessage
1157 void CtdlFreeMessage(struct CtdlMessage *msg)
1161 if (is_valid_message(msg) == 0)
1163 if (msg != NULL) free (msg);
1167 for (i = 0; i < 256; ++i)
1168 if (msg->cm_fields[i] != NULL) {
1169 free(msg->cm_fields[i]);
1172 msg->cm_magic = 0; /* just in case */
1178 * Pre callback function for multipart/alternative
1180 * NOTE: this differs from the standard behavior for a reason. Normally when
1181 * displaying multipart/alternative you want to show the _last_ usable
1182 * format in the message. Here we show the _first_ one, because it's
1183 * usually text/plain. Since this set of functions is designed for text
1184 * output to non-MIME-aware clients, this is the desired behavior.
1187 void fixed_output_pre(char *name, char *filename, char *partnum, char *disp,
1188 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
1189 char *cbid, void *cbuserdata)
1193 ma = (struct ma_info *)cbuserdata;
1194 CtdlLogPrintf(CTDL_DEBUG, "fixed_output_pre() type=<%s>\n", cbtype);
1195 if (!strcasecmp(cbtype, "multipart/alternative")) {
1199 if (!strcasecmp(cbtype, "message/rfc822")) {
1205 * Post callback function for multipart/alternative
1207 void fixed_output_post(char *name, char *filename, char *partnum, char *disp,
1208 void *content, char *cbtype, char *cbcharset, size_t length,
1209 char *encoding, char *cbid, void *cbuserdata)
1213 ma = (struct ma_info *)cbuserdata;
1214 CtdlLogPrintf(CTDL_DEBUG, "fixed_output_post() type=<%s>\n", cbtype);
1215 if (!strcasecmp(cbtype, "multipart/alternative")) {
1219 if (!strcasecmp(cbtype, "message/rfc822")) {
1225 * Inline callback function for mime parser that wants to display text
1227 void fixed_output(char *name, char *filename, char *partnum, char *disp,
1228 void *content, char *cbtype, char *cbcharset, size_t length,
1229 char *encoding, char *cbid, void *cbuserdata)
1236 ma = (struct ma_info *)cbuserdata;
1238 CtdlLogPrintf(CTDL_DEBUG,
1239 "fixed_output() part %s: %s (%s) (%ld bytes)\n",
1240 partnum, filename, cbtype, (long)length);
1243 * If we're in the middle of a multipart/alternative scope and
1244 * we've already printed another section, skip this one.
1246 if ( (ma->is_ma) && (ma->did_print) ) {
1247 CtdlLogPrintf(CTDL_DEBUG, "Skipping part %s (%s)\n", partnum, cbtype);
1252 if ( (!strcasecmp(cbtype, "text/plain"))
1253 || (IsEmptyStr(cbtype)) ) {
1256 client_write(wptr, length);
1257 if (wptr[length-1] != '\n') {
1264 if (!strcasecmp(cbtype, "text/html")) {
1265 ptr = html_to_ascii(content, length, 80, 0);
1267 client_write(ptr, wlen);
1268 if (ptr[wlen-1] != '\n') {
1275 if (ma->use_fo_hooks) {
1276 if (PerformFixedOutputHooks(cbtype, content, length)) {
1277 /* above function returns nonzero if it handled the part */
1282 if (strncasecmp(cbtype, "multipart/", 10)) {
1283 cprintf("Part %s: %s (%s) (%ld bytes)\r\n",
1284 partnum, filename, cbtype, (long)length);
1290 * The client is elegant and sophisticated and wants to be choosy about
1291 * MIME content types, so figure out which multipart/alternative part
1292 * we're going to send.
1294 * We use a system of weights. When we find a part that matches one of the
1295 * MIME types we've declared as preferential, we can store it in ma->chosen_part
1296 * and then set ma->chosen_pref to that MIME type's position in our preference
1297 * list. If we then hit another match, we only replace the first match if
1298 * the preference value is lower.
1300 void choose_preferred(char *name, char *filename, char *partnum, char *disp,
1301 void *content, char *cbtype, char *cbcharset, size_t length,
1302 char *encoding, char *cbid, void *cbuserdata)
1308 ma = (struct ma_info *)cbuserdata;
1310 // NOTE: REMOVING THIS CONDITIONAL FIXES BUG 220
1311 // http://bugzilla.citadel.org/show_bug.cgi?id=220
1312 // I don't know if there are any side effects! Please TEST TEST TEST
1313 //if (ma->is_ma > 0) {
1315 for (i=0; i<num_tokens(CC->preferred_formats, '|'); ++i) {
1316 extract_token(buf, CC->preferred_formats, i, '|', sizeof buf);
1317 if ( (!strcasecmp(buf, cbtype)) && (!ma->freeze) ) {
1318 if (i < ma->chosen_pref) {
1319 CtdlLogPrintf(CTDL_DEBUG, "Setting chosen part: <%s>\n", partnum);
1320 safestrncpy(ma->chosen_part, partnum, sizeof ma->chosen_part);
1321 ma->chosen_pref = i;
1328 * Now that we've chosen our preferred part, output it.
1330 void output_preferred(char *name, char *filename, char *partnum, char *disp,
1331 void *content, char *cbtype, char *cbcharset, size_t length,
1332 char *encoding, char *cbid, void *cbuserdata)
1336 int add_newline = 0;
1340 ma = (struct ma_info *)cbuserdata;
1342 /* This is not the MIME part you're looking for... */
1343 if (strcasecmp(partnum, ma->chosen_part)) return;
1345 /* If the content-type of this part is in our preferred formats
1346 * list, we can simply output it verbatim.
1348 for (i=0; i<num_tokens(CC->preferred_formats, '|'); ++i) {
1349 extract_token(buf, CC->preferred_formats, i, '|', sizeof buf);
1350 if (!strcasecmp(buf, cbtype)) {
1351 /* Yeah! Go! W00t!! */
1353 text_content = (char *)content;
1354 if (text_content[length-1] != '\n') {
1357 cprintf("Content-type: %s", cbtype);
1358 if (!IsEmptyStr(cbcharset)) {
1359 cprintf("; charset=%s", cbcharset);
1361 cprintf("\nContent-length: %d\n",
1362 (int)(length + add_newline) );
1363 if (!IsEmptyStr(encoding)) {
1364 cprintf("Content-transfer-encoding: %s\n", encoding);
1367 cprintf("Content-transfer-encoding: 7bit\n");
1369 cprintf("X-Citadel-MSG4-Partnum: %s\n", partnum);
1371 client_write(content, length);
1372 if (add_newline) cprintf("\n");
1377 /* No translations required or possible: output as text/plain */
1378 cprintf("Content-type: text/plain\n\n");
1379 fixed_output(name, filename, partnum, disp, content, cbtype, cbcharset,
1380 length, encoding, cbid, cbuserdata);
1385 char desired_section[64];
1392 * Callback function for
1394 void extract_encapsulated_message(char *name, char *filename, char *partnum, char *disp,
1395 void *content, char *cbtype, char *cbcharset, size_t length,
1396 char *encoding, char *cbid, void *cbuserdata)
1398 struct encapmsg *encap;
1400 encap = (struct encapmsg *)cbuserdata;
1402 /* Only proceed if this is the desired section... */
1403 if (!strcasecmp(encap->desired_section, partnum)) {
1404 encap->msglen = length;
1405 encap->msg = malloc(length + 2);
1406 memcpy(encap->msg, content, length);
1416 * Get a message off disk. (returns om_* values found in msgbase.h)
1419 int CtdlOutputMsg(long msg_num, /* message number (local) to fetch */
1420 int mode, /* how would you like that message? */
1421 int headers_only, /* eschew the message body? */
1422 int do_proto, /* do Citadel protocol responses? */
1423 int crlf, /* Use CRLF newlines instead of LF? */
1424 char *section, /* NULL or a message/rfc822 section */
1425 int flags /* should the bessage be exported clean? */
1427 struct CtdlMessage *TheMessage = NULL;
1428 int retcode = om_no_such_msg;
1429 struct encapmsg encap;
1431 CtdlLogPrintf(CTDL_DEBUG, "CtdlOutputMsg() msgnum=%ld, mode=%d, section=%s\n",
1433 (section ? section : "<>")
1436 if ((!(CC->logged_in)) && (!(CC->internal_pgm))) {
1437 if (do_proto) cprintf("%d Not logged in.\n",
1438 ERROR + NOT_LOGGED_IN);
1439 return(om_not_logged_in);
1442 /* FIXME: check message id against msglist for this room */
1445 * Fetch the message from disk. If we're in HEADERS_FAST mode,
1446 * request that we don't even bother loading the body into memory.
1448 if (headers_only == HEADERS_FAST) {
1449 TheMessage = CtdlFetchMessage(msg_num, 0);
1452 TheMessage = CtdlFetchMessage(msg_num, 1);
1455 if (TheMessage == NULL) {
1456 if (do_proto) cprintf("%d Can't locate msg %ld on disk\n",
1457 ERROR + MESSAGE_NOT_FOUND, msg_num);
1458 return(om_no_such_msg);
1461 /* Here is the weird form of this command, to process only an
1462 * encapsulated message/rfc822 section.
1464 if (section) if (!IsEmptyStr(section)) if (strcmp(section, "0")) {
1465 memset(&encap, 0, sizeof encap);
1466 safestrncpy(encap.desired_section, section, sizeof encap.desired_section);
1467 mime_parser(TheMessage->cm_fields['M'],
1469 *extract_encapsulated_message,
1470 NULL, NULL, (void *)&encap, 0
1472 CtdlFreeMessage(TheMessage);
1476 encap.msg[encap.msglen] = 0;
1477 TheMessage = convert_internet_message(encap.msg);
1478 encap.msg = NULL; /* no free() here, TheMessage owns it now */
1480 /* Now we let it fall through to the bottom of this
1481 * function, because TheMessage now contains the
1482 * encapsulated message instead of the top-level
1483 * message. Isn't that neat?
1488 if (do_proto) cprintf("%d msg %ld has no part %s\n",
1489 ERROR + MESSAGE_NOT_FOUND, msg_num, section);
1490 retcode = om_no_such_msg;
1495 /* Ok, output the message now */
1496 retcode = CtdlOutputPreLoadedMsg(TheMessage, mode, headers_only, do_proto, crlf, flags);
1497 CtdlFreeMessage(TheMessage);
1503 char *qp_encode_email_addrs(char *source)
1505 char user[256], node[256], name[256];
1506 const char headerStr[] = "=?UTF-8?Q?";
1510 int need_to_encode = 0;
1516 long nAddrPtrMax = 50;
1521 if (source == NULL) return source;
1522 if (IsEmptyStr(source)) return source;
1524 AddrPtr = malloc (sizeof (long) * nAddrPtrMax);
1525 AddrUtf8 = malloc (sizeof (long) * nAddrPtrMax);
1526 memset(AddrUtf8, 0, sizeof (long) * nAddrPtrMax);
1529 while (!IsEmptyStr (&source[i])) {
1530 if (nColons >= nAddrPtrMax){
1533 ptr = (long *) malloc(sizeof (long) * nAddrPtrMax * 2);
1534 memcpy (ptr, AddrPtr, sizeof (long) * nAddrPtrMax);
1535 free (AddrPtr), AddrPtr = ptr;
1537 ptr = (long *) malloc(sizeof (long) * nAddrPtrMax * 2);
1538 memset(&ptr[nAddrPtrMax], 0,
1539 sizeof (long) * nAddrPtrMax);
1541 memcpy (ptr, AddrUtf8, sizeof (long) * nAddrPtrMax);
1542 free (AddrUtf8), AddrUtf8 = ptr;
1545 if (((unsigned char) source[i] < 32) ||
1546 ((unsigned char) source[i] > 126)) {
1548 AddrUtf8[nColons] = 1;
1550 if (source[i] == '"')
1551 InQuotes = !InQuotes;
1552 if (!InQuotes && source[i] == ',') {
1553 AddrPtr[nColons] = i;
1558 if (need_to_encode == 0) {
1565 EncodedMaxLen = nColons * (sizeof(headerStr) + 3) + SourceLen * 3;
1566 Encoded = (char*) malloc (EncodedMaxLen);
1568 for (i = 0; i < nColons; i++)
1569 source[AddrPtr[i]++] = '\0';
1573 for (i = 0; i < nColons && nPtr != NULL; i++) {
1574 nmax = EncodedMaxLen - (nPtr - Encoded);
1576 process_rfc822_addr(&source[AddrPtr[i]],
1580 /* TODO: libIDN here ! */
1581 if (IsEmptyStr(name)) {
1582 n = snprintf(nPtr, nmax,
1583 (i==0)?"%s@%s" : ",%s@%s",
1587 EncodedName = rfc2047encode(name, strlen(name));
1588 n = snprintf(nPtr, nmax,
1589 (i==0)?"%s <%s@%s>" : ",%s <%s@%s>",
1590 EncodedName, user, node);
1595 n = snprintf(nPtr, nmax,
1596 (i==0)?"%s" : ",%s",
1597 &source[AddrPtr[i]]);
1603 ptr = (char*) malloc(EncodedMaxLen * 2);
1604 memcpy(ptr, Encoded, EncodedMaxLen);
1605 nnPtr = ptr + (nPtr - Encoded), nPtr = nnPtr;
1606 free(Encoded), Encoded = ptr;
1608 i--; /* do it once more with properly lengthened buffer */
1611 for (i = 0; i < nColons; i++)
1612 source[--AddrPtr[i]] = ',';
1619 /* If the last item in a list of recipients was truncated to a partial address,
1620 * remove it completely in order to avoid choking libSieve
1622 void sanitize_truncated_recipient(char *str)
1625 if (num_tokens(str, ',') < 2) return;
1627 int len = strlen(str);
1628 if (len < 900) return;
1629 if (len > 998) str[998] = 0;
1631 char *cptr = strrchr(str, ',');
1634 char *lptr = strchr(cptr, '<');
1635 char *rptr = strchr(cptr, '>');
1637 if ( (lptr) && (rptr) && (rptr > lptr) ) return;
1645 * Get a message off disk. (returns om_* values found in msgbase.h)
1647 int CtdlOutputPreLoadedMsg(
1648 struct CtdlMessage *TheMessage,
1649 int mode, /* how would you like that message? */
1650 int headers_only, /* eschew the message body? */
1651 int do_proto, /* do Citadel protocol responses? */
1652 int crlf, /* Use CRLF newlines instead of LF? */
1653 int flags /* should the bessage be exported clean? */
1657 cit_uint8_t ch, prev_ch;
1659 char display_name[256];
1661 char *nl; /* newline string */
1663 int subject_found = 0;
1666 /* Buffers needed for RFC822 translation. These are all filled
1667 * using functions that are bounds-checked, and therefore we can
1668 * make them substantially smaller than SIZ.
1675 char datestamp[100];
1677 CtdlLogPrintf(CTDL_DEBUG, "CtdlOutputPreLoadedMsg(TheMessage=%s, %d, %d, %d, %d\n",
1678 ((TheMessage == NULL) ? "NULL" : "not null"),
1679 mode, headers_only, do_proto, crlf);
1681 strcpy(mid, "unknown");
1682 nl = (crlf ? "\r\n" : "\n");
1684 if (!is_valid_message(TheMessage)) {
1685 CtdlLogPrintf(CTDL_ERR,
1686 "ERROR: invalid preloaded message for output\n");
1688 return(om_no_such_msg);
1691 /* Are we downloading a MIME component? */
1692 if (mode == MT_DOWNLOAD) {
1693 if (TheMessage->cm_format_type != FMT_RFC822) {
1695 cprintf("%d This is not a MIME message.\n",
1696 ERROR + ILLEGAL_VALUE);
1697 } else if (CC->download_fp != NULL) {
1698 if (do_proto) cprintf(
1699 "%d You already have a download open.\n",
1700 ERROR + RESOURCE_BUSY);
1702 /* Parse the message text component */
1703 mptr = TheMessage->cm_fields['M'];
1704 mime_parser(mptr, NULL, *mime_download, NULL, NULL, NULL, 0);
1705 /* If there's no file open by this time, the requested
1706 * section wasn't found, so print an error
1708 if (CC->download_fp == NULL) {
1709 if (do_proto) cprintf(
1710 "%d Section %s not found.\n",
1711 ERROR + FILE_NOT_FOUND,
1712 CC->download_desired_section);
1715 return((CC->download_fp != NULL) ? om_ok : om_mime_error);
1718 /* MT_SPEW_SECTION is like MT_DOWNLOAD except it outputs the whole MIME part
1719 * in a single server operation instead of opening a download file.
1721 if (mode == MT_SPEW_SECTION) {
1722 if (TheMessage->cm_format_type != FMT_RFC822) {
1724 cprintf("%d This is not a MIME message.\n",
1725 ERROR + ILLEGAL_VALUE);
1727 /* Parse the message text component */
1730 mptr = TheMessage->cm_fields['M'];
1731 mime_parser(mptr, NULL, *mime_spew_section, NULL, NULL, (void *)&found_it, 0);
1732 /* If section wasn't found, print an error
1735 if (do_proto) cprintf(
1736 "%d Section %s not found.\n",
1737 ERROR + FILE_NOT_FOUND,
1738 CC->download_desired_section);
1741 return((CC->download_fp != NULL) ? om_ok : om_mime_error);
1744 /* now for the user-mode message reading loops */
1745 if (do_proto) cprintf("%d msg:\n", LISTING_FOLLOWS);
1747 /* Does the caller want to skip the headers? */
1748 if (headers_only == HEADERS_NONE) goto START_TEXT;
1750 /* Tell the client which format type we're using. */
1751 if ( (mode == MT_CITADEL) && (do_proto) ) {
1752 cprintf("type=%d\n", TheMessage->cm_format_type);
1755 /* nhdr=yes means that we're only displaying headers, no body */
1756 if ( (TheMessage->cm_anon_type == MES_ANONONLY)
1757 && ((mode == MT_CITADEL) || (mode == MT_MIME))
1760 cprintf("nhdr=yes\n");
1763 /* begin header processing loop for Citadel message format */
1765 if ((mode == MT_CITADEL) || (mode == MT_MIME)) {
1767 safestrncpy(display_name, "<unknown>", sizeof display_name);
1768 if (TheMessage->cm_fields['A']) {
1769 strcpy(buf, TheMessage->cm_fields['A']);
1770 if (TheMessage->cm_anon_type == MES_ANONONLY) {
1771 safestrncpy(display_name, "****", sizeof display_name);
1773 else if (TheMessage->cm_anon_type == MES_ANONOPT) {
1774 safestrncpy(display_name, "anonymous", sizeof display_name);
1777 safestrncpy(display_name, buf, sizeof display_name);
1779 if ((is_room_aide())
1780 && ((TheMessage->cm_anon_type == MES_ANONONLY)
1781 || (TheMessage->cm_anon_type == MES_ANONOPT))) {
1782 size_t tmp = strlen(display_name);
1783 snprintf(&display_name[tmp],
1784 sizeof display_name - tmp,
1789 /* Don't show Internet address for users on the
1790 * local Citadel network.
1793 if (TheMessage->cm_fields['N'] != NULL)
1794 if (!IsEmptyStr(TheMessage->cm_fields['N']))
1795 if (haschar(TheMessage->cm_fields['N'], '.') == 0) {
1799 /* Now spew the header fields in the order we like them. */
1800 safestrncpy(allkeys, FORDER, sizeof allkeys);
1801 for (i=0; i<strlen(allkeys); ++i) {
1802 k = (int) allkeys[i];
1804 if ( (TheMessage->cm_fields[k] != NULL)
1805 && (msgkeys[k] != NULL) ) {
1806 if ((k == 'V') || (k == 'R') || (k == 'Y')) {
1807 sanitize_truncated_recipient(TheMessage->cm_fields[k]);
1810 if (do_proto) cprintf("%s=%s\n",
1814 else if ((k == 'F') && (suppress_f)) {
1817 /* Masquerade display name if needed */
1819 if (do_proto) cprintf("%s=%s\n",
1821 TheMessage->cm_fields[k]
1830 /* begin header processing loop for RFC822 transfer format */
1835 strcpy(snode, NODENAME);
1836 if (mode == MT_RFC822) {
1837 for (i = 0; i < 256; ++i) {
1838 if (TheMessage->cm_fields[i]) {
1839 mptr = mpptr = TheMessage->cm_fields[i];
1842 safestrncpy(luser, mptr, sizeof luser);
1843 safestrncpy(suser, mptr, sizeof suser);
1845 else if (i == 'Y') {
1846 if ((flags & QP_EADDR) != 0) {
1847 mptr = qp_encode_email_addrs(mptr);
1849 sanitize_truncated_recipient(mptr);
1850 cprintf("CC: %s%s", mptr, nl);
1852 else if (i == 'P') {
1853 cprintf("Return-Path: %s%s", mptr, nl);
1855 else if (i == 'L') {
1856 cprintf("List-ID: %s%s", mptr, nl);
1858 else if (i == 'V') {
1859 if ((flags & QP_EADDR) != 0)
1860 mptr = qp_encode_email_addrs(mptr);
1861 cprintf("Envelope-To: %s%s", mptr, nl);
1863 else if (i == 'U') {
1864 cprintf("Subject: %s%s", mptr, nl);
1868 safestrncpy(mid, mptr, sizeof mid);
1870 safestrncpy(fuser, mptr, sizeof fuser);
1871 /* else if (i == 'O')
1872 cprintf("X-Citadel-Room: %s%s",
1875 safestrncpy(snode, mptr, sizeof snode);
1878 if (haschar(mptr, '@') == 0)
1880 sanitize_truncated_recipient(mptr);
1881 cprintf("To: %s@%s", mptr, config.c_fqdn);
1886 if ((flags & QP_EADDR) != 0) {
1887 mptr = qp_encode_email_addrs(mptr);
1889 sanitize_truncated_recipient(mptr);
1890 cprintf("To: %s", mptr);
1894 else if (i == 'T') {
1895 datestring(datestamp, sizeof datestamp,
1896 atol(mptr), DATESTRING_RFC822);
1897 cprintf("Date: %s%s", datestamp, nl);
1899 else if (i == 'W') {
1900 cprintf("References: ");
1901 k = num_tokens(mptr, '|');
1902 for (j=0; j<k; ++j) {
1903 extract_token(buf, mptr, j, '|', sizeof buf);
1904 cprintf("<%s>", buf);
1917 if (subject_found == 0) {
1918 cprintf("Subject: (no subject)%s", nl);
1922 for (i=0; !IsEmptyStr(&suser[i]); ++i) {
1923 suser[i] = tolower(suser[i]);
1924 if (!isalnum(suser[i])) suser[i]='_';
1927 if (mode == MT_RFC822) {
1928 if (!strcasecmp(snode, NODENAME)) {
1929 safestrncpy(snode, FQDN, sizeof snode);
1932 /* Construct a fun message id */
1933 cprintf("Message-ID: <%s", mid);/// todo: this possibly breaks threadding mails.
1934 if (strchr(mid, '@')==NULL) {
1935 cprintf("@%s", snode);
1939 if (!is_room_aide() && (TheMessage->cm_anon_type == MES_ANONONLY)) {
1940 cprintf("From: \"----\" <x@x.org>%s", nl);
1942 else if (!is_room_aide() && (TheMessage->cm_anon_type == MES_ANONOPT)) {
1943 cprintf("From: \"anonymous\" <x@x.org>%s", nl);
1945 else if (!IsEmptyStr(fuser)) {
1946 cprintf("From: \"%s\" <%s>%s", luser, fuser, nl);
1949 cprintf("From: \"%s\" <%s@%s>%s", luser, suser, snode, nl);
1952 /* Blank line signifying RFC822 end-of-headers */
1953 if (TheMessage->cm_format_type != FMT_RFC822) {
1958 /* end header processing loop ... at this point, we're in the text */
1960 if (headers_only == HEADERS_FAST) goto DONE;
1961 mptr = TheMessage->cm_fields['M'];
1963 /* Tell the client about the MIME parts in this message */
1964 if (TheMessage->cm_format_type == FMT_RFC822) {
1965 if ( (mode == MT_CITADEL) || (mode == MT_MIME) ) {
1966 memset(&ma, 0, sizeof(struct ma_info));
1967 mime_parser(mptr, NULL,
1968 (do_proto ? *list_this_part : NULL),
1969 (do_proto ? *list_this_pref : NULL),
1970 (do_proto ? *list_this_suff : NULL),
1973 else if (mode == MT_RFC822) { /* unparsed RFC822 dump */
1974 char *start_of_text = NULL;
1975 start_of_text = strstr(mptr, "\n\r\n");
1976 if (start_of_text == NULL) start_of_text = strstr(mptr, "\n\n");
1977 if (start_of_text == NULL) start_of_text = mptr;
1979 start_of_text = strstr(start_of_text, "\n");
1984 int nllen = strlen(nl);
1986 while (ch=*mptr, ch!=0) {
1992 ((headers_only == HEADERS_NONE) && (mptr >= start_of_text))
1993 || ((headers_only == HEADERS_ONLY) && (mptr < start_of_text))
1994 || ((headers_only != HEADERS_NONE) && (headers_only != HEADERS_ONLY))
1997 sprintf(&outbuf[outlen], "%s", nl);
2001 outbuf[outlen++] = ch;
2005 if (flags & ESC_DOT)
2007 if ((prev_ch == 10) && (ch == '.') && ((*(mptr+1) == 13) || (*(mptr+1) == 10)))
2009 outbuf[outlen++] = '.';
2014 if (outlen > 1000) {
2015 client_write(outbuf, outlen);
2020 client_write(outbuf, outlen);
2028 if (headers_only == HEADERS_ONLY) {
2032 /* signify start of msg text */
2033 if ( (mode == MT_CITADEL) || (mode == MT_MIME) ) {
2034 if (do_proto) cprintf("text\n");
2037 /* If the format type on disk is 1 (fixed-format), then we want
2038 * everything to be output completely literally ... regardless of
2039 * what message transfer format is in use.
2041 if (TheMessage->cm_format_type == FMT_FIXED) {
2043 if (mode == MT_MIME) {
2044 cprintf("Content-type: text/plain\n\n");
2048 while (ch = *mptr++, ch > 0) {
2051 if ((ch == 10) || (buflen > 250)) {
2053 cprintf("%s%s", buf, nl);
2062 if (!IsEmptyStr(buf))
2063 cprintf("%s%s", buf, nl);
2066 /* If the message on disk is format 0 (Citadel vari-format), we
2067 * output using the formatter at 80 columns. This is the final output
2068 * form if the transfer format is RFC822, but if the transfer format
2069 * is Citadel proprietary, it'll still work, because the indentation
2070 * for new paragraphs is correct and the client will reformat the
2071 * message to the reader's screen width.
2073 if (TheMessage->cm_format_type == FMT_CITADEL) {
2074 if (mode == MT_MIME) {
2075 cprintf("Content-type: text/x-citadel-variformat\n\n");
2077 memfmout(mptr, 0, nl);
2080 /* If the message on disk is format 4 (MIME), we've gotta hand it
2081 * off to the MIME parser. The client has already been told that
2082 * this message is format 1 (fixed format), so the callback function
2083 * we use will display those parts as-is.
2085 if (TheMessage->cm_format_type == FMT_RFC822) {
2086 memset(&ma, 0, sizeof(struct ma_info));
2088 if (mode == MT_MIME) {
2089 ma.use_fo_hooks = 0;
2090 strcpy(ma.chosen_part, "1");
2091 ma.chosen_pref = 9999;
2092 mime_parser(mptr, NULL,
2093 *choose_preferred, *fixed_output_pre,
2094 *fixed_output_post, (void *)&ma, 0);
2095 mime_parser(mptr, NULL,
2096 *output_preferred, NULL, NULL, (void *)&ma, CC->msg4_dont_decode);
2099 ma.use_fo_hooks = 1;
2100 mime_parser(mptr, NULL,
2101 *fixed_output, *fixed_output_pre,
2102 *fixed_output_post, (void *)&ma, 0);
2107 DONE: /* now we're done */
2108 if (do_proto) cprintf("000\n");
2115 * display a message (mode 0 - Citadel proprietary)
2117 void cmd_msg0(char *cmdbuf)
2120 int headers_only = HEADERS_ALL;
2122 msgid = extract_long(cmdbuf, 0);
2123 headers_only = extract_int(cmdbuf, 1);
2125 CtdlOutputMsg(msgid, MT_CITADEL, headers_only, 1, 0, NULL, 0);
2131 * display a message (mode 2 - RFC822)
2133 void cmd_msg2(char *cmdbuf)
2136 int headers_only = HEADERS_ALL;
2138 msgid = extract_long(cmdbuf, 0);
2139 headers_only = extract_int(cmdbuf, 1);
2141 CtdlOutputMsg(msgid, MT_RFC822, headers_only, 1, 1, NULL, 0);
2147 * display a message (mode 3 - IGnet raw format - internal programs only)
2149 void cmd_msg3(char *cmdbuf)
2152 struct CtdlMessage *msg = NULL;
2155 if (CC->internal_pgm == 0) {
2156 cprintf("%d This command is for internal programs only.\n",
2157 ERROR + HIGHER_ACCESS_REQUIRED);
2161 msgnum = extract_long(cmdbuf, 0);
2162 msg = CtdlFetchMessage(msgnum, 1);
2164 cprintf("%d Message %ld not found.\n",
2165 ERROR + MESSAGE_NOT_FOUND, msgnum);
2169 serialize_message(&smr, msg);
2170 CtdlFreeMessage(msg);
2173 cprintf("%d Unable to serialize message\n",
2174 ERROR + INTERNAL_ERROR);
2178 cprintf("%d %ld\n", BINARY_FOLLOWS, (long)smr.len);
2179 client_write((char *)smr.ser, (int)smr.len);
2186 * Display a message using MIME content types
2188 void cmd_msg4(char *cmdbuf)
2193 msgid = extract_long(cmdbuf, 0);
2194 extract_token(section, cmdbuf, 1, '|', sizeof section);
2195 CtdlOutputMsg(msgid, MT_MIME, 0, 1, 0, (section[0] ? section : NULL) , 0);
2201 * Client tells us its preferred message format(s)
2203 void cmd_msgp(char *cmdbuf)
2205 if (!strcasecmp(cmdbuf, "dont_decode")) {
2206 CC->msg4_dont_decode = 1;
2207 cprintf("%d MSG4 will not pre-decode messages.\n", CIT_OK);
2210 safestrncpy(CC->preferred_formats, cmdbuf, sizeof(CC->preferred_formats));
2211 cprintf("%d Preferred MIME formats have been set.\n", CIT_OK);
2217 * Open a component of a MIME message as a download file
2219 void cmd_opna(char *cmdbuf)
2222 char desired_section[128];
2224 msgid = extract_long(cmdbuf, 0);
2225 extract_token(desired_section, cmdbuf, 1, '|', sizeof desired_section);
2226 safestrncpy(CC->download_desired_section, desired_section,
2227 sizeof CC->download_desired_section);
2228 CtdlOutputMsg(msgid, MT_DOWNLOAD, 0, 1, 1, NULL, 0);
2233 * Open a component of a MIME message and transmit it all at once
2235 void cmd_dlat(char *cmdbuf)
2238 char desired_section[128];
2240 msgid = extract_long(cmdbuf, 0);
2241 extract_token(desired_section, cmdbuf, 1, '|', sizeof desired_section);
2242 safestrncpy(CC->download_desired_section, desired_section,
2243 sizeof CC->download_desired_section);
2244 CtdlOutputMsg(msgid, MT_SPEW_SECTION, 0, 1, 1, NULL, 0);
2249 * Save one or more message pointers into a specified room
2250 * (Returns 0 for success, nonzero for failure)
2251 * roomname may be NULL to use the current room
2253 * Note that the 'supplied_msg' field may be set to NULL, in which case
2254 * the message will be fetched from disk, by number, if we need to perform
2255 * replication checks. This adds an additional database read, so if the
2256 * caller already has the message in memory then it should be supplied. (Obviously
2257 * this mode of operation only works if we're saving a single message.)
2259 int CtdlSaveMsgPointersInRoom(char *roomname, long newmsgidlist[], int num_newmsgs,
2260 int do_repl_check, struct CtdlMessage *supplied_msg)
2263 char hold_rm[ROOMNAMELEN];
2264 struct cdbdata *cdbfr;
2267 long highest_msg = 0L;
2270 struct CtdlMessage *msg = NULL;
2272 long *msgs_to_be_merged = NULL;
2273 int num_msgs_to_be_merged = 0;
2275 CtdlLogPrintf(CTDL_DEBUG,
2276 "CtdlSaveMsgPointersInRoom(room=%s, num_msgs=%d, repl=%d)\n",
2277 roomname, num_newmsgs, do_repl_check);
2279 strcpy(hold_rm, CC->room.QRname);
2282 if (newmsgidlist == NULL) return(ERROR + INTERNAL_ERROR);
2283 if (num_newmsgs < 1) return(ERROR + INTERNAL_ERROR);
2284 if (num_newmsgs > 1) supplied_msg = NULL;
2286 /* Now the regular stuff */
2287 if (lgetroom(&CC->room,
2288 ((roomname != NULL) ? roomname : CC->room.QRname) )
2290 CtdlLogPrintf(CTDL_ERR, "No such room <%s>\n", roomname);
2291 return(ERROR + ROOM_NOT_FOUND);
2295 msgs_to_be_merged = malloc(sizeof(long) * num_newmsgs);
2296 num_msgs_to_be_merged = 0;
2299 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long));
2300 if (cdbfr == NULL) {
2304 msglist = (long *) cdbfr->ptr;
2305 cdbfr->ptr = NULL; /* CtdlSaveMsgPointerInRoom() now owns this memory */
2306 num_msgs = cdbfr->len / sizeof(long);
2311 /* Create a list of msgid's which were supplied by the caller, but do
2312 * not already exist in the target room. It is absolutely taboo to
2313 * have more than one reference to the same message in a room.
2315 for (i=0; i<num_newmsgs; ++i) {
2317 if (num_msgs > 0) for (j=0; j<num_msgs; ++j) {
2318 if (msglist[j] == newmsgidlist[i]) {
2323 msgs_to_be_merged[num_msgs_to_be_merged++] = newmsgidlist[i];
2327 CtdlLogPrintf(9, "%d unique messages to be merged\n", num_msgs_to_be_merged);
2330 * Now merge the new messages
2332 msglist = realloc(msglist, (sizeof(long) * (num_msgs + num_msgs_to_be_merged)) );
2333 if (msglist == NULL) {
2334 CtdlLogPrintf(CTDL_ALERT, "ERROR: can't realloc message list!\n");
2336 memcpy(&msglist[num_msgs], msgs_to_be_merged, (sizeof(long) * num_msgs_to_be_merged) );
2337 num_msgs += num_msgs_to_be_merged;
2339 /* Sort the message list, so all the msgid's are in order */
2340 num_msgs = sort_msglist(msglist, num_msgs);
2342 /* Determine the highest message number */
2343 highest_msg = msglist[num_msgs - 1];
2345 /* Write it back to disk. */
2346 cdb_store(CDB_MSGLISTS, &CC->room.QRnumber, (int)sizeof(long),
2347 msglist, (int)(num_msgs * sizeof(long)));
2349 /* Free up the memory we used. */
2352 /* Update the highest-message pointer and unlock the room. */
2353 CC->room.QRhighest = highest_msg;
2354 lputroom(&CC->room);
2356 /* Perform replication checks if necessary */
2357 if ( (DoesThisRoomNeedEuidIndexing(&CC->room)) && (do_repl_check) ) {
2358 CtdlLogPrintf(CTDL_DEBUG, "CtdlSaveMsgPointerInRoom() doing repl checks\n");
2360 for (i=0; i<num_msgs_to_be_merged; ++i) {
2361 msgid = msgs_to_be_merged[i];
2363 if (supplied_msg != NULL) {
2367 msg = CtdlFetchMessage(msgid, 0);
2371 ReplicationChecks(msg);
2373 /* If the message has an Exclusive ID, index that... */
2374 if (msg->cm_fields['E'] != NULL) {
2375 index_message_by_euid(msg->cm_fields['E'], &CC->room, msgid);
2378 /* Free up the memory we may have allocated */
2379 if (msg != supplied_msg) {
2380 CtdlFreeMessage(msg);
2388 CtdlLogPrintf(CTDL_DEBUG, "CtdlSaveMsgPointerInRoom() skips repl checks\n");
2391 /* Submit this room for processing by hooks */
2392 PerformRoomHooks(&CC->room);
2394 /* Go back to the room we were in before we wandered here... */
2395 getroom(&CC->room, hold_rm);
2397 /* Bump the reference count for all messages which were merged */
2398 for (i=0; i<num_msgs_to_be_merged; ++i) {
2399 AdjRefCount(msgs_to_be_merged[i], +1);
2402 /* Free up memory... */
2403 if (msgs_to_be_merged != NULL) {
2404 free(msgs_to_be_merged);
2407 /* Return success. */
2413 * This is the same as CtdlSaveMsgPointersInRoom() but it only accepts
2416 int CtdlSaveMsgPointerInRoom(char *roomname, long msgid,
2417 int do_repl_check, struct CtdlMessage *supplied_msg)
2419 return CtdlSaveMsgPointersInRoom(roomname, &msgid, 1, do_repl_check, supplied_msg);
2426 * Message base operation to save a new message to the message store
2427 * (returns new message number)
2429 * This is the back end for CtdlSubmitMsg() and should not be directly
2430 * called by server-side modules.
2433 long send_message(struct CtdlMessage *msg) {
2441 /* Get a new message number */
2442 newmsgid = get_new_message_number();
2443 snprintf(msgidbuf, sizeof msgidbuf, "%010ld@%s", newmsgid, config.c_fqdn);
2445 /* Generate an ID if we don't have one already */
2446 if (msg->cm_fields['I']==NULL) {
2447 msg->cm_fields['I'] = strdup(msgidbuf);
2450 /* If the message is big, set its body aside for storage elsewhere */
2451 if (msg->cm_fields['M'] != NULL) {
2452 if (strlen(msg->cm_fields['M']) > BIGMSG) {
2454 holdM = msg->cm_fields['M'];
2455 msg->cm_fields['M'] = NULL;
2459 /* Serialize our data structure for storage in the database */
2460 serialize_message(&smr, msg);
2463 msg->cm_fields['M'] = holdM;
2467 cprintf("%d Unable to serialize message\n",
2468 ERROR + INTERNAL_ERROR);
2472 /* Write our little bundle of joy into the message base */
2473 if (cdb_store(CDB_MSGMAIN, &newmsgid, (int)sizeof(long),
2474 smr.ser, smr.len) < 0) {
2475 CtdlLogPrintf(CTDL_ERR, "Can't store message\n");
2479 cdb_store(CDB_BIGMSGS,
2489 /* Free the memory we used for the serialized message */
2492 /* Return the *local* message ID to the caller
2493 * (even if we're storing an incoming network message)
2501 * Serialize a struct CtdlMessage into the format used on disk and network.
2503 * This function loads up a "struct ser_ret" (defined in server.h) which
2504 * contains the length of the serialized message and a pointer to the
2505 * serialized message in memory. THE LATTER MUST BE FREED BY THE CALLER.
2507 void serialize_message(struct ser_ret *ret, /* return values */
2508 struct CtdlMessage *msg) /* unserialized msg */
2510 size_t wlen, fieldlen;
2512 static char *forder = FORDER;
2515 * Check for valid message format
2517 if (is_valid_message(msg) == 0) {
2518 CtdlLogPrintf(CTDL_ERR, "serialize_message() aborting due to invalid message\n");
2525 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL)
2526 ret->len = ret->len +
2527 strlen(msg->cm_fields[(int)forder[i]]) + 2;
2529 ret->ser = malloc(ret->len);
2530 if (ret->ser == NULL) {
2531 CtdlLogPrintf(CTDL_ERR, "serialize_message() malloc(%ld) failed: %s\n",
2532 (long)ret->len, strerror(errno));
2539 ret->ser[1] = msg->cm_anon_type;
2540 ret->ser[2] = msg->cm_format_type;
2543 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL) {
2544 fieldlen = strlen(msg->cm_fields[(int)forder[i]]);
2545 ret->ser[wlen++] = (char)forder[i];
2546 safestrncpy((char *)&ret->ser[wlen], msg->cm_fields[(int)forder[i]], fieldlen+1);
2547 wlen = wlen + fieldlen + 1;
2549 if (ret->len != wlen) CtdlLogPrintf(CTDL_ERR, "ERROR: len=%ld wlen=%ld\n",
2550 (long)ret->len, (long)wlen);
2557 * Serialize a struct CtdlMessage into the format used on disk and network.
2559 * This function loads up a "struct ser_ret" (defined in server.h) which
2560 * contains the length of the serialized message and a pointer to the
2561 * serialized message in memory. THE LATTER MUST BE FREED BY THE CALLER.
2563 void dump_message(struct CtdlMessage *msg, /* unserialized msg */
2564 long Siz) /* how many chars ? */
2568 static char *forder = FORDER;
2572 * Check for valid message format
2574 if (is_valid_message(msg) == 0) {
2575 CtdlLogPrintf(CTDL_ERR, "dump_message() aborting due to invalid message\n");
2579 buf = (char*) malloc (Siz + 1);
2583 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL) {
2584 snprintf (buf, Siz, " msg[%c] = %s ...\n", (char) forder[i],
2585 msg->cm_fields[(int)forder[i]]);
2586 client_write (buf, strlen(buf));
2595 * Check to see if any messages already exist in the current room which
2596 * carry the same Exclusive ID as this one. If any are found, delete them.
2598 void ReplicationChecks(struct CtdlMessage *msg) {
2599 long old_msgnum = (-1L);
2601 if (DoesThisRoomNeedEuidIndexing(&CC->room) == 0) return;
2603 CtdlLogPrintf(CTDL_DEBUG, "Performing replication checks in <%s>\n",
2606 /* No exclusive id? Don't do anything. */
2607 if (msg == NULL) return;
2608 if (msg->cm_fields['E'] == NULL) return;
2609 if (IsEmptyStr(msg->cm_fields['E'])) return;
2610 /*CtdlLogPrintf(CTDL_DEBUG, "Exclusive ID: <%s> for room <%s>\n",
2611 msg->cm_fields['E'], CC->room.QRname);*/
2613 old_msgnum = locate_message_by_euid(msg->cm_fields['E'], &CC->room);
2614 if (old_msgnum > 0L) {
2615 CtdlLogPrintf(CTDL_DEBUG, "ReplicationChecks() replacing message %ld\n", old_msgnum);
2616 CtdlDeleteMessages(CC->room.QRname, &old_msgnum, 1, "");
2623 * Save a message to disk and submit it into the delivery system.
2625 long CtdlSubmitMsg(struct CtdlMessage *msg, /* message to save */
2626 struct recptypes *recps, /* recipients (if mail) */
2627 char *force, /* force a particular room? */
2628 int flags /* should the bessage be exported clean? */
2630 char submit_filename[128];
2631 char generated_timestamp[32];
2632 char hold_rm[ROOMNAMELEN];
2633 char actual_rm[ROOMNAMELEN];
2634 char force_room[ROOMNAMELEN];
2635 char content_type[SIZ]; /* We have to learn this */
2636 char recipient[SIZ];
2639 struct ctdluser userbuf;
2641 struct MetaData smi;
2642 FILE *network_fp = NULL;
2643 static int seqnum = 1;
2644 struct CtdlMessage *imsg = NULL;
2646 size_t instr_alloc = 0;
2648 char *hold_R, *hold_D;
2649 char *collected_addresses = NULL;
2650 struct addresses_to_be_filed *aptr = NULL;
2651 char *saved_rfc822_version = NULL;
2652 int qualified_for_journaling = 0;
2653 struct CitContext *CCC = CC; /* CachedCitContext - performance boost */
2654 char bounce_to[1024] = "";
2657 CtdlLogPrintf(CTDL_DEBUG, "CtdlSubmitMsg() called\n");
2658 if (is_valid_message(msg) == 0) return(-1); /* self check */
2660 /* If this message has no timestamp, we take the liberty of
2661 * giving it one, right now.
2663 if (msg->cm_fields['T'] == NULL) {
2664 snprintf(generated_timestamp, sizeof generated_timestamp, "%ld", (long)time(NULL));
2665 msg->cm_fields['T'] = strdup(generated_timestamp);
2668 /* If this message has no path, we generate one.
2670 if (msg->cm_fields['P'] == NULL) {
2671 if (msg->cm_fields['A'] != NULL) {
2672 msg->cm_fields['P'] = strdup(msg->cm_fields['A']);
2673 for (a=0; !IsEmptyStr(&msg->cm_fields['P'][a]); ++a) {
2674 if (isspace(msg->cm_fields['P'][a])) {
2675 msg->cm_fields['P'][a] = ' ';
2680 msg->cm_fields['P'] = strdup("unknown");
2684 if (force == NULL) {
2685 strcpy(force_room, "");
2688 strcpy(force_room, force);
2691 /* Learn about what's inside, because it's what's inside that counts */
2692 if (msg->cm_fields['M'] == NULL) {
2693 CtdlLogPrintf(CTDL_ERR, "ERROR: attempt to save message with NULL body\n");
2697 switch (msg->cm_format_type) {
2699 strcpy(content_type, "text/x-citadel-variformat");
2702 strcpy(content_type, "text/plain");
2705 strcpy(content_type, "text/plain");
2706 mptr = bmstrcasestr(msg->cm_fields['M'], "Content-type:");
2709 safestrncpy(content_type, &mptr[13], sizeof content_type);
2710 striplt(content_type);
2711 aptr = content_type;
2712 while (!IsEmptyStr(aptr)) {
2724 /* Goto the correct room */
2725 CtdlLogPrintf(CTDL_DEBUG, "Selected room %s\n", (recps) ? CCC->room.QRname : SENTITEMS);
2726 strcpy(hold_rm, CCC->room.QRname);
2727 strcpy(actual_rm, CCC->room.QRname);
2728 if (recps != NULL) {
2729 strcpy(actual_rm, SENTITEMS);
2732 /* If the user is a twit, move to the twit room for posting */
2734 if (CCC->user.axlevel == 2) {
2735 strcpy(hold_rm, actual_rm);
2736 strcpy(actual_rm, config.c_twitroom);
2737 CtdlLogPrintf(CTDL_DEBUG, "Diverting to twit room\n");
2741 /* ...or if this message is destined for Aide> then go there. */
2742 if (!IsEmptyStr(force_room)) {
2743 strcpy(actual_rm, force_room);
2746 CtdlLogPrintf(CTDL_DEBUG, "Final selection: %s\n", actual_rm);
2747 if (strcasecmp(actual_rm, CCC->room.QRname)) {
2748 /* getroom(&CCC->room, actual_rm); */
2749 usergoto(actual_rm, 0, 1, NULL, NULL);
2753 * If this message has no O (room) field, generate one.
2755 if (msg->cm_fields['O'] == NULL) {
2756 msg->cm_fields['O'] = strdup(CCC->room.QRname);
2759 /* Perform "before save" hooks (aborting if any return nonzero) */
2760 CtdlLogPrintf(CTDL_DEBUG, "Performing before-save hooks\n");
2761 if (PerformMessageHooks(msg, EVT_BEFORESAVE) > 0) return(-3);
2764 * If this message has an Exclusive ID, and the room is replication
2765 * checking enabled, then do replication checks.
2767 if (DoesThisRoomNeedEuidIndexing(&CCC->room)) {
2768 ReplicationChecks(msg);
2771 /* Save it to disk */
2772 CtdlLogPrintf(CTDL_DEBUG, "Saving to disk\n");
2773 newmsgid = send_message(msg);
2774 if (newmsgid <= 0L) return(-5);
2776 /* Write a supplemental message info record. This doesn't have to
2777 * be a critical section because nobody else knows about this message
2780 CtdlLogPrintf(CTDL_DEBUG, "Creating MetaData record\n");
2781 memset(&smi, 0, sizeof(struct MetaData));
2782 smi.meta_msgnum = newmsgid;
2783 smi.meta_refcount = 0;
2784 safestrncpy(smi.meta_content_type, content_type,
2785 sizeof smi.meta_content_type);
2788 * Measure how big this message will be when rendered as RFC822.
2789 * We do this for two reasons:
2790 * 1. We need the RFC822 length for the new metadata record, so the
2791 * POP and IMAP services don't have to calculate message lengths
2792 * while the user is waiting (multiplied by potentially hundreds
2793 * or thousands of messages).
2794 * 2. If journaling is enabled, we will need an RFC822 version of the
2795 * message to attach to the journalized copy.
2797 if (CCC->redirect_buffer != NULL) {
2798 CtdlLogPrintf(CTDL_ALERT, "CCC->redirect_buffer is not NULL during message submission!\n");
2801 CCC->redirect_buffer = malloc(SIZ);
2802 CCC->redirect_len = 0;
2803 CCC->redirect_alloc = SIZ;
2804 CtdlOutputPreLoadedMsg(msg, MT_RFC822, HEADERS_ALL, 0, 1, QP_EADDR);
2805 smi.meta_rfc822_length = CCC->redirect_len;
2806 saved_rfc822_version = CCC->redirect_buffer;
2807 CCC->redirect_buffer = NULL;
2808 CCC->redirect_len = 0;
2809 CCC->redirect_alloc = 0;
2813 /* Now figure out where to store the pointers */
2814 CtdlLogPrintf(CTDL_DEBUG, "Storing pointers\n");
2816 /* If this is being done by the networker delivering a private
2817 * message, we want to BYPASS saving the sender's copy (because there
2818 * is no local sender; it would otherwise go to the Trashcan).
2820 if ((!CCC->internal_pgm) || (recps == NULL)) {
2821 if (CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 1, msg) != 0) {
2822 CtdlLogPrintf(CTDL_ERR, "ERROR saving message pointer!\n");
2823 CtdlSaveMsgPointerInRoom(config.c_aideroom, newmsgid, 0, msg);
2827 /* For internet mail, drop a copy in the outbound queue room */
2828 if ((recps != NULL) && (recps->num_internet > 0)) {
2829 CtdlSaveMsgPointerInRoom(SMTP_SPOOLOUT_ROOM, newmsgid, 0, msg);
2832 /* If other rooms are specified, drop them there too. */
2833 if ((recps != NULL) && (recps->num_room > 0))
2834 for (i=0; i<num_tokens(recps->recp_room, '|'); ++i) {
2835 extract_token(recipient, recps->recp_room, i,
2836 '|', sizeof recipient);
2837 CtdlLogPrintf(CTDL_DEBUG, "Delivering to room <%s>\n", recipient);
2838 CtdlSaveMsgPointerInRoom(recipient, newmsgid, 0, msg);
2841 /* Bump this user's messages posted counter. */
2842 CtdlLogPrintf(CTDL_DEBUG, "Updating user\n");
2843 lgetuser(&CCC->user, CCC->curr_user);
2844 CCC->user.posted = CCC->user.posted + 1;
2845 lputuser(&CCC->user);
2847 /* Decide where bounces need to be delivered */
2848 if ((recps != NULL) && (recps->bounce_to != NULL)) {
2849 safestrncpy(bounce_to, recps->bounce_to, sizeof bounce_to);
2851 else if (CCC->logged_in) {
2852 snprintf(bounce_to, sizeof bounce_to, "%s@%s", CCC->user.fullname, config.c_nodename);
2855 snprintf(bounce_to, sizeof bounce_to, "%s@%s", msg->cm_fields['A'], msg->cm_fields['N']);
2858 /* If this is private, local mail, make a copy in the
2859 * recipient's mailbox and bump the reference count.
2861 if ((recps != NULL) && (recps->num_local > 0))
2862 for (i=0; i<num_tokens(recps->recp_local, '|'); ++i) {
2863 extract_token(recipient, recps->recp_local, i,
2864 '|', sizeof recipient);
2865 CtdlLogPrintf(CTDL_DEBUG, "Delivering private local mail to <%s>\n",
2867 if (getuser(&userbuf, recipient) == 0) {
2868 // Add a flag so the Funambol module knows its mail
2869 msg->cm_fields['W'] = strdup(recipient);
2870 MailboxName(actual_rm, sizeof actual_rm, &userbuf, MAILROOM);
2871 CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 0, msg);
2872 BumpNewMailCounter(userbuf.usernum);
2873 if (!IsEmptyStr(config.c_funambol_host) || !IsEmptyStr(config.c_pager_program)) {
2874 /* Generate a instruction message for the Funambol notification
2875 * server, in the same style as the SMTP queue
2878 instr = malloc(instr_alloc);
2879 snprintf(instr, instr_alloc,
2880 "Content-type: %s\n\nmsgid|%ld\nsubmitted|%ld\n"
2882 SPOOLMIME, newmsgid, (long)time(NULL),
2886 imsg = malloc(sizeof(struct CtdlMessage));
2887 memset(imsg, 0, sizeof(struct CtdlMessage));
2888 imsg->cm_magic = CTDLMESSAGE_MAGIC;
2889 imsg->cm_anon_type = MES_NORMAL;
2890 imsg->cm_format_type = FMT_RFC822;
2891 imsg->cm_fields['A'] = strdup("Citadel");
2892 imsg->cm_fields['J'] = strdup("do not journal");
2893 imsg->cm_fields['M'] = instr; /* imsg owns this memory now */
2894 imsg->cm_fields['W'] = strdup(recipient);
2895 CtdlSubmitMsg(imsg, NULL, FNBL_QUEUE_ROOM, 0);
2896 CtdlFreeMessage(imsg);
2900 CtdlLogPrintf(CTDL_DEBUG, "No user <%s>\n", recipient);
2901 CtdlSaveMsgPointerInRoom(config.c_aideroom,
2906 /* Perform "after save" hooks */
2907 CtdlLogPrintf(CTDL_DEBUG, "Performing after-save hooks\n");
2908 PerformMessageHooks(msg, EVT_AFTERSAVE);
2910 /* For IGnet mail, we have to save a new copy into the spooler for
2911 * each recipient, with the R and D fields set to the recipient and
2912 * destination-node. This has two ugly side effects: all other
2913 * recipients end up being unlisted in this recipient's copy of the
2914 * message, and it has to deliver multiple messages to the same
2915 * node. We'll revisit this again in a year or so when everyone has
2916 * a network spool receiver that can handle the new style messages.
2918 if ((recps != NULL) && (recps->num_ignet > 0))
2919 for (i=0; i<num_tokens(recps->recp_ignet, '|'); ++i) {
2920 extract_token(recipient, recps->recp_ignet, i,
2921 '|', sizeof recipient);
2923 hold_R = msg->cm_fields['R'];
2924 hold_D = msg->cm_fields['D'];
2925 msg->cm_fields['R'] = malloc(SIZ);
2926 msg->cm_fields['D'] = malloc(128);
2927 extract_token(msg->cm_fields['R'], recipient, 0, '@', SIZ);
2928 extract_token(msg->cm_fields['D'], recipient, 1, '@', 128);
2930 serialize_message(&smr, msg);
2932 snprintf(submit_filename, sizeof submit_filename,
2933 "%s/netmail.%04lx.%04x.%04x",
2935 (long) getpid(), CCC->cs_pid, ++seqnum);
2936 network_fp = fopen(submit_filename, "wb+");
2937 if (network_fp != NULL) {
2938 fwrite(smr.ser, smr.len, 1, network_fp);
2944 free(msg->cm_fields['R']);
2945 free(msg->cm_fields['D']);
2946 msg->cm_fields['R'] = hold_R;
2947 msg->cm_fields['D'] = hold_D;
2950 /* Go back to the room we started from */
2951 CtdlLogPrintf(CTDL_DEBUG, "Returning to original room %s\n", hold_rm);
2952 if (strcasecmp(hold_rm, CCC->room.QRname))
2953 usergoto(hold_rm, 0, 1, NULL, NULL);
2955 /* For internet mail, generate delivery instructions.
2956 * Yes, this is recursive. Deal with it. Infinite recursion does
2957 * not happen because the delivery instructions message does not
2958 * contain a recipient.
2960 if ((recps != NULL) && (recps->num_internet > 0)) {
2961 CtdlLogPrintf(CTDL_DEBUG, "Generating delivery instructions\n");
2963 instr = malloc(instr_alloc);
2964 snprintf(instr, instr_alloc,
2965 "Content-type: %s\n\nmsgid|%ld\nsubmitted|%ld\n"
2967 SPOOLMIME, newmsgid, (long)time(NULL),
2971 if (recps->envelope_from != NULL) {
2972 tmp = strlen(instr);
2973 snprintf(&instr[tmp], instr_alloc-tmp, "envelope_from|%s\n", recps->envelope_from);
2976 for (i=0; i<num_tokens(recps->recp_internet, '|'); ++i) {
2977 tmp = strlen(instr);
2978 extract_token(recipient, recps->recp_internet, i, '|', sizeof recipient);
2979 if ((tmp + strlen(recipient) + 32) > instr_alloc) {
2980 instr_alloc = instr_alloc * 2;
2981 instr = realloc(instr, instr_alloc);
2983 snprintf(&instr[tmp], instr_alloc - tmp, "remote|%s|0||\n", recipient);
2986 imsg = malloc(sizeof(struct CtdlMessage));
2987 memset(imsg, 0, sizeof(struct CtdlMessage));
2988 imsg->cm_magic = CTDLMESSAGE_MAGIC;
2989 imsg->cm_anon_type = MES_NORMAL;
2990 imsg->cm_format_type = FMT_RFC822;
2991 imsg->cm_fields['A'] = strdup("Citadel");
2992 imsg->cm_fields['J'] = strdup("do not journal");
2993 imsg->cm_fields['M'] = instr; /* imsg owns this memory now */
2994 CtdlSubmitMsg(imsg, NULL, SMTP_SPOOLOUT_ROOM, QP_EADDR);
2995 CtdlFreeMessage(imsg);
2999 * Any addresses to harvest for someone's address book?
3001 if ( (CCC->logged_in) && (recps != NULL) ) {
3002 collected_addresses = harvest_collected_addresses(msg);
3005 if (collected_addresses != NULL) {
3006 aptr = (struct addresses_to_be_filed *)
3007 malloc(sizeof(struct addresses_to_be_filed));
3008 MailboxName(actual_rm, sizeof actual_rm,
3009 &CCC->user, USERCONTACTSROOM);
3010 aptr->roomname = strdup(actual_rm);
3011 aptr->collected_addresses = collected_addresses;
3012 begin_critical_section(S_ATBF);
3015 end_critical_section(S_ATBF);
3019 * Determine whether this message qualifies for journaling.
3021 if (msg->cm_fields['J'] != NULL) {
3022 qualified_for_journaling = 0;
3025 if (recps == NULL) {
3026 qualified_for_journaling = config.c_journal_pubmsgs;
3028 else if (recps->num_local + recps->num_ignet + recps->num_internet > 0) {
3029 qualified_for_journaling = config.c_journal_email;
3032 qualified_for_journaling = config.c_journal_pubmsgs;
3037 * Do we have to perform journaling? If so, hand off the saved
3038 * RFC822 version will be handed off to the journaler for background
3039 * submit. Otherwise, we have to free the memory ourselves.
3041 if (saved_rfc822_version != NULL) {
3042 if (qualified_for_journaling) {
3043 JournalBackgroundSubmit(msg, saved_rfc822_version, recps);
3046 free(saved_rfc822_version);
3059 * Convenience function for generating small administrative messages.
3061 void quickie_message(const char *from, const char *fromaddr, char *to, char *room, const char *text,
3062 int format_type, const char *subject)
3064 struct CtdlMessage *msg;
3065 struct recptypes *recp = NULL;
3067 msg = malloc(sizeof(struct CtdlMessage));
3068 memset(msg, 0, sizeof(struct CtdlMessage));
3069 msg->cm_magic = CTDLMESSAGE_MAGIC;
3070 msg->cm_anon_type = MES_NORMAL;
3071 msg->cm_format_type = format_type;
3074 msg->cm_fields['A'] = strdup(from);
3076 else if (fromaddr != NULL) {
3077 msg->cm_fields['A'] = strdup(fromaddr);
3078 if (strchr(msg->cm_fields['A'], '@')) {
3079 *strchr(msg->cm_fields['A'], '@') = 0;
3083 msg->cm_fields['A'] = strdup("Citadel");
3086 if (fromaddr != NULL) msg->cm_fields['F'] = strdup(fromaddr);
3087 if (room != NULL) msg->cm_fields['O'] = strdup(room);
3088 msg->cm_fields['N'] = strdup(NODENAME);
3090 msg->cm_fields['R'] = strdup(to);
3091 recp = validate_recipients(to, NULL, 0);
3093 if (subject != NULL) {
3094 msg->cm_fields['U'] = strdup(subject);
3096 msg->cm_fields['M'] = strdup(text);
3098 CtdlSubmitMsg(msg, recp, room, 0);
3099 CtdlFreeMessage(msg);
3100 if (recp != NULL) free_recipients(recp);
3106 * Back end function used by CtdlMakeMessage() and similar functions
3108 char *CtdlReadMessageBody(char *terminator, /* token signalling EOT */
3109 size_t maxlen, /* maximum message length */
3110 char *exist, /* if non-null, append to it;
3111 exist is ALWAYS freed */
3112 int crlf, /* CRLF newlines instead of LF */
3113 int sock /* socket handle or 0 for this session's client socket */
3117 size_t message_len = 0;
3118 size_t buffer_len = 0;
3125 if (exist == NULL) {
3132 message_len = strlen(exist);
3133 buffer_len = message_len + 4096;
3134 m = realloc(exist, buffer_len);
3141 /* Do we need to change leading ".." to "." for SMTP escaping? */
3142 if (!strcmp(terminator, ".")) {
3146 /* flush the input if we have nowhere to store it */
3151 /* read in the lines of message text one by one */
3154 if (sock_getln(sock, buf, (sizeof buf - 3)) < 0) finished = 1;
3157 if (client_getln(buf, (sizeof buf - 3)) < 1) finished = 1;
3159 if (!strcmp(buf, terminator)) finished = 1;
3161 strcat(buf, "\r\n");
3167 /* Unescape SMTP-style input of two dots at the beginning of the line */
3169 if (!strncmp(buf, "..", 2)) {
3170 strcpy(buf, &buf[1]);
3174 if ( (!flushing) && (!finished) ) {
3175 /* Measure the line */
3176 linelen = strlen(buf);
3178 /* augment the buffer if we have to */
3179 if ((message_len + linelen) >= buffer_len) {
3180 ptr = realloc(m, (buffer_len * 2) );
3181 if (ptr == NULL) { /* flush if can't allocate */
3184 buffer_len = (buffer_len * 2);
3186 CtdlLogPrintf(CTDL_DEBUG, "buffer_len is now %ld\n", (long)buffer_len);
3190 /* Add the new line to the buffer. NOTE: this loop must avoid
3191 * using functions like strcat() and strlen() because they
3192 * traverse the entire buffer upon every call, and doing that
3193 * for a multi-megabyte message slows it down beyond usability.
3195 strcpy(&m[message_len], buf);
3196 message_len += linelen;
3199 /* if we've hit the max msg length, flush the rest */
3200 if (message_len >= maxlen) flushing = 1;
3202 } while (!finished);
3210 * Build a binary message to be saved on disk.
3211 * (NOTE: if you supply 'preformatted_text', the buffer you give it
3212 * will become part of the message. This means you are no longer
3213 * responsible for managing that memory -- it will be freed along with
3214 * the rest of the fields when CtdlFreeMessage() is called.)
3217 struct CtdlMessage *CtdlMakeMessage(
3218 struct ctdluser *author, /* author's user structure */
3219 char *recipient, /* NULL if it's not mail */
3220 char *recp_cc, /* NULL if it's not mail */
3221 char *room, /* room where it's going */
3222 int type, /* see MES_ types in header file */
3223 int format_type, /* variformat, plain text, MIME... */
3224 char *fake_name, /* who we're masquerading as */
3225 char *my_email, /* which of my email addresses to use (empty is ok) */
3226 char *subject, /* Subject (optional) */
3227 char *supplied_euid, /* ...or NULL if this is irrelevant */
3228 char *preformatted_text, /* ...or NULL to read text from client */
3229 char *references /* Thread references */
3231 char dest_node[256];
3233 struct CtdlMessage *msg;
3235 msg = malloc(sizeof(struct CtdlMessage));
3236 memset(msg, 0, sizeof(struct CtdlMessage));
3237 msg->cm_magic = CTDLMESSAGE_MAGIC;
3238 msg->cm_anon_type = type;
3239 msg->cm_format_type = format_type;
3241 /* Don't confuse the poor folks if it's not routed mail. */
3242 strcpy(dest_node, "");
3244 if (recipient != NULL) striplt(recipient);
3245 if (recp_cc != NULL) striplt(recp_cc);
3247 /* Path or Return-Path */
3248 if (my_email == NULL) my_email = "";
3250 if (!IsEmptyStr(my_email)) {
3251 msg->cm_fields['P'] = strdup(my_email);
3254 snprintf(buf, sizeof buf, "%s", author->fullname);
3255 msg->cm_fields['P'] = strdup(buf);
3257 convert_spaces_to_underscores(msg->cm_fields['P']);
3259 snprintf(buf, sizeof buf, "%ld", (long)time(NULL)); /* timestamp */
3260 msg->cm_fields['T'] = strdup(buf);
3262 if ((fake_name != NULL) && (fake_name[0])) { /* author */
3263 msg->cm_fields['A'] = strdup(fake_name);
3266 msg->cm_fields['A'] = strdup(author->fullname);
3269 if (CC->room.QRflags & QR_MAILBOX) { /* room */
3270 msg->cm_fields['O'] = strdup(&CC->room.QRname[11]);
3273 msg->cm_fields['O'] = strdup(CC->room.QRname);
3276 msg->cm_fields['N'] = strdup(NODENAME); /* nodename */
3277 msg->cm_fields['H'] = strdup(HUMANNODE); /* hnodename */
3279 if ((recipient != NULL) && (recipient[0] != 0)) {
3280 msg->cm_fields['R'] = strdup(recipient);
3282 if ((recp_cc != NULL) && (recp_cc[0] != 0)) {
3283 msg->cm_fields['Y'] = strdup(recp_cc);
3285 if (dest_node[0] != 0) {
3286 msg->cm_fields['D'] = strdup(dest_node);
3289 if (!IsEmptyStr(my_email)) {
3290 msg->cm_fields['F'] = strdup(my_email);
3292 else if ( (author == &CC->user) && (!IsEmptyStr(CC->cs_inet_email)) ) {
3293 msg->cm_fields['F'] = strdup(CC->cs_inet_email);
3296 if (subject != NULL) {
3299 length = strlen(subject);
3305 while ((subject[i] != '\0') &&
3306 (IsAscii = isascii(subject[i]) != 0 ))
3309 msg->cm_fields['U'] = strdup(subject);
3310 else /* ok, we've got utf8 in the string. */
3312 msg->cm_fields['U'] = rfc2047encode(subject, length);
3318 if (supplied_euid != NULL) {
3319 msg->cm_fields['E'] = strdup(supplied_euid);
3322 if (references != NULL) {
3323 if (!IsEmptyStr(references)) {
3324 msg->cm_fields['W'] = strdup(references);
3328 if (preformatted_text != NULL) {
3329 msg->cm_fields['M'] = preformatted_text;
3332 msg->cm_fields['M'] = CtdlReadMessageBody("000", config.c_maxmsglen, NULL, 0, 0);
3340 * Check to see whether we have permission to post a message in the current
3341 * room. Returns a *CITADEL ERROR CODE* and puts a message in errmsgbuf, or
3342 * returns 0 on success.
3344 int CtdlDoIHavePermissionToPostInThisRoom(char *errmsgbuf,
3346 const char* RemoteIdentifier,
3350 if (!(CC->logged_in) &&
3351 (PostPublic == POST_LOGGED_IN)) {
3352 snprintf(errmsgbuf, n, "Not logged in.");
3353 return (ERROR + NOT_LOGGED_IN);
3355 else if (PostPublic == CHECK_EXISTANCE) {
3356 return (0); // We're Evaling whether a recipient exists
3358 else if (!(CC->logged_in)) {
3360 if ((CC->room.QRflags & QR_READONLY)) {
3361 snprintf(errmsgbuf, n, "Not logged in.");
3362 return (ERROR + NOT_LOGGED_IN);
3364 if (CC->room.QRflags2 & QR2_MODERATED) {
3365 snprintf(errmsgbuf, n, "Not logged in Moderation feature not yet implemented!");
3366 return (ERROR + NOT_LOGGED_IN);
3368 if ((PostPublic!=POST_LMTP) &&(CC->room.QRflags2 & QR2_SMTP_PUBLIC) == 0) {
3373 if (RemoteIdentifier == NULL)
3375 snprintf(errmsgbuf, n, "Need sender to permit access.");
3376 return (ERROR + USERNAME_REQUIRED);
3379 assoc_file_name(filename, sizeof filename, &CC->room, ctdl_netcfg_dir);
3380 begin_critical_section(S_NETCONFIGS);
3381 if (!read_spoolcontrol_file(&sc, filename))
3383 end_critical_section(S_NETCONFIGS);
3384 snprintf(errmsgbuf, n,
3385 "This mailing list only accepts posts from subscribers.");
3386 return (ERROR + NO_SUCH_USER);
3388 end_critical_section(S_NETCONFIGS);
3389 found = is_recipient (sc, RemoteIdentifier);
3390 free_spoolcontrol_struct(&sc);
3395 snprintf(errmsgbuf, n,
3396 "This mailing list only accepts posts from subscribers.");
3397 return (ERROR + NO_SUCH_USER);
3404 if ((CC->user.axlevel < 2)
3405 && ((CC->room.QRflags & QR_MAILBOX) == 0)) {
3406 snprintf(errmsgbuf, n, "Need to be validated to enter "
3407 "(except in %s> to sysop)", MAILROOM);
3408 return (ERROR + HIGHER_ACCESS_REQUIRED);
3411 CtdlRoomAccess(&CC->room, &CC->user, &ra, NULL);
3412 if (!(ra & UA_POSTALLOWED)) {
3413 snprintf(errmsgbuf, n, "Higher access is required to post in this room.");
3414 return (ERROR + HIGHER_ACCESS_REQUIRED);
3417 strcpy(errmsgbuf, "Ok");
3423 * Check to see if the specified user has Internet mail permission
3424 * (returns nonzero if permission is granted)
3426 int CtdlCheckInternetMailPermission(struct ctdluser *who) {
3428 /* Do not allow twits to send Internet mail */
3429 if (who->axlevel <= 2) return(0);
3431 /* Globally enabled? */
3432 if (config.c_restrict == 0) return(1);
3434 /* User flagged ok? */
3435 if (who->flags & US_INTERNET) return(2);
3437 /* Aide level access? */
3438 if (who->axlevel >= 6) return(3);
3440 /* No mail for you! */
3446 * Validate recipients, count delivery types and errors, and handle aliasing
3447 * FIXME check for dupes!!!!!
3449 * Returns 0 if all addresses are ok, ret->num_error = -1 if no addresses
3450 * were specified, or the number of addresses found invalid.
3452 * Caller needs to free the result using free_recipients()
3454 struct recptypes *validate_recipients(char *supplied_recipients,
3455 const char *RemoteIdentifier,
3457 struct recptypes *ret;
3458 char *recipients = NULL;
3459 char this_recp[256];
3460 char this_recp_cooked[256];
3466 struct ctdluser tempUS;
3467 struct ctdlroom tempQR;
3468 struct ctdlroom tempQR2;
3474 ret = (struct recptypes *) malloc(sizeof(struct recptypes));
3475 if (ret == NULL) return(NULL);
3477 /* Set all strings to null and numeric values to zero */
3478 memset(ret, 0, sizeof(struct recptypes));
3480 if (supplied_recipients == NULL) {
3481 recipients = strdup("");
3484 recipients = strdup(supplied_recipients);
3487 /* Allocate some memory. Yes, this allocates 500% more memory than we will
3488 * actually need, but it's healthier for the heap than doing lots of tiny
3489 * realloc() calls instead.
3492 ret->errormsg = malloc(strlen(recipients) + 1024);
3493 ret->recp_local = malloc(strlen(recipients) + 1024);
3494 ret->recp_internet = malloc(strlen(recipients) + 1024);
3495 ret->recp_ignet = malloc(strlen(recipients) + 1024);
3496 ret->recp_room = malloc(strlen(recipients) + 1024);
3497 ret->display_recp = malloc(strlen(recipients) + 1024);
3499 ret->errormsg[0] = 0;
3500 ret->recp_local[0] = 0;
3501 ret->recp_internet[0] = 0;
3502 ret->recp_ignet[0] = 0;
3503 ret->recp_room[0] = 0;
3504 ret->display_recp[0] = 0;
3506 ret->recptypes_magic = RECPTYPES_MAGIC;
3508 /* Change all valid separator characters to commas */
3509 for (i=0; !IsEmptyStr(&recipients[i]); ++i) {
3510 if ((recipients[i] == ';') || (recipients[i] == '|')) {
3511 recipients[i] = ',';
3515 /* Now start extracting recipients... */
3517 while (!IsEmptyStr(recipients)) {
3519 for (i=0; i<=strlen(recipients); ++i) {
3520 if (recipients[i] == '\"') in_quotes = 1 - in_quotes;
3521 if ( ( (recipients[i] == ',') && (!in_quotes) ) || (recipients[i] == 0) ) {
3522 safestrncpy(this_recp, recipients, i+1);
3524 if (recipients[i] == ',') {
3525 strcpy(recipients, &recipients[i+1]);
3528 strcpy(recipients, "");
3535 if (IsEmptyStr(this_recp))
3537 CtdlLogPrintf(CTDL_DEBUG, "Evaluating recipient #%d: %s\n", num_recps, this_recp);
3539 mailtype = alias(this_recp);
3540 mailtype = alias(this_recp);
3541 mailtype = alias(this_recp);
3543 for (j=0; !IsEmptyStr(&this_recp[j]); ++j) {
3544 if (this_recp[j]=='_') {
3545 this_recp_cooked[j] = ' ';
3548 this_recp_cooked[j] = this_recp[j];
3551 this_recp_cooked[j] = '\0';
3556 if (!strcasecmp(this_recp, "sysop")) {
3558 strcpy(this_recp, config.c_aideroom);
3559 if (!IsEmptyStr(ret->recp_room)) {
3560 strcat(ret->recp_room, "|");
3562 strcat(ret->recp_room, this_recp);
3564 else if ( (!strncasecmp(this_recp, "room_", 5))
3565 && (!getroom(&tempQR, &this_recp_cooked[5])) ) {
3567 /* Save room so we can restore it later */
3571 /* Check permissions to send mail to this room */
3572 err = CtdlDoIHavePermissionToPostInThisRoom(errmsg,
3584 if (!IsEmptyStr(ret->recp_room)) {
3585 strcat(ret->recp_room, "|");
3587 strcat(ret->recp_room, &this_recp_cooked[5]);
3590 /* Restore room in case something needs it */
3594 else if (getuser(&tempUS, this_recp) == 0) {
3596 strcpy(this_recp, tempUS.fullname);
3597 if (!IsEmptyStr(ret->recp_local)) {
3598 strcat(ret->recp_local, "|");
3600 strcat(ret->recp_local, this_recp);
3602 else if (getuser(&tempUS, this_recp_cooked) == 0) {
3604 strcpy(this_recp, tempUS.fullname);
3605 if (!IsEmptyStr(ret->recp_local)) {
3606 strcat(ret->recp_local, "|");
3608 strcat(ret->recp_local, this_recp);
3616 /* Yes, you're reading this correctly: if the target
3617 * domain points back to the local system or an attached
3618 * Citadel directory, the address is invalid. That's
3619 * because if the address were valid, we would have
3620 * already translated it to a local address by now.
3622 if (IsDirectory(this_recp, 0)) {
3627 ++ret->num_internet;
3628 if (!IsEmptyStr(ret->recp_internet)) {
3629 strcat(ret->recp_internet, "|");
3631 strcat(ret->recp_internet, this_recp);
3636 if (!IsEmptyStr(ret->recp_ignet)) {
3637 strcat(ret->recp_ignet, "|");
3639 strcat(ret->recp_ignet, this_recp);
3647 if (IsEmptyStr(errmsg)) {
3648 snprintf(append, sizeof append, "Invalid recipient: %s", this_recp);
3651 snprintf(append, sizeof append, "%s", errmsg);
3653 if ( (strlen(ret->errormsg) + strlen(append) + 3) < SIZ) {
3654 if (!IsEmptyStr(ret->errormsg)) {
3655 strcat(ret->errormsg, "; ");
3657 strcat(ret->errormsg, append);
3661 if (IsEmptyStr(ret->display_recp)) {
3662 strcpy(append, this_recp);
3665 snprintf(append, sizeof append, ", %s", this_recp);
3667 if ( (strlen(ret->display_recp)+strlen(append)) < SIZ) {
3668 strcat(ret->display_recp, append);
3673 if ((ret->num_local + ret->num_internet + ret->num_ignet +
3674 ret->num_room + ret->num_error) == 0) {
3675 ret->num_error = (-1);
3676 strcpy(ret->errormsg, "No recipients specified.");
3679 CtdlLogPrintf(CTDL_DEBUG, "validate_recipients()\n");
3680 CtdlLogPrintf(CTDL_DEBUG, " local: %d <%s>\n", ret->num_local, ret->recp_local);
3681 CtdlLogPrintf(CTDL_DEBUG, " room: %d <%s>\n", ret->num_room, ret->recp_room);
3682 CtdlLogPrintf(CTDL_DEBUG, " inet: %d <%s>\n", ret->num_internet, ret->recp_internet);
3683 CtdlLogPrintf(CTDL_DEBUG, " ignet: %d <%s>\n", ret->num_ignet, ret->recp_ignet);
3684 CtdlLogPrintf(CTDL_DEBUG, " error: %d <%s>\n", ret->num_error, ret->errormsg);
3692 * Destructor for struct recptypes
3694 void free_recipients(struct recptypes *valid) {
3696 if (valid == NULL) {
3700 if (valid->recptypes_magic != RECPTYPES_MAGIC) {
3701 CtdlLogPrintf(CTDL_EMERG, "Attempt to call free_recipients() on some other data type!\n");
3705 if (valid->errormsg != NULL) free(valid->errormsg);
3706 if (valid->recp_local != NULL) free(valid->recp_local);
3707 if (valid->recp_internet != NULL) free(valid->recp_internet);
3708 if (valid->recp_ignet != NULL) free(valid->recp_ignet);
3709 if (valid->recp_room != NULL) free(valid->recp_room);
3710 if (valid->display_recp != NULL) free(valid->display_recp);
3711 if (valid->bounce_to != NULL) free(valid->bounce_to);
3712 if (valid->envelope_from != NULL) free(valid->envelope_from);
3719 * message entry - mode 0 (normal)
3721 void cmd_ent0(char *entargs)
3727 char supplied_euid[128];
3729 int format_type = 0;
3730 char newusername[256];
3731 char newuseremail[256];
3732 struct CtdlMessage *msg;
3736 struct recptypes *valid = NULL;
3737 struct recptypes *valid_to = NULL;
3738 struct recptypes *valid_cc = NULL;
3739 struct recptypes *valid_bcc = NULL;
3741 int subject_required = 0;
3746 int newuseremail_ok = 0;
3747 char references[SIZ];
3752 post = extract_int(entargs, 0);
3753 extract_token(recp, entargs, 1, '|', sizeof recp);
3754 anon_flag = extract_int(entargs, 2);
3755 format_type = extract_int(entargs, 3);
3756 extract_token(subject, entargs, 4, '|', sizeof subject);
3757 extract_token(newusername, entargs, 5, '|', sizeof newusername);
3758 do_confirm = extract_int(entargs, 6);
3759 extract_token(cc, entargs, 7, '|', sizeof cc);
3760 extract_token(bcc, entargs, 8, '|', sizeof bcc);
3761 switch(CC->room.QRdefaultview) {
3764 extract_token(supplied_euid, entargs, 9, '|', sizeof supplied_euid);
3767 supplied_euid[0] = 0;
3770 extract_token(newuseremail, entargs, 10, '|', sizeof newuseremail);
3771 extract_token(references, entargs, 11, '|', sizeof references);
3772 for (ptr=references; *ptr != 0; ++ptr) {
3773 if (*ptr == '!') *ptr = '|';
3776 /* first check to make sure the request is valid. */
3778 err = CtdlDoIHavePermissionToPostInThisRoom(errmsg, sizeof errmsg, NULL, POST_LOGGED_IN);
3781 cprintf("%d %s\n", err, errmsg);
3785 /* Check some other permission type things. */
3787 if (IsEmptyStr(newusername)) {
3788 strcpy(newusername, CC->user.fullname);
3790 if ( (CC->user.axlevel < 6)
3791 && (strcasecmp(newusername, CC->user.fullname))
3792 && (strcasecmp(newusername, CC->cs_inet_fn))
3794 cprintf("%d You don't have permission to author messages as '%s'.\n",
3795 ERROR + HIGHER_ACCESS_REQUIRED,
3802 if (IsEmptyStr(newuseremail)) {
3803 newuseremail_ok = 1;
3806 if (!IsEmptyStr(newuseremail)) {
3807 if (!strcasecmp(newuseremail, CC->cs_inet_email)) {
3808 newuseremail_ok = 1;
3810 else if (!IsEmptyStr(CC->cs_inet_other_emails)) {
3811 j = num_tokens(CC->cs_inet_other_emails, '|');
3812 for (i=0; i<j; ++i) {
3813 extract_token(buf, CC->cs_inet_other_emails, i, '|', sizeof buf);
3814 if (!strcasecmp(newuseremail, buf)) {
3815 newuseremail_ok = 1;
3821 if (!newuseremail_ok) {
3822 cprintf("%d You don't have permission to author messages as '%s'.\n",
3823 ERROR + HIGHER_ACCESS_REQUIRED,
3829 CC->cs_flags |= CS_POSTING;
3831 /* In mailbox rooms we have to behave a little differently --
3832 * make sure the user has specified at least one recipient. Then
3833 * validate the recipient(s). We do this for the Mail> room, as
3834 * well as any room which has the "Mailbox" view set.
3837 if ( ( (CC->room.QRflags & QR_MAILBOX) && (!strcasecmp(&CC->room.QRname[11], MAILROOM)) )
3838 || ( (CC->room.QRflags & QR_MAILBOX) && (CC->curr_view == VIEW_MAILBOX) )
3840 if (CC->user.axlevel < 2) {
3841 strcpy(recp, "sysop");
3846 valid_to = validate_recipients(recp, NULL, 0);
3847 if (valid_to->num_error > 0) {
3848 cprintf("%d %s\n", ERROR + NO_SUCH_USER, valid_to->errormsg);
3849 free_recipients(valid_to);
3853 valid_cc = validate_recipients(cc, NULL, 0);
3854 if (valid_cc->num_error > 0) {
3855 cprintf("%d %s\n", ERROR + NO_SUCH_USER, valid_cc->errormsg);
3856 free_recipients(valid_to);
3857 free_recipients(valid_cc);
3861 valid_bcc = validate_recipients(bcc, NULL, 0);
3862 if (valid_bcc->num_error > 0) {
3863 cprintf("%d %s\n", ERROR + NO_SUCH_USER, valid_bcc->errormsg);
3864 free_recipients(valid_to);
3865 free_recipients(valid_cc);
3866 free_recipients(valid_bcc);
3870 /* Recipient required, but none were specified */
3871 if ( (valid_to->num_error < 0) && (valid_cc->num_error < 0) && (valid_bcc->num_error < 0) ) {
3872 free_recipients(valid_to);
3873 free_recipients(valid_cc);
3874 free_recipients(valid_bcc);
3875 cprintf("%d At least one recipient is required.\n", ERROR + NO_SUCH_USER);
3879 if (valid_to->num_internet + valid_cc->num_internet + valid_bcc->num_internet > 0) {
3880 if (CtdlCheckInternetMailPermission(&CC->user)==0) {
3881 cprintf("%d You do not have permission "
3882 "to send Internet mail.\n",
3883 ERROR + HIGHER_ACCESS_REQUIRED);
3884 free_recipients(valid_to);
3885 free_recipients(valid_cc);
3886 free_recipients(valid_bcc);
3891 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)
3892 && (CC->user.axlevel < 4) ) {
3893 cprintf("%d Higher access required for network mail.\n",
3894 ERROR + HIGHER_ACCESS_REQUIRED);
3895 free_recipients(valid_to);
3896 free_recipients(valid_cc);
3897 free_recipients(valid_bcc);
3901 if ((RESTRICT_INTERNET == 1)
3902 && (valid_to->num_internet + valid_cc->num_internet + valid_bcc->num_internet > 0)
3903 && ((CC->user.flags & US_INTERNET) == 0)
3904 && (!CC->internal_pgm)) {
3905 cprintf("%d You don't have access to Internet mail.\n",
3906 ERROR + HIGHER_ACCESS_REQUIRED);
3907 free_recipients(valid_to);
3908 free_recipients(valid_cc);
3909 free_recipients(valid_bcc);
3915 /* Is this a room which has anonymous-only or anonymous-option? */
3916 anonymous = MES_NORMAL;
3917 if (CC->room.QRflags & QR_ANONONLY) {
3918 anonymous = MES_ANONONLY;
3920 if (CC->room.QRflags & QR_ANONOPT) {
3921 if (anon_flag == 1) { /* only if the user requested it */
3922 anonymous = MES_ANONOPT;
3926 if ((CC->room.QRflags & QR_MAILBOX) == 0) {
3930 /* Recommend to the client that the use of a message subject is
3931 * strongly recommended in this room, if either the SUBJECTREQ flag
3932 * is set, or if there is one or more Internet email recipients.
3934 if (CC->room.QRflags2 & QR2_SUBJECTREQ) subject_required = 1;
3935 if ((valid_to) && (valid_to->num_internet > 0)) subject_required = 1;
3936 if ((valid_cc) && (valid_cc->num_internet > 0)) subject_required = 1;
3937 if ((valid_bcc) && (valid_bcc->num_internet > 0)) subject_required = 1;
3939 /* If we're only checking the validity of the request, return
3940 * success without creating the message.
3943 cprintf("%d %s|%d\n", CIT_OK,
3944 ((valid_to != NULL) ? valid_to->display_recp : ""),
3946 free_recipients(valid_to);
3947 free_recipients(valid_cc);
3948 free_recipients(valid_bcc);
3952 /* We don't need these anymore because we'll do it differently below */
3953 free_recipients(valid_to);
3954 free_recipients(valid_cc);
3955 free_recipients(valid_bcc);
3957 /* Read in the message from the client. */
3959 cprintf("%d send message\n", START_CHAT_MODE);
3961 cprintf("%d send message\n", SEND_LISTING);
3964 msg = CtdlMakeMessage(&CC->user, recp, cc,
3965 CC->room.QRname, anonymous, format_type,
3966 newusername, newuseremail, subject,
3967 ((!IsEmptyStr(supplied_euid)) ? supplied_euid : NULL),
3970 /* Put together one big recipients struct containing to/cc/bcc all in
3971 * one. This is for the envelope.
3973 char *all_recps = malloc(SIZ * 3);
3974 strcpy(all_recps, recp);
3975 if (!IsEmptyStr(cc)) {
3976 if (!IsEmptyStr(all_recps)) {
3977 strcat(all_recps, ",");
3979 strcat(all_recps, cc);
3981 if (!IsEmptyStr(bcc)) {
3982 if (!IsEmptyStr(all_recps)) {
3983 strcat(all_recps, ",");
3985 strcat(all_recps, bcc);
3987 if (!IsEmptyStr(all_recps)) {
3988 valid = validate_recipients(all_recps, NULL, 0);
3996 msgnum = CtdlSubmitMsg(msg, valid, "", QP_EADDR);
3999 cprintf("%ld\n", msgnum);
4001 cprintf("Message accepted.\n");
4004 cprintf("Internal error.\n");
4006 if (msg->cm_fields['E'] != NULL) {
4007 cprintf("%s\n", msg->cm_fields['E']);
4014 CtdlFreeMessage(msg);
4016 if (valid != NULL) {
4017 free_recipients(valid);
4025 * API function to delete messages which match a set of criteria
4026 * (returns the actual number of messages deleted)
4028 int CtdlDeleteMessages(char *room_name, /* which room */
4029 long *dmsgnums, /* array of msg numbers to be deleted */
4030 int num_dmsgnums, /* number of msgs to be deleted, or 0 for "any" */
4031 char *content_type /* or "" for any. regular expressions expected. */
4034 struct ctdlroom qrbuf;
4035 struct cdbdata *cdbfr;
4036 long *msglist = NULL;
4037 long *dellist = NULL;
4040 int num_deleted = 0;
4042 struct MetaData smi;
4045 int need_to_free_re = 0;
4047 if (content_type) if (!IsEmptyStr(content_type)) {
4048 regcomp(&re, content_type, 0);
4049 need_to_free_re = 1;
4051 CtdlLogPrintf(CTDL_DEBUG, "CtdlDeleteMessages(%s, %d msgs, %s)\n",
4052 room_name, num_dmsgnums, content_type);
4054 /* get room record, obtaining a lock... */
4055 if (lgetroom(&qrbuf, room_name) != 0) {
4056 CtdlLogPrintf(CTDL_ERR, "CtdlDeleteMessages(): Room <%s> not found\n",
4058 if (need_to_free_re) regfree(&re);
4059 return (0); /* room not found */
4061 cdbfr = cdb_fetch(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long));
4063 if (cdbfr != NULL) {
4064 dellist = malloc(cdbfr->len);
4065 msglist = (long *) cdbfr->ptr;
4066 cdbfr->ptr = NULL; /* CtdlDeleteMessages() now owns this memory */
4067 num_msgs = cdbfr->len / sizeof(long);
4071 for (i = 0; i < num_msgs; ++i) {
4074 /* Set/clear a bit for each criterion */
4076 /* 0 messages in the list or a null list means that we are
4077 * interested in deleting any messages which meet the other criteria.
4079 if ((num_dmsgnums == 0) || (dmsgnums == NULL)) {
4080 delete_this |= 0x01;
4083 for (j=0; j<num_dmsgnums; ++j) {
4084 if (msglist[i] == dmsgnums[j]) {
4085 delete_this |= 0x01;
4090 if (IsEmptyStr(content_type)) {
4091 delete_this |= 0x02;
4093 GetMetaData(&smi, msglist[i]);
4094 if (regexec(&re, smi.meta_content_type, 1, &pm, 0) == 0) {
4095 delete_this |= 0x02;
4099 /* Delete message only if all bits are set */
4100 if (delete_this == 0x03) {
4101 dellist[num_deleted++] = msglist[i];
4106 num_msgs = sort_msglist(msglist, num_msgs);
4107 cdb_store(CDB_MSGLISTS, &qrbuf.QRnumber, (int)sizeof(long),
4108 msglist, (int)(num_msgs * sizeof(long)));
4110 qrbuf.QRhighest = msglist[num_msgs - 1];
4114 /* Go through the messages we pulled out of the index, and decrement
4115 * their reference counts by 1. If this is the only room the message
4116 * was in, the reference count will reach zero and the message will
4117 * automatically be deleted from the database. We do this in a
4118 * separate pass because there might be plug-in hooks getting called,
4119 * and we don't want that happening during an S_ROOMS critical
4122 if (num_deleted) for (i=0; i<num_deleted; ++i) {
4123 PerformDeleteHooks(qrbuf.QRname, dellist[i]);
4124 AdjRefCount(dellist[i], -1);
4127 /* Now free the memory we used, and go away. */
4128 if (msglist != NULL) free(msglist);
4129 if (dellist != NULL) free(dellist);
4130 CtdlLogPrintf(CTDL_DEBUG, "%d message(s) deleted.\n", num_deleted);
4131 if (need_to_free_re) regfree(&re);
4132 return (num_deleted);
4138 * Check whether the current user has permission to delete messages from
4139 * the current room (returns 1 for yes, 0 for no)
4141 int CtdlDoIHavePermissionToDeleteMessagesFromThisRoom(void) {
4143 CtdlRoomAccess(&CC->room, &CC->user, &ra, NULL);
4144 if (ra & UA_DELETEALLOWED) return(1);
4152 * Delete message from current room
4154 void cmd_dele(char *args)
4163 extract_token(msgset, args, 0, '|', sizeof msgset);
4164 num_msgs = num_tokens(msgset, ',');
4166 cprintf("%d Nothing to do.\n", CIT_OK);
4170 if (CtdlDoIHavePermissionToDeleteMessagesFromThisRoom() == 0) {
4171 cprintf("%d Higher access required.\n",
4172 ERROR + HIGHER_ACCESS_REQUIRED);
4177 * Build our message set to be moved/copied
4179 msgs = malloc(num_msgs * sizeof(long));
4180 for (i=0; i<num_msgs; ++i) {
4181 extract_token(msgtok, msgset, i, ',', sizeof msgtok);
4182 msgs[i] = atol(msgtok);
4185 num_deleted = CtdlDeleteMessages(CC->room.QRname, msgs, num_msgs, "");
4189 cprintf("%d %d message%s deleted.\n", CIT_OK,
4190 num_deleted, ((num_deleted != 1) ? "s" : ""));
4192 cprintf("%d Message not found.\n", ERROR + MESSAGE_NOT_FOUND);
4200 * move or copy a message to another room
4202 void cmd_move(char *args)
4209 char targ[ROOMNAMELEN];
4210 struct ctdlroom qtemp;
4217 extract_token(msgset, args, 0, '|', sizeof msgset);
4218 num_msgs = num_tokens(msgset, ',');
4220 cprintf("%d Nothing to do.\n", CIT_OK);
4224 extract_token(targ, args, 1, '|', sizeof targ);
4225 convert_room_name_macros(targ, sizeof targ);
4226 targ[ROOMNAMELEN - 1] = 0;
4227 is_copy = extract_int(args, 2);
4229 if (getroom(&qtemp, targ) != 0) {
4230 cprintf("%d '%s' does not exist.\n", ERROR + ROOM_NOT_FOUND, targ);
4234 if (!strcasecmp(qtemp.QRname, CC->room.QRname)) {
4235 cprintf("%d Source and target rooms are the same.\n", ERROR + ALREADY_EXISTS);
4239 getuser(&CC->user, CC->curr_user);
4240 CtdlRoomAccess(&qtemp, &CC->user, &ra, NULL);
4242 /* Check for permission to perform this operation.
4243 * Remember: "CC->room" is source, "qtemp" is target.
4247 /* Aides can move/copy */
4248 if (CC->user.axlevel >= 6) permit = 1;
4250 /* Room aides can move/copy */
4251 if (CC->user.usernum == CC->room.QRroomaide) permit = 1;
4253 /* Permit move/copy from personal rooms */
4254 if ((CC->room.QRflags & QR_MAILBOX)
4255 && (qtemp.QRflags & QR_MAILBOX)) permit = 1;
4257 /* Permit only copy from public to personal room */
4259 && (!(CC->room.QRflags & QR_MAILBOX))
4260 && (qtemp.QRflags & QR_MAILBOX)) permit = 1;
4262 /* Permit message removal from collaborative delete rooms */
4263 if (CC->room.QRflags2 & QR2_COLLABDEL) permit = 1;
4265 /* Users allowed to post into the target room may move into it too. */
4266 if ((CC->room.QRflags & QR_MAILBOX) &&
4267 (qtemp.QRflags & UA_POSTALLOWED)) permit = 1;
4269 /* User must have access to target room */
4270 if (!(ra & UA_KNOWN)) permit = 0;
4273 cprintf("%d Higher access required.\n",
4274 ERROR + HIGHER_ACCESS_REQUIRED);
4279 * Build our message set to be moved/copied
4281 msgs = malloc(num_msgs * sizeof(long));
4282 for (i=0; i<num_msgs; ++i) {
4283 extract_token(msgtok, msgset, i, ',', sizeof msgtok);
4284 msgs[i] = atol(msgtok);
4290 err = CtdlSaveMsgPointersInRoom(targ, msgs, num_msgs, 1, NULL);
4292 cprintf("%d Cannot store message(s) in %s: error %d\n",
4298 /* Now delete the message from the source room,
4299 * if this is a 'move' rather than a 'copy' operation.
4302 CtdlDeleteMessages(CC->room.QRname, msgs, num_msgs, "");
4306 cprintf("%d Message(s) %s.\n", CIT_OK, (is_copy ? "copied" : "moved") );
4312 * GetMetaData() - Get the supplementary record for a message
4314 void GetMetaData(struct MetaData *smibuf, long msgnum)
4317 struct cdbdata *cdbsmi;
4320 memset(smibuf, 0, sizeof(struct MetaData));
4321 smibuf->meta_msgnum = msgnum;
4322 smibuf->meta_refcount = 1; /* Default reference count is 1 */
4324 /* Use the negative of the message number for its supp record index */
4325 TheIndex = (0L - msgnum);
4327 cdbsmi = cdb_fetch(CDB_MSGMAIN, &TheIndex, sizeof(long));
4328 if (cdbsmi == NULL) {
4329 return; /* record not found; go with defaults */
4331 memcpy(smibuf, cdbsmi->ptr,
4332 ((cdbsmi->len > sizeof(struct MetaData)) ?
4333 sizeof(struct MetaData) : cdbsmi->len));
4340 * PutMetaData() - (re)write supplementary record for a message
4342 void PutMetaData(struct MetaData *smibuf)
4346 /* Use the negative of the message number for the metadata db index */
4347 TheIndex = (0L - smibuf->meta_msgnum);
4349 cdb_store(CDB_MSGMAIN,
4350 &TheIndex, (int)sizeof(long),
4351 smibuf, (int)sizeof(struct MetaData));
4356 * AdjRefCount - submit an adjustment to the reference count for a message.
4357 * (These are just queued -- we actually process them later.)
4359 void AdjRefCount(long msgnum, int incr)
4361 struct arcq new_arcq;
4363 begin_critical_section(S_SUPPMSGMAIN);
4364 if (arcfp == NULL) {
4365 arcfp = fopen(file_arcq, "ab+");
4367 end_critical_section(S_SUPPMSGMAIN);
4369 /* msgnum < 0 means that we're trying to close the file */
4371 CtdlLogPrintf(CTDL_DEBUG, "Closing the AdjRefCount queue file\n");
4372 begin_critical_section(S_SUPPMSGMAIN);
4373 if (arcfp != NULL) {
4377 end_critical_section(S_SUPPMSGMAIN);
4382 * If we can't open the queue, perform the operation synchronously.
4384 if (arcfp == NULL) {
4385 TDAP_AdjRefCount(msgnum, incr);
4389 new_arcq.arcq_msgnum = msgnum;
4390 new_arcq.arcq_delta = incr;
4391 fwrite(&new_arcq, sizeof(struct arcq), 1, arcfp);
4399 * TDAP_ProcessAdjRefCountQueue()
4401 * Process the queue of message count adjustments that was created by calls
4402 * to AdjRefCount() ... by reading the queue and calling TDAP_AdjRefCount()
4403 * for each one. This should be an "off hours" operation.
4405 int TDAP_ProcessAdjRefCountQueue(void)
4407 char file_arcq_temp[PATH_MAX];
4410 struct arcq arcq_rec;
4411 int num_records_processed = 0;
4413 snprintf(file_arcq_temp, sizeof file_arcq_temp, "%s.%04x", file_arcq, rand());
4415 begin_critical_section(S_SUPPMSGMAIN);
4416 if (arcfp != NULL) {
4421 r = link(file_arcq, file_arcq_temp);
4423 CtdlLogPrintf(CTDL_CRIT, "%s: %s\n", file_arcq_temp, strerror(errno));
4424 end_critical_section(S_SUPPMSGMAIN);
4425 return(num_records_processed);
4429 end_critical_section(S_SUPPMSGMAIN);
4431 fp = fopen(file_arcq_temp, "rb");
4433 CtdlLogPrintf(CTDL_CRIT, "%s: %s\n", file_arcq_temp, strerror(errno));
4434 return(num_records_processed);
4437 while (fread(&arcq_rec, sizeof(struct arcq), 1, fp) == 1) {
4438 TDAP_AdjRefCount(arcq_rec.arcq_msgnum, arcq_rec.arcq_delta);
4439 ++num_records_processed;
4443 r = unlink(file_arcq_temp);
4445 CtdlLogPrintf(CTDL_CRIT, "%s: %s\n", file_arcq_temp, strerror(errno));
4448 return(num_records_processed);
4454 * TDAP_AdjRefCount - adjust the reference count for a message.
4455 * This one does it "for real" because it's called by
4456 * the autopurger function that processes the queue
4457 * created by AdjRefCount(). If a message's reference
4458 * count becomes zero, we also delete the message from
4459 * disk and de-index it.
4461 void TDAP_AdjRefCount(long msgnum, int incr)
4464 struct MetaData smi;
4467 /* This is a *tight* critical section; please keep it that way, as
4468 * it may get called while nested in other critical sections.
4469 * Complicating this any further will surely cause deadlock!
4471 begin_critical_section(S_SUPPMSGMAIN);
4472 GetMetaData(&smi, msgnum);
4473 smi.meta_refcount += incr;
4475 end_critical_section(S_SUPPMSGMAIN);
4476 CtdlLogPrintf(CTDL_DEBUG, "msg %ld ref count delta %+d, is now %d\n",
4477 msgnum, incr, smi.meta_refcount);
4479 /* If the reference count is now zero, delete the message
4480 * (and its supplementary record as well).
4482 if (smi.meta_refcount == 0) {
4483 CtdlLogPrintf(CTDL_DEBUG, "Deleting message <%ld>\n", msgnum);
4485 /* Call delete hooks with NULL room to show it has gone altogether */
4486 PerformDeleteHooks(NULL, msgnum);
4488 /* Remove from message base */
4490 cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long));
4491 cdb_delete(CDB_BIGMSGS, &delnum, (int)sizeof(long));
4493 /* Remove metadata record */
4494 delnum = (0L - msgnum);
4495 cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long));
4501 * Write a generic object to this room
4503 * Note: this could be much more efficient. Right now we use two temporary
4504 * files, and still pull the message into memory as with all others.
4506 void CtdlWriteObject(char *req_room, /* Room to stuff it in */
4507 char *content_type, /* MIME type of this object */
4508 char *raw_message, /* Data to be written */
4509 off_t raw_length, /* Size of raw_message */
4510 struct ctdluser *is_mailbox, /* Mailbox room? */
4511 int is_binary, /* Is encoding necessary? */
4512 int is_unique, /* Del others of this type? */
4513 unsigned int flags /* Internal save flags */
4517 struct ctdlroom qrbuf;
4518 char roomname[ROOMNAMELEN];
4519 struct CtdlMessage *msg;
4520 char *encoded_message = NULL;
4522 if (is_mailbox != NULL) {
4523 MailboxName(roomname, sizeof roomname, is_mailbox, req_room);
4526 safestrncpy(roomname, req_room, sizeof(roomname));
4529 CtdlLogPrintf(CTDL_DEBUG, "Raw length is %ld\n", (long)raw_length);
4532 encoded_message = malloc((size_t) (((raw_length * 134) / 100) + 4096 ) );
4535 encoded_message = malloc((size_t)(raw_length + 4096));
4538 sprintf(encoded_message, "Content-type: %s\n", content_type);
4541 sprintf(&encoded_message[strlen(encoded_message)],
4542 "Content-transfer-encoding: base64\n\n"
4546 sprintf(&encoded_message[strlen(encoded_message)],
4547 "Content-transfer-encoding: 7bit\n\n"
4553 &encoded_message[strlen(encoded_message)],
4561 &encoded_message[strlen(encoded_message)],
4567 CtdlLogPrintf(CTDL_DEBUG, "Allocating\n");
4568 msg = malloc(sizeof(struct CtdlMessage));
4569 memset(msg, 0, sizeof(struct CtdlMessage));
4570 msg->cm_magic = CTDLMESSAGE_MAGIC;
4571 msg->cm_anon_type = MES_NORMAL;
4572 msg->cm_format_type = 4;
4573 msg->cm_fields['A'] = strdup(CC->user.fullname);
4574 msg->cm_fields['O'] = strdup(req_room);
4575 msg->cm_fields['N'] = strdup(config.c_nodename);
4576 msg->cm_fields['H'] = strdup(config.c_humannode);
4577 msg->cm_flags = flags;
4579 msg->cm_fields['M'] = encoded_message;
4581 /* Create the requested room if we have to. */
4582 if (getroom(&qrbuf, roomname) != 0) {
4583 create_room(roomname,
4584 ( (is_mailbox != NULL) ? 5 : 3 ),
4585 "", 0, 1, 0, VIEW_BBS);
4587 /* If the caller specified this object as unique, delete all
4588 * other objects of this type that are currently in the room.
4591 CtdlLogPrintf(CTDL_DEBUG, "Deleted %d other msgs of this type\n",
4592 CtdlDeleteMessages(roomname, NULL, 0, content_type)
4595 /* Now write the data */
4596 CtdlSubmitMsg(msg, NULL, roomname, 0);
4597 CtdlFreeMessage(msg);
4605 void CtdlGetSysConfigBackend(long msgnum, void *userdata) {
4606 config_msgnum = msgnum;
4610 char *CtdlGetSysConfig(char *sysconfname) {
4611 char hold_rm[ROOMNAMELEN];
4614 struct CtdlMessage *msg;
4617 strcpy(hold_rm, CC->room.QRname);
4618 if (getroom(&CC->room, SYSCONFIGROOM) != 0) {
4619 getroom(&CC->room, hold_rm);
4624 /* We want the last (and probably only) config in this room */
4625 begin_critical_section(S_CONFIG);
4626 config_msgnum = (-1L);
4627 CtdlForEachMessage(MSGS_LAST, 1, NULL, sysconfname, NULL,
4628 CtdlGetSysConfigBackend, NULL);
4629 msgnum = config_msgnum;
4630 end_critical_section(S_CONFIG);
4636 msg = CtdlFetchMessage(msgnum, 1);
4638 conf = strdup(msg->cm_fields['M']);
4639 CtdlFreeMessage(msg);
4646 getroom(&CC->room, hold_rm);
4648 if (conf != NULL) do {
4649 extract_token(buf, conf, 0, '\n', sizeof buf);
4650 strcpy(conf, &conf[strlen(buf)+1]);
4651 } while ( (!IsEmptyStr(conf)) && (!IsEmptyStr(buf)) );
4657 void CtdlPutSysConfig(char *sysconfname, char *sysconfdata) {
4658 CtdlWriteObject(SYSCONFIGROOM, sysconfname, sysconfdata, (strlen(sysconfdata)+1), NULL, 0, 1, 0);
4663 * Determine whether a given Internet address belongs to the current user
4665 int CtdlIsMe(char *addr, int addr_buf_len)
4667 struct recptypes *recp;
4670 recp = validate_recipients(addr, NULL, 0);
4671 if (recp == NULL) return(0);
4673 if (recp->num_local == 0) {
4674 free_recipients(recp);
4678 for (i=0; i<recp->num_local; ++i) {
4679 extract_token(addr, recp->recp_local, i, '|', addr_buf_len);
4680 if (!strcasecmp(addr, CC->user.fullname)) {
4681 free_recipients(recp);
4686 free_recipients(recp);
4692 * Citadel protocol command to do the same
4694 void cmd_isme(char *argbuf) {
4697 if (CtdlAccessCheck(ac_logged_in)) return;
4698 extract_token(addr, argbuf, 0, '|', sizeof addr);
4700 if (CtdlIsMe(addr, sizeof addr)) {
4701 cprintf("%d %s\n", CIT_OK, addr);
4704 cprintf("%d Not you.\n", ERROR + ILLEGAL_VALUE);
4710 /*****************************************************************************/
4711 /* MODULE INITIALIZATION STUFF */
4712 /*****************************************************************************/
4714 CTDL_MODULE_INIT(msgbase)
4716 CtdlRegisterProtoHook(cmd_msgs, "MSGS", "Output a list of messages in the current room");
4717 CtdlRegisterProtoHook(cmd_msg0, "MSG0", "Output a message in plain text format");
4718 CtdlRegisterProtoHook(cmd_msg2, "MSG2", "Output a message in RFC822 format");
4719 CtdlRegisterProtoHook(cmd_msg3, "MSG3", "Output a message in raw format (deprecated)");
4720 CtdlRegisterProtoHook(cmd_msg4, "MSG4", "Output a message in the client's preferred format");
4721 CtdlRegisterProtoHook(cmd_msgp, "MSGP", "Select preferred format for MSG4 output");
4722 CtdlRegisterProtoHook(cmd_opna, "OPNA", "Open an attachment for download");
4723 CtdlRegisterProtoHook(cmd_dlat, "DLAT", "Download an attachment");
4724 CtdlRegisterProtoHook(cmd_ent0, "ENT0", "Enter a message");
4725 CtdlRegisterProtoHook(cmd_dele, "DELE", "Delete a message");
4726 CtdlRegisterProtoHook(cmd_move, "MOVE", "Move or copy a message to another room");
4727 CtdlRegisterProtoHook(cmd_isme, "ISME", "Determine whether an email address belongs to a user");
4729 /* return our Subversion id for the Log */