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 */
250 fp = fopen(file_citadel_control, "r");
252 lprintf(CTDL_CRIT, "Cannot open %s: %s\n",
253 file_citadel_control,
257 fread((char *) &CitControl, sizeof(struct CitControl), 1, fp);
263 * Back end for the MSGS command: output message number only.
265 void simple_listing(long msgnum, void *userdata)
267 cprintf("%ld\n", msgnum);
273 * Back end for the MSGS command: output header summary.
275 void headers_listing(long msgnum, void *userdata)
277 struct CtdlMessage *msg;
279 msg = CtdlFetchMessage(msgnum, 0);
281 cprintf("%ld|0|||||\n", msgnum);
285 cprintf("%ld|%s|%s|%s|%s|%s|\n",
287 (msg->cm_fields['T'] ? msg->cm_fields['T'] : "0"),
288 (msg->cm_fields['A'] ? msg->cm_fields['A'] : ""),
289 (msg->cm_fields['N'] ? msg->cm_fields['N'] : ""),
290 (msg->cm_fields['F'] ? msg->cm_fields['F'] : ""),
291 (msg->cm_fields['U'] ? msg->cm_fields['U'] : "")
293 CtdlFreeMessage(msg);
298 /* Determine if a given message matches the fields in a message template.
299 * Return 0 for a successful match.
301 int CtdlMsgCmp(struct CtdlMessage *msg, struct CtdlMessage *template) {
304 /* If there aren't any fields in the template, all messages will
307 if (template == NULL) return(0);
309 /* Null messages are bogus. */
310 if (msg == NULL) return(1);
312 for (i='A'; i<='Z'; ++i) {
313 if (template->cm_fields[i] != NULL) {
314 if (msg->cm_fields[i] == NULL) {
317 if (strcasecmp(msg->cm_fields[i],
318 template->cm_fields[i])) return 1;
322 /* All compares succeeded: we have a match! */
329 * Retrieve the "seen" message list for the current room.
331 void CtdlGetSeen(char *buf, int which_set) {
334 /* Learn about the user and room in question */
335 CtdlGetRelationship(&vbuf, &CC->user, &CC->room);
337 if (which_set == ctdlsetseen_seen)
338 safestrncpy(buf, vbuf.v_seen, SIZ);
339 if (which_set == ctdlsetseen_answered)
340 safestrncpy(buf, vbuf.v_answered, SIZ);
346 * Manipulate the "seen msgs" string (or other message set strings)
348 void CtdlSetSeen(long *target_msgnums, int num_target_msgnums,
349 int target_setting, int which_set,
350 struct ctdluser *which_user, struct ctdlroom *which_room) {
351 struct cdbdata *cdbfr;
363 char *is_set; /* actually an array of booleans */
366 char setstr[SIZ], lostr[SIZ], histr[SIZ];
369 /* Don't bother doing *anything* if we were passed a list of zero messages */
370 if (num_target_msgnums < 1) {
374 lprintf(CTDL_DEBUG, "CtdlSetSeen(%d msgs starting with %ld, %d, %d)\n",
375 num_target_msgnums, target_msgnums[0],
376 target_setting, which_set);
378 /* Learn about the user and room in question */
379 CtdlGetRelationship(&vbuf,
380 ((which_user != NULL) ? which_user : &CC->user),
381 ((which_room != NULL) ? which_room : &CC->room)
384 /* Load the message list */
385 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long));
387 msglist = (long *) cdbfr->ptr;
388 cdbfr->ptr = NULL; /* CtdlSetSeen() now owns this memory */
389 num_msgs = cdbfr->len / sizeof(long);
392 return; /* No messages at all? No further action. */
395 is_set = malloc(num_msgs * sizeof(char));
396 memset(is_set, 0, (num_msgs * sizeof(char)) );
398 /* Decide which message set we're manipulating */
400 case ctdlsetseen_seen:
401 safestrncpy(vset, vbuf.v_seen, sizeof vset);
403 case ctdlsetseen_answered:
404 safestrncpy(vset, vbuf.v_answered, sizeof vset);
408 /* lprintf(CTDL_DEBUG, "before optimize: %s\n", vset); */
410 /* Translate the existing sequence set into an array of booleans */
411 num_sets = num_tokens(vset, ',');
412 for (s=0; s<num_sets; ++s) {
413 extract_token(setstr, vset, s, ',', sizeof setstr);
415 extract_token(lostr, setstr, 0, ':', sizeof lostr);
416 if (num_tokens(setstr, ':') >= 2) {
417 extract_token(histr, setstr, 1, ':', sizeof histr);
418 if (!strcmp(histr, "*")) {
419 snprintf(histr, sizeof histr, "%ld", LONG_MAX);
423 strcpy(histr, lostr);
428 for (i = 0; i < num_msgs; ++i) {
429 if ((msglist[i] >= lo) && (msglist[i] <= hi)) {
435 /* Now translate the array of booleans back into a sequence set */
440 for (i=0; i<num_msgs; ++i) {
442 is_seen = is_set[i]; /* Default to existing setting */
444 for (k=0; k<num_target_msgnums; ++k) {
445 if (msglist[i] == target_msgnums[k]) {
446 is_seen = target_setting;
451 if (lo < 0L) lo = msglist[i];
455 if ( ((is_seen == 0) && (was_seen == 1))
456 || ((is_seen == 1) && (i == num_msgs-1)) ) {
458 /* begin trim-o-matic code */
461 while ( (strlen(vset) + 20) > sizeof vset) {
462 remove_token(vset, 0, ',');
464 if (j--) break; /* loop no more than 9 times */
466 if ( (trimming) && (which_set == ctdlsetseen_seen) ) {
470 snprintf(lostr, sizeof lostr,
471 "1:%ld,%s", t, vset);
472 safestrncpy(vset, lostr, sizeof vset);
474 /* end trim-o-matic code */
482 snprintf(&vset[tmp], (sizeof vset) - tmp,
486 snprintf(&vset[tmp], (sizeof vset) - tmp,
495 /* Decide which message set we're manipulating */
497 case ctdlsetseen_seen:
498 safestrncpy(vbuf.v_seen, vset, sizeof vbuf.v_seen);
500 case ctdlsetseen_answered:
501 safestrncpy(vbuf.v_answered, vset,
502 sizeof vbuf.v_answered);
507 /* lprintf(CTDL_DEBUG, " after optimize: %s\n", vset); */
509 CtdlSetRelationship(&vbuf,
510 ((which_user != NULL) ? which_user : &CC->user),
511 ((which_room != NULL) ? which_room : &CC->room)
517 * API function to perform an operation for each qualifying message in the
518 * current room. (Returns the number of messages processed.)
520 int CtdlForEachMessage(int mode, long ref, char *search_string,
522 struct CtdlMessage *compare,
523 void (*CallBack) (long, void *),
529 struct cdbdata *cdbfr;
530 long *msglist = NULL;
532 int num_processed = 0;
535 struct CtdlMessage *msg = NULL;
538 int printed_lastold = 0;
539 int num_search_msgs = 0;
540 long *search_msgs = NULL;
542 /* Learn about the user and room in question */
544 getuser(&CC->user, CC->curr_user);
545 CtdlGetRelationship(&vbuf, &CC->user, &CC->room);
547 /* Load the message list */
548 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long));
550 msglist = (long *) cdbfr->ptr;
551 cdbfr->ptr = NULL; /* CtdlForEachMessage() now owns this memory */
552 num_msgs = cdbfr->len / sizeof(long);
555 return 0; /* No messages at all? No further action. */
560 * Now begin the traversal.
562 if (num_msgs > 0) for (a = 0; a < num_msgs; ++a) {
564 /* If the caller is looking for a specific MIME type, filter
565 * out all messages which are not of the type requested.
567 if (content_type != NULL) if (strlen(content_type) > 0) {
569 /* This call to GetMetaData() sits inside this loop
570 * so that we only do the extra database read per msg
571 * if we need to. Doing the extra read all the time
572 * really kills the server. If we ever need to use
573 * metadata for another search criterion, we need to
574 * move the read somewhere else -- but still be smart
575 * enough to only do the read if the caller has
576 * specified something that will need it.
578 GetMetaData(&smi, msglist[a]);
580 if (strcasecmp(smi.meta_content_type, content_type)) {
586 num_msgs = sort_msglist(msglist, num_msgs);
588 /* If a template was supplied, filter out the messages which
589 * don't match. (This could induce some delays!)
592 if (compare != NULL) {
593 for (a = 0; a < num_msgs; ++a) {
594 msg = CtdlFetchMessage(msglist[a], 1);
596 if (CtdlMsgCmp(msg, compare)) {
599 CtdlFreeMessage(msg);
605 /* If a search string was specified, get a message list from
606 * the full text index and remove messages which aren't on both
610 * Since the lists are sorted and strictly ascending, and the
611 * output list is guaranteed to be shorter than or equal to the
612 * input list, we overwrite the bottom of the input list. This
613 * eliminates the need to memmove big chunks of the list over and
616 if ( (num_msgs > 0) && (mode == MSGS_SEARCH) && (search_string) ) {
617 ft_search(&num_search_msgs, &search_msgs, search_string);
618 if (num_search_msgs > 0) {
622 orig_num_msgs = num_msgs;
624 for (i=0; i<orig_num_msgs; ++i) {
625 for (j=0; j<num_search_msgs; ++j) {
626 if (msglist[i] == search_msgs[j]) {
627 msglist[num_msgs++] = msglist[i];
633 num_msgs = 0; /* No messages qualify */
635 if (search_msgs != NULL) free(search_msgs);
637 /* Now that we've purged messages which don't contain the search
638 * string, treat a MSGS_SEARCH just like a MSGS_ALL from this
645 * Now iterate through the message list, according to the
646 * criteria supplied by the caller.
649 for (a = 0; a < num_msgs; ++a) {
650 thismsg = msglist[a];
651 if (mode == MSGS_ALL) {
655 is_seen = is_msg_in_sequence_set(
656 vbuf.v_seen, thismsg);
657 if (is_seen) lastold = thismsg;
663 || ((mode == MSGS_OLD) && (is_seen))
664 || ((mode == MSGS_NEW) && (!is_seen))
665 || ((mode == MSGS_LAST) && (a >= (num_msgs - ref)))
666 || ((mode == MSGS_FIRST) && (a < ref))
667 || ((mode == MSGS_GT) && (thismsg > ref))
668 || ((mode == MSGS_EQ) && (thismsg == ref))
671 if ((mode == MSGS_NEW) && (CC->user.flags & US_LASTOLD) && (lastold > 0L) && (printed_lastold == 0) && (!is_seen)) {
673 CallBack(lastold, userdata);
677 if (CallBack) CallBack(thismsg, userdata);
681 free(msglist); /* Clean up */
682 return num_processed;
688 * cmd_msgs() - get list of message #'s in this room
689 * implements the MSGS server command using CtdlForEachMessage()
691 void cmd_msgs(char *cmdbuf)
700 int with_template = 0;
701 struct CtdlMessage *template = NULL;
702 int with_headers = 0;
703 char search_string[1024];
705 extract_token(which, cmdbuf, 0, '|', sizeof which);
706 cm_ref = extract_int(cmdbuf, 1);
707 extract_token(search_string, cmdbuf, 1, '|', sizeof search_string);
708 with_template = extract_int(cmdbuf, 2);
709 with_headers = extract_int(cmdbuf, 3);
712 if (!strncasecmp(which, "OLD", 3))
714 else if (!strncasecmp(which, "NEW", 3))
716 else if (!strncasecmp(which, "FIRST", 5))
718 else if (!strncasecmp(which, "LAST", 4))
720 else if (!strncasecmp(which, "GT", 2))
722 else if (!strncasecmp(which, "SEARCH", 6))
727 if ((!(CC->logged_in)) && (!(CC->internal_pgm))) {
728 cprintf("%d not logged in\n", ERROR + NOT_LOGGED_IN);
732 if ( (mode == MSGS_SEARCH) && (!config.c_enable_fulltext) ) {
733 cprintf("%d Full text index is not enabled on this server.\n",
734 ERROR + CMD_NOT_SUPPORTED);
740 cprintf("%d Send template then receive message list\n",
742 template = (struct CtdlMessage *)
743 malloc(sizeof(struct CtdlMessage));
744 memset(template, 0, sizeof(struct CtdlMessage));
745 template->cm_magic = CTDLMESSAGE_MAGIC;
746 template->cm_anon_type = MES_NORMAL;
748 while(client_getln(buf, sizeof buf), strcmp(buf,"000")) {
749 extract_token(tfield, buf, 0, '|', sizeof tfield);
750 extract_token(tvalue, buf, 1, '|', sizeof tvalue);
751 for (i='A'; i<='Z'; ++i) if (msgkeys[i]!=NULL) {
752 if (!strcasecmp(tfield, msgkeys[i])) {
753 template->cm_fields[i] =
761 cprintf("%d \n", LISTING_FOLLOWS);
764 CtdlForEachMessage(mode,
765 ( (mode == MSGS_SEARCH) ? 0 : cm_ref ),
766 ( (mode == MSGS_SEARCH) ? search_string : NULL ),
769 (with_headers ? headers_listing : simple_listing),
772 if (template != NULL) CtdlFreeMessage(template);
780 * help_subst() - support routine for help file viewer
782 void help_subst(char *strbuf, char *source, char *dest)
787 while (p = pattern2(strbuf, source), (p >= 0)) {
788 strcpy(workbuf, &strbuf[p + strlen(source)]);
789 strcpy(&strbuf[p], dest);
790 strcat(strbuf, workbuf);
795 void do_help_subst(char *buffer)
799 help_subst(buffer, "^nodename", config.c_nodename);
800 help_subst(buffer, "^humannode", config.c_humannode);
801 help_subst(buffer, "^fqdn", config.c_fqdn);
802 help_subst(buffer, "^username", CC->user.fullname);
803 snprintf(buf2, sizeof buf2, "%ld", CC->user.usernum);
804 help_subst(buffer, "^usernum", buf2);
805 help_subst(buffer, "^sysadm", config.c_sysadm);
806 help_subst(buffer, "^variantname", CITADEL);
807 snprintf(buf2, sizeof buf2, "%d", config.c_maxsessions);
808 help_subst(buffer, "^maxsessions", buf2);
809 help_subst(buffer, "^bbsdir", ctdl_bbsbase_dir);
815 * memfmout() - Citadel text formatter and paginator.
816 * Although the original purpose of this routine was to format
817 * text to the reader's screen width, all we're really using it
818 * for here is to format text out to 80 columns before sending it
819 * to the client. The client software may reformat it again.
822 char *mptr, /* where are we going to get our text from? */
823 char subst, /* nonzero if we should do substitutions */
824 char *nl) /* string to terminate lines with */
832 static int width = 80;
837 c = 1; /* c is the current pos */
841 while (ch = *mptr, ((ch != 0) && (strlen(buffer) < 126))) {
843 buffer[strlen(buffer) + 1] = 0;
844 buffer[strlen(buffer)] = ch;
847 if (buffer[0] == '^')
848 do_help_subst(buffer);
850 buffer[strlen(buffer) + 1] = 0;
852 strcpy(buffer, &buffer[1]);
860 if (((ch == 13) || (ch == 10)) && (old != 13) && (old != 10)) {
863 if (((old == 13) || (old == 10)) && (isspace(real))) {
868 if (((strlen(aaa) + c) > (width - 5)) && (strlen(aaa) > (width - 5))) {
869 cprintf("%s%s", nl, aaa);
878 if ((strlen(aaa) + c) > (width - 5)) {
887 if ((ch == 13) || (ch == 10)) {
888 cprintf("%s%s", aaa, nl);
895 cprintf("%s%s", aaa, nl);
901 * Callback function for mime parser that simply lists the part
903 void list_this_part(char *name, char *filename, char *partnum, char *disp,
904 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
909 ma = (struct ma_info *)cbuserdata;
910 if (ma->is_ma == 0) {
911 cprintf("part=%s|%s|%s|%s|%s|%ld\n",
912 name, filename, partnum, disp, cbtype, (long)length);
917 * Callback function for multipart prefix
919 void list_this_pref(char *name, char *filename, char *partnum, char *disp,
920 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
925 ma = (struct ma_info *)cbuserdata;
926 if (!strcasecmp(cbtype, "multipart/alternative")) {
930 if (ma->is_ma == 0) {
931 cprintf("pref=%s|%s\n", partnum, cbtype);
936 * Callback function for multipart sufffix
938 void list_this_suff(char *name, char *filename, char *partnum, char *disp,
939 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
944 ma = (struct ma_info *)cbuserdata;
945 if (ma->is_ma == 0) {
946 cprintf("suff=%s|%s\n", partnum, cbtype);
948 if (!strcasecmp(cbtype, "multipart/alternative")) {
955 * Callback function for mime parser that opens a section for downloading
957 void mime_download(char *name, char *filename, char *partnum, char *disp,
958 void *content, char *cbtype, char *cbcharset, size_t length,
959 char *encoding, void *cbuserdata)
962 /* Silently go away if there's already a download open... */
963 if (CC->download_fp != NULL)
966 /* ...or if this is not the desired section */
967 if (strcasecmp(CC->download_desired_section, partnum))
970 CC->download_fp = tmpfile();
971 if (CC->download_fp == NULL)
974 fwrite(content, length, 1, CC->download_fp);
975 fflush(CC->download_fp);
976 rewind(CC->download_fp);
978 OpenCmdResult(filename, cbtype);
984 * Callback function for mime parser that outputs a section all at once
986 void mime_spew_section(char *name, char *filename, char *partnum, char *disp,
987 void *content, char *cbtype, char *cbcharset, size_t length,
988 char *encoding, void *cbuserdata)
990 int *found_it = (int *)cbuserdata;
992 /* ...or if this is not the desired section */
993 if (strcasecmp(CC->download_desired_section, partnum))
998 cprintf("%d %d\n", BINARY_FOLLOWS, length);
999 client_write(content, length);
1005 * Load a message from disk into memory.
1006 * This is used by CtdlOutputMsg() and other fetch functions.
1008 * NOTE: Caller is responsible for freeing the returned CtdlMessage struct
1009 * using the CtdlMessageFree() function.
1011 struct CtdlMessage *CtdlFetchMessage(long msgnum, int with_body)
1013 struct cdbdata *dmsgtext;
1014 struct CtdlMessage *ret = NULL;
1018 cit_uint8_t field_header;
1020 lprintf(CTDL_DEBUG, "CtdlFetchMessage(%ld, %d)\n", msgnum, with_body);
1022 dmsgtext = cdb_fetch(CDB_MSGMAIN, &msgnum, sizeof(long));
1023 if (dmsgtext == NULL) {
1026 mptr = dmsgtext->ptr;
1027 upper_bound = mptr + dmsgtext->len;
1029 /* Parse the three bytes that begin EVERY message on disk.
1030 * The first is always 0xFF, the on-disk magic number.
1031 * The second is the anonymous/public type byte.
1032 * The third is the format type byte (vari, fixed, or MIME).
1037 "Message %ld appears to be corrupted.\n",
1042 ret = (struct CtdlMessage *) malloc(sizeof(struct CtdlMessage));
1043 memset(ret, 0, sizeof(struct CtdlMessage));
1045 ret->cm_magic = CTDLMESSAGE_MAGIC;
1046 ret->cm_anon_type = *mptr++; /* Anon type byte */
1047 ret->cm_format_type = *mptr++; /* Format type byte */
1050 * The rest is zero or more arbitrary fields. Load them in.
1051 * We're done when we encounter either a zero-length field or
1052 * have just processed the 'M' (message text) field.
1055 if (mptr >= upper_bound) {
1058 field_header = *mptr++;
1059 ret->cm_fields[field_header] = strdup(mptr);
1061 while (*mptr++ != 0); /* advance to next field */
1063 } while ((mptr < upper_bound) && (field_header != 'M'));
1067 /* Always make sure there's something in the msg text field. If
1068 * it's NULL, the message text is most likely stored separately,
1069 * so go ahead and fetch that. Failing that, just set a dummy
1070 * body so other code doesn't barf.
1072 if ( (ret->cm_fields['M'] == NULL) && (with_body) ) {
1073 dmsgtext = cdb_fetch(CDB_BIGMSGS, &msgnum, sizeof(long));
1074 if (dmsgtext != NULL) {
1075 ret->cm_fields['M'] = strdup(dmsgtext->ptr);
1079 if (ret->cm_fields['M'] == NULL) {
1080 ret->cm_fields['M'] = strdup("\r\n\r\n (no text)\r\n");
1083 /* Perform "before read" hooks (aborting if any return nonzero) */
1084 if (PerformMessageHooks(ret, EVT_BEFOREREAD) > 0) {
1085 CtdlFreeMessage(ret);
1094 * Returns 1 if the supplied pointer points to a valid Citadel message.
1095 * If the pointer is NULL or the magic number check fails, returns 0.
1097 int is_valid_message(struct CtdlMessage *msg) {
1100 if ((msg->cm_magic) != CTDLMESSAGE_MAGIC) {
1101 lprintf(CTDL_WARNING, "is_valid_message() -- self-check failed\n");
1109 * 'Destructor' for struct CtdlMessage
1111 void CtdlFreeMessage(struct CtdlMessage *msg)
1115 if (is_valid_message(msg) == 0)
1117 if (msg != NULL) free (msg);
1121 for (i = 0; i < 256; ++i)
1122 if (msg->cm_fields[i] != NULL) {
1123 free(msg->cm_fields[i]);
1126 msg->cm_magic = 0; /* just in case */
1132 * Pre callback function for multipart/alternative
1134 * NOTE: this differs from the standard behavior for a reason. Normally when
1135 * displaying multipart/alternative you want to show the _last_ usable
1136 * format in the message. Here we show the _first_ one, because it's
1137 * usually text/plain. Since this set of functions is designed for text
1138 * output to non-MIME-aware clients, this is the desired behavior.
1141 void fixed_output_pre(char *name, char *filename, char *partnum, char *disp,
1142 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
1147 ma = (struct ma_info *)cbuserdata;
1148 lprintf(CTDL_DEBUG, "fixed_output_pre() type=<%s>\n", cbtype);
1149 if (!strcasecmp(cbtype, "multipart/alternative")) {
1153 if (!strcasecmp(cbtype, "message/rfc822")) {
1159 * Post callback function for multipart/alternative
1161 void fixed_output_post(char *name, char *filename, char *partnum, char *disp,
1162 void *content, char *cbtype, char *cbcharset, size_t length,
1163 char *encoding, void *cbuserdata)
1167 ma = (struct ma_info *)cbuserdata;
1168 lprintf(CTDL_DEBUG, "fixed_output_post() type=<%s>\n", cbtype);
1169 if (!strcasecmp(cbtype, "multipart/alternative")) {
1173 if (!strcasecmp(cbtype, "message/rfc822")) {
1179 * Inline callback function for mime parser that wants to display text
1181 void fixed_output(char *name, char *filename, char *partnum, char *disp,
1182 void *content, char *cbtype, char *cbcharset, size_t length,
1183 char *encoding, void *cbuserdata)
1190 ma = (struct ma_info *)cbuserdata;
1193 "fixed_output() part %s: %s (%s) (%ld bytes)\n",
1194 partnum, filename, cbtype, (long)length);
1197 * If we're in the middle of a multipart/alternative scope and
1198 * we've already printed another section, skip this one.
1200 if ( (ma->is_ma) && (ma->did_print) ) {
1201 lprintf(CTDL_DEBUG, "Skipping part %s (%s)\n",
1207 if ( (!strcasecmp(cbtype, "text/plain"))
1208 || (strlen(cbtype)==0) ) {
1211 client_write(wptr, length);
1212 if (wptr[length-1] != '\n') {
1219 if (!strcasecmp(cbtype, "text/html")) {
1220 ptr = html_to_ascii(content, length, 80, 0);
1222 client_write(ptr, wlen);
1223 if (ptr[wlen-1] != '\n') {
1230 if (ma->use_fo_hooks) {
1231 if (PerformFixedOutputHooks(cbtype, content, length)) {
1232 /* above function returns nonzero if it handled the part */
1237 if (strncasecmp(cbtype, "multipart/", 10)) {
1238 cprintf("Part %s: %s (%s) (%ld bytes)\r\n",
1239 partnum, filename, cbtype, (long)length);
1245 * The client is elegant and sophisticated and wants to be choosy about
1246 * MIME content types, so figure out which multipart/alternative part
1247 * we're going to send.
1249 * We use a system of weights. When we find a part that matches one of the
1250 * MIME types we've declared as preferential, we can store it in ma->chosen_part
1251 * and then set ma->chosen_pref to that MIME type's position in our preference
1252 * list. If we then hit another match, we only replace the first match if
1253 * the preference value is lower.
1255 void choose_preferred(char *name, char *filename, char *partnum, char *disp,
1256 void *content, char *cbtype, char *cbcharset, size_t length,
1257 char *encoding, void *cbuserdata)
1263 ma = (struct ma_info *)cbuserdata;
1265 if (ma->is_ma > 0) {
1266 for (i=0; i<num_tokens(CC->preferred_formats, '|'); ++i) {
1267 extract_token(buf, CC->preferred_formats, i, '|', sizeof buf);
1268 lprintf(CTDL_DEBUG, "Is <%s> == <%s> ??\n", buf, cbtype);
1269 if ( (!strcasecmp(buf, cbtype)) && (!ma->freeze) ) {
1270 if (i < ma->chosen_pref) {
1271 safestrncpy(ma->chosen_part, partnum, sizeof ma->chosen_part);
1272 ma->chosen_pref = i;
1280 * Now that we've chosen our preferred part, output it.
1282 void output_preferred(char *name, char *filename, char *partnum, char *disp,
1283 void *content, char *cbtype, char *cbcharset, size_t length,
1284 char *encoding, void *cbuserdata)
1288 int add_newline = 0;
1292 ma = (struct ma_info *)cbuserdata;
1294 /* This is not the MIME part you're looking for... */
1295 if (strcasecmp(partnum, ma->chosen_part)) return;
1297 /* If the content-type of this part is in our preferred formats
1298 * list, we can simply output it verbatim.
1300 for (i=0; i<num_tokens(CC->preferred_formats, '|'); ++i) {
1301 extract_token(buf, CC->preferred_formats, i, '|', sizeof buf);
1302 if (!strcasecmp(buf, cbtype)) {
1303 /* Yeah! Go! W00t!! */
1305 text_content = (char *)content;
1306 if (text_content[length-1] != '\n') {
1310 cprintf("Content-type: %s", cbtype);
1311 if (strlen(cbcharset) > 0) {
1312 cprintf("; charset=%s", cbcharset);
1314 cprintf("\nContent-length: %d\n",
1315 (int)(length + add_newline) );
1316 if (strlen(encoding) > 0) {
1317 cprintf("Content-transfer-encoding: %s\n", encoding);
1320 cprintf("Content-transfer-encoding: 7bit\n");
1323 client_write(content, length);
1324 if (add_newline) cprintf("\n");
1329 /* No translations required or possible: output as text/plain */
1330 cprintf("Content-type: text/plain\n\n");
1331 fixed_output(name, filename, partnum, disp, content, cbtype, cbcharset,
1332 length, encoding, cbuserdata);
1337 char desired_section[64];
1344 * Callback function for
1346 void extract_encapsulated_message(char *name, char *filename, char *partnum, char *disp,
1347 void *content, char *cbtype, char *cbcharset, size_t length,
1348 char *encoding, void *cbuserdata)
1350 struct encapmsg *encap;
1352 encap = (struct encapmsg *)cbuserdata;
1354 /* Only proceed if this is the desired section... */
1355 if (!strcasecmp(encap->desired_section, partnum)) {
1356 encap->msglen = length;
1357 encap->msg = malloc(length + 2);
1358 memcpy(encap->msg, content, length);
1368 * Get a message off disk. (returns om_* values found in msgbase.h)
1371 int CtdlOutputMsg(long msg_num, /* message number (local) to fetch */
1372 int mode, /* how would you like that message? */
1373 int headers_only, /* eschew the message body? */
1374 int do_proto, /* do Citadel protocol responses? */
1375 int crlf, /* Use CRLF newlines instead of LF? */
1376 char *section /* NULL or a message/rfc822 section */
1378 struct CtdlMessage *TheMessage = NULL;
1379 int retcode = om_no_such_msg;
1380 struct encapmsg encap;
1382 lprintf(CTDL_DEBUG, "CtdlOutputMsg() msgnum=%ld, mode=%d, section=%s\n",
1384 (section ? section : "<>")
1387 if ((!(CC->logged_in)) && (!(CC->internal_pgm))) {
1388 if (do_proto) cprintf("%d Not logged in.\n",
1389 ERROR + NOT_LOGGED_IN);
1390 return(om_not_logged_in);
1393 /* FIXME: check message id against msglist for this room */
1396 * Fetch the message from disk. If we're in any sort of headers
1397 * only mode, request that we don't even bother loading the body
1400 if ( (headers_only == HEADERS_FAST) || (headers_only == HEADERS_ONLY) ) {
1401 TheMessage = CtdlFetchMessage(msg_num, 0);
1404 TheMessage = CtdlFetchMessage(msg_num, 1);
1407 if (TheMessage == NULL) {
1408 if (do_proto) cprintf("%d Can't locate msg %ld on disk\n",
1409 ERROR + MESSAGE_NOT_FOUND, msg_num);
1410 return(om_no_such_msg);
1413 /* Here is the weird form of this command, to process only an
1414 * encapsulated message/rfc822 section.
1416 if (section) if (strlen(section)>0) if (strcmp(section, "0")) {
1417 memset(&encap, 0, sizeof encap);
1418 safestrncpy(encap.desired_section, section, sizeof encap.desired_section);
1419 mime_parser(TheMessage->cm_fields['M'],
1421 *extract_encapsulated_message,
1422 NULL, NULL, (void *)&encap, 0
1424 CtdlFreeMessage(TheMessage);
1428 encap.msg[encap.msglen] = 0;
1429 TheMessage = convert_internet_message(encap.msg);
1430 encap.msg = NULL; /* no free() here, TheMessage owns it now */
1432 /* Now we let it fall through to the bottom of this
1433 * function, because TheMessage now contains the
1434 * encapsulated message instead of the top-level
1435 * message. Isn't that neat?
1440 if (do_proto) cprintf("%d msg %ld has no part %s\n",
1441 ERROR + MESSAGE_NOT_FOUND, msg_num, section);
1442 retcode = om_no_such_msg;
1447 /* Ok, output the message now */
1448 retcode = CtdlOutputPreLoadedMsg(
1450 headers_only, do_proto, crlf);
1451 CtdlFreeMessage(TheMessage);
1458 * Get a message off disk. (returns om_* values found in msgbase.h)
1461 int CtdlOutputPreLoadedMsg(
1462 struct CtdlMessage *TheMessage,
1463 int mode, /* how would you like that message? */
1464 int headers_only, /* eschew the message body? */
1465 int do_proto, /* do Citadel protocol responses? */
1466 int crlf /* Use CRLF newlines instead of LF? */
1472 char display_name[256];
1474 char *nl; /* newline string */
1476 int subject_found = 0;
1479 /* Buffers needed for RFC822 translation. These are all filled
1480 * using functions that are bounds-checked, and therefore we can
1481 * make them substantially smaller than SIZ.
1489 char datestamp[100];
1491 lprintf(CTDL_DEBUG, "CtdlOutputPreLoadedMsg(TheMessage=%s, %d, %d, %d, %d\n",
1492 ((TheMessage == NULL) ? "NULL" : "not null"),
1493 mode, headers_only, do_proto, crlf);
1495 strcpy(mid, "unknown");
1496 nl = (crlf ? "\r\n" : "\n");
1498 if (!is_valid_message(TheMessage)) {
1500 "ERROR: invalid preloaded message for output\n");
1501 return(om_no_such_msg);
1504 /* Are we downloading a MIME component? */
1505 if (mode == MT_DOWNLOAD) {
1506 if (TheMessage->cm_format_type != FMT_RFC822) {
1508 cprintf("%d This is not a MIME message.\n",
1509 ERROR + ILLEGAL_VALUE);
1510 } else if (CC->download_fp != NULL) {
1511 if (do_proto) cprintf(
1512 "%d You already have a download open.\n",
1513 ERROR + RESOURCE_BUSY);
1515 /* Parse the message text component */
1516 mptr = TheMessage->cm_fields['M'];
1517 mime_parser(mptr, NULL, *mime_download, NULL, NULL, NULL, 0);
1518 /* If there's no file open by this time, the requested
1519 * section wasn't found, so print an error
1521 if (CC->download_fp == NULL) {
1522 if (do_proto) cprintf(
1523 "%d Section %s not found.\n",
1524 ERROR + FILE_NOT_FOUND,
1525 CC->download_desired_section);
1528 return((CC->download_fp != NULL) ? om_ok : om_mime_error);
1531 /* MT_SPEW_SECTION is like MT_DOWNLOAD except it outputs the whole MIME part
1532 * in a single server operation instead of opening a download file.
1534 if (mode == MT_SPEW_SECTION) {
1535 if (TheMessage->cm_format_type != FMT_RFC822) {
1537 cprintf("%d This is not a MIME message.\n",
1538 ERROR + ILLEGAL_VALUE);
1540 /* Parse the message text component */
1543 mptr = TheMessage->cm_fields['M'];
1544 mime_parser(mptr, NULL, *mime_spew_section, NULL, NULL, (void *)&found_it, 0);
1545 /* If section wasn't found, print an error
1548 if (do_proto) cprintf(
1549 "%d Section %s not found.\n",
1550 ERROR + FILE_NOT_FOUND,
1551 CC->download_desired_section);
1554 return((CC->download_fp != NULL) ? om_ok : om_mime_error);
1557 /* now for the user-mode message reading loops */
1558 if (do_proto) cprintf("%d msg:\n", LISTING_FOLLOWS);
1560 /* Does the caller want to skip the headers? */
1561 if (headers_only == HEADERS_NONE) goto START_TEXT;
1563 /* Tell the client which format type we're using. */
1564 if ( (mode == MT_CITADEL) && (do_proto) ) {
1565 cprintf("type=%d\n", TheMessage->cm_format_type);
1568 /* nhdr=yes means that we're only displaying headers, no body */
1569 if ( (TheMessage->cm_anon_type == MES_ANONONLY)
1570 && (mode == MT_CITADEL)
1573 cprintf("nhdr=yes\n");
1576 /* begin header processing loop for Citadel message format */
1578 if ((mode == MT_CITADEL) || (mode == MT_MIME)) {
1580 safestrncpy(display_name, "<unknown>", sizeof display_name);
1581 if (TheMessage->cm_fields['A']) {
1582 strcpy(buf, TheMessage->cm_fields['A']);
1583 if (TheMessage->cm_anon_type == MES_ANONONLY) {
1584 safestrncpy(display_name, "****", sizeof display_name);
1586 else if (TheMessage->cm_anon_type == MES_ANONOPT) {
1587 safestrncpy(display_name, "anonymous", sizeof display_name);
1590 safestrncpy(display_name, buf, sizeof display_name);
1592 if ((is_room_aide())
1593 && ((TheMessage->cm_anon_type == MES_ANONONLY)
1594 || (TheMessage->cm_anon_type == MES_ANONOPT))) {
1595 size_t tmp = strlen(display_name);
1596 snprintf(&display_name[tmp],
1597 sizeof display_name - tmp,
1602 /* Don't show Internet address for users on the
1603 * local Citadel network.
1606 if (TheMessage->cm_fields['N'] != NULL)
1607 if (strlen(TheMessage->cm_fields['N']) > 0)
1608 if (haschar(TheMessage->cm_fields['N'], '.') == 0) {
1612 /* Now spew the header fields in the order we like them. */
1613 safestrncpy(allkeys, FORDER, sizeof allkeys);
1614 for (i=0; i<strlen(allkeys); ++i) {
1615 k = (int) allkeys[i];
1617 if ( (TheMessage->cm_fields[k] != NULL)
1618 && (msgkeys[k] != NULL) ) {
1620 if (do_proto) cprintf("%s=%s\n",
1624 else if ((k == 'F') && (suppress_f)) {
1627 /* Masquerade display name if needed */
1629 if (do_proto) cprintf("%s=%s\n",
1631 TheMessage->cm_fields[k]
1640 /* begin header processing loop for RFC822 transfer format */
1645 strcpy(snode, NODENAME);
1646 strcpy(lnode, HUMANNODE);
1647 if (mode == MT_RFC822) {
1648 for (i = 0; i < 256; ++i) {
1649 if (TheMessage->cm_fields[i]) {
1650 mptr = TheMessage->cm_fields[i];
1653 safestrncpy(luser, mptr, sizeof luser);
1654 safestrncpy(suser, mptr, sizeof suser);
1656 else if (i == 'Y') {
1657 cprintf("CC: %s%s", mptr, nl);
1659 else if (i == 'P') {
1660 cprintf("Return-Path: %s%s", mptr, nl);
1662 else if (i == 'V') {
1663 cprintf("Envelope-To: %s%s", mptr, nl);
1665 else if (i == 'U') {
1666 cprintf("Subject: %s%s", mptr, nl);
1670 safestrncpy(mid, mptr, sizeof mid);
1672 safestrncpy(lnode, mptr, sizeof lnode);
1674 safestrncpy(fuser, mptr, sizeof fuser);
1675 /* else if (i == 'O')
1676 cprintf("X-Citadel-Room: %s%s",
1679 safestrncpy(snode, mptr, sizeof snode);
1681 cprintf("To: %s%s", mptr, nl);
1682 else if (i == 'T') {
1683 datestring(datestamp, sizeof datestamp,
1684 atol(mptr), DATESTRING_RFC822);
1685 cprintf("Date: %s%s", datestamp, nl);
1689 if (subject_found == 0) {
1690 cprintf("Subject: (no subject)%s", nl);
1694 for (i=0; i<strlen(suser); ++i) {
1695 suser[i] = tolower(suser[i]);
1696 if (!isalnum(suser[i])) suser[i]='_';
1699 if (mode == MT_RFC822) {
1700 if (!strcasecmp(snode, NODENAME)) {
1701 safestrncpy(snode, FQDN, sizeof snode);
1704 /* Construct a fun message id */
1705 cprintf("Message-ID: <%s", mid);
1706 if (strchr(mid, '@')==NULL) {
1707 cprintf("@%s", snode);
1711 if (!is_room_aide() && (TheMessage->cm_anon_type == MES_ANONONLY)) {
1712 cprintf("From: \"----\" <x@x.org>%s", nl);
1714 else if (!is_room_aide() && (TheMessage->cm_anon_type == MES_ANONOPT)) {
1715 cprintf("From: \"anonymous\" <x@x.org>%s", nl);
1717 else if (strlen(fuser) > 0) {
1718 cprintf("From: \"%s\" <%s>%s", luser, fuser, nl);
1721 cprintf("From: \"%s\" <%s@%s>%s", luser, suser, snode, nl);
1724 cprintf("Organization: %s%s", lnode, nl);
1726 /* Blank line signifying RFC822 end-of-headers */
1727 if (TheMessage->cm_format_type != FMT_RFC822) {
1732 /* end header processing loop ... at this point, we're in the text */
1734 if (headers_only == HEADERS_FAST) goto DONE;
1735 mptr = TheMessage->cm_fields['M'];
1737 /* Tell the client about the MIME parts in this message */
1738 if (TheMessage->cm_format_type == FMT_RFC822) {
1739 if ( (mode == MT_CITADEL) || (mode == MT_MIME) ) {
1740 memset(&ma, 0, sizeof(struct ma_info));
1741 mime_parser(mptr, NULL,
1742 (do_proto ? *list_this_part : NULL),
1743 (do_proto ? *list_this_pref : NULL),
1744 (do_proto ? *list_this_suff : NULL),
1747 else if (mode == MT_RFC822) { /* unparsed RFC822 dump */
1748 char *start_of_text = NULL;
1749 start_of_text = strstr(mptr, "\n\r\n");
1750 if (start_of_text == NULL) start_of_text = strstr(mptr, "\n\n");
1751 if (start_of_text == NULL) start_of_text = mptr;
1753 start_of_text = strstr(start_of_text, "\n");
1758 int nllen = strlen(nl);
1759 while (ch=*mptr, ch!=0) {
1765 ((headers_only == HEADERS_NONE) && (mptr >= start_of_text))
1766 || ((headers_only == HEADERS_ONLY) && (mptr < start_of_text))
1767 || ((headers_only != HEADERS_NONE) && (headers_only != HEADERS_ONLY))
1770 sprintf(&outbuf[outlen], "%s", nl);
1774 outbuf[outlen++] = ch;
1779 if (outlen > 1000) {
1780 client_write(outbuf, outlen);
1785 client_write(outbuf, outlen);
1793 if (headers_only == HEADERS_ONLY) {
1797 /* signify start of msg text */
1798 if ( (mode == MT_CITADEL) || (mode == MT_MIME) ) {
1799 if (do_proto) cprintf("text\n");
1802 /* If the format type on disk is 1 (fixed-format), then we want
1803 * everything to be output completely literally ... regardless of
1804 * what message transfer format is in use.
1806 if (TheMessage->cm_format_type == FMT_FIXED) {
1807 if (mode == MT_MIME) {
1808 cprintf("Content-type: text/plain\n\n");
1811 while (ch = *mptr++, ch > 0) {
1814 if ((ch == 10) || (strlen(buf) > 250)) {
1815 cprintf("%s%s", buf, nl);
1818 buf[strlen(buf) + 1] = 0;
1819 buf[strlen(buf)] = ch;
1822 if (strlen(buf) > 0)
1823 cprintf("%s%s", buf, nl);
1826 /* If the message on disk is format 0 (Citadel vari-format), we
1827 * output using the formatter at 80 columns. This is the final output
1828 * form if the transfer format is RFC822, but if the transfer format
1829 * is Citadel proprietary, it'll still work, because the indentation
1830 * for new paragraphs is correct and the client will reformat the
1831 * message to the reader's screen width.
1833 if (TheMessage->cm_format_type == FMT_CITADEL) {
1834 if (mode == MT_MIME) {
1835 cprintf("Content-type: text/x-citadel-variformat\n\n");
1837 memfmout(mptr, 0, nl);
1840 /* If the message on disk is format 4 (MIME), we've gotta hand it
1841 * off to the MIME parser. The client has already been told that
1842 * this message is format 1 (fixed format), so the callback function
1843 * we use will display those parts as-is.
1845 if (TheMessage->cm_format_type == FMT_RFC822) {
1846 memset(&ma, 0, sizeof(struct ma_info));
1848 if (mode == MT_MIME) {
1849 ma.use_fo_hooks = 0;
1850 strcpy(ma.chosen_part, "1");
1851 ma.chosen_pref = 9999;
1852 mime_parser(mptr, NULL,
1853 *choose_preferred, *fixed_output_pre,
1854 *fixed_output_post, (void *)&ma, 0);
1855 mime_parser(mptr, NULL,
1856 *output_preferred, NULL, NULL, (void *)&ma, 0);
1859 ma.use_fo_hooks = 1;
1860 mime_parser(mptr, NULL,
1861 *fixed_output, *fixed_output_pre,
1862 *fixed_output_post, (void *)&ma, 0);
1867 DONE: /* now we're done */
1868 if (do_proto) cprintf("000\n");
1875 * display a message (mode 0 - Citadel proprietary)
1877 void cmd_msg0(char *cmdbuf)
1880 int headers_only = HEADERS_ALL;
1882 msgid = extract_long(cmdbuf, 0);
1883 headers_only = extract_int(cmdbuf, 1);
1885 CtdlOutputMsg(msgid, MT_CITADEL, headers_only, 1, 0, NULL);
1891 * display a message (mode 2 - RFC822)
1893 void cmd_msg2(char *cmdbuf)
1896 int headers_only = HEADERS_ALL;
1898 msgid = extract_long(cmdbuf, 0);
1899 headers_only = extract_int(cmdbuf, 1);
1901 CtdlOutputMsg(msgid, MT_RFC822, headers_only, 1, 1, NULL);
1907 * display a message (mode 3 - IGnet raw format - internal programs only)
1909 void cmd_msg3(char *cmdbuf)
1912 struct CtdlMessage *msg = NULL;
1915 if (CC->internal_pgm == 0) {
1916 cprintf("%d This command is for internal programs only.\n",
1917 ERROR + HIGHER_ACCESS_REQUIRED);
1921 msgnum = extract_long(cmdbuf, 0);
1922 msg = CtdlFetchMessage(msgnum, 1);
1924 cprintf("%d Message %ld not found.\n",
1925 ERROR + MESSAGE_NOT_FOUND, msgnum);
1929 serialize_message(&smr, msg);
1930 CtdlFreeMessage(msg);
1933 cprintf("%d Unable to serialize message\n",
1934 ERROR + INTERNAL_ERROR);
1938 cprintf("%d %ld\n", BINARY_FOLLOWS, (long)smr.len);
1939 client_write((char *)smr.ser, (int)smr.len);
1946 * Display a message using MIME content types
1948 void cmd_msg4(char *cmdbuf)
1953 msgid = extract_long(cmdbuf, 0);
1954 extract_token(section, cmdbuf, 1, '|', sizeof section);
1955 CtdlOutputMsg(msgid, MT_MIME, 0, 1, 0, (section[0] ? section : NULL) );
1961 * Client tells us its preferred message format(s)
1963 void cmd_msgp(char *cmdbuf)
1965 safestrncpy(CC->preferred_formats, cmdbuf,
1966 sizeof(CC->preferred_formats));
1967 cprintf("%d ok\n", CIT_OK);
1972 * Open a component of a MIME message as a download file
1974 void cmd_opna(char *cmdbuf)
1977 char desired_section[128];
1979 msgid = extract_long(cmdbuf, 0);
1980 extract_token(desired_section, cmdbuf, 1, '|', sizeof desired_section);
1981 safestrncpy(CC->download_desired_section, desired_section,
1982 sizeof CC->download_desired_section);
1983 CtdlOutputMsg(msgid, MT_DOWNLOAD, 0, 1, 1, NULL);
1988 * Open a component of a MIME message and transmit it all at once
1990 void cmd_dlat(char *cmdbuf)
1993 char desired_section[128];
1995 msgid = extract_long(cmdbuf, 0);
1996 extract_token(desired_section, cmdbuf, 1, '|', sizeof desired_section);
1997 safestrncpy(CC->download_desired_section, desired_section,
1998 sizeof CC->download_desired_section);
1999 CtdlOutputMsg(msgid, MT_SPEW_SECTION, 0, 1, 1, NULL);
2004 * Save one or more message pointers into a specified room
2005 * (Returns 0 for success, nonzero for failure)
2006 * roomname may be NULL to use the current room
2008 * Note that the 'supplied_msg' field may be set to NULL, in which case
2009 * the message will be fetched from disk, by number, if we need to perform
2010 * replication checks. This adds an additional database read, so if the
2011 * caller already has the message in memory then it should be supplied. (Obviously
2012 * this mode of operation only works if we're saving a single message.)
2014 int CtdlSaveMsgPointersInRoom(char *roomname, long newmsgidlist[], int num_newmsgs,
2015 int do_repl_check, struct CtdlMessage *supplied_msg)
2018 char hold_rm[ROOMNAMELEN];
2019 struct cdbdata *cdbfr;
2022 long highest_msg = 0L;
2025 struct CtdlMessage *msg = NULL;
2027 long *msgs_to_be_merged = NULL;
2028 int num_msgs_to_be_merged = 0;
2031 "CtdlSaveMsgPointersInRoom(room=%s, num_msgs=%d, repl=%d)\n",
2032 roomname, num_newmsgs, do_repl_check);
2034 strcpy(hold_rm, CC->room.QRname);
2037 if (newmsgidlist == NULL) return(ERROR + INTERNAL_ERROR);
2038 if (num_newmsgs < 1) return(ERROR + INTERNAL_ERROR);
2039 if (num_newmsgs > 1) supplied_msg = NULL;
2041 /* Now the regular stuff */
2042 if (lgetroom(&CC->room,
2043 ((roomname != NULL) ? roomname : CC->room.QRname) )
2045 lprintf(CTDL_ERR, "No such room <%s>\n", roomname);
2046 return(ERROR + ROOM_NOT_FOUND);
2050 msgs_to_be_merged = malloc(sizeof(long) * num_newmsgs);
2051 num_msgs_to_be_merged = 0;
2054 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long));
2055 if (cdbfr == NULL) {
2059 msglist = (long *) cdbfr->ptr;
2060 cdbfr->ptr = NULL; /* CtdlSaveMsgPointerInRoom() now owns this memory */
2061 num_msgs = cdbfr->len / sizeof(long);
2066 /* Create a list of msgid's which were supplied by the caller, but do
2067 * not already exist in the target room. It is absolutely taboo to
2068 * have more than one reference to the same message in a room.
2070 for (i=0; i<num_newmsgs; ++i) {
2072 if (num_msgs > 0) for (j=0; j<num_msgs; ++j) {
2073 if (msglist[j] == newmsgidlist[i]) {
2078 msgs_to_be_merged[num_msgs_to_be_merged++] = newmsgidlist[i];
2082 lprintf(9, "%d unique messages to be merged\n", num_msgs_to_be_merged);
2085 * Now merge the new messages
2087 msglist = realloc(msglist, (sizeof(long) * (num_msgs + num_msgs_to_be_merged)) );
2088 if (msglist == NULL) {
2089 lprintf(CTDL_ALERT, "ERROR: can't realloc message list!\n");
2091 memcpy(&msglist[num_msgs], msgs_to_be_merged, (sizeof(long) * num_msgs_to_be_merged) );
2092 num_msgs += num_msgs_to_be_merged;
2094 /* Sort the message list, so all the msgid's are in order */
2095 num_msgs = sort_msglist(msglist, num_msgs);
2097 /* Determine the highest message number */
2098 highest_msg = msglist[num_msgs - 1];
2100 /* Write it back to disk. */
2101 cdb_store(CDB_MSGLISTS, &CC->room.QRnumber, (int)sizeof(long),
2102 msglist, (int)(num_msgs * sizeof(long)));
2104 /* Free up the memory we used. */
2107 /* Update the highest-message pointer and unlock the room. */
2108 CC->room.QRhighest = highest_msg;
2109 lputroom(&CC->room);
2111 /* Perform replication checks if necessary */
2112 if ( (DoesThisRoomNeedEuidIndexing(&CC->room)) && (do_repl_check) ) {
2113 lprintf(CTDL_DEBUG, "CtdlSaveMsgPointerInRoom() doing repl checks\n");
2115 for (i=0; i<num_msgs_to_be_merged; ++i) {
2116 msgid = msgs_to_be_merged[i];
2118 if (supplied_msg != NULL) {
2122 msg = CtdlFetchMessage(msgid, 0);
2126 ReplicationChecks(msg);
2128 /* If the message has an Exclusive ID, index that... */
2129 if (msg->cm_fields['E'] != NULL) {
2130 index_message_by_euid(msg->cm_fields['E'], &CC->room, msgid);
2133 /* Free up the memory we may have allocated */
2134 if (msg != supplied_msg) {
2135 CtdlFreeMessage(msg);
2143 lprintf(CTDL_DEBUG, "CtdlSaveMsgPointerInRoom() skips repl checks\n");
2146 /* Submit this room for net processing */
2147 network_queue_room(&CC->room, NULL);
2149 #ifdef HAVE_LIBSIEVE
2150 /* If this is someone's inbox, submit the room for sieve processing */
2151 if (!strcasecmp(&CC->room.QRname[11], MAILROOM)) {
2152 sieve_queue_room(&CC->room);
2154 #endif /* HAVE_LIBSIEVE */
2156 /* Go back to the room we were in before we wandered here... */
2157 getroom(&CC->room, hold_rm);
2159 /* Bump the reference count for all messages which were merged */
2160 for (i=0; i<num_msgs_to_be_merged; ++i) {
2161 AdjRefCount(msgs_to_be_merged[i], +1);
2164 /* Free up memory... */
2165 if (msgs_to_be_merged != NULL) {
2166 free(msgs_to_be_merged);
2169 /* Return success. */
2175 * This is the same as CtdlSaveMsgPointersInRoom() but it only accepts
2178 int CtdlSaveMsgPointerInRoom(char *roomname, long msgid,
2179 int do_repl_check, struct CtdlMessage *supplied_msg)
2181 return CtdlSaveMsgPointersInRoom(roomname, &msgid, 1, do_repl_check, supplied_msg);
2188 * Message base operation to save a new message to the message store
2189 * (returns new message number)
2191 * This is the back end for CtdlSubmitMsg() and should not be directly
2192 * called by server-side modules.
2195 long send_message(struct CtdlMessage *msg) {
2203 /* Get a new message number */
2204 newmsgid = get_new_message_number();
2205 snprintf(msgidbuf, sizeof msgidbuf, "%010ld@%s", newmsgid, config.c_fqdn);
2207 /* Generate an ID if we don't have one already */
2208 if (msg->cm_fields['I']==NULL) {
2209 msg->cm_fields['I'] = strdup(msgidbuf);
2212 /* If the message is big, set its body aside for storage elsewhere */
2213 if (msg->cm_fields['M'] != NULL) {
2214 if (strlen(msg->cm_fields['M']) > BIGMSG) {
2216 holdM = msg->cm_fields['M'];
2217 msg->cm_fields['M'] = NULL;
2221 /* Serialize our data structure for storage in the database */
2222 serialize_message(&smr, msg);
2225 msg->cm_fields['M'] = holdM;
2229 cprintf("%d Unable to serialize message\n",
2230 ERROR + INTERNAL_ERROR);
2234 /* Write our little bundle of joy into the message base */
2235 if (cdb_store(CDB_MSGMAIN, &newmsgid, (int)sizeof(long),
2236 smr.ser, smr.len) < 0) {
2237 lprintf(CTDL_ERR, "Can't store message\n");
2241 cdb_store(CDB_BIGMSGS,
2251 /* Free the memory we used for the serialized message */
2254 /* Return the *local* message ID to the caller
2255 * (even if we're storing an incoming network message)
2263 * Serialize a struct CtdlMessage into the format used on disk and network.
2265 * This function loads up a "struct ser_ret" (defined in server.h) which
2266 * contains the length of the serialized message and a pointer to the
2267 * serialized message in memory. THE LATTER MUST BE FREED BY THE CALLER.
2269 void serialize_message(struct ser_ret *ret, /* return values */
2270 struct CtdlMessage *msg) /* unserialized msg */
2272 size_t wlen, fieldlen;
2274 static char *forder = FORDER;
2277 * Check for valid message format
2279 if (is_valid_message(msg) == 0) {
2280 lprintf(CTDL_ERR, "serialize_message() aborting due to invalid message\n");
2287 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL)
2288 ret->len = ret->len +
2289 strlen(msg->cm_fields[(int)forder[i]]) + 2;
2291 ret->ser = malloc(ret->len);
2292 if (ret->ser == NULL) {
2293 lprintf(CTDL_ERR, "serialize_message() malloc(%ld) failed: %s\n",
2294 (long)ret->len, strerror(errno));
2301 ret->ser[1] = msg->cm_anon_type;
2302 ret->ser[2] = msg->cm_format_type;
2305 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL) {
2306 fieldlen = strlen(msg->cm_fields[(int)forder[i]]);
2307 ret->ser[wlen++] = (char)forder[i];
2308 safestrncpy((char *)&ret->ser[wlen], msg->cm_fields[(int)forder[i]], fieldlen+1);
2309 wlen = wlen + fieldlen + 1;
2311 if (ret->len != wlen) lprintf(CTDL_ERR, "ERROR: len=%ld wlen=%ld\n",
2312 (long)ret->len, (long)wlen);
2320 * Check to see if any messages already exist in the current room which
2321 * carry the same Exclusive ID as this one. If any are found, delete them.
2323 void ReplicationChecks(struct CtdlMessage *msg) {
2324 long old_msgnum = (-1L);
2326 if (DoesThisRoomNeedEuidIndexing(&CC->room) == 0) return;
2328 lprintf(CTDL_DEBUG, "Performing replication checks in <%s>\n",
2331 /* No exclusive id? Don't do anything. */
2332 if (msg == NULL) return;
2333 if (msg->cm_fields['E'] == NULL) return;
2334 if (strlen(msg->cm_fields['E']) == 0) return;
2335 /*lprintf(CTDL_DEBUG, "Exclusive ID: <%s> for room <%s>\n",
2336 msg->cm_fields['E'], CC->room.QRname);*/
2338 old_msgnum = locate_message_by_euid(msg->cm_fields['E'], &CC->room);
2339 if (old_msgnum > 0L) {
2340 lprintf(CTDL_DEBUG, "ReplicationChecks() replacing message %ld\n", old_msgnum);
2341 CtdlDeleteMessages(CC->room.QRname, &old_msgnum, 1, "");
2348 * Save a message to disk and submit it into the delivery system.
2350 long CtdlSubmitMsg(struct CtdlMessage *msg, /* message to save */
2351 struct recptypes *recps, /* recipients (if mail) */
2352 char *force /* force a particular room? */
2354 char submit_filename[128];
2355 char generated_timestamp[32];
2356 char hold_rm[ROOMNAMELEN];
2357 char actual_rm[ROOMNAMELEN];
2358 char force_room[ROOMNAMELEN];
2359 char content_type[SIZ]; /* We have to learn this */
2360 char recipient[SIZ];
2363 struct ctdluser userbuf;
2365 struct MetaData smi;
2366 FILE *network_fp = NULL;
2367 static int seqnum = 1;
2368 struct CtdlMessage *imsg = NULL;
2371 char *hold_R, *hold_D;
2372 char *collected_addresses = NULL;
2373 struct addresses_to_be_filed *aptr = NULL;
2374 char *saved_rfc822_version = NULL;
2375 int qualified_for_journaling = 0;
2377 lprintf(CTDL_DEBUG, "CtdlSubmitMsg() called\n");
2378 if (is_valid_message(msg) == 0) return(-1); /* self check */
2380 /* If this message has no timestamp, we take the liberty of
2381 * giving it one, right now.
2383 if (msg->cm_fields['T'] == NULL) {
2384 snprintf(generated_timestamp, sizeof generated_timestamp, "%ld", (long)time(NULL));
2385 msg->cm_fields['T'] = strdup(generated_timestamp);
2388 /* If this message has no path, we generate one.
2390 if (msg->cm_fields['P'] == NULL) {
2391 if (msg->cm_fields['A'] != NULL) {
2392 msg->cm_fields['P'] = strdup(msg->cm_fields['A']);
2393 for (a=0; a<strlen(msg->cm_fields['P']); ++a) {
2394 if (isspace(msg->cm_fields['P'][a])) {
2395 msg->cm_fields['P'][a] = ' ';
2400 msg->cm_fields['P'] = strdup("unknown");
2404 if (force == NULL) {
2405 strcpy(force_room, "");
2408 strcpy(force_room, force);
2411 /* Learn about what's inside, because it's what's inside that counts */
2412 if (msg->cm_fields['M'] == NULL) {
2413 lprintf(CTDL_ERR, "ERROR: attempt to save message with NULL body\n");
2417 switch (msg->cm_format_type) {
2419 strcpy(content_type, "text/x-citadel-variformat");
2422 strcpy(content_type, "text/plain");
2425 strcpy(content_type, "text/plain");
2426 mptr = bmstrcasestr(msg->cm_fields['M'], "Content-type: ");
2428 safestrncpy(content_type, &mptr[14],
2429 sizeof content_type);
2430 for (a = 0; a < strlen(content_type); ++a) {
2431 if ((content_type[a] == ';')
2432 || (content_type[a] == ' ')
2433 || (content_type[a] == 13)
2434 || (content_type[a] == 10)) {
2435 content_type[a] = 0;
2441 /* Goto the correct room */
2442 lprintf(CTDL_DEBUG, "Selected room %s\n", (recps) ? CC->room.QRname : SENTITEMS);
2443 strcpy(hold_rm, CC->room.QRname);
2444 strcpy(actual_rm, CC->room.QRname);
2445 if (recps != NULL) {
2446 strcpy(actual_rm, SENTITEMS);
2449 /* If the user is a twit, move to the twit room for posting */
2451 if (CC->user.axlevel == 2) {
2452 strcpy(hold_rm, actual_rm);
2453 strcpy(actual_rm, config.c_twitroom);
2454 lprintf(CTDL_DEBUG, "Diverting to twit room\n");
2458 /* ...or if this message is destined for Aide> then go there. */
2459 if (strlen(force_room) > 0) {
2460 strcpy(actual_rm, force_room);
2463 lprintf(CTDL_DEBUG, "Final selection: %s\n", actual_rm);
2464 if (strcasecmp(actual_rm, CC->room.QRname)) {
2465 /* getroom(&CC->room, actual_rm); */
2466 usergoto(actual_rm, 0, 1, NULL, NULL);
2470 * If this message has no O (room) field, generate one.
2472 if (msg->cm_fields['O'] == NULL) {
2473 msg->cm_fields['O'] = strdup(CC->room.QRname);
2476 /* Perform "before save" hooks (aborting if any return nonzero) */
2477 lprintf(CTDL_DEBUG, "Performing before-save hooks\n");
2478 if (PerformMessageHooks(msg, EVT_BEFORESAVE) > 0) return(-3);
2481 * If this message has an Exclusive ID, and the room is replication
2482 * checking enabled, then do replication checks.
2484 if (DoesThisRoomNeedEuidIndexing(&CC->room)) {
2485 ReplicationChecks(msg);
2488 /* Save it to disk */
2489 lprintf(CTDL_DEBUG, "Saving to disk\n");
2490 newmsgid = send_message(msg);
2491 if (newmsgid <= 0L) return(-5);
2493 /* Write a supplemental message info record. This doesn't have to
2494 * be a critical section because nobody else knows about this message
2497 lprintf(CTDL_DEBUG, "Creating MetaData record\n");
2498 memset(&smi, 0, sizeof(struct MetaData));
2499 smi.meta_msgnum = newmsgid;
2500 smi.meta_refcount = 0;
2501 safestrncpy(smi.meta_content_type, content_type,
2502 sizeof smi.meta_content_type);
2505 * Measure how big this message will be when rendered as RFC822.
2506 * We do this for two reasons:
2507 * 1. We need the RFC822 length for the new metadata record, so the
2508 * POP and IMAP services don't have to calculate message lengths
2509 * while the user is waiting (multiplied by potentially hundreds
2510 * or thousands of messages).
2511 * 2. If journaling is enabled, we will need an RFC822 version of the
2512 * message to attach to the journalized copy.
2514 if (CC->redirect_buffer != NULL) {
2515 lprintf(CTDL_ALERT, "CC->redirect_buffer is not NULL during message submission!\n");
2518 CC->redirect_buffer = malloc(SIZ);
2519 CC->redirect_len = 0;
2520 CC->redirect_alloc = SIZ;
2521 CtdlOutputPreLoadedMsg(msg, MT_RFC822, HEADERS_ALL, 0, 1);
2522 smi.meta_rfc822_length = CC->redirect_len;
2523 saved_rfc822_version = CC->redirect_buffer;
2524 CC->redirect_buffer = NULL;
2525 CC->redirect_len = 0;
2526 CC->redirect_alloc = 0;
2530 /* Now figure out where to store the pointers */
2531 lprintf(CTDL_DEBUG, "Storing pointers\n");
2533 /* If this is being done by the networker delivering a private
2534 * message, we want to BYPASS saving the sender's copy (because there
2535 * is no local sender; it would otherwise go to the Trashcan).
2537 if ((!CC->internal_pgm) || (recps == NULL)) {
2538 if (CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 1, msg) != 0) {
2539 lprintf(CTDL_ERR, "ERROR saving message pointer!\n");
2540 CtdlSaveMsgPointerInRoom(config.c_aideroom, newmsgid, 0, msg);
2544 /* For internet mail, drop a copy in the outbound queue room */
2546 if (recps->num_internet > 0) {
2547 CtdlSaveMsgPointerInRoom(SMTP_SPOOLOUT_ROOM, newmsgid, 0, msg);
2550 /* If other rooms are specified, drop them there too. */
2552 if (recps->num_room > 0)
2553 for (i=0; i<num_tokens(recps->recp_room, '|'); ++i) {
2554 extract_token(recipient, recps->recp_room, i,
2555 '|', sizeof recipient);
2556 lprintf(CTDL_DEBUG, "Delivering to room <%s>\n", recipient);
2557 CtdlSaveMsgPointerInRoom(recipient, newmsgid, 0, msg);
2560 /* Bump this user's messages posted counter. */
2561 lprintf(CTDL_DEBUG, "Updating user\n");
2562 lgetuser(&CC->user, CC->curr_user);
2563 CC->user.posted = CC->user.posted + 1;
2564 lputuser(&CC->user);
2566 /* If this is private, local mail, make a copy in the
2567 * recipient's mailbox and bump the reference count.
2570 if (recps->num_local > 0)
2571 for (i=0; i<num_tokens(recps->recp_local, '|'); ++i) {
2572 extract_token(recipient, recps->recp_local, i,
2573 '|', sizeof recipient);
2574 lprintf(CTDL_DEBUG, "Delivering private local mail to <%s>\n",
2576 if (getuser(&userbuf, recipient) == 0) {
2577 // Add a flag so the Funambol module knows its mail
2578 msg->cm_fields['W'] = strdup(recipient);
2579 MailboxName(actual_rm, sizeof actual_rm,
2580 &userbuf, MAILROOM);
2581 CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 0, msg);
2582 BumpNewMailCounter(userbuf.usernum);
2583 if (strlen(config.c_funambol_host) > 0) {
2584 /* Generate a instruction message for the Funambol notification
2585 server, in the same style as the SMTP queue */
2586 instr = malloc(SIZ * 2);
2587 snprintf(instr, SIZ * 2,
2588 "Content-type: %s\n\nmsgid|%ld\nsubmitted|%ld\n"
2590 SPOOLMIME, newmsgid, (long)time(NULL),
2591 msg->cm_fields['A'], msg->cm_fields['N']
2594 imsg = malloc(sizeof(struct CtdlMessage));
2595 memset(imsg, 0, sizeof(struct CtdlMessage));
2596 imsg->cm_magic = CTDLMESSAGE_MAGIC;
2597 imsg->cm_anon_type = MES_NORMAL;
2598 imsg->cm_format_type = FMT_RFC822;
2599 imsg->cm_fields['A'] = strdup("Citadel");
2600 imsg->cm_fields['J'] = strdup("do not journal");
2601 imsg->cm_fields['M'] = instr;
2602 imsg->cm_fields['W'] = strdup(recipient);
2603 CtdlSubmitMsg(imsg, NULL, FNBL_QUEUE_ROOM);
2604 CtdlFreeMessage(imsg);
2608 lprintf(CTDL_DEBUG, "No user <%s>\n", recipient);
2609 CtdlSaveMsgPointerInRoom(config.c_aideroom,
2614 /* Perform "after save" hooks */
2615 lprintf(CTDL_DEBUG, "Performing after-save hooks\n");
2616 PerformMessageHooks(msg, EVT_AFTERSAVE);
2618 /* For IGnet mail, we have to save a new copy into the spooler for
2619 * each recipient, with the R and D fields set to the recipient and
2620 * destination-node. This has two ugly side effects: all other
2621 * recipients end up being unlisted in this recipient's copy of the
2622 * message, and it has to deliver multiple messages to the same
2623 * node. We'll revisit this again in a year or so when everyone has
2624 * a network spool receiver that can handle the new style messages.
2627 if (recps->num_ignet > 0)
2628 for (i=0; i<num_tokens(recps->recp_ignet, '|'); ++i) {
2629 extract_token(recipient, recps->recp_ignet, i,
2630 '|', sizeof recipient);
2632 hold_R = msg->cm_fields['R'];
2633 hold_D = msg->cm_fields['D'];
2634 msg->cm_fields['R'] = malloc(SIZ);
2635 msg->cm_fields['D'] = malloc(128);
2636 extract_token(msg->cm_fields['R'], recipient, 0, '@', SIZ);
2637 extract_token(msg->cm_fields['D'], recipient, 1, '@', 128);
2639 serialize_message(&smr, msg);
2641 snprintf(submit_filename, sizeof submit_filename,
2642 "%s/netmail.%04lx.%04x.%04x",
2644 (long) getpid(), CC->cs_pid, ++seqnum);
2645 network_fp = fopen(submit_filename, "wb+");
2646 if (network_fp != NULL) {
2647 fwrite(smr.ser, smr.len, 1, network_fp);
2653 free(msg->cm_fields['R']);
2654 free(msg->cm_fields['D']);
2655 msg->cm_fields['R'] = hold_R;
2656 msg->cm_fields['D'] = hold_D;
2659 /* Go back to the room we started from */
2660 lprintf(CTDL_DEBUG, "Returning to original room %s\n", hold_rm);
2661 if (strcasecmp(hold_rm, CC->room.QRname))
2662 /* getroom(&CC->room, hold_rm); */
2663 usergoto(hold_rm, 0, 1, NULL, NULL);
2665 /* For internet mail, generate delivery instructions.
2666 * Yes, this is recursive. Deal with it. Infinite recursion does
2667 * not happen because the delivery instructions message does not
2668 * contain a recipient.
2671 if (recps->num_internet > 0) {
2672 lprintf(CTDL_DEBUG, "Generating delivery instructions\n");
2673 instr = malloc(SIZ * 2);
2674 snprintf(instr, SIZ * 2,
2675 "Content-type: %s\n\nmsgid|%ld\nsubmitted|%ld\n"
2677 SPOOLMIME, newmsgid, (long)time(NULL),
2678 msg->cm_fields['A'], msg->cm_fields['N']
2681 for (i=0; i<num_tokens(recps->recp_internet, '|'); ++i) {
2682 size_t tmp = strlen(instr);
2683 extract_token(recipient, recps->recp_internet,
2684 i, '|', sizeof recipient);
2685 snprintf(&instr[tmp], SIZ * 2 - tmp,
2686 "remote|%s|0||\n", recipient);
2689 imsg = malloc(sizeof(struct CtdlMessage));
2690 memset(imsg, 0, sizeof(struct CtdlMessage));
2691 imsg->cm_magic = CTDLMESSAGE_MAGIC;
2692 imsg->cm_anon_type = MES_NORMAL;
2693 imsg->cm_format_type = FMT_RFC822;
2694 imsg->cm_fields['A'] = strdup("Citadel");
2695 imsg->cm_fields['J'] = strdup("do not journal");
2696 imsg->cm_fields['M'] = instr;
2697 CtdlSubmitMsg(imsg, NULL, SMTP_SPOOLOUT_ROOM);
2698 CtdlFreeMessage(imsg);
2702 * Any addresses to harvest for someone's address book?
2704 if ( (CC->logged_in) && (recps != NULL) ) {
2705 collected_addresses = harvest_collected_addresses(msg);
2708 if (collected_addresses != NULL) {
2709 begin_critical_section(S_ATBF);
2710 aptr = (struct addresses_to_be_filed *)
2711 malloc(sizeof(struct addresses_to_be_filed));
2713 MailboxName(actual_rm, sizeof actual_rm,
2714 &CC->user, USERCONTACTSROOM);
2715 aptr->roomname = strdup(actual_rm);
2716 aptr->collected_addresses = collected_addresses;
2718 end_critical_section(S_ATBF);
2722 * Determine whether this message qualifies for journaling.
2724 if (msg->cm_fields['J'] != NULL) {
2725 qualified_for_journaling = 0;
2728 if (recps == NULL) {
2729 qualified_for_journaling = config.c_journal_pubmsgs;
2731 else if (recps->num_local + recps->num_ignet + recps->num_internet > 0) {
2732 qualified_for_journaling = config.c_journal_email;
2735 qualified_for_journaling = config.c_journal_pubmsgs;
2740 * Do we have to perform journaling? If so, hand off the saved
2741 * RFC822 version will be handed off to the journaler for background
2742 * submit. Otherwise, we have to free the memory ourselves.
2744 if (saved_rfc822_version != NULL) {
2745 if (qualified_for_journaling) {
2746 JournalBackgroundSubmit(msg, saved_rfc822_version, recps);
2749 free(saved_rfc822_version);
2762 * Convenience function for generating small administrative messages.
2764 void quickie_message(char *from, char *fromaddr, char *to, char *room, char *text,
2765 int format_type, char *subject)
2767 struct CtdlMessage *msg;
2768 struct recptypes *recp = NULL;
2770 msg = malloc(sizeof(struct CtdlMessage));
2771 memset(msg, 0, sizeof(struct CtdlMessage));
2772 msg->cm_magic = CTDLMESSAGE_MAGIC;
2773 msg->cm_anon_type = MES_NORMAL;
2774 msg->cm_format_type = format_type;
2777 msg->cm_fields['A'] = strdup(from);
2779 else if (fromaddr != NULL) {
2780 msg->cm_fields['A'] = strdup(fromaddr);
2781 if (strchr(msg->cm_fields['A'], '@')) {
2782 *strchr(msg->cm_fields['A'], '@') = 0;
2786 msg->cm_fields['A'] = strdup("Citadel");
2789 if (fromaddr != NULL) msg->cm_fields['F'] = strdup(fromaddr);
2790 if (room != NULL) msg->cm_fields['O'] = strdup(room);
2791 msg->cm_fields['N'] = strdup(NODENAME);
2793 msg->cm_fields['R'] = strdup(to);
2794 recp = validate_recipients(to);
2796 if (subject != NULL) {
2797 msg->cm_fields['U'] = strdup(subject);
2799 msg->cm_fields['M'] = strdup(text);
2801 CtdlSubmitMsg(msg, recp, room);
2802 CtdlFreeMessage(msg);
2803 if (recp != NULL) free(recp);
2809 * Back end function used by CtdlMakeMessage() and similar functions
2811 char *CtdlReadMessageBody(char *terminator, /* token signalling EOT */
2812 size_t maxlen, /* maximum message length */
2813 char *exist, /* if non-null, append to it;
2814 exist is ALWAYS freed */
2815 int crlf /* CRLF newlines instead of LF */
2819 size_t message_len = 0;
2820 size_t buffer_len = 0;
2827 if (exist == NULL) {
2834 message_len = strlen(exist);
2835 buffer_len = message_len + 4096;
2836 m = realloc(exist, buffer_len);
2843 /* Do we need to change leading ".." to "." for SMTP escaping? */
2844 if (!strcmp(terminator, ".")) {
2848 /* flush the input if we have nowhere to store it */
2853 /* read in the lines of message text one by one */
2855 if (client_getln(buf, (sizeof buf - 3)) < 1) finished = 1;
2856 if (!strcmp(buf, terminator)) finished = 1;
2858 strcat(buf, "\r\n");
2864 /* Unescape SMTP-style input of two dots at the beginning of the line */
2866 if (!strncmp(buf, "..", 2)) {
2867 strcpy(buf, &buf[1]);
2871 if ( (!flushing) && (!finished) ) {
2872 /* Measure the line */
2873 linelen = strlen(buf);
2875 /* augment the buffer if we have to */
2876 if ((message_len + linelen) >= buffer_len) {
2877 ptr = realloc(m, (buffer_len * 2) );
2878 if (ptr == NULL) { /* flush if can't allocate */
2881 buffer_len = (buffer_len * 2);
2883 lprintf(CTDL_DEBUG, "buffer_len is now %ld\n", (long)buffer_len);
2887 /* Add the new line to the buffer. NOTE: this loop must avoid
2888 * using functions like strcat() and strlen() because they
2889 * traverse the entire buffer upon every call, and doing that
2890 * for a multi-megabyte message slows it down beyond usability.
2892 strcpy(&m[message_len], buf);
2893 message_len += linelen;
2896 /* if we've hit the max msg length, flush the rest */
2897 if (message_len >= maxlen) flushing = 1;
2899 } while (!finished);
2907 * Build a binary message to be saved on disk.
2908 * (NOTE: if you supply 'preformatted_text', the buffer you give it
2909 * will become part of the message. This means you are no longer
2910 * responsible for managing that memory -- it will be freed along with
2911 * the rest of the fields when CtdlFreeMessage() is called.)
2914 struct CtdlMessage *CtdlMakeMessage(
2915 struct ctdluser *author, /* author's user structure */
2916 char *recipient, /* NULL if it's not mail */
2917 char *recp_cc, /* NULL if it's not mail */
2918 char *room, /* room where it's going */
2919 int type, /* see MES_ types in header file */
2920 int format_type, /* variformat, plain text, MIME... */
2921 char *fake_name, /* who we're masquerading as */
2922 char *subject, /* Subject (optional) */
2923 char *supplied_euid, /* ...or NULL if this is irrelevant */
2924 char *preformatted_text /* ...or NULL to read text from client */
2926 char dest_node[SIZ];
2928 struct CtdlMessage *msg;
2930 msg = malloc(sizeof(struct CtdlMessage));
2931 memset(msg, 0, sizeof(struct CtdlMessage));
2932 msg->cm_magic = CTDLMESSAGE_MAGIC;
2933 msg->cm_anon_type = type;
2934 msg->cm_format_type = format_type;
2936 /* Don't confuse the poor folks if it's not routed mail. */
2937 strcpy(dest_node, "");
2942 snprintf(buf, sizeof buf, "cit%ld", author->usernum); /* Path */
2943 msg->cm_fields['P'] = strdup(buf);
2945 snprintf(buf, sizeof buf, "%ld", (long)time(NULL)); /* timestamp */
2946 msg->cm_fields['T'] = strdup(buf);
2948 if (fake_name[0]) /* author */
2949 msg->cm_fields['A'] = strdup(fake_name);
2951 msg->cm_fields['A'] = strdup(author->fullname);
2953 if (CC->room.QRflags & QR_MAILBOX) { /* room */
2954 msg->cm_fields['O'] = strdup(&CC->room.QRname[11]);
2957 msg->cm_fields['O'] = strdup(CC->room.QRname);
2960 msg->cm_fields['N'] = strdup(NODENAME); /* nodename */
2961 msg->cm_fields['H'] = strdup(HUMANNODE); /* hnodename */
2963 if (recipient[0] != 0) {
2964 msg->cm_fields['R'] = strdup(recipient);
2966 if (recp_cc[0] != 0) {
2967 msg->cm_fields['Y'] = strdup(recp_cc);
2969 if (dest_node[0] != 0) {
2970 msg->cm_fields['D'] = strdup(dest_node);
2973 if ( (author == &CC->user) && (strlen(CC->cs_inet_email) > 0) ) {
2974 msg->cm_fields['F'] = strdup(CC->cs_inet_email);
2977 if (subject != NULL) {
2980 length = strlen(subject);
2986 while ((subject[i] != '\0') &&
2987 (IsAscii = isascii(subject[i]) != 0 ))
2990 msg->cm_fields['U'] = strdup(subject);
2991 else /* ok, we've got utf8 in the string. */
2993 msg->cm_fields['U'] = rfc2047encode(subject, length);
2999 if (supplied_euid != NULL) {
3000 msg->cm_fields['E'] = strdup(supplied_euid);
3003 if (preformatted_text != NULL) {
3004 msg->cm_fields['M'] = preformatted_text;
3007 msg->cm_fields['M'] = CtdlReadMessageBody("000",
3008 config.c_maxmsglen, NULL, 0);
3016 * Check to see whether we have permission to post a message in the current
3017 * room. Returns a *CITADEL ERROR CODE* and puts a message in errmsgbuf, or
3018 * returns 0 on success.
3020 int CtdlDoIHavePermissionToPostInThisRoom(char *errmsgbuf, size_t n) {
3023 if (!(CC->logged_in)) {
3024 snprintf(errmsgbuf, n, "Not logged in.");
3025 return (ERROR + NOT_LOGGED_IN);
3028 if ((CC->user.axlevel < 2)
3029 && ((CC->room.QRflags & QR_MAILBOX) == 0)) {
3030 snprintf(errmsgbuf, n, "Need to be validated to enter "
3031 "(except in %s> to sysop)", MAILROOM);
3032 return (ERROR + HIGHER_ACCESS_REQUIRED);
3035 CtdlRoomAccess(&CC->room, &CC->user, &ra, NULL);
3036 if (!(ra & UA_POSTALLOWED)) {
3037 snprintf(errmsgbuf, n, "Higher access is required to post in this room.");
3038 return (ERROR + HIGHER_ACCESS_REQUIRED);
3041 strcpy(errmsgbuf, "Ok");
3047 * Check to see if the specified user has Internet mail permission
3048 * (returns nonzero if permission is granted)
3050 int CtdlCheckInternetMailPermission(struct ctdluser *who) {
3052 /* Do not allow twits to send Internet mail */
3053 if (who->axlevel <= 2) return(0);
3055 /* Globally enabled? */
3056 if (config.c_restrict == 0) return(1);
3058 /* User flagged ok? */
3059 if (who->flags & US_INTERNET) return(2);
3061 /* Aide level access? */
3062 if (who->axlevel >= 6) return(3);
3064 /* No mail for you! */
3070 * Validate recipients, count delivery types and errors, and handle aliasing
3071 * FIXME check for dupes!!!!!
3072 * Returns 0 if all addresses are ok, ret->num_error = -1 if no addresses
3073 * were specified, or the number of addresses found invalid.
3074 * caller needs to free the result.
3076 struct recptypes *validate_recipients(char *supplied_recipients) {
3077 struct recptypes *ret;
3078 char recipients[SIZ];
3079 char this_recp[256];
3080 char this_recp_cooked[256];
3086 struct ctdluser tempUS;
3087 struct ctdlroom tempQR;
3091 ret = (struct recptypes *) malloc(sizeof(struct recptypes));
3092 if (ret == NULL) return(NULL);
3093 memset(ret, 0, sizeof(struct recptypes));
3096 ret->num_internet = 0;
3101 if (supplied_recipients == NULL) {
3102 strcpy(recipients, "");
3105 safestrncpy(recipients, supplied_recipients, sizeof recipients);
3108 /* Change all valid separator characters to commas */
3109 for (i=0; i<strlen(recipients); ++i) {
3110 if ((recipients[i] == ';') || (recipients[i] == '|')) {
3111 recipients[i] = ',';
3115 /* Now start extracting recipients... */
3117 while (strlen(recipients) > 0) {
3119 for (i=0; i<=strlen(recipients); ++i) {
3120 if (recipients[i] == '\"') in_quotes = 1 - in_quotes;
3121 if ( ( (recipients[i] == ',') && (!in_quotes) ) || (recipients[i] == 0) ) {
3122 safestrncpy(this_recp, recipients, i+1);
3124 if (recipients[i] == ',') {
3125 strcpy(recipients, &recipients[i+1]);
3128 strcpy(recipients, "");
3135 lprintf(CTDL_DEBUG, "Evaluating recipient #%d: %s\n", num_recps, this_recp);
3137 mailtype = alias(this_recp);
3138 mailtype = alias(this_recp);
3139 mailtype = alias(this_recp);
3140 for (j=0; j<=strlen(this_recp); ++j) {
3141 if (this_recp[j]=='_') {
3142 this_recp_cooked[j] = ' ';
3145 this_recp_cooked[j] = this_recp[j];
3151 if (!strcasecmp(this_recp, "sysop")) {
3153 strcpy(this_recp, config.c_aideroom);
3154 if (strlen(ret->recp_room) > 0) {
3155 strcat(ret->recp_room, "|");
3157 strcat(ret->recp_room, this_recp);
3159 else if (getuser(&tempUS, this_recp) == 0) {
3161 strcpy(this_recp, tempUS.fullname);
3162 if (strlen(ret->recp_local) > 0) {
3163 strcat(ret->recp_local, "|");
3165 strcat(ret->recp_local, this_recp);
3167 else if (getuser(&tempUS, this_recp_cooked) == 0) {
3169 strcpy(this_recp, tempUS.fullname);
3170 if (strlen(ret->recp_local) > 0) {
3171 strcat(ret->recp_local, "|");
3173 strcat(ret->recp_local, this_recp);
3175 else if ( (!strncasecmp(this_recp, "room_", 5))
3176 && (!getroom(&tempQR, &this_recp_cooked[5])) ) {
3178 if (strlen(ret->recp_room) > 0) {
3179 strcat(ret->recp_room, "|");
3181 strcat(ret->recp_room, &this_recp_cooked[5]);
3189 /* Yes, you're reading this correctly: if the target
3190 * domain points back to the local system or an attached
3191 * Citadel directory, the address is invalid. That's
3192 * because if the address were valid, we would have
3193 * already translated it to a local address by now.
3195 if (IsDirectory(this_recp)) {
3200 ++ret->num_internet;
3201 if (strlen(ret->recp_internet) > 0) {
3202 strcat(ret->recp_internet, "|");
3204 strcat(ret->recp_internet, this_recp);
3209 if (strlen(ret->recp_ignet) > 0) {
3210 strcat(ret->recp_ignet, "|");
3212 strcat(ret->recp_ignet, this_recp);
3220 if (strlen(ret->errormsg) == 0) {
3221 snprintf(append, sizeof append,
3222 "Invalid recipient: %s",
3226 snprintf(append, sizeof append,
3229 if ( (strlen(ret->errormsg) + strlen(append)) < SIZ) {
3230 strcat(ret->errormsg, append);
3234 if (strlen(ret->display_recp) == 0) {
3235 strcpy(append, this_recp);
3238 snprintf(append, sizeof append, ", %s",
3241 if ( (strlen(ret->display_recp)+strlen(append)) < SIZ) {
3242 strcat(ret->display_recp, append);
3247 if ((ret->num_local + ret->num_internet + ret->num_ignet +
3248 ret->num_room + ret->num_error) == 0) {
3249 ret->num_error = (-1);
3250 strcpy(ret->errormsg, "No recipients specified.");
3253 lprintf(CTDL_DEBUG, "validate_recipients()\n");
3254 lprintf(CTDL_DEBUG, " local: %d <%s>\n", ret->num_local, ret->recp_local);
3255 lprintf(CTDL_DEBUG, " room: %d <%s>\n", ret->num_room, ret->recp_room);
3256 lprintf(CTDL_DEBUG, " inet: %d <%s>\n", ret->num_internet, ret->recp_internet);
3257 lprintf(CTDL_DEBUG, " ignet: %d <%s>\n", ret->num_ignet, ret->recp_ignet);
3258 lprintf(CTDL_DEBUG, " error: %d <%s>\n", ret->num_error, ret->errormsg);
3266 * message entry - mode 0 (normal)
3268 void cmd_ent0(char *entargs)
3274 char supplied_euid[128];
3275 char masquerade_as[SIZ];
3277 int format_type = 0;
3278 char newusername[SIZ];
3279 struct CtdlMessage *msg;
3283 struct recptypes *valid = NULL;
3284 struct recptypes *valid_to = NULL;
3285 struct recptypes *valid_cc = NULL;
3286 struct recptypes *valid_bcc = NULL;
3293 post = extract_int(entargs, 0);
3294 extract_token(recp, entargs, 1, '|', sizeof recp);
3295 anon_flag = extract_int(entargs, 2);
3296 format_type = extract_int(entargs, 3);
3297 extract_token(subject, entargs, 4, '|', sizeof subject);
3298 do_confirm = extract_int(entargs, 6);
3299 extract_token(cc, entargs, 7, '|', sizeof cc);
3300 extract_token(bcc, entargs, 8, '|', sizeof bcc);
3301 switch(CC->room.QRdefaultview) {
3304 extract_token(supplied_euid, entargs, 9, '|', sizeof supplied_euid);
3307 supplied_euid[0] = 0;
3311 /* first check to make sure the request is valid. */
3313 err = CtdlDoIHavePermissionToPostInThisRoom(errmsg, sizeof errmsg);
3315 cprintf("%d %s\n", err, errmsg);
3319 /* Check some other permission type things. */
3322 if (CC->user.axlevel < 6) {
3323 cprintf("%d You don't have permission to masquerade.\n",
3324 ERROR + HIGHER_ACCESS_REQUIRED);
3327 extract_token(newusername, entargs, 5, '|', sizeof newusername);
3328 memset(CC->fake_postname, 0, sizeof(CC->fake_postname) );
3329 safestrncpy(CC->fake_postname, newusername,
3330 sizeof(CC->fake_postname) );
3331 cprintf("%d ok\n", CIT_OK);
3334 CC->cs_flags |= CS_POSTING;
3336 /* In the Mail> room we have to behave a little differently --
3337 * make sure the user has specified at least one recipient. Then
3338 * validate the recipient(s).
3340 if ( (CC->room.QRflags & QR_MAILBOX)
3341 && (!strcasecmp(&CC->room.QRname[11], MAILROOM)) ) {
3343 if (CC->user.axlevel < 2) {
3344 strcpy(recp, "sysop");
3349 valid_to = validate_recipients(recp);
3350 if (valid_to->num_error > 0) {
3351 cprintf("%d Invalid recipient (To)\n", ERROR + NO_SUCH_USER);
3356 valid_cc = validate_recipients(cc);
3357 if (valid_cc->num_error > 0) {
3358 cprintf("%d Invalid recipient (CC)\n", ERROR + NO_SUCH_USER);
3364 valid_bcc = validate_recipients(bcc);
3365 if (valid_bcc->num_error > 0) {
3366 cprintf("%d Invalid recipient (BCC)\n", ERROR + NO_SUCH_USER);
3373 /* Recipient required, but none were specified */
3374 if ( (valid_to->num_error < 0) && (valid_cc->num_error < 0) && (valid_bcc->num_error < 0) ) {
3378 cprintf("%d At least one recipient is required.\n", ERROR + NO_SUCH_USER);
3382 if (valid_to->num_internet + valid_cc->num_internet + valid_bcc->num_internet > 0) {
3383 if (CtdlCheckInternetMailPermission(&CC->user)==0) {
3384 cprintf("%d You do not have permission "
3385 "to send Internet mail.\n",
3386 ERROR + HIGHER_ACCESS_REQUIRED);
3394 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)
3395 && (CC->user.axlevel < 4) ) {
3396 cprintf("%d Higher access required for network mail.\n",
3397 ERROR + HIGHER_ACCESS_REQUIRED);
3404 if ((RESTRICT_INTERNET == 1)
3405 && (valid_to->num_internet + valid_cc->num_internet + valid_bcc->num_internet > 0)
3406 && ((CC->user.flags & US_INTERNET) == 0)
3407 && (!CC->internal_pgm)) {
3408 cprintf("%d You don't have access to Internet mail.\n",
3409 ERROR + HIGHER_ACCESS_REQUIRED);
3418 /* Is this a room which has anonymous-only or anonymous-option? */
3419 anonymous = MES_NORMAL;
3420 if (CC->room.QRflags & QR_ANONONLY) {
3421 anonymous = MES_ANONONLY;
3423 if (CC->room.QRflags & QR_ANONOPT) {
3424 if (anon_flag == 1) { /* only if the user requested it */
3425 anonymous = MES_ANONOPT;
3429 if ((CC->room.QRflags & QR_MAILBOX) == 0) {
3433 /* If we're only checking the validity of the request, return
3434 * success without creating the message.
3437 cprintf("%d %s\n", CIT_OK,
3438 ((valid_to != NULL) ? valid_to->display_recp : "") );
3445 /* We don't need these anymore because we'll do it differently below */
3450 /* Handle author masquerading */
3451 if (CC->fake_postname[0]) {
3452 strcpy(masquerade_as, CC->fake_postname);
3454 else if (CC->fake_username[0]) {
3455 strcpy(masquerade_as, CC->fake_username);
3458 strcpy(masquerade_as, "");
3461 /* Read in the message from the client. */
3463 cprintf("%d send message\n", START_CHAT_MODE);
3465 cprintf("%d send message\n", SEND_LISTING);
3468 msg = CtdlMakeMessage(&CC->user, recp, cc,
3469 CC->room.QRname, anonymous, format_type,
3470 masquerade_as, subject,
3471 ((strlen(supplied_euid) > 0) ? supplied_euid : NULL),
3474 /* Put together one big recipients struct containing to/cc/bcc all in
3475 * one. This is for the envelope.
3477 char *all_recps = malloc(SIZ * 3);
3478 strcpy(all_recps, recp);
3479 if (strlen(cc) > 0) {
3480 if (strlen(all_recps) > 0) {
3481 strcat(all_recps, ",");
3483 strcat(all_recps, cc);
3485 if (strlen(bcc) > 0) {
3486 if (strlen(all_recps) > 0) {
3487 strcat(all_recps, ",");
3489 strcat(all_recps, bcc);
3491 if (strlen(all_recps) > 0) {
3492 valid = validate_recipients(all_recps);
3500 msgnum = CtdlSubmitMsg(msg, valid, "");
3503 cprintf("%ld\n", msgnum);
3505 cprintf("Message accepted.\n");
3508 cprintf("Internal error.\n");
3510 if (msg->cm_fields['E'] != NULL) {
3511 cprintf("%s\n", msg->cm_fields['E']);
3518 CtdlFreeMessage(msg);
3520 CC->fake_postname[0] = '\0';
3521 if (valid != NULL) {
3530 * API function to delete messages which match a set of criteria
3531 * (returns the actual number of messages deleted)
3533 int CtdlDeleteMessages(char *room_name, /* which room */
3534 long *dmsgnums, /* array of msg numbers to be deleted */
3535 int num_dmsgnums, /* number of msgs to be deleted, or 0 for "any" */
3536 char *content_type /* or "" for any */
3540 struct ctdlroom qrbuf;
3541 struct cdbdata *cdbfr;
3542 long *msglist = NULL;
3543 long *dellist = NULL;
3546 int num_deleted = 0;
3548 struct MetaData smi;
3550 lprintf(CTDL_DEBUG, "CtdlDeleteMessages(%s, %d msgs, %s)\n",
3551 room_name, num_dmsgnums, content_type);
3553 /* get room record, obtaining a lock... */
3554 if (lgetroom(&qrbuf, room_name) != 0) {
3555 lprintf(CTDL_ERR, "CtdlDeleteMessages(): Room <%s> not found\n",
3557 return (0); /* room not found */
3559 cdbfr = cdb_fetch(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long));
3561 if (cdbfr != NULL) {
3562 dellist = malloc(cdbfr->len);
3563 msglist = (long *) cdbfr->ptr;
3564 cdbfr->ptr = NULL; /* CtdlDeleteMessages() now owns this memory */
3565 num_msgs = cdbfr->len / sizeof(long);
3569 for (i = 0; i < num_msgs; ++i) {
3572 /* Set/clear a bit for each criterion */
3574 /* 0 messages in the list or a null list means that we are
3575 * interested in deleting any messages which meet the other criteria.
3577 if ((num_dmsgnums == 0) || (dmsgnums == NULL)) {
3578 delete_this |= 0x01;
3581 for (j=0; j<num_dmsgnums; ++j) {
3582 if (msglist[i] == dmsgnums[j]) {
3583 delete_this |= 0x01;
3588 if (strlen(content_type) == 0) {
3589 delete_this |= 0x02;
3591 GetMetaData(&smi, msglist[i]);
3592 if (!strcasecmp(smi.meta_content_type,
3594 delete_this |= 0x02;
3598 /* Delete message only if all bits are set */
3599 if (delete_this == 0x03) {
3600 dellist[num_deleted++] = msglist[i];
3605 num_msgs = sort_msglist(msglist, num_msgs);
3606 cdb_store(CDB_MSGLISTS, &qrbuf.QRnumber, (int)sizeof(long),
3607 msglist, (int)(num_msgs * sizeof(long)));
3609 qrbuf.QRhighest = msglist[num_msgs - 1];
3613 /* Go through the messages we pulled out of the index, and decrement
3614 * their reference counts by 1. If this is the only room the message
3615 * was in, the reference count will reach zero and the message will
3616 * automatically be deleted from the database. We do this in a
3617 * separate pass because there might be plug-in hooks getting called,
3618 * and we don't want that happening during an S_ROOMS critical
3621 if (num_deleted) for (i=0; i<num_deleted; ++i) {
3622 PerformDeleteHooks(qrbuf.QRname, dellist[i]);
3623 AdjRefCount(dellist[i], -1);
3626 /* Now free the memory we used, and go away. */
3627 if (msglist != NULL) free(msglist);
3628 if (dellist != NULL) free(dellist);
3629 lprintf(CTDL_DEBUG, "%d message(s) deleted.\n", num_deleted);
3630 return (num_deleted);
3636 * Check whether the current user has permission to delete messages from
3637 * the current room (returns 1 for yes, 0 for no)
3639 int CtdlDoIHavePermissionToDeleteMessagesFromThisRoom(void) {
3641 CtdlRoomAccess(&CC->room, &CC->user, &ra, NULL);
3642 if (ra & UA_DELETEALLOWED) return(1);
3650 * Delete message from current room
3652 void cmd_dele(char *args)
3661 extract_token(msgset, args, 0, '|', sizeof msgset);
3662 num_msgs = num_tokens(msgset, ',');
3664 cprintf("%d Nothing to do.\n", CIT_OK);
3668 if (CtdlDoIHavePermissionToDeleteMessagesFromThisRoom() == 0) {
3669 cprintf("%d Higher access required.\n",
3670 ERROR + HIGHER_ACCESS_REQUIRED);
3675 * Build our message set to be moved/copied
3677 msgs = malloc(num_msgs * sizeof(long));
3678 for (i=0; i<num_msgs; ++i) {
3679 extract_token(msgtok, msgset, i, ',', sizeof msgtok);
3680 msgs[i] = atol(msgtok);
3683 num_deleted = CtdlDeleteMessages(CC->room.QRname, msgs, num_msgs, "");
3687 cprintf("%d %d message%s deleted.\n", CIT_OK,
3688 num_deleted, ((num_deleted != 1) ? "s" : ""));
3690 cprintf("%d Message not found.\n", ERROR + MESSAGE_NOT_FOUND);
3696 * Back end API function for moves and deletes (multiple messages)
3698 int CtdlCopyMsgsToRoom(long *msgnums, int num_msgs, char *dest) {
3701 err = CtdlSaveMsgPointersInRoom(dest, msgnums, num_msgs, 1, NULL);
3702 if (err != 0) return(err);
3711 * move or copy a message to another room
3713 void cmd_move(char *args)
3720 char targ[ROOMNAMELEN];
3721 struct ctdlroom qtemp;
3728 extract_token(msgset, args, 0, '|', sizeof msgset);
3729 num_msgs = num_tokens(msgset, ',');
3731 cprintf("%d Nothing to do.\n", CIT_OK);
3735 extract_token(targ, args, 1, '|', sizeof targ);
3736 convert_room_name_macros(targ, sizeof targ);
3737 targ[ROOMNAMELEN - 1] = 0;
3738 is_copy = extract_int(args, 2);
3740 if (getroom(&qtemp, targ) != 0) {
3741 cprintf("%d '%s' does not exist.\n",
3742 ERROR + ROOM_NOT_FOUND, targ);
3746 getuser(&CC->user, CC->curr_user);
3747 CtdlRoomAccess(&qtemp, &CC->user, &ra, NULL);
3749 /* Check for permission to perform this operation.
3750 * Remember: "CC->room" is source, "qtemp" is target.
3754 /* Aides can move/copy */
3755 if (CC->user.axlevel >= 6) permit = 1;
3757 /* Room aides can move/copy */
3758 if (CC->user.usernum == CC->room.QRroomaide) permit = 1;
3760 /* Permit move/copy from personal rooms */
3761 if ((CC->room.QRflags & QR_MAILBOX)
3762 && (qtemp.QRflags & QR_MAILBOX)) permit = 1;
3764 /* Permit only copy from public to personal room */
3766 && (!(CC->room.QRflags & QR_MAILBOX))
3767 && (qtemp.QRflags & QR_MAILBOX)) permit = 1;
3769 /* User must have access to target room */
3770 if (!(ra & UA_KNOWN)) permit = 0;
3773 cprintf("%d Higher access required.\n",
3774 ERROR + HIGHER_ACCESS_REQUIRED);
3779 * Build our message set to be moved/copied
3781 msgs = malloc(num_msgs * sizeof(long));
3782 for (i=0; i<num_msgs; ++i) {
3783 extract_token(msgtok, msgset, i, ',', sizeof msgtok);
3784 msgs[i] = atol(msgtok);
3790 err = CtdlCopyMsgsToRoom(msgs, num_msgs, targ);
3792 cprintf("%d Cannot store message(s) in %s: error %d\n",
3798 /* Now delete the message from the source room,
3799 * if this is a 'move' rather than a 'copy' operation.
3802 CtdlDeleteMessages(CC->room.QRname, msgs, num_msgs, "");
3806 cprintf("%d Message(s) %s.\n", CIT_OK, (is_copy ? "copied" : "moved") );
3812 * GetMetaData() - Get the supplementary record for a message
3814 void GetMetaData(struct MetaData *smibuf, long msgnum)
3817 struct cdbdata *cdbsmi;
3820 memset(smibuf, 0, sizeof(struct MetaData));
3821 smibuf->meta_msgnum = msgnum;
3822 smibuf->meta_refcount = 1; /* Default reference count is 1 */
3824 /* Use the negative of the message number for its supp record index */
3825 TheIndex = (0L - msgnum);
3827 cdbsmi = cdb_fetch(CDB_MSGMAIN, &TheIndex, sizeof(long));
3828 if (cdbsmi == NULL) {
3829 return; /* record not found; go with defaults */
3831 memcpy(smibuf, cdbsmi->ptr,
3832 ((cdbsmi->len > sizeof(struct MetaData)) ?
3833 sizeof(struct MetaData) : cdbsmi->len));
3840 * PutMetaData() - (re)write supplementary record for a message
3842 void PutMetaData(struct MetaData *smibuf)
3846 /* Use the negative of the message number for the metadata db index */
3847 TheIndex = (0L - smibuf->meta_msgnum);
3849 cdb_store(CDB_MSGMAIN,
3850 &TheIndex, (int)sizeof(long),
3851 smibuf, (int)sizeof(struct MetaData));
3856 * AdjRefCount - submit an adjustment to the reference count for a message.
3857 * (These are just queued -- we actually process them later.)
3859 void AdjRefCount(long msgnum, int incr)
3861 struct arcq new_arcq;
3863 begin_critical_section(S_SUPPMSGMAIN);
3864 if (arcfp == NULL) {
3865 arcfp = fopen(file_arcq, "ab+");
3867 end_critical_section(S_SUPPMSGMAIN);
3869 /* msgnum < 0 means that we're trying to close the file */
3871 lprintf(CTDL_DEBUG, "Closing the AdjRefCount queue file\n");
3872 begin_critical_section(S_SUPPMSGMAIN);
3873 if (arcfp != NULL) {
3877 end_critical_section(S_SUPPMSGMAIN);
3882 * If we can't open the queue, perform the operation synchronously.
3884 if (arcfp == NULL) {
3885 TDAP_AdjRefCount(msgnum, incr);
3889 new_arcq.arcq_msgnum = msgnum;
3890 new_arcq.arcq_delta = incr;
3891 fwrite(&new_arcq, sizeof(struct arcq), 1, arcfp);
3899 * TDAP_ProcessAdjRefCountQueue()
3901 * Process the queue of message count adjustments that was created by calls
3902 * to AdjRefCount() ... by reading the queue and calling TDAP_AdjRefCount()
3903 * for each one. This should be an "off hours" operation.
3905 int TDAP_ProcessAdjRefCountQueue(void)
3907 char file_arcq_temp[PATH_MAX];
3910 struct arcq arcq_rec;
3911 int num_records_processed = 0;
3913 snprintf(file_arcq_temp, sizeof file_arcq_temp, "%s2", file_arcq);
3915 begin_critical_section(S_SUPPMSGMAIN);
3916 if (arcfp != NULL) {
3921 r = link(file_arcq, file_arcq_temp);
3923 lprintf(CTDL_CRIT, "%s: %s\n", file_arcq_temp, strerror(errno));
3924 end_critical_section(S_SUPPMSGMAIN);
3925 return(num_records_processed);
3929 end_critical_section(S_SUPPMSGMAIN);
3931 fp = fopen(file_arcq_temp, "rb");
3933 lprintf(CTDL_CRIT, "%s: %s\n", file_arcq_temp, strerror(errno));
3934 return(num_records_processed);
3937 while (fread(&arcq_rec, sizeof(struct arcq), 1, fp) == 1) {
3938 TDAP_AdjRefCount(arcq_rec.arcq_msgnum, arcq_rec.arcq_delta);
3939 ++num_records_processed;
3943 r = unlink(file_arcq_temp);
3945 lprintf(CTDL_CRIT, "%s: %s\n", file_arcq_temp, strerror(errno));
3948 return(num_records_processed);
3954 * TDAP_AdjRefCount - adjust the reference count for a message.
3955 * This one does it "for real" because it's called by
3956 * the autopurger function that processes the queue
3957 * created by AdjRefCount(). If a message's reference
3958 * count becomes zero, we also delete the message from
3959 * disk and de-index it.
3961 void TDAP_AdjRefCount(long msgnum, int incr)
3964 struct MetaData smi;
3967 /* This is a *tight* critical section; please keep it that way, as
3968 * it may get called while nested in other critical sections.
3969 * Complicating this any further will surely cause deadlock!
3971 begin_critical_section(S_SUPPMSGMAIN);
3972 GetMetaData(&smi, msgnum);
3973 smi.meta_refcount += incr;
3975 end_critical_section(S_SUPPMSGMAIN);
3976 lprintf(CTDL_DEBUG, "msg %ld ref count delta %d, is now %d\n",
3977 msgnum, incr, smi.meta_refcount);
3979 /* If the reference count is now zero, delete the message
3980 * (and its supplementary record as well).
3982 if (smi.meta_refcount == 0) {
3983 lprintf(CTDL_DEBUG, "Deleting message <%ld>\n", msgnum);
3985 /* Remove from fulltext index */
3986 if (config.c_enable_fulltext) {
3987 ft_index_message(msgnum, 0);
3990 /* Remove from message base */
3992 cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long));
3993 cdb_delete(CDB_BIGMSGS, &delnum, (int)sizeof(long));
3995 /* Remove metadata record */
3996 delnum = (0L - msgnum);
3997 cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long));
4003 * Write a generic object to this room
4005 * Note: this could be much more efficient. Right now we use two temporary
4006 * files, and still pull the message into memory as with all others.
4008 void CtdlWriteObject(char *req_room, /* Room to stuff it in */
4009 char *content_type, /* MIME type of this object */
4010 char *tempfilename, /* Where to fetch it from */
4011 struct ctdluser *is_mailbox, /* Mailbox room? */
4012 int is_binary, /* Is encoding necessary? */
4013 int is_unique, /* Del others of this type? */
4014 unsigned int flags /* Internal save flags */
4019 struct ctdlroom qrbuf;
4020 char roomname[ROOMNAMELEN];
4021 struct CtdlMessage *msg;
4023 char *raw_message = NULL;
4024 char *encoded_message = NULL;
4025 off_t raw_length = 0;
4027 if (is_mailbox != NULL) {
4028 MailboxName(roomname, sizeof roomname, is_mailbox, req_room);
4031 safestrncpy(roomname, req_room, sizeof(roomname));
4034 fp = fopen(tempfilename, "rb");
4036 lprintf(CTDL_CRIT, "Cannot open %s: %s\n",
4037 tempfilename, strerror(errno));
4040 fseek(fp, 0L, SEEK_END);
4041 raw_length = ftell(fp);
4043 lprintf(CTDL_DEBUG, "Raw length is %ld\n", (long)raw_length);
4045 raw_message = malloc((size_t)raw_length + 2);
4046 fread(raw_message, (size_t)raw_length, 1, fp);
4050 encoded_message = malloc((size_t)
4051 (((raw_length * 134) / 100) + 4096 ) );
4054 encoded_message = malloc((size_t)(raw_length + 4096));
4057 sprintf(encoded_message, "Content-type: %s\n", content_type);
4060 sprintf(&encoded_message[strlen(encoded_message)],
4061 "Content-transfer-encoding: base64\n\n"
4065 sprintf(&encoded_message[strlen(encoded_message)],
4066 "Content-transfer-encoding: 7bit\n\n"
4072 &encoded_message[strlen(encoded_message)],
4078 raw_message[raw_length] = 0;
4080 &encoded_message[strlen(encoded_message)],
4088 lprintf(CTDL_DEBUG, "Allocating\n");
4089 msg = malloc(sizeof(struct CtdlMessage));
4090 memset(msg, 0, sizeof(struct CtdlMessage));
4091 msg->cm_magic = CTDLMESSAGE_MAGIC;
4092 msg->cm_anon_type = MES_NORMAL;
4093 msg->cm_format_type = 4;
4094 msg->cm_fields['A'] = strdup(CC->user.fullname);
4095 msg->cm_fields['O'] = strdup(req_room);
4096 msg->cm_fields['N'] = strdup(config.c_nodename);
4097 msg->cm_fields['H'] = strdup(config.c_humannode);
4098 msg->cm_flags = flags;
4100 msg->cm_fields['M'] = encoded_message;
4102 /* Create the requested room if we have to. */
4103 if (getroom(&qrbuf, roomname) != 0) {
4104 create_room(roomname,
4105 ( (is_mailbox != NULL) ? 5 : 3 ),
4106 "", 0, 1, 0, VIEW_BBS);
4108 /* If the caller specified this object as unique, delete all
4109 * other objects of this type that are currently in the room.
4112 lprintf(CTDL_DEBUG, "Deleted %d other msgs of this type\n",
4113 CtdlDeleteMessages(roomname, NULL, 0, content_type)
4116 /* Now write the data */
4117 CtdlSubmitMsg(msg, NULL, roomname);
4118 CtdlFreeMessage(msg);
4126 void CtdlGetSysConfigBackend(long msgnum, void *userdata) {
4127 config_msgnum = msgnum;
4131 char *CtdlGetSysConfig(char *sysconfname) {
4132 char hold_rm[ROOMNAMELEN];
4135 struct CtdlMessage *msg;
4138 strcpy(hold_rm, CC->room.QRname);
4139 if (getroom(&CC->room, SYSCONFIGROOM) != 0) {
4140 getroom(&CC->room, hold_rm);
4145 /* We want the last (and probably only) config in this room */
4146 begin_critical_section(S_CONFIG);
4147 config_msgnum = (-1L);
4148 CtdlForEachMessage(MSGS_LAST, 1, NULL, sysconfname, NULL,
4149 CtdlGetSysConfigBackend, NULL);
4150 msgnum = config_msgnum;
4151 end_critical_section(S_CONFIG);
4157 msg = CtdlFetchMessage(msgnum, 1);
4159 conf = strdup(msg->cm_fields['M']);
4160 CtdlFreeMessage(msg);
4167 getroom(&CC->room, hold_rm);
4169 if (conf != NULL) do {
4170 extract_token(buf, conf, 0, '\n', sizeof buf);
4171 strcpy(conf, &conf[strlen(buf)+1]);
4172 } while ( (strlen(conf)>0) && (strlen(buf)>0) );
4177 void CtdlPutSysConfig(char *sysconfname, char *sysconfdata) {
4178 char temp[PATH_MAX];
4181 CtdlMakeTempFileName(temp, sizeof temp);
4183 fp = fopen(temp, "w");
4184 if (fp == NULL) return;
4185 fprintf(fp, "%s", sysconfdata);
4188 /* this handy API function does all the work for us */
4189 CtdlWriteObject(SYSCONFIGROOM, sysconfname, temp, NULL, 0, 1, 0);
4195 * Determine whether a given Internet address belongs to the current user
4197 int CtdlIsMe(char *addr, int addr_buf_len)
4199 struct recptypes *recp;
4202 recp = validate_recipients(addr);
4203 if (recp == NULL) return(0);
4205 if (recp->num_local == 0) {
4210 for (i=0; i<recp->num_local; ++i) {
4211 extract_token(addr, recp->recp_local, i, '|', addr_buf_len);
4212 if (!strcasecmp(addr, CC->user.fullname)) {
4224 * Citadel protocol command to do the same
4226 void cmd_isme(char *argbuf) {
4229 if (CtdlAccessCheck(ac_logged_in)) return;
4230 extract_token(addr, argbuf, 0, '|', sizeof addr);
4232 if (CtdlIsMe(addr, sizeof addr)) {
4233 cprintf("%d %s\n", CIT_OK, addr);
4236 cprintf("%d Not you.\n", ERROR + ILLEGAL_VALUE);