4 * Implements the message store.
14 #if TIME_WITH_SYS_TIME
15 # include <sys/time.h>
19 # include <sys/time.h>
32 #include <sys/types.h>
36 #include "serv_extensions.h"
40 #include "sysdep_decls.h"
41 #include "citserver.h"
48 #include "mime_parser.h"
51 #include "internet_addressing.h"
52 #include "serv_fulltext.h"
54 #include "euidindex.h"
55 #include "journaling.h"
56 #include "citadel_dirs.h"
57 #include "serv_network.h"
60 # include "serv_sieve.h"
61 #endif /* HAVE_LIBSIEVE */
64 struct addresses_to_be_filed *atbf = NULL;
66 /* This temp file holds the queue of operations for AdjRefCount() */
67 static FILE *arcfp = NULL;
70 * This really belongs in serv_network.c, but I don't know how to export
71 * symbols between modules.
73 struct FilterList *filterlist = NULL;
77 * These are the four-character field headers we use when outputting
78 * messages in Citadel format (as opposed to RFC822 format).
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,
84 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
85 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
86 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
87 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
88 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
116 * This function is self explanatory.
117 * (What can I say, I'm in a weird mood today...)
119 void remove_any_whitespace_to_the_left_or_right_of_at_symbol(char *name)
123 for (i = 0; i < strlen(name); ++i) {
124 if (name[i] == '@') {
125 while (isspace(name[i - 1]) && i > 0) {
126 strcpy(&name[i - 1], &name[i]);
129 while (isspace(name[i + 1])) {
130 strcpy(&name[i + 1], &name[i + 2]);
138 * Aliasing for network mail.
139 * (Error messages have been commented out, because this is a server.)
141 int alias(char *name)
142 { /* process alias and routing info for mail */
145 char aaa[SIZ], bbb[SIZ];
146 char *ignetcfg = NULL;
147 char *ignetmap = NULL;
154 remove_any_whitespace_to_the_left_or_right_of_at_symbol(name);
155 stripallbut(name, '<', '>');
157 fp = fopen(file_mail_aliases, "r");
159 fp = fopen("/dev/null", "r");
166 while (fgets(aaa, sizeof aaa, fp) != NULL) {
167 while (isspace(name[0]))
168 strcpy(name, &name[1]);
169 aaa[strlen(aaa) - 1] = 0;
171 for (a = 0; a < strlen(aaa); ++a) {
173 strcpy(bbb, &aaa[a + 1]);
177 if (!strcasecmp(name, aaa))
182 /* Hit the Global Address Book */
183 if (CtdlDirectoryLookup(aaa, name, sizeof aaa) == 0) {
187 lprintf(CTDL_INFO, "Mail is being forwarded to %s\n", name);
189 /* Change "user @ xxx" to "user" if xxx is an alias for this host */
190 for (a=0; a<strlen(name); ++a) {
191 if (name[a] == '@') {
192 if (CtdlHostAlias(&name[a+1]) == hostalias_localhost) {
194 lprintf(CTDL_INFO, "Changed to <%s>\n", name);
199 /* determine local or remote type, see citadel.h */
200 at = haschar(name, '@');
201 if (at == 0) return(MES_LOCAL); /* no @'s - local address */
202 if (at > 1) return(MES_ERROR); /* >1 @'s - invalid address */
203 remove_any_whitespace_to_the_left_or_right_of_at_symbol(name);
205 /* figure out the delivery mode */
206 extract_token(node, name, 1, '@', sizeof node);
208 /* If there are one or more dots in the nodename, we assume that it
209 * is an FQDN and will attempt SMTP delivery to the Internet.
211 if (haschar(node, '.') > 0) {
212 return(MES_INTERNET);
215 /* Otherwise we look in the IGnet maps for a valid Citadel node.
216 * Try directly-connected nodes first...
218 ignetcfg = CtdlGetSysConfig(IGNETCFG);
219 for (i=0; i<num_tokens(ignetcfg, '\n'); ++i) {
220 extract_token(buf, ignetcfg, i, '\n', sizeof buf);
221 extract_token(testnode, buf, 0, '|', sizeof testnode);
222 if (!strcasecmp(node, testnode)) {
230 * Then try nodes that are two or more hops away.
232 ignetmap = CtdlGetSysConfig(IGNETMAP);
233 for (i=0; i<num_tokens(ignetmap, '\n'); ++i) {
234 extract_token(buf, ignetmap, i, '\n', sizeof buf);
235 extract_token(testnode, buf, 0, '|', sizeof testnode);
236 if (!strcasecmp(node, testnode)) {
243 /* If we get to this point it's an invalid node name */
249 * Back end for the MSGS command: output message number only.
251 void simple_listing(long msgnum, void *userdata)
253 cprintf("%ld\n", msgnum);
259 * Back end for the MSGS command: output header summary.
261 void headers_listing(long msgnum, void *userdata)
263 struct CtdlMessage *msg;
265 msg = CtdlFetchMessage(msgnum, 0);
267 cprintf("%ld|0|||||\n", msgnum);
271 cprintf("%ld|%s|%s|%s|%s|%s|\n",
273 (msg->cm_fields['T'] ? msg->cm_fields['T'] : "0"),
274 (msg->cm_fields['A'] ? msg->cm_fields['A'] : ""),
275 (msg->cm_fields['N'] ? msg->cm_fields['N'] : ""),
276 (msg->cm_fields['F'] ? msg->cm_fields['F'] : ""),
277 (msg->cm_fields['U'] ? msg->cm_fields['U'] : "")
279 CtdlFreeMessage(msg);
284 /* Determine if a given message matches the fields in a message template.
285 * Return 0 for a successful match.
287 int CtdlMsgCmp(struct CtdlMessage *msg, struct CtdlMessage *template) {
290 /* If there aren't any fields in the template, all messages will
293 if (template == NULL) return(0);
295 /* Null messages are bogus. */
296 if (msg == NULL) return(1);
298 for (i='A'; i<='Z'; ++i) {
299 if (template->cm_fields[i] != NULL) {
300 if (msg->cm_fields[i] == NULL) {
303 if (strcasecmp(msg->cm_fields[i],
304 template->cm_fields[i])) return 1;
308 /* All compares succeeded: we have a match! */
315 * Retrieve the "seen" message list for the current room.
317 void CtdlGetSeen(char *buf, int which_set) {
320 /* Learn about the user and room in question */
321 CtdlGetRelationship(&vbuf, &CC->user, &CC->room);
323 if (which_set == ctdlsetseen_seen)
324 safestrncpy(buf, vbuf.v_seen, SIZ);
325 if (which_set == ctdlsetseen_answered)
326 safestrncpy(buf, vbuf.v_answered, SIZ);
332 * Manipulate the "seen msgs" string (or other message set strings)
334 void CtdlSetSeen(long *target_msgnums, int num_target_msgnums,
335 int target_setting, int which_set,
336 struct ctdluser *which_user, struct ctdlroom *which_room) {
337 struct cdbdata *cdbfr;
349 char *is_set; /* actually an array of booleans */
352 char setstr[SIZ], lostr[SIZ], histr[SIZ];
355 /* Don't bother doing *anything* if we were passed a list of zero messages */
356 if (num_target_msgnums < 1) {
360 lprintf(CTDL_DEBUG, "CtdlSetSeen(%d msgs starting with %ld, %d, %d)\n",
361 num_target_msgnums, target_msgnums[0],
362 target_setting, which_set);
364 /* Learn about the user and room in question */
365 CtdlGetRelationship(&vbuf,
366 ((which_user != NULL) ? which_user : &CC->user),
367 ((which_room != NULL) ? which_room : &CC->room)
370 /* Load the message list */
371 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long));
373 msglist = (long *) cdbfr->ptr;
374 cdbfr->ptr = NULL; /* CtdlSetSeen() now owns this memory */
375 num_msgs = cdbfr->len / sizeof(long);
378 return; /* No messages at all? No further action. */
381 is_set = malloc(num_msgs * sizeof(char));
382 memset(is_set, 0, (num_msgs * sizeof(char)) );
384 /* Decide which message set we're manipulating */
386 case ctdlsetseen_seen:
387 safestrncpy(vset, vbuf.v_seen, sizeof vset);
389 case ctdlsetseen_answered:
390 safestrncpy(vset, vbuf.v_answered, sizeof vset);
394 /* lprintf(CTDL_DEBUG, "before optimize: %s\n", vset); */
396 /* Translate the existing sequence set into an array of booleans */
397 num_sets = num_tokens(vset, ',');
398 for (s=0; s<num_sets; ++s) {
399 extract_token(setstr, vset, s, ',', sizeof setstr);
401 extract_token(lostr, setstr, 0, ':', sizeof lostr);
402 if (num_tokens(setstr, ':') >= 2) {
403 extract_token(histr, setstr, 1, ':', sizeof histr);
404 if (!strcmp(histr, "*")) {
405 snprintf(histr, sizeof histr, "%ld", LONG_MAX);
409 strcpy(histr, lostr);
414 for (i = 0; i < num_msgs; ++i) {
415 if ((msglist[i] >= lo) && (msglist[i] <= hi)) {
421 /* Now translate the array of booleans back into a sequence set */
426 for (i=0; i<num_msgs; ++i) {
428 is_seen = is_set[i]; /* Default to existing setting */
430 for (k=0; k<num_target_msgnums; ++k) {
431 if (msglist[i] == target_msgnums[k]) {
432 is_seen = target_setting;
437 if (lo < 0L) lo = msglist[i];
441 if ( ((is_seen == 0) && (was_seen == 1))
442 || ((is_seen == 1) && (i == num_msgs-1)) ) {
444 /* begin trim-o-matic code */
447 while ( (strlen(vset) + 20) > sizeof vset) {
448 remove_token(vset, 0, ',');
450 if (j--) break; /* loop no more than 9 times */
452 if ( (trimming) && (which_set == ctdlsetseen_seen) ) {
456 snprintf(lostr, sizeof lostr,
457 "1:%ld,%s", t, vset);
458 safestrncpy(vset, lostr, sizeof vset);
460 /* end trim-o-matic code */
468 snprintf(&vset[tmp], (sizeof vset) - tmp,
472 snprintf(&vset[tmp], (sizeof vset) - tmp,
481 /* Decide which message set we're manipulating */
483 case ctdlsetseen_seen:
484 safestrncpy(vbuf.v_seen, vset, sizeof vbuf.v_seen);
486 case ctdlsetseen_answered:
487 safestrncpy(vbuf.v_answered, vset,
488 sizeof vbuf.v_answered);
493 /* lprintf(CTDL_DEBUG, " after optimize: %s\n", vset); */
495 CtdlSetRelationship(&vbuf,
496 ((which_user != NULL) ? which_user : &CC->user),
497 ((which_room != NULL) ? which_room : &CC->room)
503 * API function to perform an operation for each qualifying message in the
504 * current room. (Returns the number of messages processed.)
506 int CtdlForEachMessage(int mode, long ref, char *search_string,
508 struct CtdlMessage *compare,
509 void (*CallBack) (long, void *),
515 struct cdbdata *cdbfr;
516 long *msglist = NULL;
518 int num_processed = 0;
521 struct CtdlMessage *msg = NULL;
524 int printed_lastold = 0;
525 int num_search_msgs = 0;
526 long *search_msgs = NULL;
528 /* Learn about the user and room in question */
529 getuser(&CC->user, CC->curr_user);
530 CtdlGetRelationship(&vbuf, &CC->user, &CC->room);
532 /* Load the message list */
533 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long));
535 msglist = (long *) cdbfr->ptr;
536 cdbfr->ptr = NULL; /* CtdlForEachMessage() now owns this memory */
537 num_msgs = cdbfr->len / sizeof(long);
540 return 0; /* No messages at all? No further action. */
545 * Now begin the traversal.
547 if (num_msgs > 0) for (a = 0; a < num_msgs; ++a) {
549 /* If the caller is looking for a specific MIME type, filter
550 * out all messages which are not of the type requested.
552 if (content_type != NULL) if (strlen(content_type) > 0) {
554 /* This call to GetMetaData() sits inside this loop
555 * so that we only do the extra database read per msg
556 * if we need to. Doing the extra read all the time
557 * really kills the server. If we ever need to use
558 * metadata for another search criterion, we need to
559 * move the read somewhere else -- but still be smart
560 * enough to only do the read if the caller has
561 * specified something that will need it.
563 GetMetaData(&smi, msglist[a]);
565 if (strcasecmp(smi.meta_content_type, content_type)) {
571 num_msgs = sort_msglist(msglist, num_msgs);
573 /* If a template was supplied, filter out the messages which
574 * don't match. (This could induce some delays!)
577 if (compare != NULL) {
578 for (a = 0; a < num_msgs; ++a) {
579 msg = CtdlFetchMessage(msglist[a], 1);
581 if (CtdlMsgCmp(msg, compare)) {
584 CtdlFreeMessage(msg);
590 /* If a search string was specified, get a message list from
591 * the full text index and remove messages which aren't on both
595 * Since the lists are sorted and strictly ascending, and the
596 * output list is guaranteed to be shorter than or equal to the
597 * input list, we overwrite the bottom of the input list. This
598 * eliminates the need to memmove big chunks of the list over and
601 if ( (num_msgs > 0) && (mode == MSGS_SEARCH) && (search_string) ) {
602 ft_search(&num_search_msgs, &search_msgs, search_string);
603 if (num_search_msgs > 0) {
607 orig_num_msgs = num_msgs;
609 for (i=0; i<orig_num_msgs; ++i) {
610 for (j=0; j<num_search_msgs; ++j) {
611 if (msglist[i] == search_msgs[j]) {
612 msglist[num_msgs++] = msglist[i];
618 num_msgs = 0; /* No messages qualify */
620 if (search_msgs != NULL) free(search_msgs);
622 /* Now that we've purged messages which don't contain the search
623 * string, treat a MSGS_SEARCH just like a MSGS_ALL from this
630 * Now iterate through the message list, according to the
631 * criteria supplied by the caller.
634 for (a = 0; a < num_msgs; ++a) {
635 thismsg = msglist[a];
636 if (mode == MSGS_ALL) {
640 is_seen = is_msg_in_sequence_set(
641 vbuf.v_seen, thismsg);
642 if (is_seen) lastold = thismsg;
648 || ((mode == MSGS_OLD) && (is_seen))
649 || ((mode == MSGS_NEW) && (!is_seen))
650 || ((mode == MSGS_LAST) && (a >= (num_msgs - ref)))
651 || ((mode == MSGS_FIRST) && (a < ref))
652 || ((mode == MSGS_GT) && (thismsg > ref))
653 || ((mode == MSGS_EQ) && (thismsg == ref))
656 if ((mode == MSGS_NEW) && (CC->user.flags & US_LASTOLD) && (lastold > 0L) && (printed_lastold == 0) && (!is_seen)) {
658 CallBack(lastold, userdata);
662 if (CallBack) CallBack(thismsg, userdata);
666 free(msglist); /* Clean up */
667 return num_processed;
673 * cmd_msgs() - get list of message #'s in this room
674 * implements the MSGS server command using CtdlForEachMessage()
676 void cmd_msgs(char *cmdbuf)
685 int with_template = 0;
686 struct CtdlMessage *template = NULL;
687 int with_headers = 0;
688 char search_string[1024];
690 extract_token(which, cmdbuf, 0, '|', sizeof which);
691 cm_ref = extract_int(cmdbuf, 1);
692 extract_token(search_string, cmdbuf, 1, '|', sizeof search_string);
693 with_template = extract_int(cmdbuf, 2);
694 with_headers = extract_int(cmdbuf, 3);
697 if (!strncasecmp(which, "OLD", 3))
699 else if (!strncasecmp(which, "NEW", 3))
701 else if (!strncasecmp(which, "FIRST", 5))
703 else if (!strncasecmp(which, "LAST", 4))
705 else if (!strncasecmp(which, "GT", 2))
707 else if (!strncasecmp(which, "SEARCH", 6))
712 if ((!(CC->logged_in)) && (!(CC->internal_pgm))) {
713 cprintf("%d not logged in\n", ERROR + NOT_LOGGED_IN);
717 if ( (mode == MSGS_SEARCH) && (!config.c_enable_fulltext) ) {
718 cprintf("%d Full text index is not enabled on this server.\n",
719 ERROR + CMD_NOT_SUPPORTED);
725 cprintf("%d Send template then receive message list\n",
727 template = (struct CtdlMessage *)
728 malloc(sizeof(struct CtdlMessage));
729 memset(template, 0, sizeof(struct CtdlMessage));
730 template->cm_magic = CTDLMESSAGE_MAGIC;
731 template->cm_anon_type = MES_NORMAL;
733 while(client_getln(buf, sizeof buf), strcmp(buf,"000")) {
734 extract_token(tfield, buf, 0, '|', sizeof tfield);
735 extract_token(tvalue, buf, 1, '|', sizeof tvalue);
736 for (i='A'; i<='Z'; ++i) if (msgkeys[i]!=NULL) {
737 if (!strcasecmp(tfield, msgkeys[i])) {
738 template->cm_fields[i] =
746 cprintf("%d \n", LISTING_FOLLOWS);
749 CtdlForEachMessage(mode,
750 ( (mode == MSGS_SEARCH) ? 0 : cm_ref ),
751 ( (mode == MSGS_SEARCH) ? search_string : NULL ),
754 (with_headers ? headers_listing : simple_listing),
757 if (template != NULL) CtdlFreeMessage(template);
765 * help_subst() - support routine for help file viewer
767 void help_subst(char *strbuf, char *source, char *dest)
772 while (p = pattern2(strbuf, source), (p >= 0)) {
773 strcpy(workbuf, &strbuf[p + strlen(source)]);
774 strcpy(&strbuf[p], dest);
775 strcat(strbuf, workbuf);
780 void do_help_subst(char *buffer)
784 help_subst(buffer, "^nodename", config.c_nodename);
785 help_subst(buffer, "^humannode", config.c_humannode);
786 help_subst(buffer, "^fqdn", config.c_fqdn);
787 help_subst(buffer, "^username", CC->user.fullname);
788 snprintf(buf2, sizeof buf2, "%ld", CC->user.usernum);
789 help_subst(buffer, "^usernum", buf2);
790 help_subst(buffer, "^sysadm", config.c_sysadm);
791 help_subst(buffer, "^variantname", CITADEL);
792 snprintf(buf2, sizeof buf2, "%d", config.c_maxsessions);
793 help_subst(buffer, "^maxsessions", buf2);
794 help_subst(buffer, "^bbsdir", ctdl_bbsbase_dir);
800 * memfmout() - Citadel text formatter and paginator.
801 * Although the original purpose of this routine was to format
802 * text to the reader's screen width, all we're really using it
803 * for here is to format text out to 80 columns before sending it
804 * to the client. The client software may reformat it again.
807 char *mptr, /* where are we going to get our text from? */
808 char subst, /* nonzero if we should do substitutions */
809 char *nl) /* string to terminate lines with */
817 static int width = 80;
822 c = 1; /* c is the current pos */
826 while (ch = *mptr, ((ch != 0) && (strlen(buffer) < 126))) {
828 buffer[strlen(buffer) + 1] = 0;
829 buffer[strlen(buffer)] = ch;
832 if (buffer[0] == '^')
833 do_help_subst(buffer);
835 buffer[strlen(buffer) + 1] = 0;
837 strcpy(buffer, &buffer[1]);
845 if (((ch == 13) || (ch == 10)) && (old != 13) && (old != 10)) {
848 if (((old == 13) || (old == 10)) && (isspace(real))) {
853 if (((strlen(aaa) + c) > (width - 5)) && (strlen(aaa) > (width - 5))) {
854 cprintf("%s%s", nl, aaa);
863 if ((strlen(aaa) + c) > (width - 5)) {
872 if ((ch == 13) || (ch == 10)) {
873 cprintf("%s%s", aaa, nl);
880 cprintf("%s%s", aaa, nl);
886 * Callback function for mime parser that simply lists the part
888 void list_this_part(char *name, char *filename, char *partnum, char *disp,
889 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
894 ma = (struct ma_info *)cbuserdata;
895 if (ma->is_ma == 0) {
896 cprintf("part=%s|%s|%s|%s|%s|%ld\n",
897 name, filename, partnum, disp, cbtype, (long)length);
902 * Callback function for multipart prefix
904 void list_this_pref(char *name, char *filename, char *partnum, char *disp,
905 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
910 ma = (struct ma_info *)cbuserdata;
911 if (!strcasecmp(cbtype, "multipart/alternative")) {
915 if (ma->is_ma == 0) {
916 cprintf("pref=%s|%s\n", partnum, cbtype);
921 * Callback function for multipart sufffix
923 void list_this_suff(char *name, char *filename, char *partnum, char *disp,
924 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
929 ma = (struct ma_info *)cbuserdata;
930 if (ma->is_ma == 0) {
931 cprintf("suff=%s|%s\n", partnum, cbtype);
933 if (!strcasecmp(cbtype, "multipart/alternative")) {
940 * Callback function for mime parser that opens a section for downloading
942 void mime_download(char *name, char *filename, char *partnum, char *disp,
943 void *content, char *cbtype, char *cbcharset, size_t length,
944 char *encoding, void *cbuserdata)
947 /* Silently go away if there's already a download open... */
948 if (CC->download_fp != NULL)
951 /* ...or if this is not the desired section */
952 if (strcasecmp(CC->download_desired_section, partnum))
955 CC->download_fp = tmpfile();
956 if (CC->download_fp == NULL)
959 fwrite(content, length, 1, CC->download_fp);
960 fflush(CC->download_fp);
961 rewind(CC->download_fp);
963 OpenCmdResult(filename, cbtype);
969 * Callback function for mime parser that outputs a section all at once
971 void mime_spew_section(char *name, char *filename, char *partnum, char *disp,
972 void *content, char *cbtype, char *cbcharset, size_t length,
973 char *encoding, void *cbuserdata)
975 int *found_it = (int *)cbuserdata;
977 /* ...or if this is not the desired section */
978 if (strcasecmp(CC->download_desired_section, partnum))
983 cprintf("%d %d\n", BINARY_FOLLOWS, length);
984 client_write(content, length);
990 * Load a message from disk into memory.
991 * This is used by CtdlOutputMsg() and other fetch functions.
993 * NOTE: Caller is responsible for freeing the returned CtdlMessage struct
994 * using the CtdlMessageFree() function.
996 struct CtdlMessage *CtdlFetchMessage(long msgnum, int with_body)
998 struct cdbdata *dmsgtext;
999 struct CtdlMessage *ret = NULL;
1003 cit_uint8_t field_header;
1005 lprintf(CTDL_DEBUG, "CtdlFetchMessage(%ld, %d)\n", msgnum, with_body);
1007 dmsgtext = cdb_fetch(CDB_MSGMAIN, &msgnum, sizeof(long));
1008 if (dmsgtext == NULL) {
1011 mptr = dmsgtext->ptr;
1012 upper_bound = mptr + dmsgtext->len;
1014 /* Parse the three bytes that begin EVERY message on disk.
1015 * The first is always 0xFF, the on-disk magic number.
1016 * The second is the anonymous/public type byte.
1017 * The third is the format type byte (vari, fixed, or MIME).
1022 "Message %ld appears to be corrupted.\n",
1027 ret = (struct CtdlMessage *) malloc(sizeof(struct CtdlMessage));
1028 memset(ret, 0, sizeof(struct CtdlMessage));
1030 ret->cm_magic = CTDLMESSAGE_MAGIC;
1031 ret->cm_anon_type = *mptr++; /* Anon type byte */
1032 ret->cm_format_type = *mptr++; /* Format type byte */
1035 * The rest is zero or more arbitrary fields. Load them in.
1036 * We're done when we encounter either a zero-length field or
1037 * have just processed the 'M' (message text) field.
1040 if (mptr >= upper_bound) {
1043 field_header = *mptr++;
1044 ret->cm_fields[field_header] = strdup(mptr);
1046 while (*mptr++ != 0); /* advance to next field */
1048 } while ((mptr < upper_bound) && (field_header != 'M'));
1052 /* Always make sure there's something in the msg text field. If
1053 * it's NULL, the message text is most likely stored separately,
1054 * so go ahead and fetch that. Failing that, just set a dummy
1055 * body so other code doesn't barf.
1057 if ( (ret->cm_fields['M'] == NULL) && (with_body) ) {
1058 dmsgtext = cdb_fetch(CDB_BIGMSGS, &msgnum, sizeof(long));
1059 if (dmsgtext != NULL) {
1060 ret->cm_fields['M'] = strdup(dmsgtext->ptr);
1064 if (ret->cm_fields['M'] == NULL) {
1065 ret->cm_fields['M'] = strdup("\r\n\r\n (no text)\r\n");
1068 /* Perform "before read" hooks (aborting if any return nonzero) */
1069 if (PerformMessageHooks(ret, EVT_BEFOREREAD) > 0) {
1070 CtdlFreeMessage(ret);
1079 * Returns 1 if the supplied pointer points to a valid Citadel message.
1080 * If the pointer is NULL or the magic number check fails, returns 0.
1082 int is_valid_message(struct CtdlMessage *msg) {
1085 if ((msg->cm_magic) != CTDLMESSAGE_MAGIC) {
1086 lprintf(CTDL_WARNING, "is_valid_message() -- self-check failed\n");
1094 * 'Destructor' for struct CtdlMessage
1096 void CtdlFreeMessage(struct CtdlMessage *msg)
1100 if (is_valid_message(msg) == 0)
1102 if (msg != NULL) free (msg);
1106 for (i = 0; i < 256; ++i)
1107 if (msg->cm_fields[i] != NULL) {
1108 free(msg->cm_fields[i]);
1111 msg->cm_magic = 0; /* just in case */
1117 * Pre callback function for multipart/alternative
1119 * NOTE: this differs from the standard behavior for a reason. Normally when
1120 * displaying multipart/alternative you want to show the _last_ usable
1121 * format in the message. Here we show the _first_ one, because it's
1122 * usually text/plain. Since this set of functions is designed for text
1123 * output to non-MIME-aware clients, this is the desired behavior.
1126 void fixed_output_pre(char *name, char *filename, char *partnum, char *disp,
1127 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
1132 ma = (struct ma_info *)cbuserdata;
1133 lprintf(CTDL_DEBUG, "fixed_output_pre() type=<%s>\n", cbtype);
1134 if (!strcasecmp(cbtype, "multipart/alternative")) {
1138 if (!strcasecmp(cbtype, "message/rfc822")) {
1144 * Post callback function for multipart/alternative
1146 void fixed_output_post(char *name, char *filename, char *partnum, char *disp,
1147 void *content, char *cbtype, char *cbcharset, size_t length,
1148 char *encoding, void *cbuserdata)
1152 ma = (struct ma_info *)cbuserdata;
1153 lprintf(CTDL_DEBUG, "fixed_output_post() type=<%s>\n", cbtype);
1154 if (!strcasecmp(cbtype, "multipart/alternative")) {
1158 if (!strcasecmp(cbtype, "message/rfc822")) {
1164 * Inline callback function for mime parser that wants to display text
1166 void fixed_output(char *name, char *filename, char *partnum, char *disp,
1167 void *content, char *cbtype, char *cbcharset, size_t length,
1168 char *encoding, void *cbuserdata)
1175 ma = (struct ma_info *)cbuserdata;
1178 "fixed_output() part %s: %s (%s) (%ld bytes)\n",
1179 partnum, filename, cbtype, (long)length);
1182 * If we're in the middle of a multipart/alternative scope and
1183 * we've already printed another section, skip this one.
1185 if ( (ma->is_ma) && (ma->did_print) ) {
1186 lprintf(CTDL_DEBUG, "Skipping part %s (%s)\n",
1192 if ( (!strcasecmp(cbtype, "text/plain"))
1193 || (strlen(cbtype)==0) ) {
1196 client_write(wptr, length);
1197 if (wptr[length-1] != '\n') {
1204 if (!strcasecmp(cbtype, "text/html")) {
1205 ptr = html_to_ascii(content, length, 80, 0);
1207 client_write(ptr, wlen);
1208 if (ptr[wlen-1] != '\n') {
1215 if (ma->use_fo_hooks) {
1216 if (PerformFixedOutputHooks(cbtype, content, length)) {
1217 /* above function returns nonzero if it handled the part */
1222 if (strncasecmp(cbtype, "multipart/", 10)) {
1223 cprintf("Part %s: %s (%s) (%ld bytes)\r\n",
1224 partnum, filename, cbtype, (long)length);
1230 * The client is elegant and sophisticated and wants to be choosy about
1231 * MIME content types, so figure out which multipart/alternative part
1232 * we're going to send.
1234 * We use a system of weights. When we find a part that matches one of the
1235 * MIME types we've declared as preferential, we can store it in ma->chosen_part
1236 * and then set ma->chosen_pref to that MIME type's position in our preference
1237 * list. If we then hit another match, we only replace the first match if
1238 * the preference value is lower.
1240 void choose_preferred(char *name, char *filename, char *partnum, char *disp,
1241 void *content, char *cbtype, char *cbcharset, size_t length,
1242 char *encoding, void *cbuserdata)
1248 ma = (struct ma_info *)cbuserdata;
1250 if (ma->is_ma > 0) {
1251 for (i=0; i<num_tokens(CC->preferred_formats, '|'); ++i) {
1252 extract_token(buf, CC->preferred_formats, i, '|', sizeof buf);
1253 if ( (!strcasecmp(buf, cbtype)) && (!ma->freeze) ) {
1254 if (i < ma->chosen_pref) {
1255 safestrncpy(ma->chosen_part, partnum, sizeof ma->chosen_part);
1256 ma->chosen_pref = i;
1264 * Now that we've chosen our preferred part, output it.
1266 void output_preferred(char *name, char *filename, char *partnum, char *disp,
1267 void *content, char *cbtype, char *cbcharset, size_t length,
1268 char *encoding, void *cbuserdata)
1272 int add_newline = 0;
1276 ma = (struct ma_info *)cbuserdata;
1278 /* This is not the MIME part you're looking for... */
1279 if (strcasecmp(partnum, ma->chosen_part)) return;
1281 /* If the content-type of this part is in our preferred formats
1282 * list, we can simply output it verbatim.
1284 for (i=0; i<num_tokens(CC->preferred_formats, '|'); ++i) {
1285 extract_token(buf, CC->preferred_formats, i, '|', sizeof buf);
1286 if (!strcasecmp(buf, cbtype)) {
1287 /* Yeah! Go! W00t!! */
1289 text_content = (char *)content;
1290 if (text_content[length-1] != '\n') {
1294 cprintf("Content-type: %s", cbtype);
1295 if (strlen(cbcharset) > 0) {
1296 cprintf("; charset=%s", cbcharset);
1298 cprintf("\nContent-length: %d\n",
1299 (int)(length + add_newline) );
1300 if (strlen(encoding) > 0) {
1301 cprintf("Content-transfer-encoding: %s\n", encoding);
1304 cprintf("Content-transfer-encoding: 7bit\n");
1307 client_write(content, length);
1308 if (add_newline) cprintf("\n");
1313 /* No translations required or possible: output as text/plain */
1314 cprintf("Content-type: text/plain\n\n");
1315 fixed_output(name, filename, partnum, disp, content, cbtype, cbcharset,
1316 length, encoding, cbuserdata);
1321 char desired_section[64];
1328 * Callback function for
1330 void extract_encapsulated_message(char *name, char *filename, char *partnum, char *disp,
1331 void *content, char *cbtype, char *cbcharset, size_t length,
1332 char *encoding, void *cbuserdata)
1334 struct encapmsg *encap;
1336 encap = (struct encapmsg *)cbuserdata;
1338 /* Only proceed if this is the desired section... */
1339 if (!strcasecmp(encap->desired_section, partnum)) {
1340 encap->msglen = length;
1341 encap->msg = malloc(length + 2);
1342 memcpy(encap->msg, content, length);
1352 * Get a message off disk. (returns om_* values found in msgbase.h)
1355 int CtdlOutputMsg(long msg_num, /* message number (local) to fetch */
1356 int mode, /* how would you like that message? */
1357 int headers_only, /* eschew the message body? */
1358 int do_proto, /* do Citadel protocol responses? */
1359 int crlf, /* Use CRLF newlines instead of LF? */
1360 char *section /* NULL or a message/rfc822 section */
1362 struct CtdlMessage *TheMessage = NULL;
1363 int retcode = om_no_such_msg;
1364 struct encapmsg encap;
1366 lprintf(CTDL_DEBUG, "CtdlOutputMsg() msgnum=%ld, mode=%d, section=%s\n",
1368 (section ? section : "<>")
1371 if ((!(CC->logged_in)) && (!(CC->internal_pgm))) {
1372 if (do_proto) cprintf("%d Not logged in.\n",
1373 ERROR + NOT_LOGGED_IN);
1374 return(om_not_logged_in);
1377 /* FIXME: check message id against msglist for this room */
1380 * Fetch the message from disk. If we're in any sort of headers
1381 * only mode, request that we don't even bother loading the body
1384 if ( (headers_only == HEADERS_FAST) || (headers_only == HEADERS_ONLY) ) {
1385 TheMessage = CtdlFetchMessage(msg_num, 0);
1388 TheMessage = CtdlFetchMessage(msg_num, 1);
1391 if (TheMessage == NULL) {
1392 if (do_proto) cprintf("%d Can't locate msg %ld on disk\n",
1393 ERROR + MESSAGE_NOT_FOUND, msg_num);
1394 return(om_no_such_msg);
1397 /* Here is the weird form of this command, to process only an
1398 * encapsulated message/rfc822 section.
1400 if (section) if (strlen(section)>0) if (strcmp(section, "0")) {
1401 memset(&encap, 0, sizeof encap);
1402 safestrncpy(encap.desired_section, section, sizeof encap.desired_section);
1403 mime_parser(TheMessage->cm_fields['M'],
1405 *extract_encapsulated_message,
1406 NULL, NULL, (void *)&encap, 0
1408 CtdlFreeMessage(TheMessage);
1412 encap.msg[encap.msglen] = 0;
1413 TheMessage = convert_internet_message(encap.msg);
1414 encap.msg = NULL; /* no free() here, TheMessage owns it now */
1416 /* Now we let it fall through to the bottom of this
1417 * function, because TheMessage now contains the
1418 * encapsulated message instead of the top-level
1419 * message. Isn't that neat?
1424 if (do_proto) cprintf("%d msg %ld has no part %s\n",
1425 ERROR + MESSAGE_NOT_FOUND, msg_num, section);
1426 retcode = om_no_such_msg;
1431 /* Ok, output the message now */
1432 retcode = CtdlOutputPreLoadedMsg(
1434 headers_only, do_proto, crlf);
1435 CtdlFreeMessage(TheMessage);
1442 * Get a message off disk. (returns om_* values found in msgbase.h)
1445 int CtdlOutputPreLoadedMsg(
1446 struct CtdlMessage *TheMessage,
1447 int mode, /* how would you like that message? */
1448 int headers_only, /* eschew the message body? */
1449 int do_proto, /* do Citadel protocol responses? */
1450 int crlf /* Use CRLF newlines instead of LF? */
1456 char display_name[256];
1458 char *nl; /* newline string */
1460 int subject_found = 0;
1463 /* Buffers needed for RFC822 translation. These are all filled
1464 * using functions that are bounds-checked, and therefore we can
1465 * make them substantially smaller than SIZ.
1473 char datestamp[100];
1475 lprintf(CTDL_DEBUG, "CtdlOutputPreLoadedMsg(TheMessage=%s, %d, %d, %d, %d\n",
1476 ((TheMessage == NULL) ? "NULL" : "not null"),
1477 mode, headers_only, do_proto, crlf);
1479 strcpy(mid, "unknown");
1480 nl = (crlf ? "\r\n" : "\n");
1482 if (!is_valid_message(TheMessage)) {
1484 "ERROR: invalid preloaded message for output\n");
1485 return(om_no_such_msg);
1488 /* Are we downloading a MIME component? */
1489 if (mode == MT_DOWNLOAD) {
1490 if (TheMessage->cm_format_type != FMT_RFC822) {
1492 cprintf("%d This is not a MIME message.\n",
1493 ERROR + ILLEGAL_VALUE);
1494 } else if (CC->download_fp != NULL) {
1495 if (do_proto) cprintf(
1496 "%d You already have a download open.\n",
1497 ERROR + RESOURCE_BUSY);
1499 /* Parse the message text component */
1500 mptr = TheMessage->cm_fields['M'];
1501 mime_parser(mptr, NULL, *mime_download, NULL, NULL, NULL, 0);
1502 /* If there's no file open by this time, the requested
1503 * section wasn't found, so print an error
1505 if (CC->download_fp == NULL) {
1506 if (do_proto) cprintf(
1507 "%d Section %s not found.\n",
1508 ERROR + FILE_NOT_FOUND,
1509 CC->download_desired_section);
1512 return((CC->download_fp != NULL) ? om_ok : om_mime_error);
1515 /* MT_SPEW_SECTION is like MT_DOWNLOAD except it outputs the whole MIME part
1516 * in a single server operation instead of opening a download file.
1518 if (mode == MT_SPEW_SECTION) {
1519 if (TheMessage->cm_format_type != FMT_RFC822) {
1521 cprintf("%d This is not a MIME message.\n",
1522 ERROR + ILLEGAL_VALUE);
1524 /* Parse the message text component */
1527 mptr = TheMessage->cm_fields['M'];
1528 mime_parser(mptr, NULL, *mime_spew_section, NULL, NULL, (void *)&found_it, 0);
1529 /* If section wasn't found, print an error
1532 if (do_proto) cprintf(
1533 "%d Section %s not found.\n",
1534 ERROR + FILE_NOT_FOUND,
1535 CC->download_desired_section);
1538 return((CC->download_fp != NULL) ? om_ok : om_mime_error);
1541 /* now for the user-mode message reading loops */
1542 if (do_proto) cprintf("%d msg:\n", LISTING_FOLLOWS);
1544 /* Does the caller want to skip the headers? */
1545 if (headers_only == HEADERS_NONE) goto START_TEXT;
1547 /* Tell the client which format type we're using. */
1548 if ( (mode == MT_CITADEL) && (do_proto) ) {
1549 cprintf("type=%d\n", TheMessage->cm_format_type);
1552 /* nhdr=yes means that we're only displaying headers, no body */
1553 if ( (TheMessage->cm_anon_type == MES_ANONONLY)
1554 && (mode == MT_CITADEL)
1557 cprintf("nhdr=yes\n");
1560 /* begin header processing loop for Citadel message format */
1562 if ((mode == MT_CITADEL) || (mode == MT_MIME)) {
1564 safestrncpy(display_name, "<unknown>", sizeof display_name);
1565 if (TheMessage->cm_fields['A']) {
1566 strcpy(buf, TheMessage->cm_fields['A']);
1567 if (TheMessage->cm_anon_type == MES_ANONONLY) {
1568 safestrncpy(display_name, "****", sizeof display_name);
1570 else if (TheMessage->cm_anon_type == MES_ANONOPT) {
1571 safestrncpy(display_name, "anonymous", sizeof display_name);
1574 safestrncpy(display_name, buf, sizeof display_name);
1576 if ((is_room_aide())
1577 && ((TheMessage->cm_anon_type == MES_ANONONLY)
1578 || (TheMessage->cm_anon_type == MES_ANONOPT))) {
1579 size_t tmp = strlen(display_name);
1580 snprintf(&display_name[tmp],
1581 sizeof display_name - tmp,
1586 /* Don't show Internet address for users on the
1587 * local Citadel network.
1590 if (TheMessage->cm_fields['N'] != NULL)
1591 if (strlen(TheMessage->cm_fields['N']) > 0)
1592 if (haschar(TheMessage->cm_fields['N'], '.') == 0) {
1596 /* Now spew the header fields in the order we like them. */
1597 safestrncpy(allkeys, FORDER, sizeof allkeys);
1598 for (i=0; i<strlen(allkeys); ++i) {
1599 k = (int) allkeys[i];
1601 if ( (TheMessage->cm_fields[k] != NULL)
1602 && (msgkeys[k] != NULL) ) {
1604 if (do_proto) cprintf("%s=%s\n",
1608 else if ((k == 'F') && (suppress_f)) {
1611 /* Masquerade display name if needed */
1613 if (do_proto) cprintf("%s=%s\n",
1615 TheMessage->cm_fields[k]
1624 /* begin header processing loop for RFC822 transfer format */
1629 strcpy(snode, NODENAME);
1630 strcpy(lnode, HUMANNODE);
1631 if (mode == MT_RFC822) {
1632 for (i = 0; i < 256; ++i) {
1633 if (TheMessage->cm_fields[i]) {
1634 mptr = TheMessage->cm_fields[i];
1637 safestrncpy(luser, mptr, sizeof luser);
1638 safestrncpy(suser, mptr, sizeof suser);
1640 else if (i == 'Y') {
1641 cprintf("CC: %s%s", mptr, nl);
1643 else if (i == 'P') {
1644 cprintf("Return-Path: %s%s", mptr, nl);
1646 else if (i == 'V') {
1647 cprintf("Envelope-To: %s%s", mptr, nl);
1649 else if (i == 'U') {
1650 cprintf("Subject: %s%s", mptr, nl);
1654 safestrncpy(mid, mptr, sizeof mid);
1656 safestrncpy(lnode, mptr, sizeof lnode);
1658 safestrncpy(fuser, mptr, sizeof fuser);
1659 /* else if (i == 'O')
1660 cprintf("X-Citadel-Room: %s%s",
1663 safestrncpy(snode, mptr, sizeof snode);
1665 cprintf("To: %s%s", mptr, nl);
1666 else if (i == 'T') {
1667 datestring(datestamp, sizeof datestamp,
1668 atol(mptr), DATESTRING_RFC822);
1669 cprintf("Date: %s%s", datestamp, nl);
1673 if (subject_found == 0) {
1674 cprintf("Subject: (no subject)%s", nl);
1678 for (i=0; i<strlen(suser); ++i) {
1679 suser[i] = tolower(suser[i]);
1680 if (!isalnum(suser[i])) suser[i]='_';
1683 if (mode == MT_RFC822) {
1684 if (!strcasecmp(snode, NODENAME)) {
1685 safestrncpy(snode, FQDN, sizeof snode);
1688 /* Construct a fun message id */
1689 cprintf("Message-ID: <%s", mid);
1690 if (strchr(mid, '@')==NULL) {
1691 cprintf("@%s", snode);
1695 if (!is_room_aide() && (TheMessage->cm_anon_type == MES_ANONONLY)) {
1696 cprintf("From: \"----\" <x@x.org>%s", nl);
1698 else if (!is_room_aide() && (TheMessage->cm_anon_type == MES_ANONOPT)) {
1699 cprintf("From: \"anonymous\" <x@x.org>%s", nl);
1701 else if (strlen(fuser) > 0) {
1702 cprintf("From: \"%s\" <%s>%s", luser, fuser, nl);
1705 cprintf("From: \"%s\" <%s@%s>%s", luser, suser, snode, nl);
1708 cprintf("Organization: %s%s", lnode, nl);
1710 /* Blank line signifying RFC822 end-of-headers */
1711 if (TheMessage->cm_format_type != FMT_RFC822) {
1716 /* end header processing loop ... at this point, we're in the text */
1718 if (headers_only == HEADERS_FAST) goto DONE;
1719 mptr = TheMessage->cm_fields['M'];
1721 /* Tell the client about the MIME parts in this message */
1722 if (TheMessage->cm_format_type == FMT_RFC822) {
1723 if ( (mode == MT_CITADEL) || (mode == MT_MIME) ) {
1724 memset(&ma, 0, sizeof(struct ma_info));
1725 mime_parser(mptr, NULL,
1726 (do_proto ? *list_this_part : NULL),
1727 (do_proto ? *list_this_pref : NULL),
1728 (do_proto ? *list_this_suff : NULL),
1731 else if (mode == MT_RFC822) { /* unparsed RFC822 dump */
1732 char *start_of_text = NULL;
1733 start_of_text = strstr(mptr, "\n\r\n");
1734 if (start_of_text == NULL) start_of_text = strstr(mptr, "\n\n");
1735 if (start_of_text == NULL) start_of_text = mptr;
1737 start_of_text = strstr(start_of_text, "\n");
1742 int nllen = strlen(nl);
1743 while (ch=*mptr, ch!=0) {
1749 ((headers_only == HEADERS_NONE) && (mptr >= start_of_text))
1750 || ((headers_only == HEADERS_ONLY) && (mptr < start_of_text))
1751 || ((headers_only != HEADERS_NONE) && (headers_only != HEADERS_ONLY))
1754 sprintf(&outbuf[outlen], "%s", nl);
1758 outbuf[outlen++] = ch;
1763 if (outlen > 1000) {
1764 client_write(outbuf, outlen);
1769 client_write(outbuf, outlen);
1777 if (headers_only == HEADERS_ONLY) {
1781 /* signify start of msg text */
1782 if ( (mode == MT_CITADEL) || (mode == MT_MIME) ) {
1783 if (do_proto) cprintf("text\n");
1786 /* If the format type on disk is 1 (fixed-format), then we want
1787 * everything to be output completely literally ... regardless of
1788 * what message transfer format is in use.
1790 if (TheMessage->cm_format_type == FMT_FIXED) {
1791 if (mode == MT_MIME) {
1792 cprintf("Content-type: text/plain\n\n");
1795 while (ch = *mptr++, ch > 0) {
1798 if ((ch == 10) || (strlen(buf) > 250)) {
1799 cprintf("%s%s", buf, nl);
1802 buf[strlen(buf) + 1] = 0;
1803 buf[strlen(buf)] = ch;
1806 if (strlen(buf) > 0)
1807 cprintf("%s%s", buf, nl);
1810 /* If the message on disk is format 0 (Citadel vari-format), we
1811 * output using the formatter at 80 columns. This is the final output
1812 * form if the transfer format is RFC822, but if the transfer format
1813 * is Citadel proprietary, it'll still work, because the indentation
1814 * for new paragraphs is correct and the client will reformat the
1815 * message to the reader's screen width.
1817 if (TheMessage->cm_format_type == FMT_CITADEL) {
1818 if (mode == MT_MIME) {
1819 cprintf("Content-type: text/x-citadel-variformat\n\n");
1821 memfmout(mptr, 0, nl);
1824 /* If the message on disk is format 4 (MIME), we've gotta hand it
1825 * off to the MIME parser. The client has already been told that
1826 * this message is format 1 (fixed format), so the callback function
1827 * we use will display those parts as-is.
1829 if (TheMessage->cm_format_type == FMT_RFC822) {
1830 memset(&ma, 0, sizeof(struct ma_info));
1832 if (mode == MT_MIME) {
1833 ma.use_fo_hooks = 0;
1834 strcpy(ma.chosen_part, "1");
1835 ma.chosen_pref = 9999;
1836 mime_parser(mptr, NULL,
1837 *choose_preferred, *fixed_output_pre,
1838 *fixed_output_post, (void *)&ma, 0);
1839 mime_parser(mptr, NULL,
1840 *output_preferred, NULL, NULL, (void *)&ma, 0);
1843 ma.use_fo_hooks = 1;
1844 mime_parser(mptr, NULL,
1845 *fixed_output, *fixed_output_pre,
1846 *fixed_output_post, (void *)&ma, 0);
1851 DONE: /* now we're done */
1852 if (do_proto) cprintf("000\n");
1859 * display a message (mode 0 - Citadel proprietary)
1861 void cmd_msg0(char *cmdbuf)
1864 int headers_only = HEADERS_ALL;
1866 msgid = extract_long(cmdbuf, 0);
1867 headers_only = extract_int(cmdbuf, 1);
1869 CtdlOutputMsg(msgid, MT_CITADEL, headers_only, 1, 0, NULL);
1875 * display a message (mode 2 - RFC822)
1877 void cmd_msg2(char *cmdbuf)
1880 int headers_only = HEADERS_ALL;
1882 msgid = extract_long(cmdbuf, 0);
1883 headers_only = extract_int(cmdbuf, 1);
1885 CtdlOutputMsg(msgid, MT_RFC822, headers_only, 1, 1, NULL);
1891 * display a message (mode 3 - IGnet raw format - internal programs only)
1893 void cmd_msg3(char *cmdbuf)
1896 struct CtdlMessage *msg = NULL;
1899 if (CC->internal_pgm == 0) {
1900 cprintf("%d This command is for internal programs only.\n",
1901 ERROR + HIGHER_ACCESS_REQUIRED);
1905 msgnum = extract_long(cmdbuf, 0);
1906 msg = CtdlFetchMessage(msgnum, 1);
1908 cprintf("%d Message %ld not found.\n",
1909 ERROR + MESSAGE_NOT_FOUND, msgnum);
1913 serialize_message(&smr, msg);
1914 CtdlFreeMessage(msg);
1917 cprintf("%d Unable to serialize message\n",
1918 ERROR + INTERNAL_ERROR);
1922 cprintf("%d %ld\n", BINARY_FOLLOWS, (long)smr.len);
1923 client_write((char *)smr.ser, (int)smr.len);
1930 * Display a message using MIME content types
1932 void cmd_msg4(char *cmdbuf)
1937 msgid = extract_long(cmdbuf, 0);
1938 extract_token(section, cmdbuf, 1, '|', sizeof section);
1939 CtdlOutputMsg(msgid, MT_MIME, 0, 1, 0, (section[0] ? section : NULL) );
1945 * Client tells us its preferred message format(s)
1947 void cmd_msgp(char *cmdbuf)
1949 safestrncpy(CC->preferred_formats, cmdbuf,
1950 sizeof(CC->preferred_formats));
1951 cprintf("%d ok\n", CIT_OK);
1956 * Open a component of a MIME message as a download file
1958 void cmd_opna(char *cmdbuf)
1961 char desired_section[128];
1963 msgid = extract_long(cmdbuf, 0);
1964 extract_token(desired_section, cmdbuf, 1, '|', sizeof desired_section);
1965 safestrncpy(CC->download_desired_section, desired_section,
1966 sizeof CC->download_desired_section);
1967 CtdlOutputMsg(msgid, MT_DOWNLOAD, 0, 1, 1, NULL);
1972 * Open a component of a MIME message and transmit it all at once
1974 void cmd_dlat(char *cmdbuf)
1977 char desired_section[128];
1979 msgid = extract_long(cmdbuf, 0);
1980 extract_token(desired_section, cmdbuf, 1, '|', sizeof desired_section);
1981 safestrncpy(CC->download_desired_section, desired_section,
1982 sizeof CC->download_desired_section);
1983 CtdlOutputMsg(msgid, MT_SPEW_SECTION, 0, 1, 1, NULL);
1988 * Save one or more message pointers into a specified room
1989 * (Returns 0 for success, nonzero for failure)
1990 * roomname may be NULL to use the current room
1992 * Note that the 'supplied_msg' field may be set to NULL, in which case
1993 * the message will be fetched from disk, by number, if we need to perform
1994 * replication checks. This adds an additional database read, so if the
1995 * caller already has the message in memory then it should be supplied. (Obviously
1996 * this mode of operation only works if we're saving a single message.)
1998 int CtdlSaveMsgPointersInRoom(char *roomname, long newmsgidlist[], int num_newmsgs,
1999 int do_repl_check, struct CtdlMessage *supplied_msg)
2002 char hold_rm[ROOMNAMELEN];
2003 struct cdbdata *cdbfr;
2006 long highest_msg = 0L;
2009 struct CtdlMessage *msg = NULL;
2011 long *msgs_to_be_merged = NULL;
2012 int num_msgs_to_be_merged = 0;
2015 "CtdlSaveMsgPointersInRoom(room=%s, num_msgs=%d, repl=%d)\n",
2016 roomname, num_newmsgs, do_repl_check);
2018 strcpy(hold_rm, CC->room.QRname);
2021 if (newmsgidlist == NULL) return(ERROR + INTERNAL_ERROR);
2022 if (num_newmsgs < 1) return(ERROR + INTERNAL_ERROR);
2023 if (num_newmsgs > 1) supplied_msg = NULL;
2025 /* Now the regular stuff */
2026 if (lgetroom(&CC->room,
2027 ((roomname != NULL) ? roomname : CC->room.QRname) )
2029 lprintf(CTDL_ERR, "No such room <%s>\n", roomname);
2030 return(ERROR + ROOM_NOT_FOUND);
2034 msgs_to_be_merged = malloc(sizeof(long) * num_newmsgs);
2035 num_msgs_to_be_merged = 0;
2038 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long));
2039 if (cdbfr == NULL) {
2043 msglist = (long *) cdbfr->ptr;
2044 cdbfr->ptr = NULL; /* CtdlSaveMsgPointerInRoom() now owns this memory */
2045 num_msgs = cdbfr->len / sizeof(long);
2050 /* Create a list of msgid's which were supplied by the caller, but do
2051 * not already exist in the target room. It is absolutely taboo to
2052 * have more than one reference to the same message in a room.
2054 for (i=0; i<num_newmsgs; ++i) {
2056 if (num_msgs > 0) for (j=0; j<num_msgs; ++j) {
2057 if (msglist[j] == newmsgidlist[i]) {
2062 msgs_to_be_merged[num_msgs_to_be_merged++] = newmsgidlist[i];
2066 lprintf(9, "%d unique messages to be merged\n", num_msgs_to_be_merged);
2069 * Now merge the new messages
2071 msglist = realloc(msglist, (sizeof(long) * (num_msgs + num_msgs_to_be_merged)) );
2072 if (msglist == NULL) {
2073 lprintf(CTDL_ALERT, "ERROR: can't realloc message list!\n");
2075 memcpy(&msglist[num_msgs], msgs_to_be_merged, (sizeof(long) * num_msgs_to_be_merged) );
2076 num_msgs += num_msgs_to_be_merged;
2078 /* Sort the message list, so all the msgid's are in order */
2079 num_msgs = sort_msglist(msglist, num_msgs);
2081 /* Determine the highest message number */
2082 highest_msg = msglist[num_msgs - 1];
2084 /* Write it back to disk. */
2085 cdb_store(CDB_MSGLISTS, &CC->room.QRnumber, (int)sizeof(long),
2086 msglist, (int)(num_msgs * sizeof(long)));
2088 /* Free up the memory we used. */
2091 /* Update the highest-message pointer and unlock the room. */
2092 CC->room.QRhighest = highest_msg;
2093 lputroom(&CC->room);
2095 /* Perform replication checks if necessary */
2096 if ( (DoesThisRoomNeedEuidIndexing(&CC->room)) && (do_repl_check) ) {
2097 lprintf(CTDL_DEBUG, "CtdlSaveMsgPointerInRoom() doing repl checks\n");
2099 for (i=0; i<num_msgs_to_be_merged; ++i) {
2100 msgid = msgs_to_be_merged[i];
2102 if (supplied_msg != NULL) {
2106 msg = CtdlFetchMessage(msgid, 0);
2110 ReplicationChecks(msg);
2112 /* If the message has an Exclusive ID, index that... */
2113 if (msg->cm_fields['E'] != NULL) {
2114 index_message_by_euid(msg->cm_fields['E'], &CC->room, msgid);
2117 /* Free up the memory we may have allocated */
2118 if (msg != supplied_msg) {
2119 CtdlFreeMessage(msg);
2127 lprintf(CTDL_DEBUG, "CtdlSaveMsgPointerInRoom() skips repl checks\n");
2130 /* Submit this room for net processing */
2131 network_queue_room(&CC->room, NULL);
2133 #ifdef HAVE_LIBSIEVE
2134 /* If this is someone's inbox, submit the room for sieve processing */
2135 if (!strcasecmp(&CC->room.QRname[11], MAILROOM)) {
2136 sieve_queue_room(&CC->room);
2138 #endif /* HAVE_LIBSIEVE */
2140 /* Go back to the room we were in before we wandered here... */
2141 getroom(&CC->room, hold_rm);
2143 /* Bump the reference count for all messages which were merged */
2144 for (i=0; i<num_msgs_to_be_merged; ++i) {
2145 AdjRefCount(msgs_to_be_merged[i], +1);
2148 /* Free up memory... */
2149 if (msgs_to_be_merged != NULL) {
2150 free(msgs_to_be_merged);
2153 /* Return success. */
2159 * This is the same as CtdlSaveMsgPointersInRoom() but it only accepts
2162 int CtdlSaveMsgPointerInRoom(char *roomname, long msgid,
2163 int do_repl_check, struct CtdlMessage *supplied_msg)
2165 return CtdlSaveMsgPointersInRoom(roomname, &msgid, 1, do_repl_check, supplied_msg);
2172 * Message base operation to save a new message to the message store
2173 * (returns new message number)
2175 * This is the back end for CtdlSubmitMsg() and should not be directly
2176 * called by server-side modules.
2179 long send_message(struct CtdlMessage *msg) {
2187 /* Get a new message number */
2188 newmsgid = get_new_message_number();
2189 snprintf(msgidbuf, sizeof msgidbuf, "%010ld@%s", newmsgid, config.c_fqdn);
2191 /* Generate an ID if we don't have one already */
2192 if (msg->cm_fields['I']==NULL) {
2193 msg->cm_fields['I'] = strdup(msgidbuf);
2196 /* If the message is big, set its body aside for storage elsewhere */
2197 if (msg->cm_fields['M'] != NULL) {
2198 if (strlen(msg->cm_fields['M']) > BIGMSG) {
2200 holdM = msg->cm_fields['M'];
2201 msg->cm_fields['M'] = NULL;
2205 /* Serialize our data structure for storage in the database */
2206 serialize_message(&smr, msg);
2209 msg->cm_fields['M'] = holdM;
2213 cprintf("%d Unable to serialize message\n",
2214 ERROR + INTERNAL_ERROR);
2218 /* Write our little bundle of joy into the message base */
2219 if (cdb_store(CDB_MSGMAIN, &newmsgid, (int)sizeof(long),
2220 smr.ser, smr.len) < 0) {
2221 lprintf(CTDL_ERR, "Can't store message\n");
2225 cdb_store(CDB_BIGMSGS,
2235 /* Free the memory we used for the serialized message */
2238 /* Return the *local* message ID to the caller
2239 * (even if we're storing an incoming network message)
2247 * Serialize a struct CtdlMessage into the format used on disk and network.
2249 * This function loads up a "struct ser_ret" (defined in server.h) which
2250 * contains the length of the serialized message and a pointer to the
2251 * serialized message in memory. THE LATTER MUST BE FREED BY THE CALLER.
2253 void serialize_message(struct ser_ret *ret, /* return values */
2254 struct CtdlMessage *msg) /* unserialized msg */
2256 size_t wlen, fieldlen;
2258 static char *forder = FORDER;
2261 * Check for valid message format
2263 if (is_valid_message(msg) == 0) {
2264 lprintf(CTDL_ERR, "serialize_message() aborting due to invalid message\n");
2271 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL)
2272 ret->len = ret->len +
2273 strlen(msg->cm_fields[(int)forder[i]]) + 2;
2275 ret->ser = malloc(ret->len);
2276 if (ret->ser == NULL) {
2277 lprintf(CTDL_ERR, "serialize_message() malloc(%ld) failed: %s\n",
2278 (long)ret->len, strerror(errno));
2285 ret->ser[1] = msg->cm_anon_type;
2286 ret->ser[2] = msg->cm_format_type;
2289 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL) {
2290 fieldlen = strlen(msg->cm_fields[(int)forder[i]]);
2291 ret->ser[wlen++] = (char)forder[i];
2292 safestrncpy((char *)&ret->ser[wlen], msg->cm_fields[(int)forder[i]], fieldlen+1);
2293 wlen = wlen + fieldlen + 1;
2295 if (ret->len != wlen) lprintf(CTDL_ERR, "ERROR: len=%ld wlen=%ld\n",
2296 (long)ret->len, (long)wlen);
2304 * Check to see if any messages already exist in the current room which
2305 * carry the same Exclusive ID as this one. If any are found, delete them.
2307 void ReplicationChecks(struct CtdlMessage *msg) {
2308 long old_msgnum = (-1L);
2310 if (DoesThisRoomNeedEuidIndexing(&CC->room) == 0) return;
2312 lprintf(CTDL_DEBUG, "Performing replication checks in <%s>\n",
2315 /* No exclusive id? Don't do anything. */
2316 if (msg == NULL) return;
2317 if (msg->cm_fields['E'] == NULL) return;
2318 if (strlen(msg->cm_fields['E']) == 0) return;
2319 /*lprintf(CTDL_DEBUG, "Exclusive ID: <%s> for room <%s>\n",
2320 msg->cm_fields['E'], CC->room.QRname);*/
2322 old_msgnum = locate_message_by_euid(msg->cm_fields['E'], &CC->room);
2323 if (old_msgnum > 0L) {
2324 lprintf(CTDL_DEBUG, "ReplicationChecks() replacing message %ld\n", old_msgnum);
2325 CtdlDeleteMessages(CC->room.QRname, &old_msgnum, 1, "");
2332 * Save a message to disk and submit it into the delivery system.
2334 long CtdlSubmitMsg(struct CtdlMessage *msg, /* message to save */
2335 struct recptypes *recps, /* recipients (if mail) */
2336 char *force /* force a particular room? */
2338 char submit_filename[128];
2339 char generated_timestamp[32];
2340 char hold_rm[ROOMNAMELEN];
2341 char actual_rm[ROOMNAMELEN];
2342 char force_room[ROOMNAMELEN];
2343 char content_type[SIZ]; /* We have to learn this */
2344 char recipient[SIZ];
2347 struct ctdluser userbuf;
2349 struct MetaData smi;
2350 FILE *network_fp = NULL;
2351 static int seqnum = 1;
2352 struct CtdlMessage *imsg = NULL;
2355 char *hold_R, *hold_D;
2356 char *collected_addresses = NULL;
2357 struct addresses_to_be_filed *aptr = NULL;
2358 char *saved_rfc822_version = NULL;
2359 int qualified_for_journaling = 0;
2361 lprintf(CTDL_DEBUG, "CtdlSubmitMsg() called\n");
2362 if (is_valid_message(msg) == 0) return(-1); /* self check */
2364 /* If this message has no timestamp, we take the liberty of
2365 * giving it one, right now.
2367 if (msg->cm_fields['T'] == NULL) {
2368 snprintf(generated_timestamp, sizeof generated_timestamp, "%ld", (long)time(NULL));
2369 msg->cm_fields['T'] = strdup(generated_timestamp);
2372 /* If this message has no path, we generate one.
2374 if (msg->cm_fields['P'] == NULL) {
2375 if (msg->cm_fields['A'] != NULL) {
2376 msg->cm_fields['P'] = strdup(msg->cm_fields['A']);
2377 for (a=0; a<strlen(msg->cm_fields['P']); ++a) {
2378 if (isspace(msg->cm_fields['P'][a])) {
2379 msg->cm_fields['P'][a] = ' ';
2384 msg->cm_fields['P'] = strdup("unknown");
2388 if (force == NULL) {
2389 strcpy(force_room, "");
2392 strcpy(force_room, force);
2395 /* Learn about what's inside, because it's what's inside that counts */
2396 if (msg->cm_fields['M'] == NULL) {
2397 lprintf(CTDL_ERR, "ERROR: attempt to save message with NULL body\n");
2401 switch (msg->cm_format_type) {
2403 strcpy(content_type, "text/x-citadel-variformat");
2406 strcpy(content_type, "text/plain");
2409 strcpy(content_type, "text/plain");
2410 mptr = bmstrcasestr(msg->cm_fields['M'], "Content-type: ");
2412 safestrncpy(content_type, &mptr[14],
2413 sizeof content_type);
2414 for (a = 0; a < strlen(content_type); ++a) {
2415 if ((content_type[a] == ';')
2416 || (content_type[a] == ' ')
2417 || (content_type[a] == 13)
2418 || (content_type[a] == 10)) {
2419 content_type[a] = 0;
2425 /* Goto the correct room */
2426 lprintf(CTDL_DEBUG, "Selected room %s\n", (recps) ? CC->room.QRname : SENTITEMS);
2427 strcpy(hold_rm, CC->room.QRname);
2428 strcpy(actual_rm, CC->room.QRname);
2429 if (recps != NULL) {
2430 strcpy(actual_rm, SENTITEMS);
2433 /* If the user is a twit, move to the twit room for posting */
2435 if (CC->user.axlevel == 2) {
2436 strcpy(hold_rm, actual_rm);
2437 strcpy(actual_rm, config.c_twitroom);
2438 lprintf(CTDL_DEBUG, "Diverting to twit room\n");
2442 /* ...or if this message is destined for Aide> then go there. */
2443 if (strlen(force_room) > 0) {
2444 strcpy(actual_rm, force_room);
2447 lprintf(CTDL_DEBUG, "Final selection: %s\n", actual_rm);
2448 if (strcasecmp(actual_rm, CC->room.QRname)) {
2449 /* getroom(&CC->room, actual_rm); */
2450 usergoto(actual_rm, 0, 1, NULL, NULL);
2454 * If this message has no O (room) field, generate one.
2456 if (msg->cm_fields['O'] == NULL) {
2457 msg->cm_fields['O'] = strdup(CC->room.QRname);
2460 /* Perform "before save" hooks (aborting if any return nonzero) */
2461 lprintf(CTDL_DEBUG, "Performing before-save hooks\n");
2462 if (PerformMessageHooks(msg, EVT_BEFORESAVE) > 0) return(-3);
2465 * If this message has an Exclusive ID, and the room is replication
2466 * checking enabled, then do replication checks.
2468 if (DoesThisRoomNeedEuidIndexing(&CC->room)) {
2469 ReplicationChecks(msg);
2472 /* Save it to disk */
2473 lprintf(CTDL_DEBUG, "Saving to disk\n");
2474 newmsgid = send_message(msg);
2475 if (newmsgid <= 0L) return(-5);
2477 /* Write a supplemental message info record. This doesn't have to
2478 * be a critical section because nobody else knows about this message
2481 lprintf(CTDL_DEBUG, "Creating MetaData record\n");
2482 memset(&smi, 0, sizeof(struct MetaData));
2483 smi.meta_msgnum = newmsgid;
2484 smi.meta_refcount = 0;
2485 safestrncpy(smi.meta_content_type, content_type,
2486 sizeof smi.meta_content_type);
2489 * Measure how big this message will be when rendered as RFC822.
2490 * We do this for two reasons:
2491 * 1. We need the RFC822 length for the new metadata record, so the
2492 * POP and IMAP services don't have to calculate message lengths
2493 * while the user is waiting (multiplied by potentially hundreds
2494 * or thousands of messages).
2495 * 2. If journaling is enabled, we will need an RFC822 version of the
2496 * message to attach to the journalized copy.
2498 if (CC->redirect_buffer != NULL) {
2499 lprintf(CTDL_ALERT, "CC->redirect_buffer is not NULL during message submission!\n");
2502 CC->redirect_buffer = malloc(SIZ);
2503 CC->redirect_len = 0;
2504 CC->redirect_alloc = SIZ;
2505 CtdlOutputPreLoadedMsg(msg, MT_RFC822, HEADERS_ALL, 0, 1);
2506 smi.meta_rfc822_length = CC->redirect_len;
2507 saved_rfc822_version = CC->redirect_buffer;
2508 CC->redirect_buffer = NULL;
2509 CC->redirect_len = 0;
2510 CC->redirect_alloc = 0;
2514 /* Now figure out where to store the pointers */
2515 lprintf(CTDL_DEBUG, "Storing pointers\n");
2517 /* If this is being done by the networker delivering a private
2518 * message, we want to BYPASS saving the sender's copy (because there
2519 * is no local sender; it would otherwise go to the Trashcan).
2521 if ((!CC->internal_pgm) || (recps == NULL)) {
2522 if (CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 1, msg) != 0) {
2523 lprintf(CTDL_ERR, "ERROR saving message pointer!\n");
2524 CtdlSaveMsgPointerInRoom(config.c_aideroom, newmsgid, 0, msg);
2528 /* For internet mail, drop a copy in the outbound queue room */
2530 if (recps->num_internet > 0) {
2531 CtdlSaveMsgPointerInRoom(SMTP_SPOOLOUT_ROOM, newmsgid, 0, msg);
2534 /* If other rooms are specified, drop them there too. */
2536 if (recps->num_room > 0)
2537 for (i=0; i<num_tokens(recps->recp_room, '|'); ++i) {
2538 extract_token(recipient, recps->recp_room, i,
2539 '|', sizeof recipient);
2540 lprintf(CTDL_DEBUG, "Delivering to room <%s>\n", recipient);
2541 CtdlSaveMsgPointerInRoom(recipient, newmsgid, 0, msg);
2544 /* Bump this user's messages posted counter. */
2545 lprintf(CTDL_DEBUG, "Updating user\n");
2546 lgetuser(&CC->user, CC->curr_user);
2547 CC->user.posted = CC->user.posted + 1;
2548 lputuser(&CC->user);
2550 /* If this is private, local mail, make a copy in the
2551 * recipient's mailbox and bump the reference count.
2554 if (recps->num_local > 0)
2555 for (i=0; i<num_tokens(recps->recp_local, '|'); ++i) {
2556 extract_token(recipient, recps->recp_local, i,
2557 '|', sizeof recipient);
2558 lprintf(CTDL_DEBUG, "Delivering private local mail to <%s>\n",
2560 if (getuser(&userbuf, recipient) == 0) {
2561 // Add a flag so the Funambol module knows its mail
2562 msg->cm_fields['W'] = strdup(recipient);
2563 MailboxName(actual_rm, sizeof actual_rm,
2564 &userbuf, MAILROOM);
2565 CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 0, msg);
2566 BumpNewMailCounter(userbuf.usernum);
2567 if (strlen(config.c_funambol_host) > 0) {
2568 /* Generate a instruction message for the Funambol notification
2569 server, in the same style as the SMTP queue */
2570 instr = malloc(SIZ * 2);
2571 snprintf(instr, SIZ * 2,
2572 "Content-type: %s\n\nmsgid|%ld\nsubmitted|%ld\n"
2574 SPOOLMIME, newmsgid, (long)time(NULL),
2575 msg->cm_fields['A'], msg->cm_fields['N']
2578 imsg = malloc(sizeof(struct CtdlMessage));
2579 memset(imsg, 0, sizeof(struct CtdlMessage));
2580 imsg->cm_magic = CTDLMESSAGE_MAGIC;
2581 imsg->cm_anon_type = MES_NORMAL;
2582 imsg->cm_format_type = FMT_RFC822;
2583 imsg->cm_fields['A'] = strdup("Citadel");
2584 imsg->cm_fields['J'] = strdup("do not journal");
2585 imsg->cm_fields['M'] = instr;
2586 imsg->cm_fields['W'] = strdup(recipient);
2587 CtdlSubmitMsg(imsg, NULL, FNBL_QUEUE_ROOM);
2588 CtdlFreeMessage(imsg);
2592 lprintf(CTDL_DEBUG, "No user <%s>\n", recipient);
2593 CtdlSaveMsgPointerInRoom(config.c_aideroom,
2598 /* Perform "after save" hooks */
2599 lprintf(CTDL_DEBUG, "Performing after-save hooks\n");
2600 PerformMessageHooks(msg, EVT_AFTERSAVE);
2602 /* For IGnet mail, we have to save a new copy into the spooler for
2603 * each recipient, with the R and D fields set to the recipient and
2604 * destination-node. This has two ugly side effects: all other
2605 * recipients end up being unlisted in this recipient's copy of the
2606 * message, and it has to deliver multiple messages to the same
2607 * node. We'll revisit this again in a year or so when everyone has
2608 * a network spool receiver that can handle the new style messages.
2611 if (recps->num_ignet > 0)
2612 for (i=0; i<num_tokens(recps->recp_ignet, '|'); ++i) {
2613 extract_token(recipient, recps->recp_ignet, i,
2614 '|', sizeof recipient);
2616 hold_R = msg->cm_fields['R'];
2617 hold_D = msg->cm_fields['D'];
2618 msg->cm_fields['R'] = malloc(SIZ);
2619 msg->cm_fields['D'] = malloc(128);
2620 extract_token(msg->cm_fields['R'], recipient, 0, '@', SIZ);
2621 extract_token(msg->cm_fields['D'], recipient, 1, '@', 128);
2623 serialize_message(&smr, msg);
2625 snprintf(submit_filename, sizeof submit_filename,
2626 "%s/netmail.%04lx.%04x.%04x",
2628 (long) getpid(), CC->cs_pid, ++seqnum);
2629 network_fp = fopen(submit_filename, "wb+");
2630 if (network_fp != NULL) {
2631 fwrite(smr.ser, smr.len, 1, network_fp);
2637 free(msg->cm_fields['R']);
2638 free(msg->cm_fields['D']);
2639 msg->cm_fields['R'] = hold_R;
2640 msg->cm_fields['D'] = hold_D;
2643 /* Go back to the room we started from */
2644 lprintf(CTDL_DEBUG, "Returning to original room %s\n", hold_rm);
2645 if (strcasecmp(hold_rm, CC->room.QRname))
2646 /* getroom(&CC->room, hold_rm); */
2647 usergoto(hold_rm, 0, 1, NULL, NULL);
2649 /* For internet mail, generate delivery instructions.
2650 * Yes, this is recursive. Deal with it. Infinite recursion does
2651 * not happen because the delivery instructions message does not
2652 * contain a recipient.
2655 if (recps->num_internet > 0) {
2656 lprintf(CTDL_DEBUG, "Generating delivery instructions\n");
2657 instr = malloc(SIZ * 2);
2658 snprintf(instr, SIZ * 2,
2659 "Content-type: %s\n\nmsgid|%ld\nsubmitted|%ld\n"
2661 SPOOLMIME, newmsgid, (long)time(NULL),
2662 msg->cm_fields['A'], msg->cm_fields['N']
2665 for (i=0; i<num_tokens(recps->recp_internet, '|'); ++i) {
2666 size_t tmp = strlen(instr);
2667 extract_token(recipient, recps->recp_internet,
2668 i, '|', sizeof recipient);
2669 snprintf(&instr[tmp], SIZ * 2 - tmp,
2670 "remote|%s|0||\n", recipient);
2673 imsg = malloc(sizeof(struct CtdlMessage));
2674 memset(imsg, 0, sizeof(struct CtdlMessage));
2675 imsg->cm_magic = CTDLMESSAGE_MAGIC;
2676 imsg->cm_anon_type = MES_NORMAL;
2677 imsg->cm_format_type = FMT_RFC822;
2678 imsg->cm_fields['A'] = strdup("Citadel");
2679 imsg->cm_fields['J'] = strdup("do not journal");
2680 imsg->cm_fields['M'] = instr;
2681 CtdlSubmitMsg(imsg, NULL, SMTP_SPOOLOUT_ROOM);
2682 CtdlFreeMessage(imsg);
2686 * Any addresses to harvest for someone's address book?
2688 if ( (CC->logged_in) && (recps != NULL) ) {
2689 collected_addresses = harvest_collected_addresses(msg);
2692 if (collected_addresses != NULL) {
2693 begin_critical_section(S_ATBF);
2694 aptr = (struct addresses_to_be_filed *)
2695 malloc(sizeof(struct addresses_to_be_filed));
2697 MailboxName(actual_rm, sizeof actual_rm,
2698 &CC->user, USERCONTACTSROOM);
2699 aptr->roomname = strdup(actual_rm);
2700 aptr->collected_addresses = collected_addresses;
2702 end_critical_section(S_ATBF);
2706 * Determine whether this message qualifies for journaling.
2708 if (msg->cm_fields['J'] != NULL) {
2709 qualified_for_journaling = 0;
2712 if (recps == NULL) {
2713 qualified_for_journaling = config.c_journal_pubmsgs;
2715 else if (recps->num_local + recps->num_ignet + recps->num_internet > 0) {
2716 qualified_for_journaling = config.c_journal_email;
2719 qualified_for_journaling = config.c_journal_pubmsgs;
2724 * Do we have to perform journaling? If so, hand off the saved
2725 * RFC822 version will be handed off to the journaler for background
2726 * submit. Otherwise, we have to free the memory ourselves.
2728 if (saved_rfc822_version != NULL) {
2729 if (qualified_for_journaling) {
2730 JournalBackgroundSubmit(msg, saved_rfc822_version, recps);
2733 free(saved_rfc822_version);
2746 * Convenience function for generating small administrative messages.
2748 void quickie_message(char *from, char *fromaddr, char *to, char *room, char *text,
2749 int format_type, char *subject)
2751 struct CtdlMessage *msg;
2752 struct recptypes *recp = NULL;
2754 msg = malloc(sizeof(struct CtdlMessage));
2755 memset(msg, 0, sizeof(struct CtdlMessage));
2756 msg->cm_magic = CTDLMESSAGE_MAGIC;
2757 msg->cm_anon_type = MES_NORMAL;
2758 msg->cm_format_type = format_type;
2761 msg->cm_fields['A'] = strdup(from);
2763 else if (fromaddr != NULL) {
2764 msg->cm_fields['A'] = strdup(fromaddr);
2765 if (strchr(msg->cm_fields['A'], '@')) {
2766 *strchr(msg->cm_fields['A'], '@') = 0;
2770 msg->cm_fields['A'] = strdup("Citadel");
2773 if (fromaddr != NULL) msg->cm_fields['F'] = strdup(fromaddr);
2774 if (room != NULL) msg->cm_fields['O'] = strdup(room);
2775 msg->cm_fields['N'] = strdup(NODENAME);
2777 msg->cm_fields['R'] = strdup(to);
2778 recp = validate_recipients(to);
2780 if (subject != NULL) {
2781 msg->cm_fields['U'] = strdup(subject);
2783 msg->cm_fields['M'] = strdup(text);
2785 CtdlSubmitMsg(msg, recp, room);
2786 CtdlFreeMessage(msg);
2787 if (recp != NULL) free(recp);
2793 * Back end function used by CtdlMakeMessage() and similar functions
2795 char *CtdlReadMessageBody(char *terminator, /* token signalling EOT */
2796 size_t maxlen, /* maximum message length */
2797 char *exist, /* if non-null, append to it;
2798 exist is ALWAYS freed */
2799 int crlf /* CRLF newlines instead of LF */
2803 size_t message_len = 0;
2804 size_t buffer_len = 0;
2811 if (exist == NULL) {
2818 message_len = strlen(exist);
2819 buffer_len = message_len + 4096;
2820 m = realloc(exist, buffer_len);
2827 /* Do we need to change leading ".." to "." for SMTP escaping? */
2828 if (!strcmp(terminator, ".")) {
2832 /* flush the input if we have nowhere to store it */
2837 /* read in the lines of message text one by one */
2839 if (client_getln(buf, (sizeof buf - 3)) < 1) finished = 1;
2840 if (!strcmp(buf, terminator)) finished = 1;
2842 strcat(buf, "\r\n");
2848 /* Unescape SMTP-style input of two dots at the beginning of the line */
2850 if (!strncmp(buf, "..", 2)) {
2851 strcpy(buf, &buf[1]);
2855 if ( (!flushing) && (!finished) ) {
2856 /* Measure the line */
2857 linelen = strlen(buf);
2859 /* augment the buffer if we have to */
2860 if ((message_len + linelen) >= buffer_len) {
2861 ptr = realloc(m, (buffer_len * 2) );
2862 if (ptr == NULL) { /* flush if can't allocate */
2865 buffer_len = (buffer_len * 2);
2867 lprintf(CTDL_DEBUG, "buffer_len is now %ld\n", (long)buffer_len);
2871 /* Add the new line to the buffer. NOTE: this loop must avoid
2872 * using functions like strcat() and strlen() because they
2873 * traverse the entire buffer upon every call, and doing that
2874 * for a multi-megabyte message slows it down beyond usability.
2876 strcpy(&m[message_len], buf);
2877 message_len += linelen;
2880 /* if we've hit the max msg length, flush the rest */
2881 if (message_len >= maxlen) flushing = 1;
2883 } while (!finished);
2891 * Build a binary message to be saved on disk.
2892 * (NOTE: if you supply 'preformatted_text', the buffer you give it
2893 * will become part of the message. This means you are no longer
2894 * responsible for managing that memory -- it will be freed along with
2895 * the rest of the fields when CtdlFreeMessage() is called.)
2898 struct CtdlMessage *CtdlMakeMessage(
2899 struct ctdluser *author, /* author's user structure */
2900 char *recipient, /* NULL if it's not mail */
2901 char *recp_cc, /* NULL if it's not mail */
2902 char *room, /* room where it's going */
2903 int type, /* see MES_ types in header file */
2904 int format_type, /* variformat, plain text, MIME... */
2905 char *fake_name, /* who we're masquerading as */
2906 char *subject, /* Subject (optional) */
2907 char *supplied_euid, /* ...or NULL if this is irrelevant */
2908 char *preformatted_text /* ...or NULL to read text from client */
2910 char dest_node[256];
2912 struct CtdlMessage *msg;
2914 msg = malloc(sizeof(struct CtdlMessage));
2915 memset(msg, 0, sizeof(struct CtdlMessage));
2916 msg->cm_magic = CTDLMESSAGE_MAGIC;
2917 msg->cm_anon_type = type;
2918 msg->cm_format_type = format_type;
2920 /* Don't confuse the poor folks if it's not routed mail. */
2921 strcpy(dest_node, "");
2926 snprintf(buf, sizeof buf, "cit%ld", author->usernum); /* Path */
2927 msg->cm_fields['P'] = strdup(buf);
2929 snprintf(buf, sizeof buf, "%ld", (long)time(NULL)); /* timestamp */
2930 msg->cm_fields['T'] = strdup(buf);
2932 if (fake_name[0]) /* author */
2933 msg->cm_fields['A'] = strdup(fake_name);
2935 msg->cm_fields['A'] = strdup(author->fullname);
2937 if (CC->room.QRflags & QR_MAILBOX) { /* room */
2938 msg->cm_fields['O'] = strdup(&CC->room.QRname[11]);
2941 msg->cm_fields['O'] = strdup(CC->room.QRname);
2944 msg->cm_fields['N'] = strdup(NODENAME); /* nodename */
2945 msg->cm_fields['H'] = strdup(HUMANNODE); /* hnodename */
2947 if (recipient[0] != 0) {
2948 msg->cm_fields['R'] = strdup(recipient);
2950 if (recp_cc[0] != 0) {
2951 msg->cm_fields['Y'] = strdup(recp_cc);
2953 if (dest_node[0] != 0) {
2954 msg->cm_fields['D'] = strdup(dest_node);
2957 if ( (author == &CC->user) && (strlen(CC->cs_inet_email) > 0) ) {
2958 msg->cm_fields['F'] = strdup(CC->cs_inet_email);
2961 if (subject != NULL) {
2964 length = strlen(subject);
2970 while ((subject[i] != '\0') &&
2971 (IsAscii = isascii(subject[i]) != 0 ))
2974 msg->cm_fields['U'] = strdup(subject);
2975 else /* ok, we've got utf8 in the string. */
2977 msg->cm_fields['U'] = rfc2047encode(subject, length);
2983 if (supplied_euid != NULL) {
2984 msg->cm_fields['E'] = strdup(supplied_euid);
2987 if (preformatted_text != NULL) {
2988 msg->cm_fields['M'] = preformatted_text;
2991 msg->cm_fields['M'] = CtdlReadMessageBody("000", config.c_maxmsglen, NULL, 0);
2999 * Check to see whether we have permission to post a message in the current
3000 * room. Returns a *CITADEL ERROR CODE* and puts a message in errmsgbuf, or
3001 * returns 0 on success.
3003 int CtdlDoIHavePermissionToPostInThisRoom(char *errmsgbuf, size_t n) {
3006 if (!(CC->logged_in)) {
3007 snprintf(errmsgbuf, n, "Not logged in.");
3008 return (ERROR + NOT_LOGGED_IN);
3011 if ((CC->user.axlevel < 2)
3012 && ((CC->room.QRflags & QR_MAILBOX) == 0)) {
3013 snprintf(errmsgbuf, n, "Need to be validated to enter "
3014 "(except in %s> to sysop)", MAILROOM);
3015 return (ERROR + HIGHER_ACCESS_REQUIRED);
3018 CtdlRoomAccess(&CC->room, &CC->user, &ra, NULL);
3019 if (!(ra & UA_POSTALLOWED)) {
3020 snprintf(errmsgbuf, n, "Higher access is required to post in this room.");
3021 return (ERROR + HIGHER_ACCESS_REQUIRED);
3024 strcpy(errmsgbuf, "Ok");
3030 * Check to see if the specified user has Internet mail permission
3031 * (returns nonzero if permission is granted)
3033 int CtdlCheckInternetMailPermission(struct ctdluser *who) {
3035 /* Do not allow twits to send Internet mail */
3036 if (who->axlevel <= 2) return(0);
3038 /* Globally enabled? */
3039 if (config.c_restrict == 0) return(1);
3041 /* User flagged ok? */
3042 if (who->flags & US_INTERNET) return(2);
3044 /* Aide level access? */
3045 if (who->axlevel >= 6) return(3);
3047 /* No mail for you! */
3053 * Validate recipients, count delivery types and errors, and handle aliasing
3054 * FIXME check for dupes!!!!!
3055 * Returns 0 if all addresses are ok, ret->num_error = -1 if no addresses
3056 * were specified, or the number of addresses found invalid.
3057 * caller needs to free the result.
3059 struct recptypes *validate_recipients(char *supplied_recipients) {
3060 struct recptypes *ret;
3061 char recipients[SIZ];
3062 char this_recp[256];
3063 char this_recp_cooked[256];
3069 struct ctdluser tempUS;
3070 struct ctdlroom tempQR;
3074 ret = (struct recptypes *) malloc(sizeof(struct recptypes));
3075 if (ret == NULL) return(NULL);
3076 memset(ret, 0, sizeof(struct recptypes));
3079 ret->num_internet = 0;
3084 if (supplied_recipients == NULL) {
3085 strcpy(recipients, "");
3088 safestrncpy(recipients, supplied_recipients, sizeof recipients);
3091 /* Change all valid separator characters to commas */
3092 for (i=0; i<strlen(recipients); ++i) {
3093 if ((recipients[i] == ';') || (recipients[i] == '|')) {
3094 recipients[i] = ',';
3098 /* Now start extracting recipients... */
3100 while (strlen(recipients) > 0) {
3102 for (i=0; i<=strlen(recipients); ++i) {
3103 if (recipients[i] == '\"') in_quotes = 1 - in_quotes;
3104 if ( ( (recipients[i] == ',') && (!in_quotes) ) || (recipients[i] == 0) ) {
3105 safestrncpy(this_recp, recipients, i+1);
3107 if (recipients[i] == ',') {
3108 strcpy(recipients, &recipients[i+1]);
3111 strcpy(recipients, "");
3118 lprintf(CTDL_DEBUG, "Evaluating recipient #%d: %s\n", num_recps, this_recp);
3120 mailtype = alias(this_recp);
3121 mailtype = alias(this_recp);
3122 mailtype = alias(this_recp);
3123 for (j=0; j<=strlen(this_recp); ++j) {
3124 if (this_recp[j]=='_') {
3125 this_recp_cooked[j] = ' ';
3128 this_recp_cooked[j] = this_recp[j];
3134 if (!strcasecmp(this_recp, "sysop")) {
3136 strcpy(this_recp, config.c_aideroom);
3137 if (strlen(ret->recp_room) > 0) {
3138 strcat(ret->recp_room, "|");
3140 strcat(ret->recp_room, this_recp);
3142 else if (getuser(&tempUS, this_recp) == 0) {
3144 strcpy(this_recp, tempUS.fullname);
3145 if (strlen(ret->recp_local) > 0) {
3146 strcat(ret->recp_local, "|");
3148 strcat(ret->recp_local, this_recp);
3150 else if (getuser(&tempUS, this_recp_cooked) == 0) {
3152 strcpy(this_recp, tempUS.fullname);
3153 if (strlen(ret->recp_local) > 0) {
3154 strcat(ret->recp_local, "|");
3156 strcat(ret->recp_local, this_recp);
3158 else if ( (!strncasecmp(this_recp, "room_", 5))
3159 && (!getroom(&tempQR, &this_recp_cooked[5])) ) {
3161 if (strlen(ret->recp_room) > 0) {
3162 strcat(ret->recp_room, "|");
3164 strcat(ret->recp_room, &this_recp_cooked[5]);
3172 /* Yes, you're reading this correctly: if the target
3173 * domain points back to the local system or an attached
3174 * Citadel directory, the address is invalid. That's
3175 * because if the address were valid, we would have
3176 * already translated it to a local address by now.
3178 if (IsDirectory(this_recp)) {
3183 ++ret->num_internet;
3184 if (strlen(ret->recp_internet) > 0) {
3185 strcat(ret->recp_internet, "|");
3187 strcat(ret->recp_internet, this_recp);
3192 if (strlen(ret->recp_ignet) > 0) {
3193 strcat(ret->recp_ignet, "|");
3195 strcat(ret->recp_ignet, this_recp);
3203 if (strlen(ret->errormsg) == 0) {
3204 snprintf(append, sizeof append,
3205 "Invalid recipient: %s",
3209 snprintf(append, sizeof append,
3212 if ( (strlen(ret->errormsg) + strlen(append)) < SIZ) {
3213 strcat(ret->errormsg, append);
3217 if (strlen(ret->display_recp) == 0) {
3218 strcpy(append, this_recp);
3221 snprintf(append, sizeof append, ", %s",
3224 if ( (strlen(ret->display_recp)+strlen(append)) < SIZ) {
3225 strcat(ret->display_recp, append);
3230 if ((ret->num_local + ret->num_internet + ret->num_ignet +
3231 ret->num_room + ret->num_error) == 0) {
3232 ret->num_error = (-1);
3233 strcpy(ret->errormsg, "No recipients specified.");
3236 lprintf(CTDL_DEBUG, "validate_recipients()\n");
3237 lprintf(CTDL_DEBUG, " local: %d <%s>\n", ret->num_local, ret->recp_local);
3238 lprintf(CTDL_DEBUG, " room: %d <%s>\n", ret->num_room, ret->recp_room);
3239 lprintf(CTDL_DEBUG, " inet: %d <%s>\n", ret->num_internet, ret->recp_internet);
3240 lprintf(CTDL_DEBUG, " ignet: %d <%s>\n", ret->num_ignet, ret->recp_ignet);
3241 lprintf(CTDL_DEBUG, " error: %d <%s>\n", ret->num_error, ret->errormsg);
3249 * message entry - mode 0 (normal)
3251 void cmd_ent0(char *entargs)
3257 char supplied_euid[128];
3259 int format_type = 0;
3260 char newusername[256];
3261 struct CtdlMessage *msg;
3265 struct recptypes *valid = NULL;
3266 struct recptypes *valid_to = NULL;
3267 struct recptypes *valid_cc = NULL;
3268 struct recptypes *valid_bcc = NULL;
3275 post = extract_int(entargs, 0);
3276 extract_token(recp, entargs, 1, '|', sizeof recp);
3277 anon_flag = extract_int(entargs, 2);
3278 format_type = extract_int(entargs, 3);
3279 extract_token(subject, entargs, 4, '|', sizeof subject);
3280 extract_token(newusername, entargs, 5, '|', sizeof newusername);
3281 do_confirm = extract_int(entargs, 6);
3282 extract_token(cc, entargs, 7, '|', sizeof cc);
3283 extract_token(bcc, entargs, 8, '|', sizeof bcc);
3284 switch(CC->room.QRdefaultview) {
3287 extract_token(supplied_euid, entargs, 9, '|', sizeof supplied_euid);
3290 supplied_euid[0] = 0;
3294 /* first check to make sure the request is valid. */
3296 err = CtdlDoIHavePermissionToPostInThisRoom(errmsg, sizeof errmsg);
3299 cprintf("%d %s\n", err, errmsg);
3303 /* Check some other permission type things. */
3305 if (strlen(newusername) == 0)
3307 strcpy(newusername, CC->user.fullname);
3309 if ( (CC->user.axlevel < 6)
3310 && (strcasecmp(newusername, CC->user.fullname))
3311 && (strcasecmp(newusername, CC->cs_inet_fn))
3313 cprintf("%d You don't have permission to author messages as '%s'.\n",
3314 ERROR + HIGHER_ACCESS_REQUIRED,
3320 CC->cs_flags |= CS_POSTING;
3322 /* In the Mail> room we have to behave a little differently --
3323 * make sure the user has specified at least one recipient. Then
3324 * validate the recipient(s).
3326 if ( (CC->room.QRflags & QR_MAILBOX)
3327 && (!strcasecmp(&CC->room.QRname[11], MAILROOM)) ) {
3329 if (CC->user.axlevel < 2) {
3330 strcpy(recp, "sysop");
3335 valid_to = validate_recipients(recp);
3336 if (valid_to->num_error > 0) {
3337 cprintf("%d Invalid recipient (To)\n", ERROR + NO_SUCH_USER);
3342 valid_cc = validate_recipients(cc);
3343 if (valid_cc->num_error > 0) {
3344 cprintf("%d Invalid recipient (CC)\n", ERROR + NO_SUCH_USER);
3350 valid_bcc = validate_recipients(bcc);
3351 if (valid_bcc->num_error > 0) {
3352 cprintf("%d Invalid recipient (BCC)\n", ERROR + NO_SUCH_USER);
3359 /* Recipient required, but none were specified */
3360 if ( (valid_to->num_error < 0) && (valid_cc->num_error < 0) && (valid_bcc->num_error < 0) ) {
3364 cprintf("%d At least one recipient is required.\n", ERROR + NO_SUCH_USER);
3368 if (valid_to->num_internet + valid_cc->num_internet + valid_bcc->num_internet > 0) {
3369 if (CtdlCheckInternetMailPermission(&CC->user)==0) {
3370 cprintf("%d You do not have permission "
3371 "to send Internet mail.\n",
3372 ERROR + HIGHER_ACCESS_REQUIRED);
3380 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)
3381 && (CC->user.axlevel < 4) ) {
3382 cprintf("%d Higher access required for network mail.\n",
3383 ERROR + HIGHER_ACCESS_REQUIRED);
3390 if ((RESTRICT_INTERNET == 1)
3391 && (valid_to->num_internet + valid_cc->num_internet + valid_bcc->num_internet > 0)
3392 && ((CC->user.flags & US_INTERNET) == 0)
3393 && (!CC->internal_pgm)) {
3394 cprintf("%d You don't have access to Internet mail.\n",
3395 ERROR + HIGHER_ACCESS_REQUIRED);
3404 /* Is this a room which has anonymous-only or anonymous-option? */
3405 anonymous = MES_NORMAL;
3406 if (CC->room.QRflags & QR_ANONONLY) {
3407 anonymous = MES_ANONONLY;
3409 if (CC->room.QRflags & QR_ANONOPT) {
3410 if (anon_flag == 1) { /* only if the user requested it */
3411 anonymous = MES_ANONOPT;
3415 if ((CC->room.QRflags & QR_MAILBOX) == 0) {
3419 /* If we're only checking the validity of the request, return
3420 * success without creating the message.
3423 cprintf("%d %s\n", CIT_OK,
3424 ((valid_to != NULL) ? valid_to->display_recp : "") );
3431 /* We don't need these anymore because we'll do it differently below */
3436 /* Read in the message from the client. */
3438 cprintf("%d send message\n", START_CHAT_MODE);
3440 cprintf("%d send message\n", SEND_LISTING);
3443 msg = CtdlMakeMessage(&CC->user, recp, cc,
3444 CC->room.QRname, anonymous, format_type,
3445 newusername, subject,
3446 ((strlen(supplied_euid) > 0) ? supplied_euid : NULL),
3449 /* Put together one big recipients struct containing to/cc/bcc all in
3450 * one. This is for the envelope.
3452 char *all_recps = malloc(SIZ * 3);
3453 strcpy(all_recps, recp);
3454 if (strlen(cc) > 0) {
3455 if (strlen(all_recps) > 0) {
3456 strcat(all_recps, ",");
3458 strcat(all_recps, cc);
3460 if (strlen(bcc) > 0) {
3461 if (strlen(all_recps) > 0) {
3462 strcat(all_recps, ",");
3464 strcat(all_recps, bcc);
3466 if (strlen(all_recps) > 0) {
3467 valid = validate_recipients(all_recps);
3475 msgnum = CtdlSubmitMsg(msg, valid, "");
3478 cprintf("%ld\n", msgnum);
3480 cprintf("Message accepted.\n");
3483 cprintf("Internal error.\n");
3485 if (msg->cm_fields['E'] != NULL) {
3486 cprintf("%s\n", msg->cm_fields['E']);
3493 CtdlFreeMessage(msg);
3495 if (valid != NULL) {
3504 * API function to delete messages which match a set of criteria
3505 * (returns the actual number of messages deleted)
3507 int CtdlDeleteMessages(char *room_name, /* which room */
3508 long *dmsgnums, /* array of msg numbers to be deleted */
3509 int num_dmsgnums, /* number of msgs to be deleted, or 0 for "any" */
3510 char *content_type /* or "" for any. regular expressions expected. */
3513 struct ctdlroom qrbuf;
3514 struct cdbdata *cdbfr;
3515 long *msglist = NULL;
3516 long *dellist = NULL;
3519 int num_deleted = 0;
3521 struct MetaData smi;
3525 if (content_type) if (strlen(content_type) > 0) {
3526 regcomp(&re, content_type, 0);
3528 lprintf(CTDL_DEBUG, "CtdlDeleteMessages(%s, %d msgs, %s)\n",
3529 room_name, num_dmsgnums, content_type);
3531 /* get room record, obtaining a lock... */
3532 if (lgetroom(&qrbuf, room_name) != 0) {
3533 lprintf(CTDL_ERR, "CtdlDeleteMessages(): Room <%s> not found\n",
3535 return (0); /* room not found */
3537 cdbfr = cdb_fetch(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long));
3539 if (cdbfr != NULL) {
3540 dellist = malloc(cdbfr->len);
3541 msglist = (long *) cdbfr->ptr;
3542 cdbfr->ptr = NULL; /* CtdlDeleteMessages() now owns this memory */
3543 num_msgs = cdbfr->len / sizeof(long);
3547 for (i = 0; i < num_msgs; ++i) {
3550 /* Set/clear a bit for each criterion */
3552 /* 0 messages in the list or a null list means that we are
3553 * interested in deleting any messages which meet the other criteria.
3555 if ((num_dmsgnums == 0) || (dmsgnums == NULL)) {
3556 delete_this |= 0x01;
3559 for (j=0; j<num_dmsgnums; ++j) {
3560 if (msglist[i] == dmsgnums[j]) {
3561 delete_this |= 0x01;
3566 if (strlen(content_type) == 0) {
3567 delete_this |= 0x02;
3569 GetMetaData(&smi, msglist[i]);
3570 if (regexec(&re, smi.meta_content_type, 1, &pm, 0) == 0) {
3571 delete_this |= 0x02;
3575 /* Delete message only if all bits are set */
3576 if (delete_this == 0x03) {
3577 dellist[num_deleted++] = msglist[i];
3582 num_msgs = sort_msglist(msglist, num_msgs);
3583 cdb_store(CDB_MSGLISTS, &qrbuf.QRnumber, (int)sizeof(long),
3584 msglist, (int)(num_msgs * sizeof(long)));
3586 qrbuf.QRhighest = msglist[num_msgs - 1];
3590 /* Go through the messages we pulled out of the index, and decrement
3591 * their reference counts by 1. If this is the only room the message
3592 * was in, the reference count will reach zero and the message will
3593 * automatically be deleted from the database. We do this in a
3594 * separate pass because there might be plug-in hooks getting called,
3595 * and we don't want that happening during an S_ROOMS critical
3598 if (num_deleted) for (i=0; i<num_deleted; ++i) {
3599 PerformDeleteHooks(qrbuf.QRname, dellist[i]);
3600 AdjRefCount(dellist[i], -1);
3603 /* Now free the memory we used, and go away. */
3604 if (msglist != NULL) free(msglist);
3605 if (dellist != NULL) free(dellist);
3606 lprintf(CTDL_DEBUG, "%d message(s) deleted.\n", num_deleted);
3607 return (num_deleted);
3613 * Check whether the current user has permission to delete messages from
3614 * the current room (returns 1 for yes, 0 for no)
3616 int CtdlDoIHavePermissionToDeleteMessagesFromThisRoom(void) {
3618 CtdlRoomAccess(&CC->room, &CC->user, &ra, NULL);
3619 if (ra & UA_DELETEALLOWED) return(1);
3627 * Delete message from current room
3629 void cmd_dele(char *args)
3638 extract_token(msgset, args, 0, '|', sizeof msgset);
3639 num_msgs = num_tokens(msgset, ',');
3641 cprintf("%d Nothing to do.\n", CIT_OK);
3645 if (CtdlDoIHavePermissionToDeleteMessagesFromThisRoom() == 0) {
3646 cprintf("%d Higher access required.\n",
3647 ERROR + HIGHER_ACCESS_REQUIRED);
3652 * Build our message set to be moved/copied
3654 msgs = malloc(num_msgs * sizeof(long));
3655 for (i=0; i<num_msgs; ++i) {
3656 extract_token(msgtok, msgset, i, ',', sizeof msgtok);
3657 msgs[i] = atol(msgtok);
3660 num_deleted = CtdlDeleteMessages(CC->room.QRname, msgs, num_msgs, "");
3664 cprintf("%d %d message%s deleted.\n", CIT_OK,
3665 num_deleted, ((num_deleted != 1) ? "s" : ""));
3667 cprintf("%d Message not found.\n", ERROR + MESSAGE_NOT_FOUND);
3673 * Back end API function for moves and deletes (multiple messages)
3675 int CtdlCopyMsgsToRoom(long *msgnums, int num_msgs, char *dest) {
3678 err = CtdlSaveMsgPointersInRoom(dest, msgnums, num_msgs, 1, NULL);
3679 if (err != 0) return(err);
3688 * move or copy a message to another room
3690 void cmd_move(char *args)
3697 char targ[ROOMNAMELEN];
3698 struct ctdlroom qtemp;
3705 extract_token(msgset, args, 0, '|', sizeof msgset);
3706 num_msgs = num_tokens(msgset, ',');
3708 cprintf("%d Nothing to do.\n", CIT_OK);
3712 extract_token(targ, args, 1, '|', sizeof targ);
3713 convert_room_name_macros(targ, sizeof targ);
3714 targ[ROOMNAMELEN - 1] = 0;
3715 is_copy = extract_int(args, 2);
3717 if (getroom(&qtemp, targ) != 0) {
3718 cprintf("%d '%s' does not exist.\n",
3719 ERROR + ROOM_NOT_FOUND, targ);
3723 getuser(&CC->user, CC->curr_user);
3724 CtdlRoomAccess(&qtemp, &CC->user, &ra, NULL);
3726 /* Check for permission to perform this operation.
3727 * Remember: "CC->room" is source, "qtemp" is target.
3731 /* Aides can move/copy */
3732 if (CC->user.axlevel >= 6) permit = 1;
3734 /* Room aides can move/copy */
3735 if (CC->user.usernum == CC->room.QRroomaide) permit = 1;
3737 /* Permit move/copy from personal rooms */
3738 if ((CC->room.QRflags & QR_MAILBOX)
3739 && (qtemp.QRflags & QR_MAILBOX)) permit = 1;
3741 /* Permit only copy from public to personal room */
3743 && (!(CC->room.QRflags & QR_MAILBOX))
3744 && (qtemp.QRflags & QR_MAILBOX)) permit = 1;
3746 /* User must have access to target room */
3747 if (!(ra & UA_KNOWN)) permit = 0;
3750 cprintf("%d Higher access required.\n",
3751 ERROR + HIGHER_ACCESS_REQUIRED);
3756 * Build our message set to be moved/copied
3758 msgs = malloc(num_msgs * sizeof(long));
3759 for (i=0; i<num_msgs; ++i) {
3760 extract_token(msgtok, msgset, i, ',', sizeof msgtok);
3761 msgs[i] = atol(msgtok);
3767 err = CtdlCopyMsgsToRoom(msgs, num_msgs, targ);
3769 cprintf("%d Cannot store message(s) in %s: error %d\n",
3775 /* Now delete the message from the source room,
3776 * if this is a 'move' rather than a 'copy' operation.
3779 CtdlDeleteMessages(CC->room.QRname, msgs, num_msgs, "");
3783 cprintf("%d Message(s) %s.\n", CIT_OK, (is_copy ? "copied" : "moved") );
3789 * GetMetaData() - Get the supplementary record for a message
3791 void GetMetaData(struct MetaData *smibuf, long msgnum)
3794 struct cdbdata *cdbsmi;
3797 memset(smibuf, 0, sizeof(struct MetaData));
3798 smibuf->meta_msgnum = msgnum;
3799 smibuf->meta_refcount = 1; /* Default reference count is 1 */
3801 /* Use the negative of the message number for its supp record index */
3802 TheIndex = (0L - msgnum);
3804 cdbsmi = cdb_fetch(CDB_MSGMAIN, &TheIndex, sizeof(long));
3805 if (cdbsmi == NULL) {
3806 return; /* record not found; go with defaults */
3808 memcpy(smibuf, cdbsmi->ptr,
3809 ((cdbsmi->len > sizeof(struct MetaData)) ?
3810 sizeof(struct MetaData) : cdbsmi->len));
3817 * PutMetaData() - (re)write supplementary record for a message
3819 void PutMetaData(struct MetaData *smibuf)
3823 /* Use the negative of the message number for the metadata db index */
3824 TheIndex = (0L - smibuf->meta_msgnum);
3826 cdb_store(CDB_MSGMAIN,
3827 &TheIndex, (int)sizeof(long),
3828 smibuf, (int)sizeof(struct MetaData));
3833 * AdjRefCount - submit an adjustment to the reference count for a message.
3834 * (These are just queued -- we actually process them later.)
3836 void AdjRefCount(long msgnum, int incr)
3838 struct arcq new_arcq;
3840 begin_critical_section(S_SUPPMSGMAIN);
3841 if (arcfp == NULL) {
3842 arcfp = fopen(file_arcq, "ab+");
3844 end_critical_section(S_SUPPMSGMAIN);
3846 /* msgnum < 0 means that we're trying to close the file */
3848 lprintf(CTDL_DEBUG, "Closing the AdjRefCount queue file\n");
3849 begin_critical_section(S_SUPPMSGMAIN);
3850 if (arcfp != NULL) {
3854 end_critical_section(S_SUPPMSGMAIN);
3859 * If we can't open the queue, perform the operation synchronously.
3861 if (arcfp == NULL) {
3862 TDAP_AdjRefCount(msgnum, incr);
3866 new_arcq.arcq_msgnum = msgnum;
3867 new_arcq.arcq_delta = incr;
3868 fwrite(&new_arcq, sizeof(struct arcq), 1, arcfp);
3876 * TDAP_ProcessAdjRefCountQueue()
3878 * Process the queue of message count adjustments that was created by calls
3879 * to AdjRefCount() ... by reading the queue and calling TDAP_AdjRefCount()
3880 * for each one. This should be an "off hours" operation.
3882 int TDAP_ProcessAdjRefCountQueue(void)
3884 char file_arcq_temp[PATH_MAX];
3887 struct arcq arcq_rec;
3888 int num_records_processed = 0;
3890 snprintf(file_arcq_temp, sizeof file_arcq_temp, "%s2", file_arcq);
3892 begin_critical_section(S_SUPPMSGMAIN);
3893 if (arcfp != NULL) {
3898 r = link(file_arcq, file_arcq_temp);
3900 lprintf(CTDL_CRIT, "%s: %s\n", file_arcq_temp, strerror(errno));
3901 end_critical_section(S_SUPPMSGMAIN);
3902 return(num_records_processed);
3906 end_critical_section(S_SUPPMSGMAIN);
3908 fp = fopen(file_arcq_temp, "rb");
3910 lprintf(CTDL_CRIT, "%s: %s\n", file_arcq_temp, strerror(errno));
3911 return(num_records_processed);
3914 while (fread(&arcq_rec, sizeof(struct arcq), 1, fp) == 1) {
3915 TDAP_AdjRefCount(arcq_rec.arcq_msgnum, arcq_rec.arcq_delta);
3916 ++num_records_processed;
3920 r = unlink(file_arcq_temp);
3922 lprintf(CTDL_CRIT, "%s: %s\n", file_arcq_temp, strerror(errno));
3925 return(num_records_processed);
3931 * TDAP_AdjRefCount - adjust the reference count for a message.
3932 * This one does it "for real" because it's called by
3933 * the autopurger function that processes the queue
3934 * created by AdjRefCount(). If a message's reference
3935 * count becomes zero, we also delete the message from
3936 * disk and de-index it.
3938 void TDAP_AdjRefCount(long msgnum, int incr)
3941 struct MetaData smi;
3944 /* This is a *tight* critical section; please keep it that way, as
3945 * it may get called while nested in other critical sections.
3946 * Complicating this any further will surely cause deadlock!
3948 begin_critical_section(S_SUPPMSGMAIN);
3949 GetMetaData(&smi, msgnum);
3950 smi.meta_refcount += incr;
3952 end_critical_section(S_SUPPMSGMAIN);
3953 lprintf(CTDL_DEBUG, "msg %ld ref count delta %d, is now %d\n",
3954 msgnum, incr, smi.meta_refcount);
3956 /* If the reference count is now zero, delete the message
3957 * (and its supplementary record as well).
3959 if (smi.meta_refcount == 0) {
3960 lprintf(CTDL_DEBUG, "Deleting message <%ld>\n", msgnum);
3962 /* Remove from fulltext index */
3963 if (config.c_enable_fulltext) {
3964 ft_index_message(msgnum, 0);
3967 /* Remove from message base */
3969 cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long));
3970 cdb_delete(CDB_BIGMSGS, &delnum, (int)sizeof(long));
3972 /* Remove metadata record */
3973 delnum = (0L - msgnum);
3974 cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long));
3980 * Write a generic object to this room
3982 * Note: this could be much more efficient. Right now we use two temporary
3983 * files, and still pull the message into memory as with all others.
3985 void CtdlWriteObject(char *req_room, /* Room to stuff it in */
3986 char *content_type, /* MIME type of this object */
3987 char *tempfilename, /* Where to fetch it from */
3988 struct ctdluser *is_mailbox, /* Mailbox room? */
3989 int is_binary, /* Is encoding necessary? */
3990 int is_unique, /* Del others of this type? */
3991 unsigned int flags /* Internal save flags */
3996 struct ctdlroom qrbuf;
3997 char roomname[ROOMNAMELEN];
3998 struct CtdlMessage *msg;
4000 char *raw_message = NULL;
4001 char *encoded_message = NULL;
4002 off_t raw_length = 0;
4004 if (is_mailbox != NULL) {
4005 MailboxName(roomname, sizeof roomname, is_mailbox, req_room);
4008 safestrncpy(roomname, req_room, sizeof(roomname));
4011 fp = fopen(tempfilename, "rb");
4013 lprintf(CTDL_CRIT, "Cannot open %s: %s\n",
4014 tempfilename, strerror(errno));
4017 fseek(fp, 0L, SEEK_END);
4018 raw_length = ftell(fp);
4020 lprintf(CTDL_DEBUG, "Raw length is %ld\n", (long)raw_length);
4022 raw_message = malloc((size_t)raw_length + 2);
4023 fread(raw_message, (size_t)raw_length, 1, fp);
4027 encoded_message = malloc((size_t)
4028 (((raw_length * 134) / 100) + 4096 ) );
4031 encoded_message = malloc((size_t)(raw_length + 4096));
4034 sprintf(encoded_message, "Content-type: %s\n", content_type);
4037 sprintf(&encoded_message[strlen(encoded_message)],
4038 "Content-transfer-encoding: base64\n\n"
4042 sprintf(&encoded_message[strlen(encoded_message)],
4043 "Content-transfer-encoding: 7bit\n\n"
4049 &encoded_message[strlen(encoded_message)],
4055 raw_message[raw_length] = 0;
4057 &encoded_message[strlen(encoded_message)],
4065 lprintf(CTDL_DEBUG, "Allocating\n");
4066 msg = malloc(sizeof(struct CtdlMessage));
4067 memset(msg, 0, sizeof(struct CtdlMessage));
4068 msg->cm_magic = CTDLMESSAGE_MAGIC;
4069 msg->cm_anon_type = MES_NORMAL;
4070 msg->cm_format_type = 4;
4071 msg->cm_fields['A'] = strdup(CC->user.fullname);
4072 msg->cm_fields['O'] = strdup(req_room);
4073 msg->cm_fields['N'] = strdup(config.c_nodename);
4074 msg->cm_fields['H'] = strdup(config.c_humannode);
4075 msg->cm_flags = flags;
4077 msg->cm_fields['M'] = encoded_message;
4079 /* Create the requested room if we have to. */
4080 if (getroom(&qrbuf, roomname) != 0) {
4081 create_room(roomname,
4082 ( (is_mailbox != NULL) ? 5 : 3 ),
4083 "", 0, 1, 0, VIEW_BBS);
4085 /* If the caller specified this object as unique, delete all
4086 * other objects of this type that are currently in the room.
4089 lprintf(CTDL_DEBUG, "Deleted %d other msgs of this type\n",
4090 CtdlDeleteMessages(roomname, NULL, 0, content_type)
4093 /* Now write the data */
4094 CtdlSubmitMsg(msg, NULL, roomname);
4095 CtdlFreeMessage(msg);
4103 void CtdlGetSysConfigBackend(long msgnum, void *userdata) {
4104 config_msgnum = msgnum;
4108 char *CtdlGetSysConfig(char *sysconfname) {
4109 char hold_rm[ROOMNAMELEN];
4112 struct CtdlMessage *msg;
4115 strcpy(hold_rm, CC->room.QRname);
4116 if (getroom(&CC->room, SYSCONFIGROOM) != 0) {
4117 getroom(&CC->room, hold_rm);
4122 /* We want the last (and probably only) config in this room */
4123 begin_critical_section(S_CONFIG);
4124 config_msgnum = (-1L);
4125 CtdlForEachMessage(MSGS_LAST, 1, NULL, sysconfname, NULL,
4126 CtdlGetSysConfigBackend, NULL);
4127 msgnum = config_msgnum;
4128 end_critical_section(S_CONFIG);
4134 msg = CtdlFetchMessage(msgnum, 1);
4136 conf = strdup(msg->cm_fields['M']);
4137 CtdlFreeMessage(msg);
4144 getroom(&CC->room, hold_rm);
4146 if (conf != NULL) do {
4147 extract_token(buf, conf, 0, '\n', sizeof buf);
4148 strcpy(conf, &conf[strlen(buf)+1]);
4149 } while ( (strlen(conf)>0) && (strlen(buf)>0) );
4154 void CtdlPutSysConfig(char *sysconfname, char *sysconfdata) {
4155 char temp[PATH_MAX];
4158 CtdlMakeTempFileName(temp, sizeof temp);
4160 fp = fopen(temp, "w");
4161 if (fp == NULL) return;
4162 fprintf(fp, "%s", sysconfdata);
4165 /* this handy API function does all the work for us */
4166 CtdlWriteObject(SYSCONFIGROOM, sysconfname, temp, NULL, 0, 1, 0);
4172 * Determine whether a given Internet address belongs to the current user
4174 int CtdlIsMe(char *addr, int addr_buf_len)
4176 struct recptypes *recp;
4179 recp = validate_recipients(addr);
4180 if (recp == NULL) return(0);
4182 if (recp->num_local == 0) {
4187 for (i=0; i<recp->num_local; ++i) {
4188 extract_token(addr, recp->recp_local, i, '|', addr_buf_len);
4189 if (!strcasecmp(addr, CC->user.fullname)) {
4201 * Citadel protocol command to do the same
4203 void cmd_isme(char *argbuf) {
4206 if (CtdlAccessCheck(ac_logged_in)) return;
4207 extract_token(addr, argbuf, 0, '|', sizeof addr);
4209 if (CtdlIsMe(addr, sizeof addr)) {
4210 cprintf("%d %s\n", CIT_OK, addr);
4213 cprintf("%d Not you.\n", ERROR + ILLEGAL_VALUE);