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,
110 * This function is self explanatory.
111 * (What can I say, I'm in a weird mood today...)
113 void remove_any_whitespace_to_the_left_or_right_of_at_symbol(char *name)
117 for (i = 0; i < strlen(name); ++i) {
118 if (name[i] == '@') {
119 while (isspace(name[i - 1]) && i > 0) {
120 strcpy(&name[i - 1], &name[i]);
123 while (isspace(name[i + 1])) {
124 strcpy(&name[i + 1], &name[i + 2]);
132 * Aliasing for network mail.
133 * (Error messages have been commented out, because this is a server.)
135 int alias(char *name)
136 { /* process alias and routing info for mail */
139 char aaa[SIZ], bbb[SIZ];
140 char *ignetcfg = NULL;
141 char *ignetmap = NULL;
147 char original_name[256];
148 safestrncpy(original_name, name, sizeof original_name);
151 remove_any_whitespace_to_the_left_or_right_of_at_symbol(name);
152 stripallbut(name, '<', '>');
154 fp = fopen(file_mail_aliases, "r");
156 fp = fopen("/dev/null", "r");
163 while (fgets(aaa, sizeof aaa, fp) != NULL) {
164 while (isspace(name[0]))
165 strcpy(name, &name[1]);
166 aaa[strlen(aaa) - 1] = 0;
168 for (a = 0; a < strlen(aaa); ++a) {
170 strcpy(bbb, &aaa[a + 1]);
174 if (!strcasecmp(name, aaa))
179 /* Hit the Global Address Book */
180 if (CtdlDirectoryLookup(aaa, name, sizeof aaa) == 0) {
184 if (strcasecmp(original_name, name)) {
185 CtdlLogPrintf(CTDL_INFO, "%s is being forwarded to %s\n", original_name, name);
188 /* Change "user @ xxx" to "user" if xxx is an alias for this host */
189 for (a=0; a<strlen(name); ++a) {
190 if (name[a] == '@') {
191 if (CtdlHostAlias(&name[a+1]) == hostalias_localhost) {
193 CtdlLogPrintf(CTDL_INFO, "Changed to <%s>\n", name);
198 /* determine local or remote type, see citadel.h */
199 at = haschar(name, '@');
200 if (at == 0) return(MES_LOCAL); /* no @'s - local address */
201 if (at > 1) return(MES_ERROR); /* >1 @'s - invalid address */
202 remove_any_whitespace_to_the_left_or_right_of_at_symbol(name);
204 /* figure out the delivery mode */
205 extract_token(node, name, 1, '@', sizeof node);
207 /* If there are one or more dots in the nodename, we assume that it
208 * is an FQDN and will attempt SMTP delivery to the Internet.
210 if (haschar(node, '.') > 0) {
211 return(MES_INTERNET);
214 /* Otherwise we look in the IGnet maps for a valid Citadel node.
215 * Try directly-connected nodes first...
217 ignetcfg = CtdlGetSysConfig(IGNETCFG);
218 for (i=0; i<num_tokens(ignetcfg, '\n'); ++i) {
219 extract_token(buf, ignetcfg, i, '\n', sizeof buf);
220 extract_token(testnode, buf, 0, '|', sizeof testnode);
221 if (!strcasecmp(node, testnode)) {
229 * Then try nodes that are two or more hops away.
231 ignetmap = CtdlGetSysConfig(IGNETMAP);
232 for (i=0; i<num_tokens(ignetmap, '\n'); ++i) {
233 extract_token(buf, ignetmap, i, '\n', sizeof buf);
234 extract_token(testnode, buf, 0, '|', sizeof testnode);
235 if (!strcasecmp(node, testnode)) {
242 /* If we get to this point it's an invalid node name */
248 * Back end for the MSGS command: output message number only.
250 void simple_listing(long msgnum, void *userdata)
252 cprintf("%ld\n", msgnum);
258 * Back end for the MSGS command: output header summary.
260 void headers_listing(long msgnum, void *userdata)
262 struct CtdlMessage *msg;
264 msg = CtdlFetchMessage(msgnum, 0);
266 cprintf("%ld|0|||||\n", msgnum);
270 cprintf("%ld|%s|%s|%s|%s|%s|\n",
272 (msg->cm_fields['T'] ? msg->cm_fields['T'] : "0"),
273 (msg->cm_fields['A'] ? msg->cm_fields['A'] : ""),
274 (msg->cm_fields['N'] ? msg->cm_fields['N'] : ""),
275 (msg->cm_fields['F'] ? msg->cm_fields['F'] : ""),
276 (msg->cm_fields['U'] ? msg->cm_fields['U'] : "")
278 CtdlFreeMessage(msg);
283 /* Determine if a given message matches the fields in a message template.
284 * Return 0 for a successful match.
286 int CtdlMsgCmp(struct CtdlMessage *msg, struct CtdlMessage *template) {
289 /* If there aren't any fields in the template, all messages will
292 if (template == NULL) return(0);
294 /* Null messages are bogus. */
295 if (msg == NULL) return(1);
297 for (i='A'; i<='Z'; ++i) {
298 if (template->cm_fields[i] != NULL) {
299 if (msg->cm_fields[i] == NULL) {
302 if (strcasecmp(msg->cm_fields[i],
303 template->cm_fields[i])) return 1;
307 /* All compares succeeded: we have a match! */
314 * Retrieve the "seen" message list for the current room.
316 void CtdlGetSeen(char *buf, int which_set) {
319 /* Learn about the user and room in question */
320 CtdlGetRelationship(&vbuf, &CC->user, &CC->room);
322 if (which_set == ctdlsetseen_seen)
323 safestrncpy(buf, vbuf.v_seen, SIZ);
324 if (which_set == ctdlsetseen_answered)
325 safestrncpy(buf, vbuf.v_answered, SIZ);
331 * Manipulate the "seen msgs" string (or other message set strings)
333 void CtdlSetSeen(long *target_msgnums, int num_target_msgnums,
334 int target_setting, int which_set,
335 struct ctdluser *which_user, struct ctdlroom *which_room) {
336 struct cdbdata *cdbfr;
348 char *is_set; /* actually an array of booleans */
351 char setstr[SIZ], lostr[SIZ], histr[SIZ];
354 /* Don't bother doing *anything* if we were passed a list of zero messages */
355 if (num_target_msgnums < 1) {
359 CtdlLogPrintf(CTDL_DEBUG, "CtdlSetSeen(%d msgs starting with %ld, %d, %d)\n",
360 num_target_msgnums, target_msgnums[0],
361 target_setting, which_set);
363 /* Learn about the user and room in question */
364 CtdlGetRelationship(&vbuf,
365 ((which_user != NULL) ? which_user : &CC->user),
366 ((which_room != NULL) ? which_room : &CC->room)
369 /* Load the message list */
370 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long));
372 msglist = (long *) cdbfr->ptr;
373 cdbfr->ptr = NULL; /* CtdlSetSeen() now owns this memory */
374 num_msgs = cdbfr->len / sizeof(long);
377 return; /* No messages at all? No further action. */
380 is_set = malloc(num_msgs * sizeof(char));
381 memset(is_set, 0, (num_msgs * sizeof(char)) );
383 /* Decide which message set we're manipulating */
385 case ctdlsetseen_seen:
386 safestrncpy(vset, vbuf.v_seen, sizeof vset);
388 case ctdlsetseen_answered:
389 safestrncpy(vset, vbuf.v_answered, sizeof vset);
393 /* CtdlLogPrintf(CTDL_DEBUG, "before optimize: %s\n", vset); */
395 /* Translate the existing sequence set into an array of booleans */
396 num_sets = num_tokens(vset, ',');
397 for (s=0; s<num_sets; ++s) {
398 extract_token(setstr, vset, s, ',', sizeof setstr);
400 extract_token(lostr, setstr, 0, ':', sizeof lostr);
401 if (num_tokens(setstr, ':') >= 2) {
402 extract_token(histr, setstr, 1, ':', sizeof histr);
403 if (!strcmp(histr, "*")) {
404 snprintf(histr, sizeof histr, "%ld", LONG_MAX);
408 strcpy(histr, lostr);
413 for (i = 0; i < num_msgs; ++i) {
414 if ((msglist[i] >= lo) && (msglist[i] <= hi)) {
420 /* Now translate the array of booleans back into a sequence set */
425 for (i=0; i<num_msgs; ++i) {
427 is_seen = is_set[i]; /* Default to existing setting */
429 for (k=0; k<num_target_msgnums; ++k) {
430 if (msglist[i] == target_msgnums[k]) {
431 is_seen = target_setting;
436 if (lo < 0L) lo = msglist[i];
440 if ( ((is_seen == 0) && (was_seen == 1))
441 || ((is_seen == 1) && (i == num_msgs-1)) ) {
443 /* begin trim-o-matic code */
446 while ( (strlen(vset) + 20) > sizeof vset) {
447 remove_token(vset, 0, ',');
449 if (j--) break; /* loop no more than 9 times */
451 if ( (trimming) && (which_set == ctdlsetseen_seen) ) {
455 snprintf(lostr, sizeof lostr,
456 "1:%ld,%s", t, vset);
457 safestrncpy(vset, lostr, sizeof vset);
459 /* end trim-o-matic code */
467 snprintf(&vset[tmp], (sizeof vset) - tmp,
471 snprintf(&vset[tmp], (sizeof vset) - tmp,
480 /* Decide which message set we're manipulating */
482 case ctdlsetseen_seen:
483 safestrncpy(vbuf.v_seen, vset, sizeof vbuf.v_seen);
485 case ctdlsetseen_answered:
486 safestrncpy(vbuf.v_answered, vset,
487 sizeof vbuf.v_answered);
492 /* CtdlLogPrintf(CTDL_DEBUG, " after optimize: %s\n", vset); */
494 CtdlSetRelationship(&vbuf,
495 ((which_user != NULL) ? which_user : &CC->user),
496 ((which_room != NULL) ? which_room : &CC->room)
502 * API function to perform an operation for each qualifying message in the
503 * current room. (Returns the number of messages processed.)
505 int CtdlForEachMessage(int mode, long ref, char *search_string,
507 struct CtdlMessage *compare,
508 void (*CallBack) (long, void *),
514 struct cdbdata *cdbfr;
515 long *msglist = NULL;
517 int num_processed = 0;
520 struct CtdlMessage *msg = NULL;
523 int printed_lastold = 0;
524 int num_search_msgs = 0;
525 long *search_msgs = NULL;
527 int need_to_free_re = 0;
530 if (content_type) if (!IsEmptyStr(content_type)) {
531 regcomp(&re, content_type, 0);
535 /* Learn about the user and room in question */
536 getuser(&CC->user, CC->curr_user);
537 CtdlGetRelationship(&vbuf, &CC->user, &CC->room);
539 /* Load the message list */
540 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long));
542 msglist = (long *) cdbfr->ptr;
543 num_msgs = cdbfr->len / sizeof(long);
545 if (need_to_free_re) regfree(&re);
546 return 0; /* No messages at all? No further action. */
551 * Now begin the traversal.
553 if (num_msgs > 0) for (a = 0; a < num_msgs; ++a) {
555 /* If the caller is looking for a specific MIME type, filter
556 * out all messages which are not of the type requested.
558 if (content_type != NULL) if (!IsEmptyStr(content_type)) {
560 /* This call to GetMetaData() sits inside this loop
561 * so that we only do the extra database read per msg
562 * if we need to. Doing the extra read all the time
563 * really kills the server. If we ever need to use
564 * metadata for another search criterion, we need to
565 * move the read somewhere else -- but still be smart
566 * enough to only do the read if the caller has
567 * specified something that will need it.
569 GetMetaData(&smi, msglist[a]);
571 /* if (strcasecmp(smi.meta_content_type, content_type)) { old non-regex way */
572 if (regexec(&re, smi.meta_content_type, 1, &pm, 0) != 0) {
578 num_msgs = sort_msglist(msglist, num_msgs);
580 /* If a template was supplied, filter out the messages which
581 * don't match. (This could induce some delays!)
584 if (compare != NULL) {
585 for (a = 0; a < num_msgs; ++a) {
586 msg = CtdlFetchMessage(msglist[a], 1);
588 if (CtdlMsgCmp(msg, compare)) {
591 CtdlFreeMessage(msg);
597 /* If a search string was specified, get a message list from
598 * the full text index and remove messages which aren't on both
602 * Since the lists are sorted and strictly ascending, and the
603 * output list is guaranteed to be shorter than or equal to the
604 * input list, we overwrite the bottom of the input list. This
605 * eliminates the need to memmove big chunks of the list over and
608 if ( (num_msgs > 0) && (mode == MSGS_SEARCH) && (search_string) ) {
610 /* Call search module via hook mechanism.
611 * NULL means use any search function available.
612 * otherwise replace with a char * to name of search routine
614 CtdlModuleDoSearch(&num_search_msgs, &search_msgs, search_string, "fulltext");
616 if (num_search_msgs > 0) {
620 orig_num_msgs = num_msgs;
622 for (i=0; i<orig_num_msgs; ++i) {
623 for (j=0; j<num_search_msgs; ++j) {
624 if (msglist[i] == search_msgs[j]) {
625 msglist[num_msgs++] = msglist[i];
631 num_msgs = 0; /* No messages qualify */
633 if (search_msgs != NULL) free(search_msgs);
635 /* Now that we've purged messages which don't contain the search
636 * string, treat a MSGS_SEARCH just like a MSGS_ALL from this
643 * Now iterate through the message list, according to the
644 * criteria supplied by the caller.
647 for (a = 0; a < num_msgs; ++a) {
648 thismsg = msglist[a];
649 if (mode == MSGS_ALL) {
653 is_seen = is_msg_in_sequence_set(
654 vbuf.v_seen, thismsg);
655 if (is_seen) lastold = thismsg;
661 || ((mode == MSGS_OLD) && (is_seen))
662 || ((mode == MSGS_NEW) && (!is_seen))
663 || ((mode == MSGS_LAST) && (a >= (num_msgs - ref)))
664 || ((mode == MSGS_FIRST) && (a < ref))
665 || ((mode == MSGS_GT) && (thismsg > ref))
666 || ((mode == MSGS_EQ) && (thismsg == ref))
669 if ((mode == MSGS_NEW) && (CC->user.flags & US_LASTOLD) && (lastold > 0L) && (printed_lastold == 0) && (!is_seen)) {
671 CallBack(lastold, userdata);
675 if (CallBack) CallBack(thismsg, userdata);
679 cdb_free(cdbfr); /* Clean up */
680 if (need_to_free_re) regfree(&re);
681 return num_processed;
687 * cmd_msgs() - get list of message #'s in this room
688 * implements the MSGS server command using CtdlForEachMessage()
690 void cmd_msgs(char *cmdbuf)
699 int with_template = 0;
700 struct CtdlMessage *template = NULL;
701 int with_headers = 0;
702 char search_string[1024];
704 extract_token(which, cmdbuf, 0, '|', sizeof which);
705 cm_ref = extract_int(cmdbuf, 1);
706 extract_token(search_string, cmdbuf, 1, '|', sizeof search_string);
707 with_template = extract_int(cmdbuf, 2);
708 with_headers = extract_int(cmdbuf, 3);
711 if (!strncasecmp(which, "OLD", 3))
713 else if (!strncasecmp(which, "NEW", 3))
715 else if (!strncasecmp(which, "FIRST", 5))
717 else if (!strncasecmp(which, "LAST", 4))
719 else if (!strncasecmp(which, "GT", 2))
721 else if (!strncasecmp(which, "SEARCH", 6))
726 if ((!(CC->logged_in)) && (!(CC->internal_pgm))) {
727 cprintf("%d not logged in\n", ERROR + NOT_LOGGED_IN);
731 if ( (mode == MSGS_SEARCH) && (!config.c_enable_fulltext) ) {
732 cprintf("%d Full text index is not enabled on this server.\n",
733 ERROR + CMD_NOT_SUPPORTED);
739 cprintf("%d Send template then receive message list\n",
741 template = (struct CtdlMessage *)
742 malloc(sizeof(struct CtdlMessage));
743 memset(template, 0, sizeof(struct CtdlMessage));
744 template->cm_magic = CTDLMESSAGE_MAGIC;
745 template->cm_anon_type = MES_NORMAL;
747 while(client_getln(buf, sizeof buf) >= 0 && strcmp(buf,"000")) {
748 extract_token(tfield, buf, 0, '|', sizeof tfield);
749 extract_token(tvalue, buf, 1, '|', sizeof tvalue);
750 for (i='A'; i<='Z'; ++i) if (msgkeys[i]!=NULL) {
751 if (!strcasecmp(tfield, msgkeys[i])) {
752 template->cm_fields[i] =
760 cprintf("%d \n", LISTING_FOLLOWS);
763 CtdlForEachMessage(mode,
764 ( (mode == MSGS_SEARCH) ? 0 : cm_ref ),
765 ( (mode == MSGS_SEARCH) ? search_string : NULL ),
768 (with_headers ? headers_listing : simple_listing),
771 if (template != NULL) CtdlFreeMessage(template);
779 * help_subst() - support routine for help file viewer
781 void help_subst(char *strbuf, char *source, char *dest)
786 while (p = pattern2(strbuf, source), (p >= 0)) {
787 strcpy(workbuf, &strbuf[p + strlen(source)]);
788 strcpy(&strbuf[p], dest);
789 strcat(strbuf, workbuf);
794 void do_help_subst(char *buffer)
798 help_subst(buffer, "^nodename", config.c_nodename);
799 help_subst(buffer, "^humannode", config.c_humannode);
800 help_subst(buffer, "^fqdn", config.c_fqdn);
801 help_subst(buffer, "^username", CC->user.fullname);
802 snprintf(buf2, sizeof buf2, "%ld", CC->user.usernum);
803 help_subst(buffer, "^usernum", buf2);
804 help_subst(buffer, "^sysadm", config.c_sysadm);
805 help_subst(buffer, "^variantname", CITADEL);
806 snprintf(buf2, sizeof buf2, "%d", config.c_maxsessions);
807 help_subst(buffer, "^maxsessions", buf2);
808 help_subst(buffer, "^bbsdir", ctdl_message_dir);
814 * memfmout() - Citadel text formatter and paginator.
815 * Although the original purpose of this routine was to format
816 * text to the reader's screen width, all we're really using it
817 * for here is to format text out to 80 columns before sending it
818 * to the client. The client software may reformat it again.
821 char *mptr, /* where are we going to get our text from? */
822 char subst, /* nonzero if we should do substitutions */
823 char *nl) /* string to terminate lines with */
831 static int width = 80;
836 c = 1; /* c is the current pos */
840 while (ch = *mptr, ((ch != 0) && (strlen(buffer) < 126))) {
842 buffer[strlen(buffer) + 1] = 0;
843 buffer[strlen(buffer)] = ch;
846 if (buffer[0] == '^')
847 do_help_subst(buffer);
849 buffer[strlen(buffer) + 1] = 0;
851 strcpy(buffer, &buffer[1]);
859 if (((ch == 13) || (ch == 10)) && (old != 13) && (old != 10)) {
862 if (((old == 13) || (old == 10)) && (isspace(real))) {
867 if (((strlen(aaa) + c) > (width - 5)) && (strlen(aaa) > (width - 5))) {
868 cprintf("%s%s", nl, aaa);
877 if ((strlen(aaa) + c) > (width - 5)) {
886 if ((ch == 13) || (ch == 10)) {
887 cprintf("%s%s", aaa, nl);
894 cprintf("%s%s", aaa, nl);
900 * Callback function for mime parser that simply lists the part
902 void list_this_part(char *name, char *filename, char *partnum, char *disp,
903 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
908 ma = (struct ma_info *)cbuserdata;
909 if (ma->is_ma == 0) {
910 cprintf("part=%s|%s|%s|%s|%s|%ld\n",
911 name, filename, partnum, disp, cbtype, (long)length);
916 * Callback function for multipart prefix
918 void list_this_pref(char *name, char *filename, char *partnum, char *disp,
919 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
924 ma = (struct ma_info *)cbuserdata;
925 if (!strcasecmp(cbtype, "multipart/alternative")) {
929 if (ma->is_ma == 0) {
930 cprintf("pref=%s|%s\n", partnum, cbtype);
935 * Callback function for multipart sufffix
937 void list_this_suff(char *name, char *filename, char *partnum, char *disp,
938 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
943 ma = (struct ma_info *)cbuserdata;
944 if (ma->is_ma == 0) {
945 cprintf("suff=%s|%s\n", partnum, cbtype);
947 if (!strcasecmp(cbtype, "multipart/alternative")) {
954 * Callback function for mime parser that opens a section for downloading
956 void mime_download(char *name, char *filename, char *partnum, char *disp,
957 void *content, char *cbtype, char *cbcharset, size_t length,
958 char *encoding, void *cbuserdata)
961 /* Silently go away if there's already a download open... */
962 if (CC->download_fp != NULL)
965 /* ...or if this is not the desired section */
966 if (strcasecmp(CC->download_desired_section, partnum))
969 CC->download_fp = tmpfile();
970 if (CC->download_fp == NULL)
973 fwrite(content, length, 1, CC->download_fp);
974 fflush(CC->download_fp);
975 rewind(CC->download_fp);
977 OpenCmdResult(filename, cbtype);
983 * Callback function for mime parser that outputs a section all at once
985 void mime_spew_section(char *name, char *filename, char *partnum, char *disp,
986 void *content, char *cbtype, char *cbcharset, size_t length,
987 char *encoding, void *cbuserdata)
989 int *found_it = (int *)cbuserdata;
991 /* ...or if this is not the desired section */
992 if (strcasecmp(CC->download_desired_section, partnum))
997 cprintf("%d %d\n", BINARY_FOLLOWS, (int)length);
998 client_write(content, length);
1004 * Load a message from disk into memory.
1005 * This is used by CtdlOutputMsg() and other fetch functions.
1007 * NOTE: Caller is responsible for freeing the returned CtdlMessage struct
1008 * using the CtdlMessageFree() function.
1010 struct CtdlMessage *CtdlFetchMessage(long msgnum, int with_body)
1012 struct cdbdata *dmsgtext;
1013 struct CtdlMessage *ret = NULL;
1017 cit_uint8_t field_header;
1019 CtdlLogPrintf(CTDL_DEBUG, "CtdlFetchMessage(%ld, %d)\n", msgnum, with_body);
1021 dmsgtext = cdb_fetch(CDB_MSGMAIN, &msgnum, sizeof(long));
1022 if (dmsgtext == NULL) {
1025 mptr = dmsgtext->ptr;
1026 upper_bound = mptr + dmsgtext->len;
1028 /* Parse the three bytes that begin EVERY message on disk.
1029 * The first is always 0xFF, the on-disk magic number.
1030 * The second is the anonymous/public type byte.
1031 * The third is the format type byte (vari, fixed, or MIME).
1035 CtdlLogPrintf(CTDL_ERR, "Message %ld appears to be corrupted.\n", msgnum);
1039 ret = (struct CtdlMessage *) malloc(sizeof(struct CtdlMessage));
1040 memset(ret, 0, sizeof(struct CtdlMessage));
1042 ret->cm_magic = CTDLMESSAGE_MAGIC;
1043 ret->cm_anon_type = *mptr++; /* Anon type byte */
1044 ret->cm_format_type = *mptr++; /* Format type byte */
1047 * The rest is zero or more arbitrary fields. Load them in.
1048 * We're done when we encounter either a zero-length field or
1049 * have just processed the 'M' (message text) field.
1052 if (mptr >= upper_bound) {
1055 field_header = *mptr++;
1056 ret->cm_fields[field_header] = strdup(mptr);
1058 while (*mptr++ != 0); /* advance to next field */
1060 } while ((mptr < upper_bound) && (field_header != 'M'));
1064 /* Always make sure there's something in the msg text field. If
1065 * it's NULL, the message text is most likely stored separately,
1066 * so go ahead and fetch that. Failing that, just set a dummy
1067 * body so other code doesn't barf.
1069 if ( (ret->cm_fields['M'] == NULL) && (with_body) ) {
1070 dmsgtext = cdb_fetch(CDB_BIGMSGS, &msgnum, sizeof(long));
1071 if (dmsgtext != NULL) {
1072 ret->cm_fields['M'] = strdup(dmsgtext->ptr);
1076 if (ret->cm_fields['M'] == NULL) {
1077 ret->cm_fields['M'] = strdup("\r\n\r\n (no text)\r\n");
1080 /* Perform "before read" hooks (aborting if any return nonzero) */
1081 if (PerformMessageHooks(ret, EVT_BEFOREREAD) > 0) {
1082 CtdlFreeMessage(ret);
1091 * Returns 1 if the supplied pointer points to a valid Citadel message.
1092 * If the pointer is NULL or the magic number check fails, returns 0.
1094 int is_valid_message(struct CtdlMessage *msg) {
1097 if ((msg->cm_magic) != CTDLMESSAGE_MAGIC) {
1098 CtdlLogPrintf(CTDL_WARNING, "is_valid_message() -- self-check failed\n");
1106 * 'Destructor' for struct CtdlMessage
1108 void CtdlFreeMessage(struct CtdlMessage *msg)
1112 if (is_valid_message(msg) == 0)
1114 if (msg != NULL) free (msg);
1118 for (i = 0; i < 256; ++i)
1119 if (msg->cm_fields[i] != NULL) {
1120 free(msg->cm_fields[i]);
1123 msg->cm_magic = 0; /* just in case */
1129 * Pre callback function for multipart/alternative
1131 * NOTE: this differs from the standard behavior for a reason. Normally when
1132 * displaying multipart/alternative you want to show the _last_ usable
1133 * format in the message. Here we show the _first_ one, because it's
1134 * usually text/plain. Since this set of functions is designed for text
1135 * output to non-MIME-aware clients, this is the desired behavior.
1138 void fixed_output_pre(char *name, char *filename, char *partnum, char *disp,
1139 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
1144 ma = (struct ma_info *)cbuserdata;
1145 CtdlLogPrintf(CTDL_DEBUG, "fixed_output_pre() type=<%s>\n", cbtype);
1146 if (!strcasecmp(cbtype, "multipart/alternative")) {
1150 if (!strcasecmp(cbtype, "message/rfc822")) {
1156 * Post callback function for multipart/alternative
1158 void fixed_output_post(char *name, char *filename, char *partnum, char *disp,
1159 void *content, char *cbtype, char *cbcharset, size_t length,
1160 char *encoding, void *cbuserdata)
1164 ma = (struct ma_info *)cbuserdata;
1165 CtdlLogPrintf(CTDL_DEBUG, "fixed_output_post() type=<%s>\n", cbtype);
1166 if (!strcasecmp(cbtype, "multipart/alternative")) {
1170 if (!strcasecmp(cbtype, "message/rfc822")) {
1176 * Inline callback function for mime parser that wants to display text
1178 void fixed_output(char *name, char *filename, char *partnum, char *disp,
1179 void *content, char *cbtype, char *cbcharset, size_t length,
1180 char *encoding, void *cbuserdata)
1187 ma = (struct ma_info *)cbuserdata;
1189 CtdlLogPrintf(CTDL_DEBUG,
1190 "fixed_output() part %s: %s (%s) (%ld bytes)\n",
1191 partnum, filename, cbtype, (long)length);
1194 * If we're in the middle of a multipart/alternative scope and
1195 * we've already printed another section, skip this one.
1197 if ( (ma->is_ma) && (ma->did_print) ) {
1198 CtdlLogPrintf(CTDL_DEBUG, "Skipping part %s (%s)\n", partnum, cbtype);
1203 if ( (!strcasecmp(cbtype, "text/plain"))
1204 || (IsEmptyStr(cbtype)) ) {
1207 client_write(wptr, length);
1208 if (wptr[length-1] != '\n') {
1215 if (!strcasecmp(cbtype, "text/html")) {
1216 ptr = html_to_ascii(content, length, 80, 0);
1218 client_write(ptr, wlen);
1219 if (ptr[wlen-1] != '\n') {
1226 if (ma->use_fo_hooks) {
1227 if (PerformFixedOutputHooks(cbtype, content, length)) {
1228 /* above function returns nonzero if it handled the part */
1233 if (strncasecmp(cbtype, "multipart/", 10)) {
1234 cprintf("Part %s: %s (%s) (%ld bytes)\r\n",
1235 partnum, filename, cbtype, (long)length);
1241 * The client is elegant and sophisticated and wants to be choosy about
1242 * MIME content types, so figure out which multipart/alternative part
1243 * we're going to send.
1245 * We use a system of weights. When we find a part that matches one of the
1246 * MIME types we've declared as preferential, we can store it in ma->chosen_part
1247 * and then set ma->chosen_pref to that MIME type's position in our preference
1248 * list. If we then hit another match, we only replace the first match if
1249 * the preference value is lower.
1251 void choose_preferred(char *name, char *filename, char *partnum, char *disp,
1252 void *content, char *cbtype, char *cbcharset, size_t length,
1253 char *encoding, void *cbuserdata)
1259 ma = (struct ma_info *)cbuserdata;
1261 // NOTE: REMOVING THIS CONDITIONAL FIXES BUG 220
1262 // http://bugzilla.citadel.org/show_bug.cgi?id=220
1263 // I don't know if there are any side effects! Please TEST TEST TEST
1264 //if (ma->is_ma > 0) {
1266 for (i=0; i<num_tokens(CC->preferred_formats, '|'); ++i) {
1267 extract_token(buf, CC->preferred_formats, i, '|', sizeof buf);
1268 if ( (!strcasecmp(buf, cbtype)) && (!ma->freeze) ) {
1269 if (i < ma->chosen_pref) {
1270 CtdlLogPrintf(CTDL_DEBUG, "Setting chosen part: <%s>\n", partnum);
1271 safestrncpy(ma->chosen_part, partnum, sizeof ma->chosen_part);
1272 ma->chosen_pref = i;
1279 * Now that we've chosen our preferred part, output it.
1281 void output_preferred(char *name, char *filename, char *partnum, char *disp,
1282 void *content, char *cbtype, char *cbcharset, size_t length,
1283 char *encoding, void *cbuserdata)
1287 int add_newline = 0;
1291 ma = (struct ma_info *)cbuserdata;
1293 /* This is not the MIME part you're looking for... */
1294 if (strcasecmp(partnum, ma->chosen_part)) return;
1296 /* If the content-type of this part is in our preferred formats
1297 * list, we can simply output it verbatim.
1299 for (i=0; i<num_tokens(CC->preferred_formats, '|'); ++i) {
1300 extract_token(buf, CC->preferred_formats, i, '|', sizeof buf);
1301 if (!strcasecmp(buf, cbtype)) {
1302 /* Yeah! Go! W00t!! */
1304 text_content = (char *)content;
1305 if (text_content[length-1] != '\n') {
1308 cprintf("Content-type: %s", cbtype);
1309 if (!IsEmptyStr(cbcharset)) {
1310 cprintf("; charset=%s", cbcharset);
1312 cprintf("\nContent-length: %d\n",
1313 (int)(length + add_newline) );
1314 if (!IsEmptyStr(encoding)) {
1315 cprintf("Content-transfer-encoding: %s\n", encoding);
1318 cprintf("Content-transfer-encoding: 7bit\n");
1320 cprintf("X-Citadel-MSG4-Partnum: %s\n", partnum);
1322 client_write(content, length);
1323 if (add_newline) cprintf("\n");
1328 /* No translations required or possible: output as text/plain */
1329 cprintf("Content-type: text/plain\n\n");
1330 fixed_output(name, filename, partnum, disp, content, cbtype, cbcharset,
1331 length, encoding, cbuserdata);
1336 char desired_section[64];
1343 * Callback function for
1345 void extract_encapsulated_message(char *name, char *filename, char *partnum, char *disp,
1346 void *content, char *cbtype, char *cbcharset, size_t length,
1347 char *encoding, void *cbuserdata)
1349 struct encapmsg *encap;
1351 encap = (struct encapmsg *)cbuserdata;
1353 /* Only proceed if this is the desired section... */
1354 if (!strcasecmp(encap->desired_section, partnum)) {
1355 encap->msglen = length;
1356 encap->msg = malloc(length + 2);
1357 memcpy(encap->msg, content, length);
1367 * Get a message off disk. (returns om_* values found in msgbase.h)
1370 int CtdlOutputMsg(long msg_num, /* message number (local) to fetch */
1371 int mode, /* how would you like that message? */
1372 int headers_only, /* eschew the message body? */
1373 int do_proto, /* do Citadel protocol responses? */
1374 int crlf, /* Use CRLF newlines instead of LF? */
1375 char *section /* NULL or a message/rfc822 section */
1377 struct CtdlMessage *TheMessage = NULL;
1378 int retcode = om_no_such_msg;
1379 struct encapmsg encap;
1381 CtdlLogPrintf(CTDL_DEBUG, "CtdlOutputMsg() msgnum=%ld, mode=%d, section=%s\n",
1383 (section ? section : "<>")
1386 if ((!(CC->logged_in)) && (!(CC->internal_pgm))) {
1387 if (do_proto) cprintf("%d Not logged in.\n",
1388 ERROR + NOT_LOGGED_IN);
1389 return(om_not_logged_in);
1392 /* FIXME: check message id against msglist for this room */
1395 * Fetch the message from disk. If we're in any sort of headers
1396 * only mode, request that we don't even bother loading the body
1399 if ( (headers_only == HEADERS_FAST) || (headers_only == HEADERS_ONLY) ) {
1400 TheMessage = CtdlFetchMessage(msg_num, 0);
1403 TheMessage = CtdlFetchMessage(msg_num, 1);
1406 if (TheMessage == NULL) {
1407 if (do_proto) cprintf("%d Can't locate msg %ld on disk\n",
1408 ERROR + MESSAGE_NOT_FOUND, msg_num);
1409 return(om_no_such_msg);
1412 /* Here is the weird form of this command, to process only an
1413 * encapsulated message/rfc822 section.
1415 if (section) if (!IsEmptyStr(section)) if (strcmp(section, "0")) {
1416 memset(&encap, 0, sizeof encap);
1417 safestrncpy(encap.desired_section, section, sizeof encap.desired_section);
1418 mime_parser(TheMessage->cm_fields['M'],
1420 *extract_encapsulated_message,
1421 NULL, NULL, (void *)&encap, 0
1423 CtdlFreeMessage(TheMessage);
1427 encap.msg[encap.msglen] = 0;
1428 TheMessage = convert_internet_message(encap.msg);
1429 encap.msg = NULL; /* no free() here, TheMessage owns it now */
1431 /* Now we let it fall through to the bottom of this
1432 * function, because TheMessage now contains the
1433 * encapsulated message instead of the top-level
1434 * message. Isn't that neat?
1439 if (do_proto) cprintf("%d msg %ld has no part %s\n",
1440 ERROR + MESSAGE_NOT_FOUND, msg_num, section);
1441 retcode = om_no_such_msg;
1446 /* Ok, output the message now */
1447 retcode = CtdlOutputPreLoadedMsg(TheMessage, mode, headers_only, do_proto, crlf);
1448 CtdlFreeMessage(TheMessage);
1453 char *qp_encode_email_addrs(char *source)
1455 char user[256], node[256], name[256];
1456 const char headerStr[] = "=?UTF-8?Q?";
1460 int need_to_encode = 0;
1466 long nAddrPtrMax = 50;
1475 AddrPtr = malloc (sizeof (long) * nAddrPtrMax);
1476 AddrUtf8 = malloc (sizeof (long) * nAddrPtrMax);
1477 memset(AddrUtf8, 0, sizeof (long) * nAddrPtrMax);
1480 while (!IsEmptyStr (&source[i])) {
1481 if (nColons > nAddrPtrMax){
1484 ptr = (long *) malloc(sizeof (long) * nAddrPtrMax * 2);
1485 memcpy (ptr, AddrPtr, sizeof (long) * nAddrPtrMax);
1486 free (AddrPtr), AddrPtr = ptr;
1487 ptr = (long *) malloc(sizeof (long) * nAddrPtrMax * 2);
1488 memset(ptr + sizeof (long) * nAddrPtrMax, 0, sizeof (long) * nAddrPtrMax - 1);
1489 memcpy (ptr, AddrUtf8, sizeof (long) * nAddrPtrMax);
1490 free (AddrUtf8), AddrUtf8 = ptr;
1493 if (((unsigned char) source[i] < 32) ||
1494 ((unsigned char) source[i] > 126)) {
1496 AddrUtf8[nColons] = 1;
1498 if (source[i] == '"')
1499 InQuotes = !InQuotes;
1500 if (!InQuotes && source[i] == ',') {
1502 AddrPtr[nColons] = i;
1506 if (need_to_encode == 0) {
1513 EncodedMaxLen = nColons * (sizeof(headerStr) + 3) + SourceLen * 3;
1514 Encoded = (char*) malloc (EncodedMaxLen);
1516 for (i = 1; i <= nColons; i++)
1517 source[AddrPtr[i]++] = '\0';
1521 for (i = 0; i <= nColons && nPtr != NULL; i++) {
1522 nmax = EncodedMaxLen - (nPtr - Encoded);
1524 process_rfc822_addr(&source[AddrPtr[i]],
1528 /* TODO: libIDN here ! */
1529 if (IsEmptyStr(name)) {
1530 n = snprintf(nPtr, nmax,
1531 (i==0)?"%s@%s" : ",%s@%s",
1535 EncodedName = rfc2047encode(name, strlen(name));
1536 n = snprintf(nPtr, nmax,
1537 (i==0)?"%s <%s@%s>" : ",%s <%s@%s>",
1538 EncodedName, user, node);
1543 n = snprintf(nPtr, nmax,
1544 (i==0)?"%s" : ",%s",
1545 &source[AddrPtr[i]]);
1551 ptr = (char*) malloc(EncodedMaxLen * 2);
1552 memcpy(ptr, Encoded, EncodedMaxLen);
1553 nnPtr = ptr + (nPtr - Encoded), nPtr = nnPtr;
1554 free(Encoded), Encoded = ptr;
1556 i--; /* do it once more with properly lengthened buffer */
1559 for (i = 1; i <= nColons; i++)
1560 source[--AddrPtr[i]] = ',';
1567 * Get a message off disk. (returns om_* values found in msgbase.h)
1569 int CtdlOutputPreLoadedMsg(
1570 struct CtdlMessage *TheMessage,
1571 int mode, /* how would you like that message? */
1572 int headers_only, /* eschew the message body? */
1573 int do_proto, /* do Citadel protocol responses? */
1574 int crlf /* Use CRLF newlines instead of LF? */
1580 char display_name[256];
1582 char *nl; /* newline string */
1584 int subject_found = 0;
1587 /* Buffers needed for RFC822 translation. These are all filled
1588 * using functions that are bounds-checked, and therefore we can
1589 * make them substantially smaller than SIZ.
1597 char datestamp[100];
1599 CtdlLogPrintf(CTDL_DEBUG, "CtdlOutputPreLoadedMsg(TheMessage=%s, %d, %d, %d, %d\n",
1600 ((TheMessage == NULL) ? "NULL" : "not null"),
1601 mode, headers_only, do_proto, crlf);
1603 strcpy(mid, "unknown");
1604 nl = (crlf ? "\r\n" : "\n");
1606 if (!is_valid_message(TheMessage)) {
1607 CtdlLogPrintf(CTDL_ERR,
1608 "ERROR: invalid preloaded message for output\n");
1609 return(om_no_such_msg);
1612 /* Are we downloading a MIME component? */
1613 if (mode == MT_DOWNLOAD) {
1614 if (TheMessage->cm_format_type != FMT_RFC822) {
1616 cprintf("%d This is not a MIME message.\n",
1617 ERROR + ILLEGAL_VALUE);
1618 } else if (CC->download_fp != NULL) {
1619 if (do_proto) cprintf(
1620 "%d You already have a download open.\n",
1621 ERROR + RESOURCE_BUSY);
1623 /* Parse the message text component */
1624 mptr = TheMessage->cm_fields['M'];
1625 mime_parser(mptr, NULL, *mime_download, NULL, NULL, NULL, 0);
1626 /* If there's no file open by this time, the requested
1627 * section wasn't found, so print an error
1629 if (CC->download_fp == NULL) {
1630 if (do_proto) cprintf(
1631 "%d Section %s not found.\n",
1632 ERROR + FILE_NOT_FOUND,
1633 CC->download_desired_section);
1636 return((CC->download_fp != NULL) ? om_ok : om_mime_error);
1639 /* MT_SPEW_SECTION is like MT_DOWNLOAD except it outputs the whole MIME part
1640 * in a single server operation instead of opening a download file.
1642 if (mode == MT_SPEW_SECTION) {
1643 if (TheMessage->cm_format_type != FMT_RFC822) {
1645 cprintf("%d This is not a MIME message.\n",
1646 ERROR + ILLEGAL_VALUE);
1648 /* Parse the message text component */
1651 mptr = TheMessage->cm_fields['M'];
1652 mime_parser(mptr, NULL, *mime_spew_section, NULL, NULL, (void *)&found_it, 0);
1653 /* If section wasn't found, print an error
1656 if (do_proto) cprintf(
1657 "%d Section %s not found.\n",
1658 ERROR + FILE_NOT_FOUND,
1659 CC->download_desired_section);
1662 return((CC->download_fp != NULL) ? om_ok : om_mime_error);
1665 /* now for the user-mode message reading loops */
1666 if (do_proto) cprintf("%d msg:\n", LISTING_FOLLOWS);
1668 /* Does the caller want to skip the headers? */
1669 if (headers_only == HEADERS_NONE) goto START_TEXT;
1671 /* Tell the client which format type we're using. */
1672 if ( (mode == MT_CITADEL) && (do_proto) ) {
1673 cprintf("type=%d\n", TheMessage->cm_format_type);
1676 /* nhdr=yes means that we're only displaying headers, no body */
1677 if ( (TheMessage->cm_anon_type == MES_ANONONLY)
1678 && (mode == MT_CITADEL)
1681 cprintf("nhdr=yes\n");
1684 /* begin header processing loop for Citadel message format */
1686 if ((mode == MT_CITADEL) || (mode == MT_MIME)) {
1688 safestrncpy(display_name, "<unknown>", sizeof display_name);
1689 if (TheMessage->cm_fields['A']) {
1690 strcpy(buf, TheMessage->cm_fields['A']);
1691 if (TheMessage->cm_anon_type == MES_ANONONLY) {
1692 safestrncpy(display_name, "****", sizeof display_name);
1694 else if (TheMessage->cm_anon_type == MES_ANONOPT) {
1695 safestrncpy(display_name, "anonymous", sizeof display_name);
1698 safestrncpy(display_name, buf, sizeof display_name);
1700 if ((is_room_aide())
1701 && ((TheMessage->cm_anon_type == MES_ANONONLY)
1702 || (TheMessage->cm_anon_type == MES_ANONOPT))) {
1703 size_t tmp = strlen(display_name);
1704 snprintf(&display_name[tmp],
1705 sizeof display_name - tmp,
1710 /* Don't show Internet address for users on the
1711 * local Citadel network.
1714 if (TheMessage->cm_fields['N'] != NULL)
1715 if (!IsEmptyStr(TheMessage->cm_fields['N']))
1716 if (haschar(TheMessage->cm_fields['N'], '.') == 0) {
1720 /* Now spew the header fields in the order we like them. */
1721 safestrncpy(allkeys, FORDER, sizeof allkeys);
1722 for (i=0; i<strlen(allkeys); ++i) {
1723 k = (int) allkeys[i];
1725 if ( (TheMessage->cm_fields[k] != NULL)
1726 && (msgkeys[k] != NULL) ) {
1728 if (do_proto) cprintf("%s=%s\n",
1732 else if ((k == 'F') && (suppress_f)) {
1735 /* Masquerade display name if needed */
1737 if (do_proto) cprintf("%s=%s\n",
1739 TheMessage->cm_fields[k]
1748 /* begin header processing loop for RFC822 transfer format */
1753 strcpy(snode, NODENAME);
1754 strcpy(lnode, HUMANNODE);
1755 if (mode == MT_RFC822) {
1756 for (i = 0; i < 256; ++i) {
1757 if (TheMessage->cm_fields[i]) {
1758 mptr = mpptr = TheMessage->cm_fields[i];
1761 safestrncpy(luser, mptr, sizeof luser);
1762 safestrncpy(suser, mptr, sizeof suser);
1764 else if (i == 'Y') {
1765 mptr = qp_encode_email_addrs(mptr);
1766 cprintf("CC: %s%s", mptr, nl);
1768 else if (i == 'P') {
1769 cprintf("Return-Path: %s%s", mptr, nl);
1771 else if (i == 'V') {
1772 mptr = qp_encode_email_addrs(mptr);
1773 cprintf("Envelope-To: %s%s", mptr, nl);
1775 else if (i == 'U') {
1776 cprintf("Subject: %s%s", mptr, nl);
1780 safestrncpy(mid, mptr, sizeof mid);
1782 safestrncpy(lnode, mptr, sizeof lnode);
1784 safestrncpy(fuser, mptr, sizeof fuser);
1785 /* else if (i == 'O')
1786 cprintf("X-Citadel-Room: %s%s",
1789 safestrncpy(snode, mptr, sizeof snode);
1792 if (haschar(mptr, '@') == 0)
1794 cprintf("To: %s@%s%s", mptr, config.c_fqdn, nl);
1798 mptr = qp_encode_email_addrs(mptr);
1799 cprintf("To: %s%s", mptr, nl);
1802 else if (i == 'T') {
1803 datestring(datestamp, sizeof datestamp,
1804 atol(mptr), DATESTRING_RFC822);
1805 cprintf("Date: %s%s", datestamp, nl);
1807 else if (i == 'W') {
1808 cprintf("References: ");
1809 k = num_tokens(mptr, '|');
1810 for (j=0; j<k; ++j) {
1811 extract_token(buf, mptr, j, '|', sizeof buf);
1812 cprintf("<%s>", buf);
1825 if (subject_found == 0) {
1826 cprintf("Subject: (no subject)%s", nl);
1830 for (i=0; !IsEmptyStr(&suser[i]); ++i) {
1831 suser[i] = tolower(suser[i]);
1832 if (!isalnum(suser[i])) suser[i]='_';
1835 if (mode == MT_RFC822) {
1836 if (!strcasecmp(snode, NODENAME)) {
1837 safestrncpy(snode, FQDN, sizeof snode);
1840 /* Construct a fun message id */
1841 cprintf("Message-ID: <%s", mid);/// todo: this possibly breaks threadding mails.
1842 if (strchr(mid, '@')==NULL) {
1843 cprintf("@%s", snode);
1847 if (!is_room_aide() && (TheMessage->cm_anon_type == MES_ANONONLY)) {
1848 cprintf("From: \"----\" <x@x.org>%s", nl);
1850 else if (!is_room_aide() && (TheMessage->cm_anon_type == MES_ANONOPT)) {
1851 cprintf("From: \"anonymous\" <x@x.org>%s", nl);
1853 else if (!IsEmptyStr(fuser)) {
1854 cprintf("From: \"%s\" <%s>%s", luser, fuser, nl);
1857 cprintf("From: \"%s\" <%s@%s>%s", luser, suser, snode, nl);
1860 cprintf("Organization: %s%s", lnode, nl);
1862 /* Blank line signifying RFC822 end-of-headers */
1863 if (TheMessage->cm_format_type != FMT_RFC822) {
1868 /* end header processing loop ... at this point, we're in the text */
1870 if (headers_only == HEADERS_FAST) goto DONE;
1871 mptr = TheMessage->cm_fields['M'];
1873 /* Tell the client about the MIME parts in this message */
1874 if (TheMessage->cm_format_type == FMT_RFC822) {
1875 if ( (mode == MT_CITADEL) || (mode == MT_MIME) ) {
1876 memset(&ma, 0, sizeof(struct ma_info));
1877 mime_parser(mptr, NULL,
1878 (do_proto ? *list_this_part : NULL),
1879 (do_proto ? *list_this_pref : NULL),
1880 (do_proto ? *list_this_suff : NULL),
1883 else if (mode == MT_RFC822) { /* unparsed RFC822 dump */
1884 char *start_of_text = NULL;
1885 start_of_text = strstr(mptr, "\n\r\n");
1886 if (start_of_text == NULL) start_of_text = strstr(mptr, "\n\n");
1887 if (start_of_text == NULL) start_of_text = mptr;
1889 start_of_text = strstr(start_of_text, "\n");
1894 int nllen = strlen(nl);
1895 while (ch=*mptr, ch!=0) {
1901 ((headers_only == HEADERS_NONE) && (mptr >= start_of_text))
1902 || ((headers_only == HEADERS_ONLY) && (mptr < start_of_text))
1903 || ((headers_only != HEADERS_NONE) && (headers_only != HEADERS_ONLY))
1906 sprintf(&outbuf[outlen], "%s", nl);
1910 outbuf[outlen++] = ch;
1915 if (outlen > 1000) {
1916 client_write(outbuf, outlen);
1921 client_write(outbuf, outlen);
1929 if (headers_only == HEADERS_ONLY) {
1933 /* signify start of msg text */
1934 if ( (mode == MT_CITADEL) || (mode == MT_MIME) ) {
1935 if (do_proto) cprintf("text\n");
1938 /* If the format type on disk is 1 (fixed-format), then we want
1939 * everything to be output completely literally ... regardless of
1940 * what message transfer format is in use.
1942 if (TheMessage->cm_format_type == FMT_FIXED) {
1944 if (mode == MT_MIME) {
1945 cprintf("Content-type: text/plain\n\n");
1949 while (ch = *mptr++, ch > 0) {
1952 if ((ch == 10) || (buflen > 250)) {
1954 cprintf("%s%s", buf, nl);
1963 if (!IsEmptyStr(buf))
1964 cprintf("%s%s", buf, nl);
1967 /* If the message on disk is format 0 (Citadel vari-format), we
1968 * output using the formatter at 80 columns. This is the final output
1969 * form if the transfer format is RFC822, but if the transfer format
1970 * is Citadel proprietary, it'll still work, because the indentation
1971 * for new paragraphs is correct and the client will reformat the
1972 * message to the reader's screen width.
1974 if (TheMessage->cm_format_type == FMT_CITADEL) {
1975 if (mode == MT_MIME) {
1976 cprintf("Content-type: text/x-citadel-variformat\n\n");
1978 memfmout(mptr, 0, nl);
1981 /* If the message on disk is format 4 (MIME), we've gotta hand it
1982 * off to the MIME parser. The client has already been told that
1983 * this message is format 1 (fixed format), so the callback function
1984 * we use will display those parts as-is.
1986 if (TheMessage->cm_format_type == FMT_RFC822) {
1987 memset(&ma, 0, sizeof(struct ma_info));
1989 if (mode == MT_MIME) {
1990 ma.use_fo_hooks = 0;
1991 strcpy(ma.chosen_part, "1");
1992 ma.chosen_pref = 9999;
1993 mime_parser(mptr, NULL,
1994 *choose_preferred, *fixed_output_pre,
1995 *fixed_output_post, (void *)&ma, 0);
1996 mime_parser(mptr, NULL,
1997 *output_preferred, NULL, NULL, (void *)&ma, CC->msg4_dont_decode);
2000 ma.use_fo_hooks = 1;
2001 mime_parser(mptr, NULL,
2002 *fixed_output, *fixed_output_pre,
2003 *fixed_output_post, (void *)&ma, 0);
2008 DONE: /* now we're done */
2009 if (do_proto) cprintf("000\n");
2016 * display a message (mode 0 - Citadel proprietary)
2018 void cmd_msg0(char *cmdbuf)
2021 int headers_only = HEADERS_ALL;
2023 msgid = extract_long(cmdbuf, 0);
2024 headers_only = extract_int(cmdbuf, 1);
2026 CtdlOutputMsg(msgid, MT_CITADEL, headers_only, 1, 0, NULL);
2032 * display a message (mode 2 - RFC822)
2034 void cmd_msg2(char *cmdbuf)
2037 int headers_only = HEADERS_ALL;
2039 msgid = extract_long(cmdbuf, 0);
2040 headers_only = extract_int(cmdbuf, 1);
2042 CtdlOutputMsg(msgid, MT_RFC822, headers_only, 1, 1, NULL);
2048 * display a message (mode 3 - IGnet raw format - internal programs only)
2050 void cmd_msg3(char *cmdbuf)
2053 struct CtdlMessage *msg = NULL;
2056 if (CC->internal_pgm == 0) {
2057 cprintf("%d This command is for internal programs only.\n",
2058 ERROR + HIGHER_ACCESS_REQUIRED);
2062 msgnum = extract_long(cmdbuf, 0);
2063 msg = CtdlFetchMessage(msgnum, 1);
2065 cprintf("%d Message %ld not found.\n",
2066 ERROR + MESSAGE_NOT_FOUND, msgnum);
2070 serialize_message(&smr, msg);
2071 CtdlFreeMessage(msg);
2074 cprintf("%d Unable to serialize message\n",
2075 ERROR + INTERNAL_ERROR);
2079 cprintf("%d %ld\n", BINARY_FOLLOWS, (long)smr.len);
2080 client_write((char *)smr.ser, (int)smr.len);
2087 * Display a message using MIME content types
2089 void cmd_msg4(char *cmdbuf)
2094 msgid = extract_long(cmdbuf, 0);
2095 extract_token(section, cmdbuf, 1, '|', sizeof section);
2096 CtdlOutputMsg(msgid, MT_MIME, 0, 1, 0, (section[0] ? section : NULL) );
2102 * Client tells us its preferred message format(s)
2104 void cmd_msgp(char *cmdbuf)
2106 if (!strcasecmp(cmdbuf, "dont_decode")) {
2107 CC->msg4_dont_decode = 1;
2108 cprintf("%d MSG4 will not pre-decode messages.\n", CIT_OK);
2111 safestrncpy(CC->preferred_formats, cmdbuf, sizeof(CC->preferred_formats));
2112 cprintf("%d Preferred MIME formats have been set.\n", CIT_OK);
2118 * Open a component of a MIME message as a download file
2120 void cmd_opna(char *cmdbuf)
2123 char desired_section[128];
2125 msgid = extract_long(cmdbuf, 0);
2126 extract_token(desired_section, cmdbuf, 1, '|', sizeof desired_section);
2127 safestrncpy(CC->download_desired_section, desired_section,
2128 sizeof CC->download_desired_section);
2129 CtdlOutputMsg(msgid, MT_DOWNLOAD, 0, 1, 1, NULL);
2134 * Open a component of a MIME message and transmit it all at once
2136 void cmd_dlat(char *cmdbuf)
2139 char desired_section[128];
2141 msgid = extract_long(cmdbuf, 0);
2142 extract_token(desired_section, cmdbuf, 1, '|', sizeof desired_section);
2143 safestrncpy(CC->download_desired_section, desired_section,
2144 sizeof CC->download_desired_section);
2145 CtdlOutputMsg(msgid, MT_SPEW_SECTION, 0, 1, 1, NULL);
2150 * Save one or more message pointers into a specified room
2151 * (Returns 0 for success, nonzero for failure)
2152 * roomname may be NULL to use the current room
2154 * Note that the 'supplied_msg' field may be set to NULL, in which case
2155 * the message will be fetched from disk, by number, if we need to perform
2156 * replication checks. This adds an additional database read, so if the
2157 * caller already has the message in memory then it should be supplied. (Obviously
2158 * this mode of operation only works if we're saving a single message.)
2160 int CtdlSaveMsgPointersInRoom(char *roomname, long newmsgidlist[], int num_newmsgs,
2161 int do_repl_check, struct CtdlMessage *supplied_msg)
2164 char hold_rm[ROOMNAMELEN];
2165 struct cdbdata *cdbfr;
2168 long highest_msg = 0L;
2171 struct CtdlMessage *msg = NULL;
2173 long *msgs_to_be_merged = NULL;
2174 int num_msgs_to_be_merged = 0;
2176 CtdlLogPrintf(CTDL_DEBUG,
2177 "CtdlSaveMsgPointersInRoom(room=%s, num_msgs=%d, repl=%d)\n",
2178 roomname, num_newmsgs, do_repl_check);
2180 strcpy(hold_rm, CC->room.QRname);
2183 if (newmsgidlist == NULL) return(ERROR + INTERNAL_ERROR);
2184 if (num_newmsgs < 1) return(ERROR + INTERNAL_ERROR);
2185 if (num_newmsgs > 1) supplied_msg = NULL;
2187 /* Now the regular stuff */
2188 if (lgetroom(&CC->room,
2189 ((roomname != NULL) ? roomname : CC->room.QRname) )
2191 CtdlLogPrintf(CTDL_ERR, "No such room <%s>\n", roomname);
2192 return(ERROR + ROOM_NOT_FOUND);
2196 msgs_to_be_merged = malloc(sizeof(long) * num_newmsgs);
2197 num_msgs_to_be_merged = 0;
2200 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long));
2201 if (cdbfr == NULL) {
2205 msglist = (long *) cdbfr->ptr;
2206 cdbfr->ptr = NULL; /* CtdlSaveMsgPointerInRoom() now owns this memory */
2207 num_msgs = cdbfr->len / sizeof(long);
2212 /* Create a list of msgid's which were supplied by the caller, but do
2213 * not already exist in the target room. It is absolutely taboo to
2214 * have more than one reference to the same message in a room.
2216 for (i=0; i<num_newmsgs; ++i) {
2218 if (num_msgs > 0) for (j=0; j<num_msgs; ++j) {
2219 if (msglist[j] == newmsgidlist[i]) {
2224 msgs_to_be_merged[num_msgs_to_be_merged++] = newmsgidlist[i];
2228 CtdlLogPrintf(9, "%d unique messages to be merged\n", num_msgs_to_be_merged);
2231 * Now merge the new messages
2233 msglist = realloc(msglist, (sizeof(long) * (num_msgs + num_msgs_to_be_merged)) );
2234 if (msglist == NULL) {
2235 CtdlLogPrintf(CTDL_ALERT, "ERROR: can't realloc message list!\n");
2237 memcpy(&msglist[num_msgs], msgs_to_be_merged, (sizeof(long) * num_msgs_to_be_merged) );
2238 num_msgs += num_msgs_to_be_merged;
2240 /* Sort the message list, so all the msgid's are in order */
2241 num_msgs = sort_msglist(msglist, num_msgs);
2243 /* Determine the highest message number */
2244 highest_msg = msglist[num_msgs - 1];
2246 /* Write it back to disk. */
2247 cdb_store(CDB_MSGLISTS, &CC->room.QRnumber, (int)sizeof(long),
2248 msglist, (int)(num_msgs * sizeof(long)));
2250 /* Free up the memory we used. */
2253 /* Update the highest-message pointer and unlock the room. */
2254 CC->room.QRhighest = highest_msg;
2255 lputroom(&CC->room);
2257 /* Perform replication checks if necessary */
2258 if ( (DoesThisRoomNeedEuidIndexing(&CC->room)) && (do_repl_check) ) {
2259 CtdlLogPrintf(CTDL_DEBUG, "CtdlSaveMsgPointerInRoom() doing repl checks\n");
2261 for (i=0; i<num_msgs_to_be_merged; ++i) {
2262 msgid = msgs_to_be_merged[i];
2264 if (supplied_msg != NULL) {
2268 msg = CtdlFetchMessage(msgid, 0);
2272 ReplicationChecks(msg);
2274 /* If the message has an Exclusive ID, index that... */
2275 if (msg->cm_fields['E'] != NULL) {
2276 index_message_by_euid(msg->cm_fields['E'], &CC->room, msgid);
2279 /* Free up the memory we may have allocated */
2280 if (msg != supplied_msg) {
2281 CtdlFreeMessage(msg);
2289 CtdlLogPrintf(CTDL_DEBUG, "CtdlSaveMsgPointerInRoom() skips repl checks\n");
2292 /* Submit this room for processing by hooks */
2293 PerformRoomHooks(&CC->room);
2295 /* Go back to the room we were in before we wandered here... */
2296 getroom(&CC->room, hold_rm);
2298 /* Bump the reference count for all messages which were merged */
2299 for (i=0; i<num_msgs_to_be_merged; ++i) {
2300 AdjRefCount(msgs_to_be_merged[i], +1);
2303 /* Free up memory... */
2304 if (msgs_to_be_merged != NULL) {
2305 free(msgs_to_be_merged);
2308 /* Return success. */
2314 * This is the same as CtdlSaveMsgPointersInRoom() but it only accepts
2317 int CtdlSaveMsgPointerInRoom(char *roomname, long msgid,
2318 int do_repl_check, struct CtdlMessage *supplied_msg)
2320 return CtdlSaveMsgPointersInRoom(roomname, &msgid, 1, do_repl_check, supplied_msg);
2327 * Message base operation to save a new message to the message store
2328 * (returns new message number)
2330 * This is the back end for CtdlSubmitMsg() and should not be directly
2331 * called by server-side modules.
2334 long send_message(struct CtdlMessage *msg) {
2342 /* Get a new message number */
2343 newmsgid = get_new_message_number();
2344 snprintf(msgidbuf, sizeof msgidbuf, "%010ld@%s", newmsgid, config.c_fqdn);
2346 /* Generate an ID if we don't have one already */
2347 if (msg->cm_fields['I']==NULL) {
2348 msg->cm_fields['I'] = strdup(msgidbuf);
2351 /* If the message is big, set its body aside for storage elsewhere */
2352 if (msg->cm_fields['M'] != NULL) {
2353 if (strlen(msg->cm_fields['M']) > BIGMSG) {
2355 holdM = msg->cm_fields['M'];
2356 msg->cm_fields['M'] = NULL;
2360 /* Serialize our data structure for storage in the database */
2361 serialize_message(&smr, msg);
2364 msg->cm_fields['M'] = holdM;
2368 cprintf("%d Unable to serialize message\n",
2369 ERROR + INTERNAL_ERROR);
2373 /* Write our little bundle of joy into the message base */
2374 if (cdb_store(CDB_MSGMAIN, &newmsgid, (int)sizeof(long),
2375 smr.ser, smr.len) < 0) {
2376 CtdlLogPrintf(CTDL_ERR, "Can't store message\n");
2380 cdb_store(CDB_BIGMSGS,
2390 /* Free the memory we used for the serialized message */
2393 /* Return the *local* message ID to the caller
2394 * (even if we're storing an incoming network message)
2402 * Serialize a struct CtdlMessage into the format used on disk and network.
2404 * This function loads up a "struct ser_ret" (defined in server.h) which
2405 * contains the length of the serialized message and a pointer to the
2406 * serialized message in memory. THE LATTER MUST BE FREED BY THE CALLER.
2408 void serialize_message(struct ser_ret *ret, /* return values */
2409 struct CtdlMessage *msg) /* unserialized msg */
2411 size_t wlen, fieldlen;
2413 static char *forder = FORDER;
2416 * Check for valid message format
2418 if (is_valid_message(msg) == 0) {
2419 CtdlLogPrintf(CTDL_ERR, "serialize_message() aborting due to invalid message\n");
2426 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL)
2427 ret->len = ret->len +
2428 strlen(msg->cm_fields[(int)forder[i]]) + 2;
2430 ret->ser = malloc(ret->len);
2431 if (ret->ser == NULL) {
2432 CtdlLogPrintf(CTDL_ERR, "serialize_message() malloc(%ld) failed: %s\n",
2433 (long)ret->len, strerror(errno));
2440 ret->ser[1] = msg->cm_anon_type;
2441 ret->ser[2] = msg->cm_format_type;
2444 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL) {
2445 fieldlen = strlen(msg->cm_fields[(int)forder[i]]);
2446 ret->ser[wlen++] = (char)forder[i];
2447 safestrncpy((char *)&ret->ser[wlen], msg->cm_fields[(int)forder[i]], fieldlen+1);
2448 wlen = wlen + fieldlen + 1;
2450 if (ret->len != wlen) CtdlLogPrintf(CTDL_ERR, "ERROR: len=%ld wlen=%ld\n",
2451 (long)ret->len, (long)wlen);
2458 * Serialize a struct CtdlMessage into the format used on disk and network.
2460 * This function loads up a "struct ser_ret" (defined in server.h) which
2461 * contains the length of the serialized message and a pointer to the
2462 * serialized message in memory. THE LATTER MUST BE FREED BY THE CALLER.
2464 void dump_message(struct CtdlMessage *msg, /* unserialized msg */
2465 long Siz) /* how many chars ? */
2469 static char *forder = FORDER;
2473 * Check for valid message format
2475 if (is_valid_message(msg) == 0) {
2476 CtdlLogPrintf(CTDL_ERR, "dump_message() aborting due to invalid message\n");
2480 buf = (char*) malloc (Siz + 1);
2484 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL) {
2485 snprintf (buf, Siz, " msg[%c] = %s ...\n", (char) forder[i],
2486 msg->cm_fields[(int)forder[i]]);
2487 client_write (buf, strlen(buf));
2496 * Check to see if any messages already exist in the current room which
2497 * carry the same Exclusive ID as this one. If any are found, delete them.
2499 void ReplicationChecks(struct CtdlMessage *msg) {
2500 long old_msgnum = (-1L);
2502 if (DoesThisRoomNeedEuidIndexing(&CC->room) == 0) return;
2504 CtdlLogPrintf(CTDL_DEBUG, "Performing replication checks in <%s>\n",
2507 /* No exclusive id? Don't do anything. */
2508 if (msg == NULL) return;
2509 if (msg->cm_fields['E'] == NULL) return;
2510 if (IsEmptyStr(msg->cm_fields['E'])) return;
2511 /*CtdlLogPrintf(CTDL_DEBUG, "Exclusive ID: <%s> for room <%s>\n",
2512 msg->cm_fields['E'], CC->room.QRname);*/
2514 old_msgnum = locate_message_by_euid(msg->cm_fields['E'], &CC->room);
2515 if (old_msgnum > 0L) {
2516 CtdlLogPrintf(CTDL_DEBUG, "ReplicationChecks() replacing message %ld\n", old_msgnum);
2517 CtdlDeleteMessages(CC->room.QRname, &old_msgnum, 1, "");
2524 * Save a message to disk and submit it into the delivery system.
2526 long CtdlSubmitMsg(struct CtdlMessage *msg, /* message to save */
2527 struct recptypes *recps, /* recipients (if mail) */
2528 char *force /* force a particular room? */
2530 char submit_filename[128];
2531 char generated_timestamp[32];
2532 char hold_rm[ROOMNAMELEN];
2533 char actual_rm[ROOMNAMELEN];
2534 char force_room[ROOMNAMELEN];
2535 char content_type[SIZ]; /* We have to learn this */
2536 char recipient[SIZ];
2539 struct ctdluser userbuf;
2541 struct MetaData smi;
2542 FILE *network_fp = NULL;
2543 static int seqnum = 1;
2544 struct CtdlMessage *imsg = NULL;
2546 size_t instr_alloc = 0;
2548 char *hold_R, *hold_D;
2549 char *collected_addresses = NULL;
2550 struct addresses_to_be_filed *aptr = NULL;
2551 char *saved_rfc822_version = NULL;
2552 int qualified_for_journaling = 0;
2553 struct CitContext *CCC = CC; /* CachedCitContext - performance boost */
2555 CtdlLogPrintf(CTDL_DEBUG, "CtdlSubmitMsg() called\n");
2556 if (is_valid_message(msg) == 0) return(-1); /* self check */
2558 /* If this message has no timestamp, we take the liberty of
2559 * giving it one, right now.
2561 if (msg->cm_fields['T'] == NULL) {
2562 snprintf(generated_timestamp, sizeof generated_timestamp, "%ld", (long)time(NULL));
2563 msg->cm_fields['T'] = strdup(generated_timestamp);
2566 /* If this message has no path, we generate one.
2568 if (msg->cm_fields['P'] == NULL) {
2569 if (msg->cm_fields['A'] != NULL) {
2570 msg->cm_fields['P'] = strdup(msg->cm_fields['A']);
2571 for (a=0; !IsEmptyStr(&msg->cm_fields['P'][a]); ++a) {
2572 if (isspace(msg->cm_fields['P'][a])) {
2573 msg->cm_fields['P'][a] = ' ';
2578 msg->cm_fields['P'] = strdup("unknown");
2582 if (force == NULL) {
2583 strcpy(force_room, "");
2586 strcpy(force_room, force);
2589 /* Learn about what's inside, because it's what's inside that counts */
2590 if (msg->cm_fields['M'] == NULL) {
2591 CtdlLogPrintf(CTDL_ERR, "ERROR: attempt to save message with NULL body\n");
2595 switch (msg->cm_format_type) {
2597 strcpy(content_type, "text/x-citadel-variformat");
2600 strcpy(content_type, "text/plain");
2603 strcpy(content_type, "text/plain");
2604 mptr = bmstrcasestr(msg->cm_fields['M'], "Content-type:");
2607 safestrncpy(content_type, &mptr[13], sizeof content_type);
2608 striplt(content_type);
2609 aptr = content_type;
2610 while (!IsEmptyStr(aptr)) {
2622 /* Goto the correct room */
2623 CtdlLogPrintf(CTDL_DEBUG, "Selected room %s\n", (recps) ? CCC->room.QRname : SENTITEMS);
2624 strcpy(hold_rm, CCC->room.QRname);
2625 strcpy(actual_rm, CCC->room.QRname);
2626 if (recps != NULL) {
2627 strcpy(actual_rm, SENTITEMS);
2630 /* If the user is a twit, move to the twit room for posting */
2632 if (CCC->user.axlevel == 2) {
2633 strcpy(hold_rm, actual_rm);
2634 strcpy(actual_rm, config.c_twitroom);
2635 CtdlLogPrintf(CTDL_DEBUG, "Diverting to twit room\n");
2639 /* ...or if this message is destined for Aide> then go there. */
2640 if (!IsEmptyStr(force_room)) {
2641 strcpy(actual_rm, force_room);
2644 CtdlLogPrintf(CTDL_DEBUG, "Final selection: %s\n", actual_rm);
2645 if (strcasecmp(actual_rm, CCC->room.QRname)) {
2646 /* getroom(&CCC->room, actual_rm); */
2647 usergoto(actual_rm, 0, 1, NULL, NULL);
2651 * If this message has no O (room) field, generate one.
2653 if (msg->cm_fields['O'] == NULL) {
2654 msg->cm_fields['O'] = strdup(CCC->room.QRname);
2657 /* Perform "before save" hooks (aborting if any return nonzero) */
2658 CtdlLogPrintf(CTDL_DEBUG, "Performing before-save hooks\n");
2659 if (PerformMessageHooks(msg, EVT_BEFORESAVE) > 0) return(-3);
2662 * If this message has an Exclusive ID, and the room is replication
2663 * checking enabled, then do replication checks.
2665 if (DoesThisRoomNeedEuidIndexing(&CCC->room)) {
2666 ReplicationChecks(msg);
2669 /* Save it to disk */
2670 CtdlLogPrintf(CTDL_DEBUG, "Saving to disk\n");
2671 newmsgid = send_message(msg);
2672 if (newmsgid <= 0L) return(-5);
2674 /* Write a supplemental message info record. This doesn't have to
2675 * be a critical section because nobody else knows about this message
2678 CtdlLogPrintf(CTDL_DEBUG, "Creating MetaData record\n");
2679 memset(&smi, 0, sizeof(struct MetaData));
2680 smi.meta_msgnum = newmsgid;
2681 smi.meta_refcount = 0;
2682 safestrncpy(smi.meta_content_type, content_type,
2683 sizeof smi.meta_content_type);
2686 * Measure how big this message will be when rendered as RFC822.
2687 * We do this for two reasons:
2688 * 1. We need the RFC822 length for the new metadata record, so the
2689 * POP and IMAP services don't have to calculate message lengths
2690 * while the user is waiting (multiplied by potentially hundreds
2691 * or thousands of messages).
2692 * 2. If journaling is enabled, we will need an RFC822 version of the
2693 * message to attach to the journalized copy.
2695 if (CCC->redirect_buffer != NULL) {
2696 CtdlLogPrintf(CTDL_ALERT, "CCC->redirect_buffer is not NULL during message submission!\n");
2699 CCC->redirect_buffer = malloc(SIZ);
2700 CCC->redirect_len = 0;
2701 CCC->redirect_alloc = SIZ;
2702 CtdlOutputPreLoadedMsg(msg, MT_RFC822, HEADERS_ALL, 0, 1);
2703 smi.meta_rfc822_length = CCC->redirect_len;
2704 saved_rfc822_version = CCC->redirect_buffer;
2705 CCC->redirect_buffer = NULL;
2706 CCC->redirect_len = 0;
2707 CCC->redirect_alloc = 0;
2711 /* Now figure out where to store the pointers */
2712 CtdlLogPrintf(CTDL_DEBUG, "Storing pointers\n");
2714 /* If this is being done by the networker delivering a private
2715 * message, we want to BYPASS saving the sender's copy (because there
2716 * is no local sender; it would otherwise go to the Trashcan).
2718 if ((!CCC->internal_pgm) || (recps == NULL)) {
2719 if (CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 1, msg) != 0) {
2720 CtdlLogPrintf(CTDL_ERR, "ERROR saving message pointer!\n");
2721 CtdlSaveMsgPointerInRoom(config.c_aideroom, newmsgid, 0, msg);
2725 /* For internet mail, drop a copy in the outbound queue room */
2727 if (recps->num_internet > 0) {
2728 CtdlSaveMsgPointerInRoom(SMTP_SPOOLOUT_ROOM, newmsgid, 0, msg);
2731 /* If other rooms are specified, drop them there too. */
2733 if (recps->num_room > 0)
2734 for (i=0; i<num_tokens(recps->recp_room, '|'); ++i) {
2735 extract_token(recipient, recps->recp_room, i,
2736 '|', sizeof recipient);
2737 CtdlLogPrintf(CTDL_DEBUG, "Delivering to room <%s>\n", recipient);
2738 CtdlSaveMsgPointerInRoom(recipient, newmsgid, 0, msg);
2741 /* Bump this user's messages posted counter. */
2742 CtdlLogPrintf(CTDL_DEBUG, "Updating user\n");
2743 lgetuser(&CCC->user, CCC->curr_user);
2744 CCC->user.posted = CCC->user.posted + 1;
2745 lputuser(&CCC->user);
2747 /* If this is private, local mail, make a copy in the
2748 * recipient's mailbox and bump the reference count.
2751 if (recps->num_local > 0)
2752 for (i=0; i<num_tokens(recps->recp_local, '|'); ++i) {
2753 extract_token(recipient, recps->recp_local, i,
2754 '|', sizeof recipient);
2755 CtdlLogPrintf(CTDL_DEBUG, "Delivering private local mail to <%s>\n",
2757 if (getuser(&userbuf, recipient) == 0) {
2758 // Add a flag so the Funambol module knows its mail
2759 msg->cm_fields['W'] = strdup(recipient);
2760 MailboxName(actual_rm, sizeof actual_rm, &userbuf, MAILROOM);
2761 CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 0, msg);
2762 BumpNewMailCounter(userbuf.usernum);
2763 if (!IsEmptyStr(config.c_funambol_host) || !IsEmptyStr(config.c_pager_program)) {
2764 /* Generate a instruction message for the Funambol notification
2765 * server, in the same style as the SMTP queue
2768 instr = malloc(instr_alloc);
2769 snprintf(instr, instr_alloc,
2770 "Content-type: %s\n\nmsgid|%ld\nsubmitted|%ld\n"
2772 SPOOLMIME, newmsgid, (long)time(NULL),
2773 msg->cm_fields['A'], msg->cm_fields['N']
2776 imsg = malloc(sizeof(struct CtdlMessage));
2777 memset(imsg, 0, sizeof(struct CtdlMessage));
2778 imsg->cm_magic = CTDLMESSAGE_MAGIC;
2779 imsg->cm_anon_type = MES_NORMAL;
2780 imsg->cm_format_type = FMT_RFC822;
2781 imsg->cm_fields['A'] = strdup("Citadel");
2782 imsg->cm_fields['J'] = strdup("do not journal");
2783 imsg->cm_fields['M'] = instr; /* imsg owns this memory now */
2784 imsg->cm_fields['W'] = strdup(recipient);
2785 CtdlSubmitMsg(imsg, NULL, FNBL_QUEUE_ROOM);
2786 CtdlFreeMessage(imsg);
2790 CtdlLogPrintf(CTDL_DEBUG, "No user <%s>\n", recipient);
2791 CtdlSaveMsgPointerInRoom(config.c_aideroom,
2796 /* Perform "after save" hooks */
2797 CtdlLogPrintf(CTDL_DEBUG, "Performing after-save hooks\n");
2798 PerformMessageHooks(msg, EVT_AFTERSAVE);
2800 /* For IGnet mail, we have to save a new copy into the spooler for
2801 * each recipient, with the R and D fields set to the recipient and
2802 * destination-node. This has two ugly side effects: all other
2803 * recipients end up being unlisted in this recipient's copy of the
2804 * message, and it has to deliver multiple messages to the same
2805 * node. We'll revisit this again in a year or so when everyone has
2806 * a network spool receiver that can handle the new style messages.
2809 if (recps->num_ignet > 0)
2810 for (i=0; i<num_tokens(recps->recp_ignet, '|'); ++i) {
2811 extract_token(recipient, recps->recp_ignet, i,
2812 '|', sizeof recipient);
2814 hold_R = msg->cm_fields['R'];
2815 hold_D = msg->cm_fields['D'];
2816 msg->cm_fields['R'] = malloc(SIZ);
2817 msg->cm_fields['D'] = malloc(128);
2818 extract_token(msg->cm_fields['R'], recipient, 0, '@', SIZ);
2819 extract_token(msg->cm_fields['D'], recipient, 1, '@', 128);
2821 serialize_message(&smr, msg);
2823 snprintf(submit_filename, sizeof submit_filename,
2824 "%s/netmail.%04lx.%04x.%04x",
2826 (long) getpid(), CCC->cs_pid, ++seqnum);
2827 network_fp = fopen(submit_filename, "wb+");
2828 if (network_fp != NULL) {
2829 fwrite(smr.ser, smr.len, 1, network_fp);
2835 free(msg->cm_fields['R']);
2836 free(msg->cm_fields['D']);
2837 msg->cm_fields['R'] = hold_R;
2838 msg->cm_fields['D'] = hold_D;
2841 /* Go back to the room we started from */
2842 CtdlLogPrintf(CTDL_DEBUG, "Returning to original room %s\n", hold_rm);
2843 if (strcasecmp(hold_rm, CCC->room.QRname))
2844 usergoto(hold_rm, 0, 1, NULL, NULL);
2846 /* For internet mail, generate delivery instructions.
2847 * Yes, this is recursive. Deal with it. Infinite recursion does
2848 * not happen because the delivery instructions message does not
2849 * contain a recipient.
2852 if (recps->num_internet > 0) {
2853 CtdlLogPrintf(CTDL_DEBUG, "Generating delivery instructions\n");
2855 instr = malloc(instr_alloc);
2856 snprintf(instr, instr_alloc,
2857 "Content-type: %s\n\nmsgid|%ld\nsubmitted|%ld\n"
2859 SPOOLMIME, newmsgid, (long)time(NULL),
2860 msg->cm_fields['A'], msg->cm_fields['N']
2863 for (i=0; i<num_tokens(recps->recp_internet, '|'); ++i) {
2864 size_t tmp = strlen(instr);
2865 extract_token(recipient, recps->recp_internet, i, '|', sizeof recipient);
2866 if ((tmp + strlen(recipient) + 32) > instr_alloc) {
2867 instr_alloc = instr_alloc * 2;
2868 instr = realloc(instr, instr_alloc);
2870 snprintf(&instr[tmp], instr_alloc - tmp, "remote|%s|0||\n", recipient);
2873 imsg = malloc(sizeof(struct CtdlMessage));
2874 memset(imsg, 0, sizeof(struct CtdlMessage));
2875 imsg->cm_magic = CTDLMESSAGE_MAGIC;
2876 imsg->cm_anon_type = MES_NORMAL;
2877 imsg->cm_format_type = FMT_RFC822;
2878 imsg->cm_fields['A'] = strdup("Citadel");
2879 imsg->cm_fields['J'] = strdup("do not journal");
2880 imsg->cm_fields['M'] = instr; /* imsg owns this memory now */
2881 CtdlSubmitMsg(imsg, NULL, SMTP_SPOOLOUT_ROOM);
2882 CtdlFreeMessage(imsg);
2886 * Any addresses to harvest for someone's address book?
2888 if ( (CCC->logged_in) && (recps != NULL) ) {
2889 collected_addresses = harvest_collected_addresses(msg);
2892 if (collected_addresses != NULL) {
2893 begin_critical_section(S_ATBF);
2894 aptr = (struct addresses_to_be_filed *)
2895 malloc(sizeof(struct addresses_to_be_filed));
2897 MailboxName(actual_rm, sizeof actual_rm,
2898 &CCC->user, USERCONTACTSROOM);
2899 aptr->roomname = strdup(actual_rm);
2900 aptr->collected_addresses = collected_addresses;
2902 end_critical_section(S_ATBF);
2906 * Determine whether this message qualifies for journaling.
2908 if (msg->cm_fields['J'] != NULL) {
2909 qualified_for_journaling = 0;
2912 if (recps == NULL) {
2913 qualified_for_journaling = config.c_journal_pubmsgs;
2915 else if (recps->num_local + recps->num_ignet + recps->num_internet > 0) {
2916 qualified_for_journaling = config.c_journal_email;
2919 qualified_for_journaling = config.c_journal_pubmsgs;
2924 * Do we have to perform journaling? If so, hand off the saved
2925 * RFC822 version will be handed off to the journaler for background
2926 * submit. Otherwise, we have to free the memory ourselves.
2928 if (saved_rfc822_version != NULL) {
2929 if (qualified_for_journaling) {
2930 JournalBackgroundSubmit(msg, saved_rfc822_version, recps);
2933 free(saved_rfc822_version);
2946 * Convenience function for generating small administrative messages.
2948 void quickie_message(char *from, char *fromaddr, char *to, char *room, char *text,
2949 int format_type, char *subject)
2951 struct CtdlMessage *msg;
2952 struct recptypes *recp = NULL;
2954 msg = malloc(sizeof(struct CtdlMessage));
2955 memset(msg, 0, sizeof(struct CtdlMessage));
2956 msg->cm_magic = CTDLMESSAGE_MAGIC;
2957 msg->cm_anon_type = MES_NORMAL;
2958 msg->cm_format_type = format_type;
2961 msg->cm_fields['A'] = strdup(from);
2963 else if (fromaddr != NULL) {
2964 msg->cm_fields['A'] = strdup(fromaddr);
2965 if (strchr(msg->cm_fields['A'], '@')) {
2966 *strchr(msg->cm_fields['A'], '@') = 0;
2970 msg->cm_fields['A'] = strdup("Citadel");
2973 if (fromaddr != NULL) msg->cm_fields['F'] = strdup(fromaddr);
2974 if (room != NULL) msg->cm_fields['O'] = strdup(room);
2975 msg->cm_fields['N'] = strdup(NODENAME);
2977 msg->cm_fields['R'] = strdup(to);
2978 recp = validate_recipients(to, NULL, 0);
2980 if (subject != NULL) {
2981 msg->cm_fields['U'] = strdup(subject);
2983 msg->cm_fields['M'] = strdup(text);
2985 CtdlSubmitMsg(msg, recp, room);
2986 CtdlFreeMessage(msg);
2987 if (recp != NULL) free_recipients(recp);
2993 * Back end function used by CtdlMakeMessage() and similar functions
2995 char *CtdlReadMessageBody(char *terminator, /* token signalling EOT */
2996 size_t maxlen, /* maximum message length */
2997 char *exist, /* if non-null, append to it;
2998 exist is ALWAYS freed */
2999 int crlf, /* CRLF newlines instead of LF */
3000 int sock /* socket handle or 0 for this session's client socket */
3004 size_t message_len = 0;
3005 size_t buffer_len = 0;
3012 if (exist == NULL) {
3019 message_len = strlen(exist);
3020 buffer_len = message_len + 4096;
3021 m = realloc(exist, buffer_len);
3028 /* Do we need to change leading ".." to "." for SMTP escaping? */
3029 if (!strcmp(terminator, ".")) {
3033 /* flush the input if we have nowhere to store it */
3038 /* read in the lines of message text one by one */
3041 if (sock_getln(sock, buf, (sizeof buf - 3)) < 0) finished = 1;
3044 if (client_getln(buf, (sizeof buf - 3)) < 1) finished = 1;
3046 if (!strcmp(buf, terminator)) finished = 1;
3048 strcat(buf, "\r\n");
3054 /* Unescape SMTP-style input of two dots at the beginning of the line */
3056 if (!strncmp(buf, "..", 2)) {
3057 strcpy(buf, &buf[1]);
3061 if ( (!flushing) && (!finished) ) {
3062 /* Measure the line */
3063 linelen = strlen(buf);
3065 /* augment the buffer if we have to */
3066 if ((message_len + linelen) >= buffer_len) {
3067 ptr = realloc(m, (buffer_len * 2) );
3068 if (ptr == NULL) { /* flush if can't allocate */
3071 buffer_len = (buffer_len * 2);
3073 CtdlLogPrintf(CTDL_DEBUG, "buffer_len is now %ld\n", (long)buffer_len);
3077 /* Add the new line to the buffer. NOTE: this loop must avoid
3078 * using functions like strcat() and strlen() because they
3079 * traverse the entire buffer upon every call, and doing that
3080 * for a multi-megabyte message slows it down beyond usability.
3082 strcpy(&m[message_len], buf);
3083 message_len += linelen;
3086 /* if we've hit the max msg length, flush the rest */
3087 if (message_len >= maxlen) flushing = 1;
3089 } while (!finished);
3097 * Build a binary message to be saved on disk.
3098 * (NOTE: if you supply 'preformatted_text', the buffer you give it
3099 * will become part of the message. This means you are no longer
3100 * responsible for managing that memory -- it will be freed along with
3101 * the rest of the fields when CtdlFreeMessage() is called.)
3104 struct CtdlMessage *CtdlMakeMessage(
3105 struct ctdluser *author, /* author's user structure */
3106 char *recipient, /* NULL if it's not mail */
3107 char *recp_cc, /* NULL if it's not mail */
3108 char *room, /* room where it's going */
3109 int type, /* see MES_ types in header file */
3110 int format_type, /* variformat, plain text, MIME... */
3111 char *fake_name, /* who we're masquerading as */
3112 char *my_email, /* which of my email addresses to use (empty is ok) */
3113 char *subject, /* Subject (optional) */
3114 char *supplied_euid, /* ...or NULL if this is irrelevant */
3115 char *preformatted_text, /* ...or NULL to read text from client */
3116 char *references /* Thread references */
3118 char dest_node[256];
3120 struct CtdlMessage *msg;
3122 msg = malloc(sizeof(struct CtdlMessage));
3123 memset(msg, 0, sizeof(struct CtdlMessage));
3124 msg->cm_magic = CTDLMESSAGE_MAGIC;
3125 msg->cm_anon_type = type;
3126 msg->cm_format_type = format_type;
3128 /* Don't confuse the poor folks if it's not routed mail. */
3129 strcpy(dest_node, "");
3134 /* Path or Return-Path */
3135 if (my_email == NULL) my_email = "";
3137 if (!IsEmptyStr(my_email)) {
3138 msg->cm_fields['P'] = strdup(my_email);
3141 snprintf(buf, sizeof buf, "%s", author->fullname);
3142 msg->cm_fields['P'] = strdup(buf);
3144 convert_spaces_to_underscores(msg->cm_fields['P']);
3146 snprintf(buf, sizeof buf, "%ld", (long)time(NULL)); /* timestamp */
3147 msg->cm_fields['T'] = strdup(buf);
3149 if (fake_name[0]) /* author */
3150 msg->cm_fields['A'] = strdup(fake_name);
3152 msg->cm_fields['A'] = strdup(author->fullname);
3154 if (CC->room.QRflags & QR_MAILBOX) { /* room */
3155 msg->cm_fields['O'] = strdup(&CC->room.QRname[11]);
3158 msg->cm_fields['O'] = strdup(CC->room.QRname);
3161 msg->cm_fields['N'] = strdup(NODENAME); /* nodename */
3162 msg->cm_fields['H'] = strdup(HUMANNODE); /* hnodename */
3164 if (recipient[0] != 0) {
3165 msg->cm_fields['R'] = strdup(recipient);
3167 if (recp_cc[0] != 0) {
3168 msg->cm_fields['Y'] = strdup(recp_cc);
3170 if (dest_node[0] != 0) {
3171 msg->cm_fields['D'] = strdup(dest_node);
3174 if (!IsEmptyStr(my_email)) {
3175 msg->cm_fields['F'] = strdup(my_email);
3177 else if ( (author == &CC->user) && (!IsEmptyStr(CC->cs_inet_email)) ) {
3178 msg->cm_fields['F'] = strdup(CC->cs_inet_email);
3181 if (subject != NULL) {
3184 length = strlen(subject);
3190 while ((subject[i] != '\0') &&
3191 (IsAscii = isascii(subject[i]) != 0 ))
3194 msg->cm_fields['U'] = strdup(subject);
3195 else /* ok, we've got utf8 in the string. */
3197 msg->cm_fields['U'] = rfc2047encode(subject, length);
3203 if (supplied_euid != NULL) {
3204 msg->cm_fields['E'] = strdup(supplied_euid);
3207 if (references != NULL) {
3208 if (!IsEmptyStr(references)) {
3209 msg->cm_fields['W'] = strdup(references);
3213 if (preformatted_text != NULL) {
3214 msg->cm_fields['M'] = preformatted_text;
3217 msg->cm_fields['M'] = CtdlReadMessageBody("000", config.c_maxmsglen, NULL, 0, 0);
3225 * Check to see whether we have permission to post a message in the current
3226 * room. Returns a *CITADEL ERROR CODE* and puts a message in errmsgbuf, or
3227 * returns 0 on success.
3229 int CtdlDoIHavePermissionToPostInThisRoom(char *errmsgbuf,
3231 const char* RemoteIdentifier,
3235 if (!(CC->logged_in) &&
3236 (PostPublic == POST_LOGGED_IN)) {
3237 snprintf(errmsgbuf, n, "Not logged in.");
3238 return (ERROR + NOT_LOGGED_IN);
3240 else if (PostPublic == CHECK_EXISTANCE) {
3241 return (0); // We're Evaling whether a recipient exists
3243 else if (!(CC->logged_in)) {
3245 if ((CC->room.QRflags & QR_READONLY)) {
3246 snprintf(errmsgbuf, n, "Not logged in.");
3247 return (ERROR + NOT_LOGGED_IN);
3249 if (CC->room.QRflags2 & QR2_MODERATED) {
3250 snprintf(errmsgbuf, n, "Not logged in Moderation feature not yet implemented!");
3251 return (ERROR + NOT_LOGGED_IN);
3253 if ((PostPublic!=POST_LMTP) &&(CC->room.QRflags2 & QR2_SMTP_PUBLIC) == 0) {
3258 if (RemoteIdentifier == NULL)
3260 snprintf(errmsgbuf, n, "Need sender to permit access.");
3261 return (ERROR + USERNAME_REQUIRED);
3264 assoc_file_name(filename, sizeof filename, &CC->room, ctdl_netcfg_dir);
3265 begin_critical_section(S_NETCONFIGS);
3266 if (!read_spoolcontrol_file(&sc, filename))
3268 end_critical_section(S_NETCONFIGS);
3269 snprintf(errmsgbuf, n,
3270 "This mailing list only accepts posts from subscribers.");
3271 return (ERROR + NO_SUCH_USER);
3273 end_critical_section(S_NETCONFIGS);
3274 found = is_recipient (sc, RemoteIdentifier);
3275 free_spoolcontrol_struct(&sc);
3280 snprintf(errmsgbuf, n,
3281 "This mailing list only accepts posts from subscribers.");
3282 return (ERROR + NO_SUCH_USER);
3289 if ((CC->user.axlevel < 2)
3290 && ((CC->room.QRflags & QR_MAILBOX) == 0)) {
3291 snprintf(errmsgbuf, n, "Need to be validated to enter "
3292 "(except in %s> to sysop)", MAILROOM);
3293 return (ERROR + HIGHER_ACCESS_REQUIRED);
3296 CtdlRoomAccess(&CC->room, &CC->user, &ra, NULL);
3297 if (!(ra & UA_POSTALLOWED)) {
3298 snprintf(errmsgbuf, n, "Higher access is required to post in this room.");
3299 return (ERROR + HIGHER_ACCESS_REQUIRED);
3302 strcpy(errmsgbuf, "Ok");
3308 * Check to see if the specified user has Internet mail permission
3309 * (returns nonzero if permission is granted)
3311 int CtdlCheckInternetMailPermission(struct ctdluser *who) {
3313 /* Do not allow twits to send Internet mail */
3314 if (who->axlevel <= 2) return(0);
3316 /* Globally enabled? */
3317 if (config.c_restrict == 0) return(1);
3319 /* User flagged ok? */
3320 if (who->flags & US_INTERNET) return(2);
3322 /* Aide level access? */
3323 if (who->axlevel >= 6) return(3);
3325 /* No mail for you! */
3331 * Validate recipients, count delivery types and errors, and handle aliasing
3332 * FIXME check for dupes!!!!!
3334 * Returns 0 if all addresses are ok, ret->num_error = -1 if no addresses
3335 * were specified, or the number of addresses found invalid.
3337 * Caller needs to free the result using free_recipients()
3339 struct recptypes *validate_recipients(char *supplied_recipients,
3340 const char *RemoteIdentifier,
3342 struct recptypes *ret;
3343 char *recipients = NULL;
3344 char this_recp[256];
3345 char this_recp_cooked[256];
3351 struct ctdluser tempUS;
3352 struct ctdlroom tempQR;
3353 struct ctdlroom tempQR2;
3359 ret = (struct recptypes *) malloc(sizeof(struct recptypes));
3360 if (ret == NULL) return(NULL);
3362 /* Set all strings to null and numeric values to zero */
3363 memset(ret, 0, sizeof(struct recptypes));
3365 if (supplied_recipients == NULL) {
3366 recipients = strdup("");
3369 recipients = strdup(supplied_recipients);
3372 /* Allocate some memory. Yes, this allocates 500% more memory than we will
3373 * actually need, but it's healthier for the heap than doing lots of tiny
3374 * realloc() calls instead.
3377 ret->errormsg = malloc(strlen(recipients) + 1024);
3378 ret->recp_local = malloc(strlen(recipients) + 1024);
3379 ret->recp_internet = malloc(strlen(recipients) + 1024);
3380 ret->recp_ignet = malloc(strlen(recipients) + 1024);
3381 ret->recp_room = malloc(strlen(recipients) + 1024);
3382 ret->display_recp = malloc(strlen(recipients) + 1024);
3384 ret->errormsg[0] = 0;
3385 ret->recp_local[0] = 0;
3386 ret->recp_internet[0] = 0;
3387 ret->recp_ignet[0] = 0;
3388 ret->recp_room[0] = 0;
3389 ret->display_recp[0] = 0;
3391 ret->recptypes_magic = RECPTYPES_MAGIC;
3393 /* Change all valid separator characters to commas */
3394 for (i=0; !IsEmptyStr(&recipients[i]); ++i) {
3395 if ((recipients[i] == ';') || (recipients[i] == '|')) {
3396 recipients[i] = ',';
3400 /* Now start extracting recipients... */
3402 while (!IsEmptyStr(recipients)) {
3404 for (i=0; i<=strlen(recipients); ++i) {
3405 if (recipients[i] == '\"') in_quotes = 1 - in_quotes;
3406 if ( ( (recipients[i] == ',') && (!in_quotes) ) || (recipients[i] == 0) ) {
3407 safestrncpy(this_recp, recipients, i+1);
3409 if (recipients[i] == ',') {
3410 strcpy(recipients, &recipients[i+1]);
3413 strcpy(recipients, "");
3420 if (IsEmptyStr(this_recp))
3422 CtdlLogPrintf(CTDL_DEBUG, "Evaluating recipient #%d: %s\n", num_recps, this_recp);
3424 mailtype = alias(this_recp);
3425 mailtype = alias(this_recp);
3426 mailtype = alias(this_recp);
3428 for (j=0; !IsEmptyStr(&this_recp[j]); ++j) {
3429 if (this_recp[j]=='_') {
3430 this_recp_cooked[j] = ' ';
3433 this_recp_cooked[j] = this_recp[j];
3436 this_recp_cooked[j] = '\0';
3441 if (!strcasecmp(this_recp, "sysop")) {
3443 strcpy(this_recp, config.c_aideroom);
3444 if (!IsEmptyStr(ret->recp_room)) {
3445 strcat(ret->recp_room, "|");
3447 strcat(ret->recp_room, this_recp);
3449 else if ( (!strncasecmp(this_recp, "room_", 5))
3450 && (!getroom(&tempQR, &this_recp_cooked[5])) ) {
3452 /* Save room so we can restore it later */
3456 /* Check permissions to send mail to this room */
3457 err = CtdlDoIHavePermissionToPostInThisRoom(errmsg,
3469 if (!IsEmptyStr(ret->recp_room)) {
3470 strcat(ret->recp_room, "|");
3472 strcat(ret->recp_room, &this_recp_cooked[5]);
3475 /* Restore room in case something needs it */
3479 else if (getuser(&tempUS, this_recp) == 0) {
3481 strcpy(this_recp, tempUS.fullname);
3482 if (!IsEmptyStr(ret->recp_local)) {
3483 strcat(ret->recp_local, "|");
3485 strcat(ret->recp_local, this_recp);
3487 else if (getuser(&tempUS, this_recp_cooked) == 0) {
3489 strcpy(this_recp, tempUS.fullname);
3490 if (!IsEmptyStr(ret->recp_local)) {
3491 strcat(ret->recp_local, "|");
3493 strcat(ret->recp_local, this_recp);
3501 /* Yes, you're reading this correctly: if the target
3502 * domain points back to the local system or an attached
3503 * Citadel directory, the address is invalid. That's
3504 * because if the address were valid, we would have
3505 * already translated it to a local address by now.
3507 if (IsDirectory(this_recp, 0)) {
3512 ++ret->num_internet;
3513 if (!IsEmptyStr(ret->recp_internet)) {
3514 strcat(ret->recp_internet, "|");
3516 strcat(ret->recp_internet, this_recp);
3521 if (!IsEmptyStr(ret->recp_ignet)) {
3522 strcat(ret->recp_ignet, "|");
3524 strcat(ret->recp_ignet, this_recp);
3532 if (IsEmptyStr(errmsg)) {
3533 snprintf(append, sizeof append, "Invalid recipient: %s", this_recp);
3536 snprintf(append, sizeof append, "%s", errmsg);
3538 if ( (strlen(ret->errormsg) + strlen(append) + 3) < SIZ) {
3539 if (!IsEmptyStr(ret->errormsg)) {
3540 strcat(ret->errormsg, "; ");
3542 strcat(ret->errormsg, append);
3546 if (IsEmptyStr(ret->display_recp)) {
3547 strcpy(append, this_recp);
3550 snprintf(append, sizeof append, ", %s", this_recp);
3552 if ( (strlen(ret->display_recp)+strlen(append)) < SIZ) {
3553 strcat(ret->display_recp, append);
3558 if ((ret->num_local + ret->num_internet + ret->num_ignet +
3559 ret->num_room + ret->num_error) == 0) {
3560 ret->num_error = (-1);
3561 strcpy(ret->errormsg, "No recipients specified.");
3564 CtdlLogPrintf(CTDL_DEBUG, "validate_recipients()\n");
3565 CtdlLogPrintf(CTDL_DEBUG, " local: %d <%s>\n", ret->num_local, ret->recp_local);
3566 CtdlLogPrintf(CTDL_DEBUG, " room: %d <%s>\n", ret->num_room, ret->recp_room);
3567 CtdlLogPrintf(CTDL_DEBUG, " inet: %d <%s>\n", ret->num_internet, ret->recp_internet);
3568 CtdlLogPrintf(CTDL_DEBUG, " ignet: %d <%s>\n", ret->num_ignet, ret->recp_ignet);
3569 CtdlLogPrintf(CTDL_DEBUG, " error: %d <%s>\n", ret->num_error, ret->errormsg);
3577 * Destructor for struct recptypes
3579 void free_recipients(struct recptypes *valid) {
3581 if (valid == NULL) {
3585 if (valid->recptypes_magic != RECPTYPES_MAGIC) {
3586 CtdlLogPrintf(CTDL_EMERG, "Attempt to call free_recipients() on some other data type!\n");
3590 if (valid->errormsg != NULL) free(valid->errormsg);
3591 if (valid->recp_local != NULL) free(valid->recp_local);
3592 if (valid->recp_internet != NULL) free(valid->recp_internet);
3593 if (valid->recp_ignet != NULL) free(valid->recp_ignet);
3594 if (valid->recp_room != NULL) free(valid->recp_room);
3595 if (valid->display_recp != NULL) free(valid->display_recp);
3602 * message entry - mode 0 (normal)
3604 void cmd_ent0(char *entargs)
3610 char supplied_euid[128];
3612 int format_type = 0;
3613 char newusername[256];
3614 char newuseremail[256];
3615 struct CtdlMessage *msg;
3619 struct recptypes *valid = NULL;
3620 struct recptypes *valid_to = NULL;
3621 struct recptypes *valid_cc = NULL;
3622 struct recptypes *valid_bcc = NULL;
3624 int subject_required = 0;
3629 int newuseremail_ok = 0;
3630 char references[SIZ];
3635 post = extract_int(entargs, 0);
3636 extract_token(recp, entargs, 1, '|', sizeof recp);
3637 anon_flag = extract_int(entargs, 2);
3638 format_type = extract_int(entargs, 3);
3639 extract_token(subject, entargs, 4, '|', sizeof subject);
3640 extract_token(newusername, entargs, 5, '|', sizeof newusername);
3641 do_confirm = extract_int(entargs, 6);
3642 extract_token(cc, entargs, 7, '|', sizeof cc);
3643 extract_token(bcc, entargs, 8, '|', sizeof bcc);
3644 switch(CC->room.QRdefaultview) {
3647 extract_token(supplied_euid, entargs, 9, '|', sizeof supplied_euid);
3650 supplied_euid[0] = 0;
3653 extract_token(newuseremail, entargs, 10, '|', sizeof newuseremail);
3654 extract_token(references, entargs, 11, '|', sizeof references);
3655 for (ptr=references; *ptr != 0; ++ptr) {
3656 if (*ptr == '!') *ptr = '|';
3659 /* first check to make sure the request is valid. */
3661 err = CtdlDoIHavePermissionToPostInThisRoom(errmsg, sizeof errmsg, NULL, POST_LOGGED_IN);
3664 cprintf("%d %s\n", err, errmsg);
3668 /* Check some other permission type things. */
3670 if (IsEmptyStr(newusername)) {
3671 strcpy(newusername, CC->user.fullname);
3673 if ( (CC->user.axlevel < 6)
3674 && (strcasecmp(newusername, CC->user.fullname))
3675 && (strcasecmp(newusername, CC->cs_inet_fn))
3677 cprintf("%d You don't have permission to author messages as '%s'.\n",
3678 ERROR + HIGHER_ACCESS_REQUIRED,
3685 if (IsEmptyStr(newuseremail)) {
3686 newuseremail_ok = 1;
3689 if (!IsEmptyStr(newuseremail)) {
3690 if (!strcasecmp(newuseremail, CC->cs_inet_email)) {
3691 newuseremail_ok = 1;
3693 else if (!IsEmptyStr(CC->cs_inet_other_emails)) {
3694 j = num_tokens(CC->cs_inet_other_emails, '|');
3695 for (i=0; i<j; ++i) {
3696 extract_token(buf, CC->cs_inet_other_emails, i, '|', sizeof buf);
3697 if (!strcasecmp(newuseremail, buf)) {
3698 newuseremail_ok = 1;
3704 if (!newuseremail_ok) {
3705 cprintf("%d You don't have permission to author messages as '%s'.\n",
3706 ERROR + HIGHER_ACCESS_REQUIRED,
3712 CC->cs_flags |= CS_POSTING;
3714 /* In mailbox rooms we have to behave a little differently --
3715 * make sure the user has specified at least one recipient. Then
3716 * validate the recipient(s). We do this for the Mail> room, as
3717 * well as any room which has the "Mailbox" view set.
3720 if ( ( (CC->room.QRflags & QR_MAILBOX) && (!strcasecmp(&CC->room.QRname[11], MAILROOM)) )
3721 || ( (CC->room.QRflags & QR_MAILBOX) && (CC->curr_view == VIEW_MAILBOX) )
3723 if (CC->user.axlevel < 2) {
3724 strcpy(recp, "sysop");
3729 valid_to = validate_recipients(recp, NULL, 0);
3730 if (valid_to->num_error > 0) {
3731 cprintf("%d %s\n", ERROR + NO_SUCH_USER, valid_to->errormsg);
3732 free_recipients(valid_to);
3736 valid_cc = validate_recipients(cc, NULL, 0);
3737 if (valid_cc->num_error > 0) {
3738 cprintf("%d %s\n", ERROR + NO_SUCH_USER, valid_cc->errormsg);
3739 free_recipients(valid_to);
3740 free_recipients(valid_cc);
3744 valid_bcc = validate_recipients(bcc, NULL, 0);
3745 if (valid_bcc->num_error > 0) {
3746 cprintf("%d %s\n", ERROR + NO_SUCH_USER, valid_bcc->errormsg);
3747 free_recipients(valid_to);
3748 free_recipients(valid_cc);
3749 free_recipients(valid_bcc);
3753 /* Recipient required, but none were specified */
3754 if ( (valid_to->num_error < 0) && (valid_cc->num_error < 0) && (valid_bcc->num_error < 0) ) {
3755 free_recipients(valid_to);
3756 free_recipients(valid_cc);
3757 free_recipients(valid_bcc);
3758 cprintf("%d At least one recipient is required.\n", ERROR + NO_SUCH_USER);
3762 if (valid_to->num_internet + valid_cc->num_internet + valid_bcc->num_internet > 0) {
3763 if (CtdlCheckInternetMailPermission(&CC->user)==0) {
3764 cprintf("%d You do not have permission "
3765 "to send Internet mail.\n",
3766 ERROR + HIGHER_ACCESS_REQUIRED);
3767 free_recipients(valid_to);
3768 free_recipients(valid_cc);
3769 free_recipients(valid_bcc);
3774 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)
3775 && (CC->user.axlevel < 4) ) {
3776 cprintf("%d Higher access required for network mail.\n",
3777 ERROR + HIGHER_ACCESS_REQUIRED);
3778 free_recipients(valid_to);
3779 free_recipients(valid_cc);
3780 free_recipients(valid_bcc);
3784 if ((RESTRICT_INTERNET == 1)
3785 && (valid_to->num_internet + valid_cc->num_internet + valid_bcc->num_internet > 0)
3786 && ((CC->user.flags & US_INTERNET) == 0)
3787 && (!CC->internal_pgm)) {
3788 cprintf("%d You don't have access to Internet mail.\n",
3789 ERROR + HIGHER_ACCESS_REQUIRED);
3790 free_recipients(valid_to);
3791 free_recipients(valid_cc);
3792 free_recipients(valid_bcc);
3798 /* Is this a room which has anonymous-only or anonymous-option? */
3799 anonymous = MES_NORMAL;
3800 if (CC->room.QRflags & QR_ANONONLY) {
3801 anonymous = MES_ANONONLY;
3803 if (CC->room.QRflags & QR_ANONOPT) {
3804 if (anon_flag == 1) { /* only if the user requested it */
3805 anonymous = MES_ANONOPT;
3809 if ((CC->room.QRflags & QR_MAILBOX) == 0) {
3813 /* Recommend to the client that the use of a message subject is
3814 * strongly recommended in this room, if either the SUBJECTREQ flag
3815 * is set, or if there is one or more Internet email recipients.
3817 if (CC->room.QRflags2 & QR2_SUBJECTREQ) subject_required = 1;
3818 if (valid_to) if (valid_to->num_internet > 0) subject_required = 1;
3819 if (valid_cc) if (valid_cc->num_internet > 0) subject_required = 1;
3820 if (valid_bcc) if (valid_bcc->num_internet > 0) subject_required = 1;
3822 /* If we're only checking the validity of the request, return
3823 * success without creating the message.
3826 cprintf("%d %s|%d\n", CIT_OK,
3827 ((valid_to != NULL) ? valid_to->display_recp : ""),
3829 free_recipients(valid_to);
3830 free_recipients(valid_cc);
3831 free_recipients(valid_bcc);
3835 /* We don't need these anymore because we'll do it differently below */
3836 free_recipients(valid_to);
3837 free_recipients(valid_cc);
3838 free_recipients(valid_bcc);
3840 /* Read in the message from the client. */
3842 cprintf("%d send message\n", START_CHAT_MODE);
3844 cprintf("%d send message\n", SEND_LISTING);
3847 msg = CtdlMakeMessage(&CC->user, recp, cc,
3848 CC->room.QRname, anonymous, format_type,
3849 newusername, newuseremail, subject,
3850 ((!IsEmptyStr(supplied_euid)) ? supplied_euid : NULL),
3853 /* Put together one big recipients struct containing to/cc/bcc all in
3854 * one. This is for the envelope.
3856 char *all_recps = malloc(SIZ * 3);
3857 strcpy(all_recps, recp);
3858 if (!IsEmptyStr(cc)) {
3859 if (!IsEmptyStr(all_recps)) {
3860 strcat(all_recps, ",");
3862 strcat(all_recps, cc);
3864 if (!IsEmptyStr(bcc)) {
3865 if (!IsEmptyStr(all_recps)) {
3866 strcat(all_recps, ",");
3868 strcat(all_recps, bcc);
3870 if (!IsEmptyStr(all_recps)) {
3871 valid = validate_recipients(all_recps, NULL, 0);
3879 msgnum = CtdlSubmitMsg(msg, valid, "");
3882 cprintf("%ld\n", msgnum);
3884 cprintf("Message accepted.\n");
3887 cprintf("Internal error.\n");
3889 if (msg->cm_fields['E'] != NULL) {
3890 cprintf("%s\n", msg->cm_fields['E']);
3897 CtdlFreeMessage(msg);
3899 if (valid != NULL) {
3900 free_recipients(valid);
3908 * API function to delete messages which match a set of criteria
3909 * (returns the actual number of messages deleted)
3911 int CtdlDeleteMessages(char *room_name, /* which room */
3912 long *dmsgnums, /* array of msg numbers to be deleted */
3913 int num_dmsgnums, /* number of msgs to be deleted, or 0 for "any" */
3914 char *content_type /* or "" for any. regular expressions expected. */
3917 struct ctdlroom qrbuf;
3918 struct cdbdata *cdbfr;
3919 long *msglist = NULL;
3920 long *dellist = NULL;
3923 int num_deleted = 0;
3925 struct MetaData smi;
3928 int need_to_free_re = 0;
3930 if (content_type) if (!IsEmptyStr(content_type)) {
3931 regcomp(&re, content_type, 0);
3932 need_to_free_re = 1;
3934 CtdlLogPrintf(CTDL_DEBUG, "CtdlDeleteMessages(%s, %d msgs, %s)\n",
3935 room_name, num_dmsgnums, content_type);
3937 /* get room record, obtaining a lock... */
3938 if (lgetroom(&qrbuf, room_name) != 0) {
3939 CtdlLogPrintf(CTDL_ERR, "CtdlDeleteMessages(): Room <%s> not found\n",
3941 if (need_to_free_re) regfree(&re);
3942 return (0); /* room not found */
3944 cdbfr = cdb_fetch(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long));
3946 if (cdbfr != NULL) {
3947 dellist = malloc(cdbfr->len);
3948 msglist = (long *) cdbfr->ptr;
3949 cdbfr->ptr = NULL; /* CtdlDeleteMessages() now owns this memory */
3950 num_msgs = cdbfr->len / sizeof(long);
3954 for (i = 0; i < num_msgs; ++i) {
3957 /* Set/clear a bit for each criterion */
3959 /* 0 messages in the list or a null list means that we are
3960 * interested in deleting any messages which meet the other criteria.
3962 if ((num_dmsgnums == 0) || (dmsgnums == NULL)) {
3963 delete_this |= 0x01;
3966 for (j=0; j<num_dmsgnums; ++j) {
3967 if (msglist[i] == dmsgnums[j]) {
3968 delete_this |= 0x01;
3973 if (IsEmptyStr(content_type)) {
3974 delete_this |= 0x02;
3976 GetMetaData(&smi, msglist[i]);
3977 if (regexec(&re, smi.meta_content_type, 1, &pm, 0) == 0) {
3978 delete_this |= 0x02;
3982 /* Delete message only if all bits are set */
3983 if (delete_this == 0x03) {
3984 dellist[num_deleted++] = msglist[i];
3989 num_msgs = sort_msglist(msglist, num_msgs);
3990 cdb_store(CDB_MSGLISTS, &qrbuf.QRnumber, (int)sizeof(long),
3991 msglist, (int)(num_msgs * sizeof(long)));
3993 qrbuf.QRhighest = msglist[num_msgs - 1];
3997 /* Go through the messages we pulled out of the index, and decrement
3998 * their reference counts by 1. If this is the only room the message
3999 * was in, the reference count will reach zero and the message will
4000 * automatically be deleted from the database. We do this in a
4001 * separate pass because there might be plug-in hooks getting called,
4002 * and we don't want that happening during an S_ROOMS critical
4005 if (num_deleted) for (i=0; i<num_deleted; ++i) {
4006 PerformDeleteHooks(qrbuf.QRname, dellist[i]);
4007 AdjRefCount(dellist[i], -1);
4010 /* Now free the memory we used, and go away. */
4011 if (msglist != NULL) free(msglist);
4012 if (dellist != NULL) free(dellist);
4013 CtdlLogPrintf(CTDL_DEBUG, "%d message(s) deleted.\n", num_deleted);
4014 if (need_to_free_re) regfree(&re);
4015 return (num_deleted);
4021 * Check whether the current user has permission to delete messages from
4022 * the current room (returns 1 for yes, 0 for no)
4024 int CtdlDoIHavePermissionToDeleteMessagesFromThisRoom(void) {
4026 CtdlRoomAccess(&CC->room, &CC->user, &ra, NULL);
4027 if (ra & UA_DELETEALLOWED) return(1);
4035 * Delete message from current room
4037 void cmd_dele(char *args)
4046 extract_token(msgset, args, 0, '|', sizeof msgset);
4047 num_msgs = num_tokens(msgset, ',');
4049 cprintf("%d Nothing to do.\n", CIT_OK);
4053 if (CtdlDoIHavePermissionToDeleteMessagesFromThisRoom() == 0) {
4054 cprintf("%d Higher access required.\n",
4055 ERROR + HIGHER_ACCESS_REQUIRED);
4060 * Build our message set to be moved/copied
4062 msgs = malloc(num_msgs * sizeof(long));
4063 for (i=0; i<num_msgs; ++i) {
4064 extract_token(msgtok, msgset, i, ',', sizeof msgtok);
4065 msgs[i] = atol(msgtok);
4068 num_deleted = CtdlDeleteMessages(CC->room.QRname, msgs, num_msgs, "");
4072 cprintf("%d %d message%s deleted.\n", CIT_OK,
4073 num_deleted, ((num_deleted != 1) ? "s" : ""));
4075 cprintf("%d Message not found.\n", ERROR + MESSAGE_NOT_FOUND);
4081 * Back end API function for moves and deletes (multiple messages)
4083 int CtdlCopyMsgsToRoom(long *msgnums, int num_msgs, char *dest) {
4086 err = CtdlSaveMsgPointersInRoom(dest, msgnums, num_msgs, 1, NULL);
4087 if (err != 0) return(err);
4096 * move or copy a message to another room
4098 void cmd_move(char *args)
4105 char targ[ROOMNAMELEN];
4106 struct ctdlroom qtemp;
4113 extract_token(msgset, args, 0, '|', sizeof msgset);
4114 num_msgs = num_tokens(msgset, ',');
4116 cprintf("%d Nothing to do.\n", CIT_OK);
4120 extract_token(targ, args, 1, '|', sizeof targ);
4121 convert_room_name_macros(targ, sizeof targ);
4122 targ[ROOMNAMELEN - 1] = 0;
4123 is_copy = extract_int(args, 2);
4125 if (getroom(&qtemp, targ) != 0) {
4126 cprintf("%d '%s' does not exist.\n",
4127 ERROR + ROOM_NOT_FOUND, targ);
4131 getuser(&CC->user, CC->curr_user);
4132 CtdlRoomAccess(&qtemp, &CC->user, &ra, NULL);
4134 /* Check for permission to perform this operation.
4135 * Remember: "CC->room" is source, "qtemp" is target.
4139 /* Aides can move/copy */
4140 if (CC->user.axlevel >= 6) permit = 1;
4142 /* Room aides can move/copy */
4143 if (CC->user.usernum == CC->room.QRroomaide) permit = 1;
4145 /* Permit move/copy from personal rooms */
4146 if ((CC->room.QRflags & QR_MAILBOX)
4147 && (qtemp.QRflags & QR_MAILBOX)) permit = 1;
4149 /* Permit only copy from public to personal room */
4151 && (!(CC->room.QRflags & QR_MAILBOX))
4152 && (qtemp.QRflags & QR_MAILBOX)) permit = 1;
4154 /* Permit message removal from collaborative delete rooms */
4155 if (CC->room.QRflags2 & QR2_COLLABDEL) permit = 1;
4157 /* Users allowed to post into the target room may move into it too. */
4158 if ((CC->room.QRflags & QR_MAILBOX) &&
4159 (qtemp.QRflags & UA_POSTALLOWED)) permit = 1;
4161 /* User must have access to target room */
4162 if (!(ra & UA_KNOWN)) permit = 0;
4165 cprintf("%d Higher access required.\n",
4166 ERROR + HIGHER_ACCESS_REQUIRED);
4171 * Build our message set to be moved/copied
4173 msgs = malloc(num_msgs * sizeof(long));
4174 for (i=0; i<num_msgs; ++i) {
4175 extract_token(msgtok, msgset, i, ',', sizeof msgtok);
4176 msgs[i] = atol(msgtok);
4182 err = CtdlCopyMsgsToRoom(msgs, num_msgs, targ);
4184 cprintf("%d Cannot store message(s) in %s: error %d\n",
4190 /* Now delete the message from the source room,
4191 * if this is a 'move' rather than a 'copy' operation.
4194 CtdlDeleteMessages(CC->room.QRname, msgs, num_msgs, "");
4198 cprintf("%d Message(s) %s.\n", CIT_OK, (is_copy ? "copied" : "moved") );
4204 * GetMetaData() - Get the supplementary record for a message
4206 void GetMetaData(struct MetaData *smibuf, long msgnum)
4209 struct cdbdata *cdbsmi;
4212 memset(smibuf, 0, sizeof(struct MetaData));
4213 smibuf->meta_msgnum = msgnum;
4214 smibuf->meta_refcount = 1; /* Default reference count is 1 */
4216 /* Use the negative of the message number for its supp record index */
4217 TheIndex = (0L - msgnum);
4219 cdbsmi = cdb_fetch(CDB_MSGMAIN, &TheIndex, sizeof(long));
4220 if (cdbsmi == NULL) {
4221 return; /* record not found; go with defaults */
4223 memcpy(smibuf, cdbsmi->ptr,
4224 ((cdbsmi->len > sizeof(struct MetaData)) ?
4225 sizeof(struct MetaData) : cdbsmi->len));
4232 * PutMetaData() - (re)write supplementary record for a message
4234 void PutMetaData(struct MetaData *smibuf)
4238 /* Use the negative of the message number for the metadata db index */
4239 TheIndex = (0L - smibuf->meta_msgnum);
4241 cdb_store(CDB_MSGMAIN,
4242 &TheIndex, (int)sizeof(long),
4243 smibuf, (int)sizeof(struct MetaData));
4248 * AdjRefCount - submit an adjustment to the reference count for a message.
4249 * (These are just queued -- we actually process them later.)
4251 void AdjRefCount(long msgnum, int incr)
4253 struct arcq new_arcq;
4255 begin_critical_section(S_SUPPMSGMAIN);
4256 if (arcfp == NULL) {
4257 arcfp = fopen(file_arcq, "ab+");
4259 end_critical_section(S_SUPPMSGMAIN);
4261 /* msgnum < 0 means that we're trying to close the file */
4263 CtdlLogPrintf(CTDL_DEBUG, "Closing the AdjRefCount queue file\n");
4264 begin_critical_section(S_SUPPMSGMAIN);
4265 if (arcfp != NULL) {
4269 end_critical_section(S_SUPPMSGMAIN);
4274 * If we can't open the queue, perform the operation synchronously.
4276 if (arcfp == NULL) {
4277 TDAP_AdjRefCount(msgnum, incr);
4281 new_arcq.arcq_msgnum = msgnum;
4282 new_arcq.arcq_delta = incr;
4283 fwrite(&new_arcq, sizeof(struct arcq), 1, arcfp);
4291 * TDAP_ProcessAdjRefCountQueue()
4293 * Process the queue of message count adjustments that was created by calls
4294 * to AdjRefCount() ... by reading the queue and calling TDAP_AdjRefCount()
4295 * for each one. This should be an "off hours" operation.
4297 int TDAP_ProcessAdjRefCountQueue(void)
4299 char file_arcq_temp[PATH_MAX];
4302 struct arcq arcq_rec;
4303 int num_records_processed = 0;
4305 snprintf(file_arcq_temp, sizeof file_arcq_temp, "%s2", file_arcq);
4307 begin_critical_section(S_SUPPMSGMAIN);
4308 if (arcfp != NULL) {
4313 r = link(file_arcq, file_arcq_temp);
4315 CtdlLogPrintf(CTDL_CRIT, "%s: %s\n", file_arcq_temp, strerror(errno));
4316 end_critical_section(S_SUPPMSGMAIN);
4317 return(num_records_processed);
4321 end_critical_section(S_SUPPMSGMAIN);
4323 fp = fopen(file_arcq_temp, "rb");
4325 CtdlLogPrintf(CTDL_CRIT, "%s: %s\n", file_arcq_temp, strerror(errno));
4326 return(num_records_processed);
4329 while (fread(&arcq_rec, sizeof(struct arcq), 1, fp) == 1) {
4330 TDAP_AdjRefCount(arcq_rec.arcq_msgnum, arcq_rec.arcq_delta);
4331 ++num_records_processed;
4335 r = unlink(file_arcq_temp);
4337 CtdlLogPrintf(CTDL_CRIT, "%s: %s\n", file_arcq_temp, strerror(errno));
4340 return(num_records_processed);
4346 * TDAP_AdjRefCount - adjust the reference count for a message.
4347 * This one does it "for real" because it's called by
4348 * the autopurger function that processes the queue
4349 * created by AdjRefCount(). If a message's reference
4350 * count becomes zero, we also delete the message from
4351 * disk and de-index it.
4353 void TDAP_AdjRefCount(long msgnum, int incr)
4356 struct MetaData smi;
4359 /* This is a *tight* critical section; please keep it that way, as
4360 * it may get called while nested in other critical sections.
4361 * Complicating this any further will surely cause deadlock!
4363 begin_critical_section(S_SUPPMSGMAIN);
4364 GetMetaData(&smi, msgnum);
4365 smi.meta_refcount += incr;
4367 end_critical_section(S_SUPPMSGMAIN);
4368 CtdlLogPrintf(CTDL_DEBUG, "msg %ld ref count delta %d, is now %d\n",
4369 msgnum, incr, smi.meta_refcount);
4371 /* If the reference count is now zero, delete the message
4372 * (and its supplementary record as well).
4374 if (smi.meta_refcount == 0) {
4375 CtdlLogPrintf(CTDL_DEBUG, "Deleting message <%ld>\n", msgnum);
4377 /* Call delete hooks with NULL room to show it has gone altogether */
4378 PerformDeleteHooks(NULL, msgnum);
4380 /* Remove from message base */
4382 cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long));
4383 cdb_delete(CDB_BIGMSGS, &delnum, (int)sizeof(long));
4385 /* Remove metadata record */
4386 delnum = (0L - msgnum);
4387 cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long));
4393 * Write a generic object to this room
4395 * Note: this could be much more efficient. Right now we use two temporary
4396 * files, and still pull the message into memory as with all others.
4398 void CtdlWriteObject(char *req_room, /* Room to stuff it in */
4399 char *content_type, /* MIME type of this object */
4400 char *tempfilename, /* Where to fetch it from */
4401 struct ctdluser *is_mailbox, /* Mailbox room? */
4402 int is_binary, /* Is encoding necessary? */
4403 int is_unique, /* Del others of this type? */
4404 unsigned int flags /* Internal save flags */
4409 struct ctdlroom qrbuf;
4410 char roomname[ROOMNAMELEN];
4411 struct CtdlMessage *msg;
4413 char *raw_message = NULL;
4414 char *encoded_message = NULL;
4415 off_t raw_length = 0;
4417 if (is_mailbox != NULL) {
4418 MailboxName(roomname, sizeof roomname, is_mailbox, req_room);
4421 safestrncpy(roomname, req_room, sizeof(roomname));
4424 fp = fopen(tempfilename, "rb");
4426 CtdlLogPrintf(CTDL_CRIT, "Cannot open %s: %s\n",
4427 tempfilename, strerror(errno));
4430 fseek(fp, 0L, SEEK_END);
4431 raw_length = ftell(fp);
4433 CtdlLogPrintf(CTDL_DEBUG, "Raw length is %ld\n", (long)raw_length);
4435 raw_message = malloc((size_t)raw_length + 2);
4436 fread(raw_message, (size_t)raw_length, 1, fp);
4440 encoded_message = malloc((size_t)
4441 (((raw_length * 134) / 100) + 4096 ) );
4444 encoded_message = malloc((size_t)(raw_length + 4096));
4447 sprintf(encoded_message, "Content-type: %s\n", content_type);
4450 sprintf(&encoded_message[strlen(encoded_message)],
4451 "Content-transfer-encoding: base64\n\n"
4455 sprintf(&encoded_message[strlen(encoded_message)],
4456 "Content-transfer-encoding: 7bit\n\n"
4462 &encoded_message[strlen(encoded_message)],
4469 raw_message[raw_length] = 0;
4471 &encoded_message[strlen(encoded_message)],
4479 CtdlLogPrintf(CTDL_DEBUG, "Allocating\n");
4480 msg = malloc(sizeof(struct CtdlMessage));
4481 memset(msg, 0, sizeof(struct CtdlMessage));
4482 msg->cm_magic = CTDLMESSAGE_MAGIC;
4483 msg->cm_anon_type = MES_NORMAL;
4484 msg->cm_format_type = 4;
4485 msg->cm_fields['A'] = strdup(CC->user.fullname);
4486 msg->cm_fields['O'] = strdup(req_room);
4487 msg->cm_fields['N'] = strdup(config.c_nodename);
4488 msg->cm_fields['H'] = strdup(config.c_humannode);
4489 msg->cm_flags = flags;
4491 msg->cm_fields['M'] = encoded_message;
4493 /* Create the requested room if we have to. */
4494 if (getroom(&qrbuf, roomname) != 0) {
4495 create_room(roomname,
4496 ( (is_mailbox != NULL) ? 5 : 3 ),
4497 "", 0, 1, 0, VIEW_BBS);
4499 /* If the caller specified this object as unique, delete all
4500 * other objects of this type that are currently in the room.
4503 CtdlLogPrintf(CTDL_DEBUG, "Deleted %d other msgs of this type\n",
4504 CtdlDeleteMessages(roomname, NULL, 0, content_type)
4507 /* Now write the data */
4508 CtdlSubmitMsg(msg, NULL, roomname);
4509 CtdlFreeMessage(msg);
4517 void CtdlGetSysConfigBackend(long msgnum, void *userdata) {
4518 config_msgnum = msgnum;
4522 char *CtdlGetSysConfig(char *sysconfname) {
4523 char hold_rm[ROOMNAMELEN];
4526 struct CtdlMessage *msg;
4529 strcpy(hold_rm, CC->room.QRname);
4530 if (getroom(&CC->room, SYSCONFIGROOM) != 0) {
4531 getroom(&CC->room, hold_rm);
4536 /* We want the last (and probably only) config in this room */
4537 begin_critical_section(S_CONFIG);
4538 config_msgnum = (-1L);
4539 CtdlForEachMessage(MSGS_LAST, 1, NULL, sysconfname, NULL,
4540 CtdlGetSysConfigBackend, NULL);
4541 msgnum = config_msgnum;
4542 end_critical_section(S_CONFIG);
4548 msg = CtdlFetchMessage(msgnum, 1);
4550 conf = strdup(msg->cm_fields['M']);
4551 CtdlFreeMessage(msg);
4558 getroom(&CC->room, hold_rm);
4560 if (conf != NULL) do {
4561 extract_token(buf, conf, 0, '\n', sizeof buf);
4562 strcpy(conf, &conf[strlen(buf)+1]);
4563 } while ( (!IsEmptyStr(conf)) && (!IsEmptyStr(buf)) );
4568 void CtdlPutSysConfig(char *sysconfname, char *sysconfdata) {
4569 char temp[PATH_MAX];
4572 CtdlMakeTempFileName(temp, sizeof temp);
4574 fp = fopen(temp, "w");
4575 if (fp == NULL) return;
4576 fprintf(fp, "%s", sysconfdata);
4579 /* this handy API function does all the work for us */
4580 CtdlWriteObject(SYSCONFIGROOM, sysconfname, temp, NULL, 0, 1, 0);
4586 * Determine whether a given Internet address belongs to the current user
4588 int CtdlIsMe(char *addr, int addr_buf_len)
4590 struct recptypes *recp;
4593 recp = validate_recipients(addr, NULL, 0);
4594 if (recp == NULL) return(0);
4596 if (recp->num_local == 0) {
4597 free_recipients(recp);
4601 for (i=0; i<recp->num_local; ++i) {
4602 extract_token(addr, recp->recp_local, i, '|', addr_buf_len);
4603 if (!strcasecmp(addr, CC->user.fullname)) {
4604 free_recipients(recp);
4609 free_recipients(recp);
4615 * Citadel protocol command to do the same
4617 void cmd_isme(char *argbuf) {
4620 if (CtdlAccessCheck(ac_logged_in)) return;
4621 extract_token(addr, argbuf, 0, '|', sizeof addr);
4623 if (CtdlIsMe(addr, sizeof addr)) {
4624 cprintf("%d %s\n", CIT_OK, addr);
4627 cprintf("%d Not you.\n", ERROR + ILLEGAL_VALUE);