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 */
1376 int flags /* should the bessage be exported clean? */
1378 struct CtdlMessage *TheMessage = NULL;
1379 int retcode = om_no_such_msg;
1380 struct encapmsg encap;
1382 CtdlLogPrintf(CTDL_DEBUG, "CtdlOutputMsg() msgnum=%ld, mode=%d, section=%s\n",
1384 (section ? section : "<>")
1387 if ((!(CC->logged_in)) && (!(CC->internal_pgm))) {
1388 if (do_proto) cprintf("%d Not logged in.\n",
1389 ERROR + NOT_LOGGED_IN);
1390 return(om_not_logged_in);
1393 /* FIXME: check message id against msglist for this room */
1396 * Fetch the message from disk. If we're in any sort of headers
1397 * only mode, request that we don't even bother loading the body
1400 if ( (headers_only == HEADERS_FAST) || (headers_only == HEADERS_ONLY) ) {
1401 TheMessage = CtdlFetchMessage(msg_num, 0);
1404 TheMessage = CtdlFetchMessage(msg_num, 1);
1407 if (TheMessage == NULL) {
1408 if (do_proto) cprintf("%d Can't locate msg %ld on disk\n",
1409 ERROR + MESSAGE_NOT_FOUND, msg_num);
1410 return(om_no_such_msg);
1413 /* Here is the weird form of this command, to process only an
1414 * encapsulated message/rfc822 section.
1416 if (section) if (!IsEmptyStr(section)) if (strcmp(section, "0")) {
1417 memset(&encap, 0, sizeof encap);
1418 safestrncpy(encap.desired_section, section, sizeof encap.desired_section);
1419 mime_parser(TheMessage->cm_fields['M'],
1421 *extract_encapsulated_message,
1422 NULL, NULL, (void *)&encap, 0
1424 CtdlFreeMessage(TheMessage);
1428 encap.msg[encap.msglen] = 0;
1429 TheMessage = convert_internet_message(encap.msg);
1430 encap.msg = NULL; /* no free() here, TheMessage owns it now */
1432 /* Now we let it fall through to the bottom of this
1433 * function, because TheMessage now contains the
1434 * encapsulated message instead of the top-level
1435 * message. Isn't that neat?
1440 if (do_proto) cprintf("%d msg %ld has no part %s\n",
1441 ERROR + MESSAGE_NOT_FOUND, msg_num, section);
1442 retcode = om_no_such_msg;
1447 /* Ok, output the message now */
1448 retcode = CtdlOutputPreLoadedMsg(TheMessage, mode, headers_only, do_proto, crlf, flags);
1449 CtdlFreeMessage(TheMessage);
1454 char *qp_encode_email_addrs(char *source)
1456 char user[256], node[256], name[256];
1457 const char headerStr[] = "=?UTF-8?Q?";
1461 int need_to_encode = 0;
1467 long nAddrPtrMax = 50;
1476 AddrPtr = malloc (sizeof (long) * nAddrPtrMax);
1477 AddrUtf8 = malloc (sizeof (long) * nAddrPtrMax);
1478 memset(AddrUtf8, 0, sizeof (long) * nAddrPtrMax);
1481 while (!IsEmptyStr (&source[i])) {
1482 if (nColons > nAddrPtrMax){
1485 ptr = (long *) malloc(sizeof (long) * nAddrPtrMax * 2);
1486 memcpy (ptr, AddrPtr, sizeof (long) * nAddrPtrMax);
1487 free (AddrPtr), AddrPtr = ptr;
1488 ptr = (long *) malloc(sizeof (long) * nAddrPtrMax * 2);
1489 memset(ptr + sizeof (long) * nAddrPtrMax, 0, sizeof (long) * nAddrPtrMax - 1);
1490 memcpy (ptr, AddrUtf8, sizeof (long) * nAddrPtrMax);
1491 free (AddrUtf8), AddrUtf8 = ptr;
1494 if (((unsigned char) source[i] < 32) ||
1495 ((unsigned char) source[i] > 126)) {
1497 AddrUtf8[nColons] = 1;
1499 if (source[i] == '"')
1500 InQuotes = !InQuotes;
1501 if (!InQuotes && source[i] == ',') {
1503 AddrPtr[nColons] = i;
1507 if (need_to_encode == 0) {
1514 EncodedMaxLen = nColons * (sizeof(headerStr) + 3) + SourceLen * 3;
1515 Encoded = (char*) malloc (EncodedMaxLen);
1517 for (i = 1; i <= nColons; i++)
1518 source[AddrPtr[i]++] = '\0';
1522 for (i = 0; i <= nColons && nPtr != NULL; i++) {
1523 nmax = EncodedMaxLen - (nPtr - Encoded);
1525 process_rfc822_addr(&source[AddrPtr[i]],
1529 /* TODO: libIDN here ! */
1530 if (IsEmptyStr(name)) {
1531 n = snprintf(nPtr, nmax,
1532 (i==0)?"%s@%s" : ",%s@%s",
1536 EncodedName = rfc2047encode(name, strlen(name));
1537 n = snprintf(nPtr, nmax,
1538 (i==0)?"%s <%s@%s>" : ",%s <%s@%s>",
1539 EncodedName, user, node);
1544 n = snprintf(nPtr, nmax,
1545 (i==0)?"%s" : ",%s",
1546 &source[AddrPtr[i]]);
1552 ptr = (char*) malloc(EncodedMaxLen * 2);
1553 memcpy(ptr, Encoded, EncodedMaxLen);
1554 nnPtr = ptr + (nPtr - Encoded), nPtr = nnPtr;
1555 free(Encoded), Encoded = ptr;
1557 i--; /* do it once more with properly lengthened buffer */
1560 for (i = 1; i <= nColons; i++)
1561 source[--AddrPtr[i]] = ',';
1568 * Get a message off disk. (returns om_* values found in msgbase.h)
1570 int CtdlOutputPreLoadedMsg(
1571 struct CtdlMessage *TheMessage,
1572 int mode, /* how would you like that message? */
1573 int headers_only, /* eschew the message body? */
1574 int do_proto, /* do Citadel protocol responses? */
1575 int crlf, /* Use CRLF newlines instead of LF? */
1576 int flags /* should the bessage be exported clean? */
1580 cit_uint8_t ch, prev_ch;
1582 char display_name[256];
1584 char *nl; /* newline string */
1586 int subject_found = 0;
1589 /* Buffers needed for RFC822 translation. These are all filled
1590 * using functions that are bounds-checked, and therefore we can
1591 * make them substantially smaller than SIZ.
1599 char datestamp[100];
1601 CtdlLogPrintf(CTDL_DEBUG, "CtdlOutputPreLoadedMsg(TheMessage=%s, %d, %d, %d, %d\n",
1602 ((TheMessage == NULL) ? "NULL" : "not null"),
1603 mode, headers_only, do_proto, crlf);
1605 strcpy(mid, "unknown");
1606 nl = (crlf ? "\r\n" : "\n");
1608 if (!is_valid_message(TheMessage)) {
1609 CtdlLogPrintf(CTDL_ERR,
1610 "ERROR: invalid preloaded message for output\n");
1611 return(om_no_such_msg);
1614 /* Are we downloading a MIME component? */
1615 if (mode == MT_DOWNLOAD) {
1616 if (TheMessage->cm_format_type != FMT_RFC822) {
1618 cprintf("%d This is not a MIME message.\n",
1619 ERROR + ILLEGAL_VALUE);
1620 } else if (CC->download_fp != NULL) {
1621 if (do_proto) cprintf(
1622 "%d You already have a download open.\n",
1623 ERROR + RESOURCE_BUSY);
1625 /* Parse the message text component */
1626 mptr = TheMessage->cm_fields['M'];
1627 mime_parser(mptr, NULL, *mime_download, NULL, NULL, NULL, 0);
1628 /* If there's no file open by this time, the requested
1629 * section wasn't found, so print an error
1631 if (CC->download_fp == NULL) {
1632 if (do_proto) cprintf(
1633 "%d Section %s not found.\n",
1634 ERROR + FILE_NOT_FOUND,
1635 CC->download_desired_section);
1638 return((CC->download_fp != NULL) ? om_ok : om_mime_error);
1641 /* MT_SPEW_SECTION is like MT_DOWNLOAD except it outputs the whole MIME part
1642 * in a single server operation instead of opening a download file.
1644 if (mode == MT_SPEW_SECTION) {
1645 if (TheMessage->cm_format_type != FMT_RFC822) {
1647 cprintf("%d This is not a MIME message.\n",
1648 ERROR + ILLEGAL_VALUE);
1650 /* Parse the message text component */
1653 mptr = TheMessage->cm_fields['M'];
1654 mime_parser(mptr, NULL, *mime_spew_section, NULL, NULL, (void *)&found_it, 0);
1655 /* If section wasn't found, print an error
1658 if (do_proto) cprintf(
1659 "%d Section %s not found.\n",
1660 ERROR + FILE_NOT_FOUND,
1661 CC->download_desired_section);
1664 return((CC->download_fp != NULL) ? om_ok : om_mime_error);
1667 /* now for the user-mode message reading loops */
1668 if (do_proto) cprintf("%d msg:\n", LISTING_FOLLOWS);
1670 /* Does the caller want to skip the headers? */
1671 if (headers_only == HEADERS_NONE) goto START_TEXT;
1673 /* Tell the client which format type we're using. */
1674 if ( (mode == MT_CITADEL) && (do_proto) ) {
1675 cprintf("type=%d\n", TheMessage->cm_format_type);
1678 /* nhdr=yes means that we're only displaying headers, no body */
1679 if ( (TheMessage->cm_anon_type == MES_ANONONLY)
1680 && (mode == MT_CITADEL)
1683 cprintf("nhdr=yes\n");
1686 /* begin header processing loop for Citadel message format */
1688 if ((mode == MT_CITADEL) || (mode == MT_MIME)) {
1690 safestrncpy(display_name, "<unknown>", sizeof display_name);
1691 if (TheMessage->cm_fields['A']) {
1692 strcpy(buf, TheMessage->cm_fields['A']);
1693 if (TheMessage->cm_anon_type == MES_ANONONLY) {
1694 safestrncpy(display_name, "****", sizeof display_name);
1696 else if (TheMessage->cm_anon_type == MES_ANONOPT) {
1697 safestrncpy(display_name, "anonymous", sizeof display_name);
1700 safestrncpy(display_name, buf, sizeof display_name);
1702 if ((is_room_aide())
1703 && ((TheMessage->cm_anon_type == MES_ANONONLY)
1704 || (TheMessage->cm_anon_type == MES_ANONOPT))) {
1705 size_t tmp = strlen(display_name);
1706 snprintf(&display_name[tmp],
1707 sizeof display_name - tmp,
1712 /* Don't show Internet address for users on the
1713 * local Citadel network.
1716 if (TheMessage->cm_fields['N'] != NULL)
1717 if (!IsEmptyStr(TheMessage->cm_fields['N']))
1718 if (haschar(TheMessage->cm_fields['N'], '.') == 0) {
1722 /* Now spew the header fields in the order we like them. */
1723 safestrncpy(allkeys, FORDER, sizeof allkeys);
1724 for (i=0; i<strlen(allkeys); ++i) {
1725 k = (int) allkeys[i];
1727 if ( (TheMessage->cm_fields[k] != NULL)
1728 && (msgkeys[k] != NULL) ) {
1730 if (do_proto) cprintf("%s=%s\n",
1734 else if ((k == 'F') && (suppress_f)) {
1737 /* Masquerade display name if needed */
1739 if (do_proto) cprintf("%s=%s\n",
1741 TheMessage->cm_fields[k]
1750 /* begin header processing loop for RFC822 transfer format */
1755 strcpy(snode, NODENAME);
1756 strcpy(lnode, HUMANNODE);
1757 if (mode == MT_RFC822) {
1758 for (i = 0; i < 256; ++i) {
1759 if (TheMessage->cm_fields[i]) {
1760 mptr = mpptr = TheMessage->cm_fields[i];
1763 safestrncpy(luser, mptr, sizeof luser);
1764 safestrncpy(suser, mptr, sizeof suser);
1766 else if (i == 'Y') {
1767 if ((flags & QP_EADDR) != 0)
1768 mptr = qp_encode_email_addrs(mptr);
1769 cprintf("CC: %s%s", mptr, nl);
1771 else if (i == 'P') {
1772 cprintf("Return-Path: %s%s", mptr, nl);
1774 else if (i == 'V') {
1775 if ((flags & QP_EADDR) != 0)
1776 mptr = qp_encode_email_addrs(mptr);
1777 cprintf("Envelope-To: %s%s", mptr, nl);
1779 else if (i == 'U') {
1780 cprintf("Subject: %s%s", mptr, nl);
1784 safestrncpy(mid, mptr, sizeof mid);
1786 safestrncpy(lnode, mptr, sizeof lnode);
1788 safestrncpy(fuser, mptr, sizeof fuser);
1789 /* else if (i == 'O')
1790 cprintf("X-Citadel-Room: %s%s",
1793 safestrncpy(snode, mptr, sizeof snode);
1796 if (haschar(mptr, '@') == 0)
1798 cprintf("To: %s@%s%s", mptr, config.c_fqdn, nl);
1802 if ((flags & QP_EADDR) != 0)
1803 mptr = qp_encode_email_addrs(mptr);
1804 cprintf("To: %s%s", mptr, nl);
1807 else if (i == 'T') {
1808 datestring(datestamp, sizeof datestamp,
1809 atol(mptr), DATESTRING_RFC822);
1810 cprintf("Date: %s%s", datestamp, nl);
1812 else if (i == 'W') {
1813 cprintf("References: ");
1814 k = num_tokens(mptr, '|');
1815 for (j=0; j<k; ++j) {
1816 extract_token(buf, mptr, j, '|', sizeof buf);
1817 cprintf("<%s>", buf);
1830 if (subject_found == 0) {
1831 cprintf("Subject: (no subject)%s", nl);
1835 for (i=0; !IsEmptyStr(&suser[i]); ++i) {
1836 suser[i] = tolower(suser[i]);
1837 if (!isalnum(suser[i])) suser[i]='_';
1840 if (mode == MT_RFC822) {
1841 if (!strcasecmp(snode, NODENAME)) {
1842 safestrncpy(snode, FQDN, sizeof snode);
1845 /* Construct a fun message id */
1846 cprintf("Message-ID: <%s", mid);/// todo: this possibly breaks threadding mails.
1847 if (strchr(mid, '@')==NULL) {
1848 cprintf("@%s", snode);
1852 if (!is_room_aide() && (TheMessage->cm_anon_type == MES_ANONONLY)) {
1853 cprintf("From: \"----\" <x@x.org>%s", nl);
1855 else if (!is_room_aide() && (TheMessage->cm_anon_type == MES_ANONOPT)) {
1856 cprintf("From: \"anonymous\" <x@x.org>%s", nl);
1858 else if (!IsEmptyStr(fuser)) {
1859 cprintf("From: \"%s\" <%s>%s", luser, fuser, nl);
1862 cprintf("From: \"%s\" <%s@%s>%s", luser, suser, snode, nl);
1865 cprintf("Organization: %s%s", lnode, nl);
1867 /* Blank line signifying RFC822 end-of-headers */
1868 if (TheMessage->cm_format_type != FMT_RFC822) {
1873 /* end header processing loop ... at this point, we're in the text */
1875 if (headers_only == HEADERS_FAST) goto DONE;
1876 mptr = TheMessage->cm_fields['M'];
1878 /* Tell the client about the MIME parts in this message */
1879 if (TheMessage->cm_format_type == FMT_RFC822) {
1880 if ( (mode == MT_CITADEL) || (mode == MT_MIME) ) {
1881 memset(&ma, 0, sizeof(struct ma_info));
1882 mime_parser(mptr, NULL,
1883 (do_proto ? *list_this_part : NULL),
1884 (do_proto ? *list_this_pref : NULL),
1885 (do_proto ? *list_this_suff : NULL),
1888 else if (mode == MT_RFC822) { /* unparsed RFC822 dump */
1889 char *start_of_text = NULL;
1890 start_of_text = strstr(mptr, "\n\r\n");
1891 if (start_of_text == NULL) start_of_text = strstr(mptr, "\n\n");
1892 if (start_of_text == NULL) start_of_text = mptr;
1894 start_of_text = strstr(start_of_text, "\n");
1899 int nllen = strlen(nl);
1901 while (ch=*mptr, ch!=0) {
1907 ((headers_only == HEADERS_NONE) && (mptr >= start_of_text))
1908 || ((headers_only == HEADERS_ONLY) && (mptr < start_of_text))
1909 || ((headers_only != HEADERS_NONE) && (headers_only != HEADERS_ONLY))
1912 sprintf(&outbuf[outlen], "%s", nl);
1916 outbuf[outlen++] = ch;
1920 if (flags & ESC_DOT)
1922 if ((prev_ch == 10) && (ch == '.') && ((*(mptr+1) == 13) || (*(mptr+1) == 10)))
1924 outbuf[outlen++] = '.';
1929 if (outlen > 1000) {
1930 client_write(outbuf, outlen);
1935 client_write(outbuf, outlen);
1943 if (headers_only == HEADERS_ONLY) {
1947 /* signify start of msg text */
1948 if ( (mode == MT_CITADEL) || (mode == MT_MIME) ) {
1949 if (do_proto) cprintf("text\n");
1952 /* If the format type on disk is 1 (fixed-format), then we want
1953 * everything to be output completely literally ... regardless of
1954 * what message transfer format is in use.
1956 if (TheMessage->cm_format_type == FMT_FIXED) {
1958 if (mode == MT_MIME) {
1959 cprintf("Content-type: text/plain\n\n");
1963 while (ch = *mptr++, ch > 0) {
1966 if ((ch == 10) || (buflen > 250)) {
1968 cprintf("%s%s", buf, nl);
1977 if (!IsEmptyStr(buf))
1978 cprintf("%s%s", buf, nl);
1981 /* If the message on disk is format 0 (Citadel vari-format), we
1982 * output using the formatter at 80 columns. This is the final output
1983 * form if the transfer format is RFC822, but if the transfer format
1984 * is Citadel proprietary, it'll still work, because the indentation
1985 * for new paragraphs is correct and the client will reformat the
1986 * message to the reader's screen width.
1988 if (TheMessage->cm_format_type == FMT_CITADEL) {
1989 if (mode == MT_MIME) {
1990 cprintf("Content-type: text/x-citadel-variformat\n\n");
1992 memfmout(mptr, 0, nl);
1995 /* If the message on disk is format 4 (MIME), we've gotta hand it
1996 * off to the MIME parser. The client has already been told that
1997 * this message is format 1 (fixed format), so the callback function
1998 * we use will display those parts as-is.
2000 if (TheMessage->cm_format_type == FMT_RFC822) {
2001 memset(&ma, 0, sizeof(struct ma_info));
2003 if (mode == MT_MIME) {
2004 ma.use_fo_hooks = 0;
2005 strcpy(ma.chosen_part, "1");
2006 ma.chosen_pref = 9999;
2007 mime_parser(mptr, NULL,
2008 *choose_preferred, *fixed_output_pre,
2009 *fixed_output_post, (void *)&ma, 0);
2010 mime_parser(mptr, NULL,
2011 *output_preferred, NULL, NULL, (void *)&ma, CC->msg4_dont_decode);
2014 ma.use_fo_hooks = 1;
2015 mime_parser(mptr, NULL,
2016 *fixed_output, *fixed_output_pre,
2017 *fixed_output_post, (void *)&ma, 0);
2022 DONE: /* now we're done */
2023 if (do_proto) cprintf("000\n");
2030 * display a message (mode 0 - Citadel proprietary)
2032 void cmd_msg0(char *cmdbuf)
2035 int headers_only = HEADERS_ALL;
2037 msgid = extract_long(cmdbuf, 0);
2038 headers_only = extract_int(cmdbuf, 1);
2040 CtdlOutputMsg(msgid, MT_CITADEL, headers_only, 1, 0, NULL, 0);
2046 * display a message (mode 2 - RFC822)
2048 void cmd_msg2(char *cmdbuf)
2051 int headers_only = HEADERS_ALL;
2053 msgid = extract_long(cmdbuf, 0);
2054 headers_only = extract_int(cmdbuf, 1);
2056 CtdlOutputMsg(msgid, MT_RFC822, headers_only, 1, 1, NULL, 0);
2062 * display a message (mode 3 - IGnet raw format - internal programs only)
2064 void cmd_msg3(char *cmdbuf)
2067 struct CtdlMessage *msg = NULL;
2070 if (CC->internal_pgm == 0) {
2071 cprintf("%d This command is for internal programs only.\n",
2072 ERROR + HIGHER_ACCESS_REQUIRED);
2076 msgnum = extract_long(cmdbuf, 0);
2077 msg = CtdlFetchMessage(msgnum, 1);
2079 cprintf("%d Message %ld not found.\n",
2080 ERROR + MESSAGE_NOT_FOUND, msgnum);
2084 serialize_message(&smr, msg);
2085 CtdlFreeMessage(msg);
2088 cprintf("%d Unable to serialize message\n",
2089 ERROR + INTERNAL_ERROR);
2093 cprintf("%d %ld\n", BINARY_FOLLOWS, (long)smr.len);
2094 client_write((char *)smr.ser, (int)smr.len);
2101 * Display a message using MIME content types
2103 void cmd_msg4(char *cmdbuf)
2108 msgid = extract_long(cmdbuf, 0);
2109 extract_token(section, cmdbuf, 1, '|', sizeof section);
2110 CtdlOutputMsg(msgid, MT_MIME, 0, 1, 0, (section[0] ? section : NULL) , 0);
2116 * Client tells us its preferred message format(s)
2118 void cmd_msgp(char *cmdbuf)
2120 if (!strcasecmp(cmdbuf, "dont_decode")) {
2121 CC->msg4_dont_decode = 1;
2122 cprintf("%d MSG4 will not pre-decode messages.\n", CIT_OK);
2125 safestrncpy(CC->preferred_formats, cmdbuf, sizeof(CC->preferred_formats));
2126 cprintf("%d Preferred MIME formats have been set.\n", CIT_OK);
2132 * Open a component of a MIME message as a download file
2134 void cmd_opna(char *cmdbuf)
2137 char desired_section[128];
2139 msgid = extract_long(cmdbuf, 0);
2140 extract_token(desired_section, cmdbuf, 1, '|', sizeof desired_section);
2141 safestrncpy(CC->download_desired_section, desired_section,
2142 sizeof CC->download_desired_section);
2143 CtdlOutputMsg(msgid, MT_DOWNLOAD, 0, 1, 1, NULL, 0);
2148 * Open a component of a MIME message and transmit it all at once
2150 void cmd_dlat(char *cmdbuf)
2153 char desired_section[128];
2155 msgid = extract_long(cmdbuf, 0);
2156 extract_token(desired_section, cmdbuf, 1, '|', sizeof desired_section);
2157 safestrncpy(CC->download_desired_section, desired_section,
2158 sizeof CC->download_desired_section);
2159 CtdlOutputMsg(msgid, MT_SPEW_SECTION, 0, 1, 1, NULL, 0);
2164 * Save one or more message pointers into a specified room
2165 * (Returns 0 for success, nonzero for failure)
2166 * roomname may be NULL to use the current room
2168 * Note that the 'supplied_msg' field may be set to NULL, in which case
2169 * the message will be fetched from disk, by number, if we need to perform
2170 * replication checks. This adds an additional database read, so if the
2171 * caller already has the message in memory then it should be supplied. (Obviously
2172 * this mode of operation only works if we're saving a single message.)
2174 int CtdlSaveMsgPointersInRoom(char *roomname, long newmsgidlist[], int num_newmsgs,
2175 int do_repl_check, struct CtdlMessage *supplied_msg)
2178 char hold_rm[ROOMNAMELEN];
2179 struct cdbdata *cdbfr;
2182 long highest_msg = 0L;
2185 struct CtdlMessage *msg = NULL;
2187 long *msgs_to_be_merged = NULL;
2188 int num_msgs_to_be_merged = 0;
2190 CtdlLogPrintf(CTDL_DEBUG,
2191 "CtdlSaveMsgPointersInRoom(room=%s, num_msgs=%d, repl=%d)\n",
2192 roomname, num_newmsgs, do_repl_check);
2194 strcpy(hold_rm, CC->room.QRname);
2197 if (newmsgidlist == NULL) return(ERROR + INTERNAL_ERROR);
2198 if (num_newmsgs < 1) return(ERROR + INTERNAL_ERROR);
2199 if (num_newmsgs > 1) supplied_msg = NULL;
2201 /* Now the regular stuff */
2202 if (lgetroom(&CC->room,
2203 ((roomname != NULL) ? roomname : CC->room.QRname) )
2205 CtdlLogPrintf(CTDL_ERR, "No such room <%s>\n", roomname);
2206 return(ERROR + ROOM_NOT_FOUND);
2210 msgs_to_be_merged = malloc(sizeof(long) * num_newmsgs);
2211 num_msgs_to_be_merged = 0;
2214 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long));
2215 if (cdbfr == NULL) {
2219 msglist = (long *) cdbfr->ptr;
2220 cdbfr->ptr = NULL; /* CtdlSaveMsgPointerInRoom() now owns this memory */
2221 num_msgs = cdbfr->len / sizeof(long);
2226 /* Create a list of msgid's which were supplied by the caller, but do
2227 * not already exist in the target room. It is absolutely taboo to
2228 * have more than one reference to the same message in a room.
2230 for (i=0; i<num_newmsgs; ++i) {
2232 if (num_msgs > 0) for (j=0; j<num_msgs; ++j) {
2233 if (msglist[j] == newmsgidlist[i]) {
2238 msgs_to_be_merged[num_msgs_to_be_merged++] = newmsgidlist[i];
2242 CtdlLogPrintf(9, "%d unique messages to be merged\n", num_msgs_to_be_merged);
2245 * Now merge the new messages
2247 msglist = realloc(msglist, (sizeof(long) * (num_msgs + num_msgs_to_be_merged)) );
2248 if (msglist == NULL) {
2249 CtdlLogPrintf(CTDL_ALERT, "ERROR: can't realloc message list!\n");
2251 memcpy(&msglist[num_msgs], msgs_to_be_merged, (sizeof(long) * num_msgs_to_be_merged) );
2252 num_msgs += num_msgs_to_be_merged;
2254 /* Sort the message list, so all the msgid's are in order */
2255 num_msgs = sort_msglist(msglist, num_msgs);
2257 /* Determine the highest message number */
2258 highest_msg = msglist[num_msgs - 1];
2260 /* Write it back to disk. */
2261 cdb_store(CDB_MSGLISTS, &CC->room.QRnumber, (int)sizeof(long),
2262 msglist, (int)(num_msgs * sizeof(long)));
2264 /* Free up the memory we used. */
2267 /* Update the highest-message pointer and unlock the room. */
2268 CC->room.QRhighest = highest_msg;
2269 lputroom(&CC->room);
2271 /* Perform replication checks if necessary */
2272 if ( (DoesThisRoomNeedEuidIndexing(&CC->room)) && (do_repl_check) ) {
2273 CtdlLogPrintf(CTDL_DEBUG, "CtdlSaveMsgPointerInRoom() doing repl checks\n");
2275 for (i=0; i<num_msgs_to_be_merged; ++i) {
2276 msgid = msgs_to_be_merged[i];
2278 if (supplied_msg != NULL) {
2282 msg = CtdlFetchMessage(msgid, 0);
2286 ReplicationChecks(msg);
2288 /* If the message has an Exclusive ID, index that... */
2289 if (msg->cm_fields['E'] != NULL) {
2290 index_message_by_euid(msg->cm_fields['E'], &CC->room, msgid);
2293 /* Free up the memory we may have allocated */
2294 if (msg != supplied_msg) {
2295 CtdlFreeMessage(msg);
2303 CtdlLogPrintf(CTDL_DEBUG, "CtdlSaveMsgPointerInRoom() skips repl checks\n");
2306 /* Submit this room for processing by hooks */
2307 PerformRoomHooks(&CC->room);
2309 /* Go back to the room we were in before we wandered here... */
2310 getroom(&CC->room, hold_rm);
2312 /* Bump the reference count for all messages which were merged */
2313 for (i=0; i<num_msgs_to_be_merged; ++i) {
2314 AdjRefCount(msgs_to_be_merged[i], +1);
2317 /* Free up memory... */
2318 if (msgs_to_be_merged != NULL) {
2319 free(msgs_to_be_merged);
2322 /* Return success. */
2328 * This is the same as CtdlSaveMsgPointersInRoom() but it only accepts
2331 int CtdlSaveMsgPointerInRoom(char *roomname, long msgid,
2332 int do_repl_check, struct CtdlMessage *supplied_msg)
2334 return CtdlSaveMsgPointersInRoom(roomname, &msgid, 1, do_repl_check, supplied_msg);
2341 * Message base operation to save a new message to the message store
2342 * (returns new message number)
2344 * This is the back end for CtdlSubmitMsg() and should not be directly
2345 * called by server-side modules.
2348 long send_message(struct CtdlMessage *msg) {
2356 /* Get a new message number */
2357 newmsgid = get_new_message_number();
2358 snprintf(msgidbuf, sizeof msgidbuf, "%010ld@%s", newmsgid, config.c_fqdn);
2360 /* Generate an ID if we don't have one already */
2361 if (msg->cm_fields['I']==NULL) {
2362 msg->cm_fields['I'] = strdup(msgidbuf);
2365 /* If the message is big, set its body aside for storage elsewhere */
2366 if (msg->cm_fields['M'] != NULL) {
2367 if (strlen(msg->cm_fields['M']) > BIGMSG) {
2369 holdM = msg->cm_fields['M'];
2370 msg->cm_fields['M'] = NULL;
2374 /* Serialize our data structure for storage in the database */
2375 serialize_message(&smr, msg);
2378 msg->cm_fields['M'] = holdM;
2382 cprintf("%d Unable to serialize message\n",
2383 ERROR + INTERNAL_ERROR);
2387 /* Write our little bundle of joy into the message base */
2388 if (cdb_store(CDB_MSGMAIN, &newmsgid, (int)sizeof(long),
2389 smr.ser, smr.len) < 0) {
2390 CtdlLogPrintf(CTDL_ERR, "Can't store message\n");
2394 cdb_store(CDB_BIGMSGS,
2404 /* Free the memory we used for the serialized message */
2407 /* Return the *local* message ID to the caller
2408 * (even if we're storing an incoming network message)
2416 * Serialize a struct CtdlMessage into the format used on disk and network.
2418 * This function loads up a "struct ser_ret" (defined in server.h) which
2419 * contains the length of the serialized message and a pointer to the
2420 * serialized message in memory. THE LATTER MUST BE FREED BY THE CALLER.
2422 void serialize_message(struct ser_ret *ret, /* return values */
2423 struct CtdlMessage *msg) /* unserialized msg */
2425 size_t wlen, fieldlen;
2427 static char *forder = FORDER;
2430 * Check for valid message format
2432 if (is_valid_message(msg) == 0) {
2433 CtdlLogPrintf(CTDL_ERR, "serialize_message() aborting due to invalid message\n");
2440 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL)
2441 ret->len = ret->len +
2442 strlen(msg->cm_fields[(int)forder[i]]) + 2;
2444 ret->ser = malloc(ret->len);
2445 if (ret->ser == NULL) {
2446 CtdlLogPrintf(CTDL_ERR, "serialize_message() malloc(%ld) failed: %s\n",
2447 (long)ret->len, strerror(errno));
2454 ret->ser[1] = msg->cm_anon_type;
2455 ret->ser[2] = msg->cm_format_type;
2458 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL) {
2459 fieldlen = strlen(msg->cm_fields[(int)forder[i]]);
2460 ret->ser[wlen++] = (char)forder[i];
2461 safestrncpy((char *)&ret->ser[wlen], msg->cm_fields[(int)forder[i]], fieldlen+1);
2462 wlen = wlen + fieldlen + 1;
2464 if (ret->len != wlen) CtdlLogPrintf(CTDL_ERR, "ERROR: len=%ld wlen=%ld\n",
2465 (long)ret->len, (long)wlen);
2472 * Serialize a struct CtdlMessage into the format used on disk and network.
2474 * This function loads up a "struct ser_ret" (defined in server.h) which
2475 * contains the length of the serialized message and a pointer to the
2476 * serialized message in memory. THE LATTER MUST BE FREED BY THE CALLER.
2478 void dump_message(struct CtdlMessage *msg, /* unserialized msg */
2479 long Siz) /* how many chars ? */
2483 static char *forder = FORDER;
2487 * Check for valid message format
2489 if (is_valid_message(msg) == 0) {
2490 CtdlLogPrintf(CTDL_ERR, "dump_message() aborting due to invalid message\n");
2494 buf = (char*) malloc (Siz + 1);
2498 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL) {
2499 snprintf (buf, Siz, " msg[%c] = %s ...\n", (char) forder[i],
2500 msg->cm_fields[(int)forder[i]]);
2501 client_write (buf, strlen(buf));
2510 * Check to see if any messages already exist in the current room which
2511 * carry the same Exclusive ID as this one. If any are found, delete them.
2513 void ReplicationChecks(struct CtdlMessage *msg) {
2514 long old_msgnum = (-1L);
2516 if (DoesThisRoomNeedEuidIndexing(&CC->room) == 0) return;
2518 CtdlLogPrintf(CTDL_DEBUG, "Performing replication checks in <%s>\n",
2521 /* No exclusive id? Don't do anything. */
2522 if (msg == NULL) return;
2523 if (msg->cm_fields['E'] == NULL) return;
2524 if (IsEmptyStr(msg->cm_fields['E'])) return;
2525 /*CtdlLogPrintf(CTDL_DEBUG, "Exclusive ID: <%s> for room <%s>\n",
2526 msg->cm_fields['E'], CC->room.QRname);*/
2528 old_msgnum = locate_message_by_euid(msg->cm_fields['E'], &CC->room);
2529 if (old_msgnum > 0L) {
2530 CtdlLogPrintf(CTDL_DEBUG, "ReplicationChecks() replacing message %ld\n", old_msgnum);
2531 CtdlDeleteMessages(CC->room.QRname, &old_msgnum, 1, "");
2538 * Save a message to disk and submit it into the delivery system.
2540 long CtdlSubmitMsg(struct CtdlMessage *msg, /* message to save */
2541 struct recptypes *recps, /* recipients (if mail) */
2542 char *force, /* force a particular room? */
2543 int flags /* should the bessage be exported clean? */
2545 char submit_filename[128];
2546 char generated_timestamp[32];
2547 char hold_rm[ROOMNAMELEN];
2548 char actual_rm[ROOMNAMELEN];
2549 char force_room[ROOMNAMELEN];
2550 char content_type[SIZ]; /* We have to learn this */
2551 char recipient[SIZ];
2554 struct ctdluser userbuf;
2556 struct MetaData smi;
2557 FILE *network_fp = NULL;
2558 static int seqnum = 1;
2559 struct CtdlMessage *imsg = NULL;
2561 size_t instr_alloc = 0;
2563 char *hold_R, *hold_D;
2564 char *collected_addresses = NULL;
2565 struct addresses_to_be_filed *aptr = NULL;
2566 char *saved_rfc822_version = NULL;
2567 int qualified_for_journaling = 0;
2568 struct CitContext *CCC = CC; /* CachedCitContext - performance boost */
2569 char bounce_to[1024] = "";
2571 CtdlLogPrintf(CTDL_DEBUG, "CtdlSubmitMsg() called\n");
2572 if (is_valid_message(msg) == 0) return(-1); /* self check */
2574 /* If this message has no timestamp, we take the liberty of
2575 * giving it one, right now.
2577 if (msg->cm_fields['T'] == NULL) {
2578 snprintf(generated_timestamp, sizeof generated_timestamp, "%ld", (long)time(NULL));
2579 msg->cm_fields['T'] = strdup(generated_timestamp);
2582 /* If this message has no path, we generate one.
2584 if (msg->cm_fields['P'] == NULL) {
2585 if (msg->cm_fields['A'] != NULL) {
2586 msg->cm_fields['P'] = strdup(msg->cm_fields['A']);
2587 for (a=0; !IsEmptyStr(&msg->cm_fields['P'][a]); ++a) {
2588 if (isspace(msg->cm_fields['P'][a])) {
2589 msg->cm_fields['P'][a] = ' ';
2594 msg->cm_fields['P'] = strdup("unknown");
2598 if (force == NULL) {
2599 strcpy(force_room, "");
2602 strcpy(force_room, force);
2605 /* Learn about what's inside, because it's what's inside that counts */
2606 if (msg->cm_fields['M'] == NULL) {
2607 CtdlLogPrintf(CTDL_ERR, "ERROR: attempt to save message with NULL body\n");
2611 switch (msg->cm_format_type) {
2613 strcpy(content_type, "text/x-citadel-variformat");
2616 strcpy(content_type, "text/plain");
2619 strcpy(content_type, "text/plain");
2620 mptr = bmstrcasestr(msg->cm_fields['M'], "Content-type:");
2623 safestrncpy(content_type, &mptr[13], sizeof content_type);
2624 striplt(content_type);
2625 aptr = content_type;
2626 while (!IsEmptyStr(aptr)) {
2638 /* Goto the correct room */
2639 CtdlLogPrintf(CTDL_DEBUG, "Selected room %s\n", (recps) ? CCC->room.QRname : SENTITEMS);
2640 strcpy(hold_rm, CCC->room.QRname);
2641 strcpy(actual_rm, CCC->room.QRname);
2642 if (recps != NULL) {
2643 strcpy(actual_rm, SENTITEMS);
2646 /* If the user is a twit, move to the twit room for posting */
2648 if (CCC->user.axlevel == 2) {
2649 strcpy(hold_rm, actual_rm);
2650 strcpy(actual_rm, config.c_twitroom);
2651 CtdlLogPrintf(CTDL_DEBUG, "Diverting to twit room\n");
2655 /* ...or if this message is destined for Aide> then go there. */
2656 if (!IsEmptyStr(force_room)) {
2657 strcpy(actual_rm, force_room);
2660 CtdlLogPrintf(CTDL_DEBUG, "Final selection: %s\n", actual_rm);
2661 if (strcasecmp(actual_rm, CCC->room.QRname)) {
2662 /* getroom(&CCC->room, actual_rm); */
2663 usergoto(actual_rm, 0, 1, NULL, NULL);
2667 * If this message has no O (room) field, generate one.
2669 if (msg->cm_fields['O'] == NULL) {
2670 msg->cm_fields['O'] = strdup(CCC->room.QRname);
2673 /* Perform "before save" hooks (aborting if any return nonzero) */
2674 CtdlLogPrintf(CTDL_DEBUG, "Performing before-save hooks\n");
2675 if (PerformMessageHooks(msg, EVT_BEFORESAVE) > 0) return(-3);
2678 * If this message has an Exclusive ID, and the room is replication
2679 * checking enabled, then do replication checks.
2681 if (DoesThisRoomNeedEuidIndexing(&CCC->room)) {
2682 ReplicationChecks(msg);
2685 /* Save it to disk */
2686 CtdlLogPrintf(CTDL_DEBUG, "Saving to disk\n");
2687 newmsgid = send_message(msg);
2688 if (newmsgid <= 0L) return(-5);
2690 /* Write a supplemental message info record. This doesn't have to
2691 * be a critical section because nobody else knows about this message
2694 CtdlLogPrintf(CTDL_DEBUG, "Creating MetaData record\n");
2695 memset(&smi, 0, sizeof(struct MetaData));
2696 smi.meta_msgnum = newmsgid;
2697 smi.meta_refcount = 0;
2698 safestrncpy(smi.meta_content_type, content_type,
2699 sizeof smi.meta_content_type);
2702 * Measure how big this message will be when rendered as RFC822.
2703 * We do this for two reasons:
2704 * 1. We need the RFC822 length for the new metadata record, so the
2705 * POP and IMAP services don't have to calculate message lengths
2706 * while the user is waiting (multiplied by potentially hundreds
2707 * or thousands of messages).
2708 * 2. If journaling is enabled, we will need an RFC822 version of the
2709 * message to attach to the journalized copy.
2711 if (CCC->redirect_buffer != NULL) {
2712 CtdlLogPrintf(CTDL_ALERT, "CCC->redirect_buffer is not NULL during message submission!\n");
2715 CCC->redirect_buffer = malloc(SIZ);
2716 CCC->redirect_len = 0;
2717 CCC->redirect_alloc = SIZ;
2718 CtdlOutputPreLoadedMsg(msg, MT_RFC822, HEADERS_ALL, 0, 1, QP_EADDR);
2719 smi.meta_rfc822_length = CCC->redirect_len;
2720 saved_rfc822_version = CCC->redirect_buffer;
2721 CCC->redirect_buffer = NULL;
2722 CCC->redirect_len = 0;
2723 CCC->redirect_alloc = 0;
2727 /* Now figure out where to store the pointers */
2728 CtdlLogPrintf(CTDL_DEBUG, "Storing pointers\n");
2730 /* If this is being done by the networker delivering a private
2731 * message, we want to BYPASS saving the sender's copy (because there
2732 * is no local sender; it would otherwise go to the Trashcan).
2734 if ((!CCC->internal_pgm) || (recps == NULL)) {
2735 if (CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 1, msg) != 0) {
2736 CtdlLogPrintf(CTDL_ERR, "ERROR saving message pointer!\n");
2737 CtdlSaveMsgPointerInRoom(config.c_aideroom, newmsgid, 0, msg);
2741 /* For internet mail, drop a copy in the outbound queue room */
2743 if (recps->num_internet > 0) {
2744 CtdlSaveMsgPointerInRoom(SMTP_SPOOLOUT_ROOM, newmsgid, 0, msg);
2747 /* If other rooms are specified, drop them there too. */
2749 if (recps->num_room > 0)
2750 for (i=0; i<num_tokens(recps->recp_room, '|'); ++i) {
2751 extract_token(recipient, recps->recp_room, i,
2752 '|', sizeof recipient);
2753 CtdlLogPrintf(CTDL_DEBUG, "Delivering to room <%s>\n", recipient);
2754 CtdlSaveMsgPointerInRoom(recipient, newmsgid, 0, msg);
2757 /* Bump this user's messages posted counter. */
2758 CtdlLogPrintf(CTDL_DEBUG, "Updating user\n");
2759 lgetuser(&CCC->user, CCC->curr_user);
2760 CCC->user.posted = CCC->user.posted + 1;
2761 lputuser(&CCC->user);
2763 /* Decide where bounces need to be delivered */
2764 if (CCC->logged_in) {
2765 snprintf(bounce_to, sizeof bounce_to, "%s@%s", CCC->user.fullname, config.c_nodename);
2768 snprintf(bounce_to, sizeof bounce_to, "%s@%s", msg->cm_fields['A'], msg->cm_fields['N']);
2771 /* If this is private, local mail, make a copy in the
2772 * recipient's mailbox and bump the reference count.
2775 if (recps->num_local > 0)
2776 for (i=0; i<num_tokens(recps->recp_local, '|'); ++i) {
2777 extract_token(recipient, recps->recp_local, i,
2778 '|', sizeof recipient);
2779 CtdlLogPrintf(CTDL_DEBUG, "Delivering private local mail to <%s>\n",
2781 if (getuser(&userbuf, recipient) == 0) {
2782 // Add a flag so the Funambol module knows its mail
2783 msg->cm_fields['W'] = strdup(recipient);
2784 MailboxName(actual_rm, sizeof actual_rm, &userbuf, MAILROOM);
2785 CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 0, msg);
2786 BumpNewMailCounter(userbuf.usernum);
2787 if (!IsEmptyStr(config.c_funambol_host) || !IsEmptyStr(config.c_pager_program)) {
2788 /* Generate a instruction message for the Funambol notification
2789 * server, in the same style as the SMTP queue
2792 instr = malloc(instr_alloc);
2793 snprintf(instr, instr_alloc,
2794 "Content-type: %s\n\nmsgid|%ld\nsubmitted|%ld\n"
2796 SPOOLMIME, newmsgid, (long)time(NULL),
2800 imsg = malloc(sizeof(struct CtdlMessage));
2801 memset(imsg, 0, sizeof(struct CtdlMessage));
2802 imsg->cm_magic = CTDLMESSAGE_MAGIC;
2803 imsg->cm_anon_type = MES_NORMAL;
2804 imsg->cm_format_type = FMT_RFC822;
2805 imsg->cm_fields['A'] = strdup("Citadel");
2806 imsg->cm_fields['J'] = strdup("do not journal");
2807 imsg->cm_fields['M'] = instr; /* imsg owns this memory now */
2808 imsg->cm_fields['W'] = strdup(recipient);
2809 CtdlSubmitMsg(imsg, NULL, FNBL_QUEUE_ROOM, 0);
2810 CtdlFreeMessage(imsg);
2814 CtdlLogPrintf(CTDL_DEBUG, "No user <%s>\n", recipient);
2815 CtdlSaveMsgPointerInRoom(config.c_aideroom,
2820 /* Perform "after save" hooks */
2821 CtdlLogPrintf(CTDL_DEBUG, "Performing after-save hooks\n");
2822 PerformMessageHooks(msg, EVT_AFTERSAVE);
2824 /* For IGnet mail, we have to save a new copy into the spooler for
2825 * each recipient, with the R and D fields set to the recipient and
2826 * destination-node. This has two ugly side effects: all other
2827 * recipients end up being unlisted in this recipient's copy of the
2828 * message, and it has to deliver multiple messages to the same
2829 * node. We'll revisit this again in a year or so when everyone has
2830 * a network spool receiver that can handle the new style messages.
2833 if (recps->num_ignet > 0)
2834 for (i=0; i<num_tokens(recps->recp_ignet, '|'); ++i) {
2835 extract_token(recipient, recps->recp_ignet, i,
2836 '|', sizeof recipient);
2838 hold_R = msg->cm_fields['R'];
2839 hold_D = msg->cm_fields['D'];
2840 msg->cm_fields['R'] = malloc(SIZ);
2841 msg->cm_fields['D'] = malloc(128);
2842 extract_token(msg->cm_fields['R'], recipient, 0, '@', SIZ);
2843 extract_token(msg->cm_fields['D'], recipient, 1, '@', 128);
2845 serialize_message(&smr, msg);
2847 snprintf(submit_filename, sizeof submit_filename,
2848 "%s/netmail.%04lx.%04x.%04x",
2850 (long) getpid(), CCC->cs_pid, ++seqnum);
2851 network_fp = fopen(submit_filename, "wb+");
2852 if (network_fp != NULL) {
2853 fwrite(smr.ser, smr.len, 1, network_fp);
2859 free(msg->cm_fields['R']);
2860 free(msg->cm_fields['D']);
2861 msg->cm_fields['R'] = hold_R;
2862 msg->cm_fields['D'] = hold_D;
2865 /* Go back to the room we started from */
2866 CtdlLogPrintf(CTDL_DEBUG, "Returning to original room %s\n", hold_rm);
2867 if (strcasecmp(hold_rm, CCC->room.QRname))
2868 usergoto(hold_rm, 0, 1, NULL, NULL);
2870 /* For internet mail, generate delivery instructions.
2871 * Yes, this is recursive. Deal with it. Infinite recursion does
2872 * not happen because the delivery instructions message does not
2873 * contain a recipient.
2876 if (recps->num_internet > 0) {
2877 CtdlLogPrintf(CTDL_DEBUG, "Generating delivery instructions\n");
2879 instr = malloc(instr_alloc);
2880 snprintf(instr, instr_alloc,
2881 "Content-type: %s\n\nmsgid|%ld\nsubmitted|%ld\n"
2883 SPOOLMIME, newmsgid, (long)time(NULL),
2887 for (i=0; i<num_tokens(recps->recp_internet, '|'); ++i) {
2888 size_t tmp = strlen(instr);
2889 extract_token(recipient, recps->recp_internet, i, '|', sizeof recipient);
2890 if ((tmp + strlen(recipient) + 32) > instr_alloc) {
2891 instr_alloc = instr_alloc * 2;
2892 instr = realloc(instr, instr_alloc);
2894 snprintf(&instr[tmp], instr_alloc - tmp, "remote|%s|0||\n", recipient);
2897 imsg = malloc(sizeof(struct CtdlMessage));
2898 memset(imsg, 0, sizeof(struct CtdlMessage));
2899 imsg->cm_magic = CTDLMESSAGE_MAGIC;
2900 imsg->cm_anon_type = MES_NORMAL;
2901 imsg->cm_format_type = FMT_RFC822;
2902 imsg->cm_fields['A'] = strdup("Citadel");
2903 imsg->cm_fields['J'] = strdup("do not journal");
2904 imsg->cm_fields['M'] = instr; /* imsg owns this memory now */
2905 CtdlSubmitMsg(imsg, NULL, SMTP_SPOOLOUT_ROOM, QP_EADDR);
2906 CtdlFreeMessage(imsg);
2910 * Any addresses to harvest for someone's address book?
2912 if ( (CCC->logged_in) && (recps != NULL) ) {
2913 collected_addresses = harvest_collected_addresses(msg);
2916 if (collected_addresses != NULL) {
2917 aptr = (struct addresses_to_be_filed *)
2918 malloc(sizeof(struct addresses_to_be_filed));
2919 MailboxName(actual_rm, sizeof actual_rm,
2920 &CCC->user, USERCONTACTSROOM);
2921 aptr->roomname = strdup(actual_rm);
2922 aptr->collected_addresses = collected_addresses;
2923 begin_critical_section(S_ATBF);
2926 end_critical_section(S_ATBF);
2930 * Determine whether this message qualifies for journaling.
2932 if (msg->cm_fields['J'] != NULL) {
2933 qualified_for_journaling = 0;
2936 if (recps == NULL) {
2937 qualified_for_journaling = config.c_journal_pubmsgs;
2939 else if (recps->num_local + recps->num_ignet + recps->num_internet > 0) {
2940 qualified_for_journaling = config.c_journal_email;
2943 qualified_for_journaling = config.c_journal_pubmsgs;
2948 * Do we have to perform journaling? If so, hand off the saved
2949 * RFC822 version will be handed off to the journaler for background
2950 * submit. Otherwise, we have to free the memory ourselves.
2952 if (saved_rfc822_version != NULL) {
2953 if (qualified_for_journaling) {
2954 JournalBackgroundSubmit(msg, saved_rfc822_version, recps);
2957 free(saved_rfc822_version);
2970 * Convenience function for generating small administrative messages.
2972 void quickie_message(char *from, char *fromaddr, char *to, char *room, char *text,
2973 int format_type, char *subject)
2975 struct CtdlMessage *msg;
2976 struct recptypes *recp = NULL;
2978 msg = malloc(sizeof(struct CtdlMessage));
2979 memset(msg, 0, sizeof(struct CtdlMessage));
2980 msg->cm_magic = CTDLMESSAGE_MAGIC;
2981 msg->cm_anon_type = MES_NORMAL;
2982 msg->cm_format_type = format_type;
2985 msg->cm_fields['A'] = strdup(from);
2987 else if (fromaddr != NULL) {
2988 msg->cm_fields['A'] = strdup(fromaddr);
2989 if (strchr(msg->cm_fields['A'], '@')) {
2990 *strchr(msg->cm_fields['A'], '@') = 0;
2994 msg->cm_fields['A'] = strdup("Citadel");
2997 if (fromaddr != NULL) msg->cm_fields['F'] = strdup(fromaddr);
2998 if (room != NULL) msg->cm_fields['O'] = strdup(room);
2999 msg->cm_fields['N'] = strdup(NODENAME);
3001 msg->cm_fields['R'] = strdup(to);
3002 recp = validate_recipients(to, NULL, 0);
3004 if (subject != NULL) {
3005 msg->cm_fields['U'] = strdup(subject);
3007 msg->cm_fields['M'] = strdup(text);
3009 CtdlSubmitMsg(msg, recp, room, 0);
3010 CtdlFreeMessage(msg);
3011 if (recp != NULL) free_recipients(recp);
3017 * Back end function used by CtdlMakeMessage() and similar functions
3019 char *CtdlReadMessageBody(char *terminator, /* token signalling EOT */
3020 size_t maxlen, /* maximum message length */
3021 char *exist, /* if non-null, append to it;
3022 exist is ALWAYS freed */
3023 int crlf, /* CRLF newlines instead of LF */
3024 int sock /* socket handle or 0 for this session's client socket */
3028 size_t message_len = 0;
3029 size_t buffer_len = 0;
3036 if (exist == NULL) {
3043 message_len = strlen(exist);
3044 buffer_len = message_len + 4096;
3045 m = realloc(exist, buffer_len);
3052 /* Do we need to change leading ".." to "." for SMTP escaping? */
3053 if (!strcmp(terminator, ".")) {
3057 /* flush the input if we have nowhere to store it */
3062 /* read in the lines of message text one by one */
3065 if (sock_getln(sock, buf, (sizeof buf - 3)) < 0) finished = 1;
3068 if (client_getln(buf, (sizeof buf - 3)) < 1) finished = 1;
3070 if (!strcmp(buf, terminator)) finished = 1;
3072 strcat(buf, "\r\n");
3078 /* Unescape SMTP-style input of two dots at the beginning of the line */
3080 if (!strncmp(buf, "..", 2)) {
3081 strcpy(buf, &buf[1]);
3085 if ( (!flushing) && (!finished) ) {
3086 /* Measure the line */
3087 linelen = strlen(buf);
3089 /* augment the buffer if we have to */
3090 if ((message_len + linelen) >= buffer_len) {
3091 ptr = realloc(m, (buffer_len * 2) );
3092 if (ptr == NULL) { /* flush if can't allocate */
3095 buffer_len = (buffer_len * 2);
3097 CtdlLogPrintf(CTDL_DEBUG, "buffer_len is now %ld\n", (long)buffer_len);
3101 /* Add the new line to the buffer. NOTE: this loop must avoid
3102 * using functions like strcat() and strlen() because they
3103 * traverse the entire buffer upon every call, and doing that
3104 * for a multi-megabyte message slows it down beyond usability.
3106 strcpy(&m[message_len], buf);
3107 message_len += linelen;
3110 /* if we've hit the max msg length, flush the rest */
3111 if (message_len >= maxlen) flushing = 1;
3113 } while (!finished);
3121 * Build a binary message to be saved on disk.
3122 * (NOTE: if you supply 'preformatted_text', the buffer you give it
3123 * will become part of the message. This means you are no longer
3124 * responsible for managing that memory -- it will be freed along with
3125 * the rest of the fields when CtdlFreeMessage() is called.)
3128 struct CtdlMessage *CtdlMakeMessage(
3129 struct ctdluser *author, /* author's user structure */
3130 char *recipient, /* NULL if it's not mail */
3131 char *recp_cc, /* NULL if it's not mail */
3132 char *room, /* room where it's going */
3133 int type, /* see MES_ types in header file */
3134 int format_type, /* variformat, plain text, MIME... */
3135 char *fake_name, /* who we're masquerading as */
3136 char *my_email, /* which of my email addresses to use (empty is ok) */
3137 char *subject, /* Subject (optional) */
3138 char *supplied_euid, /* ...or NULL if this is irrelevant */
3139 char *preformatted_text, /* ...or NULL to read text from client */
3140 char *references /* Thread references */
3142 char dest_node[256];
3144 struct CtdlMessage *msg;
3146 msg = malloc(sizeof(struct CtdlMessage));
3147 memset(msg, 0, sizeof(struct CtdlMessage));
3148 msg->cm_magic = CTDLMESSAGE_MAGIC;
3149 msg->cm_anon_type = type;
3150 msg->cm_format_type = format_type;
3152 /* Don't confuse the poor folks if it's not routed mail. */
3153 strcpy(dest_node, "");
3158 /* Path or Return-Path */
3159 if (my_email == NULL) my_email = "";
3161 if (!IsEmptyStr(my_email)) {
3162 msg->cm_fields['P'] = strdup(my_email);
3165 snprintf(buf, sizeof buf, "%s", author->fullname);
3166 msg->cm_fields['P'] = strdup(buf);
3168 convert_spaces_to_underscores(msg->cm_fields['P']);
3170 snprintf(buf, sizeof buf, "%ld", (long)time(NULL)); /* timestamp */
3171 msg->cm_fields['T'] = strdup(buf);
3173 if (fake_name[0]) /* author */
3174 msg->cm_fields['A'] = strdup(fake_name);
3176 msg->cm_fields['A'] = strdup(author->fullname);
3178 if (CC->room.QRflags & QR_MAILBOX) { /* room */
3179 msg->cm_fields['O'] = strdup(&CC->room.QRname[11]);
3182 msg->cm_fields['O'] = strdup(CC->room.QRname);
3185 msg->cm_fields['N'] = strdup(NODENAME); /* nodename */
3186 msg->cm_fields['H'] = strdup(HUMANNODE); /* hnodename */
3188 if (recipient[0] != 0) {
3189 msg->cm_fields['R'] = strdup(recipient);
3191 if (recp_cc[0] != 0) {
3192 msg->cm_fields['Y'] = strdup(recp_cc);
3194 if (dest_node[0] != 0) {
3195 msg->cm_fields['D'] = strdup(dest_node);
3198 if (!IsEmptyStr(my_email)) {
3199 msg->cm_fields['F'] = strdup(my_email);
3201 else if ( (author == &CC->user) && (!IsEmptyStr(CC->cs_inet_email)) ) {
3202 msg->cm_fields['F'] = strdup(CC->cs_inet_email);
3205 if (subject != NULL) {
3208 length = strlen(subject);
3214 while ((subject[i] != '\0') &&
3215 (IsAscii = isascii(subject[i]) != 0 ))
3218 msg->cm_fields['U'] = strdup(subject);
3219 else /* ok, we've got utf8 in the string. */
3221 msg->cm_fields['U'] = rfc2047encode(subject, length);
3227 if (supplied_euid != NULL) {
3228 msg->cm_fields['E'] = strdup(supplied_euid);
3231 if (references != NULL) {
3232 if (!IsEmptyStr(references)) {
3233 msg->cm_fields['W'] = strdup(references);
3237 if (preformatted_text != NULL) {
3238 msg->cm_fields['M'] = preformatted_text;
3241 msg->cm_fields['M'] = CtdlReadMessageBody("000", config.c_maxmsglen, NULL, 0, 0);
3249 * Check to see whether we have permission to post a message in the current
3250 * room. Returns a *CITADEL ERROR CODE* and puts a message in errmsgbuf, or
3251 * returns 0 on success.
3253 int CtdlDoIHavePermissionToPostInThisRoom(char *errmsgbuf,
3255 const char* RemoteIdentifier,
3259 if (!(CC->logged_in) &&
3260 (PostPublic == POST_LOGGED_IN)) {
3261 snprintf(errmsgbuf, n, "Not logged in.");
3262 return (ERROR + NOT_LOGGED_IN);
3264 else if (PostPublic == CHECK_EXISTANCE) {
3265 return (0); // We're Evaling whether a recipient exists
3267 else if (!(CC->logged_in)) {
3269 if ((CC->room.QRflags & QR_READONLY)) {
3270 snprintf(errmsgbuf, n, "Not logged in.");
3271 return (ERROR + NOT_LOGGED_IN);
3273 if (CC->room.QRflags2 & QR2_MODERATED) {
3274 snprintf(errmsgbuf, n, "Not logged in Moderation feature not yet implemented!");
3275 return (ERROR + NOT_LOGGED_IN);
3277 if ((PostPublic!=POST_LMTP) &&(CC->room.QRflags2 & QR2_SMTP_PUBLIC) == 0) {
3282 if (RemoteIdentifier == NULL)
3284 snprintf(errmsgbuf, n, "Need sender to permit access.");
3285 return (ERROR + USERNAME_REQUIRED);
3288 assoc_file_name(filename, sizeof filename, &CC->room, ctdl_netcfg_dir);
3289 begin_critical_section(S_NETCONFIGS);
3290 if (!read_spoolcontrol_file(&sc, filename))
3292 end_critical_section(S_NETCONFIGS);
3293 snprintf(errmsgbuf, n,
3294 "This mailing list only accepts posts from subscribers.");
3295 return (ERROR + NO_SUCH_USER);
3297 end_critical_section(S_NETCONFIGS);
3298 found = is_recipient (sc, RemoteIdentifier);
3299 free_spoolcontrol_struct(&sc);
3304 snprintf(errmsgbuf, n,
3305 "This mailing list only accepts posts from subscribers.");
3306 return (ERROR + NO_SUCH_USER);
3313 if ((CC->user.axlevel < 2)
3314 && ((CC->room.QRflags & QR_MAILBOX) == 0)) {
3315 snprintf(errmsgbuf, n, "Need to be validated to enter "
3316 "(except in %s> to sysop)", MAILROOM);
3317 return (ERROR + HIGHER_ACCESS_REQUIRED);
3320 CtdlRoomAccess(&CC->room, &CC->user, &ra, NULL);
3321 if (!(ra & UA_POSTALLOWED)) {
3322 snprintf(errmsgbuf, n, "Higher access is required to post in this room.");
3323 return (ERROR + HIGHER_ACCESS_REQUIRED);
3326 strcpy(errmsgbuf, "Ok");
3332 * Check to see if the specified user has Internet mail permission
3333 * (returns nonzero if permission is granted)
3335 int CtdlCheckInternetMailPermission(struct ctdluser *who) {
3337 /* Do not allow twits to send Internet mail */
3338 if (who->axlevel <= 2) return(0);
3340 /* Globally enabled? */
3341 if (config.c_restrict == 0) return(1);
3343 /* User flagged ok? */
3344 if (who->flags & US_INTERNET) return(2);
3346 /* Aide level access? */
3347 if (who->axlevel >= 6) return(3);
3349 /* No mail for you! */
3355 * Validate recipients, count delivery types and errors, and handle aliasing
3356 * FIXME check for dupes!!!!!
3358 * Returns 0 if all addresses are ok, ret->num_error = -1 if no addresses
3359 * were specified, or the number of addresses found invalid.
3361 * Caller needs to free the result using free_recipients()
3363 struct recptypes *validate_recipients(char *supplied_recipients,
3364 const char *RemoteIdentifier,
3366 struct recptypes *ret;
3367 char *recipients = NULL;
3368 char this_recp[256];
3369 char this_recp_cooked[256];
3375 struct ctdluser tempUS;
3376 struct ctdlroom tempQR;
3377 struct ctdlroom tempQR2;
3383 ret = (struct recptypes *) malloc(sizeof(struct recptypes));
3384 if (ret == NULL) return(NULL);
3386 /* Set all strings to null and numeric values to zero */
3387 memset(ret, 0, sizeof(struct recptypes));
3389 if (supplied_recipients == NULL) {
3390 recipients = strdup("");
3393 recipients = strdup(supplied_recipients);
3396 /* Allocate some memory. Yes, this allocates 500% more memory than we will
3397 * actually need, but it's healthier for the heap than doing lots of tiny
3398 * realloc() calls instead.
3401 ret->errormsg = malloc(strlen(recipients) + 1024);
3402 ret->recp_local = malloc(strlen(recipients) + 1024);
3403 ret->recp_internet = malloc(strlen(recipients) + 1024);
3404 ret->recp_ignet = malloc(strlen(recipients) + 1024);
3405 ret->recp_room = malloc(strlen(recipients) + 1024);
3406 ret->display_recp = malloc(strlen(recipients) + 1024);
3408 ret->errormsg[0] = 0;
3409 ret->recp_local[0] = 0;
3410 ret->recp_internet[0] = 0;
3411 ret->recp_ignet[0] = 0;
3412 ret->recp_room[0] = 0;
3413 ret->display_recp[0] = 0;
3415 ret->recptypes_magic = RECPTYPES_MAGIC;
3417 /* Change all valid separator characters to commas */
3418 for (i=0; !IsEmptyStr(&recipients[i]); ++i) {
3419 if ((recipients[i] == ';') || (recipients[i] == '|')) {
3420 recipients[i] = ',';
3424 /* Now start extracting recipients... */
3426 while (!IsEmptyStr(recipients)) {
3428 for (i=0; i<=strlen(recipients); ++i) {
3429 if (recipients[i] == '\"') in_quotes = 1 - in_quotes;
3430 if ( ( (recipients[i] == ',') && (!in_quotes) ) || (recipients[i] == 0) ) {
3431 safestrncpy(this_recp, recipients, i+1);
3433 if (recipients[i] == ',') {
3434 strcpy(recipients, &recipients[i+1]);
3437 strcpy(recipients, "");
3444 if (IsEmptyStr(this_recp))
3446 CtdlLogPrintf(CTDL_DEBUG, "Evaluating recipient #%d: %s\n", num_recps, this_recp);
3448 mailtype = alias(this_recp);
3449 mailtype = alias(this_recp);
3450 mailtype = alias(this_recp);
3452 for (j=0; !IsEmptyStr(&this_recp[j]); ++j) {
3453 if (this_recp[j]=='_') {
3454 this_recp_cooked[j] = ' ';
3457 this_recp_cooked[j] = this_recp[j];
3460 this_recp_cooked[j] = '\0';
3465 if (!strcasecmp(this_recp, "sysop")) {
3467 strcpy(this_recp, config.c_aideroom);
3468 if (!IsEmptyStr(ret->recp_room)) {
3469 strcat(ret->recp_room, "|");
3471 strcat(ret->recp_room, this_recp);
3473 else if ( (!strncasecmp(this_recp, "room_", 5))
3474 && (!getroom(&tempQR, &this_recp_cooked[5])) ) {
3476 /* Save room so we can restore it later */
3480 /* Check permissions to send mail to this room */
3481 err = CtdlDoIHavePermissionToPostInThisRoom(errmsg,
3493 if (!IsEmptyStr(ret->recp_room)) {
3494 strcat(ret->recp_room, "|");
3496 strcat(ret->recp_room, &this_recp_cooked[5]);
3499 /* Restore room in case something needs it */
3503 else if (getuser(&tempUS, this_recp) == 0) {
3505 strcpy(this_recp, tempUS.fullname);
3506 if (!IsEmptyStr(ret->recp_local)) {
3507 strcat(ret->recp_local, "|");
3509 strcat(ret->recp_local, this_recp);
3511 else if (getuser(&tempUS, this_recp_cooked) == 0) {
3513 strcpy(this_recp, tempUS.fullname);
3514 if (!IsEmptyStr(ret->recp_local)) {
3515 strcat(ret->recp_local, "|");
3517 strcat(ret->recp_local, this_recp);
3525 /* Yes, you're reading this correctly: if the target
3526 * domain points back to the local system or an attached
3527 * Citadel directory, the address is invalid. That's
3528 * because if the address were valid, we would have
3529 * already translated it to a local address by now.
3531 if (IsDirectory(this_recp, 0)) {
3536 ++ret->num_internet;
3537 if (!IsEmptyStr(ret->recp_internet)) {
3538 strcat(ret->recp_internet, "|");
3540 strcat(ret->recp_internet, this_recp);
3545 if (!IsEmptyStr(ret->recp_ignet)) {
3546 strcat(ret->recp_ignet, "|");
3548 strcat(ret->recp_ignet, this_recp);
3556 if (IsEmptyStr(errmsg)) {
3557 snprintf(append, sizeof append, "Invalid recipient: %s", this_recp);
3560 snprintf(append, sizeof append, "%s", errmsg);
3562 if ( (strlen(ret->errormsg) + strlen(append) + 3) < SIZ) {
3563 if (!IsEmptyStr(ret->errormsg)) {
3564 strcat(ret->errormsg, "; ");
3566 strcat(ret->errormsg, append);
3570 if (IsEmptyStr(ret->display_recp)) {
3571 strcpy(append, this_recp);
3574 snprintf(append, sizeof append, ", %s", this_recp);
3576 if ( (strlen(ret->display_recp)+strlen(append)) < SIZ) {
3577 strcat(ret->display_recp, append);
3582 if ((ret->num_local + ret->num_internet + ret->num_ignet +
3583 ret->num_room + ret->num_error) == 0) {
3584 ret->num_error = (-1);
3585 strcpy(ret->errormsg, "No recipients specified.");
3588 CtdlLogPrintf(CTDL_DEBUG, "validate_recipients()\n");
3589 CtdlLogPrintf(CTDL_DEBUG, " local: %d <%s>\n", ret->num_local, ret->recp_local);
3590 CtdlLogPrintf(CTDL_DEBUG, " room: %d <%s>\n", ret->num_room, ret->recp_room);
3591 CtdlLogPrintf(CTDL_DEBUG, " inet: %d <%s>\n", ret->num_internet, ret->recp_internet);
3592 CtdlLogPrintf(CTDL_DEBUG, " ignet: %d <%s>\n", ret->num_ignet, ret->recp_ignet);
3593 CtdlLogPrintf(CTDL_DEBUG, " error: %d <%s>\n", ret->num_error, ret->errormsg);
3601 * Destructor for struct recptypes
3603 void free_recipients(struct recptypes *valid) {
3605 if (valid == NULL) {
3609 if (valid->recptypes_magic != RECPTYPES_MAGIC) {
3610 CtdlLogPrintf(CTDL_EMERG, "Attempt to call free_recipients() on some other data type!\n");
3614 if (valid->errormsg != NULL) free(valid->errormsg);
3615 if (valid->recp_local != NULL) free(valid->recp_local);
3616 if (valid->recp_internet != NULL) free(valid->recp_internet);
3617 if (valid->recp_ignet != NULL) free(valid->recp_ignet);
3618 if (valid->recp_room != NULL) free(valid->recp_room);
3619 if (valid->display_recp != NULL) free(valid->display_recp);
3626 * message entry - mode 0 (normal)
3628 void cmd_ent0(char *entargs)
3634 char supplied_euid[128];
3636 int format_type = 0;
3637 char newusername[256];
3638 char newuseremail[256];
3639 struct CtdlMessage *msg;
3643 struct recptypes *valid = NULL;
3644 struct recptypes *valid_to = NULL;
3645 struct recptypes *valid_cc = NULL;
3646 struct recptypes *valid_bcc = NULL;
3648 int subject_required = 0;
3653 int newuseremail_ok = 0;
3654 char references[SIZ];
3659 post = extract_int(entargs, 0);
3660 extract_token(recp, entargs, 1, '|', sizeof recp);
3661 anon_flag = extract_int(entargs, 2);
3662 format_type = extract_int(entargs, 3);
3663 extract_token(subject, entargs, 4, '|', sizeof subject);
3664 extract_token(newusername, entargs, 5, '|', sizeof newusername);
3665 do_confirm = extract_int(entargs, 6);
3666 extract_token(cc, entargs, 7, '|', sizeof cc);
3667 extract_token(bcc, entargs, 8, '|', sizeof bcc);
3668 switch(CC->room.QRdefaultview) {
3671 extract_token(supplied_euid, entargs, 9, '|', sizeof supplied_euid);
3674 supplied_euid[0] = 0;
3677 extract_token(newuseremail, entargs, 10, '|', sizeof newuseremail);
3678 extract_token(references, entargs, 11, '|', sizeof references);
3679 for (ptr=references; *ptr != 0; ++ptr) {
3680 if (*ptr == '!') *ptr = '|';
3683 /* first check to make sure the request is valid. */
3685 err = CtdlDoIHavePermissionToPostInThisRoom(errmsg, sizeof errmsg, NULL, POST_LOGGED_IN);
3688 cprintf("%d %s\n", err, errmsg);
3692 /* Check some other permission type things. */
3694 if (IsEmptyStr(newusername)) {
3695 strcpy(newusername, CC->user.fullname);
3697 if ( (CC->user.axlevel < 6)
3698 && (strcasecmp(newusername, CC->user.fullname))
3699 && (strcasecmp(newusername, CC->cs_inet_fn))
3701 cprintf("%d You don't have permission to author messages as '%s'.\n",
3702 ERROR + HIGHER_ACCESS_REQUIRED,
3709 if (IsEmptyStr(newuseremail)) {
3710 newuseremail_ok = 1;
3713 if (!IsEmptyStr(newuseremail)) {
3714 if (!strcasecmp(newuseremail, CC->cs_inet_email)) {
3715 newuseremail_ok = 1;
3717 else if (!IsEmptyStr(CC->cs_inet_other_emails)) {
3718 j = num_tokens(CC->cs_inet_other_emails, '|');
3719 for (i=0; i<j; ++i) {
3720 extract_token(buf, CC->cs_inet_other_emails, i, '|', sizeof buf);
3721 if (!strcasecmp(newuseremail, buf)) {
3722 newuseremail_ok = 1;
3728 if (!newuseremail_ok) {
3729 cprintf("%d You don't have permission to author messages as '%s'.\n",
3730 ERROR + HIGHER_ACCESS_REQUIRED,
3736 CC->cs_flags |= CS_POSTING;
3738 /* In mailbox rooms we have to behave a little differently --
3739 * make sure the user has specified at least one recipient. Then
3740 * validate the recipient(s). We do this for the Mail> room, as
3741 * well as any room which has the "Mailbox" view set.
3744 if ( ( (CC->room.QRflags & QR_MAILBOX) && (!strcasecmp(&CC->room.QRname[11], MAILROOM)) )
3745 || ( (CC->room.QRflags & QR_MAILBOX) && (CC->curr_view == VIEW_MAILBOX) )
3747 if (CC->user.axlevel < 2) {
3748 strcpy(recp, "sysop");
3753 valid_to = validate_recipients(recp, NULL, 0);
3754 if (valid_to->num_error > 0) {
3755 cprintf("%d %s\n", ERROR + NO_SUCH_USER, valid_to->errormsg);
3756 free_recipients(valid_to);
3760 valid_cc = validate_recipients(cc, NULL, 0);
3761 if (valid_cc->num_error > 0) {
3762 cprintf("%d %s\n", ERROR + NO_SUCH_USER, valid_cc->errormsg);
3763 free_recipients(valid_to);
3764 free_recipients(valid_cc);
3768 valid_bcc = validate_recipients(bcc, NULL, 0);
3769 if (valid_bcc->num_error > 0) {
3770 cprintf("%d %s\n", ERROR + NO_SUCH_USER, valid_bcc->errormsg);
3771 free_recipients(valid_to);
3772 free_recipients(valid_cc);
3773 free_recipients(valid_bcc);
3777 /* Recipient required, but none were specified */
3778 if ( (valid_to->num_error < 0) && (valid_cc->num_error < 0) && (valid_bcc->num_error < 0) ) {
3779 free_recipients(valid_to);
3780 free_recipients(valid_cc);
3781 free_recipients(valid_bcc);
3782 cprintf("%d At least one recipient is required.\n", ERROR + NO_SUCH_USER);
3786 if (valid_to->num_internet + valid_cc->num_internet + valid_bcc->num_internet > 0) {
3787 if (CtdlCheckInternetMailPermission(&CC->user)==0) {
3788 cprintf("%d You do not have permission "
3789 "to send Internet mail.\n",
3790 ERROR + HIGHER_ACCESS_REQUIRED);
3791 free_recipients(valid_to);
3792 free_recipients(valid_cc);
3793 free_recipients(valid_bcc);
3798 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)
3799 && (CC->user.axlevel < 4) ) {
3800 cprintf("%d Higher access required for network mail.\n",
3801 ERROR + HIGHER_ACCESS_REQUIRED);
3802 free_recipients(valid_to);
3803 free_recipients(valid_cc);
3804 free_recipients(valid_bcc);
3808 if ((RESTRICT_INTERNET == 1)
3809 && (valid_to->num_internet + valid_cc->num_internet + valid_bcc->num_internet > 0)
3810 && ((CC->user.flags & US_INTERNET) == 0)
3811 && (!CC->internal_pgm)) {
3812 cprintf("%d You don't have access to Internet mail.\n",
3813 ERROR + HIGHER_ACCESS_REQUIRED);
3814 free_recipients(valid_to);
3815 free_recipients(valid_cc);
3816 free_recipients(valid_bcc);
3822 /* Is this a room which has anonymous-only or anonymous-option? */
3823 anonymous = MES_NORMAL;
3824 if (CC->room.QRflags & QR_ANONONLY) {
3825 anonymous = MES_ANONONLY;
3827 if (CC->room.QRflags & QR_ANONOPT) {
3828 if (anon_flag == 1) { /* only if the user requested it */
3829 anonymous = MES_ANONOPT;
3833 if ((CC->room.QRflags & QR_MAILBOX) == 0) {
3837 /* Recommend to the client that the use of a message subject is
3838 * strongly recommended in this room, if either the SUBJECTREQ flag
3839 * is set, or if there is one or more Internet email recipients.
3841 if (CC->room.QRflags2 & QR2_SUBJECTREQ) subject_required = 1;
3842 if (valid_to) if (valid_to->num_internet > 0) subject_required = 1;
3843 if (valid_cc) if (valid_cc->num_internet > 0) subject_required = 1;
3844 if (valid_bcc) if (valid_bcc->num_internet > 0) subject_required = 1;
3846 /* If we're only checking the validity of the request, return
3847 * success without creating the message.
3850 cprintf("%d %s|%d\n", CIT_OK,
3851 ((valid_to != NULL) ? valid_to->display_recp : ""),
3853 free_recipients(valid_to);
3854 free_recipients(valid_cc);
3855 free_recipients(valid_bcc);
3859 /* We don't need these anymore because we'll do it differently below */
3860 free_recipients(valid_to);
3861 free_recipients(valid_cc);
3862 free_recipients(valid_bcc);
3864 /* Read in the message from the client. */
3866 cprintf("%d send message\n", START_CHAT_MODE);
3868 cprintf("%d send message\n", SEND_LISTING);
3871 msg = CtdlMakeMessage(&CC->user, recp, cc,
3872 CC->room.QRname, anonymous, format_type,
3873 newusername, newuseremail, subject,
3874 ((!IsEmptyStr(supplied_euid)) ? supplied_euid : NULL),
3877 /* Put together one big recipients struct containing to/cc/bcc all in
3878 * one. This is for the envelope.
3880 char *all_recps = malloc(SIZ * 3);
3881 strcpy(all_recps, recp);
3882 if (!IsEmptyStr(cc)) {
3883 if (!IsEmptyStr(all_recps)) {
3884 strcat(all_recps, ",");
3886 strcat(all_recps, cc);
3888 if (!IsEmptyStr(bcc)) {
3889 if (!IsEmptyStr(all_recps)) {
3890 strcat(all_recps, ",");
3892 strcat(all_recps, bcc);
3894 if (!IsEmptyStr(all_recps)) {
3895 valid = validate_recipients(all_recps, NULL, 0);
3903 msgnum = CtdlSubmitMsg(msg, valid, "", QP_EADDR);
3906 cprintf("%ld\n", msgnum);
3908 cprintf("Message accepted.\n");
3911 cprintf("Internal error.\n");
3913 if (msg->cm_fields['E'] != NULL) {
3914 cprintf("%s\n", msg->cm_fields['E']);
3921 CtdlFreeMessage(msg);
3923 if (valid != NULL) {
3924 free_recipients(valid);
3932 * API function to delete messages which match a set of criteria
3933 * (returns the actual number of messages deleted)
3935 int CtdlDeleteMessages(char *room_name, /* which room */
3936 long *dmsgnums, /* array of msg numbers to be deleted */
3937 int num_dmsgnums, /* number of msgs to be deleted, or 0 for "any" */
3938 char *content_type /* or "" for any. regular expressions expected. */
3941 struct ctdlroom qrbuf;
3942 struct cdbdata *cdbfr;
3943 long *msglist = NULL;
3944 long *dellist = NULL;
3947 int num_deleted = 0;
3949 struct MetaData smi;
3952 int need_to_free_re = 0;
3954 if (content_type) if (!IsEmptyStr(content_type)) {
3955 regcomp(&re, content_type, 0);
3956 need_to_free_re = 1;
3958 CtdlLogPrintf(CTDL_DEBUG, "CtdlDeleteMessages(%s, %d msgs, %s)\n",
3959 room_name, num_dmsgnums, content_type);
3961 /* get room record, obtaining a lock... */
3962 if (lgetroom(&qrbuf, room_name) != 0) {
3963 CtdlLogPrintf(CTDL_ERR, "CtdlDeleteMessages(): Room <%s> not found\n",
3965 if (need_to_free_re) regfree(&re);
3966 return (0); /* room not found */
3968 cdbfr = cdb_fetch(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long));
3970 if (cdbfr != NULL) {
3971 dellist = malloc(cdbfr->len);
3972 msglist = (long *) cdbfr->ptr;
3973 cdbfr->ptr = NULL; /* CtdlDeleteMessages() now owns this memory */
3974 num_msgs = cdbfr->len / sizeof(long);
3978 for (i = 0; i < num_msgs; ++i) {
3981 /* Set/clear a bit for each criterion */
3983 /* 0 messages in the list or a null list means that we are
3984 * interested in deleting any messages which meet the other criteria.
3986 if ((num_dmsgnums == 0) || (dmsgnums == NULL)) {
3987 delete_this |= 0x01;
3990 for (j=0; j<num_dmsgnums; ++j) {
3991 if (msglist[i] == dmsgnums[j]) {
3992 delete_this |= 0x01;
3997 if (IsEmptyStr(content_type)) {
3998 delete_this |= 0x02;
4000 GetMetaData(&smi, msglist[i]);
4001 if (regexec(&re, smi.meta_content_type, 1, &pm, 0) == 0) {
4002 delete_this |= 0x02;
4006 /* Delete message only if all bits are set */
4007 if (delete_this == 0x03) {
4008 dellist[num_deleted++] = msglist[i];
4013 num_msgs = sort_msglist(msglist, num_msgs);
4014 cdb_store(CDB_MSGLISTS, &qrbuf.QRnumber, (int)sizeof(long),
4015 msglist, (int)(num_msgs * sizeof(long)));
4017 qrbuf.QRhighest = msglist[num_msgs - 1];
4021 /* Go through the messages we pulled out of the index, and decrement
4022 * their reference counts by 1. If this is the only room the message
4023 * was in, the reference count will reach zero and the message will
4024 * automatically be deleted from the database. We do this in a
4025 * separate pass because there might be plug-in hooks getting called,
4026 * and we don't want that happening during an S_ROOMS critical
4029 if (num_deleted) for (i=0; i<num_deleted; ++i) {
4030 PerformDeleteHooks(qrbuf.QRname, dellist[i]);
4031 AdjRefCount(dellist[i], -1);
4034 /* Now free the memory we used, and go away. */
4035 if (msglist != NULL) free(msglist);
4036 if (dellist != NULL) free(dellist);
4037 CtdlLogPrintf(CTDL_DEBUG, "%d message(s) deleted.\n", num_deleted);
4038 if (need_to_free_re) regfree(&re);
4039 return (num_deleted);
4045 * Check whether the current user has permission to delete messages from
4046 * the current room (returns 1 for yes, 0 for no)
4048 int CtdlDoIHavePermissionToDeleteMessagesFromThisRoom(void) {
4050 CtdlRoomAccess(&CC->room, &CC->user, &ra, NULL);
4051 if (ra & UA_DELETEALLOWED) return(1);
4059 * Delete message from current room
4061 void cmd_dele(char *args)
4070 extract_token(msgset, args, 0, '|', sizeof msgset);
4071 num_msgs = num_tokens(msgset, ',');
4073 cprintf("%d Nothing to do.\n", CIT_OK);
4077 if (CtdlDoIHavePermissionToDeleteMessagesFromThisRoom() == 0) {
4078 cprintf("%d Higher access required.\n",
4079 ERROR + HIGHER_ACCESS_REQUIRED);
4084 * Build our message set to be moved/copied
4086 msgs = malloc(num_msgs * sizeof(long));
4087 for (i=0; i<num_msgs; ++i) {
4088 extract_token(msgtok, msgset, i, ',', sizeof msgtok);
4089 msgs[i] = atol(msgtok);
4092 num_deleted = CtdlDeleteMessages(CC->room.QRname, msgs, num_msgs, "");
4096 cprintf("%d %d message%s deleted.\n", CIT_OK,
4097 num_deleted, ((num_deleted != 1) ? "s" : ""));
4099 cprintf("%d Message not found.\n", ERROR + MESSAGE_NOT_FOUND);
4105 * Back end API function for moves and deletes (multiple messages)
4107 int CtdlCopyMsgsToRoom(long *msgnums, int num_msgs, char *dest) {
4110 err = CtdlSaveMsgPointersInRoom(dest, msgnums, num_msgs, 1, NULL);
4111 if (err != 0) return(err);
4120 * move or copy a message to another room
4122 void cmd_move(char *args)
4129 char targ[ROOMNAMELEN];
4130 struct ctdlroom qtemp;
4137 extract_token(msgset, args, 0, '|', sizeof msgset);
4138 num_msgs = num_tokens(msgset, ',');
4140 cprintf("%d Nothing to do.\n", CIT_OK);
4144 extract_token(targ, args, 1, '|', sizeof targ);
4145 convert_room_name_macros(targ, sizeof targ);
4146 targ[ROOMNAMELEN - 1] = 0;
4147 is_copy = extract_int(args, 2);
4149 if (getroom(&qtemp, targ) != 0) {
4150 cprintf("%d '%s' does not exist.\n",
4151 ERROR + ROOM_NOT_FOUND, targ);
4155 getuser(&CC->user, CC->curr_user);
4156 CtdlRoomAccess(&qtemp, &CC->user, &ra, NULL);
4158 /* Check for permission to perform this operation.
4159 * Remember: "CC->room" is source, "qtemp" is target.
4163 /* Aides can move/copy */
4164 if (CC->user.axlevel >= 6) permit = 1;
4166 /* Room aides can move/copy */
4167 if (CC->user.usernum == CC->room.QRroomaide) permit = 1;
4169 /* Permit move/copy from personal rooms */
4170 if ((CC->room.QRflags & QR_MAILBOX)
4171 && (qtemp.QRflags & QR_MAILBOX)) permit = 1;
4173 /* Permit only copy from public to personal room */
4175 && (!(CC->room.QRflags & QR_MAILBOX))
4176 && (qtemp.QRflags & QR_MAILBOX)) permit = 1;
4178 /* Permit message removal from collaborative delete rooms */
4179 if (CC->room.QRflags2 & QR2_COLLABDEL) permit = 1;
4181 /* Users allowed to post into the target room may move into it too. */
4182 if ((CC->room.QRflags & QR_MAILBOX) &&
4183 (qtemp.QRflags & UA_POSTALLOWED)) permit = 1;
4185 /* User must have access to target room */
4186 if (!(ra & UA_KNOWN)) permit = 0;
4189 cprintf("%d Higher access required.\n",
4190 ERROR + HIGHER_ACCESS_REQUIRED);
4195 * Build our message set to be moved/copied
4197 msgs = malloc(num_msgs * sizeof(long));
4198 for (i=0; i<num_msgs; ++i) {
4199 extract_token(msgtok, msgset, i, ',', sizeof msgtok);
4200 msgs[i] = atol(msgtok);
4206 err = CtdlCopyMsgsToRoom(msgs, num_msgs, targ);
4208 cprintf("%d Cannot store message(s) in %s: error %d\n",
4214 /* Now delete the message from the source room,
4215 * if this is a 'move' rather than a 'copy' operation.
4218 CtdlDeleteMessages(CC->room.QRname, msgs, num_msgs, "");
4222 cprintf("%d Message(s) %s.\n", CIT_OK, (is_copy ? "copied" : "moved") );
4228 * GetMetaData() - Get the supplementary record for a message
4230 void GetMetaData(struct MetaData *smibuf, long msgnum)
4233 struct cdbdata *cdbsmi;
4236 memset(smibuf, 0, sizeof(struct MetaData));
4237 smibuf->meta_msgnum = msgnum;
4238 smibuf->meta_refcount = 1; /* Default reference count is 1 */
4240 /* Use the negative of the message number for its supp record index */
4241 TheIndex = (0L - msgnum);
4243 cdbsmi = cdb_fetch(CDB_MSGMAIN, &TheIndex, sizeof(long));
4244 if (cdbsmi == NULL) {
4245 return; /* record not found; go with defaults */
4247 memcpy(smibuf, cdbsmi->ptr,
4248 ((cdbsmi->len > sizeof(struct MetaData)) ?
4249 sizeof(struct MetaData) : cdbsmi->len));
4256 * PutMetaData() - (re)write supplementary record for a message
4258 void PutMetaData(struct MetaData *smibuf)
4262 /* Use the negative of the message number for the metadata db index */
4263 TheIndex = (0L - smibuf->meta_msgnum);
4265 cdb_store(CDB_MSGMAIN,
4266 &TheIndex, (int)sizeof(long),
4267 smibuf, (int)sizeof(struct MetaData));
4272 * AdjRefCount - submit an adjustment to the reference count for a message.
4273 * (These are just queued -- we actually process them later.)
4275 void AdjRefCount(long msgnum, int incr)
4277 struct arcq new_arcq;
4279 begin_critical_section(S_SUPPMSGMAIN);
4280 if (arcfp == NULL) {
4281 arcfp = fopen(file_arcq, "ab+");
4283 end_critical_section(S_SUPPMSGMAIN);
4285 /* msgnum < 0 means that we're trying to close the file */
4287 CtdlLogPrintf(CTDL_DEBUG, "Closing the AdjRefCount queue file\n");
4288 begin_critical_section(S_SUPPMSGMAIN);
4289 if (arcfp != NULL) {
4293 end_critical_section(S_SUPPMSGMAIN);
4298 * If we can't open the queue, perform the operation synchronously.
4300 if (arcfp == NULL) {
4301 TDAP_AdjRefCount(msgnum, incr);
4305 new_arcq.arcq_msgnum = msgnum;
4306 new_arcq.arcq_delta = incr;
4307 fwrite(&new_arcq, sizeof(struct arcq), 1, arcfp);
4315 * TDAP_ProcessAdjRefCountQueue()
4317 * Process the queue of message count adjustments that was created by calls
4318 * to AdjRefCount() ... by reading the queue and calling TDAP_AdjRefCount()
4319 * for each one. This should be an "off hours" operation.
4321 int TDAP_ProcessAdjRefCountQueue(void)
4323 char file_arcq_temp[PATH_MAX];
4326 struct arcq arcq_rec;
4327 int num_records_processed = 0;
4329 snprintf(file_arcq_temp, sizeof file_arcq_temp, "%s2", file_arcq);
4331 begin_critical_section(S_SUPPMSGMAIN);
4332 if (arcfp != NULL) {
4337 r = link(file_arcq, file_arcq_temp);
4339 CtdlLogPrintf(CTDL_CRIT, "%s: %s\n", file_arcq_temp, strerror(errno));
4340 end_critical_section(S_SUPPMSGMAIN);
4341 return(num_records_processed);
4345 end_critical_section(S_SUPPMSGMAIN);
4347 fp = fopen(file_arcq_temp, "rb");
4349 CtdlLogPrintf(CTDL_CRIT, "%s: %s\n", file_arcq_temp, strerror(errno));
4350 return(num_records_processed);
4353 while (fread(&arcq_rec, sizeof(struct arcq), 1, fp) == 1) {
4354 TDAP_AdjRefCount(arcq_rec.arcq_msgnum, arcq_rec.arcq_delta);
4355 ++num_records_processed;
4359 r = unlink(file_arcq_temp);
4361 CtdlLogPrintf(CTDL_CRIT, "%s: %s\n", file_arcq_temp, strerror(errno));
4364 return(num_records_processed);
4370 * TDAP_AdjRefCount - adjust the reference count for a message.
4371 * This one does it "for real" because it's called by
4372 * the autopurger function that processes the queue
4373 * created by AdjRefCount(). If a message's reference
4374 * count becomes zero, we also delete the message from
4375 * disk and de-index it.
4377 void TDAP_AdjRefCount(long msgnum, int incr)
4380 struct MetaData smi;
4383 /* This is a *tight* critical section; please keep it that way, as
4384 * it may get called while nested in other critical sections.
4385 * Complicating this any further will surely cause deadlock!
4387 begin_critical_section(S_SUPPMSGMAIN);
4388 GetMetaData(&smi, msgnum);
4389 smi.meta_refcount += incr;
4391 end_critical_section(S_SUPPMSGMAIN);
4392 CtdlLogPrintf(CTDL_DEBUG, "msg %ld ref count delta %d, is now %d\n",
4393 msgnum, incr, smi.meta_refcount);
4395 /* If the reference count is now zero, delete the message
4396 * (and its supplementary record as well).
4398 if (smi.meta_refcount == 0) {
4399 CtdlLogPrintf(CTDL_DEBUG, "Deleting message <%ld>\n", msgnum);
4401 /* Call delete hooks with NULL room to show it has gone altogether */
4402 PerformDeleteHooks(NULL, msgnum);
4404 /* Remove from message base */
4406 cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long));
4407 cdb_delete(CDB_BIGMSGS, &delnum, (int)sizeof(long));
4409 /* Remove metadata record */
4410 delnum = (0L - msgnum);
4411 cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long));
4417 * Write a generic object to this room
4419 * Note: this could be much more efficient. Right now we use two temporary
4420 * files, and still pull the message into memory as with all others.
4422 void CtdlWriteObject(char *req_room, /* Room to stuff it in */
4423 char *content_type, /* MIME type of this object */
4424 char *tempfilename, /* Where to fetch it from */
4425 struct ctdluser *is_mailbox, /* Mailbox room? */
4426 int is_binary, /* Is encoding necessary? */
4427 int is_unique, /* Del others of this type? */
4428 unsigned int flags /* Internal save flags */
4433 struct ctdlroom qrbuf;
4434 char roomname[ROOMNAMELEN];
4435 struct CtdlMessage *msg;
4437 char *raw_message = NULL;
4438 char *encoded_message = NULL;
4439 off_t raw_length = 0;
4441 if (is_mailbox != NULL) {
4442 MailboxName(roomname, sizeof roomname, is_mailbox, req_room);
4445 safestrncpy(roomname, req_room, sizeof(roomname));
4448 fp = fopen(tempfilename, "rb");
4450 CtdlLogPrintf(CTDL_CRIT, "Cannot open %s: %s\n",
4451 tempfilename, strerror(errno));
4454 fseek(fp, 0L, SEEK_END);
4455 raw_length = ftell(fp);
4457 CtdlLogPrintf(CTDL_DEBUG, "Raw length is %ld\n", (long)raw_length);
4459 raw_message = malloc((size_t)raw_length + 2);
4460 fread(raw_message, (size_t)raw_length, 1, fp);
4464 encoded_message = malloc((size_t)
4465 (((raw_length * 134) / 100) + 4096 ) );
4468 encoded_message = malloc((size_t)(raw_length + 4096));
4471 sprintf(encoded_message, "Content-type: %s\n", content_type);
4474 sprintf(&encoded_message[strlen(encoded_message)],
4475 "Content-transfer-encoding: base64\n\n"
4479 sprintf(&encoded_message[strlen(encoded_message)],
4480 "Content-transfer-encoding: 7bit\n\n"
4486 &encoded_message[strlen(encoded_message)],
4493 raw_message[raw_length] = 0;
4495 &encoded_message[strlen(encoded_message)],
4503 CtdlLogPrintf(CTDL_DEBUG, "Allocating\n");
4504 msg = malloc(sizeof(struct CtdlMessage));
4505 memset(msg, 0, sizeof(struct CtdlMessage));
4506 msg->cm_magic = CTDLMESSAGE_MAGIC;
4507 msg->cm_anon_type = MES_NORMAL;
4508 msg->cm_format_type = 4;
4509 msg->cm_fields['A'] = strdup(CC->user.fullname);
4510 msg->cm_fields['O'] = strdup(req_room);
4511 msg->cm_fields['N'] = strdup(config.c_nodename);
4512 msg->cm_fields['H'] = strdup(config.c_humannode);
4513 msg->cm_flags = flags;
4515 msg->cm_fields['M'] = encoded_message;
4517 /* Create the requested room if we have to. */
4518 if (getroom(&qrbuf, roomname) != 0) {
4519 create_room(roomname,
4520 ( (is_mailbox != NULL) ? 5 : 3 ),
4521 "", 0, 1, 0, VIEW_BBS);
4523 /* If the caller specified this object as unique, delete all
4524 * other objects of this type that are currently in the room.
4527 CtdlLogPrintf(CTDL_DEBUG, "Deleted %d other msgs of this type\n",
4528 CtdlDeleteMessages(roomname, NULL, 0, content_type)
4531 /* Now write the data */
4532 CtdlSubmitMsg(msg, NULL, roomname, 0);
4533 CtdlFreeMessage(msg);
4541 void CtdlGetSysConfigBackend(long msgnum, void *userdata) {
4542 config_msgnum = msgnum;
4546 char *CtdlGetSysConfig(char *sysconfname) {
4547 char hold_rm[ROOMNAMELEN];
4550 struct CtdlMessage *msg;
4553 strcpy(hold_rm, CC->room.QRname);
4554 if (getroom(&CC->room, SYSCONFIGROOM) != 0) {
4555 getroom(&CC->room, hold_rm);
4560 /* We want the last (and probably only) config in this room */
4561 begin_critical_section(S_CONFIG);
4562 config_msgnum = (-1L);
4563 CtdlForEachMessage(MSGS_LAST, 1, NULL, sysconfname, NULL,
4564 CtdlGetSysConfigBackend, NULL);
4565 msgnum = config_msgnum;
4566 end_critical_section(S_CONFIG);
4572 msg = CtdlFetchMessage(msgnum, 1);
4574 conf = strdup(msg->cm_fields['M']);
4575 CtdlFreeMessage(msg);
4582 getroom(&CC->room, hold_rm);
4584 if (conf != NULL) do {
4585 extract_token(buf, conf, 0, '\n', sizeof buf);
4586 strcpy(conf, &conf[strlen(buf)+1]);
4587 } while ( (!IsEmptyStr(conf)) && (!IsEmptyStr(buf)) );
4592 void CtdlPutSysConfig(char *sysconfname, char *sysconfdata) {
4593 char temp[PATH_MAX];
4596 CtdlMakeTempFileName(temp, sizeof temp);
4598 fp = fopen(temp, "w");
4599 if (fp == NULL) return;
4600 fprintf(fp, "%s", sysconfdata);
4603 /* this handy API function does all the work for us */
4604 CtdlWriteObject(SYSCONFIGROOM, sysconfname, temp, NULL, 0, 1, 0);
4610 * Determine whether a given Internet address belongs to the current user
4612 int CtdlIsMe(char *addr, int addr_buf_len)
4614 struct recptypes *recp;
4617 recp = validate_recipients(addr, NULL, 0);
4618 if (recp == NULL) return(0);
4620 if (recp->num_local == 0) {
4621 free_recipients(recp);
4625 for (i=0; i<recp->num_local; ++i) {
4626 extract_token(addr, recp->recp_local, i, '|', addr_buf_len);
4627 if (!strcasecmp(addr, CC->user.fullname)) {
4628 free_recipients(recp);
4633 free_recipients(recp);
4639 * Citadel protocol command to do the same
4641 void cmd_isme(char *argbuf) {
4644 if (CtdlAccessCheck(ac_logged_in)) return;
4645 extract_token(addr, argbuf, 0, '|', sizeof addr);
4647 if (CtdlIsMe(addr, sizeof addr)) {
4648 cprintf("%d %s\n", CIT_OK, addr);
4651 cprintf("%d Not you.\n", ERROR + ILLEGAL_VALUE);