2 * Implements the message store.
4 * Copyright (c) 1987-2012 by the citadel.org team
6 * This program is open source software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License version 3.
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
21 #if TIME_WITH_SYS_TIME
22 # include <sys/time.h>
26 # include <sys/time.h>
39 #include <sys/types.h>
41 #include <libcitadel.h>
44 #include "serv_extensions.h"
48 #include "sysdep_decls.h"
49 #include "citserver.h"
56 #include "internet_addressing.h"
57 #include "euidindex.h"
58 #include "journaling.h"
59 #include "citadel_dirs.h"
60 #include "clientsocket.h"
63 #include "ctdl_module.h"
66 struct addresses_to_be_filed *atbf = NULL;
68 /* This temp file holds the queue of operations for AdjRefCount() */
69 static FILE *arcfp = NULL;
70 void AdjRefCountList(long *msgnum, long nmsg, int incr);
72 int MessageDebugEnabled = 0;
74 #define DBGLOG(LEVEL) if ((LEVEL != LOG_DEBUG) || (MessageDebugEnabled != 0))
75 #define CCCID CCC->cs_pid
76 #define MSG_syslog(LEVEL, FORMAT, ...) \
77 DBGLOG(LEVEL) syslog(LEVEL, \
81 #define MSGM_syslog(LEVEL, FORMAT) \
82 DBGLOG(LEVEL) syslog(LEVEL, \
87 * These are the four-character field headers we use when outputting
88 * messages in Citadel format (as opposed to RFC822 format).
91 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
92 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
93 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
94 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
95 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
96 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
97 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
98 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
127 * This function is self explanatory.
128 * (What can I say, I'm in a weird mood today...)
130 void remove_any_whitespace_to_the_left_or_right_of_at_symbol(char *name)
134 for (i = 0; i < strlen(name); ++i) {
135 if (name[i] == '@') {
136 while (isspace(name[i - 1]) && i > 0) {
137 strcpy(&name[i - 1], &name[i]);
140 while (isspace(name[i + 1])) {
141 strcpy(&name[i + 1], &name[i + 2]);
149 * Aliasing for network mail.
150 * (Error messages have been commented out, because this is a server.)
152 int alias(char *name)
153 { /* process alias and routing info for mail */
154 struct CitContext *CCC = CC;
157 char aaa[SIZ], bbb[SIZ];
158 char *ignetcfg = NULL;
159 char *ignetmap = NULL;
165 char original_name[256];
166 safestrncpy(original_name, name, sizeof original_name);
169 remove_any_whitespace_to_the_left_or_right_of_at_symbol(name);
170 stripallbut(name, '<', '>');
172 fp = fopen(file_mail_aliases, "r");
174 fp = fopen("/dev/null", "r");
181 while (fgets(aaa, sizeof aaa, fp) != NULL) {
182 while (isspace(name[0]))
183 strcpy(name, &name[1]);
184 aaa[strlen(aaa) - 1] = 0;
186 for (a = 0; a < strlen(aaa); ++a) {
188 strcpy(bbb, &aaa[a + 1]);
192 if (!strcasecmp(name, aaa))
197 /* Hit the Global Address Book */
198 if (CtdlDirectoryLookup(aaa, name, sizeof aaa) == 0) {
202 if (strcasecmp(original_name, name)) {
203 MSG_syslog(LOG_INFO, "%s is being forwarded to %s\n", original_name, name);
206 /* Change "user @ xxx" to "user" if xxx is an alias for this host */
207 for (a=0; a<strlen(name); ++a) {
208 if (name[a] == '@') {
209 if (CtdlHostAlias(&name[a+1]) == hostalias_localhost) {
211 MSG_syslog(LOG_INFO, "Changed to <%s>\n", name);
216 /* determine local or remote type, see citadel.h */
217 at = haschar(name, '@');
218 if (at == 0) return(MES_LOCAL); /* no @'s - local address */
219 if (at > 1) return(MES_ERROR); /* >1 @'s - invalid address */
220 remove_any_whitespace_to_the_left_or_right_of_at_symbol(name);
222 /* figure out the delivery mode */
223 extract_token(node, name, 1, '@', sizeof node);
225 /* If there are one or more dots in the nodename, we assume that it
226 * is an FQDN and will attempt SMTP delivery to the Internet.
228 if (haschar(node, '.') > 0) {
229 return(MES_INTERNET);
232 /* Otherwise we look in the IGnet maps for a valid Citadel node.
233 * Try directly-connected nodes first...
235 ignetcfg = CtdlGetSysConfig(IGNETCFG);
236 for (i=0; i<num_tokens(ignetcfg, '\n'); ++i) {
237 extract_token(buf, ignetcfg, i, '\n', sizeof buf);
238 extract_token(testnode, buf, 0, '|', sizeof testnode);
239 if (!strcasecmp(node, testnode)) {
247 * Then try nodes that are two or more hops away.
249 ignetmap = CtdlGetSysConfig(IGNETMAP);
250 for (i=0; i<num_tokens(ignetmap, '\n'); ++i) {
251 extract_token(buf, ignetmap, i, '\n', sizeof buf);
252 extract_token(testnode, buf, 0, '|', sizeof testnode);
253 if (!strcasecmp(node, testnode)) {
260 /* If we get to this point it's an invalid node name */
266 * Back end for the MSGS command: output message number only.
268 void simple_listing(long msgnum, void *userdata)
270 cprintf("%ld\n", msgnum);
276 * Back end for the MSGS command: output header summary.
278 void headers_listing(long msgnum, void *userdata)
280 struct CtdlMessage *msg;
282 msg = CtdlFetchMessage(msgnum, 0);
284 cprintf("%ld|0|||||\n", msgnum);
288 cprintf("%ld|%s|%s|%s|%s|%s|\n",
290 (msg->cm_fields['T'] ? msg->cm_fields['T'] : "0"),
291 (msg->cm_fields['A'] ? msg->cm_fields['A'] : ""),
292 (msg->cm_fields['N'] ? msg->cm_fields['N'] : ""),
293 (msg->cm_fields['F'] ? msg->cm_fields['F'] : ""),
294 (msg->cm_fields['U'] ? msg->cm_fields['U'] : "")
296 CtdlFreeMessage(msg);
300 * Back end for the MSGS command: output EUID header.
302 void headers_euid(long msgnum, void *userdata)
304 struct CtdlMessage *msg;
306 msg = CtdlFetchMessage(msgnum, 0);
308 cprintf("%ld||\n", msgnum);
312 cprintf("%ld|%s|%s\n",
314 (msg->cm_fields['E'] ? msg->cm_fields['E'] : ""),
315 (msg->cm_fields['T'] ? msg->cm_fields['T'] : "0"));
316 CtdlFreeMessage(msg);
323 /* Determine if a given message matches the fields in a message template.
324 * Return 0 for a successful match.
326 int CtdlMsgCmp(struct CtdlMessage *msg, struct CtdlMessage *template) {
329 /* If there aren't any fields in the template, all messages will
332 if (template == NULL) return(0);
334 /* Null messages are bogus. */
335 if (msg == NULL) return(1);
337 for (i='A'; i<='Z'; ++i) {
338 if (template->cm_fields[i] != NULL) {
339 if (msg->cm_fields[i] == NULL) {
340 /* Considered equal if temmplate is empty string */
341 if (IsEmptyStr(template->cm_fields[i])) continue;
344 if (strcasecmp(msg->cm_fields[i],
345 template->cm_fields[i])) return 1;
349 /* All compares succeeded: we have a match! */
356 * Retrieve the "seen" message list for the current room.
358 void CtdlGetSeen(char *buf, int which_set) {
359 struct CitContext *CCC = CC;
362 /* Learn about the user and room in question */
363 CtdlGetRelationship(&vbuf, &CCC->user, &CCC->room);
365 if (which_set == ctdlsetseen_seen)
366 safestrncpy(buf, vbuf.v_seen, SIZ);
367 if (which_set == ctdlsetseen_answered)
368 safestrncpy(buf, vbuf.v_answered, SIZ);
374 * Manipulate the "seen msgs" string (or other message set strings)
376 void CtdlSetSeen(long *target_msgnums, int num_target_msgnums,
377 int target_setting, int which_set,
378 struct ctdluser *which_user, struct ctdlroom *which_room) {
379 struct CitContext *CCC = CC;
380 struct cdbdata *cdbfr;
394 char *is_set; /* actually an array of booleans */
396 /* Don't bother doing *anything* if we were passed a list of zero messages */
397 if (num_target_msgnums < 1) {
401 /* If no room was specified, we go with the current room. */
403 which_room = &CCC->room;
406 /* If no user was specified, we go with the current user. */
408 which_user = &CCC->user;
411 MSG_syslog(LOG_DEBUG, "CtdlSetSeen(%d msgs starting with %ld, %s, %d) in <%s>\n",
412 num_target_msgnums, target_msgnums[0],
413 (target_setting ? "SET" : "CLEAR"),
417 /* Learn about the user and room in question */
418 CtdlGetRelationship(&vbuf, which_user, which_room);
420 /* Load the message list */
421 cdbfr = cdb_fetch(CDB_MSGLISTS, &which_room->QRnumber, sizeof(long));
423 msglist = (long *) cdbfr->ptr;
424 cdbfr->ptr = NULL; /* CtdlSetSeen() now owns this memory */
425 num_msgs = cdbfr->len / sizeof(long);
428 return; /* No messages at all? No further action. */
431 is_set = malloc(num_msgs * sizeof(char));
432 memset(is_set, 0, (num_msgs * sizeof(char)) );
434 /* Decide which message set we're manipulating */
436 case ctdlsetseen_seen:
437 vset = NewStrBufPlain(vbuf.v_seen, -1);
439 case ctdlsetseen_answered:
440 vset = NewStrBufPlain(vbuf.v_answered, -1);
447 #if 0 /* This is a special diagnostic section. Do not allow it to run during normal operation. */
448 MSG_syslog(LOG_DEBUG, "There are %d messages in the room.\n", num_msgs);
449 for (i=0; i<num_msgs; ++i) {
450 if ((i > 0) && (msglist[i] <= msglist[i-1])) abort();
452 MSG_syslog(LOG_DEBUG, "We are twiddling %d of them.\n", num_target_msgnums);
453 for (k=0; k<num_target_msgnums; ++k) {
454 if ((k > 0) && (target_msgnums[k] <= target_msgnums[k-1])) abort();
458 MSG_syslog(LOG_DEBUG, "before update: %s\n", ChrPtr(vset));
460 /* Translate the existing sequence set into an array of booleans */
461 setstr = NewStrBuf();
465 while (StrBufExtract_NextToken(setstr, vset, &pvset, ',') >= 0) {
467 StrBufExtract_token(lostr, setstr, 0, ':');
468 if (StrBufNum_tokens(setstr, ':') >= 2) {
469 StrBufExtract_token(histr, setstr, 1, ':');
473 StrBufAppendBuf(histr, lostr, 0);
476 if (!strcmp(ChrPtr(histr), "*")) {
483 for (i = 0; i < num_msgs; ++i) {
484 if ((msglist[i] >= lo) && (msglist[i] <= hi)) {
494 /* Now translate the array of booleans back into a sequence set */
500 for (i=0; i<num_msgs; ++i) {
504 for (k=0; k<num_target_msgnums; ++k) {
505 if (msglist[i] == target_msgnums[k]) {
506 is_seen = target_setting;
510 if ((was_seen == 0) && (is_seen == 1)) {
513 else if ((was_seen == 1) && (is_seen == 0)) {
516 if (StrLength(vset) > 0) {
517 StrBufAppendBufPlain(vset, HKEY(","), 0);
520 StrBufAppendPrintf(vset, "%ld", hi);
523 StrBufAppendPrintf(vset, "%ld:%ld", lo, hi);
527 if ((is_seen) && (i == num_msgs - 1)) {
528 if (StrLength(vset) > 0) {
529 StrBufAppendBufPlain(vset, HKEY(","), 0);
531 if ((i==0) || (was_seen == 0)) {
532 StrBufAppendPrintf(vset, "%ld", msglist[i]);
535 StrBufAppendPrintf(vset, "%ld:%ld", lo, msglist[i]);
543 * We will have to stuff this string back into a 4096 byte buffer, so if it's
544 * larger than that now, truncate it by removing tokens from the beginning.
545 * The limit of 100 iterations is there to prevent an infinite loop in case
546 * something unexpected happens.
548 int number_of_truncations = 0;
549 while ( (StrLength(vset) > SIZ) && (number_of_truncations < 100) ) {
550 StrBufRemove_token(vset, 0, ',');
551 ++number_of_truncations;
555 * If we're truncating the sequence set of messages marked with the 'seen' flag,
556 * we want the earliest messages (the truncated ones) to be marked, not unmarked.
557 * Otherwise messages at the beginning will suddenly appear to be 'unseen'.
559 if ( (which_set == ctdlsetseen_seen) && (number_of_truncations > 0) ) {
561 first_tok = NewStrBuf();
562 StrBufExtract_token(first_tok, vset, 0, ',');
563 StrBufRemove_token(vset, 0, ',');
565 if (StrBufNum_tokens(first_tok, ':') > 1) {
566 StrBufRemove_token(first_tok, 0, ':');
570 new_set = NewStrBuf();
571 StrBufAppendBufPlain(new_set, HKEY("1:"), 0);
572 StrBufAppendBuf(new_set, first_tok, 0);
573 StrBufAppendBufPlain(new_set, HKEY(":"), 0);
574 StrBufAppendBuf(new_set, vset, 0);
577 FreeStrBuf(&first_tok);
581 MSG_syslog(LOG_DEBUG, " after update: %s\n", ChrPtr(vset));
583 /* Decide which message set we're manipulating */
585 case ctdlsetseen_seen:
586 safestrncpy(vbuf.v_seen, ChrPtr(vset), sizeof vbuf.v_seen);
588 case ctdlsetseen_answered:
589 safestrncpy(vbuf.v_answered, ChrPtr(vset), sizeof vbuf.v_answered);
595 CtdlSetRelationship(&vbuf, which_user, which_room);
601 * API function to perform an operation for each qualifying message in the
602 * current room. (Returns the number of messages processed.)
604 int CtdlForEachMessage(int mode, long ref, char *search_string,
606 struct CtdlMessage *compare,
607 ForEachMsgCallback CallBack,
610 struct CitContext *CCC = CC;
613 struct cdbdata *cdbfr;
614 long *msglist = NULL;
616 int num_processed = 0;
619 struct CtdlMessage *msg = NULL;
622 int printed_lastold = 0;
623 int num_search_msgs = 0;
624 long *search_msgs = NULL;
626 int need_to_free_re = 0;
629 if ((content_type) && (!IsEmptyStr(content_type))) {
630 regcomp(&re, content_type, 0);
634 /* Learn about the user and room in question */
635 if (server_shutting_down) {
636 if (need_to_free_re) regfree(&re);
639 CtdlGetUser(&CCC->user, CCC->curr_user);
641 if (server_shutting_down) {
642 if (need_to_free_re) regfree(&re);
645 CtdlGetRelationship(&vbuf, &CCC->user, &CCC->room);
647 if (server_shutting_down) {
648 if (need_to_free_re) regfree(&re);
652 /* Load the message list */
653 cdbfr = cdb_fetch(CDB_MSGLISTS, &CCC->room.QRnumber, sizeof(long));
655 if (need_to_free_re) regfree(&re);
656 return 0; /* No messages at all? No further action. */
659 msglist = (long *) cdbfr->ptr;
660 num_msgs = cdbfr->len / sizeof(long);
662 cdbfr->ptr = NULL; /* clear this so that cdb_free() doesn't free it */
663 cdb_free(cdbfr); /* we own this memory now */
666 * Now begin the traversal.
668 if (num_msgs > 0) for (a = 0; a < num_msgs; ++a) {
670 /* If the caller is looking for a specific MIME type, filter
671 * out all messages which are not of the type requested.
673 if ((content_type != NULL) && (!IsEmptyStr(content_type))) {
675 /* This call to GetMetaData() sits inside this loop
676 * so that we only do the extra database read per msg
677 * if we need to. Doing the extra read all the time
678 * really kills the server. If we ever need to use
679 * metadata for another search criterion, we need to
680 * move the read somewhere else -- but still be smart
681 * enough to only do the read if the caller has
682 * specified something that will need it.
684 if (server_shutting_down) {
685 if (need_to_free_re) regfree(&re);
689 GetMetaData(&smi, msglist[a]);
691 /* if (strcasecmp(smi.meta_content_type, content_type)) { old non-regex way */
692 if (regexec(&re, smi.meta_content_type, 1, &pm, 0) != 0) {
698 num_msgs = sort_msglist(msglist, num_msgs);
700 /* If a template was supplied, filter out the messages which
701 * don't match. (This could induce some delays!)
704 if (compare != NULL) {
705 for (a = 0; a < num_msgs; ++a) {
706 if (server_shutting_down) {
707 if (need_to_free_re) regfree(&re);
711 msg = CtdlFetchMessage(msglist[a], 1);
713 if (CtdlMsgCmp(msg, compare)) {
716 CtdlFreeMessage(msg);
722 /* If a search string was specified, get a message list from
723 * the full text index and remove messages which aren't on both
727 * Since the lists are sorted and strictly ascending, and the
728 * output list is guaranteed to be shorter than or equal to the
729 * input list, we overwrite the bottom of the input list. This
730 * eliminates the need to memmove big chunks of the list over and
733 if ( (num_msgs > 0) && (mode == MSGS_SEARCH) && (search_string) ) {
735 /* Call search module via hook mechanism.
736 * NULL means use any search function available.
737 * otherwise replace with a char * to name of search routine
739 CtdlModuleDoSearch(&num_search_msgs, &search_msgs, search_string, "fulltext");
741 if (num_search_msgs > 0) {
745 orig_num_msgs = num_msgs;
747 for (i=0; i<orig_num_msgs; ++i) {
748 for (j=0; j<num_search_msgs; ++j) {
749 if (msglist[i] == search_msgs[j]) {
750 msglist[num_msgs++] = msglist[i];
756 num_msgs = 0; /* No messages qualify */
758 if (search_msgs != NULL) free(search_msgs);
760 /* Now that we've purged messages which don't contain the search
761 * string, treat a MSGS_SEARCH just like a MSGS_ALL from this
768 * Now iterate through the message list, according to the
769 * criteria supplied by the caller.
772 for (a = 0; a < num_msgs; ++a) {
773 if (server_shutting_down) {
774 if (need_to_free_re) regfree(&re);
776 return num_processed;
778 thismsg = msglist[a];
779 if (mode == MSGS_ALL) {
783 is_seen = is_msg_in_sequence_set(
784 vbuf.v_seen, thismsg);
785 if (is_seen) lastold = thismsg;
791 || ((mode == MSGS_OLD) && (is_seen))
792 || ((mode == MSGS_NEW) && (!is_seen))
793 || ((mode == MSGS_LAST) && (a >= (num_msgs - ref)))
794 || ((mode == MSGS_FIRST) && (a < ref))
795 || ((mode == MSGS_GT) && (thismsg > ref))
796 || ((mode == MSGS_LT) && (thismsg < ref))
797 || ((mode == MSGS_EQ) && (thismsg == ref))
800 if ((mode == MSGS_NEW) && (CCC->user.flags & US_LASTOLD) && (lastold > 0L) && (printed_lastold == 0) && (!is_seen)) {
802 CallBack(lastold, userdata);
806 if (CallBack) CallBack(thismsg, userdata);
810 if (need_to_free_re) regfree(&re);
813 * We cache the most recent msglist in order to do security checks later
815 if (CCC->client_socket > 0) {
816 if (CCC->cached_msglist != NULL) {
817 free(CCC->cached_msglist);
819 CCC->cached_msglist = msglist;
820 CCC->cached_num_msgs = num_msgs;
826 return num_processed;
832 * cmd_msgs() - get list of message #'s in this room
833 * implements the MSGS server command using CtdlForEachMessage()
835 void cmd_msgs(char *cmdbuf)
844 int with_template = 0;
845 struct CtdlMessage *template = NULL;
846 char search_string[1024];
847 ForEachMsgCallback CallBack;
849 if (CtdlAccessCheck(ac_logged_in_or_guest)) return;
851 extract_token(which, cmdbuf, 0, '|', sizeof which);
852 cm_ref = extract_int(cmdbuf, 1);
853 extract_token(search_string, cmdbuf, 1, '|', sizeof search_string);
854 with_template = extract_int(cmdbuf, 2);
855 switch (extract_int(cmdbuf, 3))
859 CallBack = simple_listing;
862 CallBack = headers_listing;
865 CallBack = headers_euid;
870 if (!strncasecmp(which, "OLD", 3))
872 else if (!strncasecmp(which, "NEW", 3))
874 else if (!strncasecmp(which, "FIRST", 5))
876 else if (!strncasecmp(which, "LAST", 4))
878 else if (!strncasecmp(which, "GT", 2))
880 else if (!strncasecmp(which, "LT", 2))
882 else if (!strncasecmp(which, "SEARCH", 6))
887 if ( (mode == MSGS_SEARCH) && (!config.c_enable_fulltext) ) {
888 cprintf("%d Full text index is not enabled on this server.\n",
889 ERROR + CMD_NOT_SUPPORTED);
895 cprintf("%d Send template then receive message list\n",
897 template = (struct CtdlMessage *)
898 malloc(sizeof(struct CtdlMessage));
899 memset(template, 0, sizeof(struct CtdlMessage));
900 template->cm_magic = CTDLMESSAGE_MAGIC;
901 template->cm_anon_type = MES_NORMAL;
903 while(client_getln(buf, sizeof buf) >= 0 && strcmp(buf,"000")) {
904 extract_token(tfield, buf, 0, '|', sizeof tfield);
905 extract_token(tvalue, buf, 1, '|', sizeof tvalue);
906 for (i='A'; i<='Z'; ++i) if (msgkeys[i]!=NULL) {
907 if (!strcasecmp(tfield, msgkeys[i])) {
908 template->cm_fields[i] =
916 cprintf("%d \n", LISTING_FOLLOWS);
919 CtdlForEachMessage(mode,
920 ( (mode == MSGS_SEARCH) ? 0 : cm_ref ),
921 ( (mode == MSGS_SEARCH) ? search_string : NULL ),
926 if (template != NULL) CtdlFreeMessage(template);
934 * help_subst() - support routine for help file viewer
936 void help_subst(char *strbuf, char *source, char *dest)
941 while (p = pattern2(strbuf, source), (p >= 0)) {
942 strcpy(workbuf, &strbuf[p + strlen(source)]);
943 strcpy(&strbuf[p], dest);
944 strcat(strbuf, workbuf);
949 void do_help_subst(char *buffer)
953 help_subst(buffer, "^nodename", config.c_nodename);
954 help_subst(buffer, "^humannode", config.c_humannode);
955 help_subst(buffer, "^fqdn", config.c_fqdn);
956 help_subst(buffer, "^username", CC->user.fullname);
957 snprintf(buf2, sizeof buf2, "%ld", CC->user.usernum);
958 help_subst(buffer, "^usernum", buf2);
959 help_subst(buffer, "^sysadm", config.c_sysadm);
960 help_subst(buffer, "^variantname", CITADEL);
961 snprintf(buf2, sizeof buf2, "%d", config.c_maxsessions);
962 help_subst(buffer, "^maxsessions", buf2);
963 help_subst(buffer, "^bbsdir", ctdl_message_dir);
969 * memfmout() - Citadel text formatter and paginator.
970 * Although the original purpose of this routine was to format
971 * text to the reader's screen width, all we're really using it
972 * for here is to format text out to 80 columns before sending it
973 * to the client. The client software may reformat it again.
976 char *mptr, /* where are we going to get our text from? */
977 const char *nl /* string to terminate lines with */
979 struct CitContext *CCC = CC;
981 unsigned char ch = 0;
988 while (ch=*(mptr++), ch != 0) {
991 if (client_write(outbuf, len) == -1)
993 MSGM_syslog(LOG_ERR, "memfmout(): aborting due to write failure.\n");
997 if (client_write(nl, nllen) == -1)
999 MSGM_syslog(LOG_ERR, "memfmout(): aborting due to write failure.\n");
1004 else if (ch == '\r') {
1005 /* Ignore carriage returns. Newlines are always LF or CRLF but never CR. */
1007 else if (isspace(ch)) {
1008 if (column > 72) { /* Beyond 72 columns, break on the next space */
1009 if (client_write(outbuf, len) == -1)
1011 MSGM_syslog(LOG_ERR, "memfmout(): aborting due to write failure.\n");
1015 if (client_write(nl, nllen) == -1)
1017 MSGM_syslog(LOG_ERR, "memfmout(): aborting due to write failure.\n");
1030 if (column > 1000) { /* Beyond 1000 columns, break anywhere */
1031 if (client_write(outbuf, len) == -1)
1033 MSGM_syslog(LOG_ERR, "memfmout(): aborting due to write failure.\n");
1037 if (client_write(nl, nllen) == -1)
1039 MSGM_syslog(LOG_ERR, "memfmout(): aborting due to write failure.\n");
1047 if (client_write(outbuf, len) == -1)
1049 MSGM_syslog(LOG_ERR, "memfmout(): aborting due to write failure.\n");
1053 client_write(nl, nllen);
1061 * Callback function for mime parser that simply lists the part
1063 void list_this_part(char *name, char *filename, char *partnum, char *disp,
1064 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
1065 char *cbid, void *cbuserdata)
1069 ma = (struct ma_info *)cbuserdata;
1070 if (ma->is_ma == 0) {
1071 cprintf("part=%s|%s|%s|%s|%s|%ld|%s|%s\n",
1084 * Callback function for multipart prefix
1086 void list_this_pref(char *name, char *filename, char *partnum, char *disp,
1087 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
1088 char *cbid, void *cbuserdata)
1092 ma = (struct ma_info *)cbuserdata;
1093 if (!strcasecmp(cbtype, "multipart/alternative")) {
1097 if (ma->is_ma == 0) {
1098 cprintf("pref=%s|%s\n", partnum, cbtype);
1103 * Callback function for multipart sufffix
1105 void list_this_suff(char *name, char *filename, char *partnum, char *disp,
1106 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
1107 char *cbid, void *cbuserdata)
1111 ma = (struct ma_info *)cbuserdata;
1112 if (ma->is_ma == 0) {
1113 cprintf("suff=%s|%s\n", partnum, cbtype);
1115 if (!strcasecmp(cbtype, "multipart/alternative")) {
1122 * Callback function for mime parser that opens a section for downloading
1124 void mime_download(char *name, char *filename, char *partnum, char *disp,
1125 void *content, char *cbtype, char *cbcharset, size_t length,
1126 char *encoding, char *cbid, void *cbuserdata)
1129 CitContext *CCC = MyContext();
1131 /* Silently go away if there's already a download open. */
1132 if (CCC->download_fp != NULL)
1136 (!IsEmptyStr(partnum) && (!strcasecmp(CCC->download_desired_section, partnum)))
1137 || (!IsEmptyStr(cbid) && (!strcasecmp(CCC->download_desired_section, cbid)))
1139 CCC->download_fp = tmpfile();
1140 if (CCC->download_fp == NULL) {
1141 MSG_syslog(LOG_EMERG, "mime_download(): Couldn't write: %s\n",
1143 cprintf("%d cannot open temporary file: %s\n",
1144 ERROR + INTERNAL_ERROR, strerror(errno));
1148 rv = fwrite(content, length, 1, CC->download_fp);
1150 MSG_syslog(LOG_EMERG, "mime_download(): Couldn't write: %s\n",
1152 cprintf("%d unable to write tempfile.\n",
1154 fclose(CCC->download_fp);
1155 CCC->download_fp = NULL;
1158 fflush(CCC->download_fp);
1159 rewind(CCC->download_fp);
1161 OpenCmdResult(filename, cbtype);
1168 * Callback function for mime parser that outputs a section all at once.
1169 * We can specify the desired section by part number *or* content-id.
1171 void mime_spew_section(char *name, char *filename, char *partnum, char *disp,
1172 void *content, char *cbtype, char *cbcharset, size_t length,
1173 char *encoding, char *cbid, void *cbuserdata)
1175 int *found_it = (int *)cbuserdata;
1178 (!IsEmptyStr(partnum) && (!strcasecmp(CC->download_desired_section, partnum)))
1179 || (!IsEmptyStr(cbid) && (!strcasecmp(CC->download_desired_section, cbid)))
1182 cprintf("%d %d|-1|%s|%s|%s\n",
1189 client_write(content, length);
1195 * Load a message from disk into memory.
1196 * This is used by CtdlOutputMsg() and other fetch functions.
1198 * NOTE: Caller is responsible for freeing the returned CtdlMessage struct
1199 * using the CtdlMessageFree() function.
1201 struct CtdlMessage *CtdlFetchMessage(long msgnum, int with_body)
1203 struct CitContext *CCC = CC;
1204 struct cdbdata *dmsgtext;
1205 struct CtdlMessage *ret = NULL;
1209 cit_uint8_t field_header;
1211 MSG_syslog(LOG_DEBUG, "CtdlFetchMessage(%ld, %d)\n", msgnum, with_body);
1212 dmsgtext = cdb_fetch(CDB_MSGMAIN, &msgnum, sizeof(long));
1213 if (dmsgtext == NULL) {
1216 mptr = dmsgtext->ptr;
1217 upper_bound = mptr + dmsgtext->len;
1219 /* Parse the three bytes that begin EVERY message on disk.
1220 * The first is always 0xFF, the on-disk magic number.
1221 * The second is the anonymous/public type byte.
1222 * The third is the format type byte (vari, fixed, or MIME).
1226 MSG_syslog(LOG_ERR, "Message %ld appears to be corrupted.\n", msgnum);
1230 ret = (struct CtdlMessage *) malloc(sizeof(struct CtdlMessage));
1231 memset(ret, 0, sizeof(struct CtdlMessage));
1233 ret->cm_magic = CTDLMESSAGE_MAGIC;
1234 ret->cm_anon_type = *mptr++; /* Anon type byte */
1235 ret->cm_format_type = *mptr++; /* Format type byte */
1238 * The rest is zero or more arbitrary fields. Load them in.
1239 * We're done when we encounter either a zero-length field or
1240 * have just processed the 'M' (message text) field.
1243 if (mptr >= upper_bound) {
1246 field_header = *mptr++;
1247 ret->cm_fields[field_header] = strdup(mptr);
1249 while (*mptr++ != 0); /* advance to next field */
1251 } while ((mptr < upper_bound) && (field_header != 'M'));
1255 /* Always make sure there's something in the msg text field. If
1256 * it's NULL, the message text is most likely stored separately,
1257 * so go ahead and fetch that. Failing that, just set a dummy
1258 * body so other code doesn't barf.
1260 if ( (ret->cm_fields['M'] == NULL) && (with_body) ) {
1261 dmsgtext = cdb_fetch(CDB_BIGMSGS, &msgnum, sizeof(long));
1262 if (dmsgtext != NULL) {
1263 ret->cm_fields['M'] = dmsgtext->ptr;
1264 dmsgtext->ptr = NULL;
1268 if (ret->cm_fields['M'] == NULL) {
1269 ret->cm_fields['M'] = strdup("\r\n\r\n (no text)\r\n");
1272 /* Perform "before read" hooks (aborting if any return nonzero) */
1273 if (PerformMessageHooks(ret, EVT_BEFOREREAD) > 0) {
1274 CtdlFreeMessage(ret);
1283 * Returns 1 if the supplied pointer points to a valid Citadel message.
1284 * If the pointer is NULL or the magic number check fails, returns 0.
1286 int is_valid_message(struct CtdlMessage *msg) {
1289 if ((msg->cm_magic) != CTDLMESSAGE_MAGIC) {
1290 struct CitContext *CCC = CC;
1291 MSGM_syslog(LOG_WARNING, "is_valid_message() -- self-check failed\n");
1297 void CtdlFreeMessageContents(struct CtdlMessage *msg)
1301 for (i = 0; i < 256; ++i)
1302 if (msg->cm_fields[i] != NULL) {
1303 free(msg->cm_fields[i]);
1306 msg->cm_magic = 0; /* just in case */
1309 * 'Destructor' for struct CtdlMessage
1311 void CtdlFreeMessage(struct CtdlMessage *msg)
1313 if (is_valid_message(msg) == 0)
1315 if (msg != NULL) free (msg);
1318 CtdlFreeMessageContents(msg);
1324 * Pre callback function for multipart/alternative
1326 * NOTE: this differs from the standard behavior for a reason. Normally when
1327 * displaying multipart/alternative you want to show the _last_ usable
1328 * format in the message. Here we show the _first_ one, because it's
1329 * usually text/plain. Since this set of functions is designed for text
1330 * output to non-MIME-aware clients, this is the desired behavior.
1333 void fixed_output_pre(char *name, char *filename, char *partnum, char *disp,
1334 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
1335 char *cbid, void *cbuserdata)
1337 struct CitContext *CCC = CC;
1340 ma = (struct ma_info *)cbuserdata;
1341 MSG_syslog(LOG_DEBUG, "fixed_output_pre() type=<%s>\n", cbtype);
1342 if (!strcasecmp(cbtype, "multipart/alternative")) {
1346 if (!strcasecmp(cbtype, "message/rfc822")) {
1352 * Post callback function for multipart/alternative
1354 void fixed_output_post(char *name, char *filename, char *partnum, char *disp,
1355 void *content, char *cbtype, char *cbcharset, size_t length,
1356 char *encoding, char *cbid, void *cbuserdata)
1358 struct CitContext *CCC = CC;
1361 ma = (struct ma_info *)cbuserdata;
1362 MSG_syslog(LOG_DEBUG, "fixed_output_post() type=<%s>\n", cbtype);
1363 if (!strcasecmp(cbtype, "multipart/alternative")) {
1367 if (!strcasecmp(cbtype, "message/rfc822")) {
1373 * Inline callback function for mime parser that wants to display text
1375 void fixed_output(char *name, char *filename, char *partnum, char *disp,
1376 void *content, char *cbtype, char *cbcharset, size_t length,
1377 char *encoding, char *cbid, void *cbuserdata)
1379 struct CitContext *CCC = CC;
1385 ma = (struct ma_info *)cbuserdata;
1387 MSG_syslog(LOG_DEBUG,
1388 "fixed_output() part %s: %s (%s) (%ld bytes)\n",
1389 partnum, filename, cbtype, (long)length);
1392 * If we're in the middle of a multipart/alternative scope and
1393 * we've already printed another section, skip this one.
1395 if ( (ma->is_ma) && (ma->did_print) ) {
1396 MSG_syslog(LOG_DEBUG, "Skipping part %s (%s)\n", partnum, cbtype);
1401 if ( (!strcasecmp(cbtype, "text/plain"))
1402 || (IsEmptyStr(cbtype)) ) {
1405 client_write(wptr, length);
1406 if (wptr[length-1] != '\n') {
1413 if (!strcasecmp(cbtype, "text/html")) {
1414 ptr = html_to_ascii(content, length, 80, 0);
1416 client_write(ptr, wlen);
1417 if ((wlen > 0) && (ptr[wlen-1] != '\n')) {
1424 if (ma->use_fo_hooks) {
1425 if (PerformFixedOutputHooks(cbtype, content, length)) {
1426 /* above function returns nonzero if it handled the part */
1431 if (strncasecmp(cbtype, "multipart/", 10)) {
1432 cprintf("Part %s: %s (%s) (%ld bytes)\r\n",
1433 partnum, filename, cbtype, (long)length);
1439 * The client is elegant and sophisticated and wants to be choosy about
1440 * MIME content types, so figure out which multipart/alternative part
1441 * we're going to send.
1443 * We use a system of weights. When we find a part that matches one of the
1444 * MIME types we've declared as preferential, we can store it in ma->chosen_part
1445 * and then set ma->chosen_pref to that MIME type's position in our preference
1446 * list. If we then hit another match, we only replace the first match if
1447 * the preference value is lower.
1449 void choose_preferred(char *name, char *filename, char *partnum, char *disp,
1450 void *content, char *cbtype, char *cbcharset, size_t length,
1451 char *encoding, char *cbid, void *cbuserdata)
1453 struct CitContext *CCC = CC;
1458 ma = (struct ma_info *)cbuserdata;
1460 // NOTE: REMOVING THIS CONDITIONAL FIXES BUG 220
1461 // http://bugzilla.citadel.org/show_bug.cgi?id=220
1462 // I don't know if there are any side effects! Please TEST TEST TEST
1463 //if (ma->is_ma > 0) {
1465 for (i=0; i<num_tokens(CCC->preferred_formats, '|'); ++i) {
1466 extract_token(buf, CCC->preferred_formats, i, '|', sizeof buf);
1467 if ( (!strcasecmp(buf, cbtype)) && (!ma->freeze) ) {
1468 if (i < ma->chosen_pref) {
1469 MSG_syslog(LOG_DEBUG, "Setting chosen part: <%s>\n", partnum);
1470 safestrncpy(ma->chosen_part, partnum, sizeof ma->chosen_part);
1471 ma->chosen_pref = i;
1478 * Now that we've chosen our preferred part, output it.
1480 void output_preferred(char *name,
1492 struct CitContext *CCC = CC;
1495 int add_newline = 0;
1498 char *decoded = NULL;
1499 size_t bytes_decoded;
1502 ma = (struct ma_info *)cbuserdata;
1504 /* This is not the MIME part you're looking for... */
1505 if (strcasecmp(partnum, ma->chosen_part)) return;
1507 /* If the content-type of this part is in our preferred formats
1508 * list, we can simply output it verbatim.
1510 for (i=0; i<num_tokens(CCC->preferred_formats, '|'); ++i) {
1511 extract_token(buf, CCC->preferred_formats, i, '|', sizeof buf);
1512 if (!strcasecmp(buf, cbtype)) {
1513 /* Yeah! Go! W00t!! */
1514 if (ma->dont_decode == 0)
1515 rc = mime_decode_now (content,
1521 break; /* Give us the chance, maybe theres another one. */
1523 if (rc == 0) text_content = (char *)content;
1525 text_content = decoded;
1526 length = bytes_decoded;
1529 if (text_content[length-1] != '\n') {
1532 cprintf("Content-type: %s", cbtype);
1533 if (!IsEmptyStr(cbcharset)) {
1534 cprintf("; charset=%s", cbcharset);
1536 cprintf("\nContent-length: %d\n",
1537 (int)(length + add_newline) );
1538 if (!IsEmptyStr(encoding)) {
1539 cprintf("Content-transfer-encoding: %s\n", encoding);
1542 cprintf("Content-transfer-encoding: 7bit\n");
1544 cprintf("X-Citadel-MSG4-Partnum: %s\n", partnum);
1546 if (client_write(text_content, length) == -1)
1548 MSGM_syslog(LOG_ERR, "output_preferred(): aborting due to write failure.\n");
1551 if (add_newline) cprintf("\n");
1552 if (decoded != NULL) free(decoded);
1557 /* No translations required or possible: output as text/plain */
1558 cprintf("Content-type: text/plain\n\n");
1560 if (ma->dont_decode == 0)
1561 rc = mime_decode_now (content,
1567 return; /* Give us the chance, maybe theres another one. */
1569 if (rc == 0) text_content = (char *)content;
1571 text_content = decoded;
1572 length = bytes_decoded;
1575 fixed_output(name, filename, partnum, disp, text_content, cbtype, cbcharset,
1576 length, encoding, cbid, cbuserdata);
1577 if (decoded != NULL) free(decoded);
1582 char desired_section[64];
1589 * Callback function for
1591 void extract_encapsulated_message(char *name, char *filename, char *partnum, char *disp,
1592 void *content, char *cbtype, char *cbcharset, size_t length,
1593 char *encoding, char *cbid, void *cbuserdata)
1595 struct encapmsg *encap;
1597 encap = (struct encapmsg *)cbuserdata;
1599 /* Only proceed if this is the desired section... */
1600 if (!strcasecmp(encap->desired_section, partnum)) {
1601 encap->msglen = length;
1602 encap->msg = malloc(length + 2);
1603 memcpy(encap->msg, content, length);
1610 * Determine whether the specified message exists in the cached_msglist
1611 * (This is a security check)
1613 int check_cached_msglist(long msgnum) {
1614 struct CitContext *CCC = CC;
1616 /* cases in which we skip the check */
1617 if (!CCC) return om_ok; /* not a session */
1618 if (CCC->client_socket <= 0) return om_ok; /* not a client session */
1619 if (CCC->cached_msglist == NULL) return om_access_denied; /* no msglist fetched */
1620 if (CCC->cached_num_msgs == 0) return om_access_denied; /* nothing to check */
1623 /* Do a binary search within the cached_msglist for the requested msgnum */
1625 int max = (CC->cached_num_msgs - 1);
1627 while (max >= min) {
1628 int middle = min + (max-min) / 2 ;
1629 if (msgnum == CCC->cached_msglist[middle]) {
1632 if (msgnum > CC->cached_msglist[middle]) {
1640 return om_access_denied;
1645 * Determine whether the currently logged in session has permission to read
1646 * messages in the current room.
1648 int CtdlDoIHavePermissionToReadMessagesInThisRoom(void) {
1649 if ( (!(CC->logged_in))
1650 && (!(CC->internal_pgm))
1651 && (!config.c_guest_logins)
1653 return(om_not_logged_in);
1660 * Get a message off disk. (returns om_* values found in msgbase.h)
1663 int CtdlOutputMsg(long msg_num, /* message number (local) to fetch */
1664 int mode, /* how would you like that message? */
1665 int headers_only, /* eschew the message body? */
1666 int do_proto, /* do Citadel protocol responses? */
1667 int crlf, /* Use CRLF newlines instead of LF? */
1668 char *section, /* NULL or a message/rfc822 section */
1669 int flags /* various flags; see msgbase.h */
1671 struct CitContext *CCC = CC;
1672 struct CtdlMessage *TheMessage = NULL;
1673 int retcode = om_no_such_msg;
1674 struct encapmsg encap;
1677 MSG_syslog(LOG_DEBUG, "CtdlOutputMsg(msgnum=%ld, mode=%d, section=%s)\n",
1679 (section ? section : "<>")
1682 r = CtdlDoIHavePermissionToReadMessagesInThisRoom();
1685 if (r == om_not_logged_in) {
1686 cprintf("%d Not logged in.\n", ERROR + NOT_LOGGED_IN);
1689 cprintf("%d An unknown error has occurred.\n", ERROR);
1696 * Check to make sure the message is actually IN this room
1698 r = check_cached_msglist(msg_num);
1699 if (r == om_access_denied) {
1700 /* Not in the cache? We get ONE shot to check it again. */
1701 CtdlForEachMessage(MSGS_ALL, 0L, NULL, NULL, NULL, NULL, NULL);
1702 r = check_cached_msglist(msg_num);
1705 MSG_syslog(LOG_DEBUG, "Security check fail: message %ld is not in %s\n",
1706 msg_num, CCC->room.QRname
1709 if (r == om_access_denied) {
1710 cprintf("%d message %ld was not found in this room\n",
1711 ERROR + HIGHER_ACCESS_REQUIRED,
1720 * Fetch the message from disk. If we're in HEADERS_FAST mode,
1721 * request that we don't even bother loading the body into memory.
1723 if (headers_only == HEADERS_FAST) {
1724 TheMessage = CtdlFetchMessage(msg_num, 0);
1727 TheMessage = CtdlFetchMessage(msg_num, 1);
1730 if (TheMessage == NULL) {
1731 if (do_proto) cprintf("%d Can't locate msg %ld on disk\n",
1732 ERROR + MESSAGE_NOT_FOUND, msg_num);
1733 return(om_no_such_msg);
1736 /* Here is the weird form of this command, to process only an
1737 * encapsulated message/rfc822 section.
1739 if (section) if (!IsEmptyStr(section)) if (strcmp(section, "0")) {
1740 memset(&encap, 0, sizeof encap);
1741 safestrncpy(encap.desired_section, section, sizeof encap.desired_section);
1742 mime_parser(TheMessage->cm_fields['M'],
1744 *extract_encapsulated_message,
1745 NULL, NULL, (void *)&encap, 0
1747 CtdlFreeMessage(TheMessage);
1751 encap.msg[encap.msglen] = 0;
1752 TheMessage = convert_internet_message(encap.msg);
1753 encap.msg = NULL; /* no free() here, TheMessage owns it now */
1755 /* Now we let it fall through to the bottom of this
1756 * function, because TheMessage now contains the
1757 * encapsulated message instead of the top-level
1758 * message. Isn't that neat?
1763 if (do_proto) cprintf("%d msg %ld has no part %s\n",
1764 ERROR + MESSAGE_NOT_FOUND, msg_num, section);
1765 retcode = om_no_such_msg;
1770 /* Ok, output the message now */
1771 retcode = CtdlOutputPreLoadedMsg(TheMessage, mode, headers_only, do_proto, crlf, flags);
1772 CtdlFreeMessage(TheMessage);
1778 char *qp_encode_email_addrs(char *source)
1780 struct CitContext *CCC = CC;
1781 char *user, *node, *name;
1782 const char headerStr[] = "=?UTF-8?Q?";
1786 int need_to_encode = 0;
1792 long nAddrPtrMax = 50;
1797 if (source == NULL) return source;
1798 if (IsEmptyStr(source)) return source;
1800 MSG_syslog(LOG_DEBUG, "qp_encode_email_addrs: [%s]\n", source);
1802 AddrPtr = malloc (sizeof (long) * nAddrPtrMax);
1803 AddrUtf8 = malloc (sizeof (long) * nAddrPtrMax);
1804 memset(AddrUtf8, 0, sizeof (long) * nAddrPtrMax);
1807 while (!IsEmptyStr (&source[i])) {
1808 if (nColons >= nAddrPtrMax){
1811 ptr = (long *) malloc(sizeof (long) * nAddrPtrMax * 2);
1812 memcpy (ptr, AddrPtr, sizeof (long) * nAddrPtrMax);
1813 free (AddrPtr), AddrPtr = ptr;
1815 ptr = (long *) malloc(sizeof (long) * nAddrPtrMax * 2);
1816 memset(&ptr[nAddrPtrMax], 0,
1817 sizeof (long) * nAddrPtrMax);
1819 memcpy (ptr, AddrUtf8, sizeof (long) * nAddrPtrMax);
1820 free (AddrUtf8), AddrUtf8 = ptr;
1823 if (((unsigned char) source[i] < 32) ||
1824 ((unsigned char) source[i] > 126)) {
1826 AddrUtf8[nColons] = 1;
1828 if (source[i] == '"')
1829 InQuotes = !InQuotes;
1830 if (!InQuotes && source[i] == ',') {
1831 AddrPtr[nColons] = i;
1836 if (need_to_encode == 0) {
1843 EncodedMaxLen = nColons * (sizeof(headerStr) + 3) + SourceLen * 3;
1844 Encoded = (char*) malloc (EncodedMaxLen);
1846 for (i = 0; i < nColons; i++)
1847 source[AddrPtr[i]++] = '\0';
1848 /* TODO: if libidn, this might get larger*/
1849 user = malloc(SourceLen + 1);
1850 node = malloc(SourceLen + 1);
1851 name = malloc(SourceLen + 1);
1855 for (i = 0; i < nColons && nPtr != NULL; i++) {
1856 nmax = EncodedMaxLen - (nPtr - Encoded);
1858 process_rfc822_addr(&source[AddrPtr[i]],
1862 /* TODO: libIDN here ! */
1863 if (IsEmptyStr(name)) {
1864 n = snprintf(nPtr, nmax,
1865 (i==0)?"%s@%s" : ",%s@%s",
1869 EncodedName = rfc2047encode(name, strlen(name));
1870 n = snprintf(nPtr, nmax,
1871 (i==0)?"%s <%s@%s>" : ",%s <%s@%s>",
1872 EncodedName, user, node);
1877 n = snprintf(nPtr, nmax,
1878 (i==0)?"%s" : ",%s",
1879 &source[AddrPtr[i]]);
1885 ptr = (char*) malloc(EncodedMaxLen * 2);
1886 memcpy(ptr, Encoded, EncodedMaxLen);
1887 nnPtr = ptr + (nPtr - Encoded), nPtr = nnPtr;
1888 free(Encoded), Encoded = ptr;
1890 i--; /* do it once more with properly lengthened buffer */
1893 for (i = 0; i < nColons; i++)
1894 source[--AddrPtr[i]] = ',';
1905 /* If the last item in a list of recipients was truncated to a partial address,
1906 * remove it completely in order to avoid choking libSieve
1908 void sanitize_truncated_recipient(char *str)
1911 if (num_tokens(str, ',') < 2) return;
1913 int len = strlen(str);
1914 if (len < 900) return;
1915 if (len > 998) str[998] = 0;
1917 char *cptr = strrchr(str, ',');
1920 char *lptr = strchr(cptr, '<');
1921 char *rptr = strchr(cptr, '>');
1923 if ( (lptr) && (rptr) && (rptr > lptr) ) return;
1929 void OutputCtdlMsgHeaders(
1930 struct CtdlMessage *TheMessage,
1931 int do_proto) /* do Citadel protocol responses? */
1937 char display_name[256];
1939 /* begin header processing loop for Citadel message format */
1940 safestrncpy(display_name, "<unknown>", sizeof display_name);
1941 if (TheMessage->cm_fields['A']) {
1942 strcpy(buf, TheMessage->cm_fields['A']);
1943 if (TheMessage->cm_anon_type == MES_ANONONLY) {
1944 safestrncpy(display_name, "****", sizeof display_name);
1946 else if (TheMessage->cm_anon_type == MES_ANONOPT) {
1947 safestrncpy(display_name, "anonymous", sizeof display_name);
1950 safestrncpy(display_name, buf, sizeof display_name);
1952 if ((is_room_aide())
1953 && ((TheMessage->cm_anon_type == MES_ANONONLY)
1954 || (TheMessage->cm_anon_type == MES_ANONOPT))) {
1955 size_t tmp = strlen(display_name);
1956 snprintf(&display_name[tmp],
1957 sizeof display_name - tmp,
1962 /* Don't show Internet address for users on the
1963 * local Citadel network.
1966 if (TheMessage->cm_fields['N'] != NULL)
1967 if (!IsEmptyStr(TheMessage->cm_fields['N']))
1968 if (haschar(TheMessage->cm_fields['N'], '.') == 0) {
1972 /* Now spew the header fields in the order we like them. */
1973 n = safestrncpy(allkeys, FORDER, sizeof allkeys);
1974 for (i=0; i<n; ++i) {
1975 k = (int) allkeys[i];
1977 if ( (TheMessage->cm_fields[k] != NULL)
1978 && (msgkeys[k] != NULL) ) {
1979 if ((k == 'V') || (k == 'R') || (k == 'Y')) {
1980 sanitize_truncated_recipient(TheMessage->cm_fields[k]);
1983 if (do_proto) cprintf("%s=%s\n",
1987 else if ((k == 'F') && (suppress_f)) {
1990 /* Masquerade display name if needed */
1992 if (do_proto) cprintf("%s=%s\n",
1994 TheMessage->cm_fields[k]
2003 void OutputRFC822MsgHeaders(
2004 struct CtdlMessage *TheMessage,
2005 int flags, /* should the bessage be exported clean */
2007 char *mid, long sizeof_mid,
2008 char *suser, long sizeof_suser,
2009 char *luser, long sizeof_luser,
2010 char *fuser, long sizeof_fuser,
2011 char *snode, long sizeof_snode)
2013 char datestamp[100];
2014 int subject_found = 0;
2021 for (i = 0; i < 256; ++i) {
2022 if (TheMessage->cm_fields[i]) {
2023 mptr = mpptr = TheMessage->cm_fields[i];
2026 safestrncpy(luser, mptr, sizeof_luser);
2027 safestrncpy(suser, mptr, sizeof_suser);
2029 else if (i == 'Y') {
2030 if ((flags & QP_EADDR) != 0) {
2031 mptr = qp_encode_email_addrs(mptr);
2033 sanitize_truncated_recipient(mptr);
2034 cprintf("CC: %s%s", mptr, nl);
2036 else if (i == 'P') {
2037 cprintf("Return-Path: %s%s", mptr, nl);
2039 else if (i == 'L') {
2040 cprintf("List-ID: %s%s", mptr, nl);
2042 else if (i == 'V') {
2043 if ((flags & QP_EADDR) != 0)
2044 mptr = qp_encode_email_addrs(mptr);
2046 while ((*hptr != '\0') && isspace(*hptr))
2048 if (!IsEmptyStr(hptr))
2049 cprintf("Envelope-To: %s%s", hptr, nl);
2051 else if (i == 'U') {
2052 cprintf("Subject: %s%s", mptr, nl);
2056 safestrncpy(mid, mptr, sizeof_mid); /// TODO: detect @ here and copy @nodename in if not found.
2058 safestrncpy(fuser, mptr, sizeof_fuser);
2059 /* else if (i == 'O')
2060 cprintf("X-Citadel-Room: %s%s",
2063 safestrncpy(snode, mptr, sizeof_snode);
2066 if (haschar(mptr, '@') == 0)
2068 sanitize_truncated_recipient(mptr);
2069 cprintf("To: %s@%s", mptr, config.c_fqdn);
2074 if ((flags & QP_EADDR) != 0) {
2075 mptr = qp_encode_email_addrs(mptr);
2077 sanitize_truncated_recipient(mptr);
2078 cprintf("To: %s", mptr);
2082 else if (i == 'T') {
2083 datestring(datestamp, sizeof datestamp,
2084 atol(mptr), DATESTRING_RFC822);
2085 cprintf("Date: %s%s", datestamp, nl);
2087 else if (i == 'W') {
2088 cprintf("References: ");
2089 k = num_tokens(mptr, '|');
2090 for (j=0; j<k; ++j) {
2091 extract_token(buf, mptr, j, '|', sizeof buf);
2092 cprintf("<%s>", buf);
2101 else if (i == 'K') {
2103 while ((*hptr != '\0') && isspace(*hptr))
2105 if (!IsEmptyStr(hptr))
2106 cprintf("Reply-To: %s%s", mptr, nl);
2112 if (subject_found == 0) {
2113 cprintf("Subject: (no subject)%s", nl);
2118 void Dump_RFC822HeadersBody(
2119 struct CtdlMessage *TheMessage,
2120 int headers_only, /* eschew the message body? */
2121 int flags, /* should the bessage be exported clean? */
2125 cit_uint8_t prev_ch;
2127 const char *StartOfText = StrBufNOTNULL;
2130 int nllen = strlen(nl);
2133 mptr = TheMessage->cm_fields['M'];
2137 while (*mptr != '\0') {
2138 if (*mptr == '\r') {
2145 eoh = (*(mptr+1) == '\r') && (*(mptr+2) == '\n');
2147 eoh = *(mptr+1) == '\n';
2151 StartOfText = strchr(StartOfText, '\n');
2152 StartOfText = strchr(StartOfText, '\n');
2155 if (((headers_only == HEADERS_NONE) && (mptr >= StartOfText)) ||
2156 ((headers_only == HEADERS_ONLY) && (mptr < StartOfText)) ||
2157 ((headers_only != HEADERS_NONE) &&
2158 (headers_only != HEADERS_ONLY))
2160 if (*mptr == '\n') {
2161 memcpy(&outbuf[outlen], nl, nllen);
2163 outbuf[outlen] = '\0';
2166 outbuf[outlen++] = *mptr;
2170 if (flags & ESC_DOT)
2172 if ((prev_ch == '\n') &&
2174 ((*(mptr+1) == '\r') || (*(mptr+1) == '\n')))
2176 outbuf[outlen++] = '.';
2181 if (outlen > 1000) {
2182 if (client_write(outbuf, outlen) == -1)
2184 struct CitContext *CCC = CC;
2185 MSGM_syslog(LOG_ERR, "Dump_RFC822HeadersBody(): aborting due to write failure.\n");
2192 client_write(outbuf, outlen);
2199 /* If the format type on disk is 1 (fixed-format), then we want
2200 * everything to be output completely literally ... regardless of
2201 * what message transfer format is in use.
2203 void DumpFormatFixed(
2204 struct CtdlMessage *TheMessage,
2205 int mode, /* how would you like that message? */
2212 int nllen = strlen (nl);
2215 mptr = TheMessage->cm_fields['M'];
2217 if (mode == MT_MIME) {
2218 cprintf("Content-type: text/plain\n\n");
2222 while (ch = *mptr++, ch > 0) {
2226 if ((buflen > 250) && (!xlline)){
2230 while ((buflen > 0) &&
2231 (!isspace(buf[buflen])))
2237 mptr -= tbuflen - buflen;
2242 /* if we reach the outer bounds of our buffer,
2243 abort without respect what whe purge. */
2246 (buflen > SIZ - nllen - 2)))
2250 memcpy (&buf[buflen], nl, nllen);
2254 if (client_write(buf, buflen) == -1)
2256 struct CitContext *CCC = CC;
2257 MSGM_syslog(LOG_ERR, "DumpFormatFixed(): aborting due to write failure.\n");
2269 if (!IsEmptyStr(buf))
2270 cprintf("%s%s", buf, nl);
2274 * Get a message off disk. (returns om_* values found in msgbase.h)
2276 int CtdlOutputPreLoadedMsg(
2277 struct CtdlMessage *TheMessage,
2278 int mode, /* how would you like that message? */
2279 int headers_only, /* eschew the message body? */
2280 int do_proto, /* do Citadel protocol responses? */
2281 int crlf, /* Use CRLF newlines instead of LF? */
2282 int flags /* should the bessage be exported clean? */
2284 struct CitContext *CCC = CC;
2287 const char *nl; /* newline string */
2290 /* Buffers needed for RFC822 translation. These are all filled
2291 * using functions that are bounds-checked, and therefore we can
2292 * make them substantially smaller than SIZ.
2300 MSG_syslog(LOG_DEBUG, "CtdlOutputPreLoadedMsg(TheMessage=%s, %d, %d, %d, %d\n",
2301 ((TheMessage == NULL) ? "NULL" : "not null"),
2302 mode, headers_only, do_proto, crlf);
2304 strcpy(mid, "unknown");
2305 nl = (crlf ? "\r\n" : "\n");
2307 if (!is_valid_message(TheMessage)) {
2308 MSGM_syslog(LOG_ERR,
2309 "ERROR: invalid preloaded message for output\n");
2311 return(om_no_such_msg);
2314 /* Suppress envelope recipients if required to avoid disclosing BCC addresses.
2315 * Pad it with spaces in order to avoid changing the RFC822 length of the message.
2317 if ( (flags & SUPPRESS_ENV_TO) && (TheMessage->cm_fields['V'] != NULL) ) {
2318 memset(TheMessage->cm_fields['V'], ' ', strlen(TheMessage->cm_fields['V']));
2321 /* Are we downloading a MIME component? */
2322 if (mode == MT_DOWNLOAD) {
2323 if (TheMessage->cm_format_type != FMT_RFC822) {
2325 cprintf("%d This is not a MIME message.\n",
2326 ERROR + ILLEGAL_VALUE);
2327 } else if (CCC->download_fp != NULL) {
2328 if (do_proto) cprintf(
2329 "%d You already have a download open.\n",
2330 ERROR + RESOURCE_BUSY);
2332 /* Parse the message text component */
2333 mptr = TheMessage->cm_fields['M'];
2334 mime_parser(mptr, NULL, *mime_download, NULL, NULL, NULL, 0);
2335 /* If there's no file open by this time, the requested
2336 * section wasn't found, so print an error
2338 if (CCC->download_fp == NULL) {
2339 if (do_proto) cprintf(
2340 "%d Section %s not found.\n",
2341 ERROR + FILE_NOT_FOUND,
2342 CCC->download_desired_section);
2345 return((CCC->download_fp != NULL) ? om_ok : om_mime_error);
2348 /* MT_SPEW_SECTION is like MT_DOWNLOAD except it outputs the whole MIME part
2349 * in a single server operation instead of opening a download file.
2351 if (mode == MT_SPEW_SECTION) {
2352 if (TheMessage->cm_format_type != FMT_RFC822) {
2354 cprintf("%d This is not a MIME message.\n",
2355 ERROR + ILLEGAL_VALUE);
2357 /* Parse the message text component */
2360 mptr = TheMessage->cm_fields['M'];
2361 mime_parser(mptr, NULL, *mime_spew_section, NULL, NULL, (void *)&found_it, 0);
2362 /* If section wasn't found, print an error
2365 if (do_proto) cprintf(
2366 "%d Section %s not found.\n",
2367 ERROR + FILE_NOT_FOUND,
2368 CCC->download_desired_section);
2371 return((CCC->download_fp != NULL) ? om_ok : om_mime_error);
2374 /* now for the user-mode message reading loops */
2375 if (do_proto) cprintf("%d msg:\n", LISTING_FOLLOWS);
2377 /* Does the caller want to skip the headers? */
2378 if (headers_only == HEADERS_NONE) goto START_TEXT;
2380 /* Tell the client which format type we're using. */
2381 if ( (mode == MT_CITADEL) && (do_proto) ) {
2382 cprintf("type=%d\n", TheMessage->cm_format_type);
2385 /* nhdr=yes means that we're only displaying headers, no body */
2386 if ( (TheMessage->cm_anon_type == MES_ANONONLY)
2387 && ((mode == MT_CITADEL) || (mode == MT_MIME))
2390 cprintf("nhdr=yes\n");
2393 if ((mode == MT_CITADEL) || (mode == MT_MIME))
2394 OutputCtdlMsgHeaders(TheMessage, do_proto);
2397 /* begin header processing loop for RFC822 transfer format */
2401 strcpy(snode, NODENAME);
2402 if (mode == MT_RFC822)
2403 OutputRFC822MsgHeaders(
2408 suser, sizeof(suser),
2409 luser, sizeof(luser),
2410 fuser, sizeof(fuser),
2411 snode, sizeof(snode)
2415 for (i=0; !IsEmptyStr(&suser[i]); ++i) {
2416 suser[i] = tolower(suser[i]);
2417 if (!isalnum(suser[i])) suser[i]='_';
2420 if (mode == MT_RFC822) {
2421 if (!strcasecmp(snode, NODENAME)) {
2422 safestrncpy(snode, FQDN, sizeof snode);
2425 /* Construct a fun message id */
2426 cprintf("Message-ID: <%s", mid);/// todo: this possibly breaks threadding mails.
2427 if (strchr(mid, '@')==NULL) {
2428 cprintf("@%s", snode);
2432 if (!is_room_aide() && (TheMessage->cm_anon_type == MES_ANONONLY)) {
2433 cprintf("From: \"----\" <x@x.org>%s", nl);
2435 else if (!is_room_aide() && (TheMessage->cm_anon_type == MES_ANONOPT)) {
2436 cprintf("From: \"anonymous\" <x@x.org>%s", nl);
2438 else if (!IsEmptyStr(fuser)) {
2439 cprintf("From: \"%s\" <%s>%s", luser, fuser, nl);
2442 cprintf("From: \"%s\" <%s@%s>%s", luser, suser, snode, nl);
2445 /* Blank line signifying RFC822 end-of-headers */
2446 if (TheMessage->cm_format_type != FMT_RFC822) {
2451 /* end header processing loop ... at this point, we're in the text */
2453 if (headers_only == HEADERS_FAST) goto DONE;
2455 /* Tell the client about the MIME parts in this message */
2456 if (TheMessage->cm_format_type == FMT_RFC822) {
2457 if ( (mode == MT_CITADEL) || (mode == MT_MIME) ) {
2458 mptr = TheMessage->cm_fields['M'];
2459 memset(&ma, 0, sizeof(struct ma_info));
2460 mime_parser(mptr, NULL,
2461 (do_proto ? *list_this_part : NULL),
2462 (do_proto ? *list_this_pref : NULL),
2463 (do_proto ? *list_this_suff : NULL),
2466 else if (mode == MT_RFC822) { /* unparsed RFC822 dump */
2467 Dump_RFC822HeadersBody(
2476 if (headers_only == HEADERS_ONLY) {
2480 /* signify start of msg text */
2481 if ( (mode == MT_CITADEL) || (mode == MT_MIME) ) {
2482 if (do_proto) cprintf("text\n");
2485 if (TheMessage->cm_format_type == FMT_FIXED)
2488 mode, /* how would you like that message? */
2491 /* If the message on disk is format 0 (Citadel vari-format), we
2492 * output using the formatter at 80 columns. This is the final output
2493 * form if the transfer format is RFC822, but if the transfer format
2494 * is Citadel proprietary, it'll still work, because the indentation
2495 * for new paragraphs is correct and the client will reformat the
2496 * message to the reader's screen width.
2498 if (TheMessage->cm_format_type == FMT_CITADEL) {
2499 mptr = TheMessage->cm_fields['M'];
2501 if (mode == MT_MIME) {
2502 cprintf("Content-type: text/x-citadel-variformat\n\n");
2507 /* If the message on disk is format 4 (MIME), we've gotta hand it
2508 * off to the MIME parser. The client has already been told that
2509 * this message is format 1 (fixed format), so the callback function
2510 * we use will display those parts as-is.
2512 if (TheMessage->cm_format_type == FMT_RFC822) {
2513 memset(&ma, 0, sizeof(struct ma_info));
2515 if (mode == MT_MIME) {
2516 ma.use_fo_hooks = 0;
2517 strcpy(ma.chosen_part, "1");
2518 ma.chosen_pref = 9999;
2519 ma.dont_decode = CCC->msg4_dont_decode;
2520 mime_parser(mptr, NULL,
2521 *choose_preferred, *fixed_output_pre,
2522 *fixed_output_post, (void *)&ma, 1);
2523 mime_parser(mptr, NULL,
2524 *output_preferred, NULL, NULL, (void *)&ma, 1);
2527 ma.use_fo_hooks = 1;
2528 mime_parser(mptr, NULL,
2529 *fixed_output, *fixed_output_pre,
2530 *fixed_output_post, (void *)&ma, 0);
2535 DONE: /* now we're done */
2536 if (do_proto) cprintf("000\n");
2542 * display a message (mode 0 - Citadel proprietary)
2544 void cmd_msg0(char *cmdbuf)
2547 int headers_only = HEADERS_ALL;
2549 msgid = extract_long(cmdbuf, 0);
2550 headers_only = extract_int(cmdbuf, 1);
2552 CtdlOutputMsg(msgid, MT_CITADEL, headers_only, 1, 0, NULL, 0);
2558 * display a message (mode 2 - RFC822)
2560 void cmd_msg2(char *cmdbuf)
2563 int headers_only = HEADERS_ALL;
2565 msgid = extract_long(cmdbuf, 0);
2566 headers_only = extract_int(cmdbuf, 1);
2568 CtdlOutputMsg(msgid, MT_RFC822, headers_only, 1, 1, NULL, 0);
2574 * display a message (mode 3 - IGnet raw format - internal programs only)
2576 void cmd_msg3(char *cmdbuf)
2579 struct CtdlMessage *msg = NULL;
2582 if (CC->internal_pgm == 0) {
2583 cprintf("%d This command is for internal programs only.\n",
2584 ERROR + HIGHER_ACCESS_REQUIRED);
2588 msgnum = extract_long(cmdbuf, 0);
2589 msg = CtdlFetchMessage(msgnum, 1);
2591 cprintf("%d Message %ld not found.\n",
2592 ERROR + MESSAGE_NOT_FOUND, msgnum);
2596 serialize_message(&smr, msg);
2597 CtdlFreeMessage(msg);
2600 cprintf("%d Unable to serialize message\n",
2601 ERROR + INTERNAL_ERROR);
2605 cprintf("%d %ld\n", BINARY_FOLLOWS, (long)smr.len);
2606 client_write((char *)smr.ser, (int)smr.len);
2613 * Display a message using MIME content types
2615 void cmd_msg4(char *cmdbuf)
2620 msgid = extract_long(cmdbuf, 0);
2621 extract_token(section, cmdbuf, 1, '|', sizeof section);
2622 CtdlOutputMsg(msgid, MT_MIME, 0, 1, 0, (section[0] ? section : NULL) , 0);
2628 * Client tells us its preferred message format(s)
2630 void cmd_msgp(char *cmdbuf)
2632 if (!strcasecmp(cmdbuf, "dont_decode")) {
2633 CC->msg4_dont_decode = 1;
2634 cprintf("%d MSG4 will not pre-decode messages.\n", CIT_OK);
2637 safestrncpy(CC->preferred_formats, cmdbuf, sizeof(CC->preferred_formats));
2638 cprintf("%d Preferred MIME formats have been set.\n", CIT_OK);
2644 * Open a component of a MIME message as a download file
2646 void cmd_opna(char *cmdbuf)
2649 char desired_section[128];
2651 msgid = extract_long(cmdbuf, 0);
2652 extract_token(desired_section, cmdbuf, 1, '|', sizeof desired_section);
2653 safestrncpy(CC->download_desired_section, desired_section,
2654 sizeof CC->download_desired_section);
2655 CtdlOutputMsg(msgid, MT_DOWNLOAD, 0, 1, 1, NULL, 0);
2660 * Open a component of a MIME message and transmit it all at once
2662 void cmd_dlat(char *cmdbuf)
2665 char desired_section[128];
2667 msgid = extract_long(cmdbuf, 0);
2668 extract_token(desired_section, cmdbuf, 1, '|', sizeof desired_section);
2669 safestrncpy(CC->download_desired_section, desired_section,
2670 sizeof CC->download_desired_section);
2671 CtdlOutputMsg(msgid, MT_SPEW_SECTION, 0, 1, 1, NULL, 0);
2676 * Save one or more message pointers into a specified room
2677 * (Returns 0 for success, nonzero for failure)
2678 * roomname may be NULL to use the current room
2680 * Note that the 'supplied_msg' field may be set to NULL, in which case
2681 * the message will be fetched from disk, by number, if we need to perform
2682 * replication checks. This adds an additional database read, so if the
2683 * caller already has the message in memory then it should be supplied. (Obviously
2684 * this mode of operation only works if we're saving a single message.)
2686 int CtdlSaveMsgPointersInRoom(char *roomname, long newmsgidlist[], int num_newmsgs,
2687 int do_repl_check, struct CtdlMessage *supplied_msg, int suppress_refcount_adj
2689 struct CitContext *CCC = CC;
2691 char hold_rm[ROOMNAMELEN];
2692 struct cdbdata *cdbfr;
2695 long highest_msg = 0L;
2698 struct CtdlMessage *msg = NULL;
2700 long *msgs_to_be_merged = NULL;
2701 int num_msgs_to_be_merged = 0;
2703 MSG_syslog(LOG_DEBUG,
2704 "CtdlSaveMsgPointersInRoom(room=%s, num_msgs=%d, repl=%d, suppress_rca=%d)\n",
2705 roomname, num_newmsgs, do_repl_check, suppress_refcount_adj
2708 strcpy(hold_rm, CCC->room.QRname);
2711 if (newmsgidlist == NULL) return(ERROR + INTERNAL_ERROR);
2712 if (num_newmsgs < 1) return(ERROR + INTERNAL_ERROR);
2713 if (num_newmsgs > 1) supplied_msg = NULL;
2715 /* Now the regular stuff */
2716 if (CtdlGetRoomLock(&CCC->room,
2717 ((roomname != NULL) ? roomname : CCC->room.QRname) )
2719 MSG_syslog(LOG_ERR, "No such room <%s>\n", roomname);
2720 return(ERROR + ROOM_NOT_FOUND);
2724 msgs_to_be_merged = malloc(sizeof(long) * num_newmsgs);
2725 num_msgs_to_be_merged = 0;
2728 cdbfr = cdb_fetch(CDB_MSGLISTS, &CCC->room.QRnumber, sizeof(long));
2729 if (cdbfr == NULL) {
2733 msglist = (long *) cdbfr->ptr;
2734 cdbfr->ptr = NULL; /* CtdlSaveMsgPointerInRoom() now owns this memory */
2735 num_msgs = cdbfr->len / sizeof(long);
2740 /* Create a list of msgid's which were supplied by the caller, but do
2741 * not already exist in the target room. It is absolutely taboo to
2742 * have more than one reference to the same message in a room.
2744 for (i=0; i<num_newmsgs; ++i) {
2746 if (num_msgs > 0) for (j=0; j<num_msgs; ++j) {
2747 if (msglist[j] == newmsgidlist[i]) {
2752 msgs_to_be_merged[num_msgs_to_be_merged++] = newmsgidlist[i];
2756 MSG_syslog(LOG_DEBUG, "%d unique messages to be merged\n", num_msgs_to_be_merged);
2759 * Now merge the new messages
2761 msglist = realloc(msglist, (sizeof(long) * (num_msgs + num_msgs_to_be_merged)) );
2762 if (msglist == NULL) {
2763 MSGM_syslog(LOG_ALERT, "ERROR: can't realloc message list!\n");
2765 memcpy(&msglist[num_msgs], msgs_to_be_merged, (sizeof(long) * num_msgs_to_be_merged) );
2766 num_msgs += num_msgs_to_be_merged;
2768 /* Sort the message list, so all the msgid's are in order */
2769 num_msgs = sort_msglist(msglist, num_msgs);
2771 /* Determine the highest message number */
2772 highest_msg = msglist[num_msgs - 1];
2774 /* Write it back to disk. */
2775 cdb_store(CDB_MSGLISTS, &CCC->room.QRnumber, (int)sizeof(long),
2776 msglist, (int)(num_msgs * sizeof(long)));
2778 /* Free up the memory we used. */
2781 /* Update the highest-message pointer and unlock the room. */
2782 CCC->room.QRhighest = highest_msg;
2783 CtdlPutRoomLock(&CCC->room);
2785 /* Perform replication checks if necessary */
2786 if ( (DoesThisRoomNeedEuidIndexing(&CCC->room)) && (do_repl_check) ) {
2787 MSGM_syslog(LOG_DEBUG, "CtdlSaveMsgPointerInRoom() doing repl checks\n");
2789 for (i=0; i<num_msgs_to_be_merged; ++i) {
2790 msgid = msgs_to_be_merged[i];
2792 if (supplied_msg != NULL) {
2796 msg = CtdlFetchMessage(msgid, 0);
2800 ReplicationChecks(msg);
2802 /* If the message has an Exclusive ID, index that... */
2803 if (msg->cm_fields['E'] != NULL) {
2804 index_message_by_euid(msg->cm_fields['E'], &CCC->room, msgid);
2807 /* Free up the memory we may have allocated */
2808 if (msg != supplied_msg) {
2809 CtdlFreeMessage(msg);
2817 MSGM_syslog(LOG_DEBUG, "CtdlSaveMsgPointerInRoom() skips repl checks\n");
2820 /* Submit this room for processing by hooks */
2821 PerformRoomHooks(&CCC->room);
2823 /* Go back to the room we were in before we wandered here... */
2824 CtdlGetRoom(&CCC->room, hold_rm);
2826 /* Bump the reference count for all messages which were merged */
2827 if (!suppress_refcount_adj) {
2828 AdjRefCountList(msgs_to_be_merged, num_msgs_to_be_merged, +1);
2831 /* Free up memory... */
2832 if (msgs_to_be_merged != NULL) {
2833 free(msgs_to_be_merged);
2836 /* Return success. */
2842 * This is the same as CtdlSaveMsgPointersInRoom() but it only accepts
2845 int CtdlSaveMsgPointerInRoom(char *roomname, long msgid,
2846 int do_repl_check, struct CtdlMessage *supplied_msg)
2848 return CtdlSaveMsgPointersInRoom(roomname, &msgid, 1, do_repl_check, supplied_msg, 0);
2855 * Message base operation to save a new message to the message store
2856 * (returns new message number)
2858 * This is the back end for CtdlSubmitMsg() and should not be directly
2859 * called by server-side modules.
2862 long send_message(struct CtdlMessage *msg) {
2863 struct CitContext *CCC = CC;
2871 /* Get a new message number */
2872 newmsgid = get_new_message_number();
2873 snprintf(msgidbuf, sizeof msgidbuf, "%08lX-%08lX@%s",
2874 (long unsigned int) time(NULL),
2875 (long unsigned int) newmsgid,
2879 /* Generate an ID if we don't have one already */
2880 if (msg->cm_fields['I']==NULL) {
2881 msg->cm_fields['I'] = strdup(msgidbuf);
2884 /* If the message is big, set its body aside for storage elsewhere */
2885 if (msg->cm_fields['M'] != NULL) {
2886 if (strlen(msg->cm_fields['M']) > BIGMSG) {
2888 holdM = msg->cm_fields['M'];
2889 msg->cm_fields['M'] = NULL;
2893 /* Serialize our data structure for storage in the database */
2894 serialize_message(&smr, msg);
2897 msg->cm_fields['M'] = holdM;
2901 cprintf("%d Unable to serialize message\n",
2902 ERROR + INTERNAL_ERROR);
2906 /* Write our little bundle of joy into the message base */
2907 if (cdb_store(CDB_MSGMAIN, &newmsgid, (int)sizeof(long),
2908 smr.ser, smr.len) < 0) {
2909 MSGM_syslog(LOG_ERR, "Can't store message\n");
2913 cdb_store(CDB_BIGMSGS,
2923 /* Free the memory we used for the serialized message */
2926 /* Return the *local* message ID to the caller
2927 * (even if we're storing an incoming network message)
2935 * Serialize a struct CtdlMessage into the format used on disk and network.
2937 * This function loads up a "struct ser_ret" (defined in server.h) which
2938 * contains the length of the serialized message and a pointer to the
2939 * serialized message in memory. THE LATTER MUST BE FREED BY THE CALLER.
2941 void serialize_message(struct ser_ret *ret, /* return values */
2942 struct CtdlMessage *msg) /* unserialized msg */
2944 struct CitContext *CCC = CC;
2945 size_t wlen, fieldlen;
2947 static char *forder = FORDER;
2950 * Check for valid message format
2952 if (is_valid_message(msg) == 0) {
2953 MSGM_syslog(LOG_ERR, "serialize_message() aborting due to invalid message\n");
2960 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL)
2961 ret->len = ret->len +
2962 strlen(msg->cm_fields[(int)forder[i]]) + 2;
2964 ret->ser = malloc(ret->len);
2965 if (ret->ser == NULL) {
2966 MSG_syslog(LOG_ERR, "serialize_message() malloc(%ld) failed: %s\n",
2967 (long)ret->len, strerror(errno));
2974 ret->ser[1] = msg->cm_anon_type;
2975 ret->ser[2] = msg->cm_format_type;
2978 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL) {
2979 fieldlen = strlen(msg->cm_fields[(int)forder[i]]);
2980 ret->ser[wlen++] = (char)forder[i];
2981 safestrncpy((char *)&ret->ser[wlen], msg->cm_fields[(int)forder[i]], fieldlen+1);
2982 wlen = wlen + fieldlen + 1;
2984 if (ret->len != wlen) {
2985 MSG_syslog(LOG_ERR, "ERROR: len=%ld wlen=%ld\n",
2986 (long)ret->len, (long)wlen);
2994 * Serialize a struct CtdlMessage into the format used on disk and network.
2996 * This function loads up a "struct ser_ret" (defined in server.h) which
2997 * contains the length of the serialized message and a pointer to the
2998 * serialized message in memory. THE LATTER MUST BE FREED BY THE CALLER.
3000 void dump_message(struct CtdlMessage *msg, /* unserialized msg */
3001 long Siz) /* how many chars ? */
3004 static char *forder = FORDER;
3008 * Check for valid message format
3010 if (is_valid_message(msg) == 0) {
3011 struct CitContext *CCC = CC;
3012 MSGM_syslog(LOG_ERR, "dump_message() aborting due to invalid message\n");
3016 buf = (char*) malloc (Siz + 1);
3018 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL) {
3019 snprintf (buf, Siz, " msg[%c] = %s ...\n", (char) forder[i],
3020 msg->cm_fields[(int)forder[i]]);
3021 if (client_write (buf, strlen(buf)) == -1)
3023 struct CitContext *CCC = CC;
3024 MSGM_syslog(LOG_ERR, "dump_message(): aborting due to write failure.\n");
3035 * Check to see if any messages already exist in the current room which
3036 * carry the same Exclusive ID as this one. If any are found, delete them.
3038 void ReplicationChecks(struct CtdlMessage *msg) {
3039 struct CitContext *CCC = CC;
3040 long old_msgnum = (-1L);
3042 if (DoesThisRoomNeedEuidIndexing(&CCC->room) == 0) return;
3044 MSG_syslog(LOG_DEBUG, "Performing replication checks in <%s>\n",
3047 /* No exclusive id? Don't do anything. */
3048 if (msg == NULL) return;
3049 if (msg->cm_fields['E'] == NULL) return;
3050 if (IsEmptyStr(msg->cm_fields['E'])) return;
3051 /*MSG_syslog(LOG_DEBUG, "Exclusive ID: <%s> for room <%s>\n",
3052 msg->cm_fields['E'], CCC->room.QRname);*/
3054 old_msgnum = CtdlLocateMessageByEuid(msg->cm_fields['E'], &CCC->room);
3055 if (old_msgnum > 0L) {
3056 MSG_syslog(LOG_DEBUG, "ReplicationChecks() replacing message %ld\n", old_msgnum);
3057 CtdlDeleteMessages(CCC->room.QRname, &old_msgnum, 1, "");
3064 * Save a message to disk and submit it into the delivery system.
3066 long CtdlSubmitMsg(struct CtdlMessage *msg, /* message to save */
3067 struct recptypes *recps, /* recipients (if mail) */
3068 const char *force, /* force a particular room? */
3069 int flags /* should the message be exported clean? */
3072 char submit_filename[128];
3073 char generated_timestamp[32];
3074 char hold_rm[ROOMNAMELEN];
3075 char actual_rm[ROOMNAMELEN];
3076 char force_room[ROOMNAMELEN];
3077 char content_type[SIZ]; /* We have to learn this */
3078 char recipient[SIZ];
3081 const char *mptr = NULL;
3082 struct ctdluser userbuf;
3084 struct MetaData smi;
3085 FILE *network_fp = NULL;
3086 static int seqnum = 1;
3087 struct CtdlMessage *imsg = NULL;
3089 size_t instr_alloc = 0;
3091 char *hold_R, *hold_D;
3092 char *collected_addresses = NULL;
3093 struct addresses_to_be_filed *aptr = NULL;
3094 StrBuf *saved_rfc822_version = NULL;
3095 int qualified_for_journaling = 0;
3096 CitContext *CCC = MyContext();
3097 char bounce_to[1024] = "";
3100 MSGM_syslog(LOG_DEBUG, "CtdlSubmitMsg() called\n");
3101 if (is_valid_message(msg) == 0) return(-1); /* self check */
3103 /* If this message has no timestamp, we take the liberty of
3104 * giving it one, right now.
3106 if (msg->cm_fields['T'] == NULL) {
3107 snprintf(generated_timestamp, sizeof generated_timestamp, "%ld", (long)time(NULL));
3108 msg->cm_fields['T'] = strdup(generated_timestamp);
3111 /* If this message has no path, we generate one.
3113 if (msg->cm_fields['P'] == NULL) {
3114 if (msg->cm_fields['A'] != NULL) {
3115 msg->cm_fields['P'] = strdup(msg->cm_fields['A']);
3116 for (a=0; !IsEmptyStr(&msg->cm_fields['P'][a]); ++a) {
3117 if (isspace(msg->cm_fields['P'][a])) {
3118 msg->cm_fields['P'][a] = ' ';
3123 msg->cm_fields['P'] = strdup("unknown");
3127 if (force == NULL) {
3128 strcpy(force_room, "");
3131 strcpy(force_room, force);
3134 /* Learn about what's inside, because it's what's inside that counts */
3135 if (msg->cm_fields['M'] == NULL) {
3136 MSGM_syslog(LOG_ERR, "ERROR: attempt to save message with NULL body\n");
3140 switch (msg->cm_format_type) {
3142 strcpy(content_type, "text/x-citadel-variformat");
3145 strcpy(content_type, "text/plain");
3148 strcpy(content_type, "text/plain");
3149 mptr = bmstrcasestr(msg->cm_fields['M'], "Content-type:");
3152 safestrncpy(content_type, &mptr[13], sizeof content_type);
3153 striplt(content_type);
3154 aptr = content_type;
3155 while (!IsEmptyStr(aptr)) {
3167 /* Goto the correct room */
3168 room = (recps) ? CCC->room.QRname : SENTITEMS;
3169 MSG_syslog(LOG_DEBUG, "Selected room %s\n", room);
3170 strcpy(hold_rm, CCC->room.QRname);
3171 strcpy(actual_rm, CCC->room.QRname);
3172 if (recps != NULL) {
3173 strcpy(actual_rm, SENTITEMS);
3176 /* If the user is a twit, move to the twit room for posting */
3178 if (CCC->user.axlevel == AxProbU) {
3179 strcpy(hold_rm, actual_rm);
3180 strcpy(actual_rm, config.c_twitroom);
3181 MSGM_syslog(LOG_DEBUG, "Diverting to twit room\n");
3185 /* ...or if this message is destined for Aide> then go there. */
3186 if (!IsEmptyStr(force_room)) {
3187 strcpy(actual_rm, force_room);
3190 MSG_syslog(LOG_INFO, "Final selection: %s (%s)\n", actual_rm, room);
3191 if (strcasecmp(actual_rm, CCC->room.QRname)) {
3192 /* CtdlGetRoom(&CCC->room, actual_rm); */
3193 CtdlUserGoto(actual_rm, 0, 1, NULL, NULL);
3197 * If this message has no O (room) field, generate one.
3199 if (msg->cm_fields['O'] == NULL) {
3200 msg->cm_fields['O'] = strdup(CCC->room.QRname);
3203 /* Perform "before save" hooks (aborting if any return nonzero) */
3204 MSGM_syslog(LOG_DEBUG, "Performing before-save hooks\n");
3205 if (PerformMessageHooks(msg, EVT_BEFORESAVE) > 0) return(-3);
3208 * If this message has an Exclusive ID, and the room is replication
3209 * checking enabled, then do replication checks.
3211 if (DoesThisRoomNeedEuidIndexing(&CCC->room)) {
3212 ReplicationChecks(msg);
3215 /* Save it to disk */
3216 MSGM_syslog(LOG_DEBUG, "Saving to disk\n");
3217 newmsgid = send_message(msg);
3218 if (newmsgid <= 0L) return(-5);
3220 /* Write a supplemental message info record. This doesn't have to
3221 * be a critical section because nobody else knows about this message
3224 MSGM_syslog(LOG_DEBUG, "Creating MetaData record\n");
3225 memset(&smi, 0, sizeof(struct MetaData));
3226 smi.meta_msgnum = newmsgid;
3227 smi.meta_refcount = 0;
3228 safestrncpy(smi.meta_content_type, content_type,
3229 sizeof smi.meta_content_type);
3232 * Measure how big this message will be when rendered as RFC822.
3233 * We do this for two reasons:
3234 * 1. We need the RFC822 length for the new metadata record, so the
3235 * POP and IMAP services don't have to calculate message lengths
3236 * while the user is waiting (multiplied by potentially hundreds
3237 * or thousands of messages).
3238 * 2. If journaling is enabled, we will need an RFC822 version of the
3239 * message to attach to the journalized copy.
3241 if (CCC->redirect_buffer != NULL) {
3242 MSGM_syslog(LOG_ALERT, "CCC->redirect_buffer is not NULL during message submission!\n");
3245 CCC->redirect_buffer = NewStrBufPlain(NULL, SIZ);
3246 CtdlOutputPreLoadedMsg(msg, MT_RFC822, HEADERS_ALL, 0, 1, QP_EADDR);
3247 smi.meta_rfc822_length = StrLength(CCC->redirect_buffer);
3248 saved_rfc822_version = CCC->redirect_buffer;
3249 CCC->redirect_buffer = NULL;
3253 /* Now figure out where to store the pointers */
3254 MSGM_syslog(LOG_DEBUG, "Storing pointers\n");
3256 /* If this is being done by the networker delivering a private
3257 * message, we want to BYPASS saving the sender's copy (because there
3258 * is no local sender; it would otherwise go to the Trashcan).
3260 if ((!CCC->internal_pgm) || (recps == NULL)) {
3261 if (CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 1, msg) != 0) {
3262 MSGM_syslog(LOG_ERR, "ERROR saving message pointer!\n");
3263 CtdlSaveMsgPointerInRoom(config.c_aideroom, newmsgid, 0, msg);
3267 /* For internet mail, drop a copy in the outbound queue room */
3268 if ((recps != NULL) && (recps->num_internet > 0)) {
3269 CtdlSaveMsgPointerInRoom(SMTP_SPOOLOUT_ROOM, newmsgid, 0, msg);
3272 /* If other rooms are specified, drop them there too. */
3273 if ((recps != NULL) && (recps->num_room > 0))
3274 for (i=0; i<num_tokens(recps->recp_room, '|'); ++i) {
3275 extract_token(recipient, recps->recp_room, i,
3276 '|', sizeof recipient);
3277 MSG_syslog(LOG_DEBUG, "Delivering to room <%s>\n", recipient);///// xxxx
3278 CtdlSaveMsgPointerInRoom(recipient, newmsgid, 0, msg);
3281 /* Bump this user's messages posted counter. */
3282 MSGM_syslog(LOG_DEBUG, "Updating user\n");
3283 CtdlGetUserLock(&CCC->user, CCC->curr_user);
3284 CCC->user.posted = CCC->user.posted + 1;
3285 CtdlPutUserLock(&CCC->user);
3287 /* Decide where bounces need to be delivered */
3288 if ((recps != NULL) && (recps->bounce_to != NULL)) {
3289 safestrncpy(bounce_to, recps->bounce_to, sizeof bounce_to);
3291 else if (CCC->logged_in) {
3292 snprintf(bounce_to, sizeof bounce_to, "%s@%s", CCC->user.fullname, config.c_nodename);
3295 snprintf(bounce_to, sizeof bounce_to, "%s@%s", msg->cm_fields['A'], msg->cm_fields['N']);
3298 /* If this is private, local mail, make a copy in the
3299 * recipient's mailbox and bump the reference count.
3301 if ((recps != NULL) && (recps->num_local > 0))
3302 for (i=0; i<num_tokens(recps->recp_local, '|'); ++i) {
3303 extract_token(recipient, recps->recp_local, i,
3304 '|', sizeof recipient);
3305 MSG_syslog(LOG_DEBUG, "Delivering private local mail to <%s>\n",
3307 if (CtdlGetUser(&userbuf, recipient) == 0) {
3308 CtdlMailboxName(actual_rm, sizeof actual_rm, &userbuf, MAILROOM);
3309 CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 0, msg);
3310 CtdlBumpNewMailCounter(userbuf.usernum);
3311 if (!IsEmptyStr(config.c_funambol_host) || !IsEmptyStr(config.c_pager_program)) {
3312 /* Generate a instruction message for the Funambol notification
3313 * server, in the same style as the SMTP queue
3316 instr = malloc(instr_alloc);
3317 snprintf(instr, instr_alloc,
3318 "Content-type: %s\n\nmsgid|%ld\nsubmitted|%ld\n"
3320 SPOOLMIME, newmsgid, (long)time(NULL),
3324 imsg = malloc(sizeof(struct CtdlMessage));
3325 memset(imsg, 0, sizeof(struct CtdlMessage));
3326 imsg->cm_magic = CTDLMESSAGE_MAGIC;
3327 imsg->cm_anon_type = MES_NORMAL;
3328 imsg->cm_format_type = FMT_RFC822;
3329 imsg->cm_fields['U'] = strdup("QMSG");
3330 imsg->cm_fields['A'] = strdup("Citadel");
3331 imsg->cm_fields['J'] = strdup("do not journal");
3332 imsg->cm_fields['M'] = instr; /* imsg owns this memory now */
3333 imsg->cm_fields['2'] = strdup(recipient);
3334 CtdlSubmitMsg(imsg, NULL, FNBL_QUEUE_ROOM, 0);
3335 CtdlFreeMessage(imsg);
3339 MSG_syslog(LOG_DEBUG, "No user <%s>\n", recipient);
3340 CtdlSaveMsgPointerInRoom(config.c_aideroom, newmsgid, 0, msg);
3344 /* Perform "after save" hooks */
3345 MSGM_syslog(LOG_DEBUG, "Performing after-save hooks\n");
3346 if (msg->cm_fields['3'] != NULL) free(msg->cm_fields['3']);
3347 msg->cm_fields['3'] = malloc(20);
3348 snprintf(msg->cm_fields['3'], 20, "%ld", newmsgid);
3349 PerformMessageHooks(msg, EVT_AFTERSAVE);
3350 free(msg->cm_fields['3']);
3351 msg->cm_fields['3'] = NULL;
3353 /* For IGnet mail, we have to save a new copy into the spooler for
3354 * each recipient, with the R and D fields set to the recipient and
3355 * destination-node. This has two ugly side effects: all other
3356 * recipients end up being unlisted in this recipient's copy of the
3357 * message, and it has to deliver multiple messages to the same
3358 * node. We'll revisit this again in a year or so when everyone has
3359 * a network spool receiver that can handle the new style messages.
3361 if ((recps != NULL) && (recps->num_ignet > 0))
3362 for (i=0; i<num_tokens(recps->recp_ignet, '|'); ++i) {
3363 extract_token(recipient, recps->recp_ignet, i,
3364 '|', sizeof recipient);
3366 hold_R = msg->cm_fields['R'];
3367 hold_D = msg->cm_fields['D'];
3368 msg->cm_fields['R'] = malloc(SIZ);
3369 msg->cm_fields['D'] = malloc(128);
3370 extract_token(msg->cm_fields['R'], recipient, 0, '@', SIZ);
3371 extract_token(msg->cm_fields['D'], recipient, 1, '@', 128);
3373 serialize_message(&smr, msg);
3375 snprintf(submit_filename, sizeof submit_filename,
3376 "%s/netmail.%04lx.%04x.%04x",
3378 (long) getpid(), CCC->cs_pid, ++seqnum);
3379 network_fp = fopen(submit_filename, "wb+");
3380 if (network_fp != NULL) {
3381 rv = fwrite(smr.ser, smr.len, 1, network_fp);
3383 MSG_syslog(LOG_EMERG, "CtdlSubmitMsg(): Couldn't write network spool file: %s\n",
3391 free(msg->cm_fields['R']);
3392 free(msg->cm_fields['D']);
3393 msg->cm_fields['R'] = hold_R;
3394 msg->cm_fields['D'] = hold_D;
3397 /* Go back to the room we started from */
3398 MSG_syslog(LOG_DEBUG, "Returning to original room %s\n", hold_rm);
3399 if (strcasecmp(hold_rm, CCC->room.QRname))
3400 CtdlUserGoto(hold_rm, 0, 1, NULL, NULL);
3402 /* For internet mail, generate delivery instructions.
3403 * Yes, this is recursive. Deal with it. Infinite recursion does
3404 * not happen because the delivery instructions message does not
3405 * contain a recipient.
3407 if ((recps != NULL) && (recps->num_internet > 0)) {
3408 StrBuf *SpoolMsg = NewStrBuf();
3411 MSGM_syslog(LOG_DEBUG, "Generating delivery instructions\n");
3413 StrBufPrintf(SpoolMsg,
3414 "Content-type: "SPOOLMIME"\n"
3423 if (recps->envelope_from != NULL) {
3424 StrBufAppendBufPlain(SpoolMsg, HKEY("envelope_from|"), 0);
3425 StrBufAppendBufPlain(SpoolMsg, recps->envelope_from, -1, 0);
3426 StrBufAppendBufPlain(SpoolMsg, HKEY("\n"), 0);
3428 if (recps->sending_room != NULL) {
3429 StrBufAppendBufPlain(SpoolMsg, HKEY("source_room|"), 0);
3430 StrBufAppendBufPlain(SpoolMsg, recps->sending_room, -1, 0);
3431 StrBufAppendBufPlain(SpoolMsg, HKEY("\n"), 0);
3434 nTokens = num_tokens(recps->recp_internet, '|');
3435 for (i = 0; i < nTokens; i++) {
3437 len = extract_token(recipient, recps->recp_internet, i, '|', sizeof recipient);
3439 StrBufAppendBufPlain(SpoolMsg, HKEY("remote|"), 0);
3440 StrBufAppendBufPlain(SpoolMsg, recipient, len, 0);
3441 StrBufAppendBufPlain(SpoolMsg, HKEY("|0||\n"), 0);
3445 imsg = malloc(sizeof(struct CtdlMessage));
3446 memset(imsg, 0, sizeof(struct CtdlMessage));
3447 imsg->cm_magic = CTDLMESSAGE_MAGIC;
3448 imsg->cm_anon_type = MES_NORMAL;
3449 imsg->cm_format_type = FMT_RFC822;
3450 imsg->cm_fields['U'] = strdup("QMSG");
3451 imsg->cm_fields['A'] = strdup("Citadel");
3452 imsg->cm_fields['J'] = strdup("do not journal");
3453 imsg->cm_fields['M'] = SmashStrBuf(&SpoolMsg); /* imsg owns this memory now */
3454 CtdlSubmitMsg(imsg, NULL, SMTP_SPOOLOUT_ROOM, QP_EADDR);
3455 CtdlFreeMessage(imsg);
3459 * Any addresses to harvest for someone's address book?
3461 if ( (CCC->logged_in) && (recps != NULL) ) {
3462 collected_addresses = harvest_collected_addresses(msg);
3465 if (collected_addresses != NULL) {
3466 aptr = (struct addresses_to_be_filed *)
3467 malloc(sizeof(struct addresses_to_be_filed));
3468 CtdlMailboxName(actual_rm, sizeof actual_rm,
3469 &CCC->user, USERCONTACTSROOM);
3470 aptr->roomname = strdup(actual_rm);
3471 aptr->collected_addresses = collected_addresses;
3472 begin_critical_section(S_ATBF);
3475 end_critical_section(S_ATBF);
3479 * Determine whether this message qualifies for journaling.
3481 if (msg->cm_fields['J'] != NULL) {
3482 qualified_for_journaling = 0;
3485 if (recps == NULL) {
3486 qualified_for_journaling = config.c_journal_pubmsgs;
3488 else if (recps->num_local + recps->num_ignet + recps->num_internet > 0) {
3489 qualified_for_journaling = config.c_journal_email;
3492 qualified_for_journaling = config.c_journal_pubmsgs;
3497 * Do we have to perform journaling? If so, hand off the saved
3498 * RFC822 version will be handed off to the journaler for background
3499 * submit. Otherwise, we have to free the memory ourselves.
3501 if (saved_rfc822_version != NULL) {
3502 if (qualified_for_journaling) {
3503 JournalBackgroundSubmit(msg, saved_rfc822_version, recps);
3506 FreeStrBuf(&saved_rfc822_version);
3516 void aide_message (char *text, char *subject)
3518 quickie_message("Citadel",NULL,NULL,AIDEROOM,text,FMT_CITADEL,subject);
3523 * Convenience function for generating small administrative messages.
3525 void quickie_message(const char *from, const char *fromaddr, char *to, char *room, const char *text,
3526 int format_type, const char *subject)
3528 struct CtdlMessage *msg;
3529 struct recptypes *recp = NULL;
3531 msg = malloc(sizeof(struct CtdlMessage));
3532 memset(msg, 0, sizeof(struct CtdlMessage));
3533 msg->cm_magic = CTDLMESSAGE_MAGIC;
3534 msg->cm_anon_type = MES_NORMAL;
3535 msg->cm_format_type = format_type;
3538 msg->cm_fields['A'] = strdup(from);
3540 else if (fromaddr != NULL) {
3541 msg->cm_fields['A'] = strdup(fromaddr);
3542 if (strchr(msg->cm_fields['A'], '@')) {
3543 *strchr(msg->cm_fields['A'], '@') = 0;
3547 msg->cm_fields['A'] = strdup("Citadel");
3550 if (fromaddr != NULL) msg->cm_fields['F'] = strdup(fromaddr);
3551 if (room != NULL) msg->cm_fields['O'] = strdup(room);
3552 msg->cm_fields['N'] = strdup(NODENAME);
3554 msg->cm_fields['R'] = strdup(to);
3555 recp = validate_recipients(to, NULL, 0);
3557 if (subject != NULL) {
3558 msg->cm_fields['U'] = strdup(subject);
3560 msg->cm_fields['M'] = strdup(text);
3562 CtdlSubmitMsg(msg, recp, room, 0);
3563 CtdlFreeMessage(msg);
3564 if (recp != NULL) free_recipients(recp);
3570 * Back end function used by CtdlMakeMessage() and similar functions
3572 StrBuf *CtdlReadMessageBodyBuf(char *terminator, /* token signalling EOT */
3574 size_t maxlen, /* maximum message length */
3575 char *exist, /* if non-null, append to it;
3576 exist is ALWAYS freed */
3577 int crlf, /* CRLF newlines instead of LF */
3578 int *sock /* socket handle or 0 for this session's client socket */
3587 LineBuf = NewStrBufPlain(NULL, SIZ);
3588 if (exist == NULL) {
3589 Message = NewStrBufPlain(NULL, 4 * SIZ);
3592 Message = NewStrBufPlain(exist, -1);
3596 /* Do we need to change leading ".." to "." for SMTP escaping? */
3597 if ((tlen == 1) && (*terminator == '.')) {
3601 /* read in the lines of message text one by one */
3604 if ((CtdlSockGetLine(sock, LineBuf, 5) < 0) ||
3609 if (CtdlClientGetLine(LineBuf) < 0) finished = 1;
3611 if ((StrLength(LineBuf) == tlen) &&
3612 (!strcmp(ChrPtr(LineBuf), terminator)))
3615 if ( (!flushing) && (!finished) ) {
3617 StrBufAppendBufPlain(LineBuf, HKEY("\r\n"), 0);
3620 StrBufAppendBufPlain(LineBuf, HKEY("\n"), 0);
3623 /* Unescape SMTP-style input of two dots at the beginning of the line */
3625 (StrLength(LineBuf) == 2) &&
3626 (!strcmp(ChrPtr(LineBuf), "..")))
3628 StrBufCutLeft(LineBuf, 1);
3631 StrBufAppendBuf(Message, LineBuf, 0);
3634 /* if we've hit the max msg length, flush the rest */
3635 if (StrLength(Message) >= maxlen) flushing = 1;
3637 } while (!finished);
3638 FreeStrBuf(&LineBuf);
3642 void DeleteAsyncMsg(ReadAsyncMsg **Msg)
3646 FreeStrBuf(&(*Msg)->MsgBuf);
3652 ReadAsyncMsg *NewAsyncMsg(const char *terminator, /* token signalling EOT */
3654 size_t maxlen, /* maximum message length */
3655 size_t expectlen, /* if we expect a message, how long should it be? */
3656 char *exist, /* if non-null, append to it;
3657 exist is ALWAYS freed */
3658 long eLen, /* length of exist */
3659 int crlf /* CRLF newlines instead of LF */
3662 ReadAsyncMsg *NewMsg;
3664 NewMsg = (ReadAsyncMsg *)malloc(sizeof(ReadAsyncMsg));
3665 memset(NewMsg, 0, sizeof(ReadAsyncMsg));
3667 if (exist == NULL) {
3670 if (expectlen == 0) {
3674 len = expectlen + 10;
3676 NewMsg->MsgBuf = NewStrBufPlain(NULL, len);
3679 NewMsg->MsgBuf = NewStrBufPlain(exist, eLen);
3682 /* Do we need to change leading ".." to "." for SMTP escaping? */
3683 if ((tlen == 1) && (*terminator == '.')) {
3687 NewMsg->terminator = terminator;
3688 NewMsg->tlen = tlen;
3690 NewMsg->maxlen = maxlen;
3692 NewMsg->crlf = crlf;
3698 * Back end function used by CtdlMakeMessage() and similar functions
3700 eReadState CtdlReadMessageBodyAsync(AsyncIO *IO)
3702 ReadAsyncMsg *ReadMsg;
3703 int MsgFinished = 0;
3704 eReadState Finished = eMustReadMore;
3709 const char *pch = ChrPtr(IO->SendBuf.Buf);
3710 const char *pchh = IO->SendBuf.ReadWritePointer;
3716 nbytes = StrLength(IO->SendBuf.Buf) - (pchh - pch);
3717 snprintf(fn, SIZ, "/tmp/foolog_ev_%s.%d",
3718 ((CitContext*)(IO->CitContext))->ServiceName,
3721 fd = fopen(fn, "a+");
3724 ReadMsg = IO->ReadMsg;
3726 /* read in the lines of message text one by one */
3728 Finished = StrBufChunkSipLine(IO->IOBuf, &IO->RecvBuf);
3731 case eMustReadMore: /// read new from socket...
3733 if (IO->RecvBuf.ReadWritePointer != NULL) {
3734 nbytes = StrLength(IO->RecvBuf.Buf) - (IO->RecvBuf.ReadWritePointer - ChrPtr(IO->RecvBuf.Buf));
3735 fprintf(fd, "Read; Line unfinished: %ld Bytes still in buffer [", nbytes);
3737 fwrite(IO->RecvBuf.ReadWritePointer, nbytes, 1, fd);
3741 fprintf(fd, "BufferEmpty! \n");
3747 case eBufferNotEmpty: /* shouldn't happen... */
3748 case eReadSuccess: /// done for now...
3750 case eReadFail: /// WHUT?
3756 if ((StrLength(IO->IOBuf) == ReadMsg->tlen) &&
3757 (!strcmp(ChrPtr(IO->IOBuf), ReadMsg->terminator))) {
3760 fprintf(fd, "found Terminator; Message Size: %d\n", StrLength(ReadMsg->MsgBuf));
3763 else if (!ReadMsg->flushing) {
3766 fprintf(fd, "Read Line: [%d][%s]\n", StrLength(IO->IOBuf), ChrPtr(IO->IOBuf));
3769 /* Unescape SMTP-style input of two dots at the beginning of the line */
3770 if ((ReadMsg->dodot) &&
3771 (StrLength(IO->IOBuf) == 2) && /* TODO: do we just unescape lines with two dots or any line? */
3772 (!strcmp(ChrPtr(IO->IOBuf), "..")))
3775 fprintf(fd, "UnEscaped!\n");
3777 StrBufCutLeft(IO->IOBuf, 1);
3780 if (ReadMsg->crlf) {
3781 StrBufAppendBufPlain(IO->IOBuf, HKEY("\r\n"), 0);
3784 StrBufAppendBufPlain(IO->IOBuf, HKEY("\n"), 0);
3787 StrBufAppendBuf(ReadMsg->MsgBuf, IO->IOBuf, 0);
3790 /* if we've hit the max msg length, flush the rest */
3791 if (StrLength(ReadMsg->MsgBuf) >= ReadMsg->maxlen) ReadMsg->flushing = 1;
3793 } while (!MsgFinished);
3796 fprintf(fd, "Done with reading; %s.\n, ",
3797 (MsgFinished)?"Message Finished": "FAILED");
3801 return eReadSuccess;
3808 * Back end function used by CtdlMakeMessage() and similar functions
3810 char *CtdlReadMessageBody(char *terminator, /* token signalling EOT */
3812 size_t maxlen, /* maximum message length */
3813 char *exist, /* if non-null, append to it;
3814 exist is ALWAYS freed */
3815 int crlf, /* CRLF newlines instead of LF */
3816 int *sock /* socket handle or 0 for this session's client socket */
3821 Message = CtdlReadMessageBodyBuf(terminator,
3827 if (Message == NULL)
3830 return SmashStrBuf(&Message);
3835 * Build a binary message to be saved on disk.
3836 * (NOTE: if you supply 'preformatted_text', the buffer you give it
3837 * will become part of the message. This means you are no longer
3838 * responsible for managing that memory -- it will be freed along with
3839 * the rest of the fields when CtdlFreeMessage() is called.)
3842 struct CtdlMessage *CtdlMakeMessage(
3843 struct ctdluser *author, /* author's user structure */
3844 char *recipient, /* NULL if it's not mail */
3845 char *recp_cc, /* NULL if it's not mail */
3846 char *room, /* room where it's going */
3847 int type, /* see MES_ types in header file */
3848 int format_type, /* variformat, plain text, MIME... */
3849 char *fake_name, /* who we're masquerading as */
3850 char *my_email, /* which of my email addresses to use (empty is ok) */
3851 char *subject, /* Subject (optional) */
3852 char *supplied_euid, /* ...or NULL if this is irrelevant */
3853 char *preformatted_text, /* ...or NULL to read text from client */
3854 char *references /* Thread references */
3856 char dest_node[256];
3858 struct CtdlMessage *msg;
3860 StrBuf *FakeEncAuthor = NULL;
3862 msg = malloc(sizeof(struct CtdlMessage));
3863 memset(msg, 0, sizeof(struct CtdlMessage));
3864 msg->cm_magic = CTDLMESSAGE_MAGIC;
3865 msg->cm_anon_type = type;
3866 msg->cm_format_type = format_type;
3868 /* Don't confuse the poor folks if it's not routed mail. */
3869 strcpy(dest_node, "");
3871 if (recipient != NULL) striplt(recipient);
3872 if (recp_cc != NULL) striplt(recp_cc);
3874 /* Path or Return-Path */
3875 if (my_email == NULL) my_email = "";
3877 if (!IsEmptyStr(my_email)) {
3878 msg->cm_fields['P'] = strdup(my_email);
3881 snprintf(buf, sizeof buf, "%s", author->fullname);
3882 msg->cm_fields['P'] = strdup(buf);
3884 convert_spaces_to_underscores(msg->cm_fields['P']);
3886 snprintf(buf, sizeof buf, "%ld", (long)time(NULL)); /* timestamp */
3887 msg->cm_fields['T'] = strdup(buf);
3889 if ((fake_name != NULL) && (fake_name[0])) { /* author */
3890 FakeAuthor = NewStrBufPlain (fake_name, -1);
3893 FakeAuthor = NewStrBufPlain (author->fullname, -1);
3895 StrBufRFC2047encode(&FakeEncAuthor, FakeAuthor);
3896 msg->cm_fields['A'] = SmashStrBuf(&FakeEncAuthor);
3897 FreeStrBuf(&FakeAuthor);
3899 if (CC->room.QRflags & QR_MAILBOX) { /* room */
3900 msg->cm_fields['O'] = strdup(&CC->room.QRname[11]);
3903 msg->cm_fields['O'] = strdup(CC->room.QRname);
3906 msg->cm_fields['N'] = strdup(NODENAME); /* nodename */
3907 msg->cm_fields['H'] = strdup(HUMANNODE); /* hnodename */
3909 if ((recipient != NULL) && (recipient[0] != 0)) {
3910 msg->cm_fields['R'] = strdup(recipient);
3912 if ((recp_cc != NULL) && (recp_cc[0] != 0)) {
3913 msg->cm_fields['Y'] = strdup(recp_cc);
3915 if (dest_node[0] != 0) {
3916 msg->cm_fields['D'] = strdup(dest_node);
3919 if (!IsEmptyStr(my_email)) {
3920 msg->cm_fields['F'] = strdup(my_email);
3922 else if ( (author == &CC->user) && (!IsEmptyStr(CC->cs_inet_email)) ) {
3923 msg->cm_fields['F'] = strdup(CC->cs_inet_email);
3926 if (subject != NULL) {
3929 length = strlen(subject);
3935 while ((subject[i] != '\0') &&
3936 (IsAscii = isascii(subject[i]) != 0 ))
3939 msg->cm_fields['U'] = strdup(subject);
3940 else /* ok, we've got utf8 in the string. */
3942 msg->cm_fields['U'] = rfc2047encode(subject, length);
3948 if (supplied_euid != NULL) {
3949 msg->cm_fields['E'] = strdup(supplied_euid);
3952 if ((references != NULL) && (!IsEmptyStr(references))) {
3953 if (msg->cm_fields['W'] != NULL)
3954 free(msg->cm_fields['W']);
3955 msg->cm_fields['W'] = strdup(references);
3958 if (preformatted_text != NULL) {
3959 msg->cm_fields['M'] = preformatted_text;
3962 msg->cm_fields['M'] = CtdlReadMessageBody(HKEY("000"), config.c_maxmsglen, NULL, 0, 0);
3968 extern int netconfig_check_roomaccess(
3971 const char* RemoteIdentifier); /* TODO: find a smarter way */
3974 * Check to see whether we have permission to post a message in the current
3975 * room. Returns a *CITADEL ERROR CODE* and puts a message in errmsgbuf, or
3976 * returns 0 on success.
3978 int CtdlDoIHavePermissionToPostInThisRoom(
3981 const char* RemoteIdentifier,
3987 if (!(CC->logged_in) &&
3988 (PostPublic == POST_LOGGED_IN)) {
3989 snprintf(errmsgbuf, n, "Not logged in.");
3990 return (ERROR + NOT_LOGGED_IN);
3992 else if (PostPublic == CHECK_EXISTANCE) {
3993 return (0); // We're Evaling whether a recipient exists
3995 else if (!(CC->logged_in)) {
3997 if ((CC->room.QRflags & QR_READONLY)) {
3998 snprintf(errmsgbuf, n, "Not logged in.");
3999 return (ERROR + NOT_LOGGED_IN);
4001 if (CC->room.QRflags2 & QR2_MODERATED) {
4002 snprintf(errmsgbuf, n, "Not logged in Moderation feature not yet implemented!");
4003 return (ERROR + NOT_LOGGED_IN);
4005 if ((PostPublic!=POST_LMTP) &&(CC->room.QRflags2 & QR2_SMTP_PUBLIC) == 0) {
4007 return netconfig_check_roomaccess(errmsgbuf, n, RemoteIdentifier);
4013 if ((CC->user.axlevel < AxProbU)
4014 && ((CC->room.QRflags & QR_MAILBOX) == 0)) {
4015 snprintf(errmsgbuf, n, "Need to be validated to enter (except in %s> to sysop)", MAILROOM);
4016 return (ERROR + HIGHER_ACCESS_REQUIRED);
4019 CtdlRoomAccess(&CC->room, &CC->user, &ra, NULL);
4021 if (ra & UA_POSTALLOWED) {
4022 strcpy(errmsgbuf, "OK to post or reply here");
4026 if ( (ra & UA_REPLYALLOWED) && (is_reply) ) {
4028 * To be thorough, we ought to check to see if the message they are
4029 * replying to is actually a valid one in this room, but unless this
4030 * actually becomes a problem we'll go with high performance instead.
4032 strcpy(errmsgbuf, "OK to reply here");
4036 if ( (ra & UA_REPLYALLOWED) && (!is_reply) ) {
4037 /* Clarify what happened with a better error message */
4038 snprintf(errmsgbuf, n, "You may only reply to existing messages here.");
4039 return (ERROR + HIGHER_ACCESS_REQUIRED);
4042 snprintf(errmsgbuf, n, "Higher access is required to post in this room.");
4043 return (ERROR + HIGHER_ACCESS_REQUIRED);
4049 * Check to see if the specified user has Internet mail permission
4050 * (returns nonzero if permission is granted)
4052 int CtdlCheckInternetMailPermission(struct ctdluser *who) {
4054 /* Do not allow twits to send Internet mail */
4055 if (who->axlevel <= AxProbU) return(0);
4057 /* Globally enabled? */
4058 if (config.c_restrict == 0) return(1);
4060 /* User flagged ok? */
4061 if (who->flags & US_INTERNET) return(2);
4063 /* Aide level access? */
4064 if (who->axlevel >= AxAideU) return(3);
4066 /* No mail for you! */
4072 * Validate recipients, count delivery types and errors, and handle aliasing
4073 * FIXME check for dupes!!!!!
4075 * Returns 0 if all addresses are ok, ret->num_error = -1 if no addresses
4076 * were specified, or the number of addresses found invalid.
4078 * Caller needs to free the result using free_recipients()
4080 struct recptypes *validate_recipients(const char *supplied_recipients,
4081 const char *RemoteIdentifier,
4083 struct CitContext *CCC = CC;
4084 struct recptypes *ret;
4085 char *recipients = NULL;
4087 char this_recp[256];
4088 char this_recp_cooked[256];
4095 struct ctdluser tempUS;
4096 struct ctdlroom tempQR;
4097 struct ctdlroom tempQR2;
4103 ret = (struct recptypes *) malloc(sizeof(struct recptypes));
4104 if (ret == NULL) return(NULL);
4106 /* Set all strings to null and numeric values to zero */
4107 memset(ret, 0, sizeof(struct recptypes));
4109 if (supplied_recipients == NULL) {
4110 recipients = strdup("");
4113 recipients = strdup(supplied_recipients);
4116 /* Allocate some memory. Yes, this allocates 500% more memory than we will
4117 * actually need, but it's healthier for the heap than doing lots of tiny
4118 * realloc() calls instead.
4120 len = strlen(recipients) + 1024;
4121 ret->errormsg = malloc(len);
4122 ret->recp_local = malloc(len);
4123 ret->recp_internet = malloc(len);
4124 ret->recp_ignet = malloc(len);
4125 ret->recp_room = malloc(len);
4126 ret->display_recp = malloc(len);
4127 ret->recp_orgroom = malloc(len);
4128 org_recp = malloc(len);
4130 ret->errormsg[0] = 0;
4131 ret->recp_local[0] = 0;
4132 ret->recp_internet[0] = 0;
4133 ret->recp_ignet[0] = 0;
4134 ret->recp_room[0] = 0;
4135 ret->recp_orgroom[0] = 0;
4136 ret->display_recp[0] = 0;
4138 ret->recptypes_magic = RECPTYPES_MAGIC;
4140 /* Change all valid separator characters to commas */
4141 for (i=0; !IsEmptyStr(&recipients[i]); ++i) {
4142 if ((recipients[i] == ';') || (recipients[i] == '|')) {
4143 recipients[i] = ',';
4147 /* Now start extracting recipients... */
4149 while (!IsEmptyStr(recipients)) {
4150 for (i=0; i<=strlen(recipients); ++i) {
4151 if (recipients[i] == '\"') in_quotes = 1 - in_quotes;
4152 if ( ( (recipients[i] == ',') && (!in_quotes) ) || (recipients[i] == 0) ) {
4153 safestrncpy(this_recp, recipients, i+1);
4155 if (recipients[i] == ',') {
4156 strcpy(recipients, &recipients[i+1]);
4159 strcpy(recipients, "");
4166 if (IsEmptyStr(this_recp))
4168 MSG_syslog(LOG_DEBUG, "Evaluating recipient #%d: %s\n", num_recps, this_recp);
4171 strcpy(org_recp, this_recp);
4172 mailtype = alias(this_recp);
4173 mailtype = alias(this_recp);
4174 mailtype = alias(this_recp);
4176 for (j=0; !IsEmptyStr(&this_recp[j]); ++j) {
4177 if (this_recp[j]=='_') {
4178 this_recp_cooked[j] = ' ';
4181 this_recp_cooked[j] = this_recp[j];
4184 this_recp_cooked[j] = '\0';
4189 if (!strcasecmp(this_recp, "sysop")) {
4191 strcpy(this_recp, config.c_aideroom);
4192 if (!IsEmptyStr(ret->recp_room)) {
4193 strcat(ret->recp_room, "|");
4195 strcat(ret->recp_room, this_recp);
4197 else if ( (!strncasecmp(this_recp, "room_", 5))
4198 && (!CtdlGetRoom(&tempQR, &this_recp_cooked[5])) ) {
4200 /* Save room so we can restore it later */
4201 tempQR2 = CCC->room;
4204 /* Check permissions to send mail to this room */
4205 err = CtdlDoIHavePermissionToPostInThisRoom(
4210 0 /* 0 = not a reply */
4219 if (!IsEmptyStr(ret->recp_room)) {
4220 strcat(ret->recp_room, "|");
4222 strcat(ret->recp_room, &this_recp_cooked[5]);
4224 if (!IsEmptyStr(ret->recp_orgroom)) {
4225 strcat(ret->recp_orgroom, "|");
4227 strcat(ret->recp_orgroom, org_recp);
4231 /* Restore room in case something needs it */
4232 CCC->room = tempQR2;
4235 else if (CtdlGetUser(&tempUS, this_recp) == 0) {
4237 strcpy(this_recp, tempUS.fullname);
4238 if (!IsEmptyStr(ret->recp_local)) {
4239 strcat(ret->recp_local, "|");
4241 strcat(ret->recp_local, this_recp);
4243 else if (CtdlGetUser(&tempUS, this_recp_cooked) == 0) {
4245 strcpy(this_recp, tempUS.fullname);
4246 if (!IsEmptyStr(ret->recp_local)) {
4247 strcat(ret->recp_local, "|");
4249 strcat(ret->recp_local, this_recp);
4257 /* Yes, you're reading this correctly: if the target
4258 * domain points back to the local system or an attached
4259 * Citadel directory, the address is invalid. That's
4260 * because if the address were valid, we would have
4261 * already translated it to a local address by now.
4263 if (IsDirectory(this_recp, 0)) {
4268 ++ret->num_internet;
4269 if (!IsEmptyStr(ret->recp_internet)) {
4270 strcat(ret->recp_internet, "|");
4272 strcat(ret->recp_internet, this_recp);
4277 if (!IsEmptyStr(ret->recp_ignet)) {
4278 strcat(ret->recp_ignet, "|");
4280 strcat(ret->recp_ignet, this_recp);
4288 if (IsEmptyStr(errmsg)) {
4289 snprintf(append, sizeof append, "Invalid recipient: %s", this_recp);
4292 snprintf(append, sizeof append, "%s", errmsg);
4294 if ( (strlen(ret->errormsg) + strlen(append) + 3) < SIZ) {
4295 if (!IsEmptyStr(ret->errormsg)) {
4296 strcat(ret->errormsg, "; ");
4298 strcat(ret->errormsg, append);
4302 if (IsEmptyStr(ret->display_recp)) {
4303 strcpy(append, this_recp);
4306 snprintf(append, sizeof append, ", %s", this_recp);
4308 if ( (strlen(ret->display_recp)+strlen(append)) < SIZ) {
4309 strcat(ret->display_recp, append);
4315 if ((ret->num_local + ret->num_internet + ret->num_ignet +
4316 ret->num_room + ret->num_error) == 0) {
4317 ret->num_error = (-1);
4318 strcpy(ret->errormsg, "No recipients specified.");
4321 MSGM_syslog(LOG_DEBUG, "validate_recipients()\n");
4322 MSG_syslog(LOG_DEBUG, " local: %d <%s>\n", ret->num_local, ret->recp_local);
4323 MSG_syslog(LOG_DEBUG, " room: %d <%s>\n", ret->num_room, ret->recp_room);
4324 MSG_syslog(LOG_DEBUG, " inet: %d <%s>\n", ret->num_internet, ret->recp_internet);
4325 MSG_syslog(LOG_DEBUG, " ignet: %d <%s>\n", ret->num_ignet, ret->recp_ignet);
4326 MSG_syslog(LOG_DEBUG, " error: %d <%s>\n", ret->num_error, ret->errormsg);
4334 * Destructor for struct recptypes
4336 void free_recipients(struct recptypes *valid) {
4338 if (valid == NULL) {
4342 if (valid->recptypes_magic != RECPTYPES_MAGIC) {
4343 struct CitContext *CCC = CC;
4344 MSGM_syslog(LOG_EMERG, "Attempt to call free_recipients() on some other data type!\n");
4348 if (valid->errormsg != NULL) free(valid->errormsg);
4349 if (valid->recp_local != NULL) free(valid->recp_local);
4350 if (valid->recp_internet != NULL) free(valid->recp_internet);
4351 if (valid->recp_ignet != NULL) free(valid->recp_ignet);
4352 if (valid->recp_room != NULL) free(valid->recp_room);
4353 if (valid->recp_orgroom != NULL) free(valid->recp_orgroom);
4354 if (valid->display_recp != NULL) free(valid->display_recp);
4355 if (valid->bounce_to != NULL) free(valid->bounce_to);
4356 if (valid->envelope_from != NULL) free(valid->envelope_from);
4357 if (valid->sending_room != NULL) free(valid->sending_room);
4364 * message entry - mode 0 (normal)
4366 void cmd_ent0(char *entargs)
4368 struct CitContext *CCC = CC;
4373 char supplied_euid[128];
4375 int format_type = 0;
4376 char newusername[256];
4377 char newuseremail[256];
4378 struct CtdlMessage *msg;
4382 struct recptypes *valid = NULL;
4383 struct recptypes *valid_to = NULL;
4384 struct recptypes *valid_cc = NULL;
4385 struct recptypes *valid_bcc = NULL;
4387 int subject_required = 0;
4389 int verbose_reply = 0;
4393 int newuseremail_ok = 0;
4394 char references[SIZ];
4399 post = extract_int(entargs, 0);
4400 extract_token(recp, entargs, 1, '|', sizeof recp);
4401 anon_flag = extract_int(entargs, 2);
4402 format_type = extract_int(entargs, 3);
4403 extract_token(subject, entargs, 4, '|', sizeof subject);
4404 extract_token(newusername, entargs, 5, '|', sizeof newusername);
4405 do_confirm = extract_int(entargs, 6);
4406 extract_token(cc, entargs, 7, '|', sizeof cc);
4407 extract_token(bcc, entargs, 8, '|', sizeof bcc);
4408 verbose_reply = extract_int(entargs, 9);
4409 switch(CC->room.QRdefaultview) {
4412 extract_token(supplied_euid, entargs, 9, '|', sizeof supplied_euid);
4415 supplied_euid[0] = 0;
4418 extract_token(newuseremail, entargs, 10, '|', sizeof newuseremail);
4419 extract_token(references, entargs, 11, '|', sizeof references);
4420 for (ptr=references; *ptr != 0; ++ptr) {
4421 if (*ptr == '!') *ptr = '|';
4424 /* first check to make sure the request is valid. */
4426 err = CtdlDoIHavePermissionToPostInThisRoom(
4431 (!IsEmptyStr(references)) /* is this a reply? or a top-level post? */
4435 cprintf("%d %s\n", err, errmsg);
4439 /* Check some other permission type things. */
4441 if (IsEmptyStr(newusername)) {
4442 strcpy(newusername, CCC->user.fullname);
4444 if ( (CCC->user.axlevel < AxAideU)
4445 && (strcasecmp(newusername, CCC->user.fullname))
4446 && (strcasecmp(newusername, CCC->cs_inet_fn))
4448 cprintf("%d You don't have permission to author messages as '%s'.\n",
4449 ERROR + HIGHER_ACCESS_REQUIRED,
4456 if (IsEmptyStr(newuseremail)) {
4457 newuseremail_ok = 1;
4460 if (!IsEmptyStr(newuseremail)) {
4461 if (!strcasecmp(newuseremail, CCC->cs_inet_email)) {
4462 newuseremail_ok = 1;
4464 else if (!IsEmptyStr(CCC->cs_inet_other_emails)) {
4465 j = num_tokens(CCC->cs_inet_other_emails, '|');
4466 for (i=0; i<j; ++i) {
4467 extract_token(buf, CCC->cs_inet_other_emails, i, '|', sizeof buf);
4468 if (!strcasecmp(newuseremail, buf)) {
4469 newuseremail_ok = 1;
4475 if (!newuseremail_ok) {
4476 cprintf("%d You don't have permission to author messages as '%s'.\n",
4477 ERROR + HIGHER_ACCESS_REQUIRED,
4483 CCC->cs_flags |= CS_POSTING;
4485 /* In mailbox rooms we have to behave a little differently --
4486 * make sure the user has specified at least one recipient. Then
4487 * validate the recipient(s). We do this for the Mail> room, as
4488 * well as any room which has the "Mailbox" view set - unless it
4489 * is the DRAFTS room which does not require recipients
4492 if ( ( ( (CCC->room.QRflags & QR_MAILBOX) && (!strcasecmp(&CCC->room.QRname[11], MAILROOM)) )
4493 || ( (CCC->room.QRflags & QR_MAILBOX) && (CCC->curr_view == VIEW_MAILBOX) )
4494 ) && (strcasecmp(&CCC->room.QRname[11], USERDRAFTROOM)) !=0 ) {
4495 if (CCC->user.axlevel < AxProbU) {
4496 strcpy(recp, "sysop");
4501 valid_to = validate_recipients(recp, NULL, 0);
4502 if (valid_to->num_error > 0) {
4503 cprintf("%d %s\n", ERROR + NO_SUCH_USER, valid_to->errormsg);
4504 free_recipients(valid_to);
4508 valid_cc = validate_recipients(cc, NULL, 0);
4509 if (valid_cc->num_error > 0) {
4510 cprintf("%d %s\n", ERROR + NO_SUCH_USER, valid_cc->errormsg);
4511 free_recipients(valid_to);
4512 free_recipients(valid_cc);
4516 valid_bcc = validate_recipients(bcc, NULL, 0);
4517 if (valid_bcc->num_error > 0) {
4518 cprintf("%d %s\n", ERROR + NO_SUCH_USER, valid_bcc->errormsg);
4519 free_recipients(valid_to);
4520 free_recipients(valid_cc);
4521 free_recipients(valid_bcc);
4525 /* Recipient required, but none were specified */
4526 if ( (valid_to->num_error < 0) && (valid_cc->num_error < 0) && (valid_bcc->num_error < 0) ) {
4527 free_recipients(valid_to);
4528 free_recipients(valid_cc);
4529 free_recipients(valid_bcc);
4530 cprintf("%d At least one recipient is required.\n", ERROR + NO_SUCH_USER);
4534 if (valid_to->num_internet + valid_cc->num_internet + valid_bcc->num_internet > 0) {
4535 if (CtdlCheckInternetMailPermission(&CCC->user)==0) {
4536 cprintf("%d You do not have permission "
4537 "to send Internet mail.\n",
4538 ERROR + HIGHER_ACCESS_REQUIRED);
4539 free_recipients(valid_to);
4540 free_recipients(valid_cc);
4541 free_recipients(valid_bcc);
4546 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)
4547 && (CCC->user.axlevel < AxNetU) ) {
4548 cprintf("%d Higher access required for network mail.\n",
4549 ERROR + HIGHER_ACCESS_REQUIRED);
4550 free_recipients(valid_to);
4551 free_recipients(valid_cc);
4552 free_recipients(valid_bcc);
4556 if ((RESTRICT_INTERNET == 1)
4557 && (valid_to->num_internet + valid_cc->num_internet + valid_bcc->num_internet > 0)
4558 && ((CCC->user.flags & US_INTERNET) == 0)
4559 && (!CCC->internal_pgm)) {
4560 cprintf("%d You don't have access to Internet mail.\n",
4561 ERROR + HIGHER_ACCESS_REQUIRED);
4562 free_recipients(valid_to);
4563 free_recipients(valid_cc);
4564 free_recipients(valid_bcc);
4570 /* Is this a room which has anonymous-only or anonymous-option? */
4571 anonymous = MES_NORMAL;
4572 if (CCC->room.QRflags & QR_ANONONLY) {
4573 anonymous = MES_ANONONLY;
4575 if (CCC->room.QRflags & QR_ANONOPT) {
4576 if (anon_flag == 1) { /* only if the user requested it */
4577 anonymous = MES_ANONOPT;
4581 if ((CCC->room.QRflags & QR_MAILBOX) == 0) {
4585 /* Recommend to the client that the use of a message subject is
4586 * strongly recommended in this room, if either the SUBJECTREQ flag
4587 * is set, or if there is one or more Internet email recipients.
4589 if (CCC->room.QRflags2 & QR2_SUBJECTREQ) subject_required = 1;
4590 if ((valid_to) && (valid_to->num_internet > 0)) subject_required = 1;
4591 if ((valid_cc) && (valid_cc->num_internet > 0)) subject_required = 1;
4592 if ((valid_bcc) && (valid_bcc->num_internet > 0)) subject_required = 1;
4594 /* If we're only checking the validity of the request, return
4595 * success without creating the message.
4598 cprintf("%d %s|%d\n", CIT_OK,
4599 ((valid_to != NULL) ? valid_to->display_recp : ""),
4601 free_recipients(valid_to);
4602 free_recipients(valid_cc);
4603 free_recipients(valid_bcc);
4607 /* We don't need these anymore because we'll do it differently below */
4608 free_recipients(valid_to);
4609 free_recipients(valid_cc);
4610 free_recipients(valid_bcc);
4612 /* Read in the message from the client. */
4614 cprintf("%d send message\n", START_CHAT_MODE);
4616 cprintf("%d send message\n", SEND_LISTING);
4619 msg = CtdlMakeMessage(&CCC->user, recp, cc,
4620 CCC->room.QRname, anonymous, format_type,
4621 newusername, newuseremail, subject,
4622 ((!IsEmptyStr(supplied_euid)) ? supplied_euid : NULL),
4625 /* Put together one big recipients struct containing to/cc/bcc all in
4626 * one. This is for the envelope.
4628 char *all_recps = malloc(SIZ * 3);
4629 strcpy(all_recps, recp);
4630 if (!IsEmptyStr(cc)) {
4631 if (!IsEmptyStr(all_recps)) {
4632 strcat(all_recps, ",");
4634 strcat(all_recps, cc);
4636 if (!IsEmptyStr(bcc)) {
4637 if (!IsEmptyStr(all_recps)) {
4638 strcat(all_recps, ",");
4640 strcat(all_recps, bcc);
4642 if (!IsEmptyStr(all_recps)) {
4643 valid = validate_recipients(all_recps, NULL, 0);
4650 if ((valid != NULL) && (valid->num_room == 1))
4652 /* posting into an ML room? set the envelope from
4653 * to the actual mail address so others get a valid
4656 msg->cm_fields['V'] = strdup(valid->recp_orgroom);
4660 msgnum = CtdlSubmitMsg(msg, valid, "", QP_EADDR);
4663 if (StrLength(CCC->StatusMessage)>0)
4665 StrBufAppendBufPlain(CCC->StatusMessage, HKEY("\n000\n"), 0);
4666 cputbuf(CCC->StatusMessage);
4669 client_write(HKEY("\n000\n"));
4673 cprintf("%ld\n", msgnum);
4675 client_write(HKEY("Message accepted.\n"));
4678 client_write(HKEY("Internal error.\n"));
4680 if (msg->cm_fields['E'] != NULL) {
4681 cprintf("%s\n", msg->cm_fields['E']);
4688 CtdlFreeMessage(msg);
4690 if (valid != NULL) {
4691 free_recipients(valid);
4699 * API function to delete messages which match a set of criteria
4700 * (returns the actual number of messages deleted)
4702 int CtdlDeleteMessages(char *room_name, /* which room */
4703 long *dmsgnums, /* array of msg numbers to be deleted */
4704 int num_dmsgnums, /* number of msgs to be deleted, or 0 for "any" */
4705 char *content_type /* or "" for any. regular expressions expected. */
4708 struct CitContext *CCC = CC;
4709 struct ctdlroom qrbuf;
4710 struct cdbdata *cdbfr;
4711 long *msglist = NULL;
4712 long *dellist = NULL;
4715 int num_deleted = 0;
4717 struct MetaData smi;
4720 int need_to_free_re = 0;
4722 if (content_type) if (!IsEmptyStr(content_type)) {
4723 regcomp(&re, content_type, 0);
4724 need_to_free_re = 1;
4726 MSG_syslog(LOG_DEBUG, "CtdlDeleteMessages(%s, %d msgs, %s)\n",
4727 room_name, num_dmsgnums, content_type);
4729 /* get room record, obtaining a lock... */
4730 if (CtdlGetRoomLock(&qrbuf, room_name) != 0) {
4731 MSG_syslog(LOG_ERR, "CtdlDeleteMessages(): Room <%s> not found\n",
4733 if (need_to_free_re) regfree(&re);
4734 return (0); /* room not found */
4736 cdbfr = cdb_fetch(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long));
4738 if (cdbfr != NULL) {
4739 dellist = malloc(cdbfr->len);
4740 msglist = (long *) cdbfr->ptr;
4741 cdbfr->ptr = NULL; /* CtdlDeleteMessages() now owns this memory */
4742 num_msgs = cdbfr->len / sizeof(long);
4746 int have_contenttype = !IsEmptyStr(content_type);
4747 int have_delmsgs = (num_dmsgnums == 0) || (dmsgnums == NULL);
4748 int have_more_del = 1;
4750 num_msgs = sort_msglist(msglist, num_msgs);
4751 if (num_dmsgnums > 1)
4752 num_dmsgnums = sort_msglist(dmsgnums, num_dmsgnums);
4755 StrBuf *dbg = NewStrBuf();
4756 for (i = 0; i < num_dmsgnums; i++)
4757 StrBufAppendPrintf(dbg, ", %ld", dmsgnums[i]);
4758 MSG_syslog(LOG_DEBUG, "Deleting before: %s", ChrPtr(dbg));
4763 while ((i < num_msgs) && (have_more_del)) {
4767 /* Set/clear a bit for each criterion */
4769 /* 0 messages in the list or a null list means that we are
4770 * interested in deleting any messages which meet the other criteria.
4773 delete_this |= 0x01;
4776 while ((i < num_msgs) && (msglist[i] < dmsgnums[j])) i++;
4777 if (msglist[i] == dmsgnums[j]) {
4778 delete_this |= 0x01;
4781 have_more_del = (j < num_dmsgnums);
4784 if (have_contenttype) {
4785 GetMetaData(&smi, msglist[i]);
4786 if (regexec(&re, smi.meta_content_type, 1, &pm, 0) == 0) {
4787 delete_this |= 0x02;
4790 delete_this |= 0x02;
4793 /* Delete message only if all bits are set */
4794 if (delete_this == 0x03) {
4795 dellist[num_deleted++] = msglist[i];
4802 StrBuf *dbg = NewStrBuf();
4803 for (i = 0; i < num_deleted; i++)
4804 StrBufAppendPrintf(dbg, ", %ld", dellist[i]);
4805 MSG_syslog(LOG_DEBUG, "Deleting: %s", ChrPtr(dbg));
4809 num_msgs = sort_msglist(msglist, num_msgs);
4810 cdb_store(CDB_MSGLISTS, &qrbuf.QRnumber, (int)sizeof(long),
4811 msglist, (int)(num_msgs * sizeof(long)));
4814 qrbuf.QRhighest = msglist[num_msgs - 1];
4816 qrbuf.QRhighest = 0;
4818 CtdlPutRoomLock(&qrbuf);
4820 /* Go through the messages we pulled out of the index, and decrement
4821 * their reference counts by 1. If this is the only room the message
4822 * was in, the reference count will reach zero and the message will
4823 * automatically be deleted from the database. We do this in a
4824 * separate pass because there might be plug-in hooks getting called,
4825 * and we don't want that happening during an S_ROOMS critical
4829 for (i=0; i<num_deleted; ++i) {
4830 PerformDeleteHooks(qrbuf.QRname, dellist[i]);
4832 AdjRefCountList(dellist, num_deleted, -1);
4834 /* Now free the memory we used, and go away. */
4835 if (msglist != NULL) free(msglist);
4836 if (dellist != NULL) free(dellist);
4837 MSG_syslog(LOG_DEBUG, "%d message(s) deleted.\n", num_deleted);
4838 if (need_to_free_re) regfree(&re);
4839 return (num_deleted);
4845 * Check whether the current user has permission to delete messages from
4846 * the current room (returns 1 for yes, 0 for no)
4848 int CtdlDoIHavePermissionToDeleteMessagesFromThisRoom(void) {
4850 CtdlRoomAccess(&CC->room, &CC->user, &ra, NULL);
4851 if (ra & UA_DELETEALLOWED) return(1);
4859 * Delete message from current room
4861 void cmd_dele(char *args)
4870 extract_token(msgset, args, 0, '|', sizeof msgset);
4871 num_msgs = num_tokens(msgset, ',');
4873 cprintf("%d Nothing to do.\n", CIT_OK);
4877 if (CtdlDoIHavePermissionToDeleteMessagesFromThisRoom() == 0) {
4878 cprintf("%d Higher access required.\n",
4879 ERROR + HIGHER_ACCESS_REQUIRED);
4884 * Build our message set to be moved/copied
4886 msgs = malloc(num_msgs * sizeof(long));
4887 for (i=0; i<num_msgs; ++i) {
4888 extract_token(msgtok, msgset, i, ',', sizeof msgtok);
4889 msgs[i] = atol(msgtok);
4892 num_deleted = CtdlDeleteMessages(CC->room.QRname, msgs, num_msgs, "");
4896 cprintf("%d %d message%s deleted.\n", CIT_OK,
4897 num_deleted, ((num_deleted != 1) ? "s" : ""));
4899 cprintf("%d Message not found.\n", ERROR + MESSAGE_NOT_FOUND);
4907 * move or copy a message to another room
4909 void cmd_move(char *args)
4916 char targ[ROOMNAMELEN];
4917 struct ctdlroom qtemp;
4924 extract_token(msgset, args, 0, '|', sizeof msgset);
4925 num_msgs = num_tokens(msgset, ',');
4927 cprintf("%d Nothing to do.\n", CIT_OK);
4931 extract_token(targ, args, 1, '|', sizeof targ);
4932 convert_room_name_macros(targ, sizeof targ);
4933 targ[ROOMNAMELEN - 1] = 0;
4934 is_copy = extract_int(args, 2);
4936 if (CtdlGetRoom(&qtemp, targ) != 0) {
4937 cprintf("%d '%s' does not exist.\n", ERROR + ROOM_NOT_FOUND, targ);
4941 if (!strcasecmp(qtemp.QRname, CC->room.QRname)) {
4942 cprintf("%d Source and target rooms are the same.\n", ERROR + ALREADY_EXISTS);
4946 CtdlGetUser(&CC->user, CC->curr_user);
4947 CtdlRoomAccess(&qtemp, &CC->user, &ra, NULL);
4949 /* Check for permission to perform this operation.
4950 * Remember: "CC->room" is source, "qtemp" is target.
4954 /* Aides can move/copy */
4955 if (CC->user.axlevel >= AxAideU) permit = 1;
4957 /* Room aides can move/copy */
4958 if (CC->user.usernum == CC->room.QRroomaide) permit = 1;
4960 /* Permit move/copy from personal rooms */
4961 if ((CC->room.QRflags & QR_MAILBOX)
4962 && (qtemp.QRflags & QR_MAILBOX)) permit = 1;
4964 /* Permit only copy from public to personal room */
4966 && (!(CC->room.QRflags & QR_MAILBOX))
4967 && (qtemp.QRflags & QR_MAILBOX)) permit = 1;
4969 /* Permit message removal from collaborative delete rooms */
4970 if (CC->room.QRflags2 & QR2_COLLABDEL) permit = 1;
4972 /* Users allowed to post into the target room may move into it too. */
4973 if ((CC->room.QRflags & QR_MAILBOX) &&
4974 (qtemp.QRflags & UA_POSTALLOWED)) permit = 1;
4976 /* User must have access to target room */
4977 if (!(ra & UA_KNOWN)) permit = 0;
4980 cprintf("%d Higher access required.\n",
4981 ERROR + HIGHER_ACCESS_REQUIRED);
4986 * Build our message set to be moved/copied
4988 msgs = malloc(num_msgs * sizeof(long));
4989 for (i=0; i<num_msgs; ++i) {
4990 extract_token(msgtok, msgset, i, ',', sizeof msgtok);
4991 msgs[i] = atol(msgtok);
4997 err = CtdlSaveMsgPointersInRoom(targ, msgs, num_msgs, 1, NULL, 0);
4999 cprintf("%d Cannot store message(s) in %s: error %d\n",
5005 /* Now delete the message from the source room,
5006 * if this is a 'move' rather than a 'copy' operation.
5009 CtdlDeleteMessages(CC->room.QRname, msgs, num_msgs, "");
5013 cprintf("%d Message(s) %s.\n", CIT_OK, (is_copy ? "copied" : "moved") );
5019 * GetMetaData() - Get the supplementary record for a message
5021 void GetMetaData(struct MetaData *smibuf, long msgnum)
5024 struct cdbdata *cdbsmi;
5027 memset(smibuf, 0, sizeof(struct MetaData));
5028 smibuf->meta_msgnum = msgnum;
5029 smibuf->meta_refcount = 1; /* Default reference count is 1 */
5031 /* Use the negative of the message number for its supp record index */
5032 TheIndex = (0L - msgnum);
5034 cdbsmi = cdb_fetch(CDB_MSGMAIN, &TheIndex, sizeof(long));
5035 if (cdbsmi == NULL) {
5036 return; /* record not found; go with defaults */
5038 memcpy(smibuf, cdbsmi->ptr,
5039 ((cdbsmi->len > sizeof(struct MetaData)) ?
5040 sizeof(struct MetaData) : cdbsmi->len));
5047 * PutMetaData() - (re)write supplementary record for a message
5049 void PutMetaData(struct MetaData *smibuf)
5053 /* Use the negative of the message number for the metadata db index */
5054 TheIndex = (0L - smibuf->meta_msgnum);
5056 cdb_store(CDB_MSGMAIN,
5057 &TheIndex, (int)sizeof(long),
5058 smibuf, (int)sizeof(struct MetaData));
5063 * AdjRefCount - submit an adjustment to the reference count for a message.
5064 * (These are just queued -- we actually process them later.)
5066 void AdjRefCount(long msgnum, int incr)
5068 struct CitContext *CCC = CC;
5069 struct arcq new_arcq;
5072 MSG_syslog(LOG_DEBUG, "AdjRefCount() msg %ld ref count delta %+d\n", msgnum, incr);
5074 begin_critical_section(S_SUPPMSGMAIN);
5075 if (arcfp == NULL) {
5076 arcfp = fopen(file_arcq, "ab+");
5077 chown(file_arcq, CTDLUID, (-1));
5078 chmod(file_arcq, 0600);
5080 end_critical_section(S_SUPPMSGMAIN);
5082 /* msgnum < 0 means that we're trying to close the file */
5084 MSGM_syslog(LOG_DEBUG, "Closing the AdjRefCount queue file\n");
5085 begin_critical_section(S_SUPPMSGMAIN);
5086 if (arcfp != NULL) {
5090 end_critical_section(S_SUPPMSGMAIN);
5095 * If we can't open the queue, perform the operation synchronously.
5097 if (arcfp == NULL) {
5098 TDAP_AdjRefCount(msgnum, incr);
5102 new_arcq.arcq_msgnum = msgnum;
5103 new_arcq.arcq_delta = incr;
5104 rv = fwrite(&new_arcq, sizeof(struct arcq), 1, arcfp);
5106 MSG_syslog(LOG_EMERG, "Couldn't write Refcount Queue File %s: %s\n",
5115 void AdjRefCountList(long *msgnum, long nmsg, int incr)
5117 struct CitContext *CCC = CC;
5118 long i, the_size, offset;
5119 struct arcq *new_arcq;
5122 MSG_syslog(LOG_DEBUG, "AdjRefCountList() msg %ld ref count delta %+d\n", nmsg, incr);
5124 begin_critical_section(S_SUPPMSGMAIN);
5125 if (arcfp == NULL) {
5126 arcfp = fopen(file_arcq, "ab+");
5127 chown(file_arcq, CTDLUID, (-1));
5128 chmod(file_arcq, 0600);
5130 end_critical_section(S_SUPPMSGMAIN);
5133 * If we can't open the queue, perform the operation synchronously.
5135 if (arcfp == NULL) {
5136 for (i = 0; i < nmsg; i++)
5137 TDAP_AdjRefCount(msgnum[i], incr);
5141 the_size = sizeof(struct arcq) * nmsg;
5142 new_arcq = malloc(the_size);
5143 for (i = 0; i < nmsg; i++) {
5144 new_arcq[i].arcq_msgnum = msgnum[i];
5145 new_arcq[i].arcq_delta = incr;
5149 while ((rv >= 0) && (offset < the_size))
5151 rv = fwrite(new_arcq + offset, 1, the_size - offset, arcfp);
5153 MSG_syslog(LOG_EMERG, "Couldn't write Refcount Queue File %s: %s\n",
5169 * TDAP_ProcessAdjRefCountQueue()
5171 * Process the queue of message count adjustments that was created by calls
5172 * to AdjRefCount() ... by reading the queue and calling TDAP_AdjRefCount()
5173 * for each one. This should be an "off hours" operation.
5175 int TDAP_ProcessAdjRefCountQueue(void)
5177 struct CitContext *CCC = CC;
5178 char file_arcq_temp[PATH_MAX];
5181 struct arcq arcq_rec;
5182 int num_records_processed = 0;
5184 snprintf(file_arcq_temp, sizeof file_arcq_temp, "%s.%04x", file_arcq, rand());
5186 begin_critical_section(S_SUPPMSGMAIN);
5187 if (arcfp != NULL) {
5192 r = link(file_arcq, file_arcq_temp);
5194 MSG_syslog(LOG_CRIT, "%s: %s\n", file_arcq_temp, strerror(errno));
5195 end_critical_section(S_SUPPMSGMAIN);
5196 return(num_records_processed);
5200 end_critical_section(S_SUPPMSGMAIN);
5202 fp = fopen(file_arcq_temp, "rb");
5204 MSG_syslog(LOG_CRIT, "%s: %s\n", file_arcq_temp, strerror(errno));
5205 return(num_records_processed);
5208 while (fread(&arcq_rec, sizeof(struct arcq), 1, fp) == 1) {
5209 TDAP_AdjRefCount(arcq_rec.arcq_msgnum, arcq_rec.arcq_delta);
5210 ++num_records_processed;
5214 r = unlink(file_arcq_temp);
5216 MSG_syslog(LOG_CRIT, "%s: %s\n", file_arcq_temp, strerror(errno));
5219 return(num_records_processed);
5225 * TDAP_AdjRefCount - adjust the reference count for a message.
5226 * This one does it "for real" because it's called by
5227 * the autopurger function that processes the queue
5228 * created by AdjRefCount(). If a message's reference
5229 * count becomes zero, we also delete the message from
5230 * disk and de-index it.
5232 void TDAP_AdjRefCount(long msgnum, int incr)
5234 struct CitContext *CCC = CC;
5236 struct MetaData smi;
5239 /* This is a *tight* critical section; please keep it that way, as
5240 * it may get called while nested in other critical sections.
5241 * Complicating this any further will surely cause deadlock!
5243 begin_critical_section(S_SUPPMSGMAIN);
5244 GetMetaData(&smi, msgnum);
5245 smi.meta_refcount += incr;
5247 end_critical_section(S_SUPPMSGMAIN);
5248 MSG_syslog(LOG_DEBUG, "TDAP_AdjRefCount() msg %ld ref count delta %+d, is now %d\n",
5249 msgnum, incr, smi.meta_refcount
5252 /* If the reference count is now zero, delete the message
5253 * (and its supplementary record as well).
5255 if (smi.meta_refcount == 0) {
5256 MSG_syslog(LOG_DEBUG, "Deleting message <%ld>\n", msgnum);
5258 /* Call delete hooks with NULL room to show it has gone altogether */
5259 PerformDeleteHooks(NULL, msgnum);
5261 /* Remove from message base */
5263 cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long));
5264 cdb_delete(CDB_BIGMSGS, &delnum, (int)sizeof(long));
5266 /* Remove metadata record */
5267 delnum = (0L - msgnum);
5268 cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long));
5274 * Write a generic object to this room
5276 * Note: this could be much more efficient. Right now we use two temporary
5277 * files, and still pull the message into memory as with all others.
5279 void CtdlWriteObject(char *req_room, /* Room to stuff it in */
5280 char *content_type, /* MIME type of this object */
5281 char *raw_message, /* Data to be written */
5282 off_t raw_length, /* Size of raw_message */
5283 struct ctdluser *is_mailbox, /* Mailbox room? */
5284 int is_binary, /* Is encoding necessary? */
5285 int is_unique, /* Del others of this type? */
5286 unsigned int flags /* Internal save flags */
5289 struct CitContext *CCC = CC;
5290 struct ctdlroom qrbuf;
5291 char roomname[ROOMNAMELEN];
5292 struct CtdlMessage *msg;
5293 char *encoded_message = NULL;
5295 if (is_mailbox != NULL) {
5296 CtdlMailboxName(roomname, sizeof roomname, is_mailbox, req_room);
5299 safestrncpy(roomname, req_room, sizeof(roomname));
5302 MSG_syslog(LOG_DEBUG, "Raw length is %ld\n", (long)raw_length);
5305 encoded_message = malloc((size_t) (((raw_length * 134) / 100) + 4096 ) );
5308 encoded_message = malloc((size_t)(raw_length + 4096));
5311 sprintf(encoded_message, "Content-type: %s\n", content_type);
5314 sprintf(&encoded_message[strlen(encoded_message)],
5315 "Content-transfer-encoding: base64\n\n"
5319 sprintf(&encoded_message[strlen(encoded_message)],
5320 "Content-transfer-encoding: 7bit\n\n"
5326 &encoded_message[strlen(encoded_message)],
5334 &encoded_message[strlen(encoded_message)],
5340 MSGM_syslog(LOG_DEBUG, "Allocating\n");
5341 msg = malloc(sizeof(struct CtdlMessage));
5342 memset(msg, 0, sizeof(struct CtdlMessage));
5343 msg->cm_magic = CTDLMESSAGE_MAGIC;
5344 msg->cm_anon_type = MES_NORMAL;
5345 msg->cm_format_type = 4;
5346 msg->cm_fields['A'] = strdup(CCC->user.fullname);
5347 msg->cm_fields['O'] = strdup(req_room);
5348 msg->cm_fields['N'] = strdup(config.c_nodename);
5349 msg->cm_fields['H'] = strdup(config.c_humannode);
5350 msg->cm_flags = flags;
5352 msg->cm_fields['M'] = encoded_message;
5354 /* Create the requested room if we have to. */
5355 if (CtdlGetRoom(&qrbuf, roomname) != 0) {
5356 CtdlCreateRoom(roomname,
5357 ( (is_mailbox != NULL) ? 5 : 3 ),
5358 "", 0, 1, 0, VIEW_BBS);
5360 /* If the caller specified this object as unique, delete all
5361 * other objects of this type that are currently in the room.
5364 MSG_syslog(LOG_DEBUG, "Deleted %d other msgs of this type\n",
5365 CtdlDeleteMessages(roomname, NULL, 0, content_type)
5368 /* Now write the data */
5369 CtdlSubmitMsg(msg, NULL, roomname, 0);
5370 CtdlFreeMessage(msg);
5378 void CtdlGetSysConfigBackend(long msgnum, void *userdata) {
5379 config_msgnum = msgnum;
5383 char *CtdlGetSysConfig(char *sysconfname) {
5384 char hold_rm[ROOMNAMELEN];
5387 struct CtdlMessage *msg;
5390 strcpy(hold_rm, CC->room.QRname);
5391 if (CtdlGetRoom(&CC->room, SYSCONFIGROOM) != 0) {
5392 CtdlGetRoom(&CC->room, hold_rm);
5397 /* We want the last (and probably only) config in this room */
5398 begin_critical_section(S_CONFIG);
5399 config_msgnum = (-1L);
5400 CtdlForEachMessage(MSGS_LAST, 1, NULL, sysconfname, NULL,
5401 CtdlGetSysConfigBackend, NULL);
5402 msgnum = config_msgnum;
5403 end_critical_section(S_CONFIG);
5409 msg = CtdlFetchMessage(msgnum, 1);
5411 conf = strdup(msg->cm_fields['M']);
5412 CtdlFreeMessage(msg);
5419 CtdlGetRoom(&CC->room, hold_rm);
5421 if (conf != NULL) do {
5422 extract_token(buf, conf, 0, '\n', sizeof buf);
5423 strcpy(conf, &conf[strlen(buf)+1]);
5424 } while ( (!IsEmptyStr(conf)) && (!IsEmptyStr(buf)) );
5430 void CtdlPutSysConfig(char *sysconfname, char *sysconfdata) {
5431 CtdlWriteObject(SYSCONFIGROOM, sysconfname, sysconfdata, (strlen(sysconfdata)+1), NULL, 0, 1, 0);
5436 * Determine whether a given Internet address belongs to the current user
5438 int CtdlIsMe(char *addr, int addr_buf_len)
5440 struct recptypes *recp;
5443 recp = validate_recipients(addr, NULL, 0);
5444 if (recp == NULL) return(0);
5446 if (recp->num_local == 0) {
5447 free_recipients(recp);
5451 for (i=0; i<recp->num_local; ++i) {
5452 extract_token(addr, recp->recp_local, i, '|', addr_buf_len);
5453 if (!strcasecmp(addr, CC->user.fullname)) {
5454 free_recipients(recp);
5459 free_recipients(recp);
5465 * Citadel protocol command to do the same
5467 void cmd_isme(char *argbuf) {
5470 if (CtdlAccessCheck(ac_logged_in)) return;
5471 extract_token(addr, argbuf, 0, '|', sizeof addr);
5473 if (CtdlIsMe(addr, sizeof addr)) {
5474 cprintf("%d %s\n", CIT_OK, addr);
5477 cprintf("%d Not you.\n", ERROR + ILLEGAL_VALUE);
5483 /*****************************************************************************/
5484 /* MODULE INITIALIZATION STUFF */
5485 /*****************************************************************************/
5486 void SetMessageDebugEnabled(const int n)
5488 MessageDebugEnabled = n;
5490 CTDL_MODULE_INIT(msgbase)
5493 CtdlRegisterDebugFlagHook(HKEY("messages"), SetMessageDebugEnabled, &MessageDebugEnabled);
5495 CtdlRegisterProtoHook(cmd_msgs, "MSGS", "Output a list of messages in the current room");
5496 CtdlRegisterProtoHook(cmd_msg0, "MSG0", "Output a message in plain text format");
5497 CtdlRegisterProtoHook(cmd_msg2, "MSG2", "Output a message in RFC822 format");
5498 CtdlRegisterProtoHook(cmd_msg3, "MSG3", "Output a message in raw format (deprecated)");
5499 CtdlRegisterProtoHook(cmd_msg4, "MSG4", "Output a message in the client's preferred format");
5500 CtdlRegisterProtoHook(cmd_msgp, "MSGP", "Select preferred format for MSG4 output");
5501 CtdlRegisterProtoHook(cmd_opna, "OPNA", "Open an attachment for download");
5502 CtdlRegisterProtoHook(cmd_dlat, "DLAT", "Download an attachment");
5503 CtdlRegisterProtoHook(cmd_ent0, "ENT0", "Enter a message");
5504 CtdlRegisterProtoHook(cmd_dele, "DELE", "Delete a message");
5505 CtdlRegisterProtoHook(cmd_move, "MOVE", "Move or copy a message to another room");
5506 CtdlRegisterProtoHook(cmd_isme, "ISME", "Determine whether an email address belongs to a user");
5509 /* return our Subversion id for the Log */