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 * We use a system of weights. When we find a part that matches one of the
1214 * MIME types we've declared as preferential, we can store it in ma->chosen_part
1215 * and then set ma->chosen_pref to that MIME type's position in our preference
1216 * list. If we then hit another match, we only replace the first match if
1217 * the preference value is lower.
1219 void choose_preferred(char *name, char *filename, char *partnum, char *disp,
1220 void *content, char *cbtype, char *cbcharset, size_t length,
1221 char *encoding, void *cbuserdata)
1227 ma = (struct ma_info *)cbuserdata;
1229 if (ma->is_ma > 0) {
1230 for (i=0; i<num_tokens(CC->preferred_formats, '|'); ++i) {
1231 extract_token(buf, CC->preferred_formats, i, '|', sizeof buf);
1232 lprintf(CTDL_DEBUG, "Is <%s> == <%s> ??\n", buf, cbtype);
1233 if ( (!strcasecmp(buf, cbtype)) && (!ma->freeze) ) {
1234 if (i < ma->chosen_pref) {
1235 safestrncpy(ma->chosen_part, partnum, sizeof ma->chosen_part);
1236 ma->chosen_pref = i;
1244 * Now that we've chosen our preferred part, output it.
1246 void output_preferred(char *name, char *filename, char *partnum, char *disp,
1247 void *content, char *cbtype, char *cbcharset, size_t length,
1248 char *encoding, void *cbuserdata)
1252 int add_newline = 0;
1256 ma = (struct ma_info *)cbuserdata;
1258 /* This is not the MIME part you're looking for... */
1259 if (strcasecmp(partnum, ma->chosen_part)) return;
1261 /* If the content-type of this part is in our preferred formats
1262 * list, we can simply output it verbatim.
1264 for (i=0; i<num_tokens(CC->preferred_formats, '|'); ++i) {
1265 extract_token(buf, CC->preferred_formats, i, '|', sizeof buf);
1266 if (!strcasecmp(buf, cbtype)) {
1267 /* Yeah! Go! W00t!! */
1269 text_content = (char *)content;
1270 if (text_content[length-1] != '\n') {
1274 cprintf("Content-type: %s", cbtype);
1275 if (strlen(cbcharset) > 0) {
1276 cprintf("; charset=%s", cbcharset);
1278 cprintf("\nContent-length: %d\n",
1279 (int)(length + add_newline) );
1280 if (strlen(encoding) > 0) {
1281 cprintf("Content-transfer-encoding: %s\n", encoding);
1284 cprintf("Content-transfer-encoding: 7bit\n");
1287 client_write(content, length);
1288 if (add_newline) cprintf("\n");
1293 /* No translations required or possible: output as text/plain */
1294 cprintf("Content-type: text/plain\n\n");
1295 fixed_output(name, filename, partnum, disp, content, cbtype, cbcharset,
1296 length, encoding, cbuserdata);
1301 char desired_section[64];
1308 * Callback function for
1310 void extract_encapsulated_message(char *name, char *filename, char *partnum, char *disp,
1311 void *content, char *cbtype, char *cbcharset, size_t length,
1312 char *encoding, void *cbuserdata)
1314 struct encapmsg *encap;
1316 encap = (struct encapmsg *)cbuserdata;
1318 /* Only proceed if this is the desired section... */
1319 if (!strcasecmp(encap->desired_section, partnum)) {
1320 encap->msglen = length;
1321 encap->msg = malloc(length + 2);
1322 memcpy(encap->msg, content, length);
1332 * Get a message off disk. (returns om_* values found in msgbase.h)
1335 int CtdlOutputMsg(long msg_num, /* message number (local) to fetch */
1336 int mode, /* how would you like that message? */
1337 int headers_only, /* eschew the message body? */
1338 int do_proto, /* do Citadel protocol responses? */
1339 int crlf, /* Use CRLF newlines instead of LF? */
1340 char *section /* NULL or a message/rfc822 section */
1342 struct CtdlMessage *TheMessage = NULL;
1343 int retcode = om_no_such_msg;
1344 struct encapmsg encap;
1346 lprintf(CTDL_DEBUG, "CtdlOutputMsg() msgnum=%ld, mode=%d, section=%s\n",
1348 (section ? section : "<>")
1351 if ((!(CC->logged_in)) && (!(CC->internal_pgm))) {
1352 if (do_proto) cprintf("%d Not logged in.\n",
1353 ERROR + NOT_LOGGED_IN);
1354 return(om_not_logged_in);
1357 /* FIXME: check message id against msglist for this room */
1360 * Fetch the message from disk. If we're in any sort of headers
1361 * only mode, request that we don't even bother loading the body
1364 if ( (headers_only == HEADERS_FAST) || (headers_only == HEADERS_ONLY) ) {
1365 TheMessage = CtdlFetchMessage(msg_num, 0);
1368 TheMessage = CtdlFetchMessage(msg_num, 1);
1371 if (TheMessage == NULL) {
1372 if (do_proto) cprintf("%d Can't locate msg %ld on disk\n",
1373 ERROR + MESSAGE_NOT_FOUND, msg_num);
1374 return(om_no_such_msg);
1377 /* Here is the weird form of this command, to process only an
1378 * encapsulated message/rfc822 section.
1380 if (section) if (strlen(section)>0) if (strcmp(section, "0")) {
1381 memset(&encap, 0, sizeof encap);
1382 safestrncpy(encap.desired_section, section, sizeof encap.desired_section);
1383 mime_parser(TheMessage->cm_fields['M'],
1385 *extract_encapsulated_message,
1386 NULL, NULL, (void *)&encap, 0
1388 CtdlFreeMessage(TheMessage);
1392 encap.msg[encap.msglen] = 0;
1393 TheMessage = convert_internet_message(encap.msg);
1394 encap.msg = NULL; /* no free() here, TheMessage owns it now */
1396 /* Now we let it fall through to the bottom of this
1397 * function, because TheMessage now contains the
1398 * encapsulated message instead of the top-level
1399 * message. Isn't that neat?
1404 if (do_proto) cprintf("%d msg %ld has no part %s\n",
1405 ERROR + MESSAGE_NOT_FOUND, msg_num, section);
1406 retcode = om_no_such_msg;
1411 /* Ok, output the message now */
1412 retcode = CtdlOutputPreLoadedMsg(
1414 headers_only, do_proto, crlf);
1415 CtdlFreeMessage(TheMessage);
1422 * Get a message off disk. (returns om_* values found in msgbase.h)
1425 int CtdlOutputPreLoadedMsg(
1426 struct CtdlMessage *TheMessage,
1427 int mode, /* how would you like that message? */
1428 int headers_only, /* eschew the message body? */
1429 int do_proto, /* do Citadel protocol responses? */
1430 int crlf /* Use CRLF newlines instead of LF? */
1436 char display_name[256];
1438 char *nl; /* newline string */
1440 int subject_found = 0;
1443 /* Buffers needed for RFC822 translation. These are all filled
1444 * using functions that are bounds-checked, and therefore we can
1445 * make them substantially smaller than SIZ.
1453 char datestamp[100];
1455 lprintf(CTDL_DEBUG, "CtdlOutputPreLoadedMsg(TheMessage=%s, %d, %d, %d, %d\n",
1456 ((TheMessage == NULL) ? "NULL" : "not null"),
1457 mode, headers_only, do_proto, crlf);
1459 strcpy(mid, "unknown");
1460 nl = (crlf ? "\r\n" : "\n");
1462 if (!is_valid_message(TheMessage)) {
1464 "ERROR: invalid preloaded message for output\n");
1465 return(om_no_such_msg);
1468 /* Are we downloading a MIME component? */
1469 if (mode == MT_DOWNLOAD) {
1470 if (TheMessage->cm_format_type != FMT_RFC822) {
1472 cprintf("%d This is not a MIME message.\n",
1473 ERROR + ILLEGAL_VALUE);
1474 } else if (CC->download_fp != NULL) {
1475 if (do_proto) cprintf(
1476 "%d You already have a download open.\n",
1477 ERROR + RESOURCE_BUSY);
1479 /* Parse the message text component */
1480 mptr = TheMessage->cm_fields['M'];
1481 mime_parser(mptr, NULL, *mime_download, NULL, NULL, NULL, 0);
1482 /* If there's no file open by this time, the requested
1483 * section wasn't found, so print an error
1485 if (CC->download_fp == NULL) {
1486 if (do_proto) cprintf(
1487 "%d Section %s not found.\n",
1488 ERROR + FILE_NOT_FOUND,
1489 CC->download_desired_section);
1492 return((CC->download_fp != NULL) ? om_ok : om_mime_error);
1495 /* now for the user-mode message reading loops */
1496 if (do_proto) cprintf("%d msg:\n", LISTING_FOLLOWS);
1498 /* Does the caller want to skip the headers? */
1499 if (headers_only == HEADERS_NONE) goto START_TEXT;
1501 /* Tell the client which format type we're using. */
1502 if ( (mode == MT_CITADEL) && (do_proto) ) {
1503 cprintf("type=%d\n", TheMessage->cm_format_type);
1506 /* nhdr=yes means that we're only displaying headers, no body */
1507 if ( (TheMessage->cm_anon_type == MES_ANONONLY)
1508 && (mode == MT_CITADEL)
1511 cprintf("nhdr=yes\n");
1514 /* begin header processing loop for Citadel message format */
1516 if ((mode == MT_CITADEL) || (mode == MT_MIME)) {
1518 safestrncpy(display_name, "<unknown>", sizeof display_name);
1519 if (TheMessage->cm_fields['A']) {
1520 strcpy(buf, TheMessage->cm_fields['A']);
1521 if (TheMessage->cm_anon_type == MES_ANONONLY) {
1522 safestrncpy(display_name, "****", sizeof display_name);
1524 else if (TheMessage->cm_anon_type == MES_ANONOPT) {
1525 safestrncpy(display_name, "anonymous", sizeof display_name);
1528 safestrncpy(display_name, buf, sizeof display_name);
1530 if ((is_room_aide())
1531 && ((TheMessage->cm_anon_type == MES_ANONONLY)
1532 || (TheMessage->cm_anon_type == MES_ANONOPT))) {
1533 size_t tmp = strlen(display_name);
1534 snprintf(&display_name[tmp],
1535 sizeof display_name - tmp,
1540 /* Don't show Internet address for users on the
1541 * local Citadel network.
1544 if (TheMessage->cm_fields['N'] != NULL)
1545 if (strlen(TheMessage->cm_fields['N']) > 0)
1546 if (haschar(TheMessage->cm_fields['N'], '.') == 0) {
1550 /* Now spew the header fields in the order we like them. */
1551 safestrncpy(allkeys, FORDER, sizeof allkeys);
1552 for (i=0; i<strlen(allkeys); ++i) {
1553 k = (int) allkeys[i];
1555 if ( (TheMessage->cm_fields[k] != NULL)
1556 && (msgkeys[k] != NULL) ) {
1558 if (do_proto) cprintf("%s=%s\n",
1562 else if ((k == 'F') && (suppress_f)) {
1565 /* Masquerade display name if needed */
1567 if (do_proto) cprintf("%s=%s\n",
1569 TheMessage->cm_fields[k]
1578 /* begin header processing loop for RFC822 transfer format */
1583 strcpy(snode, NODENAME);
1584 strcpy(lnode, HUMANNODE);
1585 if (mode == MT_RFC822) {
1586 for (i = 0; i < 256; ++i) {
1587 if (TheMessage->cm_fields[i]) {
1588 mptr = TheMessage->cm_fields[i];
1591 safestrncpy(luser, mptr, sizeof luser);
1592 safestrncpy(suser, mptr, sizeof suser);
1594 else if (i == 'Y') {
1595 cprintf("CC: %s%s", mptr, nl);
1597 else if (i == 'P') {
1598 cprintf("Return-Path: %s%s", mptr, nl);
1600 else if (i == 'V') {
1601 cprintf("Envelope-To: %s%s", mptr, nl);
1603 else if (i == 'U') {
1604 cprintf("Subject: %s%s", mptr, nl);
1608 safestrncpy(mid, mptr, sizeof mid);
1610 safestrncpy(lnode, mptr, sizeof lnode);
1612 safestrncpy(fuser, mptr, sizeof fuser);
1613 /* else if (i == 'O')
1614 cprintf("X-Citadel-Room: %s%s",
1617 safestrncpy(snode, mptr, sizeof snode);
1619 cprintf("To: %s%s", mptr, nl);
1620 else if (i == 'T') {
1621 datestring(datestamp, sizeof datestamp,
1622 atol(mptr), DATESTRING_RFC822);
1623 cprintf("Date: %s%s", datestamp, nl);
1627 if (subject_found == 0) {
1628 cprintf("Subject: (no subject)%s", nl);
1632 for (i=0; i<strlen(suser); ++i) {
1633 suser[i] = tolower(suser[i]);
1634 if (!isalnum(suser[i])) suser[i]='_';
1637 if (mode == MT_RFC822) {
1638 if (!strcasecmp(snode, NODENAME)) {
1639 safestrncpy(snode, FQDN, sizeof snode);
1642 /* Construct a fun message id */
1643 cprintf("Message-ID: <%s", mid);
1644 if (strchr(mid, '@')==NULL) {
1645 cprintf("@%s", snode);
1649 if (!is_room_aide() && (TheMessage->cm_anon_type == MES_ANONONLY)) {
1650 cprintf("From: \"----\" <x@x.org>%s", nl);
1652 else if (!is_room_aide() && (TheMessage->cm_anon_type == MES_ANONOPT)) {
1653 cprintf("From: \"anonymous\" <x@x.org>%s", nl);
1655 else if (strlen(fuser) > 0) {
1656 cprintf("From: \"%s\" <%s>%s", luser, fuser, nl);
1659 cprintf("From: \"%s\" <%s@%s>%s", luser, suser, snode, nl);
1662 cprintf("Organization: %s%s", lnode, nl);
1664 /* Blank line signifying RFC822 end-of-headers */
1665 if (TheMessage->cm_format_type != FMT_RFC822) {
1670 /* end header processing loop ... at this point, we're in the text */
1672 if (headers_only == HEADERS_FAST) goto DONE;
1673 mptr = TheMessage->cm_fields['M'];
1675 /* Tell the client about the MIME parts in this message */
1676 if (TheMessage->cm_format_type == FMT_RFC822) {
1677 if ( (mode == MT_CITADEL) || (mode == MT_MIME) ) {
1678 memset(&ma, 0, sizeof(struct ma_info));
1679 mime_parser(mptr, NULL,
1680 (do_proto ? *list_this_part : NULL),
1681 (do_proto ? *list_this_pref : NULL),
1682 (do_proto ? *list_this_suff : NULL),
1685 else if (mode == MT_RFC822) { /* unparsed RFC822 dump */
1686 char *start_of_text = NULL;
1687 start_of_text = strstr(mptr, "\n\r\n");
1688 if (start_of_text == NULL) start_of_text = strstr(mptr, "\n\n");
1689 if (start_of_text == NULL) start_of_text = mptr;
1691 start_of_text = strstr(start_of_text, "\n");
1693 while (ch=*mptr, ch!=0) {
1697 else switch(headers_only) {
1699 if (mptr >= start_of_text) {
1700 if (ch == 10) cprintf("%s", nl);
1701 else cprintf("%c", ch);
1705 if (mptr < start_of_text) {
1706 if (ch == 10) cprintf("%s", nl);
1707 else cprintf("%c", ch);
1711 if (ch == 10) cprintf("%s", nl);
1712 else cprintf("%c", ch);
1721 if (headers_only == HEADERS_ONLY) {
1725 /* signify start of msg text */
1726 if ( (mode == MT_CITADEL) || (mode == MT_MIME) ) {
1727 if (do_proto) cprintf("text\n");
1730 /* If the format type on disk is 1 (fixed-format), then we want
1731 * everything to be output completely literally ... regardless of
1732 * what message transfer format is in use.
1734 if (TheMessage->cm_format_type == FMT_FIXED) {
1735 if (mode == MT_MIME) {
1736 cprintf("Content-type: text/plain\n\n");
1739 while (ch = *mptr++, ch > 0) {
1742 if ((ch == 10) || (strlen(buf) > 250)) {
1743 cprintf("%s%s", buf, nl);
1746 buf[strlen(buf) + 1] = 0;
1747 buf[strlen(buf)] = ch;
1750 if (strlen(buf) > 0)
1751 cprintf("%s%s", buf, nl);
1754 /* If the message on disk is format 0 (Citadel vari-format), we
1755 * output using the formatter at 80 columns. This is the final output
1756 * form if the transfer format is RFC822, but if the transfer format
1757 * is Citadel proprietary, it'll still work, because the indentation
1758 * for new paragraphs is correct and the client will reformat the
1759 * message to the reader's screen width.
1761 if (TheMessage->cm_format_type == FMT_CITADEL) {
1762 if (mode == MT_MIME) {
1763 cprintf("Content-type: text/x-citadel-variformat\n\n");
1765 memfmout(mptr, 0, nl);
1768 /* If the message on disk is format 4 (MIME), we've gotta hand it
1769 * off to the MIME parser. The client has already been told that
1770 * this message is format 1 (fixed format), so the callback function
1771 * we use will display those parts as-is.
1773 if (TheMessage->cm_format_type == FMT_RFC822) {
1774 memset(&ma, 0, sizeof(struct ma_info));
1776 if (mode == MT_MIME) {
1777 ma.use_fo_hooks = 0;
1778 strcpy(ma.chosen_part, "1");
1779 ma.chosen_pref = 9999;
1780 mime_parser(mptr, NULL,
1781 *choose_preferred, *fixed_output_pre,
1782 *fixed_output_post, (void *)&ma, 0);
1783 mime_parser(mptr, NULL,
1784 *output_preferred, NULL, NULL, (void *)&ma, 0);
1787 ma.use_fo_hooks = 1;
1788 mime_parser(mptr, NULL,
1789 *fixed_output, *fixed_output_pre,
1790 *fixed_output_post, (void *)&ma, 0);
1795 DONE: /* now we're done */
1796 if (do_proto) cprintf("000\n");
1803 * display a message (mode 0 - Citadel proprietary)
1805 void cmd_msg0(char *cmdbuf)
1808 int headers_only = HEADERS_ALL;
1810 msgid = extract_long(cmdbuf, 0);
1811 headers_only = extract_int(cmdbuf, 1);
1813 CtdlOutputMsg(msgid, MT_CITADEL, headers_only, 1, 0, NULL);
1819 * display a message (mode 2 - RFC822)
1821 void cmd_msg2(char *cmdbuf)
1824 int headers_only = HEADERS_ALL;
1826 msgid = extract_long(cmdbuf, 0);
1827 headers_only = extract_int(cmdbuf, 1);
1829 CtdlOutputMsg(msgid, MT_RFC822, headers_only, 1, 1, NULL);
1835 * display a message (mode 3 - IGnet raw format - internal programs only)
1837 void cmd_msg3(char *cmdbuf)
1840 struct CtdlMessage *msg;
1843 if (CC->internal_pgm == 0) {
1844 cprintf("%d This command is for internal programs only.\n",
1845 ERROR + HIGHER_ACCESS_REQUIRED);
1849 msgnum = extract_long(cmdbuf, 0);
1850 msg = CtdlFetchMessage(msgnum, 1);
1852 cprintf("%d Message %ld not found.\n",
1853 ERROR + MESSAGE_NOT_FOUND, msgnum);
1857 serialize_message(&smr, msg);
1858 CtdlFreeMessage(msg);
1861 cprintf("%d Unable to serialize message\n",
1862 ERROR + INTERNAL_ERROR);
1866 cprintf("%d %ld\n", BINARY_FOLLOWS, (long)smr.len);
1867 client_write((char *)smr.ser, (int)smr.len);
1874 * Display a message using MIME content types
1876 void cmd_msg4(char *cmdbuf)
1881 msgid = extract_long(cmdbuf, 0);
1882 extract_token(section, cmdbuf, 1, '|', sizeof section);
1883 CtdlOutputMsg(msgid, MT_MIME, 0, 1, 0, (section[0] ? section : NULL) );
1889 * Client tells us its preferred message format(s)
1891 void cmd_msgp(char *cmdbuf)
1893 safestrncpy(CC->preferred_formats, cmdbuf,
1894 sizeof(CC->preferred_formats));
1895 cprintf("%d ok\n", CIT_OK);
1900 * Open a component of a MIME message as a download file
1902 void cmd_opna(char *cmdbuf)
1905 char desired_section[128];
1907 msgid = extract_long(cmdbuf, 0);
1908 extract_token(desired_section, cmdbuf, 1, '|', sizeof desired_section);
1909 safestrncpy(CC->download_desired_section, desired_section,
1910 sizeof CC->download_desired_section);
1911 CtdlOutputMsg(msgid, MT_DOWNLOAD, 0, 1, 1, NULL);
1915 * Save one or more message pointers into a specified room
1916 * (Returns 0 for success, nonzero for failure)
1917 * roomname may be NULL to use the current room
1919 * Note that the 'supplied_msg' field may be set to NULL, in which case
1920 * the message will be fetched from disk, by number, if we need to perform
1921 * replication checks. This adds an additional database read, so if the
1922 * caller already has the message in memory then it should be supplied. (Obviously
1923 * this mode of operation only works if we're saving a single message.)
1925 int CtdlSaveMsgPointersInRoom(char *roomname, long newmsgidlist[], int num_newmsgs,
1926 int do_repl_check, struct CtdlMessage *supplied_msg)
1929 char hold_rm[ROOMNAMELEN];
1930 struct cdbdata *cdbfr;
1933 long highest_msg = 0L;
1936 struct CtdlMessage *msg = NULL;
1938 long *msgs_to_be_merged = NULL;
1939 int num_msgs_to_be_merged = 0;
1942 "CtdlSaveMsgPointersInRoom(room=%s, num_msgs=%d, repl=%d)\n",
1943 roomname, num_newmsgs, do_repl_check);
1945 strcpy(hold_rm, CC->room.QRname);
1948 if (newmsgidlist == NULL) return(ERROR + INTERNAL_ERROR);
1949 if (num_newmsgs < 1) return(ERROR + INTERNAL_ERROR);
1950 if (num_newmsgs > 1) supplied_msg = NULL;
1952 /* Now the regular stuff */
1953 if (lgetroom(&CC->room,
1954 ((roomname != NULL) ? roomname : CC->room.QRname) )
1956 lprintf(CTDL_ERR, "No such room <%s>\n", roomname);
1957 return(ERROR + ROOM_NOT_FOUND);
1961 msgs_to_be_merged = malloc(sizeof(long) * num_newmsgs);
1962 num_msgs_to_be_merged = 0;
1965 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long));
1966 if (cdbfr == NULL) {
1970 msglist = (long *) cdbfr->ptr;
1971 cdbfr->ptr = NULL; /* CtdlSaveMsgPointerInRoom() now owns this memory */
1972 num_msgs = cdbfr->len / sizeof(long);
1977 /* Create a list of msgid's which were supplied by the caller, but do
1978 * not already exist in the target room. It is absolutely taboo to
1979 * have more than one reference to the same message in a room.
1981 for (i=0; i<num_newmsgs; ++i) {
1983 if (num_msgs > 0) for (j=0; j<num_msgs; ++j) {
1984 if (msglist[j] == newmsgidlist[i]) {
1989 msgs_to_be_merged[num_msgs_to_be_merged++] = newmsgidlist[i];
1993 lprintf(9, "%d unique messages to be merged\n", num_msgs_to_be_merged);
1996 * Now merge the new messages
1998 msglist = realloc(msglist, (sizeof(long) * (num_msgs + num_msgs_to_be_merged)) );
1999 if (msglist == NULL) {
2000 lprintf(CTDL_ALERT, "ERROR: can't realloc message list!\n");
2002 memcpy(&msglist[num_msgs], msgs_to_be_merged, (sizeof(long) * num_msgs_to_be_merged) );
2003 num_msgs += num_msgs_to_be_merged;
2005 /* Sort the message list, so all the msgid's are in order */
2006 num_msgs = sort_msglist(msglist, num_msgs);
2008 /* Determine the highest message number */
2009 highest_msg = msglist[num_msgs - 1];
2011 /* Write it back to disk. */
2012 cdb_store(CDB_MSGLISTS, &CC->room.QRnumber, (int)sizeof(long),
2013 msglist, (int)(num_msgs * sizeof(long)));
2015 /* Free up the memory we used. */
2018 /* Update the highest-message pointer and unlock the room. */
2019 CC->room.QRhighest = highest_msg;
2020 lputroom(&CC->room);
2022 /* Perform replication checks if necessary */
2023 if ( (DoesThisRoomNeedEuidIndexing(&CC->room)) && (do_repl_check) ) {
2024 lprintf(CTDL_DEBUG, "CtdlSaveMsgPointerInRoom() doing repl checks\n");
2026 for (i=0; i<num_msgs_to_be_merged; ++i) {
2027 msgid = msgs_to_be_merged[i];
2029 if (supplied_msg != NULL) {
2033 msg = CtdlFetchMessage(msgid, 0);
2037 ReplicationChecks(msg);
2039 /* If the message has an Exclusive ID, index that... */
2040 if (msg->cm_fields['E'] != NULL) {
2041 index_message_by_euid(msg->cm_fields['E'], &CC->room, msgid);
2044 /* Free up the memory we may have allocated */
2045 if (msg != supplied_msg) {
2046 CtdlFreeMessage(msg);
2054 lprintf(CTDL_DEBUG, "CtdlSaveMsgPointerInRoom() skips repl checks\n");
2057 /* Submit this room for net processing */
2058 network_queue_room(&CC->room, NULL);
2060 #ifdef HAVE_LIBSIEVE
2061 /* If this is someone's inbox, submit the room for sieve processing */
2062 if (!strcasecmp(&CC->room.QRname[11], MAILROOM)) {
2063 sieve_queue_room(&CC->room);
2065 #endif /* HAVE_LIBSIEVE */
2067 /* Go back to the room we were in before we wandered here... */
2068 getroom(&CC->room, hold_rm);
2070 /* Bump the reference count for all messages which were merged */
2071 for (i=0; i<num_msgs_to_be_merged; ++i) {
2072 AdjRefCount(msgs_to_be_merged[i], +1);
2075 /* Free up memory... */
2076 if (msgs_to_be_merged != NULL) {
2077 free(msgs_to_be_merged);
2080 /* Return success. */
2086 * This is the same as CtdlSaveMsgPointersInRoom() but it only accepts
2089 int CtdlSaveMsgPointerInRoom(char *roomname, long msgid,
2090 int do_repl_check, struct CtdlMessage *supplied_msg)
2092 return CtdlSaveMsgPointersInRoom(roomname, &msgid, 1, do_repl_check, supplied_msg);
2099 * Message base operation to save a new message to the message store
2100 * (returns new message number)
2102 * This is the back end for CtdlSubmitMsg() and should not be directly
2103 * called by server-side modules.
2106 long send_message(struct CtdlMessage *msg) {
2114 /* Get a new message number */
2115 newmsgid = get_new_message_number();
2116 snprintf(msgidbuf, sizeof msgidbuf, "%010ld@%s", newmsgid, config.c_fqdn);
2118 /* Generate an ID if we don't have one already */
2119 if (msg->cm_fields['I']==NULL) {
2120 msg->cm_fields['I'] = strdup(msgidbuf);
2123 /* If the message is big, set its body aside for storage elsewhere */
2124 if (msg->cm_fields['M'] != NULL) {
2125 if (strlen(msg->cm_fields['M']) > BIGMSG) {
2127 holdM = msg->cm_fields['M'];
2128 msg->cm_fields['M'] = NULL;
2132 /* Serialize our data structure for storage in the database */
2133 serialize_message(&smr, msg);
2136 msg->cm_fields['M'] = holdM;
2140 cprintf("%d Unable to serialize message\n",
2141 ERROR + INTERNAL_ERROR);
2145 /* Write our little bundle of joy into the message base */
2146 if (cdb_store(CDB_MSGMAIN, &newmsgid, (int)sizeof(long),
2147 smr.ser, smr.len) < 0) {
2148 lprintf(CTDL_ERR, "Can't store message\n");
2152 cdb_store(CDB_BIGMSGS,
2162 /* Free the memory we used for the serialized message */
2165 /* Return the *local* message ID to the caller
2166 * (even if we're storing an incoming network message)
2174 * Serialize a struct CtdlMessage into the format used on disk and network.
2176 * This function loads up a "struct ser_ret" (defined in server.h) which
2177 * contains the length of the serialized message and a pointer to the
2178 * serialized message in memory. THE LATTER MUST BE FREED BY THE CALLER.
2180 void serialize_message(struct ser_ret *ret, /* return values */
2181 struct CtdlMessage *msg) /* unserialized msg */
2183 size_t wlen, fieldlen;
2185 static char *forder = FORDER;
2188 * Check for valid message format
2190 if (is_valid_message(msg) == 0) {
2191 lprintf(CTDL_ERR, "serialize_message() aborting due to invalid message\n");
2198 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL)
2199 ret->len = ret->len +
2200 strlen(msg->cm_fields[(int)forder[i]]) + 2;
2202 ret->ser = malloc(ret->len);
2203 if (ret->ser == NULL) {
2204 lprintf(CTDL_ERR, "serialize_message() malloc(%ld) failed: %s\n",
2205 (long)ret->len, strerror(errno));
2212 ret->ser[1] = msg->cm_anon_type;
2213 ret->ser[2] = msg->cm_format_type;
2216 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL) {
2217 fieldlen = strlen(msg->cm_fields[(int)forder[i]]);
2218 ret->ser[wlen++] = (char)forder[i];
2219 safestrncpy((char *)&ret->ser[wlen], msg->cm_fields[(int)forder[i]], fieldlen+1);
2220 wlen = wlen + fieldlen + 1;
2222 if (ret->len != wlen) lprintf(CTDL_ERR, "ERROR: len=%ld wlen=%ld\n",
2223 (long)ret->len, (long)wlen);
2231 * Check to see if any messages already exist in the current room which
2232 * carry the same Exclusive ID as this one. If any are found, delete them.
2234 void ReplicationChecks(struct CtdlMessage *msg) {
2235 long old_msgnum = (-1L);
2237 if (DoesThisRoomNeedEuidIndexing(&CC->room) == 0) return;
2239 lprintf(CTDL_DEBUG, "Performing replication checks in <%s>\n",
2242 /* No exclusive id? Don't do anything. */
2243 if (msg == NULL) return;
2244 if (msg->cm_fields['E'] == NULL) return;
2245 if (strlen(msg->cm_fields['E']) == 0) return;
2246 /*lprintf(CTDL_DEBUG, "Exclusive ID: <%s> for room <%s>\n",
2247 msg->cm_fields['E'], CC->room.QRname);*/
2249 old_msgnum = locate_message_by_euid(msg->cm_fields['E'], &CC->room);
2250 if (old_msgnum > 0L) {
2251 lprintf(CTDL_DEBUG, "ReplicationChecks() replacing message %ld\n", old_msgnum);
2252 CtdlDeleteMessages(CC->room.QRname, &old_msgnum, 1, "", 0);
2259 * Save a message to disk and submit it into the delivery system.
2261 long CtdlSubmitMsg(struct CtdlMessage *msg, /* message to save */
2262 struct recptypes *recps, /* recipients (if mail) */
2263 char *force /* force a particular room? */
2265 char submit_filename[128];
2266 char generated_timestamp[32];
2267 char hold_rm[ROOMNAMELEN];
2268 char actual_rm[ROOMNAMELEN];
2269 char force_room[ROOMNAMELEN];
2270 char content_type[SIZ]; /* We have to learn this */
2271 char recipient[SIZ];
2274 struct ctdluser userbuf;
2276 struct MetaData smi;
2277 FILE *network_fp = NULL;
2278 static int seqnum = 1;
2279 struct CtdlMessage *imsg = NULL;
2282 char *hold_R, *hold_D;
2283 char *collected_addresses = NULL;
2284 struct addresses_to_be_filed *aptr = NULL;
2285 char *saved_rfc822_version = NULL;
2286 int qualified_for_journaling = 0;
2288 lprintf(CTDL_DEBUG, "CtdlSubmitMsg() called\n");
2289 if (is_valid_message(msg) == 0) return(-1); /* self check */
2291 /* If this message has no timestamp, we take the liberty of
2292 * giving it one, right now.
2294 if (msg->cm_fields['T'] == NULL) {
2295 snprintf(generated_timestamp, sizeof generated_timestamp, "%ld", (long)time(NULL));
2296 msg->cm_fields['T'] = strdup(generated_timestamp);
2299 /* If this message has no path, we generate one.
2301 if (msg->cm_fields['P'] == NULL) {
2302 if (msg->cm_fields['A'] != NULL) {
2303 msg->cm_fields['P'] = strdup(msg->cm_fields['A']);
2304 for (a=0; a<strlen(msg->cm_fields['P']); ++a) {
2305 if (isspace(msg->cm_fields['P'][a])) {
2306 msg->cm_fields['P'][a] = ' ';
2311 msg->cm_fields['P'] = strdup("unknown");
2315 if (force == NULL) {
2316 strcpy(force_room, "");
2319 strcpy(force_room, force);
2322 /* Learn about what's inside, because it's what's inside that counts */
2323 if (msg->cm_fields['M'] == NULL) {
2324 lprintf(CTDL_ERR, "ERROR: attempt to save message with NULL body\n");
2328 switch (msg->cm_format_type) {
2330 strcpy(content_type, "text/x-citadel-variformat");
2333 strcpy(content_type, "text/plain");
2336 strcpy(content_type, "text/plain");
2337 mptr = bmstrcasestr(msg->cm_fields['M'], "Content-type: ");
2339 safestrncpy(content_type, &mptr[14],
2340 sizeof content_type);
2341 for (a = 0; a < strlen(content_type); ++a) {
2342 if ((content_type[a] == ';')
2343 || (content_type[a] == ' ')
2344 || (content_type[a] == 13)
2345 || (content_type[a] == 10)) {
2346 content_type[a] = 0;
2352 /* Goto the correct room */
2353 lprintf(CTDL_DEBUG, "Selected room %s\n", (recps) ? CC->room.QRname : SENTITEMS);
2354 strcpy(hold_rm, CC->room.QRname);
2355 strcpy(actual_rm, CC->room.QRname);
2356 if (recps != NULL) {
2357 strcpy(actual_rm, SENTITEMS);
2360 /* If the user is a twit, move to the twit room for posting */
2362 if (CC->user.axlevel == 2) {
2363 strcpy(hold_rm, actual_rm);
2364 strcpy(actual_rm, config.c_twitroom);
2365 lprintf(CTDL_DEBUG, "Diverting to twit room\n");
2369 /* ...or if this message is destined for Aide> then go there. */
2370 if (strlen(force_room) > 0) {
2371 strcpy(actual_rm, force_room);
2374 lprintf(CTDL_DEBUG, "Final selection: %s\n", actual_rm);
2375 if (strcasecmp(actual_rm, CC->room.QRname)) {
2376 /* getroom(&CC->room, actual_rm); */
2377 usergoto(actual_rm, 0, 1, NULL, NULL);
2381 * If this message has no O (room) field, generate one.
2383 if (msg->cm_fields['O'] == NULL) {
2384 msg->cm_fields['O'] = strdup(CC->room.QRname);
2387 /* Perform "before save" hooks (aborting if any return nonzero) */
2388 lprintf(CTDL_DEBUG, "Performing before-save hooks\n");
2389 if (PerformMessageHooks(msg, EVT_BEFORESAVE) > 0) return(-3);
2392 * If this message has an Exclusive ID, and the room is replication
2393 * checking enabled, then do replication checks.
2395 if (DoesThisRoomNeedEuidIndexing(&CC->room)) {
2396 ReplicationChecks(msg);
2399 /* Save it to disk */
2400 lprintf(CTDL_DEBUG, "Saving to disk\n");
2401 newmsgid = send_message(msg);
2402 if (newmsgid <= 0L) return(-5);
2404 /* Write a supplemental message info record. This doesn't have to
2405 * be a critical section because nobody else knows about this message
2408 lprintf(CTDL_DEBUG, "Creating MetaData record\n");
2409 memset(&smi, 0, sizeof(struct MetaData));
2410 smi.meta_msgnum = newmsgid;
2411 smi.meta_refcount = 0;
2412 safestrncpy(smi.meta_content_type, content_type,
2413 sizeof smi.meta_content_type);
2416 * Measure how big this message will be when rendered as RFC822.
2417 * We do this for two reasons:
2418 * 1. We need the RFC822 length for the new metadata record, so the
2419 * POP and IMAP services don't have to calculate message lengths
2420 * while the user is waiting (multiplied by potentially hundreds
2421 * or thousands of messages).
2422 * 2. If journaling is enabled, we will need an RFC822 version of the
2423 * message to attach to the journalized copy.
2425 if (CC->redirect_buffer != NULL) {
2426 lprintf(CTDL_ALERT, "CC->redirect_buffer is not NULL during message submission!\n");
2429 CC->redirect_buffer = malloc(SIZ);
2430 CC->redirect_len = 0;
2431 CC->redirect_alloc = SIZ;
2432 CtdlOutputPreLoadedMsg(msg, MT_RFC822, HEADERS_ALL, 0, 1);
2433 smi.meta_rfc822_length = CC->redirect_len;
2434 saved_rfc822_version = CC->redirect_buffer;
2435 CC->redirect_buffer = NULL;
2436 CC->redirect_len = 0;
2437 CC->redirect_alloc = 0;
2441 /* Now figure out where to store the pointers */
2442 lprintf(CTDL_DEBUG, "Storing pointers\n");
2444 /* If this is being done by the networker delivering a private
2445 * message, we want to BYPASS saving the sender's copy (because there
2446 * is no local sender; it would otherwise go to the Trashcan).
2448 if ((!CC->internal_pgm) || (recps == NULL)) {
2449 if (CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 1, msg) != 0) {
2450 lprintf(CTDL_ERR, "ERROR saving message pointer!\n");
2451 CtdlSaveMsgPointerInRoom(config.c_aideroom, newmsgid, 0, msg);
2455 /* For internet mail, drop a copy in the outbound queue room */
2457 if (recps->num_internet > 0) {
2458 CtdlSaveMsgPointerInRoom(SMTP_SPOOLOUT_ROOM, newmsgid, 0, msg);
2461 /* If other rooms are specified, drop them there too. */
2463 if (recps->num_room > 0)
2464 for (i=0; i<num_tokens(recps->recp_room, '|'); ++i) {
2465 extract_token(recipient, recps->recp_room, i,
2466 '|', sizeof recipient);
2467 lprintf(CTDL_DEBUG, "Delivering to room <%s>\n", recipient);
2468 CtdlSaveMsgPointerInRoom(recipient, newmsgid, 0, msg);
2471 /* Bump this user's messages posted counter. */
2472 lprintf(CTDL_DEBUG, "Updating user\n");
2473 lgetuser(&CC->user, CC->curr_user);
2474 CC->user.posted = CC->user.posted + 1;
2475 lputuser(&CC->user);
2477 /* If this is private, local mail, make a copy in the
2478 * recipient's mailbox and bump the reference count.
2481 if (recps->num_local > 0)
2482 for (i=0; i<num_tokens(recps->recp_local, '|'); ++i) {
2483 extract_token(recipient, recps->recp_local, i,
2484 '|', sizeof recipient);
2485 lprintf(CTDL_DEBUG, "Delivering private local mail to <%s>\n",
2487 if (getuser(&userbuf, recipient) == 0) {
2488 MailboxName(actual_rm, sizeof actual_rm,
2489 &userbuf, MAILROOM);
2490 CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 0, msg);
2491 BumpNewMailCounter(userbuf.usernum);
2494 lprintf(CTDL_DEBUG, "No user <%s>\n", recipient);
2495 CtdlSaveMsgPointerInRoom(config.c_aideroom,
2500 /* Perform "after save" hooks */
2501 lprintf(CTDL_DEBUG, "Performing after-save hooks\n");
2502 PerformMessageHooks(msg, EVT_AFTERSAVE);
2504 /* For IGnet mail, we have to save a new copy into the spooler for
2505 * each recipient, with the R and D fields set to the recipient and
2506 * destination-node. This has two ugly side effects: all other
2507 * recipients end up being unlisted in this recipient's copy of the
2508 * message, and it has to deliver multiple messages to the same
2509 * node. We'll revisit this again in a year or so when everyone has
2510 * a network spool receiver that can handle the new style messages.
2513 if (recps->num_ignet > 0)
2514 for (i=0; i<num_tokens(recps->recp_ignet, '|'); ++i) {
2515 extract_token(recipient, recps->recp_ignet, i,
2516 '|', sizeof recipient);
2518 hold_R = msg->cm_fields['R'];
2519 hold_D = msg->cm_fields['D'];
2520 msg->cm_fields['R'] = malloc(SIZ);
2521 msg->cm_fields['D'] = malloc(128);
2522 extract_token(msg->cm_fields['R'], recipient, 0, '@', SIZ);
2523 extract_token(msg->cm_fields['D'], recipient, 1, '@', 128);
2525 serialize_message(&smr, msg);
2527 snprintf(submit_filename, sizeof submit_filename,
2528 "%s/netmail.%04lx.%04x.%04x",
2530 (long) getpid(), CC->cs_pid, ++seqnum);
2531 network_fp = fopen(submit_filename, "wb+");
2532 if (network_fp != NULL) {
2533 fwrite(smr.ser, smr.len, 1, network_fp);
2539 free(msg->cm_fields['R']);
2540 free(msg->cm_fields['D']);
2541 msg->cm_fields['R'] = hold_R;
2542 msg->cm_fields['D'] = hold_D;
2545 /* Go back to the room we started from */
2546 lprintf(CTDL_DEBUG, "Returning to original room %s\n", hold_rm);
2547 if (strcasecmp(hold_rm, CC->room.QRname))
2548 /* getroom(&CC->room, hold_rm); */
2549 usergoto(hold_rm, 0, 1, NULL, NULL);
2551 /* For internet mail, generate delivery instructions.
2552 * Yes, this is recursive. Deal with it. Infinite recursion does
2553 * not happen because the delivery instructions message does not
2554 * contain a recipient.
2557 if (recps->num_internet > 0) {
2558 lprintf(CTDL_DEBUG, "Generating delivery instructions\n");
2559 instr = malloc(SIZ * 2);
2560 snprintf(instr, SIZ * 2,
2561 "Content-type: %s\n\nmsgid|%ld\nsubmitted|%ld\n"
2563 SPOOLMIME, newmsgid, (long)time(NULL),
2564 msg->cm_fields['A'], msg->cm_fields['N']
2567 for (i=0; i<num_tokens(recps->recp_internet, '|'); ++i) {
2568 size_t tmp = strlen(instr);
2569 extract_token(recipient, recps->recp_internet,
2570 i, '|', sizeof recipient);
2571 snprintf(&instr[tmp], SIZ * 2 - tmp,
2572 "remote|%s|0||\n", recipient);
2575 imsg = malloc(sizeof(struct CtdlMessage));
2576 memset(imsg, 0, sizeof(struct CtdlMessage));
2577 imsg->cm_magic = CTDLMESSAGE_MAGIC;
2578 imsg->cm_anon_type = MES_NORMAL;
2579 imsg->cm_format_type = FMT_RFC822;
2580 imsg->cm_fields['A'] = strdup("Citadel");
2581 imsg->cm_fields['J'] = strdup("do not journal");
2582 imsg->cm_fields['M'] = instr;
2583 CtdlSubmitMsg(imsg, NULL, SMTP_SPOOLOUT_ROOM);
2584 CtdlFreeMessage(imsg);
2588 * Any addresses to harvest for someone's address book?
2590 if ( (CC->logged_in) && (recps != NULL) ) {
2591 collected_addresses = harvest_collected_addresses(msg);
2594 if (collected_addresses != NULL) {
2595 begin_critical_section(S_ATBF);
2596 aptr = (struct addresses_to_be_filed *)
2597 malloc(sizeof(struct addresses_to_be_filed));
2599 MailboxName(actual_rm, sizeof actual_rm,
2600 &CC->user, USERCONTACTSROOM);
2601 aptr->roomname = strdup(actual_rm);
2602 aptr->collected_addresses = collected_addresses;
2604 end_critical_section(S_ATBF);
2608 * Determine whether this message qualifies for journaling.
2610 if (msg->cm_fields['J'] != NULL) {
2611 qualified_for_journaling = 0;
2614 if (recps == NULL) {
2615 qualified_for_journaling = config.c_journal_pubmsgs;
2617 else if (recps->num_local + recps->num_ignet + recps->num_internet > 0) {
2618 qualified_for_journaling = config.c_journal_email;
2621 qualified_for_journaling = config.c_journal_pubmsgs;
2626 * Do we have to perform journaling? If so, hand off the saved
2627 * RFC822 version will be handed off to the journaler for background
2628 * submit. Otherwise, we have to free the memory ourselves.
2630 if (saved_rfc822_version != NULL) {
2631 if (qualified_for_journaling) {
2632 JournalBackgroundSubmit(msg, saved_rfc822_version, recps);
2635 free(saved_rfc822_version);
2648 * Convenience function for generating small administrative messages.
2650 void quickie_message(char *from, char *to, char *room, char *text,
2651 int format_type, char *subject)
2653 struct CtdlMessage *msg;
2654 struct recptypes *recp = NULL;
2656 msg = malloc(sizeof(struct CtdlMessage));
2657 memset(msg, 0, sizeof(struct CtdlMessage));
2658 msg->cm_magic = CTDLMESSAGE_MAGIC;
2659 msg->cm_anon_type = MES_NORMAL;
2660 msg->cm_format_type = format_type;
2661 msg->cm_fields['A'] = strdup(from);
2662 if (room != NULL) msg->cm_fields['O'] = strdup(room);
2663 msg->cm_fields['N'] = strdup(NODENAME);
2665 msg->cm_fields['R'] = strdup(to);
2666 recp = validate_recipients(to);
2668 if (subject != NULL) {
2669 msg->cm_fields['U'] = strdup(subject);
2671 msg->cm_fields['M'] = strdup(text);
2673 CtdlSubmitMsg(msg, recp, room);
2674 CtdlFreeMessage(msg);
2675 if (recp != NULL) free(recp);
2681 * Back end function used by CtdlMakeMessage() and similar functions
2683 char *CtdlReadMessageBody(char *terminator, /* token signalling EOT */
2684 size_t maxlen, /* maximum message length */
2685 char *exist, /* if non-null, append to it;
2686 exist is ALWAYS freed */
2687 int crlf /* CRLF newlines instead of LF */
2691 size_t message_len = 0;
2692 size_t buffer_len = 0;
2699 if (exist == NULL) {
2706 message_len = strlen(exist);
2707 buffer_len = message_len + 4096;
2708 m = realloc(exist, buffer_len);
2715 /* Do we need to change leading ".." to "." for SMTP escaping? */
2716 if (!strcmp(terminator, ".")) {
2720 /* flush the input if we have nowhere to store it */
2725 /* read in the lines of message text one by one */
2727 if (client_getln(buf, (sizeof buf - 3)) < 1) finished = 1;
2728 if (!strcmp(buf, terminator)) finished = 1;
2730 strcat(buf, "\r\n");
2736 /* Unescape SMTP-style input of two dots at the beginning of the line */
2738 if (!strncmp(buf, "..", 2)) {
2739 strcpy(buf, &buf[1]);
2743 if ( (!flushing) && (!finished) ) {
2744 /* Measure the line */
2745 linelen = strlen(buf);
2747 /* augment the buffer if we have to */
2748 if ((message_len + linelen) >= buffer_len) {
2749 ptr = realloc(m, (buffer_len * 2) );
2750 if (ptr == NULL) { /* flush if can't allocate */
2753 buffer_len = (buffer_len * 2);
2755 lprintf(CTDL_DEBUG, "buffer_len is now %ld\n", (long)buffer_len);
2759 /* Add the new line to the buffer. NOTE: this loop must avoid
2760 * using functions like strcat() and strlen() because they
2761 * traverse the entire buffer upon every call, and doing that
2762 * for a multi-megabyte message slows it down beyond usability.
2764 strcpy(&m[message_len], buf);
2765 message_len += linelen;
2768 /* if we've hit the max msg length, flush the rest */
2769 if (message_len >= maxlen) flushing = 1;
2771 } while (!finished);
2779 * Build a binary message to be saved on disk.
2780 * (NOTE: if you supply 'preformatted_text', the buffer you give it
2781 * will become part of the message. This means you are no longer
2782 * responsible for managing that memory -- it will be freed along with
2783 * the rest of the fields when CtdlFreeMessage() is called.)
2786 struct CtdlMessage *CtdlMakeMessage(
2787 struct ctdluser *author, /* author's user structure */
2788 char *recipient, /* NULL if it's not mail */
2789 char *recp_cc, /* NULL if it's not mail */
2790 char *room, /* room where it's going */
2791 int type, /* see MES_ types in header file */
2792 int format_type, /* variformat, plain text, MIME... */
2793 char *fake_name, /* who we're masquerading as */
2794 char *subject, /* Subject (optional) */
2795 char *supplied_euid, /* ...or NULL if this is irrelevant */
2796 char *preformatted_text /* ...or NULL to read text from client */
2798 char dest_node[SIZ];
2800 struct CtdlMessage *msg;
2802 msg = malloc(sizeof(struct CtdlMessage));
2803 memset(msg, 0, sizeof(struct CtdlMessage));
2804 msg->cm_magic = CTDLMESSAGE_MAGIC;
2805 msg->cm_anon_type = type;
2806 msg->cm_format_type = format_type;
2808 /* Don't confuse the poor folks if it's not routed mail. */
2809 strcpy(dest_node, "");
2814 snprintf(buf, sizeof buf, "cit%ld", author->usernum); /* Path */
2815 msg->cm_fields['P'] = strdup(buf);
2817 snprintf(buf, sizeof buf, "%ld", (long)time(NULL)); /* timestamp */
2818 msg->cm_fields['T'] = strdup(buf);
2820 if (fake_name[0]) /* author */
2821 msg->cm_fields['A'] = strdup(fake_name);
2823 msg->cm_fields['A'] = strdup(author->fullname);
2825 if (CC->room.QRflags & QR_MAILBOX) { /* room */
2826 msg->cm_fields['O'] = strdup(&CC->room.QRname[11]);
2829 msg->cm_fields['O'] = strdup(CC->room.QRname);
2832 msg->cm_fields['N'] = strdup(NODENAME); /* nodename */
2833 msg->cm_fields['H'] = strdup(HUMANNODE); /* hnodename */
2835 if (recipient[0] != 0) {
2836 msg->cm_fields['R'] = strdup(recipient);
2838 if (recp_cc[0] != 0) {
2839 msg->cm_fields['Y'] = strdup(recp_cc);
2841 if (dest_node[0] != 0) {
2842 msg->cm_fields['D'] = strdup(dest_node);
2845 if ( (author == &CC->user) && (strlen(CC->cs_inet_email) > 0) ) {
2846 msg->cm_fields['F'] = strdup(CC->cs_inet_email);
2849 if (subject != NULL) {
2851 if (strlen(subject) > 0) {
2852 msg->cm_fields['U'] = strdup(subject);
2856 if (supplied_euid != NULL) {
2857 msg->cm_fields['E'] = strdup(supplied_euid);
2860 if (preformatted_text != NULL) {
2861 msg->cm_fields['M'] = preformatted_text;
2864 msg->cm_fields['M'] = CtdlReadMessageBody("000",
2865 config.c_maxmsglen, NULL, 0);
2873 * Check to see whether we have permission to post a message in the current
2874 * room. Returns a *CITADEL ERROR CODE* and puts a message in errmsgbuf, or
2875 * returns 0 on success.
2877 int CtdlDoIHavePermissionToPostInThisRoom(char *errmsgbuf, size_t n) {
2879 if (!(CC->logged_in)) {
2880 snprintf(errmsgbuf, n, "Not logged in.");
2881 return (ERROR + NOT_LOGGED_IN);
2884 if ((CC->user.axlevel < 2)
2885 && ((CC->room.QRflags & QR_MAILBOX) == 0)) {
2886 snprintf(errmsgbuf, n, "Need to be validated to enter "
2887 "(except in %s> to sysop)", MAILROOM);
2888 return (ERROR + HIGHER_ACCESS_REQUIRED);
2891 if ((CC->user.axlevel < 4)
2892 && (CC->room.QRflags & QR_NETWORK)) {
2893 snprintf(errmsgbuf, n, "Need net privileges to enter here.");
2894 return (ERROR + HIGHER_ACCESS_REQUIRED);
2897 if ((CC->user.axlevel < 6)
2898 && (CC->room.QRflags & QR_READONLY)) {
2899 snprintf(errmsgbuf, n, "Sorry, this is a read-only room.");
2900 return (ERROR + HIGHER_ACCESS_REQUIRED);
2903 strcpy(errmsgbuf, "Ok");
2909 * Check to see if the specified user has Internet mail permission
2910 * (returns nonzero if permission is granted)
2912 int CtdlCheckInternetMailPermission(struct ctdluser *who) {
2914 /* Do not allow twits to send Internet mail */
2915 if (who->axlevel <= 2) return(0);
2917 /* Globally enabled? */
2918 if (config.c_restrict == 0) return(1);
2920 /* User flagged ok? */
2921 if (who->flags & US_INTERNET) return(2);
2923 /* Aide level access? */
2924 if (who->axlevel >= 6) return(3);
2926 /* No mail for you! */
2932 * Validate recipients, count delivery types and errors, and handle aliasing
2933 * FIXME check for dupes!!!!!
2934 * Returns 0 if all addresses are ok, -1 if no addresses were specified,
2935 * or the number of addresses found invalid.
2937 struct recptypes *validate_recipients(char *supplied_recipients) {
2938 struct recptypes *ret;
2939 char recipients[SIZ];
2940 char this_recp[256];
2941 char this_recp_cooked[256];
2947 struct ctdluser tempUS;
2948 struct ctdlroom tempQR;
2952 ret = (struct recptypes *) malloc(sizeof(struct recptypes));
2953 if (ret == NULL) return(NULL);
2954 memset(ret, 0, sizeof(struct recptypes));
2957 ret->num_internet = 0;
2962 if (supplied_recipients == NULL) {
2963 strcpy(recipients, "");
2966 safestrncpy(recipients, supplied_recipients, sizeof recipients);
2969 /* Change all valid separator characters to commas */
2970 for (i=0; i<strlen(recipients); ++i) {
2971 if ((recipients[i] == ';') || (recipients[i] == '|')) {
2972 recipients[i] = ',';
2976 /* Now start extracting recipients... */
2978 while (strlen(recipients) > 0) {
2980 for (i=0; i<=strlen(recipients); ++i) {
2981 if (recipients[i] == '\"') in_quotes = 1 - in_quotes;
2982 if ( ( (recipients[i] == ',') && (!in_quotes) ) || (recipients[i] == 0) ) {
2983 safestrncpy(this_recp, recipients, i+1);
2985 if (recipients[i] == ',') {
2986 strcpy(recipients, &recipients[i+1]);
2989 strcpy(recipients, "");
2996 lprintf(CTDL_DEBUG, "Evaluating recipient #%d: %s\n", num_recps, this_recp);
2998 mailtype = alias(this_recp);
2999 mailtype = alias(this_recp);
3000 mailtype = alias(this_recp);
3001 for (j=0; j<=strlen(this_recp); ++j) {
3002 if (this_recp[j]=='_') {
3003 this_recp_cooked[j] = ' ';
3006 this_recp_cooked[j] = this_recp[j];
3012 if (!strcasecmp(this_recp, "sysop")) {
3014 strcpy(this_recp, config.c_aideroom);
3015 if (strlen(ret->recp_room) > 0) {
3016 strcat(ret->recp_room, "|");
3018 strcat(ret->recp_room, this_recp);
3020 else if (getuser(&tempUS, this_recp) == 0) {
3022 strcpy(this_recp, tempUS.fullname);
3023 if (strlen(ret->recp_local) > 0) {
3024 strcat(ret->recp_local, "|");
3026 strcat(ret->recp_local, this_recp);
3028 else if (getuser(&tempUS, this_recp_cooked) == 0) {
3030 strcpy(this_recp, tempUS.fullname);
3031 if (strlen(ret->recp_local) > 0) {
3032 strcat(ret->recp_local, "|");
3034 strcat(ret->recp_local, this_recp);
3036 else if ( (!strncasecmp(this_recp, "room_", 5))
3037 && (!getroom(&tempQR, &this_recp_cooked[5])) ) {
3039 if (strlen(ret->recp_room) > 0) {
3040 strcat(ret->recp_room, "|");
3042 strcat(ret->recp_room, &this_recp_cooked[5]);
3050 /* Yes, you're reading this correctly: if the target
3051 * domain points back to the local system or an attached
3052 * Citadel directory, the address is invalid. That's
3053 * because if the address were valid, we would have
3054 * already translated it to a local address by now.
3056 if (IsDirectory(this_recp)) {
3061 ++ret->num_internet;
3062 if (strlen(ret->recp_internet) > 0) {
3063 strcat(ret->recp_internet, "|");
3065 strcat(ret->recp_internet, this_recp);
3070 if (strlen(ret->recp_ignet) > 0) {
3071 strcat(ret->recp_ignet, "|");
3073 strcat(ret->recp_ignet, this_recp);
3081 if (strlen(ret->errormsg) == 0) {
3082 snprintf(append, sizeof append,
3083 "Invalid recipient: %s",
3087 snprintf(append, sizeof append,
3090 if ( (strlen(ret->errormsg) + strlen(append)) < SIZ) {
3091 strcat(ret->errormsg, append);
3095 if (strlen(ret->display_recp) == 0) {
3096 strcpy(append, this_recp);
3099 snprintf(append, sizeof append, ", %s",
3102 if ( (strlen(ret->display_recp)+strlen(append)) < SIZ) {
3103 strcat(ret->display_recp, append);
3108 if ((ret->num_local + ret->num_internet + ret->num_ignet +
3109 ret->num_room + ret->num_error) == 0) {
3110 ret->num_error = (-1);
3111 strcpy(ret->errormsg, "No recipients specified.");
3114 lprintf(CTDL_DEBUG, "validate_recipients()\n");
3115 lprintf(CTDL_DEBUG, " local: %d <%s>\n", ret->num_local, ret->recp_local);
3116 lprintf(CTDL_DEBUG, " room: %d <%s>\n", ret->num_room, ret->recp_room);
3117 lprintf(CTDL_DEBUG, " inet: %d <%s>\n", ret->num_internet, ret->recp_internet);
3118 lprintf(CTDL_DEBUG, " ignet: %d <%s>\n", ret->num_ignet, ret->recp_ignet);
3119 lprintf(CTDL_DEBUG, " error: %d <%s>\n", ret->num_error, ret->errormsg);
3127 * message entry - mode 0 (normal)
3129 void cmd_ent0(char *entargs)
3135 char supplied_euid[128];
3136 char masquerade_as[SIZ];
3138 int format_type = 0;
3139 char newusername[SIZ];
3140 struct CtdlMessage *msg;
3144 struct recptypes *valid = NULL;
3145 struct recptypes *valid_to = NULL;
3146 struct recptypes *valid_cc = NULL;
3147 struct recptypes *valid_bcc = NULL;
3154 post = extract_int(entargs, 0);
3155 extract_token(recp, entargs, 1, '|', sizeof recp);
3156 anon_flag = extract_int(entargs, 2);
3157 format_type = extract_int(entargs, 3);
3158 extract_token(subject, entargs, 4, '|', sizeof subject);
3159 do_confirm = extract_int(entargs, 6);
3160 extract_token(cc, entargs, 7, '|', sizeof cc);
3161 extract_token(bcc, entargs, 8, '|', sizeof bcc);
3162 switch(CC->room.QRdefaultview) {
3165 extract_token(supplied_euid, entargs, 9, '|', sizeof supplied_euid);
3168 supplied_euid[0] = 0;
3172 /* first check to make sure the request is valid. */
3174 err = CtdlDoIHavePermissionToPostInThisRoom(errmsg, sizeof errmsg);
3176 cprintf("%d %s\n", err, errmsg);
3180 /* Check some other permission type things. */
3183 if (CC->user.axlevel < 6) {
3184 cprintf("%d You don't have permission to masquerade.\n",
3185 ERROR + HIGHER_ACCESS_REQUIRED);
3188 extract_token(newusername, entargs, 5, '|', sizeof newusername);
3189 memset(CC->fake_postname, 0, sizeof(CC->fake_postname) );
3190 safestrncpy(CC->fake_postname, newusername,
3191 sizeof(CC->fake_postname) );
3192 cprintf("%d ok\n", CIT_OK);
3195 CC->cs_flags |= CS_POSTING;
3197 /* In the Mail> room we have to behave a little differently --
3198 * make sure the user has specified at least one recipient. Then
3199 * validate the recipient(s).
3201 if ( (CC->room.QRflags & QR_MAILBOX)
3202 && (!strcasecmp(&CC->room.QRname[11], MAILROOM)) ) {
3204 if (CC->user.axlevel < 2) {
3205 strcpy(recp, "sysop");
3210 valid_to = validate_recipients(recp);
3211 if (valid_to->num_error > 0) {
3212 cprintf("%d Invalid recipient (To)\n", ERROR + NO_SUCH_USER);
3217 valid_cc = validate_recipients(cc);
3218 if (valid_cc->num_error > 0) {
3219 cprintf("%d Invalid recipient (CC)\n", ERROR + NO_SUCH_USER);
3225 valid_bcc = validate_recipients(bcc);
3226 if (valid_bcc->num_error > 0) {
3227 cprintf("%d Invalid recipient (BCC)\n", ERROR + NO_SUCH_USER);
3234 /* Recipient required, but none were specified */
3235 if ( (valid_to->num_error < 0) && (valid_cc->num_error < 0) && (valid_bcc->num_error < 0) ) {
3239 cprintf("%d At least one recipient is required.\n", ERROR + NO_SUCH_USER);
3243 if (valid_to->num_internet + valid_cc->num_internet + valid_bcc->num_internet > 0) {
3244 if (CtdlCheckInternetMailPermission(&CC->user)==0) {
3245 cprintf("%d You do not have permission "
3246 "to send Internet mail.\n",
3247 ERROR + HIGHER_ACCESS_REQUIRED);
3255 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)
3256 && (CC->user.axlevel < 4) ) {
3257 cprintf("%d Higher access required for network mail.\n",
3258 ERROR + HIGHER_ACCESS_REQUIRED);
3265 if ((RESTRICT_INTERNET == 1)
3266 && (valid_to->num_internet + valid_cc->num_internet + valid_bcc->num_internet > 0)
3267 && ((CC->user.flags & US_INTERNET) == 0)
3268 && (!CC->internal_pgm)) {
3269 cprintf("%d You don't have access to Internet mail.\n",
3270 ERROR + HIGHER_ACCESS_REQUIRED);
3279 /* Is this a room which has anonymous-only or anonymous-option? */
3280 anonymous = MES_NORMAL;
3281 if (CC->room.QRflags & QR_ANONONLY) {
3282 anonymous = MES_ANONONLY;
3284 if (CC->room.QRflags & QR_ANONOPT) {
3285 if (anon_flag == 1) { /* only if the user requested it */
3286 anonymous = MES_ANONOPT;
3290 if ((CC->room.QRflags & QR_MAILBOX) == 0) {
3294 /* If we're only checking the validity of the request, return
3295 * success without creating the message.
3298 cprintf("%d %s\n", CIT_OK,
3299 ((valid_to != NULL) ? valid_to->display_recp : "") );
3306 /* We don't need these anymore because we'll do it differently below */
3311 /* Handle author masquerading */
3312 if (CC->fake_postname[0]) {
3313 strcpy(masquerade_as, CC->fake_postname);
3315 else if (CC->fake_username[0]) {
3316 strcpy(masquerade_as, CC->fake_username);
3319 strcpy(masquerade_as, "");
3322 /* Read in the message from the client. */
3324 cprintf("%d send message\n", START_CHAT_MODE);
3326 cprintf("%d send message\n", SEND_LISTING);
3329 msg = CtdlMakeMessage(&CC->user, recp, cc,
3330 CC->room.QRname, anonymous, format_type,
3331 masquerade_as, subject,
3332 ((strlen(supplied_euid) > 0) ? supplied_euid : NULL),
3335 /* Put together one big recipients struct containing to/cc/bcc all in
3336 * one. This is for the envelope.
3338 char *all_recps = malloc(SIZ * 3);
3339 strcpy(all_recps, recp);
3340 if (strlen(cc) > 0) {
3341 if (strlen(all_recps) > 0) {
3342 strcat(all_recps, ",");
3344 strcat(all_recps, cc);
3346 if (strlen(bcc) > 0) {
3347 if (strlen(all_recps) > 0) {
3348 strcat(all_recps, ",");
3350 strcat(all_recps, bcc);
3352 if (strlen(all_recps) > 0) {
3353 valid = validate_recipients(all_recps);
3361 msgnum = CtdlSubmitMsg(msg, valid, "");
3364 cprintf("%ld\n", msgnum);
3366 cprintf("Message accepted.\n");
3369 cprintf("Internal error.\n");
3371 if (msg->cm_fields['E'] != NULL) {
3372 cprintf("%s\n", msg->cm_fields['E']);
3379 CtdlFreeMessage(msg);
3381 CC->fake_postname[0] = '\0';
3382 if (valid != NULL) {
3391 * API function to delete messages which match a set of criteria
3392 * (returns the actual number of messages deleted)
3394 int CtdlDeleteMessages(char *room_name, /* which room */
3395 long *dmsgnums, /* array of msg numbers to be deleted */
3396 int num_dmsgnums, /* number of msgs to be deleted, or 0 for "any" */
3397 char *content_type, /* or "" for any */
3398 int deferred /* let TDAP sweep it later */
3402 struct ctdlroom qrbuf;
3403 struct cdbdata *cdbfr;
3404 long *msglist = NULL;
3405 long *dellist = NULL;
3408 int num_deleted = 0;
3410 struct MetaData smi;
3412 lprintf(CTDL_DEBUG, "CtdlDeleteMessages(%s, %d msgs, %s, %d)\n",
3413 room_name, num_dmsgnums, content_type, deferred);
3415 /* get room record, obtaining a lock... */
3416 if (lgetroom(&qrbuf, room_name) != 0) {
3417 lprintf(CTDL_ERR, "CtdlDeleteMessages(): Room <%s> not found\n",
3419 return (0); /* room not found */
3421 cdbfr = cdb_fetch(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long));
3423 if (cdbfr != NULL) {
3424 dellist = malloc(cdbfr->len);
3425 msglist = (long *) cdbfr->ptr;
3426 cdbfr->ptr = NULL; /* CtdlDeleteMessages() now owns this memory */
3427 num_msgs = cdbfr->len / sizeof(long);
3431 for (i = 0; i < num_msgs; ++i) {
3434 /* Set/clear a bit for each criterion */
3436 /* 0 messages in the list or a null list means that we are
3437 * interested in deleting any messages which meet the other criteria.
3439 if ((num_dmsgnums == 0) || (dmsgnums == NULL)) {
3440 delete_this |= 0x01;
3443 for (j=0; j<num_dmsgnums; ++j) {
3444 if (msglist[i] == dmsgnums[j]) {
3445 delete_this |= 0x01;
3450 if (strlen(content_type) == 0) {
3451 delete_this |= 0x02;
3453 GetMetaData(&smi, msglist[i]);
3454 if (!strcasecmp(smi.meta_content_type,
3456 delete_this |= 0x02;
3460 /* Delete message only if all bits are set */
3461 if (delete_this == 0x03) {
3462 dellist[num_deleted++] = msglist[i];
3467 num_msgs = sort_msglist(msglist, num_msgs);
3468 cdb_store(CDB_MSGLISTS, &qrbuf.QRnumber, (int)sizeof(long),
3469 msglist, (int)(num_msgs * sizeof(long)));
3471 qrbuf.QRhighest = msglist[num_msgs - 1];
3476 * If the delete operation is "deferred" (and technically, any delete
3477 * operation not performed by THE DREADED AUTO-PURGER ought to be
3478 * a deferred delete) then we save a pointer to the message in the
3479 * DELETED_MSGS_ROOM. This will cause the reference count to remain
3480 * at least 1, which will save the user from having to synchronously
3481 * wait for various disk-intensive operations to complete.
3483 * Slick -- we now use the new bulk API for moving messages.
3485 if ( (deferred) && (num_deleted) ) {
3486 CtdlCopyMsgsToRoom(dellist, num_deleted, DELETED_MSGS_ROOM);
3489 /* Go through the messages we pulled out of the index, and decrement
3490 * their reference counts by 1. If this is the only room the message
3491 * was in, the reference count will reach zero and the message will
3492 * automatically be deleted from the database. We do this in a
3493 * separate pass because there might be plug-in hooks getting called,
3494 * and we don't want that happening during an S_ROOMS critical
3497 if (num_deleted) for (i=0; i<num_deleted; ++i) {
3498 PerformDeleteHooks(qrbuf.QRname, dellist[i]);
3499 AdjRefCount(dellist[i], -1);
3502 /* Now free the memory we used, and go away. */
3503 if (msglist != NULL) free(msglist);
3504 if (dellist != NULL) free(dellist);
3505 lprintf(CTDL_DEBUG, "%d message(s) deleted.\n", num_deleted);
3506 return (num_deleted);
3512 * Check whether the current user has permission to delete messages from
3513 * the current room (returns 1 for yes, 0 for no)
3515 int CtdlDoIHavePermissionToDeleteMessagesFromThisRoom(void) {
3516 getuser(&CC->user, CC->curr_user);
3517 if ((CC->user.axlevel < 6)
3518 && (CC->user.usernum != CC->room.QRroomaide)
3519 && ((CC->room.QRflags & QR_MAILBOX) == 0)
3520 && (!(CC->internal_pgm))) {
3529 * Delete message from current room
3531 void cmd_dele(char *args)
3540 extract_token(msgset, args, 0, '|', sizeof msgset);
3541 num_msgs = num_tokens(msgset, ',');
3543 cprintf("%d Nothing to do.\n", CIT_OK);
3547 if (CtdlDoIHavePermissionToDeleteMessagesFromThisRoom() == 0) {
3548 cprintf("%d Higher access required.\n",
3549 ERROR + HIGHER_ACCESS_REQUIRED);
3554 * Build our message set to be moved/copied
3556 msgs = malloc(num_msgs * sizeof(long));
3557 for (i=0; i<num_msgs; ++i) {
3558 extract_token(msgtok, msgset, i, ',', sizeof msgtok);
3559 msgs[i] = atol(msgtok);
3562 num_deleted = CtdlDeleteMessages(CC->room.QRname, msgs, num_msgs, "", 1);
3566 cprintf("%d %d message%s deleted.\n", CIT_OK,
3567 num_deleted, ((num_deleted != 1) ? "s" : ""));
3569 cprintf("%d Message not found.\n", ERROR + MESSAGE_NOT_FOUND);
3575 * Back end API function for moves and deletes (multiple messages)
3577 int CtdlCopyMsgsToRoom(long *msgnums, int num_msgs, char *dest) {
3580 err = CtdlSaveMsgPointersInRoom(dest, msgnums, num_msgs, 1, NULL);
3581 if (err != 0) return(err);
3590 * move or copy a message to another room
3592 void cmd_move(char *args)
3599 char targ[ROOMNAMELEN];
3600 struct ctdlroom qtemp;
3607 extract_token(msgset, args, 0, '|', sizeof msgset);
3608 num_msgs = num_tokens(msgset, ',');
3610 cprintf("%d Nothing to do.\n", CIT_OK);
3614 extract_token(targ, args, 1, '|', sizeof targ);
3615 convert_room_name_macros(targ, sizeof targ);
3616 targ[ROOMNAMELEN - 1] = 0;
3617 is_copy = extract_int(args, 2);
3619 if (getroom(&qtemp, targ) != 0) {
3620 cprintf("%d '%s' does not exist.\n",
3621 ERROR + ROOM_NOT_FOUND, targ);
3625 getuser(&CC->user, CC->curr_user);
3626 CtdlRoomAccess(&qtemp, &CC->user, &ra, NULL);
3628 /* Check for permission to perform this operation.
3629 * Remember: "CC->room" is source, "qtemp" is target.
3633 /* Aides can move/copy */
3634 if (CC->user.axlevel >= 6) permit = 1;
3636 /* Room aides can move/copy */
3637 if (CC->user.usernum == CC->room.QRroomaide) permit = 1;
3639 /* Permit move/copy from personal rooms */
3640 if ((CC->room.QRflags & QR_MAILBOX)
3641 && (qtemp.QRflags & QR_MAILBOX)) permit = 1;
3643 /* Permit only copy from public to personal room */
3645 && (!(CC->room.QRflags & QR_MAILBOX))
3646 && (qtemp.QRflags & QR_MAILBOX)) permit = 1;
3648 /* User must have access to target room */
3649 if (!(ra & UA_KNOWN)) permit = 0;
3652 cprintf("%d Higher access required.\n",
3653 ERROR + HIGHER_ACCESS_REQUIRED);
3658 * Build our message set to be moved/copied
3660 msgs = malloc(num_msgs * sizeof(long));
3661 for (i=0; i<num_msgs; ++i) {
3662 extract_token(msgtok, msgset, i, ',', sizeof msgtok);
3663 msgs[i] = atol(msgtok);
3669 err = CtdlCopyMsgsToRoom(msgs, num_msgs, targ);
3671 cprintf("%d Cannot store message(s) in %s: error %d\n",
3677 /* Now delete the message from the source room,
3678 * if this is a 'move' rather than a 'copy' operation.
3681 CtdlDeleteMessages(CC->room.QRname, msgs, num_msgs, "", 0);
3685 cprintf("%d Message(s) %s.\n", CIT_OK, (is_copy ? "copied" : "moved") );
3691 * GetMetaData() - Get the supplementary record for a message
3693 void GetMetaData(struct MetaData *smibuf, long msgnum)
3696 struct cdbdata *cdbsmi;
3699 memset(smibuf, 0, sizeof(struct MetaData));
3700 smibuf->meta_msgnum = msgnum;
3701 smibuf->meta_refcount = 1; /* Default reference count is 1 */
3703 /* Use the negative of the message number for its supp record index */
3704 TheIndex = (0L - msgnum);
3706 cdbsmi = cdb_fetch(CDB_MSGMAIN, &TheIndex, sizeof(long));
3707 if (cdbsmi == NULL) {
3708 return; /* record not found; go with defaults */
3710 memcpy(smibuf, cdbsmi->ptr,
3711 ((cdbsmi->len > sizeof(struct MetaData)) ?
3712 sizeof(struct MetaData) : cdbsmi->len));
3719 * PutMetaData() - (re)write supplementary record for a message
3721 void PutMetaData(struct MetaData *smibuf)
3725 /* Use the negative of the message number for the metadata db index */
3726 TheIndex = (0L - smibuf->meta_msgnum);
3728 lprintf(CTDL_DEBUG, "PutMetaData(%ld) - ref count is %d\n",
3729 smibuf->meta_msgnum, smibuf->meta_refcount);
3731 cdb_store(CDB_MSGMAIN,
3732 &TheIndex, (int)sizeof(long),
3733 smibuf, (int)sizeof(struct MetaData));
3738 * AdjRefCount - change the reference count for a message;
3739 * delete the message if it reaches zero
3741 void AdjRefCount(long msgnum, int incr)
3744 struct MetaData smi;
3747 /* This is a *tight* critical section; please keep it that way, as
3748 * it may get called while nested in other critical sections.
3749 * Complicating this any further will surely cause deadlock!
3751 begin_critical_section(S_SUPPMSGMAIN);
3752 GetMetaData(&smi, msgnum);
3753 smi.meta_refcount += incr;
3755 end_critical_section(S_SUPPMSGMAIN);
3756 lprintf(CTDL_DEBUG, "msg %ld ref count incr %d, is now %d\n",
3757 msgnum, incr, smi.meta_refcount);
3759 /* If the reference count is now zero, delete the message
3760 * (and its supplementary record as well).
3762 if (smi.meta_refcount == 0) {
3763 lprintf(CTDL_DEBUG, "Deleting message <%ld>\n", msgnum);
3765 /* Remove from fulltext index */
3766 if (config.c_enable_fulltext) {
3767 ft_index_message(msgnum, 0);
3770 /* Remove from message base */
3772 cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long));
3773 cdb_delete(CDB_BIGMSGS, &delnum, (int)sizeof(long));
3775 /* Remove metadata record */
3776 delnum = (0L - msgnum);
3777 cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long));
3782 * Write a generic object to this room
3784 * Note: this could be much more efficient. Right now we use two temporary
3785 * files, and still pull the message into memory as with all others.
3787 void CtdlWriteObject(char *req_room, /* Room to stuff it in */
3788 char *content_type, /* MIME type of this object */
3789 char *tempfilename, /* Where to fetch it from */
3790 struct ctdluser *is_mailbox, /* Mailbox room? */
3791 int is_binary, /* Is encoding necessary? */
3792 int is_unique, /* Del others of this type? */
3793 unsigned int flags /* Internal save flags */
3798 struct ctdlroom qrbuf;
3799 char roomname[ROOMNAMELEN];
3800 struct CtdlMessage *msg;
3802 char *raw_message = NULL;
3803 char *encoded_message = NULL;
3804 off_t raw_length = 0;
3806 if (is_mailbox != NULL) {
3807 MailboxName(roomname, sizeof roomname, is_mailbox, req_room);
3810 safestrncpy(roomname, req_room, sizeof(roomname));
3813 fp = fopen(tempfilename, "rb");
3815 lprintf(CTDL_CRIT, "Cannot open %s: %s\n",
3816 tempfilename, strerror(errno));
3819 fseek(fp, 0L, SEEK_END);
3820 raw_length = ftell(fp);
3822 lprintf(CTDL_DEBUG, "Raw length is %ld\n", (long)raw_length);
3824 raw_message = malloc((size_t)raw_length + 2);
3825 fread(raw_message, (size_t)raw_length, 1, fp);
3829 encoded_message = malloc((size_t)
3830 (((raw_length * 134) / 100) + 4096 ) );
3833 encoded_message = malloc((size_t)(raw_length + 4096));
3836 sprintf(encoded_message, "Content-type: %s\n", content_type);
3839 sprintf(&encoded_message[strlen(encoded_message)],
3840 "Content-transfer-encoding: base64\n\n"
3844 sprintf(&encoded_message[strlen(encoded_message)],
3845 "Content-transfer-encoding: 7bit\n\n"
3851 &encoded_message[strlen(encoded_message)],
3857 raw_message[raw_length] = 0;
3859 &encoded_message[strlen(encoded_message)],
3867 lprintf(CTDL_DEBUG, "Allocating\n");
3868 msg = malloc(sizeof(struct CtdlMessage));
3869 memset(msg, 0, sizeof(struct CtdlMessage));
3870 msg->cm_magic = CTDLMESSAGE_MAGIC;
3871 msg->cm_anon_type = MES_NORMAL;
3872 msg->cm_format_type = 4;
3873 msg->cm_fields['A'] = strdup(CC->user.fullname);
3874 msg->cm_fields['O'] = strdup(req_room);
3875 msg->cm_fields['N'] = strdup(config.c_nodename);
3876 msg->cm_fields['H'] = strdup(config.c_humannode);
3877 msg->cm_flags = flags;
3879 msg->cm_fields['M'] = encoded_message;
3881 /* Create the requested room if we have to. */
3882 if (getroom(&qrbuf, roomname) != 0) {
3883 create_room(roomname,
3884 ( (is_mailbox != NULL) ? 5 : 3 ),
3885 "", 0, 1, 0, VIEW_BBS);
3887 /* If the caller specified this object as unique, delete all
3888 * other objects of this type that are currently in the room.
3891 lprintf(CTDL_DEBUG, "Deleted %d other msgs of this type\n",
3892 CtdlDeleteMessages(roomname, NULL, 0, content_type, 0)
3895 /* Now write the data */
3896 CtdlSubmitMsg(msg, NULL, roomname);
3897 CtdlFreeMessage(msg);
3905 void CtdlGetSysConfigBackend(long msgnum, void *userdata) {
3906 config_msgnum = msgnum;
3910 char *CtdlGetSysConfig(char *sysconfname) {
3911 char hold_rm[ROOMNAMELEN];
3914 struct CtdlMessage *msg;
3917 strcpy(hold_rm, CC->room.QRname);
3918 if (getroom(&CC->room, SYSCONFIGROOM) != 0) {
3919 getroom(&CC->room, hold_rm);
3924 /* We want the last (and probably only) config in this room */
3925 begin_critical_section(S_CONFIG);
3926 config_msgnum = (-1L);
3927 CtdlForEachMessage(MSGS_LAST, 1, NULL, sysconfname, NULL,
3928 CtdlGetSysConfigBackend, NULL);
3929 msgnum = config_msgnum;
3930 end_critical_section(S_CONFIG);
3936 msg = CtdlFetchMessage(msgnum, 1);
3938 conf = strdup(msg->cm_fields['M']);
3939 CtdlFreeMessage(msg);
3946 getroom(&CC->room, hold_rm);
3948 if (conf != NULL) do {
3949 extract_token(buf, conf, 0, '\n', sizeof buf);
3950 strcpy(conf, &conf[strlen(buf)+1]);
3951 } while ( (strlen(conf)>0) && (strlen(buf)>0) );
3956 void CtdlPutSysConfig(char *sysconfname, char *sysconfdata) {
3957 char temp[PATH_MAX];
3960 CtdlMakeTempFileName(temp, sizeof temp);
3962 fp = fopen(temp, "w");
3963 if (fp == NULL) return;
3964 fprintf(fp, "%s", sysconfdata);
3967 /* this handy API function does all the work for us */
3968 CtdlWriteObject(SYSCONFIGROOM, sysconfname, temp, NULL, 0, 1, 0);
3974 * Determine whether a given Internet address belongs to the current user
3976 int CtdlIsMe(char *addr, int addr_buf_len)
3978 struct recptypes *recp;
3981 recp = validate_recipients(addr);
3982 if (recp == NULL) return(0);
3984 if (recp->num_local == 0) {
3989 for (i=0; i<recp->num_local; ++i) {
3990 extract_token(addr, recp->recp_local, i, '|', addr_buf_len);
3991 if (!strcasecmp(addr, CC->user.fullname)) {
4003 * Citadel protocol command to do the same
4005 void cmd_isme(char *argbuf) {
4008 if (CtdlAccessCheck(ac_logged_in)) return;
4009 extract_token(addr, argbuf, 0, '|', sizeof addr);
4011 if (CtdlIsMe(addr, sizeof addr)) {
4012 cprintf("%d %s\n", CIT_OK, addr);
4015 cprintf("%d Not you.\n", ERROR + ILLEGAL_VALUE);