4 * Implements the message store.
14 #if TIME_WITH_SYS_TIME
15 # include <sys/time.h>
19 # include <sys/time.h>
34 #include "serv_extensions.h"
38 #include "sysdep_decls.h"
39 #include "citserver.h"
46 #include "mime_parser.h"
49 #include "internet_addressing.h"
50 #include "serv_fulltext.h"
52 #include "euidindex.h"
53 #include "journaling.h"
54 #include "citadel_dirs.h"
55 #include "serv_network.h"
58 # include "serv_sieve.h"
59 #endif /* HAVE_LIBSIEVE */
62 struct addresses_to_be_filed *atbf = NULL;
65 * This really belongs in serv_network.c, but I don't know how to export
66 * symbols between modules.
68 struct FilterList *filterlist = NULL;
72 * These are the four-character field headers we use when outputting
73 * messages in Citadel format (as opposed to RFC822 format).
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,
83 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
111 * This function is self explanatory.
112 * (What can I say, I'm in a weird mood today...)
114 void remove_any_whitespace_to_the_left_or_right_of_at_symbol(char *name)
118 for (i = 0; i < strlen(name); ++i) {
119 if (name[i] == '@') {
120 while (isspace(name[i - 1]) && i > 0) {
121 strcpy(&name[i - 1], &name[i]);
124 while (isspace(name[i + 1])) {
125 strcpy(&name[i + 1], &name[i + 2]);
133 * Aliasing for network mail.
134 * (Error messages have been commented out, because this is a server.)
136 int alias(char *name)
137 { /* process alias and routing info for mail */
140 char aaa[SIZ], bbb[SIZ];
141 char *ignetcfg = NULL;
142 char *ignetmap = NULL;
149 remove_any_whitespace_to_the_left_or_right_of_at_symbol(name);
150 stripallbut(name, '<', '>');
152 fp = fopen(file_mail_aliases, "r");
154 fp = fopen("/dev/null", "r");
161 while (fgets(aaa, sizeof aaa, fp) != NULL) {
162 while (isspace(name[0]))
163 strcpy(name, &name[1]);
164 aaa[strlen(aaa) - 1] = 0;
166 for (a = 0; a < strlen(aaa); ++a) {
168 strcpy(bbb, &aaa[a + 1]);
172 if (!strcasecmp(name, aaa))
177 /* Hit the Global Address Book */
178 if (CtdlDirectoryLookup(aaa, name, sizeof aaa) == 0) {
182 lprintf(CTDL_INFO, "Mail is being forwarded to %s\n", name);
184 /* Change "user @ xxx" to "user" if xxx is an alias for this host */
185 for (a=0; a<strlen(name); ++a) {
186 if (name[a] == '@') {
187 if (CtdlHostAlias(&name[a+1]) == hostalias_localhost) {
189 lprintf(CTDL_INFO, "Changed to <%s>\n", name);
194 /* determine local or remote type, see citadel.h */
195 at = haschar(name, '@');
196 if (at == 0) return(MES_LOCAL); /* no @'s - local address */
197 if (at > 1) return(MES_ERROR); /* >1 @'s - invalid address */
198 remove_any_whitespace_to_the_left_or_right_of_at_symbol(name);
200 /* figure out the delivery mode */
201 extract_token(node, name, 1, '@', sizeof node);
203 /* If there are one or more dots in the nodename, we assume that it
204 * is an FQDN and will attempt SMTP delivery to the Internet.
206 if (haschar(node, '.') > 0) {
207 return(MES_INTERNET);
210 /* Otherwise we look in the IGnet maps for a valid Citadel node.
211 * Try directly-connected nodes first...
213 ignetcfg = CtdlGetSysConfig(IGNETCFG);
214 for (i=0; i<num_tokens(ignetcfg, '\n'); ++i) {
215 extract_token(buf, ignetcfg, i, '\n', sizeof buf);
216 extract_token(testnode, buf, 0, '|', sizeof testnode);
217 if (!strcasecmp(node, testnode)) {
225 * Then try nodes that are two or more hops away.
227 ignetmap = CtdlGetSysConfig(IGNETMAP);
228 for (i=0; i<num_tokens(ignetmap, '\n'); ++i) {
229 extract_token(buf, ignetmap, i, '\n', sizeof buf);
230 extract_token(testnode, buf, 0, '|', sizeof testnode);
231 if (!strcasecmp(node, testnode)) {
238 /* If we get to this point it's an invalid node name */
247 fp = fopen(file_citadel_control, "r");
249 lprintf(CTDL_CRIT, "Cannot open %s: %s\n",
250 file_citadel_control,
254 fread((char *) &CitControl, sizeof(struct CitControl), 1, fp);
260 * Back end for the MSGS command: output message number only.
262 void simple_listing(long msgnum, void *userdata)
264 cprintf("%ld\n", msgnum);
270 * Back end for the MSGS command: output header summary.
272 void headers_listing(long msgnum, void *userdata)
274 struct CtdlMessage *msg;
276 msg = CtdlFetchMessage(msgnum, 0);
278 cprintf("%ld|0|||||\n", msgnum);
282 cprintf("%ld|%s|%s|%s|%s|%s|\n",
284 (msg->cm_fields['T'] ? msg->cm_fields['T'] : "0"),
285 (msg->cm_fields['A'] ? msg->cm_fields['A'] : ""),
286 (msg->cm_fields['N'] ? msg->cm_fields['N'] : ""),
287 (msg->cm_fields['F'] ? msg->cm_fields['F'] : ""),
288 (msg->cm_fields['U'] ? msg->cm_fields['U'] : "")
290 CtdlFreeMessage(msg);
295 /* Determine if a given message matches the fields in a message template.
296 * Return 0 for a successful match.
298 int CtdlMsgCmp(struct CtdlMessage *msg, struct CtdlMessage *template) {
301 /* If there aren't any fields in the template, all messages will
304 if (template == NULL) return(0);
306 /* Null messages are bogus. */
307 if (msg == NULL) return(1);
309 for (i='A'; i<='Z'; ++i) {
310 if (template->cm_fields[i] != NULL) {
311 if (msg->cm_fields[i] == NULL) {
314 if (strcasecmp(msg->cm_fields[i],
315 template->cm_fields[i])) return 1;
319 /* All compares succeeded: we have a match! */
326 * Retrieve the "seen" message list for the current room.
328 void CtdlGetSeen(char *buf, int which_set) {
331 /* Learn about the user and room in question */
332 CtdlGetRelationship(&vbuf, &CC->user, &CC->room);
334 if (which_set == ctdlsetseen_seen)
335 safestrncpy(buf, vbuf.v_seen, SIZ);
336 if (which_set == ctdlsetseen_answered)
337 safestrncpy(buf, vbuf.v_answered, SIZ);
343 * Manipulate the "seen msgs" string (or other message set strings)
345 void CtdlSetSeen(long *target_msgnums, int num_target_msgnums,
346 int target_setting, int which_set,
347 struct ctdluser *which_user, struct ctdlroom *which_room) {
348 struct cdbdata *cdbfr;
360 char *is_set; /* actually an array of booleans */
363 char setstr[SIZ], lostr[SIZ], histr[SIZ];
366 lprintf(CTDL_DEBUG, "CtdlSetSeen(%d msgs starting with %ld, %d, %d)\n",
367 num_target_msgnums, target_msgnums[0],
368 target_setting, which_set);
370 /* Learn about the user and room in question */
371 CtdlGetRelationship(&vbuf,
372 ((which_user != NULL) ? which_user : &CC->user),
373 ((which_room != NULL) ? which_room : &CC->room)
376 /* Load the message list */
377 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long));
379 msglist = (long *) cdbfr->ptr;
380 cdbfr->ptr = NULL; /* CtdlSetSeen() now owns this memory */
381 num_msgs = cdbfr->len / sizeof(long);
384 return; /* No messages at all? No further action. */
387 is_set = malloc(num_msgs * sizeof(char));
388 memset(is_set, 0, (num_msgs * sizeof(char)) );
390 /* Decide which message set we're manipulating */
392 case ctdlsetseen_seen:
393 safestrncpy(vset, vbuf.v_seen, sizeof vset);
395 case ctdlsetseen_answered:
396 safestrncpy(vset, vbuf.v_answered, sizeof vset);
400 /* lprintf(CTDL_DEBUG, "before optimize: %s\n", vset); */
402 /* Translate the existing sequence set into an array of booleans */
403 num_sets = num_tokens(vset, ',');
404 for (s=0; s<num_sets; ++s) {
405 extract_token(setstr, vset, s, ',', sizeof setstr);
407 extract_token(lostr, setstr, 0, ':', sizeof lostr);
408 if (num_tokens(setstr, ':') >= 2) {
409 extract_token(histr, setstr, 1, ':', sizeof histr);
410 if (!strcmp(histr, "*")) {
411 snprintf(histr, sizeof histr, "%ld", LONG_MAX);
415 strcpy(histr, lostr);
420 for (i = 0; i < num_msgs; ++i) {
421 if ((msglist[i] >= lo) && (msglist[i] <= hi)) {
427 /* Now translate the array of booleans back into a sequence set */
432 for (i=0; i<num_msgs; ++i) {
434 is_seen = is_set[i]; /* Default to existing setting */
436 for (k=0; k<num_target_msgnums; ++k) {
437 if (msglist[i] == target_msgnums[k]) {
438 is_seen = target_setting;
443 if (lo < 0L) lo = msglist[i];
447 if ( ((is_seen == 0) && (was_seen == 1))
448 || ((is_seen == 1) && (i == num_msgs-1)) ) {
450 /* begin trim-o-matic code */
453 while ( (strlen(vset) + 20) > sizeof vset) {
454 remove_token(vset, 0, ',');
456 if (j--) break; /* loop no more than 9 times */
458 if ( (trimming) && (which_set == ctdlsetseen_seen) ) {
462 snprintf(lostr, sizeof lostr,
463 "1:%ld,%s", t, vset);
464 safestrncpy(vset, lostr, sizeof vset);
466 /* end trim-o-matic code */
474 snprintf(&vset[tmp], (sizeof vset) - tmp,
478 snprintf(&vset[tmp], (sizeof vset) - tmp,
487 /* Decide which message set we're manipulating */
489 case ctdlsetseen_seen:
490 safestrncpy(vbuf.v_seen, vset, sizeof vbuf.v_seen);
492 case ctdlsetseen_answered:
493 safestrncpy(vbuf.v_answered, vset,
494 sizeof vbuf.v_answered);
499 /* lprintf(CTDL_DEBUG, " after optimize: %s\n", vset); */
501 CtdlSetRelationship(&vbuf,
502 ((which_user != NULL) ? which_user : &CC->user),
503 ((which_room != NULL) ? which_room : &CC->room)
509 * API function to perform an operation for each qualifying message in the
510 * current room. (Returns the number of messages processed.)
512 int CtdlForEachMessage(int mode, long ref, char *search_string,
514 struct CtdlMessage *compare,
515 void (*CallBack) (long, void *),
521 struct cdbdata *cdbfr;
522 long *msglist = NULL;
524 int num_processed = 0;
527 struct CtdlMessage *msg;
530 int printed_lastold = 0;
531 int num_search_msgs = 0;
532 long *search_msgs = NULL;
534 /* 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 cdbfr->ptr = NULL; /* CtdlForEachMessage() now owns this memory */
544 num_msgs = cdbfr->len / sizeof(long);
547 return 0; /* No messages at all? No further action. */
552 * Now begin the traversal.
554 if (num_msgs > 0) for (a = 0; a < num_msgs; ++a) {
556 /* If the caller is looking for a specific MIME type, filter
557 * out all messages which are not of the type requested.
559 if (content_type != NULL) if (strlen(content_type) > 0) {
561 /* This call to GetMetaData() sits inside this loop
562 * so that we only do the extra database read per msg
563 * if we need to. Doing the extra read all the time
564 * really kills the server. If we ever need to use
565 * metadata for another search criterion, we need to
566 * move the read somewhere else -- but still be smart
567 * enough to only do the read if the caller has
568 * specified something that will need it.
570 GetMetaData(&smi, msglist[a]);
572 if (strcasecmp(smi.meta_content_type, content_type)) {
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) ) {
609 ft_search(&num_search_msgs, &search_msgs, search_string);
610 if (num_search_msgs > 0) {
614 orig_num_msgs = num_msgs;
616 for (i=0; i<orig_num_msgs; ++i) {
617 for (j=0; j<num_search_msgs; ++j) {
618 if (msglist[i] == search_msgs[j]) {
619 msglist[num_msgs++] = msglist[i];
625 num_msgs = 0; /* No messages qualify */
627 if (search_msgs != NULL) free(search_msgs);
629 /* Now that we've purged messages which don't contain the search
630 * string, treat a MSGS_SEARCH just like a MSGS_ALL from this
637 * Now iterate through the message list, according to the
638 * criteria supplied by the caller.
641 for (a = 0; a < num_msgs; ++a) {
642 thismsg = msglist[a];
643 if (mode == MSGS_ALL) {
647 is_seen = is_msg_in_sequence_set(
648 vbuf.v_seen, thismsg);
649 if (is_seen) lastold = thismsg;
655 || ((mode == MSGS_OLD) && (is_seen))
656 || ((mode == MSGS_NEW) && (!is_seen))
657 || ((mode == MSGS_LAST) && (a >= (num_msgs - ref)))
658 || ((mode == MSGS_FIRST) && (a < ref))
659 || ((mode == MSGS_GT) && (thismsg > ref))
660 || ((mode == MSGS_EQ) && (thismsg == ref))
663 if ((mode == MSGS_NEW) && (CC->user.flags & US_LASTOLD) && (lastold > 0L) && (printed_lastold == 0) && (!is_seen)) {
665 CallBack(lastold, userdata);
669 if (CallBack) CallBack(thismsg, userdata);
673 free(msglist); /* Clean up */
674 return num_processed;
680 * cmd_msgs() - get list of message #'s in this room
681 * implements the MSGS server command using CtdlForEachMessage()
683 void cmd_msgs(char *cmdbuf)
692 int with_template = 0;
693 struct CtdlMessage *template = NULL;
694 int with_headers = 0;
695 char search_string[1024];
697 extract_token(which, cmdbuf, 0, '|', sizeof which);
698 cm_ref = extract_int(cmdbuf, 1);
699 extract_token(search_string, cmdbuf, 1, '|', sizeof search_string);
700 with_template = extract_int(cmdbuf, 2);
701 with_headers = extract_int(cmdbuf, 3);
704 if (!strncasecmp(which, "OLD", 3))
706 else if (!strncasecmp(which, "NEW", 3))
708 else if (!strncasecmp(which, "FIRST", 5))
710 else if (!strncasecmp(which, "LAST", 4))
712 else if (!strncasecmp(which, "GT", 2))
714 else if (!strncasecmp(which, "SEARCH", 6))
719 if ((!(CC->logged_in)) && (!(CC->internal_pgm))) {
720 cprintf("%d not logged in\n", ERROR + NOT_LOGGED_IN);
724 if ( (mode == MSGS_SEARCH) && (!config.c_enable_fulltext) ) {
725 cprintf("%d Full text index is not enabled on this server.\n",
726 ERROR + CMD_NOT_SUPPORTED);
732 cprintf("%d Send template then receive message list\n",
734 template = (struct CtdlMessage *)
735 malloc(sizeof(struct CtdlMessage));
736 memset(template, 0, sizeof(struct CtdlMessage));
737 while(client_getln(buf, sizeof buf), strcmp(buf,"000")) {
738 extract_token(tfield, buf, 0, '|', sizeof tfield);
739 extract_token(tvalue, buf, 1, '|', sizeof tvalue);
740 for (i='A'; i<='Z'; ++i) if (msgkeys[i]!=NULL) {
741 if (!strcasecmp(tfield, msgkeys[i])) {
742 template->cm_fields[i] =
750 cprintf("%d \n", LISTING_FOLLOWS);
753 CtdlForEachMessage(mode,
754 ( (mode == MSGS_SEARCH) ? 0 : cm_ref ),
755 ( (mode == MSGS_SEARCH) ? search_string : NULL ),
758 (with_headers ? headers_listing : simple_listing),
761 if (template != NULL) CtdlFreeMessage(template);
769 * help_subst() - support routine for help file viewer
771 void help_subst(char *strbuf, char *source, char *dest)
776 while (p = pattern2(strbuf, source), (p >= 0)) {
777 strcpy(workbuf, &strbuf[p + strlen(source)]);
778 strcpy(&strbuf[p], dest);
779 strcat(strbuf, workbuf);
784 void do_help_subst(char *buffer)
788 help_subst(buffer, "^nodename", config.c_nodename);
789 help_subst(buffer, "^humannode", config.c_humannode);
790 help_subst(buffer, "^fqdn", config.c_fqdn);
791 help_subst(buffer, "^username", CC->user.fullname);
792 snprintf(buf2, sizeof buf2, "%ld", CC->user.usernum);
793 help_subst(buffer, "^usernum", buf2);
794 help_subst(buffer, "^sysadm", config.c_sysadm);
795 help_subst(buffer, "^variantname", CITADEL);
796 snprintf(buf2, sizeof buf2, "%d", config.c_maxsessions);
797 help_subst(buffer, "^maxsessions", buf2);
798 help_subst(buffer, "^bbsdir", ctdl_bbsbase_dir);
804 * memfmout() - Citadel text formatter and paginator.
805 * Although the original purpose of this routine was to format
806 * text to the reader's screen width, all we're really using it
807 * for here is to format text out to 80 columns before sending it
808 * to the client. The client software may reformat it again.
811 char *mptr, /* where are we going to get our text from? */
812 char subst, /* nonzero if we should do substitutions */
813 char *nl) /* string to terminate lines with */
821 static int width = 80;
826 c = 1; /* c is the current pos */
830 while (ch = *mptr, ((ch != 0) && (strlen(buffer) < 126))) {
832 buffer[strlen(buffer) + 1] = 0;
833 buffer[strlen(buffer)] = ch;
836 if (buffer[0] == '^')
837 do_help_subst(buffer);
839 buffer[strlen(buffer) + 1] = 0;
841 strcpy(buffer, &buffer[1]);
849 if (((ch == 13) || (ch == 10)) && (old != 13) && (old != 10)) {
852 if (((old == 13) || (old == 10)) && (isspace(real))) {
857 if (((strlen(aaa) + c) > (width - 5)) && (strlen(aaa) > (width - 5))) {
858 cprintf("%s%s", nl, aaa);
867 if ((strlen(aaa) + c) > (width - 5)) {
876 if ((ch == 13) || (ch == 10)) {
877 cprintf("%s%s", aaa, nl);
884 cprintf("%s%s", aaa, nl);
890 * Callback function for mime parser that simply lists the part
892 void list_this_part(char *name, char *filename, char *partnum, char *disp,
893 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
898 ma = (struct ma_info *)cbuserdata;
899 if (ma->is_ma == 0) {
900 cprintf("part=%s|%s|%s|%s|%s|%ld\n",
901 name, filename, partnum, disp, cbtype, (long)length);
906 * Callback function for multipart prefix
908 void list_this_pref(char *name, char *filename, char *partnum, char *disp,
909 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
914 ma = (struct ma_info *)cbuserdata;
915 if (!strcasecmp(cbtype, "multipart/alternative")) {
919 if (ma->is_ma == 0) {
920 cprintf("pref=%s|%s\n", partnum, cbtype);
925 * Callback function for multipart sufffix
927 void list_this_suff(char *name, char *filename, char *partnum, char *disp,
928 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
933 ma = (struct ma_info *)cbuserdata;
934 if (ma->is_ma == 0) {
935 cprintf("suff=%s|%s\n", partnum, cbtype);
937 if (!strcasecmp(cbtype, "multipart/alternative")) {
944 * Callback function for mime parser that opens a section for downloading
946 void mime_download(char *name, char *filename, char *partnum, char *disp,
947 void *content, char *cbtype, char *cbcharset, size_t length,
948 char *encoding, void *cbuserdata)
951 /* Silently go away if there's already a download open... */
952 if (CC->download_fp != NULL)
955 /* ...or if this is not the desired section */
956 if (strcasecmp(CC->download_desired_section, partnum))
959 CC->download_fp = tmpfile();
960 if (CC->download_fp == NULL)
963 fwrite(content, length, 1, CC->download_fp);
964 fflush(CC->download_fp);
965 rewind(CC->download_fp);
967 OpenCmdResult(filename, cbtype);
973 * Load a message from disk into memory.
974 * This is used by CtdlOutputMsg() and other fetch functions.
976 * NOTE: Caller is responsible for freeing the returned CtdlMessage struct
977 * using the CtdlMessageFree() function.
979 struct CtdlMessage *CtdlFetchMessage(long msgnum, int with_body)
981 struct cdbdata *dmsgtext;
982 struct CtdlMessage *ret = NULL;
986 cit_uint8_t field_header;
988 lprintf(CTDL_DEBUG, "CtdlFetchMessage(%ld, %d)\n", msgnum, with_body);
990 dmsgtext = cdb_fetch(CDB_MSGMAIN, &msgnum, sizeof(long));
991 if (dmsgtext == NULL) {
994 mptr = dmsgtext->ptr;
995 upper_bound = mptr + dmsgtext->len;
997 /* Parse the three bytes that begin EVERY message on disk.
998 * The first is always 0xFF, the on-disk magic number.
999 * The second is the anonymous/public type byte.
1000 * The third is the format type byte (vari, fixed, or MIME).
1005 "Message %ld appears to be corrupted.\n",
1010 ret = (struct CtdlMessage *) malloc(sizeof(struct CtdlMessage));
1011 memset(ret, 0, sizeof(struct CtdlMessage));
1013 ret->cm_magic = CTDLMESSAGE_MAGIC;
1014 ret->cm_anon_type = *mptr++; /* Anon type byte */
1015 ret->cm_format_type = *mptr++; /* Format type byte */
1018 * The rest is zero or more arbitrary fields. Load them in.
1019 * We're done when we encounter either a zero-length field or
1020 * have just processed the 'M' (message text) field.
1023 if (mptr >= upper_bound) {
1026 field_header = *mptr++;
1027 ret->cm_fields[field_header] = strdup(mptr);
1029 while (*mptr++ != 0); /* advance to next field */
1031 } while ((mptr < upper_bound) && (field_header != 'M'));
1035 /* Always make sure there's something in the msg text field. If
1036 * it's NULL, the message text is most likely stored separately,
1037 * so go ahead and fetch that. Failing that, just set a dummy
1038 * body so other code doesn't barf.
1040 if ( (ret->cm_fields['M'] == NULL) && (with_body) ) {
1041 dmsgtext = cdb_fetch(CDB_BIGMSGS, &msgnum, sizeof(long));
1042 if (dmsgtext != NULL) {
1043 ret->cm_fields['M'] = strdup(dmsgtext->ptr);
1047 if (ret->cm_fields['M'] == NULL) {
1048 ret->cm_fields['M'] = strdup("\r\n\r\n (no text)\r\n");
1051 /* Perform "before read" hooks (aborting if any return nonzero) */
1052 if (PerformMessageHooks(ret, EVT_BEFOREREAD) > 0) {
1053 CtdlFreeMessage(ret);
1062 * Returns 1 if the supplied pointer points to a valid Citadel message.
1063 * If the pointer is NULL or the magic number check fails, returns 0.
1065 int is_valid_message(struct CtdlMessage *msg) {
1068 if ((msg->cm_magic) != CTDLMESSAGE_MAGIC) {
1069 lprintf(CTDL_WARNING, "is_valid_message() -- self-check failed\n");
1077 * 'Destructor' for struct CtdlMessage
1079 void CtdlFreeMessage(struct CtdlMessage *msg)
1083 if (is_valid_message(msg) == 0) return;
1085 for (i = 0; i < 256; ++i)
1086 if (msg->cm_fields[i] != NULL) {
1087 free(msg->cm_fields[i]);
1090 msg->cm_magic = 0; /* just in case */
1096 * Pre callback function for multipart/alternative
1098 * NOTE: this differs from the standard behavior for a reason. Normally when
1099 * displaying multipart/alternative you want to show the _last_ usable
1100 * format in the message. Here we show the _first_ one, because it's
1101 * usually text/plain. Since this set of functions is designed for text
1102 * output to non-MIME-aware clients, this is the desired behavior.
1105 void fixed_output_pre(char *name, char *filename, char *partnum, char *disp,
1106 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
1111 ma = (struct ma_info *)cbuserdata;
1112 lprintf(CTDL_DEBUG, "fixed_output_pre() type=<%s>\n", cbtype);
1113 if (!strcasecmp(cbtype, "multipart/alternative")) {
1117 if (!strcasecmp(cbtype, "message/rfc822")) {
1123 * Post callback function for multipart/alternative
1125 void fixed_output_post(char *name, char *filename, char *partnum, char *disp,
1126 void *content, char *cbtype, char *cbcharset, size_t length,
1127 char *encoding, void *cbuserdata)
1131 ma = (struct ma_info *)cbuserdata;
1132 lprintf(CTDL_DEBUG, "fixed_output_post() type=<%s>\n", cbtype);
1133 if (!strcasecmp(cbtype, "multipart/alternative")) {
1137 if (!strcasecmp(cbtype, "message/rfc822")) {
1143 * Inline callback function for mime parser that wants to display text
1145 void fixed_output(char *name, char *filename, char *partnum, char *disp,
1146 void *content, char *cbtype, char *cbcharset, size_t length,
1147 char *encoding, void *cbuserdata)
1154 ma = (struct ma_info *)cbuserdata;
1157 "fixed_output() part %s: %s (%s) (%ld bytes)\n",
1158 partnum, filename, cbtype, (long)length);
1161 * If we're in the middle of a multipart/alternative scope and
1162 * we've already printed another section, skip this one.
1164 if ( (ma->is_ma) && (ma->did_print) ) {
1165 lprintf(CTDL_DEBUG, "Skipping part %s (%s)\n",
1171 if ( (!strcasecmp(cbtype, "text/plain"))
1172 || (strlen(cbtype)==0) ) {
1175 client_write(wptr, length);
1176 if (wptr[length-1] != '\n') {
1183 if (!strcasecmp(cbtype, "text/html")) {
1184 ptr = html_to_ascii(content, length, 80, 0);
1186 client_write(ptr, wlen);
1187 if (ptr[wlen-1] != '\n') {
1194 if (ma->use_fo_hooks) {
1195 if (PerformFixedOutputHooks(cbtype, content, length)) {
1196 /* above function returns nonzero if it handled the part */
1201 if (strncasecmp(cbtype, "multipart/", 10)) {
1202 cprintf("Part %s: %s (%s) (%ld bytes)\r\n",
1203 partnum, filename, cbtype, (long)length);
1209 * The client is elegant and sophisticated and wants to be choosy about
1210 * MIME content types, so figure out which multipart/alternative part
1211 * we're going to send.
1213 void choose_preferred(char *name, char *filename, char *partnum, char *disp,
1214 void *content, char *cbtype, char *cbcharset, size_t length,
1215 char *encoding, void *cbuserdata)
1221 ma = (struct ma_info *)cbuserdata;
1223 if (ma->is_ma > 0) {
1224 for (i=0; i<num_tokens(CC->preferred_formats, '|'); ++i) {
1225 extract_token(buf, CC->preferred_formats, i, '|', sizeof buf);
1226 if ( (!strcasecmp(buf, cbtype)) && (!ma->freeze) ) {
1227 safestrncpy(ma->chosen_part, partnum, sizeof ma->chosen_part);
1234 * Now that we've chosen our preferred part, output it.
1236 void output_preferred(char *name, char *filename, char *partnum, char *disp,
1237 void *content, char *cbtype, char *cbcharset, size_t length,
1238 char *encoding, void *cbuserdata)
1242 int add_newline = 0;
1246 ma = (struct ma_info *)cbuserdata;
1248 /* This is not the MIME part you're looking for... */
1249 if (strcasecmp(partnum, ma->chosen_part)) return;
1251 /* If the content-type of this part is in our preferred formats
1252 * list, we can simply output it verbatim.
1254 for (i=0; i<num_tokens(CC->preferred_formats, '|'); ++i) {
1255 extract_token(buf, CC->preferred_formats, i, '|', sizeof buf);
1256 if (!strcasecmp(buf, cbtype)) {
1257 /* Yeah! Go! W00t!! */
1259 text_content = (char *)content;
1260 if (text_content[length-1] != '\n') {
1264 cprintf("Content-type: %s", cbtype);
1265 if (strlen(cbcharset) > 0) {
1266 cprintf("; charset=%s", cbcharset);
1268 cprintf("\nContent-length: %d\n",
1269 (int)(length + add_newline) );
1270 if (strlen(encoding) > 0) {
1271 cprintf("Content-transfer-encoding: %s\n", encoding);
1274 cprintf("Content-transfer-encoding: 7bit\n");
1277 client_write(content, length);
1278 if (add_newline) cprintf("\n");
1283 /* No translations required or possible: output as text/plain */
1284 cprintf("Content-type: text/plain\n\n");
1285 fixed_output(name, filename, partnum, disp, content, cbtype, cbcharset,
1286 length, encoding, cbuserdata);
1291 char desired_section[64];
1298 * Callback function for
1300 void extract_encapsulated_message(char *name, char *filename, char *partnum, char *disp,
1301 void *content, char *cbtype, char *cbcharset, size_t length,
1302 char *encoding, void *cbuserdata)
1304 struct encapmsg *encap;
1306 encap = (struct encapmsg *)cbuserdata;
1308 /* Only proceed if this is the desired section... */
1309 if (!strcasecmp(encap->desired_section, partnum)) {
1310 encap->msglen = length;
1311 encap->msg = malloc(length + 2);
1312 memcpy(encap->msg, content, length);
1322 * Get a message off disk. (returns om_* values found in msgbase.h)
1325 int CtdlOutputMsg(long msg_num, /* message number (local) to fetch */
1326 int mode, /* how would you like that message? */
1327 int headers_only, /* eschew the message body? */
1328 int do_proto, /* do Citadel protocol responses? */
1329 int crlf, /* Use CRLF newlines instead of LF? */
1330 char *section /* NULL or a message/rfc822 section */
1332 struct CtdlMessage *TheMessage = NULL;
1333 int retcode = om_no_such_msg;
1334 struct encapmsg encap;
1336 lprintf(CTDL_DEBUG, "CtdlOutputMsg() msgnum=%ld, mode=%d, section=%s\n",
1338 (section ? section : "<>")
1341 if ((!(CC->logged_in)) && (!(CC->internal_pgm))) {
1342 if (do_proto) cprintf("%d Not logged in.\n",
1343 ERROR + NOT_LOGGED_IN);
1344 return(om_not_logged_in);
1347 /* FIXME: check message id against msglist for this room */
1350 * Fetch the message from disk. If we're in any sort of headers
1351 * only mode, request that we don't even bother loading the body
1354 if ( (headers_only == HEADERS_FAST) || (headers_only == HEADERS_ONLY) ) {
1355 TheMessage = CtdlFetchMessage(msg_num, 0);
1358 TheMessage = CtdlFetchMessage(msg_num, 1);
1361 if (TheMessage == NULL) {
1362 if (do_proto) cprintf("%d Can't locate msg %ld on disk\n",
1363 ERROR + MESSAGE_NOT_FOUND, msg_num);
1364 return(om_no_such_msg);
1367 /* Here is the weird form of this command, to process only an
1368 * encapsulated message/rfc822 section.
1370 if (section) if (strlen(section)>0) if (strcmp(section, "0")) {
1371 memset(&encap, 0, sizeof encap);
1372 safestrncpy(encap.desired_section, section, sizeof encap.desired_section);
1373 mime_parser(TheMessage->cm_fields['M'],
1375 *extract_encapsulated_message,
1376 NULL, NULL, (void *)&encap, 0
1378 CtdlFreeMessage(TheMessage);
1382 encap.msg[encap.msglen] = 0;
1383 TheMessage = convert_internet_message(encap.msg);
1384 encap.msg = NULL; /* no free() here, TheMessage owns it now */
1386 /* Now we let it fall through to the bottom of this
1387 * function, because TheMessage now contains the
1388 * encapsulated message instead of the top-level
1389 * message. Isn't that neat?
1394 if (do_proto) cprintf("%d msg %ld has no part %s\n",
1395 ERROR + MESSAGE_NOT_FOUND, msg_num, section);
1396 retcode = om_no_such_msg;
1401 /* Ok, output the message now */
1402 retcode = CtdlOutputPreLoadedMsg(
1404 headers_only, do_proto, crlf);
1405 CtdlFreeMessage(TheMessage);
1412 * Get a message off disk. (returns om_* values found in msgbase.h)
1415 int CtdlOutputPreLoadedMsg(
1416 struct CtdlMessage *TheMessage,
1417 int mode, /* how would you like that message? */
1418 int headers_only, /* eschew the message body? */
1419 int do_proto, /* do Citadel protocol responses? */
1420 int crlf /* Use CRLF newlines instead of LF? */
1426 char display_name[256];
1428 char *nl; /* newline string */
1430 int subject_found = 0;
1433 /* Buffers needed for RFC822 translation. These are all filled
1434 * using functions that are bounds-checked, and therefore we can
1435 * make them substantially smaller than SIZ.
1443 char datestamp[100];
1445 lprintf(CTDL_DEBUG, "CtdlOutputPreLoadedMsg(TheMessage=%s, %d, %d, %d, %d\n",
1446 ((TheMessage == NULL) ? "NULL" : "not null"),
1447 mode, headers_only, do_proto, crlf);
1449 strcpy(mid, "unknown");
1450 nl = (crlf ? "\r\n" : "\n");
1452 if (!is_valid_message(TheMessage)) {
1454 "ERROR: invalid preloaded message for output\n");
1455 return(om_no_such_msg);
1458 /* Are we downloading a MIME component? */
1459 if (mode == MT_DOWNLOAD) {
1460 if (TheMessage->cm_format_type != FMT_RFC822) {
1462 cprintf("%d This is not a MIME message.\n",
1463 ERROR + ILLEGAL_VALUE);
1464 } else if (CC->download_fp != NULL) {
1465 if (do_proto) cprintf(
1466 "%d You already have a download open.\n",
1467 ERROR + RESOURCE_BUSY);
1469 /* Parse the message text component */
1470 mptr = TheMessage->cm_fields['M'];
1471 mime_parser(mptr, NULL, *mime_download, NULL, NULL, NULL, 0);
1472 /* If there's no file open by this time, the requested
1473 * section wasn't found, so print an error
1475 if (CC->download_fp == NULL) {
1476 if (do_proto) cprintf(
1477 "%d Section %s not found.\n",
1478 ERROR + FILE_NOT_FOUND,
1479 CC->download_desired_section);
1482 return((CC->download_fp != NULL) ? om_ok : om_mime_error);
1485 /* now for the user-mode message reading loops */
1486 if (do_proto) cprintf("%d msg:\n", LISTING_FOLLOWS);
1488 /* Does the caller want to skip the headers? */
1489 if (headers_only == HEADERS_NONE) goto START_TEXT;
1491 /* Tell the client which format type we're using. */
1492 if ( (mode == MT_CITADEL) && (do_proto) ) {
1493 cprintf("type=%d\n", TheMessage->cm_format_type);
1496 /* nhdr=yes means that we're only displaying headers, no body */
1497 if ( (TheMessage->cm_anon_type == MES_ANONONLY)
1498 && (mode == MT_CITADEL)
1501 cprintf("nhdr=yes\n");
1504 /* begin header processing loop for Citadel message format */
1506 if ((mode == MT_CITADEL) || (mode == MT_MIME)) {
1508 safestrncpy(display_name, "<unknown>", sizeof display_name);
1509 if (TheMessage->cm_fields['A']) {
1510 strcpy(buf, TheMessage->cm_fields['A']);
1511 if (TheMessage->cm_anon_type == MES_ANONONLY) {
1512 safestrncpy(display_name, "****", sizeof display_name);
1514 else if (TheMessage->cm_anon_type == MES_ANONOPT) {
1515 safestrncpy(display_name, "anonymous", sizeof display_name);
1518 safestrncpy(display_name, buf, sizeof display_name);
1520 if ((is_room_aide())
1521 && ((TheMessage->cm_anon_type == MES_ANONONLY)
1522 || (TheMessage->cm_anon_type == MES_ANONOPT))) {
1523 size_t tmp = strlen(display_name);
1524 snprintf(&display_name[tmp],
1525 sizeof display_name - tmp,
1530 /* Don't show Internet address for users on the
1531 * local Citadel network.
1534 if (TheMessage->cm_fields['N'] != NULL)
1535 if (strlen(TheMessage->cm_fields['N']) > 0)
1536 if (haschar(TheMessage->cm_fields['N'], '.') == 0) {
1540 /* Now spew the header fields in the order we like them. */
1541 safestrncpy(allkeys, FORDER, sizeof allkeys);
1542 for (i=0; i<strlen(allkeys); ++i) {
1543 k = (int) allkeys[i];
1545 if ( (TheMessage->cm_fields[k] != NULL)
1546 && (msgkeys[k] != NULL) ) {
1548 if (do_proto) cprintf("%s=%s\n",
1552 else if ((k == 'F') && (suppress_f)) {
1555 /* Masquerade display name if needed */
1557 if (do_proto) cprintf("%s=%s\n",
1559 TheMessage->cm_fields[k]
1568 /* begin header processing loop for RFC822 transfer format */
1573 strcpy(snode, NODENAME);
1574 strcpy(lnode, HUMANNODE);
1575 if (mode == MT_RFC822) {
1576 for (i = 0; i < 256; ++i) {
1577 if (TheMessage->cm_fields[i]) {
1578 mptr = TheMessage->cm_fields[i];
1581 safestrncpy(luser, mptr, sizeof luser);
1582 safestrncpy(suser, mptr, sizeof suser);
1584 else if (i == 'Y') {
1585 cprintf("CC: %s%s", mptr, nl);
1587 else if (i == 'P') {
1588 cprintf("Return-Path: %s%s", mptr, nl);
1590 else if (i == 'V') {
1591 cprintf("Envelope-To: %s%s", mptr, nl);
1593 else if (i == 'U') {
1594 cprintf("Subject: %s%s", mptr, nl);
1598 safestrncpy(mid, mptr, sizeof mid);
1600 safestrncpy(lnode, mptr, sizeof lnode);
1602 safestrncpy(fuser, mptr, sizeof fuser);
1603 /* else if (i == 'O')
1604 cprintf("X-Citadel-Room: %s%s",
1607 safestrncpy(snode, mptr, sizeof snode);
1609 cprintf("To: %s%s", mptr, nl);
1610 else if (i == 'T') {
1611 datestring(datestamp, sizeof datestamp,
1612 atol(mptr), DATESTRING_RFC822);
1613 cprintf("Date: %s%s", datestamp, nl);
1617 if (subject_found == 0) {
1618 cprintf("Subject: (no subject)%s", nl);
1622 for (i=0; i<strlen(suser); ++i) {
1623 suser[i] = tolower(suser[i]);
1624 if (!isalnum(suser[i])) suser[i]='_';
1627 if (mode == MT_RFC822) {
1628 if (!strcasecmp(snode, NODENAME)) {
1629 safestrncpy(snode, FQDN, sizeof snode);
1632 /* Construct a fun message id */
1633 cprintf("Message-ID: <%s", mid);
1634 if (strchr(mid, '@')==NULL) {
1635 cprintf("@%s", snode);
1639 if (!is_room_aide() && (TheMessage->cm_anon_type == MES_ANONONLY)) {
1640 cprintf("From: \"----\" <x@x.org>%s", nl);
1642 else if (!is_room_aide() && (TheMessage->cm_anon_type == MES_ANONOPT)) {
1643 cprintf("From: \"anonymous\" <x@x.org>%s", nl);
1645 else if (strlen(fuser) > 0) {
1646 cprintf("From: \"%s\" <%s>%s", luser, fuser, nl);
1649 cprintf("From: \"%s\" <%s@%s>%s", luser, suser, snode, nl);
1652 cprintf("Organization: %s%s", lnode, nl);
1654 /* Blank line signifying RFC822 end-of-headers */
1655 if (TheMessage->cm_format_type != FMT_RFC822) {
1660 /* end header processing loop ... at this point, we're in the text */
1662 if (headers_only == HEADERS_FAST) goto DONE;
1663 mptr = TheMessage->cm_fields['M'];
1665 /* Tell the client about the MIME parts in this message */
1666 if (TheMessage->cm_format_type == FMT_RFC822) {
1667 if ( (mode == MT_CITADEL) || (mode == MT_MIME) ) {
1668 memset(&ma, 0, sizeof(struct ma_info));
1669 mime_parser(mptr, NULL,
1670 (do_proto ? *list_this_part : NULL),
1671 (do_proto ? *list_this_pref : NULL),
1672 (do_proto ? *list_this_suff : NULL),
1675 else if (mode == MT_RFC822) { /* unparsed RFC822 dump */
1676 char *start_of_text = NULL;
1677 start_of_text = strstr(mptr, "\n\r\n");
1678 if (start_of_text == NULL) start_of_text = strstr(mptr, "\n\n");
1679 if (start_of_text == NULL) start_of_text = mptr;
1681 start_of_text = strstr(start_of_text, "\n");
1683 while (ch=*mptr, ch!=0) {
1687 else switch(headers_only) {
1689 if (mptr >= start_of_text) {
1690 if (ch == 10) cprintf("%s", nl);
1691 else cprintf("%c", ch);
1695 if (mptr < start_of_text) {
1696 if (ch == 10) cprintf("%s", nl);
1697 else cprintf("%c", ch);
1701 if (ch == 10) cprintf("%s", nl);
1702 else cprintf("%c", ch);
1711 if (headers_only == HEADERS_ONLY) {
1715 /* signify start of msg text */
1716 if ( (mode == MT_CITADEL) || (mode == MT_MIME) ) {
1717 if (do_proto) cprintf("text\n");
1720 /* If the format type on disk is 1 (fixed-format), then we want
1721 * everything to be output completely literally ... regardless of
1722 * what message transfer format is in use.
1724 if (TheMessage->cm_format_type == FMT_FIXED) {
1725 if (mode == MT_MIME) {
1726 cprintf("Content-type: text/plain\n\n");
1729 while (ch = *mptr++, ch > 0) {
1732 if ((ch == 10) || (strlen(buf) > 250)) {
1733 cprintf("%s%s", buf, nl);
1736 buf[strlen(buf) + 1] = 0;
1737 buf[strlen(buf)] = ch;
1740 if (strlen(buf) > 0)
1741 cprintf("%s%s", buf, nl);
1744 /* If the message on disk is format 0 (Citadel vari-format), we
1745 * output using the formatter at 80 columns. This is the final output
1746 * form if the transfer format is RFC822, but if the transfer format
1747 * is Citadel proprietary, it'll still work, because the indentation
1748 * for new paragraphs is correct and the client will reformat the
1749 * message to the reader's screen width.
1751 if (TheMessage->cm_format_type == FMT_CITADEL) {
1752 if (mode == MT_MIME) {
1753 cprintf("Content-type: text/x-citadel-variformat\n\n");
1755 memfmout(mptr, 0, nl);
1758 /* If the message on disk is format 4 (MIME), we've gotta hand it
1759 * off to the MIME parser. The client has already been told that
1760 * this message is format 1 (fixed format), so the callback function
1761 * we use will display those parts as-is.
1763 if (TheMessage->cm_format_type == FMT_RFC822) {
1764 memset(&ma, 0, sizeof(struct ma_info));
1766 if (mode == MT_MIME) {
1767 ma.use_fo_hooks = 0;
1768 strcpy(ma.chosen_part, "1");
1769 mime_parser(mptr, NULL,
1770 *choose_preferred, *fixed_output_pre,
1771 *fixed_output_post, (void *)&ma, 0);
1772 mime_parser(mptr, NULL,
1773 *output_preferred, NULL, NULL, (void *)&ma, 0);
1776 ma.use_fo_hooks = 1;
1777 mime_parser(mptr, NULL,
1778 *fixed_output, *fixed_output_pre,
1779 *fixed_output_post, (void *)&ma, 0);
1784 DONE: /* now we're done */
1785 if (do_proto) cprintf("000\n");
1792 * display a message (mode 0 - Citadel proprietary)
1794 void cmd_msg0(char *cmdbuf)
1797 int headers_only = HEADERS_ALL;
1799 msgid = extract_long(cmdbuf, 0);
1800 headers_only = extract_int(cmdbuf, 1);
1802 CtdlOutputMsg(msgid, MT_CITADEL, headers_only, 1, 0, NULL);
1808 * display a message (mode 2 - RFC822)
1810 void cmd_msg2(char *cmdbuf)
1813 int headers_only = HEADERS_ALL;
1815 msgid = extract_long(cmdbuf, 0);
1816 headers_only = extract_int(cmdbuf, 1);
1818 CtdlOutputMsg(msgid, MT_RFC822, headers_only, 1, 1, NULL);
1824 * display a message (mode 3 - IGnet raw format - internal programs only)
1826 void cmd_msg3(char *cmdbuf)
1829 struct CtdlMessage *msg;
1832 if (CC->internal_pgm == 0) {
1833 cprintf("%d This command is for internal programs only.\n",
1834 ERROR + HIGHER_ACCESS_REQUIRED);
1838 msgnum = extract_long(cmdbuf, 0);
1839 msg = CtdlFetchMessage(msgnum, 1);
1841 cprintf("%d Message %ld not found.\n",
1842 ERROR + MESSAGE_NOT_FOUND, msgnum);
1846 serialize_message(&smr, msg);
1847 CtdlFreeMessage(msg);
1850 cprintf("%d Unable to serialize message\n",
1851 ERROR + INTERNAL_ERROR);
1855 cprintf("%d %ld\n", BINARY_FOLLOWS, (long)smr.len);
1856 client_write((char *)smr.ser, (int)smr.len);
1863 * Display a message using MIME content types
1865 void cmd_msg4(char *cmdbuf)
1870 msgid = extract_long(cmdbuf, 0);
1871 extract_token(section, cmdbuf, 1, '|', sizeof section);
1872 CtdlOutputMsg(msgid, MT_MIME, 0, 1, 0, (section[0] ? section : NULL) );
1878 * Client tells us its preferred message format(s)
1880 void cmd_msgp(char *cmdbuf)
1882 safestrncpy(CC->preferred_formats, cmdbuf,
1883 sizeof(CC->preferred_formats));
1884 cprintf("%d ok\n", CIT_OK);
1889 * Open a component of a MIME message as a download file
1891 void cmd_opna(char *cmdbuf)
1894 char desired_section[128];
1896 msgid = extract_long(cmdbuf, 0);
1897 extract_token(desired_section, cmdbuf, 1, '|', sizeof desired_section);
1898 safestrncpy(CC->download_desired_section, desired_section,
1899 sizeof CC->download_desired_section);
1900 CtdlOutputMsg(msgid, MT_DOWNLOAD, 0, 1, 1, NULL);
1904 * Save one or more message pointers into a specified room
1905 * (Returns 0 for success, nonzero for failure)
1906 * roomname may be NULL to use the current room
1908 * Note that the 'supplied_msg' field may be set to NULL, in which case
1909 * the message will be fetched from disk, by number, if we need to perform
1910 * replication checks. This adds an additional database read, so if the
1911 * caller already has the message in memory then it should be supplied. (Obviously
1912 * this mode of operation only works if we're saving a single message.)
1914 int CtdlSaveMsgPointersInRoom(char *roomname, long newmsgidlist[], int num_newmsgs,
1915 int do_repl_check, struct CtdlMessage *supplied_msg)
1918 char hold_rm[ROOMNAMELEN];
1919 struct cdbdata *cdbfr;
1922 long highest_msg = 0L;
1925 struct CtdlMessage *msg = NULL;
1927 long *msgs_to_be_merged = NULL;
1928 int num_msgs_to_be_merged = 0;
1931 "CtdlSaveMsgPointersInRoom(room=%s, num_msgs=%d, repl=%d)\n",
1932 roomname, num_newmsgs, do_repl_check);
1934 strcpy(hold_rm, CC->room.QRname);
1937 if (newmsgidlist == NULL) return(ERROR + INTERNAL_ERROR);
1938 if (num_newmsgs < 1) return(ERROR + INTERNAL_ERROR);
1939 if (num_newmsgs > 1) supplied_msg = NULL;
1941 /* Now the regular stuff */
1942 if (lgetroom(&CC->room,
1943 ((roomname != NULL) ? roomname : CC->room.QRname) )
1945 lprintf(CTDL_ERR, "No such room <%s>\n", roomname);
1946 return(ERROR + ROOM_NOT_FOUND);
1950 msgs_to_be_merged = malloc(sizeof(long) * num_newmsgs);
1951 num_msgs_to_be_merged = 0;
1954 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long));
1955 if (cdbfr == NULL) {
1959 msglist = (long *) cdbfr->ptr;
1960 cdbfr->ptr = NULL; /* CtdlSaveMsgPointerInRoom() now owns this memory */
1961 num_msgs = cdbfr->len / sizeof(long);
1966 /* Create a list of msgid's which were supplied by the caller, but do
1967 * not already exist in the target room. It is absolutely taboo to
1968 * have more than one reference to the same message in a room.
1970 for (i=0; i<num_newmsgs; ++i) {
1972 if (num_msgs > 0) for (j=0; j<num_msgs; ++j) {
1973 if (msglist[j] == newmsgidlist[i]) {
1978 msgs_to_be_merged[num_msgs_to_be_merged++] = newmsgidlist[i];
1982 lprintf(9, "%d unique messages to be merged\n", num_msgs_to_be_merged);
1985 * Now merge the new messages
1987 msglist = realloc(msglist, (sizeof(long) * (num_msgs + num_msgs_to_be_merged)) );
1988 if (msglist == NULL) {
1989 lprintf(CTDL_ALERT, "ERROR: can't realloc message list!\n");
1991 memcpy(&msglist[num_msgs], msgs_to_be_merged, (sizeof(long) * num_msgs_to_be_merged) );
1992 num_msgs += num_msgs_to_be_merged;
1994 /* Sort the message list, so all the msgid's are in order */
1995 num_msgs = sort_msglist(msglist, num_msgs);
1997 /* Determine the highest message number */
1998 highest_msg = msglist[num_msgs - 1];
2000 /* Write it back to disk. */
2001 cdb_store(CDB_MSGLISTS, &CC->room.QRnumber, (int)sizeof(long),
2002 msglist, (int)(num_msgs * sizeof(long)));
2004 /* Free up the memory we used. */
2007 /* Update the highest-message pointer and unlock the room. */
2008 CC->room.QRhighest = highest_msg;
2009 lputroom(&CC->room);
2011 /* Perform replication checks if necessary */
2012 if ( (DoesThisRoomNeedEuidIndexing(&CC->room)) && (do_repl_check) ) {
2013 lprintf(CTDL_DEBUG, "CtdlSaveMsgPointerInRoom() doing repl checks\n");
2015 for (i=0; i<num_msgs_to_be_merged; ++i) {
2016 msgid = msgs_to_be_merged[i];
2018 if (supplied_msg != NULL) {
2022 msg = CtdlFetchMessage(msgid, 0);
2026 ReplicationChecks(msg);
2028 /* If the message has an Exclusive ID, index that... */
2029 if (msg->cm_fields['E'] != NULL) {
2030 index_message_by_euid(msg->cm_fields['E'], &CC->room, msgid);
2033 /* Free up the memory we may have allocated */
2034 if (msg != supplied_msg) {
2035 CtdlFreeMessage(msg);
2043 lprintf(CTDL_DEBUG, "CtdlSaveMsgPointerInRoom() skips repl checks\n");
2046 /* Submit this room for net processing */
2047 network_queue_room(&CC->room, NULL);
2049 #ifdef HAVE_LIBSIEVE
2050 /* If this is someone's inbox, submit the room for sieve processing */
2051 if (!strcasecmp(&CC->room.QRname[11], MAILROOM)) {
2052 sieve_queue_room(&CC->room);
2054 #endif /* HAVE_LIBSIEVE */
2056 /* Go back to the room we were in before we wandered here... */
2057 getroom(&CC->room, hold_rm);
2059 /* Bump the reference count for all messages which were merged */
2060 for (i=0; i<num_msgs_to_be_merged; ++i) {
2061 AdjRefCount(msgs_to_be_merged[i], +1);
2064 /* Free up memory... */
2065 if (msgs_to_be_merged != NULL) {
2066 free(msgs_to_be_merged);
2069 /* Return success. */
2075 * This is the same as CtdlSaveMsgPointersInRoom() but it only accepts
2078 int CtdlSaveMsgPointerInRoom(char *roomname, long msgid,
2079 int do_repl_check, struct CtdlMessage *supplied_msg)
2081 return CtdlSaveMsgPointersInRoom(roomname, &msgid, 1, do_repl_check, supplied_msg);
2088 * Message base operation to save a new message to the message store
2089 * (returns new message number)
2091 * This is the back end for CtdlSubmitMsg() and should not be directly
2092 * called by server-side modules.
2095 long send_message(struct CtdlMessage *msg) {
2103 /* Get a new message number */
2104 newmsgid = get_new_message_number();
2105 snprintf(msgidbuf, sizeof msgidbuf, "%010ld@%s", newmsgid, config.c_fqdn);
2107 /* Generate an ID if we don't have one already */
2108 if (msg->cm_fields['I']==NULL) {
2109 msg->cm_fields['I'] = strdup(msgidbuf);
2112 /* If the message is big, set its body aside for storage elsewhere */
2113 if (msg->cm_fields['M'] != NULL) {
2114 if (strlen(msg->cm_fields['M']) > BIGMSG) {
2116 holdM = msg->cm_fields['M'];
2117 msg->cm_fields['M'] = NULL;
2121 /* Serialize our data structure for storage in the database */
2122 serialize_message(&smr, msg);
2125 msg->cm_fields['M'] = holdM;
2129 cprintf("%d Unable to serialize message\n",
2130 ERROR + INTERNAL_ERROR);
2134 /* Write our little bundle of joy into the message base */
2135 if (cdb_store(CDB_MSGMAIN, &newmsgid, (int)sizeof(long),
2136 smr.ser, smr.len) < 0) {
2137 lprintf(CTDL_ERR, "Can't store message\n");
2141 cdb_store(CDB_BIGMSGS,
2151 /* Free the memory we used for the serialized message */
2154 /* Return the *local* message ID to the caller
2155 * (even if we're storing an incoming network message)
2163 * Serialize a struct CtdlMessage into the format used on disk and network.
2165 * This function loads up a "struct ser_ret" (defined in server.h) which
2166 * contains the length of the serialized message and a pointer to the
2167 * serialized message in memory. THE LATTER MUST BE FREED BY THE CALLER.
2169 void serialize_message(struct ser_ret *ret, /* return values */
2170 struct CtdlMessage *msg) /* unserialized msg */
2172 size_t wlen, fieldlen;
2174 static char *forder = FORDER;
2177 * Check for valid message format
2179 if (is_valid_message(msg) == 0) {
2180 lprintf(CTDL_ERR, "serialize_message() aborting due to invalid message\n");
2187 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL)
2188 ret->len = ret->len +
2189 strlen(msg->cm_fields[(int)forder[i]]) + 2;
2191 ret->ser = malloc(ret->len);
2192 if (ret->ser == NULL) {
2193 lprintf(CTDL_ERR, "serialize_message() malloc(%ld) failed: %s\n",
2194 (long)ret->len, strerror(errno));
2201 ret->ser[1] = msg->cm_anon_type;
2202 ret->ser[2] = msg->cm_format_type;
2205 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL) {
2206 fieldlen = strlen(msg->cm_fields[(int)forder[i]]);
2207 ret->ser[wlen++] = (char)forder[i];
2208 safestrncpy((char *)&ret->ser[wlen], msg->cm_fields[(int)forder[i]], fieldlen+1);
2209 wlen = wlen + fieldlen + 1;
2211 if (ret->len != wlen) lprintf(CTDL_ERR, "ERROR: len=%ld wlen=%ld\n",
2212 (long)ret->len, (long)wlen);
2220 * Check to see if any messages already exist in the current room which
2221 * carry the same Exclusive ID as this one. If any are found, delete them.
2223 void ReplicationChecks(struct CtdlMessage *msg) {
2224 long old_msgnum = (-1L);
2226 if (DoesThisRoomNeedEuidIndexing(&CC->room) == 0) return;
2228 lprintf(CTDL_DEBUG, "Performing replication checks in <%s>\n",
2231 /* No exclusive id? Don't do anything. */
2232 if (msg == NULL) return;
2233 if (msg->cm_fields['E'] == NULL) return;
2234 if (strlen(msg->cm_fields['E']) == 0) return;
2235 /*lprintf(CTDL_DEBUG, "Exclusive ID: <%s> for room <%s>\n",
2236 msg->cm_fields['E'], CC->room.QRname);*/
2238 old_msgnum = locate_message_by_euid(msg->cm_fields['E'], &CC->room);
2239 if (old_msgnum > 0L) {
2240 lprintf(CTDL_DEBUG, "ReplicationChecks() replacing message %ld\n", old_msgnum);
2241 CtdlDeleteMessages(CC->room.QRname, &old_msgnum, 1, "", 0);
2248 * Save a message to disk and submit it into the delivery system.
2250 long CtdlSubmitMsg(struct CtdlMessage *msg, /* message to save */
2251 struct recptypes *recps, /* recipients (if mail) */
2252 char *force /* force a particular room? */
2254 char submit_filename[128];
2255 char generated_timestamp[32];
2256 char hold_rm[ROOMNAMELEN];
2257 char actual_rm[ROOMNAMELEN];
2258 char force_room[ROOMNAMELEN];
2259 char content_type[SIZ]; /* We have to learn this */
2260 char recipient[SIZ];
2263 struct ctdluser userbuf;
2265 struct MetaData smi;
2266 FILE *network_fp = NULL;
2267 static int seqnum = 1;
2268 struct CtdlMessage *imsg = NULL;
2271 char *hold_R, *hold_D;
2272 char *collected_addresses = NULL;
2273 struct addresses_to_be_filed *aptr = NULL;
2274 char *saved_rfc822_version = NULL;
2275 int qualified_for_journaling = 0;
2277 lprintf(CTDL_DEBUG, "CtdlSubmitMsg() called\n");
2278 if (is_valid_message(msg) == 0) return(-1); /* self check */
2280 /* If this message has no timestamp, we take the liberty of
2281 * giving it one, right now.
2283 if (msg->cm_fields['T'] == NULL) {
2284 snprintf(generated_timestamp, sizeof generated_timestamp, "%ld", (long)time(NULL));
2285 msg->cm_fields['T'] = strdup(generated_timestamp);
2288 /* If this message has no path, we generate one.
2290 if (msg->cm_fields['P'] == NULL) {
2291 if (msg->cm_fields['A'] != NULL) {
2292 msg->cm_fields['P'] = strdup(msg->cm_fields['A']);
2293 for (a=0; a<strlen(msg->cm_fields['P']); ++a) {
2294 if (isspace(msg->cm_fields['P'][a])) {
2295 msg->cm_fields['P'][a] = ' ';
2300 msg->cm_fields['P'] = strdup("unknown");
2304 if (force == NULL) {
2305 strcpy(force_room, "");
2308 strcpy(force_room, force);
2311 /* Learn about what's inside, because it's what's inside that counts */
2312 if (msg->cm_fields['M'] == NULL) {
2313 lprintf(CTDL_ERR, "ERROR: attempt to save message with NULL body\n");
2317 switch (msg->cm_format_type) {
2319 strcpy(content_type, "text/x-citadel-variformat");
2322 strcpy(content_type, "text/plain");
2325 strcpy(content_type, "text/plain");
2326 mptr = bmstrcasestr(msg->cm_fields['M'], "Content-type: ");
2328 safestrncpy(content_type, &mptr[14],
2329 sizeof content_type);
2330 for (a = 0; a < strlen(content_type); ++a) {
2331 if ((content_type[a] == ';')
2332 || (content_type[a] == ' ')
2333 || (content_type[a] == 13)
2334 || (content_type[a] == 10)) {
2335 content_type[a] = 0;
2341 /* Goto the correct room */
2342 lprintf(CTDL_DEBUG, "Selected room %s\n", (recps) ? CC->room.QRname : SENTITEMS);
2343 strcpy(hold_rm, CC->room.QRname);
2344 strcpy(actual_rm, CC->room.QRname);
2345 if (recps != NULL) {
2346 strcpy(actual_rm, SENTITEMS);
2349 /* If the user is a twit, move to the twit room for posting */
2351 if (CC->user.axlevel == 2) {
2352 strcpy(hold_rm, actual_rm);
2353 strcpy(actual_rm, config.c_twitroom);
2354 lprintf(CTDL_DEBUG, "Diverting to twit room\n");
2358 /* ...or if this message is destined for Aide> then go there. */
2359 if (strlen(force_room) > 0) {
2360 strcpy(actual_rm, force_room);
2363 lprintf(CTDL_DEBUG, "Final selection: %s\n", actual_rm);
2364 if (strcasecmp(actual_rm, CC->room.QRname)) {
2365 /* getroom(&CC->room, actual_rm); */
2366 usergoto(actual_rm, 0, 1, NULL, NULL);
2370 * If this message has no O (room) field, generate one.
2372 if (msg->cm_fields['O'] == NULL) {
2373 msg->cm_fields['O'] = strdup(CC->room.QRname);
2376 /* Perform "before save" hooks (aborting if any return nonzero) */
2377 lprintf(CTDL_DEBUG, "Performing before-save hooks\n");
2378 if (PerformMessageHooks(msg, EVT_BEFORESAVE) > 0) return(-3);
2381 * If this message has an Exclusive ID, and the room is replication
2382 * checking enabled, then do replication checks.
2384 if (DoesThisRoomNeedEuidIndexing(&CC->room)) {
2385 ReplicationChecks(msg);
2388 /* Save it to disk */
2389 lprintf(CTDL_DEBUG, "Saving to disk\n");
2390 newmsgid = send_message(msg);
2391 if (newmsgid <= 0L) return(-5);
2393 /* Write a supplemental message info record. This doesn't have to
2394 * be a critical section because nobody else knows about this message
2397 lprintf(CTDL_DEBUG, "Creating MetaData record\n");
2398 memset(&smi, 0, sizeof(struct MetaData));
2399 smi.meta_msgnum = newmsgid;
2400 smi.meta_refcount = 0;
2401 safestrncpy(smi.meta_content_type, content_type,
2402 sizeof smi.meta_content_type);
2405 * Measure how big this message will be when rendered as RFC822.
2406 * We do this for two reasons:
2407 * 1. We need the RFC822 length for the new metadata record, so the
2408 * POP and IMAP services don't have to calculate message lengths
2409 * while the user is waiting (multiplied by potentially hundreds
2410 * or thousands of messages).
2411 * 2. If journaling is enabled, we will need an RFC822 version of the
2412 * message to attach to the journalized copy.
2414 if (CC->redirect_buffer != NULL) {
2415 lprintf(CTDL_ALERT, "CC->redirect_buffer is not NULL during message submission!\n");
2418 CC->redirect_buffer = malloc(SIZ);
2419 CC->redirect_len = 0;
2420 CC->redirect_alloc = SIZ;
2421 CtdlOutputPreLoadedMsg(msg, MT_RFC822, HEADERS_ALL, 0, 1);
2422 smi.meta_rfc822_length = CC->redirect_len;
2423 saved_rfc822_version = CC->redirect_buffer;
2424 CC->redirect_buffer = NULL;
2425 CC->redirect_len = 0;
2426 CC->redirect_alloc = 0;
2430 /* Now figure out where to store the pointers */
2431 lprintf(CTDL_DEBUG, "Storing pointers\n");
2433 /* If this is being done by the networker delivering a private
2434 * message, we want to BYPASS saving the sender's copy (because there
2435 * is no local sender; it would otherwise go to the Trashcan).
2437 if ((!CC->internal_pgm) || (recps == NULL)) {
2438 if (CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 1, msg) != 0) {
2439 lprintf(CTDL_ERR, "ERROR saving message pointer!\n");
2440 CtdlSaveMsgPointerInRoom(config.c_aideroom, newmsgid, 0, msg);
2444 /* For internet mail, drop a copy in the outbound queue room */
2446 if (recps->num_internet > 0) {
2447 CtdlSaveMsgPointerInRoom(SMTP_SPOOLOUT_ROOM, newmsgid, 0, msg);
2450 /* If other rooms are specified, drop them there too. */
2452 if (recps->num_room > 0)
2453 for (i=0; i<num_tokens(recps->recp_room, '|'); ++i) {
2454 extract_token(recipient, recps->recp_room, i,
2455 '|', sizeof recipient);
2456 lprintf(CTDL_DEBUG, "Delivering to room <%s>\n", recipient);
2457 CtdlSaveMsgPointerInRoom(recipient, newmsgid, 0, msg);
2460 /* Bump this user's messages posted counter. */
2461 lprintf(CTDL_DEBUG, "Updating user\n");
2462 lgetuser(&CC->user, CC->curr_user);
2463 CC->user.posted = CC->user.posted + 1;
2464 lputuser(&CC->user);
2466 /* If this is private, local mail, make a copy in the
2467 * recipient's mailbox and bump the reference count.
2470 if (recps->num_local > 0)
2471 for (i=0; i<num_tokens(recps->recp_local, '|'); ++i) {
2472 extract_token(recipient, recps->recp_local, i,
2473 '|', sizeof recipient);
2474 lprintf(CTDL_DEBUG, "Delivering private local mail to <%s>\n",
2476 if (getuser(&userbuf, recipient) == 0) {
2477 MailboxName(actual_rm, sizeof actual_rm,
2478 &userbuf, MAILROOM);
2479 CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 0, msg);
2480 BumpNewMailCounter(userbuf.usernum);
2483 lprintf(CTDL_DEBUG, "No user <%s>\n", recipient);
2484 CtdlSaveMsgPointerInRoom(config.c_aideroom,
2489 /* Perform "after save" hooks */
2490 lprintf(CTDL_DEBUG, "Performing after-save hooks\n");
2491 PerformMessageHooks(msg, EVT_AFTERSAVE);
2493 /* For IGnet mail, we have to save a new copy into the spooler for
2494 * each recipient, with the R and D fields set to the recipient and
2495 * destination-node. This has two ugly side effects: all other
2496 * recipients end up being unlisted in this recipient's copy of the
2497 * message, and it has to deliver multiple messages to the same
2498 * node. We'll revisit this again in a year or so when everyone has
2499 * a network spool receiver that can handle the new style messages.
2502 if (recps->num_ignet > 0)
2503 for (i=0; i<num_tokens(recps->recp_ignet, '|'); ++i) {
2504 extract_token(recipient, recps->recp_ignet, i,
2505 '|', sizeof recipient);
2507 hold_R = msg->cm_fields['R'];
2508 hold_D = msg->cm_fields['D'];
2509 msg->cm_fields['R'] = malloc(SIZ);
2510 msg->cm_fields['D'] = malloc(128);
2511 extract_token(msg->cm_fields['R'], recipient, 0, '@', SIZ);
2512 extract_token(msg->cm_fields['D'], recipient, 1, '@', 128);
2514 serialize_message(&smr, msg);
2516 snprintf(submit_filename, sizeof submit_filename,
2517 "%s/netmail.%04lx.%04x.%04x",
2519 (long) getpid(), CC->cs_pid, ++seqnum);
2520 network_fp = fopen(submit_filename, "wb+");
2521 if (network_fp != NULL) {
2522 fwrite(smr.ser, smr.len, 1, network_fp);
2528 free(msg->cm_fields['R']);
2529 free(msg->cm_fields['D']);
2530 msg->cm_fields['R'] = hold_R;
2531 msg->cm_fields['D'] = hold_D;
2534 /* Go back to the room we started from */
2535 lprintf(CTDL_DEBUG, "Returning to original room %s\n", hold_rm);
2536 if (strcasecmp(hold_rm, CC->room.QRname))
2537 /* getroom(&CC->room, hold_rm); */
2538 usergoto(hold_rm, 0, 1, NULL, NULL);
2540 /* For internet mail, generate delivery instructions.
2541 * Yes, this is recursive. Deal with it. Infinite recursion does
2542 * not happen because the delivery instructions message does not
2543 * contain a recipient.
2546 if (recps->num_internet > 0) {
2547 lprintf(CTDL_DEBUG, "Generating delivery instructions\n");
2548 instr = malloc(SIZ * 2);
2549 snprintf(instr, SIZ * 2,
2550 "Content-type: %s\n\nmsgid|%ld\nsubmitted|%ld\n"
2552 SPOOLMIME, newmsgid, (long)time(NULL),
2553 msg->cm_fields['A'], msg->cm_fields['N']
2556 for (i=0; i<num_tokens(recps->recp_internet, '|'); ++i) {
2557 size_t tmp = strlen(instr);
2558 extract_token(recipient, recps->recp_internet,
2559 i, '|', sizeof recipient);
2560 snprintf(&instr[tmp], SIZ * 2 - tmp,
2561 "remote|%s|0||\n", recipient);
2564 imsg = malloc(sizeof(struct CtdlMessage));
2565 memset(imsg, 0, sizeof(struct CtdlMessage));
2566 imsg->cm_magic = CTDLMESSAGE_MAGIC;
2567 imsg->cm_anon_type = MES_NORMAL;
2568 imsg->cm_format_type = FMT_RFC822;
2569 imsg->cm_fields['A'] = strdup("Citadel");
2570 imsg->cm_fields['J'] = strdup("do not journal");
2571 imsg->cm_fields['M'] = instr;
2572 CtdlSubmitMsg(imsg, NULL, SMTP_SPOOLOUT_ROOM);
2573 CtdlFreeMessage(imsg);
2577 * Any addresses to harvest for someone's address book?
2579 if ( (CC->logged_in) && (recps != NULL) ) {
2580 collected_addresses = harvest_collected_addresses(msg);
2583 if (collected_addresses != NULL) {
2584 begin_critical_section(S_ATBF);
2585 aptr = (struct addresses_to_be_filed *)
2586 malloc(sizeof(struct addresses_to_be_filed));
2588 MailboxName(actual_rm, sizeof actual_rm,
2589 &CC->user, USERCONTACTSROOM);
2590 aptr->roomname = strdup(actual_rm);
2591 aptr->collected_addresses = collected_addresses;
2593 end_critical_section(S_ATBF);
2597 * Determine whether this message qualifies for journaling.
2599 if (msg->cm_fields['J'] != NULL) {
2600 qualified_for_journaling = 0;
2603 if (recps == NULL) {
2604 qualified_for_journaling = config.c_journal_pubmsgs;
2606 else if (recps->num_local + recps->num_ignet + recps->num_internet > 0) {
2607 qualified_for_journaling = config.c_journal_email;
2610 qualified_for_journaling = config.c_journal_pubmsgs;
2615 * Do we have to perform journaling? If so, hand off the saved
2616 * RFC822 version will be handed off to the journaler for background
2617 * submit. Otherwise, we have to free the memory ourselves.
2619 if (saved_rfc822_version != NULL) {
2620 if (qualified_for_journaling) {
2621 JournalBackgroundSubmit(msg, saved_rfc822_version, recps);
2624 free(saved_rfc822_version);
2637 * Convenience function for generating small administrative messages.
2639 void quickie_message(char *from, char *to, char *room, char *text,
2640 int format_type, char *subject)
2642 struct CtdlMessage *msg;
2643 struct recptypes *recp = NULL;
2645 msg = malloc(sizeof(struct CtdlMessage));
2646 memset(msg, 0, sizeof(struct CtdlMessage));
2647 msg->cm_magic = CTDLMESSAGE_MAGIC;
2648 msg->cm_anon_type = MES_NORMAL;
2649 msg->cm_format_type = format_type;
2650 msg->cm_fields['A'] = strdup(from);
2651 if (room != NULL) msg->cm_fields['O'] = strdup(room);
2652 msg->cm_fields['N'] = strdup(NODENAME);
2654 msg->cm_fields['R'] = strdup(to);
2655 recp = validate_recipients(to);
2657 if (subject != NULL) {
2658 msg->cm_fields['U'] = strdup(subject);
2660 msg->cm_fields['M'] = strdup(text);
2662 CtdlSubmitMsg(msg, recp, room);
2663 CtdlFreeMessage(msg);
2664 if (recp != NULL) free(recp);
2670 * Back end function used by CtdlMakeMessage() and similar functions
2672 char *CtdlReadMessageBody(char *terminator, /* token signalling EOT */
2673 size_t maxlen, /* maximum message length */
2674 char *exist, /* if non-null, append to it;
2675 exist is ALWAYS freed */
2676 int crlf /* CRLF newlines instead of LF */
2680 size_t message_len = 0;
2681 size_t buffer_len = 0;
2688 if (exist == NULL) {
2695 message_len = strlen(exist);
2696 buffer_len = message_len + 4096;
2697 m = realloc(exist, buffer_len);
2704 /* Do we need to change leading ".." to "." for SMTP escaping? */
2705 if (!strcmp(terminator, ".")) {
2709 /* flush the input if we have nowhere to store it */
2714 /* read in the lines of message text one by one */
2716 if (client_getln(buf, (sizeof buf - 3)) < 1) finished = 1;
2717 if (!strcmp(buf, terminator)) finished = 1;
2719 strcat(buf, "\r\n");
2725 /* Unescape SMTP-style input of two dots at the beginning of the line */
2727 if (!strncmp(buf, "..", 2)) {
2728 strcpy(buf, &buf[1]);
2732 if ( (!flushing) && (!finished) ) {
2733 /* Measure the line */
2734 linelen = strlen(buf);
2736 /* augment the buffer if we have to */
2737 if ((message_len + linelen) >= buffer_len) {
2738 ptr = realloc(m, (buffer_len * 2) );
2739 if (ptr == NULL) { /* flush if can't allocate */
2742 buffer_len = (buffer_len * 2);
2744 lprintf(CTDL_DEBUG, "buffer_len is now %ld\n", (long)buffer_len);
2748 /* Add the new line to the buffer. NOTE: this loop must avoid
2749 * using functions like strcat() and strlen() because they
2750 * traverse the entire buffer upon every call, and doing that
2751 * for a multi-megabyte message slows it down beyond usability.
2753 strcpy(&m[message_len], buf);
2754 message_len += linelen;
2757 /* if we've hit the max msg length, flush the rest */
2758 if (message_len >= maxlen) flushing = 1;
2760 } while (!finished);
2768 * Build a binary message to be saved on disk.
2769 * (NOTE: if you supply 'preformatted_text', the buffer you give it
2770 * will become part of the message. This means you are no longer
2771 * responsible for managing that memory -- it will be freed along with
2772 * the rest of the fields when CtdlFreeMessage() is called.)
2775 struct CtdlMessage *CtdlMakeMessage(
2776 struct ctdluser *author, /* author's user structure */
2777 char *recipient, /* NULL if it's not mail */
2778 char *recp_cc, /* NULL if it's not mail */
2779 char *room, /* room where it's going */
2780 int type, /* see MES_ types in header file */
2781 int format_type, /* variformat, plain text, MIME... */
2782 char *fake_name, /* who we're masquerading as */
2783 char *subject, /* Subject (optional) */
2784 char *supplied_euid, /* ...or NULL if this is irrelevant */
2785 char *preformatted_text /* ...or NULL to read text from client */
2787 char dest_node[SIZ];
2789 struct CtdlMessage *msg;
2791 msg = malloc(sizeof(struct CtdlMessage));
2792 memset(msg, 0, sizeof(struct CtdlMessage));
2793 msg->cm_magic = CTDLMESSAGE_MAGIC;
2794 msg->cm_anon_type = type;
2795 msg->cm_format_type = format_type;
2797 /* Don't confuse the poor folks if it's not routed mail. */
2798 strcpy(dest_node, "");
2803 snprintf(buf, sizeof buf, "cit%ld", author->usernum); /* Path */
2804 msg->cm_fields['P'] = strdup(buf);
2806 snprintf(buf, sizeof buf, "%ld", (long)time(NULL)); /* timestamp */
2807 msg->cm_fields['T'] = strdup(buf);
2809 if (fake_name[0]) /* author */
2810 msg->cm_fields['A'] = strdup(fake_name);
2812 msg->cm_fields['A'] = strdup(author->fullname);
2814 if (CC->room.QRflags & QR_MAILBOX) { /* room */
2815 msg->cm_fields['O'] = strdup(&CC->room.QRname[11]);
2818 msg->cm_fields['O'] = strdup(CC->room.QRname);
2821 msg->cm_fields['N'] = strdup(NODENAME); /* nodename */
2822 msg->cm_fields['H'] = strdup(HUMANNODE); /* hnodename */
2824 if (recipient[0] != 0) {
2825 msg->cm_fields['R'] = strdup(recipient);
2827 if (recp_cc[0] != 0) {
2828 msg->cm_fields['Y'] = strdup(recp_cc);
2830 if (dest_node[0] != 0) {
2831 msg->cm_fields['D'] = strdup(dest_node);
2834 if ( (author == &CC->user) && (strlen(CC->cs_inet_email) > 0) ) {
2835 msg->cm_fields['F'] = strdup(CC->cs_inet_email);
2838 if (subject != NULL) {
2840 if (strlen(subject) > 0) {
2841 msg->cm_fields['U'] = strdup(subject);
2845 if (supplied_euid != NULL) {
2846 msg->cm_fields['E'] = strdup(supplied_euid);
2849 if (preformatted_text != NULL) {
2850 msg->cm_fields['M'] = preformatted_text;
2853 msg->cm_fields['M'] = CtdlReadMessageBody("000",
2854 config.c_maxmsglen, NULL, 0);
2862 * Check to see whether we have permission to post a message in the current
2863 * room. Returns a *CITADEL ERROR CODE* and puts a message in errmsgbuf, or
2864 * returns 0 on success.
2866 int CtdlDoIHavePermissionToPostInThisRoom(char *errmsgbuf, size_t n) {
2868 if (!(CC->logged_in)) {
2869 snprintf(errmsgbuf, n, "Not logged in.");
2870 return (ERROR + NOT_LOGGED_IN);
2873 if ((CC->user.axlevel < 2)
2874 && ((CC->room.QRflags & QR_MAILBOX) == 0)) {
2875 snprintf(errmsgbuf, n, "Need to be validated to enter "
2876 "(except in %s> to sysop)", MAILROOM);
2877 return (ERROR + HIGHER_ACCESS_REQUIRED);
2880 if ((CC->user.axlevel < 4)
2881 && (CC->room.QRflags & QR_NETWORK)) {
2882 snprintf(errmsgbuf, n, "Need net privileges to enter here.");
2883 return (ERROR + HIGHER_ACCESS_REQUIRED);
2886 if ((CC->user.axlevel < 6)
2887 && (CC->room.QRflags & QR_READONLY)) {
2888 snprintf(errmsgbuf, n, "Sorry, this is a read-only room.");
2889 return (ERROR + HIGHER_ACCESS_REQUIRED);
2892 strcpy(errmsgbuf, "Ok");
2898 * Check to see if the specified user has Internet mail permission
2899 * (returns nonzero if permission is granted)
2901 int CtdlCheckInternetMailPermission(struct ctdluser *who) {
2903 /* Do not allow twits to send Internet mail */
2904 if (who->axlevel <= 2) return(0);
2906 /* Globally enabled? */
2907 if (config.c_restrict == 0) return(1);
2909 /* User flagged ok? */
2910 if (who->flags & US_INTERNET) return(2);
2912 /* Aide level access? */
2913 if (who->axlevel >= 6) return(3);
2915 /* No mail for you! */
2921 * Validate recipients, count delivery types and errors, and handle aliasing
2922 * FIXME check for dupes!!!!!
2923 * Returns 0 if all addresses are ok, -1 if no addresses were specified,
2924 * or the number of addresses found invalid.
2926 struct recptypes *validate_recipients(char *supplied_recipients) {
2927 struct recptypes *ret;
2928 char recipients[SIZ];
2929 char this_recp[256];
2930 char this_recp_cooked[256];
2936 struct ctdluser tempUS;
2937 struct ctdlroom tempQR;
2941 ret = (struct recptypes *) malloc(sizeof(struct recptypes));
2942 if (ret == NULL) return(NULL);
2943 memset(ret, 0, sizeof(struct recptypes));
2946 ret->num_internet = 0;
2951 if (supplied_recipients == NULL) {
2952 strcpy(recipients, "");
2955 safestrncpy(recipients, supplied_recipients, sizeof recipients);
2958 /* Change all valid separator characters to commas */
2959 for (i=0; i<strlen(recipients); ++i) {
2960 if ((recipients[i] == ';') || (recipients[i] == '|')) {
2961 recipients[i] = ',';
2965 /* Now start extracting recipients... */
2967 while (strlen(recipients) > 0) {
2969 for (i=0; i<=strlen(recipients); ++i) {
2970 if (recipients[i] == '\"') in_quotes = 1 - in_quotes;
2971 if ( ( (recipients[i] == ',') && (!in_quotes) ) || (recipients[i] == 0) ) {
2972 safestrncpy(this_recp, recipients, i+1);
2974 if (recipients[i] == ',') {
2975 strcpy(recipients, &recipients[i+1]);
2978 strcpy(recipients, "");
2985 lprintf(CTDL_DEBUG, "Evaluating recipient #%d: %s\n", num_recps, this_recp);
2987 mailtype = alias(this_recp);
2988 mailtype = alias(this_recp);
2989 mailtype = alias(this_recp);
2990 for (j=0; j<=strlen(this_recp); ++j) {
2991 if (this_recp[j]=='_') {
2992 this_recp_cooked[j] = ' ';
2995 this_recp_cooked[j] = this_recp[j];
3001 if (!strcasecmp(this_recp, "sysop")) {
3003 strcpy(this_recp, config.c_aideroom);
3004 if (strlen(ret->recp_room) > 0) {
3005 strcat(ret->recp_room, "|");
3007 strcat(ret->recp_room, this_recp);
3009 else if (getuser(&tempUS, this_recp) == 0) {
3011 strcpy(this_recp, tempUS.fullname);
3012 if (strlen(ret->recp_local) > 0) {
3013 strcat(ret->recp_local, "|");
3015 strcat(ret->recp_local, this_recp);
3017 else if (getuser(&tempUS, this_recp_cooked) == 0) {
3019 strcpy(this_recp, tempUS.fullname);
3020 if (strlen(ret->recp_local) > 0) {
3021 strcat(ret->recp_local, "|");
3023 strcat(ret->recp_local, this_recp);
3025 else if ( (!strncasecmp(this_recp, "room_", 5))
3026 && (!getroom(&tempQR, &this_recp_cooked[5])) ) {
3028 if (strlen(ret->recp_room) > 0) {
3029 strcat(ret->recp_room, "|");
3031 strcat(ret->recp_room, &this_recp_cooked[5]);
3039 /* Yes, you're reading this correctly: if the target
3040 * domain points back to the local system or an attached
3041 * Citadel directory, the address is invalid. That's
3042 * because if the address were valid, we would have
3043 * already translated it to a local address by now.
3045 if (IsDirectory(this_recp)) {
3050 ++ret->num_internet;
3051 if (strlen(ret->recp_internet) > 0) {
3052 strcat(ret->recp_internet, "|");
3054 strcat(ret->recp_internet, this_recp);
3059 if (strlen(ret->recp_ignet) > 0) {
3060 strcat(ret->recp_ignet, "|");
3062 strcat(ret->recp_ignet, this_recp);
3070 if (strlen(ret->errormsg) == 0) {
3071 snprintf(append, sizeof append,
3072 "Invalid recipient: %s",
3076 snprintf(append, sizeof append,
3079 if ( (strlen(ret->errormsg) + strlen(append)) < SIZ) {
3080 strcat(ret->errormsg, append);
3084 if (strlen(ret->display_recp) == 0) {
3085 strcpy(append, this_recp);
3088 snprintf(append, sizeof append, ", %s",
3091 if ( (strlen(ret->display_recp)+strlen(append)) < SIZ) {
3092 strcat(ret->display_recp, append);
3097 if ((ret->num_local + ret->num_internet + ret->num_ignet +
3098 ret->num_room + ret->num_error) == 0) {
3099 ret->num_error = (-1);
3100 strcpy(ret->errormsg, "No recipients specified.");
3103 lprintf(CTDL_DEBUG, "validate_recipients()\n");
3104 lprintf(CTDL_DEBUG, " local: %d <%s>\n", ret->num_local, ret->recp_local);
3105 lprintf(CTDL_DEBUG, " room: %d <%s>\n", ret->num_room, ret->recp_room);
3106 lprintf(CTDL_DEBUG, " inet: %d <%s>\n", ret->num_internet, ret->recp_internet);
3107 lprintf(CTDL_DEBUG, " ignet: %d <%s>\n", ret->num_ignet, ret->recp_ignet);
3108 lprintf(CTDL_DEBUG, " error: %d <%s>\n", ret->num_error, ret->errormsg);
3116 * message entry - mode 0 (normal)
3118 void cmd_ent0(char *entargs)
3124 char supplied_euid[128];
3125 char masquerade_as[SIZ];
3127 int format_type = 0;
3128 char newusername[SIZ];
3129 struct CtdlMessage *msg;
3133 struct recptypes *valid = NULL;
3134 struct recptypes *valid_to = NULL;
3135 struct recptypes *valid_cc = NULL;
3136 struct recptypes *valid_bcc = NULL;
3143 post = extract_int(entargs, 0);
3144 extract_token(recp, entargs, 1, '|', sizeof recp);
3145 anon_flag = extract_int(entargs, 2);
3146 format_type = extract_int(entargs, 3);
3147 extract_token(subject, entargs, 4, '|', sizeof subject);
3148 do_confirm = extract_int(entargs, 6);
3149 extract_token(cc, entargs, 7, '|', sizeof cc);
3150 extract_token(bcc, entargs, 8, '|', sizeof bcc);
3151 switch(CC->room.QRdefaultview) {
3154 extract_token(supplied_euid, entargs, 9, '|', sizeof supplied_euid);
3157 supplied_euid[0] = 0;
3161 /* first check to make sure the request is valid. */
3163 err = CtdlDoIHavePermissionToPostInThisRoom(errmsg, sizeof errmsg);
3165 cprintf("%d %s\n", err, errmsg);
3169 /* Check some other permission type things. */
3172 if (CC->user.axlevel < 6) {
3173 cprintf("%d You don't have permission to masquerade.\n",
3174 ERROR + HIGHER_ACCESS_REQUIRED);
3177 extract_token(newusername, entargs, 5, '|', sizeof newusername);
3178 memset(CC->fake_postname, 0, sizeof(CC->fake_postname) );
3179 safestrncpy(CC->fake_postname, newusername,
3180 sizeof(CC->fake_postname) );
3181 cprintf("%d ok\n", CIT_OK);
3184 CC->cs_flags |= CS_POSTING;
3186 /* In the Mail> room we have to behave a little differently --
3187 * make sure the user has specified at least one recipient. Then
3188 * validate the recipient(s).
3190 if ( (CC->room.QRflags & QR_MAILBOX)
3191 && (!strcasecmp(&CC->room.QRname[11], MAILROOM)) ) {
3193 if (CC->user.axlevel < 2) {
3194 strcpy(recp, "sysop");
3199 valid_to = validate_recipients(recp);
3200 if (valid_to->num_error > 0) {
3201 cprintf("%d Invalid recipient (To)\n", ERROR + NO_SUCH_USER);
3206 valid_cc = validate_recipients(cc);
3207 if (valid_cc->num_error > 0) {
3208 cprintf("%d Invalid recipient (CC)\n", ERROR + NO_SUCH_USER);
3214 valid_bcc = validate_recipients(bcc);
3215 if (valid_bcc->num_error > 0) {
3216 cprintf("%d Invalid recipient (BCC)\n", ERROR + NO_SUCH_USER);
3223 /* Recipient required, but none were specified */
3224 if ( (valid_to->num_error < 0) && (valid_cc->num_error < 0) && (valid_bcc->num_error < 0) ) {
3228 cprintf("%d At least one recipient is required.\n", ERROR + NO_SUCH_USER);
3232 if (valid_to->num_internet + valid_cc->num_internet + valid_bcc->num_internet > 0) {
3233 if (CtdlCheckInternetMailPermission(&CC->user)==0) {
3234 cprintf("%d You do not have permission "
3235 "to send Internet mail.\n",
3236 ERROR + HIGHER_ACCESS_REQUIRED);
3244 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)
3245 && (CC->user.axlevel < 4) ) {
3246 cprintf("%d Higher access required for network mail.\n",
3247 ERROR + HIGHER_ACCESS_REQUIRED);
3254 if ((RESTRICT_INTERNET == 1)
3255 && (valid_to->num_internet + valid_cc->num_internet + valid_bcc->num_internet > 0)
3256 && ((CC->user.flags & US_INTERNET) == 0)
3257 && (!CC->internal_pgm)) {
3258 cprintf("%d You don't have access to Internet mail.\n",
3259 ERROR + HIGHER_ACCESS_REQUIRED);
3268 /* Is this a room which has anonymous-only or anonymous-option? */
3269 anonymous = MES_NORMAL;
3270 if (CC->room.QRflags & QR_ANONONLY) {
3271 anonymous = MES_ANONONLY;
3273 if (CC->room.QRflags & QR_ANONOPT) {
3274 if (anon_flag == 1) { /* only if the user requested it */
3275 anonymous = MES_ANONOPT;
3279 if ((CC->room.QRflags & QR_MAILBOX) == 0) {
3283 /* If we're only checking the validity of the request, return
3284 * success without creating the message.
3287 cprintf("%d %s\n", CIT_OK,
3288 ((valid_to != NULL) ? valid_to->display_recp : "") );
3295 /* We don't need these anymore because we'll do it differently below */
3300 /* Handle author masquerading */
3301 if (CC->fake_postname[0]) {
3302 strcpy(masquerade_as, CC->fake_postname);
3304 else if (CC->fake_username[0]) {
3305 strcpy(masquerade_as, CC->fake_username);
3308 strcpy(masquerade_as, "");
3311 /* Read in the message from the client. */
3313 cprintf("%d send message\n", START_CHAT_MODE);
3315 cprintf("%d send message\n", SEND_LISTING);
3318 msg = CtdlMakeMessage(&CC->user, recp, cc,
3319 CC->room.QRname, anonymous, format_type,
3320 masquerade_as, subject,
3321 ((strlen(supplied_euid) > 0) ? supplied_euid : NULL),
3324 /* Put together one big recipients struct containing to/cc/bcc all in
3325 * one. This is for the envelope.
3327 char *all_recps = malloc(SIZ * 3);
3328 strcpy(all_recps, recp);
3329 if (strlen(cc) > 0) {
3330 if (strlen(all_recps) > 0) {
3331 strcat(all_recps, ",");
3333 strcat(all_recps, cc);
3335 if (strlen(bcc) > 0) {
3336 if (strlen(all_recps) > 0) {
3337 strcat(all_recps, ",");
3339 strcat(all_recps, bcc);
3341 if (strlen(all_recps) > 0) {
3342 valid = validate_recipients(all_recps);
3350 msgnum = CtdlSubmitMsg(msg, valid, "");
3353 cprintf("%ld\n", msgnum);
3355 cprintf("Message accepted.\n");
3358 cprintf("Internal error.\n");
3360 if (msg->cm_fields['E'] != NULL) {
3361 cprintf("%s\n", msg->cm_fields['E']);
3368 CtdlFreeMessage(msg);
3370 CC->fake_postname[0] = '\0';
3371 if (valid != NULL) {
3380 * API function to delete messages which match a set of criteria
3381 * (returns the actual number of messages deleted)
3383 int CtdlDeleteMessages(char *room_name, /* which room */
3384 long *dmsgnums, /* array of msg numbers to be deleted */
3385 int num_dmsgnums, /* number of msgs to be deleted, or 0 for "any" */
3386 char *content_type, /* or "" for any */
3387 int deferred /* let TDAP sweep it later */
3391 struct ctdlroom qrbuf;
3392 struct cdbdata *cdbfr;
3393 long *msglist = NULL;
3394 long *dellist = NULL;
3397 int num_deleted = 0;
3399 struct MetaData smi;
3401 lprintf(CTDL_DEBUG, "CtdlDeleteMessages(%s, %d msgs, %s, %d)\n",
3402 room_name, num_dmsgnums, content_type, deferred);
3404 /* get room record, obtaining a lock... */
3405 if (lgetroom(&qrbuf, room_name) != 0) {
3406 lprintf(CTDL_ERR, "CtdlDeleteMessages(): Room <%s> not found\n",
3408 return (0); /* room not found */
3410 cdbfr = cdb_fetch(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long));
3412 if (cdbfr != NULL) {
3413 dellist = malloc(cdbfr->len);
3414 msglist = (long *) cdbfr->ptr;
3415 cdbfr->ptr = NULL; /* CtdlDeleteMessages() now owns this memory */
3416 num_msgs = cdbfr->len / sizeof(long);
3420 for (i = 0; i < num_msgs; ++i) {
3423 /* Set/clear a bit for each criterion */
3425 /* 0 messages in the list or a null list means that we are
3426 * interested in deleting any messages which meet the other criteria.
3428 if ((num_dmsgnums == 0) || (dmsgnums == NULL)) {
3429 delete_this |= 0x01;
3432 for (j=0; j<num_dmsgnums; ++j) {
3433 if (msglist[i] == dmsgnums[j]) {
3434 delete_this |= 0x01;
3439 if (strlen(content_type) == 0) {
3440 delete_this |= 0x02;
3442 GetMetaData(&smi, msglist[i]);
3443 if (!strcasecmp(smi.meta_content_type,
3445 delete_this |= 0x02;
3449 /* Delete message only if all bits are set */
3450 if (delete_this == 0x03) {
3451 dellist[num_deleted++] = msglist[i];
3456 num_msgs = sort_msglist(msglist, num_msgs);
3457 cdb_store(CDB_MSGLISTS, &qrbuf.QRnumber, (int)sizeof(long),
3458 msglist, (int)(num_msgs * sizeof(long)));
3460 qrbuf.QRhighest = msglist[num_msgs - 1];
3465 * If the delete operation is "deferred" (and technically, any delete
3466 * operation not performed by THE DREADED AUTO-PURGER ought to be
3467 * a deferred delete) then we save a pointer to the message in the
3468 * DELETED_MSGS_ROOM. This will cause the reference count to remain
3469 * at least 1, which will save the user from having to synchronously
3470 * wait for various disk-intensive operations to complete.
3472 * Slick -- we now use the new bulk API for moving messages.
3474 if ( (deferred) && (num_deleted) ) {
3475 CtdlCopyMsgsToRoom(dellist, num_deleted, DELETED_MSGS_ROOM);
3478 /* Go through the messages we pulled out of the index, and decrement
3479 * their reference counts by 1. If this is the only room the message
3480 * was in, the reference count will reach zero and the message will
3481 * automatically be deleted from the database. We do this in a
3482 * separate pass because there might be plug-in hooks getting called,
3483 * and we don't want that happening during an S_ROOMS critical
3486 if (num_deleted) for (i=0; i<num_deleted; ++i) {
3487 PerformDeleteHooks(qrbuf.QRname, dellist[i]);
3488 AdjRefCount(dellist[i], -1);
3491 /* Now free the memory we used, and go away. */
3492 if (msglist != NULL) free(msglist);
3493 if (dellist != NULL) free(dellist);
3494 lprintf(CTDL_DEBUG, "%d message(s) deleted.\n", num_deleted);
3495 return (num_deleted);
3501 * Check whether the current user has permission to delete messages from
3502 * the current room (returns 1 for yes, 0 for no)
3504 int CtdlDoIHavePermissionToDeleteMessagesFromThisRoom(void) {
3505 getuser(&CC->user, CC->curr_user);
3506 if ((CC->user.axlevel < 6)
3507 && (CC->user.usernum != CC->room.QRroomaide)
3508 && ((CC->room.QRflags & QR_MAILBOX) == 0)
3509 && (!(CC->internal_pgm))) {
3518 * Delete message from current room
3520 void cmd_dele(char *args)
3529 extract_token(msgset, args, 0, '|', sizeof msgset);
3530 num_msgs = num_tokens(msgset, ',');
3532 cprintf("%d Nothing to do.\n", CIT_OK);
3536 if (CtdlDoIHavePermissionToDeleteMessagesFromThisRoom() == 0) {
3537 cprintf("%d Higher access required.\n",
3538 ERROR + HIGHER_ACCESS_REQUIRED);
3543 * Build our message set to be moved/copied
3545 msgs = malloc(num_msgs * sizeof(long));
3546 for (i=0; i<num_msgs; ++i) {
3547 extract_token(msgtok, msgset, i, ',', sizeof msgtok);
3548 msgs[i] = atol(msgtok);
3551 num_deleted = CtdlDeleteMessages(CC->room.QRname, msgs, num_msgs, "", 1);
3555 cprintf("%d %d message%s deleted.\n", CIT_OK,
3556 num_deleted, ((num_deleted != 1) ? "s" : ""));
3558 cprintf("%d Message not found.\n", ERROR + MESSAGE_NOT_FOUND);
3564 * Back end API function for moves and deletes (multiple messages)
3566 int CtdlCopyMsgsToRoom(long *msgnums, int num_msgs, char *dest) {
3569 err = CtdlSaveMsgPointersInRoom(dest, msgnums, num_msgs, 1, NULL);
3570 if (err != 0) return(err);
3579 * move or copy a message to another room
3581 void cmd_move(char *args)
3588 char targ[ROOMNAMELEN];
3589 struct ctdlroom qtemp;
3596 extract_token(msgset, args, 0, '|', sizeof msgset);
3597 num_msgs = num_tokens(msgset, ',');
3599 cprintf("%d Nothing to do.\n", CIT_OK);
3603 extract_token(targ, args, 1, '|', sizeof targ);
3604 convert_room_name_macros(targ, sizeof targ);
3605 targ[ROOMNAMELEN - 1] = 0;
3606 is_copy = extract_int(args, 2);
3608 if (getroom(&qtemp, targ) != 0) {
3609 cprintf("%d '%s' does not exist.\n",
3610 ERROR + ROOM_NOT_FOUND, targ);
3614 getuser(&CC->user, CC->curr_user);
3615 CtdlRoomAccess(&qtemp, &CC->user, &ra, NULL);
3617 /* Check for permission to perform this operation.
3618 * Remember: "CC->room" is source, "qtemp" is target.
3622 /* Aides can move/copy */
3623 if (CC->user.axlevel >= 6) permit = 1;
3625 /* Room aides can move/copy */
3626 if (CC->user.usernum == CC->room.QRroomaide) permit = 1;
3628 /* Permit move/copy from personal rooms */
3629 if ((CC->room.QRflags & QR_MAILBOX)
3630 && (qtemp.QRflags & QR_MAILBOX)) permit = 1;
3632 /* Permit only copy from public to personal room */
3634 && (!(CC->room.QRflags & QR_MAILBOX))
3635 && (qtemp.QRflags & QR_MAILBOX)) permit = 1;
3637 /* User must have access to target room */
3638 if (!(ra & UA_KNOWN)) permit = 0;
3641 cprintf("%d Higher access required.\n",
3642 ERROR + HIGHER_ACCESS_REQUIRED);
3647 * Build our message set to be moved/copied
3649 msgs = malloc(num_msgs * sizeof(long));
3650 for (i=0; i<num_msgs; ++i) {
3651 extract_token(msgtok, msgset, i, ',', sizeof msgtok);
3652 msgs[i] = atol(msgtok);
3658 err = CtdlCopyMsgsToRoom(msgs, num_msgs, targ);
3660 cprintf("%d Cannot store message(s) in %s: error %d\n",
3666 /* Now delete the message from the source room,
3667 * if this is a 'move' rather than a 'copy' operation.
3670 CtdlDeleteMessages(CC->room.QRname, msgs, num_msgs, "", 0);
3674 cprintf("%d Message(s) %s.\n", CIT_OK, (is_copy ? "copied" : "moved") );
3680 * GetMetaData() - Get the supplementary record for a message
3682 void GetMetaData(struct MetaData *smibuf, long msgnum)
3685 struct cdbdata *cdbsmi;
3688 memset(smibuf, 0, sizeof(struct MetaData));
3689 smibuf->meta_msgnum = msgnum;
3690 smibuf->meta_refcount = 1; /* Default reference count is 1 */
3692 /* Use the negative of the message number for its supp record index */
3693 TheIndex = (0L - msgnum);
3695 cdbsmi = cdb_fetch(CDB_MSGMAIN, &TheIndex, sizeof(long));
3696 if (cdbsmi == NULL) {
3697 return; /* record not found; go with defaults */
3699 memcpy(smibuf, cdbsmi->ptr,
3700 ((cdbsmi->len > sizeof(struct MetaData)) ?
3701 sizeof(struct MetaData) : cdbsmi->len));
3708 * PutMetaData() - (re)write supplementary record for a message
3710 void PutMetaData(struct MetaData *smibuf)
3714 /* Use the negative of the message number for the metadata db index */
3715 TheIndex = (0L - smibuf->meta_msgnum);
3717 lprintf(CTDL_DEBUG, "PutMetaData(%ld) - ref count is %d\n",
3718 smibuf->meta_msgnum, smibuf->meta_refcount);
3720 cdb_store(CDB_MSGMAIN,
3721 &TheIndex, (int)sizeof(long),
3722 smibuf, (int)sizeof(struct MetaData));
3727 * AdjRefCount - change the reference count for a message;
3728 * delete the message if it reaches zero
3730 void AdjRefCount(long msgnum, int incr)
3733 struct MetaData smi;
3736 /* This is a *tight* critical section; please keep it that way, as
3737 * it may get called while nested in other critical sections.
3738 * Complicating this any further will surely cause deadlock!
3740 begin_critical_section(S_SUPPMSGMAIN);
3741 GetMetaData(&smi, msgnum);
3742 smi.meta_refcount += incr;
3744 end_critical_section(S_SUPPMSGMAIN);
3745 lprintf(CTDL_DEBUG, "msg %ld ref count incr %d, is now %d\n",
3746 msgnum, incr, smi.meta_refcount);
3748 /* If the reference count is now zero, delete the message
3749 * (and its supplementary record as well).
3751 if (smi.meta_refcount == 0) {
3752 lprintf(CTDL_DEBUG, "Deleting message <%ld>\n", msgnum);
3754 /* Remove from fulltext index */
3755 if (config.c_enable_fulltext) {
3756 ft_index_message(msgnum, 0);
3759 /* Remove from message base */
3761 cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long));
3762 cdb_delete(CDB_BIGMSGS, &delnum, (int)sizeof(long));
3764 /* Remove metadata record */
3765 delnum = (0L - msgnum);
3766 cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long));
3771 * Write a generic object to this room
3773 * Note: this could be much more efficient. Right now we use two temporary
3774 * files, and still pull the message into memory as with all others.
3776 void CtdlWriteObject(char *req_room, /* Room to stuff it in */
3777 char *content_type, /* MIME type of this object */
3778 char *tempfilename, /* Where to fetch it from */
3779 struct ctdluser *is_mailbox, /* Mailbox room? */
3780 int is_binary, /* Is encoding necessary? */
3781 int is_unique, /* Del others of this type? */
3782 unsigned int flags /* Internal save flags */
3787 struct ctdlroom qrbuf;
3788 char roomname[ROOMNAMELEN];
3789 struct CtdlMessage *msg;
3791 char *raw_message = NULL;
3792 char *encoded_message = NULL;
3793 off_t raw_length = 0;
3795 if (is_mailbox != NULL) {
3796 MailboxName(roomname, sizeof roomname, is_mailbox, req_room);
3799 safestrncpy(roomname, req_room, sizeof(roomname));
3802 fp = fopen(tempfilename, "rb");
3804 lprintf(CTDL_CRIT, "Cannot open %s: %s\n",
3805 tempfilename, strerror(errno));
3808 fseek(fp, 0L, SEEK_END);
3809 raw_length = ftell(fp);
3811 lprintf(CTDL_DEBUG, "Raw length is %ld\n", (long)raw_length);
3813 raw_message = malloc((size_t)raw_length + 2);
3814 fread(raw_message, (size_t)raw_length, 1, fp);
3818 encoded_message = malloc((size_t)
3819 (((raw_length * 134) / 100) + 4096 ) );
3822 encoded_message = malloc((size_t)(raw_length + 4096));
3825 sprintf(encoded_message, "Content-type: %s\n", content_type);
3828 sprintf(&encoded_message[strlen(encoded_message)],
3829 "Content-transfer-encoding: base64\n\n"
3833 sprintf(&encoded_message[strlen(encoded_message)],
3834 "Content-transfer-encoding: 7bit\n\n"
3840 &encoded_message[strlen(encoded_message)],
3846 raw_message[raw_length] = 0;
3848 &encoded_message[strlen(encoded_message)],
3856 lprintf(CTDL_DEBUG, "Allocating\n");
3857 msg = malloc(sizeof(struct CtdlMessage));
3858 memset(msg, 0, sizeof(struct CtdlMessage));
3859 msg->cm_magic = CTDLMESSAGE_MAGIC;
3860 msg->cm_anon_type = MES_NORMAL;
3861 msg->cm_format_type = 4;
3862 msg->cm_fields['A'] = strdup(CC->user.fullname);
3863 msg->cm_fields['O'] = strdup(req_room);
3864 msg->cm_fields['N'] = strdup(config.c_nodename);
3865 msg->cm_fields['H'] = strdup(config.c_humannode);
3866 msg->cm_flags = flags;
3868 msg->cm_fields['M'] = encoded_message;
3870 /* Create the requested room if we have to. */
3871 if (getroom(&qrbuf, roomname) != 0) {
3872 create_room(roomname,
3873 ( (is_mailbox != NULL) ? 5 : 3 ),
3874 "", 0, 1, 0, VIEW_BBS);
3876 /* If the caller specified this object as unique, delete all
3877 * other objects of this type that are currently in the room.
3880 lprintf(CTDL_DEBUG, "Deleted %d other msgs of this type\n",
3881 CtdlDeleteMessages(roomname, NULL, 0, content_type, 0)
3884 /* Now write the data */
3885 CtdlSubmitMsg(msg, NULL, roomname);
3886 CtdlFreeMessage(msg);
3894 void CtdlGetSysConfigBackend(long msgnum, void *userdata) {
3895 config_msgnum = msgnum;
3899 char *CtdlGetSysConfig(char *sysconfname) {
3900 char hold_rm[ROOMNAMELEN];
3903 struct CtdlMessage *msg;
3906 strcpy(hold_rm, CC->room.QRname);
3907 if (getroom(&CC->room, SYSCONFIGROOM) != 0) {
3908 getroom(&CC->room, hold_rm);
3913 /* We want the last (and probably only) config in this room */
3914 begin_critical_section(S_CONFIG);
3915 config_msgnum = (-1L);
3916 CtdlForEachMessage(MSGS_LAST, 1, NULL, sysconfname, NULL,
3917 CtdlGetSysConfigBackend, NULL);
3918 msgnum = config_msgnum;
3919 end_critical_section(S_CONFIG);
3925 msg = CtdlFetchMessage(msgnum, 1);
3927 conf = strdup(msg->cm_fields['M']);
3928 CtdlFreeMessage(msg);
3935 getroom(&CC->room, hold_rm);
3937 if (conf != NULL) do {
3938 extract_token(buf, conf, 0, '\n', sizeof buf);
3939 strcpy(conf, &conf[strlen(buf)+1]);
3940 } while ( (strlen(conf)>0) && (strlen(buf)>0) );
3945 void CtdlPutSysConfig(char *sysconfname, char *sysconfdata) {
3946 char temp[PATH_MAX];
3949 CtdlMakeTempFileName(temp, sizeof temp);
3951 fp = fopen(temp, "w");
3952 if (fp == NULL) return;
3953 fprintf(fp, "%s", sysconfdata);
3956 /* this handy API function does all the work for us */
3957 CtdlWriteObject(SYSCONFIGROOM, sysconfname, temp, NULL, 0, 1, 0);
3963 * Determine whether a given Internet address belongs to the current user
3965 int CtdlIsMe(char *addr, int addr_buf_len)
3967 struct recptypes *recp;
3970 recp = validate_recipients(addr);
3971 if (recp == NULL) return(0);
3973 if (recp->num_local == 0) {
3978 for (i=0; i<recp->num_local; ++i) {
3979 extract_token(addr, recp->recp_local, i, '|', addr_buf_len);
3980 if (!strcasecmp(addr, CC->user.fullname)) {
3992 * Citadel protocol command to do the same
3994 void cmd_isme(char *argbuf) {
3997 if (CtdlAccessCheck(ac_logged_in)) return;
3998 extract_token(addr, argbuf, 0, '|', sizeof addr);
4000 if (CtdlIsMe(addr, sizeof addr)) {
4001 cprintf("%d %s\n", CIT_OK, addr);
4004 cprintf("%d Not you.\n", ERROR + ILLEGAL_VALUE);