4 * Implements the message store.
14 #if TIME_WITH_SYS_TIME
15 # include <sys/time.h>
19 # include <sys/time.h>
32 #include <sys/types.h>
34 #include <libcitadel.h>
37 #include "serv_extensions.h"
41 #include "sysdep_decls.h"
42 #include "citserver.h"
49 #include "internet_addressing.h"
50 #include "euidindex.h"
51 #include "journaling.h"
52 #include "citadel_dirs.h"
53 #include "clientsocket.h"
54 #include "serv_network.h"
58 struct addresses_to_be_filed *atbf = NULL;
60 /* This temp file holds the queue of operations for AdjRefCount() */
61 static FILE *arcfp = NULL;
64 * This really belongs in serv_network.c, but I don't know how to export
65 * symbols between modules.
67 struct FilterList *filterlist = NULL;
71 * These are the four-character field headers we use when outputting
72 * messages in Citadel format (as opposed to RFC822 format).
75 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
76 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
77 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
78 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
79 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
80 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
81 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
82 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
111 * This function is self explanatory.
112 * (What can I say, I'm in a weird mood today...)
114 void remove_any_whitespace_to_the_left_or_right_of_at_symbol(char *name)
118 for (i = 0; i < strlen(name); ++i) {
119 if (name[i] == '@') {
120 while (isspace(name[i - 1]) && i > 0) {
121 strcpy(&name[i - 1], &name[i]);
124 while (isspace(name[i + 1])) {
125 strcpy(&name[i + 1], &name[i + 2]);
133 * Aliasing for network mail.
134 * (Error messages have been commented out, because this is a server.)
136 int alias(char *name)
137 { /* process alias and routing info for mail */
140 char aaa[SIZ], bbb[SIZ];
141 char *ignetcfg = NULL;
142 char *ignetmap = NULL;
148 char original_name[256];
149 safestrncpy(original_name, name, sizeof original_name);
152 remove_any_whitespace_to_the_left_or_right_of_at_symbol(name);
153 stripallbut(name, '<', '>');
155 fp = fopen(file_mail_aliases, "r");
157 fp = fopen("/dev/null", "r");
164 while (fgets(aaa, sizeof aaa, fp) != NULL) {
165 while (isspace(name[0]))
166 strcpy(name, &name[1]);
167 aaa[strlen(aaa) - 1] = 0;
169 for (a = 0; a < strlen(aaa); ++a) {
171 strcpy(bbb, &aaa[a + 1]);
175 if (!strcasecmp(name, aaa))
180 /* Hit the Global Address Book */
181 if (CtdlDirectoryLookup(aaa, name, sizeof aaa) == 0) {
185 if (strcasecmp(original_name, name)) {
186 CtdlLogPrintf(CTDL_INFO, "%s is being forwarded to %s\n", original_name, name);
189 /* Change "user @ xxx" to "user" if xxx is an alias for this host */
190 for (a=0; a<strlen(name); ++a) {
191 if (name[a] == '@') {
192 if (CtdlHostAlias(&name[a+1]) == hostalias_localhost) {
194 CtdlLogPrintf(CTDL_INFO, "Changed to <%s>\n", name);
199 /* determine local or remote type, see citadel.h */
200 at = haschar(name, '@');
201 if (at == 0) return(MES_LOCAL); /* no @'s - local address */
202 if (at > 1) return(MES_ERROR); /* >1 @'s - invalid address */
203 remove_any_whitespace_to_the_left_or_right_of_at_symbol(name);
205 /* figure out the delivery mode */
206 extract_token(node, name, 1, '@', sizeof node);
208 /* If there are one or more dots in the nodename, we assume that it
209 * is an FQDN and will attempt SMTP delivery to the Internet.
211 if (haschar(node, '.') > 0) {
212 return(MES_INTERNET);
215 /* Otherwise we look in the IGnet maps for a valid Citadel node.
216 * Try directly-connected nodes first...
218 ignetcfg = CtdlGetSysConfig(IGNETCFG);
219 for (i=0; i<num_tokens(ignetcfg, '\n'); ++i) {
220 extract_token(buf, ignetcfg, i, '\n', sizeof buf);
221 extract_token(testnode, buf, 0, '|', sizeof testnode);
222 if (!strcasecmp(node, testnode)) {
230 * Then try nodes that are two or more hops away.
232 ignetmap = CtdlGetSysConfig(IGNETMAP);
233 for (i=0; i<num_tokens(ignetmap, '\n'); ++i) {
234 extract_token(buf, ignetmap, i, '\n', sizeof buf);
235 extract_token(testnode, buf, 0, '|', sizeof testnode);
236 if (!strcasecmp(node, testnode)) {
243 /* If we get to this point it's an invalid node name */
249 * Back end for the MSGS command: output message number only.
251 void simple_listing(long msgnum, void *userdata)
253 cprintf("%ld\n", msgnum);
259 * Back end for the MSGS command: output header summary.
261 void headers_listing(long msgnum, void *userdata)
263 struct CtdlMessage *msg;
265 msg = CtdlFetchMessage(msgnum, 0);
267 cprintf("%ld|0|||||\n", msgnum);
271 cprintf("%ld|%s|%s|%s|%s|%s|\n",
273 (msg->cm_fields['T'] ? msg->cm_fields['T'] : "0"),
274 (msg->cm_fields['A'] ? msg->cm_fields['A'] : ""),
275 (msg->cm_fields['N'] ? msg->cm_fields['N'] : ""),
276 (msg->cm_fields['F'] ? msg->cm_fields['F'] : ""),
277 (msg->cm_fields['U'] ? msg->cm_fields['U'] : "")
279 CtdlFreeMessage(msg);
284 /* Determine if a given message matches the fields in a message template.
285 * Return 0 for a successful match.
287 int CtdlMsgCmp(struct CtdlMessage *msg, struct CtdlMessage *template) {
290 /* If there aren't any fields in the template, all messages will
293 if (template == NULL) return(0);
295 /* Null messages are bogus. */
296 if (msg == NULL) return(1);
298 for (i='A'; i<='Z'; ++i) {
299 if (template->cm_fields[i] != NULL) {
300 if (msg->cm_fields[i] == NULL) {
303 if (strcasecmp(msg->cm_fields[i],
304 template->cm_fields[i])) return 1;
308 /* All compares succeeded: we have a match! */
315 * Retrieve the "seen" message list for the current room.
317 void CtdlGetSeen(char *buf, int which_set) {
320 /* Learn about the user and room in question */
321 CtdlGetRelationship(&vbuf, &CC->user, &CC->room);
323 if (which_set == ctdlsetseen_seen)
324 safestrncpy(buf, vbuf.v_seen, SIZ);
325 if (which_set == ctdlsetseen_answered)
326 safestrncpy(buf, vbuf.v_answered, SIZ);
332 * Manipulate the "seen msgs" string (or other message set strings)
334 void CtdlSetSeen(long *target_msgnums, int num_target_msgnums,
335 int target_setting, int which_set,
336 struct ctdluser *which_user, struct ctdlroom *which_room) {
337 struct cdbdata *cdbfr;
349 char *is_set; /* actually an array of booleans */
352 char setstr[SIZ], lostr[SIZ], histr[SIZ];
355 /* Don't bother doing *anything* if we were passed a list of zero messages */
356 if (num_target_msgnums < 1) {
360 /* If no room was specified, we go with the current room. */
362 which_room = &CC->room;
365 /* If no user was specified, we go with the current user. */
367 which_user = &CC->user;
370 CtdlLogPrintf(CTDL_DEBUG, "
\e[31mCtdlSetSeen(%d msgs starting with %ld, %s, %d) in <%s>
\e[0m\n",
371 num_target_msgnums, target_msgnums[0],
372 (target_setting ? "SET" : "CLEAR"),
376 /* Learn about the user and room in question */
377 CtdlGetRelationship(&vbuf, which_user, which_room);
379 /* Load the message list */
380 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long));
382 msglist = (long *) cdbfr->ptr;
383 cdbfr->ptr = NULL; /* CtdlSetSeen() now owns this memory */
384 num_msgs = cdbfr->len / sizeof(long);
387 return; /* No messages at all? No further action. */
390 is_set = malloc(num_msgs * sizeof(char));
391 memset(is_set, 0, (num_msgs * sizeof(char)) );
393 /* Decide which message set we're manipulating */
395 case ctdlsetseen_seen:
396 safestrncpy(vset, vbuf.v_seen, sizeof vset);
398 case ctdlsetseen_answered:
399 safestrncpy(vset, vbuf.v_answered, sizeof vset);
403 CtdlLogPrintf(CTDL_DEBUG, "
\e[31mbefore optimize: %s
\e[0m\n", vset);
405 /* Translate the existing sequence set into an array of booleans */
406 num_sets = num_tokens(vset, ',');
407 for (s=0; s<num_sets; ++s) {
408 extract_token(setstr, vset, s, ',', sizeof setstr);
410 extract_token(lostr, setstr, 0, ':', sizeof lostr);
411 if (num_tokens(setstr, ':') >= 2) {
412 extract_token(histr, setstr, 1, ':', sizeof histr);
415 strcpy(histr, lostr);
418 if (!strcmp(histr, "*")) {
425 for (i = 0; i < num_msgs; ++i) {
426 if ((msglist[i] >= lo) && (msglist[i] <= hi)) {
432 CtdlLogPrintf(CTDL_DEBUG, "
\e[31m after optimize: %s
\e[0m\n", vset);
434 /* Now translate the array of booleans back into a sequence set */
438 was_seen = ((which_set == ctdlsetseen_seen) ? 1 : 0);
440 for (i=0; i<num_msgs; ++i) {
442 is_seen = is_set[i]; /* Default to existing setting */
444 for (k=0; k<num_target_msgnums; ++k) {
445 if (msglist[i] == target_msgnums[k]) {
446 is_seen = target_setting;
451 if (lo < 0L) lo = msglist[i];
455 if ( ((is_seen == 0) && (was_seen == 1))
456 || ((is_seen == 1) && (i == num_msgs-1)) ) {
458 /* begin trim-o-matic code */
461 while ( (strlen(vset) + 20) > sizeof vset) {
462 remove_token(vset, 0, ',');
464 if (j--) break; /* loop no more than 9 times */
466 if ( (trimming) && (which_set == ctdlsetseen_seen) ) {
470 snprintf(lostr, sizeof lostr,
471 "1:%ld,%s", t, vset);
472 safestrncpy(vset, lostr, sizeof vset);
475 CtdlLogPrintf(CTDL_DEBUG, "
\e32m ** TRIMMING **
\e[0m\n");
477 /* end trim-o-matic code */
484 if ((lo == -1) && (hi == -1)) {
485 /* bogus pair, do nothing */
488 snprintf(&vset[tmp], (sizeof vset) - tmp, "%ld", lo);
491 snprintf(&vset[tmp], (sizeof vset) - tmp, "%ld:%ld", lo, hi);
499 /* Decide which message set we're manipulating */
501 case ctdlsetseen_seen:
502 safestrncpy(vbuf.v_seen, vset, sizeof vbuf.v_seen);
504 case ctdlsetseen_answered:
505 safestrncpy(vbuf.v_answered, vset, sizeof vbuf.v_answered);
510 CtdlLogPrintf(CTDL_DEBUG, "
\e[31m after update: %s
\e[0m\n", vset);
512 CtdlSetRelationship(&vbuf, which_user, which_room);
517 * API function to perform an operation for each qualifying message in the
518 * current room. (Returns the number of messages processed.)
520 int CtdlForEachMessage(int mode, long ref, char *search_string,
522 struct CtdlMessage *compare,
523 void (*CallBack) (long, void *),
529 struct cdbdata *cdbfr;
530 long *msglist = NULL;
532 int num_processed = 0;
535 struct CtdlMessage *msg = NULL;
538 int printed_lastold = 0;
539 int num_search_msgs = 0;
540 long *search_msgs = NULL;
542 int need_to_free_re = 0;
545 if (content_type) if (!IsEmptyStr(content_type)) {
546 regcomp(&re, content_type, 0);
550 /* Learn about the user and room in question */
551 getuser(&CC->user, CC->curr_user);
552 CtdlGetRelationship(&vbuf, &CC->user, &CC->room);
554 /* Load the message list */
555 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long));
557 msglist = (long *) cdbfr->ptr;
558 num_msgs = cdbfr->len / sizeof(long);
560 if (need_to_free_re) regfree(&re);
561 return 0; /* No messages at all? No further action. */
566 * Now begin the traversal.
568 if (num_msgs > 0) for (a = 0; a < num_msgs; ++a) {
570 /* If the caller is looking for a specific MIME type, filter
571 * out all messages which are not of the type requested.
573 if (content_type != NULL) if (!IsEmptyStr(content_type)) {
575 /* This call to GetMetaData() sits inside this loop
576 * so that we only do the extra database read per msg
577 * if we need to. Doing the extra read all the time
578 * really kills the server. If we ever need to use
579 * metadata for another search criterion, we need to
580 * move the read somewhere else -- but still be smart
581 * enough to only do the read if the caller has
582 * specified something that will need it.
584 GetMetaData(&smi, msglist[a]);
586 /* if (strcasecmp(smi.meta_content_type, content_type)) { old non-regex way */
587 if (regexec(&re, smi.meta_content_type, 1, &pm, 0) != 0) {
593 num_msgs = sort_msglist(msglist, num_msgs);
595 /* If a template was supplied, filter out the messages which
596 * don't match. (This could induce some delays!)
599 if (compare != NULL) {
600 for (a = 0; a < num_msgs; ++a) {
601 msg = CtdlFetchMessage(msglist[a], 1);
603 if (CtdlMsgCmp(msg, compare)) {
606 CtdlFreeMessage(msg);
612 /* If a search string was specified, get a message list from
613 * the full text index and remove messages which aren't on both
617 * Since the lists are sorted and strictly ascending, and the
618 * output list is guaranteed to be shorter than or equal to the
619 * input list, we overwrite the bottom of the input list. This
620 * eliminates the need to memmove big chunks of the list over and
623 if ( (num_msgs > 0) && (mode == MSGS_SEARCH) && (search_string) ) {
625 /* Call search module via hook mechanism.
626 * NULL means use any search function available.
627 * otherwise replace with a char * to name of search routine
629 CtdlModuleDoSearch(&num_search_msgs, &search_msgs, search_string, "fulltext");
631 if (num_search_msgs > 0) {
635 orig_num_msgs = num_msgs;
637 for (i=0; i<orig_num_msgs; ++i) {
638 for (j=0; j<num_search_msgs; ++j) {
639 if (msglist[i] == search_msgs[j]) {
640 msglist[num_msgs++] = msglist[i];
646 num_msgs = 0; /* No messages qualify */
648 if (search_msgs != NULL) free(search_msgs);
650 /* Now that we've purged messages which don't contain the search
651 * string, treat a MSGS_SEARCH just like a MSGS_ALL from this
658 * Now iterate through the message list, according to the
659 * criteria supplied by the caller.
662 for (a = 0; a < num_msgs; ++a) {
663 thismsg = msglist[a];
664 if (mode == MSGS_ALL) {
668 is_seen = is_msg_in_sequence_set(
669 vbuf.v_seen, thismsg);
670 if (is_seen) lastold = thismsg;
676 || ((mode == MSGS_OLD) && (is_seen))
677 || ((mode == MSGS_NEW) && (!is_seen))
678 || ((mode == MSGS_LAST) && (a >= (num_msgs - ref)))
679 || ((mode == MSGS_FIRST) && (a < ref))
680 || ((mode == MSGS_GT) && (thismsg > ref))
681 || ((mode == MSGS_EQ) && (thismsg == ref))
684 if ((mode == MSGS_NEW) && (CC->user.flags & US_LASTOLD) && (lastold > 0L) && (printed_lastold == 0) && (!is_seen)) {
686 CallBack(lastold, userdata);
690 if (CallBack) CallBack(thismsg, userdata);
694 cdb_free(cdbfr); /* Clean up */
695 if (need_to_free_re) regfree(&re);
696 return num_processed;
702 * cmd_msgs() - get list of message #'s in this room
703 * implements the MSGS server command using CtdlForEachMessage()
705 void cmd_msgs(char *cmdbuf)
714 int with_template = 0;
715 struct CtdlMessage *template = NULL;
716 int with_headers = 0;
717 char search_string[1024];
719 extract_token(which, cmdbuf, 0, '|', sizeof which);
720 cm_ref = extract_int(cmdbuf, 1);
721 extract_token(search_string, cmdbuf, 1, '|', sizeof search_string);
722 with_template = extract_int(cmdbuf, 2);
723 with_headers = extract_int(cmdbuf, 3);
726 if (!strncasecmp(which, "OLD", 3))
728 else if (!strncasecmp(which, "NEW", 3))
730 else if (!strncasecmp(which, "FIRST", 5))
732 else if (!strncasecmp(which, "LAST", 4))
734 else if (!strncasecmp(which, "GT", 2))
736 else if (!strncasecmp(which, "SEARCH", 6))
741 if ((!(CC->logged_in)) && (!(CC->internal_pgm))) {
742 cprintf("%d not logged in\n", ERROR + NOT_LOGGED_IN);
746 if ( (mode == MSGS_SEARCH) && (!config.c_enable_fulltext) ) {
747 cprintf("%d Full text index is not enabled on this server.\n",
748 ERROR + CMD_NOT_SUPPORTED);
754 cprintf("%d Send template then receive message list\n",
756 template = (struct CtdlMessage *)
757 malloc(sizeof(struct CtdlMessage));
758 memset(template, 0, sizeof(struct CtdlMessage));
759 template->cm_magic = CTDLMESSAGE_MAGIC;
760 template->cm_anon_type = MES_NORMAL;
762 while(client_getln(buf, sizeof buf) >= 0 && strcmp(buf,"000")) {
763 extract_token(tfield, buf, 0, '|', sizeof tfield);
764 extract_token(tvalue, buf, 1, '|', sizeof tvalue);
765 for (i='A'; i<='Z'; ++i) if (msgkeys[i]!=NULL) {
766 if (!strcasecmp(tfield, msgkeys[i])) {
767 template->cm_fields[i] =
775 cprintf("%d \n", LISTING_FOLLOWS);
778 CtdlForEachMessage(mode,
779 ( (mode == MSGS_SEARCH) ? 0 : cm_ref ),
780 ( (mode == MSGS_SEARCH) ? search_string : NULL ),
783 (with_headers ? headers_listing : simple_listing),
786 if (template != NULL) CtdlFreeMessage(template);
794 * help_subst() - support routine for help file viewer
796 void help_subst(char *strbuf, char *source, char *dest)
801 while (p = pattern2(strbuf, source), (p >= 0)) {
802 strcpy(workbuf, &strbuf[p + strlen(source)]);
803 strcpy(&strbuf[p], dest);
804 strcat(strbuf, workbuf);
809 void do_help_subst(char *buffer)
813 help_subst(buffer, "^nodename", config.c_nodename);
814 help_subst(buffer, "^humannode", config.c_humannode);
815 help_subst(buffer, "^fqdn", config.c_fqdn);
816 help_subst(buffer, "^username", CC->user.fullname);
817 snprintf(buf2, sizeof buf2, "%ld", CC->user.usernum);
818 help_subst(buffer, "^usernum", buf2);
819 help_subst(buffer, "^sysadm", config.c_sysadm);
820 help_subst(buffer, "^variantname", CITADEL);
821 snprintf(buf2, sizeof buf2, "%d", config.c_maxsessions);
822 help_subst(buffer, "^maxsessions", buf2);
823 help_subst(buffer, "^bbsdir", ctdl_message_dir);
829 * memfmout() - Citadel text formatter and paginator.
830 * Although the original purpose of this routine was to format
831 * text to the reader's screen width, all we're really using it
832 * for here is to format text out to 80 columns before sending it
833 * to the client. The client software may reformat it again.
836 char *mptr, /* where are we going to get our text from? */
837 char subst, /* nonzero if we should do substitutions */
838 char *nl) /* string to terminate lines with */
846 static int width = 80;
851 c = 1; /* c is the current pos */
855 while (ch = *mptr, ((ch != 0) && (strlen(buffer) < 126))) {
857 buffer[strlen(buffer) + 1] = 0;
858 buffer[strlen(buffer)] = ch;
861 if (buffer[0] == '^')
862 do_help_subst(buffer);
864 buffer[strlen(buffer) + 1] = 0;
866 strcpy(buffer, &buffer[1]);
874 if (((ch == 13) || (ch == 10)) && (old != 13) && (old != 10)) {
877 if (((old == 13) || (old == 10)) && (isspace(real))) {
882 if (((strlen(aaa) + c) > (width - 5)) && (strlen(aaa) > (width - 5))) {
883 cprintf("%s%s", nl, aaa);
892 if ((strlen(aaa) + c) > (width - 5)) {
901 if ((ch == 13) || (ch == 10)) {
902 cprintf("%s%s", aaa, nl);
909 cprintf("%s%s", aaa, nl);
915 * Callback function for mime parser that simply lists the part
917 void list_this_part(char *name, char *filename, char *partnum, char *disp,
918 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
923 ma = (struct ma_info *)cbuserdata;
924 if (ma->is_ma == 0) {
925 cprintf("part=%s|%s|%s|%s|%s|%ld\n",
926 name, filename, partnum, disp, cbtype, (long)length);
931 * Callback function for multipart prefix
933 void list_this_pref(char *name, char *filename, char *partnum, char *disp,
934 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
939 ma = (struct ma_info *)cbuserdata;
940 if (!strcasecmp(cbtype, "multipart/alternative")) {
944 if (ma->is_ma == 0) {
945 cprintf("pref=%s|%s\n", partnum, cbtype);
950 * Callback function for multipart sufffix
952 void list_this_suff(char *name, char *filename, char *partnum, char *disp,
953 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
958 ma = (struct ma_info *)cbuserdata;
959 if (ma->is_ma == 0) {
960 cprintf("suff=%s|%s\n", partnum, cbtype);
962 if (!strcasecmp(cbtype, "multipart/alternative")) {
969 * Callback function for mime parser that opens a section for downloading
971 void mime_download(char *name, char *filename, char *partnum, char *disp,
972 void *content, char *cbtype, char *cbcharset, size_t length,
973 char *encoding, void *cbuserdata)
976 /* Silently go away if there's already a download open... */
977 if (CC->download_fp != NULL)
980 /* ...or if this is not the desired section */
981 if (strcasecmp(CC->download_desired_section, partnum))
984 CC->download_fp = tmpfile();
985 if (CC->download_fp == NULL)
988 fwrite(content, length, 1, CC->download_fp);
989 fflush(CC->download_fp);
990 rewind(CC->download_fp);
992 OpenCmdResult(filename, cbtype);
998 * Callback function for mime parser that outputs a section all at once
1000 void mime_spew_section(char *name, char *filename, char *partnum, char *disp,
1001 void *content, char *cbtype, char *cbcharset, size_t length,
1002 char *encoding, void *cbuserdata)
1004 int *found_it = (int *)cbuserdata;
1006 /* ...or if this is not the desired section */
1007 if (strcasecmp(CC->download_desired_section, partnum))
1012 cprintf("%d %d\n", BINARY_FOLLOWS, (int)length);
1013 client_write(content, length);
1019 * Load a message from disk into memory.
1020 * This is used by CtdlOutputMsg() and other fetch functions.
1022 * NOTE: Caller is responsible for freeing the returned CtdlMessage struct
1023 * using the CtdlMessageFree() function.
1025 struct CtdlMessage *CtdlFetchMessage(long msgnum, int with_body)
1027 struct cdbdata *dmsgtext;
1028 struct CtdlMessage *ret = NULL;
1032 cit_uint8_t field_header;
1034 CtdlLogPrintf(CTDL_DEBUG, "CtdlFetchMessage(%ld, %d)\n", msgnum, with_body);
1036 dmsgtext = cdb_fetch(CDB_MSGMAIN, &msgnum, sizeof(long));
1037 if (dmsgtext == NULL) {
1040 mptr = dmsgtext->ptr;
1041 upper_bound = mptr + dmsgtext->len;
1043 /* Parse the three bytes that begin EVERY message on disk.
1044 * The first is always 0xFF, the on-disk magic number.
1045 * The second is the anonymous/public type byte.
1046 * The third is the format type byte (vari, fixed, or MIME).
1050 CtdlLogPrintf(CTDL_ERR, "Message %ld appears to be corrupted.\n", msgnum);
1054 ret = (struct CtdlMessage *) malloc(sizeof(struct CtdlMessage));
1055 memset(ret, 0, sizeof(struct CtdlMessage));
1057 ret->cm_magic = CTDLMESSAGE_MAGIC;
1058 ret->cm_anon_type = *mptr++; /* Anon type byte */
1059 ret->cm_format_type = *mptr++; /* Format type byte */
1062 * The rest is zero or more arbitrary fields. Load them in.
1063 * We're done when we encounter either a zero-length field or
1064 * have just processed the 'M' (message text) field.
1067 if (mptr >= upper_bound) {
1070 field_header = *mptr++;
1071 ret->cm_fields[field_header] = strdup(mptr);
1073 while (*mptr++ != 0); /* advance to next field */
1075 } while ((mptr < upper_bound) && (field_header != 'M'));
1079 /* Always make sure there's something in the msg text field. If
1080 * it's NULL, the message text is most likely stored separately,
1081 * so go ahead and fetch that. Failing that, just set a dummy
1082 * body so other code doesn't barf.
1084 if ( (ret->cm_fields['M'] == NULL) && (with_body) ) {
1085 dmsgtext = cdb_fetch(CDB_BIGMSGS, &msgnum, sizeof(long));
1086 if (dmsgtext != NULL) {
1087 ret->cm_fields['M'] = strdup(dmsgtext->ptr);
1091 if (ret->cm_fields['M'] == NULL) {
1092 ret->cm_fields['M'] = strdup("\r\n\r\n (no text)\r\n");
1095 /* Perform "before read" hooks (aborting if any return nonzero) */
1096 if (PerformMessageHooks(ret, EVT_BEFOREREAD) > 0) {
1097 CtdlFreeMessage(ret);
1106 * Returns 1 if the supplied pointer points to a valid Citadel message.
1107 * If the pointer is NULL or the magic number check fails, returns 0.
1109 int is_valid_message(struct CtdlMessage *msg) {
1112 if ((msg->cm_magic) != CTDLMESSAGE_MAGIC) {
1113 CtdlLogPrintf(CTDL_WARNING, "is_valid_message() -- self-check failed\n");
1121 * 'Destructor' for struct CtdlMessage
1123 void CtdlFreeMessage(struct CtdlMessage *msg)
1127 if (is_valid_message(msg) == 0)
1129 if (msg != NULL) free (msg);
1133 for (i = 0; i < 256; ++i)
1134 if (msg->cm_fields[i] != NULL) {
1135 free(msg->cm_fields[i]);
1138 msg->cm_magic = 0; /* just in case */
1144 * Pre callback function for multipart/alternative
1146 * NOTE: this differs from the standard behavior for a reason. Normally when
1147 * displaying multipart/alternative you want to show the _last_ usable
1148 * format in the message. Here we show the _first_ one, because it's
1149 * usually text/plain. Since this set of functions is designed for text
1150 * output to non-MIME-aware clients, this is the desired behavior.
1153 void fixed_output_pre(char *name, char *filename, char *partnum, char *disp,
1154 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
1159 ma = (struct ma_info *)cbuserdata;
1160 CtdlLogPrintf(CTDL_DEBUG, "fixed_output_pre() type=<%s>\n", cbtype);
1161 if (!strcasecmp(cbtype, "multipart/alternative")) {
1165 if (!strcasecmp(cbtype, "message/rfc822")) {
1171 * Post callback function for multipart/alternative
1173 void fixed_output_post(char *name, char *filename, char *partnum, char *disp,
1174 void *content, char *cbtype, char *cbcharset, size_t length,
1175 char *encoding, void *cbuserdata)
1179 ma = (struct ma_info *)cbuserdata;
1180 CtdlLogPrintf(CTDL_DEBUG, "fixed_output_post() type=<%s>\n", cbtype);
1181 if (!strcasecmp(cbtype, "multipart/alternative")) {
1185 if (!strcasecmp(cbtype, "message/rfc822")) {
1191 * Inline callback function for mime parser that wants to display text
1193 void fixed_output(char *name, char *filename, char *partnum, char *disp,
1194 void *content, char *cbtype, char *cbcharset, size_t length,
1195 char *encoding, void *cbuserdata)
1202 ma = (struct ma_info *)cbuserdata;
1204 CtdlLogPrintf(CTDL_DEBUG,
1205 "fixed_output() part %s: %s (%s) (%ld bytes)\n",
1206 partnum, filename, cbtype, (long)length);
1209 * If we're in the middle of a multipart/alternative scope and
1210 * we've already printed another section, skip this one.
1212 if ( (ma->is_ma) && (ma->did_print) ) {
1213 CtdlLogPrintf(CTDL_DEBUG, "Skipping part %s (%s)\n", partnum, cbtype);
1218 if ( (!strcasecmp(cbtype, "text/plain"))
1219 || (IsEmptyStr(cbtype)) ) {
1222 client_write(wptr, length);
1223 if (wptr[length-1] != '\n') {
1230 if (!strcasecmp(cbtype, "text/html")) {
1231 ptr = html_to_ascii(content, length, 80, 0);
1233 client_write(ptr, wlen);
1234 if (ptr[wlen-1] != '\n') {
1241 if (ma->use_fo_hooks) {
1242 if (PerformFixedOutputHooks(cbtype, content, length)) {
1243 /* above function returns nonzero if it handled the part */
1248 if (strncasecmp(cbtype, "multipart/", 10)) {
1249 cprintf("Part %s: %s (%s) (%ld bytes)\r\n",
1250 partnum, filename, cbtype, (long)length);
1256 * The client is elegant and sophisticated and wants to be choosy about
1257 * MIME content types, so figure out which multipart/alternative part
1258 * we're going to send.
1260 * We use a system of weights. When we find a part that matches one of the
1261 * MIME types we've declared as preferential, we can store it in ma->chosen_part
1262 * and then set ma->chosen_pref to that MIME type's position in our preference
1263 * list. If we then hit another match, we only replace the first match if
1264 * the preference value is lower.
1266 void choose_preferred(char *name, char *filename, char *partnum, char *disp,
1267 void *content, char *cbtype, char *cbcharset, size_t length,
1268 char *encoding, void *cbuserdata)
1274 ma = (struct ma_info *)cbuserdata;
1276 // NOTE: REMOVING THIS CONDITIONAL FIXES BUG 220
1277 // http://bugzilla.citadel.org/show_bug.cgi?id=220
1278 // I don't know if there are any side effects! Please TEST TEST TEST
1279 //if (ma->is_ma > 0) {
1281 for (i=0; i<num_tokens(CC->preferred_formats, '|'); ++i) {
1282 extract_token(buf, CC->preferred_formats, i, '|', sizeof buf);
1283 if ( (!strcasecmp(buf, cbtype)) && (!ma->freeze) ) {
1284 if (i < ma->chosen_pref) {
1285 CtdlLogPrintf(CTDL_DEBUG, "Setting chosen part: <%s>\n", partnum);
1286 safestrncpy(ma->chosen_part, partnum, sizeof ma->chosen_part);
1287 ma->chosen_pref = i;
1294 * Now that we've chosen our preferred part, output it.
1296 void output_preferred(char *name, char *filename, char *partnum, char *disp,
1297 void *content, char *cbtype, char *cbcharset, size_t length,
1298 char *encoding, void *cbuserdata)
1302 int add_newline = 0;
1306 ma = (struct ma_info *)cbuserdata;
1308 /* This is not the MIME part you're looking for... */
1309 if (strcasecmp(partnum, ma->chosen_part)) return;
1311 /* If the content-type of this part is in our preferred formats
1312 * list, we can simply output it verbatim.
1314 for (i=0; i<num_tokens(CC->preferred_formats, '|'); ++i) {
1315 extract_token(buf, CC->preferred_formats, i, '|', sizeof buf);
1316 if (!strcasecmp(buf, cbtype)) {
1317 /* Yeah! Go! W00t!! */
1319 text_content = (char *)content;
1320 if (text_content[length-1] != '\n') {
1323 cprintf("Content-type: %s", cbtype);
1324 if (!IsEmptyStr(cbcharset)) {
1325 cprintf("; charset=%s", cbcharset);
1327 cprintf("\nContent-length: %d\n",
1328 (int)(length + add_newline) );
1329 if (!IsEmptyStr(encoding)) {
1330 cprintf("Content-transfer-encoding: %s\n", encoding);
1333 cprintf("Content-transfer-encoding: 7bit\n");
1335 cprintf("X-Citadel-MSG4-Partnum: %s\n", partnum);
1337 client_write(content, length);
1338 if (add_newline) cprintf("\n");
1343 /* No translations required or possible: output as text/plain */
1344 cprintf("Content-type: text/plain\n\n");
1345 fixed_output(name, filename, partnum, disp, content, cbtype, cbcharset,
1346 length, encoding, cbuserdata);
1351 char desired_section[64];
1358 * Callback function for
1360 void extract_encapsulated_message(char *name, char *filename, char *partnum, char *disp,
1361 void *content, char *cbtype, char *cbcharset, size_t length,
1362 char *encoding, void *cbuserdata)
1364 struct encapmsg *encap;
1366 encap = (struct encapmsg *)cbuserdata;
1368 /* Only proceed if this is the desired section... */
1369 if (!strcasecmp(encap->desired_section, partnum)) {
1370 encap->msglen = length;
1371 encap->msg = malloc(length + 2);
1372 memcpy(encap->msg, content, length);
1382 * Get a message off disk. (returns om_* values found in msgbase.h)
1385 int CtdlOutputMsg(long msg_num, /* message number (local) to fetch */
1386 int mode, /* how would you like that message? */
1387 int headers_only, /* eschew the message body? */
1388 int do_proto, /* do Citadel protocol responses? */
1389 int crlf, /* Use CRLF newlines instead of LF? */
1390 char *section, /* NULL or a message/rfc822 section */
1391 int flags /* should the bessage be exported clean? */
1393 struct CtdlMessage *TheMessage = NULL;
1394 int retcode = om_no_such_msg;
1395 struct encapmsg encap;
1397 CtdlLogPrintf(CTDL_DEBUG, "CtdlOutputMsg() msgnum=%ld, mode=%d, section=%s\n",
1399 (section ? section : "<>")
1402 if ((!(CC->logged_in)) && (!(CC->internal_pgm))) {
1403 if (do_proto) cprintf("%d Not logged in.\n",
1404 ERROR + NOT_LOGGED_IN);
1405 return(om_not_logged_in);
1408 /* FIXME: check message id against msglist for this room */
1411 * Fetch the message from disk. If we're in any sort of headers
1412 * only mode, request that we don't even bother loading the body
1415 if ( (headers_only == HEADERS_FAST) || (headers_only == HEADERS_ONLY) ) {
1416 TheMessage = CtdlFetchMessage(msg_num, 0);
1419 TheMessage = CtdlFetchMessage(msg_num, 1);
1422 if (TheMessage == NULL) {
1423 if (do_proto) cprintf("%d Can't locate msg %ld on disk\n",
1424 ERROR + MESSAGE_NOT_FOUND, msg_num);
1425 return(om_no_such_msg);
1428 /* Here is the weird form of this command, to process only an
1429 * encapsulated message/rfc822 section.
1431 if (section) if (!IsEmptyStr(section)) if (strcmp(section, "0")) {
1432 memset(&encap, 0, sizeof encap);
1433 safestrncpy(encap.desired_section, section, sizeof encap.desired_section);
1434 mime_parser(TheMessage->cm_fields['M'],
1436 *extract_encapsulated_message,
1437 NULL, NULL, (void *)&encap, 0
1439 CtdlFreeMessage(TheMessage);
1443 encap.msg[encap.msglen] = 0;
1444 TheMessage = convert_internet_message(encap.msg);
1445 encap.msg = NULL; /* no free() here, TheMessage owns it now */
1447 /* Now we let it fall through to the bottom of this
1448 * function, because TheMessage now contains the
1449 * encapsulated message instead of the top-level
1450 * message. Isn't that neat?
1455 if (do_proto) cprintf("%d msg %ld has no part %s\n",
1456 ERROR + MESSAGE_NOT_FOUND, msg_num, section);
1457 retcode = om_no_such_msg;
1462 /* Ok, output the message now */
1463 retcode = CtdlOutputPreLoadedMsg(TheMessage, mode, headers_only, do_proto, crlf, flags);
1464 CtdlFreeMessage(TheMessage);
1470 char *qp_encode_email_addrs(char *source)
1472 char user[256], node[256], name[256];
1473 const char headerStr[] = "=?UTF-8?Q?";
1477 int need_to_encode = 0;
1483 long nAddrPtrMax = 50;
1488 if (source == NULL) return source;
1489 if (IsEmptyStr(source)) return source;
1491 AddrPtr = malloc (sizeof (long) * nAddrPtrMax);
1492 AddrUtf8 = malloc (sizeof (long) * nAddrPtrMax);
1493 memset(AddrUtf8, 0, sizeof (long) * nAddrPtrMax);
1496 while (!IsEmptyStr (&source[i])) {
1497 if (nColons >= nAddrPtrMax){
1500 ptr = (long *) malloc(sizeof (long) * nAddrPtrMax * 2);
1501 memcpy (ptr, AddrPtr, sizeof (long) * nAddrPtrMax);
1502 free (AddrPtr), AddrPtr = ptr;
1504 ptr = (long *) malloc(sizeof (long) * nAddrPtrMax * 2);
1505 memset(&ptr[nAddrPtrMax], 0,
1506 sizeof (long) * nAddrPtrMax);
1508 memcpy (ptr, AddrUtf8, sizeof (long) * nAddrPtrMax);
1509 free (AddrUtf8), AddrUtf8 = ptr;
1512 if (((unsigned char) source[i] < 32) ||
1513 ((unsigned char) source[i] > 126)) {
1515 AddrUtf8[nColons] = 1;
1517 if (source[i] == '"')
1518 InQuotes = !InQuotes;
1519 if (!InQuotes && source[i] == ',') {
1520 AddrPtr[nColons] = i;
1525 if (need_to_encode == 0) {
1532 EncodedMaxLen = nColons * (sizeof(headerStr) + 3) + SourceLen * 3;
1533 Encoded = (char*) malloc (EncodedMaxLen);
1535 for (i = 0; i < nColons; i++)
1536 source[AddrPtr[i]++] = '\0';
1540 for (i = 0; i < nColons && nPtr != NULL; i++) {
1541 nmax = EncodedMaxLen - (nPtr - Encoded);
1543 process_rfc822_addr(&source[AddrPtr[i]],
1547 /* TODO: libIDN here ! */
1548 if (IsEmptyStr(name)) {
1549 n = snprintf(nPtr, nmax,
1550 (i==0)?"%s@%s" : ",%s@%s",
1554 EncodedName = rfc2047encode(name, strlen(name));
1555 n = snprintf(nPtr, nmax,
1556 (i==0)?"%s <%s@%s>" : ",%s <%s@%s>",
1557 EncodedName, user, node);
1562 n = snprintf(nPtr, nmax,
1563 (i==0)?"%s" : ",%s",
1564 &source[AddrPtr[i]]);
1570 ptr = (char*) malloc(EncodedMaxLen * 2);
1571 memcpy(ptr, Encoded, EncodedMaxLen);
1572 nnPtr = ptr + (nPtr - Encoded), nPtr = nnPtr;
1573 free(Encoded), Encoded = ptr;
1575 i--; /* do it once more with properly lengthened buffer */
1578 for (i = 0; i < nColons; i++)
1579 source[--AddrPtr[i]] = ',';
1587 * Get a message off disk. (returns om_* values found in msgbase.h)
1589 int CtdlOutputPreLoadedMsg(
1590 struct CtdlMessage *TheMessage,
1591 int mode, /* how would you like that message? */
1592 int headers_only, /* eschew the message body? */
1593 int do_proto, /* do Citadel protocol responses? */
1594 int crlf, /* Use CRLF newlines instead of LF? */
1595 int flags /* should the bessage be exported clean? */
1599 cit_uint8_t ch, prev_ch;
1601 char display_name[256];
1603 char *nl; /* newline string */
1605 int subject_found = 0;
1608 /* Buffers needed for RFC822 translation. These are all filled
1609 * using functions that are bounds-checked, and therefore we can
1610 * make them substantially smaller than SIZ.
1618 char datestamp[100];
1620 CtdlLogPrintf(CTDL_DEBUG, "CtdlOutputPreLoadedMsg(TheMessage=%s, %d, %d, %d, %d\n",
1621 ((TheMessage == NULL) ? "NULL" : "not null"),
1622 mode, headers_only, do_proto, crlf);
1624 strcpy(mid, "unknown");
1625 nl = (crlf ? "\r\n" : "\n");
1627 if (!is_valid_message(TheMessage)) {
1628 CtdlLogPrintf(CTDL_ERR,
1629 "ERROR: invalid preloaded message for output\n");
1631 return(om_no_such_msg);
1634 /* Are we downloading a MIME component? */
1635 if (mode == MT_DOWNLOAD) {
1636 if (TheMessage->cm_format_type != FMT_RFC822) {
1638 cprintf("%d This is not a MIME message.\n",
1639 ERROR + ILLEGAL_VALUE);
1640 } else if (CC->download_fp != NULL) {
1641 if (do_proto) cprintf(
1642 "%d You already have a download open.\n",
1643 ERROR + RESOURCE_BUSY);
1645 /* Parse the message text component */
1646 mptr = TheMessage->cm_fields['M'];
1647 mime_parser(mptr, NULL, *mime_download, NULL, NULL, NULL, 0);
1648 /* If there's no file open by this time, the requested
1649 * section wasn't found, so print an error
1651 if (CC->download_fp == NULL) {
1652 if (do_proto) cprintf(
1653 "%d Section %s not found.\n",
1654 ERROR + FILE_NOT_FOUND,
1655 CC->download_desired_section);
1658 return((CC->download_fp != NULL) ? om_ok : om_mime_error);
1661 /* MT_SPEW_SECTION is like MT_DOWNLOAD except it outputs the whole MIME part
1662 * in a single server operation instead of opening a download file.
1664 if (mode == MT_SPEW_SECTION) {
1665 if (TheMessage->cm_format_type != FMT_RFC822) {
1667 cprintf("%d This is not a MIME message.\n",
1668 ERROR + ILLEGAL_VALUE);
1670 /* Parse the message text component */
1673 mptr = TheMessage->cm_fields['M'];
1674 mime_parser(mptr, NULL, *mime_spew_section, NULL, NULL, (void *)&found_it, 0);
1675 /* If section wasn't found, print an error
1678 if (do_proto) cprintf(
1679 "%d Section %s not found.\n",
1680 ERROR + FILE_NOT_FOUND,
1681 CC->download_desired_section);
1684 return((CC->download_fp != NULL) ? om_ok : om_mime_error);
1687 /* now for the user-mode message reading loops */
1688 if (do_proto) cprintf("%d msg:\n", LISTING_FOLLOWS);
1690 /* Does the caller want to skip the headers? */
1691 if (headers_only == HEADERS_NONE) goto START_TEXT;
1693 /* Tell the client which format type we're using. */
1694 if ( (mode == MT_CITADEL) && (do_proto) ) {
1695 cprintf("type=%d\n", TheMessage->cm_format_type);
1698 /* nhdr=yes means that we're only displaying headers, no body */
1699 if ( (TheMessage->cm_anon_type == MES_ANONONLY)
1700 && (mode == MT_CITADEL)
1703 cprintf("nhdr=yes\n");
1706 /* begin header processing loop for Citadel message format */
1708 if ((mode == MT_CITADEL) || (mode == MT_MIME)) {
1710 safestrncpy(display_name, "<unknown>", sizeof display_name);
1711 if (TheMessage->cm_fields['A']) {
1712 strcpy(buf, TheMessage->cm_fields['A']);
1713 if (TheMessage->cm_anon_type == MES_ANONONLY) {
1714 safestrncpy(display_name, "****", sizeof display_name);
1716 else if (TheMessage->cm_anon_type == MES_ANONOPT) {
1717 safestrncpy(display_name, "anonymous", sizeof display_name);
1720 safestrncpy(display_name, buf, sizeof display_name);
1722 if ((is_room_aide())
1723 && ((TheMessage->cm_anon_type == MES_ANONONLY)
1724 || (TheMessage->cm_anon_type == MES_ANONOPT))) {
1725 size_t tmp = strlen(display_name);
1726 snprintf(&display_name[tmp],
1727 sizeof display_name - tmp,
1732 /* Don't show Internet address for users on the
1733 * local Citadel network.
1736 if (TheMessage->cm_fields['N'] != NULL)
1737 if (!IsEmptyStr(TheMessage->cm_fields['N']))
1738 if (haschar(TheMessage->cm_fields['N'], '.') == 0) {
1742 /* Now spew the header fields in the order we like them. */
1743 safestrncpy(allkeys, FORDER, sizeof allkeys);
1744 for (i=0; i<strlen(allkeys); ++i) {
1745 k = (int) allkeys[i];
1747 if ( (TheMessage->cm_fields[k] != NULL)
1748 && (msgkeys[k] != NULL) ) {
1750 if (do_proto) cprintf("%s=%s\n",
1754 else if ((k == 'F') && (suppress_f)) {
1757 /* Masquerade display name if needed */
1759 if (do_proto) cprintf("%s=%s\n",
1761 TheMessage->cm_fields[k]
1770 /* begin header processing loop for RFC822 transfer format */
1775 strcpy(snode, NODENAME);
1776 strcpy(lnode, HUMANNODE);
1777 if (mode == MT_RFC822) {
1778 for (i = 0; i < 256; ++i) {
1779 if (TheMessage->cm_fields[i]) {
1780 mptr = mpptr = TheMessage->cm_fields[i];
1783 safestrncpy(luser, mptr, sizeof luser);
1784 safestrncpy(suser, mptr, sizeof suser);
1786 else if (i == 'Y') {
1787 if ((flags & QP_EADDR) != 0)
1788 mptr = qp_encode_email_addrs(mptr);
1789 cprintf("CC: %s%s", mptr, nl);
1791 else if (i == 'P') {
1792 cprintf("Return-Path: %s%s", mptr, nl);
1794 else if (i == 'L') {
1795 cprintf("List-ID: %s%s", mptr, nl);
1797 else if (i == 'V') {
1798 if ((flags & QP_EADDR) != 0)
1799 mptr = qp_encode_email_addrs(mptr);
1800 cprintf("Envelope-To: %s%s", mptr, nl);
1802 else if (i == 'U') {
1803 cprintf("Subject: %s%s", mptr, nl);
1807 safestrncpy(mid, mptr, sizeof mid);
1809 safestrncpy(lnode, mptr, sizeof lnode);
1811 safestrncpy(fuser, mptr, sizeof fuser);
1812 /* else if (i == 'O')
1813 cprintf("X-Citadel-Room: %s%s",
1816 safestrncpy(snode, mptr, sizeof snode);
1819 if (haschar(mptr, '@') == 0)
1821 cprintf("To: %s@%s%s", mptr, config.c_fqdn, nl);
1825 if ((flags & QP_EADDR) != 0)
1826 mptr = qp_encode_email_addrs(mptr);
1827 cprintf("To: %s%s", mptr, nl);
1830 else if (i == 'T') {
1831 datestring(datestamp, sizeof datestamp,
1832 atol(mptr), DATESTRING_RFC822);
1833 cprintf("Date: %s%s", datestamp, nl);
1835 else if (i == 'W') {
1836 cprintf("References: ");
1837 k = num_tokens(mptr, '|');
1838 for (j=0; j<k; ++j) {
1839 extract_token(buf, mptr, j, '|', sizeof buf);
1840 cprintf("<%s>", buf);
1853 if (subject_found == 0) {
1854 cprintf("Subject: (no subject)%s", nl);
1858 for (i=0; !IsEmptyStr(&suser[i]); ++i) {
1859 suser[i] = tolower(suser[i]);
1860 if (!isalnum(suser[i])) suser[i]='_';
1863 if (mode == MT_RFC822) {
1864 if (!strcasecmp(snode, NODENAME)) {
1865 safestrncpy(snode, FQDN, sizeof snode);
1868 /* Construct a fun message id */
1869 cprintf("Message-ID: <%s", mid);/// todo: this possibly breaks threadding mails.
1870 if (strchr(mid, '@')==NULL) {
1871 cprintf("@%s", snode);
1875 if (!is_room_aide() && (TheMessage->cm_anon_type == MES_ANONONLY)) {
1876 cprintf("From: \"----\" <x@x.org>%s", nl);
1878 else if (!is_room_aide() && (TheMessage->cm_anon_type == MES_ANONOPT)) {
1879 cprintf("From: \"anonymous\" <x@x.org>%s", nl);
1881 else if (!IsEmptyStr(fuser)) {
1882 cprintf("From: \"%s\" <%s>%s", luser, fuser, nl);
1885 cprintf("From: \"%s\" <%s@%s>%s", luser, suser, snode, nl);
1888 cprintf("Organization: %s%s", lnode, nl);
1890 /* Blank line signifying RFC822 end-of-headers */
1891 if (TheMessage->cm_format_type != FMT_RFC822) {
1896 /* end header processing loop ... at this point, we're in the text */
1898 if (headers_only == HEADERS_FAST) goto DONE;
1899 mptr = TheMessage->cm_fields['M'];
1901 /* Tell the client about the MIME parts in this message */
1902 if (TheMessage->cm_format_type == FMT_RFC822) {
1903 if ( (mode == MT_CITADEL) || (mode == MT_MIME) ) {
1904 memset(&ma, 0, sizeof(struct ma_info));
1905 mime_parser(mptr, NULL,
1906 (do_proto ? *list_this_part : NULL),
1907 (do_proto ? *list_this_pref : NULL),
1908 (do_proto ? *list_this_suff : NULL),
1911 else if (mode == MT_RFC822) { /* unparsed RFC822 dump */
1912 char *start_of_text = NULL;
1913 start_of_text = strstr(mptr, "\n\r\n");
1914 if (start_of_text == NULL) start_of_text = strstr(mptr, "\n\n");
1915 if (start_of_text == NULL) start_of_text = mptr;
1917 start_of_text = strstr(start_of_text, "\n");
1922 int nllen = strlen(nl);
1924 while (ch=*mptr, ch!=0) {
1930 ((headers_only == HEADERS_NONE) && (mptr >= start_of_text))
1931 || ((headers_only == HEADERS_ONLY) && (mptr < start_of_text))
1932 || ((headers_only != HEADERS_NONE) && (headers_only != HEADERS_ONLY))
1935 sprintf(&outbuf[outlen], "%s", nl);
1939 outbuf[outlen++] = ch;
1943 if (flags & ESC_DOT)
1945 if ((prev_ch == 10) && (ch == '.') && ((*(mptr+1) == 13) || (*(mptr+1) == 10)))
1947 outbuf[outlen++] = '.';
1952 if (outlen > 1000) {
1953 client_write(outbuf, outlen);
1958 client_write(outbuf, outlen);
1966 if (headers_only == HEADERS_ONLY) {
1970 /* signify start of msg text */
1971 if ( (mode == MT_CITADEL) || (mode == MT_MIME) ) {
1972 if (do_proto) cprintf("text\n");
1975 /* If the format type on disk is 1 (fixed-format), then we want
1976 * everything to be output completely literally ... regardless of
1977 * what message transfer format is in use.
1979 if (TheMessage->cm_format_type == FMT_FIXED) {
1981 if (mode == MT_MIME) {
1982 cprintf("Content-type: text/plain\n\n");
1986 while (ch = *mptr++, ch > 0) {
1989 if ((ch == 10) || (buflen > 250)) {
1991 cprintf("%s%s", buf, nl);
2000 if (!IsEmptyStr(buf))
2001 cprintf("%s%s", buf, nl);
2004 /* If the message on disk is format 0 (Citadel vari-format), we
2005 * output using the formatter at 80 columns. This is the final output
2006 * form if the transfer format is RFC822, but if the transfer format
2007 * is Citadel proprietary, it'll still work, because the indentation
2008 * for new paragraphs is correct and the client will reformat the
2009 * message to the reader's screen width.
2011 if (TheMessage->cm_format_type == FMT_CITADEL) {
2012 if (mode == MT_MIME) {
2013 cprintf("Content-type: text/x-citadel-variformat\n\n");
2015 memfmout(mptr, 0, nl);
2018 /* If the message on disk is format 4 (MIME), we've gotta hand it
2019 * off to the MIME parser. The client has already been told that
2020 * this message is format 1 (fixed format), so the callback function
2021 * we use will display those parts as-is.
2023 if (TheMessage->cm_format_type == FMT_RFC822) {
2024 memset(&ma, 0, sizeof(struct ma_info));
2026 if (mode == MT_MIME) {
2027 ma.use_fo_hooks = 0;
2028 strcpy(ma.chosen_part, "1");
2029 ma.chosen_pref = 9999;
2030 mime_parser(mptr, NULL,
2031 *choose_preferred, *fixed_output_pre,
2032 *fixed_output_post, (void *)&ma, 0);
2033 mime_parser(mptr, NULL,
2034 *output_preferred, NULL, NULL, (void *)&ma, CC->msg4_dont_decode);
2037 ma.use_fo_hooks = 1;
2038 mime_parser(mptr, NULL,
2039 *fixed_output, *fixed_output_pre,
2040 *fixed_output_post, (void *)&ma, 0);
2045 DONE: /* now we're done */
2046 if (do_proto) cprintf("000\n");
2053 * display a message (mode 0 - Citadel proprietary)
2055 void cmd_msg0(char *cmdbuf)
2058 int headers_only = HEADERS_ALL;
2060 msgid = extract_long(cmdbuf, 0);
2061 headers_only = extract_int(cmdbuf, 1);
2063 CtdlOutputMsg(msgid, MT_CITADEL, headers_only, 1, 0, NULL, 0);
2069 * display a message (mode 2 - RFC822)
2071 void cmd_msg2(char *cmdbuf)
2074 int headers_only = HEADERS_ALL;
2076 msgid = extract_long(cmdbuf, 0);
2077 headers_only = extract_int(cmdbuf, 1);
2079 CtdlOutputMsg(msgid, MT_RFC822, headers_only, 1, 1, NULL, 0);
2085 * display a message (mode 3 - IGnet raw format - internal programs only)
2087 void cmd_msg3(char *cmdbuf)
2090 struct CtdlMessage *msg = NULL;
2093 if (CC->internal_pgm == 0) {
2094 cprintf("%d This command is for internal programs only.\n",
2095 ERROR + HIGHER_ACCESS_REQUIRED);
2099 msgnum = extract_long(cmdbuf, 0);
2100 msg = CtdlFetchMessage(msgnum, 1);
2102 cprintf("%d Message %ld not found.\n",
2103 ERROR + MESSAGE_NOT_FOUND, msgnum);
2107 serialize_message(&smr, msg);
2108 CtdlFreeMessage(msg);
2111 cprintf("%d Unable to serialize message\n",
2112 ERROR + INTERNAL_ERROR);
2116 cprintf("%d %ld\n", BINARY_FOLLOWS, (long)smr.len);
2117 client_write((char *)smr.ser, (int)smr.len);
2124 * Display a message using MIME content types
2126 void cmd_msg4(char *cmdbuf)
2131 msgid = extract_long(cmdbuf, 0);
2132 extract_token(section, cmdbuf, 1, '|', sizeof section);
2133 CtdlOutputMsg(msgid, MT_MIME, 0, 1, 0, (section[0] ? section : NULL) , 0);
2139 * Client tells us its preferred message format(s)
2141 void cmd_msgp(char *cmdbuf)
2143 if (!strcasecmp(cmdbuf, "dont_decode")) {
2144 CC->msg4_dont_decode = 1;
2145 cprintf("%d MSG4 will not pre-decode messages.\n", CIT_OK);
2148 safestrncpy(CC->preferred_formats, cmdbuf, sizeof(CC->preferred_formats));
2149 cprintf("%d Preferred MIME formats have been set.\n", CIT_OK);
2155 * Open a component of a MIME message as a download file
2157 void cmd_opna(char *cmdbuf)
2160 char desired_section[128];
2162 msgid = extract_long(cmdbuf, 0);
2163 extract_token(desired_section, cmdbuf, 1, '|', sizeof desired_section);
2164 safestrncpy(CC->download_desired_section, desired_section,
2165 sizeof CC->download_desired_section);
2166 CtdlOutputMsg(msgid, MT_DOWNLOAD, 0, 1, 1, NULL, 0);
2171 * Open a component of a MIME message and transmit it all at once
2173 void cmd_dlat(char *cmdbuf)
2176 char desired_section[128];
2178 msgid = extract_long(cmdbuf, 0);
2179 extract_token(desired_section, cmdbuf, 1, '|', sizeof desired_section);
2180 safestrncpy(CC->download_desired_section, desired_section,
2181 sizeof CC->download_desired_section);
2182 CtdlOutputMsg(msgid, MT_SPEW_SECTION, 0, 1, 1, NULL, 0);
2187 * Save one or more message pointers into a specified room
2188 * (Returns 0 for success, nonzero for failure)
2189 * roomname may be NULL to use the current room
2191 * Note that the 'supplied_msg' field may be set to NULL, in which case
2192 * the message will be fetched from disk, by number, if we need to perform
2193 * replication checks. This adds an additional database read, so if the
2194 * caller already has the message in memory then it should be supplied. (Obviously
2195 * this mode of operation only works if we're saving a single message.)
2197 int CtdlSaveMsgPointersInRoom(char *roomname, long newmsgidlist[], int num_newmsgs,
2198 int do_repl_check, struct CtdlMessage *supplied_msg)
2201 char hold_rm[ROOMNAMELEN];
2202 struct cdbdata *cdbfr;
2205 long highest_msg = 0L;
2208 struct CtdlMessage *msg = NULL;
2210 long *msgs_to_be_merged = NULL;
2211 int num_msgs_to_be_merged = 0;
2213 CtdlLogPrintf(CTDL_DEBUG,
2214 "CtdlSaveMsgPointersInRoom(room=%s, num_msgs=%d, repl=%d)\n",
2215 roomname, num_newmsgs, do_repl_check);
2217 strcpy(hold_rm, CC->room.QRname);
2220 if (newmsgidlist == NULL) return(ERROR + INTERNAL_ERROR);
2221 if (num_newmsgs < 1) return(ERROR + INTERNAL_ERROR);
2222 if (num_newmsgs > 1) supplied_msg = NULL;
2224 /* Now the regular stuff */
2225 if (lgetroom(&CC->room,
2226 ((roomname != NULL) ? roomname : CC->room.QRname) )
2228 CtdlLogPrintf(CTDL_ERR, "No such room <%s>\n", roomname);
2229 return(ERROR + ROOM_NOT_FOUND);
2233 msgs_to_be_merged = malloc(sizeof(long) * num_newmsgs);
2234 num_msgs_to_be_merged = 0;
2237 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long));
2238 if (cdbfr == NULL) {
2242 msglist = (long *) cdbfr->ptr;
2243 cdbfr->ptr = NULL; /* CtdlSaveMsgPointerInRoom() now owns this memory */
2244 num_msgs = cdbfr->len / sizeof(long);
2249 /* Create a list of msgid's which were supplied by the caller, but do
2250 * not already exist in the target room. It is absolutely taboo to
2251 * have more than one reference to the same message in a room.
2253 for (i=0; i<num_newmsgs; ++i) {
2255 if (num_msgs > 0) for (j=0; j<num_msgs; ++j) {
2256 if (msglist[j] == newmsgidlist[i]) {
2261 msgs_to_be_merged[num_msgs_to_be_merged++] = newmsgidlist[i];
2265 CtdlLogPrintf(9, "%d unique messages to be merged\n", num_msgs_to_be_merged);
2268 * Now merge the new messages
2270 msglist = realloc(msglist, (sizeof(long) * (num_msgs + num_msgs_to_be_merged)) );
2271 if (msglist == NULL) {
2272 CtdlLogPrintf(CTDL_ALERT, "ERROR: can't realloc message list!\n");
2274 memcpy(&msglist[num_msgs], msgs_to_be_merged, (sizeof(long) * num_msgs_to_be_merged) );
2275 num_msgs += num_msgs_to_be_merged;
2277 /* Sort the message list, so all the msgid's are in order */
2278 num_msgs = sort_msglist(msglist, num_msgs);
2280 /* Determine the highest message number */
2281 highest_msg = msglist[num_msgs - 1];
2283 /* Write it back to disk. */
2284 cdb_store(CDB_MSGLISTS, &CC->room.QRnumber, (int)sizeof(long),
2285 msglist, (int)(num_msgs * sizeof(long)));
2287 /* Free up the memory we used. */
2290 /* Update the highest-message pointer and unlock the room. */
2291 CC->room.QRhighest = highest_msg;
2292 lputroom(&CC->room);
2294 /* Perform replication checks if necessary */
2295 if ( (DoesThisRoomNeedEuidIndexing(&CC->room)) && (do_repl_check) ) {
2296 CtdlLogPrintf(CTDL_DEBUG, "CtdlSaveMsgPointerInRoom() doing repl checks\n");
2298 for (i=0; i<num_msgs_to_be_merged; ++i) {
2299 msgid = msgs_to_be_merged[i];
2301 if (supplied_msg != NULL) {
2305 msg = CtdlFetchMessage(msgid, 0);
2309 ReplicationChecks(msg);
2311 /* If the message has an Exclusive ID, index that... */
2312 if (msg->cm_fields['E'] != NULL) {
2313 index_message_by_euid(msg->cm_fields['E'], &CC->room, msgid);
2316 /* Free up the memory we may have allocated */
2317 if (msg != supplied_msg) {
2318 CtdlFreeMessage(msg);
2326 CtdlLogPrintf(CTDL_DEBUG, "CtdlSaveMsgPointerInRoom() skips repl checks\n");
2329 /* Submit this room for processing by hooks */
2330 PerformRoomHooks(&CC->room);
2332 /* Go back to the room we were in before we wandered here... */
2333 getroom(&CC->room, hold_rm);
2335 /* Bump the reference count for all messages which were merged */
2336 for (i=0; i<num_msgs_to_be_merged; ++i) {
2337 AdjRefCount(msgs_to_be_merged[i], +1);
2340 /* Free up memory... */
2341 if (msgs_to_be_merged != NULL) {
2342 free(msgs_to_be_merged);
2345 /* Return success. */
2351 * This is the same as CtdlSaveMsgPointersInRoom() but it only accepts
2354 int CtdlSaveMsgPointerInRoom(char *roomname, long msgid,
2355 int do_repl_check, struct CtdlMessage *supplied_msg)
2357 return CtdlSaveMsgPointersInRoom(roomname, &msgid, 1, do_repl_check, supplied_msg);
2364 * Message base operation to save a new message to the message store
2365 * (returns new message number)
2367 * This is the back end for CtdlSubmitMsg() and should not be directly
2368 * called by server-side modules.
2371 long send_message(struct CtdlMessage *msg) {
2379 /* Get a new message number */
2380 newmsgid = get_new_message_number();
2381 snprintf(msgidbuf, sizeof msgidbuf, "%010ld@%s", newmsgid, config.c_fqdn);
2383 /* Generate an ID if we don't have one already */
2384 if (msg->cm_fields['I']==NULL) {
2385 msg->cm_fields['I'] = strdup(msgidbuf);
2388 /* If the message is big, set its body aside for storage elsewhere */
2389 if (msg->cm_fields['M'] != NULL) {
2390 if (strlen(msg->cm_fields['M']) > BIGMSG) {
2392 holdM = msg->cm_fields['M'];
2393 msg->cm_fields['M'] = NULL;
2397 /* Serialize our data structure for storage in the database */
2398 serialize_message(&smr, msg);
2401 msg->cm_fields['M'] = holdM;
2405 cprintf("%d Unable to serialize message\n",
2406 ERROR + INTERNAL_ERROR);
2410 /* Write our little bundle of joy into the message base */
2411 if (cdb_store(CDB_MSGMAIN, &newmsgid, (int)sizeof(long),
2412 smr.ser, smr.len) < 0) {
2413 CtdlLogPrintf(CTDL_ERR, "Can't store message\n");
2417 cdb_store(CDB_BIGMSGS,
2427 /* Free the memory we used for the serialized message */
2430 /* Return the *local* message ID to the caller
2431 * (even if we're storing an incoming network message)
2439 * Serialize a struct CtdlMessage into the format used on disk and network.
2441 * This function loads up a "struct ser_ret" (defined in server.h) which
2442 * contains the length of the serialized message and a pointer to the
2443 * serialized message in memory. THE LATTER MUST BE FREED BY THE CALLER.
2445 void serialize_message(struct ser_ret *ret, /* return values */
2446 struct CtdlMessage *msg) /* unserialized msg */
2448 size_t wlen, fieldlen;
2450 static char *forder = FORDER;
2453 * Check for valid message format
2455 if (is_valid_message(msg) == 0) {
2456 CtdlLogPrintf(CTDL_ERR, "serialize_message() aborting due to invalid message\n");
2463 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL)
2464 ret->len = ret->len +
2465 strlen(msg->cm_fields[(int)forder[i]]) + 2;
2467 ret->ser = malloc(ret->len);
2468 if (ret->ser == NULL) {
2469 CtdlLogPrintf(CTDL_ERR, "serialize_message() malloc(%ld) failed: %s\n",
2470 (long)ret->len, strerror(errno));
2477 ret->ser[1] = msg->cm_anon_type;
2478 ret->ser[2] = msg->cm_format_type;
2481 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL) {
2482 fieldlen = strlen(msg->cm_fields[(int)forder[i]]);
2483 ret->ser[wlen++] = (char)forder[i];
2484 safestrncpy((char *)&ret->ser[wlen], msg->cm_fields[(int)forder[i]], fieldlen+1);
2485 wlen = wlen + fieldlen + 1;
2487 if (ret->len != wlen) CtdlLogPrintf(CTDL_ERR, "ERROR: len=%ld wlen=%ld\n",
2488 (long)ret->len, (long)wlen);
2495 * Serialize a struct CtdlMessage into the format used on disk and network.
2497 * This function loads up a "struct ser_ret" (defined in server.h) which
2498 * contains the length of the serialized message and a pointer to the
2499 * serialized message in memory. THE LATTER MUST BE FREED BY THE CALLER.
2501 void dump_message(struct CtdlMessage *msg, /* unserialized msg */
2502 long Siz) /* how many chars ? */
2506 static char *forder = FORDER;
2510 * Check for valid message format
2512 if (is_valid_message(msg) == 0) {
2513 CtdlLogPrintf(CTDL_ERR, "dump_message() aborting due to invalid message\n");
2517 buf = (char*) malloc (Siz + 1);
2521 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL) {
2522 snprintf (buf, Siz, " msg[%c] = %s ...\n", (char) forder[i],
2523 msg->cm_fields[(int)forder[i]]);
2524 client_write (buf, strlen(buf));
2533 * Check to see if any messages already exist in the current room which
2534 * carry the same Exclusive ID as this one. If any are found, delete them.
2536 void ReplicationChecks(struct CtdlMessage *msg) {
2537 long old_msgnum = (-1L);
2539 if (DoesThisRoomNeedEuidIndexing(&CC->room) == 0) return;
2541 CtdlLogPrintf(CTDL_DEBUG, "Performing replication checks in <%s>\n",
2544 /* No exclusive id? Don't do anything. */
2545 if (msg == NULL) return;
2546 if (msg->cm_fields['E'] == NULL) return;
2547 if (IsEmptyStr(msg->cm_fields['E'])) return;
2548 /*CtdlLogPrintf(CTDL_DEBUG, "Exclusive ID: <%s> for room <%s>\n",
2549 msg->cm_fields['E'], CC->room.QRname);*/
2551 old_msgnum = locate_message_by_euid(msg->cm_fields['E'], &CC->room);
2552 if (old_msgnum > 0L) {
2553 CtdlLogPrintf(CTDL_DEBUG, "ReplicationChecks() replacing message %ld\n", old_msgnum);
2554 CtdlDeleteMessages(CC->room.QRname, &old_msgnum, 1, "");
2561 * Save a message to disk and submit it into the delivery system.
2563 long CtdlSubmitMsg(struct CtdlMessage *msg, /* message to save */
2564 struct recptypes *recps, /* recipients (if mail) */
2565 char *force, /* force a particular room? */
2566 int flags /* should the bessage be exported clean? */
2568 char submit_filename[128];
2569 char generated_timestamp[32];
2570 char hold_rm[ROOMNAMELEN];
2571 char actual_rm[ROOMNAMELEN];
2572 char force_room[ROOMNAMELEN];
2573 char content_type[SIZ]; /* We have to learn this */
2574 char recipient[SIZ];
2577 struct ctdluser userbuf;
2579 struct MetaData smi;
2580 FILE *network_fp = NULL;
2581 static int seqnum = 1;
2582 struct CtdlMessage *imsg = NULL;
2584 size_t instr_alloc = 0;
2586 char *hold_R, *hold_D;
2587 char *collected_addresses = NULL;
2588 struct addresses_to_be_filed *aptr = NULL;
2589 char *saved_rfc822_version = NULL;
2590 int qualified_for_journaling = 0;
2591 struct CitContext *CCC = CC; /* CachedCitContext - performance boost */
2592 char bounce_to[1024] = "";
2594 CtdlLogPrintf(CTDL_DEBUG, "CtdlSubmitMsg() called\n");
2595 if (is_valid_message(msg) == 0) return(-1); /* self check */
2597 /* If this message has no timestamp, we take the liberty of
2598 * giving it one, right now.
2600 if (msg->cm_fields['T'] == NULL) {
2601 snprintf(generated_timestamp, sizeof generated_timestamp, "%ld", (long)time(NULL));
2602 msg->cm_fields['T'] = strdup(generated_timestamp);
2605 /* If this message has no path, we generate one.
2607 if (msg->cm_fields['P'] == NULL) {
2608 if (msg->cm_fields['A'] != NULL) {
2609 msg->cm_fields['P'] = strdup(msg->cm_fields['A']);
2610 for (a=0; !IsEmptyStr(&msg->cm_fields['P'][a]); ++a) {
2611 if (isspace(msg->cm_fields['P'][a])) {
2612 msg->cm_fields['P'][a] = ' ';
2617 msg->cm_fields['P'] = strdup("unknown");
2621 if (force == NULL) {
2622 strcpy(force_room, "");
2625 strcpy(force_room, force);
2628 /* Learn about what's inside, because it's what's inside that counts */
2629 if (msg->cm_fields['M'] == NULL) {
2630 CtdlLogPrintf(CTDL_ERR, "ERROR: attempt to save message with NULL body\n");
2634 switch (msg->cm_format_type) {
2636 strcpy(content_type, "text/x-citadel-variformat");
2639 strcpy(content_type, "text/plain");
2642 strcpy(content_type, "text/plain");
2643 mptr = bmstrcasestr(msg->cm_fields['M'], "Content-type:");
2646 safestrncpy(content_type, &mptr[13], sizeof content_type);
2647 striplt(content_type);
2648 aptr = content_type;
2649 while (!IsEmptyStr(aptr)) {
2661 /* Goto the correct room */
2662 CtdlLogPrintf(CTDL_DEBUG, "Selected room %s\n", (recps) ? CCC->room.QRname : SENTITEMS);
2663 strcpy(hold_rm, CCC->room.QRname);
2664 strcpy(actual_rm, CCC->room.QRname);
2665 if (recps != NULL) {
2666 strcpy(actual_rm, SENTITEMS);
2669 /* If the user is a twit, move to the twit room for posting */
2671 if (CCC->user.axlevel == 2) {
2672 strcpy(hold_rm, actual_rm);
2673 strcpy(actual_rm, config.c_twitroom);
2674 CtdlLogPrintf(CTDL_DEBUG, "Diverting to twit room\n");
2678 /* ...or if this message is destined for Aide> then go there. */
2679 if (!IsEmptyStr(force_room)) {
2680 strcpy(actual_rm, force_room);
2683 CtdlLogPrintf(CTDL_DEBUG, "Final selection: %s\n", actual_rm);
2684 if (strcasecmp(actual_rm, CCC->room.QRname)) {
2685 /* getroom(&CCC->room, actual_rm); */
2686 usergoto(actual_rm, 0, 1, NULL, NULL);
2690 * If this message has no O (room) field, generate one.
2692 if (msg->cm_fields['O'] == NULL) {
2693 msg->cm_fields['O'] = strdup(CCC->room.QRname);
2696 /* Perform "before save" hooks (aborting if any return nonzero) */
2697 CtdlLogPrintf(CTDL_DEBUG, "Performing before-save hooks\n");
2698 if (PerformMessageHooks(msg, EVT_BEFORESAVE) > 0) return(-3);
2701 * If this message has an Exclusive ID, and the room is replication
2702 * checking enabled, then do replication checks.
2704 if (DoesThisRoomNeedEuidIndexing(&CCC->room)) {
2705 ReplicationChecks(msg);
2708 /* Save it to disk */
2709 CtdlLogPrintf(CTDL_DEBUG, "Saving to disk\n");
2710 newmsgid = send_message(msg);
2711 if (newmsgid <= 0L) return(-5);
2713 /* Write a supplemental message info record. This doesn't have to
2714 * be a critical section because nobody else knows about this message
2717 CtdlLogPrintf(CTDL_DEBUG, "Creating MetaData record\n");
2718 memset(&smi, 0, sizeof(struct MetaData));
2719 smi.meta_msgnum = newmsgid;
2720 smi.meta_refcount = 0;
2721 safestrncpy(smi.meta_content_type, content_type,
2722 sizeof smi.meta_content_type);
2725 * Measure how big this message will be when rendered as RFC822.
2726 * We do this for two reasons:
2727 * 1. We need the RFC822 length for the new metadata record, so the
2728 * POP and IMAP services don't have to calculate message lengths
2729 * while the user is waiting (multiplied by potentially hundreds
2730 * or thousands of messages).
2731 * 2. If journaling is enabled, we will need an RFC822 version of the
2732 * message to attach to the journalized copy.
2734 if (CCC->redirect_buffer != NULL) {
2735 CtdlLogPrintf(CTDL_ALERT, "CCC->redirect_buffer is not NULL during message submission!\n");
2738 CCC->redirect_buffer = malloc(SIZ);
2739 CCC->redirect_len = 0;
2740 CCC->redirect_alloc = SIZ;
2741 CtdlOutputPreLoadedMsg(msg, MT_RFC822, HEADERS_ALL, 0, 1, QP_EADDR);
2742 smi.meta_rfc822_length = CCC->redirect_len;
2743 saved_rfc822_version = CCC->redirect_buffer;
2744 CCC->redirect_buffer = NULL;
2745 CCC->redirect_len = 0;
2746 CCC->redirect_alloc = 0;
2750 /* Now figure out where to store the pointers */
2751 CtdlLogPrintf(CTDL_DEBUG, "Storing pointers\n");
2753 /* If this is being done by the networker delivering a private
2754 * message, we want to BYPASS saving the sender's copy (because there
2755 * is no local sender; it would otherwise go to the Trashcan).
2757 if ((!CCC->internal_pgm) || (recps == NULL)) {
2758 if (CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 1, msg) != 0) {
2759 CtdlLogPrintf(CTDL_ERR, "ERROR saving message pointer!\n");
2760 CtdlSaveMsgPointerInRoom(config.c_aideroom, newmsgid, 0, msg);
2764 /* For internet mail, drop a copy in the outbound queue room */
2766 if (recps->num_internet > 0) {
2767 CtdlSaveMsgPointerInRoom(SMTP_SPOOLOUT_ROOM, newmsgid, 0, msg);
2770 /* If other rooms are specified, drop them there too. */
2772 if (recps->num_room > 0)
2773 for (i=0; i<num_tokens(recps->recp_room, '|'); ++i) {
2774 extract_token(recipient, recps->recp_room, i,
2775 '|', sizeof recipient);
2776 CtdlLogPrintf(CTDL_DEBUG, "Delivering to room <%s>\n", recipient);
2777 CtdlSaveMsgPointerInRoom(recipient, newmsgid, 0, msg);
2780 /* Bump this user's messages posted counter. */
2781 CtdlLogPrintf(CTDL_DEBUG, "Updating user\n");
2782 lgetuser(&CCC->user, CCC->curr_user);
2783 CCC->user.posted = CCC->user.posted + 1;
2784 lputuser(&CCC->user);
2786 /* Decide where bounces need to be delivered */
2787 if (CCC->logged_in) {
2788 snprintf(bounce_to, sizeof bounce_to, "%s@%s", CCC->user.fullname, config.c_nodename);
2791 snprintf(bounce_to, sizeof bounce_to, "%s@%s", msg->cm_fields['A'], msg->cm_fields['N']);
2794 /* If this is private, local mail, make a copy in the
2795 * recipient's mailbox and bump the reference count.
2798 if (recps->num_local > 0)
2799 for (i=0; i<num_tokens(recps->recp_local, '|'); ++i) {
2800 extract_token(recipient, recps->recp_local, i,
2801 '|', sizeof recipient);
2802 CtdlLogPrintf(CTDL_DEBUG, "Delivering private local mail to <%s>\n",
2804 if (getuser(&userbuf, recipient) == 0) {
2805 // Add a flag so the Funambol module knows its mail
2806 msg->cm_fields['W'] = strdup(recipient);
2807 MailboxName(actual_rm, sizeof actual_rm, &userbuf, MAILROOM);
2808 CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 0, msg);
2809 BumpNewMailCounter(userbuf.usernum);
2810 if (!IsEmptyStr(config.c_funambol_host) || !IsEmptyStr(config.c_pager_program)) {
2811 /* Generate a instruction message for the Funambol notification
2812 * server, in the same style as the SMTP queue
2815 instr = malloc(instr_alloc);
2816 snprintf(instr, instr_alloc,
2817 "Content-type: %s\n\nmsgid|%ld\nsubmitted|%ld\n"
2819 SPOOLMIME, newmsgid, (long)time(NULL),
2823 imsg = malloc(sizeof(struct CtdlMessage));
2824 memset(imsg, 0, sizeof(struct CtdlMessage));
2825 imsg->cm_magic = CTDLMESSAGE_MAGIC;
2826 imsg->cm_anon_type = MES_NORMAL;
2827 imsg->cm_format_type = FMT_RFC822;
2828 imsg->cm_fields['A'] = strdup("Citadel");
2829 imsg->cm_fields['J'] = strdup("do not journal");
2830 imsg->cm_fields['M'] = instr; /* imsg owns this memory now */
2831 imsg->cm_fields['W'] = strdup(recipient);
2832 CtdlSubmitMsg(imsg, NULL, FNBL_QUEUE_ROOM, 0);
2833 CtdlFreeMessage(imsg);
2837 CtdlLogPrintf(CTDL_DEBUG, "No user <%s>\n", recipient);
2838 CtdlSaveMsgPointerInRoom(config.c_aideroom,
2843 /* Perform "after save" hooks */
2844 CtdlLogPrintf(CTDL_DEBUG, "Performing after-save hooks\n");
2845 PerformMessageHooks(msg, EVT_AFTERSAVE);
2847 /* For IGnet mail, we have to save a new copy into the spooler for
2848 * each recipient, with the R and D fields set to the recipient and
2849 * destination-node. This has two ugly side effects: all other
2850 * recipients end up being unlisted in this recipient's copy of the
2851 * message, and it has to deliver multiple messages to the same
2852 * node. We'll revisit this again in a year or so when everyone has
2853 * a network spool receiver that can handle the new style messages.
2856 if (recps->num_ignet > 0)
2857 for (i=0; i<num_tokens(recps->recp_ignet, '|'); ++i) {
2858 extract_token(recipient, recps->recp_ignet, i,
2859 '|', sizeof recipient);
2861 hold_R = msg->cm_fields['R'];
2862 hold_D = msg->cm_fields['D'];
2863 msg->cm_fields['R'] = malloc(SIZ);
2864 msg->cm_fields['D'] = malloc(128);
2865 extract_token(msg->cm_fields['R'], recipient, 0, '@', SIZ);
2866 extract_token(msg->cm_fields['D'], recipient, 1, '@', 128);
2868 serialize_message(&smr, msg);
2870 snprintf(submit_filename, sizeof submit_filename,
2871 "%s/netmail.%04lx.%04x.%04x",
2873 (long) getpid(), CCC->cs_pid, ++seqnum);
2874 network_fp = fopen(submit_filename, "wb+");
2875 if (network_fp != NULL) {
2876 fwrite(smr.ser, smr.len, 1, network_fp);
2882 free(msg->cm_fields['R']);
2883 free(msg->cm_fields['D']);
2884 msg->cm_fields['R'] = hold_R;
2885 msg->cm_fields['D'] = hold_D;
2888 /* Go back to the room we started from */
2889 CtdlLogPrintf(CTDL_DEBUG, "Returning to original room %s\n", hold_rm);
2890 if (strcasecmp(hold_rm, CCC->room.QRname))
2891 usergoto(hold_rm, 0, 1, NULL, NULL);
2893 /* For internet mail, generate delivery instructions.
2894 * Yes, this is recursive. Deal with it. Infinite recursion does
2895 * not happen because the delivery instructions message does not
2896 * contain a recipient.
2899 if (recps->num_internet > 0) {
2900 CtdlLogPrintf(CTDL_DEBUG, "Generating delivery instructions\n");
2902 instr = malloc(instr_alloc);
2903 snprintf(instr, instr_alloc,
2904 "Content-type: %s\n\nmsgid|%ld\nsubmitted|%ld\n"
2906 SPOOLMIME, newmsgid, (long)time(NULL),
2910 for (i=0; i<num_tokens(recps->recp_internet, '|'); ++i) {
2911 size_t tmp = strlen(instr);
2912 extract_token(recipient, recps->recp_internet, i, '|', sizeof recipient);
2913 if ((tmp + strlen(recipient) + 32) > instr_alloc) {
2914 instr_alloc = instr_alloc * 2;
2915 instr = realloc(instr, instr_alloc);
2917 snprintf(&instr[tmp], instr_alloc - tmp, "remote|%s|0||\n", recipient);
2920 imsg = malloc(sizeof(struct CtdlMessage));
2921 memset(imsg, 0, sizeof(struct CtdlMessage));
2922 imsg->cm_magic = CTDLMESSAGE_MAGIC;
2923 imsg->cm_anon_type = MES_NORMAL;
2924 imsg->cm_format_type = FMT_RFC822;
2925 imsg->cm_fields['A'] = strdup("Citadel");
2926 imsg->cm_fields['J'] = strdup("do not journal");
2927 imsg->cm_fields['M'] = instr; /* imsg owns this memory now */
2928 CtdlSubmitMsg(imsg, NULL, SMTP_SPOOLOUT_ROOM, QP_EADDR);
2929 CtdlFreeMessage(imsg);
2933 * Any addresses to harvest for someone's address book?
2935 if ( (CCC->logged_in) && (recps != NULL) ) {
2936 collected_addresses = harvest_collected_addresses(msg);
2939 if (collected_addresses != NULL) {
2940 aptr = (struct addresses_to_be_filed *)
2941 malloc(sizeof(struct addresses_to_be_filed));
2942 MailboxName(actual_rm, sizeof actual_rm,
2943 &CCC->user, USERCONTACTSROOM);
2944 aptr->roomname = strdup(actual_rm);
2945 aptr->collected_addresses = collected_addresses;
2946 begin_critical_section(S_ATBF);
2949 end_critical_section(S_ATBF);
2953 * Determine whether this message qualifies for journaling.
2955 if (msg->cm_fields['J'] != NULL) {
2956 qualified_for_journaling = 0;
2959 if (recps == NULL) {
2960 qualified_for_journaling = config.c_journal_pubmsgs;
2962 else if (recps->num_local + recps->num_ignet + recps->num_internet > 0) {
2963 qualified_for_journaling = config.c_journal_email;
2966 qualified_for_journaling = config.c_journal_pubmsgs;
2971 * Do we have to perform journaling? If so, hand off the saved
2972 * RFC822 version will be handed off to the journaler for background
2973 * submit. Otherwise, we have to free the memory ourselves.
2975 if (saved_rfc822_version != NULL) {
2976 if (qualified_for_journaling) {
2977 JournalBackgroundSubmit(msg, saved_rfc822_version, recps);
2980 free(saved_rfc822_version);
2993 * Convenience function for generating small administrative messages.
2995 void quickie_message(char *from, char *fromaddr, char *to, char *room, char *text,
2996 int format_type, char *subject)
2998 struct CtdlMessage *msg;
2999 struct recptypes *recp = NULL;
3001 msg = malloc(sizeof(struct CtdlMessage));
3002 memset(msg, 0, sizeof(struct CtdlMessage));
3003 msg->cm_magic = CTDLMESSAGE_MAGIC;
3004 msg->cm_anon_type = MES_NORMAL;
3005 msg->cm_format_type = format_type;
3008 msg->cm_fields['A'] = strdup(from);
3010 else if (fromaddr != NULL) {
3011 msg->cm_fields['A'] = strdup(fromaddr);
3012 if (strchr(msg->cm_fields['A'], '@')) {
3013 *strchr(msg->cm_fields['A'], '@') = 0;
3017 msg->cm_fields['A'] = strdup("Citadel");
3020 if (fromaddr != NULL) msg->cm_fields['F'] = strdup(fromaddr);
3021 if (room != NULL) msg->cm_fields['O'] = strdup(room);
3022 msg->cm_fields['N'] = strdup(NODENAME);
3024 msg->cm_fields['R'] = strdup(to);
3025 recp = validate_recipients(to, NULL, 0);
3027 if (subject != NULL) {
3028 msg->cm_fields['U'] = strdup(subject);
3030 msg->cm_fields['M'] = strdup(text);
3032 CtdlSubmitMsg(msg, recp, room, 0);
3033 CtdlFreeMessage(msg);
3034 if (recp != NULL) free_recipients(recp);
3040 * Back end function used by CtdlMakeMessage() and similar functions
3042 char *CtdlReadMessageBody(char *terminator, /* token signalling EOT */
3043 size_t maxlen, /* maximum message length */
3044 char *exist, /* if non-null, append to it;
3045 exist is ALWAYS freed */
3046 int crlf, /* CRLF newlines instead of LF */
3047 int sock /* socket handle or 0 for this session's client socket */
3051 size_t message_len = 0;
3052 size_t buffer_len = 0;
3059 if (exist == NULL) {
3066 message_len = strlen(exist);
3067 buffer_len = message_len + 4096;
3068 m = realloc(exist, buffer_len);
3075 /* Do we need to change leading ".." to "." for SMTP escaping? */
3076 if (!strcmp(terminator, ".")) {
3080 /* flush the input if we have nowhere to store it */
3085 /* read in the lines of message text one by one */
3088 if (sock_getln(sock, buf, (sizeof buf - 3)) < 0) finished = 1;
3091 if (client_getln(buf, (sizeof buf - 3)) < 1) finished = 1;
3093 if (!strcmp(buf, terminator)) finished = 1;
3095 strcat(buf, "\r\n");
3101 /* Unescape SMTP-style input of two dots at the beginning of the line */
3103 if (!strncmp(buf, "..", 2)) {
3104 strcpy(buf, &buf[1]);
3108 if ( (!flushing) && (!finished) ) {
3109 /* Measure the line */
3110 linelen = strlen(buf);
3112 /* augment the buffer if we have to */
3113 if ((message_len + linelen) >= buffer_len) {
3114 ptr = realloc(m, (buffer_len * 2) );
3115 if (ptr == NULL) { /* flush if can't allocate */
3118 buffer_len = (buffer_len * 2);
3120 CtdlLogPrintf(CTDL_DEBUG, "buffer_len is now %ld\n", (long)buffer_len);
3124 /* Add the new line to the buffer. NOTE: this loop must avoid
3125 * using functions like strcat() and strlen() because they
3126 * traverse the entire buffer upon every call, and doing that
3127 * for a multi-megabyte message slows it down beyond usability.
3129 strcpy(&m[message_len], buf);
3130 message_len += linelen;
3133 /* if we've hit the max msg length, flush the rest */
3134 if (message_len >= maxlen) flushing = 1;
3136 } while (!finished);
3144 * Build a binary message to be saved on disk.
3145 * (NOTE: if you supply 'preformatted_text', the buffer you give it
3146 * will become part of the message. This means you are no longer
3147 * responsible for managing that memory -- it will be freed along with
3148 * the rest of the fields when CtdlFreeMessage() is called.)
3151 struct CtdlMessage *CtdlMakeMessage(
3152 struct ctdluser *author, /* author's user structure */
3153 char *recipient, /* NULL if it's not mail */
3154 char *recp_cc, /* NULL if it's not mail */
3155 char *room, /* room where it's going */
3156 int type, /* see MES_ types in header file */
3157 int format_type, /* variformat, plain text, MIME... */
3158 char *fake_name, /* who we're masquerading as */
3159 char *my_email, /* which of my email addresses to use (empty is ok) */
3160 char *subject, /* Subject (optional) */
3161 char *supplied_euid, /* ...or NULL if this is irrelevant */
3162 char *preformatted_text, /* ...or NULL to read text from client */
3163 char *references /* Thread references */
3165 char dest_node[256];
3167 struct CtdlMessage *msg;
3169 msg = malloc(sizeof(struct CtdlMessage));
3170 memset(msg, 0, sizeof(struct CtdlMessage));
3171 msg->cm_magic = CTDLMESSAGE_MAGIC;
3172 msg->cm_anon_type = type;
3173 msg->cm_format_type = format_type;
3175 /* Don't confuse the poor folks if it's not routed mail. */
3176 strcpy(dest_node, "");
3181 /* Path or Return-Path */
3182 if (my_email == NULL) my_email = "";
3184 if (!IsEmptyStr(my_email)) {
3185 msg->cm_fields['P'] = strdup(my_email);
3188 snprintf(buf, sizeof buf, "%s", author->fullname);
3189 msg->cm_fields['P'] = strdup(buf);
3191 convert_spaces_to_underscores(msg->cm_fields['P']);
3193 snprintf(buf, sizeof buf, "%ld", (long)time(NULL)); /* timestamp */
3194 msg->cm_fields['T'] = strdup(buf);
3196 if (fake_name[0]) /* author */
3197 msg->cm_fields['A'] = strdup(fake_name);
3199 msg->cm_fields['A'] = strdup(author->fullname);
3201 if (CC->room.QRflags & QR_MAILBOX) { /* room */
3202 msg->cm_fields['O'] = strdup(&CC->room.QRname[11]);
3205 msg->cm_fields['O'] = strdup(CC->room.QRname);
3208 msg->cm_fields['N'] = strdup(NODENAME); /* nodename */
3209 msg->cm_fields['H'] = strdup(HUMANNODE); /* hnodename */
3211 if (recipient[0] != 0) {
3212 msg->cm_fields['R'] = strdup(recipient);
3214 if (recp_cc[0] != 0) {
3215 msg->cm_fields['Y'] = strdup(recp_cc);
3217 if (dest_node[0] != 0) {
3218 msg->cm_fields['D'] = strdup(dest_node);
3221 if (!IsEmptyStr(my_email)) {
3222 msg->cm_fields['F'] = strdup(my_email);
3224 else if ( (author == &CC->user) && (!IsEmptyStr(CC->cs_inet_email)) ) {
3225 msg->cm_fields['F'] = strdup(CC->cs_inet_email);
3228 if (subject != NULL) {
3231 length = strlen(subject);
3237 while ((subject[i] != '\0') &&
3238 (IsAscii = isascii(subject[i]) != 0 ))
3241 msg->cm_fields['U'] = strdup(subject);
3242 else /* ok, we've got utf8 in the string. */
3244 msg->cm_fields['U'] = rfc2047encode(subject, length);
3250 if (supplied_euid != NULL) {
3251 msg->cm_fields['E'] = strdup(supplied_euid);
3254 if (references != NULL) {
3255 if (!IsEmptyStr(references)) {
3256 msg->cm_fields['W'] = strdup(references);
3260 if (preformatted_text != NULL) {
3261 msg->cm_fields['M'] = preformatted_text;
3264 msg->cm_fields['M'] = CtdlReadMessageBody("000", config.c_maxmsglen, NULL, 0, 0);
3272 * Check to see whether we have permission to post a message in the current
3273 * room. Returns a *CITADEL ERROR CODE* and puts a message in errmsgbuf, or
3274 * returns 0 on success.
3276 int CtdlDoIHavePermissionToPostInThisRoom(char *errmsgbuf,
3278 const char* RemoteIdentifier,
3282 if (!(CC->logged_in) &&
3283 (PostPublic == POST_LOGGED_IN)) {
3284 snprintf(errmsgbuf, n, "Not logged in.");
3285 return (ERROR + NOT_LOGGED_IN);
3287 else if (PostPublic == CHECK_EXISTANCE) {
3288 return (0); // We're Evaling whether a recipient exists
3290 else if (!(CC->logged_in)) {
3292 if ((CC->room.QRflags & QR_READONLY)) {
3293 snprintf(errmsgbuf, n, "Not logged in.");
3294 return (ERROR + NOT_LOGGED_IN);
3296 if (CC->room.QRflags2 & QR2_MODERATED) {
3297 snprintf(errmsgbuf, n, "Not logged in Moderation feature not yet implemented!");
3298 return (ERROR + NOT_LOGGED_IN);
3300 if ((PostPublic!=POST_LMTP) &&(CC->room.QRflags2 & QR2_SMTP_PUBLIC) == 0) {
3305 if (RemoteIdentifier == NULL)
3307 snprintf(errmsgbuf, n, "Need sender to permit access.");
3308 return (ERROR + USERNAME_REQUIRED);
3311 assoc_file_name(filename, sizeof filename, &CC->room, ctdl_netcfg_dir);
3312 begin_critical_section(S_NETCONFIGS);
3313 if (!read_spoolcontrol_file(&sc, filename))
3315 end_critical_section(S_NETCONFIGS);
3316 snprintf(errmsgbuf, n,
3317 "This mailing list only accepts posts from subscribers.");
3318 return (ERROR + NO_SUCH_USER);
3320 end_critical_section(S_NETCONFIGS);
3321 found = is_recipient (sc, RemoteIdentifier);
3322 free_spoolcontrol_struct(&sc);
3327 snprintf(errmsgbuf, n,
3328 "This mailing list only accepts posts from subscribers.");
3329 return (ERROR + NO_SUCH_USER);
3336 if ((CC->user.axlevel < 2)
3337 && ((CC->room.QRflags & QR_MAILBOX) == 0)) {
3338 snprintf(errmsgbuf, n, "Need to be validated to enter "
3339 "(except in %s> to sysop)", MAILROOM);
3340 return (ERROR + HIGHER_ACCESS_REQUIRED);
3343 CtdlRoomAccess(&CC->room, &CC->user, &ra, NULL);
3344 if (!(ra & UA_POSTALLOWED)) {
3345 snprintf(errmsgbuf, n, "Higher access is required to post in this room.");
3346 return (ERROR + HIGHER_ACCESS_REQUIRED);
3349 strcpy(errmsgbuf, "Ok");
3355 * Check to see if the specified user has Internet mail permission
3356 * (returns nonzero if permission is granted)
3358 int CtdlCheckInternetMailPermission(struct ctdluser *who) {
3360 /* Do not allow twits to send Internet mail */
3361 if (who->axlevel <= 2) return(0);
3363 /* Globally enabled? */
3364 if (config.c_restrict == 0) return(1);
3366 /* User flagged ok? */
3367 if (who->flags & US_INTERNET) return(2);
3369 /* Aide level access? */
3370 if (who->axlevel >= 6) return(3);
3372 /* No mail for you! */
3378 * Validate recipients, count delivery types and errors, and handle aliasing
3379 * FIXME check for dupes!!!!!
3381 * Returns 0 if all addresses are ok, ret->num_error = -1 if no addresses
3382 * were specified, or the number of addresses found invalid.
3384 * Caller needs to free the result using free_recipients()
3386 struct recptypes *validate_recipients(char *supplied_recipients,
3387 const char *RemoteIdentifier,
3389 struct recptypes *ret;
3390 char *recipients = NULL;
3391 char this_recp[256];
3392 char this_recp_cooked[256];
3398 struct ctdluser tempUS;
3399 struct ctdlroom tempQR;
3400 struct ctdlroom tempQR2;
3406 ret = (struct recptypes *) malloc(sizeof(struct recptypes));
3407 if (ret == NULL) return(NULL);
3409 /* Set all strings to null and numeric values to zero */
3410 memset(ret, 0, sizeof(struct recptypes));
3412 if (supplied_recipients == NULL) {
3413 recipients = strdup("");
3416 recipients = strdup(supplied_recipients);
3419 /* Allocate some memory. Yes, this allocates 500% more memory than we will
3420 * actually need, but it's healthier for the heap than doing lots of tiny
3421 * realloc() calls instead.
3424 ret->errormsg = malloc(strlen(recipients) + 1024);
3425 ret->recp_local = malloc(strlen(recipients) + 1024);
3426 ret->recp_internet = malloc(strlen(recipients) + 1024);
3427 ret->recp_ignet = malloc(strlen(recipients) + 1024);
3428 ret->recp_room = malloc(strlen(recipients) + 1024);
3429 ret->display_recp = malloc(strlen(recipients) + 1024);
3431 ret->errormsg[0] = 0;
3432 ret->recp_local[0] = 0;
3433 ret->recp_internet[0] = 0;
3434 ret->recp_ignet[0] = 0;
3435 ret->recp_room[0] = 0;
3436 ret->display_recp[0] = 0;
3438 ret->recptypes_magic = RECPTYPES_MAGIC;
3440 /* Change all valid separator characters to commas */
3441 for (i=0; !IsEmptyStr(&recipients[i]); ++i) {
3442 if ((recipients[i] == ';') || (recipients[i] == '|')) {
3443 recipients[i] = ',';
3447 /* Now start extracting recipients... */
3449 while (!IsEmptyStr(recipients)) {
3451 for (i=0; i<=strlen(recipients); ++i) {
3452 if (recipients[i] == '\"') in_quotes = 1 - in_quotes;
3453 if ( ( (recipients[i] == ',') && (!in_quotes) ) || (recipients[i] == 0) ) {
3454 safestrncpy(this_recp, recipients, i+1);
3456 if (recipients[i] == ',') {
3457 strcpy(recipients, &recipients[i+1]);
3460 strcpy(recipients, "");
3467 if (IsEmptyStr(this_recp))
3469 CtdlLogPrintf(CTDL_DEBUG, "Evaluating recipient #%d: %s\n", num_recps, this_recp);
3471 mailtype = alias(this_recp);
3472 mailtype = alias(this_recp);
3473 mailtype = alias(this_recp);
3475 for (j=0; !IsEmptyStr(&this_recp[j]); ++j) {
3476 if (this_recp[j]=='_') {
3477 this_recp_cooked[j] = ' ';
3480 this_recp_cooked[j] = this_recp[j];
3483 this_recp_cooked[j] = '\0';
3488 if (!strcasecmp(this_recp, "sysop")) {
3490 strcpy(this_recp, config.c_aideroom);
3491 if (!IsEmptyStr(ret->recp_room)) {
3492 strcat(ret->recp_room, "|");
3494 strcat(ret->recp_room, this_recp);
3496 else if ( (!strncasecmp(this_recp, "room_", 5))
3497 && (!getroom(&tempQR, &this_recp_cooked[5])) ) {
3499 /* Save room so we can restore it later */
3503 /* Check permissions to send mail to this room */
3504 err = CtdlDoIHavePermissionToPostInThisRoom(errmsg,
3516 if (!IsEmptyStr(ret->recp_room)) {
3517 strcat(ret->recp_room, "|");
3519 strcat(ret->recp_room, &this_recp_cooked[5]);
3522 /* Restore room in case something needs it */
3526 else if (getuser(&tempUS, this_recp) == 0) {
3528 strcpy(this_recp, tempUS.fullname);
3529 if (!IsEmptyStr(ret->recp_local)) {
3530 strcat(ret->recp_local, "|");
3532 strcat(ret->recp_local, this_recp);
3534 else if (getuser(&tempUS, this_recp_cooked) == 0) {
3536 strcpy(this_recp, tempUS.fullname);
3537 if (!IsEmptyStr(ret->recp_local)) {
3538 strcat(ret->recp_local, "|");
3540 strcat(ret->recp_local, this_recp);
3548 /* Yes, you're reading this correctly: if the target
3549 * domain points back to the local system or an attached
3550 * Citadel directory, the address is invalid. That's
3551 * because if the address were valid, we would have
3552 * already translated it to a local address by now.
3554 if (IsDirectory(this_recp, 0)) {
3559 ++ret->num_internet;
3560 if (!IsEmptyStr(ret->recp_internet)) {
3561 strcat(ret->recp_internet, "|");
3563 strcat(ret->recp_internet, this_recp);
3568 if (!IsEmptyStr(ret->recp_ignet)) {
3569 strcat(ret->recp_ignet, "|");
3571 strcat(ret->recp_ignet, this_recp);
3579 if (IsEmptyStr(errmsg)) {
3580 snprintf(append, sizeof append, "Invalid recipient: %s", this_recp);
3583 snprintf(append, sizeof append, "%s", errmsg);
3585 if ( (strlen(ret->errormsg) + strlen(append) + 3) < SIZ) {
3586 if (!IsEmptyStr(ret->errormsg)) {
3587 strcat(ret->errormsg, "; ");
3589 strcat(ret->errormsg, append);
3593 if (IsEmptyStr(ret->display_recp)) {
3594 strcpy(append, this_recp);
3597 snprintf(append, sizeof append, ", %s", this_recp);
3599 if ( (strlen(ret->display_recp)+strlen(append)) < SIZ) {
3600 strcat(ret->display_recp, append);
3605 if ((ret->num_local + ret->num_internet + ret->num_ignet +
3606 ret->num_room + ret->num_error) == 0) {
3607 ret->num_error = (-1);
3608 strcpy(ret->errormsg, "No recipients specified.");
3611 CtdlLogPrintf(CTDL_DEBUG, "validate_recipients()\n");
3612 CtdlLogPrintf(CTDL_DEBUG, " local: %d <%s>\n", ret->num_local, ret->recp_local);
3613 CtdlLogPrintf(CTDL_DEBUG, " room: %d <%s>\n", ret->num_room, ret->recp_room);
3614 CtdlLogPrintf(CTDL_DEBUG, " inet: %d <%s>\n", ret->num_internet, ret->recp_internet);
3615 CtdlLogPrintf(CTDL_DEBUG, " ignet: %d <%s>\n", ret->num_ignet, ret->recp_ignet);
3616 CtdlLogPrintf(CTDL_DEBUG, " error: %d <%s>\n", ret->num_error, ret->errormsg);
3624 * Destructor for struct recptypes
3626 void free_recipients(struct recptypes *valid) {
3628 if (valid == NULL) {
3632 if (valid->recptypes_magic != RECPTYPES_MAGIC) {
3633 CtdlLogPrintf(CTDL_EMERG, "Attempt to call free_recipients() on some other data type!\n");
3637 if (valid->errormsg != NULL) free(valid->errormsg);
3638 if (valid->recp_local != NULL) free(valid->recp_local);
3639 if (valid->recp_internet != NULL) free(valid->recp_internet);
3640 if (valid->recp_ignet != NULL) free(valid->recp_ignet);
3641 if (valid->recp_room != NULL) free(valid->recp_room);
3642 if (valid->display_recp != NULL) free(valid->display_recp);
3649 * message entry - mode 0 (normal)
3651 void cmd_ent0(char *entargs)
3657 char supplied_euid[128];
3659 int format_type = 0;
3660 char newusername[256];
3661 char newuseremail[256];
3662 struct CtdlMessage *msg;
3666 struct recptypes *valid = NULL;
3667 struct recptypes *valid_to = NULL;
3668 struct recptypes *valid_cc = NULL;
3669 struct recptypes *valid_bcc = NULL;
3671 int subject_required = 0;
3676 int newuseremail_ok = 0;
3677 char references[SIZ];
3682 post = extract_int(entargs, 0);
3683 extract_token(recp, entargs, 1, '|', sizeof recp);
3684 anon_flag = extract_int(entargs, 2);
3685 format_type = extract_int(entargs, 3);
3686 extract_token(subject, entargs, 4, '|', sizeof subject);
3687 extract_token(newusername, entargs, 5, '|', sizeof newusername);
3688 do_confirm = extract_int(entargs, 6);
3689 extract_token(cc, entargs, 7, '|', sizeof cc);
3690 extract_token(bcc, entargs, 8, '|', sizeof bcc);
3691 switch(CC->room.QRdefaultview) {
3694 extract_token(supplied_euid, entargs, 9, '|', sizeof supplied_euid);
3697 supplied_euid[0] = 0;
3700 extract_token(newuseremail, entargs, 10, '|', sizeof newuseremail);
3701 extract_token(references, entargs, 11, '|', sizeof references);
3702 for (ptr=references; *ptr != 0; ++ptr) {
3703 if (*ptr == '!') *ptr = '|';
3706 /* first check to make sure the request is valid. */
3708 err = CtdlDoIHavePermissionToPostInThisRoom(errmsg, sizeof errmsg, NULL, POST_LOGGED_IN);
3711 cprintf("%d %s\n", err, errmsg);
3715 /* Check some other permission type things. */
3717 if (IsEmptyStr(newusername)) {
3718 strcpy(newusername, CC->user.fullname);
3720 if ( (CC->user.axlevel < 6)
3721 && (strcasecmp(newusername, CC->user.fullname))
3722 && (strcasecmp(newusername, CC->cs_inet_fn))
3724 cprintf("%d You don't have permission to author messages as '%s'.\n",
3725 ERROR + HIGHER_ACCESS_REQUIRED,
3732 if (IsEmptyStr(newuseremail)) {
3733 newuseremail_ok = 1;
3736 if (!IsEmptyStr(newuseremail)) {
3737 if (!strcasecmp(newuseremail, CC->cs_inet_email)) {
3738 newuseremail_ok = 1;
3740 else if (!IsEmptyStr(CC->cs_inet_other_emails)) {
3741 j = num_tokens(CC->cs_inet_other_emails, '|');
3742 for (i=0; i<j; ++i) {
3743 extract_token(buf, CC->cs_inet_other_emails, i, '|', sizeof buf);
3744 if (!strcasecmp(newuseremail, buf)) {
3745 newuseremail_ok = 1;
3751 if (!newuseremail_ok) {
3752 cprintf("%d You don't have permission to author messages as '%s'.\n",
3753 ERROR + HIGHER_ACCESS_REQUIRED,
3759 CC->cs_flags |= CS_POSTING;
3761 /* In mailbox rooms we have to behave a little differently --
3762 * make sure the user has specified at least one recipient. Then
3763 * validate the recipient(s). We do this for the Mail> room, as
3764 * well as any room which has the "Mailbox" view set.
3767 if ( ( (CC->room.QRflags & QR_MAILBOX) && (!strcasecmp(&CC->room.QRname[11], MAILROOM)) )
3768 || ( (CC->room.QRflags & QR_MAILBOX) && (CC->curr_view == VIEW_MAILBOX) )
3770 if (CC->user.axlevel < 2) {
3771 strcpy(recp, "sysop");
3776 valid_to = validate_recipients(recp, NULL, 0);
3777 if (valid_to->num_error > 0) {
3778 cprintf("%d %s\n", ERROR + NO_SUCH_USER, valid_to->errormsg);
3779 free_recipients(valid_to);
3783 valid_cc = validate_recipients(cc, NULL, 0);
3784 if (valid_cc->num_error > 0) {
3785 cprintf("%d %s\n", ERROR + NO_SUCH_USER, valid_cc->errormsg);
3786 free_recipients(valid_to);
3787 free_recipients(valid_cc);
3791 valid_bcc = validate_recipients(bcc, NULL, 0);
3792 if (valid_bcc->num_error > 0) {
3793 cprintf("%d %s\n", ERROR + NO_SUCH_USER, valid_bcc->errormsg);
3794 free_recipients(valid_to);
3795 free_recipients(valid_cc);
3796 free_recipients(valid_bcc);
3800 /* Recipient required, but none were specified */
3801 if ( (valid_to->num_error < 0) && (valid_cc->num_error < 0) && (valid_bcc->num_error < 0) ) {
3802 free_recipients(valid_to);
3803 free_recipients(valid_cc);
3804 free_recipients(valid_bcc);
3805 cprintf("%d At least one recipient is required.\n", ERROR + NO_SUCH_USER);
3809 if (valid_to->num_internet + valid_cc->num_internet + valid_bcc->num_internet > 0) {
3810 if (CtdlCheckInternetMailPermission(&CC->user)==0) {
3811 cprintf("%d You do not have permission "
3812 "to send Internet mail.\n",
3813 ERROR + HIGHER_ACCESS_REQUIRED);
3814 free_recipients(valid_to);
3815 free_recipients(valid_cc);
3816 free_recipients(valid_bcc);
3821 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)
3822 && (CC->user.axlevel < 4) ) {
3823 cprintf("%d Higher access required for network mail.\n",
3824 ERROR + HIGHER_ACCESS_REQUIRED);
3825 free_recipients(valid_to);
3826 free_recipients(valid_cc);
3827 free_recipients(valid_bcc);
3831 if ((RESTRICT_INTERNET == 1)
3832 && (valid_to->num_internet + valid_cc->num_internet + valid_bcc->num_internet > 0)
3833 && ((CC->user.flags & US_INTERNET) == 0)
3834 && (!CC->internal_pgm)) {
3835 cprintf("%d You don't have access to Internet mail.\n",
3836 ERROR + HIGHER_ACCESS_REQUIRED);
3837 free_recipients(valid_to);
3838 free_recipients(valid_cc);
3839 free_recipients(valid_bcc);
3845 /* Is this a room which has anonymous-only or anonymous-option? */
3846 anonymous = MES_NORMAL;
3847 if (CC->room.QRflags & QR_ANONONLY) {
3848 anonymous = MES_ANONONLY;
3850 if (CC->room.QRflags & QR_ANONOPT) {
3851 if (anon_flag == 1) { /* only if the user requested it */
3852 anonymous = MES_ANONOPT;
3856 if ((CC->room.QRflags & QR_MAILBOX) == 0) {
3860 /* Recommend to the client that the use of a message subject is
3861 * strongly recommended in this room, if either the SUBJECTREQ flag
3862 * is set, or if there is one or more Internet email recipients.
3864 if (CC->room.QRflags2 & QR2_SUBJECTREQ) subject_required = 1;
3865 if (valid_to) if (valid_to->num_internet > 0) subject_required = 1;
3866 if (valid_cc) if (valid_cc->num_internet > 0) subject_required = 1;
3867 if (valid_bcc) if (valid_bcc->num_internet > 0) subject_required = 1;
3869 /* If we're only checking the validity of the request, return
3870 * success without creating the message.
3873 cprintf("%d %s|%d\n", CIT_OK,
3874 ((valid_to != NULL) ? valid_to->display_recp : ""),
3876 free_recipients(valid_to);
3877 free_recipients(valid_cc);
3878 free_recipients(valid_bcc);
3882 /* We don't need these anymore because we'll do it differently below */
3883 free_recipients(valid_to);
3884 free_recipients(valid_cc);
3885 free_recipients(valid_bcc);
3887 /* Read in the message from the client. */
3889 cprintf("%d send message\n", START_CHAT_MODE);
3891 cprintf("%d send message\n", SEND_LISTING);
3894 msg = CtdlMakeMessage(&CC->user, recp, cc,
3895 CC->room.QRname, anonymous, format_type,
3896 newusername, newuseremail, subject,
3897 ((!IsEmptyStr(supplied_euid)) ? supplied_euid : NULL),
3900 /* Put together one big recipients struct containing to/cc/bcc all in
3901 * one. This is for the envelope.
3903 char *all_recps = malloc(SIZ * 3);
3904 strcpy(all_recps, recp);
3905 if (!IsEmptyStr(cc)) {
3906 if (!IsEmptyStr(all_recps)) {
3907 strcat(all_recps, ",");
3909 strcat(all_recps, cc);
3911 if (!IsEmptyStr(bcc)) {
3912 if (!IsEmptyStr(all_recps)) {
3913 strcat(all_recps, ",");
3915 strcat(all_recps, bcc);
3917 if (!IsEmptyStr(all_recps)) {
3918 valid = validate_recipients(all_recps, NULL, 0);
3926 msgnum = CtdlSubmitMsg(msg, valid, "", QP_EADDR);
3929 cprintf("%ld\n", msgnum);
3931 cprintf("Message accepted.\n");
3934 cprintf("Internal error.\n");
3936 if (msg->cm_fields['E'] != NULL) {
3937 cprintf("%s\n", msg->cm_fields['E']);
3944 CtdlFreeMessage(msg);
3946 if (valid != NULL) {
3947 free_recipients(valid);
3955 * API function to delete messages which match a set of criteria
3956 * (returns the actual number of messages deleted)
3958 int CtdlDeleteMessages(char *room_name, /* which room */
3959 long *dmsgnums, /* array of msg numbers to be deleted */
3960 int num_dmsgnums, /* number of msgs to be deleted, or 0 for "any" */
3961 char *content_type /* or "" for any. regular expressions expected. */
3964 struct ctdlroom qrbuf;
3965 struct cdbdata *cdbfr;
3966 long *msglist = NULL;
3967 long *dellist = NULL;
3970 int num_deleted = 0;
3972 struct MetaData smi;
3975 int need_to_free_re = 0;
3977 if (content_type) if (!IsEmptyStr(content_type)) {
3978 regcomp(&re, content_type, 0);
3979 need_to_free_re = 1;
3981 CtdlLogPrintf(CTDL_DEBUG, "CtdlDeleteMessages(%s, %d msgs, %s)\n",
3982 room_name, num_dmsgnums, content_type);
3984 /* get room record, obtaining a lock... */
3985 if (lgetroom(&qrbuf, room_name) != 0) {
3986 CtdlLogPrintf(CTDL_ERR, "CtdlDeleteMessages(): Room <%s> not found\n",
3988 if (need_to_free_re) regfree(&re);
3989 return (0); /* room not found */
3991 cdbfr = cdb_fetch(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long));
3993 if (cdbfr != NULL) {
3994 dellist = malloc(cdbfr->len);
3995 msglist = (long *) cdbfr->ptr;
3996 cdbfr->ptr = NULL; /* CtdlDeleteMessages() now owns this memory */
3997 num_msgs = cdbfr->len / sizeof(long);
4001 for (i = 0; i < num_msgs; ++i) {
4004 /* Set/clear a bit for each criterion */
4006 /* 0 messages in the list or a null list means that we are
4007 * interested in deleting any messages which meet the other criteria.
4009 if ((num_dmsgnums == 0) || (dmsgnums == NULL)) {
4010 delete_this |= 0x01;
4013 for (j=0; j<num_dmsgnums; ++j) {
4014 if (msglist[i] == dmsgnums[j]) {
4015 delete_this |= 0x01;
4020 if (IsEmptyStr(content_type)) {
4021 delete_this |= 0x02;
4023 GetMetaData(&smi, msglist[i]);
4024 if (regexec(&re, smi.meta_content_type, 1, &pm, 0) == 0) {
4025 delete_this |= 0x02;
4029 /* Delete message only if all bits are set */
4030 if (delete_this == 0x03) {
4031 dellist[num_deleted++] = msglist[i];
4036 num_msgs = sort_msglist(msglist, num_msgs);
4037 cdb_store(CDB_MSGLISTS, &qrbuf.QRnumber, (int)sizeof(long),
4038 msglist, (int)(num_msgs * sizeof(long)));
4040 qrbuf.QRhighest = msglist[num_msgs - 1];
4044 /* Go through the messages we pulled out of the index, and decrement
4045 * their reference counts by 1. If this is the only room the message
4046 * was in, the reference count will reach zero and the message will
4047 * automatically be deleted from the database. We do this in a
4048 * separate pass because there might be plug-in hooks getting called,
4049 * and we don't want that happening during an S_ROOMS critical
4052 if (num_deleted) for (i=0; i<num_deleted; ++i) {
4053 PerformDeleteHooks(qrbuf.QRname, dellist[i]);
4054 AdjRefCount(dellist[i], -1);
4057 /* Now free the memory we used, and go away. */
4058 if (msglist != NULL) free(msglist);
4059 if (dellist != NULL) free(dellist);
4060 CtdlLogPrintf(CTDL_DEBUG, "%d message(s) deleted.\n", num_deleted);
4061 if (need_to_free_re) regfree(&re);
4062 return (num_deleted);
4068 * Check whether the current user has permission to delete messages from
4069 * the current room (returns 1 for yes, 0 for no)
4071 int CtdlDoIHavePermissionToDeleteMessagesFromThisRoom(void) {
4073 CtdlRoomAccess(&CC->room, &CC->user, &ra, NULL);
4074 if (ra & UA_DELETEALLOWED) return(1);
4082 * Delete message from current room
4084 void cmd_dele(char *args)
4093 extract_token(msgset, args, 0, '|', sizeof msgset);
4094 num_msgs = num_tokens(msgset, ',');
4096 cprintf("%d Nothing to do.\n", CIT_OK);
4100 if (CtdlDoIHavePermissionToDeleteMessagesFromThisRoom() == 0) {
4101 cprintf("%d Higher access required.\n",
4102 ERROR + HIGHER_ACCESS_REQUIRED);
4107 * Build our message set to be moved/copied
4109 msgs = malloc(num_msgs * sizeof(long));
4110 for (i=0; i<num_msgs; ++i) {
4111 extract_token(msgtok, msgset, i, ',', sizeof msgtok);
4112 msgs[i] = atol(msgtok);
4115 num_deleted = CtdlDeleteMessages(CC->room.QRname, msgs, num_msgs, "");
4119 cprintf("%d %d message%s deleted.\n", CIT_OK,
4120 num_deleted, ((num_deleted != 1) ? "s" : ""));
4122 cprintf("%d Message not found.\n", ERROR + MESSAGE_NOT_FOUND);
4128 * Back end API function for moves and deletes (multiple messages)
4130 int CtdlCopyMsgsToRoom(long *msgnums, int num_msgs, char *dest) {
4133 err = CtdlSaveMsgPointersInRoom(dest, msgnums, num_msgs, 1, NULL);
4134 if (err != 0) return(err);
4143 * move or copy a message to another room
4145 void cmd_move(char *args)
4152 char targ[ROOMNAMELEN];
4153 struct ctdlroom qtemp;
4160 extract_token(msgset, args, 0, '|', sizeof msgset);
4161 num_msgs = num_tokens(msgset, ',');
4163 cprintf("%d Nothing to do.\n", CIT_OK);
4167 extract_token(targ, args, 1, '|', sizeof targ);
4168 convert_room_name_macros(targ, sizeof targ);
4169 targ[ROOMNAMELEN - 1] = 0;
4170 is_copy = extract_int(args, 2);
4172 if (getroom(&qtemp, targ) != 0) {
4173 cprintf("%d '%s' does not exist.\n",
4174 ERROR + ROOM_NOT_FOUND, targ);
4178 getuser(&CC->user, CC->curr_user);
4179 CtdlRoomAccess(&qtemp, &CC->user, &ra, NULL);
4181 /* Check for permission to perform this operation.
4182 * Remember: "CC->room" is source, "qtemp" is target.
4186 /* Aides can move/copy */
4187 if (CC->user.axlevel >= 6) permit = 1;
4189 /* Room aides can move/copy */
4190 if (CC->user.usernum == CC->room.QRroomaide) permit = 1;
4192 /* Permit move/copy from personal rooms */
4193 if ((CC->room.QRflags & QR_MAILBOX)
4194 && (qtemp.QRflags & QR_MAILBOX)) permit = 1;
4196 /* Permit only copy from public to personal room */
4198 && (!(CC->room.QRflags & QR_MAILBOX))
4199 && (qtemp.QRflags & QR_MAILBOX)) permit = 1;
4201 /* Permit message removal from collaborative delete rooms */
4202 if (CC->room.QRflags2 & QR2_COLLABDEL) permit = 1;
4204 /* Users allowed to post into the target room may move into it too. */
4205 if ((CC->room.QRflags & QR_MAILBOX) &&
4206 (qtemp.QRflags & UA_POSTALLOWED)) permit = 1;
4208 /* User must have access to target room */
4209 if (!(ra & UA_KNOWN)) permit = 0;
4212 cprintf("%d Higher access required.\n",
4213 ERROR + HIGHER_ACCESS_REQUIRED);
4218 * Build our message set to be moved/copied
4220 msgs = malloc(num_msgs * sizeof(long));
4221 for (i=0; i<num_msgs; ++i) {
4222 extract_token(msgtok, msgset, i, ',', sizeof msgtok);
4223 msgs[i] = atol(msgtok);
4229 err = CtdlCopyMsgsToRoom(msgs, num_msgs, targ);
4231 cprintf("%d Cannot store message(s) in %s: error %d\n",
4237 /* Now delete the message from the source room,
4238 * if this is a 'move' rather than a 'copy' operation.
4241 CtdlDeleteMessages(CC->room.QRname, msgs, num_msgs, "");
4245 cprintf("%d Message(s) %s.\n", CIT_OK, (is_copy ? "copied" : "moved") );
4251 * GetMetaData() - Get the supplementary record for a message
4253 void GetMetaData(struct MetaData *smibuf, long msgnum)
4256 struct cdbdata *cdbsmi;
4259 memset(smibuf, 0, sizeof(struct MetaData));
4260 smibuf->meta_msgnum = msgnum;
4261 smibuf->meta_refcount = 1; /* Default reference count is 1 */
4263 /* Use the negative of the message number for its supp record index */
4264 TheIndex = (0L - msgnum);
4266 cdbsmi = cdb_fetch(CDB_MSGMAIN, &TheIndex, sizeof(long));
4267 if (cdbsmi == NULL) {
4268 return; /* record not found; go with defaults */
4270 memcpy(smibuf, cdbsmi->ptr,
4271 ((cdbsmi->len > sizeof(struct MetaData)) ?
4272 sizeof(struct MetaData) : cdbsmi->len));
4279 * PutMetaData() - (re)write supplementary record for a message
4281 void PutMetaData(struct MetaData *smibuf)
4285 /* Use the negative of the message number for the metadata db index */
4286 TheIndex = (0L - smibuf->meta_msgnum);
4288 cdb_store(CDB_MSGMAIN,
4289 &TheIndex, (int)sizeof(long),
4290 smibuf, (int)sizeof(struct MetaData));
4295 * AdjRefCount - submit an adjustment to the reference count for a message.
4296 * (These are just queued -- we actually process them later.)
4298 void AdjRefCount(long msgnum, int incr)
4300 struct arcq new_arcq;
4302 begin_critical_section(S_SUPPMSGMAIN);
4303 if (arcfp == NULL) {
4304 arcfp = fopen(file_arcq, "ab+");
4306 end_critical_section(S_SUPPMSGMAIN);
4308 /* msgnum < 0 means that we're trying to close the file */
4310 CtdlLogPrintf(CTDL_DEBUG, "Closing the AdjRefCount queue file\n");
4311 begin_critical_section(S_SUPPMSGMAIN);
4312 if (arcfp != NULL) {
4316 end_critical_section(S_SUPPMSGMAIN);
4321 * If we can't open the queue, perform the operation synchronously.
4323 if (arcfp == NULL) {
4324 TDAP_AdjRefCount(msgnum, incr);
4328 new_arcq.arcq_msgnum = msgnum;
4329 new_arcq.arcq_delta = incr;
4330 fwrite(&new_arcq, sizeof(struct arcq), 1, arcfp);
4338 * TDAP_ProcessAdjRefCountQueue()
4340 * Process the queue of message count adjustments that was created by calls
4341 * to AdjRefCount() ... by reading the queue and calling TDAP_AdjRefCount()
4342 * for each one. This should be an "off hours" operation.
4344 int TDAP_ProcessAdjRefCountQueue(void)
4346 char file_arcq_temp[PATH_MAX];
4349 struct arcq arcq_rec;
4350 int num_records_processed = 0;
4352 snprintf(file_arcq_temp, sizeof file_arcq_temp, "%s.%04x", file_arcq, rand());
4354 begin_critical_section(S_SUPPMSGMAIN);
4355 if (arcfp != NULL) {
4360 r = link(file_arcq, file_arcq_temp);
4362 CtdlLogPrintf(CTDL_CRIT, "%s: %s\n", file_arcq_temp, strerror(errno));
4363 end_critical_section(S_SUPPMSGMAIN);
4364 return(num_records_processed);
4368 end_critical_section(S_SUPPMSGMAIN);
4370 fp = fopen(file_arcq_temp, "rb");
4372 CtdlLogPrintf(CTDL_CRIT, "%s: %s\n", file_arcq_temp, strerror(errno));
4373 return(num_records_processed);
4376 while (fread(&arcq_rec, sizeof(struct arcq), 1, fp) == 1) {
4377 TDAP_AdjRefCount(arcq_rec.arcq_msgnum, arcq_rec.arcq_delta);
4378 ++num_records_processed;
4382 r = unlink(file_arcq_temp);
4384 CtdlLogPrintf(CTDL_CRIT, "%s: %s\n", file_arcq_temp, strerror(errno));
4387 return(num_records_processed);
4393 * TDAP_AdjRefCount - adjust the reference count for a message.
4394 * This one does it "for real" because it's called by
4395 * the autopurger function that processes the queue
4396 * created by AdjRefCount(). If a message's reference
4397 * count becomes zero, we also delete the message from
4398 * disk and de-index it.
4400 void TDAP_AdjRefCount(long msgnum, int incr)
4403 struct MetaData smi;
4406 /* This is a *tight* critical section; please keep it that way, as
4407 * it may get called while nested in other critical sections.
4408 * Complicating this any further will surely cause deadlock!
4410 begin_critical_section(S_SUPPMSGMAIN);
4411 GetMetaData(&smi, msgnum);
4412 smi.meta_refcount += incr;
4414 end_critical_section(S_SUPPMSGMAIN);
4415 CtdlLogPrintf(CTDL_DEBUG, "msg %ld ref count delta %+d, is now %d\n",
4416 msgnum, incr, smi.meta_refcount);
4418 /* If the reference count is now zero, delete the message
4419 * (and its supplementary record as well).
4421 if (smi.meta_refcount == 0) {
4422 CtdlLogPrintf(CTDL_DEBUG, "Deleting message <%ld>\n", msgnum);
4424 /* Call delete hooks with NULL room to show it has gone altogether */
4425 PerformDeleteHooks(NULL, msgnum);
4427 /* Remove from message base */
4429 cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long));
4430 cdb_delete(CDB_BIGMSGS, &delnum, (int)sizeof(long));
4432 /* Remove metadata record */
4433 delnum = (0L - msgnum);
4434 cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long));
4440 * Write a generic object to this room
4442 * Note: this could be much more efficient. Right now we use two temporary
4443 * files, and still pull the message into memory as with all others.
4445 void CtdlWriteObject(char *req_room, /* Room to stuff it in */
4446 char *content_type, /* MIME type of this object */
4447 char *raw_message, /* Data to be written */
4448 off_t raw_length, /* Size of raw_message */
4449 struct ctdluser *is_mailbox, /* Mailbox room? */
4450 int is_binary, /* Is encoding necessary? */
4451 int is_unique, /* Del others of this type? */
4452 unsigned int flags /* Internal save flags */
4456 struct ctdlroom qrbuf;
4457 char roomname[ROOMNAMELEN];
4458 struct CtdlMessage *msg;
4459 char *encoded_message = NULL;
4461 if (is_mailbox != NULL) {
4462 MailboxName(roomname, sizeof roomname, is_mailbox, req_room);
4465 safestrncpy(roomname, req_room, sizeof(roomname));
4468 CtdlLogPrintf(CTDL_DEBUG, "Raw length is %ld\n", (long)raw_length);
4471 encoded_message = malloc((size_t) (((raw_length * 134) / 100) + 4096 ) );
4474 encoded_message = malloc((size_t)(raw_length + 4096));
4477 sprintf(encoded_message, "Content-type: %s\n", content_type);
4480 sprintf(&encoded_message[strlen(encoded_message)],
4481 "Content-transfer-encoding: base64\n\n"
4485 sprintf(&encoded_message[strlen(encoded_message)],
4486 "Content-transfer-encoding: 7bit\n\n"
4492 &encoded_message[strlen(encoded_message)],
4500 &encoded_message[strlen(encoded_message)],
4506 CtdlLogPrintf(CTDL_DEBUG, "Allocating\n");
4507 msg = malloc(sizeof(struct CtdlMessage));
4508 memset(msg, 0, sizeof(struct CtdlMessage));
4509 msg->cm_magic = CTDLMESSAGE_MAGIC;
4510 msg->cm_anon_type = MES_NORMAL;
4511 msg->cm_format_type = 4;
4512 msg->cm_fields['A'] = strdup(CC->user.fullname);
4513 msg->cm_fields['O'] = strdup(req_room);
4514 msg->cm_fields['N'] = strdup(config.c_nodename);
4515 msg->cm_fields['H'] = strdup(config.c_humannode);
4516 msg->cm_flags = flags;
4518 msg->cm_fields['M'] = encoded_message;
4520 /* Create the requested room if we have to. */
4521 if (getroom(&qrbuf, roomname) != 0) {
4522 create_room(roomname,
4523 ( (is_mailbox != NULL) ? 5 : 3 ),
4524 "", 0, 1, 0, VIEW_BBS);
4526 /* If the caller specified this object as unique, delete all
4527 * other objects of this type that are currently in the room.
4530 CtdlLogPrintf(CTDL_DEBUG, "Deleted %d other msgs of this type\n",
4531 CtdlDeleteMessages(roomname, NULL, 0, content_type)
4534 /* Now write the data */
4535 CtdlSubmitMsg(msg, NULL, roomname, 0);
4536 CtdlFreeMessage(msg);
4544 void CtdlGetSysConfigBackend(long msgnum, void *userdata) {
4545 config_msgnum = msgnum;
4549 char *CtdlGetSysConfig(char *sysconfname) {
4550 char hold_rm[ROOMNAMELEN];
4553 struct CtdlMessage *msg;
4556 strcpy(hold_rm, CC->room.QRname);
4557 if (getroom(&CC->room, SYSCONFIGROOM) != 0) {
4558 getroom(&CC->room, hold_rm);
4563 /* We want the last (and probably only) config in this room */
4564 begin_critical_section(S_CONFIG);
4565 config_msgnum = (-1L);
4566 CtdlForEachMessage(MSGS_LAST, 1, NULL, sysconfname, NULL,
4567 CtdlGetSysConfigBackend, NULL);
4568 msgnum = config_msgnum;
4569 end_critical_section(S_CONFIG);
4575 msg = CtdlFetchMessage(msgnum, 1);
4577 conf = strdup(msg->cm_fields['M']);
4578 CtdlFreeMessage(msg);
4585 getroom(&CC->room, hold_rm);
4587 if (conf != NULL) do {
4588 extract_token(buf, conf, 0, '\n', sizeof buf);
4589 strcpy(conf, &conf[strlen(buf)+1]);
4590 } while ( (!IsEmptyStr(conf)) && (!IsEmptyStr(buf)) );
4596 void CtdlPutSysConfig(char *sysconfname, char *sysconfdata) {
4597 CtdlWriteObject(SYSCONFIGROOM, sysconfname, sysconfdata, (strlen(sysconfdata)+1), NULL, 0, 1, 0);
4602 * Determine whether a given Internet address belongs to the current user
4604 int CtdlIsMe(char *addr, int addr_buf_len)
4606 struct recptypes *recp;
4609 recp = validate_recipients(addr, NULL, 0);
4610 if (recp == NULL) return(0);
4612 if (recp->num_local == 0) {
4613 free_recipients(recp);
4617 for (i=0; i<recp->num_local; ++i) {
4618 extract_token(addr, recp->recp_local, i, '|', addr_buf_len);
4619 if (!strcasecmp(addr, CC->user.fullname)) {
4620 free_recipients(recp);
4625 free_recipients(recp);
4631 * Citadel protocol command to do the same
4633 void cmd_isme(char *argbuf) {
4636 if (CtdlAccessCheck(ac_logged_in)) return;
4637 extract_token(addr, argbuf, 0, '|', sizeof addr);
4639 if (CtdlIsMe(addr, sizeof addr)) {
4640 cprintf("%d %s\n", CIT_OK, addr);
4643 cprintf("%d Not you.\n", ERROR + ILLEGAL_VALUE);