4 * Implements the message store.
14 #if TIME_WITH_SYS_TIME
15 # include <sys/time.h>
19 # include <sys/time.h>
34 #include "serv_extensions.h"
38 #include "sysdep_decls.h"
39 #include "citserver.h"
46 #include "mime_parser.h"
49 #include "internet_addressing.h"
50 #include "serv_fulltext.h"
52 #include "euidindex.h"
53 #include "journaling.h"
54 #include "citadel_dirs.h"
55 #include "serv_network.h"
58 struct addresses_to_be_filed *atbf = NULL;
61 * This really belongs in serv_network.c, but I don't know how to export
62 * symbols between modules.
64 struct FilterList *filterlist = NULL;
68 * These are the four-character field headers we use when outputting
69 * messages in Citadel format (as opposed to RFC822 format).
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,
79 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
107 * This function is self explanatory.
108 * (What can I say, I'm in a weird mood today...)
110 void remove_any_whitespace_to_the_left_or_right_of_at_symbol(char *name)
114 for (i = 0; i < strlen(name); ++i) {
115 if (name[i] == '@') {
116 while (isspace(name[i - 1]) && i > 0) {
117 strcpy(&name[i - 1], &name[i]);
120 while (isspace(name[i + 1])) {
121 strcpy(&name[i + 1], &name[i + 2]);
129 * Aliasing for network mail.
130 * (Error messages have been commented out, because this is a server.)
132 int alias(char *name)
133 { /* process alias and routing info for mail */
136 char aaa[SIZ], bbb[SIZ];
137 char *ignetcfg = NULL;
138 char *ignetmap = NULL;
145 remove_any_whitespace_to_the_left_or_right_of_at_symbol(name);
146 stripallbut(name, '<', '>');
148 fp = fopen(file_mail_aliases, "r");
150 fp = fopen("/dev/null", "r");
157 while (fgets(aaa, sizeof aaa, fp) != NULL) {
158 while (isspace(name[0]))
159 strcpy(name, &name[1]);
160 aaa[strlen(aaa) - 1] = 0;
162 for (a = 0; a < strlen(aaa); ++a) {
164 strcpy(bbb, &aaa[a + 1]);
168 if (!strcasecmp(name, aaa))
173 /* Hit the Global Address Book */
174 if (CtdlDirectoryLookup(aaa, name, sizeof aaa) == 0) {
178 lprintf(CTDL_INFO, "Mail is being forwarded to %s\n", name);
180 /* Change "user @ xxx" to "user" if xxx is an alias for this host */
181 for (a=0; a<strlen(name); ++a) {
182 if (name[a] == '@') {
183 if (CtdlHostAlias(&name[a+1]) == hostalias_localhost) {
185 lprintf(CTDL_INFO, "Changed to <%s>\n", name);
190 /* determine local or remote type, see citadel.h */
191 at = haschar(name, '@');
192 if (at == 0) return(MES_LOCAL); /* no @'s - local address */
193 if (at > 1) return(MES_ERROR); /* >1 @'s - invalid address */
194 remove_any_whitespace_to_the_left_or_right_of_at_symbol(name);
196 /* figure out the delivery mode */
197 extract_token(node, name, 1, '@', sizeof node);
199 /* If there are one or more dots in the nodename, we assume that it
200 * is an FQDN and will attempt SMTP delivery to the Internet.
202 if (haschar(node, '.') > 0) {
203 return(MES_INTERNET);
206 /* Otherwise we look in the IGnet maps for a valid Citadel node.
207 * Try directly-connected nodes first...
209 ignetcfg = CtdlGetSysConfig(IGNETCFG);
210 for (i=0; i<num_tokens(ignetcfg, '\n'); ++i) {
211 extract_token(buf, ignetcfg, i, '\n', sizeof buf);
212 extract_token(testnode, buf, 0, '|', sizeof testnode);
213 if (!strcasecmp(node, testnode)) {
221 * Then try nodes that are two or more hops away.
223 ignetmap = CtdlGetSysConfig(IGNETMAP);
224 for (i=0; i<num_tokens(ignetmap, '\n'); ++i) {
225 extract_token(buf, ignetmap, i, '\n', sizeof buf);
226 extract_token(testnode, buf, 0, '|', sizeof testnode);
227 if (!strcasecmp(node, testnode)) {
234 /* If we get to this point it's an invalid node name */
243 fp = fopen(file_citadel_control, "r");
245 lprintf(CTDL_CRIT, "Cannot open %s: %s\n",
246 file_citadel_control,
250 fread((char *) &CitControl, sizeof(struct CitControl), 1, fp);
256 * Back end for the MSGS command: output message number only.
258 void simple_listing(long msgnum, void *userdata)
260 cprintf("%ld\n", msgnum);
266 * Back end for the MSGS command: output header summary.
268 void headers_listing(long msgnum, void *userdata)
270 struct CtdlMessage *msg;
272 msg = CtdlFetchMessage(msgnum, 0);
274 cprintf("%ld|0|||||\n", msgnum);
278 cprintf("%ld|%s|%s|%s|%s|%s|\n",
280 (msg->cm_fields['T'] ? msg->cm_fields['T'] : "0"),
281 (msg->cm_fields['A'] ? msg->cm_fields['A'] : ""),
282 (msg->cm_fields['N'] ? msg->cm_fields['N'] : ""),
283 (msg->cm_fields['F'] ? msg->cm_fields['F'] : ""),
284 (msg->cm_fields['U'] ? msg->cm_fields['U'] : "")
286 CtdlFreeMessage(msg);
291 /* Determine if a given message matches the fields in a message template.
292 * Return 0 for a successful match.
294 int CtdlMsgCmp(struct CtdlMessage *msg, struct CtdlMessage *template) {
297 /* If there aren't any fields in the template, all messages will
300 if (template == NULL) return(0);
302 /* Null messages are bogus. */
303 if (msg == NULL) return(1);
305 for (i='A'; i<='Z'; ++i) {
306 if (template->cm_fields[i] != NULL) {
307 if (msg->cm_fields[i] == NULL) {
310 if (strcasecmp(msg->cm_fields[i],
311 template->cm_fields[i])) return 1;
315 /* All compares succeeded: we have a match! */
322 * Retrieve the "seen" message list for the current room.
324 void CtdlGetSeen(char *buf, int which_set) {
327 /* Learn about the user and room in question */
328 CtdlGetRelationship(&vbuf, &CC->user, &CC->room);
330 if (which_set == ctdlsetseen_seen)
331 safestrncpy(buf, vbuf.v_seen, SIZ);
332 if (which_set == ctdlsetseen_answered)
333 safestrncpy(buf, vbuf.v_answered, SIZ);
339 * Manipulate the "seen msgs" string (or other message set strings)
341 void CtdlSetSeen(long *target_msgnums, int num_target_msgnums,
342 int target_setting, int which_set,
343 struct ctdluser *which_user, struct ctdlroom *which_room) {
344 struct cdbdata *cdbfr;
356 char *is_set; /* actually an array of booleans */
359 char setstr[SIZ], lostr[SIZ], histr[SIZ];
362 lprintf(CTDL_DEBUG, "CtdlSetSeen(%d msgs starting with %ld, %d, %d)\n",
363 num_target_msgnums, target_msgnums[0],
364 target_setting, which_set);
366 /* Learn about the user and room in question */
367 CtdlGetRelationship(&vbuf,
368 ((which_user != NULL) ? which_user : &CC->user),
369 ((which_room != NULL) ? which_room : &CC->room)
372 /* Load the message list */
373 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long));
375 msglist = (long *) cdbfr->ptr;
376 cdbfr->ptr = NULL; /* CtdlSetSeen() now owns this memory */
377 num_msgs = cdbfr->len / sizeof(long);
380 return; /* No messages at all? No further action. */
383 is_set = malloc(num_msgs * sizeof(char));
384 memset(is_set, 0, (num_msgs * sizeof(char)) );
386 /* Decide which message set we're manipulating */
388 case ctdlsetseen_seen:
389 safestrncpy(vset, vbuf.v_seen, sizeof vset);
391 case ctdlsetseen_answered:
392 safestrncpy(vset, vbuf.v_answered, sizeof vset);
396 /* lprintf(CTDL_DEBUG, "before optimize: %s\n", vset); */
398 /* Translate the existing sequence set into an array of booleans */
399 num_sets = num_tokens(vset, ',');
400 for (s=0; s<num_sets; ++s) {
401 extract_token(setstr, vset, s, ',', sizeof setstr);
403 extract_token(lostr, setstr, 0, ':', sizeof lostr);
404 if (num_tokens(setstr, ':') >= 2) {
405 extract_token(histr, setstr, 1, ':', sizeof histr);
406 if (!strcmp(histr, "*")) {
407 snprintf(histr, sizeof histr, "%ld", LONG_MAX);
411 strcpy(histr, lostr);
416 for (i = 0; i < num_msgs; ++i) {
417 if ((msglist[i] >= lo) && (msglist[i] <= hi)) {
423 /* Now translate the array of booleans back into a sequence set */
428 for (i=0; i<num_msgs; ++i) {
430 is_seen = is_set[i]; /* Default to existing setting */
432 for (k=0; k<num_target_msgnums; ++k) {
433 if (msglist[i] == target_msgnums[k]) {
434 is_seen = target_setting;
439 if (lo < 0L) lo = msglist[i];
443 if ( ((is_seen == 0) && (was_seen == 1))
444 || ((is_seen == 1) && (i == num_msgs-1)) ) {
446 /* begin trim-o-matic code */
449 while ( (strlen(vset) + 20) > sizeof vset) {
450 remove_token(vset, 0, ',');
452 if (j--) break; /* loop no more than 9 times */
454 if ( (trimming) && (which_set == ctdlsetseen_seen) ) {
458 snprintf(lostr, sizeof lostr,
459 "1:%ld,%s", t, vset);
460 safestrncpy(vset, lostr, sizeof vset);
462 /* end trim-o-matic code */
470 snprintf(&vset[tmp], (sizeof vset) - tmp,
474 snprintf(&vset[tmp], (sizeof vset) - tmp,
483 /* Decide which message set we're manipulating */
485 case ctdlsetseen_seen:
486 safestrncpy(vbuf.v_seen, vset, sizeof vbuf.v_seen);
488 case ctdlsetseen_answered:
489 safestrncpy(vbuf.v_answered, vset,
490 sizeof vbuf.v_answered);
495 /* lprintf(CTDL_DEBUG, " after optimize: %s\n", vset); */
497 CtdlSetRelationship(&vbuf,
498 ((which_user != NULL) ? which_user : &CC->user),
499 ((which_room != NULL) ? which_room : &CC->room)
505 * API function to perform an operation for each qualifying message in the
506 * current room. (Returns the number of messages processed.)
508 int CtdlForEachMessage(int mode, long ref, char *search_string,
510 struct CtdlMessage *compare,
511 void (*CallBack) (long, void *),
517 struct cdbdata *cdbfr;
518 long *msglist = NULL;
520 int num_processed = 0;
523 struct CtdlMessage *msg;
526 int printed_lastold = 0;
527 int num_search_msgs = 0;
528 long *search_msgs = NULL;
530 /* Learn about the user and room in question */
532 getuser(&CC->user, CC->curr_user);
533 CtdlGetRelationship(&vbuf, &CC->user, &CC->room);
535 /* Load the message list */
536 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long));
538 msglist = (long *) cdbfr->ptr;
539 cdbfr->ptr = NULL; /* CtdlForEachMessage() now owns this memory */
540 num_msgs = cdbfr->len / sizeof(long);
543 return 0; /* No messages at all? No further action. */
548 * Now begin the traversal.
550 if (num_msgs > 0) for (a = 0; a < num_msgs; ++a) {
552 /* If the caller is looking for a specific MIME type, filter
553 * out all messages which are not of the type requested.
555 if (content_type != NULL) if (strlen(content_type) > 0) {
557 /* This call to GetMetaData() sits inside this loop
558 * so that we only do the extra database read per msg
559 * if we need to. Doing the extra read all the time
560 * really kills the server. If we ever need to use
561 * metadata for another search criterion, we need to
562 * move the read somewhere else -- but still be smart
563 * enough to only do the read if the caller has
564 * specified something that will need it.
566 GetMetaData(&smi, msglist[a]);
568 if (strcasecmp(smi.meta_content_type, content_type)) {
574 num_msgs = sort_msglist(msglist, num_msgs);
576 /* If a template was supplied, filter out the messages which
577 * don't match. (This could induce some delays!)
580 if (compare != NULL) {
581 for (a = 0; a < num_msgs; ++a) {
582 msg = CtdlFetchMessage(msglist[a], 1);
584 if (CtdlMsgCmp(msg, compare)) {
587 CtdlFreeMessage(msg);
593 /* If a search string was specified, get a message list from
594 * the full text index and remove messages which aren't on both
598 * Since the lists are sorted and strictly ascending, and the
599 * output list is guaranteed to be shorter than or equal to the
600 * input list, we overwrite the bottom of the input list. This
601 * eliminates the need to memmove big chunks of the list over and
604 if ( (num_msgs > 0) && (mode == MSGS_SEARCH) && (search_string) ) {
605 ft_search(&num_search_msgs, &search_msgs, search_string);
606 if (num_search_msgs > 0) {
610 orig_num_msgs = num_msgs;
612 for (i=0; i<orig_num_msgs; ++i) {
613 for (j=0; j<num_search_msgs; ++j) {
614 if (msglist[i] == search_msgs[j]) {
615 msglist[num_msgs++] = msglist[i];
621 num_msgs = 0; /* No messages qualify */
623 if (search_msgs != NULL) free(search_msgs);
625 /* Now that we've purged messages which don't contain the search
626 * string, treat a MSGS_SEARCH just like a MSGS_ALL from this
633 * Now iterate through the message list, according to the
634 * criteria supplied by the caller.
637 for (a = 0; a < num_msgs; ++a) {
638 thismsg = msglist[a];
639 if (mode == MSGS_ALL) {
643 is_seen = is_msg_in_sequence_set(
644 vbuf.v_seen, thismsg);
645 if (is_seen) lastold = thismsg;
651 || ((mode == MSGS_OLD) && (is_seen))
652 || ((mode == MSGS_NEW) && (!is_seen))
653 || ((mode == MSGS_LAST) && (a >= (num_msgs - ref)))
654 || ((mode == MSGS_FIRST) && (a < ref))
655 || ((mode == MSGS_GT) && (thismsg > ref))
656 || ((mode == MSGS_EQ) && (thismsg == ref))
659 if ((mode == MSGS_NEW) && (CC->user.flags & US_LASTOLD) && (lastold > 0L) && (printed_lastold == 0) && (!is_seen)) {
661 CallBack(lastold, userdata);
665 if (CallBack) CallBack(thismsg, userdata);
669 free(msglist); /* Clean up */
670 return num_processed;
676 * cmd_msgs() - get list of message #'s in this room
677 * implements the MSGS server command using CtdlForEachMessage()
679 void cmd_msgs(char *cmdbuf)
688 int with_template = 0;
689 struct CtdlMessage *template = NULL;
690 int with_headers = 0;
691 char search_string[1024];
693 extract_token(which, cmdbuf, 0, '|', sizeof which);
694 cm_ref = extract_int(cmdbuf, 1);
695 extract_token(search_string, cmdbuf, 1, '|', sizeof search_string);
696 with_template = extract_int(cmdbuf, 2);
697 with_headers = extract_int(cmdbuf, 3);
700 if (!strncasecmp(which, "OLD", 3))
702 else if (!strncasecmp(which, "NEW", 3))
704 else if (!strncasecmp(which, "FIRST", 5))
706 else if (!strncasecmp(which, "LAST", 4))
708 else if (!strncasecmp(which, "GT", 2))
710 else if (!strncasecmp(which, "SEARCH", 6))
715 if ((!(CC->logged_in)) && (!(CC->internal_pgm))) {
716 cprintf("%d not logged in\n", ERROR + NOT_LOGGED_IN);
720 if ( (mode == MSGS_SEARCH) && (!config.c_enable_fulltext) ) {
721 cprintf("%d Full text index is not enabled on this server.\n",
722 ERROR + CMD_NOT_SUPPORTED);
728 cprintf("%d Send template then receive message list\n",
730 template = (struct CtdlMessage *)
731 malloc(sizeof(struct CtdlMessage));
732 memset(template, 0, sizeof(struct CtdlMessage));
733 while(client_getln(buf, sizeof buf), strcmp(buf,"000")) {
734 extract_token(tfield, buf, 0, '|', sizeof tfield);
735 extract_token(tvalue, buf, 1, '|', sizeof tvalue);
736 for (i='A'; i<='Z'; ++i) if (msgkeys[i]!=NULL) {
737 if (!strcasecmp(tfield, msgkeys[i])) {
738 template->cm_fields[i] =
746 cprintf("%d \n", LISTING_FOLLOWS);
749 CtdlForEachMessage(mode,
750 ( (mode == MSGS_SEARCH) ? 0 : cm_ref ),
751 ( (mode == MSGS_SEARCH) ? search_string : NULL ),
754 (with_headers ? headers_listing : simple_listing),
757 if (template != NULL) CtdlFreeMessage(template);
765 * help_subst() - support routine for help file viewer
767 void help_subst(char *strbuf, char *source, char *dest)
772 while (p = pattern2(strbuf, source), (p >= 0)) {
773 strcpy(workbuf, &strbuf[p + strlen(source)]);
774 strcpy(&strbuf[p], dest);
775 strcat(strbuf, workbuf);
780 void do_help_subst(char *buffer)
784 help_subst(buffer, "^nodename", config.c_nodename);
785 help_subst(buffer, "^humannode", config.c_humannode);
786 help_subst(buffer, "^fqdn", config.c_fqdn);
787 help_subst(buffer, "^username", CC->user.fullname);
788 snprintf(buf2, sizeof buf2, "%ld", CC->user.usernum);
789 help_subst(buffer, "^usernum", buf2);
790 help_subst(buffer, "^sysadm", config.c_sysadm);
791 help_subst(buffer, "^variantname", CITADEL);
792 snprintf(buf2, sizeof buf2, "%d", config.c_maxsessions);
793 help_subst(buffer, "^maxsessions", buf2);
794 help_subst(buffer, "^bbsdir", ctdl_bbsbase_dir);
800 * memfmout() - Citadel text formatter and paginator.
801 * Although the original purpose of this routine was to format
802 * text to the reader's screen width, all we're really using it
803 * for here is to format text out to 80 columns before sending it
804 * to the client. The client software may reformat it again.
807 char *mptr, /* where are we going to get our text from? */
808 char subst, /* nonzero if we should do substitutions */
809 char *nl) /* string to terminate lines with */
817 static int width = 80;
822 c = 1; /* c is the current pos */
826 while (ch = *mptr, ((ch != 0) && (strlen(buffer) < 126))) {
828 buffer[strlen(buffer) + 1] = 0;
829 buffer[strlen(buffer)] = ch;
832 if (buffer[0] == '^')
833 do_help_subst(buffer);
835 buffer[strlen(buffer) + 1] = 0;
837 strcpy(buffer, &buffer[1]);
845 if (((ch == 13) || (ch == 10)) && (old != 13) && (old != 10)) {
848 if (((old == 13) || (old == 10)) && (isspace(real))) {
853 if (((strlen(aaa) + c) > (width - 5)) && (strlen(aaa) > (width - 5))) {
854 cprintf("%s%s", nl, aaa);
863 if ((strlen(aaa) + c) > (width - 5)) {
872 if ((ch == 13) || (ch == 10)) {
873 cprintf("%s%s", aaa, nl);
880 cprintf("%s%s", aaa, nl);
886 * Callback function for mime parser that simply lists the part
888 void list_this_part(char *name, char *filename, char *partnum, char *disp,
889 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
894 ma = (struct ma_info *)cbuserdata;
895 if (ma->is_ma == 0) {
896 cprintf("part=%s|%s|%s|%s|%s|%ld\n",
897 name, filename, partnum, disp, cbtype, (long)length);
902 * Callback function for multipart prefix
904 void list_this_pref(char *name, char *filename, char *partnum, char *disp,
905 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
910 ma = (struct ma_info *)cbuserdata;
911 if (!strcasecmp(cbtype, "multipart/alternative")) {
915 if (ma->is_ma == 0) {
916 cprintf("pref=%s|%s\n", partnum, cbtype);
921 * Callback function for multipart sufffix
923 void list_this_suff(char *name, char *filename, char *partnum, char *disp,
924 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
929 ma = (struct ma_info *)cbuserdata;
930 if (ma->is_ma == 0) {
931 cprintf("suff=%s|%s\n", partnum, cbtype);
933 if (!strcasecmp(cbtype, "multipart/alternative")) {
940 * Callback function for mime parser that opens a section for downloading
942 void mime_download(char *name, char *filename, char *partnum, char *disp,
943 void *content, char *cbtype, char *cbcharset, size_t length,
944 char *encoding, void *cbuserdata)
947 /* Silently go away if there's already a download open... */
948 if (CC->download_fp != NULL)
951 /* ...or if this is not the desired section */
952 if (strcasecmp(CC->download_desired_section, partnum))
955 CC->download_fp = tmpfile();
956 if (CC->download_fp == NULL)
959 fwrite(content, length, 1, CC->download_fp);
960 fflush(CC->download_fp);
961 rewind(CC->download_fp);
963 OpenCmdResult(filename, cbtype);
969 * Load a message from disk into memory.
970 * This is used by CtdlOutputMsg() and other fetch functions.
972 * NOTE: Caller is responsible for freeing the returned CtdlMessage struct
973 * using the CtdlMessageFree() function.
975 struct CtdlMessage *CtdlFetchMessage(long msgnum, int with_body)
977 struct cdbdata *dmsgtext;
978 struct CtdlMessage *ret = NULL;
982 cit_uint8_t field_header;
984 lprintf(CTDL_DEBUG, "CtdlFetchMessage(%ld, %d)\n", msgnum, with_body);
986 dmsgtext = cdb_fetch(CDB_MSGMAIN, &msgnum, sizeof(long));
987 if (dmsgtext == NULL) {
990 mptr = dmsgtext->ptr;
991 upper_bound = mptr + dmsgtext->len;
993 /* Parse the three bytes that begin EVERY message on disk.
994 * The first is always 0xFF, the on-disk magic number.
995 * The second is the anonymous/public type byte.
996 * The third is the format type byte (vari, fixed, or MIME).
1001 "Message %ld appears to be corrupted.\n",
1006 ret = (struct CtdlMessage *) malloc(sizeof(struct CtdlMessage));
1007 memset(ret, 0, sizeof(struct CtdlMessage));
1009 ret->cm_magic = CTDLMESSAGE_MAGIC;
1010 ret->cm_anon_type = *mptr++; /* Anon type byte */
1011 ret->cm_format_type = *mptr++; /* Format type byte */
1014 * The rest is zero or more arbitrary fields. Load them in.
1015 * We're done when we encounter either a zero-length field or
1016 * have just processed the 'M' (message text) field.
1019 if (mptr >= upper_bound) {
1022 field_header = *mptr++;
1023 ret->cm_fields[field_header] = strdup(mptr);
1025 while (*mptr++ != 0); /* advance to next field */
1027 } while ((mptr < upper_bound) && (field_header != 'M'));
1031 /* Always make sure there's something in the msg text field. If
1032 * it's NULL, the message text is most likely stored separately,
1033 * so go ahead and fetch that. Failing that, just set a dummy
1034 * body so other code doesn't barf.
1036 if ( (ret->cm_fields['M'] == NULL) && (with_body) ) {
1037 dmsgtext = cdb_fetch(CDB_BIGMSGS, &msgnum, sizeof(long));
1038 if (dmsgtext != NULL) {
1039 ret->cm_fields['M'] = strdup(dmsgtext->ptr);
1043 if (ret->cm_fields['M'] == NULL) {
1044 ret->cm_fields['M'] = strdup("\r\n\r\n (no text)\r\n");
1047 /* Perform "before read" hooks (aborting if any return nonzero) */
1048 if (PerformMessageHooks(ret, EVT_BEFOREREAD) > 0) {
1049 CtdlFreeMessage(ret);
1058 * Returns 1 if the supplied pointer points to a valid Citadel message.
1059 * If the pointer is NULL or the magic number check fails, returns 0.
1061 int is_valid_message(struct CtdlMessage *msg) {
1064 if ((msg->cm_magic) != CTDLMESSAGE_MAGIC) {
1065 lprintf(CTDL_WARNING, "is_valid_message() -- self-check failed\n");
1073 * 'Destructor' for struct CtdlMessage
1075 void CtdlFreeMessage(struct CtdlMessage *msg)
1079 if (is_valid_message(msg) == 0) return;
1081 for (i = 0; i < 256; ++i)
1082 if (msg->cm_fields[i] != NULL) {
1083 free(msg->cm_fields[i]);
1086 msg->cm_magic = 0; /* just in case */
1092 * Pre callback function for multipart/alternative
1094 * NOTE: this differs from the standard behavior for a reason. Normally when
1095 * displaying multipart/alternative you want to show the _last_ usable
1096 * format in the message. Here we show the _first_ one, because it's
1097 * usually text/plain. Since this set of functions is designed for text
1098 * output to non-MIME-aware clients, this is the desired behavior.
1101 void fixed_output_pre(char *name, char *filename, char *partnum, char *disp,
1102 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
1107 ma = (struct ma_info *)cbuserdata;
1108 lprintf(CTDL_DEBUG, "fixed_output_pre() type=<%s>\n", cbtype);
1109 if (!strcasecmp(cbtype, "multipart/alternative")) {
1113 if (!strcasecmp(cbtype, "message/rfc822")) {
1119 * Post callback function for multipart/alternative
1121 void fixed_output_post(char *name, char *filename, char *partnum, char *disp,
1122 void *content, char *cbtype, char *cbcharset, size_t length,
1123 char *encoding, void *cbuserdata)
1127 ma = (struct ma_info *)cbuserdata;
1128 lprintf(CTDL_DEBUG, "fixed_output_post() type=<%s>\n", cbtype);
1129 if (!strcasecmp(cbtype, "multipart/alternative")) {
1133 if (!strcasecmp(cbtype, "message/rfc822")) {
1139 * Inline callback function for mime parser that wants to display text
1141 void fixed_output(char *name, char *filename, char *partnum, char *disp,
1142 void *content, char *cbtype, char *cbcharset, size_t length,
1143 char *encoding, void *cbuserdata)
1150 ma = (struct ma_info *)cbuserdata;
1153 "fixed_output() part %s: %s (%s) (%ld bytes)\n",
1154 partnum, filename, cbtype, (long)length);
1157 * If we're in the middle of a multipart/alternative scope and
1158 * we've already printed another section, skip this one.
1160 if ( (ma->is_ma) && (ma->did_print) ) {
1161 lprintf(CTDL_DEBUG, "Skipping part %s (%s)\n",
1167 if ( (!strcasecmp(cbtype, "text/plain"))
1168 || (strlen(cbtype)==0) ) {
1171 client_write(wptr, length);
1172 if (wptr[length-1] != '\n') {
1179 if (!strcasecmp(cbtype, "text/html")) {
1180 ptr = html_to_ascii(content, length, 80, 0);
1182 client_write(ptr, wlen);
1183 if (ptr[wlen-1] != '\n') {
1190 if (ma->use_fo_hooks) {
1191 if (PerformFixedOutputHooks(cbtype, content, length)) {
1192 /* above function returns nonzero if it handled the part */
1197 if (strncasecmp(cbtype, "multipart/", 10)) {
1198 cprintf("Part %s: %s (%s) (%ld bytes)\r\n",
1199 partnum, filename, cbtype, (long)length);
1205 * The client is elegant and sophisticated and wants to be choosy about
1206 * MIME content types, so figure out which multipart/alternative part
1207 * we're going to send.
1209 void choose_preferred(char *name, char *filename, char *partnum, char *disp,
1210 void *content, char *cbtype, char *cbcharset, size_t length,
1211 char *encoding, void *cbuserdata)
1217 ma = (struct ma_info *)cbuserdata;
1219 if (ma->is_ma > 0) {
1220 for (i=0; i<num_tokens(CC->preferred_formats, '|'); ++i) {
1221 extract_token(buf, CC->preferred_formats, i, '|', sizeof buf);
1222 if ( (!strcasecmp(buf, cbtype)) && (!ma->freeze) ) {
1223 safestrncpy(ma->chosen_part, partnum, sizeof ma->chosen_part);
1230 * Now that we've chosen our preferred part, output it.
1232 void output_preferred(char *name, char *filename, char *partnum, char *disp,
1233 void *content, char *cbtype, char *cbcharset, size_t length,
1234 char *encoding, void *cbuserdata)
1238 int add_newline = 0;
1242 ma = (struct ma_info *)cbuserdata;
1244 /* This is not the MIME part you're looking for... */
1245 if (strcasecmp(partnum, ma->chosen_part)) return;
1247 /* If the content-type of this part is in our preferred formats
1248 * list, we can simply output it verbatim.
1250 for (i=0; i<num_tokens(CC->preferred_formats, '|'); ++i) {
1251 extract_token(buf, CC->preferred_formats, i, '|', sizeof buf);
1252 if (!strcasecmp(buf, cbtype)) {
1253 /* Yeah! Go! W00t!! */
1255 text_content = (char *)content;
1256 if (text_content[length-1] != '\n') {
1260 cprintf("Content-type: %s", cbtype);
1261 if (strlen(cbcharset) > 0) {
1262 cprintf("; charset=%s", cbcharset);
1264 cprintf("\nContent-length: %d\n",
1265 (int)(length + add_newline) );
1266 if (strlen(encoding) > 0) {
1267 cprintf("Content-transfer-encoding: %s\n", encoding);
1270 cprintf("Content-transfer-encoding: 7bit\n");
1273 client_write(content, length);
1274 if (add_newline) cprintf("\n");
1279 /* No translations required or possible: output as text/plain */
1280 cprintf("Content-type: text/plain\n\n");
1281 fixed_output(name, filename, partnum, disp, content, cbtype, cbcharset,
1282 length, encoding, cbuserdata);
1287 char desired_section[64];
1294 * Callback function for
1296 void extract_encapsulated_message(char *name, char *filename, char *partnum, char *disp,
1297 void *content, char *cbtype, char *cbcharset, size_t length,
1298 char *encoding, void *cbuserdata)
1300 struct encapmsg *encap;
1302 encap = (struct encapmsg *)cbuserdata;
1304 /* Only proceed if this is the desired section... */
1305 if (!strcasecmp(encap->desired_section, partnum)) {
1306 encap->msglen = length;
1307 encap->msg = malloc(length + 2);
1308 memcpy(encap->msg, content, length);
1318 * Get a message off disk. (returns om_* values found in msgbase.h)
1321 int CtdlOutputMsg(long msg_num, /* message number (local) to fetch */
1322 int mode, /* how would you like that message? */
1323 int headers_only, /* eschew the message body? */
1324 int do_proto, /* do Citadel protocol responses? */
1325 int crlf, /* Use CRLF newlines instead of LF? */
1326 char *section /* NULL or a message/rfc822 section */
1328 struct CtdlMessage *TheMessage = NULL;
1329 int retcode = om_no_such_msg;
1330 struct encapmsg encap;
1332 lprintf(CTDL_DEBUG, "CtdlOutputMsg() msgnum=%ld, mode=%d, section=%s\n",
1334 (section ? section : "<>")
1337 if ((!(CC->logged_in)) && (!(CC->internal_pgm))) {
1338 if (do_proto) cprintf("%d Not logged in.\n",
1339 ERROR + NOT_LOGGED_IN);
1340 return(om_not_logged_in);
1343 /* FIXME: check message id against msglist for this room */
1346 * Fetch the message from disk. If we're in any sort of headers
1347 * only mode, request that we don't even bother loading the body
1350 if ( (headers_only == HEADERS_FAST) || (headers_only == HEADERS_ONLY) ) {
1351 TheMessage = CtdlFetchMessage(msg_num, 0);
1354 TheMessage = CtdlFetchMessage(msg_num, 1);
1357 if (TheMessage == NULL) {
1358 if (do_proto) cprintf("%d Can't locate msg %ld on disk\n",
1359 ERROR + MESSAGE_NOT_FOUND, msg_num);
1360 return(om_no_such_msg);
1363 /* Here is the weird form of this command, to process only an
1364 * encapsulated message/rfc822 section.
1366 if (section) if (strlen(section)>0) if (strcmp(section, "0")) {
1367 memset(&encap, 0, sizeof encap);
1368 safestrncpy(encap.desired_section, section, sizeof encap.desired_section);
1369 mime_parser(TheMessage->cm_fields['M'],
1371 *extract_encapsulated_message,
1372 NULL, NULL, (void *)&encap, 0
1374 CtdlFreeMessage(TheMessage);
1378 encap.msg[encap.msglen] = 0;
1379 TheMessage = convert_internet_message(encap.msg);
1380 encap.msg = NULL; /* no free() here, TheMessage owns it now */
1382 /* Now we let it fall through to the bottom of this
1383 * function, because TheMessage now contains the
1384 * encapsulated message instead of the top-level
1385 * message. Isn't that neat?
1390 if (do_proto) cprintf("%d msg %ld has no part %s\n",
1391 ERROR + MESSAGE_NOT_FOUND, msg_num, section);
1392 retcode = om_no_such_msg;
1397 /* Ok, output the message now */
1398 retcode = CtdlOutputPreLoadedMsg(
1400 headers_only, do_proto, crlf);
1401 CtdlFreeMessage(TheMessage);
1408 * Get a message off disk. (returns om_* values found in msgbase.h)
1411 int CtdlOutputPreLoadedMsg(
1412 struct CtdlMessage *TheMessage,
1413 int mode, /* how would you like that message? */
1414 int headers_only, /* eschew the message body? */
1415 int do_proto, /* do Citadel protocol responses? */
1416 int crlf /* Use CRLF newlines instead of LF? */
1422 char display_name[256];
1424 char *nl; /* newline string */
1426 int subject_found = 0;
1429 /* Buffers needed for RFC822 translation. These are all filled
1430 * using functions that are bounds-checked, and therefore we can
1431 * make them substantially smaller than SIZ.
1439 char datestamp[100];
1441 lprintf(CTDL_DEBUG, "CtdlOutputPreLoadedMsg(TheMessage=%s, %d, %d, %d, %d\n",
1442 ((TheMessage == NULL) ? "NULL" : "not null"),
1443 mode, headers_only, do_proto, crlf);
1445 strcpy(mid, "unknown");
1446 nl = (crlf ? "\r\n" : "\n");
1448 if (!is_valid_message(TheMessage)) {
1450 "ERROR: invalid preloaded message for output\n");
1451 return(om_no_such_msg);
1454 /* Are we downloading a MIME component? */
1455 if (mode == MT_DOWNLOAD) {
1456 if (TheMessage->cm_format_type != FMT_RFC822) {
1458 cprintf("%d This is not a MIME message.\n",
1459 ERROR + ILLEGAL_VALUE);
1460 } else if (CC->download_fp != NULL) {
1461 if (do_proto) cprintf(
1462 "%d You already have a download open.\n",
1463 ERROR + RESOURCE_BUSY);
1465 /* Parse the message text component */
1466 mptr = TheMessage->cm_fields['M'];
1467 mime_parser(mptr, NULL, *mime_download, NULL, NULL, NULL, 0);
1468 /* If there's no file open by this time, the requested
1469 * section wasn't found, so print an error
1471 if (CC->download_fp == NULL) {
1472 if (do_proto) cprintf(
1473 "%d Section %s not found.\n",
1474 ERROR + FILE_NOT_FOUND,
1475 CC->download_desired_section);
1478 return((CC->download_fp != NULL) ? om_ok : om_mime_error);
1481 /* now for the user-mode message reading loops */
1482 if (do_proto) cprintf("%d msg:\n", LISTING_FOLLOWS);
1484 /* Does the caller want to skip the headers? */
1485 if (headers_only == HEADERS_NONE) goto START_TEXT;
1487 /* Tell the client which format type we're using. */
1488 if ( (mode == MT_CITADEL) && (do_proto) ) {
1489 cprintf("type=%d\n", TheMessage->cm_format_type);
1492 /* nhdr=yes means that we're only displaying headers, no body */
1493 if ( (TheMessage->cm_anon_type == MES_ANONONLY)
1494 && (mode == MT_CITADEL)
1497 cprintf("nhdr=yes\n");
1500 /* begin header processing loop for Citadel message format */
1502 if ((mode == MT_CITADEL) || (mode == MT_MIME)) {
1504 safestrncpy(display_name, "<unknown>", sizeof display_name);
1505 if (TheMessage->cm_fields['A']) {
1506 strcpy(buf, TheMessage->cm_fields['A']);
1507 if (TheMessage->cm_anon_type == MES_ANONONLY) {
1508 safestrncpy(display_name, "****", sizeof display_name);
1510 else if (TheMessage->cm_anon_type == MES_ANONOPT) {
1511 safestrncpy(display_name, "anonymous", sizeof display_name);
1514 safestrncpy(display_name, buf, sizeof display_name);
1516 if ((is_room_aide())
1517 && ((TheMessage->cm_anon_type == MES_ANONONLY)
1518 || (TheMessage->cm_anon_type == MES_ANONOPT))) {
1519 size_t tmp = strlen(display_name);
1520 snprintf(&display_name[tmp],
1521 sizeof display_name - tmp,
1526 /* Don't show Internet address for users on the
1527 * local Citadel network.
1530 if (TheMessage->cm_fields['N'] != NULL)
1531 if (strlen(TheMessage->cm_fields['N']) > 0)
1532 if (haschar(TheMessage->cm_fields['N'], '.') == 0) {
1536 /* Now spew the header fields in the order we like them. */
1537 safestrncpy(allkeys, FORDER, sizeof allkeys);
1538 for (i=0; i<strlen(allkeys); ++i) {
1539 k = (int) allkeys[i];
1541 if ( (TheMessage->cm_fields[k] != NULL)
1542 && (msgkeys[k] != NULL) ) {
1544 if (do_proto) cprintf("%s=%s\n",
1548 else if ((k == 'F') && (suppress_f)) {
1551 /* Masquerade display name if needed */
1553 if (do_proto) cprintf("%s=%s\n",
1555 TheMessage->cm_fields[k]
1564 /* begin header processing loop for RFC822 transfer format */
1569 strcpy(snode, NODENAME);
1570 strcpy(lnode, HUMANNODE);
1571 if (mode == MT_RFC822) {
1572 for (i = 0; i < 256; ++i) {
1573 if (TheMessage->cm_fields[i]) {
1574 mptr = TheMessage->cm_fields[i];
1577 safestrncpy(luser, mptr, sizeof luser);
1578 safestrncpy(suser, mptr, sizeof suser);
1580 else if (i == 'Y') {
1581 cprintf("CC: %s%s", mptr, nl);
1583 else if (i == 'U') {
1584 cprintf("Subject: %s%s", mptr, nl);
1588 safestrncpy(mid, mptr, sizeof mid);
1590 safestrncpy(lnode, mptr, sizeof lnode);
1592 safestrncpy(fuser, mptr, sizeof fuser);
1593 /* else if (i == 'O')
1594 cprintf("X-Citadel-Room: %s%s",
1597 safestrncpy(snode, mptr, sizeof snode);
1599 cprintf("To: %s%s", mptr, nl);
1600 else if (i == 'T') {
1601 datestring(datestamp, sizeof datestamp,
1602 atol(mptr), DATESTRING_RFC822);
1603 cprintf("Date: %s%s", datestamp, nl);
1607 if (subject_found == 0) {
1608 cprintf("Subject: (no subject)%s", nl);
1612 for (i=0; i<strlen(suser); ++i) {
1613 suser[i] = tolower(suser[i]);
1614 if (!isalnum(suser[i])) suser[i]='_';
1617 if (mode == MT_RFC822) {
1618 if (!strcasecmp(snode, NODENAME)) {
1619 safestrncpy(snode, FQDN, sizeof snode);
1622 /* Construct a fun message id */
1623 cprintf("Message-ID: <%s", mid);
1624 if (strchr(mid, '@')==NULL) {
1625 cprintf("@%s", snode);
1629 if (!is_room_aide() && (TheMessage->cm_anon_type == MES_ANONONLY)) {
1630 cprintf("From: \"----\" <x@x.org>%s", nl);
1632 else if (!is_room_aide() && (TheMessage->cm_anon_type == MES_ANONOPT)) {
1633 cprintf("From: \"anonymous\" <x@x.org>%s", nl);
1635 else if (strlen(fuser) > 0) {
1636 cprintf("From: \"%s\" <%s>%s", luser, fuser, nl);
1639 cprintf("From: \"%s\" <%s@%s>%s", luser, suser, snode, nl);
1642 cprintf("Organization: %s%s", lnode, nl);
1644 /* Blank line signifying RFC822 end-of-headers */
1645 if (TheMessage->cm_format_type != FMT_RFC822) {
1650 /* end header processing loop ... at this point, we're in the text */
1652 if (headers_only == HEADERS_FAST) goto DONE;
1653 mptr = TheMessage->cm_fields['M'];
1655 /* Tell the client about the MIME parts in this message */
1656 if (TheMessage->cm_format_type == FMT_RFC822) {
1657 if ( (mode == MT_CITADEL) || (mode == MT_MIME) ) {
1658 memset(&ma, 0, sizeof(struct ma_info));
1659 mime_parser(mptr, NULL,
1660 (do_proto ? *list_this_part : NULL),
1661 (do_proto ? *list_this_pref : NULL),
1662 (do_proto ? *list_this_suff : NULL),
1665 else if (mode == MT_RFC822) { /* unparsed RFC822 dump */
1666 char *start_of_text = NULL;
1667 start_of_text = strstr(mptr, "\n\r\n");
1668 if (start_of_text == NULL) start_of_text = strstr(mptr, "\n\n");
1669 if (start_of_text == NULL) start_of_text = mptr;
1671 start_of_text = strstr(start_of_text, "\n");
1673 while (ch=*mptr, ch!=0) {
1677 else switch(headers_only) {
1679 if (mptr >= start_of_text) {
1680 if (ch == 10) cprintf("%s", nl);
1681 else cprintf("%c", ch);
1685 if (mptr < start_of_text) {
1686 if (ch == 10) cprintf("%s", nl);
1687 else cprintf("%c", ch);
1691 if (ch == 10) cprintf("%s", nl);
1692 else cprintf("%c", ch);
1701 if (headers_only == HEADERS_ONLY) {
1705 /* signify start of msg text */
1706 if ( (mode == MT_CITADEL) || (mode == MT_MIME) ) {
1707 if (do_proto) cprintf("text\n");
1710 /* If the format type on disk is 1 (fixed-format), then we want
1711 * everything to be output completely literally ... regardless of
1712 * what message transfer format is in use.
1714 if (TheMessage->cm_format_type == FMT_FIXED) {
1715 if (mode == MT_MIME) {
1716 cprintf("Content-type: text/plain\n\n");
1719 while (ch = *mptr++, ch > 0) {
1722 if ((ch == 10) || (strlen(buf) > 250)) {
1723 cprintf("%s%s", buf, nl);
1726 buf[strlen(buf) + 1] = 0;
1727 buf[strlen(buf)] = ch;
1730 if (strlen(buf) > 0)
1731 cprintf("%s%s", buf, nl);
1734 /* If the message on disk is format 0 (Citadel vari-format), we
1735 * output using the formatter at 80 columns. This is the final output
1736 * form if the transfer format is RFC822, but if the transfer format
1737 * is Citadel proprietary, it'll still work, because the indentation
1738 * for new paragraphs is correct and the client will reformat the
1739 * message to the reader's screen width.
1741 if (TheMessage->cm_format_type == FMT_CITADEL) {
1742 if (mode == MT_MIME) {
1743 cprintf("Content-type: text/x-citadel-variformat\n\n");
1745 memfmout(mptr, 0, nl);
1748 /* If the message on disk is format 4 (MIME), we've gotta hand it
1749 * off to the MIME parser. The client has already been told that
1750 * this message is format 1 (fixed format), so the callback function
1751 * we use will display those parts as-is.
1753 if (TheMessage->cm_format_type == FMT_RFC822) {
1754 memset(&ma, 0, sizeof(struct ma_info));
1756 if (mode == MT_MIME) {
1757 ma.use_fo_hooks = 0;
1758 strcpy(ma.chosen_part, "1");
1759 mime_parser(mptr, NULL,
1760 *choose_preferred, *fixed_output_pre,
1761 *fixed_output_post, (void *)&ma, 0);
1762 mime_parser(mptr, NULL,
1763 *output_preferred, NULL, NULL, (void *)&ma, 0);
1766 ma.use_fo_hooks = 1;
1767 mime_parser(mptr, NULL,
1768 *fixed_output, *fixed_output_pre,
1769 *fixed_output_post, (void *)&ma, 0);
1774 DONE: /* now we're done */
1775 if (do_proto) cprintf("000\n");
1782 * display a message (mode 0 - Citadel proprietary)
1784 void cmd_msg0(char *cmdbuf)
1787 int headers_only = HEADERS_ALL;
1789 msgid = extract_long(cmdbuf, 0);
1790 headers_only = extract_int(cmdbuf, 1);
1792 CtdlOutputMsg(msgid, MT_CITADEL, headers_only, 1, 0, NULL);
1798 * display a message (mode 2 - RFC822)
1800 void cmd_msg2(char *cmdbuf)
1803 int headers_only = HEADERS_ALL;
1805 msgid = extract_long(cmdbuf, 0);
1806 headers_only = extract_int(cmdbuf, 1);
1808 CtdlOutputMsg(msgid, MT_RFC822, headers_only, 1, 1, NULL);
1814 * display a message (mode 3 - IGnet raw format - internal programs only)
1816 void cmd_msg3(char *cmdbuf)
1819 struct CtdlMessage *msg;
1822 if (CC->internal_pgm == 0) {
1823 cprintf("%d This command is for internal programs only.\n",
1824 ERROR + HIGHER_ACCESS_REQUIRED);
1828 msgnum = extract_long(cmdbuf, 0);
1829 msg = CtdlFetchMessage(msgnum, 1);
1831 cprintf("%d Message %ld not found.\n",
1832 ERROR + MESSAGE_NOT_FOUND, msgnum);
1836 serialize_message(&smr, msg);
1837 CtdlFreeMessage(msg);
1840 cprintf("%d Unable to serialize message\n",
1841 ERROR + INTERNAL_ERROR);
1845 cprintf("%d %ld\n", BINARY_FOLLOWS, (long)smr.len);
1846 client_write((char *)smr.ser, (int)smr.len);
1853 * Display a message using MIME content types
1855 void cmd_msg4(char *cmdbuf)
1860 msgid = extract_long(cmdbuf, 0);
1861 extract_token(section, cmdbuf, 1, '|', sizeof section);
1862 CtdlOutputMsg(msgid, MT_MIME, 0, 1, 0, (section[0] ? section : NULL) );
1868 * Client tells us its preferred message format(s)
1870 void cmd_msgp(char *cmdbuf)
1872 safestrncpy(CC->preferred_formats, cmdbuf,
1873 sizeof(CC->preferred_formats));
1874 cprintf("%d ok\n", CIT_OK);
1879 * Open a component of a MIME message as a download file
1881 void cmd_opna(char *cmdbuf)
1884 char desired_section[128];
1886 msgid = extract_long(cmdbuf, 0);
1887 extract_token(desired_section, cmdbuf, 1, '|', sizeof desired_section);
1888 safestrncpy(CC->download_desired_section, desired_section,
1889 sizeof CC->download_desired_section);
1890 CtdlOutputMsg(msgid, MT_DOWNLOAD, 0, 1, 1, NULL);
1894 * Save one or more message pointers into a specified room
1895 * (Returns 0 for success, nonzero for failure)
1896 * roomname may be NULL to use the current room
1898 * Note that the 'supplied_msg' field may be set to NULL, in which case
1899 * the message will be fetched from disk, by number, if we need to perform
1900 * replication checks. This adds an additional database read, so if the
1901 * caller already has the message in memory then it should be supplied. (Obviously
1902 * this mode of operation only works if we're saving a single message.)
1904 int CtdlSaveMsgPointersInRoom(char *roomname, long newmsgidlist[], int num_newmsgs,
1905 int do_repl_check, struct CtdlMessage *supplied_msg)
1908 char hold_rm[ROOMNAMELEN];
1909 struct cdbdata *cdbfr;
1912 long highest_msg = 0L;
1915 struct CtdlMessage *msg = NULL;
1917 long *msgs_to_be_merged = NULL;
1918 int num_msgs_to_be_merged = 0;
1921 "CtdlSaveMsgPointersInRoom(room=%s, num_msgs=%d, repl=%d)\n",
1922 roomname, num_newmsgs, do_repl_check);
1924 strcpy(hold_rm, CC->room.QRname);
1927 if (newmsgidlist == NULL) return(ERROR + INTERNAL_ERROR);
1928 if (num_newmsgs < 1) return(ERROR + INTERNAL_ERROR);
1929 if (num_newmsgs > 1) supplied_msg = NULL;
1931 /* Now the regular stuff */
1932 if (lgetroom(&CC->room,
1933 ((roomname != NULL) ? roomname : CC->room.QRname) )
1935 lprintf(CTDL_ERR, "No such room <%s>\n", roomname);
1936 return(ERROR + ROOM_NOT_FOUND);
1940 msgs_to_be_merged = malloc(sizeof(long) * num_newmsgs);
1941 num_msgs_to_be_merged = 0;
1944 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long));
1945 if (cdbfr == NULL) {
1949 msglist = (long *) cdbfr->ptr;
1950 cdbfr->ptr = NULL; /* CtdlSaveMsgPointerInRoom() now owns this memory */
1951 num_msgs = cdbfr->len / sizeof(long);
1956 /* Create a list of msgid's which were supplied by the caller, but do
1957 * not already exist in the target room. It is absolutely taboo to
1958 * have more than one reference to the same message in a room.
1960 for (i=0; i<num_newmsgs; ++i) {
1962 if (num_msgs > 0) for (j=0; j<num_msgs; ++j) {
1963 if (msglist[j] == newmsgidlist[i]) {
1968 msgs_to_be_merged[num_msgs_to_be_merged++] = newmsgidlist[i];
1972 lprintf(9, "%d unique messages to be merged\n", num_msgs_to_be_merged);
1975 * Now merge the new messages
1977 msglist = realloc(msglist, (sizeof(long) * (num_msgs + num_msgs_to_be_merged)) );
1978 if (msglist == NULL) {
1979 lprintf(CTDL_ALERT, "ERROR: can't realloc message list!\n");
1981 memcpy(&msglist[num_msgs], msgs_to_be_merged, (sizeof(long) * num_msgs_to_be_merged) );
1982 num_msgs += num_msgs_to_be_merged;
1984 /* Sort the message list, so all the msgid's are in order */
1985 num_msgs = sort_msglist(msglist, num_msgs);
1987 /* Determine the highest message number */
1988 highest_msg = msglist[num_msgs - 1];
1990 /* Write it back to disk. */
1991 cdb_store(CDB_MSGLISTS, &CC->room.QRnumber, (int)sizeof(long),
1992 msglist, (int)(num_msgs * sizeof(long)));
1994 /* Free up the memory we used. */
1997 /* Update the highest-message pointer and unlock the room. */
1998 CC->room.QRhighest = highest_msg;
1999 lputroom(&CC->room);
2001 /* Perform replication checks if necessary */
2002 if ( (DoesThisRoomNeedEuidIndexing(&CC->room)) && (do_repl_check) ) {
2003 lprintf(CTDL_DEBUG, "CtdlSaveMsgPointerInRoom() doing repl checks\n");
2005 for (i=0; i<num_msgs_to_be_merged; ++i) {
2006 msgid = msgs_to_be_merged[i];
2008 if (supplied_msg != NULL) {
2012 msg = CtdlFetchMessage(msgid, 0);
2016 ReplicationChecks(msg);
2018 /* If the message has an Exclusive ID, index that... */
2019 if (msg->cm_fields['E'] != NULL) {
2020 index_message_by_euid(msg->cm_fields['E'], &CC->room, msgid);
2023 /* Free up the memory we may have allocated */
2024 if (msg != supplied_msg) {
2025 CtdlFreeMessage(msg);
2033 lprintf(CTDL_DEBUG, "CtdlSaveMsgPointerInRoom() skips repl checks\n");
2036 /* Submit this room for net processing */
2037 network_queue_room(&CC->room, NULL);
2039 /* Go back to the room we were in before we wandered here... */
2040 getroom(&CC->room, hold_rm);
2042 /* Bump the reference count for all messages which were merged */
2043 for (i=0; i<num_msgs_to_be_merged; ++i) {
2044 AdjRefCount(msgs_to_be_merged[i], +1);
2047 /* Free up memory... */
2048 if (msgs_to_be_merged != NULL) {
2049 free(msgs_to_be_merged);
2052 /* Return success. */
2058 * This is the same as CtdlSaveMsgPointersInRoom() but it only accepts
2061 int CtdlSaveMsgPointerInRoom(char *roomname, long msgid,
2062 int do_repl_check, struct CtdlMessage *supplied_msg)
2064 return CtdlSaveMsgPointersInRoom(roomname, &msgid, 1, do_repl_check, supplied_msg);
2071 * Message base operation to save a new message to the message store
2072 * (returns new message number)
2074 * This is the back end for CtdlSubmitMsg() and should not be directly
2075 * called by server-side modules.
2078 long send_message(struct CtdlMessage *msg) {
2086 /* Get a new message number */
2087 newmsgid = get_new_message_number();
2088 snprintf(msgidbuf, sizeof msgidbuf, "%010ld@%s", newmsgid, config.c_fqdn);
2090 /* Generate an ID if we don't have one already */
2091 if (msg->cm_fields['I']==NULL) {
2092 msg->cm_fields['I'] = strdup(msgidbuf);
2095 /* If the message is big, set its body aside for storage elsewhere */
2096 if (msg->cm_fields['M'] != NULL) {
2097 if (strlen(msg->cm_fields['M']) > BIGMSG) {
2099 holdM = msg->cm_fields['M'];
2100 msg->cm_fields['M'] = NULL;
2104 /* Serialize our data structure for storage in the database */
2105 serialize_message(&smr, msg);
2108 msg->cm_fields['M'] = holdM;
2112 cprintf("%d Unable to serialize message\n",
2113 ERROR + INTERNAL_ERROR);
2117 /* Write our little bundle of joy into the message base */
2118 if (cdb_store(CDB_MSGMAIN, &newmsgid, (int)sizeof(long),
2119 smr.ser, smr.len) < 0) {
2120 lprintf(CTDL_ERR, "Can't store message\n");
2124 cdb_store(CDB_BIGMSGS,
2134 /* Free the memory we used for the serialized message */
2137 /* Return the *local* message ID to the caller
2138 * (even if we're storing an incoming network message)
2146 * Serialize a struct CtdlMessage into the format used on disk and network.
2148 * This function loads up a "struct ser_ret" (defined in server.h) which
2149 * contains the length of the serialized message and a pointer to the
2150 * serialized message in memory. THE LATTER MUST BE FREED BY THE CALLER.
2152 void serialize_message(struct ser_ret *ret, /* return values */
2153 struct CtdlMessage *msg) /* unserialized msg */
2155 size_t wlen, fieldlen;
2157 static char *forder = FORDER;
2160 * Check for valid message format
2162 if (is_valid_message(msg) == 0) {
2163 lprintf(CTDL_ERR, "serialize_message() aborting due to invalid message\n");
2170 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL)
2171 ret->len = ret->len +
2172 strlen(msg->cm_fields[(int)forder[i]]) + 2;
2174 ret->ser = malloc(ret->len);
2175 if (ret->ser == NULL) {
2176 lprintf(CTDL_ERR, "serialize_message() malloc(%ld) failed: %s\n",
2177 (long)ret->len, strerror(errno));
2184 ret->ser[1] = msg->cm_anon_type;
2185 ret->ser[2] = msg->cm_format_type;
2188 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL) {
2189 fieldlen = strlen(msg->cm_fields[(int)forder[i]]);
2190 ret->ser[wlen++] = (char)forder[i];
2191 safestrncpy((char *)&ret->ser[wlen], msg->cm_fields[(int)forder[i]], fieldlen+1);
2192 wlen = wlen + fieldlen + 1;
2194 if (ret->len != wlen) lprintf(CTDL_ERR, "ERROR: len=%ld wlen=%ld\n",
2195 (long)ret->len, (long)wlen);
2203 * Check to see if any messages already exist in the current room which
2204 * carry the same Exclusive ID as this one. If any are found, delete them.
2206 void ReplicationChecks(struct CtdlMessage *msg) {
2207 long old_msgnum = (-1L);
2209 if (DoesThisRoomNeedEuidIndexing(&CC->room) == 0) return;
2211 lprintf(CTDL_DEBUG, "Performing replication checks in <%s>\n",
2214 /* No exclusive id? Don't do anything. */
2215 if (msg == NULL) return;
2216 if (msg->cm_fields['E'] == NULL) return;
2217 if (strlen(msg->cm_fields['E']) == 0) return;
2218 /*lprintf(CTDL_DEBUG, "Exclusive ID: <%s> for room <%s>\n",
2219 msg->cm_fields['E'], CC->room.QRname);*/
2221 old_msgnum = locate_message_by_euid(msg->cm_fields['E'], &CC->room);
2222 if (old_msgnum > 0L) {
2223 lprintf(CTDL_DEBUG, "ReplicationChecks() replacing message %ld\n", old_msgnum);
2224 CtdlDeleteMessages(CC->room.QRname, &old_msgnum, 1, "", 0);
2231 * Save a message to disk and submit it into the delivery system.
2233 long CtdlSubmitMsg(struct CtdlMessage *msg, /* message to save */
2234 struct recptypes *recps, /* recipients (if mail) */
2235 char *force /* force a particular room? */
2237 char submit_filename[128];
2238 char generated_timestamp[32];
2239 char hold_rm[ROOMNAMELEN];
2240 char actual_rm[ROOMNAMELEN];
2241 char force_room[ROOMNAMELEN];
2242 char content_type[SIZ]; /* We have to learn this */
2243 char recipient[SIZ];
2246 struct ctdluser userbuf;
2248 struct MetaData smi;
2249 FILE *network_fp = NULL;
2250 static int seqnum = 1;
2251 struct CtdlMessage *imsg = NULL;
2254 char *hold_R, *hold_D;
2255 char *collected_addresses = NULL;
2256 struct addresses_to_be_filed *aptr = NULL;
2257 char *saved_rfc822_version = NULL;
2258 int qualified_for_journaling = 0;
2260 lprintf(CTDL_DEBUG, "CtdlSubmitMsg() called\n");
2261 if (is_valid_message(msg) == 0) return(-1); /* self check */
2263 /* If this message has no timestamp, we take the liberty of
2264 * giving it one, right now.
2266 if (msg->cm_fields['T'] == NULL) {
2267 snprintf(generated_timestamp, sizeof generated_timestamp, "%ld", (long)time(NULL));
2268 msg->cm_fields['T'] = strdup(generated_timestamp);
2271 /* If this message has no path, we generate one.
2273 if (msg->cm_fields['P'] == NULL) {
2274 if (msg->cm_fields['A'] != NULL) {
2275 msg->cm_fields['P'] = strdup(msg->cm_fields['A']);
2276 for (a=0; a<strlen(msg->cm_fields['P']); ++a) {
2277 if (isspace(msg->cm_fields['P'][a])) {
2278 msg->cm_fields['P'][a] = ' ';
2283 msg->cm_fields['P'] = strdup("unknown");
2287 if (force == NULL) {
2288 strcpy(force_room, "");
2291 strcpy(force_room, force);
2294 /* Learn about what's inside, because it's what's inside that counts */
2295 if (msg->cm_fields['M'] == NULL) {
2296 lprintf(CTDL_ERR, "ERROR: attempt to save message with NULL body\n");
2300 switch (msg->cm_format_type) {
2302 strcpy(content_type, "text/x-citadel-variformat");
2305 strcpy(content_type, "text/plain");
2308 strcpy(content_type, "text/plain");
2309 mptr = bmstrcasestr(msg->cm_fields['M'], "Content-type: ");
2311 safestrncpy(content_type, &mptr[14],
2312 sizeof content_type);
2313 for (a = 0; a < strlen(content_type); ++a) {
2314 if ((content_type[a] == ';')
2315 || (content_type[a] == ' ')
2316 || (content_type[a] == 13)
2317 || (content_type[a] == 10)) {
2318 content_type[a] = 0;
2324 /* Goto the correct room */
2325 lprintf(CTDL_DEBUG, "Selected room %s\n", (recps) ? CC->room.QRname : SENTITEMS);
2326 strcpy(hold_rm, CC->room.QRname);
2327 strcpy(actual_rm, CC->room.QRname);
2328 if (recps != NULL) {
2329 strcpy(actual_rm, SENTITEMS);
2332 /* If the user is a twit, move to the twit room for posting */
2334 if (CC->user.axlevel == 2) {
2335 strcpy(hold_rm, actual_rm);
2336 strcpy(actual_rm, config.c_twitroom);
2337 lprintf(CTDL_DEBUG, "Diverting to twit room\n");
2341 /* ...or if this message is destined for Aide> then go there. */
2342 if (strlen(force_room) > 0) {
2343 strcpy(actual_rm, force_room);
2346 lprintf(CTDL_DEBUG, "Final selection: %s\n", actual_rm);
2347 if (strcasecmp(actual_rm, CC->room.QRname)) {
2348 /* getroom(&CC->room, actual_rm); */
2349 usergoto(actual_rm, 0, 1, NULL, NULL);
2353 * If this message has no O (room) field, generate one.
2355 if (msg->cm_fields['O'] == NULL) {
2356 msg->cm_fields['O'] = strdup(CC->room.QRname);
2359 /* Perform "before save" hooks (aborting if any return nonzero) */
2360 lprintf(CTDL_DEBUG, "Performing before-save hooks\n");
2361 if (PerformMessageHooks(msg, EVT_BEFORESAVE) > 0) return(-3);
2364 * If this message has an Exclusive ID, and the room is replication
2365 * checking enabled, then do replication checks.
2367 if (DoesThisRoomNeedEuidIndexing(&CC->room)) {
2368 ReplicationChecks(msg);
2371 /* Save it to disk */
2372 lprintf(CTDL_DEBUG, "Saving to disk\n");
2373 newmsgid = send_message(msg);
2374 if (newmsgid <= 0L) return(-5);
2376 /* Write a supplemental message info record. This doesn't have to
2377 * be a critical section because nobody else knows about this message
2380 lprintf(CTDL_DEBUG, "Creating MetaData record\n");
2381 memset(&smi, 0, sizeof(struct MetaData));
2382 smi.meta_msgnum = newmsgid;
2383 smi.meta_refcount = 0;
2384 safestrncpy(smi.meta_content_type, content_type,
2385 sizeof smi.meta_content_type);
2388 * Measure how big this message will be when rendered as RFC822.
2389 * We do this for two reasons:
2390 * 1. We need the RFC822 length for the new metadata record, so the
2391 * POP and IMAP services don't have to calculate message lengths
2392 * while the user is waiting (multiplied by potentially hundreds
2393 * or thousands of messages).
2394 * 2. If journaling is enabled, we will need an RFC822 version of the
2395 * message to attach to the journalized copy.
2397 if (CC->redirect_buffer != NULL) {
2398 lprintf(CTDL_ALERT, "CC->redirect_buffer is not NULL during message submission!\n");
2401 CC->redirect_buffer = malloc(SIZ);
2402 CC->redirect_len = 0;
2403 CC->redirect_alloc = SIZ;
2404 CtdlOutputPreLoadedMsg(msg, MT_RFC822, HEADERS_ALL, 0, 1);
2405 smi.meta_rfc822_length = CC->redirect_len;
2406 saved_rfc822_version = CC->redirect_buffer;
2407 CC->redirect_buffer = NULL;
2408 CC->redirect_len = 0;
2409 CC->redirect_alloc = 0;
2413 /* Now figure out where to store the pointers */
2414 lprintf(CTDL_DEBUG, "Storing pointers\n");
2416 /* If this is being done by the networker delivering a private
2417 * message, we want to BYPASS saving the sender's copy (because there
2418 * is no local sender; it would otherwise go to the Trashcan).
2420 if ((!CC->internal_pgm) || (recps == NULL)) {
2421 if (CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 1, msg) != 0) {
2422 lprintf(CTDL_ERR, "ERROR saving message pointer!\n");
2423 CtdlSaveMsgPointerInRoom(config.c_aideroom, newmsgid, 0, msg);
2427 /* For internet mail, drop a copy in the outbound queue room */
2429 if (recps->num_internet > 0) {
2430 CtdlSaveMsgPointerInRoom(SMTP_SPOOLOUT_ROOM, newmsgid, 0, msg);
2433 /* If other rooms are specified, drop them there too. */
2435 if (recps->num_room > 0)
2436 for (i=0; i<num_tokens(recps->recp_room, '|'); ++i) {
2437 extract_token(recipient, recps->recp_room, i,
2438 '|', sizeof recipient);
2439 lprintf(CTDL_DEBUG, "Delivering to room <%s>\n", recipient);
2440 CtdlSaveMsgPointerInRoom(recipient, newmsgid, 0, msg);
2443 /* Bump this user's messages posted counter. */
2444 lprintf(CTDL_DEBUG, "Updating user\n");
2445 lgetuser(&CC->user, CC->curr_user);
2446 CC->user.posted = CC->user.posted + 1;
2447 lputuser(&CC->user);
2449 /* If this is private, local mail, make a copy in the
2450 * recipient's mailbox and bump the reference count.
2453 if (recps->num_local > 0)
2454 for (i=0; i<num_tokens(recps->recp_local, '|'); ++i) {
2455 extract_token(recipient, recps->recp_local, i,
2456 '|', sizeof recipient);
2457 lprintf(CTDL_DEBUG, "Delivering private local mail to <%s>\n",
2459 if (getuser(&userbuf, recipient) == 0) {
2460 MailboxName(actual_rm, sizeof actual_rm,
2461 &userbuf, MAILROOM);
2462 CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 0, msg);
2463 BumpNewMailCounter(userbuf.usernum);
2466 lprintf(CTDL_DEBUG, "No user <%s>\n", recipient);
2467 CtdlSaveMsgPointerInRoom(config.c_aideroom,
2472 /* Perform "after save" hooks */
2473 lprintf(CTDL_DEBUG, "Performing after-save hooks\n");
2474 PerformMessageHooks(msg, EVT_AFTERSAVE);
2476 /* For IGnet mail, we have to save a new copy into the spooler for
2477 * each recipient, with the R and D fields set to the recipient and
2478 * destination-node. This has two ugly side effects: all other
2479 * recipients end up being unlisted in this recipient's copy of the
2480 * message, and it has to deliver multiple messages to the same
2481 * node. We'll revisit this again in a year or so when everyone has
2482 * a network spool receiver that can handle the new style messages.
2485 if (recps->num_ignet > 0)
2486 for (i=0; i<num_tokens(recps->recp_ignet, '|'); ++i) {
2487 extract_token(recipient, recps->recp_ignet, i,
2488 '|', sizeof recipient);
2490 hold_R = msg->cm_fields['R'];
2491 hold_D = msg->cm_fields['D'];
2492 msg->cm_fields['R'] = malloc(SIZ);
2493 msg->cm_fields['D'] = malloc(128);
2494 extract_token(msg->cm_fields['R'], recipient, 0, '@', SIZ);
2495 extract_token(msg->cm_fields['D'], recipient, 1, '@', 128);
2497 serialize_message(&smr, msg);
2499 snprintf(submit_filename, sizeof submit_filename,
2500 "%s/netmail.%04lx.%04x.%04x",
2502 (long) getpid(), CC->cs_pid, ++seqnum);
2503 network_fp = fopen(submit_filename, "wb+");
2504 if (network_fp != NULL) {
2505 fwrite(smr.ser, smr.len, 1, network_fp);
2511 free(msg->cm_fields['R']);
2512 free(msg->cm_fields['D']);
2513 msg->cm_fields['R'] = hold_R;
2514 msg->cm_fields['D'] = hold_D;
2517 /* Go back to the room we started from */
2518 lprintf(CTDL_DEBUG, "Returning to original room %s\n", hold_rm);
2519 if (strcasecmp(hold_rm, CC->room.QRname))
2520 /* getroom(&CC->room, hold_rm); */
2521 usergoto(hold_rm, 0, 1, NULL, NULL);
2523 /* For internet mail, generate delivery instructions.
2524 * Yes, this is recursive. Deal with it. Infinite recursion does
2525 * not happen because the delivery instructions message does not
2526 * contain a recipient.
2529 if (recps->num_internet > 0) {
2530 lprintf(CTDL_DEBUG, "Generating delivery instructions\n");
2531 instr = malloc(SIZ * 2);
2532 snprintf(instr, SIZ * 2,
2533 "Content-type: %s\n\nmsgid|%ld\nsubmitted|%ld\n"
2535 SPOOLMIME, newmsgid, (long)time(NULL),
2536 msg->cm_fields['A'], msg->cm_fields['N']
2539 for (i=0; i<num_tokens(recps->recp_internet, '|'); ++i) {
2540 size_t tmp = strlen(instr);
2541 extract_token(recipient, recps->recp_internet,
2542 i, '|', sizeof recipient);
2543 snprintf(&instr[tmp], SIZ * 2 - tmp,
2544 "remote|%s|0||\n", recipient);
2547 imsg = malloc(sizeof(struct CtdlMessage));
2548 memset(imsg, 0, sizeof(struct CtdlMessage));
2549 imsg->cm_magic = CTDLMESSAGE_MAGIC;
2550 imsg->cm_anon_type = MES_NORMAL;
2551 imsg->cm_format_type = FMT_RFC822;
2552 imsg->cm_fields['A'] = strdup("Citadel");
2553 imsg->cm_fields['J'] = strdup("do not journal");
2554 imsg->cm_fields['M'] = instr;
2555 CtdlSubmitMsg(imsg, NULL, SMTP_SPOOLOUT_ROOM);
2556 CtdlFreeMessage(imsg);
2560 * Any addresses to harvest for someone's address book?
2562 if ( (CC->logged_in) && (recps != NULL) ) {
2563 collected_addresses = harvest_collected_addresses(msg);
2566 if (collected_addresses != NULL) {
2567 begin_critical_section(S_ATBF);
2568 aptr = (struct addresses_to_be_filed *)
2569 malloc(sizeof(struct addresses_to_be_filed));
2571 MailboxName(actual_rm, sizeof actual_rm,
2572 &CC->user, USERCONTACTSROOM);
2573 aptr->roomname = strdup(actual_rm);
2574 aptr->collected_addresses = collected_addresses;
2576 end_critical_section(S_ATBF);
2580 * Determine whether this message qualifies for journaling.
2582 if (msg->cm_fields['J'] != NULL) {
2583 qualified_for_journaling = 0;
2586 if (recps == NULL) {
2587 qualified_for_journaling = config.c_journal_pubmsgs;
2589 else if (recps->num_local + recps->num_ignet + recps->num_internet > 0) {
2590 qualified_for_journaling = config.c_journal_email;
2593 qualified_for_journaling = config.c_journal_pubmsgs;
2598 * Do we have to perform journaling? If so, hand off the saved
2599 * RFC822 version will be handed off to the journaler for background
2600 * submit. Otherwise, we have to free the memory ourselves.
2602 if (saved_rfc822_version != NULL) {
2603 if (qualified_for_journaling) {
2604 JournalBackgroundSubmit(msg, saved_rfc822_version, recps);
2607 free(saved_rfc822_version);
2620 * Convenience function for generating small administrative messages.
2622 void quickie_message(char *from, char *to, char *room, char *text,
2623 int format_type, char *subject)
2625 struct CtdlMessage *msg;
2626 struct recptypes *recp = NULL;
2628 msg = malloc(sizeof(struct CtdlMessage));
2629 memset(msg, 0, sizeof(struct CtdlMessage));
2630 msg->cm_magic = CTDLMESSAGE_MAGIC;
2631 msg->cm_anon_type = MES_NORMAL;
2632 msg->cm_format_type = format_type;
2633 msg->cm_fields['A'] = strdup(from);
2634 if (room != NULL) msg->cm_fields['O'] = strdup(room);
2635 msg->cm_fields['N'] = strdup(NODENAME);
2637 msg->cm_fields['R'] = strdup(to);
2638 recp = validate_recipients(to);
2640 if (subject != NULL) {
2641 msg->cm_fields['U'] = strdup(subject);
2643 msg->cm_fields['M'] = strdup(text);
2645 CtdlSubmitMsg(msg, recp, room);
2646 CtdlFreeMessage(msg);
2647 if (recp != NULL) free(recp);
2653 * Back end function used by CtdlMakeMessage() and similar functions
2655 char *CtdlReadMessageBody(char *terminator, /* token signalling EOT */
2656 size_t maxlen, /* maximum message length */
2657 char *exist, /* if non-null, append to it;
2658 exist is ALWAYS freed */
2659 int crlf /* CRLF newlines instead of LF */
2663 size_t message_len = 0;
2664 size_t buffer_len = 0;
2671 if (exist == NULL) {
2678 message_len = strlen(exist);
2679 buffer_len = message_len + 4096;
2680 m = realloc(exist, buffer_len);
2687 /* Do we need to change leading ".." to "." for SMTP escaping? */
2688 if (!strcmp(terminator, ".")) {
2692 /* flush the input if we have nowhere to store it */
2697 /* read in the lines of message text one by one */
2699 if (client_getln(buf, (sizeof buf - 3)) < 1) finished = 1;
2700 if (!strcmp(buf, terminator)) finished = 1;
2702 strcat(buf, "\r\n");
2708 /* Unescape SMTP-style input of two dots at the beginning of the line */
2710 if (!strncmp(buf, "..", 2)) {
2711 strcpy(buf, &buf[1]);
2715 if ( (!flushing) && (!finished) ) {
2716 /* Measure the line */
2717 linelen = strlen(buf);
2719 /* augment the buffer if we have to */
2720 if ((message_len + linelen) >= buffer_len) {
2721 ptr = realloc(m, (buffer_len * 2) );
2722 if (ptr == NULL) { /* flush if can't allocate */
2725 buffer_len = (buffer_len * 2);
2727 lprintf(CTDL_DEBUG, "buffer_len is now %ld\n", (long)buffer_len);
2731 /* Add the new line to the buffer. NOTE: this loop must avoid
2732 * using functions like strcat() and strlen() because they
2733 * traverse the entire buffer upon every call, and doing that
2734 * for a multi-megabyte message slows it down beyond usability.
2736 strcpy(&m[message_len], buf);
2737 message_len += linelen;
2740 /* if we've hit the max msg length, flush the rest */
2741 if (message_len >= maxlen) flushing = 1;
2743 } while (!finished);
2751 * Build a binary message to be saved on disk.
2752 * (NOTE: if you supply 'preformatted_text', the buffer you give it
2753 * will become part of the message. This means you are no longer
2754 * responsible for managing that memory -- it will be freed along with
2755 * the rest of the fields when CtdlFreeMessage() is called.)
2758 struct CtdlMessage *CtdlMakeMessage(
2759 struct ctdluser *author, /* author's user structure */
2760 char *recipient, /* NULL if it's not mail */
2761 char *recp_cc, /* NULL if it's not mail */
2762 char *room, /* room where it's going */
2763 int type, /* see MES_ types in header file */
2764 int format_type, /* variformat, plain text, MIME... */
2765 char *fake_name, /* who we're masquerading as */
2766 char *subject, /* Subject (optional) */
2767 char *supplied_euid, /* ...or NULL if this is irrelevant */
2768 char *preformatted_text /* ...or NULL to read text from client */
2770 char dest_node[SIZ];
2772 struct CtdlMessage *msg;
2774 msg = malloc(sizeof(struct CtdlMessage));
2775 memset(msg, 0, sizeof(struct CtdlMessage));
2776 msg->cm_magic = CTDLMESSAGE_MAGIC;
2777 msg->cm_anon_type = type;
2778 msg->cm_format_type = format_type;
2780 /* Don't confuse the poor folks if it's not routed mail. */
2781 strcpy(dest_node, "");
2786 snprintf(buf, sizeof buf, "cit%ld", author->usernum); /* Path */
2787 msg->cm_fields['P'] = strdup(buf);
2789 snprintf(buf, sizeof buf, "%ld", (long)time(NULL)); /* timestamp */
2790 msg->cm_fields['T'] = strdup(buf);
2792 if (fake_name[0]) /* author */
2793 msg->cm_fields['A'] = strdup(fake_name);
2795 msg->cm_fields['A'] = strdup(author->fullname);
2797 if (CC->room.QRflags & QR_MAILBOX) { /* room */
2798 msg->cm_fields['O'] = strdup(&CC->room.QRname[11]);
2801 msg->cm_fields['O'] = strdup(CC->room.QRname);
2804 msg->cm_fields['N'] = strdup(NODENAME); /* nodename */
2805 msg->cm_fields['H'] = strdup(HUMANNODE); /* hnodename */
2807 if (recipient[0] != 0) {
2808 msg->cm_fields['R'] = strdup(recipient);
2810 if (recp_cc[0] != 0) {
2811 msg->cm_fields['Y'] = strdup(recp_cc);
2813 if (dest_node[0] != 0) {
2814 msg->cm_fields['D'] = strdup(dest_node);
2817 if ( (author == &CC->user) && (strlen(CC->cs_inet_email) > 0) ) {
2818 msg->cm_fields['F'] = strdup(CC->cs_inet_email);
2821 if (subject != NULL) {
2823 if (strlen(subject) > 0) {
2824 msg->cm_fields['U'] = strdup(subject);
2828 if (supplied_euid != NULL) {
2829 msg->cm_fields['E'] = strdup(supplied_euid);
2832 if (preformatted_text != NULL) {
2833 msg->cm_fields['M'] = preformatted_text;
2836 msg->cm_fields['M'] = CtdlReadMessageBody("000",
2837 config.c_maxmsglen, NULL, 0);
2845 * Check to see whether we have permission to post a message in the current
2846 * room. Returns a *CITADEL ERROR CODE* and puts a message in errmsgbuf, or
2847 * returns 0 on success.
2849 int CtdlDoIHavePermissionToPostInThisRoom(char *errmsgbuf, size_t n) {
2851 if (!(CC->logged_in)) {
2852 snprintf(errmsgbuf, n, "Not logged in.");
2853 return (ERROR + NOT_LOGGED_IN);
2856 if ((CC->user.axlevel < 2)
2857 && ((CC->room.QRflags & QR_MAILBOX) == 0)) {
2858 snprintf(errmsgbuf, n, "Need to be validated to enter "
2859 "(except in %s> to sysop)", MAILROOM);
2860 return (ERROR + HIGHER_ACCESS_REQUIRED);
2863 if ((CC->user.axlevel < 4)
2864 && (CC->room.QRflags & QR_NETWORK)) {
2865 snprintf(errmsgbuf, n, "Need net privileges to enter here.");
2866 return (ERROR + HIGHER_ACCESS_REQUIRED);
2869 if ((CC->user.axlevel < 6)
2870 && (CC->room.QRflags & QR_READONLY)) {
2871 snprintf(errmsgbuf, n, "Sorry, this is a read-only room.");
2872 return (ERROR + HIGHER_ACCESS_REQUIRED);
2875 strcpy(errmsgbuf, "Ok");
2881 * Check to see if the specified user has Internet mail permission
2882 * (returns nonzero if permission is granted)
2884 int CtdlCheckInternetMailPermission(struct ctdluser *who) {
2886 /* Do not allow twits to send Internet mail */
2887 if (who->axlevel <= 2) return(0);
2889 /* Globally enabled? */
2890 if (config.c_restrict == 0) return(1);
2892 /* User flagged ok? */
2893 if (who->flags & US_INTERNET) return(2);
2895 /* Aide level access? */
2896 if (who->axlevel >= 6) return(3);
2898 /* No mail for you! */
2904 * Validate recipients, count delivery types and errors, and handle aliasing
2905 * FIXME check for dupes!!!!!
2906 * Returns 0 if all addresses are ok, -1 if no addresses were specified,
2907 * or the number of addresses found invalid.
2909 struct recptypes *validate_recipients(char *supplied_recipients) {
2910 struct recptypes *ret;
2911 char recipients[SIZ];
2912 char this_recp[256];
2913 char this_recp_cooked[256];
2919 struct ctdluser tempUS;
2920 struct ctdlroom tempQR;
2924 ret = (struct recptypes *) malloc(sizeof(struct recptypes));
2925 if (ret == NULL) return(NULL);
2926 memset(ret, 0, sizeof(struct recptypes));
2929 ret->num_internet = 0;
2934 if (supplied_recipients == NULL) {
2935 strcpy(recipients, "");
2938 safestrncpy(recipients, supplied_recipients, sizeof recipients);
2941 /* Change all valid separator characters to commas */
2942 for (i=0; i<strlen(recipients); ++i) {
2943 if ((recipients[i] == ';') || (recipients[i] == '|')) {
2944 recipients[i] = ',';
2948 /* Now start extracting recipients... */
2950 while (strlen(recipients) > 0) {
2952 for (i=0; i<=strlen(recipients); ++i) {
2953 if (recipients[i] == '\"') in_quotes = 1 - in_quotes;
2954 if ( ( (recipients[i] == ',') && (!in_quotes) ) || (recipients[i] == 0) ) {
2955 safestrncpy(this_recp, recipients, i+1);
2957 if (recipients[i] == ',') {
2958 strcpy(recipients, &recipients[i+1]);
2961 strcpy(recipients, "");
2968 lprintf(CTDL_DEBUG, "Evaluating recipient #%d: %s\n", num_recps, this_recp);
2970 mailtype = alias(this_recp);
2971 mailtype = alias(this_recp);
2972 mailtype = alias(this_recp);
2973 for (j=0; j<=strlen(this_recp); ++j) {
2974 if (this_recp[j]=='_') {
2975 this_recp_cooked[j] = ' ';
2978 this_recp_cooked[j] = this_recp[j];
2984 if (!strcasecmp(this_recp, "sysop")) {
2986 strcpy(this_recp, config.c_aideroom);
2987 if (strlen(ret->recp_room) > 0) {
2988 strcat(ret->recp_room, "|");
2990 strcat(ret->recp_room, this_recp);
2992 else if (getuser(&tempUS, this_recp) == 0) {
2994 strcpy(this_recp, tempUS.fullname);
2995 if (strlen(ret->recp_local) > 0) {
2996 strcat(ret->recp_local, "|");
2998 strcat(ret->recp_local, this_recp);
3000 else if (getuser(&tempUS, this_recp_cooked) == 0) {
3002 strcpy(this_recp, tempUS.fullname);
3003 if (strlen(ret->recp_local) > 0) {
3004 strcat(ret->recp_local, "|");
3006 strcat(ret->recp_local, this_recp);
3008 else if ( (!strncasecmp(this_recp, "room_", 5))
3009 && (!getroom(&tempQR, &this_recp_cooked[5])) ) {
3011 if (strlen(ret->recp_room) > 0) {
3012 strcat(ret->recp_room, "|");
3014 strcat(ret->recp_room, &this_recp_cooked[5]);
3022 /* Yes, you're reading this correctly: if the target
3023 * domain points back to the local system or an attached
3024 * Citadel directory, the address is invalid. That's
3025 * because if the address were valid, we would have
3026 * already translated it to a local address by now.
3028 if (IsDirectory(this_recp)) {
3033 ++ret->num_internet;
3034 if (strlen(ret->recp_internet) > 0) {
3035 strcat(ret->recp_internet, "|");
3037 strcat(ret->recp_internet, this_recp);
3042 if (strlen(ret->recp_ignet) > 0) {
3043 strcat(ret->recp_ignet, "|");
3045 strcat(ret->recp_ignet, this_recp);
3053 if (strlen(ret->errormsg) == 0) {
3054 snprintf(append, sizeof append,
3055 "Invalid recipient: %s",
3059 snprintf(append, sizeof append,
3062 if ( (strlen(ret->errormsg) + strlen(append)) < SIZ) {
3063 strcat(ret->errormsg, append);
3067 if (strlen(ret->display_recp) == 0) {
3068 strcpy(append, this_recp);
3071 snprintf(append, sizeof append, ", %s",
3074 if ( (strlen(ret->display_recp)+strlen(append)) < SIZ) {
3075 strcat(ret->display_recp, append);
3080 if ((ret->num_local + ret->num_internet + ret->num_ignet +
3081 ret->num_room + ret->num_error) == 0) {
3082 ret->num_error = (-1);
3083 strcpy(ret->errormsg, "No recipients specified.");
3086 lprintf(CTDL_DEBUG, "validate_recipients()\n");
3087 lprintf(CTDL_DEBUG, " local: %d <%s>\n", ret->num_local, ret->recp_local);
3088 lprintf(CTDL_DEBUG, " room: %d <%s>\n", ret->num_room, ret->recp_room);
3089 lprintf(CTDL_DEBUG, " inet: %d <%s>\n", ret->num_internet, ret->recp_internet);
3090 lprintf(CTDL_DEBUG, " ignet: %d <%s>\n", ret->num_ignet, ret->recp_ignet);
3091 lprintf(CTDL_DEBUG, " error: %d <%s>\n", ret->num_error, ret->errormsg);
3099 * message entry - mode 0 (normal)
3101 void cmd_ent0(char *entargs)
3107 char supplied_euid[128];
3108 char masquerade_as[SIZ];
3110 int format_type = 0;
3111 char newusername[SIZ];
3112 struct CtdlMessage *msg;
3116 struct recptypes *valid = NULL;
3117 struct recptypes *valid_to = NULL;
3118 struct recptypes *valid_cc = NULL;
3119 struct recptypes *valid_bcc = NULL;
3126 post = extract_int(entargs, 0);
3127 extract_token(recp, entargs, 1, '|', sizeof recp);
3128 anon_flag = extract_int(entargs, 2);
3129 format_type = extract_int(entargs, 3);
3130 extract_token(subject, entargs, 4, '|', sizeof subject);
3131 do_confirm = extract_int(entargs, 6);
3132 extract_token(cc, entargs, 7, '|', sizeof cc);
3133 extract_token(bcc, entargs, 8, '|', sizeof bcc);
3134 switch(CC->room.QRdefaultview) {
3137 extract_token(supplied_euid, entargs, 9, '|', sizeof supplied_euid);
3140 supplied_euid[0] = 0;
3144 /* first check to make sure the request is valid. */
3146 err = CtdlDoIHavePermissionToPostInThisRoom(errmsg, sizeof errmsg);
3148 cprintf("%d %s\n", err, errmsg);
3152 /* Check some other permission type things. */
3155 if (CC->user.axlevel < 6) {
3156 cprintf("%d You don't have permission to masquerade.\n",
3157 ERROR + HIGHER_ACCESS_REQUIRED);
3160 extract_token(newusername, entargs, 5, '|', sizeof newusername);
3161 memset(CC->fake_postname, 0, sizeof(CC->fake_postname) );
3162 safestrncpy(CC->fake_postname, newusername,
3163 sizeof(CC->fake_postname) );
3164 cprintf("%d ok\n", CIT_OK);
3167 CC->cs_flags |= CS_POSTING;
3169 /* In the Mail> room we have to behave a little differently --
3170 * make sure the user has specified at least one recipient. Then
3171 * validate the recipient(s).
3173 if ( (CC->room.QRflags & QR_MAILBOX)
3174 && (!strcasecmp(&CC->room.QRname[11], MAILROOM)) ) {
3176 if (CC->user.axlevel < 2) {
3177 strcpy(recp, "sysop");
3182 valid_to = validate_recipients(recp);
3183 if (valid_to->num_error > 0) {
3184 cprintf("%d Invalid recipient (To)\n", ERROR + NO_SUCH_USER);
3189 valid_cc = validate_recipients(cc);
3190 if (valid_cc->num_error > 0) {
3191 cprintf("%d Invalid recipient (CC)\n", ERROR + NO_SUCH_USER);
3197 valid_bcc = validate_recipients(bcc);
3198 if (valid_bcc->num_error > 0) {
3199 cprintf("%d Invalid recipient (BCC)\n", ERROR + NO_SUCH_USER);
3206 /* Recipient required, but none were specified */
3207 if ( (valid_to->num_error < 0) && (valid_cc->num_error < 0) && (valid_bcc->num_error < 0) ) {
3211 cprintf("%d At least one recipient is required.\n", ERROR + NO_SUCH_USER);
3215 if (valid_to->num_internet + valid_cc->num_internet + valid_bcc->num_internet > 0) {
3216 if (CtdlCheckInternetMailPermission(&CC->user)==0) {
3217 cprintf("%d You do not have permission "
3218 "to send Internet mail.\n",
3219 ERROR + HIGHER_ACCESS_REQUIRED);
3227 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)
3228 && (CC->user.axlevel < 4) ) {
3229 cprintf("%d Higher access required for network mail.\n",
3230 ERROR + HIGHER_ACCESS_REQUIRED);
3237 if ((RESTRICT_INTERNET == 1)
3238 && (valid_to->num_internet + valid_cc->num_internet + valid_bcc->num_internet > 0)
3239 && ((CC->user.flags & US_INTERNET) == 0)
3240 && (!CC->internal_pgm)) {
3241 cprintf("%d You don't have access to Internet mail.\n",
3242 ERROR + HIGHER_ACCESS_REQUIRED);
3251 /* Is this a room which has anonymous-only or anonymous-option? */
3252 anonymous = MES_NORMAL;
3253 if (CC->room.QRflags & QR_ANONONLY) {
3254 anonymous = MES_ANONONLY;
3256 if (CC->room.QRflags & QR_ANONOPT) {
3257 if (anon_flag == 1) { /* only if the user requested it */
3258 anonymous = MES_ANONOPT;
3262 if ((CC->room.QRflags & QR_MAILBOX) == 0) {
3266 /* If we're only checking the validity of the request, return
3267 * success without creating the message.
3270 cprintf("%d %s\n", CIT_OK,
3271 ((valid_to != NULL) ? valid_to->display_recp : "") );
3278 /* We don't need these anymore because we'll do it differently below */
3283 /* Handle author masquerading */
3284 if (CC->fake_postname[0]) {
3285 strcpy(masquerade_as, CC->fake_postname);
3287 else if (CC->fake_username[0]) {
3288 strcpy(masquerade_as, CC->fake_username);
3291 strcpy(masquerade_as, "");
3294 /* Read in the message from the client. */
3296 cprintf("%d send message\n", START_CHAT_MODE);
3298 cprintf("%d send message\n", SEND_LISTING);
3301 msg = CtdlMakeMessage(&CC->user, recp, cc,
3302 CC->room.QRname, anonymous, format_type,
3303 masquerade_as, subject,
3304 ((strlen(supplied_euid) > 0) ? supplied_euid : NULL),
3307 /* Put together one big recipients struct containing to/cc/bcc all in
3308 * one. This is for the envelope.
3310 char *all_recps = malloc(SIZ * 3);
3311 strcpy(all_recps, recp);
3312 if (strlen(cc) > 0) {
3313 if (strlen(all_recps) > 0) {
3314 strcat(all_recps, ",");
3316 strcat(all_recps, cc);
3318 if (strlen(bcc) > 0) {
3319 if (strlen(all_recps) > 0) {
3320 strcat(all_recps, ",");
3322 strcat(all_recps, bcc);
3324 if (strlen(all_recps) > 0) {
3325 valid = validate_recipients(all_recps);
3333 msgnum = CtdlSubmitMsg(msg, valid, "");
3336 cprintf("%ld\n", msgnum);
3338 cprintf("Message accepted.\n");
3341 cprintf("Internal error.\n");
3343 if (msg->cm_fields['E'] != NULL) {
3344 cprintf("%s\n", msg->cm_fields['E']);
3351 CtdlFreeMessage(msg);
3353 CC->fake_postname[0] = '\0';
3354 if (valid != NULL) {
3363 * API function to delete messages which match a set of criteria
3364 * (returns the actual number of messages deleted)
3366 int CtdlDeleteMessages(char *room_name, /* which room */
3367 long *dmsgnums, /* array of msg numbers to be deleted */
3368 int num_dmsgnums, /* number of msgs to be deleted, or 0 for "any" */
3369 char *content_type, /* or "" for any */
3370 int deferred /* let TDAP sweep it later */
3374 struct ctdlroom qrbuf;
3375 struct cdbdata *cdbfr;
3376 long *msglist = NULL;
3377 long *dellist = NULL;
3380 int num_deleted = 0;
3382 struct MetaData smi;
3384 lprintf(CTDL_DEBUG, "CtdlDeleteMessages(%s, %d msgs, %s, %d)\n",
3385 room_name, num_dmsgnums, content_type, deferred);
3387 /* get room record, obtaining a lock... */
3388 if (lgetroom(&qrbuf, room_name) != 0) {
3389 lprintf(CTDL_ERR, "CtdlDeleteMessages(): Room <%s> not found\n",
3391 return (0); /* room not found */
3393 cdbfr = cdb_fetch(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long));
3395 if (cdbfr != NULL) {
3396 dellist = malloc(cdbfr->len);
3397 msglist = (long *) cdbfr->ptr;
3398 cdbfr->ptr = NULL; /* CtdlDeleteMessages() now owns this memory */
3399 num_msgs = cdbfr->len / sizeof(long);
3403 for (i = 0; i < num_msgs; ++i) {
3406 /* Set/clear a bit for each criterion */
3408 /* 0 messages in the list or a null list means that we are
3409 * interested in deleting any messages which meet the other criteria.
3411 if ((num_dmsgnums == 0) || (dmsgnums == NULL)) {
3412 delete_this |= 0x01;
3415 for (j=0; j<num_dmsgnums; ++j) {
3416 if (msglist[i] == dmsgnums[j]) {
3417 delete_this |= 0x01;
3422 if (strlen(content_type) == 0) {
3423 delete_this |= 0x02;
3425 GetMetaData(&smi, msglist[i]);
3426 if (!strcasecmp(smi.meta_content_type,
3428 delete_this |= 0x02;
3432 /* Delete message only if all bits are set */
3433 if (delete_this == 0x03) {
3434 dellist[num_deleted++] = msglist[i];
3439 num_msgs = sort_msglist(msglist, num_msgs);
3440 cdb_store(CDB_MSGLISTS, &qrbuf.QRnumber, (int)sizeof(long),
3441 msglist, (int)(num_msgs * sizeof(long)));
3443 qrbuf.QRhighest = msglist[num_msgs - 1];
3448 * If the delete operation is "deferred" (and technically, any delete
3449 * operation not performed by THE DREADED AUTO-PURGER ought to be
3450 * a deferred delete) then we save a pointer to the message in the
3451 * DELETED_MSGS_ROOM. This will cause the reference count to remain
3452 * at least 1, which will save the user from having to synchronously
3453 * wait for various disk-intensive operations to complete.
3455 * Slick -- we now use the new bulk API for moving messages.
3457 if ( (deferred) && (num_deleted) ) {
3458 CtdlCopyMsgsToRoom(dellist, num_deleted, DELETED_MSGS_ROOM);
3461 /* Go through the messages we pulled out of the index, and decrement
3462 * their reference counts by 1. If this is the only room the message
3463 * was in, the reference count will reach zero and the message will
3464 * automatically be deleted from the database. We do this in a
3465 * separate pass because there might be plug-in hooks getting called,
3466 * and we don't want that happening during an S_ROOMS critical
3469 if (num_deleted) for (i=0; i<num_deleted; ++i) {
3470 PerformDeleteHooks(qrbuf.QRname, dellist[i]);
3471 AdjRefCount(dellist[i], -1);
3474 /* Now free the memory we used, and go away. */
3475 if (msglist != NULL) free(msglist);
3476 if (dellist != NULL) free(dellist);
3477 lprintf(CTDL_DEBUG, "%d message(s) deleted.\n", num_deleted);
3478 return (num_deleted);
3484 * Check whether the current user has permission to delete messages from
3485 * the current room (returns 1 for yes, 0 for no)
3487 int CtdlDoIHavePermissionToDeleteMessagesFromThisRoom(void) {
3488 getuser(&CC->user, CC->curr_user);
3489 if ((CC->user.axlevel < 6)
3490 && (CC->user.usernum != CC->room.QRroomaide)
3491 && ((CC->room.QRflags & QR_MAILBOX) == 0)
3492 && (!(CC->internal_pgm))) {
3501 * Delete message from current room
3503 void cmd_dele(char *args)
3512 extract_token(msgset, args, 0, '|', sizeof msgset);
3513 num_msgs = num_tokens(msgset, ',');
3515 cprintf("%d Nothing to do.\n", CIT_OK);
3519 if (CtdlDoIHavePermissionToDeleteMessagesFromThisRoom() == 0) {
3520 cprintf("%d Higher access required.\n",
3521 ERROR + HIGHER_ACCESS_REQUIRED);
3526 * Build our message set to be moved/copied
3528 msgs = malloc(num_msgs * sizeof(long));
3529 for (i=0; i<num_msgs; ++i) {
3530 extract_token(msgtok, msgset, i, ',', sizeof msgtok);
3531 msgs[i] = atol(msgtok);
3534 num_deleted = CtdlDeleteMessages(CC->room.QRname, msgs, num_msgs, "", 1);
3538 cprintf("%d %d message%s deleted.\n", CIT_OK,
3539 num_deleted, ((num_deleted != 1) ? "s" : ""));
3541 cprintf("%d Message not found.\n", ERROR + MESSAGE_NOT_FOUND);
3547 * Back end API function for moves and deletes (multiple messages)
3549 int CtdlCopyMsgsToRoom(long *msgnums, int num_msgs, char *dest) {
3552 err = CtdlSaveMsgPointersInRoom(dest, msgnums, num_msgs, 1, NULL);
3553 if (err != 0) return(err);
3562 * move or copy a message to another room
3564 void cmd_move(char *args)
3571 char targ[ROOMNAMELEN];
3572 struct ctdlroom qtemp;
3579 extract_token(msgset, args, 0, '|', sizeof msgset);
3580 num_msgs = num_tokens(msgset, ',');
3582 cprintf("%d Nothing to do.\n", CIT_OK);
3586 extract_token(targ, args, 1, '|', sizeof targ);
3587 convert_room_name_macros(targ, sizeof targ);
3588 targ[ROOMNAMELEN - 1] = 0;
3589 is_copy = extract_int(args, 2);
3591 if (getroom(&qtemp, targ) != 0) {
3592 cprintf("%d '%s' does not exist.\n",
3593 ERROR + ROOM_NOT_FOUND, targ);
3597 getuser(&CC->user, CC->curr_user);
3598 CtdlRoomAccess(&qtemp, &CC->user, &ra, NULL);
3600 /* Check for permission to perform this operation.
3601 * Remember: "CC->room" is source, "qtemp" is target.
3605 /* Aides can move/copy */
3606 if (CC->user.axlevel >= 6) permit = 1;
3608 /* Room aides can move/copy */
3609 if (CC->user.usernum == CC->room.QRroomaide) permit = 1;
3611 /* Permit move/copy from personal rooms */
3612 if ((CC->room.QRflags & QR_MAILBOX)
3613 && (qtemp.QRflags & QR_MAILBOX)) permit = 1;
3615 /* Permit only copy from public to personal room */
3617 && (!(CC->room.QRflags & QR_MAILBOX))
3618 && (qtemp.QRflags & QR_MAILBOX)) permit = 1;
3620 /* User must have access to target room */
3621 if (!(ra & UA_KNOWN)) permit = 0;
3624 cprintf("%d Higher access required.\n",
3625 ERROR + HIGHER_ACCESS_REQUIRED);
3630 * Build our message set to be moved/copied
3632 msgs = malloc(num_msgs * sizeof(long));
3633 for (i=0; i<num_msgs; ++i) {
3634 extract_token(msgtok, msgset, i, ',', sizeof msgtok);
3635 msgs[i] = atol(msgtok);
3641 err = CtdlCopyMsgsToRoom(msgs, num_msgs, targ);
3643 cprintf("%d Cannot store message(s) in %s: error %d\n",
3649 /* Now delete the message from the source room,
3650 * if this is a 'move' rather than a 'copy' operation.
3653 CtdlDeleteMessages(CC->room.QRname, msgs, num_msgs, "", 0);
3657 cprintf("%d Message(s) %s.\n", CIT_OK, (is_copy ? "copied" : "moved") );
3663 * GetMetaData() - Get the supplementary record for a message
3665 void GetMetaData(struct MetaData *smibuf, long msgnum)
3668 struct cdbdata *cdbsmi;
3671 memset(smibuf, 0, sizeof(struct MetaData));
3672 smibuf->meta_msgnum = msgnum;
3673 smibuf->meta_refcount = 1; /* Default reference count is 1 */
3675 /* Use the negative of the message number for its supp record index */
3676 TheIndex = (0L - msgnum);
3678 cdbsmi = cdb_fetch(CDB_MSGMAIN, &TheIndex, sizeof(long));
3679 if (cdbsmi == NULL) {
3680 return; /* record not found; go with defaults */
3682 memcpy(smibuf, cdbsmi->ptr,
3683 ((cdbsmi->len > sizeof(struct MetaData)) ?
3684 sizeof(struct MetaData) : cdbsmi->len));
3691 * PutMetaData() - (re)write supplementary record for a message
3693 void PutMetaData(struct MetaData *smibuf)
3697 /* Use the negative of the message number for the metadata db index */
3698 TheIndex = (0L - smibuf->meta_msgnum);
3700 lprintf(CTDL_DEBUG, "PutMetaData(%ld) - ref count is %d\n",
3701 smibuf->meta_msgnum, smibuf->meta_refcount);
3703 cdb_store(CDB_MSGMAIN,
3704 &TheIndex, (int)sizeof(long),
3705 smibuf, (int)sizeof(struct MetaData));
3710 * AdjRefCount - change the reference count for a message;
3711 * delete the message if it reaches zero
3713 void AdjRefCount(long msgnum, int incr)
3716 struct MetaData smi;
3719 /* This is a *tight* critical section; please keep it that way, as
3720 * it may get called while nested in other critical sections.
3721 * Complicating this any further will surely cause deadlock!
3723 begin_critical_section(S_SUPPMSGMAIN);
3724 GetMetaData(&smi, msgnum);
3725 smi.meta_refcount += incr;
3727 end_critical_section(S_SUPPMSGMAIN);
3728 lprintf(CTDL_DEBUG, "msg %ld ref count incr %d, is now %d\n",
3729 msgnum, incr, smi.meta_refcount);
3731 /* If the reference count is now zero, delete the message
3732 * (and its supplementary record as well).
3734 if (smi.meta_refcount == 0) {
3735 lprintf(CTDL_DEBUG, "Deleting message <%ld>\n", msgnum);
3737 /* Remove from fulltext index */
3738 if (config.c_enable_fulltext) {
3739 ft_index_message(msgnum, 0);
3742 /* Remove from message base */
3744 cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long));
3745 cdb_delete(CDB_BIGMSGS, &delnum, (int)sizeof(long));
3747 /* Remove metadata record */
3748 delnum = (0L - msgnum);
3749 cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long));
3754 * Write a generic object to this room
3756 * Note: this could be much more efficient. Right now we use two temporary
3757 * files, and still pull the message into memory as with all others.
3759 void CtdlWriteObject(char *req_room, /* Room to stuff it in */
3760 char *content_type, /* MIME type of this object */
3761 char *tempfilename, /* Where to fetch it from */
3762 struct ctdluser *is_mailbox, /* Mailbox room? */
3763 int is_binary, /* Is encoding necessary? */
3764 int is_unique, /* Del others of this type? */
3765 unsigned int flags /* Internal save flags */
3770 struct ctdlroom qrbuf;
3771 char roomname[ROOMNAMELEN];
3772 struct CtdlMessage *msg;
3774 char *raw_message = NULL;
3775 char *encoded_message = NULL;
3776 off_t raw_length = 0;
3778 if (is_mailbox != NULL) {
3779 MailboxName(roomname, sizeof roomname, is_mailbox, req_room);
3782 safestrncpy(roomname, req_room, sizeof(roomname));
3785 fp = fopen(tempfilename, "rb");
3787 lprintf(CTDL_CRIT, "Cannot open %s: %s\n",
3788 tempfilename, strerror(errno));
3791 fseek(fp, 0L, SEEK_END);
3792 raw_length = ftell(fp);
3794 lprintf(CTDL_DEBUG, "Raw length is %ld\n", (long)raw_length);
3796 raw_message = malloc((size_t)raw_length + 2);
3797 fread(raw_message, (size_t)raw_length, 1, fp);
3801 encoded_message = malloc((size_t)
3802 (((raw_length * 134) / 100) + 4096 ) );
3805 encoded_message = malloc((size_t)(raw_length + 4096));
3808 sprintf(encoded_message, "Content-type: %s\n", content_type);
3811 sprintf(&encoded_message[strlen(encoded_message)],
3812 "Content-transfer-encoding: base64\n\n"
3816 sprintf(&encoded_message[strlen(encoded_message)],
3817 "Content-transfer-encoding: 7bit\n\n"
3823 &encoded_message[strlen(encoded_message)],
3829 raw_message[raw_length] = 0;
3831 &encoded_message[strlen(encoded_message)],
3839 lprintf(CTDL_DEBUG, "Allocating\n");
3840 msg = malloc(sizeof(struct CtdlMessage));
3841 memset(msg, 0, sizeof(struct CtdlMessage));
3842 msg->cm_magic = CTDLMESSAGE_MAGIC;
3843 msg->cm_anon_type = MES_NORMAL;
3844 msg->cm_format_type = 4;
3845 msg->cm_fields['A'] = strdup(CC->user.fullname);
3846 msg->cm_fields['O'] = strdup(req_room);
3847 msg->cm_fields['N'] = strdup(config.c_nodename);
3848 msg->cm_fields['H'] = strdup(config.c_humannode);
3849 msg->cm_flags = flags;
3851 msg->cm_fields['M'] = encoded_message;
3853 /* Create the requested room if we have to. */
3854 if (getroom(&qrbuf, roomname) != 0) {
3855 create_room(roomname,
3856 ( (is_mailbox != NULL) ? 5 : 3 ),
3857 "", 0, 1, 0, VIEW_BBS);
3859 /* If the caller specified this object as unique, delete all
3860 * other objects of this type that are currently in the room.
3863 lprintf(CTDL_DEBUG, "Deleted %d other msgs of this type\n",
3864 CtdlDeleteMessages(roomname, NULL, 0, content_type, 0)
3867 /* Now write the data */
3868 CtdlSubmitMsg(msg, NULL, roomname);
3869 CtdlFreeMessage(msg);
3877 void CtdlGetSysConfigBackend(long msgnum, void *userdata) {
3878 config_msgnum = msgnum;
3882 char *CtdlGetSysConfig(char *sysconfname) {
3883 char hold_rm[ROOMNAMELEN];
3886 struct CtdlMessage *msg;
3889 strcpy(hold_rm, CC->room.QRname);
3890 if (getroom(&CC->room, SYSCONFIGROOM) != 0) {
3891 getroom(&CC->room, hold_rm);
3896 /* We want the last (and probably only) config in this room */
3897 begin_critical_section(S_CONFIG);
3898 config_msgnum = (-1L);
3899 CtdlForEachMessage(MSGS_LAST, 1, NULL, sysconfname, NULL,
3900 CtdlGetSysConfigBackend, NULL);
3901 msgnum = config_msgnum;
3902 end_critical_section(S_CONFIG);
3908 msg = CtdlFetchMessage(msgnum, 1);
3910 conf = strdup(msg->cm_fields['M']);
3911 CtdlFreeMessage(msg);
3918 getroom(&CC->room, hold_rm);
3920 if (conf != NULL) do {
3921 extract_token(buf, conf, 0, '\n', sizeof buf);
3922 strcpy(conf, &conf[strlen(buf)+1]);
3923 } while ( (strlen(conf)>0) && (strlen(buf)>0) );
3928 void CtdlPutSysConfig(char *sysconfname, char *sysconfdata) {
3929 char temp[PATH_MAX];
3932 CtdlMakeTempFileName(temp, sizeof temp);
3934 fp = fopen(temp, "w");
3935 if (fp == NULL) return;
3936 fprintf(fp, "%s", sysconfdata);
3939 /* this handy API function does all the work for us */
3940 CtdlWriteObject(SYSCONFIGROOM, sysconfname, temp, NULL, 0, 1, 0);
3946 * Determine whether a given Internet address belongs to the current user
3948 int CtdlIsMe(char *addr, int addr_buf_len)
3950 struct recptypes *recp;
3953 recp = validate_recipients(addr);
3954 if (recp == NULL) return(0);
3956 if (recp->num_local == 0) {
3961 for (i=0; i<recp->num_local; ++i) {
3962 extract_token(addr, recp->recp_local, i, '|', addr_buf_len);
3963 if (!strcasecmp(addr, CC->user.fullname)) {
3975 * Citadel protocol command to do the same
3977 void cmd_isme(char *argbuf) {
3980 if (CtdlAccessCheck(ac_logged_in)) return;
3981 extract_token(addr, argbuf, 0, '|', sizeof addr);
3983 if (CtdlIsMe(addr, sizeof addr)) {
3984 cprintf("%d %s\n", CIT_OK, addr);
3987 cprintf("%d Not you.\n", ERROR + ILLEGAL_VALUE);