4 * Implements the message store.
14 #if TIME_WITH_SYS_TIME
15 # include <sys/time.h>
19 # include <sys/time.h>
34 #include "serv_extensions.h"
38 #include "sysdep_decls.h"
39 #include "citserver.h"
46 #include "mime_parser.h"
49 #include "internet_addressing.h"
50 #include "serv_fulltext.h"
52 #include "euidindex.h"
53 #include "journaling.h"
54 #include "citadel_dirs.h"
55 #include "serv_network.h"
58 # include "serv_sieve.h"
59 #endif /* HAVE_LIBSIEVE */
62 struct addresses_to_be_filed *atbf = NULL;
64 /* This temp file holds the queue of operations for AdjRefCount() */
65 static FILE *arcfp = NULL;
68 * This really belongs in serv_network.c, but I don't know how to export
69 * symbols between modules.
71 struct FilterList *filterlist = NULL;
75 * These are the four-character field headers we use when outputting
76 * messages in Citadel format (as opposed to RFC822 format).
79 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
80 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
81 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
82 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
83 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
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,
114 * This function is self explanatory.
115 * (What can I say, I'm in a weird mood today...)
117 void remove_any_whitespace_to_the_left_or_right_of_at_symbol(char *name)
121 for (i = 0; i < strlen(name); ++i) {
122 if (name[i] == '@') {
123 while (isspace(name[i - 1]) && i > 0) {
124 strcpy(&name[i - 1], &name[i]);
127 while (isspace(name[i + 1])) {
128 strcpy(&name[i + 1], &name[i + 2]);
136 * Aliasing for network mail.
137 * (Error messages have been commented out, because this is a server.)
139 int alias(char *name)
140 { /* process alias and routing info for mail */
143 char aaa[SIZ], bbb[SIZ];
144 char *ignetcfg = NULL;
145 char *ignetmap = NULL;
152 remove_any_whitespace_to_the_left_or_right_of_at_symbol(name);
153 stripallbut(name, '<', '>');
155 fp = fopen(file_mail_aliases, "r");
157 fp = fopen("/dev/null", "r");
164 while (fgets(aaa, sizeof aaa, fp) != NULL) {
165 while (isspace(name[0]))
166 strcpy(name, &name[1]);
167 aaa[strlen(aaa) - 1] = 0;
169 for (a = 0; a < strlen(aaa); ++a) {
171 strcpy(bbb, &aaa[a + 1]);
175 if (!strcasecmp(name, aaa))
180 /* Hit the Global Address Book */
181 if (CtdlDirectoryLookup(aaa, name, sizeof aaa) == 0) {
185 lprintf(CTDL_INFO, "Mail is being forwarded to %s\n", name);
187 /* Change "user @ xxx" to "user" if xxx is an alias for this host */
188 for (a=0; a<strlen(name); ++a) {
189 if (name[a] == '@') {
190 if (CtdlHostAlias(&name[a+1]) == hostalias_localhost) {
192 lprintf(CTDL_INFO, "Changed to <%s>\n", name);
197 /* determine local or remote type, see citadel.h */
198 at = haschar(name, '@');
199 if (at == 0) return(MES_LOCAL); /* no @'s - local address */
200 if (at > 1) return(MES_ERROR); /* >1 @'s - invalid address */
201 remove_any_whitespace_to_the_left_or_right_of_at_symbol(name);
203 /* figure out the delivery mode */
204 extract_token(node, name, 1, '@', sizeof node);
206 /* If there are one or more dots in the nodename, we assume that it
207 * is an FQDN and will attempt SMTP delivery to the Internet.
209 if (haschar(node, '.') > 0) {
210 return(MES_INTERNET);
213 /* Otherwise we look in the IGnet maps for a valid Citadel node.
214 * Try directly-connected nodes first...
216 ignetcfg = CtdlGetSysConfig(IGNETCFG);
217 for (i=0; i<num_tokens(ignetcfg, '\n'); ++i) {
218 extract_token(buf, ignetcfg, i, '\n', sizeof buf);
219 extract_token(testnode, buf, 0, '|', sizeof testnode);
220 if (!strcasecmp(node, testnode)) {
228 * Then try nodes that are two or more hops away.
230 ignetmap = CtdlGetSysConfig(IGNETMAP);
231 for (i=0; i<num_tokens(ignetmap, '\n'); ++i) {
232 extract_token(buf, ignetmap, i, '\n', sizeof buf);
233 extract_token(testnode, buf, 0, '|', sizeof testnode);
234 if (!strcasecmp(node, testnode)) {
241 /* If we get to this point it's an invalid node name */
247 * Back end for the MSGS command: output message number only.
249 void simple_listing(long msgnum, void *userdata)
251 cprintf("%ld\n", msgnum);
257 * Back end for the MSGS command: output header summary.
259 void headers_listing(long msgnum, void *userdata)
261 struct CtdlMessage *msg;
263 msg = CtdlFetchMessage(msgnum, 0);
265 cprintf("%ld|0|||||\n", msgnum);
269 cprintf("%ld|%s|%s|%s|%s|%s|\n",
271 (msg->cm_fields['T'] ? msg->cm_fields['T'] : "0"),
272 (msg->cm_fields['A'] ? msg->cm_fields['A'] : ""),
273 (msg->cm_fields['N'] ? msg->cm_fields['N'] : ""),
274 (msg->cm_fields['F'] ? msg->cm_fields['F'] : ""),
275 (msg->cm_fields['U'] ? msg->cm_fields['U'] : "")
277 CtdlFreeMessage(msg);
282 /* Determine if a given message matches the fields in a message template.
283 * Return 0 for a successful match.
285 int CtdlMsgCmp(struct CtdlMessage *msg, struct CtdlMessage *template) {
288 /* If there aren't any fields in the template, all messages will
291 if (template == NULL) return(0);
293 /* Null messages are bogus. */
294 if (msg == NULL) return(1);
296 for (i='A'; i<='Z'; ++i) {
297 if (template->cm_fields[i] != NULL) {
298 if (msg->cm_fields[i] == NULL) {
301 if (strcasecmp(msg->cm_fields[i],
302 template->cm_fields[i])) return 1;
306 /* All compares succeeded: we have a match! */
313 * Retrieve the "seen" message list for the current room.
315 void CtdlGetSeen(char *buf, int which_set) {
318 /* Learn about the user and room in question */
319 CtdlGetRelationship(&vbuf, &CC->user, &CC->room);
321 if (which_set == ctdlsetseen_seen)
322 safestrncpy(buf, vbuf.v_seen, SIZ);
323 if (which_set == ctdlsetseen_answered)
324 safestrncpy(buf, vbuf.v_answered, SIZ);
330 * Manipulate the "seen msgs" string (or other message set strings)
332 void CtdlSetSeen(long *target_msgnums, int num_target_msgnums,
333 int target_setting, int which_set,
334 struct ctdluser *which_user, struct ctdlroom *which_room) {
335 struct cdbdata *cdbfr;
347 char *is_set; /* actually an array of booleans */
350 char setstr[SIZ], lostr[SIZ], histr[SIZ];
353 /* Don't bother doing *anything* if we were passed a list of zero messages */
354 if (num_target_msgnums < 1) {
358 lprintf(CTDL_DEBUG, "CtdlSetSeen(%d msgs starting with %ld, %d, %d)\n",
359 num_target_msgnums, target_msgnums[0],
360 target_setting, which_set);
362 /* Learn about the user and room in question */
363 CtdlGetRelationship(&vbuf,
364 ((which_user != NULL) ? which_user : &CC->user),
365 ((which_room != NULL) ? which_room : &CC->room)
368 /* Load the message list */
369 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long));
371 msglist = (long *) cdbfr->ptr;
372 cdbfr->ptr = NULL; /* CtdlSetSeen() now owns this memory */
373 num_msgs = cdbfr->len / sizeof(long);
376 return; /* No messages at all? No further action. */
379 is_set = malloc(num_msgs * sizeof(char));
380 memset(is_set, 0, (num_msgs * sizeof(char)) );
382 /* Decide which message set we're manipulating */
384 case ctdlsetseen_seen:
385 safestrncpy(vset, vbuf.v_seen, sizeof vset);
387 case ctdlsetseen_answered:
388 safestrncpy(vset, vbuf.v_answered, sizeof vset);
392 /* lprintf(CTDL_DEBUG, "before optimize: %s\n", vset); */
394 /* Translate the existing sequence set into an array of booleans */
395 num_sets = num_tokens(vset, ',');
396 for (s=0; s<num_sets; ++s) {
397 extract_token(setstr, vset, s, ',', sizeof setstr);
399 extract_token(lostr, setstr, 0, ':', sizeof lostr);
400 if (num_tokens(setstr, ':') >= 2) {
401 extract_token(histr, setstr, 1, ':', sizeof histr);
402 if (!strcmp(histr, "*")) {
403 snprintf(histr, sizeof histr, "%ld", LONG_MAX);
407 strcpy(histr, lostr);
412 for (i = 0; i < num_msgs; ++i) {
413 if ((msglist[i] >= lo) && (msglist[i] <= hi)) {
419 /* Now translate the array of booleans back into a sequence set */
424 for (i=0; i<num_msgs; ++i) {
426 is_seen = is_set[i]; /* Default to existing setting */
428 for (k=0; k<num_target_msgnums; ++k) {
429 if (msglist[i] == target_msgnums[k]) {
430 is_seen = target_setting;
435 if (lo < 0L) lo = msglist[i];
439 if ( ((is_seen == 0) && (was_seen == 1))
440 || ((is_seen == 1) && (i == num_msgs-1)) ) {
442 /* begin trim-o-matic code */
445 while ( (strlen(vset) + 20) > sizeof vset) {
446 remove_token(vset, 0, ',');
448 if (j--) break; /* loop no more than 9 times */
450 if ( (trimming) && (which_set == ctdlsetseen_seen) ) {
454 snprintf(lostr, sizeof lostr,
455 "1:%ld,%s", t, vset);
456 safestrncpy(vset, lostr, sizeof vset);
458 /* end trim-o-matic code */
466 snprintf(&vset[tmp], (sizeof vset) - tmp,
470 snprintf(&vset[tmp], (sizeof vset) - tmp,
479 /* Decide which message set we're manipulating */
481 case ctdlsetseen_seen:
482 safestrncpy(vbuf.v_seen, vset, sizeof vbuf.v_seen);
484 case ctdlsetseen_answered:
485 safestrncpy(vbuf.v_answered, vset,
486 sizeof vbuf.v_answered);
491 /* lprintf(CTDL_DEBUG, " after optimize: %s\n", vset); */
493 CtdlSetRelationship(&vbuf,
494 ((which_user != NULL) ? which_user : &CC->user),
495 ((which_room != NULL) ? which_room : &CC->room)
501 * API function to perform an operation for each qualifying message in the
502 * current room. (Returns the number of messages processed.)
504 int CtdlForEachMessage(int mode, long ref, char *search_string,
506 struct CtdlMessage *compare,
507 void (*CallBack) (long, void *),
513 struct cdbdata *cdbfr;
514 long *msglist = NULL;
516 int num_processed = 0;
519 struct CtdlMessage *msg = NULL;
522 int printed_lastold = 0;
523 int num_search_msgs = 0;
524 long *search_msgs = NULL;
526 /* Learn about the user and room in question */
527 getuser(&CC->user, CC->curr_user);
528 CtdlGetRelationship(&vbuf, &CC->user, &CC->room);
530 /* Load the message list */
531 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long));
533 msglist = (long *) cdbfr->ptr;
534 cdbfr->ptr = NULL; /* CtdlForEachMessage() now owns this memory */
535 num_msgs = cdbfr->len / sizeof(long);
538 return 0; /* No messages at all? No further action. */
543 * Now begin the traversal.
545 if (num_msgs > 0) for (a = 0; a < num_msgs; ++a) {
547 /* If the caller is looking for a specific MIME type, filter
548 * out all messages which are not of the type requested.
550 if (content_type != NULL) if (strlen(content_type) > 0) {
552 /* This call to GetMetaData() sits inside this loop
553 * so that we only do the extra database read per msg
554 * if we need to. Doing the extra read all the time
555 * really kills the server. If we ever need to use
556 * metadata for another search criterion, we need to
557 * move the read somewhere else -- but still be smart
558 * enough to only do the read if the caller has
559 * specified something that will need it.
561 GetMetaData(&smi, msglist[a]);
563 if (strcasecmp(smi.meta_content_type, content_type)) {
569 num_msgs = sort_msglist(msglist, num_msgs);
571 /* If a template was supplied, filter out the messages which
572 * don't match. (This could induce some delays!)
575 if (compare != NULL) {
576 for (a = 0; a < num_msgs; ++a) {
577 msg = CtdlFetchMessage(msglist[a], 1);
579 if (CtdlMsgCmp(msg, compare)) {
582 CtdlFreeMessage(msg);
588 /* If a search string was specified, get a message list from
589 * the full text index and remove messages which aren't on both
593 * Since the lists are sorted and strictly ascending, and the
594 * output list is guaranteed to be shorter than or equal to the
595 * input list, we overwrite the bottom of the input list. This
596 * eliminates the need to memmove big chunks of the list over and
599 if ( (num_msgs > 0) && (mode == MSGS_SEARCH) && (search_string) ) {
600 ft_search(&num_search_msgs, &search_msgs, search_string);
601 if (num_search_msgs > 0) {
605 orig_num_msgs = num_msgs;
607 for (i=0; i<orig_num_msgs; ++i) {
608 for (j=0; j<num_search_msgs; ++j) {
609 if (msglist[i] == search_msgs[j]) {
610 msglist[num_msgs++] = msglist[i];
616 num_msgs = 0; /* No messages qualify */
618 if (search_msgs != NULL) free(search_msgs);
620 /* Now that we've purged messages which don't contain the search
621 * string, treat a MSGS_SEARCH just like a MSGS_ALL from this
628 * Now iterate through the message list, according to the
629 * criteria supplied by the caller.
632 for (a = 0; a < num_msgs; ++a) {
633 thismsg = msglist[a];
634 if (mode == MSGS_ALL) {
638 is_seen = is_msg_in_sequence_set(
639 vbuf.v_seen, thismsg);
640 if (is_seen) lastold = thismsg;
646 || ((mode == MSGS_OLD) && (is_seen))
647 || ((mode == MSGS_NEW) && (!is_seen))
648 || ((mode == MSGS_LAST) && (a >= (num_msgs - ref)))
649 || ((mode == MSGS_FIRST) && (a < ref))
650 || ((mode == MSGS_GT) && (thismsg > ref))
651 || ((mode == MSGS_EQ) && (thismsg == ref))
654 if ((mode == MSGS_NEW) && (CC->user.flags & US_LASTOLD) && (lastold > 0L) && (printed_lastold == 0) && (!is_seen)) {
656 CallBack(lastold, userdata);
660 if (CallBack) CallBack(thismsg, userdata);
664 free(msglist); /* Clean up */
665 return num_processed;
671 * cmd_msgs() - get list of message #'s in this room
672 * implements the MSGS server command using CtdlForEachMessage()
674 void cmd_msgs(char *cmdbuf)
683 int with_template = 0;
684 struct CtdlMessage *template = NULL;
685 int with_headers = 0;
686 char search_string[1024];
688 extract_token(which, cmdbuf, 0, '|', sizeof which);
689 cm_ref = extract_int(cmdbuf, 1);
690 extract_token(search_string, cmdbuf, 1, '|', sizeof search_string);
691 with_template = extract_int(cmdbuf, 2);
692 with_headers = extract_int(cmdbuf, 3);
695 if (!strncasecmp(which, "OLD", 3))
697 else if (!strncasecmp(which, "NEW", 3))
699 else if (!strncasecmp(which, "FIRST", 5))
701 else if (!strncasecmp(which, "LAST", 4))
703 else if (!strncasecmp(which, "GT", 2))
705 else if (!strncasecmp(which, "SEARCH", 6))
710 if ((!(CC->logged_in)) && (!(CC->internal_pgm))) {
711 cprintf("%d not logged in\n", ERROR + NOT_LOGGED_IN);
715 if ( (mode == MSGS_SEARCH) && (!config.c_enable_fulltext) ) {
716 cprintf("%d Full text index is not enabled on this server.\n",
717 ERROR + CMD_NOT_SUPPORTED);
723 cprintf("%d Send template then receive message list\n",
725 template = (struct CtdlMessage *)
726 malloc(sizeof(struct CtdlMessage));
727 memset(template, 0, sizeof(struct CtdlMessage));
728 template->cm_magic = CTDLMESSAGE_MAGIC;
729 template->cm_anon_type = MES_NORMAL;
731 while(client_getln(buf, sizeof buf), strcmp(buf,"000")) {
732 extract_token(tfield, buf, 0, '|', sizeof tfield);
733 extract_token(tvalue, buf, 1, '|', sizeof tvalue);
734 for (i='A'; i<='Z'; ++i) if (msgkeys[i]!=NULL) {
735 if (!strcasecmp(tfield, msgkeys[i])) {
736 template->cm_fields[i] =
744 cprintf("%d \n", LISTING_FOLLOWS);
747 CtdlForEachMessage(mode,
748 ( (mode == MSGS_SEARCH) ? 0 : cm_ref ),
749 ( (mode == MSGS_SEARCH) ? search_string : NULL ),
752 (with_headers ? headers_listing : simple_listing),
755 if (template != NULL) CtdlFreeMessage(template);
763 * help_subst() - support routine for help file viewer
765 void help_subst(char *strbuf, char *source, char *dest)
770 while (p = pattern2(strbuf, source), (p >= 0)) {
771 strcpy(workbuf, &strbuf[p + strlen(source)]);
772 strcpy(&strbuf[p], dest);
773 strcat(strbuf, workbuf);
778 void do_help_subst(char *buffer)
782 help_subst(buffer, "^nodename", config.c_nodename);
783 help_subst(buffer, "^humannode", config.c_humannode);
784 help_subst(buffer, "^fqdn", config.c_fqdn);
785 help_subst(buffer, "^username", CC->user.fullname);
786 snprintf(buf2, sizeof buf2, "%ld", CC->user.usernum);
787 help_subst(buffer, "^usernum", buf2);
788 help_subst(buffer, "^sysadm", config.c_sysadm);
789 help_subst(buffer, "^variantname", CITADEL);
790 snprintf(buf2, sizeof buf2, "%d", config.c_maxsessions);
791 help_subst(buffer, "^maxsessions", buf2);
792 help_subst(buffer, "^bbsdir", ctdl_bbsbase_dir);
798 * memfmout() - Citadel text formatter and paginator.
799 * Although the original purpose of this routine was to format
800 * text to the reader's screen width, all we're really using it
801 * for here is to format text out to 80 columns before sending it
802 * to the client. The client software may reformat it again.
805 char *mptr, /* where are we going to get our text from? */
806 char subst, /* nonzero if we should do substitutions */
807 char *nl) /* string to terminate lines with */
815 static int width = 80;
820 c = 1; /* c is the current pos */
824 while (ch = *mptr, ((ch != 0) && (strlen(buffer) < 126))) {
826 buffer[strlen(buffer) + 1] = 0;
827 buffer[strlen(buffer)] = ch;
830 if (buffer[0] == '^')
831 do_help_subst(buffer);
833 buffer[strlen(buffer) + 1] = 0;
835 strcpy(buffer, &buffer[1]);
843 if (((ch == 13) || (ch == 10)) && (old != 13) && (old != 10)) {
846 if (((old == 13) || (old == 10)) && (isspace(real))) {
851 if (((strlen(aaa) + c) > (width - 5)) && (strlen(aaa) > (width - 5))) {
852 cprintf("%s%s", nl, aaa);
861 if ((strlen(aaa) + c) > (width - 5)) {
870 if ((ch == 13) || (ch == 10)) {
871 cprintf("%s%s", aaa, nl);
878 cprintf("%s%s", aaa, nl);
884 * Callback function for mime parser that simply lists the part
886 void list_this_part(char *name, char *filename, char *partnum, char *disp,
887 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
892 ma = (struct ma_info *)cbuserdata;
893 if (ma->is_ma == 0) {
894 cprintf("part=%s|%s|%s|%s|%s|%ld\n",
895 name, filename, partnum, disp, cbtype, (long)length);
900 * Callback function for multipart prefix
902 void list_this_pref(char *name, char *filename, char *partnum, char *disp,
903 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
908 ma = (struct ma_info *)cbuserdata;
909 if (!strcasecmp(cbtype, "multipart/alternative")) {
913 if (ma->is_ma == 0) {
914 cprintf("pref=%s|%s\n", partnum, cbtype);
919 * Callback function for multipart sufffix
921 void list_this_suff(char *name, char *filename, char *partnum, char *disp,
922 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
927 ma = (struct ma_info *)cbuserdata;
928 if (ma->is_ma == 0) {
929 cprintf("suff=%s|%s\n", partnum, cbtype);
931 if (!strcasecmp(cbtype, "multipart/alternative")) {
938 * Callback function for mime parser that opens a section for downloading
940 void mime_download(char *name, char *filename, char *partnum, char *disp,
941 void *content, char *cbtype, char *cbcharset, size_t length,
942 char *encoding, void *cbuserdata)
945 /* Silently go away if there's already a download open... */
946 if (CC->download_fp != NULL)
949 /* ...or if this is not the desired section */
950 if (strcasecmp(CC->download_desired_section, partnum))
953 CC->download_fp = tmpfile();
954 if (CC->download_fp == NULL)
957 fwrite(content, length, 1, CC->download_fp);
958 fflush(CC->download_fp);
959 rewind(CC->download_fp);
961 OpenCmdResult(filename, cbtype);
967 * Callback function for mime parser that outputs a section all at once
969 void mime_spew_section(char *name, char *filename, char *partnum, char *disp,
970 void *content, char *cbtype, char *cbcharset, size_t length,
971 char *encoding, void *cbuserdata)
973 int *found_it = (int *)cbuserdata;
975 /* ...or if this is not the desired section */
976 if (strcasecmp(CC->download_desired_section, partnum))
981 cprintf("%d %d\n", BINARY_FOLLOWS, length);
982 client_write(content, length);
988 * Load a message from disk into memory.
989 * This is used by CtdlOutputMsg() and other fetch functions.
991 * NOTE: Caller is responsible for freeing the returned CtdlMessage struct
992 * using the CtdlMessageFree() function.
994 struct CtdlMessage *CtdlFetchMessage(long msgnum, int with_body)
996 struct cdbdata *dmsgtext;
997 struct CtdlMessage *ret = NULL;
1001 cit_uint8_t field_header;
1003 lprintf(CTDL_DEBUG, "CtdlFetchMessage(%ld, %d)\n", msgnum, with_body);
1005 dmsgtext = cdb_fetch(CDB_MSGMAIN, &msgnum, sizeof(long));
1006 if (dmsgtext == NULL) {
1009 mptr = dmsgtext->ptr;
1010 upper_bound = mptr + dmsgtext->len;
1012 /* Parse the three bytes that begin EVERY message on disk.
1013 * The first is always 0xFF, the on-disk magic number.
1014 * The second is the anonymous/public type byte.
1015 * The third is the format type byte (vari, fixed, or MIME).
1020 "Message %ld appears to be corrupted.\n",
1025 ret = (struct CtdlMessage *) malloc(sizeof(struct CtdlMessage));
1026 memset(ret, 0, sizeof(struct CtdlMessage));
1028 ret->cm_magic = CTDLMESSAGE_MAGIC;
1029 ret->cm_anon_type = *mptr++; /* Anon type byte */
1030 ret->cm_format_type = *mptr++; /* Format type byte */
1033 * The rest is zero or more arbitrary fields. Load them in.
1034 * We're done when we encounter either a zero-length field or
1035 * have just processed the 'M' (message text) field.
1038 if (mptr >= upper_bound) {
1041 field_header = *mptr++;
1042 ret->cm_fields[field_header] = strdup(mptr);
1044 while (*mptr++ != 0); /* advance to next field */
1046 } while ((mptr < upper_bound) && (field_header != 'M'));
1050 /* Always make sure there's something in the msg text field. If
1051 * it's NULL, the message text is most likely stored separately,
1052 * so go ahead and fetch that. Failing that, just set a dummy
1053 * body so other code doesn't barf.
1055 if ( (ret->cm_fields['M'] == NULL) && (with_body) ) {
1056 dmsgtext = cdb_fetch(CDB_BIGMSGS, &msgnum, sizeof(long));
1057 if (dmsgtext != NULL) {
1058 ret->cm_fields['M'] = strdup(dmsgtext->ptr);
1062 if (ret->cm_fields['M'] == NULL) {
1063 ret->cm_fields['M'] = strdup("\r\n\r\n (no text)\r\n");
1066 /* Perform "before read" hooks (aborting if any return nonzero) */
1067 if (PerformMessageHooks(ret, EVT_BEFOREREAD) > 0) {
1068 CtdlFreeMessage(ret);
1077 * Returns 1 if the supplied pointer points to a valid Citadel message.
1078 * If the pointer is NULL or the magic number check fails, returns 0.
1080 int is_valid_message(struct CtdlMessage *msg) {
1083 if ((msg->cm_magic) != CTDLMESSAGE_MAGIC) {
1084 lprintf(CTDL_WARNING, "is_valid_message() -- self-check failed\n");
1092 * 'Destructor' for struct CtdlMessage
1094 void CtdlFreeMessage(struct CtdlMessage *msg)
1098 if (is_valid_message(msg) == 0)
1100 if (msg != NULL) free (msg);
1104 for (i = 0; i < 256; ++i)
1105 if (msg->cm_fields[i] != NULL) {
1106 free(msg->cm_fields[i]);
1109 msg->cm_magic = 0; /* just in case */
1115 * Pre callback function for multipart/alternative
1117 * NOTE: this differs from the standard behavior for a reason. Normally when
1118 * displaying multipart/alternative you want to show the _last_ usable
1119 * format in the message. Here we show the _first_ one, because it's
1120 * usually text/plain. Since this set of functions is designed for text
1121 * output to non-MIME-aware clients, this is the desired behavior.
1124 void fixed_output_pre(char *name, char *filename, char *partnum, char *disp,
1125 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
1130 ma = (struct ma_info *)cbuserdata;
1131 lprintf(CTDL_DEBUG, "fixed_output_pre() type=<%s>\n", cbtype);
1132 if (!strcasecmp(cbtype, "multipart/alternative")) {
1136 if (!strcasecmp(cbtype, "message/rfc822")) {
1142 * Post callback function for multipart/alternative
1144 void fixed_output_post(char *name, char *filename, char *partnum, char *disp,
1145 void *content, char *cbtype, char *cbcharset, size_t length,
1146 char *encoding, void *cbuserdata)
1150 ma = (struct ma_info *)cbuserdata;
1151 lprintf(CTDL_DEBUG, "fixed_output_post() type=<%s>\n", cbtype);
1152 if (!strcasecmp(cbtype, "multipart/alternative")) {
1156 if (!strcasecmp(cbtype, "message/rfc822")) {
1162 * Inline callback function for mime parser that wants to display text
1164 void fixed_output(char *name, char *filename, char *partnum, char *disp,
1165 void *content, char *cbtype, char *cbcharset, size_t length,
1166 char *encoding, void *cbuserdata)
1173 ma = (struct ma_info *)cbuserdata;
1176 "fixed_output() part %s: %s (%s) (%ld bytes)\n",
1177 partnum, filename, cbtype, (long)length);
1180 * If we're in the middle of a multipart/alternative scope and
1181 * we've already printed another section, skip this one.
1183 if ( (ma->is_ma) && (ma->did_print) ) {
1184 lprintf(CTDL_DEBUG, "Skipping part %s (%s)\n",
1190 if ( (!strcasecmp(cbtype, "text/plain"))
1191 || (strlen(cbtype)==0) ) {
1194 client_write(wptr, length);
1195 if (wptr[length-1] != '\n') {
1202 if (!strcasecmp(cbtype, "text/html")) {
1203 ptr = html_to_ascii(content, length, 80, 0);
1205 client_write(ptr, wlen);
1206 if (ptr[wlen-1] != '\n') {
1213 if (ma->use_fo_hooks) {
1214 if (PerformFixedOutputHooks(cbtype, content, length)) {
1215 /* above function returns nonzero if it handled the part */
1220 if (strncasecmp(cbtype, "multipart/", 10)) {
1221 cprintf("Part %s: %s (%s) (%ld bytes)\r\n",
1222 partnum, filename, cbtype, (long)length);
1228 * The client is elegant and sophisticated and wants to be choosy about
1229 * MIME content types, so figure out which multipart/alternative part
1230 * we're going to send.
1232 * We use a system of weights. When we find a part that matches one of the
1233 * MIME types we've declared as preferential, we can store it in ma->chosen_part
1234 * and then set ma->chosen_pref to that MIME type's position in our preference
1235 * list. If we then hit another match, we only replace the first match if
1236 * the preference value is lower.
1238 void choose_preferred(char *name, char *filename, char *partnum, char *disp,
1239 void *content, char *cbtype, char *cbcharset, size_t length,
1240 char *encoding, void *cbuserdata)
1246 ma = (struct ma_info *)cbuserdata;
1248 if (ma->is_ma > 0) {
1249 for (i=0; i<num_tokens(CC->preferred_formats, '|'); ++i) {
1250 extract_token(buf, CC->preferred_formats, i, '|', sizeof buf);
1251 if ( (!strcasecmp(buf, cbtype)) && (!ma->freeze) ) {
1252 if (i < ma->chosen_pref) {
1253 safestrncpy(ma->chosen_part, partnum, sizeof ma->chosen_part);
1254 ma->chosen_pref = i;
1262 * Now that we've chosen our preferred part, output it.
1264 void output_preferred(char *name, char *filename, char *partnum, char *disp,
1265 void *content, char *cbtype, char *cbcharset, size_t length,
1266 char *encoding, void *cbuserdata)
1270 int add_newline = 0;
1274 ma = (struct ma_info *)cbuserdata;
1276 /* This is not the MIME part you're looking for... */
1277 if (strcasecmp(partnum, ma->chosen_part)) return;
1279 /* If the content-type of this part is in our preferred formats
1280 * list, we can simply output it verbatim.
1282 for (i=0; i<num_tokens(CC->preferred_formats, '|'); ++i) {
1283 extract_token(buf, CC->preferred_formats, i, '|', sizeof buf);
1284 if (!strcasecmp(buf, cbtype)) {
1285 /* Yeah! Go! W00t!! */
1287 text_content = (char *)content;
1288 if (text_content[length-1] != '\n') {
1292 cprintf("Content-type: %s", cbtype);
1293 if (strlen(cbcharset) > 0) {
1294 cprintf("; charset=%s", cbcharset);
1296 cprintf("\nContent-length: %d\n",
1297 (int)(length + add_newline) );
1298 if (strlen(encoding) > 0) {
1299 cprintf("Content-transfer-encoding: %s\n", encoding);
1302 cprintf("Content-transfer-encoding: 7bit\n");
1305 client_write(content, length);
1306 if (add_newline) cprintf("\n");
1311 /* No translations required or possible: output as text/plain */
1312 cprintf("Content-type: text/plain\n\n");
1313 fixed_output(name, filename, partnum, disp, content, cbtype, cbcharset,
1314 length, encoding, cbuserdata);
1319 char desired_section[64];
1326 * Callback function for
1328 void extract_encapsulated_message(char *name, char *filename, char *partnum, char *disp,
1329 void *content, char *cbtype, char *cbcharset, size_t length,
1330 char *encoding, void *cbuserdata)
1332 struct encapmsg *encap;
1334 encap = (struct encapmsg *)cbuserdata;
1336 /* Only proceed if this is the desired section... */
1337 if (!strcasecmp(encap->desired_section, partnum)) {
1338 encap->msglen = length;
1339 encap->msg = malloc(length + 2);
1340 memcpy(encap->msg, content, length);
1350 * Get a message off disk. (returns om_* values found in msgbase.h)
1353 int CtdlOutputMsg(long msg_num, /* message number (local) to fetch */
1354 int mode, /* how would you like that message? */
1355 int headers_only, /* eschew the message body? */
1356 int do_proto, /* do Citadel protocol responses? */
1357 int crlf, /* Use CRLF newlines instead of LF? */
1358 char *section /* NULL or a message/rfc822 section */
1360 struct CtdlMessage *TheMessage = NULL;
1361 int retcode = om_no_such_msg;
1362 struct encapmsg encap;
1364 lprintf(CTDL_DEBUG, "CtdlOutputMsg() msgnum=%ld, mode=%d, section=%s\n",
1366 (section ? section : "<>")
1369 if ((!(CC->logged_in)) && (!(CC->internal_pgm))) {
1370 if (do_proto) cprintf("%d Not logged in.\n",
1371 ERROR + NOT_LOGGED_IN);
1372 return(om_not_logged_in);
1375 /* FIXME: check message id against msglist for this room */
1378 * Fetch the message from disk. If we're in any sort of headers
1379 * only mode, request that we don't even bother loading the body
1382 if ( (headers_only == HEADERS_FAST) || (headers_only == HEADERS_ONLY) ) {
1383 TheMessage = CtdlFetchMessage(msg_num, 0);
1386 TheMessage = CtdlFetchMessage(msg_num, 1);
1389 if (TheMessage == NULL) {
1390 if (do_proto) cprintf("%d Can't locate msg %ld on disk\n",
1391 ERROR + MESSAGE_NOT_FOUND, msg_num);
1392 return(om_no_such_msg);
1395 /* Here is the weird form of this command, to process only an
1396 * encapsulated message/rfc822 section.
1398 if (section) if (strlen(section)>0) if (strcmp(section, "0")) {
1399 memset(&encap, 0, sizeof encap);
1400 safestrncpy(encap.desired_section, section, sizeof encap.desired_section);
1401 mime_parser(TheMessage->cm_fields['M'],
1403 *extract_encapsulated_message,
1404 NULL, NULL, (void *)&encap, 0
1406 CtdlFreeMessage(TheMessage);
1410 encap.msg[encap.msglen] = 0;
1411 TheMessage = convert_internet_message(encap.msg);
1412 encap.msg = NULL; /* no free() here, TheMessage owns it now */
1414 /* Now we let it fall through to the bottom of this
1415 * function, because TheMessage now contains the
1416 * encapsulated message instead of the top-level
1417 * message. Isn't that neat?
1422 if (do_proto) cprintf("%d msg %ld has no part %s\n",
1423 ERROR + MESSAGE_NOT_FOUND, msg_num, section);
1424 retcode = om_no_such_msg;
1429 /* Ok, output the message now */
1430 retcode = CtdlOutputPreLoadedMsg(
1432 headers_only, do_proto, crlf);
1433 CtdlFreeMessage(TheMessage);
1440 * Get a message off disk. (returns om_* values found in msgbase.h)
1443 int CtdlOutputPreLoadedMsg(
1444 struct CtdlMessage *TheMessage,
1445 int mode, /* how would you like that message? */
1446 int headers_only, /* eschew the message body? */
1447 int do_proto, /* do Citadel protocol responses? */
1448 int crlf /* Use CRLF newlines instead of LF? */
1454 char display_name[256];
1456 char *nl; /* newline string */
1458 int subject_found = 0;
1461 /* Buffers needed for RFC822 translation. These are all filled
1462 * using functions that are bounds-checked, and therefore we can
1463 * make them substantially smaller than SIZ.
1471 char datestamp[100];
1473 lprintf(CTDL_DEBUG, "CtdlOutputPreLoadedMsg(TheMessage=%s, %d, %d, %d, %d\n",
1474 ((TheMessage == NULL) ? "NULL" : "not null"),
1475 mode, headers_only, do_proto, crlf);
1477 strcpy(mid, "unknown");
1478 nl = (crlf ? "\r\n" : "\n");
1480 if (!is_valid_message(TheMessage)) {
1482 "ERROR: invalid preloaded message for output\n");
1483 return(om_no_such_msg);
1486 /* Are we downloading a MIME component? */
1487 if (mode == MT_DOWNLOAD) {
1488 if (TheMessage->cm_format_type != FMT_RFC822) {
1490 cprintf("%d This is not a MIME message.\n",
1491 ERROR + ILLEGAL_VALUE);
1492 } else if (CC->download_fp != NULL) {
1493 if (do_proto) cprintf(
1494 "%d You already have a download open.\n",
1495 ERROR + RESOURCE_BUSY);
1497 /* Parse the message text component */
1498 mptr = TheMessage->cm_fields['M'];
1499 mime_parser(mptr, NULL, *mime_download, NULL, NULL, NULL, 0);
1500 /* If there's no file open by this time, the requested
1501 * section wasn't found, so print an error
1503 if (CC->download_fp == NULL) {
1504 if (do_proto) cprintf(
1505 "%d Section %s not found.\n",
1506 ERROR + FILE_NOT_FOUND,
1507 CC->download_desired_section);
1510 return((CC->download_fp != NULL) ? om_ok : om_mime_error);
1513 /* MT_SPEW_SECTION is like MT_DOWNLOAD except it outputs the whole MIME part
1514 * in a single server operation instead of opening a download file.
1516 if (mode == MT_SPEW_SECTION) {
1517 if (TheMessage->cm_format_type != FMT_RFC822) {
1519 cprintf("%d This is not a MIME message.\n",
1520 ERROR + ILLEGAL_VALUE);
1522 /* Parse the message text component */
1525 mptr = TheMessage->cm_fields['M'];
1526 mime_parser(mptr, NULL, *mime_spew_section, NULL, NULL, (void *)&found_it, 0);
1527 /* If section wasn't found, print an error
1530 if (do_proto) cprintf(
1531 "%d Section %s not found.\n",
1532 ERROR + FILE_NOT_FOUND,
1533 CC->download_desired_section);
1536 return((CC->download_fp != NULL) ? om_ok : om_mime_error);
1539 /* now for the user-mode message reading loops */
1540 if (do_proto) cprintf("%d msg:\n", LISTING_FOLLOWS);
1542 /* Does the caller want to skip the headers? */
1543 if (headers_only == HEADERS_NONE) goto START_TEXT;
1545 /* Tell the client which format type we're using. */
1546 if ( (mode == MT_CITADEL) && (do_proto) ) {
1547 cprintf("type=%d\n", TheMessage->cm_format_type);
1550 /* nhdr=yes means that we're only displaying headers, no body */
1551 if ( (TheMessage->cm_anon_type == MES_ANONONLY)
1552 && (mode == MT_CITADEL)
1555 cprintf("nhdr=yes\n");
1558 /* begin header processing loop for Citadel message format */
1560 if ((mode == MT_CITADEL) || (mode == MT_MIME)) {
1562 safestrncpy(display_name, "<unknown>", sizeof display_name);
1563 if (TheMessage->cm_fields['A']) {
1564 strcpy(buf, TheMessage->cm_fields['A']);
1565 if (TheMessage->cm_anon_type == MES_ANONONLY) {
1566 safestrncpy(display_name, "****", sizeof display_name);
1568 else if (TheMessage->cm_anon_type == MES_ANONOPT) {
1569 safestrncpy(display_name, "anonymous", sizeof display_name);
1572 safestrncpy(display_name, buf, sizeof display_name);
1574 if ((is_room_aide())
1575 && ((TheMessage->cm_anon_type == MES_ANONONLY)
1576 || (TheMessage->cm_anon_type == MES_ANONOPT))) {
1577 size_t tmp = strlen(display_name);
1578 snprintf(&display_name[tmp],
1579 sizeof display_name - tmp,
1584 /* Don't show Internet address for users on the
1585 * local Citadel network.
1588 if (TheMessage->cm_fields['N'] != NULL)
1589 if (strlen(TheMessage->cm_fields['N']) > 0)
1590 if (haschar(TheMessage->cm_fields['N'], '.') == 0) {
1594 /* Now spew the header fields in the order we like them. */
1595 safestrncpy(allkeys, FORDER, sizeof allkeys);
1596 for (i=0; i<strlen(allkeys); ++i) {
1597 k = (int) allkeys[i];
1599 if ( (TheMessage->cm_fields[k] != NULL)
1600 && (msgkeys[k] != NULL) ) {
1602 if (do_proto) cprintf("%s=%s\n",
1606 else if ((k == 'F') && (suppress_f)) {
1609 /* Masquerade display name if needed */
1611 if (do_proto) cprintf("%s=%s\n",
1613 TheMessage->cm_fields[k]
1622 /* begin header processing loop for RFC822 transfer format */
1627 strcpy(snode, NODENAME);
1628 strcpy(lnode, HUMANNODE);
1629 if (mode == MT_RFC822) {
1630 for (i = 0; i < 256; ++i) {
1631 if (TheMessage->cm_fields[i]) {
1632 mptr = TheMessage->cm_fields[i];
1635 safestrncpy(luser, mptr, sizeof luser);
1636 safestrncpy(suser, mptr, sizeof suser);
1638 else if (i == 'Y') {
1639 cprintf("CC: %s%s", mptr, nl);
1641 else if (i == 'P') {
1642 cprintf("Return-Path: %s%s", mptr, nl);
1644 else if (i == 'V') {
1645 cprintf("Envelope-To: %s%s", mptr, nl);
1647 else if (i == 'U') {
1648 cprintf("Subject: %s%s", mptr, nl);
1652 safestrncpy(mid, mptr, sizeof mid);
1654 safestrncpy(lnode, mptr, sizeof lnode);
1656 safestrncpy(fuser, mptr, sizeof fuser);
1657 /* else if (i == 'O')
1658 cprintf("X-Citadel-Room: %s%s",
1661 safestrncpy(snode, mptr, sizeof snode);
1663 cprintf("To: %s%s", mptr, nl);
1664 else if (i == 'T') {
1665 datestring(datestamp, sizeof datestamp,
1666 atol(mptr), DATESTRING_RFC822);
1667 cprintf("Date: %s%s", datestamp, nl);
1671 if (subject_found == 0) {
1672 cprintf("Subject: (no subject)%s", nl);
1676 for (i=0; i<strlen(suser); ++i) {
1677 suser[i] = tolower(suser[i]);
1678 if (!isalnum(suser[i])) suser[i]='_';
1681 if (mode == MT_RFC822) {
1682 if (!strcasecmp(snode, NODENAME)) {
1683 safestrncpy(snode, FQDN, sizeof snode);
1686 /* Construct a fun message id */
1687 cprintf("Message-ID: <%s", mid);
1688 if (strchr(mid, '@')==NULL) {
1689 cprintf("@%s", snode);
1693 if (!is_room_aide() && (TheMessage->cm_anon_type == MES_ANONONLY)) {
1694 cprintf("From: \"----\" <x@x.org>%s", nl);
1696 else if (!is_room_aide() && (TheMessage->cm_anon_type == MES_ANONOPT)) {
1697 cprintf("From: \"anonymous\" <x@x.org>%s", nl);
1699 else if (strlen(fuser) > 0) {
1700 cprintf("From: \"%s\" <%s>%s", luser, fuser, nl);
1703 cprintf("From: \"%s\" <%s@%s>%s", luser, suser, snode, nl);
1706 cprintf("Organization: %s%s", lnode, nl);
1708 /* Blank line signifying RFC822 end-of-headers */
1709 if (TheMessage->cm_format_type != FMT_RFC822) {
1714 /* end header processing loop ... at this point, we're in the text */
1716 if (headers_only == HEADERS_FAST) goto DONE;
1717 mptr = TheMessage->cm_fields['M'];
1719 /* Tell the client about the MIME parts in this message */
1720 if (TheMessage->cm_format_type == FMT_RFC822) {
1721 if ( (mode == MT_CITADEL) || (mode == MT_MIME) ) {
1722 memset(&ma, 0, sizeof(struct ma_info));
1723 mime_parser(mptr, NULL,
1724 (do_proto ? *list_this_part : NULL),
1725 (do_proto ? *list_this_pref : NULL),
1726 (do_proto ? *list_this_suff : NULL),
1729 else if (mode == MT_RFC822) { /* unparsed RFC822 dump */
1730 char *start_of_text = NULL;
1731 start_of_text = strstr(mptr, "\n\r\n");
1732 if (start_of_text == NULL) start_of_text = strstr(mptr, "\n\n");
1733 if (start_of_text == NULL) start_of_text = mptr;
1735 start_of_text = strstr(start_of_text, "\n");
1740 int nllen = strlen(nl);
1741 while (ch=*mptr, ch!=0) {
1747 ((headers_only == HEADERS_NONE) && (mptr >= start_of_text))
1748 || ((headers_only == HEADERS_ONLY) && (mptr < start_of_text))
1749 || ((headers_only != HEADERS_NONE) && (headers_only != HEADERS_ONLY))
1752 sprintf(&outbuf[outlen], "%s", nl);
1756 outbuf[outlen++] = ch;
1761 if (outlen > 1000) {
1762 client_write(outbuf, outlen);
1767 client_write(outbuf, outlen);
1775 if (headers_only == HEADERS_ONLY) {
1779 /* signify start of msg text */
1780 if ( (mode == MT_CITADEL) || (mode == MT_MIME) ) {
1781 if (do_proto) cprintf("text\n");
1784 /* If the format type on disk is 1 (fixed-format), then we want
1785 * everything to be output completely literally ... regardless of
1786 * what message transfer format is in use.
1788 if (TheMessage->cm_format_type == FMT_FIXED) {
1789 if (mode == MT_MIME) {
1790 cprintf("Content-type: text/plain\n\n");
1793 while (ch = *mptr++, ch > 0) {
1796 if ((ch == 10) || (strlen(buf) > 250)) {
1797 cprintf("%s%s", buf, nl);
1800 buf[strlen(buf) + 1] = 0;
1801 buf[strlen(buf)] = ch;
1804 if (strlen(buf) > 0)
1805 cprintf("%s%s", buf, nl);
1808 /* If the message on disk is format 0 (Citadel vari-format), we
1809 * output using the formatter at 80 columns. This is the final output
1810 * form if the transfer format is RFC822, but if the transfer format
1811 * is Citadel proprietary, it'll still work, because the indentation
1812 * for new paragraphs is correct and the client will reformat the
1813 * message to the reader's screen width.
1815 if (TheMessage->cm_format_type == FMT_CITADEL) {
1816 if (mode == MT_MIME) {
1817 cprintf("Content-type: text/x-citadel-variformat\n\n");
1819 memfmout(mptr, 0, nl);
1822 /* If the message on disk is format 4 (MIME), we've gotta hand it
1823 * off to the MIME parser. The client has already been told that
1824 * this message is format 1 (fixed format), so the callback function
1825 * we use will display those parts as-is.
1827 if (TheMessage->cm_format_type == FMT_RFC822) {
1828 memset(&ma, 0, sizeof(struct ma_info));
1830 if (mode == MT_MIME) {
1831 ma.use_fo_hooks = 0;
1832 strcpy(ma.chosen_part, "1");
1833 ma.chosen_pref = 9999;
1834 mime_parser(mptr, NULL,
1835 *choose_preferred, *fixed_output_pre,
1836 *fixed_output_post, (void *)&ma, 0);
1837 mime_parser(mptr, NULL,
1838 *output_preferred, NULL, NULL, (void *)&ma, 0);
1841 ma.use_fo_hooks = 1;
1842 mime_parser(mptr, NULL,
1843 *fixed_output, *fixed_output_pre,
1844 *fixed_output_post, (void *)&ma, 0);
1849 DONE: /* now we're done */
1850 if (do_proto) cprintf("000\n");
1857 * display a message (mode 0 - Citadel proprietary)
1859 void cmd_msg0(char *cmdbuf)
1862 int headers_only = HEADERS_ALL;
1864 msgid = extract_long(cmdbuf, 0);
1865 headers_only = extract_int(cmdbuf, 1);
1867 CtdlOutputMsg(msgid, MT_CITADEL, headers_only, 1, 0, NULL);
1873 * display a message (mode 2 - RFC822)
1875 void cmd_msg2(char *cmdbuf)
1878 int headers_only = HEADERS_ALL;
1880 msgid = extract_long(cmdbuf, 0);
1881 headers_only = extract_int(cmdbuf, 1);
1883 CtdlOutputMsg(msgid, MT_RFC822, headers_only, 1, 1, NULL);
1889 * display a message (mode 3 - IGnet raw format - internal programs only)
1891 void cmd_msg3(char *cmdbuf)
1894 struct CtdlMessage *msg = NULL;
1897 if (CC->internal_pgm == 0) {
1898 cprintf("%d This command is for internal programs only.\n",
1899 ERROR + HIGHER_ACCESS_REQUIRED);
1903 msgnum = extract_long(cmdbuf, 0);
1904 msg = CtdlFetchMessage(msgnum, 1);
1906 cprintf("%d Message %ld not found.\n",
1907 ERROR + MESSAGE_NOT_FOUND, msgnum);
1911 serialize_message(&smr, msg);
1912 CtdlFreeMessage(msg);
1915 cprintf("%d Unable to serialize message\n",
1916 ERROR + INTERNAL_ERROR);
1920 cprintf("%d %ld\n", BINARY_FOLLOWS, (long)smr.len);
1921 client_write((char *)smr.ser, (int)smr.len);
1928 * Display a message using MIME content types
1930 void cmd_msg4(char *cmdbuf)
1935 msgid = extract_long(cmdbuf, 0);
1936 extract_token(section, cmdbuf, 1, '|', sizeof section);
1937 CtdlOutputMsg(msgid, MT_MIME, 0, 1, 0, (section[0] ? section : NULL) );
1943 * Client tells us its preferred message format(s)
1945 void cmd_msgp(char *cmdbuf)
1947 safestrncpy(CC->preferred_formats, cmdbuf,
1948 sizeof(CC->preferred_formats));
1949 cprintf("%d ok\n", CIT_OK);
1954 * Open a component of a MIME message as a download file
1956 void cmd_opna(char *cmdbuf)
1959 char desired_section[128];
1961 msgid = extract_long(cmdbuf, 0);
1962 extract_token(desired_section, cmdbuf, 1, '|', sizeof desired_section);
1963 safestrncpy(CC->download_desired_section, desired_section,
1964 sizeof CC->download_desired_section);
1965 CtdlOutputMsg(msgid, MT_DOWNLOAD, 0, 1, 1, NULL);
1970 * Open a component of a MIME message and transmit it all at once
1972 void cmd_dlat(char *cmdbuf)
1975 char desired_section[128];
1977 msgid = extract_long(cmdbuf, 0);
1978 extract_token(desired_section, cmdbuf, 1, '|', sizeof desired_section);
1979 safestrncpy(CC->download_desired_section, desired_section,
1980 sizeof CC->download_desired_section);
1981 CtdlOutputMsg(msgid, MT_SPEW_SECTION, 0, 1, 1, NULL);
1986 * Save one or more message pointers into a specified room
1987 * (Returns 0 for success, nonzero for failure)
1988 * roomname may be NULL to use the current room
1990 * Note that the 'supplied_msg' field may be set to NULL, in which case
1991 * the message will be fetched from disk, by number, if we need to perform
1992 * replication checks. This adds an additional database read, so if the
1993 * caller already has the message in memory then it should be supplied. (Obviously
1994 * this mode of operation only works if we're saving a single message.)
1996 int CtdlSaveMsgPointersInRoom(char *roomname, long newmsgidlist[], int num_newmsgs,
1997 int do_repl_check, struct CtdlMessage *supplied_msg)
2000 char hold_rm[ROOMNAMELEN];
2001 struct cdbdata *cdbfr;
2004 long highest_msg = 0L;
2007 struct CtdlMessage *msg = NULL;
2009 long *msgs_to_be_merged = NULL;
2010 int num_msgs_to_be_merged = 0;
2013 "CtdlSaveMsgPointersInRoom(room=%s, num_msgs=%d, repl=%d)\n",
2014 roomname, num_newmsgs, do_repl_check);
2016 strcpy(hold_rm, CC->room.QRname);
2019 if (newmsgidlist == NULL) return(ERROR + INTERNAL_ERROR);
2020 if (num_newmsgs < 1) return(ERROR + INTERNAL_ERROR);
2021 if (num_newmsgs > 1) supplied_msg = NULL;
2023 /* Now the regular stuff */
2024 if (lgetroom(&CC->room,
2025 ((roomname != NULL) ? roomname : CC->room.QRname) )
2027 lprintf(CTDL_ERR, "No such room <%s>\n", roomname);
2028 return(ERROR + ROOM_NOT_FOUND);
2032 msgs_to_be_merged = malloc(sizeof(long) * num_newmsgs);
2033 num_msgs_to_be_merged = 0;
2036 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long));
2037 if (cdbfr == NULL) {
2041 msglist = (long *) cdbfr->ptr;
2042 cdbfr->ptr = NULL; /* CtdlSaveMsgPointerInRoom() now owns this memory */
2043 num_msgs = cdbfr->len / sizeof(long);
2048 /* Create a list of msgid's which were supplied by the caller, but do
2049 * not already exist in the target room. It is absolutely taboo to
2050 * have more than one reference to the same message in a room.
2052 for (i=0; i<num_newmsgs; ++i) {
2054 if (num_msgs > 0) for (j=0; j<num_msgs; ++j) {
2055 if (msglist[j] == newmsgidlist[i]) {
2060 msgs_to_be_merged[num_msgs_to_be_merged++] = newmsgidlist[i];
2064 lprintf(9, "%d unique messages to be merged\n", num_msgs_to_be_merged);
2067 * Now merge the new messages
2069 msglist = realloc(msglist, (sizeof(long) * (num_msgs + num_msgs_to_be_merged)) );
2070 if (msglist == NULL) {
2071 lprintf(CTDL_ALERT, "ERROR: can't realloc message list!\n");
2073 memcpy(&msglist[num_msgs], msgs_to_be_merged, (sizeof(long) * num_msgs_to_be_merged) );
2074 num_msgs += num_msgs_to_be_merged;
2076 /* Sort the message list, so all the msgid's are in order */
2077 num_msgs = sort_msglist(msglist, num_msgs);
2079 /* Determine the highest message number */
2080 highest_msg = msglist[num_msgs - 1];
2082 /* Write it back to disk. */
2083 cdb_store(CDB_MSGLISTS, &CC->room.QRnumber, (int)sizeof(long),
2084 msglist, (int)(num_msgs * sizeof(long)));
2086 /* Free up the memory we used. */
2089 /* Update the highest-message pointer and unlock the room. */
2090 CC->room.QRhighest = highest_msg;
2091 lputroom(&CC->room);
2093 /* Perform replication checks if necessary */
2094 if ( (DoesThisRoomNeedEuidIndexing(&CC->room)) && (do_repl_check) ) {
2095 lprintf(CTDL_DEBUG, "CtdlSaveMsgPointerInRoom() doing repl checks\n");
2097 for (i=0; i<num_msgs_to_be_merged; ++i) {
2098 msgid = msgs_to_be_merged[i];
2100 if (supplied_msg != NULL) {
2104 msg = CtdlFetchMessage(msgid, 0);
2108 ReplicationChecks(msg);
2110 /* If the message has an Exclusive ID, index that... */
2111 if (msg->cm_fields['E'] != NULL) {
2112 index_message_by_euid(msg->cm_fields['E'], &CC->room, msgid);
2115 /* Free up the memory we may have allocated */
2116 if (msg != supplied_msg) {
2117 CtdlFreeMessage(msg);
2125 lprintf(CTDL_DEBUG, "CtdlSaveMsgPointerInRoom() skips repl checks\n");
2128 /* Submit this room for net processing */
2129 network_queue_room(&CC->room, NULL);
2131 #ifdef HAVE_LIBSIEVE
2132 /* If this is someone's inbox, submit the room for sieve processing */
2133 if (!strcasecmp(&CC->room.QRname[11], MAILROOM)) {
2134 sieve_queue_room(&CC->room);
2136 #endif /* HAVE_LIBSIEVE */
2138 /* Go back to the room we were in before we wandered here... */
2139 getroom(&CC->room, hold_rm);
2141 /* Bump the reference count for all messages which were merged */
2142 for (i=0; i<num_msgs_to_be_merged; ++i) {
2143 AdjRefCount(msgs_to_be_merged[i], +1);
2146 /* Free up memory... */
2147 if (msgs_to_be_merged != NULL) {
2148 free(msgs_to_be_merged);
2151 /* Return success. */
2157 * This is the same as CtdlSaveMsgPointersInRoom() but it only accepts
2160 int CtdlSaveMsgPointerInRoom(char *roomname, long msgid,
2161 int do_repl_check, struct CtdlMessage *supplied_msg)
2163 return CtdlSaveMsgPointersInRoom(roomname, &msgid, 1, do_repl_check, supplied_msg);
2170 * Message base operation to save a new message to the message store
2171 * (returns new message number)
2173 * This is the back end for CtdlSubmitMsg() and should not be directly
2174 * called by server-side modules.
2177 long send_message(struct CtdlMessage *msg) {
2185 /* Get a new message number */
2186 newmsgid = get_new_message_number();
2187 snprintf(msgidbuf, sizeof msgidbuf, "%010ld@%s", newmsgid, config.c_fqdn);
2189 /* Generate an ID if we don't have one already */
2190 if (msg->cm_fields['I']==NULL) {
2191 msg->cm_fields['I'] = strdup(msgidbuf);
2194 /* If the message is big, set its body aside for storage elsewhere */
2195 if (msg->cm_fields['M'] != NULL) {
2196 if (strlen(msg->cm_fields['M']) > BIGMSG) {
2198 holdM = msg->cm_fields['M'];
2199 msg->cm_fields['M'] = NULL;
2203 /* Serialize our data structure for storage in the database */
2204 serialize_message(&smr, msg);
2207 msg->cm_fields['M'] = holdM;
2211 cprintf("%d Unable to serialize message\n",
2212 ERROR + INTERNAL_ERROR);
2216 /* Write our little bundle of joy into the message base */
2217 if (cdb_store(CDB_MSGMAIN, &newmsgid, (int)sizeof(long),
2218 smr.ser, smr.len) < 0) {
2219 lprintf(CTDL_ERR, "Can't store message\n");
2223 cdb_store(CDB_BIGMSGS,
2233 /* Free the memory we used for the serialized message */
2236 /* Return the *local* message ID to the caller
2237 * (even if we're storing an incoming network message)
2245 * Serialize a struct CtdlMessage into the format used on disk and network.
2247 * This function loads up a "struct ser_ret" (defined in server.h) which
2248 * contains the length of the serialized message and a pointer to the
2249 * serialized message in memory. THE LATTER MUST BE FREED BY THE CALLER.
2251 void serialize_message(struct ser_ret *ret, /* return values */
2252 struct CtdlMessage *msg) /* unserialized msg */
2254 size_t wlen, fieldlen;
2256 static char *forder = FORDER;
2259 * Check for valid message format
2261 if (is_valid_message(msg) == 0) {
2262 lprintf(CTDL_ERR, "serialize_message() aborting due to invalid message\n");
2269 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL)
2270 ret->len = ret->len +
2271 strlen(msg->cm_fields[(int)forder[i]]) + 2;
2273 ret->ser = malloc(ret->len);
2274 if (ret->ser == NULL) {
2275 lprintf(CTDL_ERR, "serialize_message() malloc(%ld) failed: %s\n",
2276 (long)ret->len, strerror(errno));
2283 ret->ser[1] = msg->cm_anon_type;
2284 ret->ser[2] = msg->cm_format_type;
2287 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL) {
2288 fieldlen = strlen(msg->cm_fields[(int)forder[i]]);
2289 ret->ser[wlen++] = (char)forder[i];
2290 safestrncpy((char *)&ret->ser[wlen], msg->cm_fields[(int)forder[i]], fieldlen+1);
2291 wlen = wlen + fieldlen + 1;
2293 if (ret->len != wlen) lprintf(CTDL_ERR, "ERROR: len=%ld wlen=%ld\n",
2294 (long)ret->len, (long)wlen);
2302 * Check to see if any messages already exist in the current room which
2303 * carry the same Exclusive ID as this one. If any are found, delete them.
2305 void ReplicationChecks(struct CtdlMessage *msg) {
2306 long old_msgnum = (-1L);
2308 if (DoesThisRoomNeedEuidIndexing(&CC->room) == 0) return;
2310 lprintf(CTDL_DEBUG, "Performing replication checks in <%s>\n",
2313 /* No exclusive id? Don't do anything. */
2314 if (msg == NULL) return;
2315 if (msg->cm_fields['E'] == NULL) return;
2316 if (strlen(msg->cm_fields['E']) == 0) return;
2317 /*lprintf(CTDL_DEBUG, "Exclusive ID: <%s> for room <%s>\n",
2318 msg->cm_fields['E'], CC->room.QRname);*/
2320 old_msgnum = locate_message_by_euid(msg->cm_fields['E'], &CC->room);
2321 if (old_msgnum > 0L) {
2322 lprintf(CTDL_DEBUG, "ReplicationChecks() replacing message %ld\n", old_msgnum);
2323 CtdlDeleteMessages(CC->room.QRname, &old_msgnum, 1, "");
2330 * Save a message to disk and submit it into the delivery system.
2332 long CtdlSubmitMsg(struct CtdlMessage *msg, /* message to save */
2333 struct recptypes *recps, /* recipients (if mail) */
2334 char *force /* force a particular room? */
2336 char submit_filename[128];
2337 char generated_timestamp[32];
2338 char hold_rm[ROOMNAMELEN];
2339 char actual_rm[ROOMNAMELEN];
2340 char force_room[ROOMNAMELEN];
2341 char content_type[SIZ]; /* We have to learn this */
2342 char recipient[SIZ];
2345 struct ctdluser userbuf;
2347 struct MetaData smi;
2348 FILE *network_fp = NULL;
2349 static int seqnum = 1;
2350 struct CtdlMessage *imsg = NULL;
2353 char *hold_R, *hold_D;
2354 char *collected_addresses = NULL;
2355 struct addresses_to_be_filed *aptr = NULL;
2356 char *saved_rfc822_version = NULL;
2357 int qualified_for_journaling = 0;
2359 lprintf(CTDL_DEBUG, "CtdlSubmitMsg() called\n");
2360 if (is_valid_message(msg) == 0) return(-1); /* self check */
2362 /* If this message has no timestamp, we take the liberty of
2363 * giving it one, right now.
2365 if (msg->cm_fields['T'] == NULL) {
2366 snprintf(generated_timestamp, sizeof generated_timestamp, "%ld", (long)time(NULL));
2367 msg->cm_fields['T'] = strdup(generated_timestamp);
2370 /* If this message has no path, we generate one.
2372 if (msg->cm_fields['P'] == NULL) {
2373 if (msg->cm_fields['A'] != NULL) {
2374 msg->cm_fields['P'] = strdup(msg->cm_fields['A']);
2375 for (a=0; a<strlen(msg->cm_fields['P']); ++a) {
2376 if (isspace(msg->cm_fields['P'][a])) {
2377 msg->cm_fields['P'][a] = ' ';
2382 msg->cm_fields['P'] = strdup("unknown");
2386 if (force == NULL) {
2387 strcpy(force_room, "");
2390 strcpy(force_room, force);
2393 /* Learn about what's inside, because it's what's inside that counts */
2394 if (msg->cm_fields['M'] == NULL) {
2395 lprintf(CTDL_ERR, "ERROR: attempt to save message with NULL body\n");
2399 switch (msg->cm_format_type) {
2401 strcpy(content_type, "text/x-citadel-variformat");
2404 strcpy(content_type, "text/plain");
2407 strcpy(content_type, "text/plain");
2408 mptr = bmstrcasestr(msg->cm_fields['M'], "Content-type: ");
2410 safestrncpy(content_type, &mptr[14],
2411 sizeof content_type);
2412 for (a = 0; a < strlen(content_type); ++a) {
2413 if ((content_type[a] == ';')
2414 || (content_type[a] == ' ')
2415 || (content_type[a] == 13)
2416 || (content_type[a] == 10)) {
2417 content_type[a] = 0;
2423 /* Goto the correct room */
2424 lprintf(CTDL_DEBUG, "Selected room %s\n", (recps) ? CC->room.QRname : SENTITEMS);
2425 strcpy(hold_rm, CC->room.QRname);
2426 strcpy(actual_rm, CC->room.QRname);
2427 if (recps != NULL) {
2428 strcpy(actual_rm, SENTITEMS);
2431 /* If the user is a twit, move to the twit room for posting */
2433 if (CC->user.axlevel == 2) {
2434 strcpy(hold_rm, actual_rm);
2435 strcpy(actual_rm, config.c_twitroom);
2436 lprintf(CTDL_DEBUG, "Diverting to twit room\n");
2440 /* ...or if this message is destined for Aide> then go there. */
2441 if (strlen(force_room) > 0) {
2442 strcpy(actual_rm, force_room);
2445 lprintf(CTDL_DEBUG, "Final selection: %s\n", actual_rm);
2446 if (strcasecmp(actual_rm, CC->room.QRname)) {
2447 /* getroom(&CC->room, actual_rm); */
2448 usergoto(actual_rm, 0, 1, NULL, NULL);
2452 * If this message has no O (room) field, generate one.
2454 if (msg->cm_fields['O'] == NULL) {
2455 msg->cm_fields['O'] = strdup(CC->room.QRname);
2458 /* Perform "before save" hooks (aborting if any return nonzero) */
2459 lprintf(CTDL_DEBUG, "Performing before-save hooks\n");
2460 if (PerformMessageHooks(msg, EVT_BEFORESAVE) > 0) return(-3);
2463 * If this message has an Exclusive ID, and the room is replication
2464 * checking enabled, then do replication checks.
2466 if (DoesThisRoomNeedEuidIndexing(&CC->room)) {
2467 ReplicationChecks(msg);
2470 /* Save it to disk */
2471 lprintf(CTDL_DEBUG, "Saving to disk\n");
2472 newmsgid = send_message(msg);
2473 if (newmsgid <= 0L) return(-5);
2475 /* Write a supplemental message info record. This doesn't have to
2476 * be a critical section because nobody else knows about this message
2479 lprintf(CTDL_DEBUG, "Creating MetaData record\n");
2480 memset(&smi, 0, sizeof(struct MetaData));
2481 smi.meta_msgnum = newmsgid;
2482 smi.meta_refcount = 0;
2483 safestrncpy(smi.meta_content_type, content_type,
2484 sizeof smi.meta_content_type);
2487 * Measure how big this message will be when rendered as RFC822.
2488 * We do this for two reasons:
2489 * 1. We need the RFC822 length for the new metadata record, so the
2490 * POP and IMAP services don't have to calculate message lengths
2491 * while the user is waiting (multiplied by potentially hundreds
2492 * or thousands of messages).
2493 * 2. If journaling is enabled, we will need an RFC822 version of the
2494 * message to attach to the journalized copy.
2496 if (CC->redirect_buffer != NULL) {
2497 lprintf(CTDL_ALERT, "CC->redirect_buffer is not NULL during message submission!\n");
2500 CC->redirect_buffer = malloc(SIZ);
2501 CC->redirect_len = 0;
2502 CC->redirect_alloc = SIZ;
2503 CtdlOutputPreLoadedMsg(msg, MT_RFC822, HEADERS_ALL, 0, 1);
2504 smi.meta_rfc822_length = CC->redirect_len;
2505 saved_rfc822_version = CC->redirect_buffer;
2506 CC->redirect_buffer = NULL;
2507 CC->redirect_len = 0;
2508 CC->redirect_alloc = 0;
2512 /* Now figure out where to store the pointers */
2513 lprintf(CTDL_DEBUG, "Storing pointers\n");
2515 /* If this is being done by the networker delivering a private
2516 * message, we want to BYPASS saving the sender's copy (because there
2517 * is no local sender; it would otherwise go to the Trashcan).
2519 if ((!CC->internal_pgm) || (recps == NULL)) {
2520 if (CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 1, msg) != 0) {
2521 lprintf(CTDL_ERR, "ERROR saving message pointer!\n");
2522 CtdlSaveMsgPointerInRoom(config.c_aideroom, newmsgid, 0, msg);
2526 /* For internet mail, drop a copy in the outbound queue room */
2528 if (recps->num_internet > 0) {
2529 CtdlSaveMsgPointerInRoom(SMTP_SPOOLOUT_ROOM, newmsgid, 0, msg);
2532 /* If other rooms are specified, drop them there too. */
2534 if (recps->num_room > 0)
2535 for (i=0; i<num_tokens(recps->recp_room, '|'); ++i) {
2536 extract_token(recipient, recps->recp_room, i,
2537 '|', sizeof recipient);
2538 lprintf(CTDL_DEBUG, "Delivering to room <%s>\n", recipient);
2539 CtdlSaveMsgPointerInRoom(recipient, newmsgid, 0, msg);
2542 /* Bump this user's messages posted counter. */
2543 lprintf(CTDL_DEBUG, "Updating user\n");
2544 lgetuser(&CC->user, CC->curr_user);
2545 CC->user.posted = CC->user.posted + 1;
2546 lputuser(&CC->user);
2548 /* If this is private, local mail, make a copy in the
2549 * recipient's mailbox and bump the reference count.
2552 if (recps->num_local > 0)
2553 for (i=0; i<num_tokens(recps->recp_local, '|'); ++i) {
2554 extract_token(recipient, recps->recp_local, i,
2555 '|', sizeof recipient);
2556 lprintf(CTDL_DEBUG, "Delivering private local mail to <%s>\n",
2558 if (getuser(&userbuf, recipient) == 0) {
2559 // Add a flag so the Funambol module knows its mail
2560 msg->cm_fields['W'] = strdup(recipient);
2561 MailboxName(actual_rm, sizeof actual_rm,
2562 &userbuf, MAILROOM);
2563 CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 0, msg);
2564 BumpNewMailCounter(userbuf.usernum);
2565 if (strlen(config.c_funambol_host) > 0) {
2566 /* Generate a instruction message for the Funambol notification
2567 server, in the same style as the SMTP queue */
2568 instr = malloc(SIZ * 2);
2569 snprintf(instr, SIZ * 2,
2570 "Content-type: %s\n\nmsgid|%ld\nsubmitted|%ld\n"
2572 SPOOLMIME, newmsgid, (long)time(NULL),
2573 msg->cm_fields['A'], msg->cm_fields['N']
2576 imsg = malloc(sizeof(struct CtdlMessage));
2577 memset(imsg, 0, sizeof(struct CtdlMessage));
2578 imsg->cm_magic = CTDLMESSAGE_MAGIC;
2579 imsg->cm_anon_type = MES_NORMAL;
2580 imsg->cm_format_type = FMT_RFC822;
2581 imsg->cm_fields['A'] = strdup("Citadel");
2582 imsg->cm_fields['J'] = strdup("do not journal");
2583 imsg->cm_fields['M'] = instr;
2584 imsg->cm_fields['W'] = strdup(recipient);
2585 CtdlSubmitMsg(imsg, NULL, FNBL_QUEUE_ROOM);
2586 CtdlFreeMessage(imsg);
2590 lprintf(CTDL_DEBUG, "No user <%s>\n", recipient);
2591 CtdlSaveMsgPointerInRoom(config.c_aideroom,
2596 /* Perform "after save" hooks */
2597 lprintf(CTDL_DEBUG, "Performing after-save hooks\n");
2598 PerformMessageHooks(msg, EVT_AFTERSAVE);
2600 /* For IGnet mail, we have to save a new copy into the spooler for
2601 * each recipient, with the R and D fields set to the recipient and
2602 * destination-node. This has two ugly side effects: all other
2603 * recipients end up being unlisted in this recipient's copy of the
2604 * message, and it has to deliver multiple messages to the same
2605 * node. We'll revisit this again in a year or so when everyone has
2606 * a network spool receiver that can handle the new style messages.
2609 if (recps->num_ignet > 0)
2610 for (i=0; i<num_tokens(recps->recp_ignet, '|'); ++i) {
2611 extract_token(recipient, recps->recp_ignet, i,
2612 '|', sizeof recipient);
2614 hold_R = msg->cm_fields['R'];
2615 hold_D = msg->cm_fields['D'];
2616 msg->cm_fields['R'] = malloc(SIZ);
2617 msg->cm_fields['D'] = malloc(128);
2618 extract_token(msg->cm_fields['R'], recipient, 0, '@', SIZ);
2619 extract_token(msg->cm_fields['D'], recipient, 1, '@', 128);
2621 serialize_message(&smr, msg);
2623 snprintf(submit_filename, sizeof submit_filename,
2624 "%s/netmail.%04lx.%04x.%04x",
2626 (long) getpid(), CC->cs_pid, ++seqnum);
2627 network_fp = fopen(submit_filename, "wb+");
2628 if (network_fp != NULL) {
2629 fwrite(smr.ser, smr.len, 1, network_fp);
2635 free(msg->cm_fields['R']);
2636 free(msg->cm_fields['D']);
2637 msg->cm_fields['R'] = hold_R;
2638 msg->cm_fields['D'] = hold_D;
2641 /* Go back to the room we started from */
2642 lprintf(CTDL_DEBUG, "Returning to original room %s\n", hold_rm);
2643 if (strcasecmp(hold_rm, CC->room.QRname))
2644 /* getroom(&CC->room, hold_rm); */
2645 usergoto(hold_rm, 0, 1, NULL, NULL);
2647 /* For internet mail, generate delivery instructions.
2648 * Yes, this is recursive. Deal with it. Infinite recursion does
2649 * not happen because the delivery instructions message does not
2650 * contain a recipient.
2653 if (recps->num_internet > 0) {
2654 lprintf(CTDL_DEBUG, "Generating delivery instructions\n");
2655 instr = malloc(SIZ * 2);
2656 snprintf(instr, SIZ * 2,
2657 "Content-type: %s\n\nmsgid|%ld\nsubmitted|%ld\n"
2659 SPOOLMIME, newmsgid, (long)time(NULL),
2660 msg->cm_fields['A'], msg->cm_fields['N']
2663 for (i=0; i<num_tokens(recps->recp_internet, '|'); ++i) {
2664 size_t tmp = strlen(instr);
2665 extract_token(recipient, recps->recp_internet,
2666 i, '|', sizeof recipient);
2667 snprintf(&instr[tmp], SIZ * 2 - tmp,
2668 "remote|%s|0||\n", recipient);
2671 imsg = malloc(sizeof(struct CtdlMessage));
2672 memset(imsg, 0, sizeof(struct CtdlMessage));
2673 imsg->cm_magic = CTDLMESSAGE_MAGIC;
2674 imsg->cm_anon_type = MES_NORMAL;
2675 imsg->cm_format_type = FMT_RFC822;
2676 imsg->cm_fields['A'] = strdup("Citadel");
2677 imsg->cm_fields['J'] = strdup("do not journal");
2678 imsg->cm_fields['M'] = instr;
2679 CtdlSubmitMsg(imsg, NULL, SMTP_SPOOLOUT_ROOM);
2680 CtdlFreeMessage(imsg);
2684 * Any addresses to harvest for someone's address book?
2686 if ( (CC->logged_in) && (recps != NULL) ) {
2687 collected_addresses = harvest_collected_addresses(msg);
2690 if (collected_addresses != NULL) {
2691 begin_critical_section(S_ATBF);
2692 aptr = (struct addresses_to_be_filed *)
2693 malloc(sizeof(struct addresses_to_be_filed));
2695 MailboxName(actual_rm, sizeof actual_rm,
2696 &CC->user, USERCONTACTSROOM);
2697 aptr->roomname = strdup(actual_rm);
2698 aptr->collected_addresses = collected_addresses;
2700 end_critical_section(S_ATBF);
2704 * Determine whether this message qualifies for journaling.
2706 if (msg->cm_fields['J'] != NULL) {
2707 qualified_for_journaling = 0;
2710 if (recps == NULL) {
2711 qualified_for_journaling = config.c_journal_pubmsgs;
2713 else if (recps->num_local + recps->num_ignet + recps->num_internet > 0) {
2714 qualified_for_journaling = config.c_journal_email;
2717 qualified_for_journaling = config.c_journal_pubmsgs;
2722 * Do we have to perform journaling? If so, hand off the saved
2723 * RFC822 version will be handed off to the journaler for background
2724 * submit. Otherwise, we have to free the memory ourselves.
2726 if (saved_rfc822_version != NULL) {
2727 if (qualified_for_journaling) {
2728 JournalBackgroundSubmit(msg, saved_rfc822_version, recps);
2731 free(saved_rfc822_version);
2744 * Convenience function for generating small administrative messages.
2746 void quickie_message(char *from, char *fromaddr, char *to, char *room, char *text,
2747 int format_type, char *subject)
2749 struct CtdlMessage *msg;
2750 struct recptypes *recp = NULL;
2752 msg = malloc(sizeof(struct CtdlMessage));
2753 memset(msg, 0, sizeof(struct CtdlMessage));
2754 msg->cm_magic = CTDLMESSAGE_MAGIC;
2755 msg->cm_anon_type = MES_NORMAL;
2756 msg->cm_format_type = format_type;
2759 msg->cm_fields['A'] = strdup(from);
2761 else if (fromaddr != NULL) {
2762 msg->cm_fields['A'] = strdup(fromaddr);
2763 if (strchr(msg->cm_fields['A'], '@')) {
2764 *strchr(msg->cm_fields['A'], '@') = 0;
2768 msg->cm_fields['A'] = strdup("Citadel");
2771 if (fromaddr != NULL) msg->cm_fields['F'] = strdup(fromaddr);
2772 if (room != NULL) msg->cm_fields['O'] = strdup(room);
2773 msg->cm_fields['N'] = strdup(NODENAME);
2775 msg->cm_fields['R'] = strdup(to);
2776 recp = validate_recipients(to);
2778 if (subject != NULL) {
2779 msg->cm_fields['U'] = strdup(subject);
2781 msg->cm_fields['M'] = strdup(text);
2783 CtdlSubmitMsg(msg, recp, room);
2784 CtdlFreeMessage(msg);
2785 if (recp != NULL) free(recp);
2791 * Back end function used by CtdlMakeMessage() and similar functions
2793 char *CtdlReadMessageBody(char *terminator, /* token signalling EOT */
2794 size_t maxlen, /* maximum message length */
2795 char *exist, /* if non-null, append to it;
2796 exist is ALWAYS freed */
2797 int crlf /* CRLF newlines instead of LF */
2801 size_t message_len = 0;
2802 size_t buffer_len = 0;
2809 if (exist == NULL) {
2816 message_len = strlen(exist);
2817 buffer_len = message_len + 4096;
2818 m = realloc(exist, buffer_len);
2825 /* Do we need to change leading ".." to "." for SMTP escaping? */
2826 if (!strcmp(terminator, ".")) {
2830 /* flush the input if we have nowhere to store it */
2835 /* read in the lines of message text one by one */
2837 if (client_getln(buf, (sizeof buf - 3)) < 1) finished = 1;
2838 if (!strcmp(buf, terminator)) finished = 1;
2840 strcat(buf, "\r\n");
2846 /* Unescape SMTP-style input of two dots at the beginning of the line */
2848 if (!strncmp(buf, "..", 2)) {
2849 strcpy(buf, &buf[1]);
2853 if ( (!flushing) && (!finished) ) {
2854 /* Measure the line */
2855 linelen = strlen(buf);
2857 /* augment the buffer if we have to */
2858 if ((message_len + linelen) >= buffer_len) {
2859 ptr = realloc(m, (buffer_len * 2) );
2860 if (ptr == NULL) { /* flush if can't allocate */
2863 buffer_len = (buffer_len * 2);
2865 lprintf(CTDL_DEBUG, "buffer_len is now %ld\n", (long)buffer_len);
2869 /* Add the new line to the buffer. NOTE: this loop must avoid
2870 * using functions like strcat() and strlen() because they
2871 * traverse the entire buffer upon every call, and doing that
2872 * for a multi-megabyte message slows it down beyond usability.
2874 strcpy(&m[message_len], buf);
2875 message_len += linelen;
2878 /* if we've hit the max msg length, flush the rest */
2879 if (message_len >= maxlen) flushing = 1;
2881 } while (!finished);
2889 * Build a binary message to be saved on disk.
2890 * (NOTE: if you supply 'preformatted_text', the buffer you give it
2891 * will become part of the message. This means you are no longer
2892 * responsible for managing that memory -- it will be freed along with
2893 * the rest of the fields when CtdlFreeMessage() is called.)
2896 struct CtdlMessage *CtdlMakeMessage(
2897 struct ctdluser *author, /* author's user structure */
2898 char *recipient, /* NULL if it's not mail */
2899 char *recp_cc, /* NULL if it's not mail */
2900 char *room, /* room where it's going */
2901 int type, /* see MES_ types in header file */
2902 int format_type, /* variformat, plain text, MIME... */
2903 char *fake_name, /* who we're masquerading as */
2904 char *subject, /* Subject (optional) */
2905 char *supplied_euid, /* ...or NULL if this is irrelevant */
2906 char *preformatted_text /* ...or NULL to read text from client */
2908 char dest_node[256];
2910 struct CtdlMessage *msg;
2912 msg = malloc(sizeof(struct CtdlMessage));
2913 memset(msg, 0, sizeof(struct CtdlMessage));
2914 msg->cm_magic = CTDLMESSAGE_MAGIC;
2915 msg->cm_anon_type = type;
2916 msg->cm_format_type = format_type;
2918 /* Don't confuse the poor folks if it's not routed mail. */
2919 strcpy(dest_node, "");
2924 snprintf(buf, sizeof buf, "cit%ld", author->usernum); /* Path */
2925 msg->cm_fields['P'] = strdup(buf);
2927 snprintf(buf, sizeof buf, "%ld", (long)time(NULL)); /* timestamp */
2928 msg->cm_fields['T'] = strdup(buf);
2930 if (fake_name[0]) /* author */
2931 msg->cm_fields['A'] = strdup(fake_name);
2933 msg->cm_fields['A'] = strdup(author->fullname);
2935 if (CC->room.QRflags & QR_MAILBOX) { /* room */
2936 msg->cm_fields['O'] = strdup(&CC->room.QRname[11]);
2939 msg->cm_fields['O'] = strdup(CC->room.QRname);
2942 msg->cm_fields['N'] = strdup(NODENAME); /* nodename */
2943 msg->cm_fields['H'] = strdup(HUMANNODE); /* hnodename */
2945 if (recipient[0] != 0) {
2946 msg->cm_fields['R'] = strdup(recipient);
2948 if (recp_cc[0] != 0) {
2949 msg->cm_fields['Y'] = strdup(recp_cc);
2951 if (dest_node[0] != 0) {
2952 msg->cm_fields['D'] = strdup(dest_node);
2955 if ( (author == &CC->user) && (strlen(CC->cs_inet_email) > 0) ) {
2956 msg->cm_fields['F'] = strdup(CC->cs_inet_email);
2959 if (subject != NULL) {
2962 length = strlen(subject);
2968 while ((subject[i] != '\0') &&
2969 (IsAscii = isascii(subject[i]) != 0 ))
2972 msg->cm_fields['U'] = strdup(subject);
2973 else /* ok, we've got utf8 in the string. */
2975 msg->cm_fields['U'] = rfc2047encode(subject, length);
2981 if (supplied_euid != NULL) {
2982 msg->cm_fields['E'] = strdup(supplied_euid);
2985 if (preformatted_text != NULL) {
2986 msg->cm_fields['M'] = preformatted_text;
2989 msg->cm_fields['M'] = CtdlReadMessageBody("000", config.c_maxmsglen, NULL, 0);
2997 * Check to see whether we have permission to post a message in the current
2998 * room. Returns a *CITADEL ERROR CODE* and puts a message in errmsgbuf, or
2999 * returns 0 on success.
3001 int CtdlDoIHavePermissionToPostInThisRoom(char *errmsgbuf, size_t n) {
3004 if (!(CC->logged_in)) {
3005 snprintf(errmsgbuf, n, "Not logged in.");
3006 return (ERROR + NOT_LOGGED_IN);
3009 if ((CC->user.axlevel < 2)
3010 && ((CC->room.QRflags & QR_MAILBOX) == 0)) {
3011 snprintf(errmsgbuf, n, "Need to be validated to enter "
3012 "(except in %s> to sysop)", MAILROOM);
3013 return (ERROR + HIGHER_ACCESS_REQUIRED);
3016 CtdlRoomAccess(&CC->room, &CC->user, &ra, NULL);
3017 if (!(ra & UA_POSTALLOWED)) {
3018 snprintf(errmsgbuf, n, "Higher access is required to post in this room.");
3019 return (ERROR + HIGHER_ACCESS_REQUIRED);
3022 strcpy(errmsgbuf, "Ok");
3028 * Check to see if the specified user has Internet mail permission
3029 * (returns nonzero if permission is granted)
3031 int CtdlCheckInternetMailPermission(struct ctdluser *who) {
3033 /* Do not allow twits to send Internet mail */
3034 if (who->axlevel <= 2) return(0);
3036 /* Globally enabled? */
3037 if (config.c_restrict == 0) return(1);
3039 /* User flagged ok? */
3040 if (who->flags & US_INTERNET) return(2);
3042 /* Aide level access? */
3043 if (who->axlevel >= 6) return(3);
3045 /* No mail for you! */
3051 * Validate recipients, count delivery types and errors, and handle aliasing
3052 * FIXME check for dupes!!!!!
3053 * Returns 0 if all addresses are ok, ret->num_error = -1 if no addresses
3054 * were specified, or the number of addresses found invalid.
3055 * caller needs to free the result.
3057 struct recptypes *validate_recipients(char *supplied_recipients) {
3058 struct recptypes *ret;
3059 char recipients[SIZ];
3060 char this_recp[256];
3061 char this_recp_cooked[256];
3067 struct ctdluser tempUS;
3068 struct ctdlroom tempQR;
3072 ret = (struct recptypes *) malloc(sizeof(struct recptypes));
3073 if (ret == NULL) return(NULL);
3074 memset(ret, 0, sizeof(struct recptypes));
3077 ret->num_internet = 0;
3082 if (supplied_recipients == NULL) {
3083 strcpy(recipients, "");
3086 safestrncpy(recipients, supplied_recipients, sizeof recipients);
3089 /* Change all valid separator characters to commas */
3090 for (i=0; i<strlen(recipients); ++i) {
3091 if ((recipients[i] == ';') || (recipients[i] == '|')) {
3092 recipients[i] = ',';
3096 /* Now start extracting recipients... */
3098 while (strlen(recipients) > 0) {
3100 for (i=0; i<=strlen(recipients); ++i) {
3101 if (recipients[i] == '\"') in_quotes = 1 - in_quotes;
3102 if ( ( (recipients[i] == ',') && (!in_quotes) ) || (recipients[i] == 0) ) {
3103 safestrncpy(this_recp, recipients, i+1);
3105 if (recipients[i] == ',') {
3106 strcpy(recipients, &recipients[i+1]);
3109 strcpy(recipients, "");
3116 lprintf(CTDL_DEBUG, "Evaluating recipient #%d: %s\n", num_recps, this_recp);
3118 mailtype = alias(this_recp);
3119 mailtype = alias(this_recp);
3120 mailtype = alias(this_recp);
3121 for (j=0; j<=strlen(this_recp); ++j) {
3122 if (this_recp[j]=='_') {
3123 this_recp_cooked[j] = ' ';
3126 this_recp_cooked[j] = this_recp[j];
3132 if (!strcasecmp(this_recp, "sysop")) {
3134 strcpy(this_recp, config.c_aideroom);
3135 if (strlen(ret->recp_room) > 0) {
3136 strcat(ret->recp_room, "|");
3138 strcat(ret->recp_room, this_recp);
3140 else if (getuser(&tempUS, this_recp) == 0) {
3142 strcpy(this_recp, tempUS.fullname);
3143 if (strlen(ret->recp_local) > 0) {
3144 strcat(ret->recp_local, "|");
3146 strcat(ret->recp_local, this_recp);
3148 else if (getuser(&tempUS, this_recp_cooked) == 0) {
3150 strcpy(this_recp, tempUS.fullname);
3151 if (strlen(ret->recp_local) > 0) {
3152 strcat(ret->recp_local, "|");
3154 strcat(ret->recp_local, this_recp);
3156 else if ( (!strncasecmp(this_recp, "room_", 5))
3157 && (!getroom(&tempQR, &this_recp_cooked[5])) ) {
3159 if (strlen(ret->recp_room) > 0) {
3160 strcat(ret->recp_room, "|");
3162 strcat(ret->recp_room, &this_recp_cooked[5]);
3170 /* Yes, you're reading this correctly: if the target
3171 * domain points back to the local system or an attached
3172 * Citadel directory, the address is invalid. That's
3173 * because if the address were valid, we would have
3174 * already translated it to a local address by now.
3176 if (IsDirectory(this_recp)) {
3181 ++ret->num_internet;
3182 if (strlen(ret->recp_internet) > 0) {
3183 strcat(ret->recp_internet, "|");
3185 strcat(ret->recp_internet, this_recp);
3190 if (strlen(ret->recp_ignet) > 0) {
3191 strcat(ret->recp_ignet, "|");
3193 strcat(ret->recp_ignet, this_recp);
3201 if (strlen(ret->errormsg) == 0) {
3202 snprintf(append, sizeof append,
3203 "Invalid recipient: %s",
3207 snprintf(append, sizeof append,
3210 if ( (strlen(ret->errormsg) + strlen(append)) < SIZ) {
3211 strcat(ret->errormsg, append);
3215 if (strlen(ret->display_recp) == 0) {
3216 strcpy(append, this_recp);
3219 snprintf(append, sizeof append, ", %s",
3222 if ( (strlen(ret->display_recp)+strlen(append)) < SIZ) {
3223 strcat(ret->display_recp, append);
3228 if ((ret->num_local + ret->num_internet + ret->num_ignet +
3229 ret->num_room + ret->num_error) == 0) {
3230 ret->num_error = (-1);
3231 strcpy(ret->errormsg, "No recipients specified.");
3234 lprintf(CTDL_DEBUG, "validate_recipients()\n");
3235 lprintf(CTDL_DEBUG, " local: %d <%s>\n", ret->num_local, ret->recp_local);
3236 lprintf(CTDL_DEBUG, " room: %d <%s>\n", ret->num_room, ret->recp_room);
3237 lprintf(CTDL_DEBUG, " inet: %d <%s>\n", ret->num_internet, ret->recp_internet);
3238 lprintf(CTDL_DEBUG, " ignet: %d <%s>\n", ret->num_ignet, ret->recp_ignet);
3239 lprintf(CTDL_DEBUG, " error: %d <%s>\n", ret->num_error, ret->errormsg);
3247 * message entry - mode 0 (normal)
3249 void cmd_ent0(char *entargs)
3255 char supplied_euid[128];
3257 int format_type = 0;
3258 char newusername[256];
3259 struct CtdlMessage *msg;
3263 struct recptypes *valid = NULL;
3264 struct recptypes *valid_to = NULL;
3265 struct recptypes *valid_cc = NULL;
3266 struct recptypes *valid_bcc = NULL;
3273 post = extract_int(entargs, 0);
3274 extract_token(recp, entargs, 1, '|', sizeof recp);
3275 anon_flag = extract_int(entargs, 2);
3276 format_type = extract_int(entargs, 3);
3277 extract_token(subject, entargs, 4, '|', sizeof subject);
3278 extract_token(newusername, entargs, 5, '|', sizeof newusername);
3279 do_confirm = extract_int(entargs, 6);
3280 extract_token(cc, entargs, 7, '|', sizeof cc);
3281 extract_token(bcc, entargs, 8, '|', sizeof bcc);
3282 switch(CC->room.QRdefaultview) {
3285 extract_token(supplied_euid, entargs, 9, '|', sizeof supplied_euid);
3288 supplied_euid[0] = 0;
3292 /* first check to make sure the request is valid. */
3294 err = CtdlDoIHavePermissionToPostInThisRoom(errmsg, sizeof errmsg);
3297 cprintf("%d %s\n", err, errmsg);
3301 /* Check some other permission type things. */
3303 if (strlen(newusername) == 0)
3305 strcpy(newusername, CC->user.fullname);
3307 if ( (CC->user.axlevel < 6)
3308 && (strcasecmp(newusername, CC->user.fullname))
3309 && (strcasecmp(newusername, CC->cs_inet_fn))
3311 cprintf("%d You don't have permission to author messages as '%s'.\n",
3312 ERROR + HIGHER_ACCESS_REQUIRED,
3318 CC->cs_flags |= CS_POSTING;
3320 /* In the Mail> room we have to behave a little differently --
3321 * make sure the user has specified at least one recipient. Then
3322 * validate the recipient(s).
3324 if ( (CC->room.QRflags & QR_MAILBOX)
3325 && (!strcasecmp(&CC->room.QRname[11], MAILROOM)) ) {
3327 if (CC->user.axlevel < 2) {
3328 strcpy(recp, "sysop");
3333 valid_to = validate_recipients(recp);
3334 if (valid_to->num_error > 0) {
3335 cprintf("%d Invalid recipient (To)\n", ERROR + NO_SUCH_USER);
3340 valid_cc = validate_recipients(cc);
3341 if (valid_cc->num_error > 0) {
3342 cprintf("%d Invalid recipient (CC)\n", ERROR + NO_SUCH_USER);
3348 valid_bcc = validate_recipients(bcc);
3349 if (valid_bcc->num_error > 0) {
3350 cprintf("%d Invalid recipient (BCC)\n", ERROR + NO_SUCH_USER);
3357 /* Recipient required, but none were specified */
3358 if ( (valid_to->num_error < 0) && (valid_cc->num_error < 0) && (valid_bcc->num_error < 0) ) {
3362 cprintf("%d At least one recipient is required.\n", ERROR + NO_SUCH_USER);
3366 if (valid_to->num_internet + valid_cc->num_internet + valid_bcc->num_internet > 0) {
3367 if (CtdlCheckInternetMailPermission(&CC->user)==0) {
3368 cprintf("%d You do not have permission "
3369 "to send Internet mail.\n",
3370 ERROR + HIGHER_ACCESS_REQUIRED);
3378 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)
3379 && (CC->user.axlevel < 4) ) {
3380 cprintf("%d Higher access required for network mail.\n",
3381 ERROR + HIGHER_ACCESS_REQUIRED);
3388 if ((RESTRICT_INTERNET == 1)
3389 && (valid_to->num_internet + valid_cc->num_internet + valid_bcc->num_internet > 0)
3390 && ((CC->user.flags & US_INTERNET) == 0)
3391 && (!CC->internal_pgm)) {
3392 cprintf("%d You don't have access to Internet mail.\n",
3393 ERROR + HIGHER_ACCESS_REQUIRED);
3402 /* Is this a room which has anonymous-only or anonymous-option? */
3403 anonymous = MES_NORMAL;
3404 if (CC->room.QRflags & QR_ANONONLY) {
3405 anonymous = MES_ANONONLY;
3407 if (CC->room.QRflags & QR_ANONOPT) {
3408 if (anon_flag == 1) { /* only if the user requested it */
3409 anonymous = MES_ANONOPT;
3413 if ((CC->room.QRflags & QR_MAILBOX) == 0) {
3417 /* If we're only checking the validity of the request, return
3418 * success without creating the message.
3421 cprintf("%d %s\n", CIT_OK,
3422 ((valid_to != NULL) ? valid_to->display_recp : "") );
3429 /* We don't need these anymore because we'll do it differently below */
3434 /* Read in the message from the client. */
3436 cprintf("%d send message\n", START_CHAT_MODE);
3438 cprintf("%d send message\n", SEND_LISTING);
3441 msg = CtdlMakeMessage(&CC->user, recp, cc,
3442 CC->room.QRname, anonymous, format_type,
3443 newusername, subject,
3444 ((strlen(supplied_euid) > 0) ? supplied_euid : NULL),
3447 /* Put together one big recipients struct containing to/cc/bcc all in
3448 * one. This is for the envelope.
3450 char *all_recps = malloc(SIZ * 3);
3451 strcpy(all_recps, recp);
3452 if (strlen(cc) > 0) {
3453 if (strlen(all_recps) > 0) {
3454 strcat(all_recps, ",");
3456 strcat(all_recps, cc);
3458 if (strlen(bcc) > 0) {
3459 if (strlen(all_recps) > 0) {
3460 strcat(all_recps, ",");
3462 strcat(all_recps, bcc);
3464 if (strlen(all_recps) > 0) {
3465 valid = validate_recipients(all_recps);
3473 msgnum = CtdlSubmitMsg(msg, valid, "");
3476 cprintf("%ld\n", msgnum);
3478 cprintf("Message accepted.\n");
3481 cprintf("Internal error.\n");
3483 if (msg->cm_fields['E'] != NULL) {
3484 cprintf("%s\n", msg->cm_fields['E']);
3491 CtdlFreeMessage(msg);
3493 if (valid != NULL) {
3502 * API function to delete messages which match a set of criteria
3503 * (returns the actual number of messages deleted)
3505 int CtdlDeleteMessages(char *room_name, /* which room */
3506 long *dmsgnums, /* array of msg numbers to be deleted */
3507 int num_dmsgnums, /* number of msgs to be deleted, or 0 for "any" */
3508 char *content_type /* or "" for any */
3512 struct ctdlroom qrbuf;
3513 struct cdbdata *cdbfr;
3514 long *msglist = NULL;
3515 long *dellist = NULL;
3518 int num_deleted = 0;
3520 struct MetaData smi;
3522 lprintf(CTDL_DEBUG, "CtdlDeleteMessages(%s, %d msgs, %s)\n",
3523 room_name, num_dmsgnums, content_type);
3525 /* get room record, obtaining a lock... */
3526 if (lgetroom(&qrbuf, room_name) != 0) {
3527 lprintf(CTDL_ERR, "CtdlDeleteMessages(): Room <%s> not found\n",
3529 return (0); /* room not found */
3531 cdbfr = cdb_fetch(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long));
3533 if (cdbfr != NULL) {
3534 dellist = malloc(cdbfr->len);
3535 msglist = (long *) cdbfr->ptr;
3536 cdbfr->ptr = NULL; /* CtdlDeleteMessages() now owns this memory */
3537 num_msgs = cdbfr->len / sizeof(long);
3541 for (i = 0; i < num_msgs; ++i) {
3544 /* Set/clear a bit for each criterion */
3546 /* 0 messages in the list or a null list means that we are
3547 * interested in deleting any messages which meet the other criteria.
3549 if ((num_dmsgnums == 0) || (dmsgnums == NULL)) {
3550 delete_this |= 0x01;
3553 for (j=0; j<num_dmsgnums; ++j) {
3554 if (msglist[i] == dmsgnums[j]) {
3555 delete_this |= 0x01;
3560 if (strlen(content_type) == 0) {
3561 delete_this |= 0x02;
3563 GetMetaData(&smi, msglist[i]);
3564 if (!strcasecmp(smi.meta_content_type,
3566 delete_this |= 0x02;
3570 /* Delete message only if all bits are set */
3571 if (delete_this == 0x03) {
3572 dellist[num_deleted++] = msglist[i];
3577 num_msgs = sort_msglist(msglist, num_msgs);
3578 cdb_store(CDB_MSGLISTS, &qrbuf.QRnumber, (int)sizeof(long),
3579 msglist, (int)(num_msgs * sizeof(long)));
3581 qrbuf.QRhighest = msglist[num_msgs - 1];
3585 /* Go through the messages we pulled out of the index, and decrement
3586 * their reference counts by 1. If this is the only room the message
3587 * was in, the reference count will reach zero and the message will
3588 * automatically be deleted from the database. We do this in a
3589 * separate pass because there might be plug-in hooks getting called,
3590 * and we don't want that happening during an S_ROOMS critical
3593 if (num_deleted) for (i=0; i<num_deleted; ++i) {
3594 PerformDeleteHooks(qrbuf.QRname, dellist[i]);
3595 AdjRefCount(dellist[i], -1);
3598 /* Now free the memory we used, and go away. */
3599 if (msglist != NULL) free(msglist);
3600 if (dellist != NULL) free(dellist);
3601 lprintf(CTDL_DEBUG, "%d message(s) deleted.\n", num_deleted);
3602 return (num_deleted);
3608 * Check whether the current user has permission to delete messages from
3609 * the current room (returns 1 for yes, 0 for no)
3611 int CtdlDoIHavePermissionToDeleteMessagesFromThisRoom(void) {
3613 CtdlRoomAccess(&CC->room, &CC->user, &ra, NULL);
3614 if (ra & UA_DELETEALLOWED) return(1);
3622 * Delete message from current room
3624 void cmd_dele(char *args)
3633 extract_token(msgset, args, 0, '|', sizeof msgset);
3634 num_msgs = num_tokens(msgset, ',');
3636 cprintf("%d Nothing to do.\n", CIT_OK);
3640 if (CtdlDoIHavePermissionToDeleteMessagesFromThisRoom() == 0) {
3641 cprintf("%d Higher access required.\n",
3642 ERROR + HIGHER_ACCESS_REQUIRED);
3647 * Build our message set to be moved/copied
3649 msgs = malloc(num_msgs * sizeof(long));
3650 for (i=0; i<num_msgs; ++i) {
3651 extract_token(msgtok, msgset, i, ',', sizeof msgtok);
3652 msgs[i] = atol(msgtok);
3655 num_deleted = CtdlDeleteMessages(CC->room.QRname, msgs, num_msgs, "");
3659 cprintf("%d %d message%s deleted.\n", CIT_OK,
3660 num_deleted, ((num_deleted != 1) ? "s" : ""));
3662 cprintf("%d Message not found.\n", ERROR + MESSAGE_NOT_FOUND);
3668 * Back end API function for moves and deletes (multiple messages)
3670 int CtdlCopyMsgsToRoom(long *msgnums, int num_msgs, char *dest) {
3673 err = CtdlSaveMsgPointersInRoom(dest, msgnums, num_msgs, 1, NULL);
3674 if (err != 0) return(err);
3683 * move or copy a message to another room
3685 void cmd_move(char *args)
3692 char targ[ROOMNAMELEN];
3693 struct ctdlroom qtemp;
3700 extract_token(msgset, args, 0, '|', sizeof msgset);
3701 num_msgs = num_tokens(msgset, ',');
3703 cprintf("%d Nothing to do.\n", CIT_OK);
3707 extract_token(targ, args, 1, '|', sizeof targ);
3708 convert_room_name_macros(targ, sizeof targ);
3709 targ[ROOMNAMELEN - 1] = 0;
3710 is_copy = extract_int(args, 2);
3712 if (getroom(&qtemp, targ) != 0) {
3713 cprintf("%d '%s' does not exist.\n",
3714 ERROR + ROOM_NOT_FOUND, targ);
3718 getuser(&CC->user, CC->curr_user);
3719 CtdlRoomAccess(&qtemp, &CC->user, &ra, NULL);
3721 /* Check for permission to perform this operation.
3722 * Remember: "CC->room" is source, "qtemp" is target.
3726 /* Aides can move/copy */
3727 if (CC->user.axlevel >= 6) permit = 1;
3729 /* Room aides can move/copy */
3730 if (CC->user.usernum == CC->room.QRroomaide) permit = 1;
3732 /* Permit move/copy from personal rooms */
3733 if ((CC->room.QRflags & QR_MAILBOX)
3734 && (qtemp.QRflags & QR_MAILBOX)) permit = 1;
3736 /* Permit only copy from public to personal room */
3738 && (!(CC->room.QRflags & QR_MAILBOX))
3739 && (qtemp.QRflags & QR_MAILBOX)) permit = 1;
3741 /* User must have access to target room */
3742 if (!(ra & UA_KNOWN)) permit = 0;
3745 cprintf("%d Higher access required.\n",
3746 ERROR + HIGHER_ACCESS_REQUIRED);
3751 * Build our message set to be moved/copied
3753 msgs = malloc(num_msgs * sizeof(long));
3754 for (i=0; i<num_msgs; ++i) {
3755 extract_token(msgtok, msgset, i, ',', sizeof msgtok);
3756 msgs[i] = atol(msgtok);
3762 err = CtdlCopyMsgsToRoom(msgs, num_msgs, targ);
3764 cprintf("%d Cannot store message(s) in %s: error %d\n",
3770 /* Now delete the message from the source room,
3771 * if this is a 'move' rather than a 'copy' operation.
3774 CtdlDeleteMessages(CC->room.QRname, msgs, num_msgs, "");
3778 cprintf("%d Message(s) %s.\n", CIT_OK, (is_copy ? "copied" : "moved") );
3784 * GetMetaData() - Get the supplementary record for a message
3786 void GetMetaData(struct MetaData *smibuf, long msgnum)
3789 struct cdbdata *cdbsmi;
3792 memset(smibuf, 0, sizeof(struct MetaData));
3793 smibuf->meta_msgnum = msgnum;
3794 smibuf->meta_refcount = 1; /* Default reference count is 1 */
3796 /* Use the negative of the message number for its supp record index */
3797 TheIndex = (0L - msgnum);
3799 cdbsmi = cdb_fetch(CDB_MSGMAIN, &TheIndex, sizeof(long));
3800 if (cdbsmi == NULL) {
3801 return; /* record not found; go with defaults */
3803 memcpy(smibuf, cdbsmi->ptr,
3804 ((cdbsmi->len > sizeof(struct MetaData)) ?
3805 sizeof(struct MetaData) : cdbsmi->len));
3812 * PutMetaData() - (re)write supplementary record for a message
3814 void PutMetaData(struct MetaData *smibuf)
3818 /* Use the negative of the message number for the metadata db index */
3819 TheIndex = (0L - smibuf->meta_msgnum);
3821 cdb_store(CDB_MSGMAIN,
3822 &TheIndex, (int)sizeof(long),
3823 smibuf, (int)sizeof(struct MetaData));
3828 * AdjRefCount - submit an adjustment to the reference count for a message.
3829 * (These are just queued -- we actually process them later.)
3831 void AdjRefCount(long msgnum, int incr)
3833 struct arcq new_arcq;
3835 begin_critical_section(S_SUPPMSGMAIN);
3836 if (arcfp == NULL) {
3837 arcfp = fopen(file_arcq, "ab+");
3839 end_critical_section(S_SUPPMSGMAIN);
3841 /* msgnum < 0 means that we're trying to close the file */
3843 lprintf(CTDL_DEBUG, "Closing the AdjRefCount queue file\n");
3844 begin_critical_section(S_SUPPMSGMAIN);
3845 if (arcfp != NULL) {
3849 end_critical_section(S_SUPPMSGMAIN);
3854 * If we can't open the queue, perform the operation synchronously.
3856 if (arcfp == NULL) {
3857 TDAP_AdjRefCount(msgnum, incr);
3861 new_arcq.arcq_msgnum = msgnum;
3862 new_arcq.arcq_delta = incr;
3863 fwrite(&new_arcq, sizeof(struct arcq), 1, arcfp);
3871 * TDAP_ProcessAdjRefCountQueue()
3873 * Process the queue of message count adjustments that was created by calls
3874 * to AdjRefCount() ... by reading the queue and calling TDAP_AdjRefCount()
3875 * for each one. This should be an "off hours" operation.
3877 int TDAP_ProcessAdjRefCountQueue(void)
3879 char file_arcq_temp[PATH_MAX];
3882 struct arcq arcq_rec;
3883 int num_records_processed = 0;
3885 snprintf(file_arcq_temp, sizeof file_arcq_temp, "%s2", file_arcq);
3887 begin_critical_section(S_SUPPMSGMAIN);
3888 if (arcfp != NULL) {
3893 r = link(file_arcq, file_arcq_temp);
3895 lprintf(CTDL_CRIT, "%s: %s\n", file_arcq_temp, strerror(errno));
3896 end_critical_section(S_SUPPMSGMAIN);
3897 return(num_records_processed);
3901 end_critical_section(S_SUPPMSGMAIN);
3903 fp = fopen(file_arcq_temp, "rb");
3905 lprintf(CTDL_CRIT, "%s: %s\n", file_arcq_temp, strerror(errno));
3906 return(num_records_processed);
3909 while (fread(&arcq_rec, sizeof(struct arcq), 1, fp) == 1) {
3910 TDAP_AdjRefCount(arcq_rec.arcq_msgnum, arcq_rec.arcq_delta);
3911 ++num_records_processed;
3915 r = unlink(file_arcq_temp);
3917 lprintf(CTDL_CRIT, "%s: %s\n", file_arcq_temp, strerror(errno));
3920 return(num_records_processed);
3926 * TDAP_AdjRefCount - adjust the reference count for a message.
3927 * This one does it "for real" because it's called by
3928 * the autopurger function that processes the queue
3929 * created by AdjRefCount(). If a message's reference
3930 * count becomes zero, we also delete the message from
3931 * disk and de-index it.
3933 void TDAP_AdjRefCount(long msgnum, int incr)
3936 struct MetaData smi;
3939 /* This is a *tight* critical section; please keep it that way, as
3940 * it may get called while nested in other critical sections.
3941 * Complicating this any further will surely cause deadlock!
3943 begin_critical_section(S_SUPPMSGMAIN);
3944 GetMetaData(&smi, msgnum);
3945 smi.meta_refcount += incr;
3947 end_critical_section(S_SUPPMSGMAIN);
3948 lprintf(CTDL_DEBUG, "msg %ld ref count delta %d, is now %d\n",
3949 msgnum, incr, smi.meta_refcount);
3951 /* If the reference count is now zero, delete the message
3952 * (and its supplementary record as well).
3954 if (smi.meta_refcount == 0) {
3955 lprintf(CTDL_DEBUG, "Deleting message <%ld>\n", msgnum);
3957 /* Remove from fulltext index */
3958 if (config.c_enable_fulltext) {
3959 ft_index_message(msgnum, 0);
3962 /* Remove from message base */
3964 cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long));
3965 cdb_delete(CDB_BIGMSGS, &delnum, (int)sizeof(long));
3967 /* Remove metadata record */
3968 delnum = (0L - msgnum);
3969 cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long));
3975 * Write a generic object to this room
3977 * Note: this could be much more efficient. Right now we use two temporary
3978 * files, and still pull the message into memory as with all others.
3980 void CtdlWriteObject(char *req_room, /* Room to stuff it in */
3981 char *content_type, /* MIME type of this object */
3982 char *tempfilename, /* Where to fetch it from */
3983 struct ctdluser *is_mailbox, /* Mailbox room? */
3984 int is_binary, /* Is encoding necessary? */
3985 int is_unique, /* Del others of this type? */
3986 unsigned int flags /* Internal save flags */
3991 struct ctdlroom qrbuf;
3992 char roomname[ROOMNAMELEN];
3993 struct CtdlMessage *msg;
3995 char *raw_message = NULL;
3996 char *encoded_message = NULL;
3997 off_t raw_length = 0;
3999 if (is_mailbox != NULL) {
4000 MailboxName(roomname, sizeof roomname, is_mailbox, req_room);
4003 safestrncpy(roomname, req_room, sizeof(roomname));
4006 fp = fopen(tempfilename, "rb");
4008 lprintf(CTDL_CRIT, "Cannot open %s: %s\n",
4009 tempfilename, strerror(errno));
4012 fseek(fp, 0L, SEEK_END);
4013 raw_length = ftell(fp);
4015 lprintf(CTDL_DEBUG, "Raw length is %ld\n", (long)raw_length);
4017 raw_message = malloc((size_t)raw_length + 2);
4018 fread(raw_message, (size_t)raw_length, 1, fp);
4022 encoded_message = malloc((size_t)
4023 (((raw_length * 134) / 100) + 4096 ) );
4026 encoded_message = malloc((size_t)(raw_length + 4096));
4029 sprintf(encoded_message, "Content-type: %s\n", content_type);
4032 sprintf(&encoded_message[strlen(encoded_message)],
4033 "Content-transfer-encoding: base64\n\n"
4037 sprintf(&encoded_message[strlen(encoded_message)],
4038 "Content-transfer-encoding: 7bit\n\n"
4044 &encoded_message[strlen(encoded_message)],
4050 raw_message[raw_length] = 0;
4052 &encoded_message[strlen(encoded_message)],
4060 lprintf(CTDL_DEBUG, "Allocating\n");
4061 msg = malloc(sizeof(struct CtdlMessage));
4062 memset(msg, 0, sizeof(struct CtdlMessage));
4063 msg->cm_magic = CTDLMESSAGE_MAGIC;
4064 msg->cm_anon_type = MES_NORMAL;
4065 msg->cm_format_type = 4;
4066 msg->cm_fields['A'] = strdup(CC->user.fullname);
4067 msg->cm_fields['O'] = strdup(req_room);
4068 msg->cm_fields['N'] = strdup(config.c_nodename);
4069 msg->cm_fields['H'] = strdup(config.c_humannode);
4070 msg->cm_flags = flags;
4072 msg->cm_fields['M'] = encoded_message;
4074 /* Create the requested room if we have to. */
4075 if (getroom(&qrbuf, roomname) != 0) {
4076 create_room(roomname,
4077 ( (is_mailbox != NULL) ? 5 : 3 ),
4078 "", 0, 1, 0, VIEW_BBS);
4080 /* If the caller specified this object as unique, delete all
4081 * other objects of this type that are currently in the room.
4084 lprintf(CTDL_DEBUG, "Deleted %d other msgs of this type\n",
4085 CtdlDeleteMessages(roomname, NULL, 0, content_type)
4088 /* Now write the data */
4089 CtdlSubmitMsg(msg, NULL, roomname);
4090 CtdlFreeMessage(msg);
4098 void CtdlGetSysConfigBackend(long msgnum, void *userdata) {
4099 config_msgnum = msgnum;
4103 char *CtdlGetSysConfig(char *sysconfname) {
4104 char hold_rm[ROOMNAMELEN];
4107 struct CtdlMessage *msg;
4110 strcpy(hold_rm, CC->room.QRname);
4111 if (getroom(&CC->room, SYSCONFIGROOM) != 0) {
4112 getroom(&CC->room, hold_rm);
4117 /* We want the last (and probably only) config in this room */
4118 begin_critical_section(S_CONFIG);
4119 config_msgnum = (-1L);
4120 CtdlForEachMessage(MSGS_LAST, 1, NULL, sysconfname, NULL,
4121 CtdlGetSysConfigBackend, NULL);
4122 msgnum = config_msgnum;
4123 end_critical_section(S_CONFIG);
4129 msg = CtdlFetchMessage(msgnum, 1);
4131 conf = strdup(msg->cm_fields['M']);
4132 CtdlFreeMessage(msg);
4139 getroom(&CC->room, hold_rm);
4141 if (conf != NULL) do {
4142 extract_token(buf, conf, 0, '\n', sizeof buf);
4143 strcpy(conf, &conf[strlen(buf)+1]);
4144 } while ( (strlen(conf)>0) && (strlen(buf)>0) );
4149 void CtdlPutSysConfig(char *sysconfname, char *sysconfdata) {
4150 char temp[PATH_MAX];
4153 CtdlMakeTempFileName(temp, sizeof temp);
4155 fp = fopen(temp, "w");
4156 if (fp == NULL) return;
4157 fprintf(fp, "%s", sysconfdata);
4160 /* this handy API function does all the work for us */
4161 CtdlWriteObject(SYSCONFIGROOM, sysconfname, temp, NULL, 0, 1, 0);
4167 * Determine whether a given Internet address belongs to the current user
4169 int CtdlIsMe(char *addr, int addr_buf_len)
4171 struct recptypes *recp;
4174 recp = validate_recipients(addr);
4175 if (recp == NULL) return(0);
4177 if (recp->num_local == 0) {
4182 for (i=0; i<recp->num_local; ++i) {
4183 extract_token(addr, recp->recp_local, i, '|', addr_buf_len);
4184 if (!strcasecmp(addr, CC->user.fullname)) {
4196 * Citadel protocol command to do the same
4198 void cmd_isme(char *argbuf) {
4201 if (CtdlAccessCheck(ac_logged_in)) return;
4202 extract_token(addr, argbuf, 0, '|', sizeof addr);
4204 if (CtdlIsMe(addr, sizeof addr)) {
4205 cprintf("%d %s\n", CIT_OK, addr);
4208 cprintf("%d Not you.\n", ERROR + ILLEGAL_VALUE);