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 = 0; 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 = 0; 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");
1616 return(om_no_such_msg);
1619 /* Are we downloading a MIME component? */
1620 if (mode == MT_DOWNLOAD) {
1621 if (TheMessage->cm_format_type != FMT_RFC822) {
1623 cprintf("%d This is not a MIME message.\n",
1624 ERROR + ILLEGAL_VALUE);
1625 } else if (CC->download_fp != NULL) {
1626 if (do_proto) cprintf(
1627 "%d You already have a download open.\n",
1628 ERROR + RESOURCE_BUSY);
1630 /* Parse the message text component */
1631 mptr = TheMessage->cm_fields['M'];
1632 mime_parser(mptr, NULL, *mime_download, NULL, NULL, NULL, 0);
1633 /* If there's no file open by this time, the requested
1634 * section wasn't found, so print an error
1636 if (CC->download_fp == NULL) {
1637 if (do_proto) cprintf(
1638 "%d Section %s not found.\n",
1639 ERROR + FILE_NOT_FOUND,
1640 CC->download_desired_section);
1643 return((CC->download_fp != NULL) ? om_ok : om_mime_error);
1646 /* MT_SPEW_SECTION is like MT_DOWNLOAD except it outputs the whole MIME part
1647 * in a single server operation instead of opening a download file.
1649 if (mode == MT_SPEW_SECTION) {
1650 if (TheMessage->cm_format_type != FMT_RFC822) {
1652 cprintf("%d This is not a MIME message.\n",
1653 ERROR + ILLEGAL_VALUE);
1655 /* Parse the message text component */
1658 mptr = TheMessage->cm_fields['M'];
1659 mime_parser(mptr, NULL, *mime_spew_section, NULL, NULL, (void *)&found_it, 0);
1660 /* If section wasn't found, print an error
1663 if (do_proto) cprintf(
1664 "%d Section %s not found.\n",
1665 ERROR + FILE_NOT_FOUND,
1666 CC->download_desired_section);
1669 return((CC->download_fp != NULL) ? om_ok : om_mime_error);
1672 /* now for the user-mode message reading loops */
1673 if (do_proto) cprintf("%d msg:\n", LISTING_FOLLOWS);
1675 /* Does the caller want to skip the headers? */
1676 if (headers_only == HEADERS_NONE) goto START_TEXT;
1678 /* Tell the client which format type we're using. */
1679 if ( (mode == MT_CITADEL) && (do_proto) ) {
1680 cprintf("type=%d\n", TheMessage->cm_format_type);
1683 /* nhdr=yes means that we're only displaying headers, no body */
1684 if ( (TheMessage->cm_anon_type == MES_ANONONLY)
1685 && (mode == MT_CITADEL)
1688 cprintf("nhdr=yes\n");
1691 /* begin header processing loop for Citadel message format */
1693 if ((mode == MT_CITADEL) || (mode == MT_MIME)) {
1695 safestrncpy(display_name, "<unknown>", sizeof display_name);
1696 if (TheMessage->cm_fields['A']) {
1697 strcpy(buf, TheMessage->cm_fields['A']);
1698 if (TheMessage->cm_anon_type == MES_ANONONLY) {
1699 safestrncpy(display_name, "****", sizeof display_name);
1701 else if (TheMessage->cm_anon_type == MES_ANONOPT) {
1702 safestrncpy(display_name, "anonymous", sizeof display_name);
1705 safestrncpy(display_name, buf, sizeof display_name);
1707 if ((is_room_aide())
1708 && ((TheMessage->cm_anon_type == MES_ANONONLY)
1709 || (TheMessage->cm_anon_type == MES_ANONOPT))) {
1710 size_t tmp = strlen(display_name);
1711 snprintf(&display_name[tmp],
1712 sizeof display_name - tmp,
1717 /* Don't show Internet address for users on the
1718 * local Citadel network.
1721 if (TheMessage->cm_fields['N'] != NULL)
1722 if (!IsEmptyStr(TheMessage->cm_fields['N']))
1723 if (haschar(TheMessage->cm_fields['N'], '.') == 0) {
1727 /* Now spew the header fields in the order we like them. */
1728 safestrncpy(allkeys, FORDER, sizeof allkeys);
1729 for (i=0; i<strlen(allkeys); ++i) {
1730 k = (int) allkeys[i];
1732 if ( (TheMessage->cm_fields[k] != NULL)
1733 && (msgkeys[k] != NULL) ) {
1735 if (do_proto) cprintf("%s=%s\n",
1739 else if ((k == 'F') && (suppress_f)) {
1742 /* Masquerade display name if needed */
1744 if (do_proto) cprintf("%s=%s\n",
1746 TheMessage->cm_fields[k]
1755 /* begin header processing loop for RFC822 transfer format */
1760 strcpy(snode, NODENAME);
1761 strcpy(lnode, HUMANNODE);
1762 if (mode == MT_RFC822) {
1763 for (i = 0; i < 256; ++i) {
1764 if (TheMessage->cm_fields[i]) {
1765 mptr = mpptr = TheMessage->cm_fields[i];
1768 safestrncpy(luser, mptr, sizeof luser);
1769 safestrncpy(suser, mptr, sizeof suser);
1771 else if (i == 'Y') {
1772 if ((flags & QP_EADDR) != 0)
1773 mptr = qp_encode_email_addrs(mptr);
1774 cprintf("CC: %s%s", mptr, nl);
1776 else if (i == 'P') {
1777 cprintf("Return-Path: %s%s", mptr, nl);
1779 else if (i == 'L') {
1780 cprintf("List-ID: %s%s", mptr, nl);
1782 else if (i == 'V') {
1783 if ((flags & QP_EADDR) != 0)
1784 mptr = qp_encode_email_addrs(mptr);
1785 cprintf("Envelope-To: %s%s", mptr, nl);
1787 else if (i == 'U') {
1788 cprintf("Subject: %s%s", mptr, nl);
1792 safestrncpy(mid, mptr, sizeof mid);
1794 safestrncpy(lnode, mptr, sizeof lnode);
1796 safestrncpy(fuser, mptr, sizeof fuser);
1797 /* else if (i == 'O')
1798 cprintf("X-Citadel-Room: %s%s",
1801 safestrncpy(snode, mptr, sizeof snode);
1804 if (haschar(mptr, '@') == 0)
1806 cprintf("To: %s@%s%s", mptr, config.c_fqdn, nl);
1810 if ((flags & QP_EADDR) != 0)
1811 mptr = qp_encode_email_addrs(mptr);
1812 cprintf("To: %s%s", mptr, nl);
1815 else if (i == 'T') {
1816 datestring(datestamp, sizeof datestamp,
1817 atol(mptr), DATESTRING_RFC822);
1818 cprintf("Date: %s%s", datestamp, nl);
1820 else if (i == 'W') {
1821 cprintf("References: ");
1822 k = num_tokens(mptr, '|');
1823 for (j=0; j<k; ++j) {
1824 extract_token(buf, mptr, j, '|', sizeof buf);
1825 cprintf("<%s>", buf);
1838 if (subject_found == 0) {
1839 cprintf("Subject: (no subject)%s", nl);
1843 for (i=0; !IsEmptyStr(&suser[i]); ++i) {
1844 suser[i] = tolower(suser[i]);
1845 if (!isalnum(suser[i])) suser[i]='_';
1848 if (mode == MT_RFC822) {
1849 if (!strcasecmp(snode, NODENAME)) {
1850 safestrncpy(snode, FQDN, sizeof snode);
1853 /* Construct a fun message id */
1854 cprintf("Message-ID: <%s", mid);/// todo: this possibly breaks threadding mails.
1855 if (strchr(mid, '@')==NULL) {
1856 cprintf("@%s", snode);
1860 if (!is_room_aide() && (TheMessage->cm_anon_type == MES_ANONONLY)) {
1861 cprintf("From: \"----\" <x@x.org>%s", nl);
1863 else if (!is_room_aide() && (TheMessage->cm_anon_type == MES_ANONOPT)) {
1864 cprintf("From: \"anonymous\" <x@x.org>%s", nl);
1866 else if (!IsEmptyStr(fuser)) {
1867 cprintf("From: \"%s\" <%s>%s", luser, fuser, nl);
1870 cprintf("From: \"%s\" <%s@%s>%s", luser, suser, snode, nl);
1873 cprintf("Organization: %s%s", lnode, nl);
1875 /* Blank line signifying RFC822 end-of-headers */
1876 if (TheMessage->cm_format_type != FMT_RFC822) {
1881 /* end header processing loop ... at this point, we're in the text */
1883 if (headers_only == HEADERS_FAST) goto DONE;
1884 mptr = TheMessage->cm_fields['M'];
1886 /* Tell the client about the MIME parts in this message */
1887 if (TheMessage->cm_format_type == FMT_RFC822) {
1888 if ( (mode == MT_CITADEL) || (mode == MT_MIME) ) {
1889 memset(&ma, 0, sizeof(struct ma_info));
1890 mime_parser(mptr, NULL,
1891 (do_proto ? *list_this_part : NULL),
1892 (do_proto ? *list_this_pref : NULL),
1893 (do_proto ? *list_this_suff : NULL),
1896 else if (mode == MT_RFC822) { /* unparsed RFC822 dump */
1897 char *start_of_text = NULL;
1898 start_of_text = strstr(mptr, "\n\r\n");
1899 if (start_of_text == NULL) start_of_text = strstr(mptr, "\n\n");
1900 if (start_of_text == NULL) start_of_text = mptr;
1902 start_of_text = strstr(start_of_text, "\n");
1907 int nllen = strlen(nl);
1909 while (ch=*mptr, ch!=0) {
1915 ((headers_only == HEADERS_NONE) && (mptr >= start_of_text))
1916 || ((headers_only == HEADERS_ONLY) && (mptr < start_of_text))
1917 || ((headers_only != HEADERS_NONE) && (headers_only != HEADERS_ONLY))
1920 sprintf(&outbuf[outlen], "%s", nl);
1924 outbuf[outlen++] = ch;
1928 if (flags & ESC_DOT)
1930 if ((prev_ch == 10) && (ch == '.') && ((*(mptr+1) == 13) || (*(mptr+1) == 10)))
1932 outbuf[outlen++] = '.';
1937 if (outlen > 1000) {
1938 client_write(outbuf, outlen);
1943 client_write(outbuf, outlen);
1951 if (headers_only == HEADERS_ONLY) {
1955 /* signify start of msg text */
1956 if ( (mode == MT_CITADEL) || (mode == MT_MIME) ) {
1957 if (do_proto) cprintf("text\n");
1960 /* If the format type on disk is 1 (fixed-format), then we want
1961 * everything to be output completely literally ... regardless of
1962 * what message transfer format is in use.
1964 if (TheMessage->cm_format_type == FMT_FIXED) {
1966 if (mode == MT_MIME) {
1967 cprintf("Content-type: text/plain\n\n");
1971 while (ch = *mptr++, ch > 0) {
1974 if ((ch == 10) || (buflen > 250)) {
1976 cprintf("%s%s", buf, nl);
1985 if (!IsEmptyStr(buf))
1986 cprintf("%s%s", buf, nl);
1989 /* If the message on disk is format 0 (Citadel vari-format), we
1990 * output using the formatter at 80 columns. This is the final output
1991 * form if the transfer format is RFC822, but if the transfer format
1992 * is Citadel proprietary, it'll still work, because the indentation
1993 * for new paragraphs is correct and the client will reformat the
1994 * message to the reader's screen width.
1996 if (TheMessage->cm_format_type == FMT_CITADEL) {
1997 if (mode == MT_MIME) {
1998 cprintf("Content-type: text/x-citadel-variformat\n\n");
2000 memfmout(mptr, 0, nl);
2003 /* If the message on disk is format 4 (MIME), we've gotta hand it
2004 * off to the MIME parser. The client has already been told that
2005 * this message is format 1 (fixed format), so the callback function
2006 * we use will display those parts as-is.
2008 if (TheMessage->cm_format_type == FMT_RFC822) {
2009 memset(&ma, 0, sizeof(struct ma_info));
2011 if (mode == MT_MIME) {
2012 ma.use_fo_hooks = 0;
2013 strcpy(ma.chosen_part, "1");
2014 ma.chosen_pref = 9999;
2015 mime_parser(mptr, NULL,
2016 *choose_preferred, *fixed_output_pre,
2017 *fixed_output_post, (void *)&ma, 0);
2018 mime_parser(mptr, NULL,
2019 *output_preferred, NULL, NULL, (void *)&ma, CC->msg4_dont_decode);
2022 ma.use_fo_hooks = 1;
2023 mime_parser(mptr, NULL,
2024 *fixed_output, *fixed_output_pre,
2025 *fixed_output_post, (void *)&ma, 0);
2030 DONE: /* now we're done */
2031 if (do_proto) cprintf("000\n");
2038 * display a message (mode 0 - Citadel proprietary)
2040 void cmd_msg0(char *cmdbuf)
2043 int headers_only = HEADERS_ALL;
2045 msgid = extract_long(cmdbuf, 0);
2046 headers_only = extract_int(cmdbuf, 1);
2048 CtdlOutputMsg(msgid, MT_CITADEL, headers_only, 1, 0, NULL, 0);
2054 * display a message (mode 2 - RFC822)
2056 void cmd_msg2(char *cmdbuf)
2059 int headers_only = HEADERS_ALL;
2061 msgid = extract_long(cmdbuf, 0);
2062 headers_only = extract_int(cmdbuf, 1);
2064 CtdlOutputMsg(msgid, MT_RFC822, headers_only, 1, 1, NULL, 0);
2070 * display a message (mode 3 - IGnet raw format - internal programs only)
2072 void cmd_msg3(char *cmdbuf)
2075 struct CtdlMessage *msg = NULL;
2078 if (CC->internal_pgm == 0) {
2079 cprintf("%d This command is for internal programs only.\n",
2080 ERROR + HIGHER_ACCESS_REQUIRED);
2084 msgnum = extract_long(cmdbuf, 0);
2085 msg = CtdlFetchMessage(msgnum, 1);
2087 cprintf("%d Message %ld not found.\n",
2088 ERROR + MESSAGE_NOT_FOUND, msgnum);
2092 serialize_message(&smr, msg);
2093 CtdlFreeMessage(msg);
2096 cprintf("%d Unable to serialize message\n",
2097 ERROR + INTERNAL_ERROR);
2101 cprintf("%d %ld\n", BINARY_FOLLOWS, (long)smr.len);
2102 client_write((char *)smr.ser, (int)smr.len);
2109 * Display a message using MIME content types
2111 void cmd_msg4(char *cmdbuf)
2116 msgid = extract_long(cmdbuf, 0);
2117 extract_token(section, cmdbuf, 1, '|', sizeof section);
2118 CtdlOutputMsg(msgid, MT_MIME, 0, 1, 0, (section[0] ? section : NULL) , 0);
2124 * Client tells us its preferred message format(s)
2126 void cmd_msgp(char *cmdbuf)
2128 if (!strcasecmp(cmdbuf, "dont_decode")) {
2129 CC->msg4_dont_decode = 1;
2130 cprintf("%d MSG4 will not pre-decode messages.\n", CIT_OK);
2133 safestrncpy(CC->preferred_formats, cmdbuf, sizeof(CC->preferred_formats));
2134 cprintf("%d Preferred MIME formats have been set.\n", CIT_OK);
2140 * Open a component of a MIME message as a download file
2142 void cmd_opna(char *cmdbuf)
2145 char desired_section[128];
2147 msgid = extract_long(cmdbuf, 0);
2148 extract_token(desired_section, cmdbuf, 1, '|', sizeof desired_section);
2149 safestrncpy(CC->download_desired_section, desired_section,
2150 sizeof CC->download_desired_section);
2151 CtdlOutputMsg(msgid, MT_DOWNLOAD, 0, 1, 1, NULL, 0);
2156 * Open a component of a MIME message and transmit it all at once
2158 void cmd_dlat(char *cmdbuf)
2161 char desired_section[128];
2163 msgid = extract_long(cmdbuf, 0);
2164 extract_token(desired_section, cmdbuf, 1, '|', sizeof desired_section);
2165 safestrncpy(CC->download_desired_section, desired_section,
2166 sizeof CC->download_desired_section);
2167 CtdlOutputMsg(msgid, MT_SPEW_SECTION, 0, 1, 1, NULL, 0);
2172 * Save one or more message pointers into a specified room
2173 * (Returns 0 for success, nonzero for failure)
2174 * roomname may be NULL to use the current room
2176 * Note that the 'supplied_msg' field may be set to NULL, in which case
2177 * the message will be fetched from disk, by number, if we need to perform
2178 * replication checks. This adds an additional database read, so if the
2179 * caller already has the message in memory then it should be supplied. (Obviously
2180 * this mode of operation only works if we're saving a single message.)
2182 int CtdlSaveMsgPointersInRoom(char *roomname, long newmsgidlist[], int num_newmsgs,
2183 int do_repl_check, struct CtdlMessage *supplied_msg)
2186 char hold_rm[ROOMNAMELEN];
2187 struct cdbdata *cdbfr;
2190 long highest_msg = 0L;
2193 struct CtdlMessage *msg = NULL;
2195 long *msgs_to_be_merged = NULL;
2196 int num_msgs_to_be_merged = 0;
2198 CtdlLogPrintf(CTDL_DEBUG,
2199 "CtdlSaveMsgPointersInRoom(room=%s, num_msgs=%d, repl=%d)\n",
2200 roomname, num_newmsgs, do_repl_check);
2202 strcpy(hold_rm, CC->room.QRname);
2205 if (newmsgidlist == NULL) return(ERROR + INTERNAL_ERROR);
2206 if (num_newmsgs < 1) return(ERROR + INTERNAL_ERROR);
2207 if (num_newmsgs > 1) supplied_msg = NULL;
2209 /* Now the regular stuff */
2210 if (lgetroom(&CC->room,
2211 ((roomname != NULL) ? roomname : CC->room.QRname) )
2213 CtdlLogPrintf(CTDL_ERR, "No such room <%s>\n", roomname);
2214 return(ERROR + ROOM_NOT_FOUND);
2218 msgs_to_be_merged = malloc(sizeof(long) * num_newmsgs);
2219 num_msgs_to_be_merged = 0;
2222 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long));
2223 if (cdbfr == NULL) {
2227 msglist = (long *) cdbfr->ptr;
2228 cdbfr->ptr = NULL; /* CtdlSaveMsgPointerInRoom() now owns this memory */
2229 num_msgs = cdbfr->len / sizeof(long);
2234 /* Create a list of msgid's which were supplied by the caller, but do
2235 * not already exist in the target room. It is absolutely taboo to
2236 * have more than one reference to the same message in a room.
2238 for (i=0; i<num_newmsgs; ++i) {
2240 if (num_msgs > 0) for (j=0; j<num_msgs; ++j) {
2241 if (msglist[j] == newmsgidlist[i]) {
2246 msgs_to_be_merged[num_msgs_to_be_merged++] = newmsgidlist[i];
2250 CtdlLogPrintf(9, "%d unique messages to be merged\n", num_msgs_to_be_merged);
2253 * Now merge the new messages
2255 msglist = realloc(msglist, (sizeof(long) * (num_msgs + num_msgs_to_be_merged)) );
2256 if (msglist == NULL) {
2257 CtdlLogPrintf(CTDL_ALERT, "ERROR: can't realloc message list!\n");
2259 memcpy(&msglist[num_msgs], msgs_to_be_merged, (sizeof(long) * num_msgs_to_be_merged) );
2260 num_msgs += num_msgs_to_be_merged;
2262 /* Sort the message list, so all the msgid's are in order */
2263 num_msgs = sort_msglist(msglist, num_msgs);
2265 /* Determine the highest message number */
2266 highest_msg = msglist[num_msgs - 1];
2268 /* Write it back to disk. */
2269 cdb_store(CDB_MSGLISTS, &CC->room.QRnumber, (int)sizeof(long),
2270 msglist, (int)(num_msgs * sizeof(long)));
2272 /* Free up the memory we used. */
2275 /* Update the highest-message pointer and unlock the room. */
2276 CC->room.QRhighest = highest_msg;
2277 lputroom(&CC->room);
2279 /* Perform replication checks if necessary */
2280 if ( (DoesThisRoomNeedEuidIndexing(&CC->room)) && (do_repl_check) ) {
2281 CtdlLogPrintf(CTDL_DEBUG, "CtdlSaveMsgPointerInRoom() doing repl checks\n");
2283 for (i=0; i<num_msgs_to_be_merged; ++i) {
2284 msgid = msgs_to_be_merged[i];
2286 if (supplied_msg != NULL) {
2290 msg = CtdlFetchMessage(msgid, 0);
2294 ReplicationChecks(msg);
2296 /* If the message has an Exclusive ID, index that... */
2297 if (msg->cm_fields['E'] != NULL) {
2298 index_message_by_euid(msg->cm_fields['E'], &CC->room, msgid);
2301 /* Free up the memory we may have allocated */
2302 if (msg != supplied_msg) {
2303 CtdlFreeMessage(msg);
2311 CtdlLogPrintf(CTDL_DEBUG, "CtdlSaveMsgPointerInRoom() skips repl checks\n");
2314 /* Submit this room for processing by hooks */
2315 PerformRoomHooks(&CC->room);
2317 /* Go back to the room we were in before we wandered here... */
2318 getroom(&CC->room, hold_rm);
2320 /* Bump the reference count for all messages which were merged */
2321 for (i=0; i<num_msgs_to_be_merged; ++i) {
2322 AdjRefCount(msgs_to_be_merged[i], +1);
2325 /* Free up memory... */
2326 if (msgs_to_be_merged != NULL) {
2327 free(msgs_to_be_merged);
2330 /* Return success. */
2336 * This is the same as CtdlSaveMsgPointersInRoom() but it only accepts
2339 int CtdlSaveMsgPointerInRoom(char *roomname, long msgid,
2340 int do_repl_check, struct CtdlMessage *supplied_msg)
2342 return CtdlSaveMsgPointersInRoom(roomname, &msgid, 1, do_repl_check, supplied_msg);
2349 * Message base operation to save a new message to the message store
2350 * (returns new message number)
2352 * This is the back end for CtdlSubmitMsg() and should not be directly
2353 * called by server-side modules.
2356 long send_message(struct CtdlMessage *msg) {
2364 /* Get a new message number */
2365 newmsgid = get_new_message_number();
2366 snprintf(msgidbuf, sizeof msgidbuf, "%010ld@%s", newmsgid, config.c_fqdn);
2368 /* Generate an ID if we don't have one already */
2369 if (msg->cm_fields['I']==NULL) {
2370 msg->cm_fields['I'] = strdup(msgidbuf);
2373 /* If the message is big, set its body aside for storage elsewhere */
2374 if (msg->cm_fields['M'] != NULL) {
2375 if (strlen(msg->cm_fields['M']) > BIGMSG) {
2377 holdM = msg->cm_fields['M'];
2378 msg->cm_fields['M'] = NULL;
2382 /* Serialize our data structure for storage in the database */
2383 serialize_message(&smr, msg);
2386 msg->cm_fields['M'] = holdM;
2390 cprintf("%d Unable to serialize message\n",
2391 ERROR + INTERNAL_ERROR);
2395 /* Write our little bundle of joy into the message base */
2396 if (cdb_store(CDB_MSGMAIN, &newmsgid, (int)sizeof(long),
2397 smr.ser, smr.len) < 0) {
2398 CtdlLogPrintf(CTDL_ERR, "Can't store message\n");
2402 cdb_store(CDB_BIGMSGS,
2412 /* Free the memory we used for the serialized message */
2415 /* Return the *local* message ID to the caller
2416 * (even if we're storing an incoming network message)
2424 * Serialize a struct CtdlMessage into the format used on disk and network.
2426 * This function loads up a "struct ser_ret" (defined in server.h) which
2427 * contains the length of the serialized message and a pointer to the
2428 * serialized message in memory. THE LATTER MUST BE FREED BY THE CALLER.
2430 void serialize_message(struct ser_ret *ret, /* return values */
2431 struct CtdlMessage *msg) /* unserialized msg */
2433 size_t wlen, fieldlen;
2435 static char *forder = FORDER;
2438 * Check for valid message format
2440 if (is_valid_message(msg) == 0) {
2441 CtdlLogPrintf(CTDL_ERR, "serialize_message() aborting due to invalid message\n");
2448 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL)
2449 ret->len = ret->len +
2450 strlen(msg->cm_fields[(int)forder[i]]) + 2;
2452 ret->ser = malloc(ret->len);
2453 if (ret->ser == NULL) {
2454 CtdlLogPrintf(CTDL_ERR, "serialize_message() malloc(%ld) failed: %s\n",
2455 (long)ret->len, strerror(errno));
2462 ret->ser[1] = msg->cm_anon_type;
2463 ret->ser[2] = msg->cm_format_type;
2466 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL) {
2467 fieldlen = strlen(msg->cm_fields[(int)forder[i]]);
2468 ret->ser[wlen++] = (char)forder[i];
2469 safestrncpy((char *)&ret->ser[wlen], msg->cm_fields[(int)forder[i]], fieldlen+1);
2470 wlen = wlen + fieldlen + 1;
2472 if (ret->len != wlen) CtdlLogPrintf(CTDL_ERR, "ERROR: len=%ld wlen=%ld\n",
2473 (long)ret->len, (long)wlen);
2480 * Serialize a struct CtdlMessage into the format used on disk and network.
2482 * This function loads up a "struct ser_ret" (defined in server.h) which
2483 * contains the length of the serialized message and a pointer to the
2484 * serialized message in memory. THE LATTER MUST BE FREED BY THE CALLER.
2486 void dump_message(struct CtdlMessage *msg, /* unserialized msg */
2487 long Siz) /* how many chars ? */
2491 static char *forder = FORDER;
2495 * Check for valid message format
2497 if (is_valid_message(msg) == 0) {
2498 CtdlLogPrintf(CTDL_ERR, "dump_message() aborting due to invalid message\n");
2502 buf = (char*) malloc (Siz + 1);
2506 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL) {
2507 snprintf (buf, Siz, " msg[%c] = %s ...\n", (char) forder[i],
2508 msg->cm_fields[(int)forder[i]]);
2509 client_write (buf, strlen(buf));
2518 * Check to see if any messages already exist in the current room which
2519 * carry the same Exclusive ID as this one. If any are found, delete them.
2521 void ReplicationChecks(struct CtdlMessage *msg) {
2522 long old_msgnum = (-1L);
2524 if (DoesThisRoomNeedEuidIndexing(&CC->room) == 0) return;
2526 CtdlLogPrintf(CTDL_DEBUG, "Performing replication checks in <%s>\n",
2529 /* No exclusive id? Don't do anything. */
2530 if (msg == NULL) return;
2531 if (msg->cm_fields['E'] == NULL) return;
2532 if (IsEmptyStr(msg->cm_fields['E'])) return;
2533 /*CtdlLogPrintf(CTDL_DEBUG, "Exclusive ID: <%s> for room <%s>\n",
2534 msg->cm_fields['E'], CC->room.QRname);*/
2536 old_msgnum = locate_message_by_euid(msg->cm_fields['E'], &CC->room);
2537 if (old_msgnum > 0L) {
2538 CtdlLogPrintf(CTDL_DEBUG, "ReplicationChecks() replacing message %ld\n", old_msgnum);
2539 CtdlDeleteMessages(CC->room.QRname, &old_msgnum, 1, "");
2546 * Save a message to disk and submit it into the delivery system.
2548 long CtdlSubmitMsg(struct CtdlMessage *msg, /* message to save */
2549 struct recptypes *recps, /* recipients (if mail) */
2550 char *force, /* force a particular room? */
2551 int flags /* should the bessage be exported clean? */
2553 char submit_filename[128];
2554 char generated_timestamp[32];
2555 char hold_rm[ROOMNAMELEN];
2556 char actual_rm[ROOMNAMELEN];
2557 char force_room[ROOMNAMELEN];
2558 char content_type[SIZ]; /* We have to learn this */
2559 char recipient[SIZ];
2562 struct ctdluser userbuf;
2564 struct MetaData smi;
2565 FILE *network_fp = NULL;
2566 static int seqnum = 1;
2567 struct CtdlMessage *imsg = NULL;
2569 size_t instr_alloc = 0;
2571 char *hold_R, *hold_D;
2572 char *collected_addresses = NULL;
2573 struct addresses_to_be_filed *aptr = NULL;
2574 char *saved_rfc822_version = NULL;
2575 int qualified_for_journaling = 0;
2576 struct CitContext *CCC = CC; /* CachedCitContext - performance boost */
2577 char bounce_to[1024] = "";
2579 CtdlLogPrintf(CTDL_DEBUG, "CtdlSubmitMsg() called\n");
2580 if (is_valid_message(msg) == 0) return(-1); /* self check */
2582 /* If this message has no timestamp, we take the liberty of
2583 * giving it one, right now.
2585 if (msg->cm_fields['T'] == NULL) {
2586 snprintf(generated_timestamp, sizeof generated_timestamp, "%ld", (long)time(NULL));
2587 msg->cm_fields['T'] = strdup(generated_timestamp);
2590 /* If this message has no path, we generate one.
2592 if (msg->cm_fields['P'] == NULL) {
2593 if (msg->cm_fields['A'] != NULL) {
2594 msg->cm_fields['P'] = strdup(msg->cm_fields['A']);
2595 for (a=0; !IsEmptyStr(&msg->cm_fields['P'][a]); ++a) {
2596 if (isspace(msg->cm_fields['P'][a])) {
2597 msg->cm_fields['P'][a] = ' ';
2602 msg->cm_fields['P'] = strdup("unknown");
2606 if (force == NULL) {
2607 strcpy(force_room, "");
2610 strcpy(force_room, force);
2613 /* Learn about what's inside, because it's what's inside that counts */
2614 if (msg->cm_fields['M'] == NULL) {
2615 CtdlLogPrintf(CTDL_ERR, "ERROR: attempt to save message with NULL body\n");
2619 switch (msg->cm_format_type) {
2621 strcpy(content_type, "text/x-citadel-variformat");
2624 strcpy(content_type, "text/plain");
2627 strcpy(content_type, "text/plain");
2628 mptr = bmstrcasestr(msg->cm_fields['M'], "Content-type:");
2631 safestrncpy(content_type, &mptr[13], sizeof content_type);
2632 striplt(content_type);
2633 aptr = content_type;
2634 while (!IsEmptyStr(aptr)) {
2646 /* Goto the correct room */
2647 CtdlLogPrintf(CTDL_DEBUG, "Selected room %s\n", (recps) ? CCC->room.QRname : SENTITEMS);
2648 strcpy(hold_rm, CCC->room.QRname);
2649 strcpy(actual_rm, CCC->room.QRname);
2650 if (recps != NULL) {
2651 strcpy(actual_rm, SENTITEMS);
2654 /* If the user is a twit, move to the twit room for posting */
2656 if (CCC->user.axlevel == 2) {
2657 strcpy(hold_rm, actual_rm);
2658 strcpy(actual_rm, config.c_twitroom);
2659 CtdlLogPrintf(CTDL_DEBUG, "Diverting to twit room\n");
2663 /* ...or if this message is destined for Aide> then go there. */
2664 if (!IsEmptyStr(force_room)) {
2665 strcpy(actual_rm, force_room);
2668 CtdlLogPrintf(CTDL_DEBUG, "Final selection: %s\n", actual_rm);
2669 if (strcasecmp(actual_rm, CCC->room.QRname)) {
2670 /* getroom(&CCC->room, actual_rm); */
2671 usergoto(actual_rm, 0, 1, NULL, NULL);
2675 * If this message has no O (room) field, generate one.
2677 if (msg->cm_fields['O'] == NULL) {
2678 msg->cm_fields['O'] = strdup(CCC->room.QRname);
2681 /* Perform "before save" hooks (aborting if any return nonzero) */
2682 CtdlLogPrintf(CTDL_DEBUG, "Performing before-save hooks\n");
2683 if (PerformMessageHooks(msg, EVT_BEFORESAVE) > 0) return(-3);
2686 * If this message has an Exclusive ID, and the room is replication
2687 * checking enabled, then do replication checks.
2689 if (DoesThisRoomNeedEuidIndexing(&CCC->room)) {
2690 ReplicationChecks(msg);
2693 /* Save it to disk */
2694 CtdlLogPrintf(CTDL_DEBUG, "Saving to disk\n");
2695 newmsgid = send_message(msg);
2696 if (newmsgid <= 0L) return(-5);
2698 /* Write a supplemental message info record. This doesn't have to
2699 * be a critical section because nobody else knows about this message
2702 CtdlLogPrintf(CTDL_DEBUG, "Creating MetaData record\n");
2703 memset(&smi, 0, sizeof(struct MetaData));
2704 smi.meta_msgnum = newmsgid;
2705 smi.meta_refcount = 0;
2706 safestrncpy(smi.meta_content_type, content_type,
2707 sizeof smi.meta_content_type);
2710 * Measure how big this message will be when rendered as RFC822.
2711 * We do this for two reasons:
2712 * 1. We need the RFC822 length for the new metadata record, so the
2713 * POP and IMAP services don't have to calculate message lengths
2714 * while the user is waiting (multiplied by potentially hundreds
2715 * or thousands of messages).
2716 * 2. If journaling is enabled, we will need an RFC822 version of the
2717 * message to attach to the journalized copy.
2719 if (CCC->redirect_buffer != NULL) {
2720 CtdlLogPrintf(CTDL_ALERT, "CCC->redirect_buffer is not NULL during message submission!\n");
2723 CCC->redirect_buffer = malloc(SIZ);
2724 CCC->redirect_len = 0;
2725 CCC->redirect_alloc = SIZ;
2726 CtdlOutputPreLoadedMsg(msg, MT_RFC822, HEADERS_ALL, 0, 1, QP_EADDR);
2727 smi.meta_rfc822_length = CCC->redirect_len;
2728 saved_rfc822_version = CCC->redirect_buffer;
2729 CCC->redirect_buffer = NULL;
2730 CCC->redirect_len = 0;
2731 CCC->redirect_alloc = 0;
2735 /* Now figure out where to store the pointers */
2736 CtdlLogPrintf(CTDL_DEBUG, "Storing pointers\n");
2738 /* If this is being done by the networker delivering a private
2739 * message, we want to BYPASS saving the sender's copy (because there
2740 * is no local sender; it would otherwise go to the Trashcan).
2742 if ((!CCC->internal_pgm) || (recps == NULL)) {
2743 if (CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 1, msg) != 0) {
2744 CtdlLogPrintf(CTDL_ERR, "ERROR saving message pointer!\n");
2745 CtdlSaveMsgPointerInRoom(config.c_aideroom, newmsgid, 0, msg);
2749 /* For internet mail, drop a copy in the outbound queue room */
2751 if (recps->num_internet > 0) {
2752 CtdlSaveMsgPointerInRoom(SMTP_SPOOLOUT_ROOM, newmsgid, 0, msg);
2755 /* If other rooms are specified, drop them there too. */
2757 if (recps->num_room > 0)
2758 for (i=0; i<num_tokens(recps->recp_room, '|'); ++i) {
2759 extract_token(recipient, recps->recp_room, i,
2760 '|', sizeof recipient);
2761 CtdlLogPrintf(CTDL_DEBUG, "Delivering to room <%s>\n", recipient);
2762 CtdlSaveMsgPointerInRoom(recipient, newmsgid, 0, msg);
2765 /* Bump this user's messages posted counter. */
2766 CtdlLogPrintf(CTDL_DEBUG, "Updating user\n");
2767 lgetuser(&CCC->user, CCC->curr_user);
2768 CCC->user.posted = CCC->user.posted + 1;
2769 lputuser(&CCC->user);
2771 /* Decide where bounces need to be delivered */
2772 if (CCC->logged_in) {
2773 snprintf(bounce_to, sizeof bounce_to, "%s@%s", CCC->user.fullname, config.c_nodename);
2776 snprintf(bounce_to, sizeof bounce_to, "%s@%s", msg->cm_fields['A'], msg->cm_fields['N']);
2779 /* If this is private, local mail, make a copy in the
2780 * recipient's mailbox and bump the reference count.
2783 if (recps->num_local > 0)
2784 for (i=0; i<num_tokens(recps->recp_local, '|'); ++i) {
2785 extract_token(recipient, recps->recp_local, i,
2786 '|', sizeof recipient);
2787 CtdlLogPrintf(CTDL_DEBUG, "Delivering private local mail to <%s>\n",
2789 if (getuser(&userbuf, recipient) == 0) {
2790 // Add a flag so the Funambol module knows its mail
2791 msg->cm_fields['W'] = strdup(recipient);
2792 MailboxName(actual_rm, sizeof actual_rm, &userbuf, MAILROOM);
2793 CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 0, msg);
2794 BumpNewMailCounter(userbuf.usernum);
2795 if (!IsEmptyStr(config.c_funambol_host) || !IsEmptyStr(config.c_pager_program)) {
2796 /* Generate a instruction message for the Funambol notification
2797 * server, in the same style as the SMTP queue
2800 instr = malloc(instr_alloc);
2801 snprintf(instr, instr_alloc,
2802 "Content-type: %s\n\nmsgid|%ld\nsubmitted|%ld\n"
2804 SPOOLMIME, newmsgid, (long)time(NULL),
2808 imsg = malloc(sizeof(struct CtdlMessage));
2809 memset(imsg, 0, sizeof(struct CtdlMessage));
2810 imsg->cm_magic = CTDLMESSAGE_MAGIC;
2811 imsg->cm_anon_type = MES_NORMAL;
2812 imsg->cm_format_type = FMT_RFC822;
2813 imsg->cm_fields['A'] = strdup("Citadel");
2814 imsg->cm_fields['J'] = strdup("do not journal");
2815 imsg->cm_fields['M'] = instr; /* imsg owns this memory now */
2816 imsg->cm_fields['W'] = strdup(recipient);
2817 CtdlSubmitMsg(imsg, NULL, FNBL_QUEUE_ROOM, 0);
2818 CtdlFreeMessage(imsg);
2822 CtdlLogPrintf(CTDL_DEBUG, "No user <%s>\n", recipient);
2823 CtdlSaveMsgPointerInRoom(config.c_aideroom,
2828 /* Perform "after save" hooks */
2829 CtdlLogPrintf(CTDL_DEBUG, "Performing after-save hooks\n");
2830 PerformMessageHooks(msg, EVT_AFTERSAVE);
2832 /* For IGnet mail, we have to save a new copy into the spooler for
2833 * each recipient, with the R and D fields set to the recipient and
2834 * destination-node. This has two ugly side effects: all other
2835 * recipients end up being unlisted in this recipient's copy of the
2836 * message, and it has to deliver multiple messages to the same
2837 * node. We'll revisit this again in a year or so when everyone has
2838 * a network spool receiver that can handle the new style messages.
2841 if (recps->num_ignet > 0)
2842 for (i=0; i<num_tokens(recps->recp_ignet, '|'); ++i) {
2843 extract_token(recipient, recps->recp_ignet, i,
2844 '|', sizeof recipient);
2846 hold_R = msg->cm_fields['R'];
2847 hold_D = msg->cm_fields['D'];
2848 msg->cm_fields['R'] = malloc(SIZ);
2849 msg->cm_fields['D'] = malloc(128);
2850 extract_token(msg->cm_fields['R'], recipient, 0, '@', SIZ);
2851 extract_token(msg->cm_fields['D'], recipient, 1, '@', 128);
2853 serialize_message(&smr, msg);
2855 snprintf(submit_filename, sizeof submit_filename,
2856 "%s/netmail.%04lx.%04x.%04x",
2858 (long) getpid(), CCC->cs_pid, ++seqnum);
2859 network_fp = fopen(submit_filename, "wb+");
2860 if (network_fp != NULL) {
2861 fwrite(smr.ser, smr.len, 1, network_fp);
2867 free(msg->cm_fields['R']);
2868 free(msg->cm_fields['D']);
2869 msg->cm_fields['R'] = hold_R;
2870 msg->cm_fields['D'] = hold_D;
2873 /* Go back to the room we started from */
2874 CtdlLogPrintf(CTDL_DEBUG, "Returning to original room %s\n", hold_rm);
2875 if (strcasecmp(hold_rm, CCC->room.QRname))
2876 usergoto(hold_rm, 0, 1, NULL, NULL);
2878 /* For internet mail, generate delivery instructions.
2879 * Yes, this is recursive. Deal with it. Infinite recursion does
2880 * not happen because the delivery instructions message does not
2881 * contain a recipient.
2884 if (recps->num_internet > 0) {
2885 CtdlLogPrintf(CTDL_DEBUG, "Generating delivery instructions\n");
2887 instr = malloc(instr_alloc);
2888 snprintf(instr, instr_alloc,
2889 "Content-type: %s\n\nmsgid|%ld\nsubmitted|%ld\n"
2891 SPOOLMIME, newmsgid, (long)time(NULL),
2895 for (i=0; i<num_tokens(recps->recp_internet, '|'); ++i) {
2896 size_t tmp = strlen(instr);
2897 extract_token(recipient, recps->recp_internet, i, '|', sizeof recipient);
2898 if ((tmp + strlen(recipient) + 32) > instr_alloc) {
2899 instr_alloc = instr_alloc * 2;
2900 instr = realloc(instr, instr_alloc);
2902 snprintf(&instr[tmp], instr_alloc - tmp, "remote|%s|0||\n", recipient);
2905 imsg = malloc(sizeof(struct CtdlMessage));
2906 memset(imsg, 0, sizeof(struct CtdlMessage));
2907 imsg->cm_magic = CTDLMESSAGE_MAGIC;
2908 imsg->cm_anon_type = MES_NORMAL;
2909 imsg->cm_format_type = FMT_RFC822;
2910 imsg->cm_fields['A'] = strdup("Citadel");
2911 imsg->cm_fields['J'] = strdup("do not journal");
2912 imsg->cm_fields['M'] = instr; /* imsg owns this memory now */
2913 CtdlSubmitMsg(imsg, NULL, SMTP_SPOOLOUT_ROOM, QP_EADDR);
2914 CtdlFreeMessage(imsg);
2918 * Any addresses to harvest for someone's address book?
2920 if ( (CCC->logged_in) && (recps != NULL) ) {
2921 collected_addresses = harvest_collected_addresses(msg);
2924 if (collected_addresses != NULL) {
2925 aptr = (struct addresses_to_be_filed *)
2926 malloc(sizeof(struct addresses_to_be_filed));
2927 MailboxName(actual_rm, sizeof actual_rm,
2928 &CCC->user, USERCONTACTSROOM);
2929 aptr->roomname = strdup(actual_rm);
2930 aptr->collected_addresses = collected_addresses;
2931 begin_critical_section(S_ATBF);
2934 end_critical_section(S_ATBF);
2938 * Determine whether this message qualifies for journaling.
2940 if (msg->cm_fields['J'] != NULL) {
2941 qualified_for_journaling = 0;
2944 if (recps == NULL) {
2945 qualified_for_journaling = config.c_journal_pubmsgs;
2947 else if (recps->num_local + recps->num_ignet + recps->num_internet > 0) {
2948 qualified_for_journaling = config.c_journal_email;
2951 qualified_for_journaling = config.c_journal_pubmsgs;
2956 * Do we have to perform journaling? If so, hand off the saved
2957 * RFC822 version will be handed off to the journaler for background
2958 * submit. Otherwise, we have to free the memory ourselves.
2960 if (saved_rfc822_version != NULL) {
2961 if (qualified_for_journaling) {
2962 JournalBackgroundSubmit(msg, saved_rfc822_version, recps);
2965 free(saved_rfc822_version);
2978 * Convenience function for generating small administrative messages.
2980 void quickie_message(char *from, char *fromaddr, char *to, char *room, char *text,
2981 int format_type, char *subject)
2983 struct CtdlMessage *msg;
2984 struct recptypes *recp = NULL;
2986 msg = malloc(sizeof(struct CtdlMessage));
2987 memset(msg, 0, sizeof(struct CtdlMessage));
2988 msg->cm_magic = CTDLMESSAGE_MAGIC;
2989 msg->cm_anon_type = MES_NORMAL;
2990 msg->cm_format_type = format_type;
2993 msg->cm_fields['A'] = strdup(from);
2995 else if (fromaddr != NULL) {
2996 msg->cm_fields['A'] = strdup(fromaddr);
2997 if (strchr(msg->cm_fields['A'], '@')) {
2998 *strchr(msg->cm_fields['A'], '@') = 0;
3002 msg->cm_fields['A'] = strdup("Citadel");
3005 if (fromaddr != NULL) msg->cm_fields['F'] = strdup(fromaddr);
3006 if (room != NULL) msg->cm_fields['O'] = strdup(room);
3007 msg->cm_fields['N'] = strdup(NODENAME);
3009 msg->cm_fields['R'] = strdup(to);
3010 recp = validate_recipients(to, NULL, 0);
3012 if (subject != NULL) {
3013 msg->cm_fields['U'] = strdup(subject);
3015 msg->cm_fields['M'] = strdup(text);
3017 CtdlSubmitMsg(msg, recp, room, 0);
3018 CtdlFreeMessage(msg);
3019 if (recp != NULL) free_recipients(recp);
3025 * Back end function used by CtdlMakeMessage() and similar functions
3027 char *CtdlReadMessageBody(char *terminator, /* token signalling EOT */
3028 size_t maxlen, /* maximum message length */
3029 char *exist, /* if non-null, append to it;
3030 exist is ALWAYS freed */
3031 int crlf, /* CRLF newlines instead of LF */
3032 int sock /* socket handle or 0 for this session's client socket */
3036 size_t message_len = 0;
3037 size_t buffer_len = 0;
3044 if (exist == NULL) {
3051 message_len = strlen(exist);
3052 buffer_len = message_len + 4096;
3053 m = realloc(exist, buffer_len);
3060 /* Do we need to change leading ".." to "." for SMTP escaping? */
3061 if (!strcmp(terminator, ".")) {
3065 /* flush the input if we have nowhere to store it */
3070 /* read in the lines of message text one by one */
3073 if (sock_getln(sock, buf, (sizeof buf - 3)) < 0) finished = 1;
3076 if (client_getln(buf, (sizeof buf - 3)) < 1) finished = 1;
3078 if (!strcmp(buf, terminator)) finished = 1;
3080 strcat(buf, "\r\n");
3086 /* Unescape SMTP-style input of two dots at the beginning of the line */
3088 if (!strncmp(buf, "..", 2)) {
3089 strcpy(buf, &buf[1]);
3093 if ( (!flushing) && (!finished) ) {
3094 /* Measure the line */
3095 linelen = strlen(buf);
3097 /* augment the buffer if we have to */
3098 if ((message_len + linelen) >= buffer_len) {
3099 ptr = realloc(m, (buffer_len * 2) );
3100 if (ptr == NULL) { /* flush if can't allocate */
3103 buffer_len = (buffer_len * 2);
3105 CtdlLogPrintf(CTDL_DEBUG, "buffer_len is now %ld\n", (long)buffer_len);
3109 /* Add the new line to the buffer. NOTE: this loop must avoid
3110 * using functions like strcat() and strlen() because they
3111 * traverse the entire buffer upon every call, and doing that
3112 * for a multi-megabyte message slows it down beyond usability.
3114 strcpy(&m[message_len], buf);
3115 message_len += linelen;
3118 /* if we've hit the max msg length, flush the rest */
3119 if (message_len >= maxlen) flushing = 1;
3121 } while (!finished);
3129 * Build a binary message to be saved on disk.
3130 * (NOTE: if you supply 'preformatted_text', the buffer you give it
3131 * will become part of the message. This means you are no longer
3132 * responsible for managing that memory -- it will be freed along with
3133 * the rest of the fields when CtdlFreeMessage() is called.)
3136 struct CtdlMessage *CtdlMakeMessage(
3137 struct ctdluser *author, /* author's user structure */
3138 char *recipient, /* NULL if it's not mail */
3139 char *recp_cc, /* NULL if it's not mail */
3140 char *room, /* room where it's going */
3141 int type, /* see MES_ types in header file */
3142 int format_type, /* variformat, plain text, MIME... */
3143 char *fake_name, /* who we're masquerading as */
3144 char *my_email, /* which of my email addresses to use (empty is ok) */
3145 char *subject, /* Subject (optional) */
3146 char *supplied_euid, /* ...or NULL if this is irrelevant */
3147 char *preformatted_text, /* ...or NULL to read text from client */
3148 char *references /* Thread references */
3150 char dest_node[256];
3152 struct CtdlMessage *msg;
3154 msg = malloc(sizeof(struct CtdlMessage));
3155 memset(msg, 0, sizeof(struct CtdlMessage));
3156 msg->cm_magic = CTDLMESSAGE_MAGIC;
3157 msg->cm_anon_type = type;
3158 msg->cm_format_type = format_type;
3160 /* Don't confuse the poor folks if it's not routed mail. */
3161 strcpy(dest_node, "");
3166 /* Path or Return-Path */
3167 if (my_email == NULL) my_email = "";
3169 if (!IsEmptyStr(my_email)) {
3170 msg->cm_fields['P'] = strdup(my_email);
3173 snprintf(buf, sizeof buf, "%s", author->fullname);
3174 msg->cm_fields['P'] = strdup(buf);
3176 convert_spaces_to_underscores(msg->cm_fields['P']);
3178 snprintf(buf, sizeof buf, "%ld", (long)time(NULL)); /* timestamp */
3179 msg->cm_fields['T'] = strdup(buf);
3181 if (fake_name[0]) /* author */
3182 msg->cm_fields['A'] = strdup(fake_name);
3184 msg->cm_fields['A'] = strdup(author->fullname);
3186 if (CC->room.QRflags & QR_MAILBOX) { /* room */
3187 msg->cm_fields['O'] = strdup(&CC->room.QRname[11]);
3190 msg->cm_fields['O'] = strdup(CC->room.QRname);
3193 msg->cm_fields['N'] = strdup(NODENAME); /* nodename */
3194 msg->cm_fields['H'] = strdup(HUMANNODE); /* hnodename */
3196 if (recipient[0] != 0) {
3197 msg->cm_fields['R'] = strdup(recipient);
3199 if (recp_cc[0] != 0) {
3200 msg->cm_fields['Y'] = strdup(recp_cc);
3202 if (dest_node[0] != 0) {
3203 msg->cm_fields['D'] = strdup(dest_node);
3206 if (!IsEmptyStr(my_email)) {
3207 msg->cm_fields['F'] = strdup(my_email);
3209 else if ( (author == &CC->user) && (!IsEmptyStr(CC->cs_inet_email)) ) {
3210 msg->cm_fields['F'] = strdup(CC->cs_inet_email);
3213 if (subject != NULL) {
3216 length = strlen(subject);
3222 while ((subject[i] != '\0') &&
3223 (IsAscii = isascii(subject[i]) != 0 ))
3226 msg->cm_fields['U'] = strdup(subject);
3227 else /* ok, we've got utf8 in the string. */
3229 msg->cm_fields['U'] = rfc2047encode(subject, length);
3235 if (supplied_euid != NULL) {
3236 msg->cm_fields['E'] = strdup(supplied_euid);
3239 if (references != NULL) {
3240 if (!IsEmptyStr(references)) {
3241 msg->cm_fields['W'] = strdup(references);
3245 if (preformatted_text != NULL) {
3246 msg->cm_fields['M'] = preformatted_text;
3249 msg->cm_fields['M'] = CtdlReadMessageBody("000", config.c_maxmsglen, NULL, 0, 0);
3257 * Check to see whether we have permission to post a message in the current
3258 * room. Returns a *CITADEL ERROR CODE* and puts a message in errmsgbuf, or
3259 * returns 0 on success.
3261 int CtdlDoIHavePermissionToPostInThisRoom(char *errmsgbuf,
3263 const char* RemoteIdentifier,
3267 if (!(CC->logged_in) &&
3268 (PostPublic == POST_LOGGED_IN)) {
3269 snprintf(errmsgbuf, n, "Not logged in.");
3270 return (ERROR + NOT_LOGGED_IN);
3272 else if (PostPublic == CHECK_EXISTANCE) {
3273 return (0); // We're Evaling whether a recipient exists
3275 else if (!(CC->logged_in)) {
3277 if ((CC->room.QRflags & QR_READONLY)) {
3278 snprintf(errmsgbuf, n, "Not logged in.");
3279 return (ERROR + NOT_LOGGED_IN);
3281 if (CC->room.QRflags2 & QR2_MODERATED) {
3282 snprintf(errmsgbuf, n, "Not logged in Moderation feature not yet implemented!");
3283 return (ERROR + NOT_LOGGED_IN);
3285 if ((PostPublic!=POST_LMTP) &&(CC->room.QRflags2 & QR2_SMTP_PUBLIC) == 0) {
3290 if (RemoteIdentifier == NULL)
3292 snprintf(errmsgbuf, n, "Need sender to permit access.");
3293 return (ERROR + USERNAME_REQUIRED);
3296 assoc_file_name(filename, sizeof filename, &CC->room, ctdl_netcfg_dir);
3297 begin_critical_section(S_NETCONFIGS);
3298 if (!read_spoolcontrol_file(&sc, filename))
3300 end_critical_section(S_NETCONFIGS);
3301 snprintf(errmsgbuf, n,
3302 "This mailing list only accepts posts from subscribers.");
3303 return (ERROR + NO_SUCH_USER);
3305 end_critical_section(S_NETCONFIGS);
3306 found = is_recipient (sc, RemoteIdentifier);
3307 free_spoolcontrol_struct(&sc);
3312 snprintf(errmsgbuf, n,
3313 "This mailing list only accepts posts from subscribers.");
3314 return (ERROR + NO_SUCH_USER);
3321 if ((CC->user.axlevel < 2)
3322 && ((CC->room.QRflags & QR_MAILBOX) == 0)) {
3323 snprintf(errmsgbuf, n, "Need to be validated to enter "
3324 "(except in %s> to sysop)", MAILROOM);
3325 return (ERROR + HIGHER_ACCESS_REQUIRED);
3328 CtdlRoomAccess(&CC->room, &CC->user, &ra, NULL);
3329 if (!(ra & UA_POSTALLOWED)) {
3330 snprintf(errmsgbuf, n, "Higher access is required to post in this room.");
3331 return (ERROR + HIGHER_ACCESS_REQUIRED);
3334 strcpy(errmsgbuf, "Ok");
3340 * Check to see if the specified user has Internet mail permission
3341 * (returns nonzero if permission is granted)
3343 int CtdlCheckInternetMailPermission(struct ctdluser *who) {
3345 /* Do not allow twits to send Internet mail */
3346 if (who->axlevel <= 2) return(0);
3348 /* Globally enabled? */
3349 if (config.c_restrict == 0) return(1);
3351 /* User flagged ok? */
3352 if (who->flags & US_INTERNET) return(2);
3354 /* Aide level access? */
3355 if (who->axlevel >= 6) return(3);
3357 /* No mail for you! */
3363 * Validate recipients, count delivery types and errors, and handle aliasing
3364 * FIXME check for dupes!!!!!
3366 * Returns 0 if all addresses are ok, ret->num_error = -1 if no addresses
3367 * were specified, or the number of addresses found invalid.
3369 * Caller needs to free the result using free_recipients()
3371 struct recptypes *validate_recipients(char *supplied_recipients,
3372 const char *RemoteIdentifier,
3374 struct recptypes *ret;
3375 char *recipients = NULL;
3376 char this_recp[256];
3377 char this_recp_cooked[256];
3383 struct ctdluser tempUS;
3384 struct ctdlroom tempQR;
3385 struct ctdlroom tempQR2;
3391 ret = (struct recptypes *) malloc(sizeof(struct recptypes));
3392 if (ret == NULL) return(NULL);
3394 /* Set all strings to null and numeric values to zero */
3395 memset(ret, 0, sizeof(struct recptypes));
3397 if (supplied_recipients == NULL) {
3398 recipients = strdup("");
3401 recipients = strdup(supplied_recipients);
3404 /* Allocate some memory. Yes, this allocates 500% more memory than we will
3405 * actually need, but it's healthier for the heap than doing lots of tiny
3406 * realloc() calls instead.
3409 ret->errormsg = malloc(strlen(recipients) + 1024);
3410 ret->recp_local = malloc(strlen(recipients) + 1024);
3411 ret->recp_internet = malloc(strlen(recipients) + 1024);
3412 ret->recp_ignet = malloc(strlen(recipients) + 1024);
3413 ret->recp_room = malloc(strlen(recipients) + 1024);
3414 ret->display_recp = malloc(strlen(recipients) + 1024);
3416 ret->errormsg[0] = 0;
3417 ret->recp_local[0] = 0;
3418 ret->recp_internet[0] = 0;
3419 ret->recp_ignet[0] = 0;
3420 ret->recp_room[0] = 0;
3421 ret->display_recp[0] = 0;
3423 ret->recptypes_magic = RECPTYPES_MAGIC;
3425 /* Change all valid separator characters to commas */
3426 for (i=0; !IsEmptyStr(&recipients[i]); ++i) {
3427 if ((recipients[i] == ';') || (recipients[i] == '|')) {
3428 recipients[i] = ',';
3432 /* Now start extracting recipients... */
3434 while (!IsEmptyStr(recipients)) {
3436 for (i=0; i<=strlen(recipients); ++i) {
3437 if (recipients[i] == '\"') in_quotes = 1 - in_quotes;
3438 if ( ( (recipients[i] == ',') && (!in_quotes) ) || (recipients[i] == 0) ) {
3439 safestrncpy(this_recp, recipients, i+1);
3441 if (recipients[i] == ',') {
3442 strcpy(recipients, &recipients[i+1]);
3445 strcpy(recipients, "");
3452 if (IsEmptyStr(this_recp))
3454 CtdlLogPrintf(CTDL_DEBUG, "Evaluating recipient #%d: %s\n", num_recps, this_recp);
3456 mailtype = alias(this_recp);
3457 mailtype = alias(this_recp);
3458 mailtype = alias(this_recp);
3460 for (j=0; !IsEmptyStr(&this_recp[j]); ++j) {
3461 if (this_recp[j]=='_') {
3462 this_recp_cooked[j] = ' ';
3465 this_recp_cooked[j] = this_recp[j];
3468 this_recp_cooked[j] = '\0';
3473 if (!strcasecmp(this_recp, "sysop")) {
3475 strcpy(this_recp, config.c_aideroom);
3476 if (!IsEmptyStr(ret->recp_room)) {
3477 strcat(ret->recp_room, "|");
3479 strcat(ret->recp_room, this_recp);
3481 else if ( (!strncasecmp(this_recp, "room_", 5))
3482 && (!getroom(&tempQR, &this_recp_cooked[5])) ) {
3484 /* Save room so we can restore it later */
3488 /* Check permissions to send mail to this room */
3489 err = CtdlDoIHavePermissionToPostInThisRoom(errmsg,
3501 if (!IsEmptyStr(ret->recp_room)) {
3502 strcat(ret->recp_room, "|");
3504 strcat(ret->recp_room, &this_recp_cooked[5]);
3507 /* Restore room in case something needs it */
3511 else if (getuser(&tempUS, this_recp) == 0) {
3513 strcpy(this_recp, tempUS.fullname);
3514 if (!IsEmptyStr(ret->recp_local)) {
3515 strcat(ret->recp_local, "|");
3517 strcat(ret->recp_local, this_recp);
3519 else if (getuser(&tempUS, this_recp_cooked) == 0) {
3521 strcpy(this_recp, tempUS.fullname);
3522 if (!IsEmptyStr(ret->recp_local)) {
3523 strcat(ret->recp_local, "|");
3525 strcat(ret->recp_local, this_recp);
3533 /* Yes, you're reading this correctly: if the target
3534 * domain points back to the local system or an attached
3535 * Citadel directory, the address is invalid. That's
3536 * because if the address were valid, we would have
3537 * already translated it to a local address by now.
3539 if (IsDirectory(this_recp, 0)) {
3544 ++ret->num_internet;
3545 if (!IsEmptyStr(ret->recp_internet)) {
3546 strcat(ret->recp_internet, "|");
3548 strcat(ret->recp_internet, this_recp);
3553 if (!IsEmptyStr(ret->recp_ignet)) {
3554 strcat(ret->recp_ignet, "|");
3556 strcat(ret->recp_ignet, this_recp);
3564 if (IsEmptyStr(errmsg)) {
3565 snprintf(append, sizeof append, "Invalid recipient: %s", this_recp);
3568 snprintf(append, sizeof append, "%s", errmsg);
3570 if ( (strlen(ret->errormsg) + strlen(append) + 3) < SIZ) {
3571 if (!IsEmptyStr(ret->errormsg)) {
3572 strcat(ret->errormsg, "; ");
3574 strcat(ret->errormsg, append);
3578 if (IsEmptyStr(ret->display_recp)) {
3579 strcpy(append, this_recp);
3582 snprintf(append, sizeof append, ", %s", this_recp);
3584 if ( (strlen(ret->display_recp)+strlen(append)) < SIZ) {
3585 strcat(ret->display_recp, append);
3590 if ((ret->num_local + ret->num_internet + ret->num_ignet +
3591 ret->num_room + ret->num_error) == 0) {
3592 ret->num_error = (-1);
3593 strcpy(ret->errormsg, "No recipients specified.");
3596 CtdlLogPrintf(CTDL_DEBUG, "validate_recipients()\n");
3597 CtdlLogPrintf(CTDL_DEBUG, " local: %d <%s>\n", ret->num_local, ret->recp_local);
3598 CtdlLogPrintf(CTDL_DEBUG, " room: %d <%s>\n", ret->num_room, ret->recp_room);
3599 CtdlLogPrintf(CTDL_DEBUG, " inet: %d <%s>\n", ret->num_internet, ret->recp_internet);
3600 CtdlLogPrintf(CTDL_DEBUG, " ignet: %d <%s>\n", ret->num_ignet, ret->recp_ignet);
3601 CtdlLogPrintf(CTDL_DEBUG, " error: %d <%s>\n", ret->num_error, ret->errormsg);
3609 * Destructor for struct recptypes
3611 void free_recipients(struct recptypes *valid) {
3613 if (valid == NULL) {
3617 if (valid->recptypes_magic != RECPTYPES_MAGIC) {
3618 CtdlLogPrintf(CTDL_EMERG, "Attempt to call free_recipients() on some other data type!\n");
3622 if (valid->errormsg != NULL) free(valid->errormsg);
3623 if (valid->recp_local != NULL) free(valid->recp_local);
3624 if (valid->recp_internet != NULL) free(valid->recp_internet);
3625 if (valid->recp_ignet != NULL) free(valid->recp_ignet);
3626 if (valid->recp_room != NULL) free(valid->recp_room);
3627 if (valid->display_recp != NULL) free(valid->display_recp);
3634 * message entry - mode 0 (normal)
3636 void cmd_ent0(char *entargs)
3642 char supplied_euid[128];
3644 int format_type = 0;
3645 char newusername[256];
3646 char newuseremail[256];
3647 struct CtdlMessage *msg;
3651 struct recptypes *valid = NULL;
3652 struct recptypes *valid_to = NULL;
3653 struct recptypes *valid_cc = NULL;
3654 struct recptypes *valid_bcc = NULL;
3656 int subject_required = 0;
3661 int newuseremail_ok = 0;
3662 char references[SIZ];
3667 post = extract_int(entargs, 0);
3668 extract_token(recp, entargs, 1, '|', sizeof recp);
3669 anon_flag = extract_int(entargs, 2);
3670 format_type = extract_int(entargs, 3);
3671 extract_token(subject, entargs, 4, '|', sizeof subject);
3672 extract_token(newusername, entargs, 5, '|', sizeof newusername);
3673 do_confirm = extract_int(entargs, 6);
3674 extract_token(cc, entargs, 7, '|', sizeof cc);
3675 extract_token(bcc, entargs, 8, '|', sizeof bcc);
3676 switch(CC->room.QRdefaultview) {
3679 extract_token(supplied_euid, entargs, 9, '|', sizeof supplied_euid);
3682 supplied_euid[0] = 0;
3685 extract_token(newuseremail, entargs, 10, '|', sizeof newuseremail);
3686 extract_token(references, entargs, 11, '|', sizeof references);
3687 for (ptr=references; *ptr != 0; ++ptr) {
3688 if (*ptr == '!') *ptr = '|';
3691 /* first check to make sure the request is valid. */
3693 err = CtdlDoIHavePermissionToPostInThisRoom(errmsg, sizeof errmsg, NULL, POST_LOGGED_IN);
3696 cprintf("%d %s\n", err, errmsg);
3700 /* Check some other permission type things. */
3702 if (IsEmptyStr(newusername)) {
3703 strcpy(newusername, CC->user.fullname);
3705 if ( (CC->user.axlevel < 6)
3706 && (strcasecmp(newusername, CC->user.fullname))
3707 && (strcasecmp(newusername, CC->cs_inet_fn))
3709 cprintf("%d You don't have permission to author messages as '%s'.\n",
3710 ERROR + HIGHER_ACCESS_REQUIRED,
3717 if (IsEmptyStr(newuseremail)) {
3718 newuseremail_ok = 1;
3721 if (!IsEmptyStr(newuseremail)) {
3722 if (!strcasecmp(newuseremail, CC->cs_inet_email)) {
3723 newuseremail_ok = 1;
3725 else if (!IsEmptyStr(CC->cs_inet_other_emails)) {
3726 j = num_tokens(CC->cs_inet_other_emails, '|');
3727 for (i=0; i<j; ++i) {
3728 extract_token(buf, CC->cs_inet_other_emails, i, '|', sizeof buf);
3729 if (!strcasecmp(newuseremail, buf)) {
3730 newuseremail_ok = 1;
3736 if (!newuseremail_ok) {
3737 cprintf("%d You don't have permission to author messages as '%s'.\n",
3738 ERROR + HIGHER_ACCESS_REQUIRED,
3744 CC->cs_flags |= CS_POSTING;
3746 /* In mailbox rooms we have to behave a little differently --
3747 * make sure the user has specified at least one recipient. Then
3748 * validate the recipient(s). We do this for the Mail> room, as
3749 * well as any room which has the "Mailbox" view set.
3752 if ( ( (CC->room.QRflags & QR_MAILBOX) && (!strcasecmp(&CC->room.QRname[11], MAILROOM)) )
3753 || ( (CC->room.QRflags & QR_MAILBOX) && (CC->curr_view == VIEW_MAILBOX) )
3755 if (CC->user.axlevel < 2) {
3756 strcpy(recp, "sysop");
3761 valid_to = validate_recipients(recp, NULL, 0);
3762 if (valid_to->num_error > 0) {
3763 cprintf("%d %s\n", ERROR + NO_SUCH_USER, valid_to->errormsg);
3764 free_recipients(valid_to);
3768 valid_cc = validate_recipients(cc, NULL, 0);
3769 if (valid_cc->num_error > 0) {
3770 cprintf("%d %s\n", ERROR + NO_SUCH_USER, valid_cc->errormsg);
3771 free_recipients(valid_to);
3772 free_recipients(valid_cc);
3776 valid_bcc = validate_recipients(bcc, NULL, 0);
3777 if (valid_bcc->num_error > 0) {
3778 cprintf("%d %s\n", ERROR + NO_SUCH_USER, valid_bcc->errormsg);
3779 free_recipients(valid_to);
3780 free_recipients(valid_cc);
3781 free_recipients(valid_bcc);
3785 /* Recipient required, but none were specified */
3786 if ( (valid_to->num_error < 0) && (valid_cc->num_error < 0) && (valid_bcc->num_error < 0) ) {
3787 free_recipients(valid_to);
3788 free_recipients(valid_cc);
3789 free_recipients(valid_bcc);
3790 cprintf("%d At least one recipient is required.\n", ERROR + NO_SUCH_USER);
3794 if (valid_to->num_internet + valid_cc->num_internet + valid_bcc->num_internet > 0) {
3795 if (CtdlCheckInternetMailPermission(&CC->user)==0) {
3796 cprintf("%d You do not have permission "
3797 "to send Internet mail.\n",
3798 ERROR + HIGHER_ACCESS_REQUIRED);
3799 free_recipients(valid_to);
3800 free_recipients(valid_cc);
3801 free_recipients(valid_bcc);
3806 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)
3807 && (CC->user.axlevel < 4) ) {
3808 cprintf("%d Higher access required for network mail.\n",
3809 ERROR + HIGHER_ACCESS_REQUIRED);
3810 free_recipients(valid_to);
3811 free_recipients(valid_cc);
3812 free_recipients(valid_bcc);
3816 if ((RESTRICT_INTERNET == 1)
3817 && (valid_to->num_internet + valid_cc->num_internet + valid_bcc->num_internet > 0)
3818 && ((CC->user.flags & US_INTERNET) == 0)
3819 && (!CC->internal_pgm)) {
3820 cprintf("%d You don't have access to Internet mail.\n",
3821 ERROR + HIGHER_ACCESS_REQUIRED);
3822 free_recipients(valid_to);
3823 free_recipients(valid_cc);
3824 free_recipients(valid_bcc);
3830 /* Is this a room which has anonymous-only or anonymous-option? */
3831 anonymous = MES_NORMAL;
3832 if (CC->room.QRflags & QR_ANONONLY) {
3833 anonymous = MES_ANONONLY;
3835 if (CC->room.QRflags & QR_ANONOPT) {
3836 if (anon_flag == 1) { /* only if the user requested it */
3837 anonymous = MES_ANONOPT;
3841 if ((CC->room.QRflags & QR_MAILBOX) == 0) {
3845 /* Recommend to the client that the use of a message subject is
3846 * strongly recommended in this room, if either the SUBJECTREQ flag
3847 * is set, or if there is one or more Internet email recipients.
3849 if (CC->room.QRflags2 & QR2_SUBJECTREQ) subject_required = 1;
3850 if (valid_to) if (valid_to->num_internet > 0) subject_required = 1;
3851 if (valid_cc) if (valid_cc->num_internet > 0) subject_required = 1;
3852 if (valid_bcc) if (valid_bcc->num_internet > 0) subject_required = 1;
3854 /* If we're only checking the validity of the request, return
3855 * success without creating the message.
3858 cprintf("%d %s|%d\n", CIT_OK,
3859 ((valid_to != NULL) ? valid_to->display_recp : ""),
3861 free_recipients(valid_to);
3862 free_recipients(valid_cc);
3863 free_recipients(valid_bcc);
3867 /* We don't need these anymore because we'll do it differently below */
3868 free_recipients(valid_to);
3869 free_recipients(valid_cc);
3870 free_recipients(valid_bcc);
3872 /* Read in the message from the client. */
3874 cprintf("%d send message\n", START_CHAT_MODE);
3876 cprintf("%d send message\n", SEND_LISTING);
3879 msg = CtdlMakeMessage(&CC->user, recp, cc,
3880 CC->room.QRname, anonymous, format_type,
3881 newusername, newuseremail, subject,
3882 ((!IsEmptyStr(supplied_euid)) ? supplied_euid : NULL),
3885 /* Put together one big recipients struct containing to/cc/bcc all in
3886 * one. This is for the envelope.
3888 char *all_recps = malloc(SIZ * 3);
3889 strcpy(all_recps, recp);
3890 if (!IsEmptyStr(cc)) {
3891 if (!IsEmptyStr(all_recps)) {
3892 strcat(all_recps, ",");
3894 strcat(all_recps, cc);
3896 if (!IsEmptyStr(bcc)) {
3897 if (!IsEmptyStr(all_recps)) {
3898 strcat(all_recps, ",");
3900 strcat(all_recps, bcc);
3902 if (!IsEmptyStr(all_recps)) {
3903 valid = validate_recipients(all_recps, NULL, 0);
3911 msgnum = CtdlSubmitMsg(msg, valid, "", QP_EADDR);
3914 cprintf("%ld\n", msgnum);
3916 cprintf("Message accepted.\n");
3919 cprintf("Internal error.\n");
3921 if (msg->cm_fields['E'] != NULL) {
3922 cprintf("%s\n", msg->cm_fields['E']);
3929 CtdlFreeMessage(msg);
3931 if (valid != NULL) {
3932 free_recipients(valid);
3940 * API function to delete messages which match a set of criteria
3941 * (returns the actual number of messages deleted)
3943 int CtdlDeleteMessages(char *room_name, /* which room */
3944 long *dmsgnums, /* array of msg numbers to be deleted */
3945 int num_dmsgnums, /* number of msgs to be deleted, or 0 for "any" */
3946 char *content_type /* or "" for any. regular expressions expected. */
3949 struct ctdlroom qrbuf;
3950 struct cdbdata *cdbfr;
3951 long *msglist = NULL;
3952 long *dellist = NULL;
3955 int num_deleted = 0;
3957 struct MetaData smi;
3960 int need_to_free_re = 0;
3962 if (content_type) if (!IsEmptyStr(content_type)) {
3963 regcomp(&re, content_type, 0);
3964 need_to_free_re = 1;
3966 CtdlLogPrintf(CTDL_DEBUG, "CtdlDeleteMessages(%s, %d msgs, %s)\n",
3967 room_name, num_dmsgnums, content_type);
3969 /* get room record, obtaining a lock... */
3970 if (lgetroom(&qrbuf, room_name) != 0) {
3971 CtdlLogPrintf(CTDL_ERR, "CtdlDeleteMessages(): Room <%s> not found\n",
3973 if (need_to_free_re) regfree(&re);
3974 return (0); /* room not found */
3976 cdbfr = cdb_fetch(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long));
3978 if (cdbfr != NULL) {
3979 dellist = malloc(cdbfr->len);
3980 msglist = (long *) cdbfr->ptr;
3981 cdbfr->ptr = NULL; /* CtdlDeleteMessages() now owns this memory */
3982 num_msgs = cdbfr->len / sizeof(long);
3986 for (i = 0; i < num_msgs; ++i) {
3989 /* Set/clear a bit for each criterion */
3991 /* 0 messages in the list or a null list means that we are
3992 * interested in deleting any messages which meet the other criteria.
3994 if ((num_dmsgnums == 0) || (dmsgnums == NULL)) {
3995 delete_this |= 0x01;
3998 for (j=0; j<num_dmsgnums; ++j) {
3999 if (msglist[i] == dmsgnums[j]) {
4000 delete_this |= 0x01;
4005 if (IsEmptyStr(content_type)) {
4006 delete_this |= 0x02;
4008 GetMetaData(&smi, msglist[i]);
4009 if (regexec(&re, smi.meta_content_type, 1, &pm, 0) == 0) {
4010 delete_this |= 0x02;
4014 /* Delete message only if all bits are set */
4015 if (delete_this == 0x03) {
4016 dellist[num_deleted++] = msglist[i];
4021 num_msgs = sort_msglist(msglist, num_msgs);
4022 cdb_store(CDB_MSGLISTS, &qrbuf.QRnumber, (int)sizeof(long),
4023 msglist, (int)(num_msgs * sizeof(long)));
4025 qrbuf.QRhighest = msglist[num_msgs - 1];
4029 /* Go through the messages we pulled out of the index, and decrement
4030 * their reference counts by 1. If this is the only room the message
4031 * was in, the reference count will reach zero and the message will
4032 * automatically be deleted from the database. We do this in a
4033 * separate pass because there might be plug-in hooks getting called,
4034 * and we don't want that happening during an S_ROOMS critical
4037 if (num_deleted) for (i=0; i<num_deleted; ++i) {
4038 PerformDeleteHooks(qrbuf.QRname, dellist[i]);
4039 AdjRefCount(dellist[i], -1);
4042 /* Now free the memory we used, and go away. */
4043 if (msglist != NULL) free(msglist);
4044 if (dellist != NULL) free(dellist);
4045 CtdlLogPrintf(CTDL_DEBUG, "%d message(s) deleted.\n", num_deleted);
4046 if (need_to_free_re) regfree(&re);
4047 return (num_deleted);
4053 * Check whether the current user has permission to delete messages from
4054 * the current room (returns 1 for yes, 0 for no)
4056 int CtdlDoIHavePermissionToDeleteMessagesFromThisRoom(void) {
4058 CtdlRoomAccess(&CC->room, &CC->user, &ra, NULL);
4059 if (ra & UA_DELETEALLOWED) return(1);
4067 * Delete message from current room
4069 void cmd_dele(char *args)
4078 extract_token(msgset, args, 0, '|', sizeof msgset);
4079 num_msgs = num_tokens(msgset, ',');
4081 cprintf("%d Nothing to do.\n", CIT_OK);
4085 if (CtdlDoIHavePermissionToDeleteMessagesFromThisRoom() == 0) {
4086 cprintf("%d Higher access required.\n",
4087 ERROR + HIGHER_ACCESS_REQUIRED);
4092 * Build our message set to be moved/copied
4094 msgs = malloc(num_msgs * sizeof(long));
4095 for (i=0; i<num_msgs; ++i) {
4096 extract_token(msgtok, msgset, i, ',', sizeof msgtok);
4097 msgs[i] = atol(msgtok);
4100 num_deleted = CtdlDeleteMessages(CC->room.QRname, msgs, num_msgs, "");
4104 cprintf("%d %d message%s deleted.\n", CIT_OK,
4105 num_deleted, ((num_deleted != 1) ? "s" : ""));
4107 cprintf("%d Message not found.\n", ERROR + MESSAGE_NOT_FOUND);
4113 * Back end API function for moves and deletes (multiple messages)
4115 int CtdlCopyMsgsToRoom(long *msgnums, int num_msgs, char *dest) {
4118 err = CtdlSaveMsgPointersInRoom(dest, msgnums, num_msgs, 1, NULL);
4119 if (err != 0) return(err);
4128 * move or copy a message to another room
4130 void cmd_move(char *args)
4137 char targ[ROOMNAMELEN];
4138 struct ctdlroom qtemp;
4145 extract_token(msgset, args, 0, '|', sizeof msgset);
4146 num_msgs = num_tokens(msgset, ',');
4148 cprintf("%d Nothing to do.\n", CIT_OK);
4152 extract_token(targ, args, 1, '|', sizeof targ);
4153 convert_room_name_macros(targ, sizeof targ);
4154 targ[ROOMNAMELEN - 1] = 0;
4155 is_copy = extract_int(args, 2);
4157 if (getroom(&qtemp, targ) != 0) {
4158 cprintf("%d '%s' does not exist.\n",
4159 ERROR + ROOM_NOT_FOUND, targ);
4163 getuser(&CC->user, CC->curr_user);
4164 CtdlRoomAccess(&qtemp, &CC->user, &ra, NULL);
4166 /* Check for permission to perform this operation.
4167 * Remember: "CC->room" is source, "qtemp" is target.
4171 /* Aides can move/copy */
4172 if (CC->user.axlevel >= 6) permit = 1;
4174 /* Room aides can move/copy */
4175 if (CC->user.usernum == CC->room.QRroomaide) permit = 1;
4177 /* Permit move/copy from personal rooms */
4178 if ((CC->room.QRflags & QR_MAILBOX)
4179 && (qtemp.QRflags & QR_MAILBOX)) permit = 1;
4181 /* Permit only copy from public to personal room */
4183 && (!(CC->room.QRflags & QR_MAILBOX))
4184 && (qtemp.QRflags & QR_MAILBOX)) permit = 1;
4186 /* Permit message removal from collaborative delete rooms */
4187 if (CC->room.QRflags2 & QR2_COLLABDEL) permit = 1;
4189 /* Users allowed to post into the target room may move into it too. */
4190 if ((CC->room.QRflags & QR_MAILBOX) &&
4191 (qtemp.QRflags & UA_POSTALLOWED)) permit = 1;
4193 /* User must have access to target room */
4194 if (!(ra & UA_KNOWN)) permit = 0;
4197 cprintf("%d Higher access required.\n",
4198 ERROR + HIGHER_ACCESS_REQUIRED);
4203 * Build our message set to be moved/copied
4205 msgs = malloc(num_msgs * sizeof(long));
4206 for (i=0; i<num_msgs; ++i) {
4207 extract_token(msgtok, msgset, i, ',', sizeof msgtok);
4208 msgs[i] = atol(msgtok);
4214 err = CtdlCopyMsgsToRoom(msgs, num_msgs, targ);
4216 cprintf("%d Cannot store message(s) in %s: error %d\n",
4222 /* Now delete the message from the source room,
4223 * if this is a 'move' rather than a 'copy' operation.
4226 CtdlDeleteMessages(CC->room.QRname, msgs, num_msgs, "");
4230 cprintf("%d Message(s) %s.\n", CIT_OK, (is_copy ? "copied" : "moved") );
4236 * GetMetaData() - Get the supplementary record for a message
4238 void GetMetaData(struct MetaData *smibuf, long msgnum)
4241 struct cdbdata *cdbsmi;
4244 memset(smibuf, 0, sizeof(struct MetaData));
4245 smibuf->meta_msgnum = msgnum;
4246 smibuf->meta_refcount = 1; /* Default reference count is 1 */
4248 /* Use the negative of the message number for its supp record index */
4249 TheIndex = (0L - msgnum);
4251 cdbsmi = cdb_fetch(CDB_MSGMAIN, &TheIndex, sizeof(long));
4252 if (cdbsmi == NULL) {
4253 return; /* record not found; go with defaults */
4255 memcpy(smibuf, cdbsmi->ptr,
4256 ((cdbsmi->len > sizeof(struct MetaData)) ?
4257 sizeof(struct MetaData) : cdbsmi->len));
4264 * PutMetaData() - (re)write supplementary record for a message
4266 void PutMetaData(struct MetaData *smibuf)
4270 /* Use the negative of the message number for the metadata db index */
4271 TheIndex = (0L - smibuf->meta_msgnum);
4273 cdb_store(CDB_MSGMAIN,
4274 &TheIndex, (int)sizeof(long),
4275 smibuf, (int)sizeof(struct MetaData));
4280 * AdjRefCount - submit an adjustment to the reference count for a message.
4281 * (These are just queued -- we actually process them later.)
4283 void AdjRefCount(long msgnum, int incr)
4285 struct arcq new_arcq;
4287 begin_critical_section(S_SUPPMSGMAIN);
4288 if (arcfp == NULL) {
4289 arcfp = fopen(file_arcq, "ab+");
4291 end_critical_section(S_SUPPMSGMAIN);
4293 /* msgnum < 0 means that we're trying to close the file */
4295 CtdlLogPrintf(CTDL_DEBUG, "Closing the AdjRefCount queue file\n");
4296 begin_critical_section(S_SUPPMSGMAIN);
4297 if (arcfp != NULL) {
4301 end_critical_section(S_SUPPMSGMAIN);
4306 * If we can't open the queue, perform the operation synchronously.
4308 if (arcfp == NULL) {
4309 TDAP_AdjRefCount(msgnum, incr);
4313 new_arcq.arcq_msgnum = msgnum;
4314 new_arcq.arcq_delta = incr;
4315 fwrite(&new_arcq, sizeof(struct arcq), 1, arcfp);
4323 * TDAP_ProcessAdjRefCountQueue()
4325 * Process the queue of message count adjustments that was created by calls
4326 * to AdjRefCount() ... by reading the queue and calling TDAP_AdjRefCount()
4327 * for each one. This should be an "off hours" operation.
4329 int TDAP_ProcessAdjRefCountQueue(void)
4331 char file_arcq_temp[PATH_MAX];
4334 struct arcq arcq_rec;
4335 int num_records_processed = 0;
4337 snprintf(file_arcq_temp, sizeof file_arcq_temp, "%s2", file_arcq);
4339 begin_critical_section(S_SUPPMSGMAIN);
4340 if (arcfp != NULL) {
4345 r = link(file_arcq, file_arcq_temp);
4347 CtdlLogPrintf(CTDL_CRIT, "%s: %s\n", file_arcq_temp, strerror(errno));
4348 end_critical_section(S_SUPPMSGMAIN);
4349 return(num_records_processed);
4353 end_critical_section(S_SUPPMSGMAIN);
4355 fp = fopen(file_arcq_temp, "rb");
4357 CtdlLogPrintf(CTDL_CRIT, "%s: %s\n", file_arcq_temp, strerror(errno));
4358 return(num_records_processed);
4361 while (fread(&arcq_rec, sizeof(struct arcq), 1, fp) == 1) {
4362 TDAP_AdjRefCount(arcq_rec.arcq_msgnum, arcq_rec.arcq_delta);
4363 ++num_records_processed;
4367 r = unlink(file_arcq_temp);
4369 CtdlLogPrintf(CTDL_CRIT, "%s: %s\n", file_arcq_temp, strerror(errno));
4372 return(num_records_processed);
4378 * TDAP_AdjRefCount - adjust the reference count for a message.
4379 * This one does it "for real" because it's called by
4380 * the autopurger function that processes the queue
4381 * created by AdjRefCount(). If a message's reference
4382 * count becomes zero, we also delete the message from
4383 * disk and de-index it.
4385 void TDAP_AdjRefCount(long msgnum, int incr)
4388 struct MetaData smi;
4391 /* This is a *tight* critical section; please keep it that way, as
4392 * it may get called while nested in other critical sections.
4393 * Complicating this any further will surely cause deadlock!
4395 begin_critical_section(S_SUPPMSGMAIN);
4396 GetMetaData(&smi, msgnum);
4397 smi.meta_refcount += incr;
4399 end_critical_section(S_SUPPMSGMAIN);
4400 CtdlLogPrintf(CTDL_DEBUG, "msg %ld ref count delta %d, is now %d\n",
4401 msgnum, incr, smi.meta_refcount);
4403 /* If the reference count is now zero, delete the message
4404 * (and its supplementary record as well).
4406 if (smi.meta_refcount == 0) {
4407 CtdlLogPrintf(CTDL_DEBUG, "Deleting message <%ld>\n", msgnum);
4409 /* Call delete hooks with NULL room to show it has gone altogether */
4410 PerformDeleteHooks(NULL, msgnum);
4412 /* Remove from message base */
4414 cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long));
4415 cdb_delete(CDB_BIGMSGS, &delnum, (int)sizeof(long));
4417 /* Remove metadata record */
4418 delnum = (0L - msgnum);
4419 cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long));
4425 * Write a generic object to this room
4427 * Note: this could be much more efficient. Right now we use two temporary
4428 * files, and still pull the message into memory as with all others.
4430 void CtdlWriteObject(char *req_room, /* Room to stuff it in */
4431 char *content_type, /* MIME type of this object */
4432 char *raw_message, /* Data to be written */
4433 off_t raw_length, /* Size of raw_message */
4434 struct ctdluser *is_mailbox, /* Mailbox room? */
4435 int is_binary, /* Is encoding necessary? */
4436 int is_unique, /* Del others of this type? */
4437 unsigned int flags /* Internal save flags */
4441 struct ctdlroom qrbuf;
4442 char roomname[ROOMNAMELEN];
4443 struct CtdlMessage *msg;
4444 char *encoded_message = NULL;
4446 if (is_mailbox != NULL) {
4447 MailboxName(roomname, sizeof roomname, is_mailbox, req_room);
4450 safestrncpy(roomname, req_room, sizeof(roomname));
4453 CtdlLogPrintf(CTDL_DEBUG, "Raw length is %ld\n", (long)raw_length);
4456 encoded_message = malloc((size_t) (((raw_length * 134) / 100) + 4096 ) );
4459 encoded_message = malloc((size_t)(raw_length + 4096));
4462 sprintf(encoded_message, "Content-type: %s\n", content_type);
4465 sprintf(&encoded_message[strlen(encoded_message)],
4466 "Content-transfer-encoding: base64\n\n"
4470 sprintf(&encoded_message[strlen(encoded_message)],
4471 "Content-transfer-encoding: 7bit\n\n"
4477 &encoded_message[strlen(encoded_message)],
4485 &encoded_message[strlen(encoded_message)],
4491 CtdlLogPrintf(CTDL_DEBUG, "Allocating\n");
4492 msg = malloc(sizeof(struct CtdlMessage));
4493 memset(msg, 0, sizeof(struct CtdlMessage));
4494 msg->cm_magic = CTDLMESSAGE_MAGIC;
4495 msg->cm_anon_type = MES_NORMAL;
4496 msg->cm_format_type = 4;
4497 msg->cm_fields['A'] = strdup(CC->user.fullname);
4498 msg->cm_fields['O'] = strdup(req_room);
4499 msg->cm_fields['N'] = strdup(config.c_nodename);
4500 msg->cm_fields['H'] = strdup(config.c_humannode);
4501 msg->cm_flags = flags;
4503 msg->cm_fields['M'] = encoded_message;
4505 /* Create the requested room if we have to. */
4506 if (getroom(&qrbuf, roomname) != 0) {
4507 create_room(roomname,
4508 ( (is_mailbox != NULL) ? 5 : 3 ),
4509 "", 0, 1, 0, VIEW_BBS);
4511 /* If the caller specified this object as unique, delete all
4512 * other objects of this type that are currently in the room.
4515 CtdlLogPrintf(CTDL_DEBUG, "Deleted %d other msgs of this type\n",
4516 CtdlDeleteMessages(roomname, NULL, 0, content_type)
4519 /* Now write the data */
4520 CtdlSubmitMsg(msg, NULL, roomname, 0);
4521 CtdlFreeMessage(msg);
4529 void CtdlGetSysConfigBackend(long msgnum, void *userdata) {
4530 config_msgnum = msgnum;
4534 char *CtdlGetSysConfig(char *sysconfname) {
4535 char hold_rm[ROOMNAMELEN];
4538 struct CtdlMessage *msg;
4541 strcpy(hold_rm, CC->room.QRname);
4542 if (getroom(&CC->room, SYSCONFIGROOM) != 0) {
4543 getroom(&CC->room, hold_rm);
4548 /* We want the last (and probably only) config in this room */
4549 begin_critical_section(S_CONFIG);
4550 config_msgnum = (-1L);
4551 CtdlForEachMessage(MSGS_LAST, 1, NULL, sysconfname, NULL,
4552 CtdlGetSysConfigBackend, NULL);
4553 msgnum = config_msgnum;
4554 end_critical_section(S_CONFIG);
4560 msg = CtdlFetchMessage(msgnum, 1);
4562 conf = strdup(msg->cm_fields['M']);
4563 CtdlFreeMessage(msg);
4570 getroom(&CC->room, hold_rm);
4572 if (conf != NULL) do {
4573 extract_token(buf, conf, 0, '\n', sizeof buf);
4574 strcpy(conf, &conf[strlen(buf)+1]);
4575 } while ( (!IsEmptyStr(conf)) && (!IsEmptyStr(buf)) );
4581 void CtdlPutSysConfig(char *sysconfname, char *sysconfdata) {
4582 CtdlWriteObject(SYSCONFIGROOM, sysconfname, sysconfdata, (strlen(sysconfdata)+1), NULL, 0, 1, 0);
4587 * Determine whether a given Internet address belongs to the current user
4589 int CtdlIsMe(char *addr, int addr_buf_len)
4591 struct recptypes *recp;
4594 recp = validate_recipients(addr, NULL, 0);
4595 if (recp == NULL) return(0);
4597 if (recp->num_local == 0) {
4598 free_recipients(recp);
4602 for (i=0; i<recp->num_local; ++i) {
4603 extract_token(addr, recp->recp_local, i, '|', addr_buf_len);
4604 if (!strcasecmp(addr, CC->user.fullname)) {
4605 free_recipients(recp);
4610 free_recipients(recp);
4616 * Citadel protocol command to do the same
4618 void cmd_isme(char *argbuf) {
4621 if (CtdlAccessCheck(ac_logged_in)) return;
4622 extract_token(addr, argbuf, 0, '|', sizeof addr);
4624 if (CtdlIsMe(addr, sizeof addr)) {
4625 cprintf("%d %s\n", CIT_OK, addr);
4628 cprintf("%d Not you.\n", ERROR + ILLEGAL_VALUE);