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);
1455 char *qp_encode_email_addrs(char *source)
1457 char user[256], node[256], name[256];
1458 const char headerStr[] = "=?UTF-8?Q?";
1462 int need_to_encode = 0;
1468 long nAddrPtrMax = 50;
1473 if (source == NULL) return source;
1474 if (IsEmptyStr(source)) return source;
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;
1489 ptr = (long *) malloc(sizeof (long) * nAddrPtrMax * 2);
1490 memset(&ptr[nAddrPtrMax], 0,
1491 sizeof (long) * nAddrPtrMax);
1493 memcpy (ptr, AddrUtf8, sizeof (long) * nAddrPtrMax);
1494 free (AddrUtf8), AddrUtf8 = ptr;
1497 if (((unsigned char) source[i] < 32) ||
1498 ((unsigned char) source[i] > 126)) {
1500 AddrUtf8[nColons] = 1;
1502 if (source[i] == '"')
1503 InQuotes = !InQuotes;
1504 if (!InQuotes && source[i] == ',') {
1505 AddrPtr[nColons] = i;
1510 if (need_to_encode == 0) {
1517 EncodedMaxLen = nColons * (sizeof(headerStr) + 3) + SourceLen * 3;
1518 Encoded = (char*) malloc (EncodedMaxLen);
1520 for (i = 1; i <= nColons; i++)
1521 source[AddrPtr[i]++] = '\0';
1525 for (i = 0; i <= nColons && nPtr != NULL; i++) {
1526 nmax = EncodedMaxLen - (nPtr - Encoded);
1528 process_rfc822_addr(&source[AddrPtr[i]],
1532 /* TODO: libIDN here ! */
1533 if (IsEmptyStr(name)) {
1534 n = snprintf(nPtr, nmax,
1535 (i==0)?"%s@%s" : ",%s@%s",
1539 EncodedName = rfc2047encode(name, strlen(name));
1540 n = snprintf(nPtr, nmax,
1541 (i==0)?"%s <%s@%s>" : ",%s <%s@%s>",
1542 EncodedName, user, node);
1547 n = snprintf(nPtr, nmax,
1548 (i==0)?"%s" : ",%s",
1549 &source[AddrPtr[i]]);
1555 ptr = (char*) malloc(EncodedMaxLen * 2);
1556 memcpy(ptr, Encoded, EncodedMaxLen);
1557 nnPtr = ptr + (nPtr - Encoded), nPtr = nnPtr;
1558 free(Encoded), Encoded = ptr;
1560 i--; /* do it once more with properly lengthened buffer */
1563 for (i = 1; i <= nColons; i++)
1564 source[--AddrPtr[i]] = ',';
1572 * Get a message off disk. (returns om_* values found in msgbase.h)
1574 int CtdlOutputPreLoadedMsg(
1575 struct CtdlMessage *TheMessage,
1576 int mode, /* how would you like that message? */
1577 int headers_only, /* eschew the message body? */
1578 int do_proto, /* do Citadel protocol responses? */
1579 int crlf, /* Use CRLF newlines instead of LF? */
1580 int flags /* should the bessage be exported clean? */
1584 cit_uint8_t ch, prev_ch;
1586 char display_name[256];
1588 char *nl; /* newline string */
1590 int subject_found = 0;
1593 /* Buffers needed for RFC822 translation. These are all filled
1594 * using functions that are bounds-checked, and therefore we can
1595 * make them substantially smaller than SIZ.
1603 char datestamp[100];
1605 CtdlLogPrintf(CTDL_DEBUG, "CtdlOutputPreLoadedMsg(TheMessage=%s, %d, %d, %d, %d\n",
1606 ((TheMessage == NULL) ? "NULL" : "not null"),
1607 mode, headers_only, do_proto, crlf);
1609 strcpy(mid, "unknown");
1610 nl = (crlf ? "\r\n" : "\n");
1612 if (!is_valid_message(TheMessage)) {
1613 CtdlLogPrintf(CTDL_ERR,
1614 "ERROR: invalid preloaded message for output\n");
1615 return(om_no_such_msg);
1618 /* Are we downloading a MIME component? */
1619 if (mode == MT_DOWNLOAD) {
1620 if (TheMessage->cm_format_type != FMT_RFC822) {
1622 cprintf("%d This is not a MIME message.\n",
1623 ERROR + ILLEGAL_VALUE);
1624 } else if (CC->download_fp != NULL) {
1625 if (do_proto) cprintf(
1626 "%d You already have a download open.\n",
1627 ERROR + RESOURCE_BUSY);
1629 /* Parse the message text component */
1630 mptr = TheMessage->cm_fields['M'];
1631 mime_parser(mptr, NULL, *mime_download, NULL, NULL, NULL, 0);
1632 /* If there's no file open by this time, the requested
1633 * section wasn't found, so print an error
1635 if (CC->download_fp == NULL) {
1636 if (do_proto) cprintf(
1637 "%d Section %s not found.\n",
1638 ERROR + FILE_NOT_FOUND,
1639 CC->download_desired_section);
1642 return((CC->download_fp != NULL) ? om_ok : om_mime_error);
1645 /* MT_SPEW_SECTION is like MT_DOWNLOAD except it outputs the whole MIME part
1646 * in a single server operation instead of opening a download file.
1648 if (mode == MT_SPEW_SECTION) {
1649 if (TheMessage->cm_format_type != FMT_RFC822) {
1651 cprintf("%d This is not a MIME message.\n",
1652 ERROR + ILLEGAL_VALUE);
1654 /* Parse the message text component */
1657 mptr = TheMessage->cm_fields['M'];
1658 mime_parser(mptr, NULL, *mime_spew_section, NULL, NULL, (void *)&found_it, 0);
1659 /* If section wasn't found, print an error
1662 if (do_proto) cprintf(
1663 "%d Section %s not found.\n",
1664 ERROR + FILE_NOT_FOUND,
1665 CC->download_desired_section);
1668 return((CC->download_fp != NULL) ? om_ok : om_mime_error);
1671 /* now for the user-mode message reading loops */
1672 if (do_proto) cprintf("%d msg:\n", LISTING_FOLLOWS);
1674 /* Does the caller want to skip the headers? */
1675 if (headers_only == HEADERS_NONE) goto START_TEXT;
1677 /* Tell the client which format type we're using. */
1678 if ( (mode == MT_CITADEL) && (do_proto) ) {
1679 cprintf("type=%d\n", TheMessage->cm_format_type);
1682 /* nhdr=yes means that we're only displaying headers, no body */
1683 if ( (TheMessage->cm_anon_type == MES_ANONONLY)
1684 && (mode == MT_CITADEL)
1687 cprintf("nhdr=yes\n");
1690 /* begin header processing loop for Citadel message format */
1692 if ((mode == MT_CITADEL) || (mode == MT_MIME)) {
1694 safestrncpy(display_name, "<unknown>", sizeof display_name);
1695 if (TheMessage->cm_fields['A']) {
1696 strcpy(buf, TheMessage->cm_fields['A']);
1697 if (TheMessage->cm_anon_type == MES_ANONONLY) {
1698 safestrncpy(display_name, "****", sizeof display_name);
1700 else if (TheMessage->cm_anon_type == MES_ANONOPT) {
1701 safestrncpy(display_name, "anonymous", sizeof display_name);
1704 safestrncpy(display_name, buf, sizeof display_name);
1706 if ((is_room_aide())
1707 && ((TheMessage->cm_anon_type == MES_ANONONLY)
1708 || (TheMessage->cm_anon_type == MES_ANONOPT))) {
1709 size_t tmp = strlen(display_name);
1710 snprintf(&display_name[tmp],
1711 sizeof display_name - tmp,
1716 /* Don't show Internet address for users on the
1717 * local Citadel network.
1720 if (TheMessage->cm_fields['N'] != NULL)
1721 if (!IsEmptyStr(TheMessage->cm_fields['N']))
1722 if (haschar(TheMessage->cm_fields['N'], '.') == 0) {
1726 /* Now spew the header fields in the order we like them. */
1727 safestrncpy(allkeys, FORDER, sizeof allkeys);
1728 for (i=0; i<strlen(allkeys); ++i) {
1729 k = (int) allkeys[i];
1731 if ( (TheMessage->cm_fields[k] != NULL)
1732 && (msgkeys[k] != NULL) ) {
1734 if (do_proto) cprintf("%s=%s\n",
1738 else if ((k == 'F') && (suppress_f)) {
1741 /* Masquerade display name if needed */
1743 if (do_proto) cprintf("%s=%s\n",
1745 TheMessage->cm_fields[k]
1754 /* begin header processing loop for RFC822 transfer format */
1759 strcpy(snode, NODENAME);
1760 strcpy(lnode, HUMANNODE);
1761 if (mode == MT_RFC822) {
1762 for (i = 0; i < 256; ++i) {
1763 if (TheMessage->cm_fields[i]) {
1764 mptr = mpptr = TheMessage->cm_fields[i];
1767 safestrncpy(luser, mptr, sizeof luser);
1768 safestrncpy(suser, mptr, sizeof suser);
1770 else if (i == 'Y') {
1771 if ((flags & QP_EADDR) != 0)
1772 mptr = qp_encode_email_addrs(mptr);
1773 cprintf("CC: %s%s", mptr, nl);
1775 else if (i == 'P') {
1776 cprintf("Return-Path: %s%s", mptr, nl);
1778 else if (i == 'V') {
1779 if ((flags & QP_EADDR) != 0)
1780 mptr = qp_encode_email_addrs(mptr);
1781 cprintf("Envelope-To: %s%s", mptr, nl);
1783 else if (i == 'U') {
1784 cprintf("Subject: %s%s", mptr, nl);
1788 safestrncpy(mid, mptr, sizeof mid);
1790 safestrncpy(lnode, mptr, sizeof lnode);
1792 safestrncpy(fuser, mptr, sizeof fuser);
1793 /* else if (i == 'O')
1794 cprintf("X-Citadel-Room: %s%s",
1797 safestrncpy(snode, mptr, sizeof snode);
1800 if (haschar(mptr, '@') == 0)
1802 cprintf("To: %s@%s%s", mptr, config.c_fqdn, nl);
1806 if ((flags & QP_EADDR) != 0)
1807 mptr = qp_encode_email_addrs(mptr);
1808 cprintf("To: %s%s", mptr, nl);
1811 else if (i == 'T') {
1812 datestring(datestamp, sizeof datestamp,
1813 atol(mptr), DATESTRING_RFC822);
1814 cprintf("Date: %s%s", datestamp, nl);
1816 else if (i == 'W') {
1817 cprintf("References: ");
1818 k = num_tokens(mptr, '|');
1819 for (j=0; j<k; ++j) {
1820 extract_token(buf, mptr, j, '|', sizeof buf);
1821 cprintf("<%s>", buf);
1834 if (subject_found == 0) {
1835 cprintf("Subject: (no subject)%s", nl);
1839 for (i=0; !IsEmptyStr(&suser[i]); ++i) {
1840 suser[i] = tolower(suser[i]);
1841 if (!isalnum(suser[i])) suser[i]='_';
1844 if (mode == MT_RFC822) {
1845 if (!strcasecmp(snode, NODENAME)) {
1846 safestrncpy(snode, FQDN, sizeof snode);
1849 /* Construct a fun message id */
1850 cprintf("Message-ID: <%s", mid);/// todo: this possibly breaks threadding mails.
1851 if (strchr(mid, '@')==NULL) {
1852 cprintf("@%s", snode);
1856 if (!is_room_aide() && (TheMessage->cm_anon_type == MES_ANONONLY)) {
1857 cprintf("From: \"----\" <x@x.org>%s", nl);
1859 else if (!is_room_aide() && (TheMessage->cm_anon_type == MES_ANONOPT)) {
1860 cprintf("From: \"anonymous\" <x@x.org>%s", nl);
1862 else if (!IsEmptyStr(fuser)) {
1863 cprintf("From: \"%s\" <%s>%s", luser, fuser, nl);
1866 cprintf("From: \"%s\" <%s@%s>%s", luser, suser, snode, nl);
1869 cprintf("Organization: %s%s", lnode, nl);
1871 /* Blank line signifying RFC822 end-of-headers */
1872 if (TheMessage->cm_format_type != FMT_RFC822) {
1877 /* end header processing loop ... at this point, we're in the text */
1879 if (headers_only == HEADERS_FAST) goto DONE;
1880 mptr = TheMessage->cm_fields['M'];
1882 /* Tell the client about the MIME parts in this message */
1883 if (TheMessage->cm_format_type == FMT_RFC822) {
1884 if ( (mode == MT_CITADEL) || (mode == MT_MIME) ) {
1885 memset(&ma, 0, sizeof(struct ma_info));
1886 mime_parser(mptr, NULL,
1887 (do_proto ? *list_this_part : NULL),
1888 (do_proto ? *list_this_pref : NULL),
1889 (do_proto ? *list_this_suff : NULL),
1892 else if (mode == MT_RFC822) { /* unparsed RFC822 dump */
1893 char *start_of_text = NULL;
1894 start_of_text = strstr(mptr, "\n\r\n");
1895 if (start_of_text == NULL) start_of_text = strstr(mptr, "\n\n");
1896 if (start_of_text == NULL) start_of_text = mptr;
1898 start_of_text = strstr(start_of_text, "\n");
1903 int nllen = strlen(nl);
1905 while (ch=*mptr, ch!=0) {
1911 ((headers_only == HEADERS_NONE) && (mptr >= start_of_text))
1912 || ((headers_only == HEADERS_ONLY) && (mptr < start_of_text))
1913 || ((headers_only != HEADERS_NONE) && (headers_only != HEADERS_ONLY))
1916 sprintf(&outbuf[outlen], "%s", nl);
1920 outbuf[outlen++] = ch;
1924 if (flags & ESC_DOT)
1926 if ((prev_ch == 10) && (ch == '.') && ((*(mptr+1) == 13) || (*(mptr+1) == 10)))
1928 outbuf[outlen++] = '.';
1933 if (outlen > 1000) {
1934 client_write(outbuf, outlen);
1939 client_write(outbuf, outlen);
1947 if (headers_only == HEADERS_ONLY) {
1951 /* signify start of msg text */
1952 if ( (mode == MT_CITADEL) || (mode == MT_MIME) ) {
1953 if (do_proto) cprintf("text\n");
1956 /* If the format type on disk is 1 (fixed-format), then we want
1957 * everything to be output completely literally ... regardless of
1958 * what message transfer format is in use.
1960 if (TheMessage->cm_format_type == FMT_FIXED) {
1962 if (mode == MT_MIME) {
1963 cprintf("Content-type: text/plain\n\n");
1967 while (ch = *mptr++, ch > 0) {
1970 if ((ch == 10) || (buflen > 250)) {
1972 cprintf("%s%s", buf, nl);
1981 if (!IsEmptyStr(buf))
1982 cprintf("%s%s", buf, nl);
1985 /* If the message on disk is format 0 (Citadel vari-format), we
1986 * output using the formatter at 80 columns. This is the final output
1987 * form if the transfer format is RFC822, but if the transfer format
1988 * is Citadel proprietary, it'll still work, because the indentation
1989 * for new paragraphs is correct and the client will reformat the
1990 * message to the reader's screen width.
1992 if (TheMessage->cm_format_type == FMT_CITADEL) {
1993 if (mode == MT_MIME) {
1994 cprintf("Content-type: text/x-citadel-variformat\n\n");
1996 memfmout(mptr, 0, nl);
1999 /* If the message on disk is format 4 (MIME), we've gotta hand it
2000 * off to the MIME parser. The client has already been told that
2001 * this message is format 1 (fixed format), so the callback function
2002 * we use will display those parts as-is.
2004 if (TheMessage->cm_format_type == FMT_RFC822) {
2005 memset(&ma, 0, sizeof(struct ma_info));
2007 if (mode == MT_MIME) {
2008 ma.use_fo_hooks = 0;
2009 strcpy(ma.chosen_part, "1");
2010 ma.chosen_pref = 9999;
2011 mime_parser(mptr, NULL,
2012 *choose_preferred, *fixed_output_pre,
2013 *fixed_output_post, (void *)&ma, 0);
2014 mime_parser(mptr, NULL,
2015 *output_preferred, NULL, NULL, (void *)&ma, CC->msg4_dont_decode);
2018 ma.use_fo_hooks = 1;
2019 mime_parser(mptr, NULL,
2020 *fixed_output, *fixed_output_pre,
2021 *fixed_output_post, (void *)&ma, 0);
2026 DONE: /* now we're done */
2027 if (do_proto) cprintf("000\n");
2034 * display a message (mode 0 - Citadel proprietary)
2036 void cmd_msg0(char *cmdbuf)
2039 int headers_only = HEADERS_ALL;
2041 msgid = extract_long(cmdbuf, 0);
2042 headers_only = extract_int(cmdbuf, 1);
2044 CtdlOutputMsg(msgid, MT_CITADEL, headers_only, 1, 0, NULL, 0);
2050 * display a message (mode 2 - RFC822)
2052 void cmd_msg2(char *cmdbuf)
2055 int headers_only = HEADERS_ALL;
2057 msgid = extract_long(cmdbuf, 0);
2058 headers_only = extract_int(cmdbuf, 1);
2060 CtdlOutputMsg(msgid, MT_RFC822, headers_only, 1, 1, NULL, 0);
2066 * display a message (mode 3 - IGnet raw format - internal programs only)
2068 void cmd_msg3(char *cmdbuf)
2071 struct CtdlMessage *msg = NULL;
2074 if (CC->internal_pgm == 0) {
2075 cprintf("%d This command is for internal programs only.\n",
2076 ERROR + HIGHER_ACCESS_REQUIRED);
2080 msgnum = extract_long(cmdbuf, 0);
2081 msg = CtdlFetchMessage(msgnum, 1);
2083 cprintf("%d Message %ld not found.\n",
2084 ERROR + MESSAGE_NOT_FOUND, msgnum);
2088 serialize_message(&smr, msg);
2089 CtdlFreeMessage(msg);
2092 cprintf("%d Unable to serialize message\n",
2093 ERROR + INTERNAL_ERROR);
2097 cprintf("%d %ld\n", BINARY_FOLLOWS, (long)smr.len);
2098 client_write((char *)smr.ser, (int)smr.len);
2105 * Display a message using MIME content types
2107 void cmd_msg4(char *cmdbuf)
2112 msgid = extract_long(cmdbuf, 0);
2113 extract_token(section, cmdbuf, 1, '|', sizeof section);
2114 CtdlOutputMsg(msgid, MT_MIME, 0, 1, 0, (section[0] ? section : NULL) , 0);
2120 * Client tells us its preferred message format(s)
2122 void cmd_msgp(char *cmdbuf)
2124 if (!strcasecmp(cmdbuf, "dont_decode")) {
2125 CC->msg4_dont_decode = 1;
2126 cprintf("%d MSG4 will not pre-decode messages.\n", CIT_OK);
2129 safestrncpy(CC->preferred_formats, cmdbuf, sizeof(CC->preferred_formats));
2130 cprintf("%d Preferred MIME formats have been set.\n", CIT_OK);
2136 * Open a component of a MIME message as a download file
2138 void cmd_opna(char *cmdbuf)
2141 char desired_section[128];
2143 msgid = extract_long(cmdbuf, 0);
2144 extract_token(desired_section, cmdbuf, 1, '|', sizeof desired_section);
2145 safestrncpy(CC->download_desired_section, desired_section,
2146 sizeof CC->download_desired_section);
2147 CtdlOutputMsg(msgid, MT_DOWNLOAD, 0, 1, 1, NULL, 0);
2152 * Open a component of a MIME message and transmit it all at once
2154 void cmd_dlat(char *cmdbuf)
2157 char desired_section[128];
2159 msgid = extract_long(cmdbuf, 0);
2160 extract_token(desired_section, cmdbuf, 1, '|', sizeof desired_section);
2161 safestrncpy(CC->download_desired_section, desired_section,
2162 sizeof CC->download_desired_section);
2163 CtdlOutputMsg(msgid, MT_SPEW_SECTION, 0, 1, 1, NULL, 0);
2168 * Save one or more message pointers into a specified room
2169 * (Returns 0 for success, nonzero for failure)
2170 * roomname may be NULL to use the current room
2172 * Note that the 'supplied_msg' field may be set to NULL, in which case
2173 * the message will be fetched from disk, by number, if we need to perform
2174 * replication checks. This adds an additional database read, so if the
2175 * caller already has the message in memory then it should be supplied. (Obviously
2176 * this mode of operation only works if we're saving a single message.)
2178 int CtdlSaveMsgPointersInRoom(char *roomname, long newmsgidlist[], int num_newmsgs,
2179 int do_repl_check, struct CtdlMessage *supplied_msg)
2182 char hold_rm[ROOMNAMELEN];
2183 struct cdbdata *cdbfr;
2186 long highest_msg = 0L;
2189 struct CtdlMessage *msg = NULL;
2191 long *msgs_to_be_merged = NULL;
2192 int num_msgs_to_be_merged = 0;
2194 CtdlLogPrintf(CTDL_DEBUG,
2195 "CtdlSaveMsgPointersInRoom(room=%s, num_msgs=%d, repl=%d)\n",
2196 roomname, num_newmsgs, do_repl_check);
2198 strcpy(hold_rm, CC->room.QRname);
2201 if (newmsgidlist == NULL) return(ERROR + INTERNAL_ERROR);
2202 if (num_newmsgs < 1) return(ERROR + INTERNAL_ERROR);
2203 if (num_newmsgs > 1) supplied_msg = NULL;
2205 /* Now the regular stuff */
2206 if (lgetroom(&CC->room,
2207 ((roomname != NULL) ? roomname : CC->room.QRname) )
2209 CtdlLogPrintf(CTDL_ERR, "No such room <%s>\n", roomname);
2210 return(ERROR + ROOM_NOT_FOUND);
2214 msgs_to_be_merged = malloc(sizeof(long) * num_newmsgs);
2215 num_msgs_to_be_merged = 0;
2218 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long));
2219 if (cdbfr == NULL) {
2223 msglist = (long *) cdbfr->ptr;
2224 cdbfr->ptr = NULL; /* CtdlSaveMsgPointerInRoom() now owns this memory */
2225 num_msgs = cdbfr->len / sizeof(long);
2230 /* Create a list of msgid's which were supplied by the caller, but do
2231 * not already exist in the target room. It is absolutely taboo to
2232 * have more than one reference to the same message in a room.
2234 for (i=0; i<num_newmsgs; ++i) {
2236 if (num_msgs > 0) for (j=0; j<num_msgs; ++j) {
2237 if (msglist[j] == newmsgidlist[i]) {
2242 msgs_to_be_merged[num_msgs_to_be_merged++] = newmsgidlist[i];
2246 CtdlLogPrintf(9, "%d unique messages to be merged\n", num_msgs_to_be_merged);
2249 * Now merge the new messages
2251 msglist = realloc(msglist, (sizeof(long) * (num_msgs + num_msgs_to_be_merged)) );
2252 if (msglist == NULL) {
2253 CtdlLogPrintf(CTDL_ALERT, "ERROR: can't realloc message list!\n");
2255 memcpy(&msglist[num_msgs], msgs_to_be_merged, (sizeof(long) * num_msgs_to_be_merged) );
2256 num_msgs += num_msgs_to_be_merged;
2258 /* Sort the message list, so all the msgid's are in order */
2259 num_msgs = sort_msglist(msglist, num_msgs);
2261 /* Determine the highest message number */
2262 highest_msg = msglist[num_msgs - 1];
2264 /* Write it back to disk. */
2265 cdb_store(CDB_MSGLISTS, &CC->room.QRnumber, (int)sizeof(long),
2266 msglist, (int)(num_msgs * sizeof(long)));
2268 /* Free up the memory we used. */
2271 /* Update the highest-message pointer and unlock the room. */
2272 CC->room.QRhighest = highest_msg;
2273 lputroom(&CC->room);
2275 /* Perform replication checks if necessary */
2276 if ( (DoesThisRoomNeedEuidIndexing(&CC->room)) && (do_repl_check) ) {
2277 CtdlLogPrintf(CTDL_DEBUG, "CtdlSaveMsgPointerInRoom() doing repl checks\n");
2279 for (i=0; i<num_msgs_to_be_merged; ++i) {
2280 msgid = msgs_to_be_merged[i];
2282 if (supplied_msg != NULL) {
2286 msg = CtdlFetchMessage(msgid, 0);
2290 ReplicationChecks(msg);
2292 /* If the message has an Exclusive ID, index that... */
2293 if (msg->cm_fields['E'] != NULL) {
2294 index_message_by_euid(msg->cm_fields['E'], &CC->room, msgid);
2297 /* Free up the memory we may have allocated */
2298 if (msg != supplied_msg) {
2299 CtdlFreeMessage(msg);
2307 CtdlLogPrintf(CTDL_DEBUG, "CtdlSaveMsgPointerInRoom() skips repl checks\n");
2310 /* Submit this room for processing by hooks */
2311 PerformRoomHooks(&CC->room);
2313 /* Go back to the room we were in before we wandered here... */
2314 getroom(&CC->room, hold_rm);
2316 /* Bump the reference count for all messages which were merged */
2317 for (i=0; i<num_msgs_to_be_merged; ++i) {
2318 AdjRefCount(msgs_to_be_merged[i], +1);
2321 /* Free up memory... */
2322 if (msgs_to_be_merged != NULL) {
2323 free(msgs_to_be_merged);
2326 /* Return success. */
2332 * This is the same as CtdlSaveMsgPointersInRoom() but it only accepts
2335 int CtdlSaveMsgPointerInRoom(char *roomname, long msgid,
2336 int do_repl_check, struct CtdlMessage *supplied_msg)
2338 return CtdlSaveMsgPointersInRoom(roomname, &msgid, 1, do_repl_check, supplied_msg);
2345 * Message base operation to save a new message to the message store
2346 * (returns new message number)
2348 * This is the back end for CtdlSubmitMsg() and should not be directly
2349 * called by server-side modules.
2352 long send_message(struct CtdlMessage *msg) {
2360 /* Get a new message number */
2361 newmsgid = get_new_message_number();
2362 snprintf(msgidbuf, sizeof msgidbuf, "%010ld@%s", newmsgid, config.c_fqdn);
2364 /* Generate an ID if we don't have one already */
2365 if (msg->cm_fields['I']==NULL) {
2366 msg->cm_fields['I'] = strdup(msgidbuf);
2369 /* If the message is big, set its body aside for storage elsewhere */
2370 if (msg->cm_fields['M'] != NULL) {
2371 if (strlen(msg->cm_fields['M']) > BIGMSG) {
2373 holdM = msg->cm_fields['M'];
2374 msg->cm_fields['M'] = NULL;
2378 /* Serialize our data structure for storage in the database */
2379 serialize_message(&smr, msg);
2382 msg->cm_fields['M'] = holdM;
2386 cprintf("%d Unable to serialize message\n",
2387 ERROR + INTERNAL_ERROR);
2391 /* Write our little bundle of joy into the message base */
2392 if (cdb_store(CDB_MSGMAIN, &newmsgid, (int)sizeof(long),
2393 smr.ser, smr.len) < 0) {
2394 CtdlLogPrintf(CTDL_ERR, "Can't store message\n");
2398 cdb_store(CDB_BIGMSGS,
2408 /* Free the memory we used for the serialized message */
2411 /* Return the *local* message ID to the caller
2412 * (even if we're storing an incoming network message)
2420 * Serialize a struct CtdlMessage into the format used on disk and network.
2422 * This function loads up a "struct ser_ret" (defined in server.h) which
2423 * contains the length of the serialized message and a pointer to the
2424 * serialized message in memory. THE LATTER MUST BE FREED BY THE CALLER.
2426 void serialize_message(struct ser_ret *ret, /* return values */
2427 struct CtdlMessage *msg) /* unserialized msg */
2429 size_t wlen, fieldlen;
2431 static char *forder = FORDER;
2434 * Check for valid message format
2436 if (is_valid_message(msg) == 0) {
2437 CtdlLogPrintf(CTDL_ERR, "serialize_message() aborting due to invalid message\n");
2444 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL)
2445 ret->len = ret->len +
2446 strlen(msg->cm_fields[(int)forder[i]]) + 2;
2448 ret->ser = malloc(ret->len);
2449 if (ret->ser == NULL) {
2450 CtdlLogPrintf(CTDL_ERR, "serialize_message() malloc(%ld) failed: %s\n",
2451 (long)ret->len, strerror(errno));
2458 ret->ser[1] = msg->cm_anon_type;
2459 ret->ser[2] = msg->cm_format_type;
2462 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL) {
2463 fieldlen = strlen(msg->cm_fields[(int)forder[i]]);
2464 ret->ser[wlen++] = (char)forder[i];
2465 safestrncpy((char *)&ret->ser[wlen], msg->cm_fields[(int)forder[i]], fieldlen+1);
2466 wlen = wlen + fieldlen + 1;
2468 if (ret->len != wlen) CtdlLogPrintf(CTDL_ERR, "ERROR: len=%ld wlen=%ld\n",
2469 (long)ret->len, (long)wlen);
2476 * Serialize a struct CtdlMessage into the format used on disk and network.
2478 * This function loads up a "struct ser_ret" (defined in server.h) which
2479 * contains the length of the serialized message and a pointer to the
2480 * serialized message in memory. THE LATTER MUST BE FREED BY THE CALLER.
2482 void dump_message(struct CtdlMessage *msg, /* unserialized msg */
2483 long Siz) /* how many chars ? */
2487 static char *forder = FORDER;
2491 * Check for valid message format
2493 if (is_valid_message(msg) == 0) {
2494 CtdlLogPrintf(CTDL_ERR, "dump_message() aborting due to invalid message\n");
2498 buf = (char*) malloc (Siz + 1);
2502 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL) {
2503 snprintf (buf, Siz, " msg[%c] = %s ...\n", (char) forder[i],
2504 msg->cm_fields[(int)forder[i]]);
2505 client_write (buf, strlen(buf));
2514 * Check to see if any messages already exist in the current room which
2515 * carry the same Exclusive ID as this one. If any are found, delete them.
2517 void ReplicationChecks(struct CtdlMessage *msg) {
2518 long old_msgnum = (-1L);
2520 if (DoesThisRoomNeedEuidIndexing(&CC->room) == 0) return;
2522 CtdlLogPrintf(CTDL_DEBUG, "Performing replication checks in <%s>\n",
2525 /* No exclusive id? Don't do anything. */
2526 if (msg == NULL) return;
2527 if (msg->cm_fields['E'] == NULL) return;
2528 if (IsEmptyStr(msg->cm_fields['E'])) return;
2529 /*CtdlLogPrintf(CTDL_DEBUG, "Exclusive ID: <%s> for room <%s>\n",
2530 msg->cm_fields['E'], CC->room.QRname);*/
2532 old_msgnum = locate_message_by_euid(msg->cm_fields['E'], &CC->room);
2533 if (old_msgnum > 0L) {
2534 CtdlLogPrintf(CTDL_DEBUG, "ReplicationChecks() replacing message %ld\n", old_msgnum);
2535 CtdlDeleteMessages(CC->room.QRname, &old_msgnum, 1, "");
2542 * Save a message to disk and submit it into the delivery system.
2544 long CtdlSubmitMsg(struct CtdlMessage *msg, /* message to save */
2545 struct recptypes *recps, /* recipients (if mail) */
2546 char *force, /* force a particular room? */
2547 int flags /* should the bessage be exported clean? */
2549 char submit_filename[128];
2550 char generated_timestamp[32];
2551 char hold_rm[ROOMNAMELEN];
2552 char actual_rm[ROOMNAMELEN];
2553 char force_room[ROOMNAMELEN];
2554 char content_type[SIZ]; /* We have to learn this */
2555 char recipient[SIZ];
2558 struct ctdluser userbuf;
2560 struct MetaData smi;
2561 FILE *network_fp = NULL;
2562 static int seqnum = 1;
2563 struct CtdlMessage *imsg = NULL;
2565 size_t instr_alloc = 0;
2567 char *hold_R, *hold_D;
2568 char *collected_addresses = NULL;
2569 struct addresses_to_be_filed *aptr = NULL;
2570 char *saved_rfc822_version = NULL;
2571 int qualified_for_journaling = 0;
2572 struct CitContext *CCC = CC; /* CachedCitContext - performance boost */
2573 char bounce_to[1024] = "";
2575 CtdlLogPrintf(CTDL_DEBUG, "CtdlSubmitMsg() called\n");
2576 if (is_valid_message(msg) == 0) return(-1); /* self check */
2578 /* If this message has no timestamp, we take the liberty of
2579 * giving it one, right now.
2581 if (msg->cm_fields['T'] == NULL) {
2582 snprintf(generated_timestamp, sizeof generated_timestamp, "%ld", (long)time(NULL));
2583 msg->cm_fields['T'] = strdup(generated_timestamp);
2586 /* If this message has no path, we generate one.
2588 if (msg->cm_fields['P'] == NULL) {
2589 if (msg->cm_fields['A'] != NULL) {
2590 msg->cm_fields['P'] = strdup(msg->cm_fields['A']);
2591 for (a=0; !IsEmptyStr(&msg->cm_fields['P'][a]); ++a) {
2592 if (isspace(msg->cm_fields['P'][a])) {
2593 msg->cm_fields['P'][a] = ' ';
2598 msg->cm_fields['P'] = strdup("unknown");
2602 if (force == NULL) {
2603 strcpy(force_room, "");
2606 strcpy(force_room, force);
2609 /* Learn about what's inside, because it's what's inside that counts */
2610 if (msg->cm_fields['M'] == NULL) {
2611 CtdlLogPrintf(CTDL_ERR, "ERROR: attempt to save message with NULL body\n");
2615 switch (msg->cm_format_type) {
2617 strcpy(content_type, "text/x-citadel-variformat");
2620 strcpy(content_type, "text/plain");
2623 strcpy(content_type, "text/plain");
2624 mptr = bmstrcasestr(msg->cm_fields['M'], "Content-type:");
2627 safestrncpy(content_type, &mptr[13], sizeof content_type);
2628 striplt(content_type);
2629 aptr = content_type;
2630 while (!IsEmptyStr(aptr)) {
2642 /* Goto the correct room */
2643 CtdlLogPrintf(CTDL_DEBUG, "Selected room %s\n", (recps) ? CCC->room.QRname : SENTITEMS);
2644 strcpy(hold_rm, CCC->room.QRname);
2645 strcpy(actual_rm, CCC->room.QRname);
2646 if (recps != NULL) {
2647 strcpy(actual_rm, SENTITEMS);
2650 /* If the user is a twit, move to the twit room for posting */
2652 if (CCC->user.axlevel == 2) {
2653 strcpy(hold_rm, actual_rm);
2654 strcpy(actual_rm, config.c_twitroom);
2655 CtdlLogPrintf(CTDL_DEBUG, "Diverting to twit room\n");
2659 /* ...or if this message is destined for Aide> then go there. */
2660 if (!IsEmptyStr(force_room)) {
2661 strcpy(actual_rm, force_room);
2664 CtdlLogPrintf(CTDL_DEBUG, "Final selection: %s\n", actual_rm);
2665 if (strcasecmp(actual_rm, CCC->room.QRname)) {
2666 /* getroom(&CCC->room, actual_rm); */
2667 usergoto(actual_rm, 0, 1, NULL, NULL);
2671 * If this message has no O (room) field, generate one.
2673 if (msg->cm_fields['O'] == NULL) {
2674 msg->cm_fields['O'] = strdup(CCC->room.QRname);
2677 /* Perform "before save" hooks (aborting if any return nonzero) */
2678 CtdlLogPrintf(CTDL_DEBUG, "Performing before-save hooks\n");
2679 if (PerformMessageHooks(msg, EVT_BEFORESAVE) > 0) return(-3);
2682 * If this message has an Exclusive ID, and the room is replication
2683 * checking enabled, then do replication checks.
2685 if (DoesThisRoomNeedEuidIndexing(&CCC->room)) {
2686 ReplicationChecks(msg);
2689 /* Save it to disk */
2690 CtdlLogPrintf(CTDL_DEBUG, "Saving to disk\n");
2691 newmsgid = send_message(msg);
2692 if (newmsgid <= 0L) return(-5);
2694 /* Write a supplemental message info record. This doesn't have to
2695 * be a critical section because nobody else knows about this message
2698 CtdlLogPrintf(CTDL_DEBUG, "Creating MetaData record\n");
2699 memset(&smi, 0, sizeof(struct MetaData));
2700 smi.meta_msgnum = newmsgid;
2701 smi.meta_refcount = 0;
2702 safestrncpy(smi.meta_content_type, content_type,
2703 sizeof smi.meta_content_type);
2706 * Measure how big this message will be when rendered as RFC822.
2707 * We do this for two reasons:
2708 * 1. We need the RFC822 length for the new metadata record, so the
2709 * POP and IMAP services don't have to calculate message lengths
2710 * while the user is waiting (multiplied by potentially hundreds
2711 * or thousands of messages).
2712 * 2. If journaling is enabled, we will need an RFC822 version of the
2713 * message to attach to the journalized copy.
2715 if (CCC->redirect_buffer != NULL) {
2716 CtdlLogPrintf(CTDL_ALERT, "CCC->redirect_buffer is not NULL during message submission!\n");
2719 CCC->redirect_buffer = malloc(SIZ);
2720 CCC->redirect_len = 0;
2721 CCC->redirect_alloc = SIZ;
2722 CtdlOutputPreLoadedMsg(msg, MT_RFC822, HEADERS_ALL, 0, 1, QP_EADDR);
2723 smi.meta_rfc822_length = CCC->redirect_len;
2724 saved_rfc822_version = CCC->redirect_buffer;
2725 CCC->redirect_buffer = NULL;
2726 CCC->redirect_len = 0;
2727 CCC->redirect_alloc = 0;
2731 /* Now figure out where to store the pointers */
2732 CtdlLogPrintf(CTDL_DEBUG, "Storing pointers\n");
2734 /* If this is being done by the networker delivering a private
2735 * message, we want to BYPASS saving the sender's copy (because there
2736 * is no local sender; it would otherwise go to the Trashcan).
2738 if ((!CCC->internal_pgm) || (recps == NULL)) {
2739 if (CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 1, msg) != 0) {
2740 CtdlLogPrintf(CTDL_ERR, "ERROR saving message pointer!\n");
2741 CtdlSaveMsgPointerInRoom(config.c_aideroom, newmsgid, 0, msg);
2745 /* For internet mail, drop a copy in the outbound queue room */
2747 if (recps->num_internet > 0) {
2748 CtdlSaveMsgPointerInRoom(SMTP_SPOOLOUT_ROOM, newmsgid, 0, msg);
2751 /* If other rooms are specified, drop them there too. */
2753 if (recps->num_room > 0)
2754 for (i=0; i<num_tokens(recps->recp_room, '|'); ++i) {
2755 extract_token(recipient, recps->recp_room, i,
2756 '|', sizeof recipient);
2757 CtdlLogPrintf(CTDL_DEBUG, "Delivering to room <%s>\n", recipient);
2758 CtdlSaveMsgPointerInRoom(recipient, newmsgid, 0, msg);
2761 /* Bump this user's messages posted counter. */
2762 CtdlLogPrintf(CTDL_DEBUG, "Updating user\n");
2763 lgetuser(&CCC->user, CCC->curr_user);
2764 CCC->user.posted = CCC->user.posted + 1;
2765 lputuser(&CCC->user);
2767 /* Decide where bounces need to be delivered */
2768 if (CCC->logged_in) {
2769 snprintf(bounce_to, sizeof bounce_to, "%s@%s", CCC->user.fullname, config.c_nodename);
2772 snprintf(bounce_to, sizeof bounce_to, "%s@%s", msg->cm_fields['A'], msg->cm_fields['N']);
2775 /* If this is private, local mail, make a copy in the
2776 * recipient's mailbox and bump the reference count.
2779 if (recps->num_local > 0)
2780 for (i=0; i<num_tokens(recps->recp_local, '|'); ++i) {
2781 extract_token(recipient, recps->recp_local, i,
2782 '|', sizeof recipient);
2783 CtdlLogPrintf(CTDL_DEBUG, "Delivering private local mail to <%s>\n",
2785 if (getuser(&userbuf, recipient) == 0) {
2786 // Add a flag so the Funambol module knows its mail
2787 msg->cm_fields['W'] = strdup(recipient);
2788 MailboxName(actual_rm, sizeof actual_rm, &userbuf, MAILROOM);
2789 CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 0, msg);
2790 BumpNewMailCounter(userbuf.usernum);
2791 if (!IsEmptyStr(config.c_funambol_host) || !IsEmptyStr(config.c_pager_program)) {
2792 /* Generate a instruction message for the Funambol notification
2793 * server, in the same style as the SMTP queue
2796 instr = malloc(instr_alloc);
2797 snprintf(instr, instr_alloc,
2798 "Content-type: %s\n\nmsgid|%ld\nsubmitted|%ld\n"
2800 SPOOLMIME, newmsgid, (long)time(NULL),
2804 imsg = malloc(sizeof(struct CtdlMessage));
2805 memset(imsg, 0, sizeof(struct CtdlMessage));
2806 imsg->cm_magic = CTDLMESSAGE_MAGIC;
2807 imsg->cm_anon_type = MES_NORMAL;
2808 imsg->cm_format_type = FMT_RFC822;
2809 imsg->cm_fields['A'] = strdup("Citadel");
2810 imsg->cm_fields['J'] = strdup("do not journal");
2811 imsg->cm_fields['M'] = instr; /* imsg owns this memory now */
2812 imsg->cm_fields['W'] = strdup(recipient);
2813 CtdlSubmitMsg(imsg, NULL, FNBL_QUEUE_ROOM, 0);
2814 CtdlFreeMessage(imsg);
2818 CtdlLogPrintf(CTDL_DEBUG, "No user <%s>\n", recipient);
2819 CtdlSaveMsgPointerInRoom(config.c_aideroom,
2824 /* Perform "after save" hooks */
2825 CtdlLogPrintf(CTDL_DEBUG, "Performing after-save hooks\n");
2826 PerformMessageHooks(msg, EVT_AFTERSAVE);
2828 /* For IGnet mail, we have to save a new copy into the spooler for
2829 * each recipient, with the R and D fields set to the recipient and
2830 * destination-node. This has two ugly side effects: all other
2831 * recipients end up being unlisted in this recipient's copy of the
2832 * message, and it has to deliver multiple messages to the same
2833 * node. We'll revisit this again in a year or so when everyone has
2834 * a network spool receiver that can handle the new style messages.
2837 if (recps->num_ignet > 0)
2838 for (i=0; i<num_tokens(recps->recp_ignet, '|'); ++i) {
2839 extract_token(recipient, recps->recp_ignet, i,
2840 '|', sizeof recipient);
2842 hold_R = msg->cm_fields['R'];
2843 hold_D = msg->cm_fields['D'];
2844 msg->cm_fields['R'] = malloc(SIZ);
2845 msg->cm_fields['D'] = malloc(128);
2846 extract_token(msg->cm_fields['R'], recipient, 0, '@', SIZ);
2847 extract_token(msg->cm_fields['D'], recipient, 1, '@', 128);
2849 serialize_message(&smr, msg);
2851 snprintf(submit_filename, sizeof submit_filename,
2852 "%s/netmail.%04lx.%04x.%04x",
2854 (long) getpid(), CCC->cs_pid, ++seqnum);
2855 network_fp = fopen(submit_filename, "wb+");
2856 if (network_fp != NULL) {
2857 fwrite(smr.ser, smr.len, 1, network_fp);
2863 free(msg->cm_fields['R']);
2864 free(msg->cm_fields['D']);
2865 msg->cm_fields['R'] = hold_R;
2866 msg->cm_fields['D'] = hold_D;
2869 /* Go back to the room we started from */
2870 CtdlLogPrintf(CTDL_DEBUG, "Returning to original room %s\n", hold_rm);
2871 if (strcasecmp(hold_rm, CCC->room.QRname))
2872 usergoto(hold_rm, 0, 1, NULL, NULL);
2874 /* For internet mail, generate delivery instructions.
2875 * Yes, this is recursive. Deal with it. Infinite recursion does
2876 * not happen because the delivery instructions message does not
2877 * contain a recipient.
2880 if (recps->num_internet > 0) {
2881 CtdlLogPrintf(CTDL_DEBUG, "Generating delivery instructions\n");
2883 instr = malloc(instr_alloc);
2884 snprintf(instr, instr_alloc,
2885 "Content-type: %s\n\nmsgid|%ld\nsubmitted|%ld\n"
2887 SPOOLMIME, newmsgid, (long)time(NULL),
2891 for (i=0; i<num_tokens(recps->recp_internet, '|'); ++i) {
2892 size_t tmp = strlen(instr);
2893 extract_token(recipient, recps->recp_internet, i, '|', sizeof recipient);
2894 if ((tmp + strlen(recipient) + 32) > instr_alloc) {
2895 instr_alloc = instr_alloc * 2;
2896 instr = realloc(instr, instr_alloc);
2898 snprintf(&instr[tmp], instr_alloc - tmp, "remote|%s|0||\n", recipient);
2901 imsg = malloc(sizeof(struct CtdlMessage));
2902 memset(imsg, 0, sizeof(struct CtdlMessage));
2903 imsg->cm_magic = CTDLMESSAGE_MAGIC;
2904 imsg->cm_anon_type = MES_NORMAL;
2905 imsg->cm_format_type = FMT_RFC822;
2906 imsg->cm_fields['A'] = strdup("Citadel");
2907 imsg->cm_fields['J'] = strdup("do not journal");
2908 imsg->cm_fields['M'] = instr; /* imsg owns this memory now */
2909 CtdlSubmitMsg(imsg, NULL, SMTP_SPOOLOUT_ROOM, QP_EADDR);
2910 CtdlFreeMessage(imsg);
2914 * Any addresses to harvest for someone's address book?
2916 if ( (CCC->logged_in) && (recps != NULL) ) {
2917 collected_addresses = harvest_collected_addresses(msg);
2920 if (collected_addresses != NULL) {
2921 aptr = (struct addresses_to_be_filed *)
2922 malloc(sizeof(struct addresses_to_be_filed));
2923 MailboxName(actual_rm, sizeof actual_rm,
2924 &CCC->user, USERCONTACTSROOM);
2925 aptr->roomname = strdup(actual_rm);
2926 aptr->collected_addresses = collected_addresses;
2927 begin_critical_section(S_ATBF);
2930 end_critical_section(S_ATBF);
2934 * Determine whether this message qualifies for journaling.
2936 if (msg->cm_fields['J'] != NULL) {
2937 qualified_for_journaling = 0;
2940 if (recps == NULL) {
2941 qualified_for_journaling = config.c_journal_pubmsgs;
2943 else if (recps->num_local + recps->num_ignet + recps->num_internet > 0) {
2944 qualified_for_journaling = config.c_journal_email;
2947 qualified_for_journaling = config.c_journal_pubmsgs;
2952 * Do we have to perform journaling? If so, hand off the saved
2953 * RFC822 version will be handed off to the journaler for background
2954 * submit. Otherwise, we have to free the memory ourselves.
2956 if (saved_rfc822_version != NULL) {
2957 if (qualified_for_journaling) {
2958 JournalBackgroundSubmit(msg, saved_rfc822_version, recps);
2961 free(saved_rfc822_version);
2974 * Convenience function for generating small administrative messages.
2976 void quickie_message(char *from, char *fromaddr, char *to, char *room, char *text,
2977 int format_type, char *subject)
2979 struct CtdlMessage *msg;
2980 struct recptypes *recp = NULL;
2982 msg = malloc(sizeof(struct CtdlMessage));
2983 memset(msg, 0, sizeof(struct CtdlMessage));
2984 msg->cm_magic = CTDLMESSAGE_MAGIC;
2985 msg->cm_anon_type = MES_NORMAL;
2986 msg->cm_format_type = format_type;
2989 msg->cm_fields['A'] = strdup(from);
2991 else if (fromaddr != NULL) {
2992 msg->cm_fields['A'] = strdup(fromaddr);
2993 if (strchr(msg->cm_fields['A'], '@')) {
2994 *strchr(msg->cm_fields['A'], '@') = 0;
2998 msg->cm_fields['A'] = strdup("Citadel");
3001 if (fromaddr != NULL) msg->cm_fields['F'] = strdup(fromaddr);
3002 if (room != NULL) msg->cm_fields['O'] = strdup(room);
3003 msg->cm_fields['N'] = strdup(NODENAME);
3005 msg->cm_fields['R'] = strdup(to);
3006 recp = validate_recipients(to, NULL, 0);
3008 if (subject != NULL) {
3009 msg->cm_fields['U'] = strdup(subject);
3011 msg->cm_fields['M'] = strdup(text);
3013 CtdlSubmitMsg(msg, recp, room, 0);
3014 CtdlFreeMessage(msg);
3015 if (recp != NULL) free_recipients(recp);
3021 * Back end function used by CtdlMakeMessage() and similar functions
3023 char *CtdlReadMessageBody(char *terminator, /* token signalling EOT */
3024 size_t maxlen, /* maximum message length */
3025 char *exist, /* if non-null, append to it;
3026 exist is ALWAYS freed */
3027 int crlf, /* CRLF newlines instead of LF */
3028 int sock /* socket handle or 0 for this session's client socket */
3032 size_t message_len = 0;
3033 size_t buffer_len = 0;
3040 if (exist == NULL) {
3047 message_len = strlen(exist);
3048 buffer_len = message_len + 4096;
3049 m = realloc(exist, buffer_len);
3056 /* Do we need to change leading ".." to "." for SMTP escaping? */
3057 if (!strcmp(terminator, ".")) {
3061 /* flush the input if we have nowhere to store it */
3066 /* read in the lines of message text one by one */
3069 if (sock_getln(sock, buf, (sizeof buf - 3)) < 0) finished = 1;
3072 if (client_getln(buf, (sizeof buf - 3)) < 1) finished = 1;
3074 if (!strcmp(buf, terminator)) finished = 1;
3076 strcat(buf, "\r\n");
3082 /* Unescape SMTP-style input of two dots at the beginning of the line */
3084 if (!strncmp(buf, "..", 2)) {
3085 strcpy(buf, &buf[1]);
3089 if ( (!flushing) && (!finished) ) {
3090 /* Measure the line */
3091 linelen = strlen(buf);
3093 /* augment the buffer if we have to */
3094 if ((message_len + linelen) >= buffer_len) {
3095 ptr = realloc(m, (buffer_len * 2) );
3096 if (ptr == NULL) { /* flush if can't allocate */
3099 buffer_len = (buffer_len * 2);
3101 CtdlLogPrintf(CTDL_DEBUG, "buffer_len is now %ld\n", (long)buffer_len);
3105 /* Add the new line to the buffer. NOTE: this loop must avoid
3106 * using functions like strcat() and strlen() because they
3107 * traverse the entire buffer upon every call, and doing that
3108 * for a multi-megabyte message slows it down beyond usability.
3110 strcpy(&m[message_len], buf);
3111 message_len += linelen;
3114 /* if we've hit the max msg length, flush the rest */
3115 if (message_len >= maxlen) flushing = 1;
3117 } while (!finished);
3125 * Build a binary message to be saved on disk.
3126 * (NOTE: if you supply 'preformatted_text', the buffer you give it
3127 * will become part of the message. This means you are no longer
3128 * responsible for managing that memory -- it will be freed along with
3129 * the rest of the fields when CtdlFreeMessage() is called.)
3132 struct CtdlMessage *CtdlMakeMessage(
3133 struct ctdluser *author, /* author's user structure */
3134 char *recipient, /* NULL if it's not mail */
3135 char *recp_cc, /* NULL if it's not mail */
3136 char *room, /* room where it's going */
3137 int type, /* see MES_ types in header file */
3138 int format_type, /* variformat, plain text, MIME... */
3139 char *fake_name, /* who we're masquerading as */
3140 char *my_email, /* which of my email addresses to use (empty is ok) */
3141 char *subject, /* Subject (optional) */
3142 char *supplied_euid, /* ...or NULL if this is irrelevant */
3143 char *preformatted_text, /* ...or NULL to read text from client */
3144 char *references /* Thread references */
3146 char dest_node[256];
3148 struct CtdlMessage *msg;
3150 msg = malloc(sizeof(struct CtdlMessage));
3151 memset(msg, 0, sizeof(struct CtdlMessage));
3152 msg->cm_magic = CTDLMESSAGE_MAGIC;
3153 msg->cm_anon_type = type;
3154 msg->cm_format_type = format_type;
3156 /* Don't confuse the poor folks if it's not routed mail. */
3157 strcpy(dest_node, "");
3162 /* Path or Return-Path */
3163 if (my_email == NULL) my_email = "";
3165 if (!IsEmptyStr(my_email)) {
3166 msg->cm_fields['P'] = strdup(my_email);
3169 snprintf(buf, sizeof buf, "%s", author->fullname);
3170 msg->cm_fields['P'] = strdup(buf);
3172 convert_spaces_to_underscores(msg->cm_fields['P']);
3174 snprintf(buf, sizeof buf, "%ld", (long)time(NULL)); /* timestamp */
3175 msg->cm_fields['T'] = strdup(buf);
3177 if (fake_name[0]) /* author */
3178 msg->cm_fields['A'] = strdup(fake_name);
3180 msg->cm_fields['A'] = strdup(author->fullname);
3182 if (CC->room.QRflags & QR_MAILBOX) { /* room */
3183 msg->cm_fields['O'] = strdup(&CC->room.QRname[11]);
3186 msg->cm_fields['O'] = strdup(CC->room.QRname);
3189 msg->cm_fields['N'] = strdup(NODENAME); /* nodename */
3190 msg->cm_fields['H'] = strdup(HUMANNODE); /* hnodename */
3192 if (recipient[0] != 0) {
3193 msg->cm_fields['R'] = strdup(recipient);
3195 if (recp_cc[0] != 0) {
3196 msg->cm_fields['Y'] = strdup(recp_cc);
3198 if (dest_node[0] != 0) {
3199 msg->cm_fields['D'] = strdup(dest_node);
3202 if (!IsEmptyStr(my_email)) {
3203 msg->cm_fields['F'] = strdup(my_email);
3205 else if ( (author == &CC->user) && (!IsEmptyStr(CC->cs_inet_email)) ) {
3206 msg->cm_fields['F'] = strdup(CC->cs_inet_email);
3209 if (subject != NULL) {
3212 length = strlen(subject);
3218 while ((subject[i] != '\0') &&
3219 (IsAscii = isascii(subject[i]) != 0 ))
3222 msg->cm_fields['U'] = strdup(subject);
3223 else /* ok, we've got utf8 in the string. */
3225 msg->cm_fields['U'] = rfc2047encode(subject, length);
3231 if (supplied_euid != NULL) {
3232 msg->cm_fields['E'] = strdup(supplied_euid);
3235 if (references != NULL) {
3236 if (!IsEmptyStr(references)) {
3237 msg->cm_fields['W'] = strdup(references);
3241 if (preformatted_text != NULL) {
3242 msg->cm_fields['M'] = preformatted_text;
3245 msg->cm_fields['M'] = CtdlReadMessageBody("000", config.c_maxmsglen, NULL, 0, 0);
3253 * Check to see whether we have permission to post a message in the current
3254 * room. Returns a *CITADEL ERROR CODE* and puts a message in errmsgbuf, or
3255 * returns 0 on success.
3257 int CtdlDoIHavePermissionToPostInThisRoom(char *errmsgbuf,
3259 const char* RemoteIdentifier,
3263 if (!(CC->logged_in) &&
3264 (PostPublic == POST_LOGGED_IN)) {
3265 snprintf(errmsgbuf, n, "Not logged in.");
3266 return (ERROR + NOT_LOGGED_IN);
3268 else if (PostPublic == CHECK_EXISTANCE) {
3269 return (0); // We're Evaling whether a recipient exists
3271 else if (!(CC->logged_in)) {
3273 if ((CC->room.QRflags & QR_READONLY)) {
3274 snprintf(errmsgbuf, n, "Not logged in.");
3275 return (ERROR + NOT_LOGGED_IN);
3277 if (CC->room.QRflags2 & QR2_MODERATED) {
3278 snprintf(errmsgbuf, n, "Not logged in Moderation feature not yet implemented!");
3279 return (ERROR + NOT_LOGGED_IN);
3281 if ((PostPublic!=POST_LMTP) &&(CC->room.QRflags2 & QR2_SMTP_PUBLIC) == 0) {
3286 if (RemoteIdentifier == NULL)
3288 snprintf(errmsgbuf, n, "Need sender to permit access.");
3289 return (ERROR + USERNAME_REQUIRED);
3292 assoc_file_name(filename, sizeof filename, &CC->room, ctdl_netcfg_dir);
3293 begin_critical_section(S_NETCONFIGS);
3294 if (!read_spoolcontrol_file(&sc, filename))
3296 end_critical_section(S_NETCONFIGS);
3297 snprintf(errmsgbuf, n,
3298 "This mailing list only accepts posts from subscribers.");
3299 return (ERROR + NO_SUCH_USER);
3301 end_critical_section(S_NETCONFIGS);
3302 found = is_recipient (sc, RemoteIdentifier);
3303 free_spoolcontrol_struct(&sc);
3308 snprintf(errmsgbuf, n,
3309 "This mailing list only accepts posts from subscribers.");
3310 return (ERROR + NO_SUCH_USER);
3317 if ((CC->user.axlevel < 2)
3318 && ((CC->room.QRflags & QR_MAILBOX) == 0)) {
3319 snprintf(errmsgbuf, n, "Need to be validated to enter "
3320 "(except in %s> to sysop)", MAILROOM);
3321 return (ERROR + HIGHER_ACCESS_REQUIRED);
3324 CtdlRoomAccess(&CC->room, &CC->user, &ra, NULL);
3325 if (!(ra & UA_POSTALLOWED)) {
3326 snprintf(errmsgbuf, n, "Higher access is required to post in this room.");
3327 return (ERROR + HIGHER_ACCESS_REQUIRED);
3330 strcpy(errmsgbuf, "Ok");
3336 * Check to see if the specified user has Internet mail permission
3337 * (returns nonzero if permission is granted)
3339 int CtdlCheckInternetMailPermission(struct ctdluser *who) {
3341 /* Do not allow twits to send Internet mail */
3342 if (who->axlevel <= 2) return(0);
3344 /* Globally enabled? */
3345 if (config.c_restrict == 0) return(1);
3347 /* User flagged ok? */
3348 if (who->flags & US_INTERNET) return(2);
3350 /* Aide level access? */
3351 if (who->axlevel >= 6) return(3);
3353 /* No mail for you! */
3359 * Validate recipients, count delivery types and errors, and handle aliasing
3360 * FIXME check for dupes!!!!!
3362 * Returns 0 if all addresses are ok, ret->num_error = -1 if no addresses
3363 * were specified, or the number of addresses found invalid.
3365 * Caller needs to free the result using free_recipients()
3367 struct recptypes *validate_recipients(char *supplied_recipients,
3368 const char *RemoteIdentifier,
3370 struct recptypes *ret;
3371 char *recipients = NULL;
3372 char this_recp[256];
3373 char this_recp_cooked[256];
3379 struct ctdluser tempUS;
3380 struct ctdlroom tempQR;
3381 struct ctdlroom tempQR2;
3387 ret = (struct recptypes *) malloc(sizeof(struct recptypes));
3388 if (ret == NULL) return(NULL);
3390 /* Set all strings to null and numeric values to zero */
3391 memset(ret, 0, sizeof(struct recptypes));
3393 if (supplied_recipients == NULL) {
3394 recipients = strdup("");
3397 recipients = strdup(supplied_recipients);
3400 /* Allocate some memory. Yes, this allocates 500% more memory than we will
3401 * actually need, but it's healthier for the heap than doing lots of tiny
3402 * realloc() calls instead.
3405 ret->errormsg = malloc(strlen(recipients) + 1024);
3406 ret->recp_local = malloc(strlen(recipients) + 1024);
3407 ret->recp_internet = malloc(strlen(recipients) + 1024);
3408 ret->recp_ignet = malloc(strlen(recipients) + 1024);
3409 ret->recp_room = malloc(strlen(recipients) + 1024);
3410 ret->display_recp = malloc(strlen(recipients) + 1024);
3412 ret->errormsg[0] = 0;
3413 ret->recp_local[0] = 0;
3414 ret->recp_internet[0] = 0;
3415 ret->recp_ignet[0] = 0;
3416 ret->recp_room[0] = 0;
3417 ret->display_recp[0] = 0;
3419 ret->recptypes_magic = RECPTYPES_MAGIC;
3421 /* Change all valid separator characters to commas */
3422 for (i=0; !IsEmptyStr(&recipients[i]); ++i) {
3423 if ((recipients[i] == ';') || (recipients[i] == '|')) {
3424 recipients[i] = ',';
3428 /* Now start extracting recipients... */
3430 while (!IsEmptyStr(recipients)) {
3432 for (i=0; i<=strlen(recipients); ++i) {
3433 if (recipients[i] == '\"') in_quotes = 1 - in_quotes;
3434 if ( ( (recipients[i] == ',') && (!in_quotes) ) || (recipients[i] == 0) ) {
3435 safestrncpy(this_recp, recipients, i+1);
3437 if (recipients[i] == ',') {
3438 strcpy(recipients, &recipients[i+1]);
3441 strcpy(recipients, "");
3448 if (IsEmptyStr(this_recp))
3450 CtdlLogPrintf(CTDL_DEBUG, "Evaluating recipient #%d: %s\n", num_recps, this_recp);
3452 mailtype = alias(this_recp);
3453 mailtype = alias(this_recp);
3454 mailtype = alias(this_recp);
3456 for (j=0; !IsEmptyStr(&this_recp[j]); ++j) {
3457 if (this_recp[j]=='_') {
3458 this_recp_cooked[j] = ' ';
3461 this_recp_cooked[j] = this_recp[j];
3464 this_recp_cooked[j] = '\0';
3469 if (!strcasecmp(this_recp, "sysop")) {
3471 strcpy(this_recp, config.c_aideroom);
3472 if (!IsEmptyStr(ret->recp_room)) {
3473 strcat(ret->recp_room, "|");
3475 strcat(ret->recp_room, this_recp);
3477 else if ( (!strncasecmp(this_recp, "room_", 5))
3478 && (!getroom(&tempQR, &this_recp_cooked[5])) ) {
3480 /* Save room so we can restore it later */
3484 /* Check permissions to send mail to this room */
3485 err = CtdlDoIHavePermissionToPostInThisRoom(errmsg,
3497 if (!IsEmptyStr(ret->recp_room)) {
3498 strcat(ret->recp_room, "|");
3500 strcat(ret->recp_room, &this_recp_cooked[5]);
3503 /* Restore room in case something needs it */
3507 else if (getuser(&tempUS, this_recp) == 0) {
3509 strcpy(this_recp, tempUS.fullname);
3510 if (!IsEmptyStr(ret->recp_local)) {
3511 strcat(ret->recp_local, "|");
3513 strcat(ret->recp_local, this_recp);
3515 else if (getuser(&tempUS, this_recp_cooked) == 0) {
3517 strcpy(this_recp, tempUS.fullname);
3518 if (!IsEmptyStr(ret->recp_local)) {
3519 strcat(ret->recp_local, "|");
3521 strcat(ret->recp_local, this_recp);
3529 /* Yes, you're reading this correctly: if the target
3530 * domain points back to the local system or an attached
3531 * Citadel directory, the address is invalid. That's
3532 * because if the address were valid, we would have
3533 * already translated it to a local address by now.
3535 if (IsDirectory(this_recp, 0)) {
3540 ++ret->num_internet;
3541 if (!IsEmptyStr(ret->recp_internet)) {
3542 strcat(ret->recp_internet, "|");
3544 strcat(ret->recp_internet, this_recp);
3549 if (!IsEmptyStr(ret->recp_ignet)) {
3550 strcat(ret->recp_ignet, "|");
3552 strcat(ret->recp_ignet, this_recp);
3560 if (IsEmptyStr(errmsg)) {
3561 snprintf(append, sizeof append, "Invalid recipient: %s", this_recp);
3564 snprintf(append, sizeof append, "%s", errmsg);
3566 if ( (strlen(ret->errormsg) + strlen(append) + 3) < SIZ) {
3567 if (!IsEmptyStr(ret->errormsg)) {
3568 strcat(ret->errormsg, "; ");
3570 strcat(ret->errormsg, append);
3574 if (IsEmptyStr(ret->display_recp)) {
3575 strcpy(append, this_recp);
3578 snprintf(append, sizeof append, ", %s", this_recp);
3580 if ( (strlen(ret->display_recp)+strlen(append)) < SIZ) {
3581 strcat(ret->display_recp, append);
3586 if ((ret->num_local + ret->num_internet + ret->num_ignet +
3587 ret->num_room + ret->num_error) == 0) {
3588 ret->num_error = (-1);
3589 strcpy(ret->errormsg, "No recipients specified.");
3592 CtdlLogPrintf(CTDL_DEBUG, "validate_recipients()\n");
3593 CtdlLogPrintf(CTDL_DEBUG, " local: %d <%s>\n", ret->num_local, ret->recp_local);
3594 CtdlLogPrintf(CTDL_DEBUG, " room: %d <%s>\n", ret->num_room, ret->recp_room);
3595 CtdlLogPrintf(CTDL_DEBUG, " inet: %d <%s>\n", ret->num_internet, ret->recp_internet);
3596 CtdlLogPrintf(CTDL_DEBUG, " ignet: %d <%s>\n", ret->num_ignet, ret->recp_ignet);
3597 CtdlLogPrintf(CTDL_DEBUG, " error: %d <%s>\n", ret->num_error, ret->errormsg);
3605 * Destructor for struct recptypes
3607 void free_recipients(struct recptypes *valid) {
3609 if (valid == NULL) {
3613 if (valid->recptypes_magic != RECPTYPES_MAGIC) {
3614 CtdlLogPrintf(CTDL_EMERG, "Attempt to call free_recipients() on some other data type!\n");
3618 if (valid->errormsg != NULL) free(valid->errormsg);
3619 if (valid->recp_local != NULL) free(valid->recp_local);
3620 if (valid->recp_internet != NULL) free(valid->recp_internet);
3621 if (valid->recp_ignet != NULL) free(valid->recp_ignet);
3622 if (valid->recp_room != NULL) free(valid->recp_room);
3623 if (valid->display_recp != NULL) free(valid->display_recp);
3630 * message entry - mode 0 (normal)
3632 void cmd_ent0(char *entargs)
3638 char supplied_euid[128];
3640 int format_type = 0;
3641 char newusername[256];
3642 char newuseremail[256];
3643 struct CtdlMessage *msg;
3647 struct recptypes *valid = NULL;
3648 struct recptypes *valid_to = NULL;
3649 struct recptypes *valid_cc = NULL;
3650 struct recptypes *valid_bcc = NULL;
3652 int subject_required = 0;
3657 int newuseremail_ok = 0;
3658 char references[SIZ];
3663 post = extract_int(entargs, 0);
3664 extract_token(recp, entargs, 1, '|', sizeof recp);
3665 anon_flag = extract_int(entargs, 2);
3666 format_type = extract_int(entargs, 3);
3667 extract_token(subject, entargs, 4, '|', sizeof subject);
3668 extract_token(newusername, entargs, 5, '|', sizeof newusername);
3669 do_confirm = extract_int(entargs, 6);
3670 extract_token(cc, entargs, 7, '|', sizeof cc);
3671 extract_token(bcc, entargs, 8, '|', sizeof bcc);
3672 switch(CC->room.QRdefaultview) {
3675 extract_token(supplied_euid, entargs, 9, '|', sizeof supplied_euid);
3678 supplied_euid[0] = 0;
3681 extract_token(newuseremail, entargs, 10, '|', sizeof newuseremail);
3682 extract_token(references, entargs, 11, '|', sizeof references);
3683 for (ptr=references; *ptr != 0; ++ptr) {
3684 if (*ptr == '!') *ptr = '|';
3687 /* first check to make sure the request is valid. */
3689 err = CtdlDoIHavePermissionToPostInThisRoom(errmsg, sizeof errmsg, NULL, POST_LOGGED_IN);
3692 cprintf("%d %s\n", err, errmsg);
3696 /* Check some other permission type things. */
3698 if (IsEmptyStr(newusername)) {
3699 strcpy(newusername, CC->user.fullname);
3701 if ( (CC->user.axlevel < 6)
3702 && (strcasecmp(newusername, CC->user.fullname))
3703 && (strcasecmp(newusername, CC->cs_inet_fn))
3705 cprintf("%d You don't have permission to author messages as '%s'.\n",
3706 ERROR + HIGHER_ACCESS_REQUIRED,
3713 if (IsEmptyStr(newuseremail)) {
3714 newuseremail_ok = 1;
3717 if (!IsEmptyStr(newuseremail)) {
3718 if (!strcasecmp(newuseremail, CC->cs_inet_email)) {
3719 newuseremail_ok = 1;
3721 else if (!IsEmptyStr(CC->cs_inet_other_emails)) {
3722 j = num_tokens(CC->cs_inet_other_emails, '|');
3723 for (i=0; i<j; ++i) {
3724 extract_token(buf, CC->cs_inet_other_emails, i, '|', sizeof buf);
3725 if (!strcasecmp(newuseremail, buf)) {
3726 newuseremail_ok = 1;
3732 if (!newuseremail_ok) {
3733 cprintf("%d You don't have permission to author messages as '%s'.\n",
3734 ERROR + HIGHER_ACCESS_REQUIRED,
3740 CC->cs_flags |= CS_POSTING;
3742 /* In mailbox rooms we have to behave a little differently --
3743 * make sure the user has specified at least one recipient. Then
3744 * validate the recipient(s). We do this for the Mail> room, as
3745 * well as any room which has the "Mailbox" view set.
3748 if ( ( (CC->room.QRflags & QR_MAILBOX) && (!strcasecmp(&CC->room.QRname[11], MAILROOM)) )
3749 || ( (CC->room.QRflags & QR_MAILBOX) && (CC->curr_view == VIEW_MAILBOX) )
3751 if (CC->user.axlevel < 2) {
3752 strcpy(recp, "sysop");
3757 valid_to = validate_recipients(recp, NULL, 0);
3758 if (valid_to->num_error > 0) {
3759 cprintf("%d %s\n", ERROR + NO_SUCH_USER, valid_to->errormsg);
3760 free_recipients(valid_to);
3764 valid_cc = validate_recipients(cc, NULL, 0);
3765 if (valid_cc->num_error > 0) {
3766 cprintf("%d %s\n", ERROR + NO_SUCH_USER, valid_cc->errormsg);
3767 free_recipients(valid_to);
3768 free_recipients(valid_cc);
3772 valid_bcc = validate_recipients(bcc, NULL, 0);
3773 if (valid_bcc->num_error > 0) {
3774 cprintf("%d %s\n", ERROR + NO_SUCH_USER, valid_bcc->errormsg);
3775 free_recipients(valid_to);
3776 free_recipients(valid_cc);
3777 free_recipients(valid_bcc);
3781 /* Recipient required, but none were specified */
3782 if ( (valid_to->num_error < 0) && (valid_cc->num_error < 0) && (valid_bcc->num_error < 0) ) {
3783 free_recipients(valid_to);
3784 free_recipients(valid_cc);
3785 free_recipients(valid_bcc);
3786 cprintf("%d At least one recipient is required.\n", ERROR + NO_SUCH_USER);
3790 if (valid_to->num_internet + valid_cc->num_internet + valid_bcc->num_internet > 0) {
3791 if (CtdlCheckInternetMailPermission(&CC->user)==0) {
3792 cprintf("%d You do not have permission "
3793 "to send Internet mail.\n",
3794 ERROR + HIGHER_ACCESS_REQUIRED);
3795 free_recipients(valid_to);
3796 free_recipients(valid_cc);
3797 free_recipients(valid_bcc);
3802 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)
3803 && (CC->user.axlevel < 4) ) {
3804 cprintf("%d Higher access required for network mail.\n",
3805 ERROR + HIGHER_ACCESS_REQUIRED);
3806 free_recipients(valid_to);
3807 free_recipients(valid_cc);
3808 free_recipients(valid_bcc);
3812 if ((RESTRICT_INTERNET == 1)
3813 && (valid_to->num_internet + valid_cc->num_internet + valid_bcc->num_internet > 0)
3814 && ((CC->user.flags & US_INTERNET) == 0)
3815 && (!CC->internal_pgm)) {
3816 cprintf("%d You don't have access to Internet mail.\n",
3817 ERROR + HIGHER_ACCESS_REQUIRED);
3818 free_recipients(valid_to);
3819 free_recipients(valid_cc);
3820 free_recipients(valid_bcc);
3826 /* Is this a room which has anonymous-only or anonymous-option? */
3827 anonymous = MES_NORMAL;
3828 if (CC->room.QRflags & QR_ANONONLY) {
3829 anonymous = MES_ANONONLY;
3831 if (CC->room.QRflags & QR_ANONOPT) {
3832 if (anon_flag == 1) { /* only if the user requested it */
3833 anonymous = MES_ANONOPT;
3837 if ((CC->room.QRflags & QR_MAILBOX) == 0) {
3841 /* Recommend to the client that the use of a message subject is
3842 * strongly recommended in this room, if either the SUBJECTREQ flag
3843 * is set, or if there is one or more Internet email recipients.
3845 if (CC->room.QRflags2 & QR2_SUBJECTREQ) subject_required = 1;
3846 if (valid_to) if (valid_to->num_internet > 0) subject_required = 1;
3847 if (valid_cc) if (valid_cc->num_internet > 0) subject_required = 1;
3848 if (valid_bcc) if (valid_bcc->num_internet > 0) subject_required = 1;
3850 /* If we're only checking the validity of the request, return
3851 * success without creating the message.
3854 cprintf("%d %s|%d\n", CIT_OK,
3855 ((valid_to != NULL) ? valid_to->display_recp : ""),
3857 free_recipients(valid_to);
3858 free_recipients(valid_cc);
3859 free_recipients(valid_bcc);
3863 /* We don't need these anymore because we'll do it differently below */
3864 free_recipients(valid_to);
3865 free_recipients(valid_cc);
3866 free_recipients(valid_bcc);
3868 /* Read in the message from the client. */
3870 cprintf("%d send message\n", START_CHAT_MODE);
3872 cprintf("%d send message\n", SEND_LISTING);
3875 msg = CtdlMakeMessage(&CC->user, recp, cc,
3876 CC->room.QRname, anonymous, format_type,
3877 newusername, newuseremail, subject,
3878 ((!IsEmptyStr(supplied_euid)) ? supplied_euid : NULL),
3881 /* Put together one big recipients struct containing to/cc/bcc all in
3882 * one. This is for the envelope.
3884 char *all_recps = malloc(SIZ * 3);
3885 strcpy(all_recps, recp);
3886 if (!IsEmptyStr(cc)) {
3887 if (!IsEmptyStr(all_recps)) {
3888 strcat(all_recps, ",");
3890 strcat(all_recps, cc);
3892 if (!IsEmptyStr(bcc)) {
3893 if (!IsEmptyStr(all_recps)) {
3894 strcat(all_recps, ",");
3896 strcat(all_recps, bcc);
3898 if (!IsEmptyStr(all_recps)) {
3899 valid = validate_recipients(all_recps, NULL, 0);
3907 msgnum = CtdlSubmitMsg(msg, valid, "", QP_EADDR);
3910 cprintf("%ld\n", msgnum);
3912 cprintf("Message accepted.\n");
3915 cprintf("Internal error.\n");
3917 if (msg->cm_fields['E'] != NULL) {
3918 cprintf("%s\n", msg->cm_fields['E']);
3925 CtdlFreeMessage(msg);
3927 if (valid != NULL) {
3928 free_recipients(valid);
3936 * API function to delete messages which match a set of criteria
3937 * (returns the actual number of messages deleted)
3939 int CtdlDeleteMessages(char *room_name, /* which room */
3940 long *dmsgnums, /* array of msg numbers to be deleted */
3941 int num_dmsgnums, /* number of msgs to be deleted, or 0 for "any" */
3942 char *content_type /* or "" for any. regular expressions expected. */
3945 struct ctdlroom qrbuf;
3946 struct cdbdata *cdbfr;
3947 long *msglist = NULL;
3948 long *dellist = NULL;
3951 int num_deleted = 0;
3953 struct MetaData smi;
3956 int need_to_free_re = 0;
3958 if (content_type) if (!IsEmptyStr(content_type)) {
3959 regcomp(&re, content_type, 0);
3960 need_to_free_re = 1;
3962 CtdlLogPrintf(CTDL_DEBUG, "CtdlDeleteMessages(%s, %d msgs, %s)\n",
3963 room_name, num_dmsgnums, content_type);
3965 /* get room record, obtaining a lock... */
3966 if (lgetroom(&qrbuf, room_name) != 0) {
3967 CtdlLogPrintf(CTDL_ERR, "CtdlDeleteMessages(): Room <%s> not found\n",
3969 if (need_to_free_re) regfree(&re);
3970 return (0); /* room not found */
3972 cdbfr = cdb_fetch(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long));
3974 if (cdbfr != NULL) {
3975 dellist = malloc(cdbfr->len);
3976 msglist = (long *) cdbfr->ptr;
3977 cdbfr->ptr = NULL; /* CtdlDeleteMessages() now owns this memory */
3978 num_msgs = cdbfr->len / sizeof(long);
3982 for (i = 0; i < num_msgs; ++i) {
3985 /* Set/clear a bit for each criterion */
3987 /* 0 messages in the list or a null list means that we are
3988 * interested in deleting any messages which meet the other criteria.
3990 if ((num_dmsgnums == 0) || (dmsgnums == NULL)) {
3991 delete_this |= 0x01;
3994 for (j=0; j<num_dmsgnums; ++j) {
3995 if (msglist[i] == dmsgnums[j]) {
3996 delete_this |= 0x01;
4001 if (IsEmptyStr(content_type)) {
4002 delete_this |= 0x02;
4004 GetMetaData(&smi, msglist[i]);
4005 if (regexec(&re, smi.meta_content_type, 1, &pm, 0) == 0) {
4006 delete_this |= 0x02;
4010 /* Delete message only if all bits are set */
4011 if (delete_this == 0x03) {
4012 dellist[num_deleted++] = msglist[i];
4017 num_msgs = sort_msglist(msglist, num_msgs);
4018 cdb_store(CDB_MSGLISTS, &qrbuf.QRnumber, (int)sizeof(long),
4019 msglist, (int)(num_msgs * sizeof(long)));
4021 qrbuf.QRhighest = msglist[num_msgs - 1];
4025 /* Go through the messages we pulled out of the index, and decrement
4026 * their reference counts by 1. If this is the only room the message
4027 * was in, the reference count will reach zero and the message will
4028 * automatically be deleted from the database. We do this in a
4029 * separate pass because there might be plug-in hooks getting called,
4030 * and we don't want that happening during an S_ROOMS critical
4033 if (num_deleted) for (i=0; i<num_deleted; ++i) {
4034 PerformDeleteHooks(qrbuf.QRname, dellist[i]);
4035 AdjRefCount(dellist[i], -1);
4038 /* Now free the memory we used, and go away. */
4039 if (msglist != NULL) free(msglist);
4040 if (dellist != NULL) free(dellist);
4041 CtdlLogPrintf(CTDL_DEBUG, "%d message(s) deleted.\n", num_deleted);
4042 if (need_to_free_re) regfree(&re);
4043 return (num_deleted);
4049 * Check whether the current user has permission to delete messages from
4050 * the current room (returns 1 for yes, 0 for no)
4052 int CtdlDoIHavePermissionToDeleteMessagesFromThisRoom(void) {
4054 CtdlRoomAccess(&CC->room, &CC->user, &ra, NULL);
4055 if (ra & UA_DELETEALLOWED) return(1);
4063 * Delete message from current room
4065 void cmd_dele(char *args)
4074 extract_token(msgset, args, 0, '|', sizeof msgset);
4075 num_msgs = num_tokens(msgset, ',');
4077 cprintf("%d Nothing to do.\n", CIT_OK);
4081 if (CtdlDoIHavePermissionToDeleteMessagesFromThisRoom() == 0) {
4082 cprintf("%d Higher access required.\n",
4083 ERROR + HIGHER_ACCESS_REQUIRED);
4088 * Build our message set to be moved/copied
4090 msgs = malloc(num_msgs * sizeof(long));
4091 for (i=0; i<num_msgs; ++i) {
4092 extract_token(msgtok, msgset, i, ',', sizeof msgtok);
4093 msgs[i] = atol(msgtok);
4096 num_deleted = CtdlDeleteMessages(CC->room.QRname, msgs, num_msgs, "");
4100 cprintf("%d %d message%s deleted.\n", CIT_OK,
4101 num_deleted, ((num_deleted != 1) ? "s" : ""));
4103 cprintf("%d Message not found.\n", ERROR + MESSAGE_NOT_FOUND);
4109 * Back end API function for moves and deletes (multiple messages)
4111 int CtdlCopyMsgsToRoom(long *msgnums, int num_msgs, char *dest) {
4114 err = CtdlSaveMsgPointersInRoom(dest, msgnums, num_msgs, 1, NULL);
4115 if (err != 0) return(err);
4124 * move or copy a message to another room
4126 void cmd_move(char *args)
4133 char targ[ROOMNAMELEN];
4134 struct ctdlroom qtemp;
4141 extract_token(msgset, args, 0, '|', sizeof msgset);
4142 num_msgs = num_tokens(msgset, ',');
4144 cprintf("%d Nothing to do.\n", CIT_OK);
4148 extract_token(targ, args, 1, '|', sizeof targ);
4149 convert_room_name_macros(targ, sizeof targ);
4150 targ[ROOMNAMELEN - 1] = 0;
4151 is_copy = extract_int(args, 2);
4153 if (getroom(&qtemp, targ) != 0) {
4154 cprintf("%d '%s' does not exist.\n",
4155 ERROR + ROOM_NOT_FOUND, targ);
4159 getuser(&CC->user, CC->curr_user);
4160 CtdlRoomAccess(&qtemp, &CC->user, &ra, NULL);
4162 /* Check for permission to perform this operation.
4163 * Remember: "CC->room" is source, "qtemp" is target.
4167 /* Aides can move/copy */
4168 if (CC->user.axlevel >= 6) permit = 1;
4170 /* Room aides can move/copy */
4171 if (CC->user.usernum == CC->room.QRroomaide) permit = 1;
4173 /* Permit move/copy from personal rooms */
4174 if ((CC->room.QRflags & QR_MAILBOX)
4175 && (qtemp.QRflags & QR_MAILBOX)) permit = 1;
4177 /* Permit only copy from public to personal room */
4179 && (!(CC->room.QRflags & QR_MAILBOX))
4180 && (qtemp.QRflags & QR_MAILBOX)) permit = 1;
4182 /* Permit message removal from collaborative delete rooms */
4183 if (CC->room.QRflags2 & QR2_COLLABDEL) permit = 1;
4185 /* Users allowed to post into the target room may move into it too. */
4186 if ((CC->room.QRflags & QR_MAILBOX) &&
4187 (qtemp.QRflags & UA_POSTALLOWED)) permit = 1;
4189 /* User must have access to target room */
4190 if (!(ra & UA_KNOWN)) permit = 0;
4193 cprintf("%d Higher access required.\n",
4194 ERROR + HIGHER_ACCESS_REQUIRED);
4199 * Build our message set to be moved/copied
4201 msgs = malloc(num_msgs * sizeof(long));
4202 for (i=0; i<num_msgs; ++i) {
4203 extract_token(msgtok, msgset, i, ',', sizeof msgtok);
4204 msgs[i] = atol(msgtok);
4210 err = CtdlCopyMsgsToRoom(msgs, num_msgs, targ);
4212 cprintf("%d Cannot store message(s) in %s: error %d\n",
4218 /* Now delete the message from the source room,
4219 * if this is a 'move' rather than a 'copy' operation.
4222 CtdlDeleteMessages(CC->room.QRname, msgs, num_msgs, "");
4226 cprintf("%d Message(s) %s.\n", CIT_OK, (is_copy ? "copied" : "moved") );
4232 * GetMetaData() - Get the supplementary record for a message
4234 void GetMetaData(struct MetaData *smibuf, long msgnum)
4237 struct cdbdata *cdbsmi;
4240 memset(smibuf, 0, sizeof(struct MetaData));
4241 smibuf->meta_msgnum = msgnum;
4242 smibuf->meta_refcount = 1; /* Default reference count is 1 */
4244 /* Use the negative of the message number for its supp record index */
4245 TheIndex = (0L - msgnum);
4247 cdbsmi = cdb_fetch(CDB_MSGMAIN, &TheIndex, sizeof(long));
4248 if (cdbsmi == NULL) {
4249 return; /* record not found; go with defaults */
4251 memcpy(smibuf, cdbsmi->ptr,
4252 ((cdbsmi->len > sizeof(struct MetaData)) ?
4253 sizeof(struct MetaData) : cdbsmi->len));
4260 * PutMetaData() - (re)write supplementary record for a message
4262 void PutMetaData(struct MetaData *smibuf)
4266 /* Use the negative of the message number for the metadata db index */
4267 TheIndex = (0L - smibuf->meta_msgnum);
4269 cdb_store(CDB_MSGMAIN,
4270 &TheIndex, (int)sizeof(long),
4271 smibuf, (int)sizeof(struct MetaData));
4276 * AdjRefCount - submit an adjustment to the reference count for a message.
4277 * (These are just queued -- we actually process them later.)
4279 void AdjRefCount(long msgnum, int incr)
4281 struct arcq new_arcq;
4283 begin_critical_section(S_SUPPMSGMAIN);
4284 if (arcfp == NULL) {
4285 arcfp = fopen(file_arcq, "ab+");
4287 end_critical_section(S_SUPPMSGMAIN);
4289 /* msgnum < 0 means that we're trying to close the file */
4291 CtdlLogPrintf(CTDL_DEBUG, "Closing the AdjRefCount queue file\n");
4292 begin_critical_section(S_SUPPMSGMAIN);
4293 if (arcfp != NULL) {
4297 end_critical_section(S_SUPPMSGMAIN);
4302 * If we can't open the queue, perform the operation synchronously.
4304 if (arcfp == NULL) {
4305 TDAP_AdjRefCount(msgnum, incr);
4309 new_arcq.arcq_msgnum = msgnum;
4310 new_arcq.arcq_delta = incr;
4311 fwrite(&new_arcq, sizeof(struct arcq), 1, arcfp);
4319 * TDAP_ProcessAdjRefCountQueue()
4321 * Process the queue of message count adjustments that was created by calls
4322 * to AdjRefCount() ... by reading the queue and calling TDAP_AdjRefCount()
4323 * for each one. This should be an "off hours" operation.
4325 int TDAP_ProcessAdjRefCountQueue(void)
4327 char file_arcq_temp[PATH_MAX];
4330 struct arcq arcq_rec;
4331 int num_records_processed = 0;
4333 snprintf(file_arcq_temp, sizeof file_arcq_temp, "%s2", file_arcq);
4335 begin_critical_section(S_SUPPMSGMAIN);
4336 if (arcfp != NULL) {
4341 r = link(file_arcq, file_arcq_temp);
4343 CtdlLogPrintf(CTDL_CRIT, "%s: %s\n", file_arcq_temp, strerror(errno));
4344 end_critical_section(S_SUPPMSGMAIN);
4345 return(num_records_processed);
4349 end_critical_section(S_SUPPMSGMAIN);
4351 fp = fopen(file_arcq_temp, "rb");
4353 CtdlLogPrintf(CTDL_CRIT, "%s: %s\n", file_arcq_temp, strerror(errno));
4354 return(num_records_processed);
4357 while (fread(&arcq_rec, sizeof(struct arcq), 1, fp) == 1) {
4358 TDAP_AdjRefCount(arcq_rec.arcq_msgnum, arcq_rec.arcq_delta);
4359 ++num_records_processed;
4363 r = unlink(file_arcq_temp);
4365 CtdlLogPrintf(CTDL_CRIT, "%s: %s\n", file_arcq_temp, strerror(errno));
4368 return(num_records_processed);
4374 * TDAP_AdjRefCount - adjust the reference count for a message.
4375 * This one does it "for real" because it's called by
4376 * the autopurger function that processes the queue
4377 * created by AdjRefCount(). If a message's reference
4378 * count becomes zero, we also delete the message from
4379 * disk and de-index it.
4381 void TDAP_AdjRefCount(long msgnum, int incr)
4384 struct MetaData smi;
4387 /* This is a *tight* critical section; please keep it that way, as
4388 * it may get called while nested in other critical sections.
4389 * Complicating this any further will surely cause deadlock!
4391 begin_critical_section(S_SUPPMSGMAIN);
4392 GetMetaData(&smi, msgnum);
4393 smi.meta_refcount += incr;
4395 end_critical_section(S_SUPPMSGMAIN);
4396 CtdlLogPrintf(CTDL_DEBUG, "msg %ld ref count delta %d, is now %d\n",
4397 msgnum, incr, smi.meta_refcount);
4399 /* If the reference count is now zero, delete the message
4400 * (and its supplementary record as well).
4402 if (smi.meta_refcount == 0) {
4403 CtdlLogPrintf(CTDL_DEBUG, "Deleting message <%ld>\n", msgnum);
4405 /* Call delete hooks with NULL room to show it has gone altogether */
4406 PerformDeleteHooks(NULL, msgnum);
4408 /* Remove from message base */
4410 cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long));
4411 cdb_delete(CDB_BIGMSGS, &delnum, (int)sizeof(long));
4413 /* Remove metadata record */
4414 delnum = (0L - msgnum);
4415 cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long));
4421 * Write a generic object to this room
4423 * Note: this could be much more efficient. Right now we use two temporary
4424 * files, and still pull the message into memory as with all others.
4426 void CtdlWriteObject(char *req_room, /* Room to stuff it in */
4427 char *content_type, /* MIME type of this object */
4428 char *tempfilename, /* Where to fetch it from */
4429 struct ctdluser *is_mailbox, /* Mailbox room? */
4430 int is_binary, /* Is encoding necessary? */
4431 int is_unique, /* Del others of this type? */
4432 unsigned int flags /* Internal save flags */
4437 struct ctdlroom qrbuf;
4438 char roomname[ROOMNAMELEN];
4439 struct CtdlMessage *msg;
4441 char *raw_message = NULL;
4442 char *encoded_message = NULL;
4443 off_t raw_length = 0;
4445 if (is_mailbox != NULL) {
4446 MailboxName(roomname, sizeof roomname, is_mailbox, req_room);
4449 safestrncpy(roomname, req_room, sizeof(roomname));
4452 fp = fopen(tempfilename, "rb");
4454 CtdlLogPrintf(CTDL_CRIT, "Cannot open %s: %s\n",
4455 tempfilename, strerror(errno));
4458 fseek(fp, 0L, SEEK_END);
4459 raw_length = ftell(fp);
4461 CtdlLogPrintf(CTDL_DEBUG, "Raw length is %ld\n", (long)raw_length);
4463 raw_message = malloc((size_t)raw_length + 2);
4464 fread(raw_message, (size_t)raw_length, 1, fp);
4468 encoded_message = malloc((size_t)
4469 (((raw_length * 134) / 100) + 4096 ) );
4472 encoded_message = malloc((size_t)(raw_length + 4096));
4475 sprintf(encoded_message, "Content-type: %s\n", content_type);
4478 sprintf(&encoded_message[strlen(encoded_message)],
4479 "Content-transfer-encoding: base64\n\n"
4483 sprintf(&encoded_message[strlen(encoded_message)],
4484 "Content-transfer-encoding: 7bit\n\n"
4490 &encoded_message[strlen(encoded_message)],
4497 raw_message[raw_length] = 0;
4499 &encoded_message[strlen(encoded_message)],
4507 CtdlLogPrintf(CTDL_DEBUG, "Allocating\n");
4508 msg = malloc(sizeof(struct CtdlMessage));
4509 memset(msg, 0, sizeof(struct CtdlMessage));
4510 msg->cm_magic = CTDLMESSAGE_MAGIC;
4511 msg->cm_anon_type = MES_NORMAL;
4512 msg->cm_format_type = 4;
4513 msg->cm_fields['A'] = strdup(CC->user.fullname);
4514 msg->cm_fields['O'] = strdup(req_room);
4515 msg->cm_fields['N'] = strdup(config.c_nodename);
4516 msg->cm_fields['H'] = strdup(config.c_humannode);
4517 msg->cm_flags = flags;
4519 msg->cm_fields['M'] = encoded_message;
4521 /* Create the requested room if we have to. */
4522 if (getroom(&qrbuf, roomname) != 0) {
4523 create_room(roomname,
4524 ( (is_mailbox != NULL) ? 5 : 3 ),
4525 "", 0, 1, 0, VIEW_BBS);
4527 /* If the caller specified this object as unique, delete all
4528 * other objects of this type that are currently in the room.
4531 CtdlLogPrintf(CTDL_DEBUG, "Deleted %d other msgs of this type\n",
4532 CtdlDeleteMessages(roomname, NULL, 0, content_type)
4535 /* Now write the data */
4536 CtdlSubmitMsg(msg, NULL, roomname, 0);
4537 CtdlFreeMessage(msg);
4545 void CtdlGetSysConfigBackend(long msgnum, void *userdata) {
4546 config_msgnum = msgnum;
4550 char *CtdlGetSysConfig(char *sysconfname) {
4551 char hold_rm[ROOMNAMELEN];
4554 struct CtdlMessage *msg;
4557 strcpy(hold_rm, CC->room.QRname);
4558 if (getroom(&CC->room, SYSCONFIGROOM) != 0) {
4559 getroom(&CC->room, hold_rm);
4564 /* We want the last (and probably only) config in this room */
4565 begin_critical_section(S_CONFIG);
4566 config_msgnum = (-1L);
4567 CtdlForEachMessage(MSGS_LAST, 1, NULL, sysconfname, NULL,
4568 CtdlGetSysConfigBackend, NULL);
4569 msgnum = config_msgnum;
4570 end_critical_section(S_CONFIG);
4576 msg = CtdlFetchMessage(msgnum, 1);
4578 conf = strdup(msg->cm_fields['M']);
4579 CtdlFreeMessage(msg);
4586 getroom(&CC->room, hold_rm);
4588 if (conf != NULL) do {
4589 extract_token(buf, conf, 0, '\n', sizeof buf);
4590 strcpy(conf, &conf[strlen(buf)+1]);
4591 } while ( (!IsEmptyStr(conf)) && (!IsEmptyStr(buf)) );
4596 void CtdlPutSysConfig(char *sysconfname, char *sysconfdata) {
4597 char temp[PATH_MAX];
4600 CtdlMakeTempFileName(temp, sizeof temp);
4602 fp = fopen(temp, "w");
4603 if (fp == NULL) return;
4604 fprintf(fp, "%s", sysconfdata);
4607 /* this handy API function does all the work for us */
4608 CtdlWriteObject(SYSCONFIGROOM, sysconfname, temp, NULL, 0, 1, 0);
4614 * Determine whether a given Internet address belongs to the current user
4616 int CtdlIsMe(char *addr, int addr_buf_len)
4618 struct recptypes *recp;
4621 recp = validate_recipients(addr, NULL, 0);
4622 if (recp == NULL) return(0);
4624 if (recp->num_local == 0) {
4625 free_recipients(recp);
4629 for (i=0; i<recp->num_local; ++i) {
4630 extract_token(addr, recp->recp_local, i, '|', addr_buf_len);
4631 if (!strcasecmp(addr, CC->user.fullname)) {
4632 free_recipients(recp);
4637 free_recipients(recp);
4643 * Citadel protocol command to do the same
4645 void cmd_isme(char *argbuf) {
4648 if (CtdlAccessCheck(ac_logged_in)) return;
4649 extract_token(addr, argbuf, 0, '|', sizeof addr);
4651 if (CtdlIsMe(addr, sizeof addr)) {
4652 cprintf("%d %s\n", CIT_OK, addr);
4655 cprintf("%d Not you.\n", ERROR + ILLEGAL_VALUE);