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;
1488 ptr = (long *) malloc(sizeof (long) * nAddrPtrMax * 2);
1489 memset(ptr + sizeof (long) * nAddrPtrMax, 0, sizeof (long) * nAddrPtrMax - 1);
1490 memcpy (ptr, AddrUtf8, sizeof (long) * nAddrPtrMax);
1491 free (AddrUtf8), AddrUtf8 = ptr;
1494 if (((unsigned char) source[i] < 32) ||
1495 ((unsigned char) source[i] > 126)) {
1497 AddrUtf8[nColons] = 1;
1499 if (source[i] == '"')
1500 InQuotes = !InQuotes;
1501 if (!InQuotes && source[i] == ',') {
1503 AddrPtr[nColons] = i;
1507 if (need_to_encode == 0) {
1514 EncodedMaxLen = nColons * (sizeof(headerStr) + 3) + SourceLen * 3;
1515 Encoded = (char*) malloc (EncodedMaxLen);
1517 for (i = 1; i <= nColons; i++)
1518 source[AddrPtr[i]++] = '\0';
1522 for (i = 0; i <= nColons && nPtr != NULL; i++) {
1523 nmax = EncodedMaxLen - (nPtr - Encoded);
1525 process_rfc822_addr(&source[AddrPtr[i]],
1529 /* TODO: libIDN here ! */
1530 if (IsEmptyStr(name)) {
1531 n = snprintf(nPtr, nmax,
1532 (i==0)?"%s@%s" : ",%s@%s",
1536 EncodedName = rfc2047encode(name, strlen(name));
1537 n = snprintf(nPtr, nmax,
1538 (i==0)?"%s <%s@%s>" : ",%s <%s@%s>",
1539 EncodedName, user, node);
1544 n = snprintf(nPtr, nmax,
1545 (i==0)?"%s" : ",%s",
1546 &source[AddrPtr[i]]);
1552 ptr = (char*) malloc(EncodedMaxLen * 2);
1553 memcpy(ptr, Encoded, EncodedMaxLen);
1554 nnPtr = ptr + (nPtr - Encoded), nPtr = nnPtr;
1555 free(Encoded), Encoded = ptr;
1557 i--; /* do it once more with properly lengthened buffer */
1560 for (i = 1; i <= nColons; i++)
1561 source[--AddrPtr[i]] = ',';
1569 * Get a message off disk. (returns om_* values found in msgbase.h)
1571 int CtdlOutputPreLoadedMsg(
1572 struct CtdlMessage *TheMessage,
1573 int mode, /* how would you like that message? */
1574 int headers_only, /* eschew the message body? */
1575 int do_proto, /* do Citadel protocol responses? */
1576 int crlf, /* Use CRLF newlines instead of LF? */
1577 int flags /* should the bessage be exported clean? */
1581 cit_uint8_t ch, prev_ch;
1583 char display_name[256];
1585 char *nl; /* newline string */
1587 int subject_found = 0;
1590 /* Buffers needed for RFC822 translation. These are all filled
1591 * using functions that are bounds-checked, and therefore we can
1592 * make them substantially smaller than SIZ.
1600 char datestamp[100];
1602 CtdlLogPrintf(CTDL_DEBUG, "CtdlOutputPreLoadedMsg(TheMessage=%s, %d, %d, %d, %d\n",
1603 ((TheMessage == NULL) ? "NULL" : "not null"),
1604 mode, headers_only, do_proto, crlf);
1606 strcpy(mid, "unknown");
1607 nl = (crlf ? "\r\n" : "\n");
1609 if (!is_valid_message(TheMessage)) {
1610 CtdlLogPrintf(CTDL_ERR,
1611 "ERROR: invalid preloaded message for output\n");
1612 return(om_no_such_msg);
1615 /* Are we downloading a MIME component? */
1616 if (mode == MT_DOWNLOAD) {
1617 if (TheMessage->cm_format_type != FMT_RFC822) {
1619 cprintf("%d This is not a MIME message.\n",
1620 ERROR + ILLEGAL_VALUE);
1621 } else if (CC->download_fp != NULL) {
1622 if (do_proto) cprintf(
1623 "%d You already have a download open.\n",
1624 ERROR + RESOURCE_BUSY);
1626 /* Parse the message text component */
1627 mptr = TheMessage->cm_fields['M'];
1628 mime_parser(mptr, NULL, *mime_download, NULL, NULL, NULL, 0);
1629 /* If there's no file open by this time, the requested
1630 * section wasn't found, so print an error
1632 if (CC->download_fp == NULL) {
1633 if (do_proto) cprintf(
1634 "%d Section %s not found.\n",
1635 ERROR + FILE_NOT_FOUND,
1636 CC->download_desired_section);
1639 return((CC->download_fp != NULL) ? om_ok : om_mime_error);
1642 /* MT_SPEW_SECTION is like MT_DOWNLOAD except it outputs the whole MIME part
1643 * in a single server operation instead of opening a download file.
1645 if (mode == MT_SPEW_SECTION) {
1646 if (TheMessage->cm_format_type != FMT_RFC822) {
1648 cprintf("%d This is not a MIME message.\n",
1649 ERROR + ILLEGAL_VALUE);
1651 /* Parse the message text component */
1654 mptr = TheMessage->cm_fields['M'];
1655 mime_parser(mptr, NULL, *mime_spew_section, NULL, NULL, (void *)&found_it, 0);
1656 /* If section wasn't found, print an error
1659 if (do_proto) cprintf(
1660 "%d Section %s not found.\n",
1661 ERROR + FILE_NOT_FOUND,
1662 CC->download_desired_section);
1665 return((CC->download_fp != NULL) ? om_ok : om_mime_error);
1668 /* now for the user-mode message reading loops */
1669 if (do_proto) cprintf("%d msg:\n", LISTING_FOLLOWS);
1671 /* Does the caller want to skip the headers? */
1672 if (headers_only == HEADERS_NONE) goto START_TEXT;
1674 /* Tell the client which format type we're using. */
1675 if ( (mode == MT_CITADEL) && (do_proto) ) {
1676 cprintf("type=%d\n", TheMessage->cm_format_type);
1679 /* nhdr=yes means that we're only displaying headers, no body */
1680 if ( (TheMessage->cm_anon_type == MES_ANONONLY)
1681 && (mode == MT_CITADEL)
1684 cprintf("nhdr=yes\n");
1687 /* begin header processing loop for Citadel message format */
1689 if ((mode == MT_CITADEL) || (mode == MT_MIME)) {
1691 safestrncpy(display_name, "<unknown>", sizeof display_name);
1692 if (TheMessage->cm_fields['A']) {
1693 strcpy(buf, TheMessage->cm_fields['A']);
1694 if (TheMessage->cm_anon_type == MES_ANONONLY) {
1695 safestrncpy(display_name, "****", sizeof display_name);
1697 else if (TheMessage->cm_anon_type == MES_ANONOPT) {
1698 safestrncpy(display_name, "anonymous", sizeof display_name);
1701 safestrncpy(display_name, buf, sizeof display_name);
1703 if ((is_room_aide())
1704 && ((TheMessage->cm_anon_type == MES_ANONONLY)
1705 || (TheMessage->cm_anon_type == MES_ANONOPT))) {
1706 size_t tmp = strlen(display_name);
1707 snprintf(&display_name[tmp],
1708 sizeof display_name - tmp,
1713 /* Don't show Internet address for users on the
1714 * local Citadel network.
1717 if (TheMessage->cm_fields['N'] != NULL)
1718 if (!IsEmptyStr(TheMessage->cm_fields['N']))
1719 if (haschar(TheMessage->cm_fields['N'], '.') == 0) {
1723 /* Now spew the header fields in the order we like them. */
1724 safestrncpy(allkeys, FORDER, sizeof allkeys);
1725 for (i=0; i<strlen(allkeys); ++i) {
1726 k = (int) allkeys[i];
1728 if ( (TheMessage->cm_fields[k] != NULL)
1729 && (msgkeys[k] != NULL) ) {
1731 if (do_proto) cprintf("%s=%s\n",
1735 else if ((k == 'F') && (suppress_f)) {
1738 /* Masquerade display name if needed */
1740 if (do_proto) cprintf("%s=%s\n",
1742 TheMessage->cm_fields[k]
1751 /* begin header processing loop for RFC822 transfer format */
1756 strcpy(snode, NODENAME);
1757 strcpy(lnode, HUMANNODE);
1758 if (mode == MT_RFC822) {
1759 for (i = 0; i < 256; ++i) {
1760 if (TheMessage->cm_fields[i]) {
1761 mptr = mpptr = TheMessage->cm_fields[i];
1764 safestrncpy(luser, mptr, sizeof luser);
1765 safestrncpy(suser, mptr, sizeof suser);
1767 else if (i == 'Y') {
1768 if ((flags & QP_EADDR) != 0)
1769 mptr = qp_encode_email_addrs(mptr);
1770 cprintf("CC: %s%s", mptr, nl);
1772 else if (i == 'P') {
1773 cprintf("Return-Path: %s%s", mptr, nl);
1775 else if (i == 'V') {
1776 if ((flags & QP_EADDR) != 0)
1777 mptr = qp_encode_email_addrs(mptr);
1778 cprintf("Envelope-To: %s%s", mptr, nl);
1780 else if (i == 'U') {
1781 cprintf("Subject: %s%s", mptr, nl);
1785 safestrncpy(mid, mptr, sizeof mid);
1787 safestrncpy(lnode, mptr, sizeof lnode);
1789 safestrncpy(fuser, mptr, sizeof fuser);
1790 /* else if (i == 'O')
1791 cprintf("X-Citadel-Room: %s%s",
1794 safestrncpy(snode, mptr, sizeof snode);
1797 if (haschar(mptr, '@') == 0)
1799 cprintf("To: %s@%s%s", mptr, config.c_fqdn, nl);
1803 if ((flags & QP_EADDR) != 0)
1804 mptr = qp_encode_email_addrs(mptr);
1805 cprintf("To: %s%s", mptr, nl);
1808 else if (i == 'T') {
1809 datestring(datestamp, sizeof datestamp,
1810 atol(mptr), DATESTRING_RFC822);
1811 cprintf("Date: %s%s", datestamp, nl);
1813 else if (i == 'W') {
1814 cprintf("References: ");
1815 k = num_tokens(mptr, '|');
1816 for (j=0; j<k; ++j) {
1817 extract_token(buf, mptr, j, '|', sizeof buf);
1818 cprintf("<%s>", buf);
1831 if (subject_found == 0) {
1832 cprintf("Subject: (no subject)%s", nl);
1836 for (i=0; !IsEmptyStr(&suser[i]); ++i) {
1837 suser[i] = tolower(suser[i]);
1838 if (!isalnum(suser[i])) suser[i]='_';
1841 if (mode == MT_RFC822) {
1842 if (!strcasecmp(snode, NODENAME)) {
1843 safestrncpy(snode, FQDN, sizeof snode);
1846 /* Construct a fun message id */
1847 cprintf("Message-ID: <%s", mid);/// todo: this possibly breaks threadding mails.
1848 if (strchr(mid, '@')==NULL) {
1849 cprintf("@%s", snode);
1853 if (!is_room_aide() && (TheMessage->cm_anon_type == MES_ANONONLY)) {
1854 cprintf("From: \"----\" <x@x.org>%s", nl);
1856 else if (!is_room_aide() && (TheMessage->cm_anon_type == MES_ANONOPT)) {
1857 cprintf("From: \"anonymous\" <x@x.org>%s", nl);
1859 else if (!IsEmptyStr(fuser)) {
1860 cprintf("From: \"%s\" <%s>%s", luser, fuser, nl);
1863 cprintf("From: \"%s\" <%s@%s>%s", luser, suser, snode, nl);
1866 cprintf("Organization: %s%s", lnode, nl);
1868 /* Blank line signifying RFC822 end-of-headers */
1869 if (TheMessage->cm_format_type != FMT_RFC822) {
1874 /* end header processing loop ... at this point, we're in the text */
1876 if (headers_only == HEADERS_FAST) goto DONE;
1877 mptr = TheMessage->cm_fields['M'];
1879 /* Tell the client about the MIME parts in this message */
1880 if (TheMessage->cm_format_type == FMT_RFC822) {
1881 if ( (mode == MT_CITADEL) || (mode == MT_MIME) ) {
1882 memset(&ma, 0, sizeof(struct ma_info));
1883 mime_parser(mptr, NULL,
1884 (do_proto ? *list_this_part : NULL),
1885 (do_proto ? *list_this_pref : NULL),
1886 (do_proto ? *list_this_suff : NULL),
1889 else if (mode == MT_RFC822) { /* unparsed RFC822 dump */
1890 char *start_of_text = NULL;
1891 start_of_text = strstr(mptr, "\n\r\n");
1892 if (start_of_text == NULL) start_of_text = strstr(mptr, "\n\n");
1893 if (start_of_text == NULL) start_of_text = mptr;
1895 start_of_text = strstr(start_of_text, "\n");
1900 int nllen = strlen(nl);
1902 while (ch=*mptr, ch!=0) {
1908 ((headers_only == HEADERS_NONE) && (mptr >= start_of_text))
1909 || ((headers_only == HEADERS_ONLY) && (mptr < start_of_text))
1910 || ((headers_only != HEADERS_NONE) && (headers_only != HEADERS_ONLY))
1913 sprintf(&outbuf[outlen], "%s", nl);
1917 outbuf[outlen++] = ch;
1921 if (flags & ESC_DOT)
1923 if ((prev_ch == 10) && (ch == '.') && ((*(mptr+1) == 13) || (*(mptr+1) == 10)))
1925 outbuf[outlen++] = '.';
1930 if (outlen > 1000) {
1931 client_write(outbuf, outlen);
1936 client_write(outbuf, outlen);
1944 if (headers_only == HEADERS_ONLY) {
1948 /* signify start of msg text */
1949 if ( (mode == MT_CITADEL) || (mode == MT_MIME) ) {
1950 if (do_proto) cprintf("text\n");
1953 /* If the format type on disk is 1 (fixed-format), then we want
1954 * everything to be output completely literally ... regardless of
1955 * what message transfer format is in use.
1957 if (TheMessage->cm_format_type == FMT_FIXED) {
1959 if (mode == MT_MIME) {
1960 cprintf("Content-type: text/plain\n\n");
1964 while (ch = *mptr++, ch > 0) {
1967 if ((ch == 10) || (buflen > 250)) {
1969 cprintf("%s%s", buf, nl);
1978 if (!IsEmptyStr(buf))
1979 cprintf("%s%s", buf, nl);
1982 /* If the message on disk is format 0 (Citadel vari-format), we
1983 * output using the formatter at 80 columns. This is the final output
1984 * form if the transfer format is RFC822, but if the transfer format
1985 * is Citadel proprietary, it'll still work, because the indentation
1986 * for new paragraphs is correct and the client will reformat the
1987 * message to the reader's screen width.
1989 if (TheMessage->cm_format_type == FMT_CITADEL) {
1990 if (mode == MT_MIME) {
1991 cprintf("Content-type: text/x-citadel-variformat\n\n");
1993 memfmout(mptr, 0, nl);
1996 /* If the message on disk is format 4 (MIME), we've gotta hand it
1997 * off to the MIME parser. The client has already been told that
1998 * this message is format 1 (fixed format), so the callback function
1999 * we use will display those parts as-is.
2001 if (TheMessage->cm_format_type == FMT_RFC822) {
2002 memset(&ma, 0, sizeof(struct ma_info));
2004 if (mode == MT_MIME) {
2005 ma.use_fo_hooks = 0;
2006 strcpy(ma.chosen_part, "1");
2007 ma.chosen_pref = 9999;
2008 mime_parser(mptr, NULL,
2009 *choose_preferred, *fixed_output_pre,
2010 *fixed_output_post, (void *)&ma, 0);
2011 mime_parser(mptr, NULL,
2012 *output_preferred, NULL, NULL, (void *)&ma, CC->msg4_dont_decode);
2015 ma.use_fo_hooks = 1;
2016 mime_parser(mptr, NULL,
2017 *fixed_output, *fixed_output_pre,
2018 *fixed_output_post, (void *)&ma, 0);
2023 DONE: /* now we're done */
2024 if (do_proto) cprintf("000\n");
2031 * display a message (mode 0 - Citadel proprietary)
2033 void cmd_msg0(char *cmdbuf)
2036 int headers_only = HEADERS_ALL;
2038 msgid = extract_long(cmdbuf, 0);
2039 headers_only = extract_int(cmdbuf, 1);
2041 CtdlOutputMsg(msgid, MT_CITADEL, headers_only, 1, 0, NULL, 0);
2047 * display a message (mode 2 - RFC822)
2049 void cmd_msg2(char *cmdbuf)
2052 int headers_only = HEADERS_ALL;
2054 msgid = extract_long(cmdbuf, 0);
2055 headers_only = extract_int(cmdbuf, 1);
2057 CtdlOutputMsg(msgid, MT_RFC822, headers_only, 1, 1, NULL, 0);
2063 * display a message (mode 3 - IGnet raw format - internal programs only)
2065 void cmd_msg3(char *cmdbuf)
2068 struct CtdlMessage *msg = NULL;
2071 if (CC->internal_pgm == 0) {
2072 cprintf("%d This command is for internal programs only.\n",
2073 ERROR + HIGHER_ACCESS_REQUIRED);
2077 msgnum = extract_long(cmdbuf, 0);
2078 msg = CtdlFetchMessage(msgnum, 1);
2080 cprintf("%d Message %ld not found.\n",
2081 ERROR + MESSAGE_NOT_FOUND, msgnum);
2085 serialize_message(&smr, msg);
2086 CtdlFreeMessage(msg);
2089 cprintf("%d Unable to serialize message\n",
2090 ERROR + INTERNAL_ERROR);
2094 cprintf("%d %ld\n", BINARY_FOLLOWS, (long)smr.len);
2095 client_write((char *)smr.ser, (int)smr.len);
2102 * Display a message using MIME content types
2104 void cmd_msg4(char *cmdbuf)
2109 msgid = extract_long(cmdbuf, 0);
2110 extract_token(section, cmdbuf, 1, '|', sizeof section);
2111 CtdlOutputMsg(msgid, MT_MIME, 0, 1, 0, (section[0] ? section : NULL) , 0);
2117 * Client tells us its preferred message format(s)
2119 void cmd_msgp(char *cmdbuf)
2121 if (!strcasecmp(cmdbuf, "dont_decode")) {
2122 CC->msg4_dont_decode = 1;
2123 cprintf("%d MSG4 will not pre-decode messages.\n", CIT_OK);
2126 safestrncpy(CC->preferred_formats, cmdbuf, sizeof(CC->preferred_formats));
2127 cprintf("%d Preferred MIME formats have been set.\n", CIT_OK);
2133 * Open a component of a MIME message as a download file
2135 void cmd_opna(char *cmdbuf)
2138 char desired_section[128];
2140 msgid = extract_long(cmdbuf, 0);
2141 extract_token(desired_section, cmdbuf, 1, '|', sizeof desired_section);
2142 safestrncpy(CC->download_desired_section, desired_section,
2143 sizeof CC->download_desired_section);
2144 CtdlOutputMsg(msgid, MT_DOWNLOAD, 0, 1, 1, NULL, 0);
2149 * Open a component of a MIME message and transmit it all at once
2151 void cmd_dlat(char *cmdbuf)
2154 char desired_section[128];
2156 msgid = extract_long(cmdbuf, 0);
2157 extract_token(desired_section, cmdbuf, 1, '|', sizeof desired_section);
2158 safestrncpy(CC->download_desired_section, desired_section,
2159 sizeof CC->download_desired_section);
2160 CtdlOutputMsg(msgid, MT_SPEW_SECTION, 0, 1, 1, NULL, 0);
2165 * Save one or more message pointers into a specified room
2166 * (Returns 0 for success, nonzero for failure)
2167 * roomname may be NULL to use the current room
2169 * Note that the 'supplied_msg' field may be set to NULL, in which case
2170 * the message will be fetched from disk, by number, if we need to perform
2171 * replication checks. This adds an additional database read, so if the
2172 * caller already has the message in memory then it should be supplied. (Obviously
2173 * this mode of operation only works if we're saving a single message.)
2175 int CtdlSaveMsgPointersInRoom(char *roomname, long newmsgidlist[], int num_newmsgs,
2176 int do_repl_check, struct CtdlMessage *supplied_msg)
2179 char hold_rm[ROOMNAMELEN];
2180 struct cdbdata *cdbfr;
2183 long highest_msg = 0L;
2186 struct CtdlMessage *msg = NULL;
2188 long *msgs_to_be_merged = NULL;
2189 int num_msgs_to_be_merged = 0;
2191 CtdlLogPrintf(CTDL_DEBUG,
2192 "CtdlSaveMsgPointersInRoom(room=%s, num_msgs=%d, repl=%d)\n",
2193 roomname, num_newmsgs, do_repl_check);
2195 strcpy(hold_rm, CC->room.QRname);
2198 if (newmsgidlist == NULL) return(ERROR + INTERNAL_ERROR);
2199 if (num_newmsgs < 1) return(ERROR + INTERNAL_ERROR);
2200 if (num_newmsgs > 1) supplied_msg = NULL;
2202 /* Now the regular stuff */
2203 if (lgetroom(&CC->room,
2204 ((roomname != NULL) ? roomname : CC->room.QRname) )
2206 CtdlLogPrintf(CTDL_ERR, "No such room <%s>\n", roomname);
2207 return(ERROR + ROOM_NOT_FOUND);
2211 msgs_to_be_merged = malloc(sizeof(long) * num_newmsgs);
2212 num_msgs_to_be_merged = 0;
2215 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long));
2216 if (cdbfr == NULL) {
2220 msglist = (long *) cdbfr->ptr;
2221 cdbfr->ptr = NULL; /* CtdlSaveMsgPointerInRoom() now owns this memory */
2222 num_msgs = cdbfr->len / sizeof(long);
2227 /* Create a list of msgid's which were supplied by the caller, but do
2228 * not already exist in the target room. It is absolutely taboo to
2229 * have more than one reference to the same message in a room.
2231 for (i=0; i<num_newmsgs; ++i) {
2233 if (num_msgs > 0) for (j=0; j<num_msgs; ++j) {
2234 if (msglist[j] == newmsgidlist[i]) {
2239 msgs_to_be_merged[num_msgs_to_be_merged++] = newmsgidlist[i];
2243 CtdlLogPrintf(9, "%d unique messages to be merged\n", num_msgs_to_be_merged);
2246 * Now merge the new messages
2248 msglist = realloc(msglist, (sizeof(long) * (num_msgs + num_msgs_to_be_merged)) );
2249 if (msglist == NULL) {
2250 CtdlLogPrintf(CTDL_ALERT, "ERROR: can't realloc message list!\n");
2252 memcpy(&msglist[num_msgs], msgs_to_be_merged, (sizeof(long) * num_msgs_to_be_merged) );
2253 num_msgs += num_msgs_to_be_merged;
2255 /* Sort the message list, so all the msgid's are in order */
2256 num_msgs = sort_msglist(msglist, num_msgs);
2258 /* Determine the highest message number */
2259 highest_msg = msglist[num_msgs - 1];
2261 /* Write it back to disk. */
2262 cdb_store(CDB_MSGLISTS, &CC->room.QRnumber, (int)sizeof(long),
2263 msglist, (int)(num_msgs * sizeof(long)));
2265 /* Free up the memory we used. */
2268 /* Update the highest-message pointer and unlock the room. */
2269 CC->room.QRhighest = highest_msg;
2270 lputroom(&CC->room);
2272 /* Perform replication checks if necessary */
2273 if ( (DoesThisRoomNeedEuidIndexing(&CC->room)) && (do_repl_check) ) {
2274 CtdlLogPrintf(CTDL_DEBUG, "CtdlSaveMsgPointerInRoom() doing repl checks\n");
2276 for (i=0; i<num_msgs_to_be_merged; ++i) {
2277 msgid = msgs_to_be_merged[i];
2279 if (supplied_msg != NULL) {
2283 msg = CtdlFetchMessage(msgid, 0);
2287 ReplicationChecks(msg);
2289 /* If the message has an Exclusive ID, index that... */
2290 if (msg->cm_fields['E'] != NULL) {
2291 index_message_by_euid(msg->cm_fields['E'], &CC->room, msgid);
2294 /* Free up the memory we may have allocated */
2295 if (msg != supplied_msg) {
2296 CtdlFreeMessage(msg);
2304 CtdlLogPrintf(CTDL_DEBUG, "CtdlSaveMsgPointerInRoom() skips repl checks\n");
2307 /* Submit this room for processing by hooks */
2308 PerformRoomHooks(&CC->room);
2310 /* Go back to the room we were in before we wandered here... */
2311 getroom(&CC->room, hold_rm);
2313 /* Bump the reference count for all messages which were merged */
2314 for (i=0; i<num_msgs_to_be_merged; ++i) {
2315 AdjRefCount(msgs_to_be_merged[i], +1);
2318 /* Free up memory... */
2319 if (msgs_to_be_merged != NULL) {
2320 free(msgs_to_be_merged);
2323 /* Return success. */
2329 * This is the same as CtdlSaveMsgPointersInRoom() but it only accepts
2332 int CtdlSaveMsgPointerInRoom(char *roomname, long msgid,
2333 int do_repl_check, struct CtdlMessage *supplied_msg)
2335 return CtdlSaveMsgPointersInRoom(roomname, &msgid, 1, do_repl_check, supplied_msg);
2342 * Message base operation to save a new message to the message store
2343 * (returns new message number)
2345 * This is the back end for CtdlSubmitMsg() and should not be directly
2346 * called by server-side modules.
2349 long send_message(struct CtdlMessage *msg) {
2357 /* Get a new message number */
2358 newmsgid = get_new_message_number();
2359 snprintf(msgidbuf, sizeof msgidbuf, "%010ld@%s", newmsgid, config.c_fqdn);
2361 /* Generate an ID if we don't have one already */
2362 if (msg->cm_fields['I']==NULL) {
2363 msg->cm_fields['I'] = strdup(msgidbuf);
2366 /* If the message is big, set its body aside for storage elsewhere */
2367 if (msg->cm_fields['M'] != NULL) {
2368 if (strlen(msg->cm_fields['M']) > BIGMSG) {
2370 holdM = msg->cm_fields['M'];
2371 msg->cm_fields['M'] = NULL;
2375 /* Serialize our data structure for storage in the database */
2376 serialize_message(&smr, msg);
2379 msg->cm_fields['M'] = holdM;
2383 cprintf("%d Unable to serialize message\n",
2384 ERROR + INTERNAL_ERROR);
2388 /* Write our little bundle of joy into the message base */
2389 if (cdb_store(CDB_MSGMAIN, &newmsgid, (int)sizeof(long),
2390 smr.ser, smr.len) < 0) {
2391 CtdlLogPrintf(CTDL_ERR, "Can't store message\n");
2395 cdb_store(CDB_BIGMSGS,
2405 /* Free the memory we used for the serialized message */
2408 /* Return the *local* message ID to the caller
2409 * (even if we're storing an incoming network message)
2417 * Serialize a struct CtdlMessage into the format used on disk and network.
2419 * This function loads up a "struct ser_ret" (defined in server.h) which
2420 * contains the length of the serialized message and a pointer to the
2421 * serialized message in memory. THE LATTER MUST BE FREED BY THE CALLER.
2423 void serialize_message(struct ser_ret *ret, /* return values */
2424 struct CtdlMessage *msg) /* unserialized msg */
2426 size_t wlen, fieldlen;
2428 static char *forder = FORDER;
2431 * Check for valid message format
2433 if (is_valid_message(msg) == 0) {
2434 CtdlLogPrintf(CTDL_ERR, "serialize_message() aborting due to invalid message\n");
2441 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL)
2442 ret->len = ret->len +
2443 strlen(msg->cm_fields[(int)forder[i]]) + 2;
2445 ret->ser = malloc(ret->len);
2446 if (ret->ser == NULL) {
2447 CtdlLogPrintf(CTDL_ERR, "serialize_message() malloc(%ld) failed: %s\n",
2448 (long)ret->len, strerror(errno));
2455 ret->ser[1] = msg->cm_anon_type;
2456 ret->ser[2] = msg->cm_format_type;
2459 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL) {
2460 fieldlen = strlen(msg->cm_fields[(int)forder[i]]);
2461 ret->ser[wlen++] = (char)forder[i];
2462 safestrncpy((char *)&ret->ser[wlen], msg->cm_fields[(int)forder[i]], fieldlen+1);
2463 wlen = wlen + fieldlen + 1;
2465 if (ret->len != wlen) CtdlLogPrintf(CTDL_ERR, "ERROR: len=%ld wlen=%ld\n",
2466 (long)ret->len, (long)wlen);
2473 * Serialize a struct CtdlMessage into the format used on disk and network.
2475 * This function loads up a "struct ser_ret" (defined in server.h) which
2476 * contains the length of the serialized message and a pointer to the
2477 * serialized message in memory. THE LATTER MUST BE FREED BY THE CALLER.
2479 void dump_message(struct CtdlMessage *msg, /* unserialized msg */
2480 long Siz) /* how many chars ? */
2484 static char *forder = FORDER;
2488 * Check for valid message format
2490 if (is_valid_message(msg) == 0) {
2491 CtdlLogPrintf(CTDL_ERR, "dump_message() aborting due to invalid message\n");
2495 buf = (char*) malloc (Siz + 1);
2499 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL) {
2500 snprintf (buf, Siz, " msg[%c] = %s ...\n", (char) forder[i],
2501 msg->cm_fields[(int)forder[i]]);
2502 client_write (buf, strlen(buf));
2511 * Check to see if any messages already exist in the current room which
2512 * carry the same Exclusive ID as this one. If any are found, delete them.
2514 void ReplicationChecks(struct CtdlMessage *msg) {
2515 long old_msgnum = (-1L);
2517 if (DoesThisRoomNeedEuidIndexing(&CC->room) == 0) return;
2519 CtdlLogPrintf(CTDL_DEBUG, "Performing replication checks in <%s>\n",
2522 /* No exclusive id? Don't do anything. */
2523 if (msg == NULL) return;
2524 if (msg->cm_fields['E'] == NULL) return;
2525 if (IsEmptyStr(msg->cm_fields['E'])) return;
2526 /*CtdlLogPrintf(CTDL_DEBUG, "Exclusive ID: <%s> for room <%s>\n",
2527 msg->cm_fields['E'], CC->room.QRname);*/
2529 old_msgnum = locate_message_by_euid(msg->cm_fields['E'], &CC->room);
2530 if (old_msgnum > 0L) {
2531 CtdlLogPrintf(CTDL_DEBUG, "ReplicationChecks() replacing message %ld\n", old_msgnum);
2532 CtdlDeleteMessages(CC->room.QRname, &old_msgnum, 1, "");
2539 * Save a message to disk and submit it into the delivery system.
2541 long CtdlSubmitMsg(struct CtdlMessage *msg, /* message to save */
2542 struct recptypes *recps, /* recipients (if mail) */
2543 char *force, /* force a particular room? */
2544 int flags /* should the bessage be exported clean? */
2546 char submit_filename[128];
2547 char generated_timestamp[32];
2548 char hold_rm[ROOMNAMELEN];
2549 char actual_rm[ROOMNAMELEN];
2550 char force_room[ROOMNAMELEN];
2551 char content_type[SIZ]; /* We have to learn this */
2552 char recipient[SIZ];
2555 struct ctdluser userbuf;
2557 struct MetaData smi;
2558 FILE *network_fp = NULL;
2559 static int seqnum = 1;
2560 struct CtdlMessage *imsg = NULL;
2562 size_t instr_alloc = 0;
2564 char *hold_R, *hold_D;
2565 char *collected_addresses = NULL;
2566 struct addresses_to_be_filed *aptr = NULL;
2567 char *saved_rfc822_version = NULL;
2568 int qualified_for_journaling = 0;
2569 struct CitContext *CCC = CC; /* CachedCitContext - performance boost */
2570 char bounce_to[1024] = "";
2572 CtdlLogPrintf(CTDL_DEBUG, "CtdlSubmitMsg() called\n");
2573 if (is_valid_message(msg) == 0) return(-1); /* self check */
2575 /* If this message has no timestamp, we take the liberty of
2576 * giving it one, right now.
2578 if (msg->cm_fields['T'] == NULL) {
2579 snprintf(generated_timestamp, sizeof generated_timestamp, "%ld", (long)time(NULL));
2580 msg->cm_fields['T'] = strdup(generated_timestamp);
2583 /* If this message has no path, we generate one.
2585 if (msg->cm_fields['P'] == NULL) {
2586 if (msg->cm_fields['A'] != NULL) {
2587 msg->cm_fields['P'] = strdup(msg->cm_fields['A']);
2588 for (a=0; !IsEmptyStr(&msg->cm_fields['P'][a]); ++a) {
2589 if (isspace(msg->cm_fields['P'][a])) {
2590 msg->cm_fields['P'][a] = ' ';
2595 msg->cm_fields['P'] = strdup("unknown");
2599 if (force == NULL) {
2600 strcpy(force_room, "");
2603 strcpy(force_room, force);
2606 /* Learn about what's inside, because it's what's inside that counts */
2607 if (msg->cm_fields['M'] == NULL) {
2608 CtdlLogPrintf(CTDL_ERR, "ERROR: attempt to save message with NULL body\n");
2612 switch (msg->cm_format_type) {
2614 strcpy(content_type, "text/x-citadel-variformat");
2617 strcpy(content_type, "text/plain");
2620 strcpy(content_type, "text/plain");
2621 mptr = bmstrcasestr(msg->cm_fields['M'], "Content-type:");
2624 safestrncpy(content_type, &mptr[13], sizeof content_type);
2625 striplt(content_type);
2626 aptr = content_type;
2627 while (!IsEmptyStr(aptr)) {
2639 /* Goto the correct room */
2640 CtdlLogPrintf(CTDL_DEBUG, "Selected room %s\n", (recps) ? CCC->room.QRname : SENTITEMS);
2641 strcpy(hold_rm, CCC->room.QRname);
2642 strcpy(actual_rm, CCC->room.QRname);
2643 if (recps != NULL) {
2644 strcpy(actual_rm, SENTITEMS);
2647 /* If the user is a twit, move to the twit room for posting */
2649 if (CCC->user.axlevel == 2) {
2650 strcpy(hold_rm, actual_rm);
2651 strcpy(actual_rm, config.c_twitroom);
2652 CtdlLogPrintf(CTDL_DEBUG, "Diverting to twit room\n");
2656 /* ...or if this message is destined for Aide> then go there. */
2657 if (!IsEmptyStr(force_room)) {
2658 strcpy(actual_rm, force_room);
2661 CtdlLogPrintf(CTDL_DEBUG, "Final selection: %s\n", actual_rm);
2662 if (strcasecmp(actual_rm, CCC->room.QRname)) {
2663 /* getroom(&CCC->room, actual_rm); */
2664 usergoto(actual_rm, 0, 1, NULL, NULL);
2668 * If this message has no O (room) field, generate one.
2670 if (msg->cm_fields['O'] == NULL) {
2671 msg->cm_fields['O'] = strdup(CCC->room.QRname);
2674 /* Perform "before save" hooks (aborting if any return nonzero) */
2675 CtdlLogPrintf(CTDL_DEBUG, "Performing before-save hooks\n");
2676 if (PerformMessageHooks(msg, EVT_BEFORESAVE) > 0) return(-3);
2679 * If this message has an Exclusive ID, and the room is replication
2680 * checking enabled, then do replication checks.
2682 if (DoesThisRoomNeedEuidIndexing(&CCC->room)) {
2683 ReplicationChecks(msg);
2686 /* Save it to disk */
2687 CtdlLogPrintf(CTDL_DEBUG, "Saving to disk\n");
2688 newmsgid = send_message(msg);
2689 if (newmsgid <= 0L) return(-5);
2691 /* Write a supplemental message info record. This doesn't have to
2692 * be a critical section because nobody else knows about this message
2695 CtdlLogPrintf(CTDL_DEBUG, "Creating MetaData record\n");
2696 memset(&smi, 0, sizeof(struct MetaData));
2697 smi.meta_msgnum = newmsgid;
2698 smi.meta_refcount = 0;
2699 safestrncpy(smi.meta_content_type, content_type,
2700 sizeof smi.meta_content_type);
2703 * Measure how big this message will be when rendered as RFC822.
2704 * We do this for two reasons:
2705 * 1. We need the RFC822 length for the new metadata record, so the
2706 * POP and IMAP services don't have to calculate message lengths
2707 * while the user is waiting (multiplied by potentially hundreds
2708 * or thousands of messages).
2709 * 2. If journaling is enabled, we will need an RFC822 version of the
2710 * message to attach to the journalized copy.
2712 if (CCC->redirect_buffer != NULL) {
2713 CtdlLogPrintf(CTDL_ALERT, "CCC->redirect_buffer is not NULL during message submission!\n");
2716 CCC->redirect_buffer = malloc(SIZ);
2717 CCC->redirect_len = 0;
2718 CCC->redirect_alloc = SIZ;
2719 CtdlOutputPreLoadedMsg(msg, MT_RFC822, HEADERS_ALL, 0, 1, QP_EADDR);
2720 smi.meta_rfc822_length = CCC->redirect_len;
2721 saved_rfc822_version = CCC->redirect_buffer;
2722 CCC->redirect_buffer = NULL;
2723 CCC->redirect_len = 0;
2724 CCC->redirect_alloc = 0;
2728 /* Now figure out where to store the pointers */
2729 CtdlLogPrintf(CTDL_DEBUG, "Storing pointers\n");
2731 /* If this is being done by the networker delivering a private
2732 * message, we want to BYPASS saving the sender's copy (because there
2733 * is no local sender; it would otherwise go to the Trashcan).
2735 if ((!CCC->internal_pgm) || (recps == NULL)) {
2736 if (CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 1, msg) != 0) {
2737 CtdlLogPrintf(CTDL_ERR, "ERROR saving message pointer!\n");
2738 CtdlSaveMsgPointerInRoom(config.c_aideroom, newmsgid, 0, msg);
2742 /* For internet mail, drop a copy in the outbound queue room */
2744 if (recps->num_internet > 0) {
2745 CtdlSaveMsgPointerInRoom(SMTP_SPOOLOUT_ROOM, newmsgid, 0, msg);
2748 /* If other rooms are specified, drop them there too. */
2750 if (recps->num_room > 0)
2751 for (i=0; i<num_tokens(recps->recp_room, '|'); ++i) {
2752 extract_token(recipient, recps->recp_room, i,
2753 '|', sizeof recipient);
2754 CtdlLogPrintf(CTDL_DEBUG, "Delivering to room <%s>\n", recipient);
2755 CtdlSaveMsgPointerInRoom(recipient, newmsgid, 0, msg);
2758 /* Bump this user's messages posted counter. */
2759 CtdlLogPrintf(CTDL_DEBUG, "Updating user\n");
2760 lgetuser(&CCC->user, CCC->curr_user);
2761 CCC->user.posted = CCC->user.posted + 1;
2762 lputuser(&CCC->user);
2764 /* Decide where bounces need to be delivered */
2765 if (CCC->logged_in) {
2766 snprintf(bounce_to, sizeof bounce_to, "%s@%s", CCC->user.fullname, config.c_nodename);
2769 snprintf(bounce_to, sizeof bounce_to, "%s@%s", msg->cm_fields['A'], msg->cm_fields['N']);
2772 /* If this is private, local mail, make a copy in the
2773 * recipient's mailbox and bump the reference count.
2776 if (recps->num_local > 0)
2777 for (i=0; i<num_tokens(recps->recp_local, '|'); ++i) {
2778 extract_token(recipient, recps->recp_local, i,
2779 '|', sizeof recipient);
2780 CtdlLogPrintf(CTDL_DEBUG, "Delivering private local mail to <%s>\n",
2782 if (getuser(&userbuf, recipient) == 0) {
2783 // Add a flag so the Funambol module knows its mail
2784 msg->cm_fields['W'] = strdup(recipient);
2785 MailboxName(actual_rm, sizeof actual_rm, &userbuf, MAILROOM);
2786 CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 0, msg);
2787 BumpNewMailCounter(userbuf.usernum);
2788 if (!IsEmptyStr(config.c_funambol_host) || !IsEmptyStr(config.c_pager_program)) {
2789 /* Generate a instruction message for the Funambol notification
2790 * server, in the same style as the SMTP queue
2793 instr = malloc(instr_alloc);
2794 snprintf(instr, instr_alloc,
2795 "Content-type: %s\n\nmsgid|%ld\nsubmitted|%ld\n"
2797 SPOOLMIME, newmsgid, (long)time(NULL),
2801 imsg = malloc(sizeof(struct CtdlMessage));
2802 memset(imsg, 0, sizeof(struct CtdlMessage));
2803 imsg->cm_magic = CTDLMESSAGE_MAGIC;
2804 imsg->cm_anon_type = MES_NORMAL;
2805 imsg->cm_format_type = FMT_RFC822;
2806 imsg->cm_fields['A'] = strdup("Citadel");
2807 imsg->cm_fields['J'] = strdup("do not journal");
2808 imsg->cm_fields['M'] = instr; /* imsg owns this memory now */
2809 imsg->cm_fields['W'] = strdup(recipient);
2810 CtdlSubmitMsg(imsg, NULL, FNBL_QUEUE_ROOM, 0);
2811 CtdlFreeMessage(imsg);
2815 CtdlLogPrintf(CTDL_DEBUG, "No user <%s>\n", recipient);
2816 CtdlSaveMsgPointerInRoom(config.c_aideroom,
2821 /* Perform "after save" hooks */
2822 CtdlLogPrintf(CTDL_DEBUG, "Performing after-save hooks\n");
2823 PerformMessageHooks(msg, EVT_AFTERSAVE);
2825 /* For IGnet mail, we have to save a new copy into the spooler for
2826 * each recipient, with the R and D fields set to the recipient and
2827 * destination-node. This has two ugly side effects: all other
2828 * recipients end up being unlisted in this recipient's copy of the
2829 * message, and it has to deliver multiple messages to the same
2830 * node. We'll revisit this again in a year or so when everyone has
2831 * a network spool receiver that can handle the new style messages.
2834 if (recps->num_ignet > 0)
2835 for (i=0; i<num_tokens(recps->recp_ignet, '|'); ++i) {
2836 extract_token(recipient, recps->recp_ignet, i,
2837 '|', sizeof recipient);
2839 hold_R = msg->cm_fields['R'];
2840 hold_D = msg->cm_fields['D'];
2841 msg->cm_fields['R'] = malloc(SIZ);
2842 msg->cm_fields['D'] = malloc(128);
2843 extract_token(msg->cm_fields['R'], recipient, 0, '@', SIZ);
2844 extract_token(msg->cm_fields['D'], recipient, 1, '@', 128);
2846 serialize_message(&smr, msg);
2848 snprintf(submit_filename, sizeof submit_filename,
2849 "%s/netmail.%04lx.%04x.%04x",
2851 (long) getpid(), CCC->cs_pid, ++seqnum);
2852 network_fp = fopen(submit_filename, "wb+");
2853 if (network_fp != NULL) {
2854 fwrite(smr.ser, smr.len, 1, network_fp);
2860 free(msg->cm_fields['R']);
2861 free(msg->cm_fields['D']);
2862 msg->cm_fields['R'] = hold_R;
2863 msg->cm_fields['D'] = hold_D;
2866 /* Go back to the room we started from */
2867 CtdlLogPrintf(CTDL_DEBUG, "Returning to original room %s\n", hold_rm);
2868 if (strcasecmp(hold_rm, CCC->room.QRname))
2869 usergoto(hold_rm, 0, 1, NULL, NULL);
2871 /* For internet mail, generate delivery instructions.
2872 * Yes, this is recursive. Deal with it. Infinite recursion does
2873 * not happen because the delivery instructions message does not
2874 * contain a recipient.
2877 if (recps->num_internet > 0) {
2878 CtdlLogPrintf(CTDL_DEBUG, "Generating delivery instructions\n");
2880 instr = malloc(instr_alloc);
2881 snprintf(instr, instr_alloc,
2882 "Content-type: %s\n\nmsgid|%ld\nsubmitted|%ld\n"
2884 SPOOLMIME, newmsgid, (long)time(NULL),
2888 for (i=0; i<num_tokens(recps->recp_internet, '|'); ++i) {
2889 size_t tmp = strlen(instr);
2890 extract_token(recipient, recps->recp_internet, i, '|', sizeof recipient);
2891 if ((tmp + strlen(recipient) + 32) > instr_alloc) {
2892 instr_alloc = instr_alloc * 2;
2893 instr = realloc(instr, instr_alloc);
2895 snprintf(&instr[tmp], instr_alloc - tmp, "remote|%s|0||\n", recipient);
2898 imsg = malloc(sizeof(struct CtdlMessage));
2899 memset(imsg, 0, sizeof(struct CtdlMessage));
2900 imsg->cm_magic = CTDLMESSAGE_MAGIC;
2901 imsg->cm_anon_type = MES_NORMAL;
2902 imsg->cm_format_type = FMT_RFC822;
2903 imsg->cm_fields['A'] = strdup("Citadel");
2904 imsg->cm_fields['J'] = strdup("do not journal");
2905 imsg->cm_fields['M'] = instr; /* imsg owns this memory now */
2906 CtdlSubmitMsg(imsg, NULL, SMTP_SPOOLOUT_ROOM, QP_EADDR);
2907 CtdlFreeMessage(imsg);
2911 * Any addresses to harvest for someone's address book?
2913 if ( (CCC->logged_in) && (recps != NULL) ) {
2914 collected_addresses = harvest_collected_addresses(msg);
2917 if (collected_addresses != NULL) {
2918 aptr = (struct addresses_to_be_filed *)
2919 malloc(sizeof(struct addresses_to_be_filed));
2920 MailboxName(actual_rm, sizeof actual_rm,
2921 &CCC->user, USERCONTACTSROOM);
2922 aptr->roomname = strdup(actual_rm);
2923 aptr->collected_addresses = collected_addresses;
2924 begin_critical_section(S_ATBF);
2927 end_critical_section(S_ATBF);
2931 * Determine whether this message qualifies for journaling.
2933 if (msg->cm_fields['J'] != NULL) {
2934 qualified_for_journaling = 0;
2937 if (recps == NULL) {
2938 qualified_for_journaling = config.c_journal_pubmsgs;
2940 else if (recps->num_local + recps->num_ignet + recps->num_internet > 0) {
2941 qualified_for_journaling = config.c_journal_email;
2944 qualified_for_journaling = config.c_journal_pubmsgs;
2949 * Do we have to perform journaling? If so, hand off the saved
2950 * RFC822 version will be handed off to the journaler for background
2951 * submit. Otherwise, we have to free the memory ourselves.
2953 if (saved_rfc822_version != NULL) {
2954 if (qualified_for_journaling) {
2955 JournalBackgroundSubmit(msg, saved_rfc822_version, recps);
2958 free(saved_rfc822_version);
2971 * Convenience function for generating small administrative messages.
2973 void quickie_message(char *from, char *fromaddr, char *to, char *room, char *text,
2974 int format_type, char *subject)
2976 struct CtdlMessage *msg;
2977 struct recptypes *recp = NULL;
2979 msg = malloc(sizeof(struct CtdlMessage));
2980 memset(msg, 0, sizeof(struct CtdlMessage));
2981 msg->cm_magic = CTDLMESSAGE_MAGIC;
2982 msg->cm_anon_type = MES_NORMAL;
2983 msg->cm_format_type = format_type;
2986 msg->cm_fields['A'] = strdup(from);
2988 else if (fromaddr != NULL) {
2989 msg->cm_fields['A'] = strdup(fromaddr);
2990 if (strchr(msg->cm_fields['A'], '@')) {
2991 *strchr(msg->cm_fields['A'], '@') = 0;
2995 msg->cm_fields['A'] = strdup("Citadel");
2998 if (fromaddr != NULL) msg->cm_fields['F'] = strdup(fromaddr);
2999 if (room != NULL) msg->cm_fields['O'] = strdup(room);
3000 msg->cm_fields['N'] = strdup(NODENAME);
3002 msg->cm_fields['R'] = strdup(to);
3003 recp = validate_recipients(to, NULL, 0);
3005 if (subject != NULL) {
3006 msg->cm_fields['U'] = strdup(subject);
3008 msg->cm_fields['M'] = strdup(text);
3010 CtdlSubmitMsg(msg, recp, room, 0);
3011 CtdlFreeMessage(msg);
3012 if (recp != NULL) free_recipients(recp);
3018 * Back end function used by CtdlMakeMessage() and similar functions
3020 char *CtdlReadMessageBody(char *terminator, /* token signalling EOT */
3021 size_t maxlen, /* maximum message length */
3022 char *exist, /* if non-null, append to it;
3023 exist is ALWAYS freed */
3024 int crlf, /* CRLF newlines instead of LF */
3025 int sock /* socket handle or 0 for this session's client socket */
3029 size_t message_len = 0;
3030 size_t buffer_len = 0;
3037 if (exist == NULL) {
3044 message_len = strlen(exist);
3045 buffer_len = message_len + 4096;
3046 m = realloc(exist, buffer_len);
3053 /* Do we need to change leading ".." to "." for SMTP escaping? */
3054 if (!strcmp(terminator, ".")) {
3058 /* flush the input if we have nowhere to store it */
3063 /* read in the lines of message text one by one */
3066 if (sock_getln(sock, buf, (sizeof buf - 3)) < 0) finished = 1;
3069 if (client_getln(buf, (sizeof buf - 3)) < 1) finished = 1;
3071 if (!strcmp(buf, terminator)) finished = 1;
3073 strcat(buf, "\r\n");
3079 /* Unescape SMTP-style input of two dots at the beginning of the line */
3081 if (!strncmp(buf, "..", 2)) {
3082 strcpy(buf, &buf[1]);
3086 if ( (!flushing) && (!finished) ) {
3087 /* Measure the line */
3088 linelen = strlen(buf);
3090 /* augment the buffer if we have to */
3091 if ((message_len + linelen) >= buffer_len) {
3092 ptr = realloc(m, (buffer_len * 2) );
3093 if (ptr == NULL) { /* flush if can't allocate */
3096 buffer_len = (buffer_len * 2);
3098 CtdlLogPrintf(CTDL_DEBUG, "buffer_len is now %ld\n", (long)buffer_len);
3102 /* Add the new line to the buffer. NOTE: this loop must avoid
3103 * using functions like strcat() and strlen() because they
3104 * traverse the entire buffer upon every call, and doing that
3105 * for a multi-megabyte message slows it down beyond usability.
3107 strcpy(&m[message_len], buf);
3108 message_len += linelen;
3111 /* if we've hit the max msg length, flush the rest */
3112 if (message_len >= maxlen) flushing = 1;
3114 } while (!finished);
3122 * Build a binary message to be saved on disk.
3123 * (NOTE: if you supply 'preformatted_text', the buffer you give it
3124 * will become part of the message. This means you are no longer
3125 * responsible for managing that memory -- it will be freed along with
3126 * the rest of the fields when CtdlFreeMessage() is called.)
3129 struct CtdlMessage *CtdlMakeMessage(
3130 struct ctdluser *author, /* author's user structure */
3131 char *recipient, /* NULL if it's not mail */
3132 char *recp_cc, /* NULL if it's not mail */
3133 char *room, /* room where it's going */
3134 int type, /* see MES_ types in header file */
3135 int format_type, /* variformat, plain text, MIME... */
3136 char *fake_name, /* who we're masquerading as */
3137 char *my_email, /* which of my email addresses to use (empty is ok) */
3138 char *subject, /* Subject (optional) */
3139 char *supplied_euid, /* ...or NULL if this is irrelevant */
3140 char *preformatted_text, /* ...or NULL to read text from client */
3141 char *references /* Thread references */
3143 char dest_node[256];
3145 struct CtdlMessage *msg;
3147 msg = malloc(sizeof(struct CtdlMessage));
3148 memset(msg, 0, sizeof(struct CtdlMessage));
3149 msg->cm_magic = CTDLMESSAGE_MAGIC;
3150 msg->cm_anon_type = type;
3151 msg->cm_format_type = format_type;
3153 /* Don't confuse the poor folks if it's not routed mail. */
3154 strcpy(dest_node, "");
3159 /* Path or Return-Path */
3160 if (my_email == NULL) my_email = "";
3162 if (!IsEmptyStr(my_email)) {
3163 msg->cm_fields['P'] = strdup(my_email);
3166 snprintf(buf, sizeof buf, "%s", author->fullname);
3167 msg->cm_fields['P'] = strdup(buf);
3169 convert_spaces_to_underscores(msg->cm_fields['P']);
3171 snprintf(buf, sizeof buf, "%ld", (long)time(NULL)); /* timestamp */
3172 msg->cm_fields['T'] = strdup(buf);
3174 if (fake_name[0]) /* author */
3175 msg->cm_fields['A'] = strdup(fake_name);
3177 msg->cm_fields['A'] = strdup(author->fullname);
3179 if (CC->room.QRflags & QR_MAILBOX) { /* room */
3180 msg->cm_fields['O'] = strdup(&CC->room.QRname[11]);
3183 msg->cm_fields['O'] = strdup(CC->room.QRname);
3186 msg->cm_fields['N'] = strdup(NODENAME); /* nodename */
3187 msg->cm_fields['H'] = strdup(HUMANNODE); /* hnodename */
3189 if (recipient[0] != 0) {
3190 msg->cm_fields['R'] = strdup(recipient);
3192 if (recp_cc[0] != 0) {
3193 msg->cm_fields['Y'] = strdup(recp_cc);
3195 if (dest_node[0] != 0) {
3196 msg->cm_fields['D'] = strdup(dest_node);
3199 if (!IsEmptyStr(my_email)) {
3200 msg->cm_fields['F'] = strdup(my_email);
3202 else if ( (author == &CC->user) && (!IsEmptyStr(CC->cs_inet_email)) ) {
3203 msg->cm_fields['F'] = strdup(CC->cs_inet_email);
3206 if (subject != NULL) {
3209 length = strlen(subject);
3215 while ((subject[i] != '\0') &&
3216 (IsAscii = isascii(subject[i]) != 0 ))
3219 msg->cm_fields['U'] = strdup(subject);
3220 else /* ok, we've got utf8 in the string. */
3222 msg->cm_fields['U'] = rfc2047encode(subject, length);
3228 if (supplied_euid != NULL) {
3229 msg->cm_fields['E'] = strdup(supplied_euid);
3232 if (references != NULL) {
3233 if (!IsEmptyStr(references)) {
3234 msg->cm_fields['W'] = strdup(references);
3238 if (preformatted_text != NULL) {
3239 msg->cm_fields['M'] = preformatted_text;
3242 msg->cm_fields['M'] = CtdlReadMessageBody("000", config.c_maxmsglen, NULL, 0, 0);
3250 * Check to see whether we have permission to post a message in the current
3251 * room. Returns a *CITADEL ERROR CODE* and puts a message in errmsgbuf, or
3252 * returns 0 on success.
3254 int CtdlDoIHavePermissionToPostInThisRoom(char *errmsgbuf,
3256 const char* RemoteIdentifier,
3260 if (!(CC->logged_in) &&
3261 (PostPublic == POST_LOGGED_IN)) {
3262 snprintf(errmsgbuf, n, "Not logged in.");
3263 return (ERROR + NOT_LOGGED_IN);
3265 else if (PostPublic == CHECK_EXISTANCE) {
3266 return (0); // We're Evaling whether a recipient exists
3268 else if (!(CC->logged_in)) {
3270 if ((CC->room.QRflags & QR_READONLY)) {
3271 snprintf(errmsgbuf, n, "Not logged in.");
3272 return (ERROR + NOT_LOGGED_IN);
3274 if (CC->room.QRflags2 & QR2_MODERATED) {
3275 snprintf(errmsgbuf, n, "Not logged in Moderation feature not yet implemented!");
3276 return (ERROR + NOT_LOGGED_IN);
3278 if ((PostPublic!=POST_LMTP) &&(CC->room.QRflags2 & QR2_SMTP_PUBLIC) == 0) {
3283 if (RemoteIdentifier == NULL)
3285 snprintf(errmsgbuf, n, "Need sender to permit access.");
3286 return (ERROR + USERNAME_REQUIRED);
3289 assoc_file_name(filename, sizeof filename, &CC->room, ctdl_netcfg_dir);
3290 begin_critical_section(S_NETCONFIGS);
3291 if (!read_spoolcontrol_file(&sc, filename))
3293 end_critical_section(S_NETCONFIGS);
3294 snprintf(errmsgbuf, n,
3295 "This mailing list only accepts posts from subscribers.");
3296 return (ERROR + NO_SUCH_USER);
3298 end_critical_section(S_NETCONFIGS);
3299 found = is_recipient (sc, RemoteIdentifier);
3300 free_spoolcontrol_struct(&sc);
3305 snprintf(errmsgbuf, n,
3306 "This mailing list only accepts posts from subscribers.");
3307 return (ERROR + NO_SUCH_USER);
3314 if ((CC->user.axlevel < 2)
3315 && ((CC->room.QRflags & QR_MAILBOX) == 0)) {
3316 snprintf(errmsgbuf, n, "Need to be validated to enter "
3317 "(except in %s> to sysop)", MAILROOM);
3318 return (ERROR + HIGHER_ACCESS_REQUIRED);
3321 CtdlRoomAccess(&CC->room, &CC->user, &ra, NULL);
3322 if (!(ra & UA_POSTALLOWED)) {
3323 snprintf(errmsgbuf, n, "Higher access is required to post in this room.");
3324 return (ERROR + HIGHER_ACCESS_REQUIRED);
3327 strcpy(errmsgbuf, "Ok");
3333 * Check to see if the specified user has Internet mail permission
3334 * (returns nonzero if permission is granted)
3336 int CtdlCheckInternetMailPermission(struct ctdluser *who) {
3338 /* Do not allow twits to send Internet mail */
3339 if (who->axlevel <= 2) return(0);
3341 /* Globally enabled? */
3342 if (config.c_restrict == 0) return(1);
3344 /* User flagged ok? */
3345 if (who->flags & US_INTERNET) return(2);
3347 /* Aide level access? */
3348 if (who->axlevel >= 6) return(3);
3350 /* No mail for you! */
3356 * Validate recipients, count delivery types and errors, and handle aliasing
3357 * FIXME check for dupes!!!!!
3359 * Returns 0 if all addresses are ok, ret->num_error = -1 if no addresses
3360 * were specified, or the number of addresses found invalid.
3362 * Caller needs to free the result using free_recipients()
3364 struct recptypes *validate_recipients(char *supplied_recipients,
3365 const char *RemoteIdentifier,
3367 struct recptypes *ret;
3368 char *recipients = NULL;
3369 char this_recp[256];
3370 char this_recp_cooked[256];
3376 struct ctdluser tempUS;
3377 struct ctdlroom tempQR;
3378 struct ctdlroom tempQR2;
3384 ret = (struct recptypes *) malloc(sizeof(struct recptypes));
3385 if (ret == NULL) return(NULL);
3387 /* Set all strings to null and numeric values to zero */
3388 memset(ret, 0, sizeof(struct recptypes));
3390 if (supplied_recipients == NULL) {
3391 recipients = strdup("");
3394 recipients = strdup(supplied_recipients);
3397 /* Allocate some memory. Yes, this allocates 500% more memory than we will
3398 * actually need, but it's healthier for the heap than doing lots of tiny
3399 * realloc() calls instead.
3402 ret->errormsg = malloc(strlen(recipients) + 1024);
3403 ret->recp_local = malloc(strlen(recipients) + 1024);
3404 ret->recp_internet = malloc(strlen(recipients) + 1024);
3405 ret->recp_ignet = malloc(strlen(recipients) + 1024);
3406 ret->recp_room = malloc(strlen(recipients) + 1024);
3407 ret->display_recp = malloc(strlen(recipients) + 1024);
3409 ret->errormsg[0] = 0;
3410 ret->recp_local[0] = 0;
3411 ret->recp_internet[0] = 0;
3412 ret->recp_ignet[0] = 0;
3413 ret->recp_room[0] = 0;
3414 ret->display_recp[0] = 0;
3416 ret->recptypes_magic = RECPTYPES_MAGIC;
3418 /* Change all valid separator characters to commas */
3419 for (i=0; !IsEmptyStr(&recipients[i]); ++i) {
3420 if ((recipients[i] == ';') || (recipients[i] == '|')) {
3421 recipients[i] = ',';
3425 /* Now start extracting recipients... */
3427 while (!IsEmptyStr(recipients)) {
3429 for (i=0; i<=strlen(recipients); ++i) {
3430 if (recipients[i] == '\"') in_quotes = 1 - in_quotes;
3431 if ( ( (recipients[i] == ',') && (!in_quotes) ) || (recipients[i] == 0) ) {
3432 safestrncpy(this_recp, recipients, i+1);
3434 if (recipients[i] == ',') {
3435 strcpy(recipients, &recipients[i+1]);
3438 strcpy(recipients, "");
3445 if (IsEmptyStr(this_recp))
3447 CtdlLogPrintf(CTDL_DEBUG, "Evaluating recipient #%d: %s\n", num_recps, this_recp);
3449 mailtype = alias(this_recp);
3450 mailtype = alias(this_recp);
3451 mailtype = alias(this_recp);
3453 for (j=0; !IsEmptyStr(&this_recp[j]); ++j) {
3454 if (this_recp[j]=='_') {
3455 this_recp_cooked[j] = ' ';
3458 this_recp_cooked[j] = this_recp[j];
3461 this_recp_cooked[j] = '\0';
3466 if (!strcasecmp(this_recp, "sysop")) {
3468 strcpy(this_recp, config.c_aideroom);
3469 if (!IsEmptyStr(ret->recp_room)) {
3470 strcat(ret->recp_room, "|");
3472 strcat(ret->recp_room, this_recp);
3474 else if ( (!strncasecmp(this_recp, "room_", 5))
3475 && (!getroom(&tempQR, &this_recp_cooked[5])) ) {
3477 /* Save room so we can restore it later */
3481 /* Check permissions to send mail to this room */
3482 err = CtdlDoIHavePermissionToPostInThisRoom(errmsg,
3494 if (!IsEmptyStr(ret->recp_room)) {
3495 strcat(ret->recp_room, "|");
3497 strcat(ret->recp_room, &this_recp_cooked[5]);
3500 /* Restore room in case something needs it */
3504 else if (getuser(&tempUS, this_recp) == 0) {
3506 strcpy(this_recp, tempUS.fullname);
3507 if (!IsEmptyStr(ret->recp_local)) {
3508 strcat(ret->recp_local, "|");
3510 strcat(ret->recp_local, this_recp);
3512 else if (getuser(&tempUS, this_recp_cooked) == 0) {
3514 strcpy(this_recp, tempUS.fullname);
3515 if (!IsEmptyStr(ret->recp_local)) {
3516 strcat(ret->recp_local, "|");
3518 strcat(ret->recp_local, this_recp);
3526 /* Yes, you're reading this correctly: if the target
3527 * domain points back to the local system or an attached
3528 * Citadel directory, the address is invalid. That's
3529 * because if the address were valid, we would have
3530 * already translated it to a local address by now.
3532 if (IsDirectory(this_recp, 0)) {
3537 ++ret->num_internet;
3538 if (!IsEmptyStr(ret->recp_internet)) {
3539 strcat(ret->recp_internet, "|");
3541 strcat(ret->recp_internet, this_recp);
3546 if (!IsEmptyStr(ret->recp_ignet)) {
3547 strcat(ret->recp_ignet, "|");
3549 strcat(ret->recp_ignet, this_recp);
3557 if (IsEmptyStr(errmsg)) {
3558 snprintf(append, sizeof append, "Invalid recipient: %s", this_recp);
3561 snprintf(append, sizeof append, "%s", errmsg);
3563 if ( (strlen(ret->errormsg) + strlen(append) + 3) < SIZ) {
3564 if (!IsEmptyStr(ret->errormsg)) {
3565 strcat(ret->errormsg, "; ");
3567 strcat(ret->errormsg, append);
3571 if (IsEmptyStr(ret->display_recp)) {
3572 strcpy(append, this_recp);
3575 snprintf(append, sizeof append, ", %s", this_recp);
3577 if ( (strlen(ret->display_recp)+strlen(append)) < SIZ) {
3578 strcat(ret->display_recp, append);
3583 if ((ret->num_local + ret->num_internet + ret->num_ignet +
3584 ret->num_room + ret->num_error) == 0) {
3585 ret->num_error = (-1);
3586 strcpy(ret->errormsg, "No recipients specified.");
3589 CtdlLogPrintf(CTDL_DEBUG, "validate_recipients()\n");
3590 CtdlLogPrintf(CTDL_DEBUG, " local: %d <%s>\n", ret->num_local, ret->recp_local);
3591 CtdlLogPrintf(CTDL_DEBUG, " room: %d <%s>\n", ret->num_room, ret->recp_room);
3592 CtdlLogPrintf(CTDL_DEBUG, " inet: %d <%s>\n", ret->num_internet, ret->recp_internet);
3593 CtdlLogPrintf(CTDL_DEBUG, " ignet: %d <%s>\n", ret->num_ignet, ret->recp_ignet);
3594 CtdlLogPrintf(CTDL_DEBUG, " error: %d <%s>\n", ret->num_error, ret->errormsg);
3602 * Destructor for struct recptypes
3604 void free_recipients(struct recptypes *valid) {
3606 if (valid == NULL) {
3610 if (valid->recptypes_magic != RECPTYPES_MAGIC) {
3611 CtdlLogPrintf(CTDL_EMERG, "Attempt to call free_recipients() on some other data type!\n");
3615 if (valid->errormsg != NULL) free(valid->errormsg);
3616 if (valid->recp_local != NULL) free(valid->recp_local);
3617 if (valid->recp_internet != NULL) free(valid->recp_internet);
3618 if (valid->recp_ignet != NULL) free(valid->recp_ignet);
3619 if (valid->recp_room != NULL) free(valid->recp_room);
3620 if (valid->display_recp != NULL) free(valid->display_recp);
3627 * message entry - mode 0 (normal)
3629 void cmd_ent0(char *entargs)
3635 char supplied_euid[128];
3637 int format_type = 0;
3638 char newusername[256];
3639 char newuseremail[256];
3640 struct CtdlMessage *msg;
3644 struct recptypes *valid = NULL;
3645 struct recptypes *valid_to = NULL;
3646 struct recptypes *valid_cc = NULL;
3647 struct recptypes *valid_bcc = NULL;
3649 int subject_required = 0;
3654 int newuseremail_ok = 0;
3655 char references[SIZ];
3660 post = extract_int(entargs, 0);
3661 extract_token(recp, entargs, 1, '|', sizeof recp);
3662 anon_flag = extract_int(entargs, 2);
3663 format_type = extract_int(entargs, 3);
3664 extract_token(subject, entargs, 4, '|', sizeof subject);
3665 extract_token(newusername, entargs, 5, '|', sizeof newusername);
3666 do_confirm = extract_int(entargs, 6);
3667 extract_token(cc, entargs, 7, '|', sizeof cc);
3668 extract_token(bcc, entargs, 8, '|', sizeof bcc);
3669 switch(CC->room.QRdefaultview) {
3672 extract_token(supplied_euid, entargs, 9, '|', sizeof supplied_euid);
3675 supplied_euid[0] = 0;
3678 extract_token(newuseremail, entargs, 10, '|', sizeof newuseremail);
3679 extract_token(references, entargs, 11, '|', sizeof references);
3680 for (ptr=references; *ptr != 0; ++ptr) {
3681 if (*ptr == '!') *ptr = '|';
3684 /* first check to make sure the request is valid. */
3686 err = CtdlDoIHavePermissionToPostInThisRoom(errmsg, sizeof errmsg, NULL, POST_LOGGED_IN);
3689 cprintf("%d %s\n", err, errmsg);
3693 /* Check some other permission type things. */
3695 if (IsEmptyStr(newusername)) {
3696 strcpy(newusername, CC->user.fullname);
3698 if ( (CC->user.axlevel < 6)
3699 && (strcasecmp(newusername, CC->user.fullname))
3700 && (strcasecmp(newusername, CC->cs_inet_fn))
3702 cprintf("%d You don't have permission to author messages as '%s'.\n",
3703 ERROR + HIGHER_ACCESS_REQUIRED,
3710 if (IsEmptyStr(newuseremail)) {
3711 newuseremail_ok = 1;
3714 if (!IsEmptyStr(newuseremail)) {
3715 if (!strcasecmp(newuseremail, CC->cs_inet_email)) {
3716 newuseremail_ok = 1;
3718 else if (!IsEmptyStr(CC->cs_inet_other_emails)) {
3719 j = num_tokens(CC->cs_inet_other_emails, '|');
3720 for (i=0; i<j; ++i) {
3721 extract_token(buf, CC->cs_inet_other_emails, i, '|', sizeof buf);
3722 if (!strcasecmp(newuseremail, buf)) {
3723 newuseremail_ok = 1;
3729 if (!newuseremail_ok) {
3730 cprintf("%d You don't have permission to author messages as '%s'.\n",
3731 ERROR + HIGHER_ACCESS_REQUIRED,
3737 CC->cs_flags |= CS_POSTING;
3739 /* In mailbox rooms we have to behave a little differently --
3740 * make sure the user has specified at least one recipient. Then
3741 * validate the recipient(s). We do this for the Mail> room, as
3742 * well as any room which has the "Mailbox" view set.
3745 if ( ( (CC->room.QRflags & QR_MAILBOX) && (!strcasecmp(&CC->room.QRname[11], MAILROOM)) )
3746 || ( (CC->room.QRflags & QR_MAILBOX) && (CC->curr_view == VIEW_MAILBOX) )
3748 if (CC->user.axlevel < 2) {
3749 strcpy(recp, "sysop");
3754 valid_to = validate_recipients(recp, NULL, 0);
3755 if (valid_to->num_error > 0) {
3756 cprintf("%d %s\n", ERROR + NO_SUCH_USER, valid_to->errormsg);
3757 free_recipients(valid_to);
3761 valid_cc = validate_recipients(cc, NULL, 0);
3762 if (valid_cc->num_error > 0) {
3763 cprintf("%d %s\n", ERROR + NO_SUCH_USER, valid_cc->errormsg);
3764 free_recipients(valid_to);
3765 free_recipients(valid_cc);
3769 valid_bcc = validate_recipients(bcc, NULL, 0);
3770 if (valid_bcc->num_error > 0) {
3771 cprintf("%d %s\n", ERROR + NO_SUCH_USER, valid_bcc->errormsg);
3772 free_recipients(valid_to);
3773 free_recipients(valid_cc);
3774 free_recipients(valid_bcc);
3778 /* Recipient required, but none were specified */
3779 if ( (valid_to->num_error < 0) && (valid_cc->num_error < 0) && (valid_bcc->num_error < 0) ) {
3780 free_recipients(valid_to);
3781 free_recipients(valid_cc);
3782 free_recipients(valid_bcc);
3783 cprintf("%d At least one recipient is required.\n", ERROR + NO_SUCH_USER);
3787 if (valid_to->num_internet + valid_cc->num_internet + valid_bcc->num_internet > 0) {
3788 if (CtdlCheckInternetMailPermission(&CC->user)==0) {
3789 cprintf("%d You do not have permission "
3790 "to send Internet mail.\n",
3791 ERROR + HIGHER_ACCESS_REQUIRED);
3792 free_recipients(valid_to);
3793 free_recipients(valid_cc);
3794 free_recipients(valid_bcc);
3799 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)
3800 && (CC->user.axlevel < 4) ) {
3801 cprintf("%d Higher access required for network mail.\n",
3802 ERROR + HIGHER_ACCESS_REQUIRED);
3803 free_recipients(valid_to);
3804 free_recipients(valid_cc);
3805 free_recipients(valid_bcc);
3809 if ((RESTRICT_INTERNET == 1)
3810 && (valid_to->num_internet + valid_cc->num_internet + valid_bcc->num_internet > 0)
3811 && ((CC->user.flags & US_INTERNET) == 0)
3812 && (!CC->internal_pgm)) {
3813 cprintf("%d You don't have access to Internet mail.\n",
3814 ERROR + HIGHER_ACCESS_REQUIRED);
3815 free_recipients(valid_to);
3816 free_recipients(valid_cc);
3817 free_recipients(valid_bcc);
3823 /* Is this a room which has anonymous-only or anonymous-option? */
3824 anonymous = MES_NORMAL;
3825 if (CC->room.QRflags & QR_ANONONLY) {
3826 anonymous = MES_ANONONLY;
3828 if (CC->room.QRflags & QR_ANONOPT) {
3829 if (anon_flag == 1) { /* only if the user requested it */
3830 anonymous = MES_ANONOPT;
3834 if ((CC->room.QRflags & QR_MAILBOX) == 0) {
3838 /* Recommend to the client that the use of a message subject is
3839 * strongly recommended in this room, if either the SUBJECTREQ flag
3840 * is set, or if there is one or more Internet email recipients.
3842 if (CC->room.QRflags2 & QR2_SUBJECTREQ) subject_required = 1;
3843 if (valid_to) if (valid_to->num_internet > 0) subject_required = 1;
3844 if (valid_cc) if (valid_cc->num_internet > 0) subject_required = 1;
3845 if (valid_bcc) if (valid_bcc->num_internet > 0) subject_required = 1;
3847 /* If we're only checking the validity of the request, return
3848 * success without creating the message.
3851 cprintf("%d %s|%d\n", CIT_OK,
3852 ((valid_to != NULL) ? valid_to->display_recp : ""),
3854 free_recipients(valid_to);
3855 free_recipients(valid_cc);
3856 free_recipients(valid_bcc);
3860 /* We don't need these anymore because we'll do it differently below */
3861 free_recipients(valid_to);
3862 free_recipients(valid_cc);
3863 free_recipients(valid_bcc);
3865 /* Read in the message from the client. */
3867 cprintf("%d send message\n", START_CHAT_MODE);
3869 cprintf("%d send message\n", SEND_LISTING);
3872 msg = CtdlMakeMessage(&CC->user, recp, cc,
3873 CC->room.QRname, anonymous, format_type,
3874 newusername, newuseremail, subject,
3875 ((!IsEmptyStr(supplied_euid)) ? supplied_euid : NULL),
3878 /* Put together one big recipients struct containing to/cc/bcc all in
3879 * one. This is for the envelope.
3881 char *all_recps = malloc(SIZ * 3);
3882 strcpy(all_recps, recp);
3883 if (!IsEmptyStr(cc)) {
3884 if (!IsEmptyStr(all_recps)) {
3885 strcat(all_recps, ",");
3887 strcat(all_recps, cc);
3889 if (!IsEmptyStr(bcc)) {
3890 if (!IsEmptyStr(all_recps)) {
3891 strcat(all_recps, ",");
3893 strcat(all_recps, bcc);
3895 if (!IsEmptyStr(all_recps)) {
3896 valid = validate_recipients(all_recps, NULL, 0);
3904 msgnum = CtdlSubmitMsg(msg, valid, "", QP_EADDR);
3907 cprintf("%ld\n", msgnum);
3909 cprintf("Message accepted.\n");
3912 cprintf("Internal error.\n");
3914 if (msg->cm_fields['E'] != NULL) {
3915 cprintf("%s\n", msg->cm_fields['E']);
3922 CtdlFreeMessage(msg);
3924 if (valid != NULL) {
3925 free_recipients(valid);
3933 * API function to delete messages which match a set of criteria
3934 * (returns the actual number of messages deleted)
3936 int CtdlDeleteMessages(char *room_name, /* which room */
3937 long *dmsgnums, /* array of msg numbers to be deleted */
3938 int num_dmsgnums, /* number of msgs to be deleted, or 0 for "any" */
3939 char *content_type /* or "" for any. regular expressions expected. */
3942 struct ctdlroom qrbuf;
3943 struct cdbdata *cdbfr;
3944 long *msglist = NULL;
3945 long *dellist = NULL;
3948 int num_deleted = 0;
3950 struct MetaData smi;
3953 int need_to_free_re = 0;
3955 if (content_type) if (!IsEmptyStr(content_type)) {
3956 regcomp(&re, content_type, 0);
3957 need_to_free_re = 1;
3959 CtdlLogPrintf(CTDL_DEBUG, "CtdlDeleteMessages(%s, %d msgs, %s)\n",
3960 room_name, num_dmsgnums, content_type);
3962 /* get room record, obtaining a lock... */
3963 if (lgetroom(&qrbuf, room_name) != 0) {
3964 CtdlLogPrintf(CTDL_ERR, "CtdlDeleteMessages(): Room <%s> not found\n",
3966 if (need_to_free_re) regfree(&re);
3967 return (0); /* room not found */
3969 cdbfr = cdb_fetch(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long));
3971 if (cdbfr != NULL) {
3972 dellist = malloc(cdbfr->len);
3973 msglist = (long *) cdbfr->ptr;
3974 cdbfr->ptr = NULL; /* CtdlDeleteMessages() now owns this memory */
3975 num_msgs = cdbfr->len / sizeof(long);
3979 for (i = 0; i < num_msgs; ++i) {
3982 /* Set/clear a bit for each criterion */
3984 /* 0 messages in the list or a null list means that we are
3985 * interested in deleting any messages which meet the other criteria.
3987 if ((num_dmsgnums == 0) || (dmsgnums == NULL)) {
3988 delete_this |= 0x01;
3991 for (j=0; j<num_dmsgnums; ++j) {
3992 if (msglist[i] == dmsgnums[j]) {
3993 delete_this |= 0x01;
3998 if (IsEmptyStr(content_type)) {
3999 delete_this |= 0x02;
4001 GetMetaData(&smi, msglist[i]);
4002 if (regexec(&re, smi.meta_content_type, 1, &pm, 0) == 0) {
4003 delete_this |= 0x02;
4007 /* Delete message only if all bits are set */
4008 if (delete_this == 0x03) {
4009 dellist[num_deleted++] = msglist[i];
4014 num_msgs = sort_msglist(msglist, num_msgs);
4015 cdb_store(CDB_MSGLISTS, &qrbuf.QRnumber, (int)sizeof(long),
4016 msglist, (int)(num_msgs * sizeof(long)));
4018 qrbuf.QRhighest = msglist[num_msgs - 1];
4022 /* Go through the messages we pulled out of the index, and decrement
4023 * their reference counts by 1. If this is the only room the message
4024 * was in, the reference count will reach zero and the message will
4025 * automatically be deleted from the database. We do this in a
4026 * separate pass because there might be plug-in hooks getting called,
4027 * and we don't want that happening during an S_ROOMS critical
4030 if (num_deleted) for (i=0; i<num_deleted; ++i) {
4031 PerformDeleteHooks(qrbuf.QRname, dellist[i]);
4032 AdjRefCount(dellist[i], -1);
4035 /* Now free the memory we used, and go away. */
4036 if (msglist != NULL) free(msglist);
4037 if (dellist != NULL) free(dellist);
4038 CtdlLogPrintf(CTDL_DEBUG, "%d message(s) deleted.\n", num_deleted);
4039 if (need_to_free_re) regfree(&re);
4040 return (num_deleted);
4046 * Check whether the current user has permission to delete messages from
4047 * the current room (returns 1 for yes, 0 for no)
4049 int CtdlDoIHavePermissionToDeleteMessagesFromThisRoom(void) {
4051 CtdlRoomAccess(&CC->room, &CC->user, &ra, NULL);
4052 if (ra & UA_DELETEALLOWED) return(1);
4060 * Delete message from current room
4062 void cmd_dele(char *args)
4071 extract_token(msgset, args, 0, '|', sizeof msgset);
4072 num_msgs = num_tokens(msgset, ',');
4074 cprintf("%d Nothing to do.\n", CIT_OK);
4078 if (CtdlDoIHavePermissionToDeleteMessagesFromThisRoom() == 0) {
4079 cprintf("%d Higher access required.\n",
4080 ERROR + HIGHER_ACCESS_REQUIRED);
4085 * Build our message set to be moved/copied
4087 msgs = malloc(num_msgs * sizeof(long));
4088 for (i=0; i<num_msgs; ++i) {
4089 extract_token(msgtok, msgset, i, ',', sizeof msgtok);
4090 msgs[i] = atol(msgtok);
4093 num_deleted = CtdlDeleteMessages(CC->room.QRname, msgs, num_msgs, "");
4097 cprintf("%d %d message%s deleted.\n", CIT_OK,
4098 num_deleted, ((num_deleted != 1) ? "s" : ""));
4100 cprintf("%d Message not found.\n", ERROR + MESSAGE_NOT_FOUND);
4106 * Back end API function for moves and deletes (multiple messages)
4108 int CtdlCopyMsgsToRoom(long *msgnums, int num_msgs, char *dest) {
4111 err = CtdlSaveMsgPointersInRoom(dest, msgnums, num_msgs, 1, NULL);
4112 if (err != 0) return(err);
4121 * move or copy a message to another room
4123 void cmd_move(char *args)
4130 char targ[ROOMNAMELEN];
4131 struct ctdlroom qtemp;
4138 extract_token(msgset, args, 0, '|', sizeof msgset);
4139 num_msgs = num_tokens(msgset, ',');
4141 cprintf("%d Nothing to do.\n", CIT_OK);
4145 extract_token(targ, args, 1, '|', sizeof targ);
4146 convert_room_name_macros(targ, sizeof targ);
4147 targ[ROOMNAMELEN - 1] = 0;
4148 is_copy = extract_int(args, 2);
4150 if (getroom(&qtemp, targ) != 0) {
4151 cprintf("%d '%s' does not exist.\n",
4152 ERROR + ROOM_NOT_FOUND, targ);
4156 getuser(&CC->user, CC->curr_user);
4157 CtdlRoomAccess(&qtemp, &CC->user, &ra, NULL);
4159 /* Check for permission to perform this operation.
4160 * Remember: "CC->room" is source, "qtemp" is target.
4164 /* Aides can move/copy */
4165 if (CC->user.axlevel >= 6) permit = 1;
4167 /* Room aides can move/copy */
4168 if (CC->user.usernum == CC->room.QRroomaide) permit = 1;
4170 /* Permit move/copy from personal rooms */
4171 if ((CC->room.QRflags & QR_MAILBOX)
4172 && (qtemp.QRflags & QR_MAILBOX)) permit = 1;
4174 /* Permit only copy from public to personal room */
4176 && (!(CC->room.QRflags & QR_MAILBOX))
4177 && (qtemp.QRflags & QR_MAILBOX)) permit = 1;
4179 /* Permit message removal from collaborative delete rooms */
4180 if (CC->room.QRflags2 & QR2_COLLABDEL) permit = 1;
4182 /* Users allowed to post into the target room may move into it too. */
4183 if ((CC->room.QRflags & QR_MAILBOX) &&
4184 (qtemp.QRflags & UA_POSTALLOWED)) permit = 1;
4186 /* User must have access to target room */
4187 if (!(ra & UA_KNOWN)) permit = 0;
4190 cprintf("%d Higher access required.\n",
4191 ERROR + HIGHER_ACCESS_REQUIRED);
4196 * Build our message set to be moved/copied
4198 msgs = malloc(num_msgs * sizeof(long));
4199 for (i=0; i<num_msgs; ++i) {
4200 extract_token(msgtok, msgset, i, ',', sizeof msgtok);
4201 msgs[i] = atol(msgtok);
4207 err = CtdlCopyMsgsToRoom(msgs, num_msgs, targ);
4209 cprintf("%d Cannot store message(s) in %s: error %d\n",
4215 /* Now delete the message from the source room,
4216 * if this is a 'move' rather than a 'copy' operation.
4219 CtdlDeleteMessages(CC->room.QRname, msgs, num_msgs, "");
4223 cprintf("%d Message(s) %s.\n", CIT_OK, (is_copy ? "copied" : "moved") );
4229 * GetMetaData() - Get the supplementary record for a message
4231 void GetMetaData(struct MetaData *smibuf, long msgnum)
4234 struct cdbdata *cdbsmi;
4237 memset(smibuf, 0, sizeof(struct MetaData));
4238 smibuf->meta_msgnum = msgnum;
4239 smibuf->meta_refcount = 1; /* Default reference count is 1 */
4241 /* Use the negative of the message number for its supp record index */
4242 TheIndex = (0L - msgnum);
4244 cdbsmi = cdb_fetch(CDB_MSGMAIN, &TheIndex, sizeof(long));
4245 if (cdbsmi == NULL) {
4246 return; /* record not found; go with defaults */
4248 memcpy(smibuf, cdbsmi->ptr,
4249 ((cdbsmi->len > sizeof(struct MetaData)) ?
4250 sizeof(struct MetaData) : cdbsmi->len));
4257 * PutMetaData() - (re)write supplementary record for a message
4259 void PutMetaData(struct MetaData *smibuf)
4263 /* Use the negative of the message number for the metadata db index */
4264 TheIndex = (0L - smibuf->meta_msgnum);
4266 cdb_store(CDB_MSGMAIN,
4267 &TheIndex, (int)sizeof(long),
4268 smibuf, (int)sizeof(struct MetaData));
4273 * AdjRefCount - submit an adjustment to the reference count for a message.
4274 * (These are just queued -- we actually process them later.)
4276 void AdjRefCount(long msgnum, int incr)
4278 struct arcq new_arcq;
4280 begin_critical_section(S_SUPPMSGMAIN);
4281 if (arcfp == NULL) {
4282 arcfp = fopen(file_arcq, "ab+");
4284 end_critical_section(S_SUPPMSGMAIN);
4286 /* msgnum < 0 means that we're trying to close the file */
4288 CtdlLogPrintf(CTDL_DEBUG, "Closing the AdjRefCount queue file\n");
4289 begin_critical_section(S_SUPPMSGMAIN);
4290 if (arcfp != NULL) {
4294 end_critical_section(S_SUPPMSGMAIN);
4299 * If we can't open the queue, perform the operation synchronously.
4301 if (arcfp == NULL) {
4302 TDAP_AdjRefCount(msgnum, incr);
4306 new_arcq.arcq_msgnum = msgnum;
4307 new_arcq.arcq_delta = incr;
4308 fwrite(&new_arcq, sizeof(struct arcq), 1, arcfp);
4316 * TDAP_ProcessAdjRefCountQueue()
4318 * Process the queue of message count adjustments that was created by calls
4319 * to AdjRefCount() ... by reading the queue and calling TDAP_AdjRefCount()
4320 * for each one. This should be an "off hours" operation.
4322 int TDAP_ProcessAdjRefCountQueue(void)
4324 char file_arcq_temp[PATH_MAX];
4327 struct arcq arcq_rec;
4328 int num_records_processed = 0;
4330 snprintf(file_arcq_temp, sizeof file_arcq_temp, "%s2", file_arcq);
4332 begin_critical_section(S_SUPPMSGMAIN);
4333 if (arcfp != NULL) {
4338 r = link(file_arcq, file_arcq_temp);
4340 CtdlLogPrintf(CTDL_CRIT, "%s: %s\n", file_arcq_temp, strerror(errno));
4341 end_critical_section(S_SUPPMSGMAIN);
4342 return(num_records_processed);
4346 end_critical_section(S_SUPPMSGMAIN);
4348 fp = fopen(file_arcq_temp, "rb");
4350 CtdlLogPrintf(CTDL_CRIT, "%s: %s\n", file_arcq_temp, strerror(errno));
4351 return(num_records_processed);
4354 while (fread(&arcq_rec, sizeof(struct arcq), 1, fp) == 1) {
4355 TDAP_AdjRefCount(arcq_rec.arcq_msgnum, arcq_rec.arcq_delta);
4356 ++num_records_processed;
4360 r = unlink(file_arcq_temp);
4362 CtdlLogPrintf(CTDL_CRIT, "%s: %s\n", file_arcq_temp, strerror(errno));
4365 return(num_records_processed);
4371 * TDAP_AdjRefCount - adjust the reference count for a message.
4372 * This one does it "for real" because it's called by
4373 * the autopurger function that processes the queue
4374 * created by AdjRefCount(). If a message's reference
4375 * count becomes zero, we also delete the message from
4376 * disk and de-index it.
4378 void TDAP_AdjRefCount(long msgnum, int incr)
4381 struct MetaData smi;
4384 /* This is a *tight* critical section; please keep it that way, as
4385 * it may get called while nested in other critical sections.
4386 * Complicating this any further will surely cause deadlock!
4388 begin_critical_section(S_SUPPMSGMAIN);
4389 GetMetaData(&smi, msgnum);
4390 smi.meta_refcount += incr;
4392 end_critical_section(S_SUPPMSGMAIN);
4393 CtdlLogPrintf(CTDL_DEBUG, "msg %ld ref count delta %d, is now %d\n",
4394 msgnum, incr, smi.meta_refcount);
4396 /* If the reference count is now zero, delete the message
4397 * (and its supplementary record as well).
4399 if (smi.meta_refcount == 0) {
4400 CtdlLogPrintf(CTDL_DEBUG, "Deleting message <%ld>\n", msgnum);
4402 /* Call delete hooks with NULL room to show it has gone altogether */
4403 PerformDeleteHooks(NULL, msgnum);
4405 /* Remove from message base */
4407 cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long));
4408 cdb_delete(CDB_BIGMSGS, &delnum, (int)sizeof(long));
4410 /* Remove metadata record */
4411 delnum = (0L - msgnum);
4412 cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long));
4418 * Write a generic object to this room
4420 * Note: this could be much more efficient. Right now we use two temporary
4421 * files, and still pull the message into memory as with all others.
4423 void CtdlWriteObject(char *req_room, /* Room to stuff it in */
4424 char *content_type, /* MIME type of this object */
4425 char *tempfilename, /* Where to fetch it from */
4426 struct ctdluser *is_mailbox, /* Mailbox room? */
4427 int is_binary, /* Is encoding necessary? */
4428 int is_unique, /* Del others of this type? */
4429 unsigned int flags /* Internal save flags */
4434 struct ctdlroom qrbuf;
4435 char roomname[ROOMNAMELEN];
4436 struct CtdlMessage *msg;
4438 char *raw_message = NULL;
4439 char *encoded_message = NULL;
4440 off_t raw_length = 0;
4442 if (is_mailbox != NULL) {
4443 MailboxName(roomname, sizeof roomname, is_mailbox, req_room);
4446 safestrncpy(roomname, req_room, sizeof(roomname));
4449 fp = fopen(tempfilename, "rb");
4451 CtdlLogPrintf(CTDL_CRIT, "Cannot open %s: %s\n",
4452 tempfilename, strerror(errno));
4455 fseek(fp, 0L, SEEK_END);
4456 raw_length = ftell(fp);
4458 CtdlLogPrintf(CTDL_DEBUG, "Raw length is %ld\n", (long)raw_length);
4460 raw_message = malloc((size_t)raw_length + 2);
4461 fread(raw_message, (size_t)raw_length, 1, fp);
4465 encoded_message = malloc((size_t)
4466 (((raw_length * 134) / 100) + 4096 ) );
4469 encoded_message = malloc((size_t)(raw_length + 4096));
4472 sprintf(encoded_message, "Content-type: %s\n", content_type);
4475 sprintf(&encoded_message[strlen(encoded_message)],
4476 "Content-transfer-encoding: base64\n\n"
4480 sprintf(&encoded_message[strlen(encoded_message)],
4481 "Content-transfer-encoding: 7bit\n\n"
4487 &encoded_message[strlen(encoded_message)],
4494 raw_message[raw_length] = 0;
4496 &encoded_message[strlen(encoded_message)],
4504 CtdlLogPrintf(CTDL_DEBUG, "Allocating\n");
4505 msg = malloc(sizeof(struct CtdlMessage));
4506 memset(msg, 0, sizeof(struct CtdlMessage));
4507 msg->cm_magic = CTDLMESSAGE_MAGIC;
4508 msg->cm_anon_type = MES_NORMAL;
4509 msg->cm_format_type = 4;
4510 msg->cm_fields['A'] = strdup(CC->user.fullname);
4511 msg->cm_fields['O'] = strdup(req_room);
4512 msg->cm_fields['N'] = strdup(config.c_nodename);
4513 msg->cm_fields['H'] = strdup(config.c_humannode);
4514 msg->cm_flags = flags;
4516 msg->cm_fields['M'] = encoded_message;
4518 /* Create the requested room if we have to. */
4519 if (getroom(&qrbuf, roomname) != 0) {
4520 create_room(roomname,
4521 ( (is_mailbox != NULL) ? 5 : 3 ),
4522 "", 0, 1, 0, VIEW_BBS);
4524 /* If the caller specified this object as unique, delete all
4525 * other objects of this type that are currently in the room.
4528 CtdlLogPrintf(CTDL_DEBUG, "Deleted %d other msgs of this type\n",
4529 CtdlDeleteMessages(roomname, NULL, 0, content_type)
4532 /* Now write the data */
4533 CtdlSubmitMsg(msg, NULL, roomname, 0);
4534 CtdlFreeMessage(msg);
4542 void CtdlGetSysConfigBackend(long msgnum, void *userdata) {
4543 config_msgnum = msgnum;
4547 char *CtdlGetSysConfig(char *sysconfname) {
4548 char hold_rm[ROOMNAMELEN];
4551 struct CtdlMessage *msg;
4554 strcpy(hold_rm, CC->room.QRname);
4555 if (getroom(&CC->room, SYSCONFIGROOM) != 0) {
4556 getroom(&CC->room, hold_rm);
4561 /* We want the last (and probably only) config in this room */
4562 begin_critical_section(S_CONFIG);
4563 config_msgnum = (-1L);
4564 CtdlForEachMessage(MSGS_LAST, 1, NULL, sysconfname, NULL,
4565 CtdlGetSysConfigBackend, NULL);
4566 msgnum = config_msgnum;
4567 end_critical_section(S_CONFIG);
4573 msg = CtdlFetchMessage(msgnum, 1);
4575 conf = strdup(msg->cm_fields['M']);
4576 CtdlFreeMessage(msg);
4583 getroom(&CC->room, hold_rm);
4585 if (conf != NULL) do {
4586 extract_token(buf, conf, 0, '\n', sizeof buf);
4587 strcpy(conf, &conf[strlen(buf)+1]);
4588 } while ( (!IsEmptyStr(conf)) && (!IsEmptyStr(buf)) );
4593 void CtdlPutSysConfig(char *sysconfname, char *sysconfdata) {
4594 char temp[PATH_MAX];
4597 CtdlMakeTempFileName(temp, sizeof temp);
4599 fp = fopen(temp, "w");
4600 if (fp == NULL) return;
4601 fprintf(fp, "%s", sysconfdata);
4604 /* this handy API function does all the work for us */
4605 CtdlWriteObject(SYSCONFIGROOM, sysconfname, temp, NULL, 0, 1, 0);
4611 * Determine whether a given Internet address belongs to the current user
4613 int CtdlIsMe(char *addr, int addr_buf_len)
4615 struct recptypes *recp;
4618 recp = validate_recipients(addr, NULL, 0);
4619 if (recp == NULL) return(0);
4621 if (recp->num_local == 0) {
4622 free_recipients(recp);
4626 for (i=0; i<recp->num_local; ++i) {
4627 extract_token(addr, recp->recp_local, i, '|', addr_buf_len);
4628 if (!strcasecmp(addr, CC->user.fullname)) {
4629 free_recipients(recp);
4634 free_recipients(recp);
4640 * Citadel protocol command to do the same
4642 void cmd_isme(char *argbuf) {
4645 if (CtdlAccessCheck(ac_logged_in)) return;
4646 extract_token(addr, argbuf, 0, '|', sizeof addr);
4648 if (CtdlIsMe(addr, sizeof addr)) {
4649 cprintf("%d %s\n", CIT_OK, addr);
4652 cprintf("%d Not you.\n", ERROR + ILLEGAL_VALUE);