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[i];
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;
2667 if (exist == NULL) {
2674 message_len = strlen(exist);
2675 buffer_len = message_len + 4096;
2676 m = realloc(exist, buffer_len);
2683 /* Do we need to change leading ".." to "." for SMTP escaping? */
2684 if (!strcmp(terminator, ".")) {
2688 /* flush the input if we have nowhere to store it */
2693 /* read in the lines of message text one by one */
2695 if (client_getln(buf, (sizeof buf - 3)) < 1) finished = 1;
2696 if (!strcmp(buf, terminator)) finished = 1;
2698 strcat(buf, "\r\n");
2704 /* Unescape SMTP-style input of two dots at the beginning of the line */
2706 if (!strncmp(buf, "..", 2)) {
2707 strcpy(buf, &buf[1]);
2711 if ( (!flushing) && (!finished) ) {
2712 /* Measure the line */
2713 linelen = strlen(buf);
2715 /* augment the buffer if we have to */
2716 if ((message_len + linelen) >= buffer_len) {
2717 ptr = realloc(m, (buffer_len * 2) );
2718 if (ptr == NULL) { /* flush if can't allocate */
2721 buffer_len = (buffer_len * 2);
2723 lprintf(CTDL_DEBUG, "buffer_len is now %ld\n", (long)buffer_len);
2727 /* Add the new line to the buffer. NOTE: this loop must avoid
2728 * using functions like strcat() and strlen() because they
2729 * traverse the entire buffer upon every call, and doing that
2730 * for a multi-megabyte message slows it down beyond usability.
2732 strcpy(&m[message_len], buf);
2733 message_len += linelen;
2736 /* if we've hit the max msg length, flush the rest */
2737 if (message_len >= maxlen) flushing = 1;
2739 } while (!finished);
2747 * Build a binary message to be saved on disk.
2748 * (NOTE: if you supply 'preformatted_text', the buffer you give it
2749 * will become part of the message. This means you are no longer
2750 * responsible for managing that memory -- it will be freed along with
2751 * the rest of the fields when CtdlFreeMessage() is called.)
2754 struct CtdlMessage *CtdlMakeMessage(
2755 struct ctdluser *author, /* author's user structure */
2756 char *recipient, /* NULL if it's not mail */
2757 char *recp_cc, /* NULL if it's not mail */
2758 char *room, /* room where it's going */
2759 int type, /* see MES_ types in header file */
2760 int format_type, /* variformat, plain text, MIME... */
2761 char *fake_name, /* who we're masquerading as */
2762 char *subject, /* Subject (optional) */
2763 char *supplied_euid, /* ...or NULL if this is irrelevant */
2764 char *preformatted_text /* ...or NULL to read text from client */
2766 char dest_node[SIZ];
2768 struct CtdlMessage *msg;
2770 msg = malloc(sizeof(struct CtdlMessage));
2771 memset(msg, 0, sizeof(struct CtdlMessage));
2772 msg->cm_magic = CTDLMESSAGE_MAGIC;
2773 msg->cm_anon_type = type;
2774 msg->cm_format_type = format_type;
2776 /* Don't confuse the poor folks if it's not routed mail. */
2777 strcpy(dest_node, "");
2782 snprintf(buf, sizeof buf, "cit%ld", author->usernum); /* Path */
2783 msg->cm_fields['P'] = strdup(buf);
2785 snprintf(buf, sizeof buf, "%ld", (long)time(NULL)); /* timestamp */
2786 msg->cm_fields['T'] = strdup(buf);
2788 if (fake_name[0]) /* author */
2789 msg->cm_fields['A'] = strdup(fake_name);
2791 msg->cm_fields['A'] = strdup(author->fullname);
2793 if (CC->room.QRflags & QR_MAILBOX) { /* room */
2794 msg->cm_fields['O'] = strdup(&CC->room.QRname[11]);
2797 msg->cm_fields['O'] = strdup(CC->room.QRname);
2800 msg->cm_fields['N'] = strdup(NODENAME); /* nodename */
2801 msg->cm_fields['H'] = strdup(HUMANNODE); /* hnodename */
2803 if (recipient[0] != 0) {
2804 msg->cm_fields['R'] = strdup(recipient);
2806 if (recp_cc[0] != 0) {
2807 msg->cm_fields['Y'] = strdup(recp_cc);
2809 if (dest_node[0] != 0) {
2810 msg->cm_fields['D'] = strdup(dest_node);
2813 if ( (author == &CC->user) && (strlen(CC->cs_inet_email) > 0) ) {
2814 msg->cm_fields['F'] = strdup(CC->cs_inet_email);
2817 if (subject != NULL) {
2819 if (strlen(subject) > 0) {
2820 msg->cm_fields['U'] = strdup(subject);
2824 if (supplied_euid != NULL) {
2825 msg->cm_fields['E'] = strdup(supplied_euid);
2828 if (preformatted_text != NULL) {
2829 msg->cm_fields['M'] = preformatted_text;
2832 msg->cm_fields['M'] = CtdlReadMessageBody("000",
2833 config.c_maxmsglen, NULL, 0);
2841 * Check to see whether we have permission to post a message in the current
2842 * room. Returns a *CITADEL ERROR CODE* and puts a message in errmsgbuf, or
2843 * returns 0 on success.
2845 int CtdlDoIHavePermissionToPostInThisRoom(char *errmsgbuf, size_t n) {
2847 if (!(CC->logged_in)) {
2848 snprintf(errmsgbuf, n, "Not logged in.");
2849 return (ERROR + NOT_LOGGED_IN);
2852 if ((CC->user.axlevel < 2)
2853 && ((CC->room.QRflags & QR_MAILBOX) == 0)) {
2854 snprintf(errmsgbuf, n, "Need to be validated to enter "
2855 "(except in %s> to sysop)", MAILROOM);
2856 return (ERROR + HIGHER_ACCESS_REQUIRED);
2859 if ((CC->user.axlevel < 4)
2860 && (CC->room.QRflags & QR_NETWORK)) {
2861 snprintf(errmsgbuf, n, "Need net privileges to enter here.");
2862 return (ERROR + HIGHER_ACCESS_REQUIRED);
2865 if ((CC->user.axlevel < 6)
2866 && (CC->room.QRflags & QR_READONLY)) {
2867 snprintf(errmsgbuf, n, "Sorry, this is a read-only room.");
2868 return (ERROR + HIGHER_ACCESS_REQUIRED);
2871 strcpy(errmsgbuf, "Ok");
2877 * Check to see if the specified user has Internet mail permission
2878 * (returns nonzero if permission is granted)
2880 int CtdlCheckInternetMailPermission(struct ctdluser *who) {
2882 /* Do not allow twits to send Internet mail */
2883 if (who->axlevel <= 2) return(0);
2885 /* Globally enabled? */
2886 if (config.c_restrict == 0) return(1);
2888 /* User flagged ok? */
2889 if (who->flags & US_INTERNET) return(2);
2891 /* Aide level access? */
2892 if (who->axlevel >= 6) return(3);
2894 /* No mail for you! */
2900 * Validate recipients, count delivery types and errors, and handle aliasing
2901 * FIXME check for dupes!!!!!
2902 * Returns 0 if all addresses are ok, -1 if no addresses were specified,
2903 * or the number of addresses found invalid.
2905 struct recptypes *validate_recipients(char *supplied_recipients) {
2906 struct recptypes *ret;
2907 char recipients[SIZ];
2908 char this_recp[256];
2909 char this_recp_cooked[256];
2915 struct ctdluser tempUS;
2916 struct ctdlroom tempQR;
2920 ret = (struct recptypes *) malloc(sizeof(struct recptypes));
2921 if (ret == NULL) return(NULL);
2922 memset(ret, 0, sizeof(struct recptypes));
2925 ret->num_internet = 0;
2930 if (supplied_recipients == NULL) {
2931 strcpy(recipients, "");
2934 safestrncpy(recipients, supplied_recipients, sizeof recipients);
2937 /* Change all valid separator characters to commas */
2938 for (i=0; i<strlen(recipients); ++i) {
2939 if ((recipients[i] == ';') || (recipients[i] == '|')) {
2940 recipients[i] = ',';
2944 /* Now start extracting recipients... */
2946 while (strlen(recipients) > 0) {
2948 for (i=0; i<=strlen(recipients); ++i) {
2949 if (recipients[i] == '\"') in_quotes = 1 - in_quotes;
2950 if ( ( (recipients[i] == ',') && (!in_quotes) ) || (recipients[i] == 0) ) {
2951 safestrncpy(this_recp, recipients, i+1);
2953 if (recipients[i] == ',') {
2954 strcpy(recipients, &recipients[i+1]);
2957 strcpy(recipients, "");
2964 lprintf(CTDL_DEBUG, "Evaluating recipient #%d: %s\n", num_recps, this_recp);
2966 mailtype = alias(this_recp);
2967 mailtype = alias(this_recp);
2968 mailtype = alias(this_recp);
2969 for (j=0; j<=strlen(this_recp); ++j) {
2970 if (this_recp[j]=='_') {
2971 this_recp_cooked[j] = ' ';
2974 this_recp_cooked[j] = this_recp[j];
2980 if (!strcasecmp(this_recp, "sysop")) {
2982 strcpy(this_recp, config.c_aideroom);
2983 if (strlen(ret->recp_room) > 0) {
2984 strcat(ret->recp_room, "|");
2986 strcat(ret->recp_room, this_recp);
2988 else if (getuser(&tempUS, this_recp) == 0) {
2990 strcpy(this_recp, tempUS.fullname);
2991 if (strlen(ret->recp_local) > 0) {
2992 strcat(ret->recp_local, "|");
2994 strcat(ret->recp_local, this_recp);
2996 else if (getuser(&tempUS, this_recp_cooked) == 0) {
2998 strcpy(this_recp, tempUS.fullname);
2999 if (strlen(ret->recp_local) > 0) {
3000 strcat(ret->recp_local, "|");
3002 strcat(ret->recp_local, this_recp);
3004 else if ( (!strncasecmp(this_recp, "room_", 5))
3005 && (!getroom(&tempQR, &this_recp_cooked[5])) ) {
3007 if (strlen(ret->recp_room) > 0) {
3008 strcat(ret->recp_room, "|");
3010 strcat(ret->recp_room, &this_recp_cooked[5]);
3018 /* Yes, you're reading this correctly: if the target
3019 * domain points back to the local system or an attached
3020 * Citadel directory, the address is invalid. That's
3021 * because if the address were valid, we would have
3022 * already translated it to a local address by now.
3024 if (IsDirectory(this_recp)) {
3029 ++ret->num_internet;
3030 if (strlen(ret->recp_internet) > 0) {
3031 strcat(ret->recp_internet, "|");
3033 strcat(ret->recp_internet, this_recp);
3038 if (strlen(ret->recp_ignet) > 0) {
3039 strcat(ret->recp_ignet, "|");
3041 strcat(ret->recp_ignet, this_recp);
3049 if (strlen(ret->errormsg) == 0) {
3050 snprintf(append, sizeof append,
3051 "Invalid recipient: %s",
3055 snprintf(append, sizeof append,
3058 if ( (strlen(ret->errormsg) + strlen(append)) < SIZ) {
3059 strcat(ret->errormsg, append);
3063 if (strlen(ret->display_recp) == 0) {
3064 strcpy(append, this_recp);
3067 snprintf(append, sizeof append, ", %s",
3070 if ( (strlen(ret->display_recp)+strlen(append)) < SIZ) {
3071 strcat(ret->display_recp, append);
3076 if ((ret->num_local + ret->num_internet + ret->num_ignet +
3077 ret->num_room + ret->num_error) == 0) {
3078 ret->num_error = (-1);
3079 strcpy(ret->errormsg, "No recipients specified.");
3082 lprintf(CTDL_DEBUG, "validate_recipients()\n");
3083 lprintf(CTDL_DEBUG, " local: %d <%s>\n", ret->num_local, ret->recp_local);
3084 lprintf(CTDL_DEBUG, " room: %d <%s>\n", ret->num_room, ret->recp_room);
3085 lprintf(CTDL_DEBUG, " inet: %d <%s>\n", ret->num_internet, ret->recp_internet);
3086 lprintf(CTDL_DEBUG, " ignet: %d <%s>\n", ret->num_ignet, ret->recp_ignet);
3087 lprintf(CTDL_DEBUG, " error: %d <%s>\n", ret->num_error, ret->errormsg);
3095 * message entry - mode 0 (normal)
3097 void cmd_ent0(char *entargs)
3103 char supplied_euid[128];
3104 char masquerade_as[SIZ];
3106 int format_type = 0;
3107 char newusername[SIZ];
3108 struct CtdlMessage *msg;
3112 struct recptypes *valid = NULL;
3113 struct recptypes *valid_to = NULL;
3114 struct recptypes *valid_cc = NULL;
3115 struct recptypes *valid_bcc = NULL;
3122 post = extract_int(entargs, 0);
3123 extract_token(recp, entargs, 1, '|', sizeof recp);
3124 anon_flag = extract_int(entargs, 2);
3125 format_type = extract_int(entargs, 3);
3126 extract_token(subject, entargs, 4, '|', sizeof subject);
3127 do_confirm = extract_int(entargs, 6);
3128 extract_token(cc, entargs, 7, '|', sizeof cc);
3129 extract_token(bcc, entargs, 8, '|', sizeof bcc);
3130 switch(CC->room.QRdefaultview) {
3133 extract_token(supplied_euid, entargs, 9, '|', sizeof supplied_euid);
3136 supplied_euid[0] = 0;
3140 /* first check to make sure the request is valid. */
3142 err = CtdlDoIHavePermissionToPostInThisRoom(errmsg, sizeof errmsg);
3144 cprintf("%d %s\n", err, errmsg);
3148 /* Check some other permission type things. */
3151 if (CC->user.axlevel < 6) {
3152 cprintf("%d You don't have permission to masquerade.\n",
3153 ERROR + HIGHER_ACCESS_REQUIRED);
3156 extract_token(newusername, entargs, 5, '|', sizeof newusername);
3157 memset(CC->fake_postname, 0, sizeof(CC->fake_postname) );
3158 safestrncpy(CC->fake_postname, newusername,
3159 sizeof(CC->fake_postname) );
3160 cprintf("%d ok\n", CIT_OK);
3163 CC->cs_flags |= CS_POSTING;
3165 /* In the Mail> room we have to behave a little differently --
3166 * make sure the user has specified at least one recipient. Then
3167 * validate the recipient(s).
3169 if ( (CC->room.QRflags & QR_MAILBOX)
3170 && (!strcasecmp(&CC->room.QRname[11], MAILROOM)) ) {
3172 if (CC->user.axlevel < 2) {
3173 strcpy(recp, "sysop");
3178 valid_to = validate_recipients(recp);
3179 if (valid_to->num_error > 0) {
3180 cprintf("%d Invalid recipient (To)\n", ERROR + NO_SUCH_USER);
3185 valid_cc = validate_recipients(cc);
3186 if (valid_cc->num_error > 0) {
3187 cprintf("%d Invalid recipient (CC)\n", ERROR + NO_SUCH_USER);
3193 valid_bcc = validate_recipients(bcc);
3194 if (valid_bcc->num_error > 0) {
3195 cprintf("%d Invalid recipient (BCC)\n", ERROR + NO_SUCH_USER);
3202 /* Recipient required, but none were specified */
3203 if ( (valid_to->num_error < 0) && (valid_cc->num_error < 0) && (valid_bcc->num_error < 0) ) {
3207 cprintf("%d At least one recipient is required.\n", ERROR + NO_SUCH_USER);
3211 if (valid_to->num_internet + valid_cc->num_internet + valid_bcc->num_internet > 0) {
3212 if (CtdlCheckInternetMailPermission(&CC->user)==0) {
3213 cprintf("%d You do not have permission "
3214 "to send Internet mail.\n",
3215 ERROR + HIGHER_ACCESS_REQUIRED);
3223 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)
3224 && (CC->user.axlevel < 4) ) {
3225 cprintf("%d Higher access required for network mail.\n",
3226 ERROR + HIGHER_ACCESS_REQUIRED);
3233 if ((RESTRICT_INTERNET == 1)
3234 && (valid_to->num_internet + valid_cc->num_internet + valid_bcc->num_internet > 0)
3235 && ((CC->user.flags & US_INTERNET) == 0)
3236 && (!CC->internal_pgm)) {
3237 cprintf("%d You don't have access to Internet mail.\n",
3238 ERROR + HIGHER_ACCESS_REQUIRED);
3247 /* Is this a room which has anonymous-only or anonymous-option? */
3248 anonymous = MES_NORMAL;
3249 if (CC->room.QRflags & QR_ANONONLY) {
3250 anonymous = MES_ANONONLY;
3252 if (CC->room.QRflags & QR_ANONOPT) {
3253 if (anon_flag == 1) { /* only if the user requested it */
3254 anonymous = MES_ANONOPT;
3258 if ((CC->room.QRflags & QR_MAILBOX) == 0) {
3262 /* If we're only checking the validity of the request, return
3263 * success without creating the message.
3266 cprintf("%d %s\n", CIT_OK,
3267 ((valid_to != NULL) ? valid_to->display_recp : "") );
3274 /* We don't need these anymore because we'll do it differently below */
3279 /* Handle author masquerading */
3280 if (CC->fake_postname[0]) {
3281 strcpy(masquerade_as, CC->fake_postname);
3283 else if (CC->fake_username[0]) {
3284 strcpy(masquerade_as, CC->fake_username);
3287 strcpy(masquerade_as, "");
3290 /* Read in the message from the client. */
3292 cprintf("%d send message\n", START_CHAT_MODE);
3294 cprintf("%d send message\n", SEND_LISTING);
3297 msg = CtdlMakeMessage(&CC->user, recp, cc,
3298 CC->room.QRname, anonymous, format_type,
3299 masquerade_as, subject,
3300 ((strlen(supplied_euid) > 0) ? supplied_euid : NULL),
3303 /* Put together one big recipients struct containing to/cc/bcc all in
3304 * one. This is for the envelope.
3306 char *all_recps = malloc(SIZ * 3);
3307 strcpy(all_recps, recp);
3308 if (strlen(cc) > 0) {
3309 if (strlen(all_recps) > 0) {
3310 strcat(all_recps, ",");
3312 strcat(all_recps, cc);
3314 if (strlen(bcc) > 0) {
3315 if (strlen(all_recps) > 0) {
3316 strcat(all_recps, ",");
3318 strcat(all_recps, bcc);
3320 if (strlen(all_recps) > 0) {
3321 valid = validate_recipients(all_recps);
3329 msgnum = CtdlSubmitMsg(msg, valid, "");
3332 cprintf("%ld\n", msgnum);
3334 cprintf("Message accepted.\n");
3337 cprintf("Internal error.\n");
3339 if (msg->cm_fields['E'] != NULL) {
3340 cprintf("%s\n", msg->cm_fields['E']);
3347 CtdlFreeMessage(msg);
3349 CC->fake_postname[0] = '\0';
3350 if (valid != NULL) {
3359 * API function to delete messages which match a set of criteria
3360 * (returns the actual number of messages deleted)
3362 int CtdlDeleteMessages(char *room_name, /* which room */
3363 long *dmsgnums, /* array of msg numbers to be deleted */
3364 int num_dmsgnums, /* number of msgs to be deleted, or 0 for "any" */
3365 char *content_type, /* or "" for any */
3366 int deferred /* let TDAP sweep it later */
3370 struct ctdlroom qrbuf;
3371 struct cdbdata *cdbfr;
3372 long *msglist = NULL;
3373 long *dellist = NULL;
3376 int num_deleted = 0;
3378 struct MetaData smi;
3380 lprintf(CTDL_DEBUG, "CtdlDeleteMessages(%s, %d msgs, %s, %d)\n",
3381 room_name, num_dmsgnums, content_type, deferred);
3383 /* get room record, obtaining a lock... */
3384 if (lgetroom(&qrbuf, room_name) != 0) {
3385 lprintf(CTDL_ERR, "CtdlDeleteMessages(): Room <%s> not found\n",
3387 return (0); /* room not found */
3389 cdbfr = cdb_fetch(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long));
3391 if (cdbfr != NULL) {
3392 dellist = malloc(cdbfr->len);
3393 msglist = (long *) cdbfr->ptr;
3394 cdbfr->ptr = NULL; /* CtdlDeleteMessages() now owns this memory */
3395 num_msgs = cdbfr->len / sizeof(long);
3399 for (i = 0; i < num_msgs; ++i) {
3402 /* Set/clear a bit for each criterion */
3404 /* 0 messages in the list or a null list means that we are
3405 * interested in deleting any messages which meet the other criteria.
3407 if ((num_dmsgnums == 0) || (dmsgnums == NULL)) {
3408 delete_this |= 0x01;
3411 for (j=0; j<num_dmsgnums; ++j) {
3412 if (msglist[i] == dmsgnums[j]) {
3413 delete_this |= 0x01;
3418 if (strlen(content_type) == 0) {
3419 delete_this |= 0x02;
3421 GetMetaData(&smi, msglist[i]);
3422 if (!strcasecmp(smi.meta_content_type,
3424 delete_this |= 0x02;
3428 /* Delete message only if all bits are set */
3429 if (delete_this == 0x03) {
3430 dellist[num_deleted++] = msglist[i];
3435 num_msgs = sort_msglist(msglist, num_msgs);
3436 cdb_store(CDB_MSGLISTS, &qrbuf.QRnumber, (int)sizeof(long),
3437 msglist, (int)(num_msgs * sizeof(long)));
3439 qrbuf.QRhighest = msglist[num_msgs - 1];
3444 * If the delete operation is "deferred" (and technically, any delete
3445 * operation not performed by THE DREADED AUTO-PURGER ought to be
3446 * a deferred delete) then we save a pointer to the message in the
3447 * DELETED_MSGS_ROOM. This will cause the reference count to remain
3448 * at least 1, which will save the user from having to synchronously
3449 * wait for various disk-intensive operations to complete.
3451 * Slick -- we now use the new bulk API for moving messages.
3453 if ( (deferred) && (num_deleted) ) {
3454 CtdlCopyMsgsToRoom(dellist, num_deleted, DELETED_MSGS_ROOM);
3457 /* Go through the messages we pulled out of the index, and decrement
3458 * their reference counts by 1. If this is the only room the message
3459 * was in, the reference count will reach zero and the message will
3460 * automatically be deleted from the database. We do this in a
3461 * separate pass because there might be plug-in hooks getting called,
3462 * and we don't want that happening during an S_ROOMS critical
3465 if (num_deleted) for (i=0; i<num_deleted; ++i) {
3466 PerformDeleteHooks(qrbuf.QRname, dellist[i]);
3467 AdjRefCount(dellist[i], -1);
3470 /* Now free the memory we used, and go away. */
3471 if (msglist != NULL) free(msglist);
3472 if (dellist != NULL) free(dellist);
3473 lprintf(CTDL_DEBUG, "%d message(s) deleted.\n", num_deleted);
3474 return (num_deleted);
3480 * Check whether the current user has permission to delete messages from
3481 * the current room (returns 1 for yes, 0 for no)
3483 int CtdlDoIHavePermissionToDeleteMessagesFromThisRoom(void) {
3484 getuser(&CC->user, CC->curr_user);
3485 if ((CC->user.axlevel < 6)
3486 && (CC->user.usernum != CC->room.QRroomaide)
3487 && ((CC->room.QRflags & QR_MAILBOX) == 0)
3488 && (!(CC->internal_pgm))) {
3497 * Delete message from current room
3499 void cmd_dele(char *args)
3508 extract_token(msgset, args, 0, '|', sizeof msgset);
3509 num_msgs = num_tokens(msgset, ',');
3511 cprintf("%d Nothing to do.\n", CIT_OK);
3515 if (CtdlDoIHavePermissionToDeleteMessagesFromThisRoom() == 0) {
3516 cprintf("%d Higher access required.\n",
3517 ERROR + HIGHER_ACCESS_REQUIRED);
3522 * Build our message set to be moved/copied
3524 msgs = malloc(num_msgs * sizeof(long));
3525 for (i=0; i<num_msgs; ++i) {
3526 extract_token(msgtok, msgset, i, ',', sizeof msgtok);
3527 msgs[i] = atol(msgtok);
3530 num_deleted = CtdlDeleteMessages(CC->room.QRname, msgs, num_msgs, "", 1);
3534 cprintf("%d %d message%s deleted.\n", CIT_OK,
3535 num_deleted, ((num_deleted != 1) ? "s" : ""));
3537 cprintf("%d Message not found.\n", ERROR + MESSAGE_NOT_FOUND);
3543 * Back end API function for moves and deletes (multiple messages)
3545 int CtdlCopyMsgsToRoom(long *msgnums, int num_msgs, char *dest) {
3548 err = CtdlSaveMsgPointersInRoom(dest, msgnums, num_msgs, 1, NULL);
3549 if (err != 0) return(err);
3558 * move or copy a message to another room
3560 void cmd_move(char *args)
3567 char targ[ROOMNAMELEN];
3568 struct ctdlroom qtemp;
3575 extract_token(msgset, args, 0, '|', sizeof msgset);
3576 num_msgs = num_tokens(msgset, ',');
3578 cprintf("%d Nothing to do.\n", CIT_OK);
3582 extract_token(targ, args, 1, '|', sizeof targ);
3583 convert_room_name_macros(targ, sizeof targ);
3584 targ[ROOMNAMELEN - 1] = 0;
3585 is_copy = extract_int(args, 2);
3587 if (getroom(&qtemp, targ) != 0) {
3588 cprintf("%d '%s' does not exist.\n",
3589 ERROR + ROOM_NOT_FOUND, targ);
3593 getuser(&CC->user, CC->curr_user);
3594 CtdlRoomAccess(&qtemp, &CC->user, &ra, NULL);
3596 /* Check for permission to perform this operation.
3597 * Remember: "CC->room" is source, "qtemp" is target.
3601 /* Aides can move/copy */
3602 if (CC->user.axlevel >= 6) permit = 1;
3604 /* Room aides can move/copy */
3605 if (CC->user.usernum == CC->room.QRroomaide) permit = 1;
3607 /* Permit move/copy from personal rooms */
3608 if ((CC->room.QRflags & QR_MAILBOX)
3609 && (qtemp.QRflags & QR_MAILBOX)) permit = 1;
3611 /* Permit only copy from public to personal room */
3613 && (!(CC->room.QRflags & QR_MAILBOX))
3614 && (qtemp.QRflags & QR_MAILBOX)) permit = 1;
3616 /* User must have access to target room */
3617 if (!(ra & UA_KNOWN)) permit = 0;
3620 cprintf("%d Higher access required.\n",
3621 ERROR + HIGHER_ACCESS_REQUIRED);
3626 * Build our message set to be moved/copied
3628 msgs = malloc(num_msgs * sizeof(long));
3629 for (i=0; i<num_msgs; ++i) {
3630 extract_token(msgtok, msgset, i, ',', sizeof msgtok);
3631 msgs[i] = atol(msgtok);
3637 err = CtdlCopyMsgsToRoom(msgs, num_msgs, targ);
3639 cprintf("%d Cannot store message(s) in %s: error %d\n",
3645 /* Now delete the message from the source room,
3646 * if this is a 'move' rather than a 'copy' operation.
3649 CtdlDeleteMessages(CC->room.QRname, msgs, num_msgs, "", 0);
3653 cprintf("%d Message(s) %s.\n", CIT_OK, (is_copy ? "copied" : "moved") );
3659 * GetMetaData() - Get the supplementary record for a message
3661 void GetMetaData(struct MetaData *smibuf, long msgnum)
3664 struct cdbdata *cdbsmi;
3667 memset(smibuf, 0, sizeof(struct MetaData));
3668 smibuf->meta_msgnum = msgnum;
3669 smibuf->meta_refcount = 1; /* Default reference count is 1 */
3671 /* Use the negative of the message number for its supp record index */
3672 TheIndex = (0L - msgnum);
3674 cdbsmi = cdb_fetch(CDB_MSGMAIN, &TheIndex, sizeof(long));
3675 if (cdbsmi == NULL) {
3676 return; /* record not found; go with defaults */
3678 memcpy(smibuf, cdbsmi->ptr,
3679 ((cdbsmi->len > sizeof(struct MetaData)) ?
3680 sizeof(struct MetaData) : cdbsmi->len));
3687 * PutMetaData() - (re)write supplementary record for a message
3689 void PutMetaData(struct MetaData *smibuf)
3693 /* Use the negative of the message number for the metadata db index */
3694 TheIndex = (0L - smibuf->meta_msgnum);
3696 lprintf(CTDL_DEBUG, "PutMetaData(%ld) - ref count is %d\n",
3697 smibuf->meta_msgnum, smibuf->meta_refcount);
3699 cdb_store(CDB_MSGMAIN,
3700 &TheIndex, (int)sizeof(long),
3701 smibuf, (int)sizeof(struct MetaData));
3706 * AdjRefCount - change the reference count for a message;
3707 * delete the message if it reaches zero
3709 void AdjRefCount(long msgnum, int incr)
3712 struct MetaData smi;
3715 /* This is a *tight* critical section; please keep it that way, as
3716 * it may get called while nested in other critical sections.
3717 * Complicating this any further will surely cause deadlock!
3719 begin_critical_section(S_SUPPMSGMAIN);
3720 GetMetaData(&smi, msgnum);
3721 smi.meta_refcount += incr;
3723 end_critical_section(S_SUPPMSGMAIN);
3724 lprintf(CTDL_DEBUG, "msg %ld ref count incr %d, is now %d\n",
3725 msgnum, incr, smi.meta_refcount);
3727 /* If the reference count is now zero, delete the message
3728 * (and its supplementary record as well).
3730 if (smi.meta_refcount == 0) {
3731 lprintf(CTDL_DEBUG, "Deleting message <%ld>\n", msgnum);
3733 /* Remove from fulltext index */
3734 if (config.c_enable_fulltext) {
3735 ft_index_message(msgnum, 0);
3738 /* Remove from message base */
3740 cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long));
3741 cdb_delete(CDB_BIGMSGS, &delnum, (int)sizeof(long));
3743 /* Remove metadata record */
3744 delnum = (0L - msgnum);
3745 cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long));
3750 * Write a generic object to this room
3752 * Note: this could be much more efficient. Right now we use two temporary
3753 * files, and still pull the message into memory as with all others.
3755 void CtdlWriteObject(char *req_room, /* Room to stuff it in */
3756 char *content_type, /* MIME type of this object */
3757 char *tempfilename, /* Where to fetch it from */
3758 struct ctdluser *is_mailbox, /* Mailbox room? */
3759 int is_binary, /* Is encoding necessary? */
3760 int is_unique, /* Del others of this type? */
3761 unsigned int flags /* Internal save flags */
3766 struct ctdlroom qrbuf;
3767 char roomname[ROOMNAMELEN];
3768 struct CtdlMessage *msg;
3770 char *raw_message = NULL;
3771 char *encoded_message = NULL;
3772 off_t raw_length = 0;
3774 if (is_mailbox != NULL) {
3775 MailboxName(roomname, sizeof roomname, is_mailbox, req_room);
3778 safestrncpy(roomname, req_room, sizeof(roomname));
3781 fp = fopen(tempfilename, "rb");
3783 lprintf(CTDL_CRIT, "Cannot open %s: %s\n",
3784 tempfilename, strerror(errno));
3787 fseek(fp, 0L, SEEK_END);
3788 raw_length = ftell(fp);
3790 lprintf(CTDL_DEBUG, "Raw length is %ld\n", (long)raw_length);
3792 raw_message = malloc((size_t)raw_length + 2);
3793 fread(raw_message, (size_t)raw_length, 1, fp);
3797 encoded_message = malloc((size_t)
3798 (((raw_length * 134) / 100) + 4096 ) );
3801 encoded_message = malloc((size_t)(raw_length + 4096));
3804 sprintf(encoded_message, "Content-type: %s\n", content_type);
3807 sprintf(&encoded_message[strlen(encoded_message)],
3808 "Content-transfer-encoding: base64\n\n"
3812 sprintf(&encoded_message[strlen(encoded_message)],
3813 "Content-transfer-encoding: 7bit\n\n"
3819 &encoded_message[strlen(encoded_message)],
3825 raw_message[raw_length] = 0;
3827 &encoded_message[strlen(encoded_message)],
3835 lprintf(CTDL_DEBUG, "Allocating\n");
3836 msg = malloc(sizeof(struct CtdlMessage));
3837 memset(msg, 0, sizeof(struct CtdlMessage));
3838 msg->cm_magic = CTDLMESSAGE_MAGIC;
3839 msg->cm_anon_type = MES_NORMAL;
3840 msg->cm_format_type = 4;
3841 msg->cm_fields['A'] = strdup(CC->user.fullname);
3842 msg->cm_fields['O'] = strdup(req_room);
3843 msg->cm_fields['N'] = strdup(config.c_nodename);
3844 msg->cm_fields['H'] = strdup(config.c_humannode);
3845 msg->cm_flags = flags;
3847 msg->cm_fields['M'] = encoded_message;
3849 /* Create the requested room if we have to. */
3850 if (getroom(&qrbuf, roomname) != 0) {
3851 create_room(roomname,
3852 ( (is_mailbox != NULL) ? 5 : 3 ),
3853 "", 0, 1, 0, VIEW_BBS);
3855 /* If the caller specified this object as unique, delete all
3856 * other objects of this type that are currently in the room.
3859 lprintf(CTDL_DEBUG, "Deleted %d other msgs of this type\n",
3860 CtdlDeleteMessages(roomname, NULL, 0, content_type, 0)
3863 /* Now write the data */
3864 CtdlSubmitMsg(msg, NULL, roomname);
3865 CtdlFreeMessage(msg);
3873 void CtdlGetSysConfigBackend(long msgnum, void *userdata) {
3874 config_msgnum = msgnum;
3878 char *CtdlGetSysConfig(char *sysconfname) {
3879 char hold_rm[ROOMNAMELEN];
3882 struct CtdlMessage *msg;
3885 strcpy(hold_rm, CC->room.QRname);
3886 if (getroom(&CC->room, SYSCONFIGROOM) != 0) {
3887 getroom(&CC->room, hold_rm);
3892 /* We want the last (and probably only) config in this room */
3893 begin_critical_section(S_CONFIG);
3894 config_msgnum = (-1L);
3895 CtdlForEachMessage(MSGS_LAST, 1, NULL, sysconfname, NULL,
3896 CtdlGetSysConfigBackend, NULL);
3897 msgnum = config_msgnum;
3898 end_critical_section(S_CONFIG);
3904 msg = CtdlFetchMessage(msgnum, 1);
3906 conf = strdup(msg->cm_fields['M']);
3907 CtdlFreeMessage(msg);
3914 getroom(&CC->room, hold_rm);
3916 if (conf != NULL) do {
3917 extract_token(buf, conf, 0, '\n', sizeof buf);
3918 strcpy(conf, &conf[strlen(buf)+1]);
3919 } while ( (strlen(conf)>0) && (strlen(buf)>0) );
3924 void CtdlPutSysConfig(char *sysconfname, char *sysconfdata) {
3925 char temp[PATH_MAX];
3928 CtdlMakeTempFileName(temp, sizeof temp);
3930 fp = fopen(temp, "w");
3931 if (fp == NULL) return;
3932 fprintf(fp, "%s", sysconfdata);
3935 /* this handy API function does all the work for us */
3936 CtdlWriteObject(SYSCONFIGROOM, sysconfname, temp, NULL, 0, 1, 0);
3942 * Determine whether a given Internet address belongs to the current user
3944 int CtdlIsMe(char *addr, int addr_buf_len)
3946 struct recptypes *recp;
3949 recp = validate_recipients(addr);
3950 if (recp == NULL) return(0);
3952 if (recp->num_local == 0) {
3957 for (i=0; i<recp->num_local; ++i) {
3958 extract_token(addr, recp->recp_local, i, '|', addr_buf_len);
3959 if (!strcasecmp(addr, CC->user.fullname)) {
3971 * Citadel protocol command to do the same
3973 void cmd_isme(char *argbuf) {
3976 if (CtdlAccessCheck(ac_logged_in)) return;
3977 extract_token(addr, argbuf, 0, '|', sizeof addr);
3979 if (CtdlIsMe(addr, sizeof addr)) {
3980 cprintf("%d %s\n", CIT_OK, addr);
3983 cprintf("%d Not you.\n", ERROR + ILLEGAL_VALUE);