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"
57 struct addresses_to_be_filed *atbf = NULL;
60 * This really belongs in serv_network.c, but I don't know how to export
61 * symbols between modules.
63 struct FilterList *filterlist = NULL;
67 * These are the four-character field headers we use when outputting
68 * messages in Citadel format (as opposed to RFC822 format).
71 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
72 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
73 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
74 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
75 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
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,
106 * This function is self explanatory.
107 * (What can I say, I'm in a weird mood today...)
109 void remove_any_whitespace_to_the_left_or_right_of_at_symbol(char *name)
113 for (i = 0; i < strlen(name); ++i) {
114 if (name[i] == '@') {
115 while (isspace(name[i - 1]) && i > 0) {
116 strcpy(&name[i - 1], &name[i]);
119 while (isspace(name[i + 1])) {
120 strcpy(&name[i + 1], &name[i + 2]);
128 * Aliasing for network mail.
129 * (Error messages have been commented out, because this is a server.)
131 int alias(char *name)
132 { /* process alias and routing info for mail */
135 char aaa[SIZ], bbb[SIZ];
136 char *ignetcfg = NULL;
137 char *ignetmap = NULL;
144 remove_any_whitespace_to_the_left_or_right_of_at_symbol(name);
145 stripallbut(name, '<', '>');
147 fp = fopen(file_mail_aliases, "r");
149 fp = fopen("/dev/null", "r");
156 while (fgets(aaa, sizeof aaa, fp) != NULL) {
157 while (isspace(name[0]))
158 strcpy(name, &name[1]);
159 aaa[strlen(aaa) - 1] = 0;
161 for (a = 0; a < strlen(aaa); ++a) {
163 strcpy(bbb, &aaa[a + 1]);
167 if (!strcasecmp(name, aaa))
172 /* Hit the Global Address Book */
173 if (CtdlDirectoryLookup(aaa, name, sizeof aaa) == 0) {
177 lprintf(CTDL_INFO, "Mail is being forwarded to %s\n", name);
179 /* Change "user @ xxx" to "user" if xxx is an alias for this host */
180 for (a=0; a<strlen(name); ++a) {
181 if (name[a] == '@') {
182 if (CtdlHostAlias(&name[a+1]) == hostalias_localhost) {
184 lprintf(CTDL_INFO, "Changed to <%s>\n", name);
189 /* determine local or remote type, see citadel.h */
190 at = haschar(name, '@');
191 if (at == 0) return(MES_LOCAL); /* no @'s - local address */
192 if (at > 1) return(MES_ERROR); /* >1 @'s - invalid address */
193 remove_any_whitespace_to_the_left_or_right_of_at_symbol(name);
195 /* figure out the delivery mode */
196 extract_token(node, name, 1, '@', sizeof node);
198 /* If there are one or more dots in the nodename, we assume that it
199 * is an FQDN and will attempt SMTP delivery to the Internet.
201 if (haschar(node, '.') > 0) {
202 return(MES_INTERNET);
205 /* Otherwise we look in the IGnet maps for a valid Citadel node.
206 * Try directly-connected nodes first...
208 ignetcfg = CtdlGetSysConfig(IGNETCFG);
209 for (i=0; i<num_tokens(ignetcfg, '\n'); ++i) {
210 extract_token(buf, ignetcfg, i, '\n', sizeof buf);
211 extract_token(testnode, buf, 0, '|', sizeof testnode);
212 if (!strcasecmp(node, testnode)) {
220 * Then try nodes that are two or more hops away.
222 ignetmap = CtdlGetSysConfig(IGNETMAP);
223 for (i=0; i<num_tokens(ignetmap, '\n'); ++i) {
224 extract_token(buf, ignetmap, i, '\n', sizeof buf);
225 extract_token(testnode, buf, 0, '|', sizeof testnode);
226 if (!strcasecmp(node, testnode)) {
233 /* If we get to this point it's an invalid node name */
242 fp = fopen(file_citadel_control, "r");
244 lprintf(CTDL_CRIT, "Cannot open %s: %s\n",
245 file_citadel_control,
249 fread((char *) &CitControl, sizeof(struct CitControl), 1, fp);
255 * Back end for the MSGS command: output message number only.
257 void simple_listing(long msgnum, void *userdata)
259 cprintf("%ld\n", msgnum);
265 * Back end for the MSGS command: output header summary.
267 void headers_listing(long msgnum, void *userdata)
269 struct CtdlMessage *msg;
271 msg = CtdlFetchMessage(msgnum, 0);
273 cprintf("%ld|0|||||\n", msgnum);
277 cprintf("%ld|%s|%s|%s|%s|%s|\n",
279 (msg->cm_fields['T'] ? msg->cm_fields['T'] : "0"),
280 (msg->cm_fields['A'] ? msg->cm_fields['A'] : ""),
281 (msg->cm_fields['N'] ? msg->cm_fields['N'] : ""),
282 (msg->cm_fields['F'] ? msg->cm_fields['F'] : ""),
283 (msg->cm_fields['U'] ? msg->cm_fields['U'] : "")
285 CtdlFreeMessage(msg);
290 /* Determine if a given message matches the fields in a message template.
291 * Return 0 for a successful match.
293 int CtdlMsgCmp(struct CtdlMessage *msg, struct CtdlMessage *template) {
296 /* If there aren't any fields in the template, all messages will
299 if (template == NULL) return(0);
301 /* Null messages are bogus. */
302 if (msg == NULL) return(1);
304 for (i='A'; i<='Z'; ++i) {
305 if (template->cm_fields[i] != NULL) {
306 if (msg->cm_fields[i] == NULL) {
309 if (strcasecmp(msg->cm_fields[i],
310 template->cm_fields[i])) return 1;
314 /* All compares succeeded: we have a match! */
321 * Retrieve the "seen" message list for the current room.
323 void CtdlGetSeen(char *buf, int which_set) {
326 /* Learn about the user and room in question */
327 CtdlGetRelationship(&vbuf, &CC->user, &CC->room);
329 if (which_set == ctdlsetseen_seen)
330 safestrncpy(buf, vbuf.v_seen, SIZ);
331 if (which_set == ctdlsetseen_answered)
332 safestrncpy(buf, vbuf.v_answered, SIZ);
338 * Manipulate the "seen msgs" string (or other message set strings)
340 void CtdlSetSeen(long *target_msgnums, int num_target_msgnums,
341 int target_setting, int which_set,
342 struct ctdluser *which_user, struct ctdlroom *which_room) {
343 struct cdbdata *cdbfr;
355 char *is_set; /* actually an array of booleans */
358 char setstr[SIZ], lostr[SIZ], histr[SIZ];
361 lprintf(CTDL_DEBUG, "CtdlSetSeen(%d msgs starting with %ld, %d, %d)\n",
362 num_target_msgnums, target_msgnums[0],
363 target_setting, which_set);
365 /* Learn about the user and room in question */
366 CtdlGetRelationship(&vbuf,
367 ((which_user != NULL) ? which_user : &CC->user),
368 ((which_room != NULL) ? which_room : &CC->room)
371 /* Load the message list */
372 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long));
374 msglist = (long *) cdbfr->ptr;
375 cdbfr->ptr = NULL; /* CtdlSetSeen() now owns this memory */
376 num_msgs = cdbfr->len / sizeof(long);
379 return; /* No messages at all? No further action. */
382 is_set = malloc(num_msgs * sizeof(char));
383 memset(is_set, 0, (num_msgs * sizeof(char)) );
385 /* Decide which message set we're manipulating */
387 case ctdlsetseen_seen:
388 safestrncpy(vset, vbuf.v_seen, sizeof vset);
390 case ctdlsetseen_answered:
391 safestrncpy(vset, vbuf.v_answered, sizeof vset);
395 /* lprintf(CTDL_DEBUG, "before optimize: %s\n", vset); */
397 /* Translate the existing sequence set into an array of booleans */
398 num_sets = num_tokens(vset, ',');
399 for (s=0; s<num_sets; ++s) {
400 extract_token(setstr, vset, s, ',', sizeof setstr);
402 extract_token(lostr, setstr, 0, ':', sizeof lostr);
403 if (num_tokens(setstr, ':') >= 2) {
404 extract_token(histr, setstr, 1, ':', sizeof histr);
405 if (!strcmp(histr, "*")) {
406 snprintf(histr, sizeof histr, "%ld", LONG_MAX);
410 strcpy(histr, lostr);
415 for (i = 0; i < num_msgs; ++i) {
416 if ((msglist[i] >= lo) && (msglist[i] <= hi)) {
422 /* Now translate the array of booleans back into a sequence set */
427 for (i=0; i<num_msgs; ++i) {
429 is_seen = is_set[i]; /* Default to existing setting */
431 for (k=0; k<num_target_msgnums; ++k) {
432 if (msglist[i] == target_msgnums[k]) {
433 is_seen = target_setting;
438 if (lo < 0L) lo = msglist[i];
442 if ( ((is_seen == 0) && (was_seen == 1))
443 || ((is_seen == 1) && (i == num_msgs-1)) ) {
445 /* begin trim-o-matic code */
448 while ( (strlen(vset) + 20) > sizeof vset) {
449 remove_token(vset, 0, ',');
451 if (j--) break; /* loop no more than 9 times */
453 if ( (trimming) && (which_set == ctdlsetseen_seen) ) {
457 snprintf(lostr, sizeof lostr,
458 "1:%ld,%s", t, vset);
459 safestrncpy(vset, lostr, sizeof vset);
461 /* end trim-o-matic code */
469 snprintf(&vset[tmp], (sizeof vset) - tmp,
473 snprintf(&vset[tmp], (sizeof vset) - tmp,
482 /* Decide which message set we're manipulating */
484 case ctdlsetseen_seen:
485 safestrncpy(vbuf.v_seen, vset, sizeof vbuf.v_seen);
487 case ctdlsetseen_answered:
488 safestrncpy(vbuf.v_answered, vset,
489 sizeof vbuf.v_answered);
494 /* lprintf(CTDL_DEBUG, " after optimize: %s\n", vset); */
496 CtdlSetRelationship(&vbuf,
497 ((which_user != NULL) ? which_user : &CC->user),
498 ((which_room != NULL) ? which_room : &CC->room)
504 * API function to perform an operation for each qualifying message in the
505 * current room. (Returns the number of messages processed.)
507 int CtdlForEachMessage(int mode, long ref, char *search_string,
509 struct CtdlMessage *compare,
510 void (*CallBack) (long, void *),
516 struct cdbdata *cdbfr;
517 long *msglist = NULL;
519 int num_processed = 0;
522 struct CtdlMessage *msg;
525 int printed_lastold = 0;
526 int num_search_msgs = 0;
527 long *search_msgs = NULL;
529 /* Learn about the user and room in question */
531 getuser(&CC->user, CC->curr_user);
532 CtdlGetRelationship(&vbuf, &CC->user, &CC->room);
534 /* Load the message list */
535 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long));
537 msglist = (long *) cdbfr->ptr;
538 cdbfr->ptr = NULL; /* CtdlForEachMessage() now owns this memory */
539 num_msgs = cdbfr->len / sizeof(long);
542 return 0; /* No messages at all? No further action. */
547 * Now begin the traversal.
549 if (num_msgs > 0) for (a = 0; a < num_msgs; ++a) {
551 /* If the caller is looking for a specific MIME type, filter
552 * out all messages which are not of the type requested.
554 if (content_type != NULL) if (strlen(content_type) > 0) {
556 /* This call to GetMetaData() sits inside this loop
557 * so that we only do the extra database read per msg
558 * if we need to. Doing the extra read all the time
559 * really kills the server. If we ever need to use
560 * metadata for another search criterion, we need to
561 * move the read somewhere else -- but still be smart
562 * enough to only do the read if the caller has
563 * specified something that will need it.
565 GetMetaData(&smi, msglist[a]);
567 if (strcasecmp(smi.meta_content_type, content_type)) {
573 num_msgs = sort_msglist(msglist, num_msgs);
575 /* If a template was supplied, filter out the messages which
576 * don't match. (This could induce some delays!)
579 if (compare != NULL) {
580 for (a = 0; a < num_msgs; ++a) {
581 msg = CtdlFetchMessage(msglist[a], 1);
583 if (CtdlMsgCmp(msg, compare)) {
586 CtdlFreeMessage(msg);
592 /* If a search string was specified, get a message list from
593 * the full text index and remove messages which aren't on both
597 * Since the lists are sorted and strictly ascending, and the
598 * output list is guaranteed to be shorter than or equal to the
599 * input list, we overwrite the bottom of the input list. This
600 * eliminates the need to memmove big chunks of the list over and
603 if ( (num_msgs > 0) && (mode == MSGS_SEARCH) && (search_string) ) {
604 ft_search(&num_search_msgs, &search_msgs, search_string);
605 if (num_search_msgs > 0) {
609 orig_num_msgs = num_msgs;
611 for (i=0; i<orig_num_msgs; ++i) {
612 for (j=0; j<num_search_msgs; ++j) {
613 if (msglist[i] == search_msgs[j]) {
614 msglist[num_msgs++] = msglist[j];
620 num_msgs = 0; /* No messages qualify */
622 if (search_msgs != NULL) free(search_msgs);
624 /* Now that we've purged messages which don't contain the search
625 * string, treat a MSGS_SEARCH just like a MSGS_ALL from this
632 * Now iterate through the message list, according to the
633 * criteria supplied by the caller.
636 for (a = 0; a < num_msgs; ++a) {
637 thismsg = msglist[a];
638 if (mode == MSGS_ALL) {
642 is_seen = is_msg_in_sequence_set(
643 vbuf.v_seen, thismsg);
644 if (is_seen) lastold = thismsg;
650 || ((mode == MSGS_OLD) && (is_seen))
651 || ((mode == MSGS_NEW) && (!is_seen))
652 || ((mode == MSGS_LAST) && (a >= (num_msgs - ref)))
653 || ((mode == MSGS_FIRST) && (a < ref))
654 || ((mode == MSGS_GT) && (thismsg > ref))
655 || ((mode == MSGS_EQ) && (thismsg == ref))
658 if ((mode == MSGS_NEW) && (CC->user.flags & US_LASTOLD) && (lastold > 0L) && (printed_lastold == 0) && (!is_seen)) {
660 CallBack(lastold, userdata);
664 if (CallBack) CallBack(thismsg, userdata);
668 free(msglist); /* Clean up */
669 return num_processed;
675 * cmd_msgs() - get list of message #'s in this room
676 * implements the MSGS server command using CtdlForEachMessage()
678 void cmd_msgs(char *cmdbuf)
687 int with_template = 0;
688 struct CtdlMessage *template = NULL;
689 int with_headers = 0;
690 char search_string[1024];
692 extract_token(which, cmdbuf, 0, '|', sizeof which);
693 cm_ref = extract_int(cmdbuf, 1);
694 extract_token(search_string, cmdbuf, 1, '|', sizeof search_string);
695 with_template = extract_int(cmdbuf, 2);
696 with_headers = extract_int(cmdbuf, 3);
699 if (!strncasecmp(which, "OLD", 3))
701 else if (!strncasecmp(which, "NEW", 3))
703 else if (!strncasecmp(which, "FIRST", 5))
705 else if (!strncasecmp(which, "LAST", 4))
707 else if (!strncasecmp(which, "GT", 2))
709 else if (!strncasecmp(which, "SEARCH", 6))
714 if ((!(CC->logged_in)) && (!(CC->internal_pgm))) {
715 cprintf("%d not logged in\n", ERROR + NOT_LOGGED_IN);
719 if ( (mode == MSGS_SEARCH) && (!config.c_enable_fulltext) ) {
720 cprintf("%d Full text index is not enabled on this server.\n",
721 ERROR + CMD_NOT_SUPPORTED);
727 cprintf("%d Send template then receive message list\n",
729 template = (struct CtdlMessage *)
730 malloc(sizeof(struct CtdlMessage));
731 memset(template, 0, sizeof(struct CtdlMessage));
732 while(client_getln(buf, sizeof buf), strcmp(buf,"000")) {
733 extract_token(tfield, buf, 0, '|', sizeof tfield);
734 extract_token(tvalue, buf, 1, '|', sizeof tvalue);
735 for (i='A'; i<='Z'; ++i) if (msgkeys[i]!=NULL) {
736 if (!strcasecmp(tfield, msgkeys[i])) {
737 template->cm_fields[i] =
745 cprintf("%d \n", LISTING_FOLLOWS);
748 CtdlForEachMessage(mode,
749 ( (mode == MSGS_SEARCH) ? 0 : cm_ref ),
750 ( (mode == MSGS_SEARCH) ? search_string : NULL ),
753 (with_headers ? headers_listing : simple_listing),
756 if (template != NULL) CtdlFreeMessage(template);
764 * help_subst() - support routine for help file viewer
766 void help_subst(char *strbuf, char *source, char *dest)
771 while (p = pattern2(strbuf, source), (p >= 0)) {
772 strcpy(workbuf, &strbuf[p + strlen(source)]);
773 strcpy(&strbuf[p], dest);
774 strcat(strbuf, workbuf);
779 void do_help_subst(char *buffer)
783 help_subst(buffer, "^nodename", config.c_nodename);
784 help_subst(buffer, "^humannode", config.c_humannode);
785 help_subst(buffer, "^fqdn", config.c_fqdn);
786 help_subst(buffer, "^username", CC->user.fullname);
787 snprintf(buf2, sizeof buf2, "%ld", CC->user.usernum);
788 help_subst(buffer, "^usernum", buf2);
789 help_subst(buffer, "^sysadm", config.c_sysadm);
790 help_subst(buffer, "^variantname", CITADEL);
791 snprintf(buf2, sizeof buf2, "%d", config.c_maxsessions);
792 help_subst(buffer, "^maxsessions", buf2);
793 help_subst(buffer, "^bbsdir", ctdl_bbsbase_dir);
799 * memfmout() - Citadel text formatter and paginator.
800 * Although the original purpose of this routine was to format
801 * text to the reader's screen width, all we're really using it
802 * for here is to format text out to 80 columns before sending it
803 * to the client. The client software may reformat it again.
806 char *mptr, /* where are we going to get our text from? */
807 char subst, /* nonzero if we should do substitutions */
808 char *nl) /* string to terminate lines with */
816 static int width = 80;
821 c = 1; /* c is the current pos */
825 while (ch = *mptr, ((ch != 0) && (strlen(buffer) < 126))) {
827 buffer[strlen(buffer) + 1] = 0;
828 buffer[strlen(buffer)] = ch;
831 if (buffer[0] == '^')
832 do_help_subst(buffer);
834 buffer[strlen(buffer) + 1] = 0;
836 strcpy(buffer, &buffer[1]);
844 if (((ch == 13) || (ch == 10)) && (old != 13) && (old != 10)) {
847 if (((old == 13) || (old == 10)) && (isspace(real))) {
852 if (((strlen(aaa) + c) > (width - 5)) && (strlen(aaa) > (width - 5))) {
853 cprintf("%s%s", nl, aaa);
862 if ((strlen(aaa) + c) > (width - 5)) {
871 if ((ch == 13) || (ch == 10)) {
872 cprintf("%s%s", aaa, nl);
879 cprintf("%s%s", aaa, nl);
885 * Callback function for mime parser that simply lists the part
887 void list_this_part(char *name, char *filename, char *partnum, char *disp,
888 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
893 ma = (struct ma_info *)cbuserdata;
894 if (ma->is_ma == 0) {
895 cprintf("part=%s|%s|%s|%s|%s|%ld\n",
896 name, filename, partnum, disp, cbtype, (long)length);
901 * Callback function for multipart prefix
903 void list_this_pref(char *name, char *filename, char *partnum, char *disp,
904 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
909 ma = (struct ma_info *)cbuserdata;
910 if (!strcasecmp(cbtype, "multipart/alternative")) {
914 if (ma->is_ma == 0) {
915 cprintf("pref=%s|%s\n", partnum, cbtype);
920 * Callback function for multipart sufffix
922 void list_this_suff(char *name, char *filename, char *partnum, char *disp,
923 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
928 ma = (struct ma_info *)cbuserdata;
929 if (ma->is_ma == 0) {
930 cprintf("suff=%s|%s\n", partnum, cbtype);
932 if (!strcasecmp(cbtype, "multipart/alternative")) {
939 * Callback function for mime parser that opens a section for downloading
941 void mime_download(char *name, char *filename, char *partnum, char *disp,
942 void *content, char *cbtype, char *cbcharset, size_t length,
943 char *encoding, void *cbuserdata)
946 /* Silently go away if there's already a download open... */
947 if (CC->download_fp != NULL)
950 /* ...or if this is not the desired section */
951 if (strcasecmp(CC->download_desired_section, partnum))
954 CC->download_fp = tmpfile();
955 if (CC->download_fp == NULL)
958 fwrite(content, length, 1, CC->download_fp);
959 fflush(CC->download_fp);
960 rewind(CC->download_fp);
962 OpenCmdResult(filename, cbtype);
968 * Load a message from disk into memory.
969 * This is used by CtdlOutputMsg() and other fetch functions.
971 * NOTE: Caller is responsible for freeing the returned CtdlMessage struct
972 * using the CtdlMessageFree() function.
974 struct CtdlMessage *CtdlFetchMessage(long msgnum, int with_body)
976 struct cdbdata *dmsgtext;
977 struct CtdlMessage *ret = NULL;
981 cit_uint8_t field_header;
983 lprintf(CTDL_DEBUG, "CtdlFetchMessage(%ld, %d)\n", msgnum, with_body);
985 dmsgtext = cdb_fetch(CDB_MSGMAIN, &msgnum, sizeof(long));
986 if (dmsgtext == NULL) {
989 mptr = dmsgtext->ptr;
990 upper_bound = mptr + dmsgtext->len;
992 /* Parse the three bytes that begin EVERY message on disk.
993 * The first is always 0xFF, the on-disk magic number.
994 * The second is the anonymous/public type byte.
995 * The third is the format type byte (vari, fixed, or MIME).
1000 "Message %ld appears to be corrupted.\n",
1005 ret = (struct CtdlMessage *) malloc(sizeof(struct CtdlMessage));
1006 memset(ret, 0, sizeof(struct CtdlMessage));
1008 ret->cm_magic = CTDLMESSAGE_MAGIC;
1009 ret->cm_anon_type = *mptr++; /* Anon type byte */
1010 ret->cm_format_type = *mptr++; /* Format type byte */
1013 * The rest is zero or more arbitrary fields. Load them in.
1014 * We're done when we encounter either a zero-length field or
1015 * have just processed the 'M' (message text) field.
1018 if (mptr >= upper_bound) {
1021 field_header = *mptr++;
1022 ret->cm_fields[field_header] = strdup(mptr);
1024 while (*mptr++ != 0); /* advance to next field */
1026 } while ((mptr < upper_bound) && (field_header != 'M'));
1030 /* Always make sure there's something in the msg text field. If
1031 * it's NULL, the message text is most likely stored separately,
1032 * so go ahead and fetch that. Failing that, just set a dummy
1033 * body so other code doesn't barf.
1035 if ( (ret->cm_fields['M'] == NULL) && (with_body) ) {
1036 dmsgtext = cdb_fetch(CDB_BIGMSGS, &msgnum, sizeof(long));
1037 if (dmsgtext != NULL) {
1038 ret->cm_fields['M'] = strdup(dmsgtext->ptr);
1042 if (ret->cm_fields['M'] == NULL) {
1043 ret->cm_fields['M'] = strdup("\r\n\r\n (no text)\r\n");
1046 /* Perform "before read" hooks (aborting if any return nonzero) */
1047 if (PerformMessageHooks(ret, EVT_BEFOREREAD) > 0) {
1048 CtdlFreeMessage(ret);
1057 * Returns 1 if the supplied pointer points to a valid Citadel message.
1058 * If the pointer is NULL or the magic number check fails, returns 0.
1060 int is_valid_message(struct CtdlMessage *msg) {
1063 if ((msg->cm_magic) != CTDLMESSAGE_MAGIC) {
1064 lprintf(CTDL_WARNING, "is_valid_message() -- self-check failed\n");
1072 * 'Destructor' for struct CtdlMessage
1074 void CtdlFreeMessage(struct CtdlMessage *msg)
1078 if (is_valid_message(msg) == 0) return;
1080 for (i = 0; i < 256; ++i)
1081 if (msg->cm_fields[i] != NULL) {
1082 free(msg->cm_fields[i]);
1085 msg->cm_magic = 0; /* just in case */
1091 * Pre callback function for multipart/alternative
1093 * NOTE: this differs from the standard behavior for a reason. Normally when
1094 * displaying multipart/alternative you want to show the _last_ usable
1095 * format in the message. Here we show the _first_ one, because it's
1096 * usually text/plain. Since this set of functions is designed for text
1097 * output to non-MIME-aware clients, this is the desired behavior.
1100 void fixed_output_pre(char *name, char *filename, char *partnum, char *disp,
1101 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
1106 ma = (struct ma_info *)cbuserdata;
1107 lprintf(CTDL_DEBUG, "fixed_output_pre() type=<%s>\n", cbtype);
1108 if (!strcasecmp(cbtype, "multipart/alternative")) {
1112 if (!strcasecmp(cbtype, "message/rfc822")) {
1118 * Post callback function for multipart/alternative
1120 void fixed_output_post(char *name, char *filename, char *partnum, char *disp,
1121 void *content, char *cbtype, char *cbcharset, size_t length,
1122 char *encoding, void *cbuserdata)
1126 ma = (struct ma_info *)cbuserdata;
1127 lprintf(CTDL_DEBUG, "fixed_output_post() type=<%s>\n", cbtype);
1128 if (!strcasecmp(cbtype, "multipart/alternative")) {
1132 if (!strcasecmp(cbtype, "message/rfc822")) {
1138 * Inline callback function for mime parser that wants to display text
1140 void fixed_output(char *name, char *filename, char *partnum, char *disp,
1141 void *content, char *cbtype, char *cbcharset, size_t length,
1142 char *encoding, void *cbuserdata)
1149 ma = (struct ma_info *)cbuserdata;
1152 "fixed_output() part %s: %s (%s) (%ld bytes)\n",
1153 partnum, filename, cbtype, (long)length);
1156 * If we're in the middle of a multipart/alternative scope and
1157 * we've already printed another section, skip this one.
1159 if ( (ma->is_ma) && (ma->did_print) ) {
1160 lprintf(CTDL_DEBUG, "Skipping part %s (%s)\n",
1166 if ( (!strcasecmp(cbtype, "text/plain"))
1167 || (strlen(cbtype)==0) ) {
1170 client_write(wptr, length);
1171 if (wptr[length-1] != '\n') {
1178 if (!strcasecmp(cbtype, "text/html")) {
1179 ptr = html_to_ascii(content, length, 80, 0);
1181 client_write(ptr, wlen);
1182 if (ptr[wlen-1] != '\n') {
1189 if (ma->use_fo_hooks) {
1190 if (PerformFixedOutputHooks(cbtype, content, length)) {
1191 /* above function returns nonzero if it handled the part */
1196 if (strncasecmp(cbtype, "multipart/", 10)) {
1197 cprintf("Part %s: %s (%s) (%ld bytes)\r\n",
1198 partnum, filename, cbtype, (long)length);
1204 * The client is elegant and sophisticated and wants to be choosy about
1205 * MIME content types, so figure out which multipart/alternative part
1206 * we're going to send.
1208 void choose_preferred(char *name, char *filename, char *partnum, char *disp,
1209 void *content, char *cbtype, char *cbcharset, size_t length,
1210 char *encoding, void *cbuserdata)
1216 ma = (struct ma_info *)cbuserdata;
1218 if (ma->is_ma > 0) {
1219 for (i=0; i<num_tokens(CC->preferred_formats, '|'); ++i) {
1220 extract_token(buf, CC->preferred_formats, i, '|', sizeof buf);
1221 if ( (!strcasecmp(buf, cbtype)) && (!ma->freeze) ) {
1222 safestrncpy(ma->chosen_part, partnum, sizeof ma->chosen_part);
1229 * Now that we've chosen our preferred part, output it.
1231 void output_preferred(char *name, char *filename, char *partnum, char *disp,
1232 void *content, char *cbtype, char *cbcharset, size_t length,
1233 char *encoding, void *cbuserdata)
1237 int add_newline = 0;
1241 ma = (struct ma_info *)cbuserdata;
1243 /* This is not the MIME part you're looking for... */
1244 if (strcasecmp(partnum, ma->chosen_part)) return;
1246 /* If the content-type of this part is in our preferred formats
1247 * list, we can simply output it verbatim.
1249 for (i=0; i<num_tokens(CC->preferred_formats, '|'); ++i) {
1250 extract_token(buf, CC->preferred_formats, i, '|', sizeof buf);
1251 if (!strcasecmp(buf, cbtype)) {
1252 /* Yeah! Go! W00t!! */
1254 text_content = (char *)content;
1255 if (text_content[length-1] != '\n') {
1259 cprintf("Content-type: %s", cbtype);
1260 if (strlen(cbcharset) > 0) {
1261 cprintf("; charset=%s", cbcharset);
1263 cprintf("\nContent-length: %d\n",
1264 (int)(length + add_newline) );
1265 if (strlen(encoding) > 0) {
1266 cprintf("Content-transfer-encoding: %s\n", encoding);
1269 cprintf("Content-transfer-encoding: 7bit\n");
1272 client_write(content, length);
1273 if (add_newline) cprintf("\n");
1278 /* No translations required or possible: output as text/plain */
1279 cprintf("Content-type: text/plain\n\n");
1280 fixed_output(name, filename, partnum, disp, content, cbtype, cbcharset,
1281 length, encoding, cbuserdata);
1286 char desired_section[64];
1293 * Callback function for
1295 void extract_encapsulated_message(char *name, char *filename, char *partnum, char *disp,
1296 void *content, char *cbtype, char *cbcharset, size_t length,
1297 char *encoding, void *cbuserdata)
1299 struct encapmsg *encap;
1301 encap = (struct encapmsg *)cbuserdata;
1303 /* Only proceed if this is the desired section... */
1304 if (!strcasecmp(encap->desired_section, partnum)) {
1305 encap->msglen = length;
1306 encap->msg = malloc(length + 2);
1307 memcpy(encap->msg, content, length);
1317 * Get a message off disk. (returns om_* values found in msgbase.h)
1320 int CtdlOutputMsg(long msg_num, /* message number (local) to fetch */
1321 int mode, /* how would you like that message? */
1322 int headers_only, /* eschew the message body? */
1323 int do_proto, /* do Citadel protocol responses? */
1324 int crlf, /* Use CRLF newlines instead of LF? */
1325 char *section /* NULL or a message/rfc822 section */
1327 struct CtdlMessage *TheMessage = NULL;
1328 int retcode = om_no_such_msg;
1329 struct encapmsg encap;
1331 lprintf(CTDL_DEBUG, "CtdlOutputMsg() msgnum=%ld, mode=%d, section=%s\n",
1333 (section ? section : "<>")
1336 if ((!(CC->logged_in)) && (!(CC->internal_pgm))) {
1337 if (do_proto) cprintf("%d Not logged in.\n",
1338 ERROR + NOT_LOGGED_IN);
1339 return(om_not_logged_in);
1342 /* FIXME: check message id against msglist for this room */
1345 * Fetch the message from disk. If we're in any sort of headers
1346 * only mode, request that we don't even bother loading the body
1349 if ( (headers_only == HEADERS_FAST) || (headers_only == HEADERS_ONLY) ) {
1350 TheMessage = CtdlFetchMessage(msg_num, 0);
1353 TheMessage = CtdlFetchMessage(msg_num, 1);
1356 if (TheMessage == NULL) {
1357 if (do_proto) cprintf("%d Can't locate msg %ld on disk\n",
1358 ERROR + MESSAGE_NOT_FOUND, msg_num);
1359 return(om_no_such_msg);
1362 /* Here is the weird form of this command, to process only an
1363 * encapsulated message/rfc822 section.
1365 if (section) if (strlen(section)>0) if (strcmp(section, "0")) {
1366 memset(&encap, 0, sizeof encap);
1367 safestrncpy(encap.desired_section, section, sizeof encap.desired_section);
1368 mime_parser(TheMessage->cm_fields['M'],
1370 *extract_encapsulated_message,
1371 NULL, NULL, (void *)&encap, 0
1373 CtdlFreeMessage(TheMessage);
1377 encap.msg[encap.msglen] = 0;
1378 TheMessage = convert_internet_message(encap.msg);
1379 encap.msg = NULL; /* no free() here, TheMessage owns it now */
1381 /* Now we let it fall through to the bottom of this
1382 * function, because TheMessage now contains the
1383 * encapsulated message instead of the top-level
1384 * message. Isn't that neat?
1389 if (do_proto) cprintf("%d msg %ld has no part %s\n",
1390 ERROR + MESSAGE_NOT_FOUND, msg_num, section);
1391 retcode = om_no_such_msg;
1396 /* Ok, output the message now */
1397 retcode = CtdlOutputPreLoadedMsg(
1399 headers_only, do_proto, crlf);
1400 CtdlFreeMessage(TheMessage);
1407 * Get a message off disk. (returns om_* values found in msgbase.h)
1410 int CtdlOutputPreLoadedMsg(
1411 struct CtdlMessage *TheMessage,
1412 int mode, /* how would you like that message? */
1413 int headers_only, /* eschew the message body? */
1414 int do_proto, /* do Citadel protocol responses? */
1415 int crlf /* Use CRLF newlines instead of LF? */
1421 char display_name[256];
1423 char *nl; /* newline string */
1425 int subject_found = 0;
1428 /* Buffers needed for RFC822 translation. These are all filled
1429 * using functions that are bounds-checked, and therefore we can
1430 * make them substantially smaller than SIZ.
1438 char datestamp[100];
1440 lprintf(CTDL_DEBUG, "CtdlOutputPreLoadedMsg(TheMessage=%s, %d, %d, %d, %d\n",
1441 ((TheMessage == NULL) ? "NULL" : "not null"),
1442 mode, headers_only, do_proto, crlf);
1444 strcpy(mid, "unknown");
1445 nl = (crlf ? "\r\n" : "\n");
1447 if (!is_valid_message(TheMessage)) {
1449 "ERROR: invalid preloaded message for output\n");
1450 return(om_no_such_msg);
1453 /* Are we downloading a MIME component? */
1454 if (mode == MT_DOWNLOAD) {
1455 if (TheMessage->cm_format_type != FMT_RFC822) {
1457 cprintf("%d This is not a MIME message.\n",
1458 ERROR + ILLEGAL_VALUE);
1459 } else if (CC->download_fp != NULL) {
1460 if (do_proto) cprintf(
1461 "%d You already have a download open.\n",
1462 ERROR + RESOURCE_BUSY);
1464 /* Parse the message text component */
1465 mptr = TheMessage->cm_fields['M'];
1466 mime_parser(mptr, NULL, *mime_download, NULL, NULL, NULL, 0);
1467 /* If there's no file open by this time, the requested
1468 * section wasn't found, so print an error
1470 if (CC->download_fp == NULL) {
1471 if (do_proto) cprintf(
1472 "%d Section %s not found.\n",
1473 ERROR + FILE_NOT_FOUND,
1474 CC->download_desired_section);
1477 return((CC->download_fp != NULL) ? om_ok : om_mime_error);
1480 /* now for the user-mode message reading loops */
1481 if (do_proto) cprintf("%d msg:\n", LISTING_FOLLOWS);
1483 /* Does the caller want to skip the headers? */
1484 if (headers_only == HEADERS_NONE) goto START_TEXT;
1486 /* Tell the client which format type we're using. */
1487 if ( (mode == MT_CITADEL) && (do_proto) ) {
1488 cprintf("type=%d\n", TheMessage->cm_format_type);
1491 /* nhdr=yes means that we're only displaying headers, no body */
1492 if ( (TheMessage->cm_anon_type == MES_ANONONLY)
1493 && (mode == MT_CITADEL)
1496 cprintf("nhdr=yes\n");
1499 /* begin header processing loop for Citadel message format */
1501 if ((mode == MT_CITADEL) || (mode == MT_MIME)) {
1503 safestrncpy(display_name, "<unknown>", sizeof display_name);
1504 if (TheMessage->cm_fields['A']) {
1505 strcpy(buf, TheMessage->cm_fields['A']);
1506 if (TheMessage->cm_anon_type == MES_ANONONLY) {
1507 safestrncpy(display_name, "****", sizeof display_name);
1509 else if (TheMessage->cm_anon_type == MES_ANONOPT) {
1510 safestrncpy(display_name, "anonymous", sizeof display_name);
1513 safestrncpy(display_name, buf, sizeof display_name);
1515 if ((is_room_aide())
1516 && ((TheMessage->cm_anon_type == MES_ANONONLY)
1517 || (TheMessage->cm_anon_type == MES_ANONOPT))) {
1518 size_t tmp = strlen(display_name);
1519 snprintf(&display_name[tmp],
1520 sizeof display_name - tmp,
1525 /* Don't show Internet address for users on the
1526 * local Citadel network.
1529 if (TheMessage->cm_fields['N'] != NULL)
1530 if (strlen(TheMessage->cm_fields['N']) > 0)
1531 if (haschar(TheMessage->cm_fields['N'], '.') == 0) {
1535 /* Now spew the header fields in the order we like them. */
1536 safestrncpy(allkeys, FORDER, sizeof allkeys);
1537 for (i=0; i<strlen(allkeys); ++i) {
1538 k = (int) allkeys[i];
1540 if ( (TheMessage->cm_fields[k] != NULL)
1541 && (msgkeys[k] != NULL) ) {
1543 if (do_proto) cprintf("%s=%s\n",
1547 else if ((k == 'F') && (suppress_f)) {
1550 /* Masquerade display name if needed */
1552 if (do_proto) cprintf("%s=%s\n",
1554 TheMessage->cm_fields[k]
1563 /* begin header processing loop for RFC822 transfer format */
1568 strcpy(snode, NODENAME);
1569 strcpy(lnode, HUMANNODE);
1570 if (mode == MT_RFC822) {
1571 for (i = 0; i < 256; ++i) {
1572 if (TheMessage->cm_fields[i]) {
1573 mptr = TheMessage->cm_fields[i];
1576 safestrncpy(luser, mptr, sizeof luser);
1577 safestrncpy(suser, mptr, sizeof suser);
1579 else if (i == 'Y') {
1580 cprintf("CC: %s%s", mptr, nl);
1582 else if (i == 'U') {
1583 cprintf("Subject: %s%s", mptr, nl);
1587 safestrncpy(mid, mptr, sizeof mid);
1589 safestrncpy(lnode, mptr, sizeof lnode);
1591 safestrncpy(fuser, mptr, sizeof fuser);
1592 /* else if (i == 'O')
1593 cprintf("X-Citadel-Room: %s%s",
1596 safestrncpy(snode, mptr, sizeof snode);
1598 cprintf("To: %s%s", mptr, nl);
1599 else if (i == 'T') {
1600 datestring(datestamp, sizeof datestamp,
1601 atol(mptr), DATESTRING_RFC822);
1602 cprintf("Date: %s%s", datestamp, nl);
1606 if (subject_found == 0) {
1607 cprintf("Subject: (no subject)%s", nl);
1611 for (i=0; i<strlen(suser); ++i) {
1612 suser[i] = tolower(suser[i]);
1613 if (!isalnum(suser[i])) suser[i]='_';
1616 if (mode == MT_RFC822) {
1617 if (!strcasecmp(snode, NODENAME)) {
1618 safestrncpy(snode, FQDN, sizeof snode);
1621 /* Construct a fun message id */
1622 cprintf("Message-ID: <%s", mid);
1623 if (strchr(mid, '@')==NULL) {
1624 cprintf("@%s", snode);
1628 if (!is_room_aide() && (TheMessage->cm_anon_type == MES_ANONONLY)) {
1629 cprintf("From: \"----\" <x@x.org>%s", nl);
1631 else if (!is_room_aide() && (TheMessage->cm_anon_type == MES_ANONOPT)) {
1632 cprintf("From: \"anonymous\" <x@x.org>%s", nl);
1634 else if (strlen(fuser) > 0) {
1635 cprintf("From: \"%s\" <%s>%s", luser, fuser, nl);
1638 cprintf("From: \"%s\" <%s@%s>%s", luser, suser, snode, nl);
1641 cprintf("Organization: %s%s", lnode, nl);
1643 /* Blank line signifying RFC822 end-of-headers */
1644 if (TheMessage->cm_format_type != FMT_RFC822) {
1649 /* end header processing loop ... at this point, we're in the text */
1651 if (headers_only == HEADERS_FAST) goto DONE;
1652 mptr = TheMessage->cm_fields['M'];
1654 /* Tell the client about the MIME parts in this message */
1655 if (TheMessage->cm_format_type == FMT_RFC822) {
1656 if ( (mode == MT_CITADEL) || (mode == MT_MIME) ) {
1657 memset(&ma, 0, sizeof(struct ma_info));
1658 mime_parser(mptr, NULL,
1659 (do_proto ? *list_this_part : NULL),
1660 (do_proto ? *list_this_pref : NULL),
1661 (do_proto ? *list_this_suff : NULL),
1664 else if (mode == MT_RFC822) { /* unparsed RFC822 dump */
1665 char *start_of_text = NULL;
1666 start_of_text = strstr(mptr, "\n\r\n");
1667 if (start_of_text == NULL) start_of_text = strstr(mptr, "\n\n");
1668 if (start_of_text == NULL) start_of_text = mptr;
1670 start_of_text = strstr(start_of_text, "\n");
1672 while (ch=*mptr, ch!=0) {
1676 else switch(headers_only) {
1678 if (mptr >= start_of_text) {
1679 if (ch == 10) cprintf("%s", nl);
1680 else cprintf("%c", ch);
1684 if (mptr < start_of_text) {
1685 if (ch == 10) cprintf("%s", nl);
1686 else cprintf("%c", ch);
1690 if (ch == 10) cprintf("%s", nl);
1691 else cprintf("%c", ch);
1700 if (headers_only == HEADERS_ONLY) {
1704 /* signify start of msg text */
1705 if ( (mode == MT_CITADEL) || (mode == MT_MIME) ) {
1706 if (do_proto) cprintf("text\n");
1709 /* If the format type on disk is 1 (fixed-format), then we want
1710 * everything to be output completely literally ... regardless of
1711 * what message transfer format is in use.
1713 if (TheMessage->cm_format_type == FMT_FIXED) {
1714 if (mode == MT_MIME) {
1715 cprintf("Content-type: text/plain\n\n");
1718 while (ch = *mptr++, ch > 0) {
1721 if ((ch == 10) || (strlen(buf) > 250)) {
1722 cprintf("%s%s", buf, nl);
1725 buf[strlen(buf) + 1] = 0;
1726 buf[strlen(buf)] = ch;
1729 if (strlen(buf) > 0)
1730 cprintf("%s%s", buf, nl);
1733 /* If the message on disk is format 0 (Citadel vari-format), we
1734 * output using the formatter at 80 columns. This is the final output
1735 * form if the transfer format is RFC822, but if the transfer format
1736 * is Citadel proprietary, it'll still work, because the indentation
1737 * for new paragraphs is correct and the client will reformat the
1738 * message to the reader's screen width.
1740 if (TheMessage->cm_format_type == FMT_CITADEL) {
1741 if (mode == MT_MIME) {
1742 cprintf("Content-type: text/x-citadel-variformat\n\n");
1744 memfmout(mptr, 0, nl);
1747 /* If the message on disk is format 4 (MIME), we've gotta hand it
1748 * off to the MIME parser. The client has already been told that
1749 * this message is format 1 (fixed format), so the callback function
1750 * we use will display those parts as-is.
1752 if (TheMessage->cm_format_type == FMT_RFC822) {
1753 memset(&ma, 0, sizeof(struct ma_info));
1755 if (mode == MT_MIME) {
1756 ma.use_fo_hooks = 0;
1757 strcpy(ma.chosen_part, "1");
1758 mime_parser(mptr, NULL,
1759 *choose_preferred, *fixed_output_pre,
1760 *fixed_output_post, (void *)&ma, 0);
1761 mime_parser(mptr, NULL,
1762 *output_preferred, NULL, NULL, (void *)&ma, 0);
1765 ma.use_fo_hooks = 1;
1766 mime_parser(mptr, NULL,
1767 *fixed_output, *fixed_output_pre,
1768 *fixed_output_post, (void *)&ma, 0);
1773 DONE: /* now we're done */
1774 if (do_proto) cprintf("000\n");
1781 * display a message (mode 0 - Citadel proprietary)
1783 void cmd_msg0(char *cmdbuf)
1786 int headers_only = HEADERS_ALL;
1788 msgid = extract_long(cmdbuf, 0);
1789 headers_only = extract_int(cmdbuf, 1);
1791 CtdlOutputMsg(msgid, MT_CITADEL, headers_only, 1, 0, NULL);
1797 * display a message (mode 2 - RFC822)
1799 void cmd_msg2(char *cmdbuf)
1802 int headers_only = HEADERS_ALL;
1804 msgid = extract_long(cmdbuf, 0);
1805 headers_only = extract_int(cmdbuf, 1);
1807 CtdlOutputMsg(msgid, MT_RFC822, headers_only, 1, 1, NULL);
1813 * display a message (mode 3 - IGnet raw format - internal programs only)
1815 void cmd_msg3(char *cmdbuf)
1818 struct CtdlMessage *msg;
1821 if (CC->internal_pgm == 0) {
1822 cprintf("%d This command is for internal programs only.\n",
1823 ERROR + HIGHER_ACCESS_REQUIRED);
1827 msgnum = extract_long(cmdbuf, 0);
1828 msg = CtdlFetchMessage(msgnum, 1);
1830 cprintf("%d Message %ld not found.\n",
1831 ERROR + MESSAGE_NOT_FOUND, msgnum);
1835 serialize_message(&smr, msg);
1836 CtdlFreeMessage(msg);
1839 cprintf("%d Unable to serialize message\n",
1840 ERROR + INTERNAL_ERROR);
1844 cprintf("%d %ld\n", BINARY_FOLLOWS, (long)smr.len);
1845 client_write((char *)smr.ser, (int)smr.len);
1852 * Display a message using MIME content types
1854 void cmd_msg4(char *cmdbuf)
1859 msgid = extract_long(cmdbuf, 0);
1860 extract_token(section, cmdbuf, 1, '|', sizeof section);
1861 CtdlOutputMsg(msgid, MT_MIME, 0, 1, 0, (section[0] ? section : NULL) );
1867 * Client tells us its preferred message format(s)
1869 void cmd_msgp(char *cmdbuf)
1871 safestrncpy(CC->preferred_formats, cmdbuf,
1872 sizeof(CC->preferred_formats));
1873 cprintf("%d ok\n", CIT_OK);
1878 * Open a component of a MIME message as a download file
1880 void cmd_opna(char *cmdbuf)
1883 char desired_section[128];
1885 msgid = extract_long(cmdbuf, 0);
1886 extract_token(desired_section, cmdbuf, 1, '|', sizeof desired_section);
1887 safestrncpy(CC->download_desired_section, desired_section,
1888 sizeof CC->download_desired_section);
1889 CtdlOutputMsg(msgid, MT_DOWNLOAD, 0, 1, 1, NULL);
1893 * Save one or more message pointers into a specified room
1894 * (Returns 0 for success, nonzero for failure)
1895 * roomname may be NULL to use the current room
1897 * Note that the 'supplied_msg' field may be set to NULL, in which case
1898 * the message will be fetched from disk, by number, if we need to perform
1899 * replication checks. This adds an additional database read, so if the
1900 * caller already has the message in memory then it should be supplied. (Obviously
1901 * this mode of operation only works if we're saving a single message.)
1903 int CtdlSaveMsgPointersInRoom(char *roomname, long newmsgidlist[], int num_newmsgs,
1904 int do_repl_check, struct CtdlMessage *supplied_msg)
1907 char hold_rm[ROOMNAMELEN];
1908 struct cdbdata *cdbfr;
1911 long highest_msg = 0L;
1914 struct CtdlMessage *msg = NULL;
1916 long *msgs_to_be_merged = NULL;
1917 int num_msgs_to_be_merged = 0;
1920 "CtdlSaveMsgPointersInRoom(room=%s, num_msgs=%d, repl=%d)\n",
1921 roomname, num_newmsgs, do_repl_check);
1923 strcpy(hold_rm, CC->room.QRname);
1926 if (newmsgidlist == NULL) return(ERROR + INTERNAL_ERROR);
1927 if (num_newmsgs < 1) return(ERROR + INTERNAL_ERROR);
1928 if (num_newmsgs > 1) supplied_msg = NULL;
1930 /* Now the regular stuff */
1931 if (lgetroom(&CC->room,
1932 ((roomname != NULL) ? roomname : CC->room.QRname) )
1934 lprintf(CTDL_ERR, "No such room <%s>\n", roomname);
1935 return(ERROR + ROOM_NOT_FOUND);
1939 msgs_to_be_merged = malloc(sizeof(long) * num_newmsgs);
1940 num_msgs_to_be_merged = 0;
1943 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long));
1944 if (cdbfr == NULL) {
1948 msglist = (long *) cdbfr->ptr;
1949 cdbfr->ptr = NULL; /* CtdlSaveMsgPointerInRoom() now owns this memory */
1950 num_msgs = cdbfr->len / sizeof(long);
1955 /* Create a list of msgid's which were supplied by the caller, but do
1956 * not already exist in the target room. It is absolutely taboo to
1957 * have more than one reference to the same message in a room.
1959 for (i=0; i<num_newmsgs; ++i) {
1961 if (num_msgs > 0) for (j=0; j<num_msgs; ++j) {
1962 if (msglist[j] == newmsgidlist[i]) {
1967 msgs_to_be_merged[num_msgs_to_be_merged++] = newmsgidlist[i];
1971 lprintf(9, "%d unique messages to be merged\n", num_msgs_to_be_merged);
1974 * Now merge the new messages
1976 msglist = realloc(msglist, (sizeof(long) * (num_msgs + num_msgs_to_be_merged)) );
1977 if (msglist == NULL) {
1978 lprintf(CTDL_ALERT, "ERROR: can't realloc message list!\n");
1980 memcpy(&msglist[num_msgs], msgs_to_be_merged, (sizeof(long) * num_msgs_to_be_merged) );
1981 num_msgs += num_msgs_to_be_merged;
1983 /* Sort the message list, so all the msgid's are in order */
1984 num_msgs = sort_msglist(msglist, num_msgs);
1986 /* Determine the highest message number */
1987 highest_msg = msglist[num_msgs - 1];
1989 /* Write it back to disk. */
1990 cdb_store(CDB_MSGLISTS, &CC->room.QRnumber, (int)sizeof(long),
1991 msglist, (int)(num_msgs * sizeof(long)));
1993 /* Free up the memory we used. */
1996 /* Update the highest-message pointer and unlock the room. */
1997 CC->room.QRhighest = highest_msg;
1998 lputroom(&CC->room);
2000 /* Perform replication checks if necessary */
2001 if ( (DoesThisRoomNeedEuidIndexing(&CC->room)) && (do_repl_check) ) {
2002 lprintf(CTDL_DEBUG, "CtdlSaveMsgPointerInRoom() doing repl checks\n");
2004 for (i=0; i<num_msgs_to_be_merged; ++i) {
2005 msgid = msgs_to_be_merged[i];
2007 if (supplied_msg != NULL) {
2011 msg = CtdlFetchMessage(msgid, 0);
2015 ReplicationChecks(msg);
2017 /* If the message has an Exclusive ID, index that... */
2018 if (msg->cm_fields['E'] != NULL) {
2019 index_message_by_euid(msg->cm_fields['E'], &CC->room, msgid);
2022 /* Free up the memory we may have allocated */
2023 if (msg != supplied_msg) {
2024 CtdlFreeMessage(msg);
2032 lprintf(CTDL_DEBUG, "CtdlSaveMsgPointerInRoom() skips repl checks\n");
2035 /* Go back to the room we were in before we wandered here... */
2036 getroom(&CC->room, hold_rm);
2038 /* Bump the reference count for all messages which were merged */
2039 for (i=0; i<num_msgs_to_be_merged; ++i) {
2040 AdjRefCount(msgs_to_be_merged[i], +1);
2043 /* Free up memory... */
2044 if (msgs_to_be_merged != NULL) {
2045 free(msgs_to_be_merged);
2048 /* Return success. */
2054 * This is the same as CtdlSaveMsgPointersInRoom() but it only accepts
2057 int CtdlSaveMsgPointerInRoom(char *roomname, long msgid,
2058 int do_repl_check, struct CtdlMessage *supplied_msg)
2060 return CtdlSaveMsgPointersInRoom(roomname, &msgid, 1, do_repl_check, supplied_msg);
2067 * Message base operation to save a new message to the message store
2068 * (returns new message number)
2070 * This is the back end for CtdlSubmitMsg() and should not be directly
2071 * called by server-side modules.
2074 long send_message(struct CtdlMessage *msg) {
2082 /* Get a new message number */
2083 newmsgid = get_new_message_number();
2084 snprintf(msgidbuf, sizeof msgidbuf, "%010ld@%s", newmsgid, config.c_fqdn);
2086 /* Generate an ID if we don't have one already */
2087 if (msg->cm_fields['I']==NULL) {
2088 msg->cm_fields['I'] = strdup(msgidbuf);
2091 /* If the message is big, set its body aside for storage elsewhere */
2092 if (msg->cm_fields['M'] != NULL) {
2093 if (strlen(msg->cm_fields['M']) > BIGMSG) {
2095 holdM = msg->cm_fields['M'];
2096 msg->cm_fields['M'] = NULL;
2100 /* Serialize our data structure for storage in the database */
2101 serialize_message(&smr, msg);
2104 msg->cm_fields['M'] = holdM;
2108 cprintf("%d Unable to serialize message\n",
2109 ERROR + INTERNAL_ERROR);
2113 /* Write our little bundle of joy into the message base */
2114 if (cdb_store(CDB_MSGMAIN, &newmsgid, (int)sizeof(long),
2115 smr.ser, smr.len) < 0) {
2116 lprintf(CTDL_ERR, "Can't store message\n");
2120 cdb_store(CDB_BIGMSGS,
2130 /* Free the memory we used for the serialized message */
2133 /* Return the *local* message ID to the caller
2134 * (even if we're storing an incoming network message)
2142 * Serialize a struct CtdlMessage into the format used on disk and network.
2144 * This function loads up a "struct ser_ret" (defined in server.h) which
2145 * contains the length of the serialized message and a pointer to the
2146 * serialized message in memory. THE LATTER MUST BE FREED BY THE CALLER.
2148 void serialize_message(struct ser_ret *ret, /* return values */
2149 struct CtdlMessage *msg) /* unserialized msg */
2151 size_t wlen, fieldlen;
2153 static char *forder = FORDER;
2156 * Check for valid message format
2158 if (is_valid_message(msg) == 0) {
2159 lprintf(CTDL_ERR, "serialize_message() aborting due to invalid message\n");
2166 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL)
2167 ret->len = ret->len +
2168 strlen(msg->cm_fields[(int)forder[i]]) + 2;
2170 ret->ser = malloc(ret->len);
2171 if (ret->ser == NULL) {
2172 lprintf(CTDL_ERR, "serialize_message() malloc(%ld) failed: %s\n",
2173 (long)ret->len, strerror(errno));
2180 ret->ser[1] = msg->cm_anon_type;
2181 ret->ser[2] = msg->cm_format_type;
2184 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL) {
2185 fieldlen = strlen(msg->cm_fields[(int)forder[i]]);
2186 ret->ser[wlen++] = (char)forder[i];
2187 safestrncpy((char *)&ret->ser[wlen], msg->cm_fields[(int)forder[i]], fieldlen+1);
2188 wlen = wlen + fieldlen + 1;
2190 if (ret->len != wlen) lprintf(CTDL_ERR, "ERROR: len=%ld wlen=%ld\n",
2191 (long)ret->len, (long)wlen);
2199 * Check to see if any messages already exist in the current room which
2200 * carry the same Exclusive ID as this one. If any are found, delete them.
2202 void ReplicationChecks(struct CtdlMessage *msg) {
2203 long old_msgnum = (-1L);
2205 if (DoesThisRoomNeedEuidIndexing(&CC->room) == 0) return;
2207 lprintf(CTDL_DEBUG, "Performing replication checks in <%s>\n",
2210 /* No exclusive id? Don't do anything. */
2211 if (msg == NULL) return;
2212 if (msg->cm_fields['E'] == NULL) return;
2213 if (strlen(msg->cm_fields['E']) == 0) return;
2214 /*lprintf(CTDL_DEBUG, "Exclusive ID: <%s> for room <%s>\n",
2215 msg->cm_fields['E'], CC->room.QRname);*/
2217 old_msgnum = locate_message_by_euid(msg->cm_fields['E'], &CC->room);
2218 if (old_msgnum > 0L) {
2219 lprintf(CTDL_DEBUG, "ReplicationChecks() replacing message %ld\n", old_msgnum);
2220 CtdlDeleteMessages(CC->room.QRname, &old_msgnum, 1, "", 0);
2227 * Save a message to disk and submit it into the delivery system.
2229 long CtdlSubmitMsg(struct CtdlMessage *msg, /* message to save */
2230 struct recptypes *recps, /* recipients (if mail) */
2231 char *force /* force a particular room? */
2233 char submit_filename[128];
2234 char generated_timestamp[32];
2235 char hold_rm[ROOMNAMELEN];
2236 char actual_rm[ROOMNAMELEN];
2237 char force_room[ROOMNAMELEN];
2238 char content_type[SIZ]; /* We have to learn this */
2239 char recipient[SIZ];
2242 struct ctdluser userbuf;
2244 struct MetaData smi;
2245 FILE *network_fp = NULL;
2246 static int seqnum = 1;
2247 struct CtdlMessage *imsg = NULL;
2250 char *hold_R, *hold_D;
2251 char *collected_addresses = NULL;
2252 struct addresses_to_be_filed *aptr = NULL;
2253 char *saved_rfc822_version = NULL;
2254 int qualified_for_journaling = 0;
2256 lprintf(CTDL_DEBUG, "CtdlSubmitMsg() called\n");
2257 if (is_valid_message(msg) == 0) return(-1); /* self check */
2259 /* If this message has no timestamp, we take the liberty of
2260 * giving it one, right now.
2262 if (msg->cm_fields['T'] == NULL) {
2263 snprintf(generated_timestamp, sizeof generated_timestamp, "%ld", (long)time(NULL));
2264 msg->cm_fields['T'] = strdup(generated_timestamp);
2267 /* If this message has no path, we generate one.
2269 if (msg->cm_fields['P'] == NULL) {
2270 if (msg->cm_fields['A'] != NULL) {
2271 msg->cm_fields['P'] = strdup(msg->cm_fields['A']);
2272 for (a=0; a<strlen(msg->cm_fields['P']); ++a) {
2273 if (isspace(msg->cm_fields['P'][a])) {
2274 msg->cm_fields['P'][a] = ' ';
2279 msg->cm_fields['P'] = strdup("unknown");
2283 if (force == NULL) {
2284 strcpy(force_room, "");
2287 strcpy(force_room, force);
2290 /* Learn about what's inside, because it's what's inside that counts */
2291 if (msg->cm_fields['M'] == NULL) {
2292 lprintf(CTDL_ERR, "ERROR: attempt to save message with NULL body\n");
2296 switch (msg->cm_format_type) {
2298 strcpy(content_type, "text/x-citadel-variformat");
2301 strcpy(content_type, "text/plain");
2304 strcpy(content_type, "text/plain");
2305 mptr = bmstrcasestr(msg->cm_fields['M'], "Content-type: ");
2307 safestrncpy(content_type, &mptr[14],
2308 sizeof content_type);
2309 for (a = 0; a < strlen(content_type); ++a) {
2310 if ((content_type[a] == ';')
2311 || (content_type[a] == ' ')
2312 || (content_type[a] == 13)
2313 || (content_type[a] == 10)) {
2314 content_type[a] = 0;
2320 /* Goto the correct room */
2321 lprintf(CTDL_DEBUG, "Selected room %s\n", (recps) ? CC->room.QRname : SENTITEMS);
2322 strcpy(hold_rm, CC->room.QRname);
2323 strcpy(actual_rm, CC->room.QRname);
2324 if (recps != NULL) {
2325 strcpy(actual_rm, SENTITEMS);
2328 /* If the user is a twit, move to the twit room for posting */
2330 if (CC->user.axlevel == 2) {
2331 strcpy(hold_rm, actual_rm);
2332 strcpy(actual_rm, config.c_twitroom);
2333 lprintf(CTDL_DEBUG, "Diverting to twit room\n");
2337 /* ...or if this message is destined for Aide> then go there. */
2338 if (strlen(force_room) > 0) {
2339 strcpy(actual_rm, force_room);
2342 lprintf(CTDL_DEBUG, "Final selection: %s\n", actual_rm);
2343 if (strcasecmp(actual_rm, CC->room.QRname)) {
2344 /* getroom(&CC->room, actual_rm); */
2345 usergoto(actual_rm, 0, 1, NULL, NULL);
2349 * If this message has no O (room) field, generate one.
2351 if (msg->cm_fields['O'] == NULL) {
2352 msg->cm_fields['O'] = strdup(CC->room.QRname);
2355 /* Perform "before save" hooks (aborting if any return nonzero) */
2356 lprintf(CTDL_DEBUG, "Performing before-save hooks\n");
2357 if (PerformMessageHooks(msg, EVT_BEFORESAVE) > 0) return(-3);
2360 * If this message has an Exclusive ID, and the room is replication
2361 * checking enabled, then do replication checks.
2363 if (DoesThisRoomNeedEuidIndexing(&CC->room)) {
2364 ReplicationChecks(msg);
2367 /* Save it to disk */
2368 lprintf(CTDL_DEBUG, "Saving to disk\n");
2369 newmsgid = send_message(msg);
2370 if (newmsgid <= 0L) return(-5);
2372 /* Write a supplemental message info record. This doesn't have to
2373 * be a critical section because nobody else knows about this message
2376 lprintf(CTDL_DEBUG, "Creating MetaData record\n");
2377 memset(&smi, 0, sizeof(struct MetaData));
2378 smi.meta_msgnum = newmsgid;
2379 smi.meta_refcount = 0;
2380 safestrncpy(smi.meta_content_type, content_type,
2381 sizeof smi.meta_content_type);
2384 * Measure how big this message will be when rendered as RFC822.
2385 * We do this for two reasons:
2386 * 1. We need the RFC822 length for the new metadata record, so the
2387 * POP and IMAP services don't have to calculate message lengths
2388 * while the user is waiting (multiplied by potentially hundreds
2389 * or thousands of messages).
2390 * 2. If journaling is enabled, we will need an RFC822 version of the
2391 * message to attach to the journalized copy.
2393 if (CC->redirect_buffer != NULL) {
2394 lprintf(CTDL_ALERT, "CC->redirect_buffer is not NULL during message submission!\n");
2397 CC->redirect_buffer = malloc(SIZ);
2398 CC->redirect_len = 0;
2399 CC->redirect_alloc = SIZ;
2400 CtdlOutputPreLoadedMsg(msg, MT_RFC822, HEADERS_ALL, 0, 1);
2401 smi.meta_rfc822_length = CC->redirect_len;
2402 saved_rfc822_version = CC->redirect_buffer;
2403 CC->redirect_buffer = NULL;
2404 CC->redirect_len = 0;
2405 CC->redirect_alloc = 0;
2409 /* Now figure out where to store the pointers */
2410 lprintf(CTDL_DEBUG, "Storing pointers\n");
2412 /* If this is being done by the networker delivering a private
2413 * message, we want to BYPASS saving the sender's copy (because there
2414 * is no local sender; it would otherwise go to the Trashcan).
2416 if ((!CC->internal_pgm) || (recps == NULL)) {
2417 if (CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 1, msg) != 0) {
2418 lprintf(CTDL_ERR, "ERROR saving message pointer!\n");
2419 CtdlSaveMsgPointerInRoom(config.c_aideroom, newmsgid, 0, msg);
2423 /* For internet mail, drop a copy in the outbound queue room */
2425 if (recps->num_internet > 0) {
2426 CtdlSaveMsgPointerInRoom(SMTP_SPOOLOUT_ROOM, newmsgid, 0, msg);
2429 /* If other rooms are specified, drop them there too. */
2431 if (recps->num_room > 0)
2432 for (i=0; i<num_tokens(recps->recp_room, '|'); ++i) {
2433 extract_token(recipient, recps->recp_room, i,
2434 '|', sizeof recipient);
2435 lprintf(CTDL_DEBUG, "Delivering to room <%s>\n", recipient);
2436 CtdlSaveMsgPointerInRoom(recipient, newmsgid, 0, msg);
2439 /* Bump this user's messages posted counter. */
2440 lprintf(CTDL_DEBUG, "Updating user\n");
2441 lgetuser(&CC->user, CC->curr_user);
2442 CC->user.posted = CC->user.posted + 1;
2443 lputuser(&CC->user);
2445 /* If this is private, local mail, make a copy in the
2446 * recipient's mailbox and bump the reference count.
2449 if (recps->num_local > 0)
2450 for (i=0; i<num_tokens(recps->recp_local, '|'); ++i) {
2451 extract_token(recipient, recps->recp_local, i,
2452 '|', sizeof recipient);
2453 lprintf(CTDL_DEBUG, "Delivering private local mail to <%s>\n",
2455 if (getuser(&userbuf, recipient) == 0) {
2456 MailboxName(actual_rm, sizeof actual_rm,
2457 &userbuf, MAILROOM);
2458 CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 0, msg);
2459 BumpNewMailCounter(userbuf.usernum);
2462 lprintf(CTDL_DEBUG, "No user <%s>\n", recipient);
2463 CtdlSaveMsgPointerInRoom(config.c_aideroom,
2468 /* Perform "after save" hooks */
2469 lprintf(CTDL_DEBUG, "Performing after-save hooks\n");
2470 PerformMessageHooks(msg, EVT_AFTERSAVE);
2472 /* For IGnet mail, we have to save a new copy into the spooler for
2473 * each recipient, with the R and D fields set to the recipient and
2474 * destination-node. This has two ugly side effects: all other
2475 * recipients end up being unlisted in this recipient's copy of the
2476 * message, and it has to deliver multiple messages to the same
2477 * node. We'll revisit this again in a year or so when everyone has
2478 * a network spool receiver that can handle the new style messages.
2481 if (recps->num_ignet > 0)
2482 for (i=0; i<num_tokens(recps->recp_ignet, '|'); ++i) {
2483 extract_token(recipient, recps->recp_ignet, i,
2484 '|', sizeof recipient);
2486 hold_R = msg->cm_fields['R'];
2487 hold_D = msg->cm_fields['D'];
2488 msg->cm_fields['R'] = malloc(SIZ);
2489 msg->cm_fields['D'] = malloc(128);
2490 extract_token(msg->cm_fields['R'], recipient, 0, '@', SIZ);
2491 extract_token(msg->cm_fields['D'], recipient, 1, '@', 128);
2493 serialize_message(&smr, msg);
2495 snprintf(submit_filename, sizeof submit_filename,
2496 "%s/netmail.%04lx.%04x.%04x",
2498 (long) getpid(), CC->cs_pid, ++seqnum);
2499 network_fp = fopen(submit_filename, "wb+");
2500 if (network_fp != NULL) {
2501 fwrite(smr.ser, smr.len, 1, network_fp);
2507 free(msg->cm_fields['R']);
2508 free(msg->cm_fields['D']);
2509 msg->cm_fields['R'] = hold_R;
2510 msg->cm_fields['D'] = hold_D;
2513 /* Go back to the room we started from */
2514 lprintf(CTDL_DEBUG, "Returning to original room %s\n", hold_rm);
2515 if (strcasecmp(hold_rm, CC->room.QRname))
2516 /* getroom(&CC->room, hold_rm); */
2517 usergoto(hold_rm, 0, 1, NULL, NULL);
2519 /* For internet mail, generate delivery instructions.
2520 * Yes, this is recursive. Deal with it. Infinite recursion does
2521 * not happen because the delivery instructions message does not
2522 * contain a recipient.
2525 if (recps->num_internet > 0) {
2526 lprintf(CTDL_DEBUG, "Generating delivery instructions\n");
2527 instr = malloc(SIZ * 2);
2528 snprintf(instr, SIZ * 2,
2529 "Content-type: %s\n\nmsgid|%ld\nsubmitted|%ld\n"
2531 SPOOLMIME, newmsgid, (long)time(NULL),
2532 msg->cm_fields['A'], msg->cm_fields['N']
2535 for (i=0; i<num_tokens(recps->recp_internet, '|'); ++i) {
2536 size_t tmp = strlen(instr);
2537 extract_token(recipient, recps->recp_internet,
2538 i, '|', sizeof recipient);
2539 snprintf(&instr[tmp], SIZ * 2 - tmp,
2540 "remote|%s|0||\n", recipient);
2543 imsg = malloc(sizeof(struct CtdlMessage));
2544 memset(imsg, 0, sizeof(struct CtdlMessage));
2545 imsg->cm_magic = CTDLMESSAGE_MAGIC;
2546 imsg->cm_anon_type = MES_NORMAL;
2547 imsg->cm_format_type = FMT_RFC822;
2548 imsg->cm_fields['A'] = strdup("Citadel");
2549 imsg->cm_fields['J'] = strdup("do not journal");
2550 imsg->cm_fields['M'] = instr;
2551 CtdlSubmitMsg(imsg, NULL, SMTP_SPOOLOUT_ROOM);
2552 CtdlFreeMessage(imsg);
2556 * Any addresses to harvest for someone's address book?
2558 if ( (CC->logged_in) && (recps != NULL) ) {
2559 collected_addresses = harvest_collected_addresses(msg);
2562 if (collected_addresses != NULL) {
2563 begin_critical_section(S_ATBF);
2564 aptr = (struct addresses_to_be_filed *)
2565 malloc(sizeof(struct addresses_to_be_filed));
2567 MailboxName(actual_rm, sizeof actual_rm,
2568 &CC->user, USERCONTACTSROOM);
2569 aptr->roomname = strdup(actual_rm);
2570 aptr->collected_addresses = collected_addresses;
2572 end_critical_section(S_ATBF);
2576 * Determine whether this message qualifies for journaling.
2578 if (msg->cm_fields['J'] != NULL) {
2579 qualified_for_journaling = 0;
2582 if (recps == NULL) {
2583 qualified_for_journaling = config.c_journal_pubmsgs;
2585 else if (recps->num_local + recps->num_ignet + recps->num_internet > 0) {
2586 qualified_for_journaling = config.c_journal_email;
2589 qualified_for_journaling = config.c_journal_pubmsgs;
2594 * Do we have to perform journaling? If so, hand off the saved
2595 * RFC822 version will be handed off to the journaler for background
2596 * submit. Otherwise, we have to free the memory ourselves.
2598 if (saved_rfc822_version != NULL) {
2599 if (qualified_for_journaling) {
2600 JournalBackgroundSubmit(msg, saved_rfc822_version, recps);
2603 free(saved_rfc822_version);
2616 * Convenience function for generating small administrative messages.
2618 void quickie_message(char *from, char *to, char *room, char *text,
2619 int format_type, char *subject)
2621 struct CtdlMessage *msg;
2622 struct recptypes *recp = NULL;
2624 msg = malloc(sizeof(struct CtdlMessage));
2625 memset(msg, 0, sizeof(struct CtdlMessage));
2626 msg->cm_magic = CTDLMESSAGE_MAGIC;
2627 msg->cm_anon_type = MES_NORMAL;
2628 msg->cm_format_type = format_type;
2629 msg->cm_fields['A'] = strdup(from);
2630 if (room != NULL) msg->cm_fields['O'] = strdup(room);
2631 msg->cm_fields['N'] = strdup(NODENAME);
2633 msg->cm_fields['R'] = strdup(to);
2634 recp = validate_recipients(to);
2636 if (subject != NULL) {
2637 msg->cm_fields['U'] = strdup(subject);
2639 msg->cm_fields['M'] = strdup(text);
2641 CtdlSubmitMsg(msg, recp, room);
2642 CtdlFreeMessage(msg);
2643 if (recp != NULL) free(recp);
2649 * Back end function used by CtdlMakeMessage() and similar functions
2651 char *CtdlReadMessageBody(char *terminator, /* token signalling EOT */
2652 size_t maxlen, /* maximum message length */
2653 char *exist, /* if non-null, append to it;
2654 exist is ALWAYS freed */
2655 int crlf /* CRLF newlines instead of LF */
2659 size_t message_len = 0;
2660 size_t buffer_len = 0;
2666 if (exist == NULL) {
2673 message_len = strlen(exist);
2674 buffer_len = message_len + 4096;
2675 m = realloc(exist, buffer_len);
2682 /* flush the input if we have nowhere to store it */
2687 /* read in the lines of message text one by one */
2689 if (client_getln(buf, (sizeof buf - 3)) < 1) finished = 1;
2690 if (!strcmp(buf, terminator)) finished = 1;
2692 strcat(buf, "\r\n");
2698 if ( (!flushing) && (!finished) ) {
2699 /* Measure the line */
2700 linelen = strlen(buf);
2702 /* augment the buffer if we have to */
2703 if ((message_len + linelen) >= buffer_len) {
2704 ptr = realloc(m, (buffer_len * 2) );
2705 if (ptr == NULL) { /* flush if can't allocate */
2708 buffer_len = (buffer_len * 2);
2710 lprintf(CTDL_DEBUG, "buffer_len is now %ld\n", (long)buffer_len);
2714 /* Add the new line to the buffer. NOTE: this loop must avoid
2715 * using functions like strcat() and strlen() because they
2716 * traverse the entire buffer upon every call, and doing that
2717 * for a multi-megabyte message slows it down beyond usability.
2719 strcpy(&m[message_len], buf);
2720 message_len += linelen;
2723 /* if we've hit the max msg length, flush the rest */
2724 if (message_len >= maxlen) flushing = 1;
2726 } while (!finished);
2734 * Build a binary message to be saved on disk.
2735 * (NOTE: if you supply 'preformatted_text', the buffer you give it
2736 * will become part of the message. This means you are no longer
2737 * responsible for managing that memory -- it will be freed along with
2738 * the rest of the fields when CtdlFreeMessage() is called.)
2741 struct CtdlMessage *CtdlMakeMessage(
2742 struct ctdluser *author, /* author's user structure */
2743 char *recipient, /* NULL if it's not mail */
2744 char *recp_cc, /* NULL if it's not mail */
2745 char *room, /* room where it's going */
2746 int type, /* see MES_ types in header file */
2747 int format_type, /* variformat, plain text, MIME... */
2748 char *fake_name, /* who we're masquerading as */
2749 char *subject, /* Subject (optional) */
2750 char *supplied_euid, /* ...or NULL if this is irrelevant */
2751 char *preformatted_text /* ...or NULL to read text from client */
2753 char dest_node[SIZ];
2755 struct CtdlMessage *msg;
2757 msg = malloc(sizeof(struct CtdlMessage));
2758 memset(msg, 0, sizeof(struct CtdlMessage));
2759 msg->cm_magic = CTDLMESSAGE_MAGIC;
2760 msg->cm_anon_type = type;
2761 msg->cm_format_type = format_type;
2763 /* Don't confuse the poor folks if it's not routed mail. */
2764 strcpy(dest_node, "");
2769 snprintf(buf, sizeof buf, "cit%ld", author->usernum); /* Path */
2770 msg->cm_fields['P'] = strdup(buf);
2772 snprintf(buf, sizeof buf, "%ld", (long)time(NULL)); /* timestamp */
2773 msg->cm_fields['T'] = strdup(buf);
2775 if (fake_name[0]) /* author */
2776 msg->cm_fields['A'] = strdup(fake_name);
2778 msg->cm_fields['A'] = strdup(author->fullname);
2780 if (CC->room.QRflags & QR_MAILBOX) { /* room */
2781 msg->cm_fields['O'] = strdup(&CC->room.QRname[11]);
2784 msg->cm_fields['O'] = strdup(CC->room.QRname);
2787 msg->cm_fields['N'] = strdup(NODENAME); /* nodename */
2788 msg->cm_fields['H'] = strdup(HUMANNODE); /* hnodename */
2790 if (recipient[0] != 0) {
2791 msg->cm_fields['R'] = strdup(recipient);
2793 if (recp_cc[0] != 0) {
2794 msg->cm_fields['Y'] = strdup(recp_cc);
2796 if (dest_node[0] != 0) {
2797 msg->cm_fields['D'] = strdup(dest_node);
2800 if ( (author == &CC->user) && (strlen(CC->cs_inet_email) > 0) ) {
2801 msg->cm_fields['F'] = strdup(CC->cs_inet_email);
2804 if (subject != NULL) {
2806 if (strlen(subject) > 0) {
2807 msg->cm_fields['U'] = strdup(subject);
2811 if (supplied_euid != NULL) {
2812 msg->cm_fields['E'] = strdup(supplied_euid);
2815 if (preformatted_text != NULL) {
2816 msg->cm_fields['M'] = preformatted_text;
2819 msg->cm_fields['M'] = CtdlReadMessageBody("000",
2820 config.c_maxmsglen, NULL, 0);
2828 * Check to see whether we have permission to post a message in the current
2829 * room. Returns a *CITADEL ERROR CODE* and puts a message in errmsgbuf, or
2830 * returns 0 on success.
2832 int CtdlDoIHavePermissionToPostInThisRoom(char *errmsgbuf, size_t n) {
2834 if (!(CC->logged_in)) {
2835 snprintf(errmsgbuf, n, "Not logged in.");
2836 return (ERROR + NOT_LOGGED_IN);
2839 if ((CC->user.axlevel < 2)
2840 && ((CC->room.QRflags & QR_MAILBOX) == 0)) {
2841 snprintf(errmsgbuf, n, "Need to be validated to enter "
2842 "(except in %s> to sysop)", MAILROOM);
2843 return (ERROR + HIGHER_ACCESS_REQUIRED);
2846 if ((CC->user.axlevel < 4)
2847 && (CC->room.QRflags & QR_NETWORK)) {
2848 snprintf(errmsgbuf, n, "Need net privileges to enter here.");
2849 return (ERROR + HIGHER_ACCESS_REQUIRED);
2852 if ((CC->user.axlevel < 6)
2853 && (CC->room.QRflags & QR_READONLY)) {
2854 snprintf(errmsgbuf, n, "Sorry, this is a read-only room.");
2855 return (ERROR + HIGHER_ACCESS_REQUIRED);
2858 strcpy(errmsgbuf, "Ok");
2864 * Check to see if the specified user has Internet mail permission
2865 * (returns nonzero if permission is granted)
2867 int CtdlCheckInternetMailPermission(struct ctdluser *who) {
2869 /* Do not allow twits to send Internet mail */
2870 if (who->axlevel <= 2) return(0);
2872 /* Globally enabled? */
2873 if (config.c_restrict == 0) return(1);
2875 /* User flagged ok? */
2876 if (who->flags & US_INTERNET) return(2);
2878 /* Aide level access? */
2879 if (who->axlevel >= 6) return(3);
2881 /* No mail for you! */
2887 * Validate recipients, count delivery types and errors, and handle aliasing
2888 * FIXME check for dupes!!!!!
2889 * Returns 0 if all addresses are ok, -1 if no addresses were specified,
2890 * or the number of addresses found invalid.
2892 struct recptypes *validate_recipients(char *supplied_recipients) {
2893 struct recptypes *ret;
2894 char recipients[SIZ];
2895 char this_recp[256];
2896 char this_recp_cooked[256];
2902 struct ctdluser tempUS;
2903 struct ctdlroom tempQR;
2907 ret = (struct recptypes *) malloc(sizeof(struct recptypes));
2908 if (ret == NULL) return(NULL);
2909 memset(ret, 0, sizeof(struct recptypes));
2912 ret->num_internet = 0;
2917 if (supplied_recipients == NULL) {
2918 strcpy(recipients, "");
2921 safestrncpy(recipients, supplied_recipients, sizeof recipients);
2924 /* Change all valid separator characters to commas */
2925 for (i=0; i<strlen(recipients); ++i) {
2926 if ((recipients[i] == ';') || (recipients[i] == '|')) {
2927 recipients[i] = ',';
2931 /* Now start extracting recipients... */
2933 while (strlen(recipients) > 0) {
2935 for (i=0; i<=strlen(recipients); ++i) {
2936 if (recipients[i] == '\"') in_quotes = 1 - in_quotes;
2937 if ( ( (recipients[i] == ',') && (!in_quotes) ) || (recipients[i] == 0) ) {
2938 safestrncpy(this_recp, recipients, i+1);
2940 if (recipients[i] == ',') {
2941 strcpy(recipients, &recipients[i+1]);
2944 strcpy(recipients, "");
2951 lprintf(CTDL_DEBUG, "Evaluating recipient #%d: %s\n", num_recps, this_recp);
2953 mailtype = alias(this_recp);
2954 mailtype = alias(this_recp);
2955 mailtype = alias(this_recp);
2956 for (j=0; j<=strlen(this_recp); ++j) {
2957 if (this_recp[j]=='_') {
2958 this_recp_cooked[j] = ' ';
2961 this_recp_cooked[j] = this_recp[j];
2967 if (!strcasecmp(this_recp, "sysop")) {
2969 strcpy(this_recp, config.c_aideroom);
2970 if (strlen(ret->recp_room) > 0) {
2971 strcat(ret->recp_room, "|");
2973 strcat(ret->recp_room, this_recp);
2975 else if (getuser(&tempUS, this_recp) == 0) {
2977 strcpy(this_recp, tempUS.fullname);
2978 if (strlen(ret->recp_local) > 0) {
2979 strcat(ret->recp_local, "|");
2981 strcat(ret->recp_local, this_recp);
2983 else if (getuser(&tempUS, this_recp_cooked) == 0) {
2985 strcpy(this_recp, tempUS.fullname);
2986 if (strlen(ret->recp_local) > 0) {
2987 strcat(ret->recp_local, "|");
2989 strcat(ret->recp_local, this_recp);
2991 else if ( (!strncasecmp(this_recp, "room_", 5))
2992 && (!getroom(&tempQR, &this_recp_cooked[5])) ) {
2994 if (strlen(ret->recp_room) > 0) {
2995 strcat(ret->recp_room, "|");
2997 strcat(ret->recp_room, &this_recp_cooked[5]);
3005 /* Yes, you're reading this correctly: if the target
3006 * domain points back to the local system or an attached
3007 * Citadel directory, the address is invalid. That's
3008 * because if the address were valid, we would have
3009 * already translated it to a local address by now.
3011 if (IsDirectory(this_recp)) {
3016 ++ret->num_internet;
3017 if (strlen(ret->recp_internet) > 0) {
3018 strcat(ret->recp_internet, "|");
3020 strcat(ret->recp_internet, this_recp);
3025 if (strlen(ret->recp_ignet) > 0) {
3026 strcat(ret->recp_ignet, "|");
3028 strcat(ret->recp_ignet, this_recp);
3036 if (strlen(ret->errormsg) == 0) {
3037 snprintf(append, sizeof append,
3038 "Invalid recipient: %s",
3042 snprintf(append, sizeof append,
3045 if ( (strlen(ret->errormsg) + strlen(append)) < SIZ) {
3046 strcat(ret->errormsg, append);
3050 if (strlen(ret->display_recp) == 0) {
3051 strcpy(append, this_recp);
3054 snprintf(append, sizeof append, ", %s",
3057 if ( (strlen(ret->display_recp)+strlen(append)) < SIZ) {
3058 strcat(ret->display_recp, append);
3063 if ((ret->num_local + ret->num_internet + ret->num_ignet +
3064 ret->num_room + ret->num_error) == 0) {
3065 ret->num_error = (-1);
3066 strcpy(ret->errormsg, "No recipients specified.");
3069 lprintf(CTDL_DEBUG, "validate_recipients()\n");
3070 lprintf(CTDL_DEBUG, " local: %d <%s>\n", ret->num_local, ret->recp_local);
3071 lprintf(CTDL_DEBUG, " room: %d <%s>\n", ret->num_room, ret->recp_room);
3072 lprintf(CTDL_DEBUG, " inet: %d <%s>\n", ret->num_internet, ret->recp_internet);
3073 lprintf(CTDL_DEBUG, " ignet: %d <%s>\n", ret->num_ignet, ret->recp_ignet);
3074 lprintf(CTDL_DEBUG, " error: %d <%s>\n", ret->num_error, ret->errormsg);
3082 * message entry - mode 0 (normal)
3084 void cmd_ent0(char *entargs)
3090 char supplied_euid[128];
3091 char masquerade_as[SIZ];
3093 int format_type = 0;
3094 char newusername[SIZ];
3095 struct CtdlMessage *msg;
3099 struct recptypes *valid = NULL;
3100 struct recptypes *valid_to = NULL;
3101 struct recptypes *valid_cc = NULL;
3102 struct recptypes *valid_bcc = NULL;
3109 post = extract_int(entargs, 0);
3110 extract_token(recp, entargs, 1, '|', sizeof recp);
3111 anon_flag = extract_int(entargs, 2);
3112 format_type = extract_int(entargs, 3);
3113 extract_token(subject, entargs, 4, '|', sizeof subject);
3114 do_confirm = extract_int(entargs, 6);
3115 extract_token(cc, entargs, 7, '|', sizeof cc);
3116 extract_token(bcc, entargs, 8, '|', sizeof bcc);
3117 switch(CC->room.QRdefaultview) {
3120 extract_token(supplied_euid, entargs, 9, '|', sizeof supplied_euid);
3123 supplied_euid[0] = 0;
3127 /* first check to make sure the request is valid. */
3129 err = CtdlDoIHavePermissionToPostInThisRoom(errmsg, sizeof errmsg);
3131 cprintf("%d %s\n", err, errmsg);
3135 /* Check some other permission type things. */
3138 if (CC->user.axlevel < 6) {
3139 cprintf("%d You don't have permission to masquerade.\n",
3140 ERROR + HIGHER_ACCESS_REQUIRED);
3143 extract_token(newusername, entargs, 5, '|', sizeof newusername);
3144 memset(CC->fake_postname, 0, sizeof(CC->fake_postname) );
3145 safestrncpy(CC->fake_postname, newusername,
3146 sizeof(CC->fake_postname) );
3147 cprintf("%d ok\n", CIT_OK);
3150 CC->cs_flags |= CS_POSTING;
3152 /* In the Mail> room we have to behave a little differently --
3153 * make sure the user has specified at least one recipient. Then
3154 * validate the recipient(s).
3156 if ( (CC->room.QRflags & QR_MAILBOX)
3157 && (!strcasecmp(&CC->room.QRname[11], MAILROOM)) ) {
3159 if (CC->user.axlevel < 2) {
3160 strcpy(recp, "sysop");
3165 valid_to = validate_recipients(recp);
3166 if (valid_to->num_error > 0) {
3167 cprintf("%d Invalid recipient (To)\n", ERROR + NO_SUCH_USER);
3172 valid_cc = validate_recipients(cc);
3173 if (valid_cc->num_error > 0) {
3174 cprintf("%d Invalid recipient (CC)\n", ERROR + NO_SUCH_USER);
3180 valid_bcc = validate_recipients(bcc);
3181 if (valid_bcc->num_error > 0) {
3182 cprintf("%d Invalid recipient (BCC)\n", ERROR + NO_SUCH_USER);
3189 /* Recipient required, but none were specified */
3190 if ( (valid_to->num_error < 0) && (valid_cc->num_error < 0) && (valid_bcc->num_error < 0) ) {
3194 cprintf("%d At least one recipient is required.\n", ERROR + NO_SUCH_USER);
3198 if (valid_to->num_internet + valid_cc->num_internet + valid_bcc->num_internet > 0) {
3199 if (CtdlCheckInternetMailPermission(&CC->user)==0) {
3200 cprintf("%d You do not have permission "
3201 "to send Internet mail.\n",
3202 ERROR + HIGHER_ACCESS_REQUIRED);
3210 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)
3211 && (CC->user.axlevel < 4) ) {
3212 cprintf("%d Higher access required for network mail.\n",
3213 ERROR + HIGHER_ACCESS_REQUIRED);
3220 if ((RESTRICT_INTERNET == 1)
3221 && (valid_to->num_internet + valid_cc->num_internet + valid_bcc->num_internet > 0)
3222 && ((CC->user.flags & US_INTERNET) == 0)
3223 && (!CC->internal_pgm)) {
3224 cprintf("%d You don't have access to Internet mail.\n",
3225 ERROR + HIGHER_ACCESS_REQUIRED);
3234 /* Is this a room which has anonymous-only or anonymous-option? */
3235 anonymous = MES_NORMAL;
3236 if (CC->room.QRflags & QR_ANONONLY) {
3237 anonymous = MES_ANONONLY;
3239 if (CC->room.QRflags & QR_ANONOPT) {
3240 if (anon_flag == 1) { /* only if the user requested it */
3241 anonymous = MES_ANONOPT;
3245 if ((CC->room.QRflags & QR_MAILBOX) == 0) {
3249 /* If we're only checking the validity of the request, return
3250 * success without creating the message.
3253 cprintf("%d %s\n", CIT_OK,
3254 ((valid_to != NULL) ? valid_to->display_recp : "") );
3261 /* We don't need these anymore because we'll do it differently below */
3266 /* Handle author masquerading */
3267 if (CC->fake_postname[0]) {
3268 strcpy(masquerade_as, CC->fake_postname);
3270 else if (CC->fake_username[0]) {
3271 strcpy(masquerade_as, CC->fake_username);
3274 strcpy(masquerade_as, "");
3277 /* Read in the message from the client. */
3279 cprintf("%d send message\n", START_CHAT_MODE);
3281 cprintf("%d send message\n", SEND_LISTING);
3284 msg = CtdlMakeMessage(&CC->user, recp, cc,
3285 CC->room.QRname, anonymous, format_type,
3286 masquerade_as, subject,
3287 ((strlen(supplied_euid) > 0) ? supplied_euid : NULL),
3290 /* Put together one big recipients struct containing to/cc/bcc all in
3291 * one. This is for the envelope.
3293 char *all_recps = malloc(SIZ * 3);
3294 strcpy(all_recps, recp);
3295 if (strlen(cc) > 0) {
3296 if (strlen(all_recps) > 0) {
3297 strcat(all_recps, ",");
3299 strcat(all_recps, cc);
3301 if (strlen(bcc) > 0) {
3302 if (strlen(all_recps) > 0) {
3303 strcat(all_recps, ",");
3305 strcat(all_recps, bcc);
3307 if (strlen(all_recps) > 0) {
3308 valid = validate_recipients(all_recps);
3316 msgnum = CtdlSubmitMsg(msg, valid, "");
3319 cprintf("%ld\n", msgnum);
3321 cprintf("Message accepted.\n");
3324 cprintf("Internal error.\n");
3326 if (msg->cm_fields['E'] != NULL) {
3327 cprintf("%s\n", msg->cm_fields['E']);
3334 CtdlFreeMessage(msg);
3336 CC->fake_postname[0] = '\0';
3337 if (valid != NULL) {
3346 * API function to delete messages which match a set of criteria
3347 * (returns the actual number of messages deleted)
3349 int CtdlDeleteMessages(char *room_name, /* which room */
3350 long *dmsgnums, /* array of msg numbers to be deleted */
3351 int num_dmsgnums, /* number of msgs to be deleted, or 0 for "any" */
3352 char *content_type, /* or "" for any */
3353 int deferred /* let TDAP sweep it later */
3357 struct ctdlroom qrbuf;
3358 struct cdbdata *cdbfr;
3359 long *msglist = NULL;
3360 long *dellist = NULL;
3363 int num_deleted = 0;
3365 struct MetaData smi;
3367 lprintf(CTDL_DEBUG, "CtdlDeleteMessages(%s, %d msgs, %s, %d)\n",
3368 room_name, num_dmsgnums, content_type, deferred);
3370 /* get room record, obtaining a lock... */
3371 if (lgetroom(&qrbuf, room_name) != 0) {
3372 lprintf(CTDL_ERR, "CtdlDeleteMessages(): Room <%s> not found\n",
3374 return (0); /* room not found */
3376 cdbfr = cdb_fetch(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long));
3378 if (cdbfr != NULL) {
3379 dellist = malloc(cdbfr->len);
3380 msglist = (long *) cdbfr->ptr;
3381 cdbfr->ptr = NULL; /* CtdlDeleteMessages() now owns this memory */
3382 num_msgs = cdbfr->len / sizeof(long);
3386 for (i = 0; i < num_msgs; ++i) {
3389 /* Set/clear a bit for each criterion */
3391 /* 0 messages in the list or a null list means that we are
3392 * interested in deleting any messages which meet the other criteria.
3394 if ((num_dmsgnums == 0) || (dmsgnums == NULL)) {
3395 delete_this |= 0x01;
3398 for (j=0; j<num_dmsgnums; ++j) {
3399 if (msglist[i] == dmsgnums[j]) {
3400 delete_this |= 0x01;
3405 if (strlen(content_type) == 0) {
3406 delete_this |= 0x02;
3408 GetMetaData(&smi, msglist[i]);
3409 if (!strcasecmp(smi.meta_content_type,
3411 delete_this |= 0x02;
3415 /* Delete message only if all bits are set */
3416 if (delete_this == 0x03) {
3417 dellist[num_deleted++] = msglist[i];
3422 num_msgs = sort_msglist(msglist, num_msgs);
3423 cdb_store(CDB_MSGLISTS, &qrbuf.QRnumber, (int)sizeof(long),
3424 msglist, (int)(num_msgs * sizeof(long)));
3426 qrbuf.QRhighest = msglist[num_msgs - 1];
3431 * If the delete operation is "deferred" (and technically, any delete
3432 * operation not performed by THE DREADED AUTO-PURGER ought to be
3433 * a deferred delete) then we save a pointer to the message in the
3434 * DELETED_MSGS_ROOM. This will cause the reference count to remain
3435 * at least 1, which will save the user from having to synchronously
3436 * wait for various disk-intensive operations to complete.
3438 * Slick -- we now use the new bulk API for moving messages.
3440 if ( (deferred) && (num_deleted) ) {
3441 CtdlCopyMsgsToRoom(dellist, num_deleted, DELETED_MSGS_ROOM);
3444 /* Go through the messages we pulled out of the index, and decrement
3445 * their reference counts by 1. If this is the only room the message
3446 * was in, the reference count will reach zero and the message will
3447 * automatically be deleted from the database. We do this in a
3448 * separate pass because there might be plug-in hooks getting called,
3449 * and we don't want that happening during an S_ROOMS critical
3452 if (num_deleted) for (i=0; i<num_deleted; ++i) {
3453 PerformDeleteHooks(qrbuf.QRname, dellist[i]);
3454 AdjRefCount(dellist[i], -1);
3457 /* Now free the memory we used, and go away. */
3458 if (msglist != NULL) free(msglist);
3459 if (dellist != NULL) free(dellist);
3460 lprintf(CTDL_DEBUG, "%d message(s) deleted.\n", num_deleted);
3461 return (num_deleted);
3467 * Check whether the current user has permission to delete messages from
3468 * the current room (returns 1 for yes, 0 for no)
3470 int CtdlDoIHavePermissionToDeleteMessagesFromThisRoom(void) {
3471 getuser(&CC->user, CC->curr_user);
3472 if ((CC->user.axlevel < 6)
3473 && (CC->user.usernum != CC->room.QRroomaide)
3474 && ((CC->room.QRflags & QR_MAILBOX) == 0)
3475 && (!(CC->internal_pgm))) {
3484 * Delete message from current room
3486 void cmd_dele(char *args)
3495 extract_token(msgset, args, 0, '|', sizeof msgset);
3496 num_msgs = num_tokens(msgset, ',');
3498 cprintf("%d Nothing to do.\n", CIT_OK);
3502 if (CtdlDoIHavePermissionToDeleteMessagesFromThisRoom() == 0) {
3503 cprintf("%d Higher access required.\n",
3504 ERROR + HIGHER_ACCESS_REQUIRED);
3509 * Build our message set to be moved/copied
3511 msgs = malloc(num_msgs * sizeof(long));
3512 for (i=0; i<num_msgs; ++i) {
3513 extract_token(msgtok, msgset, i, ',', sizeof msgtok);
3514 msgs[i] = atol(msgtok);
3517 num_deleted = CtdlDeleteMessages(CC->room.QRname, msgs, num_msgs, "", 1);
3521 cprintf("%d %d message%s deleted.\n", CIT_OK,
3522 num_deleted, ((num_deleted != 1) ? "s" : ""));
3524 cprintf("%d Message not found.\n", ERROR + MESSAGE_NOT_FOUND);
3530 * Back end API function for moves and deletes (multiple messages)
3532 int CtdlCopyMsgsToRoom(long *msgnums, int num_msgs, char *dest) {
3535 err = CtdlSaveMsgPointersInRoom(dest, msgnums, num_msgs, 1, NULL);
3536 if (err != 0) return(err);
3545 * move or copy a message to another room
3547 void cmd_move(char *args)
3554 char targ[ROOMNAMELEN];
3555 struct ctdlroom qtemp;
3562 extract_token(msgset, args, 0, '|', sizeof msgset);
3563 num_msgs = num_tokens(msgset, ',');
3565 cprintf("%d Nothing to do.\n", CIT_OK);
3569 extract_token(targ, args, 1, '|', sizeof targ);
3570 convert_room_name_macros(targ, sizeof targ);
3571 targ[ROOMNAMELEN - 1] = 0;
3572 is_copy = extract_int(args, 2);
3574 if (getroom(&qtemp, targ) != 0) {
3575 cprintf("%d '%s' does not exist.\n",
3576 ERROR + ROOM_NOT_FOUND, targ);
3580 getuser(&CC->user, CC->curr_user);
3581 CtdlRoomAccess(&qtemp, &CC->user, &ra, NULL);
3583 /* Check for permission to perform this operation.
3584 * Remember: "CC->room" is source, "qtemp" is target.
3588 /* Aides can move/copy */
3589 if (CC->user.axlevel >= 6) permit = 1;
3591 /* Room aides can move/copy */
3592 if (CC->user.usernum == CC->room.QRroomaide) permit = 1;
3594 /* Permit move/copy from personal rooms */
3595 if ((CC->room.QRflags & QR_MAILBOX)
3596 && (qtemp.QRflags & QR_MAILBOX)) permit = 1;
3598 /* Permit only copy from public to personal room */
3600 && (!(CC->room.QRflags & QR_MAILBOX))
3601 && (qtemp.QRflags & QR_MAILBOX)) permit = 1;
3603 /* User must have access to target room */
3604 if (!(ra & UA_KNOWN)) permit = 0;
3607 cprintf("%d Higher access required.\n",
3608 ERROR + HIGHER_ACCESS_REQUIRED);
3613 * Build our message set to be moved/copied
3615 msgs = malloc(num_msgs * sizeof(long));
3616 for (i=0; i<num_msgs; ++i) {
3617 extract_token(msgtok, msgset, i, ',', sizeof msgtok);
3618 msgs[i] = atol(msgtok);
3624 err = CtdlCopyMsgsToRoom(msgs, num_msgs, targ);
3626 cprintf("%d Cannot store message(s) in %s: error %d\n",
3632 /* Now delete the message from the source room,
3633 * if this is a 'move' rather than a 'copy' operation.
3636 CtdlDeleteMessages(CC->room.QRname, msgs, num_msgs, "", 0);
3640 cprintf("%d Message(s) %s.\n", CIT_OK, (is_copy ? "copied" : "moved") );
3646 * GetMetaData() - Get the supplementary record for a message
3648 void GetMetaData(struct MetaData *smibuf, long msgnum)
3651 struct cdbdata *cdbsmi;
3654 memset(smibuf, 0, sizeof(struct MetaData));
3655 smibuf->meta_msgnum = msgnum;
3656 smibuf->meta_refcount = 1; /* Default reference count is 1 */
3658 /* Use the negative of the message number for its supp record index */
3659 TheIndex = (0L - msgnum);
3661 cdbsmi = cdb_fetch(CDB_MSGMAIN, &TheIndex, sizeof(long));
3662 if (cdbsmi == NULL) {
3663 return; /* record not found; go with defaults */
3665 memcpy(smibuf, cdbsmi->ptr,
3666 ((cdbsmi->len > sizeof(struct MetaData)) ?
3667 sizeof(struct MetaData) : cdbsmi->len));
3674 * PutMetaData() - (re)write supplementary record for a message
3676 void PutMetaData(struct MetaData *smibuf)
3680 /* Use the negative of the message number for the metadata db index */
3681 TheIndex = (0L - smibuf->meta_msgnum);
3683 lprintf(CTDL_DEBUG, "PutMetaData(%ld) - ref count is %d\n",
3684 smibuf->meta_msgnum, smibuf->meta_refcount);
3686 cdb_store(CDB_MSGMAIN,
3687 &TheIndex, (int)sizeof(long),
3688 smibuf, (int)sizeof(struct MetaData));
3693 * AdjRefCount - change the reference count for a message;
3694 * delete the message if it reaches zero
3696 void AdjRefCount(long msgnum, int incr)
3699 struct MetaData smi;
3702 /* This is a *tight* critical section; please keep it that way, as
3703 * it may get called while nested in other critical sections.
3704 * Complicating this any further will surely cause deadlock!
3706 begin_critical_section(S_SUPPMSGMAIN);
3707 GetMetaData(&smi, msgnum);
3708 smi.meta_refcount += incr;
3710 end_critical_section(S_SUPPMSGMAIN);
3711 lprintf(CTDL_DEBUG, "msg %ld ref count incr %d, is now %d\n",
3712 msgnum, incr, smi.meta_refcount);
3714 /* If the reference count is now zero, delete the message
3715 * (and its supplementary record as well).
3717 if (smi.meta_refcount == 0) {
3718 lprintf(CTDL_DEBUG, "Deleting message <%ld>\n", msgnum);
3720 /* Remove from fulltext index */
3721 if (config.c_enable_fulltext) {
3722 ft_index_message(msgnum, 0);
3725 /* Remove from message base */
3727 cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long));
3728 cdb_delete(CDB_BIGMSGS, &delnum, (int)sizeof(long));
3730 /* Remove metadata record */
3731 delnum = (0L - msgnum);
3732 cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long));
3737 * Write a generic object to this room
3739 * Note: this could be much more efficient. Right now we use two temporary
3740 * files, and still pull the message into memory as with all others.
3742 void CtdlWriteObject(char *req_room, /* Room to stuff it in */
3743 char *content_type, /* MIME type of this object */
3744 char *tempfilename, /* Where to fetch it from */
3745 struct ctdluser *is_mailbox, /* Mailbox room? */
3746 int is_binary, /* Is encoding necessary? */
3747 int is_unique, /* Del others of this type? */
3748 unsigned int flags /* Internal save flags */
3753 struct ctdlroom qrbuf;
3754 char roomname[ROOMNAMELEN];
3755 struct CtdlMessage *msg;
3757 char *raw_message = NULL;
3758 char *encoded_message = NULL;
3759 off_t raw_length = 0;
3761 if (is_mailbox != NULL) {
3762 MailboxName(roomname, sizeof roomname, is_mailbox, req_room);
3765 safestrncpy(roomname, req_room, sizeof(roomname));
3768 fp = fopen(tempfilename, "rb");
3770 lprintf(CTDL_CRIT, "Cannot open %s: %s\n",
3771 tempfilename, strerror(errno));
3774 fseek(fp, 0L, SEEK_END);
3775 raw_length = ftell(fp);
3777 lprintf(CTDL_DEBUG, "Raw length is %ld\n", (long)raw_length);
3779 raw_message = malloc((size_t)raw_length + 2);
3780 fread(raw_message, (size_t)raw_length, 1, fp);
3784 encoded_message = malloc((size_t)
3785 (((raw_length * 134) / 100) + 4096 ) );
3788 encoded_message = malloc((size_t)(raw_length + 4096));
3791 sprintf(encoded_message, "Content-type: %s\n", content_type);
3794 sprintf(&encoded_message[strlen(encoded_message)],
3795 "Content-transfer-encoding: base64\n\n"
3799 sprintf(&encoded_message[strlen(encoded_message)],
3800 "Content-transfer-encoding: 7bit\n\n"
3806 &encoded_message[strlen(encoded_message)],
3812 raw_message[raw_length] = 0;
3814 &encoded_message[strlen(encoded_message)],
3822 lprintf(CTDL_DEBUG, "Allocating\n");
3823 msg = malloc(sizeof(struct CtdlMessage));
3824 memset(msg, 0, sizeof(struct CtdlMessage));
3825 msg->cm_magic = CTDLMESSAGE_MAGIC;
3826 msg->cm_anon_type = MES_NORMAL;
3827 msg->cm_format_type = 4;
3828 msg->cm_fields['A'] = strdup(CC->user.fullname);
3829 msg->cm_fields['O'] = strdup(req_room);
3830 msg->cm_fields['N'] = strdup(config.c_nodename);
3831 msg->cm_fields['H'] = strdup(config.c_humannode);
3832 msg->cm_flags = flags;
3834 msg->cm_fields['M'] = encoded_message;
3836 /* Create the requested room if we have to. */
3837 if (getroom(&qrbuf, roomname) != 0) {
3838 create_room(roomname,
3839 ( (is_mailbox != NULL) ? 5 : 3 ),
3840 "", 0, 1, 0, VIEW_BBS);
3842 /* If the caller specified this object as unique, delete all
3843 * other objects of this type that are currently in the room.
3846 lprintf(CTDL_DEBUG, "Deleted %d other msgs of this type\n",
3847 CtdlDeleteMessages(roomname, NULL, 0, content_type, 0)
3850 /* Now write the data */
3851 CtdlSubmitMsg(msg, NULL, roomname);
3852 CtdlFreeMessage(msg);
3860 void CtdlGetSysConfigBackend(long msgnum, void *userdata) {
3861 config_msgnum = msgnum;
3865 char *CtdlGetSysConfig(char *sysconfname) {
3866 char hold_rm[ROOMNAMELEN];
3869 struct CtdlMessage *msg;
3872 strcpy(hold_rm, CC->room.QRname);
3873 if (getroom(&CC->room, SYSCONFIGROOM) != 0) {
3874 getroom(&CC->room, hold_rm);
3879 /* We want the last (and probably only) config in this room */
3880 begin_critical_section(S_CONFIG);
3881 config_msgnum = (-1L);
3882 CtdlForEachMessage(MSGS_LAST, 1, NULL, sysconfname, NULL,
3883 CtdlGetSysConfigBackend, NULL);
3884 msgnum = config_msgnum;
3885 end_critical_section(S_CONFIG);
3891 msg = CtdlFetchMessage(msgnum, 1);
3893 conf = strdup(msg->cm_fields['M']);
3894 CtdlFreeMessage(msg);
3901 getroom(&CC->room, hold_rm);
3903 if (conf != NULL) do {
3904 extract_token(buf, conf, 0, '\n', sizeof buf);
3905 strcpy(conf, &conf[strlen(buf)+1]);
3906 } while ( (strlen(conf)>0) && (strlen(buf)>0) );
3911 void CtdlPutSysConfig(char *sysconfname, char *sysconfdata) {
3912 char temp[PATH_MAX];
3915 CtdlMakeTempFileName(temp, sizeof temp);
3917 fp = fopen(temp, "w");
3918 if (fp == NULL) return;
3919 fprintf(fp, "%s", sysconfdata);
3922 /* this handy API function does all the work for us */
3923 CtdlWriteObject(SYSCONFIGROOM, sysconfname, temp, NULL, 0, 1, 0);
3929 * Determine whether a given Internet address belongs to the current user
3931 int CtdlIsMe(char *addr, int addr_buf_len)
3933 struct recptypes *recp;
3936 recp = validate_recipients(addr);
3937 if (recp == NULL) return(0);
3939 if (recp->num_local == 0) {
3944 for (i=0; i<recp->num_local; ++i) {
3945 extract_token(addr, recp->recp_local, i, '|', addr_buf_len);
3946 if (!strcasecmp(addr, CC->user.fullname)) {
3958 * Citadel protocol command to do the same
3960 void cmd_isme(char *argbuf) {
3963 if (CtdlAccessCheck(ac_logged_in)) return;
3964 extract_token(addr, argbuf, 0, '|', sizeof addr);
3966 if (CtdlIsMe(addr, sizeof addr)) {
3967 cprintf("%d %s\n", CIT_OK, addr);
3970 cprintf("%d Not you.\n", ERROR + ILLEGAL_VALUE);