4 * Implements the message store.
14 #if TIME_WITH_SYS_TIME
15 # include <sys/time.h>
19 # include <sys/time.h>
34 #include "serv_extensions.h"
38 #include "sysdep_decls.h"
39 #include "citserver.h"
46 #include "mime_parser.h"
49 #include "internet_addressing.h"
50 #include "serv_fulltext.h"
52 #include "euidindex.h"
53 #include "journaling.h"
54 #include "citadel_dirs.h"
55 #include "serv_network.h"
58 # include "serv_sieve.h"
59 #endif /* HAVE_LIBSIEVE */
62 struct addresses_to_be_filed *atbf = NULL;
65 * This really belongs in serv_network.c, but I don't know how to export
66 * symbols between modules.
68 struct FilterList *filterlist = NULL;
72 * These are the four-character field headers we use when outputting
73 * messages in Citadel format (as opposed to RFC822 format).
76 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
77 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
78 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
79 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
80 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
81 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
82 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
83 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
111 * This function is self explanatory.
112 * (What can I say, I'm in a weird mood today...)
114 void remove_any_whitespace_to_the_left_or_right_of_at_symbol(char *name)
118 for (i = 0; i < strlen(name); ++i) {
119 if (name[i] == '@') {
120 while (isspace(name[i - 1]) && i > 0) {
121 strcpy(&name[i - 1], &name[i]);
124 while (isspace(name[i + 1])) {
125 strcpy(&name[i + 1], &name[i + 2]);
133 * Aliasing for network mail.
134 * (Error messages have been commented out, because this is a server.)
136 int alias(char *name)
137 { /* process alias and routing info for mail */
140 char aaa[SIZ], bbb[SIZ];
141 char *ignetcfg = NULL;
142 char *ignetmap = NULL;
149 remove_any_whitespace_to_the_left_or_right_of_at_symbol(name);
150 stripallbut(name, '<', '>');
152 fp = fopen(file_mail_aliases, "r");
154 fp = fopen("/dev/null", "r");
161 while (fgets(aaa, sizeof aaa, fp) != NULL) {
162 while (isspace(name[0]))
163 strcpy(name, &name[1]);
164 aaa[strlen(aaa) - 1] = 0;
166 for (a = 0; a < strlen(aaa); ++a) {
168 strcpy(bbb, &aaa[a + 1]);
172 if (!strcasecmp(name, aaa))
177 /* Hit the Global Address Book */
178 if (CtdlDirectoryLookup(aaa, name, sizeof aaa) == 0) {
182 lprintf(CTDL_INFO, "Mail is being forwarded to %s\n", name);
184 /* Change "user @ xxx" to "user" if xxx is an alias for this host */
185 for (a=0; a<strlen(name); ++a) {
186 if (name[a] == '@') {
187 if (CtdlHostAlias(&name[a+1]) == hostalias_localhost) {
189 lprintf(CTDL_INFO, "Changed to <%s>\n", name);
194 /* determine local or remote type, see citadel.h */
195 at = haschar(name, '@');
196 if (at == 0) return(MES_LOCAL); /* no @'s - local address */
197 if (at > 1) return(MES_ERROR); /* >1 @'s - invalid address */
198 remove_any_whitespace_to_the_left_or_right_of_at_symbol(name);
200 /* figure out the delivery mode */
201 extract_token(node, name, 1, '@', sizeof node);
203 /* If there are one or more dots in the nodename, we assume that it
204 * is an FQDN and will attempt SMTP delivery to the Internet.
206 if (haschar(node, '.') > 0) {
207 return(MES_INTERNET);
210 /* Otherwise we look in the IGnet maps for a valid Citadel node.
211 * Try directly-connected nodes first...
213 ignetcfg = CtdlGetSysConfig(IGNETCFG);
214 for (i=0; i<num_tokens(ignetcfg, '\n'); ++i) {
215 extract_token(buf, ignetcfg, i, '\n', sizeof buf);
216 extract_token(testnode, buf, 0, '|', sizeof testnode);
217 if (!strcasecmp(node, testnode)) {
225 * Then try nodes that are two or more hops away.
227 ignetmap = CtdlGetSysConfig(IGNETMAP);
228 for (i=0; i<num_tokens(ignetmap, '\n'); ++i) {
229 extract_token(buf, ignetmap, i, '\n', sizeof buf);
230 extract_token(testnode, buf, 0, '|', sizeof testnode);
231 if (!strcasecmp(node, testnode)) {
238 /* If we get to this point it's an invalid node name */
247 fp = fopen(file_citadel_control, "r");
249 lprintf(CTDL_CRIT, "Cannot open %s: %s\n",
250 file_citadel_control,
254 fread((char *) &CitControl, sizeof(struct CitControl), 1, fp);
260 * Back end for the MSGS command: output message number only.
262 void simple_listing(long msgnum, void *userdata)
264 cprintf("%ld\n", msgnum);
270 * Back end for the MSGS command: output header summary.
272 void headers_listing(long msgnum, void *userdata)
274 struct CtdlMessage *msg;
276 msg = CtdlFetchMessage(msgnum, 0);
278 cprintf("%ld|0|||||\n", msgnum);
282 cprintf("%ld|%s|%s|%s|%s|%s|\n",
284 (msg->cm_fields['T'] ? msg->cm_fields['T'] : "0"),
285 (msg->cm_fields['A'] ? msg->cm_fields['A'] : ""),
286 (msg->cm_fields['N'] ? msg->cm_fields['N'] : ""),
287 (msg->cm_fields['F'] ? msg->cm_fields['F'] : ""),
288 (msg->cm_fields['U'] ? msg->cm_fields['U'] : "")
290 CtdlFreeMessage(msg);
295 /* Determine if a given message matches the fields in a message template.
296 * Return 0 for a successful match.
298 int CtdlMsgCmp(struct CtdlMessage *msg, struct CtdlMessage *template) {
301 /* If there aren't any fields in the template, all messages will
304 if (template == NULL) return(0);
306 /* Null messages are bogus. */
307 if (msg == NULL) return(1);
309 for (i='A'; i<='Z'; ++i) {
310 if (template->cm_fields[i] != NULL) {
311 if (msg->cm_fields[i] == NULL) {
314 if (strcasecmp(msg->cm_fields[i],
315 template->cm_fields[i])) return 1;
319 /* All compares succeeded: we have a match! */
326 * Retrieve the "seen" message list for the current room.
328 void CtdlGetSeen(char *buf, int which_set) {
331 /* Learn about the user and room in question */
332 CtdlGetRelationship(&vbuf, &CC->user, &CC->room);
334 if (which_set == ctdlsetseen_seen)
335 safestrncpy(buf, vbuf.v_seen, SIZ);
336 if (which_set == ctdlsetseen_answered)
337 safestrncpy(buf, vbuf.v_answered, SIZ);
343 * Manipulate the "seen msgs" string (or other message set strings)
345 void CtdlSetSeen(long *target_msgnums, int num_target_msgnums,
346 int target_setting, int which_set,
347 struct ctdluser *which_user, struct ctdlroom *which_room) {
348 struct cdbdata *cdbfr;
360 char *is_set; /* actually an array of booleans */
363 char setstr[SIZ], lostr[SIZ], histr[SIZ];
366 lprintf(CTDL_DEBUG, "CtdlSetSeen(%d msgs starting with %ld, %d, %d)\n",
367 num_target_msgnums, target_msgnums[0],
368 target_setting, which_set);
370 /* Learn about the user and room in question */
371 CtdlGetRelationship(&vbuf,
372 ((which_user != NULL) ? which_user : &CC->user),
373 ((which_room != NULL) ? which_room : &CC->room)
376 /* Load the message list */
377 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long));
379 msglist = (long *) cdbfr->ptr;
380 cdbfr->ptr = NULL; /* CtdlSetSeen() now owns this memory */
381 num_msgs = cdbfr->len / sizeof(long);
384 return; /* No messages at all? No further action. */
387 is_set = malloc(num_msgs * sizeof(char));
388 memset(is_set, 0, (num_msgs * sizeof(char)) );
390 /* Decide which message set we're manipulating */
392 case ctdlsetseen_seen:
393 safestrncpy(vset, vbuf.v_seen, sizeof vset);
395 case ctdlsetseen_answered:
396 safestrncpy(vset, vbuf.v_answered, sizeof vset);
400 /* lprintf(CTDL_DEBUG, "before optimize: %s\n", vset); */
402 /* Translate the existing sequence set into an array of booleans */
403 num_sets = num_tokens(vset, ',');
404 for (s=0; s<num_sets; ++s) {
405 extract_token(setstr, vset, s, ',', sizeof setstr);
407 extract_token(lostr, setstr, 0, ':', sizeof lostr);
408 if (num_tokens(setstr, ':') >= 2) {
409 extract_token(histr, setstr, 1, ':', sizeof histr);
410 if (!strcmp(histr, "*")) {
411 snprintf(histr, sizeof histr, "%ld", LONG_MAX);
415 strcpy(histr, lostr);
420 for (i = 0; i < num_msgs; ++i) {
421 if ((msglist[i] >= lo) && (msglist[i] <= hi)) {
427 /* Now translate the array of booleans back into a sequence set */
432 for (i=0; i<num_msgs; ++i) {
434 is_seen = is_set[i]; /* Default to existing setting */
436 for (k=0; k<num_target_msgnums; ++k) {
437 if (msglist[i] == target_msgnums[k]) {
438 is_seen = target_setting;
443 if (lo < 0L) lo = msglist[i];
447 if ( ((is_seen == 0) && (was_seen == 1))
448 || ((is_seen == 1) && (i == num_msgs-1)) ) {
450 /* begin trim-o-matic code */
453 while ( (strlen(vset) + 20) > sizeof vset) {
454 remove_token(vset, 0, ',');
456 if (j--) break; /* loop no more than 9 times */
458 if ( (trimming) && (which_set == ctdlsetseen_seen) ) {
462 snprintf(lostr, sizeof lostr,
463 "1:%ld,%s", t, vset);
464 safestrncpy(vset, lostr, sizeof vset);
466 /* end trim-o-matic code */
474 snprintf(&vset[tmp], (sizeof vset) - tmp,
478 snprintf(&vset[tmp], (sizeof vset) - tmp,
487 /* Decide which message set we're manipulating */
489 case ctdlsetseen_seen:
490 safestrncpy(vbuf.v_seen, vset, sizeof vbuf.v_seen);
492 case ctdlsetseen_answered:
493 safestrncpy(vbuf.v_answered, vset,
494 sizeof vbuf.v_answered);
499 /* lprintf(CTDL_DEBUG, " after optimize: %s\n", vset); */
501 CtdlSetRelationship(&vbuf,
502 ((which_user != NULL) ? which_user : &CC->user),
503 ((which_room != NULL) ? which_room : &CC->room)
509 * API function to perform an operation for each qualifying message in the
510 * current room. (Returns the number of messages processed.)
512 int CtdlForEachMessage(int mode, long ref, char *search_string,
514 struct CtdlMessage *compare,
515 void (*CallBack) (long, void *),
521 struct cdbdata *cdbfr;
522 long *msglist = NULL;
524 int num_processed = 0;
527 struct CtdlMessage *msg;
530 int printed_lastold = 0;
531 int num_search_msgs = 0;
532 long *search_msgs = NULL;
534 /* Learn about the user and room in question */
536 getuser(&CC->user, CC->curr_user);
537 CtdlGetRelationship(&vbuf, &CC->user, &CC->room);
539 /* Load the message list */
540 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long));
542 msglist = (long *) cdbfr->ptr;
543 cdbfr->ptr = NULL; /* CtdlForEachMessage() now owns this memory */
544 num_msgs = cdbfr->len / sizeof(long);
547 return 0; /* No messages at all? No further action. */
552 * Now begin the traversal.
554 if (num_msgs > 0) for (a = 0; a < num_msgs; ++a) {
556 /* If the caller is looking for a specific MIME type, filter
557 * out all messages which are not of the type requested.
559 if (content_type != NULL) if (strlen(content_type) > 0) {
561 /* This call to GetMetaData() sits inside this loop
562 * so that we only do the extra database read per msg
563 * if we need to. Doing the extra read all the time
564 * really kills the server. If we ever need to use
565 * metadata for another search criterion, we need to
566 * move the read somewhere else -- but still be smart
567 * enough to only do the read if the caller has
568 * specified something that will need it.
570 GetMetaData(&smi, msglist[a]);
572 if (strcasecmp(smi.meta_content_type, content_type)) {
578 num_msgs = sort_msglist(msglist, num_msgs);
580 /* If a template was supplied, filter out the messages which
581 * don't match. (This could induce some delays!)
584 if (compare != NULL) {
585 for (a = 0; a < num_msgs; ++a) {
586 msg = CtdlFetchMessage(msglist[a], 1);
588 if (CtdlMsgCmp(msg, compare)) {
591 CtdlFreeMessage(msg);
597 /* If a search string was specified, get a message list from
598 * the full text index and remove messages which aren't on both
602 * Since the lists are sorted and strictly ascending, and the
603 * output list is guaranteed to be shorter than or equal to the
604 * input list, we overwrite the bottom of the input list. This
605 * eliminates the need to memmove big chunks of the list over and
608 if ( (num_msgs > 0) && (mode == MSGS_SEARCH) && (search_string) ) {
609 ft_search(&num_search_msgs, &search_msgs, search_string);
610 if (num_search_msgs > 0) {
614 orig_num_msgs = num_msgs;
616 for (i=0; i<orig_num_msgs; ++i) {
617 for (j=0; j<num_search_msgs; ++j) {
618 if (msglist[i] == search_msgs[j]) {
619 msglist[num_msgs++] = msglist[i];
625 num_msgs = 0; /* No messages qualify */
627 if (search_msgs != NULL) free(search_msgs);
629 /* Now that we've purged messages which don't contain the search
630 * string, treat a MSGS_SEARCH just like a MSGS_ALL from this
637 * Now iterate through the message list, according to the
638 * criteria supplied by the caller.
641 for (a = 0; a < num_msgs; ++a) {
642 thismsg = msglist[a];
643 if (mode == MSGS_ALL) {
647 is_seen = is_msg_in_sequence_set(
648 vbuf.v_seen, thismsg);
649 if (is_seen) lastold = thismsg;
655 || ((mode == MSGS_OLD) && (is_seen))
656 || ((mode == MSGS_NEW) && (!is_seen))
657 || ((mode == MSGS_LAST) && (a >= (num_msgs - ref)))
658 || ((mode == MSGS_FIRST) && (a < ref))
659 || ((mode == MSGS_GT) && (thismsg > ref))
660 || ((mode == MSGS_EQ) && (thismsg == ref))
663 if ((mode == MSGS_NEW) && (CC->user.flags & US_LASTOLD) && (lastold > 0L) && (printed_lastold == 0) && (!is_seen)) {
665 CallBack(lastold, userdata);
669 if (CallBack) CallBack(thismsg, userdata);
673 free(msglist); /* Clean up */
674 return num_processed;
680 * cmd_msgs() - get list of message #'s in this room
681 * implements the MSGS server command using CtdlForEachMessage()
683 void cmd_msgs(char *cmdbuf)
692 int with_template = 0;
693 struct CtdlMessage *template = NULL;
694 int with_headers = 0;
695 char search_string[1024];
697 extract_token(which, cmdbuf, 0, '|', sizeof which);
698 cm_ref = extract_int(cmdbuf, 1);
699 extract_token(search_string, cmdbuf, 1, '|', sizeof search_string);
700 with_template = extract_int(cmdbuf, 2);
701 with_headers = extract_int(cmdbuf, 3);
704 if (!strncasecmp(which, "OLD", 3))
706 else if (!strncasecmp(which, "NEW", 3))
708 else if (!strncasecmp(which, "FIRST", 5))
710 else if (!strncasecmp(which, "LAST", 4))
712 else if (!strncasecmp(which, "GT", 2))
714 else if (!strncasecmp(which, "SEARCH", 6))
719 if ((!(CC->logged_in)) && (!(CC->internal_pgm))) {
720 cprintf("%d not logged in\n", ERROR + NOT_LOGGED_IN);
724 if ( (mode == MSGS_SEARCH) && (!config.c_enable_fulltext) ) {
725 cprintf("%d Full text index is not enabled on this server.\n",
726 ERROR + CMD_NOT_SUPPORTED);
732 cprintf("%d Send template then receive message list\n",
734 template = (struct CtdlMessage *)
735 malloc(sizeof(struct CtdlMessage));
736 memset(template, 0, sizeof(struct CtdlMessage));
737 while(client_getln(buf, sizeof buf), strcmp(buf,"000")) {
738 extract_token(tfield, buf, 0, '|', sizeof tfield);
739 extract_token(tvalue, buf, 1, '|', sizeof tvalue);
740 for (i='A'; i<='Z'; ++i) if (msgkeys[i]!=NULL) {
741 if (!strcasecmp(tfield, msgkeys[i])) {
742 template->cm_fields[i] =
750 cprintf("%d \n", LISTING_FOLLOWS);
753 CtdlForEachMessage(mode,
754 ( (mode == MSGS_SEARCH) ? 0 : cm_ref ),
755 ( (mode == MSGS_SEARCH) ? search_string : NULL ),
758 (with_headers ? headers_listing : simple_listing),
761 if (template != NULL) CtdlFreeMessage(template);
769 * help_subst() - support routine for help file viewer
771 void help_subst(char *strbuf, char *source, char *dest)
776 while (p = pattern2(strbuf, source), (p >= 0)) {
777 strcpy(workbuf, &strbuf[p + strlen(source)]);
778 strcpy(&strbuf[p], dest);
779 strcat(strbuf, workbuf);
784 void do_help_subst(char *buffer)
788 help_subst(buffer, "^nodename", config.c_nodename);
789 help_subst(buffer, "^humannode", config.c_humannode);
790 help_subst(buffer, "^fqdn", config.c_fqdn);
791 help_subst(buffer, "^username", CC->user.fullname);
792 snprintf(buf2, sizeof buf2, "%ld", CC->user.usernum);
793 help_subst(buffer, "^usernum", buf2);
794 help_subst(buffer, "^sysadm", config.c_sysadm);
795 help_subst(buffer, "^variantname", CITADEL);
796 snprintf(buf2, sizeof buf2, "%d", config.c_maxsessions);
797 help_subst(buffer, "^maxsessions", buf2);
798 help_subst(buffer, "^bbsdir", ctdl_bbsbase_dir);
804 * memfmout() - Citadel text formatter and paginator.
805 * Although the original purpose of this routine was to format
806 * text to the reader's screen width, all we're really using it
807 * for here is to format text out to 80 columns before sending it
808 * to the client. The client software may reformat it again.
811 char *mptr, /* where are we going to get our text from? */
812 char subst, /* nonzero if we should do substitutions */
813 char *nl) /* string to terminate lines with */
821 static int width = 80;
826 c = 1; /* c is the current pos */
830 while (ch = *mptr, ((ch != 0) && (strlen(buffer) < 126))) {
832 buffer[strlen(buffer) + 1] = 0;
833 buffer[strlen(buffer)] = ch;
836 if (buffer[0] == '^')
837 do_help_subst(buffer);
839 buffer[strlen(buffer) + 1] = 0;
841 strcpy(buffer, &buffer[1]);
849 if (((ch == 13) || (ch == 10)) && (old != 13) && (old != 10)) {
852 if (((old == 13) || (old == 10)) && (isspace(real))) {
857 if (((strlen(aaa) + c) > (width - 5)) && (strlen(aaa) > (width - 5))) {
858 cprintf("%s%s", nl, aaa);
867 if ((strlen(aaa) + c) > (width - 5)) {
876 if ((ch == 13) || (ch == 10)) {
877 cprintf("%s%s", aaa, nl);
884 cprintf("%s%s", aaa, nl);
890 * Callback function for mime parser that simply lists the part
892 void list_this_part(char *name, char *filename, char *partnum, char *disp,
893 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
898 ma = (struct ma_info *)cbuserdata;
899 if (ma->is_ma == 0) {
900 cprintf("part=%s|%s|%s|%s|%s|%ld\n",
901 name, filename, partnum, disp, cbtype, (long)length);
906 * Callback function for multipart prefix
908 void list_this_pref(char *name, char *filename, char *partnum, char *disp,
909 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
914 ma = (struct ma_info *)cbuserdata;
915 if (!strcasecmp(cbtype, "multipart/alternative")) {
919 if (ma->is_ma == 0) {
920 cprintf("pref=%s|%s\n", partnum, cbtype);
925 * Callback function for multipart sufffix
927 void list_this_suff(char *name, char *filename, char *partnum, char *disp,
928 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
933 ma = (struct ma_info *)cbuserdata;
934 if (ma->is_ma == 0) {
935 cprintf("suff=%s|%s\n", partnum, cbtype);
937 if (!strcasecmp(cbtype, "multipart/alternative")) {
944 * Callback function for mime parser that opens a section for downloading
946 void mime_download(char *name, char *filename, char *partnum, char *disp,
947 void *content, char *cbtype, char *cbcharset, size_t length,
948 char *encoding, void *cbuserdata)
951 /* Silently go away if there's already a download open... */
952 if (CC->download_fp != NULL)
955 /* ...or if this is not the desired section */
956 if (strcasecmp(CC->download_desired_section, partnum))
959 CC->download_fp = tmpfile();
960 if (CC->download_fp == NULL)
963 fwrite(content, length, 1, CC->download_fp);
964 fflush(CC->download_fp);
965 rewind(CC->download_fp);
967 OpenCmdResult(filename, cbtype);
973 * Load a message from disk into memory.
974 * This is used by CtdlOutputMsg() and other fetch functions.
976 * NOTE: Caller is responsible for freeing the returned CtdlMessage struct
977 * using the CtdlMessageFree() function.
979 struct CtdlMessage *CtdlFetchMessage(long msgnum, int with_body)
981 struct cdbdata *dmsgtext;
982 struct CtdlMessage *ret = NULL;
986 cit_uint8_t field_header;
988 lprintf(CTDL_DEBUG, "CtdlFetchMessage(%ld, %d)\n", msgnum, with_body);
990 dmsgtext = cdb_fetch(CDB_MSGMAIN, &msgnum, sizeof(long));
991 if (dmsgtext == NULL) {
994 mptr = dmsgtext->ptr;
995 upper_bound = mptr + dmsgtext->len;
997 /* Parse the three bytes that begin EVERY message on disk.
998 * The first is always 0xFF, the on-disk magic number.
999 * The second is the anonymous/public type byte.
1000 * The third is the format type byte (vari, fixed, or MIME).
1005 "Message %ld appears to be corrupted.\n",
1010 ret = (struct CtdlMessage *) malloc(sizeof(struct CtdlMessage));
1011 memset(ret, 0, sizeof(struct CtdlMessage));
1013 ret->cm_magic = CTDLMESSAGE_MAGIC;
1014 ret->cm_anon_type = *mptr++; /* Anon type byte */
1015 ret->cm_format_type = *mptr++; /* Format type byte */
1018 * The rest is zero or more arbitrary fields. Load them in.
1019 * We're done when we encounter either a zero-length field or
1020 * have just processed the 'M' (message text) field.
1023 if (mptr >= upper_bound) {
1026 field_header = *mptr++;
1027 ret->cm_fields[field_header] = strdup(mptr);
1029 while (*mptr++ != 0); /* advance to next field */
1031 } while ((mptr < upper_bound) && (field_header != 'M'));
1035 /* Always make sure there's something in the msg text field. If
1036 * it's NULL, the message text is most likely stored separately,
1037 * so go ahead and fetch that. Failing that, just set a dummy
1038 * body so other code doesn't barf.
1040 if ( (ret->cm_fields['M'] == NULL) && (with_body) ) {
1041 dmsgtext = cdb_fetch(CDB_BIGMSGS, &msgnum, sizeof(long));
1042 if (dmsgtext != NULL) {
1043 ret->cm_fields['M'] = strdup(dmsgtext->ptr);
1047 if (ret->cm_fields['M'] == NULL) {
1048 ret->cm_fields['M'] = strdup("\r\n\r\n (no text)\r\n");
1051 /* Perform "before read" hooks (aborting if any return nonzero) */
1052 if (PerformMessageHooks(ret, EVT_BEFOREREAD) > 0) {
1053 CtdlFreeMessage(ret);
1062 * Returns 1 if the supplied pointer points to a valid Citadel message.
1063 * If the pointer is NULL or the magic number check fails, returns 0.
1065 int is_valid_message(struct CtdlMessage *msg) {
1068 if ((msg->cm_magic) != CTDLMESSAGE_MAGIC) {
1069 lprintf(CTDL_WARNING, "is_valid_message() -- self-check failed\n");
1077 * 'Destructor' for struct CtdlMessage
1079 void CtdlFreeMessage(struct CtdlMessage *msg)
1083 if (is_valid_message(msg) == 0) return;
1085 for (i = 0; i < 256; ++i)
1086 if (msg->cm_fields[i] != NULL) {
1087 free(msg->cm_fields[i]);
1090 msg->cm_magic = 0; /* just in case */
1096 * Pre callback function for multipart/alternative
1098 * NOTE: this differs from the standard behavior for a reason. Normally when
1099 * displaying multipart/alternative you want to show the _last_ usable
1100 * format in the message. Here we show the _first_ one, because it's
1101 * usually text/plain. Since this set of functions is designed for text
1102 * output to non-MIME-aware clients, this is the desired behavior.
1105 void fixed_output_pre(char *name, char *filename, char *partnum, char *disp,
1106 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
1111 ma = (struct ma_info *)cbuserdata;
1112 lprintf(CTDL_DEBUG, "fixed_output_pre() type=<%s>\n", cbtype);
1113 if (!strcasecmp(cbtype, "multipart/alternative")) {
1117 if (!strcasecmp(cbtype, "message/rfc822")) {
1123 * Post callback function for multipart/alternative
1125 void fixed_output_post(char *name, char *filename, char *partnum, char *disp,
1126 void *content, char *cbtype, char *cbcharset, size_t length,
1127 char *encoding, void *cbuserdata)
1131 ma = (struct ma_info *)cbuserdata;
1132 lprintf(CTDL_DEBUG, "fixed_output_post() type=<%s>\n", cbtype);
1133 if (!strcasecmp(cbtype, "multipart/alternative")) {
1137 if (!strcasecmp(cbtype, "message/rfc822")) {
1143 * Inline callback function for mime parser that wants to display text
1145 void fixed_output(char *name, char *filename, char *partnum, char *disp,
1146 void *content, char *cbtype, char *cbcharset, size_t length,
1147 char *encoding, void *cbuserdata)
1154 ma = (struct ma_info *)cbuserdata;
1157 "fixed_output() part %s: %s (%s) (%ld bytes)\n",
1158 partnum, filename, cbtype, (long)length);
1161 * If we're in the middle of a multipart/alternative scope and
1162 * we've already printed another section, skip this one.
1164 if ( (ma->is_ma) && (ma->did_print) ) {
1165 lprintf(CTDL_DEBUG, "Skipping part %s (%s)\n",
1171 if ( (!strcasecmp(cbtype, "text/plain"))
1172 || (strlen(cbtype)==0) ) {
1175 client_write(wptr, length);
1176 if (wptr[length-1] != '\n') {
1183 if (!strcasecmp(cbtype, "text/html")) {
1184 ptr = html_to_ascii(content, length, 80, 0);
1186 client_write(ptr, wlen);
1187 if (ptr[wlen-1] != '\n') {
1194 if (ma->use_fo_hooks) {
1195 if (PerformFixedOutputHooks(cbtype, content, length)) {
1196 /* above function returns nonzero if it handled the part */
1201 if (strncasecmp(cbtype, "multipart/", 10)) {
1202 cprintf("Part %s: %s (%s) (%ld bytes)\r\n",
1203 partnum, filename, cbtype, (long)length);
1209 * The client is elegant and sophisticated and wants to be choosy about
1210 * MIME content types, so figure out which multipart/alternative part
1211 * we're going to send.
1213 * We use a system of weights. When we find a part that matches one of the
1214 * MIME types we've declared as preferential, we can store it in ma->chosen_part
1215 * and then set ma->chosen_pref to that MIME type's position in our preference
1216 * list. If we then hit another match, we only replace the first match if
1217 * the preference value is lower.
1219 void choose_preferred(char *name, char *filename, char *partnum, char *disp,
1220 void *content, char *cbtype, char *cbcharset, size_t length,
1221 char *encoding, void *cbuserdata)
1227 ma = (struct ma_info *)cbuserdata;
1229 if (ma->is_ma > 0) {
1230 for (i=0; i<num_tokens(CC->preferred_formats, '|'); ++i) {
1231 extract_token(buf, CC->preferred_formats, i, '|', sizeof buf);
1232 lprintf(CTDL_DEBUG, "Is <%s> == <%s> ??\n", buf, cbtype);
1233 if ( (!strcasecmp(buf, cbtype)) && (!ma->freeze) ) {
1234 if (i < ma->chosen_pref) {
1235 safestrncpy(ma->chosen_part, partnum, sizeof ma->chosen_part);
1236 ma->chosen_pref = i;
1244 * Now that we've chosen our preferred part, output it.
1246 void output_preferred(char *name, char *filename, char *partnum, char *disp,
1247 void *content, char *cbtype, char *cbcharset, size_t length,
1248 char *encoding, void *cbuserdata)
1252 int add_newline = 0;
1256 ma = (struct ma_info *)cbuserdata;
1258 /* This is not the MIME part you're looking for... */
1259 if (strcasecmp(partnum, ma->chosen_part)) return;
1261 /* If the content-type of this part is in our preferred formats
1262 * list, we can simply output it verbatim.
1264 for (i=0; i<num_tokens(CC->preferred_formats, '|'); ++i) {
1265 extract_token(buf, CC->preferred_formats, i, '|', sizeof buf);
1266 if (!strcasecmp(buf, cbtype)) {
1267 /* Yeah! Go! W00t!! */
1269 text_content = (char *)content;
1270 if (text_content[length-1] != '\n') {
1274 cprintf("Content-type: %s", cbtype);
1275 if (strlen(cbcharset) > 0) {
1276 cprintf("; charset=%s", cbcharset);
1278 cprintf("\nContent-length: %d\n",
1279 (int)(length + add_newline) );
1280 if (strlen(encoding) > 0) {
1281 cprintf("Content-transfer-encoding: %s\n", encoding);
1284 cprintf("Content-transfer-encoding: 7bit\n");
1287 client_write(content, length);
1288 if (add_newline) cprintf("\n");
1293 /* No translations required or possible: output as text/plain */
1294 cprintf("Content-type: text/plain\n\n");
1295 fixed_output(name, filename, partnum, disp, content, cbtype, cbcharset,
1296 length, encoding, cbuserdata);
1301 char desired_section[64];
1308 * Callback function for
1310 void extract_encapsulated_message(char *name, char *filename, char *partnum, char *disp,
1311 void *content, char *cbtype, char *cbcharset, size_t length,
1312 char *encoding, void *cbuserdata)
1314 struct encapmsg *encap;
1316 encap = (struct encapmsg *)cbuserdata;
1318 /* Only proceed if this is the desired section... */
1319 if (!strcasecmp(encap->desired_section, partnum)) {
1320 encap->msglen = length;
1321 encap->msg = malloc(length + 2);
1322 memcpy(encap->msg, content, length);
1332 * Get a message off disk. (returns om_* values found in msgbase.h)
1335 int CtdlOutputMsg(long msg_num, /* message number (local) to fetch */
1336 int mode, /* how would you like that message? */
1337 int headers_only, /* eschew the message body? */
1338 int do_proto, /* do Citadel protocol responses? */
1339 int crlf, /* Use CRLF newlines instead of LF? */
1340 char *section /* NULL or a message/rfc822 section */
1342 struct CtdlMessage *TheMessage = NULL;
1343 int retcode = om_no_such_msg;
1344 struct encapmsg encap;
1346 lprintf(CTDL_DEBUG, "CtdlOutputMsg() msgnum=%ld, mode=%d, section=%s\n",
1348 (section ? section : "<>")
1351 if ((!(CC->logged_in)) && (!(CC->internal_pgm))) {
1352 if (do_proto) cprintf("%d Not logged in.\n",
1353 ERROR + NOT_LOGGED_IN);
1354 return(om_not_logged_in);
1357 /* FIXME: check message id against msglist for this room */
1360 * Fetch the message from disk. If we're in any sort of headers
1361 * only mode, request that we don't even bother loading the body
1364 if ( (headers_only == HEADERS_FAST) || (headers_only == HEADERS_ONLY) ) {
1365 TheMessage = CtdlFetchMessage(msg_num, 0);
1368 TheMessage = CtdlFetchMessage(msg_num, 1);
1371 if (TheMessage == NULL) {
1372 if (do_proto) cprintf("%d Can't locate msg %ld on disk\n",
1373 ERROR + MESSAGE_NOT_FOUND, msg_num);
1374 return(om_no_such_msg);
1377 /* Here is the weird form of this command, to process only an
1378 * encapsulated message/rfc822 section.
1380 if (section) if (strlen(section)>0) if (strcmp(section, "0")) {
1381 memset(&encap, 0, sizeof encap);
1382 safestrncpy(encap.desired_section, section, sizeof encap.desired_section);
1383 mime_parser(TheMessage->cm_fields['M'],
1385 *extract_encapsulated_message,
1386 NULL, NULL, (void *)&encap, 0
1388 CtdlFreeMessage(TheMessage);
1392 encap.msg[encap.msglen] = 0;
1393 TheMessage = convert_internet_message(encap.msg);
1394 encap.msg = NULL; /* no free() here, TheMessage owns it now */
1396 /* Now we let it fall through to the bottom of this
1397 * function, because TheMessage now contains the
1398 * encapsulated message instead of the top-level
1399 * message. Isn't that neat?
1404 if (do_proto) cprintf("%d msg %ld has no part %s\n",
1405 ERROR + MESSAGE_NOT_FOUND, msg_num, section);
1406 retcode = om_no_such_msg;
1411 /* Ok, output the message now */
1412 retcode = CtdlOutputPreLoadedMsg(
1414 headers_only, do_proto, crlf);
1415 CtdlFreeMessage(TheMessage);
1422 * Get a message off disk. (returns om_* values found in msgbase.h)
1425 int CtdlOutputPreLoadedMsg(
1426 struct CtdlMessage *TheMessage,
1427 int mode, /* how would you like that message? */
1428 int headers_only, /* eschew the message body? */
1429 int do_proto, /* do Citadel protocol responses? */
1430 int crlf /* Use CRLF newlines instead of LF? */
1436 char display_name[256];
1438 char *nl; /* newline string */
1440 int subject_found = 0;
1443 /* Buffers needed for RFC822 translation. These are all filled
1444 * using functions that are bounds-checked, and therefore we can
1445 * make them substantially smaller than SIZ.
1453 char datestamp[100];
1455 lprintf(CTDL_DEBUG, "CtdlOutputPreLoadedMsg(TheMessage=%s, %d, %d, %d, %d\n",
1456 ((TheMessage == NULL) ? "NULL" : "not null"),
1457 mode, headers_only, do_proto, crlf);
1459 strcpy(mid, "unknown");
1460 nl = (crlf ? "\r\n" : "\n");
1462 if (!is_valid_message(TheMessage)) {
1464 "ERROR: invalid preloaded message for output\n");
1465 return(om_no_such_msg);
1468 /* Are we downloading a MIME component? */
1469 if (mode == MT_DOWNLOAD) {
1470 if (TheMessage->cm_format_type != FMT_RFC822) {
1472 cprintf("%d This is not a MIME message.\n",
1473 ERROR + ILLEGAL_VALUE);
1474 } else if (CC->download_fp != NULL) {
1475 if (do_proto) cprintf(
1476 "%d You already have a download open.\n",
1477 ERROR + RESOURCE_BUSY);
1479 /* Parse the message text component */
1480 mptr = TheMessage->cm_fields['M'];
1481 mime_parser(mptr, NULL, *mime_download, NULL, NULL, NULL, 0);
1482 /* If there's no file open by this time, the requested
1483 * section wasn't found, so print an error
1485 if (CC->download_fp == NULL) {
1486 if (do_proto) cprintf(
1487 "%d Section %s not found.\n",
1488 ERROR + FILE_NOT_FOUND,
1489 CC->download_desired_section);
1492 return((CC->download_fp != NULL) ? om_ok : om_mime_error);
1495 /* now for the user-mode message reading loops */
1496 if (do_proto) cprintf("%d msg:\n", LISTING_FOLLOWS);
1498 /* Does the caller want to skip the headers? */
1499 if (headers_only == HEADERS_NONE) goto START_TEXT;
1501 /* Tell the client which format type we're using. */
1502 if ( (mode == MT_CITADEL) && (do_proto) ) {
1503 cprintf("type=%d\n", TheMessage->cm_format_type);
1506 /* nhdr=yes means that we're only displaying headers, no body */
1507 if ( (TheMessage->cm_anon_type == MES_ANONONLY)
1508 && (mode == MT_CITADEL)
1511 cprintf("nhdr=yes\n");
1514 /* begin header processing loop for Citadel message format */
1516 if ((mode == MT_CITADEL) || (mode == MT_MIME)) {
1518 safestrncpy(display_name, "<unknown>", sizeof display_name);
1519 if (TheMessage->cm_fields['A']) {
1520 strcpy(buf, TheMessage->cm_fields['A']);
1521 if (TheMessage->cm_anon_type == MES_ANONONLY) {
1522 safestrncpy(display_name, "****", sizeof display_name);
1524 else if (TheMessage->cm_anon_type == MES_ANONOPT) {
1525 safestrncpy(display_name, "anonymous", sizeof display_name);
1528 safestrncpy(display_name, buf, sizeof display_name);
1530 if ((is_room_aide())
1531 && ((TheMessage->cm_anon_type == MES_ANONONLY)
1532 || (TheMessage->cm_anon_type == MES_ANONOPT))) {
1533 size_t tmp = strlen(display_name);
1534 snprintf(&display_name[tmp],
1535 sizeof display_name - tmp,
1540 /* Don't show Internet address for users on the
1541 * local Citadel network.
1544 if (TheMessage->cm_fields['N'] != NULL)
1545 if (strlen(TheMessage->cm_fields['N']) > 0)
1546 if (haschar(TheMessage->cm_fields['N'], '.') == 0) {
1550 /* Now spew the header fields in the order we like them. */
1551 safestrncpy(allkeys, FORDER, sizeof allkeys);
1552 for (i=0; i<strlen(allkeys); ++i) {
1553 k = (int) allkeys[i];
1555 if ( (TheMessage->cm_fields[k] != NULL)
1556 && (msgkeys[k] != NULL) ) {
1558 if (do_proto) cprintf("%s=%s\n",
1562 else if ((k == 'F') && (suppress_f)) {
1565 /* Masquerade display name if needed */
1567 if (do_proto) cprintf("%s=%s\n",
1569 TheMessage->cm_fields[k]
1578 /* begin header processing loop for RFC822 transfer format */
1583 strcpy(snode, NODENAME);
1584 strcpy(lnode, HUMANNODE);
1585 if (mode == MT_RFC822) {
1586 for (i = 0; i < 256; ++i) {
1587 if (TheMessage->cm_fields[i]) {
1588 mptr = TheMessage->cm_fields[i];
1591 safestrncpy(luser, mptr, sizeof luser);
1592 safestrncpy(suser, mptr, sizeof suser);
1594 else if (i == 'Y') {
1595 cprintf("CC: %s%s", mptr, nl);
1597 else if (i == 'P') {
1598 cprintf("Return-Path: %s%s", mptr, nl);
1600 else if (i == 'V') {
1601 cprintf("Envelope-To: %s%s", mptr, nl);
1603 else if (i == 'U') {
1604 cprintf("Subject: %s%s", mptr, nl);
1608 safestrncpy(mid, mptr, sizeof mid);
1610 safestrncpy(lnode, mptr, sizeof lnode);
1612 safestrncpy(fuser, mptr, sizeof fuser);
1613 /* else if (i == 'O')
1614 cprintf("X-Citadel-Room: %s%s",
1617 safestrncpy(snode, mptr, sizeof snode);
1619 cprintf("To: %s%s", mptr, nl);
1620 else if (i == 'T') {
1621 datestring(datestamp, sizeof datestamp,
1622 atol(mptr), DATESTRING_RFC822);
1623 cprintf("Date: %s%s", datestamp, nl);
1627 if (subject_found == 0) {
1628 cprintf("Subject: (no subject)%s", nl);
1632 for (i=0; i<strlen(suser); ++i) {
1633 suser[i] = tolower(suser[i]);
1634 if (!isalnum(suser[i])) suser[i]='_';
1637 if (mode == MT_RFC822) {
1638 if (!strcasecmp(snode, NODENAME)) {
1639 safestrncpy(snode, FQDN, sizeof snode);
1642 /* Construct a fun message id */
1643 cprintf("Message-ID: <%s", mid);
1644 if (strchr(mid, '@')==NULL) {
1645 cprintf("@%s", snode);
1649 if (!is_room_aide() && (TheMessage->cm_anon_type == MES_ANONONLY)) {
1650 cprintf("From: \"----\" <x@x.org>%s", nl);
1652 else if (!is_room_aide() && (TheMessage->cm_anon_type == MES_ANONOPT)) {
1653 cprintf("From: \"anonymous\" <x@x.org>%s", nl);
1655 else if (strlen(fuser) > 0) {
1656 cprintf("From: \"%s\" <%s>%s", luser, fuser, nl);
1659 cprintf("From: \"%s\" <%s@%s>%s", luser, suser, snode, nl);
1662 cprintf("Organization: %s%s", lnode, nl);
1664 /* Blank line signifying RFC822 end-of-headers */
1665 if (TheMessage->cm_format_type != FMT_RFC822) {
1670 /* end header processing loop ... at this point, we're in the text */
1672 if (headers_only == HEADERS_FAST) goto DONE;
1673 mptr = TheMessage->cm_fields['M'];
1675 /* Tell the client about the MIME parts in this message */
1676 if (TheMessage->cm_format_type == FMT_RFC822) {
1677 if ( (mode == MT_CITADEL) || (mode == MT_MIME) ) {
1678 memset(&ma, 0, sizeof(struct ma_info));
1679 mime_parser(mptr, NULL,
1680 (do_proto ? *list_this_part : NULL),
1681 (do_proto ? *list_this_pref : NULL),
1682 (do_proto ? *list_this_suff : NULL),
1685 else if (mode == MT_RFC822) { /* unparsed RFC822 dump */
1686 char *start_of_text = NULL;
1687 start_of_text = strstr(mptr, "\n\r\n");
1688 if (start_of_text == NULL) start_of_text = strstr(mptr, "\n\n");
1689 if (start_of_text == NULL) start_of_text = mptr;
1691 start_of_text = strstr(start_of_text, "\n");
1693 while (ch=*mptr, ch!=0) {
1697 else switch(headers_only) {
1699 if (mptr >= start_of_text) {
1700 if (ch == 10) cprintf("%s", nl);
1701 else cprintf("%c", ch);
1705 if (mptr < start_of_text) {
1706 if (ch == 10) cprintf("%s", nl);
1707 else cprintf("%c", ch);
1711 if (ch == 10) cprintf("%s", nl);
1712 else cprintf("%c", ch);
1721 if (headers_only == HEADERS_ONLY) {
1725 /* signify start of msg text */
1726 if ( (mode == MT_CITADEL) || (mode == MT_MIME) ) {
1727 if (do_proto) cprintf("text\n");
1730 /* If the format type on disk is 1 (fixed-format), then we want
1731 * everything to be output completely literally ... regardless of
1732 * what message transfer format is in use.
1734 if (TheMessage->cm_format_type == FMT_FIXED) {
1735 if (mode == MT_MIME) {
1736 cprintf("Content-type: text/plain\n\n");
1739 while (ch = *mptr++, ch > 0) {
1742 if ((ch == 10) || (strlen(buf) > 250)) {
1743 cprintf("%s%s", buf, nl);
1746 buf[strlen(buf) + 1] = 0;
1747 buf[strlen(buf)] = ch;
1750 if (strlen(buf) > 0)
1751 cprintf("%s%s", buf, nl);
1754 /* If the message on disk is format 0 (Citadel vari-format), we
1755 * output using the formatter at 80 columns. This is the final output
1756 * form if the transfer format is RFC822, but if the transfer format
1757 * is Citadel proprietary, it'll still work, because the indentation
1758 * for new paragraphs is correct and the client will reformat the
1759 * message to the reader's screen width.
1761 if (TheMessage->cm_format_type == FMT_CITADEL) {
1762 if (mode == MT_MIME) {
1763 cprintf("Content-type: text/x-citadel-variformat\n\n");
1765 memfmout(mptr, 0, nl);
1768 /* If the message on disk is format 4 (MIME), we've gotta hand it
1769 * off to the MIME parser. The client has already been told that
1770 * this message is format 1 (fixed format), so the callback function
1771 * we use will display those parts as-is.
1773 if (TheMessage->cm_format_type == FMT_RFC822) {
1774 memset(&ma, 0, sizeof(struct ma_info));
1776 if (mode == MT_MIME) {
1777 ma.use_fo_hooks = 0;
1778 strcpy(ma.chosen_part, "1");
1779 ma.chosen_pref = 9999;
1780 mime_parser(mptr, NULL,
1781 *choose_preferred, *fixed_output_pre,
1782 *fixed_output_post, (void *)&ma, 0);
1783 mime_parser(mptr, NULL,
1784 *output_preferred, NULL, NULL, (void *)&ma, 0);
1787 ma.use_fo_hooks = 1;
1788 mime_parser(mptr, NULL,
1789 *fixed_output, *fixed_output_pre,
1790 *fixed_output_post, (void *)&ma, 0);
1795 DONE: /* now we're done */
1796 if (do_proto) cprintf("000\n");
1803 * display a message (mode 0 - Citadel proprietary)
1805 void cmd_msg0(char *cmdbuf)
1808 int headers_only = HEADERS_ALL;
1810 msgid = extract_long(cmdbuf, 0);
1811 headers_only = extract_int(cmdbuf, 1);
1813 CtdlOutputMsg(msgid, MT_CITADEL, headers_only, 1, 0, NULL);
1819 * display a message (mode 2 - RFC822)
1821 void cmd_msg2(char *cmdbuf)
1824 int headers_only = HEADERS_ALL;
1826 msgid = extract_long(cmdbuf, 0);
1827 headers_only = extract_int(cmdbuf, 1);
1829 CtdlOutputMsg(msgid, MT_RFC822, headers_only, 1, 1, NULL);
1835 * display a message (mode 3 - IGnet raw format - internal programs only)
1837 void cmd_msg3(char *cmdbuf)
1840 struct CtdlMessage *msg;
1843 if (CC->internal_pgm == 0) {
1844 cprintf("%d This command is for internal programs only.\n",
1845 ERROR + HIGHER_ACCESS_REQUIRED);
1849 msgnum = extract_long(cmdbuf, 0);
1850 msg = CtdlFetchMessage(msgnum, 1);
1852 cprintf("%d Message %ld not found.\n",
1853 ERROR + MESSAGE_NOT_FOUND, msgnum);
1857 serialize_message(&smr, msg);
1858 CtdlFreeMessage(msg);
1861 cprintf("%d Unable to serialize message\n",
1862 ERROR + INTERNAL_ERROR);
1866 cprintf("%d %ld\n", BINARY_FOLLOWS, (long)smr.len);
1867 client_write((char *)smr.ser, (int)smr.len);
1874 * Display a message using MIME content types
1876 void cmd_msg4(char *cmdbuf)
1881 msgid = extract_long(cmdbuf, 0);
1882 extract_token(section, cmdbuf, 1, '|', sizeof section);
1883 CtdlOutputMsg(msgid, MT_MIME, 0, 1, 0, (section[0] ? section : NULL) );
1889 * Client tells us its preferred message format(s)
1891 void cmd_msgp(char *cmdbuf)
1893 safestrncpy(CC->preferred_formats, cmdbuf,
1894 sizeof(CC->preferred_formats));
1895 cprintf("%d ok\n", CIT_OK);
1900 * Open a component of a MIME message as a download file
1902 void cmd_opna(char *cmdbuf)
1905 char desired_section[128];
1907 msgid = extract_long(cmdbuf, 0);
1908 extract_token(desired_section, cmdbuf, 1, '|', sizeof desired_section);
1909 safestrncpy(CC->download_desired_section, desired_section,
1910 sizeof CC->download_desired_section);
1911 CtdlOutputMsg(msgid, MT_DOWNLOAD, 0, 1, 1, NULL);
1915 * Save one or more message pointers into a specified room
1916 * (Returns 0 for success, nonzero for failure)
1917 * roomname may be NULL to use the current room
1919 * Note that the 'supplied_msg' field may be set to NULL, in which case
1920 * the message will be fetched from disk, by number, if we need to perform
1921 * replication checks. This adds an additional database read, so if the
1922 * caller already has the message in memory then it should be supplied. (Obviously
1923 * this mode of operation only works if we're saving a single message.)
1925 int CtdlSaveMsgPointersInRoom(char *roomname, long newmsgidlist[], int num_newmsgs,
1926 int do_repl_check, struct CtdlMessage *supplied_msg)
1929 char hold_rm[ROOMNAMELEN];
1930 struct cdbdata *cdbfr;
1933 long highest_msg = 0L;
1936 struct CtdlMessage *msg = NULL;
1938 long *msgs_to_be_merged = NULL;
1939 int num_msgs_to_be_merged = 0;
1942 "CtdlSaveMsgPointersInRoom(room=%s, num_msgs=%d, repl=%d)\n",
1943 roomname, num_newmsgs, do_repl_check);
1945 strcpy(hold_rm, CC->room.QRname);
1948 if (newmsgidlist == NULL) return(ERROR + INTERNAL_ERROR);
1949 if (num_newmsgs < 1) return(ERROR + INTERNAL_ERROR);
1950 if (num_newmsgs > 1) supplied_msg = NULL;
1952 /* Now the regular stuff */
1953 if (lgetroom(&CC->room,
1954 ((roomname != NULL) ? roomname : CC->room.QRname) )
1956 lprintf(CTDL_ERR, "No such room <%s>\n", roomname);
1957 return(ERROR + ROOM_NOT_FOUND);
1961 msgs_to_be_merged = malloc(sizeof(long) * num_newmsgs);
1962 num_msgs_to_be_merged = 0;
1965 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long));
1966 if (cdbfr == NULL) {
1970 msglist = (long *) cdbfr->ptr;
1971 cdbfr->ptr = NULL; /* CtdlSaveMsgPointerInRoom() now owns this memory */
1972 num_msgs = cdbfr->len / sizeof(long);
1977 /* Create a list of msgid's which were supplied by the caller, but do
1978 * not already exist in the target room. It is absolutely taboo to
1979 * have more than one reference to the same message in a room.
1981 for (i=0; i<num_newmsgs; ++i) {
1983 if (num_msgs > 0) for (j=0; j<num_msgs; ++j) {
1984 if (msglist[j] == newmsgidlist[i]) {
1989 msgs_to_be_merged[num_msgs_to_be_merged++] = newmsgidlist[i];
1993 lprintf(9, "%d unique messages to be merged\n", num_msgs_to_be_merged);
1996 * Now merge the new messages
1998 msglist = realloc(msglist, (sizeof(long) * (num_msgs + num_msgs_to_be_merged)) );
1999 if (msglist == NULL) {
2000 lprintf(CTDL_ALERT, "ERROR: can't realloc message list!\n");
2002 memcpy(&msglist[num_msgs], msgs_to_be_merged, (sizeof(long) * num_msgs_to_be_merged) );
2003 num_msgs += num_msgs_to_be_merged;
2005 /* Sort the message list, so all the msgid's are in order */
2006 num_msgs = sort_msglist(msglist, num_msgs);
2008 /* Determine the highest message number */
2009 highest_msg = msglist[num_msgs - 1];
2011 /* Write it back to disk. */
2012 cdb_store(CDB_MSGLISTS, &CC->room.QRnumber, (int)sizeof(long),
2013 msglist, (int)(num_msgs * sizeof(long)));
2015 /* Free up the memory we used. */
2018 /* Update the highest-message pointer and unlock the room. */
2019 CC->room.QRhighest = highest_msg;
2020 lputroom(&CC->room);
2022 /* Perform replication checks if necessary */
2023 if ( (DoesThisRoomNeedEuidIndexing(&CC->room)) && (do_repl_check) ) {
2024 lprintf(CTDL_DEBUG, "CtdlSaveMsgPointerInRoom() doing repl checks\n");
2026 for (i=0; i<num_msgs_to_be_merged; ++i) {
2027 msgid = msgs_to_be_merged[i];
2029 if (supplied_msg != NULL) {
2033 msg = CtdlFetchMessage(msgid, 0);
2037 ReplicationChecks(msg);
2039 /* If the message has an Exclusive ID, index that... */
2040 if (msg->cm_fields['E'] != NULL) {
2041 index_message_by_euid(msg->cm_fields['E'], &CC->room, msgid);
2044 /* Free up the memory we may have allocated */
2045 if (msg != supplied_msg) {
2046 CtdlFreeMessage(msg);
2054 lprintf(CTDL_DEBUG, "CtdlSaveMsgPointerInRoom() skips repl checks\n");
2057 /* Submit this room for net processing */
2058 network_queue_room(&CC->room, NULL);
2060 #ifdef HAVE_LIBSIEVE
2061 /* If this is someone's inbox, submit the room for sieve processing */
2062 if (!strcasecmp(&CC->room.QRname[11], MAILROOM)) {
2063 sieve_queue_room(&CC->room);
2065 #endif /* HAVE_LIBSIEVE */
2067 /* Go back to the room we were in before we wandered here... */
2068 getroom(&CC->room, hold_rm);
2070 /* Bump the reference count for all messages which were merged */
2071 for (i=0; i<num_msgs_to_be_merged; ++i) {
2072 AdjRefCount(msgs_to_be_merged[i], +1);
2075 /* Free up memory... */
2076 if (msgs_to_be_merged != NULL) {
2077 free(msgs_to_be_merged);
2080 /* Return success. */
2086 * This is the same as CtdlSaveMsgPointersInRoom() but it only accepts
2089 int CtdlSaveMsgPointerInRoom(char *roomname, long msgid,
2090 int do_repl_check, struct CtdlMessage *supplied_msg)
2092 return CtdlSaveMsgPointersInRoom(roomname, &msgid, 1, do_repl_check, supplied_msg);
2099 * Message base operation to save a new message to the message store
2100 * (returns new message number)
2102 * This is the back end for CtdlSubmitMsg() and should not be directly
2103 * called by server-side modules.
2106 long send_message(struct CtdlMessage *msg) {
2114 /* Get a new message number */
2115 newmsgid = get_new_message_number();
2116 snprintf(msgidbuf, sizeof msgidbuf, "%010ld@%s", newmsgid, config.c_fqdn);
2118 /* Generate an ID if we don't have one already */
2119 if (msg->cm_fields['I']==NULL) {
2120 msg->cm_fields['I'] = strdup(msgidbuf);
2123 /* If the message is big, set its body aside for storage elsewhere */
2124 if (msg->cm_fields['M'] != NULL) {
2125 if (strlen(msg->cm_fields['M']) > BIGMSG) {
2127 holdM = msg->cm_fields['M'];
2128 msg->cm_fields['M'] = NULL;
2132 /* Serialize our data structure for storage in the database */
2133 serialize_message(&smr, msg);
2136 msg->cm_fields['M'] = holdM;
2140 cprintf("%d Unable to serialize message\n",
2141 ERROR + INTERNAL_ERROR);
2145 /* Write our little bundle of joy into the message base */
2146 if (cdb_store(CDB_MSGMAIN, &newmsgid, (int)sizeof(long),
2147 smr.ser, smr.len) < 0) {
2148 lprintf(CTDL_ERR, "Can't store message\n");
2152 cdb_store(CDB_BIGMSGS,
2162 /* Free the memory we used for the serialized message */
2165 /* Return the *local* message ID to the caller
2166 * (even if we're storing an incoming network message)
2174 * Serialize a struct CtdlMessage into the format used on disk and network.
2176 * This function loads up a "struct ser_ret" (defined in server.h) which
2177 * contains the length of the serialized message and a pointer to the
2178 * serialized message in memory. THE LATTER MUST BE FREED BY THE CALLER.
2180 void serialize_message(struct ser_ret *ret, /* return values */
2181 struct CtdlMessage *msg) /* unserialized msg */
2183 size_t wlen, fieldlen;
2185 static char *forder = FORDER;
2188 * Check for valid message format
2190 if (is_valid_message(msg) == 0) {
2191 lprintf(CTDL_ERR, "serialize_message() aborting due to invalid message\n");
2198 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL)
2199 ret->len = ret->len +
2200 strlen(msg->cm_fields[(int)forder[i]]) + 2;
2202 ret->ser = malloc(ret->len);
2203 if (ret->ser == NULL) {
2204 lprintf(CTDL_ERR, "serialize_message() malloc(%ld) failed: %s\n",
2205 (long)ret->len, strerror(errno));
2212 ret->ser[1] = msg->cm_anon_type;
2213 ret->ser[2] = msg->cm_format_type;
2216 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL) {
2217 fieldlen = strlen(msg->cm_fields[(int)forder[i]]);
2218 ret->ser[wlen++] = (char)forder[i];
2219 safestrncpy((char *)&ret->ser[wlen], msg->cm_fields[(int)forder[i]], fieldlen+1);
2220 wlen = wlen + fieldlen + 1;
2222 if (ret->len != wlen) lprintf(CTDL_ERR, "ERROR: len=%ld wlen=%ld\n",
2223 (long)ret->len, (long)wlen);
2231 * Check to see if any messages already exist in the current room which
2232 * carry the same Exclusive ID as this one. If any are found, delete them.
2234 void ReplicationChecks(struct CtdlMessage *msg) {
2235 long old_msgnum = (-1L);
2237 if (DoesThisRoomNeedEuidIndexing(&CC->room) == 0) return;
2239 lprintf(CTDL_DEBUG, "Performing replication checks in <%s>\n",
2242 /* No exclusive id? Don't do anything. */
2243 if (msg == NULL) return;
2244 if (msg->cm_fields['E'] == NULL) return;
2245 if (strlen(msg->cm_fields['E']) == 0) return;
2246 /*lprintf(CTDL_DEBUG, "Exclusive ID: <%s> for room <%s>\n",
2247 msg->cm_fields['E'], CC->room.QRname);*/
2249 old_msgnum = locate_message_by_euid(msg->cm_fields['E'], &CC->room);
2250 if (old_msgnum > 0L) {
2251 lprintf(CTDL_DEBUG, "ReplicationChecks() replacing message %ld\n", old_msgnum);
2252 CtdlDeleteMessages(CC->room.QRname, &old_msgnum, 1, "", 0);
2259 * Save a message to disk and submit it into the delivery system.
2261 long CtdlSubmitMsg(struct CtdlMessage *msg, /* message to save */
2262 struct recptypes *recps, /* recipients (if mail) */
2263 char *force /* force a particular room? */
2265 char submit_filename[128];
2266 char generated_timestamp[32];
2267 char hold_rm[ROOMNAMELEN];
2268 char actual_rm[ROOMNAMELEN];
2269 char force_room[ROOMNAMELEN];
2270 char content_type[SIZ]; /* We have to learn this */
2271 char recipient[SIZ];
2274 struct ctdluser userbuf;
2276 struct MetaData smi;
2277 FILE *network_fp = NULL;
2278 static int seqnum = 1;
2279 struct CtdlMessage *imsg = NULL;
2282 char *hold_R, *hold_D;
2283 char *collected_addresses = NULL;
2284 struct addresses_to_be_filed *aptr = NULL;
2285 char *saved_rfc822_version = NULL;
2286 int qualified_for_journaling = 0;
2288 lprintf(CTDL_DEBUG, "CtdlSubmitMsg() called\n");
2289 if (is_valid_message(msg) == 0) return(-1); /* self check */
2291 /* If this message has no timestamp, we take the liberty of
2292 * giving it one, right now.
2294 if (msg->cm_fields['T'] == NULL) {
2295 snprintf(generated_timestamp, sizeof generated_timestamp, "%ld", (long)time(NULL));
2296 msg->cm_fields['T'] = strdup(generated_timestamp);
2299 /* If this message has no path, we generate one.
2301 if (msg->cm_fields['P'] == NULL) {
2302 if (msg->cm_fields['A'] != NULL) {
2303 msg->cm_fields['P'] = strdup(msg->cm_fields['A']);
2304 for (a=0; a<strlen(msg->cm_fields['P']); ++a) {
2305 if (isspace(msg->cm_fields['P'][a])) {
2306 msg->cm_fields['P'][a] = ' ';
2311 msg->cm_fields['P'] = strdup("unknown");
2315 if (force == NULL) {
2316 strcpy(force_room, "");
2319 strcpy(force_room, force);
2322 /* Learn about what's inside, because it's what's inside that counts */
2323 if (msg->cm_fields['M'] == NULL) {
2324 lprintf(CTDL_ERR, "ERROR: attempt to save message with NULL body\n");
2328 switch (msg->cm_format_type) {
2330 strcpy(content_type, "text/x-citadel-variformat");
2333 strcpy(content_type, "text/plain");
2336 strcpy(content_type, "text/plain");
2337 mptr = bmstrcasestr(msg->cm_fields['M'], "Content-type: ");
2339 safestrncpy(content_type, &mptr[14],
2340 sizeof content_type);
2341 for (a = 0; a < strlen(content_type); ++a) {
2342 if ((content_type[a] == ';')
2343 || (content_type[a] == ' ')
2344 || (content_type[a] == 13)
2345 || (content_type[a] == 10)) {
2346 content_type[a] = 0;
2352 /* Goto the correct room */
2353 lprintf(CTDL_DEBUG, "Selected room %s\n", (recps) ? CC->room.QRname : SENTITEMS);
2354 strcpy(hold_rm, CC->room.QRname);
2355 strcpy(actual_rm, CC->room.QRname);
2356 if (recps != NULL) {
2357 strcpy(actual_rm, SENTITEMS);
2360 /* If the user is a twit, move to the twit room for posting */
2362 if (CC->user.axlevel == 2) {
2363 strcpy(hold_rm, actual_rm);
2364 strcpy(actual_rm, config.c_twitroom);
2365 lprintf(CTDL_DEBUG, "Diverting to twit room\n");
2369 /* ...or if this message is destined for Aide> then go there. */
2370 if (strlen(force_room) > 0) {
2371 strcpy(actual_rm, force_room);
2374 lprintf(CTDL_DEBUG, "Final selection: %s\n", actual_rm);
2375 if (strcasecmp(actual_rm, CC->room.QRname)) {
2376 /* getroom(&CC->room, actual_rm); */
2377 usergoto(actual_rm, 0, 1, NULL, NULL);
2381 * If this message has no O (room) field, generate one.
2383 if (msg->cm_fields['O'] == NULL) {
2384 msg->cm_fields['O'] = strdup(CC->room.QRname);
2387 /* Perform "before save" hooks (aborting if any return nonzero) */
2388 lprintf(CTDL_DEBUG, "Performing before-save hooks\n");
2389 if (PerformMessageHooks(msg, EVT_BEFORESAVE) > 0) return(-3);
2392 * If this message has an Exclusive ID, and the room is replication
2393 * checking enabled, then do replication checks.
2395 if (DoesThisRoomNeedEuidIndexing(&CC->room)) {
2396 ReplicationChecks(msg);
2399 /* Save it to disk */
2400 lprintf(CTDL_DEBUG, "Saving to disk\n");
2401 newmsgid = send_message(msg);
2402 if (newmsgid <= 0L) return(-5);
2404 /* Write a supplemental message info record. This doesn't have to
2405 * be a critical section because nobody else knows about this message
2408 lprintf(CTDL_DEBUG, "Creating MetaData record\n");
2409 memset(&smi, 0, sizeof(struct MetaData));
2410 smi.meta_msgnum = newmsgid;
2411 smi.meta_refcount = 0;
2412 safestrncpy(smi.meta_content_type, content_type,
2413 sizeof smi.meta_content_type);
2416 * Measure how big this message will be when rendered as RFC822.
2417 * We do this for two reasons:
2418 * 1. We need the RFC822 length for the new metadata record, so the
2419 * POP and IMAP services don't have to calculate message lengths
2420 * while the user is waiting (multiplied by potentially hundreds
2421 * or thousands of messages).
2422 * 2. If journaling is enabled, we will need an RFC822 version of the
2423 * message to attach to the journalized copy.
2425 if (CC->redirect_buffer != NULL) {
2426 lprintf(CTDL_ALERT, "CC->redirect_buffer is not NULL during message submission!\n");
2429 CC->redirect_buffer = malloc(SIZ);
2430 CC->redirect_len = 0;
2431 CC->redirect_alloc = SIZ;
2432 CtdlOutputPreLoadedMsg(msg, MT_RFC822, HEADERS_ALL, 0, 1);
2433 smi.meta_rfc822_length = CC->redirect_len;
2434 saved_rfc822_version = CC->redirect_buffer;
2435 CC->redirect_buffer = NULL;
2436 CC->redirect_len = 0;
2437 CC->redirect_alloc = 0;
2441 /* Now figure out where to store the pointers */
2442 lprintf(CTDL_DEBUG, "Storing pointers\n");
2444 /* If this is being done by the networker delivering a private
2445 * message, we want to BYPASS saving the sender's copy (because there
2446 * is no local sender; it would otherwise go to the Trashcan).
2448 if ((!CC->internal_pgm) || (recps == NULL)) {
2449 if (CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 1, msg) != 0) {
2450 lprintf(CTDL_ERR, "ERROR saving message pointer!\n");
2451 CtdlSaveMsgPointerInRoom(config.c_aideroom, newmsgid, 0, msg);
2455 /* For internet mail, drop a copy in the outbound queue room */
2457 if (recps->num_internet > 0) {
2458 CtdlSaveMsgPointerInRoom(SMTP_SPOOLOUT_ROOM, newmsgid, 0, msg);
2461 /* If other rooms are specified, drop them there too. */
2463 if (recps->num_room > 0)
2464 for (i=0; i<num_tokens(recps->recp_room, '|'); ++i) {
2465 extract_token(recipient, recps->recp_room, i,
2466 '|', sizeof recipient);
2467 lprintf(CTDL_DEBUG, "Delivering to room <%s>\n", recipient);
2468 CtdlSaveMsgPointerInRoom(recipient, newmsgid, 0, msg);
2471 /* Bump this user's messages posted counter. */
2472 lprintf(CTDL_DEBUG, "Updating user\n");
2473 lgetuser(&CC->user, CC->curr_user);
2474 CC->user.posted = CC->user.posted + 1;
2475 lputuser(&CC->user);
2477 /* If this is private, local mail, make a copy in the
2478 * recipient's mailbox and bump the reference count.
2481 if (recps->num_local > 0)
2482 for (i=0; i<num_tokens(recps->recp_local, '|'); ++i) {
2483 extract_token(recipient, recps->recp_local, i,
2484 '|', sizeof recipient);
2485 lprintf(CTDL_DEBUG, "Delivering private local mail to <%s>\n",
2487 if (getuser(&userbuf, recipient) == 0) {
2488 MailboxName(actual_rm, sizeof actual_rm,
2489 &userbuf, MAILROOM);
2490 CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 0, msg);
2491 BumpNewMailCounter(userbuf.usernum);
2494 lprintf(CTDL_DEBUG, "No user <%s>\n", recipient);
2495 CtdlSaveMsgPointerInRoom(config.c_aideroom,
2500 /* Perform "after save" hooks */
2501 lprintf(CTDL_DEBUG, "Performing after-save hooks\n");
2502 PerformMessageHooks(msg, EVT_AFTERSAVE);
2504 /* For IGnet mail, we have to save a new copy into the spooler for
2505 * each recipient, with the R and D fields set to the recipient and
2506 * destination-node. This has two ugly side effects: all other
2507 * recipients end up being unlisted in this recipient's copy of the
2508 * message, and it has to deliver multiple messages to the same
2509 * node. We'll revisit this again in a year or so when everyone has
2510 * a network spool receiver that can handle the new style messages.
2513 if (recps->num_ignet > 0)
2514 for (i=0; i<num_tokens(recps->recp_ignet, '|'); ++i) {
2515 extract_token(recipient, recps->recp_ignet, i,
2516 '|', sizeof recipient);
2518 hold_R = msg->cm_fields['R'];
2519 hold_D = msg->cm_fields['D'];
2520 msg->cm_fields['R'] = malloc(SIZ);
2521 msg->cm_fields['D'] = malloc(128);
2522 extract_token(msg->cm_fields['R'], recipient, 0, '@', SIZ);
2523 extract_token(msg->cm_fields['D'], recipient, 1, '@', 128);
2525 serialize_message(&smr, msg);
2527 snprintf(submit_filename, sizeof submit_filename,
2528 "%s/netmail.%04lx.%04x.%04x",
2530 (long) getpid(), CC->cs_pid, ++seqnum);
2531 network_fp = fopen(submit_filename, "wb+");
2532 if (network_fp != NULL) {
2533 fwrite(smr.ser, smr.len, 1, network_fp);
2539 free(msg->cm_fields['R']);
2540 free(msg->cm_fields['D']);
2541 msg->cm_fields['R'] = hold_R;
2542 msg->cm_fields['D'] = hold_D;
2545 /* Go back to the room we started from */
2546 lprintf(CTDL_DEBUG, "Returning to original room %s\n", hold_rm);
2547 if (strcasecmp(hold_rm, CC->room.QRname))
2548 /* getroom(&CC->room, hold_rm); */
2549 usergoto(hold_rm, 0, 1, NULL, NULL);
2551 /* For internet mail, generate delivery instructions.
2552 * Yes, this is recursive. Deal with it. Infinite recursion does
2553 * not happen because the delivery instructions message does not
2554 * contain a recipient.
2557 if (recps->num_internet > 0) {
2558 lprintf(CTDL_DEBUG, "Generating delivery instructions\n");
2559 instr = malloc(SIZ * 2);
2560 snprintf(instr, SIZ * 2,
2561 "Content-type: %s\n\nmsgid|%ld\nsubmitted|%ld\n"
2563 SPOOLMIME, newmsgid, (long)time(NULL),
2564 msg->cm_fields['A'], msg->cm_fields['N']
2567 for (i=0; i<num_tokens(recps->recp_internet, '|'); ++i) {
2568 size_t tmp = strlen(instr);
2569 extract_token(recipient, recps->recp_internet,
2570 i, '|', sizeof recipient);
2571 snprintf(&instr[tmp], SIZ * 2 - tmp,
2572 "remote|%s|0||\n", recipient);
2575 imsg = malloc(sizeof(struct CtdlMessage));
2576 memset(imsg, 0, sizeof(struct CtdlMessage));
2577 imsg->cm_magic = CTDLMESSAGE_MAGIC;
2578 imsg->cm_anon_type = MES_NORMAL;
2579 imsg->cm_format_type = FMT_RFC822;
2580 imsg->cm_fields['A'] = strdup("Citadel");
2581 imsg->cm_fields['J'] = strdup("do not journal");
2582 imsg->cm_fields['M'] = instr;
2583 CtdlSubmitMsg(imsg, NULL, SMTP_SPOOLOUT_ROOM);
2584 CtdlFreeMessage(imsg);
2588 * Any addresses to harvest for someone's address book?
2590 if ( (CC->logged_in) && (recps != NULL) ) {
2591 collected_addresses = harvest_collected_addresses(msg);
2594 if (collected_addresses != NULL) {
2595 begin_critical_section(S_ATBF);
2596 aptr = (struct addresses_to_be_filed *)
2597 malloc(sizeof(struct addresses_to_be_filed));
2599 MailboxName(actual_rm, sizeof actual_rm,
2600 &CC->user, USERCONTACTSROOM);
2601 aptr->roomname = strdup(actual_rm);
2602 aptr->collected_addresses = collected_addresses;
2604 end_critical_section(S_ATBF);
2608 * Determine whether this message qualifies for journaling.
2610 if (msg->cm_fields['J'] != NULL) {
2611 qualified_for_journaling = 0;
2614 if (recps == NULL) {
2615 qualified_for_journaling = config.c_journal_pubmsgs;
2617 else if (recps->num_local + recps->num_ignet + recps->num_internet > 0) {
2618 qualified_for_journaling = config.c_journal_email;
2621 qualified_for_journaling = config.c_journal_pubmsgs;
2626 * Do we have to perform journaling? If so, hand off the saved
2627 * RFC822 version will be handed off to the journaler for background
2628 * submit. Otherwise, we have to free the memory ourselves.
2630 if (saved_rfc822_version != NULL) {
2631 if (qualified_for_journaling) {
2632 JournalBackgroundSubmit(msg, saved_rfc822_version, recps);
2635 free(saved_rfc822_version);
2648 * Convenience function for generating small administrative messages.
2650 void quickie_message(char *from, char *fromaddr, char *to, char *room, char *text,
2651 int format_type, char *subject)
2653 struct CtdlMessage *msg;
2654 struct recptypes *recp = NULL;
2656 msg = malloc(sizeof(struct CtdlMessage));
2657 memset(msg, 0, sizeof(struct CtdlMessage));
2658 msg->cm_magic = CTDLMESSAGE_MAGIC;
2659 msg->cm_anon_type = MES_NORMAL;
2660 msg->cm_format_type = format_type;
2663 msg->cm_fields['A'] = strdup(from);
2665 else if (fromaddr != NULL) {
2666 msg->cm_fields['A'] = strdup(fromaddr);
2667 if (strchr(msg->cm_fields['A'], '@')) {
2668 *strchr(msg->cm_fields['A'], '@') = 0;
2672 msg->cm_fields['A'] = strdup("Citadel");
2675 if (fromaddr != NULL) msg->cm_fields['F'] = strdup(fromaddr);
2676 if (room != NULL) msg->cm_fields['O'] = strdup(room);
2677 msg->cm_fields['N'] = strdup(NODENAME);
2679 msg->cm_fields['R'] = strdup(to);
2680 recp = validate_recipients(to);
2682 if (subject != NULL) {
2683 msg->cm_fields['U'] = strdup(subject);
2685 msg->cm_fields['M'] = strdup(text);
2687 CtdlSubmitMsg(msg, recp, room);
2688 CtdlFreeMessage(msg);
2689 if (recp != NULL) free(recp);
2695 * Back end function used by CtdlMakeMessage() and similar functions
2697 char *CtdlReadMessageBody(char *terminator, /* token signalling EOT */
2698 size_t maxlen, /* maximum message length */
2699 char *exist, /* if non-null, append to it;
2700 exist is ALWAYS freed */
2701 int crlf /* CRLF newlines instead of LF */
2705 size_t message_len = 0;
2706 size_t buffer_len = 0;
2713 if (exist == NULL) {
2720 message_len = strlen(exist);
2721 buffer_len = message_len + 4096;
2722 m = realloc(exist, buffer_len);
2729 /* Do we need to change leading ".." to "." for SMTP escaping? */
2730 if (!strcmp(terminator, ".")) {
2734 /* flush the input if we have nowhere to store it */
2739 /* read in the lines of message text one by one */
2741 if (client_getln(buf, (sizeof buf - 3)) < 1) finished = 1;
2742 if (!strcmp(buf, terminator)) finished = 1;
2744 strcat(buf, "\r\n");
2750 /* Unescape SMTP-style input of two dots at the beginning of the line */
2752 if (!strncmp(buf, "..", 2)) {
2753 strcpy(buf, &buf[1]);
2757 if ( (!flushing) && (!finished) ) {
2758 /* Measure the line */
2759 linelen = strlen(buf);
2761 /* augment the buffer if we have to */
2762 if ((message_len + linelen) >= buffer_len) {
2763 ptr = realloc(m, (buffer_len * 2) );
2764 if (ptr == NULL) { /* flush if can't allocate */
2767 buffer_len = (buffer_len * 2);
2769 lprintf(CTDL_DEBUG, "buffer_len is now %ld\n", (long)buffer_len);
2773 /* Add the new line to the buffer. NOTE: this loop must avoid
2774 * using functions like strcat() and strlen() because they
2775 * traverse the entire buffer upon every call, and doing that
2776 * for a multi-megabyte message slows it down beyond usability.
2778 strcpy(&m[message_len], buf);
2779 message_len += linelen;
2782 /* if we've hit the max msg length, flush the rest */
2783 if (message_len >= maxlen) flushing = 1;
2785 } while (!finished);
2793 * Build a binary message to be saved on disk.
2794 * (NOTE: if you supply 'preformatted_text', the buffer you give it
2795 * will become part of the message. This means you are no longer
2796 * responsible for managing that memory -- it will be freed along with
2797 * the rest of the fields when CtdlFreeMessage() is called.)
2800 struct CtdlMessage *CtdlMakeMessage(
2801 struct ctdluser *author, /* author's user structure */
2802 char *recipient, /* NULL if it's not mail */
2803 char *recp_cc, /* NULL if it's not mail */
2804 char *room, /* room where it's going */
2805 int type, /* see MES_ types in header file */
2806 int format_type, /* variformat, plain text, MIME... */
2807 char *fake_name, /* who we're masquerading as */
2808 char *subject, /* Subject (optional) */
2809 char *supplied_euid, /* ...or NULL if this is irrelevant */
2810 char *preformatted_text /* ...or NULL to read text from client */
2812 char dest_node[SIZ];
2814 struct CtdlMessage *msg;
2816 msg = malloc(sizeof(struct CtdlMessage));
2817 memset(msg, 0, sizeof(struct CtdlMessage));
2818 msg->cm_magic = CTDLMESSAGE_MAGIC;
2819 msg->cm_anon_type = type;
2820 msg->cm_format_type = format_type;
2822 /* Don't confuse the poor folks if it's not routed mail. */
2823 strcpy(dest_node, "");
2828 snprintf(buf, sizeof buf, "cit%ld", author->usernum); /* Path */
2829 msg->cm_fields['P'] = strdup(buf);
2831 snprintf(buf, sizeof buf, "%ld", (long)time(NULL)); /* timestamp */
2832 msg->cm_fields['T'] = strdup(buf);
2834 if (fake_name[0]) /* author */
2835 msg->cm_fields['A'] = strdup(fake_name);
2837 msg->cm_fields['A'] = strdup(author->fullname);
2839 if (CC->room.QRflags & QR_MAILBOX) { /* room */
2840 msg->cm_fields['O'] = strdup(&CC->room.QRname[11]);
2843 msg->cm_fields['O'] = strdup(CC->room.QRname);
2846 msg->cm_fields['N'] = strdup(NODENAME); /* nodename */
2847 msg->cm_fields['H'] = strdup(HUMANNODE); /* hnodename */
2849 if (recipient[0] != 0) {
2850 msg->cm_fields['R'] = strdup(recipient);
2852 if (recp_cc[0] != 0) {
2853 msg->cm_fields['Y'] = strdup(recp_cc);
2855 if (dest_node[0] != 0) {
2856 msg->cm_fields['D'] = strdup(dest_node);
2859 if ( (author == &CC->user) && (strlen(CC->cs_inet_email) > 0) ) {
2860 msg->cm_fields['F'] = strdup(CC->cs_inet_email);
2863 if (subject != NULL) {
2865 if (strlen(subject) > 0) {
2866 msg->cm_fields['U'] = strdup(subject);
2870 if (supplied_euid != NULL) {
2871 msg->cm_fields['E'] = strdup(supplied_euid);
2874 if (preformatted_text != NULL) {
2875 msg->cm_fields['M'] = preformatted_text;
2878 msg->cm_fields['M'] = CtdlReadMessageBody("000",
2879 config.c_maxmsglen, NULL, 0);
2887 * Check to see whether we have permission to post a message in the current
2888 * room. Returns a *CITADEL ERROR CODE* and puts a message in errmsgbuf, or
2889 * returns 0 on success.
2891 int CtdlDoIHavePermissionToPostInThisRoom(char *errmsgbuf, size_t n) {
2893 if (!(CC->logged_in)) {
2894 snprintf(errmsgbuf, n, "Not logged in.");
2895 return (ERROR + NOT_LOGGED_IN);
2898 if ((CC->user.axlevel < 2)
2899 && ((CC->room.QRflags & QR_MAILBOX) == 0)) {
2900 snprintf(errmsgbuf, n, "Need to be validated to enter "
2901 "(except in %s> to sysop)", MAILROOM);
2902 return (ERROR + HIGHER_ACCESS_REQUIRED);
2905 if ((CC->user.axlevel < 4)
2906 && (CC->room.QRflags & QR_NETWORK)) {
2907 snprintf(errmsgbuf, n, "Need net privileges to enter here.");
2908 return (ERROR + HIGHER_ACCESS_REQUIRED);
2911 if ((CC->user.axlevel < 6)
2912 && (CC->room.QRflags & QR_READONLY)) {
2913 snprintf(errmsgbuf, n, "Sorry, this is a read-only room.");
2914 return (ERROR + HIGHER_ACCESS_REQUIRED);
2917 strcpy(errmsgbuf, "Ok");
2923 * Check to see if the specified user has Internet mail permission
2924 * (returns nonzero if permission is granted)
2926 int CtdlCheckInternetMailPermission(struct ctdluser *who) {
2928 /* Do not allow twits to send Internet mail */
2929 if (who->axlevel <= 2) return(0);
2931 /* Globally enabled? */
2932 if (config.c_restrict == 0) return(1);
2934 /* User flagged ok? */
2935 if (who->flags & US_INTERNET) return(2);
2937 /* Aide level access? */
2938 if (who->axlevel >= 6) return(3);
2940 /* No mail for you! */
2946 * Validate recipients, count delivery types and errors, and handle aliasing
2947 * FIXME check for dupes!!!!!
2948 * Returns 0 if all addresses are ok, -1 if no addresses were specified,
2949 * or the number of addresses found invalid.
2951 struct recptypes *validate_recipients(char *supplied_recipients) {
2952 struct recptypes *ret;
2953 char recipients[SIZ];
2954 char this_recp[256];
2955 char this_recp_cooked[256];
2961 struct ctdluser tempUS;
2962 struct ctdlroom tempQR;
2966 ret = (struct recptypes *) malloc(sizeof(struct recptypes));
2967 if (ret == NULL) return(NULL);
2968 memset(ret, 0, sizeof(struct recptypes));
2971 ret->num_internet = 0;
2976 if (supplied_recipients == NULL) {
2977 strcpy(recipients, "");
2980 safestrncpy(recipients, supplied_recipients, sizeof recipients);
2983 /* Change all valid separator characters to commas */
2984 for (i=0; i<strlen(recipients); ++i) {
2985 if ((recipients[i] == ';') || (recipients[i] == '|')) {
2986 recipients[i] = ',';
2990 /* Now start extracting recipients... */
2992 while (strlen(recipients) > 0) {
2994 for (i=0; i<=strlen(recipients); ++i) {
2995 if (recipients[i] == '\"') in_quotes = 1 - in_quotes;
2996 if ( ( (recipients[i] == ',') && (!in_quotes) ) || (recipients[i] == 0) ) {
2997 safestrncpy(this_recp, recipients, i+1);
2999 if (recipients[i] == ',') {
3000 strcpy(recipients, &recipients[i+1]);
3003 strcpy(recipients, "");
3010 lprintf(CTDL_DEBUG, "Evaluating recipient #%d: %s\n", num_recps, this_recp);
3012 mailtype = alias(this_recp);
3013 mailtype = alias(this_recp);
3014 mailtype = alias(this_recp);
3015 for (j=0; j<=strlen(this_recp); ++j) {
3016 if (this_recp[j]=='_') {
3017 this_recp_cooked[j] = ' ';
3020 this_recp_cooked[j] = this_recp[j];
3026 if (!strcasecmp(this_recp, "sysop")) {
3028 strcpy(this_recp, config.c_aideroom);
3029 if (strlen(ret->recp_room) > 0) {
3030 strcat(ret->recp_room, "|");
3032 strcat(ret->recp_room, this_recp);
3034 else if (getuser(&tempUS, this_recp) == 0) {
3036 strcpy(this_recp, tempUS.fullname);
3037 if (strlen(ret->recp_local) > 0) {
3038 strcat(ret->recp_local, "|");
3040 strcat(ret->recp_local, this_recp);
3042 else if (getuser(&tempUS, this_recp_cooked) == 0) {
3044 strcpy(this_recp, tempUS.fullname);
3045 if (strlen(ret->recp_local) > 0) {
3046 strcat(ret->recp_local, "|");
3048 strcat(ret->recp_local, this_recp);
3050 else if ( (!strncasecmp(this_recp, "room_", 5))
3051 && (!getroom(&tempQR, &this_recp_cooked[5])) ) {
3053 if (strlen(ret->recp_room) > 0) {
3054 strcat(ret->recp_room, "|");
3056 strcat(ret->recp_room, &this_recp_cooked[5]);
3064 /* Yes, you're reading this correctly: if the target
3065 * domain points back to the local system or an attached
3066 * Citadel directory, the address is invalid. That's
3067 * because if the address were valid, we would have
3068 * already translated it to a local address by now.
3070 if (IsDirectory(this_recp)) {
3075 ++ret->num_internet;
3076 if (strlen(ret->recp_internet) > 0) {
3077 strcat(ret->recp_internet, "|");
3079 strcat(ret->recp_internet, this_recp);
3084 if (strlen(ret->recp_ignet) > 0) {
3085 strcat(ret->recp_ignet, "|");
3087 strcat(ret->recp_ignet, this_recp);
3095 if (strlen(ret->errormsg) == 0) {
3096 snprintf(append, sizeof append,
3097 "Invalid recipient: %s",
3101 snprintf(append, sizeof append,
3104 if ( (strlen(ret->errormsg) + strlen(append)) < SIZ) {
3105 strcat(ret->errormsg, append);
3109 if (strlen(ret->display_recp) == 0) {
3110 strcpy(append, this_recp);
3113 snprintf(append, sizeof append, ", %s",
3116 if ( (strlen(ret->display_recp)+strlen(append)) < SIZ) {
3117 strcat(ret->display_recp, append);
3122 if ((ret->num_local + ret->num_internet + ret->num_ignet +
3123 ret->num_room + ret->num_error) == 0) {
3124 ret->num_error = (-1);
3125 strcpy(ret->errormsg, "No recipients specified.");
3128 lprintf(CTDL_DEBUG, "validate_recipients()\n");
3129 lprintf(CTDL_DEBUG, " local: %d <%s>\n", ret->num_local, ret->recp_local);
3130 lprintf(CTDL_DEBUG, " room: %d <%s>\n", ret->num_room, ret->recp_room);
3131 lprintf(CTDL_DEBUG, " inet: %d <%s>\n", ret->num_internet, ret->recp_internet);
3132 lprintf(CTDL_DEBUG, " ignet: %d <%s>\n", ret->num_ignet, ret->recp_ignet);
3133 lprintf(CTDL_DEBUG, " error: %d <%s>\n", ret->num_error, ret->errormsg);
3141 * message entry - mode 0 (normal)
3143 void cmd_ent0(char *entargs)
3149 char supplied_euid[128];
3150 char masquerade_as[SIZ];
3152 int format_type = 0;
3153 char newusername[SIZ];
3154 struct CtdlMessage *msg;
3158 struct recptypes *valid = NULL;
3159 struct recptypes *valid_to = NULL;
3160 struct recptypes *valid_cc = NULL;
3161 struct recptypes *valid_bcc = NULL;
3168 post = extract_int(entargs, 0);
3169 extract_token(recp, entargs, 1, '|', sizeof recp);
3170 anon_flag = extract_int(entargs, 2);
3171 format_type = extract_int(entargs, 3);
3172 extract_token(subject, entargs, 4, '|', sizeof subject);
3173 do_confirm = extract_int(entargs, 6);
3174 extract_token(cc, entargs, 7, '|', sizeof cc);
3175 extract_token(bcc, entargs, 8, '|', sizeof bcc);
3176 switch(CC->room.QRdefaultview) {
3179 extract_token(supplied_euid, entargs, 9, '|', sizeof supplied_euid);
3182 supplied_euid[0] = 0;
3186 /* first check to make sure the request is valid. */
3188 err = CtdlDoIHavePermissionToPostInThisRoom(errmsg, sizeof errmsg);
3190 cprintf("%d %s\n", err, errmsg);
3194 /* Check some other permission type things. */
3197 if (CC->user.axlevel < 6) {
3198 cprintf("%d You don't have permission to masquerade.\n",
3199 ERROR + HIGHER_ACCESS_REQUIRED);
3202 extract_token(newusername, entargs, 5, '|', sizeof newusername);
3203 memset(CC->fake_postname, 0, sizeof(CC->fake_postname) );
3204 safestrncpy(CC->fake_postname, newusername,
3205 sizeof(CC->fake_postname) );
3206 cprintf("%d ok\n", CIT_OK);
3209 CC->cs_flags |= CS_POSTING;
3211 /* In the Mail> room we have to behave a little differently --
3212 * make sure the user has specified at least one recipient. Then
3213 * validate the recipient(s).
3215 if ( (CC->room.QRflags & QR_MAILBOX)
3216 && (!strcasecmp(&CC->room.QRname[11], MAILROOM)) ) {
3218 if (CC->user.axlevel < 2) {
3219 strcpy(recp, "sysop");
3224 valid_to = validate_recipients(recp);
3225 if (valid_to->num_error > 0) {
3226 cprintf("%d Invalid recipient (To)\n", ERROR + NO_SUCH_USER);
3231 valid_cc = validate_recipients(cc);
3232 if (valid_cc->num_error > 0) {
3233 cprintf("%d Invalid recipient (CC)\n", ERROR + NO_SUCH_USER);
3239 valid_bcc = validate_recipients(bcc);
3240 if (valid_bcc->num_error > 0) {
3241 cprintf("%d Invalid recipient (BCC)\n", ERROR + NO_SUCH_USER);
3248 /* Recipient required, but none were specified */
3249 if ( (valid_to->num_error < 0) && (valid_cc->num_error < 0) && (valid_bcc->num_error < 0) ) {
3253 cprintf("%d At least one recipient is required.\n", ERROR + NO_SUCH_USER);
3257 if (valid_to->num_internet + valid_cc->num_internet + valid_bcc->num_internet > 0) {
3258 if (CtdlCheckInternetMailPermission(&CC->user)==0) {
3259 cprintf("%d You do not have permission "
3260 "to send Internet mail.\n",
3261 ERROR + HIGHER_ACCESS_REQUIRED);
3269 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)
3270 && (CC->user.axlevel < 4) ) {
3271 cprintf("%d Higher access required for network mail.\n",
3272 ERROR + HIGHER_ACCESS_REQUIRED);
3279 if ((RESTRICT_INTERNET == 1)
3280 && (valid_to->num_internet + valid_cc->num_internet + valid_bcc->num_internet > 0)
3281 && ((CC->user.flags & US_INTERNET) == 0)
3282 && (!CC->internal_pgm)) {
3283 cprintf("%d You don't have access to Internet mail.\n",
3284 ERROR + HIGHER_ACCESS_REQUIRED);
3293 /* Is this a room which has anonymous-only or anonymous-option? */
3294 anonymous = MES_NORMAL;
3295 if (CC->room.QRflags & QR_ANONONLY) {
3296 anonymous = MES_ANONONLY;
3298 if (CC->room.QRflags & QR_ANONOPT) {
3299 if (anon_flag == 1) { /* only if the user requested it */
3300 anonymous = MES_ANONOPT;
3304 if ((CC->room.QRflags & QR_MAILBOX) == 0) {
3308 /* If we're only checking the validity of the request, return
3309 * success without creating the message.
3312 cprintf("%d %s\n", CIT_OK,
3313 ((valid_to != NULL) ? valid_to->display_recp : "") );
3320 /* We don't need these anymore because we'll do it differently below */
3325 /* Handle author masquerading */
3326 if (CC->fake_postname[0]) {
3327 strcpy(masquerade_as, CC->fake_postname);
3329 else if (CC->fake_username[0]) {
3330 strcpy(masquerade_as, CC->fake_username);
3333 strcpy(masquerade_as, "");
3336 /* Read in the message from the client. */
3338 cprintf("%d send message\n", START_CHAT_MODE);
3340 cprintf("%d send message\n", SEND_LISTING);
3343 msg = CtdlMakeMessage(&CC->user, recp, cc,
3344 CC->room.QRname, anonymous, format_type,
3345 masquerade_as, subject,
3346 ((strlen(supplied_euid) > 0) ? supplied_euid : NULL),
3349 /* Put together one big recipients struct containing to/cc/bcc all in
3350 * one. This is for the envelope.
3352 char *all_recps = malloc(SIZ * 3);
3353 strcpy(all_recps, recp);
3354 if (strlen(cc) > 0) {
3355 if (strlen(all_recps) > 0) {
3356 strcat(all_recps, ",");
3358 strcat(all_recps, cc);
3360 if (strlen(bcc) > 0) {
3361 if (strlen(all_recps) > 0) {
3362 strcat(all_recps, ",");
3364 strcat(all_recps, bcc);
3366 if (strlen(all_recps) > 0) {
3367 valid = validate_recipients(all_recps);
3375 msgnum = CtdlSubmitMsg(msg, valid, "");
3378 cprintf("%ld\n", msgnum);
3380 cprintf("Message accepted.\n");
3383 cprintf("Internal error.\n");
3385 if (msg->cm_fields['E'] != NULL) {
3386 cprintf("%s\n", msg->cm_fields['E']);
3393 CtdlFreeMessage(msg);
3395 CC->fake_postname[0] = '\0';
3396 if (valid != NULL) {
3405 * API function to delete messages which match a set of criteria
3406 * (returns the actual number of messages deleted)
3408 int CtdlDeleteMessages(char *room_name, /* which room */
3409 long *dmsgnums, /* array of msg numbers to be deleted */
3410 int num_dmsgnums, /* number of msgs to be deleted, or 0 for "any" */
3411 char *content_type, /* or "" for any */
3412 int deferred /* let TDAP sweep it later */
3416 struct ctdlroom qrbuf;
3417 struct cdbdata *cdbfr;
3418 long *msglist = NULL;
3419 long *dellist = NULL;
3422 int num_deleted = 0;
3424 struct MetaData smi;
3426 lprintf(CTDL_DEBUG, "CtdlDeleteMessages(%s, %d msgs, %s, %d)\n",
3427 room_name, num_dmsgnums, content_type, deferred);
3429 /* get room record, obtaining a lock... */
3430 if (lgetroom(&qrbuf, room_name) != 0) {
3431 lprintf(CTDL_ERR, "CtdlDeleteMessages(): Room <%s> not found\n",
3433 return (0); /* room not found */
3435 cdbfr = cdb_fetch(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long));
3437 if (cdbfr != NULL) {
3438 dellist = malloc(cdbfr->len);
3439 msglist = (long *) cdbfr->ptr;
3440 cdbfr->ptr = NULL; /* CtdlDeleteMessages() now owns this memory */
3441 num_msgs = cdbfr->len / sizeof(long);
3445 for (i = 0; i < num_msgs; ++i) {
3448 /* Set/clear a bit for each criterion */
3450 /* 0 messages in the list or a null list means that we are
3451 * interested in deleting any messages which meet the other criteria.
3453 if ((num_dmsgnums == 0) || (dmsgnums == NULL)) {
3454 delete_this |= 0x01;
3457 for (j=0; j<num_dmsgnums; ++j) {
3458 if (msglist[i] == dmsgnums[j]) {
3459 delete_this |= 0x01;
3464 if (strlen(content_type) == 0) {
3465 delete_this |= 0x02;
3467 GetMetaData(&smi, msglist[i]);
3468 if (!strcasecmp(smi.meta_content_type,
3470 delete_this |= 0x02;
3474 /* Delete message only if all bits are set */
3475 if (delete_this == 0x03) {
3476 dellist[num_deleted++] = msglist[i];
3481 num_msgs = sort_msglist(msglist, num_msgs);
3482 cdb_store(CDB_MSGLISTS, &qrbuf.QRnumber, (int)sizeof(long),
3483 msglist, (int)(num_msgs * sizeof(long)));
3485 qrbuf.QRhighest = msglist[num_msgs - 1];
3490 * If the delete operation is "deferred" (and technically, any delete
3491 * operation not performed by THE DREADED AUTO-PURGER ought to be
3492 * a deferred delete) then we save a pointer to the message in the
3493 * DELETED_MSGS_ROOM. This will cause the reference count to remain
3494 * at least 1, which will save the user from having to synchronously
3495 * wait for various disk-intensive operations to complete.
3497 * Slick -- we now use the new bulk API for moving messages.
3499 if ( (deferred) && (num_deleted) ) {
3500 CtdlCopyMsgsToRoom(dellist, num_deleted, DELETED_MSGS_ROOM);
3503 /* Go through the messages we pulled out of the index, and decrement
3504 * their reference counts by 1. If this is the only room the message
3505 * was in, the reference count will reach zero and the message will
3506 * automatically be deleted from the database. We do this in a
3507 * separate pass because there might be plug-in hooks getting called,
3508 * and we don't want that happening during an S_ROOMS critical
3511 if (num_deleted) for (i=0; i<num_deleted; ++i) {
3512 PerformDeleteHooks(qrbuf.QRname, dellist[i]);
3513 AdjRefCount(dellist[i], -1);
3516 /* Now free the memory we used, and go away. */
3517 if (msglist != NULL) free(msglist);
3518 if (dellist != NULL) free(dellist);
3519 lprintf(CTDL_DEBUG, "%d message(s) deleted.\n", num_deleted);
3520 return (num_deleted);
3526 * Check whether the current user has permission to delete messages from
3527 * the current room (returns 1 for yes, 0 for no)
3529 int CtdlDoIHavePermissionToDeleteMessagesFromThisRoom(void) {
3530 getuser(&CC->user, CC->curr_user);
3531 if ((CC->user.axlevel < 6)
3532 && (CC->user.usernum != CC->room.QRroomaide)
3533 && ((CC->room.QRflags & QR_MAILBOX) == 0)
3534 && (!(CC->internal_pgm))) {
3543 * Delete message from current room
3545 void cmd_dele(char *args)
3554 extract_token(msgset, args, 0, '|', sizeof msgset);
3555 num_msgs = num_tokens(msgset, ',');
3557 cprintf("%d Nothing to do.\n", CIT_OK);
3561 if (CtdlDoIHavePermissionToDeleteMessagesFromThisRoom() == 0) {
3562 cprintf("%d Higher access required.\n",
3563 ERROR + HIGHER_ACCESS_REQUIRED);
3568 * Build our message set to be moved/copied
3570 msgs = malloc(num_msgs * sizeof(long));
3571 for (i=0; i<num_msgs; ++i) {
3572 extract_token(msgtok, msgset, i, ',', sizeof msgtok);
3573 msgs[i] = atol(msgtok);
3576 num_deleted = CtdlDeleteMessages(CC->room.QRname, msgs, num_msgs, "", 1);
3580 cprintf("%d %d message%s deleted.\n", CIT_OK,
3581 num_deleted, ((num_deleted != 1) ? "s" : ""));
3583 cprintf("%d Message not found.\n", ERROR + MESSAGE_NOT_FOUND);
3589 * Back end API function for moves and deletes (multiple messages)
3591 int CtdlCopyMsgsToRoom(long *msgnums, int num_msgs, char *dest) {
3594 err = CtdlSaveMsgPointersInRoom(dest, msgnums, num_msgs, 1, NULL);
3595 if (err != 0) return(err);
3604 * move or copy a message to another room
3606 void cmd_move(char *args)
3613 char targ[ROOMNAMELEN];
3614 struct ctdlroom qtemp;
3621 extract_token(msgset, args, 0, '|', sizeof msgset);
3622 num_msgs = num_tokens(msgset, ',');
3624 cprintf("%d Nothing to do.\n", CIT_OK);
3628 extract_token(targ, args, 1, '|', sizeof targ);
3629 convert_room_name_macros(targ, sizeof targ);
3630 targ[ROOMNAMELEN - 1] = 0;
3631 is_copy = extract_int(args, 2);
3633 if (getroom(&qtemp, targ) != 0) {
3634 cprintf("%d '%s' does not exist.\n",
3635 ERROR + ROOM_NOT_FOUND, targ);
3639 getuser(&CC->user, CC->curr_user);
3640 CtdlRoomAccess(&qtemp, &CC->user, &ra, NULL);
3642 /* Check for permission to perform this operation.
3643 * Remember: "CC->room" is source, "qtemp" is target.
3647 /* Aides can move/copy */
3648 if (CC->user.axlevel >= 6) permit = 1;
3650 /* Room aides can move/copy */
3651 if (CC->user.usernum == CC->room.QRroomaide) permit = 1;
3653 /* Permit move/copy from personal rooms */
3654 if ((CC->room.QRflags & QR_MAILBOX)
3655 && (qtemp.QRflags & QR_MAILBOX)) permit = 1;
3657 /* Permit only copy from public to personal room */
3659 && (!(CC->room.QRflags & QR_MAILBOX))
3660 && (qtemp.QRflags & QR_MAILBOX)) permit = 1;
3662 /* User must have access to target room */
3663 if (!(ra & UA_KNOWN)) permit = 0;
3666 cprintf("%d Higher access required.\n",
3667 ERROR + HIGHER_ACCESS_REQUIRED);
3672 * Build our message set to be moved/copied
3674 msgs = malloc(num_msgs * sizeof(long));
3675 for (i=0; i<num_msgs; ++i) {
3676 extract_token(msgtok, msgset, i, ',', sizeof msgtok);
3677 msgs[i] = atol(msgtok);
3683 err = CtdlCopyMsgsToRoom(msgs, num_msgs, targ);
3685 cprintf("%d Cannot store message(s) in %s: error %d\n",
3691 /* Now delete the message from the source room,
3692 * if this is a 'move' rather than a 'copy' operation.
3695 CtdlDeleteMessages(CC->room.QRname, msgs, num_msgs, "", 0);
3699 cprintf("%d Message(s) %s.\n", CIT_OK, (is_copy ? "copied" : "moved") );
3705 * GetMetaData() - Get the supplementary record for a message
3707 void GetMetaData(struct MetaData *smibuf, long msgnum)
3710 struct cdbdata *cdbsmi;
3713 memset(smibuf, 0, sizeof(struct MetaData));
3714 smibuf->meta_msgnum = msgnum;
3715 smibuf->meta_refcount = 1; /* Default reference count is 1 */
3717 /* Use the negative of the message number for its supp record index */
3718 TheIndex = (0L - msgnum);
3720 cdbsmi = cdb_fetch(CDB_MSGMAIN, &TheIndex, sizeof(long));
3721 if (cdbsmi == NULL) {
3722 return; /* record not found; go with defaults */
3724 memcpy(smibuf, cdbsmi->ptr,
3725 ((cdbsmi->len > sizeof(struct MetaData)) ?
3726 sizeof(struct MetaData) : cdbsmi->len));
3733 * PutMetaData() - (re)write supplementary record for a message
3735 void PutMetaData(struct MetaData *smibuf)
3739 /* Use the negative of the message number for the metadata db index */
3740 TheIndex = (0L - smibuf->meta_msgnum);
3742 lprintf(CTDL_DEBUG, "PutMetaData(%ld) - ref count is %d\n",
3743 smibuf->meta_msgnum, smibuf->meta_refcount);
3745 cdb_store(CDB_MSGMAIN,
3746 &TheIndex, (int)sizeof(long),
3747 smibuf, (int)sizeof(struct MetaData));
3752 * AdjRefCount - change the reference count for a message;
3753 * delete the message if it reaches zero
3755 void AdjRefCount(long msgnum, int incr)
3758 struct MetaData smi;
3761 /* This is a *tight* critical section; please keep it that way, as
3762 * it may get called while nested in other critical sections.
3763 * Complicating this any further will surely cause deadlock!
3765 begin_critical_section(S_SUPPMSGMAIN);
3766 GetMetaData(&smi, msgnum);
3767 smi.meta_refcount += incr;
3769 end_critical_section(S_SUPPMSGMAIN);
3770 lprintf(CTDL_DEBUG, "msg %ld ref count incr %d, is now %d\n",
3771 msgnum, incr, smi.meta_refcount);
3773 /* If the reference count is now zero, delete the message
3774 * (and its supplementary record as well).
3776 if (smi.meta_refcount == 0) {
3777 lprintf(CTDL_DEBUG, "Deleting message <%ld>\n", msgnum);
3779 /* Remove from fulltext index */
3780 if (config.c_enable_fulltext) {
3781 ft_index_message(msgnum, 0);
3784 /* Remove from message base */
3786 cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long));
3787 cdb_delete(CDB_BIGMSGS, &delnum, (int)sizeof(long));
3789 /* Remove metadata record */
3790 delnum = (0L - msgnum);
3791 cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long));
3796 * Write a generic object to this room
3798 * Note: this could be much more efficient. Right now we use two temporary
3799 * files, and still pull the message into memory as with all others.
3801 void CtdlWriteObject(char *req_room, /* Room to stuff it in */
3802 char *content_type, /* MIME type of this object */
3803 char *tempfilename, /* Where to fetch it from */
3804 struct ctdluser *is_mailbox, /* Mailbox room? */
3805 int is_binary, /* Is encoding necessary? */
3806 int is_unique, /* Del others of this type? */
3807 unsigned int flags /* Internal save flags */
3812 struct ctdlroom qrbuf;
3813 char roomname[ROOMNAMELEN];
3814 struct CtdlMessage *msg;
3816 char *raw_message = NULL;
3817 char *encoded_message = NULL;
3818 off_t raw_length = 0;
3820 if (is_mailbox != NULL) {
3821 MailboxName(roomname, sizeof roomname, is_mailbox, req_room);
3824 safestrncpy(roomname, req_room, sizeof(roomname));
3827 fp = fopen(tempfilename, "rb");
3829 lprintf(CTDL_CRIT, "Cannot open %s: %s\n",
3830 tempfilename, strerror(errno));
3833 fseek(fp, 0L, SEEK_END);
3834 raw_length = ftell(fp);
3836 lprintf(CTDL_DEBUG, "Raw length is %ld\n", (long)raw_length);
3838 raw_message = malloc((size_t)raw_length + 2);
3839 fread(raw_message, (size_t)raw_length, 1, fp);
3843 encoded_message = malloc((size_t)
3844 (((raw_length * 134) / 100) + 4096 ) );
3847 encoded_message = malloc((size_t)(raw_length + 4096));
3850 sprintf(encoded_message, "Content-type: %s\n", content_type);
3853 sprintf(&encoded_message[strlen(encoded_message)],
3854 "Content-transfer-encoding: base64\n\n"
3858 sprintf(&encoded_message[strlen(encoded_message)],
3859 "Content-transfer-encoding: 7bit\n\n"
3865 &encoded_message[strlen(encoded_message)],
3871 raw_message[raw_length] = 0;
3873 &encoded_message[strlen(encoded_message)],
3881 lprintf(CTDL_DEBUG, "Allocating\n");
3882 msg = malloc(sizeof(struct CtdlMessage));
3883 memset(msg, 0, sizeof(struct CtdlMessage));
3884 msg->cm_magic = CTDLMESSAGE_MAGIC;
3885 msg->cm_anon_type = MES_NORMAL;
3886 msg->cm_format_type = 4;
3887 msg->cm_fields['A'] = strdup(CC->user.fullname);
3888 msg->cm_fields['O'] = strdup(req_room);
3889 msg->cm_fields['N'] = strdup(config.c_nodename);
3890 msg->cm_fields['H'] = strdup(config.c_humannode);
3891 msg->cm_flags = flags;
3893 msg->cm_fields['M'] = encoded_message;
3895 /* Create the requested room if we have to. */
3896 if (getroom(&qrbuf, roomname) != 0) {
3897 create_room(roomname,
3898 ( (is_mailbox != NULL) ? 5 : 3 ),
3899 "", 0, 1, 0, VIEW_BBS);
3901 /* If the caller specified this object as unique, delete all
3902 * other objects of this type that are currently in the room.
3905 lprintf(CTDL_DEBUG, "Deleted %d other msgs of this type\n",
3906 CtdlDeleteMessages(roomname, NULL, 0, content_type, 0)
3909 /* Now write the data */
3910 CtdlSubmitMsg(msg, NULL, roomname);
3911 CtdlFreeMessage(msg);
3919 void CtdlGetSysConfigBackend(long msgnum, void *userdata) {
3920 config_msgnum = msgnum;
3924 char *CtdlGetSysConfig(char *sysconfname) {
3925 char hold_rm[ROOMNAMELEN];
3928 struct CtdlMessage *msg;
3931 strcpy(hold_rm, CC->room.QRname);
3932 if (getroom(&CC->room, SYSCONFIGROOM) != 0) {
3933 getroom(&CC->room, hold_rm);
3938 /* We want the last (and probably only) config in this room */
3939 begin_critical_section(S_CONFIG);
3940 config_msgnum = (-1L);
3941 CtdlForEachMessage(MSGS_LAST, 1, NULL, sysconfname, NULL,
3942 CtdlGetSysConfigBackend, NULL);
3943 msgnum = config_msgnum;
3944 end_critical_section(S_CONFIG);
3950 msg = CtdlFetchMessage(msgnum, 1);
3952 conf = strdup(msg->cm_fields['M']);
3953 CtdlFreeMessage(msg);
3960 getroom(&CC->room, hold_rm);
3962 if (conf != NULL) do {
3963 extract_token(buf, conf, 0, '\n', sizeof buf);
3964 strcpy(conf, &conf[strlen(buf)+1]);
3965 } while ( (strlen(conf)>0) && (strlen(buf)>0) );
3970 void CtdlPutSysConfig(char *sysconfname, char *sysconfdata) {
3971 char temp[PATH_MAX];
3974 CtdlMakeTempFileName(temp, sizeof temp);
3976 fp = fopen(temp, "w");
3977 if (fp == NULL) return;
3978 fprintf(fp, "%s", sysconfdata);
3981 /* this handy API function does all the work for us */
3982 CtdlWriteObject(SYSCONFIGROOM, sysconfname, temp, NULL, 0, 1, 0);
3988 * Determine whether a given Internet address belongs to the current user
3990 int CtdlIsMe(char *addr, int addr_buf_len)
3992 struct recptypes *recp;
3995 recp = validate_recipients(addr);
3996 if (recp == NULL) return(0);
3998 if (recp->num_local == 0) {
4003 for (i=0; i<recp->num_local; ++i) {
4004 extract_token(addr, recp->recp_local, i, '|', addr_buf_len);
4005 if (!strcasecmp(addr, CC->user.fullname)) {
4017 * Citadel protocol command to do the same
4019 void cmd_isme(char *argbuf) {
4022 if (CtdlAccessCheck(ac_logged_in)) return;
4023 extract_token(addr, argbuf, 0, '|', sizeof addr);
4025 if (CtdlIsMe(addr, sizeof addr)) {
4026 cprintf("%d %s\n", CIT_OK, addr);
4029 cprintf("%d Not you.\n", ERROR + ILLEGAL_VALUE);