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);
2585 lprintf(CTDL_DEBUG, "No user <%s>\n", recipient);
2586 CtdlSaveMsgPointerInRoom(config.c_aideroom,
2591 /* Perform "after save" hooks */
2592 lprintf(CTDL_DEBUG, "Performing after-save hooks\n");
2593 PerformMessageHooks(msg, EVT_AFTERSAVE);
2595 /* For IGnet mail, we have to save a new copy into the spooler for
2596 * each recipient, with the R and D fields set to the recipient and
2597 * destination-node. This has two ugly side effects: all other
2598 * recipients end up being unlisted in this recipient's copy of the
2599 * message, and it has to deliver multiple messages to the same
2600 * node. We'll revisit this again in a year or so when everyone has
2601 * a network spool receiver that can handle the new style messages.
2604 if (recps->num_ignet > 0)
2605 for (i=0; i<num_tokens(recps->recp_ignet, '|'); ++i) {
2606 extract_token(recipient, recps->recp_ignet, i,
2607 '|', sizeof recipient);
2609 hold_R = msg->cm_fields['R'];
2610 hold_D = msg->cm_fields['D'];
2611 msg->cm_fields['R'] = malloc(SIZ);
2612 msg->cm_fields['D'] = malloc(128);
2613 extract_token(msg->cm_fields['R'], recipient, 0, '@', SIZ);
2614 extract_token(msg->cm_fields['D'], recipient, 1, '@', 128);
2616 serialize_message(&smr, msg);
2618 snprintf(submit_filename, sizeof submit_filename,
2619 "%s/netmail.%04lx.%04x.%04x",
2621 (long) getpid(), CC->cs_pid, ++seqnum);
2622 network_fp = fopen(submit_filename, "wb+");
2623 if (network_fp != NULL) {
2624 fwrite(smr.ser, smr.len, 1, network_fp);
2630 free(msg->cm_fields['R']);
2631 free(msg->cm_fields['D']);
2632 msg->cm_fields['R'] = hold_R;
2633 msg->cm_fields['D'] = hold_D;
2636 /* Go back to the room we started from */
2637 lprintf(CTDL_DEBUG, "Returning to original room %s\n", hold_rm);
2638 if (strcasecmp(hold_rm, CC->room.QRname))
2639 /* getroom(&CC->room, hold_rm); */
2640 usergoto(hold_rm, 0, 1, NULL, NULL);
2642 /* For internet mail, generate delivery instructions.
2643 * Yes, this is recursive. Deal with it. Infinite recursion does
2644 * not happen because the delivery instructions message does not
2645 * contain a recipient.
2648 if (recps->num_internet > 0) {
2649 lprintf(CTDL_DEBUG, "Generating delivery instructions\n");
2650 instr = malloc(SIZ * 2);
2651 snprintf(instr, SIZ * 2,
2652 "Content-type: %s\n\nmsgid|%ld\nsubmitted|%ld\n"
2654 SPOOLMIME, newmsgid, (long)time(NULL),
2655 msg->cm_fields['A'], msg->cm_fields['N']
2658 for (i=0; i<num_tokens(recps->recp_internet, '|'); ++i) {
2659 size_t tmp = strlen(instr);
2660 extract_token(recipient, recps->recp_internet,
2661 i, '|', sizeof recipient);
2662 snprintf(&instr[tmp], SIZ * 2 - tmp,
2663 "remote|%s|0||\n", recipient);
2666 imsg = malloc(sizeof(struct CtdlMessage));
2667 memset(imsg, 0, sizeof(struct CtdlMessage));
2668 imsg->cm_magic = CTDLMESSAGE_MAGIC;
2669 imsg->cm_anon_type = MES_NORMAL;
2670 imsg->cm_format_type = FMT_RFC822;
2671 imsg->cm_fields['A'] = strdup("Citadel");
2672 imsg->cm_fields['J'] = strdup("do not journal");
2673 imsg->cm_fields['M'] = instr;
2674 CtdlSubmitMsg(imsg, NULL, SMTP_SPOOLOUT_ROOM);
2675 CtdlFreeMessage(imsg);
2679 * Any addresses to harvest for someone's address book?
2681 if ( (CC->logged_in) && (recps != NULL) ) {
2682 collected_addresses = harvest_collected_addresses(msg);
2685 if (collected_addresses != NULL) {
2686 begin_critical_section(S_ATBF);
2687 aptr = (struct addresses_to_be_filed *)
2688 malloc(sizeof(struct addresses_to_be_filed));
2690 MailboxName(actual_rm, sizeof actual_rm,
2691 &CC->user, USERCONTACTSROOM);
2692 aptr->roomname = strdup(actual_rm);
2693 aptr->collected_addresses = collected_addresses;
2695 end_critical_section(S_ATBF);
2699 * Determine whether this message qualifies for journaling.
2701 if (msg->cm_fields['J'] != NULL) {
2702 qualified_for_journaling = 0;
2705 if (recps == NULL) {
2706 qualified_for_journaling = config.c_journal_pubmsgs;
2708 else if (recps->num_local + recps->num_ignet + recps->num_internet > 0) {
2709 qualified_for_journaling = config.c_journal_email;
2712 qualified_for_journaling = config.c_journal_pubmsgs;
2717 * Do we have to perform journaling? If so, hand off the saved
2718 * RFC822 version will be handed off to the journaler for background
2719 * submit. Otherwise, we have to free the memory ourselves.
2721 if (saved_rfc822_version != NULL) {
2722 if (qualified_for_journaling) {
2723 JournalBackgroundSubmit(msg, saved_rfc822_version, recps);
2726 free(saved_rfc822_version);
2739 * Convenience function for generating small administrative messages.
2741 void quickie_message(char *from, char *fromaddr, char *to, char *room, char *text,
2742 int format_type, char *subject)
2744 struct CtdlMessage *msg;
2745 struct recptypes *recp = NULL;
2747 msg = malloc(sizeof(struct CtdlMessage));
2748 memset(msg, 0, sizeof(struct CtdlMessage));
2749 msg->cm_magic = CTDLMESSAGE_MAGIC;
2750 msg->cm_anon_type = MES_NORMAL;
2751 msg->cm_format_type = format_type;
2754 msg->cm_fields['A'] = strdup(from);
2756 else if (fromaddr != NULL) {
2757 msg->cm_fields['A'] = strdup(fromaddr);
2758 if (strchr(msg->cm_fields['A'], '@')) {
2759 *strchr(msg->cm_fields['A'], '@') = 0;
2763 msg->cm_fields['A'] = strdup("Citadel");
2766 if (fromaddr != NULL) msg->cm_fields['F'] = strdup(fromaddr);
2767 if (room != NULL) msg->cm_fields['O'] = strdup(room);
2768 msg->cm_fields['N'] = strdup(NODENAME);
2770 msg->cm_fields['R'] = strdup(to);
2771 recp = validate_recipients(to);
2773 if (subject != NULL) {
2774 msg->cm_fields['U'] = strdup(subject);
2776 msg->cm_fields['M'] = strdup(text);
2778 CtdlSubmitMsg(msg, recp, room);
2779 CtdlFreeMessage(msg);
2780 if (recp != NULL) free(recp);
2786 * Back end function used by CtdlMakeMessage() and similar functions
2788 char *CtdlReadMessageBody(char *terminator, /* token signalling EOT */
2789 size_t maxlen, /* maximum message length */
2790 char *exist, /* if non-null, append to it;
2791 exist is ALWAYS freed */
2792 int crlf /* CRLF newlines instead of LF */
2796 size_t message_len = 0;
2797 size_t buffer_len = 0;
2804 if (exist == NULL) {
2811 message_len = strlen(exist);
2812 buffer_len = message_len + 4096;
2813 m = realloc(exist, buffer_len);
2820 /* Do we need to change leading ".." to "." for SMTP escaping? */
2821 if (!strcmp(terminator, ".")) {
2825 /* flush the input if we have nowhere to store it */
2830 /* read in the lines of message text one by one */
2832 if (client_getln(buf, (sizeof buf - 3)) < 1) finished = 1;
2833 if (!strcmp(buf, terminator)) finished = 1;
2835 strcat(buf, "\r\n");
2841 /* Unescape SMTP-style input of two dots at the beginning of the line */
2843 if (!strncmp(buf, "..", 2)) {
2844 strcpy(buf, &buf[1]);
2848 if ( (!flushing) && (!finished) ) {
2849 /* Measure the line */
2850 linelen = strlen(buf);
2852 /* augment the buffer if we have to */
2853 if ((message_len + linelen) >= buffer_len) {
2854 ptr = realloc(m, (buffer_len * 2) );
2855 if (ptr == NULL) { /* flush if can't allocate */
2858 buffer_len = (buffer_len * 2);
2860 lprintf(CTDL_DEBUG, "buffer_len is now %ld\n", (long)buffer_len);
2864 /* Add the new line to the buffer. NOTE: this loop must avoid
2865 * using functions like strcat() and strlen() because they
2866 * traverse the entire buffer upon every call, and doing that
2867 * for a multi-megabyte message slows it down beyond usability.
2869 strcpy(&m[message_len], buf);
2870 message_len += linelen;
2873 /* if we've hit the max msg length, flush the rest */
2874 if (message_len >= maxlen) flushing = 1;
2876 } while (!finished);
2884 * Build a binary message to be saved on disk.
2885 * (NOTE: if you supply 'preformatted_text', the buffer you give it
2886 * will become part of the message. This means you are no longer
2887 * responsible for managing that memory -- it will be freed along with
2888 * the rest of the fields when CtdlFreeMessage() is called.)
2891 struct CtdlMessage *CtdlMakeMessage(
2892 struct ctdluser *author, /* author's user structure */
2893 char *recipient, /* NULL if it's not mail */
2894 char *recp_cc, /* NULL if it's not mail */
2895 char *room, /* room where it's going */
2896 int type, /* see MES_ types in header file */
2897 int format_type, /* variformat, plain text, MIME... */
2898 char *fake_name, /* who we're masquerading as */
2899 char *subject, /* Subject (optional) */
2900 char *supplied_euid, /* ...or NULL if this is irrelevant */
2901 char *preformatted_text /* ...or NULL to read text from client */
2903 char dest_node[SIZ];
2905 struct CtdlMessage *msg;
2907 msg = malloc(sizeof(struct CtdlMessage));
2908 memset(msg, 0, sizeof(struct CtdlMessage));
2909 msg->cm_magic = CTDLMESSAGE_MAGIC;
2910 msg->cm_anon_type = type;
2911 msg->cm_format_type = format_type;
2913 /* Don't confuse the poor folks if it's not routed mail. */
2914 strcpy(dest_node, "");
2919 snprintf(buf, sizeof buf, "cit%ld", author->usernum); /* Path */
2920 msg->cm_fields['P'] = strdup(buf);
2922 snprintf(buf, sizeof buf, "%ld", (long)time(NULL)); /* timestamp */
2923 msg->cm_fields['T'] = strdup(buf);
2925 if (fake_name[0]) /* author */
2926 msg->cm_fields['A'] = strdup(fake_name);
2928 msg->cm_fields['A'] = strdup(author->fullname);
2930 if (CC->room.QRflags & QR_MAILBOX) { /* room */
2931 msg->cm_fields['O'] = strdup(&CC->room.QRname[11]);
2934 msg->cm_fields['O'] = strdup(CC->room.QRname);
2937 msg->cm_fields['N'] = strdup(NODENAME); /* nodename */
2938 msg->cm_fields['H'] = strdup(HUMANNODE); /* hnodename */
2940 if (recipient[0] != 0) {
2941 msg->cm_fields['R'] = strdup(recipient);
2943 if (recp_cc[0] != 0) {
2944 msg->cm_fields['Y'] = strdup(recp_cc);
2946 if (dest_node[0] != 0) {
2947 msg->cm_fields['D'] = strdup(dest_node);
2950 if ( (author == &CC->user) && (strlen(CC->cs_inet_email) > 0) ) {
2951 msg->cm_fields['F'] = strdup(CC->cs_inet_email);
2954 if (subject != NULL) {
2957 length = strlen(subject);
2963 while ((subject[i] != '\0') &&
2964 (IsAscii = isascii(subject[i]) != 0 ))
2967 msg->cm_fields['U'] = strdup(subject);
2968 else /* ok, we've got utf8 in the string. */
2970 msg->cm_fields['U'] = rfc2047encode(subject, length);
2976 if (supplied_euid != NULL) {
2977 msg->cm_fields['E'] = strdup(supplied_euid);
2980 if (preformatted_text != NULL) {
2981 msg->cm_fields['M'] = preformatted_text;
2984 msg->cm_fields['M'] = CtdlReadMessageBody("000",
2985 config.c_maxmsglen, NULL, 0);
2993 * Check to see whether we have permission to post a message in the current
2994 * room. Returns a *CITADEL ERROR CODE* and puts a message in errmsgbuf, or
2995 * returns 0 on success.
2997 int CtdlDoIHavePermissionToPostInThisRoom(char *errmsgbuf, size_t n) {
3000 if (!(CC->logged_in)) {
3001 snprintf(errmsgbuf, n, "Not logged in.");
3002 return (ERROR + NOT_LOGGED_IN);
3005 if ((CC->user.axlevel < 2)
3006 && ((CC->room.QRflags & QR_MAILBOX) == 0)) {
3007 snprintf(errmsgbuf, n, "Need to be validated to enter "
3008 "(except in %s> to sysop)", MAILROOM);
3009 return (ERROR + HIGHER_ACCESS_REQUIRED);
3012 CtdlRoomAccess(&CC->room, &CC->user, &ra, NULL);
3013 if (!(ra & UA_POSTALLOWED)) {
3014 snprintf(errmsgbuf, n, "Higher access is required to post in this room.");
3015 return (ERROR + HIGHER_ACCESS_REQUIRED);
3018 strcpy(errmsgbuf, "Ok");
3024 * Check to see if the specified user has Internet mail permission
3025 * (returns nonzero if permission is granted)
3027 int CtdlCheckInternetMailPermission(struct ctdluser *who) {
3029 /* Do not allow twits to send Internet mail */
3030 if (who->axlevel <= 2) return(0);
3032 /* Globally enabled? */
3033 if (config.c_restrict == 0) return(1);
3035 /* User flagged ok? */
3036 if (who->flags & US_INTERNET) return(2);
3038 /* Aide level access? */
3039 if (who->axlevel >= 6) return(3);
3041 /* No mail for you! */
3047 * Validate recipients, count delivery types and errors, and handle aliasing
3048 * FIXME check for dupes!!!!!
3049 * Returns 0 if all addresses are ok, ret->num_error = -1 if no addresses
3050 * were specified, or the number of addresses found invalid.
3051 * caller needs to free the result.
3053 struct recptypes *validate_recipients(char *supplied_recipients) {
3054 struct recptypes *ret;
3055 char recipients[SIZ];
3056 char this_recp[256];
3057 char this_recp_cooked[256];
3063 struct ctdluser tempUS;
3064 struct ctdlroom tempQR;
3068 ret = (struct recptypes *) malloc(sizeof(struct recptypes));
3069 if (ret == NULL) return(NULL);
3070 memset(ret, 0, sizeof(struct recptypes));
3073 ret->num_internet = 0;
3078 if (supplied_recipients == NULL) {
3079 strcpy(recipients, "");
3082 safestrncpy(recipients, supplied_recipients, sizeof recipients);
3085 /* Change all valid separator characters to commas */
3086 for (i=0; i<strlen(recipients); ++i) {
3087 if ((recipients[i] == ';') || (recipients[i] == '|')) {
3088 recipients[i] = ',';
3092 /* Now start extracting recipients... */
3094 while (strlen(recipients) > 0) {
3096 for (i=0; i<=strlen(recipients); ++i) {
3097 if (recipients[i] == '\"') in_quotes = 1 - in_quotes;
3098 if ( ( (recipients[i] == ',') && (!in_quotes) ) || (recipients[i] == 0) ) {
3099 safestrncpy(this_recp, recipients, i+1);
3101 if (recipients[i] == ',') {
3102 strcpy(recipients, &recipients[i+1]);
3105 strcpy(recipients, "");
3112 lprintf(CTDL_DEBUG, "Evaluating recipient #%d: %s\n", num_recps, this_recp);
3114 mailtype = alias(this_recp);
3115 mailtype = alias(this_recp);
3116 mailtype = alias(this_recp);
3117 for (j=0; j<=strlen(this_recp); ++j) {
3118 if (this_recp[j]=='_') {
3119 this_recp_cooked[j] = ' ';
3122 this_recp_cooked[j] = this_recp[j];
3128 if (!strcasecmp(this_recp, "sysop")) {
3130 strcpy(this_recp, config.c_aideroom);
3131 if (strlen(ret->recp_room) > 0) {
3132 strcat(ret->recp_room, "|");
3134 strcat(ret->recp_room, this_recp);
3136 else if (getuser(&tempUS, this_recp) == 0) {
3138 strcpy(this_recp, tempUS.fullname);
3139 if (strlen(ret->recp_local) > 0) {
3140 strcat(ret->recp_local, "|");
3142 strcat(ret->recp_local, this_recp);
3144 else if (getuser(&tempUS, this_recp_cooked) == 0) {
3146 strcpy(this_recp, tempUS.fullname);
3147 if (strlen(ret->recp_local) > 0) {
3148 strcat(ret->recp_local, "|");
3150 strcat(ret->recp_local, this_recp);
3152 else if ( (!strncasecmp(this_recp, "room_", 5))
3153 && (!getroom(&tempQR, &this_recp_cooked[5])) ) {
3155 if (strlen(ret->recp_room) > 0) {
3156 strcat(ret->recp_room, "|");
3158 strcat(ret->recp_room, &this_recp_cooked[5]);
3166 /* Yes, you're reading this correctly: if the target
3167 * domain points back to the local system or an attached
3168 * Citadel directory, the address is invalid. That's
3169 * because if the address were valid, we would have
3170 * already translated it to a local address by now.
3172 if (IsDirectory(this_recp)) {
3177 ++ret->num_internet;
3178 if (strlen(ret->recp_internet) > 0) {
3179 strcat(ret->recp_internet, "|");
3181 strcat(ret->recp_internet, this_recp);
3186 if (strlen(ret->recp_ignet) > 0) {
3187 strcat(ret->recp_ignet, "|");
3189 strcat(ret->recp_ignet, this_recp);
3197 if (strlen(ret->errormsg) == 0) {
3198 snprintf(append, sizeof append,
3199 "Invalid recipient: %s",
3203 snprintf(append, sizeof append,
3206 if ( (strlen(ret->errormsg) + strlen(append)) < SIZ) {
3207 strcat(ret->errormsg, append);
3211 if (strlen(ret->display_recp) == 0) {
3212 strcpy(append, this_recp);
3215 snprintf(append, sizeof append, ", %s",
3218 if ( (strlen(ret->display_recp)+strlen(append)) < SIZ) {
3219 strcat(ret->display_recp, append);
3224 if ((ret->num_local + ret->num_internet + ret->num_ignet +
3225 ret->num_room + ret->num_error) == 0) {
3226 ret->num_error = (-1);
3227 strcpy(ret->errormsg, "No recipients specified.");
3230 lprintf(CTDL_DEBUG, "validate_recipients()\n");
3231 lprintf(CTDL_DEBUG, " local: %d <%s>\n", ret->num_local, ret->recp_local);
3232 lprintf(CTDL_DEBUG, " room: %d <%s>\n", ret->num_room, ret->recp_room);
3233 lprintf(CTDL_DEBUG, " inet: %d <%s>\n", ret->num_internet, ret->recp_internet);
3234 lprintf(CTDL_DEBUG, " ignet: %d <%s>\n", ret->num_ignet, ret->recp_ignet);
3235 lprintf(CTDL_DEBUG, " error: %d <%s>\n", ret->num_error, ret->errormsg);
3243 * message entry - mode 0 (normal)
3245 void cmd_ent0(char *entargs)
3251 char supplied_euid[128];
3252 char masquerade_as[SIZ];
3254 int format_type = 0;
3255 char newusername[SIZ];
3256 struct CtdlMessage *msg;
3260 struct recptypes *valid = NULL;
3261 struct recptypes *valid_to = NULL;
3262 struct recptypes *valid_cc = NULL;
3263 struct recptypes *valid_bcc = NULL;
3270 post = extract_int(entargs, 0);
3271 extract_token(recp, entargs, 1, '|', sizeof recp);
3272 anon_flag = extract_int(entargs, 2);
3273 format_type = extract_int(entargs, 3);
3274 extract_token(subject, entargs, 4, '|', sizeof subject);
3275 do_confirm = extract_int(entargs, 6);
3276 extract_token(cc, entargs, 7, '|', sizeof cc);
3277 extract_token(bcc, entargs, 8, '|', sizeof bcc);
3278 switch(CC->room.QRdefaultview) {
3281 extract_token(supplied_euid, entargs, 9, '|', sizeof supplied_euid);
3284 supplied_euid[0] = 0;
3288 /* first check to make sure the request is valid. */
3290 err = CtdlDoIHavePermissionToPostInThisRoom(errmsg, sizeof errmsg);
3292 cprintf("%d %s\n", err, errmsg);
3296 /* Check some other permission type things. */
3299 if (CC->user.axlevel < 6) {
3300 cprintf("%d You don't have permission to masquerade.\n",
3301 ERROR + HIGHER_ACCESS_REQUIRED);
3304 extract_token(newusername, entargs, 5, '|', sizeof newusername);
3305 memset(CC->fake_postname, 0, sizeof(CC->fake_postname) );
3306 safestrncpy(CC->fake_postname, newusername,
3307 sizeof(CC->fake_postname) );
3308 cprintf("%d ok\n", CIT_OK);
3311 CC->cs_flags |= CS_POSTING;
3313 /* In the Mail> room we have to behave a little differently --
3314 * make sure the user has specified at least one recipient. Then
3315 * validate the recipient(s).
3317 if ( (CC->room.QRflags & QR_MAILBOX)
3318 && (!strcasecmp(&CC->room.QRname[11], MAILROOM)) ) {
3320 if (CC->user.axlevel < 2) {
3321 strcpy(recp, "sysop");
3326 valid_to = validate_recipients(recp);
3327 if (valid_to->num_error > 0) {
3328 cprintf("%d Invalid recipient (To)\n", ERROR + NO_SUCH_USER);
3333 valid_cc = validate_recipients(cc);
3334 if (valid_cc->num_error > 0) {
3335 cprintf("%d Invalid recipient (CC)\n", ERROR + NO_SUCH_USER);
3341 valid_bcc = validate_recipients(bcc);
3342 if (valid_bcc->num_error > 0) {
3343 cprintf("%d Invalid recipient (BCC)\n", ERROR + NO_SUCH_USER);
3350 /* Recipient required, but none were specified */
3351 if ( (valid_to->num_error < 0) && (valid_cc->num_error < 0) && (valid_bcc->num_error < 0) ) {
3355 cprintf("%d At least one recipient is required.\n", ERROR + NO_SUCH_USER);
3359 if (valid_to->num_internet + valid_cc->num_internet + valid_bcc->num_internet > 0) {
3360 if (CtdlCheckInternetMailPermission(&CC->user)==0) {
3361 cprintf("%d You do not have permission "
3362 "to send Internet mail.\n",
3363 ERROR + HIGHER_ACCESS_REQUIRED);
3371 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)
3372 && (CC->user.axlevel < 4) ) {
3373 cprintf("%d Higher access required for network mail.\n",
3374 ERROR + HIGHER_ACCESS_REQUIRED);
3381 if ((RESTRICT_INTERNET == 1)
3382 && (valid_to->num_internet + valid_cc->num_internet + valid_bcc->num_internet > 0)
3383 && ((CC->user.flags & US_INTERNET) == 0)
3384 && (!CC->internal_pgm)) {
3385 cprintf("%d You don't have access to Internet mail.\n",
3386 ERROR + HIGHER_ACCESS_REQUIRED);
3395 /* Is this a room which has anonymous-only or anonymous-option? */
3396 anonymous = MES_NORMAL;
3397 if (CC->room.QRflags & QR_ANONONLY) {
3398 anonymous = MES_ANONONLY;
3400 if (CC->room.QRflags & QR_ANONOPT) {
3401 if (anon_flag == 1) { /* only if the user requested it */
3402 anonymous = MES_ANONOPT;
3406 if ((CC->room.QRflags & QR_MAILBOX) == 0) {
3410 /* If we're only checking the validity of the request, return
3411 * success without creating the message.
3414 cprintf("%d %s\n", CIT_OK,
3415 ((valid_to != NULL) ? valid_to->display_recp : "") );
3422 /* We don't need these anymore because we'll do it differently below */
3427 /* Handle author masquerading */
3428 if (CC->fake_postname[0]) {
3429 strcpy(masquerade_as, CC->fake_postname);
3431 else if (CC->fake_username[0]) {
3432 strcpy(masquerade_as, CC->fake_username);
3435 strcpy(masquerade_as, "");
3438 /* Read in the message from the client. */
3440 cprintf("%d send message\n", START_CHAT_MODE);
3442 cprintf("%d send message\n", SEND_LISTING);
3445 msg = CtdlMakeMessage(&CC->user, recp, cc,
3446 CC->room.QRname, anonymous, format_type,
3447 masquerade_as, subject,
3448 ((strlen(supplied_euid) > 0) ? supplied_euid : NULL),
3451 /* Put together one big recipients struct containing to/cc/bcc all in
3452 * one. This is for the envelope.
3454 char *all_recps = malloc(SIZ * 3);
3455 strcpy(all_recps, recp);
3456 if (strlen(cc) > 0) {
3457 if (strlen(all_recps) > 0) {
3458 strcat(all_recps, ",");
3460 strcat(all_recps, cc);
3462 if (strlen(bcc) > 0) {
3463 if (strlen(all_recps) > 0) {
3464 strcat(all_recps, ",");
3466 strcat(all_recps, bcc);
3468 if (strlen(all_recps) > 0) {
3469 valid = validate_recipients(all_recps);
3477 msgnum = CtdlSubmitMsg(msg, valid, "");
3480 cprintf("%ld\n", msgnum);
3482 cprintf("Message accepted.\n");
3485 cprintf("Internal error.\n");
3487 if (msg->cm_fields['E'] != NULL) {
3488 cprintf("%s\n", msg->cm_fields['E']);
3495 CtdlFreeMessage(msg);
3497 CC->fake_postname[0] = '\0';
3498 if (valid != NULL) {
3507 * API function to delete messages which match a set of criteria
3508 * (returns the actual number of messages deleted)
3510 int CtdlDeleteMessages(char *room_name, /* which room */
3511 long *dmsgnums, /* array of msg numbers to be deleted */
3512 int num_dmsgnums, /* number of msgs to be deleted, or 0 for "any" */
3513 char *content_type /* or "" for any */
3517 struct ctdlroom qrbuf;
3518 struct cdbdata *cdbfr;
3519 long *msglist = NULL;
3520 long *dellist = NULL;
3523 int num_deleted = 0;
3525 struct MetaData smi;
3527 lprintf(CTDL_DEBUG, "CtdlDeleteMessages(%s, %d msgs, %s)\n",
3528 room_name, num_dmsgnums, content_type);
3530 /* get room record, obtaining a lock... */
3531 if (lgetroom(&qrbuf, room_name) != 0) {
3532 lprintf(CTDL_ERR, "CtdlDeleteMessages(): Room <%s> not found\n",
3534 return (0); /* room not found */
3536 cdbfr = cdb_fetch(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long));
3538 if (cdbfr != NULL) {
3539 dellist = malloc(cdbfr->len);
3540 msglist = (long *) cdbfr->ptr;
3541 cdbfr->ptr = NULL; /* CtdlDeleteMessages() now owns this memory */
3542 num_msgs = cdbfr->len / sizeof(long);
3546 for (i = 0; i < num_msgs; ++i) {
3549 /* Set/clear a bit for each criterion */
3551 /* 0 messages in the list or a null list means that we are
3552 * interested in deleting any messages which meet the other criteria.
3554 if ((num_dmsgnums == 0) || (dmsgnums == NULL)) {
3555 delete_this |= 0x01;
3558 for (j=0; j<num_dmsgnums; ++j) {
3559 if (msglist[i] == dmsgnums[j]) {
3560 delete_this |= 0x01;
3565 if (strlen(content_type) == 0) {
3566 delete_this |= 0x02;
3568 GetMetaData(&smi, msglist[i]);
3569 if (!strcasecmp(smi.meta_content_type,
3571 delete_this |= 0x02;
3575 /* Delete message only if all bits are set */
3576 if (delete_this == 0x03) {
3577 dellist[num_deleted++] = msglist[i];
3582 num_msgs = sort_msglist(msglist, num_msgs);
3583 cdb_store(CDB_MSGLISTS, &qrbuf.QRnumber, (int)sizeof(long),
3584 msglist, (int)(num_msgs * sizeof(long)));
3586 qrbuf.QRhighest = msglist[num_msgs - 1];
3590 /* Go through the messages we pulled out of the index, and decrement
3591 * their reference counts by 1. If this is the only room the message
3592 * was in, the reference count will reach zero and the message will
3593 * automatically be deleted from the database. We do this in a
3594 * separate pass because there might be plug-in hooks getting called,
3595 * and we don't want that happening during an S_ROOMS critical
3598 if (num_deleted) for (i=0; i<num_deleted; ++i) {
3599 PerformDeleteHooks(qrbuf.QRname, dellist[i]);
3600 AdjRefCount(dellist[i], -1);
3603 /* Now free the memory we used, and go away. */
3604 if (msglist != NULL) free(msglist);
3605 if (dellist != NULL) free(dellist);
3606 lprintf(CTDL_DEBUG, "%d message(s) deleted.\n", num_deleted);
3607 return (num_deleted);
3613 * Check whether the current user has permission to delete messages from
3614 * the current room (returns 1 for yes, 0 for no)
3616 int CtdlDoIHavePermissionToDeleteMessagesFromThisRoom(void) {
3617 getuser(&CC->user, CC->curr_user);
3618 if ((CC->user.axlevel < 6)
3619 && (CC->user.usernum != CC->room.QRroomaide)
3620 && ((CC->room.QRflags & QR_MAILBOX) == 0)
3621 && (!(CC->internal_pgm))) {
3630 * Delete message from current room
3632 void cmd_dele(char *args)
3641 extract_token(msgset, args, 0, '|', sizeof msgset);
3642 num_msgs = num_tokens(msgset, ',');
3644 cprintf("%d Nothing to do.\n", CIT_OK);
3648 if (CtdlDoIHavePermissionToDeleteMessagesFromThisRoom() == 0) {
3649 cprintf("%d Higher access required.\n",
3650 ERROR + HIGHER_ACCESS_REQUIRED);
3655 * Build our message set to be moved/copied
3657 msgs = malloc(num_msgs * sizeof(long));
3658 for (i=0; i<num_msgs; ++i) {
3659 extract_token(msgtok, msgset, i, ',', sizeof msgtok);
3660 msgs[i] = atol(msgtok);
3663 num_deleted = CtdlDeleteMessages(CC->room.QRname, msgs, num_msgs, "");
3667 cprintf("%d %d message%s deleted.\n", CIT_OK,
3668 num_deleted, ((num_deleted != 1) ? "s" : ""));
3670 cprintf("%d Message not found.\n", ERROR + MESSAGE_NOT_FOUND);
3676 * Back end API function for moves and deletes (multiple messages)
3678 int CtdlCopyMsgsToRoom(long *msgnums, int num_msgs, char *dest) {
3681 err = CtdlSaveMsgPointersInRoom(dest, msgnums, num_msgs, 1, NULL);
3682 if (err != 0) return(err);
3691 * move or copy a message to another room
3693 void cmd_move(char *args)
3700 char targ[ROOMNAMELEN];
3701 struct ctdlroom qtemp;
3708 extract_token(msgset, args, 0, '|', sizeof msgset);
3709 num_msgs = num_tokens(msgset, ',');
3711 cprintf("%d Nothing to do.\n", CIT_OK);
3715 extract_token(targ, args, 1, '|', sizeof targ);
3716 convert_room_name_macros(targ, sizeof targ);
3717 targ[ROOMNAMELEN - 1] = 0;
3718 is_copy = extract_int(args, 2);
3720 if (getroom(&qtemp, targ) != 0) {
3721 cprintf("%d '%s' does not exist.\n",
3722 ERROR + ROOM_NOT_FOUND, targ);
3726 getuser(&CC->user, CC->curr_user);
3727 CtdlRoomAccess(&qtemp, &CC->user, &ra, NULL);
3729 /* Check for permission to perform this operation.
3730 * Remember: "CC->room" is source, "qtemp" is target.
3734 /* Aides can move/copy */
3735 if (CC->user.axlevel >= 6) permit = 1;
3737 /* Room aides can move/copy */
3738 if (CC->user.usernum == CC->room.QRroomaide) permit = 1;
3740 /* Permit move/copy from personal rooms */
3741 if ((CC->room.QRflags & QR_MAILBOX)
3742 && (qtemp.QRflags & QR_MAILBOX)) permit = 1;
3744 /* Permit only copy from public to personal room */
3746 && (!(CC->room.QRflags & QR_MAILBOX))
3747 && (qtemp.QRflags & QR_MAILBOX)) permit = 1;
3749 /* User must have access to target room */
3750 if (!(ra & UA_KNOWN)) permit = 0;
3753 cprintf("%d Higher access required.\n",
3754 ERROR + HIGHER_ACCESS_REQUIRED);
3759 * Build our message set to be moved/copied
3761 msgs = malloc(num_msgs * sizeof(long));
3762 for (i=0; i<num_msgs; ++i) {
3763 extract_token(msgtok, msgset, i, ',', sizeof msgtok);
3764 msgs[i] = atol(msgtok);
3770 err = CtdlCopyMsgsToRoom(msgs, num_msgs, targ);
3772 cprintf("%d Cannot store message(s) in %s: error %d\n",
3778 /* Now delete the message from the source room,
3779 * if this is a 'move' rather than a 'copy' operation.
3782 CtdlDeleteMessages(CC->room.QRname, msgs, num_msgs, "");
3786 cprintf("%d Message(s) %s.\n", CIT_OK, (is_copy ? "copied" : "moved") );
3792 * GetMetaData() - Get the supplementary record for a message
3794 void GetMetaData(struct MetaData *smibuf, long msgnum)
3797 struct cdbdata *cdbsmi;
3800 memset(smibuf, 0, sizeof(struct MetaData));
3801 smibuf->meta_msgnum = msgnum;
3802 smibuf->meta_refcount = 1; /* Default reference count is 1 */
3804 /* Use the negative of the message number for its supp record index */
3805 TheIndex = (0L - msgnum);
3807 cdbsmi = cdb_fetch(CDB_MSGMAIN, &TheIndex, sizeof(long));
3808 if (cdbsmi == NULL) {
3809 return; /* record not found; go with defaults */
3811 memcpy(smibuf, cdbsmi->ptr,
3812 ((cdbsmi->len > sizeof(struct MetaData)) ?
3813 sizeof(struct MetaData) : cdbsmi->len));
3820 * PutMetaData() - (re)write supplementary record for a message
3822 void PutMetaData(struct MetaData *smibuf)
3826 /* Use the negative of the message number for the metadata db index */
3827 TheIndex = (0L - smibuf->meta_msgnum);
3829 cdb_store(CDB_MSGMAIN,
3830 &TheIndex, (int)sizeof(long),
3831 smibuf, (int)sizeof(struct MetaData));
3836 * AdjRefCount - submit an adjustment to the reference count for a message.
3837 * (These are just queued -- we actually process them later.)
3839 void AdjRefCount(long msgnum, int incr)
3841 struct arcq new_arcq;
3843 begin_critical_section(S_SUPPMSGMAIN);
3844 if (arcfp == NULL) {
3845 arcfp = fopen(file_arcq, "ab+");
3847 end_critical_section(S_SUPPMSGMAIN);
3849 /* msgnum < 0 means that we're trying to close the file */
3851 lprintf(CTDL_DEBUG, "Closing the AdjRefCount queue file\n");
3852 begin_critical_section(S_SUPPMSGMAIN);
3853 if (arcfp != NULL) {
3857 end_critical_section(S_SUPPMSGMAIN);
3862 * If we can't open the queue, perform the operation synchronously.
3864 if (arcfp == NULL) {
3865 TDAP_AdjRefCount(msgnum, incr);
3869 new_arcq.arcq_msgnum = msgnum;
3870 new_arcq.arcq_delta = incr;
3871 fwrite(&new_arcq, sizeof(struct arcq), 1, arcfp);
3879 * TDAP_ProcessAdjRefCountQueue()
3881 * Process the queue of message count adjustments that was created by calls
3882 * to AdjRefCount() ... by reading the queue and calling TDAP_AdjRefCount()
3883 * for each one. This should be an "off hours" operation.
3885 int TDAP_ProcessAdjRefCountQueue(void)
3887 char file_arcq_temp[PATH_MAX];
3890 struct arcq arcq_rec;
3891 int num_records_processed = 0;
3893 snprintf(file_arcq_temp, sizeof file_arcq_temp, "%s2", file_arcq);
3895 begin_critical_section(S_SUPPMSGMAIN);
3896 if (arcfp != NULL) {
3901 r = link(file_arcq, file_arcq_temp);
3903 lprintf(CTDL_CRIT, "%s: %s\n", file_arcq_temp, strerror(errno));
3904 end_critical_section(S_SUPPMSGMAIN);
3905 return(num_records_processed);
3909 end_critical_section(S_SUPPMSGMAIN);
3911 fp = fopen(file_arcq_temp, "rb");
3913 lprintf(CTDL_CRIT, "%s: %s\n", file_arcq_temp, strerror(errno));
3914 return(num_records_processed);
3917 while (fread(&arcq_rec, sizeof(struct arcq), 1, fp) == 1) {
3918 TDAP_AdjRefCount(arcq_rec.arcq_msgnum, arcq_rec.arcq_delta);
3919 ++num_records_processed;
3923 r = unlink(file_arcq_temp);
3925 lprintf(CTDL_CRIT, "%s: %s\n", file_arcq_temp, strerror(errno));
3928 return(num_records_processed);
3934 * TDAP_AdjRefCount - adjust the reference count for a message.
3935 * This one does it "for real" because it's called by
3936 * the autopurger function that processes the queue
3937 * created by AdjRefCount(). If a message's reference
3938 * count becomes zero, we also delete the message from
3939 * disk and de-index it.
3941 void TDAP_AdjRefCount(long msgnum, int incr)
3944 struct MetaData smi;
3947 /* This is a *tight* critical section; please keep it that way, as
3948 * it may get called while nested in other critical sections.
3949 * Complicating this any further will surely cause deadlock!
3951 begin_critical_section(S_SUPPMSGMAIN);
3952 GetMetaData(&smi, msgnum);
3953 smi.meta_refcount += incr;
3955 end_critical_section(S_SUPPMSGMAIN);
3956 lprintf(CTDL_DEBUG, "msg %ld ref count delta %d, is now %d\n",
3957 msgnum, incr, smi.meta_refcount);
3959 /* If the reference count is now zero, delete the message
3960 * (and its supplementary record as well).
3962 if (smi.meta_refcount == 0) {
3963 lprintf(CTDL_DEBUG, "Deleting message <%ld>\n", msgnum);
3965 /* Remove from fulltext index */
3966 if (config.c_enable_fulltext) {
3967 ft_index_message(msgnum, 0);
3970 /* Remove from message base */
3972 cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long));
3973 cdb_delete(CDB_BIGMSGS, &delnum, (int)sizeof(long));
3975 /* Remove metadata record */
3976 delnum = (0L - msgnum);
3977 cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long));
3983 * Write a generic object to this room
3985 * Note: this could be much more efficient. Right now we use two temporary
3986 * files, and still pull the message into memory as with all others.
3988 void CtdlWriteObject(char *req_room, /* Room to stuff it in */
3989 char *content_type, /* MIME type of this object */
3990 char *tempfilename, /* Where to fetch it from */
3991 struct ctdluser *is_mailbox, /* Mailbox room? */
3992 int is_binary, /* Is encoding necessary? */
3993 int is_unique, /* Del others of this type? */
3994 unsigned int flags /* Internal save flags */
3999 struct ctdlroom qrbuf;
4000 char roomname[ROOMNAMELEN];
4001 struct CtdlMessage *msg;
4003 char *raw_message = NULL;
4004 char *encoded_message = NULL;
4005 off_t raw_length = 0;
4007 if (is_mailbox != NULL) {
4008 MailboxName(roomname, sizeof roomname, is_mailbox, req_room);
4011 safestrncpy(roomname, req_room, sizeof(roomname));
4014 fp = fopen(tempfilename, "rb");
4016 lprintf(CTDL_CRIT, "Cannot open %s: %s\n",
4017 tempfilename, strerror(errno));
4020 fseek(fp, 0L, SEEK_END);
4021 raw_length = ftell(fp);
4023 lprintf(CTDL_DEBUG, "Raw length is %ld\n", (long)raw_length);
4025 raw_message = malloc((size_t)raw_length + 2);
4026 fread(raw_message, (size_t)raw_length, 1, fp);
4030 encoded_message = malloc((size_t)
4031 (((raw_length * 134) / 100) + 4096 ) );
4034 encoded_message = malloc((size_t)(raw_length + 4096));
4037 sprintf(encoded_message, "Content-type: %s\n", content_type);
4040 sprintf(&encoded_message[strlen(encoded_message)],
4041 "Content-transfer-encoding: base64\n\n"
4045 sprintf(&encoded_message[strlen(encoded_message)],
4046 "Content-transfer-encoding: 7bit\n\n"
4052 &encoded_message[strlen(encoded_message)],
4058 raw_message[raw_length] = 0;
4060 &encoded_message[strlen(encoded_message)],
4068 lprintf(CTDL_DEBUG, "Allocating\n");
4069 msg = malloc(sizeof(struct CtdlMessage));
4070 memset(msg, 0, sizeof(struct CtdlMessage));
4071 msg->cm_magic = CTDLMESSAGE_MAGIC;
4072 msg->cm_anon_type = MES_NORMAL;
4073 msg->cm_format_type = 4;
4074 msg->cm_fields['A'] = strdup(CC->user.fullname);
4075 msg->cm_fields['O'] = strdup(req_room);
4076 msg->cm_fields['N'] = strdup(config.c_nodename);
4077 msg->cm_fields['H'] = strdup(config.c_humannode);
4078 msg->cm_flags = flags;
4080 msg->cm_fields['M'] = encoded_message;
4082 /* Create the requested room if we have to. */
4083 if (getroom(&qrbuf, roomname) != 0) {
4084 create_room(roomname,
4085 ( (is_mailbox != NULL) ? 5 : 3 ),
4086 "", 0, 1, 0, VIEW_BBS);
4088 /* If the caller specified this object as unique, delete all
4089 * other objects of this type that are currently in the room.
4092 lprintf(CTDL_DEBUG, "Deleted %d other msgs of this type\n",
4093 CtdlDeleteMessages(roomname, NULL, 0, content_type)
4096 /* Now write the data */
4097 CtdlSubmitMsg(msg, NULL, roomname);
4098 CtdlFreeMessage(msg);
4106 void CtdlGetSysConfigBackend(long msgnum, void *userdata) {
4107 config_msgnum = msgnum;
4111 char *CtdlGetSysConfig(char *sysconfname) {
4112 char hold_rm[ROOMNAMELEN];
4115 struct CtdlMessage *msg;
4118 strcpy(hold_rm, CC->room.QRname);
4119 if (getroom(&CC->room, SYSCONFIGROOM) != 0) {
4120 getroom(&CC->room, hold_rm);
4125 /* We want the last (and probably only) config in this room */
4126 begin_critical_section(S_CONFIG);
4127 config_msgnum = (-1L);
4128 CtdlForEachMessage(MSGS_LAST, 1, NULL, sysconfname, NULL,
4129 CtdlGetSysConfigBackend, NULL);
4130 msgnum = config_msgnum;
4131 end_critical_section(S_CONFIG);
4137 msg = CtdlFetchMessage(msgnum, 1);
4139 conf = strdup(msg->cm_fields['M']);
4140 CtdlFreeMessage(msg);
4147 getroom(&CC->room, hold_rm);
4149 if (conf != NULL) do {
4150 extract_token(buf, conf, 0, '\n', sizeof buf);
4151 strcpy(conf, &conf[strlen(buf)+1]);
4152 } while ( (strlen(conf)>0) && (strlen(buf)>0) );
4157 void CtdlPutSysConfig(char *sysconfname, char *sysconfdata) {
4158 char temp[PATH_MAX];
4161 CtdlMakeTempFileName(temp, sizeof temp);
4163 fp = fopen(temp, "w");
4164 if (fp == NULL) return;
4165 fprintf(fp, "%s", sysconfdata);
4168 /* this handy API function does all the work for us */
4169 CtdlWriteObject(SYSCONFIGROOM, sysconfname, temp, NULL, 0, 1, 0);
4175 * Determine whether a given Internet address belongs to the current user
4177 int CtdlIsMe(char *addr, int addr_buf_len)
4179 struct recptypes *recp;
4182 recp = validate_recipients(addr);
4183 if (recp == NULL) return(0);
4185 if (recp->num_local == 0) {
4190 for (i=0; i<recp->num_local; ++i) {
4191 extract_token(addr, recp->recp_local, i, '|', addr_buf_len);
4192 if (!strcasecmp(addr, CC->user.fullname)) {
4204 * Citadel protocol command to do the same
4206 void cmd_isme(char *argbuf) {
4209 if (CtdlAccessCheck(ac_logged_in)) return;
4210 extract_token(addr, argbuf, 0, '|', sizeof addr);
4212 if (CtdlIsMe(addr, sizeof addr)) {
4213 cprintf("%d %s\n", CIT_OK, addr);
4216 cprintf("%d Not you.\n", ERROR + ILLEGAL_VALUE);