4 * Implements the message store.
14 #if TIME_WITH_SYS_TIME
15 # include <sys/time.h>
19 # include <sys/time.h>
32 #include <sys/types.h>
34 #include <libcitadel.h>
37 #include "serv_extensions.h"
41 #include "sysdep_decls.h"
42 #include "citserver.h"
49 #include "internet_addressing.h"
50 #include "euidindex.h"
51 #include "journaling.h"
52 #include "citadel_dirs.h"
53 #include "clientsocket.h"
54 #include "serv_network.h"
57 #include "ctdl_module.h"
60 struct addresses_to_be_filed *atbf = NULL;
62 /* This temp file holds the queue of operations for AdjRefCount() */
63 static FILE *arcfp = NULL;
66 * This really belongs in serv_network.c, but I don't know how to export
67 * symbols between modules.
69 struct FilterList *filterlist = NULL;
73 * These are the four-character field headers we use when outputting
74 * messages in Citadel format (as opposed to RFC822 format).
77 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
78 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
79 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
80 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
81 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
82 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
83 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
84 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
113 * This function is self explanatory.
114 * (What can I say, I'm in a weird mood today...)
116 void remove_any_whitespace_to_the_left_or_right_of_at_symbol(char *name)
120 for (i = 0; i < strlen(name); ++i) {
121 if (name[i] == '@') {
122 while (isspace(name[i - 1]) && i > 0) {
123 strcpy(&name[i - 1], &name[i]);
126 while (isspace(name[i + 1])) {
127 strcpy(&name[i + 1], &name[i + 2]);
135 * Aliasing for network mail.
136 * (Error messages have been commented out, because this is a server.)
138 int alias(char *name)
139 { /* process alias and routing info for mail */
142 char aaa[SIZ], bbb[SIZ];
143 char *ignetcfg = NULL;
144 char *ignetmap = NULL;
150 char original_name[256];
151 safestrncpy(original_name, name, sizeof original_name);
154 remove_any_whitespace_to_the_left_or_right_of_at_symbol(name);
155 stripallbut(name, '<', '>');
157 fp = fopen(file_mail_aliases, "r");
159 fp = fopen("/dev/null", "r");
166 while (fgets(aaa, sizeof aaa, fp) != NULL) {
167 while (isspace(name[0]))
168 strcpy(name, &name[1]);
169 aaa[strlen(aaa) - 1] = 0;
171 for (a = 0; a < strlen(aaa); ++a) {
173 strcpy(bbb, &aaa[a + 1]);
177 if (!strcasecmp(name, aaa))
182 /* Hit the Global Address Book */
183 if (CtdlDirectoryLookup(aaa, name, sizeof aaa) == 0) {
187 if (strcasecmp(original_name, name)) {
188 CtdlLogPrintf(CTDL_INFO, "%s is being forwarded to %s\n", original_name, name);
191 /* Change "user @ xxx" to "user" if xxx is an alias for this host */
192 for (a=0; a<strlen(name); ++a) {
193 if (name[a] == '@') {
194 if (CtdlHostAlias(&name[a+1]) == hostalias_localhost) {
196 CtdlLogPrintf(CTDL_INFO, "Changed to <%s>\n", name);
201 /* determine local or remote type, see citadel.h */
202 at = haschar(name, '@');
203 if (at == 0) return(MES_LOCAL); /* no @'s - local address */
204 if (at > 1) return(MES_ERROR); /* >1 @'s - invalid address */
205 remove_any_whitespace_to_the_left_or_right_of_at_symbol(name);
207 /* figure out the delivery mode */
208 extract_token(node, name, 1, '@', sizeof node);
210 /* If there are one or more dots in the nodename, we assume that it
211 * is an FQDN and will attempt SMTP delivery to the Internet.
213 if (haschar(node, '.') > 0) {
214 return(MES_INTERNET);
217 /* Otherwise we look in the IGnet maps for a valid Citadel node.
218 * Try directly-connected nodes first...
220 ignetcfg = CtdlGetSysConfig(IGNETCFG);
221 for (i=0; i<num_tokens(ignetcfg, '\n'); ++i) {
222 extract_token(buf, ignetcfg, i, '\n', sizeof buf);
223 extract_token(testnode, buf, 0, '|', sizeof testnode);
224 if (!strcasecmp(node, testnode)) {
232 * Then try nodes that are two or more hops away.
234 ignetmap = CtdlGetSysConfig(IGNETMAP);
235 for (i=0; i<num_tokens(ignetmap, '\n'); ++i) {
236 extract_token(buf, ignetmap, i, '\n', sizeof buf);
237 extract_token(testnode, buf, 0, '|', sizeof testnode);
238 if (!strcasecmp(node, testnode)) {
245 /* If we get to this point it's an invalid node name */
251 * Back end for the MSGS command: output message number only.
253 void simple_listing(long msgnum, void *userdata)
255 cprintf("%ld\n", msgnum);
261 * Back end for the MSGS command: output header summary.
263 void headers_listing(long msgnum, void *userdata)
265 struct CtdlMessage *msg;
267 msg = CtdlFetchMessage(msgnum, 0);
269 cprintf("%ld|0|||||\n", msgnum);
273 cprintf("%ld|%s|%s|%s|%s|%s|\n",
275 (msg->cm_fields['T'] ? msg->cm_fields['T'] : "0"),
276 (msg->cm_fields['A'] ? msg->cm_fields['A'] : ""),
277 (msg->cm_fields['N'] ? msg->cm_fields['N'] : ""),
278 (msg->cm_fields['F'] ? msg->cm_fields['F'] : ""),
279 (msg->cm_fields['U'] ? msg->cm_fields['U'] : "")
281 CtdlFreeMessage(msg);
286 /* Determine if a given message matches the fields in a message template.
287 * Return 0 for a successful match.
289 int CtdlMsgCmp(struct CtdlMessage *msg, struct CtdlMessage *template) {
292 /* If there aren't any fields in the template, all messages will
295 if (template == NULL) return(0);
297 /* Null messages are bogus. */
298 if (msg == NULL) return(1);
300 for (i='A'; i<='Z'; ++i) {
301 if (template->cm_fields[i] != NULL) {
302 if (msg->cm_fields[i] == NULL) {
303 /* Considered equal if temmplate is empty string */
304 if (IsEmptyStr(template->cm_fields[i])) continue;
307 if (strcasecmp(msg->cm_fields[i],
308 template->cm_fields[i])) return 1;
312 /* All compares succeeded: we have a match! */
319 * Retrieve the "seen" message list for the current room.
321 void CtdlGetSeen(char *buf, int which_set) {
324 /* Learn about the user and room in question */
325 CtdlGetRelationship(&vbuf, &CC->user, &CC->room);
327 if (which_set == ctdlsetseen_seen)
328 safestrncpy(buf, vbuf.v_seen, SIZ);
329 if (which_set == ctdlsetseen_answered)
330 safestrncpy(buf, vbuf.v_answered, SIZ);
336 * Manipulate the "seen msgs" string (or other message set strings)
338 void CtdlSetSeen(long *target_msgnums, int num_target_msgnums,
339 int target_setting, int which_set,
340 struct ctdluser *which_user, struct ctdlroom *which_room) {
341 struct cdbdata *cdbfr;
355 char *is_set; /* actually an array of booleans */
358 /* Don't bother doing *anything* if we were passed a list of zero messages */
359 if (num_target_msgnums < 1) {
363 /* If no room was specified, we go with the current room. */
365 which_room = &CC->room;
368 /* If no user was specified, we go with the current user. */
370 which_user = &CC->user;
373 CtdlLogPrintf(CTDL_DEBUG, "CtdlSetSeen(%d msgs starting with %ld, %s, %d) in <%s>\n",
374 num_target_msgnums, target_msgnums[0],
375 (target_setting ? "SET" : "CLEAR"),
379 /* Learn about the user and room in question */
380 CtdlGetRelationship(&vbuf, which_user, which_room);
382 /* Load the message list */
383 cdbfr = cdb_fetch(CDB_MSGLISTS, &which_room->QRnumber, sizeof(long));
385 msglist = (long *) cdbfr->ptr;
386 cdbfr->ptr = NULL; /* CtdlSetSeen() now owns this memory */
387 num_msgs = cdbfr->len / sizeof(long);
390 return; /* No messages at all? No further action. */
393 is_set = malloc(num_msgs * sizeof(char));
394 memset(is_set, 0, (num_msgs * sizeof(char)) );
396 /* Decide which message set we're manipulating */
398 case ctdlsetseen_seen:
399 vset = NewStrBufPlain(vbuf.v_seen, -1);
401 case ctdlsetseen_answered:
402 vset = NewStrBufPlain(vbuf.v_answered, -1);
409 #if 0 /* This is a special diagnostic section. Do not allow it to run during normal operation. */
410 CtdlLogPrintf(CTDL_DEBUG, "There are %d messages in the room.\n", num_msgs);
411 for (i=0; i<num_msgs; ++i) {
412 if ((i > 0) && (msglist[i] <= msglist[i-1])) abort();
414 CtdlLogPrintf(CTDL_DEBUG, "We are twiddling %d of them.\n", num_target_msgnums);
415 for (k=0; k<num_target_msgnums; ++k) {
416 if ((k > 0) && (target_msgnums[k] <= target_msgnums[k-1])) abort();
420 CtdlLogPrintf(CTDL_DEBUG, "before update: %s\n", ChrPtr(vset));
422 /* Translate the existing sequence set into an array of booleans */
423 setstr = NewStrBuf();
427 while (StrBufExtract_NextToken(setstr, vset, &pvset, ',')) {
428 /* CtdlLogPrintf(CTDL_DEBUG, "Token: '%s'\n", ChrPtr(setstr)); NOTE ZERO-LENGTH TOKENS */
430 StrBufExtract_token(lostr, setstr, 0, ':');
431 if (StrBufNum_tokens(setstr, ':') >= 2) {
432 StrBufExtract_token(histr, setstr, 1, ':');
436 StrBufAppendBuf(histr, lostr, 0);
439 if (!strcmp(ChrPtr(histr), "*")) {
446 for (i = 0; i < num_msgs; ++i) {
447 if ((msglist[i] >= lo) && (msglist[i] <= hi)) {
457 /* Now translate the array of booleans back into a sequence set */
463 for (i=0; i<num_msgs; ++i) {
467 for (k=0; k<num_target_msgnums; ++k) {
468 if (msglist[i] == target_msgnums[k]) {
469 is_seen = target_setting;
473 w = 0; /* set to 1 if we write something to the string */
475 if ((was_seen == 0) && (is_seen == 1)) {
478 else if ((was_seen == 1) && (is_seen == 0)) {
482 if (StrLength(vset) > 0) {
483 StrBufAppendBufPlain(vset, HKEY(","), 0);
486 StrBufAppendPrintf(vset, "%ld", hi);
489 StrBufAppendPrintf(vset, "%ld:%ld", lo, hi);
492 else if ((is_seen) && (i == num_msgs - 1)) {
494 if (StrLength(vset) > 0) {
495 StrBufAppendBufPlain(vset, HKEY(","), 0);
497 if ((i==0) || (was_seen == 0)) {
498 StrBufAppendPrintf(vset, "%ld", msglist[i]);
501 StrBufAppendPrintf(vset, "%ld:%ld", lo, msglist[i]);
505 /* If the string is getting too long, truncate it at the beginning; repeat up to 9 times * /
506 if (w) for (j=0; j<9; ++j) {
507 if ((StrLength(vset) + 20) > sizeof vset) {
508 remove_token(vset, 0, ',');
509 if (which_set == ctdlsetseen_seen) {
511 sprintf(temp, "1:%ld,", atol(vset)-1L);
517 we don't get to long anymore.
523 while (StrLength(vset) > SIZ)
524 StrBufRemove_token(vset, 0, ',');
526 CtdlLogPrintf(CTDL_DEBUG, " after update: %s\n", ChrPtr(vset));
528 /* Decide which message set we're manipulating */
530 case ctdlsetseen_seen:
531 safestrncpy(vbuf.v_seen, ChrPtr(vset), sizeof vbuf.v_seen);
533 case ctdlsetseen_answered:
534 safestrncpy(vbuf.v_answered, ChrPtr(vset), sizeof vbuf.v_answered);
540 CtdlSetRelationship(&vbuf, which_user, which_room);
546 * API function to perform an operation for each qualifying message in the
547 * current room. (Returns the number of messages processed.)
549 int CtdlForEachMessage(int mode, long ref, char *search_string,
551 struct CtdlMessage *compare,
552 void (*CallBack) (long, void *),
558 struct cdbdata *cdbfr;
559 long *msglist = NULL;
561 int num_processed = 0;
564 struct CtdlMessage *msg = NULL;
567 int printed_lastold = 0;
568 int num_search_msgs = 0;
569 long *search_msgs = NULL;
571 int need_to_free_re = 0;
574 if ((content_type) && (!IsEmptyStr(content_type))) {
575 regcomp(&re, content_type, 0);
579 /* Learn about the user and room in question */
580 getuser(&CC->user, CC->curr_user);
581 CtdlGetRelationship(&vbuf, &CC->user, &CC->room);
583 /* Load the message list */
584 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long));
586 msglist = (long *) cdbfr->ptr;
587 num_msgs = cdbfr->len / sizeof(long);
589 if (need_to_free_re) regfree(&re);
590 return 0; /* No messages at all? No further action. */
595 * Now begin the traversal.
597 if (num_msgs > 0) for (a = 0; a < num_msgs; ++a) {
599 /* If the caller is looking for a specific MIME type, filter
600 * out all messages which are not of the type requested.
602 if ((content_type != NULL) && (!IsEmptyStr(content_type))) {
604 /* This call to GetMetaData() sits inside this loop
605 * so that we only do the extra database read per msg
606 * if we need to. Doing the extra read all the time
607 * really kills the server. If we ever need to use
608 * metadata for another search criterion, we need to
609 * move the read somewhere else -- but still be smart
610 * enough to only do the read if the caller has
611 * specified something that will need it.
613 GetMetaData(&smi, msglist[a]);
615 /* if (strcasecmp(smi.meta_content_type, content_type)) { old non-regex way */
616 if (regexec(&re, smi.meta_content_type, 1, &pm, 0) != 0) {
622 num_msgs = sort_msglist(msglist, num_msgs);
624 /* If a template was supplied, filter out the messages which
625 * don't match. (This could induce some delays!)
628 if (compare != NULL) {
629 for (a = 0; a < num_msgs; ++a) {
630 msg = CtdlFetchMessage(msglist[a], 1);
632 if (CtdlMsgCmp(msg, compare)) {
635 CtdlFreeMessage(msg);
641 /* If a search string was specified, get a message list from
642 * the full text index and remove messages which aren't on both
646 * Since the lists are sorted and strictly ascending, and the
647 * output list is guaranteed to be shorter than or equal to the
648 * input list, we overwrite the bottom of the input list. This
649 * eliminates the need to memmove big chunks of the list over and
652 if ( (num_msgs > 0) && (mode == MSGS_SEARCH) && (search_string) ) {
654 /* Call search module via hook mechanism.
655 * NULL means use any search function available.
656 * otherwise replace with a char * to name of search routine
658 CtdlModuleDoSearch(&num_search_msgs, &search_msgs, search_string, "fulltext");
660 if (num_search_msgs > 0) {
664 orig_num_msgs = num_msgs;
666 for (i=0; i<orig_num_msgs; ++i) {
667 for (j=0; j<num_search_msgs; ++j) {
668 if (msglist[i] == search_msgs[j]) {
669 msglist[num_msgs++] = msglist[i];
675 num_msgs = 0; /* No messages qualify */
677 if (search_msgs != NULL) free(search_msgs);
679 /* Now that we've purged messages which don't contain the search
680 * string, treat a MSGS_SEARCH just like a MSGS_ALL from this
687 * Now iterate through the message list, according to the
688 * criteria supplied by the caller.
691 for (a = 0; a < num_msgs; ++a) {
692 thismsg = msglist[a];
693 if (mode == MSGS_ALL) {
697 is_seen = is_msg_in_sequence_set(
698 vbuf.v_seen, thismsg);
699 if (is_seen) lastold = thismsg;
705 || ((mode == MSGS_OLD) && (is_seen))
706 || ((mode == MSGS_NEW) && (!is_seen))
707 || ((mode == MSGS_LAST) && (a >= (num_msgs - ref)))
708 || ((mode == MSGS_FIRST) && (a < ref))
709 || ((mode == MSGS_GT) && (thismsg > ref))
710 || ((mode == MSGS_EQ) && (thismsg == ref))
713 if ((mode == MSGS_NEW) && (CC->user.flags & US_LASTOLD) && (lastold > 0L) && (printed_lastold == 0) && (!is_seen)) {
715 CallBack(lastold, userdata);
719 if (CallBack) CallBack(thismsg, userdata);
723 cdb_free(cdbfr); /* Clean up */
724 if (need_to_free_re) regfree(&re);
725 return num_processed;
731 * cmd_msgs() - get list of message #'s in this room
732 * implements the MSGS server command using CtdlForEachMessage()
734 void cmd_msgs(char *cmdbuf)
743 int with_template = 0;
744 struct CtdlMessage *template = NULL;
745 int with_headers = 0;
746 char search_string[1024];
748 extract_token(which, cmdbuf, 0, '|', sizeof which);
749 cm_ref = extract_int(cmdbuf, 1);
750 extract_token(search_string, cmdbuf, 1, '|', sizeof search_string);
751 with_template = extract_int(cmdbuf, 2);
752 with_headers = extract_int(cmdbuf, 3);
755 if (!strncasecmp(which, "OLD", 3))
757 else if (!strncasecmp(which, "NEW", 3))
759 else if (!strncasecmp(which, "FIRST", 5))
761 else if (!strncasecmp(which, "LAST", 4))
763 else if (!strncasecmp(which, "GT", 2))
765 else if (!strncasecmp(which, "SEARCH", 6))
770 if ((!(CC->logged_in)) && (!(CC->internal_pgm))) {
771 cprintf("%d not logged in\n", ERROR + NOT_LOGGED_IN);
775 if ( (mode == MSGS_SEARCH) && (!config.c_enable_fulltext) ) {
776 cprintf("%d Full text index is not enabled on this server.\n",
777 ERROR + CMD_NOT_SUPPORTED);
783 cprintf("%d Send template then receive message list\n",
785 template = (struct CtdlMessage *)
786 malloc(sizeof(struct CtdlMessage));
787 memset(template, 0, sizeof(struct CtdlMessage));
788 template->cm_magic = CTDLMESSAGE_MAGIC;
789 template->cm_anon_type = MES_NORMAL;
791 while(client_getln(buf, sizeof buf) >= 0 && strcmp(buf,"000")) {
792 extract_token(tfield, buf, 0, '|', sizeof tfield);
793 extract_token(tvalue, buf, 1, '|', sizeof tvalue);
794 for (i='A'; i<='Z'; ++i) if (msgkeys[i]!=NULL) {
795 if (!strcasecmp(tfield, msgkeys[i])) {
796 template->cm_fields[i] =
804 cprintf("%d \n", LISTING_FOLLOWS);
807 CtdlForEachMessage(mode,
808 ( (mode == MSGS_SEARCH) ? 0 : cm_ref ),
809 ( (mode == MSGS_SEARCH) ? search_string : NULL ),
812 (with_headers ? headers_listing : simple_listing),
815 if (template != NULL) CtdlFreeMessage(template);
823 * help_subst() - support routine for help file viewer
825 void help_subst(char *strbuf, char *source, char *dest)
830 while (p = pattern2(strbuf, source), (p >= 0)) {
831 strcpy(workbuf, &strbuf[p + strlen(source)]);
832 strcpy(&strbuf[p], dest);
833 strcat(strbuf, workbuf);
838 void do_help_subst(char *buffer)
842 help_subst(buffer, "^nodename", config.c_nodename);
843 help_subst(buffer, "^humannode", config.c_humannode);
844 help_subst(buffer, "^fqdn", config.c_fqdn);
845 help_subst(buffer, "^username", CC->user.fullname);
846 snprintf(buf2, sizeof buf2, "%ld", CC->user.usernum);
847 help_subst(buffer, "^usernum", buf2);
848 help_subst(buffer, "^sysadm", config.c_sysadm);
849 help_subst(buffer, "^variantname", CITADEL);
850 snprintf(buf2, sizeof buf2, "%d", config.c_maxsessions);
851 help_subst(buffer, "^maxsessions", buf2);
852 help_subst(buffer, "^bbsdir", ctdl_message_dir);
858 * memfmout() - Citadel text formatter and paginator.
859 * Although the original purpose of this routine was to format
860 * text to the reader's screen width, all we're really using it
861 * for here is to format text out to 80 columns before sending it
862 * to the client. The client software may reformat it again.
865 char *mptr, /* where are we going to get our text from? */
866 char subst, /* nonzero if we should do substitutions */
867 char *nl) /* string to terminate lines with */
875 static int width = 80;
880 c = 1; /* c is the current pos */
884 while (ch = *mptr, ((ch != 0) && (strlen(buffer) < 126))) {
886 buffer[strlen(buffer) + 1] = 0;
887 buffer[strlen(buffer)] = ch;
890 if (buffer[0] == '^')
891 do_help_subst(buffer);
893 buffer[strlen(buffer) + 1] = 0;
895 strcpy(buffer, &buffer[1]);
903 if (((ch == 13) || (ch == 10)) && (old != 13) && (old != 10)) {
906 if (((old == 13) || (old == 10)) && (isspace(real))) {
911 if (((strlen(aaa) + c) > (width - 5)) && (strlen(aaa) > (width - 5))) {
912 cprintf("%s%s", nl, aaa);
921 if ((strlen(aaa) + c) > (width - 5)) {
930 if ((ch == 13) || (ch == 10)) {
931 cprintf("%s%s", aaa, nl);
938 cprintf("%s%s", aaa, nl);
944 * Callback function for mime parser that simply lists the part
946 void list_this_part(char *name, char *filename, char *partnum, char *disp,
947 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
948 char *cbid, void *cbuserdata)
952 ma = (struct ma_info *)cbuserdata;
953 if (ma->is_ma == 0) {
954 cprintf("part=%s|%s|%s|%s|%s|%ld|%s\n",
955 name, filename, partnum, disp, cbtype, (long)length, cbid);
960 * Callback function for multipart prefix
962 void list_this_pref(char *name, char *filename, char *partnum, char *disp,
963 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
964 char *cbid, void *cbuserdata)
968 ma = (struct ma_info *)cbuserdata;
969 if (!strcasecmp(cbtype, "multipart/alternative")) {
973 if (ma->is_ma == 0) {
974 cprintf("pref=%s|%s\n", partnum, cbtype);
979 * Callback function for multipart sufffix
981 void list_this_suff(char *name, char *filename, char *partnum, char *disp,
982 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
983 char *cbid, void *cbuserdata)
987 ma = (struct ma_info *)cbuserdata;
988 if (ma->is_ma == 0) {
989 cprintf("suff=%s|%s\n", partnum, cbtype);
991 if (!strcasecmp(cbtype, "multipart/alternative")) {
998 * Callback function for mime parser that opens a section for downloading
1000 void mime_download(char *name, char *filename, char *partnum, char *disp,
1001 void *content, char *cbtype, char *cbcharset, size_t length,
1002 char *encoding, char *cbid, void *cbuserdata)
1006 /* Silently go away if there's already a download open. */
1007 if (CC->download_fp != NULL)
1011 (!IsEmptyStr(partnum) && (!strcasecmp(CC->download_desired_section, partnum)))
1012 || (!IsEmptyStr(cbid) && (!strcasecmp(CC->download_desired_section, cbid)))
1014 CC->download_fp = tmpfile();
1015 if (CC->download_fp == NULL)
1018 rv = fwrite(content, length, 1, CC->download_fp);
1019 fflush(CC->download_fp);
1020 rewind(CC->download_fp);
1022 OpenCmdResult(filename, cbtype);
1029 * Callback function for mime parser that outputs a section all at once.
1030 * We can specify the desired section by part number *or* content-id.
1032 void mime_spew_section(char *name, char *filename, char *partnum, char *disp,
1033 void *content, char *cbtype, char *cbcharset, size_t length,
1034 char *encoding, char *cbid, void *cbuserdata)
1036 int *found_it = (int *)cbuserdata;
1039 (!IsEmptyStr(partnum) && (!strcasecmp(CC->download_desired_section, partnum)))
1040 || (!IsEmptyStr(cbid) && (!strcasecmp(CC->download_desired_section, cbid)))
1043 cprintf("%d %d|-1|%s|%s\n",
1049 client_write(content, length);
1056 * Load a message from disk into memory.
1057 * This is used by CtdlOutputMsg() and other fetch functions.
1059 * NOTE: Caller is responsible for freeing the returned CtdlMessage struct
1060 * using the CtdlMessageFree() function.
1062 struct CtdlMessage *CtdlFetchMessage(long msgnum, int with_body)
1064 struct cdbdata *dmsgtext;
1065 struct CtdlMessage *ret = NULL;
1069 cit_uint8_t field_header;
1071 CtdlLogPrintf(CTDL_DEBUG, "CtdlFetchMessage(%ld, %d)\n", msgnum, with_body);
1073 dmsgtext = cdb_fetch(CDB_MSGMAIN, &msgnum, sizeof(long));
1074 if (dmsgtext == NULL) {
1077 mptr = dmsgtext->ptr;
1078 upper_bound = mptr + dmsgtext->len;
1080 /* Parse the three bytes that begin EVERY message on disk.
1081 * The first is always 0xFF, the on-disk magic number.
1082 * The second is the anonymous/public type byte.
1083 * The third is the format type byte (vari, fixed, or MIME).
1087 CtdlLogPrintf(CTDL_ERR, "Message %ld appears to be corrupted.\n", msgnum);
1091 ret = (struct CtdlMessage *) malloc(sizeof(struct CtdlMessage));
1092 memset(ret, 0, sizeof(struct CtdlMessage));
1094 ret->cm_magic = CTDLMESSAGE_MAGIC;
1095 ret->cm_anon_type = *mptr++; /* Anon type byte */
1096 ret->cm_format_type = *mptr++; /* Format type byte */
1099 * The rest is zero or more arbitrary fields. Load them in.
1100 * We're done when we encounter either a zero-length field or
1101 * have just processed the 'M' (message text) field.
1104 if (mptr >= upper_bound) {
1107 field_header = *mptr++;
1108 ret->cm_fields[field_header] = strdup(mptr);
1110 while (*mptr++ != 0); /* advance to next field */
1112 } while ((mptr < upper_bound) && (field_header != 'M'));
1116 /* Always make sure there's something in the msg text field. If
1117 * it's NULL, the message text is most likely stored separately,
1118 * so go ahead and fetch that. Failing that, just set a dummy
1119 * body so other code doesn't barf.
1121 if ( (ret->cm_fields['M'] == NULL) && (with_body) ) {
1122 dmsgtext = cdb_fetch(CDB_BIGMSGS, &msgnum, sizeof(long));
1123 if (dmsgtext != NULL) {
1124 ret->cm_fields['M'] = strdup(dmsgtext->ptr);
1128 if (ret->cm_fields['M'] == NULL) {
1129 ret->cm_fields['M'] = strdup("\r\n\r\n (no text)\r\n");
1132 /* Perform "before read" hooks (aborting if any return nonzero) */
1133 if (PerformMessageHooks(ret, EVT_BEFOREREAD) > 0) {
1134 CtdlFreeMessage(ret);
1143 * Returns 1 if the supplied pointer points to a valid Citadel message.
1144 * If the pointer is NULL or the magic number check fails, returns 0.
1146 int is_valid_message(struct CtdlMessage *msg) {
1149 if ((msg->cm_magic) != CTDLMESSAGE_MAGIC) {
1150 CtdlLogPrintf(CTDL_WARNING, "is_valid_message() -- self-check failed\n");
1158 * 'Destructor' for struct CtdlMessage
1160 void CtdlFreeMessage(struct CtdlMessage *msg)
1164 if (is_valid_message(msg) == 0)
1166 if (msg != NULL) free (msg);
1170 for (i = 0; i < 256; ++i)
1171 if (msg->cm_fields[i] != NULL) {
1172 free(msg->cm_fields[i]);
1175 msg->cm_magic = 0; /* just in case */
1181 * Pre callback function for multipart/alternative
1183 * NOTE: this differs from the standard behavior for a reason. Normally when
1184 * displaying multipart/alternative you want to show the _last_ usable
1185 * format in the message. Here we show the _first_ one, because it's
1186 * usually text/plain. Since this set of functions is designed for text
1187 * output to non-MIME-aware clients, this is the desired behavior.
1190 void fixed_output_pre(char *name, char *filename, char *partnum, char *disp,
1191 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
1192 char *cbid, void *cbuserdata)
1196 ma = (struct ma_info *)cbuserdata;
1197 CtdlLogPrintf(CTDL_DEBUG, "fixed_output_pre() type=<%s>\n", cbtype);
1198 if (!strcasecmp(cbtype, "multipart/alternative")) {
1202 if (!strcasecmp(cbtype, "message/rfc822")) {
1208 * Post callback function for multipart/alternative
1210 void fixed_output_post(char *name, char *filename, char *partnum, char *disp,
1211 void *content, char *cbtype, char *cbcharset, size_t length,
1212 char *encoding, char *cbid, void *cbuserdata)
1216 ma = (struct ma_info *)cbuserdata;
1217 CtdlLogPrintf(CTDL_DEBUG, "fixed_output_post() type=<%s>\n", cbtype);
1218 if (!strcasecmp(cbtype, "multipart/alternative")) {
1222 if (!strcasecmp(cbtype, "message/rfc822")) {
1228 * Inline callback function for mime parser that wants to display text
1230 void fixed_output(char *name, char *filename, char *partnum, char *disp,
1231 void *content, char *cbtype, char *cbcharset, size_t length,
1232 char *encoding, char *cbid, void *cbuserdata)
1239 ma = (struct ma_info *)cbuserdata;
1241 CtdlLogPrintf(CTDL_DEBUG,
1242 "fixed_output() part %s: %s (%s) (%ld bytes)\n",
1243 partnum, filename, cbtype, (long)length);
1246 * If we're in the middle of a multipart/alternative scope and
1247 * we've already printed another section, skip this one.
1249 if ( (ma->is_ma) && (ma->did_print) ) {
1250 CtdlLogPrintf(CTDL_DEBUG, "Skipping part %s (%s)\n", partnum, cbtype);
1255 if ( (!strcasecmp(cbtype, "text/plain"))
1256 || (IsEmptyStr(cbtype)) ) {
1259 client_write(wptr, length);
1260 if (wptr[length-1] != '\n') {
1267 if (!strcasecmp(cbtype, "text/html")) {
1268 ptr = html_to_ascii(content, length, 80, 0);
1270 client_write(ptr, wlen);
1271 if (ptr[wlen-1] != '\n') {
1278 if (ma->use_fo_hooks) {
1279 if (PerformFixedOutputHooks(cbtype, content, length)) {
1280 /* above function returns nonzero if it handled the part */
1285 if (strncasecmp(cbtype, "multipart/", 10)) {
1286 cprintf("Part %s: %s (%s) (%ld bytes)\r\n",
1287 partnum, filename, cbtype, (long)length);
1293 * The client is elegant and sophisticated and wants to be choosy about
1294 * MIME content types, so figure out which multipart/alternative part
1295 * we're going to send.
1297 * We use a system of weights. When we find a part that matches one of the
1298 * MIME types we've declared as preferential, we can store it in ma->chosen_part
1299 * and then set ma->chosen_pref to that MIME type's position in our preference
1300 * list. If we then hit another match, we only replace the first match if
1301 * the preference value is lower.
1303 void choose_preferred(char *name, char *filename, char *partnum, char *disp,
1304 void *content, char *cbtype, char *cbcharset, size_t length,
1305 char *encoding, char *cbid, void *cbuserdata)
1311 ma = (struct ma_info *)cbuserdata;
1313 // NOTE: REMOVING THIS CONDITIONAL FIXES BUG 220
1314 // http://bugzilla.citadel.org/show_bug.cgi?id=220
1315 // I don't know if there are any side effects! Please TEST TEST TEST
1316 //if (ma->is_ma > 0) {
1318 for (i=0; i<num_tokens(CC->preferred_formats, '|'); ++i) {
1319 extract_token(buf, CC->preferred_formats, i, '|', sizeof buf);
1320 if ( (!strcasecmp(buf, cbtype)) && (!ma->freeze) ) {
1321 if (i < ma->chosen_pref) {
1322 CtdlLogPrintf(CTDL_DEBUG, "Setting chosen part: <%s>\n", partnum);
1323 safestrncpy(ma->chosen_part, partnum, sizeof ma->chosen_part);
1324 ma->chosen_pref = i;
1331 * Now that we've chosen our preferred part, output it.
1333 void output_preferred(char *name, char *filename, char *partnum, char *disp,
1334 void *content, char *cbtype, char *cbcharset, size_t length,
1335 char *encoding, char *cbid, void *cbuserdata)
1339 int add_newline = 0;
1343 ma = (struct ma_info *)cbuserdata;
1345 /* This is not the MIME part you're looking for... */
1346 if (strcasecmp(partnum, ma->chosen_part)) return;
1348 /* If the content-type of this part is in our preferred formats
1349 * list, we can simply output it verbatim.
1351 for (i=0; i<num_tokens(CC->preferred_formats, '|'); ++i) {
1352 extract_token(buf, CC->preferred_formats, i, '|', sizeof buf);
1353 if (!strcasecmp(buf, cbtype)) {
1354 /* Yeah! Go! W00t!! */
1356 text_content = (char *)content;
1357 if (text_content[length-1] != '\n') {
1360 cprintf("Content-type: %s", cbtype);
1361 if (!IsEmptyStr(cbcharset)) {
1362 cprintf("; charset=%s", cbcharset);
1364 cprintf("\nContent-length: %d\n",
1365 (int)(length + add_newline) );
1366 if (!IsEmptyStr(encoding)) {
1367 cprintf("Content-transfer-encoding: %s\n", encoding);
1370 cprintf("Content-transfer-encoding: 7bit\n");
1372 cprintf("X-Citadel-MSG4-Partnum: %s\n", partnum);
1374 client_write(content, length);
1375 if (add_newline) cprintf("\n");
1380 /* No translations required or possible: output as text/plain */
1381 cprintf("Content-type: text/plain\n\n");
1382 fixed_output(name, filename, partnum, disp, content, cbtype, cbcharset,
1383 length, encoding, cbid, cbuserdata);
1388 char desired_section[64];
1395 * Callback function for
1397 void extract_encapsulated_message(char *name, char *filename, char *partnum, char *disp,
1398 void *content, char *cbtype, char *cbcharset, size_t length,
1399 char *encoding, char *cbid, void *cbuserdata)
1401 struct encapmsg *encap;
1403 encap = (struct encapmsg *)cbuserdata;
1405 /* Only proceed if this is the desired section... */
1406 if (!strcasecmp(encap->desired_section, partnum)) {
1407 encap->msglen = length;
1408 encap->msg = malloc(length + 2);
1409 memcpy(encap->msg, content, length);
1419 * Get a message off disk. (returns om_* values found in msgbase.h)
1422 int CtdlOutputMsg(long msg_num, /* message number (local) to fetch */
1423 int mode, /* how would you like that message? */
1424 int headers_only, /* eschew the message body? */
1425 int do_proto, /* do Citadel protocol responses? */
1426 int crlf, /* Use CRLF newlines instead of LF? */
1427 char *section, /* NULL or a message/rfc822 section */
1428 int flags /* should the bessage be exported clean? */
1430 struct CtdlMessage *TheMessage = NULL;
1431 int retcode = om_no_such_msg;
1432 struct encapmsg encap;
1434 CtdlLogPrintf(CTDL_DEBUG, "CtdlOutputMsg() msgnum=%ld, mode=%d, section=%s\n",
1436 (section ? section : "<>")
1439 if ((!(CC->logged_in)) && (!(CC->internal_pgm))) {
1440 if (do_proto) cprintf("%d Not logged in.\n",
1441 ERROR + NOT_LOGGED_IN);
1442 return(om_not_logged_in);
1445 /* FIXME: check message id against msglist for this room */
1448 * Fetch the message from disk. If we're in HEADERS_FAST mode,
1449 * request that we don't even bother loading the body into memory.
1451 if (headers_only == HEADERS_FAST) {
1452 TheMessage = CtdlFetchMessage(msg_num, 0);
1455 TheMessage = CtdlFetchMessage(msg_num, 1);
1458 if (TheMessage == NULL) {
1459 if (do_proto) cprintf("%d Can't locate msg %ld on disk\n",
1460 ERROR + MESSAGE_NOT_FOUND, msg_num);
1461 return(om_no_such_msg);
1464 /* Here is the weird form of this command, to process only an
1465 * encapsulated message/rfc822 section.
1467 if (section) if (!IsEmptyStr(section)) if (strcmp(section, "0")) {
1468 memset(&encap, 0, sizeof encap);
1469 safestrncpy(encap.desired_section, section, sizeof encap.desired_section);
1470 mime_parser(TheMessage->cm_fields['M'],
1472 *extract_encapsulated_message,
1473 NULL, NULL, (void *)&encap, 0
1475 CtdlFreeMessage(TheMessage);
1479 encap.msg[encap.msglen] = 0;
1480 TheMessage = convert_internet_message(encap.msg);
1481 encap.msg = NULL; /* no free() here, TheMessage owns it now */
1483 /* Now we let it fall through to the bottom of this
1484 * function, because TheMessage now contains the
1485 * encapsulated message instead of the top-level
1486 * message. Isn't that neat?
1491 if (do_proto) cprintf("%d msg %ld has no part %s\n",
1492 ERROR + MESSAGE_NOT_FOUND, msg_num, section);
1493 retcode = om_no_such_msg;
1498 /* Ok, output the message now */
1499 retcode = CtdlOutputPreLoadedMsg(TheMessage, mode, headers_only, do_proto, crlf, flags);
1500 CtdlFreeMessage(TheMessage);
1506 char *qp_encode_email_addrs(char *source)
1508 char user[256], node[256], name[256];
1509 const char headerStr[] = "=?UTF-8?Q?";
1513 int need_to_encode = 0;
1519 long nAddrPtrMax = 50;
1524 if (source == NULL) return source;
1525 if (IsEmptyStr(source)) return source;
1527 AddrPtr = malloc (sizeof (long) * nAddrPtrMax);
1528 AddrUtf8 = malloc (sizeof (long) * nAddrPtrMax);
1529 memset(AddrUtf8, 0, sizeof (long) * nAddrPtrMax);
1532 while (!IsEmptyStr (&source[i])) {
1533 if (nColons >= nAddrPtrMax){
1536 ptr = (long *) malloc(sizeof (long) * nAddrPtrMax * 2);
1537 memcpy (ptr, AddrPtr, sizeof (long) * nAddrPtrMax);
1538 free (AddrPtr), AddrPtr = ptr;
1540 ptr = (long *) malloc(sizeof (long) * nAddrPtrMax * 2);
1541 memset(&ptr[nAddrPtrMax], 0,
1542 sizeof (long) * nAddrPtrMax);
1544 memcpy (ptr, AddrUtf8, sizeof (long) * nAddrPtrMax);
1545 free (AddrUtf8), AddrUtf8 = ptr;
1548 if (((unsigned char) source[i] < 32) ||
1549 ((unsigned char) source[i] > 126)) {
1551 AddrUtf8[nColons] = 1;
1553 if (source[i] == '"')
1554 InQuotes = !InQuotes;
1555 if (!InQuotes && source[i] == ',') {
1556 AddrPtr[nColons] = i;
1561 if (need_to_encode == 0) {
1568 EncodedMaxLen = nColons * (sizeof(headerStr) + 3) + SourceLen * 3;
1569 Encoded = (char*) malloc (EncodedMaxLen);
1571 for (i = 0; i < nColons; i++)
1572 source[AddrPtr[i]++] = '\0';
1576 for (i = 0; i < nColons && nPtr != NULL; i++) {
1577 nmax = EncodedMaxLen - (nPtr - Encoded);
1579 process_rfc822_addr(&source[AddrPtr[i]],
1583 /* TODO: libIDN here ! */
1584 if (IsEmptyStr(name)) {
1585 n = snprintf(nPtr, nmax,
1586 (i==0)?"%s@%s" : ",%s@%s",
1590 EncodedName = rfc2047encode(name, strlen(name));
1591 n = snprintf(nPtr, nmax,
1592 (i==0)?"%s <%s@%s>" : ",%s <%s@%s>",
1593 EncodedName, user, node);
1598 n = snprintf(nPtr, nmax,
1599 (i==0)?"%s" : ",%s",
1600 &source[AddrPtr[i]]);
1606 ptr = (char*) malloc(EncodedMaxLen * 2);
1607 memcpy(ptr, Encoded, EncodedMaxLen);
1608 nnPtr = ptr + (nPtr - Encoded), nPtr = nnPtr;
1609 free(Encoded), Encoded = ptr;
1611 i--; /* do it once more with properly lengthened buffer */
1614 for (i = 0; i < nColons; i++)
1615 source[--AddrPtr[i]] = ',';
1622 /* If the last item in a list of recipients was truncated to a partial address,
1623 * remove it completely in order to avoid choking libSieve
1625 void sanitize_truncated_recipient(char *str)
1628 if (num_tokens(str, ',') < 2) return;
1630 int len = strlen(str);
1631 if (len < 900) return;
1632 if (len > 998) str[998] = 0;
1634 char *cptr = strrchr(str, ',');
1637 char *lptr = strchr(cptr, '<');
1638 char *rptr = strchr(cptr, '>');
1640 if ( (lptr) && (rptr) && (rptr > lptr) ) return;
1648 * Get a message off disk. (returns om_* values found in msgbase.h)
1650 int CtdlOutputPreLoadedMsg(
1651 struct CtdlMessage *TheMessage,
1652 int mode, /* how would you like that message? */
1653 int headers_only, /* eschew the message body? */
1654 int do_proto, /* do Citadel protocol responses? */
1655 int crlf, /* Use CRLF newlines instead of LF? */
1656 int flags /* should the bessage be exported clean? */
1660 cit_uint8_t ch, prev_ch;
1662 char display_name[256];
1664 char *nl; /* newline string */
1666 int subject_found = 0;
1669 /* Buffers needed for RFC822 translation. These are all filled
1670 * using functions that are bounds-checked, and therefore we can
1671 * make them substantially smaller than SIZ.
1678 char datestamp[100];
1680 CtdlLogPrintf(CTDL_DEBUG, "CtdlOutputPreLoadedMsg(TheMessage=%s, %d, %d, %d, %d\n",
1681 ((TheMessage == NULL) ? "NULL" : "not null"),
1682 mode, headers_only, do_proto, crlf);
1684 strcpy(mid, "unknown");
1685 nl = (crlf ? "\r\n" : "\n");
1687 if (!is_valid_message(TheMessage)) {
1688 CtdlLogPrintf(CTDL_ERR,
1689 "ERROR: invalid preloaded message for output\n");
1691 return(om_no_such_msg);
1694 /* Are we downloading a MIME component? */
1695 if (mode == MT_DOWNLOAD) {
1696 if (TheMessage->cm_format_type != FMT_RFC822) {
1698 cprintf("%d This is not a MIME message.\n",
1699 ERROR + ILLEGAL_VALUE);
1700 } else if (CC->download_fp != NULL) {
1701 if (do_proto) cprintf(
1702 "%d You already have a download open.\n",
1703 ERROR + RESOURCE_BUSY);
1705 /* Parse the message text component */
1706 mptr = TheMessage->cm_fields['M'];
1707 mime_parser(mptr, NULL, *mime_download, NULL, NULL, NULL, 0);
1708 /* If there's no file open by this time, the requested
1709 * section wasn't found, so print an error
1711 if (CC->download_fp == NULL) {
1712 if (do_proto) cprintf(
1713 "%d Section %s not found.\n",
1714 ERROR + FILE_NOT_FOUND,
1715 CC->download_desired_section);
1718 return((CC->download_fp != NULL) ? om_ok : om_mime_error);
1721 /* MT_SPEW_SECTION is like MT_DOWNLOAD except it outputs the whole MIME part
1722 * in a single server operation instead of opening a download file.
1724 if (mode == MT_SPEW_SECTION) {
1725 if (TheMessage->cm_format_type != FMT_RFC822) {
1727 cprintf("%d This is not a MIME message.\n",
1728 ERROR + ILLEGAL_VALUE);
1730 /* Parse the message text component */
1733 mptr = TheMessage->cm_fields['M'];
1734 mime_parser(mptr, NULL, *mime_spew_section, NULL, NULL, (void *)&found_it, 0);
1735 /* If section wasn't found, print an error
1738 if (do_proto) cprintf(
1739 "%d Section %s not found.\n",
1740 ERROR + FILE_NOT_FOUND,
1741 CC->download_desired_section);
1744 return((CC->download_fp != NULL) ? om_ok : om_mime_error);
1747 /* now for the user-mode message reading loops */
1748 if (do_proto) cprintf("%d msg:\n", LISTING_FOLLOWS);
1750 /* Does the caller want to skip the headers? */
1751 if (headers_only == HEADERS_NONE) goto START_TEXT;
1753 /* Tell the client which format type we're using. */
1754 if ( (mode == MT_CITADEL) && (do_proto) ) {
1755 cprintf("type=%d\n", TheMessage->cm_format_type);
1758 /* nhdr=yes means that we're only displaying headers, no body */
1759 if ( (TheMessage->cm_anon_type == MES_ANONONLY)
1760 && ((mode == MT_CITADEL) || (mode == MT_MIME))
1763 cprintf("nhdr=yes\n");
1766 /* begin header processing loop for Citadel message format */
1768 if ((mode == MT_CITADEL) || (mode == MT_MIME)) {
1770 safestrncpy(display_name, "<unknown>", sizeof display_name);
1771 if (TheMessage->cm_fields['A']) {
1772 strcpy(buf, TheMessage->cm_fields['A']);
1773 if (TheMessage->cm_anon_type == MES_ANONONLY) {
1774 safestrncpy(display_name, "****", sizeof display_name);
1776 else if (TheMessage->cm_anon_type == MES_ANONOPT) {
1777 safestrncpy(display_name, "anonymous", sizeof display_name);
1780 safestrncpy(display_name, buf, sizeof display_name);
1782 if ((is_room_aide())
1783 && ((TheMessage->cm_anon_type == MES_ANONONLY)
1784 || (TheMessage->cm_anon_type == MES_ANONOPT))) {
1785 size_t tmp = strlen(display_name);
1786 snprintf(&display_name[tmp],
1787 sizeof display_name - tmp,
1792 /* Don't show Internet address for users on the
1793 * local Citadel network.
1796 if (TheMessage->cm_fields['N'] != NULL)
1797 if (!IsEmptyStr(TheMessage->cm_fields['N']))
1798 if (haschar(TheMessage->cm_fields['N'], '.') == 0) {
1802 /* Now spew the header fields in the order we like them. */
1803 safestrncpy(allkeys, FORDER, sizeof allkeys);
1804 for (i=0; i<strlen(allkeys); ++i) {
1805 k = (int) allkeys[i];
1807 if ( (TheMessage->cm_fields[k] != NULL)
1808 && (msgkeys[k] != NULL) ) {
1809 if ((k == 'V') || (k == 'R') || (k == 'Y')) {
1810 sanitize_truncated_recipient(TheMessage->cm_fields[k]);
1813 if (do_proto) cprintf("%s=%s\n",
1817 else if ((k == 'F') && (suppress_f)) {
1820 /* Masquerade display name if needed */
1822 if (do_proto) cprintf("%s=%s\n",
1824 TheMessage->cm_fields[k]
1833 /* begin header processing loop for RFC822 transfer format */
1838 strcpy(snode, NODENAME);
1839 if (mode == MT_RFC822) {
1840 for (i = 0; i < 256; ++i) {
1841 if (TheMessage->cm_fields[i]) {
1842 mptr = mpptr = TheMessage->cm_fields[i];
1845 safestrncpy(luser, mptr, sizeof luser);
1846 safestrncpy(suser, mptr, sizeof suser);
1848 else if (i == 'Y') {
1849 if ((flags & QP_EADDR) != 0) {
1850 mptr = qp_encode_email_addrs(mptr);
1852 sanitize_truncated_recipient(mptr);
1853 cprintf("CC: %s%s", mptr, nl);
1855 else if (i == 'P') {
1856 cprintf("Return-Path: %s%s", mptr, nl);
1858 else if (i == 'L') {
1859 cprintf("List-ID: %s%s", mptr, nl);
1861 else if (i == 'V') {
1862 if ((flags & QP_EADDR) != 0)
1863 mptr = qp_encode_email_addrs(mptr);
1864 cprintf("Envelope-To: %s%s", mptr, nl);
1866 else if (i == 'U') {
1867 cprintf("Subject: %s%s", mptr, nl);
1871 safestrncpy(mid, mptr, sizeof mid);
1873 safestrncpy(fuser, mptr, sizeof fuser);
1874 /* else if (i == 'O')
1875 cprintf("X-Citadel-Room: %s%s",
1878 safestrncpy(snode, mptr, sizeof snode);
1881 if (haschar(mptr, '@') == 0)
1883 sanitize_truncated_recipient(mptr);
1884 cprintf("To: %s@%s", mptr, config.c_fqdn);
1889 if ((flags & QP_EADDR) != 0) {
1890 mptr = qp_encode_email_addrs(mptr);
1892 sanitize_truncated_recipient(mptr);
1893 cprintf("To: %s", mptr);
1897 else if (i == 'T') {
1898 datestring(datestamp, sizeof datestamp,
1899 atol(mptr), DATESTRING_RFC822);
1900 cprintf("Date: %s%s", datestamp, nl);
1902 else if (i == 'W') {
1903 cprintf("References: ");
1904 k = num_tokens(mptr, '|');
1905 for (j=0; j<k; ++j) {
1906 extract_token(buf, mptr, j, '|', sizeof buf);
1907 cprintf("<%s>", buf);
1920 if (subject_found == 0) {
1921 cprintf("Subject: (no subject)%s", nl);
1925 for (i=0; !IsEmptyStr(&suser[i]); ++i) {
1926 suser[i] = tolower(suser[i]);
1927 if (!isalnum(suser[i])) suser[i]='_';
1930 if (mode == MT_RFC822) {
1931 if (!strcasecmp(snode, NODENAME)) {
1932 safestrncpy(snode, FQDN, sizeof snode);
1935 /* Construct a fun message id */
1936 cprintf("Message-ID: <%s", mid);/// todo: this possibly breaks threadding mails.
1937 if (strchr(mid, '@')==NULL) {
1938 cprintf("@%s", snode);
1942 if (!is_room_aide() && (TheMessage->cm_anon_type == MES_ANONONLY)) {
1943 cprintf("From: \"----\" <x@x.org>%s", nl);
1945 else if (!is_room_aide() && (TheMessage->cm_anon_type == MES_ANONOPT)) {
1946 cprintf("From: \"anonymous\" <x@x.org>%s", nl);
1948 else if (!IsEmptyStr(fuser)) {
1949 cprintf("From: \"%s\" <%s>%s", luser, fuser, nl);
1952 cprintf("From: \"%s\" <%s@%s>%s", luser, suser, snode, nl);
1955 /* Blank line signifying RFC822 end-of-headers */
1956 if (TheMessage->cm_format_type != FMT_RFC822) {
1961 /* end header processing loop ... at this point, we're in the text */
1963 if (headers_only == HEADERS_FAST) goto DONE;
1964 mptr = TheMessage->cm_fields['M'];
1966 /* Tell the client about the MIME parts in this message */
1967 if (TheMessage->cm_format_type == FMT_RFC822) {
1968 if ( (mode == MT_CITADEL) || (mode == MT_MIME) ) {
1969 memset(&ma, 0, sizeof(struct ma_info));
1970 mime_parser(mptr, NULL,
1971 (do_proto ? *list_this_part : NULL),
1972 (do_proto ? *list_this_pref : NULL),
1973 (do_proto ? *list_this_suff : NULL),
1976 else if (mode == MT_RFC822) { /* unparsed RFC822 dump */
1977 char *start_of_text = NULL;
1978 start_of_text = strstr(mptr, "\n\r\n");
1979 if (start_of_text == NULL) start_of_text = strstr(mptr, "\n\n");
1980 if (start_of_text == NULL) start_of_text = mptr;
1982 start_of_text = strstr(start_of_text, "\n");
1987 int nllen = strlen(nl);
1989 while (ch=*mptr, ch!=0) {
1995 ((headers_only == HEADERS_NONE) && (mptr >= start_of_text))
1996 || ((headers_only == HEADERS_ONLY) && (mptr < start_of_text))
1997 || ((headers_only != HEADERS_NONE) && (headers_only != HEADERS_ONLY))
2000 sprintf(&outbuf[outlen], "%s", nl);
2004 outbuf[outlen++] = ch;
2008 if (flags & ESC_DOT)
2010 if ((prev_ch == 10) && (ch == '.') && ((*(mptr+1) == 13) || (*(mptr+1) == 10)))
2012 outbuf[outlen++] = '.';
2017 if (outlen > 1000) {
2018 client_write(outbuf, outlen);
2023 client_write(outbuf, outlen);
2031 if (headers_only == HEADERS_ONLY) {
2035 /* signify start of msg text */
2036 if ( (mode == MT_CITADEL) || (mode == MT_MIME) ) {
2037 if (do_proto) cprintf("text\n");
2040 /* If the format type on disk is 1 (fixed-format), then we want
2041 * everything to be output completely literally ... regardless of
2042 * what message transfer format is in use.
2044 if (TheMessage->cm_format_type == FMT_FIXED) {
2046 if (mode == MT_MIME) {
2047 cprintf("Content-type: text/plain\n\n");
2051 while (ch = *mptr++, ch > 0) {
2054 if ((ch == 10) || (buflen > 250)) {
2056 cprintf("%s%s", buf, nl);
2065 if (!IsEmptyStr(buf))
2066 cprintf("%s%s", buf, nl);
2069 /* If the message on disk is format 0 (Citadel vari-format), we
2070 * output using the formatter at 80 columns. This is the final output
2071 * form if the transfer format is RFC822, but if the transfer format
2072 * is Citadel proprietary, it'll still work, because the indentation
2073 * for new paragraphs is correct and the client will reformat the
2074 * message to the reader's screen width.
2076 if (TheMessage->cm_format_type == FMT_CITADEL) {
2077 if (mode == MT_MIME) {
2078 cprintf("Content-type: text/x-citadel-variformat\n\n");
2080 memfmout(mptr, 0, nl);
2083 /* If the message on disk is format 4 (MIME), we've gotta hand it
2084 * off to the MIME parser. The client has already been told that
2085 * this message is format 1 (fixed format), so the callback function
2086 * we use will display those parts as-is.
2088 if (TheMessage->cm_format_type == FMT_RFC822) {
2089 memset(&ma, 0, sizeof(struct ma_info));
2091 if (mode == MT_MIME) {
2092 ma.use_fo_hooks = 0;
2093 strcpy(ma.chosen_part, "1");
2094 ma.chosen_pref = 9999;
2095 mime_parser(mptr, NULL,
2096 *choose_preferred, *fixed_output_pre,
2097 *fixed_output_post, (void *)&ma, 0);
2098 mime_parser(mptr, NULL,
2099 *output_preferred, NULL, NULL, (void *)&ma, CC->msg4_dont_decode);
2102 ma.use_fo_hooks = 1;
2103 mime_parser(mptr, NULL,
2104 *fixed_output, *fixed_output_pre,
2105 *fixed_output_post, (void *)&ma, 0);
2110 DONE: /* now we're done */
2111 if (do_proto) cprintf("000\n");
2118 * display a message (mode 0 - Citadel proprietary)
2120 void cmd_msg0(char *cmdbuf)
2123 int headers_only = HEADERS_ALL;
2125 msgid = extract_long(cmdbuf, 0);
2126 headers_only = extract_int(cmdbuf, 1);
2128 CtdlOutputMsg(msgid, MT_CITADEL, headers_only, 1, 0, NULL, 0);
2134 * display a message (mode 2 - RFC822)
2136 void cmd_msg2(char *cmdbuf)
2139 int headers_only = HEADERS_ALL;
2141 msgid = extract_long(cmdbuf, 0);
2142 headers_only = extract_int(cmdbuf, 1);
2144 CtdlOutputMsg(msgid, MT_RFC822, headers_only, 1, 1, NULL, 0);
2150 * display a message (mode 3 - IGnet raw format - internal programs only)
2152 void cmd_msg3(char *cmdbuf)
2155 struct CtdlMessage *msg = NULL;
2158 if (CC->internal_pgm == 0) {
2159 cprintf("%d This command is for internal programs only.\n",
2160 ERROR + HIGHER_ACCESS_REQUIRED);
2164 msgnum = extract_long(cmdbuf, 0);
2165 msg = CtdlFetchMessage(msgnum, 1);
2167 cprintf("%d Message %ld not found.\n",
2168 ERROR + MESSAGE_NOT_FOUND, msgnum);
2172 serialize_message(&smr, msg);
2173 CtdlFreeMessage(msg);
2176 cprintf("%d Unable to serialize message\n",
2177 ERROR + INTERNAL_ERROR);
2181 cprintf("%d %ld\n", BINARY_FOLLOWS, (long)smr.len);
2182 client_write((char *)smr.ser, (int)smr.len);
2189 * Display a message using MIME content types
2191 void cmd_msg4(char *cmdbuf)
2196 msgid = extract_long(cmdbuf, 0);
2197 extract_token(section, cmdbuf, 1, '|', sizeof section);
2198 CtdlOutputMsg(msgid, MT_MIME, 0, 1, 0, (section[0] ? section : NULL) , 0);
2204 * Client tells us its preferred message format(s)
2206 void cmd_msgp(char *cmdbuf)
2208 if (!strcasecmp(cmdbuf, "dont_decode")) {
2209 CC->msg4_dont_decode = 1;
2210 cprintf("%d MSG4 will not pre-decode messages.\n", CIT_OK);
2213 safestrncpy(CC->preferred_formats, cmdbuf, sizeof(CC->preferred_formats));
2214 cprintf("%d Preferred MIME formats have been set.\n", CIT_OK);
2220 * Open a component of a MIME message as a download file
2222 void cmd_opna(char *cmdbuf)
2225 char desired_section[128];
2227 msgid = extract_long(cmdbuf, 0);
2228 extract_token(desired_section, cmdbuf, 1, '|', sizeof desired_section);
2229 safestrncpy(CC->download_desired_section, desired_section,
2230 sizeof CC->download_desired_section);
2231 CtdlOutputMsg(msgid, MT_DOWNLOAD, 0, 1, 1, NULL, 0);
2236 * Open a component of a MIME message and transmit it all at once
2238 void cmd_dlat(char *cmdbuf)
2241 char desired_section[128];
2243 msgid = extract_long(cmdbuf, 0);
2244 extract_token(desired_section, cmdbuf, 1, '|', sizeof desired_section);
2245 safestrncpy(CC->download_desired_section, desired_section,
2246 sizeof CC->download_desired_section);
2247 CtdlOutputMsg(msgid, MT_SPEW_SECTION, 0, 1, 1, NULL, 0);
2252 * Save one or more message pointers into a specified room
2253 * (Returns 0 for success, nonzero for failure)
2254 * roomname may be NULL to use the current room
2256 * Note that the 'supplied_msg' field may be set to NULL, in which case
2257 * the message will be fetched from disk, by number, if we need to perform
2258 * replication checks. This adds an additional database read, so if the
2259 * caller already has the message in memory then it should be supplied. (Obviously
2260 * this mode of operation only works if we're saving a single message.)
2262 int CtdlSaveMsgPointersInRoom(char *roomname, long newmsgidlist[], int num_newmsgs,
2263 int do_repl_check, struct CtdlMessage *supplied_msg)
2266 char hold_rm[ROOMNAMELEN];
2267 struct cdbdata *cdbfr;
2270 long highest_msg = 0L;
2273 struct CtdlMessage *msg = NULL;
2275 long *msgs_to_be_merged = NULL;
2276 int num_msgs_to_be_merged = 0;
2278 CtdlLogPrintf(CTDL_DEBUG,
2279 "CtdlSaveMsgPointersInRoom(room=%s, num_msgs=%d, repl=%d)\n",
2280 roomname, num_newmsgs, do_repl_check);
2282 strcpy(hold_rm, CC->room.QRname);
2285 if (newmsgidlist == NULL) return(ERROR + INTERNAL_ERROR);
2286 if (num_newmsgs < 1) return(ERROR + INTERNAL_ERROR);
2287 if (num_newmsgs > 1) supplied_msg = NULL;
2289 /* Now the regular stuff */
2290 if (lgetroom(&CC->room,
2291 ((roomname != NULL) ? roomname : CC->room.QRname) )
2293 CtdlLogPrintf(CTDL_ERR, "No such room <%s>\n", roomname);
2294 return(ERROR + ROOM_NOT_FOUND);
2298 msgs_to_be_merged = malloc(sizeof(long) * num_newmsgs);
2299 num_msgs_to_be_merged = 0;
2302 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long));
2303 if (cdbfr == NULL) {
2307 msglist = (long *) cdbfr->ptr;
2308 cdbfr->ptr = NULL; /* CtdlSaveMsgPointerInRoom() now owns this memory */
2309 num_msgs = cdbfr->len / sizeof(long);
2314 /* Create a list of msgid's which were supplied by the caller, but do
2315 * not already exist in the target room. It is absolutely taboo to
2316 * have more than one reference to the same message in a room.
2318 for (i=0; i<num_newmsgs; ++i) {
2320 if (num_msgs > 0) for (j=0; j<num_msgs; ++j) {
2321 if (msglist[j] == newmsgidlist[i]) {
2326 msgs_to_be_merged[num_msgs_to_be_merged++] = newmsgidlist[i];
2330 CtdlLogPrintf(9, "%d unique messages to be merged\n", num_msgs_to_be_merged);
2333 * Now merge the new messages
2335 msglist = realloc(msglist, (sizeof(long) * (num_msgs + num_msgs_to_be_merged)) );
2336 if (msglist == NULL) {
2337 CtdlLogPrintf(CTDL_ALERT, "ERROR: can't realloc message list!\n");
2339 memcpy(&msglist[num_msgs], msgs_to_be_merged, (sizeof(long) * num_msgs_to_be_merged) );
2340 num_msgs += num_msgs_to_be_merged;
2342 /* Sort the message list, so all the msgid's are in order */
2343 num_msgs = sort_msglist(msglist, num_msgs);
2345 /* Determine the highest message number */
2346 highest_msg = msglist[num_msgs - 1];
2348 /* Write it back to disk. */
2349 cdb_store(CDB_MSGLISTS, &CC->room.QRnumber, (int)sizeof(long),
2350 msglist, (int)(num_msgs * sizeof(long)));
2352 /* Free up the memory we used. */
2355 /* Update the highest-message pointer and unlock the room. */
2356 CC->room.QRhighest = highest_msg;
2357 lputroom(&CC->room);
2359 /* Perform replication checks if necessary */
2360 if ( (DoesThisRoomNeedEuidIndexing(&CC->room)) && (do_repl_check) ) {
2361 CtdlLogPrintf(CTDL_DEBUG, "CtdlSaveMsgPointerInRoom() doing repl checks\n");
2363 for (i=0; i<num_msgs_to_be_merged; ++i) {
2364 msgid = msgs_to_be_merged[i];
2366 if (supplied_msg != NULL) {
2370 msg = CtdlFetchMessage(msgid, 0);
2374 ReplicationChecks(msg);
2376 /* If the message has an Exclusive ID, index that... */
2377 if (msg->cm_fields['E'] != NULL) {
2378 index_message_by_euid(msg->cm_fields['E'], &CC->room, msgid);
2381 /* Free up the memory we may have allocated */
2382 if (msg != supplied_msg) {
2383 CtdlFreeMessage(msg);
2391 CtdlLogPrintf(CTDL_DEBUG, "CtdlSaveMsgPointerInRoom() skips repl checks\n");
2394 /* Submit this room for processing by hooks */
2395 PerformRoomHooks(&CC->room);
2397 /* Go back to the room we were in before we wandered here... */
2398 getroom(&CC->room, hold_rm);
2400 /* Bump the reference count for all messages which were merged */
2401 for (i=0; i<num_msgs_to_be_merged; ++i) {
2402 AdjRefCount(msgs_to_be_merged[i], +1);
2405 /* Free up memory... */
2406 if (msgs_to_be_merged != NULL) {
2407 free(msgs_to_be_merged);
2410 /* Return success. */
2416 * This is the same as CtdlSaveMsgPointersInRoom() but it only accepts
2419 int CtdlSaveMsgPointerInRoom(char *roomname, long msgid,
2420 int do_repl_check, struct CtdlMessage *supplied_msg)
2422 return CtdlSaveMsgPointersInRoom(roomname, &msgid, 1, do_repl_check, supplied_msg);
2429 * Message base operation to save a new message to the message store
2430 * (returns new message number)
2432 * This is the back end for CtdlSubmitMsg() and should not be directly
2433 * called by server-side modules.
2436 long send_message(struct CtdlMessage *msg) {
2444 /* Get a new message number */
2445 newmsgid = get_new_message_number();
2446 snprintf(msgidbuf, sizeof msgidbuf, "%010ld@%s", newmsgid, config.c_fqdn);
2448 /* Generate an ID if we don't have one already */
2449 if (msg->cm_fields['I']==NULL) {
2450 msg->cm_fields['I'] = strdup(msgidbuf);
2453 /* If the message is big, set its body aside for storage elsewhere */
2454 if (msg->cm_fields['M'] != NULL) {
2455 if (strlen(msg->cm_fields['M']) > BIGMSG) {
2457 holdM = msg->cm_fields['M'];
2458 msg->cm_fields['M'] = NULL;
2462 /* Serialize our data structure for storage in the database */
2463 serialize_message(&smr, msg);
2466 msg->cm_fields['M'] = holdM;
2470 cprintf("%d Unable to serialize message\n",
2471 ERROR + INTERNAL_ERROR);
2475 /* Write our little bundle of joy into the message base */
2476 if (cdb_store(CDB_MSGMAIN, &newmsgid, (int)sizeof(long),
2477 smr.ser, smr.len) < 0) {
2478 CtdlLogPrintf(CTDL_ERR, "Can't store message\n");
2482 cdb_store(CDB_BIGMSGS,
2492 /* Free the memory we used for the serialized message */
2495 /* Return the *local* message ID to the caller
2496 * (even if we're storing an incoming network message)
2504 * Serialize a struct CtdlMessage into the format used on disk and network.
2506 * This function loads up a "struct ser_ret" (defined in server.h) which
2507 * contains the length of the serialized message and a pointer to the
2508 * serialized message in memory. THE LATTER MUST BE FREED BY THE CALLER.
2510 void serialize_message(struct ser_ret *ret, /* return values */
2511 struct CtdlMessage *msg) /* unserialized msg */
2513 size_t wlen, fieldlen;
2515 static char *forder = FORDER;
2518 * Check for valid message format
2520 if (is_valid_message(msg) == 0) {
2521 CtdlLogPrintf(CTDL_ERR, "serialize_message() aborting due to invalid message\n");
2528 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL)
2529 ret->len = ret->len +
2530 strlen(msg->cm_fields[(int)forder[i]]) + 2;
2532 ret->ser = malloc(ret->len);
2533 if (ret->ser == NULL) {
2534 CtdlLogPrintf(CTDL_ERR, "serialize_message() malloc(%ld) failed: %s\n",
2535 (long)ret->len, strerror(errno));
2542 ret->ser[1] = msg->cm_anon_type;
2543 ret->ser[2] = msg->cm_format_type;
2546 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL) {
2547 fieldlen = strlen(msg->cm_fields[(int)forder[i]]);
2548 ret->ser[wlen++] = (char)forder[i];
2549 safestrncpy((char *)&ret->ser[wlen], msg->cm_fields[(int)forder[i]], fieldlen+1);
2550 wlen = wlen + fieldlen + 1;
2552 if (ret->len != wlen) CtdlLogPrintf(CTDL_ERR, "ERROR: len=%ld wlen=%ld\n",
2553 (long)ret->len, (long)wlen);
2560 * Serialize a struct CtdlMessage into the format used on disk and network.
2562 * This function loads up a "struct ser_ret" (defined in server.h) which
2563 * contains the length of the serialized message and a pointer to the
2564 * serialized message in memory. THE LATTER MUST BE FREED BY THE CALLER.
2566 void dump_message(struct CtdlMessage *msg, /* unserialized msg */
2567 long Siz) /* how many chars ? */
2571 static char *forder = FORDER;
2575 * Check for valid message format
2577 if (is_valid_message(msg) == 0) {
2578 CtdlLogPrintf(CTDL_ERR, "dump_message() aborting due to invalid message\n");
2582 buf = (char*) malloc (Siz + 1);
2586 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL) {
2587 snprintf (buf, Siz, " msg[%c] = %s ...\n", (char) forder[i],
2588 msg->cm_fields[(int)forder[i]]);
2589 client_write (buf, strlen(buf));
2598 * Check to see if any messages already exist in the current room which
2599 * carry the same Exclusive ID as this one. If any are found, delete them.
2601 void ReplicationChecks(struct CtdlMessage *msg) {
2602 long old_msgnum = (-1L);
2604 if (DoesThisRoomNeedEuidIndexing(&CC->room) == 0) return;
2606 CtdlLogPrintf(CTDL_DEBUG, "Performing replication checks in <%s>\n",
2609 /* No exclusive id? Don't do anything. */
2610 if (msg == NULL) return;
2611 if (msg->cm_fields['E'] == NULL) return;
2612 if (IsEmptyStr(msg->cm_fields['E'])) return;
2613 /*CtdlLogPrintf(CTDL_DEBUG, "Exclusive ID: <%s> for room <%s>\n",
2614 msg->cm_fields['E'], CC->room.QRname);*/
2616 old_msgnum = locate_message_by_euid(msg->cm_fields['E'], &CC->room);
2617 if (old_msgnum > 0L) {
2618 CtdlLogPrintf(CTDL_DEBUG, "ReplicationChecks() replacing message %ld\n", old_msgnum);
2619 CtdlDeleteMessages(CC->room.QRname, &old_msgnum, 1, "");
2626 * Save a message to disk and submit it into the delivery system.
2628 long CtdlSubmitMsg(struct CtdlMessage *msg, /* message to save */
2629 struct recptypes *recps, /* recipients (if mail) */
2630 char *force, /* force a particular room? */
2631 int flags /* should the bessage be exported clean? */
2633 char submit_filename[128];
2634 char generated_timestamp[32];
2635 char hold_rm[ROOMNAMELEN];
2636 char actual_rm[ROOMNAMELEN];
2637 char force_room[ROOMNAMELEN];
2638 char content_type[SIZ]; /* We have to learn this */
2639 char recipient[SIZ];
2642 struct ctdluser userbuf;
2644 struct MetaData smi;
2645 FILE *network_fp = NULL;
2646 static int seqnum = 1;
2647 struct CtdlMessage *imsg = NULL;
2649 size_t instr_alloc = 0;
2651 char *hold_R, *hold_D;
2652 char *collected_addresses = NULL;
2653 struct addresses_to_be_filed *aptr = NULL;
2654 char *saved_rfc822_version = NULL;
2655 int qualified_for_journaling = 0;
2656 struct CitContext *CCC = CC; /* CachedCitContext - performance boost */
2657 char bounce_to[1024] = "";
2661 CtdlLogPrintf(CTDL_DEBUG, "CtdlSubmitMsg() called\n");
2662 if (is_valid_message(msg) == 0) return(-1); /* self check */
2664 /* If this message has no timestamp, we take the liberty of
2665 * giving it one, right now.
2667 if (msg->cm_fields['T'] == NULL) {
2668 snprintf(generated_timestamp, sizeof generated_timestamp, "%ld", (long)time(NULL));
2669 msg->cm_fields['T'] = strdup(generated_timestamp);
2672 /* If this message has no path, we generate one.
2674 if (msg->cm_fields['P'] == NULL) {
2675 if (msg->cm_fields['A'] != NULL) {
2676 msg->cm_fields['P'] = strdup(msg->cm_fields['A']);
2677 for (a=0; !IsEmptyStr(&msg->cm_fields['P'][a]); ++a) {
2678 if (isspace(msg->cm_fields['P'][a])) {
2679 msg->cm_fields['P'][a] = ' ';
2684 msg->cm_fields['P'] = strdup("unknown");
2688 if (force == NULL) {
2689 strcpy(force_room, "");
2692 strcpy(force_room, force);
2695 /* Learn about what's inside, because it's what's inside that counts */
2696 if (msg->cm_fields['M'] == NULL) {
2697 CtdlLogPrintf(CTDL_ERR, "ERROR: attempt to save message with NULL body\n");
2701 switch (msg->cm_format_type) {
2703 strcpy(content_type, "text/x-citadel-variformat");
2706 strcpy(content_type, "text/plain");
2709 strcpy(content_type, "text/plain");
2710 mptr = bmstrcasestr(msg->cm_fields['M'], "Content-type:");
2713 safestrncpy(content_type, &mptr[13], sizeof content_type);
2714 striplt(content_type);
2715 aptr = content_type;
2716 while (!IsEmptyStr(aptr)) {
2728 /* Goto the correct room */
2729 CtdlLogPrintf(CTDL_DEBUG, "Selected room %s\n", (recps) ? CCC->room.QRname : SENTITEMS);
2730 strcpy(hold_rm, CCC->room.QRname);
2731 strcpy(actual_rm, CCC->room.QRname);
2732 if (recps != NULL) {
2733 strcpy(actual_rm, SENTITEMS);
2736 /* If the user is a twit, move to the twit room for posting */
2738 if (CCC->user.axlevel == 2) {
2739 strcpy(hold_rm, actual_rm);
2740 strcpy(actual_rm, config.c_twitroom);
2741 CtdlLogPrintf(CTDL_DEBUG, "Diverting to twit room\n");
2745 /* ...or if this message is destined for Aide> then go there. */
2746 if (!IsEmptyStr(force_room)) {
2747 strcpy(actual_rm, force_room);
2750 CtdlLogPrintf(CTDL_DEBUG, "Final selection: %s\n", actual_rm);
2751 if (strcasecmp(actual_rm, CCC->room.QRname)) {
2752 /* getroom(&CCC->room, actual_rm); */
2753 usergoto(actual_rm, 0, 1, NULL, NULL);
2757 * If this message has no O (room) field, generate one.
2759 if (msg->cm_fields['O'] == NULL) {
2760 msg->cm_fields['O'] = strdup(CCC->room.QRname);
2763 /* Perform "before save" hooks (aborting if any return nonzero) */
2764 CtdlLogPrintf(CTDL_DEBUG, "Performing before-save hooks\n");
2765 if (PerformMessageHooks(msg, EVT_BEFORESAVE) > 0) return(-3);
2768 * If this message has an Exclusive ID, and the room is replication
2769 * checking enabled, then do replication checks.
2771 if (DoesThisRoomNeedEuidIndexing(&CCC->room)) {
2772 ReplicationChecks(msg);
2775 /* Save it to disk */
2776 CtdlLogPrintf(CTDL_DEBUG, "Saving to disk\n");
2777 newmsgid = send_message(msg);
2778 if (newmsgid <= 0L) return(-5);
2780 /* Write a supplemental message info record. This doesn't have to
2781 * be a critical section because nobody else knows about this message
2784 CtdlLogPrintf(CTDL_DEBUG, "Creating MetaData record\n");
2785 memset(&smi, 0, sizeof(struct MetaData));
2786 smi.meta_msgnum = newmsgid;
2787 smi.meta_refcount = 0;
2788 safestrncpy(smi.meta_content_type, content_type,
2789 sizeof smi.meta_content_type);
2792 * Measure how big this message will be when rendered as RFC822.
2793 * We do this for two reasons:
2794 * 1. We need the RFC822 length for the new metadata record, so the
2795 * POP and IMAP services don't have to calculate message lengths
2796 * while the user is waiting (multiplied by potentially hundreds
2797 * or thousands of messages).
2798 * 2. If journaling is enabled, we will need an RFC822 version of the
2799 * message to attach to the journalized copy.
2801 if (CCC->redirect_buffer != NULL) {
2802 CtdlLogPrintf(CTDL_ALERT, "CCC->redirect_buffer is not NULL during message submission!\n");
2805 CCC->redirect_buffer = malloc(SIZ);
2806 CCC->redirect_len = 0;
2807 CCC->redirect_alloc = SIZ;
2808 CtdlOutputPreLoadedMsg(msg, MT_RFC822, HEADERS_ALL, 0, 1, QP_EADDR);
2809 smi.meta_rfc822_length = CCC->redirect_len;
2810 saved_rfc822_version = CCC->redirect_buffer;
2811 CCC->redirect_buffer = NULL;
2812 CCC->redirect_len = 0;
2813 CCC->redirect_alloc = 0;
2817 /* Now figure out where to store the pointers */
2818 CtdlLogPrintf(CTDL_DEBUG, "Storing pointers\n");
2820 /* If this is being done by the networker delivering a private
2821 * message, we want to BYPASS saving the sender's copy (because there
2822 * is no local sender; it would otherwise go to the Trashcan).
2824 if ((!CCC->internal_pgm) || (recps == NULL)) {
2825 if (CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 1, msg) != 0) {
2826 CtdlLogPrintf(CTDL_ERR, "ERROR saving message pointer!\n");
2827 CtdlSaveMsgPointerInRoom(config.c_aideroom, newmsgid, 0, msg);
2831 /* For internet mail, drop a copy in the outbound queue room */
2832 if ((recps != NULL) && (recps->num_internet > 0)) {
2833 CtdlSaveMsgPointerInRoom(SMTP_SPOOLOUT_ROOM, newmsgid, 0, msg);
2836 /* If other rooms are specified, drop them there too. */
2837 if ((recps != NULL) && (recps->num_room > 0))
2838 for (i=0; i<num_tokens(recps->recp_room, '|'); ++i) {
2839 extract_token(recipient, recps->recp_room, i,
2840 '|', sizeof recipient);
2841 CtdlLogPrintf(CTDL_DEBUG, "Delivering to room <%s>\n", recipient);
2842 CtdlSaveMsgPointerInRoom(recipient, newmsgid, 0, msg);
2845 /* Bump this user's messages posted counter. */
2846 CtdlLogPrintf(CTDL_DEBUG, "Updating user\n");
2847 lgetuser(&CCC->user, CCC->curr_user);
2848 CCC->user.posted = CCC->user.posted + 1;
2849 lputuser(&CCC->user);
2851 /* Decide where bounces need to be delivered */
2852 if ((recps != NULL) && (recps->bounce_to != NULL)) {
2853 safestrncpy(bounce_to, recps->bounce_to, sizeof bounce_to);
2855 else if (CCC->logged_in) {
2856 snprintf(bounce_to, sizeof bounce_to, "%s@%s", CCC->user.fullname, config.c_nodename);
2859 snprintf(bounce_to, sizeof bounce_to, "%s@%s", msg->cm_fields['A'], msg->cm_fields['N']);
2862 /* If this is private, local mail, make a copy in the
2863 * recipient's mailbox and bump the reference count.
2865 if ((recps != NULL) && (recps->num_local > 0))
2866 for (i=0; i<num_tokens(recps->recp_local, '|'); ++i) {
2867 extract_token(recipient, recps->recp_local, i,
2868 '|', sizeof recipient);
2869 CtdlLogPrintf(CTDL_DEBUG, "Delivering private local mail to <%s>\n",
2871 if (getuser(&userbuf, recipient) == 0) {
2872 // Add a flag so the Funambol module knows its mail
2873 msg->cm_fields['W'] = strdup(recipient);
2874 MailboxName(actual_rm, sizeof actual_rm, &userbuf, MAILROOM);
2875 CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 0, msg);
2876 BumpNewMailCounter(userbuf.usernum);
2877 if (!IsEmptyStr(config.c_funambol_host) || !IsEmptyStr(config.c_pager_program)) {
2878 /* Generate a instruction message for the Funambol notification
2879 * server, in the same style as the SMTP queue
2882 instr = malloc(instr_alloc);
2883 snprintf(instr, instr_alloc,
2884 "Content-type: %s\n\nmsgid|%ld\nsubmitted|%ld\n"
2886 SPOOLMIME, newmsgid, (long)time(NULL),
2890 imsg = malloc(sizeof(struct CtdlMessage));
2891 memset(imsg, 0, sizeof(struct CtdlMessage));
2892 imsg->cm_magic = CTDLMESSAGE_MAGIC;
2893 imsg->cm_anon_type = MES_NORMAL;
2894 imsg->cm_format_type = FMT_RFC822;
2895 imsg->cm_fields['A'] = strdup("Citadel");
2896 imsg->cm_fields['J'] = strdup("do not journal");
2897 imsg->cm_fields['M'] = instr; /* imsg owns this memory now */
2898 imsg->cm_fields['W'] = strdup(recipient);
2899 CtdlSubmitMsg(imsg, NULL, FNBL_QUEUE_ROOM, 0);
2900 CtdlFreeMessage(imsg);
2904 CtdlLogPrintf(CTDL_DEBUG, "No user <%s>\n", recipient);
2905 CtdlSaveMsgPointerInRoom(config.c_aideroom,
2910 /* Perform "after save" hooks */
2911 CtdlLogPrintf(CTDL_DEBUG, "Performing after-save hooks\n");
2912 PerformMessageHooks(msg, EVT_AFTERSAVE);
2914 /* For IGnet mail, we have to save a new copy into the spooler for
2915 * each recipient, with the R and D fields set to the recipient and
2916 * destination-node. This has two ugly side effects: all other
2917 * recipients end up being unlisted in this recipient's copy of the
2918 * message, and it has to deliver multiple messages to the same
2919 * node. We'll revisit this again in a year or so when everyone has
2920 * a network spool receiver that can handle the new style messages.
2922 if ((recps != NULL) && (recps->num_ignet > 0))
2923 for (i=0; i<num_tokens(recps->recp_ignet, '|'); ++i) {
2924 extract_token(recipient, recps->recp_ignet, i,
2925 '|', sizeof recipient);
2927 hold_R = msg->cm_fields['R'];
2928 hold_D = msg->cm_fields['D'];
2929 msg->cm_fields['R'] = malloc(SIZ);
2930 msg->cm_fields['D'] = malloc(128);
2931 extract_token(msg->cm_fields['R'], recipient, 0, '@', SIZ);
2932 extract_token(msg->cm_fields['D'], recipient, 1, '@', 128);
2934 serialize_message(&smr, msg);
2936 snprintf(submit_filename, sizeof submit_filename,
2937 "%s/netmail.%04lx.%04x.%04x",
2939 (long) getpid(), CCC->cs_pid, ++seqnum);
2940 network_fp = fopen(submit_filename, "wb+");
2941 if (network_fp != NULL) {
2942 rv = fwrite(smr.ser, smr.len, 1, network_fp);
2948 free(msg->cm_fields['R']);
2949 free(msg->cm_fields['D']);
2950 msg->cm_fields['R'] = hold_R;
2951 msg->cm_fields['D'] = hold_D;
2954 /* Go back to the room we started from */
2955 CtdlLogPrintf(CTDL_DEBUG, "Returning to original room %s\n", hold_rm);
2956 if (strcasecmp(hold_rm, CCC->room.QRname))
2957 usergoto(hold_rm, 0, 1, NULL, NULL);
2959 /* For internet mail, generate delivery instructions.
2960 * Yes, this is recursive. Deal with it. Infinite recursion does
2961 * not happen because the delivery instructions message does not
2962 * contain a recipient.
2964 if ((recps != NULL) && (recps->num_internet > 0)) {
2965 CtdlLogPrintf(CTDL_DEBUG, "Generating delivery instructions\n");
2967 instr = malloc(instr_alloc);
2968 snprintf(instr, instr_alloc,
2969 "Content-type: %s\n\nmsgid|%ld\nsubmitted|%ld\n"
2971 SPOOLMIME, newmsgid, (long)time(NULL),
2975 if (recps->envelope_from != NULL) {
2976 tmp = strlen(instr);
2977 snprintf(&instr[tmp], instr_alloc-tmp, "envelope_from|%s\n", recps->envelope_from);
2980 for (i=0; i<num_tokens(recps->recp_internet, '|'); ++i) {
2981 tmp = strlen(instr);
2982 extract_token(recipient, recps->recp_internet, i, '|', sizeof recipient);
2983 if ((tmp + strlen(recipient) + 32) > instr_alloc) {
2984 instr_alloc = instr_alloc * 2;
2985 instr = realloc(instr, instr_alloc);
2987 snprintf(&instr[tmp], instr_alloc - tmp, "remote|%s|0||\n", recipient);
2990 imsg = malloc(sizeof(struct CtdlMessage));
2991 memset(imsg, 0, sizeof(struct CtdlMessage));
2992 imsg->cm_magic = CTDLMESSAGE_MAGIC;
2993 imsg->cm_anon_type = MES_NORMAL;
2994 imsg->cm_format_type = FMT_RFC822;
2995 imsg->cm_fields['A'] = strdup("Citadel");
2996 imsg->cm_fields['J'] = strdup("do not journal");
2997 imsg->cm_fields['M'] = instr; /* imsg owns this memory now */
2998 CtdlSubmitMsg(imsg, NULL, SMTP_SPOOLOUT_ROOM, QP_EADDR);
2999 CtdlFreeMessage(imsg);
3003 * Any addresses to harvest for someone's address book?
3005 if ( (CCC->logged_in) && (recps != NULL) ) {
3006 collected_addresses = harvest_collected_addresses(msg);
3009 if (collected_addresses != NULL) {
3010 aptr = (struct addresses_to_be_filed *)
3011 malloc(sizeof(struct addresses_to_be_filed));
3012 MailboxName(actual_rm, sizeof actual_rm,
3013 &CCC->user, USERCONTACTSROOM);
3014 aptr->roomname = strdup(actual_rm);
3015 aptr->collected_addresses = collected_addresses;
3016 begin_critical_section(S_ATBF);
3019 end_critical_section(S_ATBF);
3023 * Determine whether this message qualifies for journaling.
3025 if (msg->cm_fields['J'] != NULL) {
3026 qualified_for_journaling = 0;
3029 if (recps == NULL) {
3030 qualified_for_journaling = config.c_journal_pubmsgs;
3032 else if (recps->num_local + recps->num_ignet + recps->num_internet > 0) {
3033 qualified_for_journaling = config.c_journal_email;
3036 qualified_for_journaling = config.c_journal_pubmsgs;
3041 * Do we have to perform journaling? If so, hand off the saved
3042 * RFC822 version will be handed off to the journaler for background
3043 * submit. Otherwise, we have to free the memory ourselves.
3045 if (saved_rfc822_version != NULL) {
3046 if (qualified_for_journaling) {
3047 JournalBackgroundSubmit(msg, saved_rfc822_version, recps);
3050 free(saved_rfc822_version);
3063 * Convenience function for generating small administrative messages.
3065 void quickie_message(const char *from, const char *fromaddr, char *to, char *room, const char *text,
3066 int format_type, const char *subject)
3068 struct CtdlMessage *msg;
3069 struct recptypes *recp = NULL;
3071 msg = malloc(sizeof(struct CtdlMessage));
3072 memset(msg, 0, sizeof(struct CtdlMessage));
3073 msg->cm_magic = CTDLMESSAGE_MAGIC;
3074 msg->cm_anon_type = MES_NORMAL;
3075 msg->cm_format_type = format_type;
3078 msg->cm_fields['A'] = strdup(from);
3080 else if (fromaddr != NULL) {
3081 msg->cm_fields['A'] = strdup(fromaddr);
3082 if (strchr(msg->cm_fields['A'], '@')) {
3083 *strchr(msg->cm_fields['A'], '@') = 0;
3087 msg->cm_fields['A'] = strdup("Citadel");
3090 if (fromaddr != NULL) msg->cm_fields['F'] = strdup(fromaddr);
3091 if (room != NULL) msg->cm_fields['O'] = strdup(room);
3092 msg->cm_fields['N'] = strdup(NODENAME);
3094 msg->cm_fields['R'] = strdup(to);
3095 recp = validate_recipients(to, NULL, 0);
3097 if (subject != NULL) {
3098 msg->cm_fields['U'] = strdup(subject);
3100 msg->cm_fields['M'] = strdup(text);
3102 CtdlSubmitMsg(msg, recp, room, 0);
3103 CtdlFreeMessage(msg);
3104 if (recp != NULL) free_recipients(recp);
3110 * Back end function used by CtdlMakeMessage() and similar functions
3112 char *CtdlReadMessageBody(char *terminator, /* token signalling EOT */
3113 size_t maxlen, /* maximum message length */
3114 char *exist, /* if non-null, append to it;
3115 exist is ALWAYS freed */
3116 int crlf, /* CRLF newlines instead of LF */
3117 int sock /* socket handle or 0 for this session's client socket */
3121 size_t message_len = 0;
3122 size_t buffer_len = 0;
3129 if (exist == NULL) {
3136 message_len = strlen(exist);
3137 buffer_len = message_len + 4096;
3138 m = realloc(exist, buffer_len);
3145 /* Do we need to change leading ".." to "." for SMTP escaping? */
3146 if (!strcmp(terminator, ".")) {
3150 /* flush the input if we have nowhere to store it */
3155 /* read in the lines of message text one by one */
3158 if (sock_getln(sock, buf, (sizeof buf - 3)) < 0) finished = 1;
3161 if (client_getln(buf, (sizeof buf - 3)) < 1) finished = 1;
3163 if (!strcmp(buf, terminator)) finished = 1;
3165 strcat(buf, "\r\n");
3171 /* Unescape SMTP-style input of two dots at the beginning of the line */
3173 if (!strncmp(buf, "..", 2)) {
3174 strcpy(buf, &buf[1]);
3178 if ( (!flushing) && (!finished) ) {
3179 /* Measure the line */
3180 linelen = strlen(buf);
3182 /* augment the buffer if we have to */
3183 if ((message_len + linelen) >= buffer_len) {
3184 ptr = realloc(m, (buffer_len * 2) );
3185 if (ptr == NULL) { /* flush if can't allocate */
3188 buffer_len = (buffer_len * 2);
3190 CtdlLogPrintf(CTDL_DEBUG, "buffer_len is now %ld\n", (long)buffer_len);
3194 /* Add the new line to the buffer. NOTE: this loop must avoid
3195 * using functions like strcat() and strlen() because they
3196 * traverse the entire buffer upon every call, and doing that
3197 * for a multi-megabyte message slows it down beyond usability.
3199 strcpy(&m[message_len], buf);
3200 message_len += linelen;
3203 /* if we've hit the max msg length, flush the rest */
3204 if (message_len >= maxlen) flushing = 1;
3206 } while (!finished);
3214 * Build a binary message to be saved on disk.
3215 * (NOTE: if you supply 'preformatted_text', the buffer you give it
3216 * will become part of the message. This means you are no longer
3217 * responsible for managing that memory -- it will be freed along with
3218 * the rest of the fields when CtdlFreeMessage() is called.)
3221 struct CtdlMessage *CtdlMakeMessage(
3222 struct ctdluser *author, /* author's user structure */
3223 char *recipient, /* NULL if it's not mail */
3224 char *recp_cc, /* NULL if it's not mail */
3225 char *room, /* room where it's going */
3226 int type, /* see MES_ types in header file */
3227 int format_type, /* variformat, plain text, MIME... */
3228 char *fake_name, /* who we're masquerading as */
3229 char *my_email, /* which of my email addresses to use (empty is ok) */
3230 char *subject, /* Subject (optional) */
3231 char *supplied_euid, /* ...or NULL if this is irrelevant */
3232 char *preformatted_text, /* ...or NULL to read text from client */
3233 char *references /* Thread references */
3235 char dest_node[256];
3237 struct CtdlMessage *msg;
3239 msg = malloc(sizeof(struct CtdlMessage));
3240 memset(msg, 0, sizeof(struct CtdlMessage));
3241 msg->cm_magic = CTDLMESSAGE_MAGIC;
3242 msg->cm_anon_type = type;
3243 msg->cm_format_type = format_type;
3245 /* Don't confuse the poor folks if it's not routed mail. */
3246 strcpy(dest_node, "");
3248 if (recipient != NULL) striplt(recipient);
3249 if (recp_cc != NULL) striplt(recp_cc);
3251 /* Path or Return-Path */
3252 if (my_email == NULL) my_email = "";
3254 if (!IsEmptyStr(my_email)) {
3255 msg->cm_fields['P'] = strdup(my_email);
3258 snprintf(buf, sizeof buf, "%s", author->fullname);
3259 msg->cm_fields['P'] = strdup(buf);
3261 convert_spaces_to_underscores(msg->cm_fields['P']);
3263 snprintf(buf, sizeof buf, "%ld", (long)time(NULL)); /* timestamp */
3264 msg->cm_fields['T'] = strdup(buf);
3266 if ((fake_name != NULL) && (fake_name[0])) { /* author */
3267 msg->cm_fields['A'] = strdup(fake_name);
3270 msg->cm_fields['A'] = strdup(author->fullname);
3273 if (CC->room.QRflags & QR_MAILBOX) { /* room */
3274 msg->cm_fields['O'] = strdup(&CC->room.QRname[11]);
3277 msg->cm_fields['O'] = strdup(CC->room.QRname);
3280 msg->cm_fields['N'] = strdup(NODENAME); /* nodename */
3281 msg->cm_fields['H'] = strdup(HUMANNODE); /* hnodename */
3283 if ((recipient != NULL) && (recipient[0] != 0)) {
3284 msg->cm_fields['R'] = strdup(recipient);
3286 if ((recp_cc != NULL) && (recp_cc[0] != 0)) {
3287 msg->cm_fields['Y'] = strdup(recp_cc);
3289 if (dest_node[0] != 0) {
3290 msg->cm_fields['D'] = strdup(dest_node);
3293 if (!IsEmptyStr(my_email)) {
3294 msg->cm_fields['F'] = strdup(my_email);
3296 else if ( (author == &CC->user) && (!IsEmptyStr(CC->cs_inet_email)) ) {
3297 msg->cm_fields['F'] = strdup(CC->cs_inet_email);
3300 if (subject != NULL) {
3303 length = strlen(subject);
3309 while ((subject[i] != '\0') &&
3310 (IsAscii = isascii(subject[i]) != 0 ))
3313 msg->cm_fields['U'] = strdup(subject);
3314 else /* ok, we've got utf8 in the string. */
3316 msg->cm_fields['U'] = rfc2047encode(subject, length);
3322 if (supplied_euid != NULL) {
3323 msg->cm_fields['E'] = strdup(supplied_euid);
3326 if (references != NULL) {
3327 if (!IsEmptyStr(references)) {
3328 msg->cm_fields['W'] = strdup(references);
3332 if (preformatted_text != NULL) {
3333 msg->cm_fields['M'] = preformatted_text;
3336 msg->cm_fields['M'] = CtdlReadMessageBody("000", config.c_maxmsglen, NULL, 0, 0);
3344 * Check to see whether we have permission to post a message in the current
3345 * room. Returns a *CITADEL ERROR CODE* and puts a message in errmsgbuf, or
3346 * returns 0 on success.
3348 int CtdlDoIHavePermissionToPostInThisRoom(char *errmsgbuf,
3350 const char* RemoteIdentifier,
3354 if (!(CC->logged_in) &&
3355 (PostPublic == POST_LOGGED_IN)) {
3356 snprintf(errmsgbuf, n, "Not logged in.");
3357 return (ERROR + NOT_LOGGED_IN);
3359 else if (PostPublic == CHECK_EXISTANCE) {
3360 return (0); // We're Evaling whether a recipient exists
3362 else if (!(CC->logged_in)) {
3364 if ((CC->room.QRflags & QR_READONLY)) {
3365 snprintf(errmsgbuf, n, "Not logged in.");
3366 return (ERROR + NOT_LOGGED_IN);
3368 if (CC->room.QRflags2 & QR2_MODERATED) {
3369 snprintf(errmsgbuf, n, "Not logged in Moderation feature not yet implemented!");
3370 return (ERROR + NOT_LOGGED_IN);
3372 if ((PostPublic!=POST_LMTP) &&(CC->room.QRflags2 & QR2_SMTP_PUBLIC) == 0) {
3377 if (RemoteIdentifier == NULL)
3379 snprintf(errmsgbuf, n, "Need sender to permit access.");
3380 return (ERROR + USERNAME_REQUIRED);
3383 assoc_file_name(filename, sizeof filename, &CC->room, ctdl_netcfg_dir);
3384 begin_critical_section(S_NETCONFIGS);
3385 if (!read_spoolcontrol_file(&sc, filename))
3387 end_critical_section(S_NETCONFIGS);
3388 snprintf(errmsgbuf, n,
3389 "This mailing list only accepts posts from subscribers.");
3390 return (ERROR + NO_SUCH_USER);
3392 end_critical_section(S_NETCONFIGS);
3393 found = is_recipient (sc, RemoteIdentifier);
3394 free_spoolcontrol_struct(&sc);
3399 snprintf(errmsgbuf, n,
3400 "This mailing list only accepts posts from subscribers.");
3401 return (ERROR + NO_SUCH_USER);
3408 if ((CC->user.axlevel < 2)
3409 && ((CC->room.QRflags & QR_MAILBOX) == 0)) {
3410 snprintf(errmsgbuf, n, "Need to be validated to enter "
3411 "(except in %s> to sysop)", MAILROOM);
3412 return (ERROR + HIGHER_ACCESS_REQUIRED);
3415 CtdlRoomAccess(&CC->room, &CC->user, &ra, NULL);
3416 if (!(ra & UA_POSTALLOWED)) {
3417 snprintf(errmsgbuf, n, "Higher access is required to post in this room.");
3418 return (ERROR + HIGHER_ACCESS_REQUIRED);
3421 strcpy(errmsgbuf, "Ok");
3427 * Check to see if the specified user has Internet mail permission
3428 * (returns nonzero if permission is granted)
3430 int CtdlCheckInternetMailPermission(struct ctdluser *who) {
3432 /* Do not allow twits to send Internet mail */
3433 if (who->axlevel <= 2) return(0);
3435 /* Globally enabled? */
3436 if (config.c_restrict == 0) return(1);
3438 /* User flagged ok? */
3439 if (who->flags & US_INTERNET) return(2);
3441 /* Aide level access? */
3442 if (who->axlevel >= 6) return(3);
3444 /* No mail for you! */
3450 * Validate recipients, count delivery types and errors, and handle aliasing
3451 * FIXME check for dupes!!!!!
3453 * Returns 0 if all addresses are ok, ret->num_error = -1 if no addresses
3454 * were specified, or the number of addresses found invalid.
3456 * Caller needs to free the result using free_recipients()
3458 struct recptypes *validate_recipients(char *supplied_recipients,
3459 const char *RemoteIdentifier,
3461 struct recptypes *ret;
3462 char *recipients = NULL;
3463 char this_recp[256];
3464 char this_recp_cooked[256];
3470 struct ctdluser tempUS;
3471 struct ctdlroom tempQR;
3472 struct ctdlroom tempQR2;
3478 ret = (struct recptypes *) malloc(sizeof(struct recptypes));
3479 if (ret == NULL) return(NULL);
3481 /* Set all strings to null and numeric values to zero */
3482 memset(ret, 0, sizeof(struct recptypes));
3484 if (supplied_recipients == NULL) {
3485 recipients = strdup("");
3488 recipients = strdup(supplied_recipients);
3491 /* Allocate some memory. Yes, this allocates 500% more memory than we will
3492 * actually need, but it's healthier for the heap than doing lots of tiny
3493 * realloc() calls instead.
3496 ret->errormsg = malloc(strlen(recipients) + 1024);
3497 ret->recp_local = malloc(strlen(recipients) + 1024);
3498 ret->recp_internet = malloc(strlen(recipients) + 1024);
3499 ret->recp_ignet = malloc(strlen(recipients) + 1024);
3500 ret->recp_room = malloc(strlen(recipients) + 1024);
3501 ret->display_recp = malloc(strlen(recipients) + 1024);
3503 ret->errormsg[0] = 0;
3504 ret->recp_local[0] = 0;
3505 ret->recp_internet[0] = 0;
3506 ret->recp_ignet[0] = 0;
3507 ret->recp_room[0] = 0;
3508 ret->display_recp[0] = 0;
3510 ret->recptypes_magic = RECPTYPES_MAGIC;
3512 /* Change all valid separator characters to commas */
3513 for (i=0; !IsEmptyStr(&recipients[i]); ++i) {
3514 if ((recipients[i] == ';') || (recipients[i] == '|')) {
3515 recipients[i] = ',';
3519 /* Now start extracting recipients... */
3521 while (!IsEmptyStr(recipients)) {
3523 for (i=0; i<=strlen(recipients); ++i) {
3524 if (recipients[i] == '\"') in_quotes = 1 - in_quotes;
3525 if ( ( (recipients[i] == ',') && (!in_quotes) ) || (recipients[i] == 0) ) {
3526 safestrncpy(this_recp, recipients, i+1);
3528 if (recipients[i] == ',') {
3529 strcpy(recipients, &recipients[i+1]);
3532 strcpy(recipients, "");
3539 if (IsEmptyStr(this_recp))
3541 CtdlLogPrintf(CTDL_DEBUG, "Evaluating recipient #%d: %s\n", num_recps, this_recp);
3543 mailtype = alias(this_recp);
3544 mailtype = alias(this_recp);
3545 mailtype = alias(this_recp);
3547 for (j=0; !IsEmptyStr(&this_recp[j]); ++j) {
3548 if (this_recp[j]=='_') {
3549 this_recp_cooked[j] = ' ';
3552 this_recp_cooked[j] = this_recp[j];
3555 this_recp_cooked[j] = '\0';
3560 if (!strcasecmp(this_recp, "sysop")) {
3562 strcpy(this_recp, config.c_aideroom);
3563 if (!IsEmptyStr(ret->recp_room)) {
3564 strcat(ret->recp_room, "|");
3566 strcat(ret->recp_room, this_recp);
3568 else if ( (!strncasecmp(this_recp, "room_", 5))
3569 && (!getroom(&tempQR, &this_recp_cooked[5])) ) {
3571 /* Save room so we can restore it later */
3575 /* Check permissions to send mail to this room */
3576 err = CtdlDoIHavePermissionToPostInThisRoom(errmsg,
3588 if (!IsEmptyStr(ret->recp_room)) {
3589 strcat(ret->recp_room, "|");
3591 strcat(ret->recp_room, &this_recp_cooked[5]);
3594 /* Restore room in case something needs it */
3598 else if (getuser(&tempUS, this_recp) == 0) {
3600 strcpy(this_recp, tempUS.fullname);
3601 if (!IsEmptyStr(ret->recp_local)) {
3602 strcat(ret->recp_local, "|");
3604 strcat(ret->recp_local, this_recp);
3606 else if (getuser(&tempUS, this_recp_cooked) == 0) {
3608 strcpy(this_recp, tempUS.fullname);
3609 if (!IsEmptyStr(ret->recp_local)) {
3610 strcat(ret->recp_local, "|");
3612 strcat(ret->recp_local, this_recp);
3620 /* Yes, you're reading this correctly: if the target
3621 * domain points back to the local system or an attached
3622 * Citadel directory, the address is invalid. That's
3623 * because if the address were valid, we would have
3624 * already translated it to a local address by now.
3626 if (IsDirectory(this_recp, 0)) {
3631 ++ret->num_internet;
3632 if (!IsEmptyStr(ret->recp_internet)) {
3633 strcat(ret->recp_internet, "|");
3635 strcat(ret->recp_internet, this_recp);
3640 if (!IsEmptyStr(ret->recp_ignet)) {
3641 strcat(ret->recp_ignet, "|");
3643 strcat(ret->recp_ignet, this_recp);
3651 if (IsEmptyStr(errmsg)) {
3652 snprintf(append, sizeof append, "Invalid recipient: %s", this_recp);
3655 snprintf(append, sizeof append, "%s", errmsg);
3657 if ( (strlen(ret->errormsg) + strlen(append) + 3) < SIZ) {
3658 if (!IsEmptyStr(ret->errormsg)) {
3659 strcat(ret->errormsg, "; ");
3661 strcat(ret->errormsg, append);
3665 if (IsEmptyStr(ret->display_recp)) {
3666 strcpy(append, this_recp);
3669 snprintf(append, sizeof append, ", %s", this_recp);
3671 if ( (strlen(ret->display_recp)+strlen(append)) < SIZ) {
3672 strcat(ret->display_recp, append);
3677 if ((ret->num_local + ret->num_internet + ret->num_ignet +
3678 ret->num_room + ret->num_error) == 0) {
3679 ret->num_error = (-1);
3680 strcpy(ret->errormsg, "No recipients specified.");
3683 CtdlLogPrintf(CTDL_DEBUG, "validate_recipients()\n");
3684 CtdlLogPrintf(CTDL_DEBUG, " local: %d <%s>\n", ret->num_local, ret->recp_local);
3685 CtdlLogPrintf(CTDL_DEBUG, " room: %d <%s>\n", ret->num_room, ret->recp_room);
3686 CtdlLogPrintf(CTDL_DEBUG, " inet: %d <%s>\n", ret->num_internet, ret->recp_internet);
3687 CtdlLogPrintf(CTDL_DEBUG, " ignet: %d <%s>\n", ret->num_ignet, ret->recp_ignet);
3688 CtdlLogPrintf(CTDL_DEBUG, " error: %d <%s>\n", ret->num_error, ret->errormsg);
3696 * Destructor for struct recptypes
3698 void free_recipients(struct recptypes *valid) {
3700 if (valid == NULL) {
3704 if (valid->recptypes_magic != RECPTYPES_MAGIC) {
3705 CtdlLogPrintf(CTDL_EMERG, "Attempt to call free_recipients() on some other data type!\n");
3709 if (valid->errormsg != NULL) free(valid->errormsg);
3710 if (valid->recp_local != NULL) free(valid->recp_local);
3711 if (valid->recp_internet != NULL) free(valid->recp_internet);
3712 if (valid->recp_ignet != NULL) free(valid->recp_ignet);
3713 if (valid->recp_room != NULL) free(valid->recp_room);
3714 if (valid->display_recp != NULL) free(valid->display_recp);
3715 if (valid->bounce_to != NULL) free(valid->bounce_to);
3716 if (valid->envelope_from != NULL) free(valid->envelope_from);
3723 * message entry - mode 0 (normal)
3725 void cmd_ent0(char *entargs)
3731 char supplied_euid[128];
3733 int format_type = 0;
3734 char newusername[256];
3735 char newuseremail[256];
3736 struct CtdlMessage *msg;
3740 struct recptypes *valid = NULL;
3741 struct recptypes *valid_to = NULL;
3742 struct recptypes *valid_cc = NULL;
3743 struct recptypes *valid_bcc = NULL;
3745 int subject_required = 0;
3750 int newuseremail_ok = 0;
3751 char references[SIZ];
3756 post = extract_int(entargs, 0);
3757 extract_token(recp, entargs, 1, '|', sizeof recp);
3758 anon_flag = extract_int(entargs, 2);
3759 format_type = extract_int(entargs, 3);
3760 extract_token(subject, entargs, 4, '|', sizeof subject);
3761 extract_token(newusername, entargs, 5, '|', sizeof newusername);
3762 do_confirm = extract_int(entargs, 6);
3763 extract_token(cc, entargs, 7, '|', sizeof cc);
3764 extract_token(bcc, entargs, 8, '|', sizeof bcc);
3765 switch(CC->room.QRdefaultview) {
3768 extract_token(supplied_euid, entargs, 9, '|', sizeof supplied_euid);
3771 supplied_euid[0] = 0;
3774 extract_token(newuseremail, entargs, 10, '|', sizeof newuseremail);
3775 extract_token(references, entargs, 11, '|', sizeof references);
3776 for (ptr=references; *ptr != 0; ++ptr) {
3777 if (*ptr == '!') *ptr = '|';
3780 /* first check to make sure the request is valid. */
3782 err = CtdlDoIHavePermissionToPostInThisRoom(errmsg, sizeof errmsg, NULL, POST_LOGGED_IN);
3785 cprintf("%d %s\n", err, errmsg);
3789 /* Check some other permission type things. */
3791 if (IsEmptyStr(newusername)) {
3792 strcpy(newusername, CC->user.fullname);
3794 if ( (CC->user.axlevel < 6)
3795 && (strcasecmp(newusername, CC->user.fullname))
3796 && (strcasecmp(newusername, CC->cs_inet_fn))
3798 cprintf("%d You don't have permission to author messages as '%s'.\n",
3799 ERROR + HIGHER_ACCESS_REQUIRED,
3806 if (IsEmptyStr(newuseremail)) {
3807 newuseremail_ok = 1;
3810 if (!IsEmptyStr(newuseremail)) {
3811 if (!strcasecmp(newuseremail, CC->cs_inet_email)) {
3812 newuseremail_ok = 1;
3814 else if (!IsEmptyStr(CC->cs_inet_other_emails)) {
3815 j = num_tokens(CC->cs_inet_other_emails, '|');
3816 for (i=0; i<j; ++i) {
3817 extract_token(buf, CC->cs_inet_other_emails, i, '|', sizeof buf);
3818 if (!strcasecmp(newuseremail, buf)) {
3819 newuseremail_ok = 1;
3825 if (!newuseremail_ok) {
3826 cprintf("%d You don't have permission to author messages as '%s'.\n",
3827 ERROR + HIGHER_ACCESS_REQUIRED,
3833 CC->cs_flags |= CS_POSTING;
3835 /* In mailbox rooms we have to behave a little differently --
3836 * make sure the user has specified at least one recipient. Then
3837 * validate the recipient(s). We do this for the Mail> room, as
3838 * well as any room which has the "Mailbox" view set.
3841 if ( ( (CC->room.QRflags & QR_MAILBOX) && (!strcasecmp(&CC->room.QRname[11], MAILROOM)) )
3842 || ( (CC->room.QRflags & QR_MAILBOX) && (CC->curr_view == VIEW_MAILBOX) )
3844 if (CC->user.axlevel < 2) {
3845 strcpy(recp, "sysop");
3850 valid_to = validate_recipients(recp, NULL, 0);
3851 if (valid_to->num_error > 0) {
3852 cprintf("%d %s\n", ERROR + NO_SUCH_USER, valid_to->errormsg);
3853 free_recipients(valid_to);
3857 valid_cc = validate_recipients(cc, NULL, 0);
3858 if (valid_cc->num_error > 0) {
3859 cprintf("%d %s\n", ERROR + NO_SUCH_USER, valid_cc->errormsg);
3860 free_recipients(valid_to);
3861 free_recipients(valid_cc);
3865 valid_bcc = validate_recipients(bcc, NULL, 0);
3866 if (valid_bcc->num_error > 0) {
3867 cprintf("%d %s\n", ERROR + NO_SUCH_USER, valid_bcc->errormsg);
3868 free_recipients(valid_to);
3869 free_recipients(valid_cc);
3870 free_recipients(valid_bcc);
3874 /* Recipient required, but none were specified */
3875 if ( (valid_to->num_error < 0) && (valid_cc->num_error < 0) && (valid_bcc->num_error < 0) ) {
3876 free_recipients(valid_to);
3877 free_recipients(valid_cc);
3878 free_recipients(valid_bcc);
3879 cprintf("%d At least one recipient is required.\n", ERROR + NO_SUCH_USER);
3883 if (valid_to->num_internet + valid_cc->num_internet + valid_bcc->num_internet > 0) {
3884 if (CtdlCheckInternetMailPermission(&CC->user)==0) {
3885 cprintf("%d You do not have permission "
3886 "to send Internet mail.\n",
3887 ERROR + HIGHER_ACCESS_REQUIRED);
3888 free_recipients(valid_to);
3889 free_recipients(valid_cc);
3890 free_recipients(valid_bcc);
3895 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)
3896 && (CC->user.axlevel < 4) ) {
3897 cprintf("%d Higher access required for network mail.\n",
3898 ERROR + HIGHER_ACCESS_REQUIRED);
3899 free_recipients(valid_to);
3900 free_recipients(valid_cc);
3901 free_recipients(valid_bcc);
3905 if ((RESTRICT_INTERNET == 1)
3906 && (valid_to->num_internet + valid_cc->num_internet + valid_bcc->num_internet > 0)
3907 && ((CC->user.flags & US_INTERNET) == 0)
3908 && (!CC->internal_pgm)) {
3909 cprintf("%d You don't have access to Internet mail.\n",
3910 ERROR + HIGHER_ACCESS_REQUIRED);
3911 free_recipients(valid_to);
3912 free_recipients(valid_cc);
3913 free_recipients(valid_bcc);
3919 /* Is this a room which has anonymous-only or anonymous-option? */
3920 anonymous = MES_NORMAL;
3921 if (CC->room.QRflags & QR_ANONONLY) {
3922 anonymous = MES_ANONONLY;
3924 if (CC->room.QRflags & QR_ANONOPT) {
3925 if (anon_flag == 1) { /* only if the user requested it */
3926 anonymous = MES_ANONOPT;
3930 if ((CC->room.QRflags & QR_MAILBOX) == 0) {
3934 /* Recommend to the client that the use of a message subject is
3935 * strongly recommended in this room, if either the SUBJECTREQ flag
3936 * is set, or if there is one or more Internet email recipients.
3938 if (CC->room.QRflags2 & QR2_SUBJECTREQ) subject_required = 1;
3939 if ((valid_to) && (valid_to->num_internet > 0)) subject_required = 1;
3940 if ((valid_cc) && (valid_cc->num_internet > 0)) subject_required = 1;
3941 if ((valid_bcc) && (valid_bcc->num_internet > 0)) subject_required = 1;
3943 /* If we're only checking the validity of the request, return
3944 * success without creating the message.
3947 cprintf("%d %s|%d\n", CIT_OK,
3948 ((valid_to != NULL) ? valid_to->display_recp : ""),
3950 free_recipients(valid_to);
3951 free_recipients(valid_cc);
3952 free_recipients(valid_bcc);
3956 /* We don't need these anymore because we'll do it differently below */
3957 free_recipients(valid_to);
3958 free_recipients(valid_cc);
3959 free_recipients(valid_bcc);
3961 /* Read in the message from the client. */
3963 cprintf("%d send message\n", START_CHAT_MODE);
3965 cprintf("%d send message\n", SEND_LISTING);
3968 msg = CtdlMakeMessage(&CC->user, recp, cc,
3969 CC->room.QRname, anonymous, format_type,
3970 newusername, newuseremail, subject,
3971 ((!IsEmptyStr(supplied_euid)) ? supplied_euid : NULL),
3974 /* Put together one big recipients struct containing to/cc/bcc all in
3975 * one. This is for the envelope.
3977 char *all_recps = malloc(SIZ * 3);
3978 strcpy(all_recps, recp);
3979 if (!IsEmptyStr(cc)) {
3980 if (!IsEmptyStr(all_recps)) {
3981 strcat(all_recps, ",");
3983 strcat(all_recps, cc);
3985 if (!IsEmptyStr(bcc)) {
3986 if (!IsEmptyStr(all_recps)) {
3987 strcat(all_recps, ",");
3989 strcat(all_recps, bcc);
3991 if (!IsEmptyStr(all_recps)) {
3992 valid = validate_recipients(all_recps, NULL, 0);
4000 msgnum = CtdlSubmitMsg(msg, valid, "", QP_EADDR);
4003 cprintf("%ld\n", msgnum);
4005 cprintf("Message accepted.\n");
4008 cprintf("Internal error.\n");
4010 if (msg->cm_fields['E'] != NULL) {
4011 cprintf("%s\n", msg->cm_fields['E']);
4018 CtdlFreeMessage(msg);
4020 if (valid != NULL) {
4021 free_recipients(valid);
4029 * API function to delete messages which match a set of criteria
4030 * (returns the actual number of messages deleted)
4032 int CtdlDeleteMessages(char *room_name, /* which room */
4033 long *dmsgnums, /* array of msg numbers to be deleted */
4034 int num_dmsgnums, /* number of msgs to be deleted, or 0 for "any" */
4035 char *content_type /* or "" for any. regular expressions expected. */
4038 struct ctdlroom qrbuf;
4039 struct cdbdata *cdbfr;
4040 long *msglist = NULL;
4041 long *dellist = NULL;
4044 int num_deleted = 0;
4046 struct MetaData smi;
4049 int need_to_free_re = 0;
4051 if (content_type) if (!IsEmptyStr(content_type)) {
4052 regcomp(&re, content_type, 0);
4053 need_to_free_re = 1;
4055 CtdlLogPrintf(CTDL_DEBUG, "CtdlDeleteMessages(%s, %d msgs, %s)\n",
4056 room_name, num_dmsgnums, content_type);
4058 /* get room record, obtaining a lock... */
4059 if (lgetroom(&qrbuf, room_name) != 0) {
4060 CtdlLogPrintf(CTDL_ERR, "CtdlDeleteMessages(): Room <%s> not found\n",
4062 if (need_to_free_re) regfree(&re);
4063 return (0); /* room not found */
4065 cdbfr = cdb_fetch(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long));
4067 if (cdbfr != NULL) {
4068 dellist = malloc(cdbfr->len);
4069 msglist = (long *) cdbfr->ptr;
4070 cdbfr->ptr = NULL; /* CtdlDeleteMessages() now owns this memory */
4071 num_msgs = cdbfr->len / sizeof(long);
4075 for (i = 0; i < num_msgs; ++i) {
4078 /* Set/clear a bit for each criterion */
4080 /* 0 messages in the list or a null list means that we are
4081 * interested in deleting any messages which meet the other criteria.
4083 if ((num_dmsgnums == 0) || (dmsgnums == NULL)) {
4084 delete_this |= 0x01;
4087 for (j=0; j<num_dmsgnums; ++j) {
4088 if (msglist[i] == dmsgnums[j]) {
4089 delete_this |= 0x01;
4094 if (IsEmptyStr(content_type)) {
4095 delete_this |= 0x02;
4097 GetMetaData(&smi, msglist[i]);
4098 if (regexec(&re, smi.meta_content_type, 1, &pm, 0) == 0) {
4099 delete_this |= 0x02;
4103 /* Delete message only if all bits are set */
4104 if (delete_this == 0x03) {
4105 dellist[num_deleted++] = msglist[i];
4110 num_msgs = sort_msglist(msglist, num_msgs);
4111 cdb_store(CDB_MSGLISTS, &qrbuf.QRnumber, (int)sizeof(long),
4112 msglist, (int)(num_msgs * sizeof(long)));
4114 qrbuf.QRhighest = msglist[num_msgs - 1];
4118 /* Go through the messages we pulled out of the index, and decrement
4119 * their reference counts by 1. If this is the only room the message
4120 * was in, the reference count will reach zero and the message will
4121 * automatically be deleted from the database. We do this in a
4122 * separate pass because there might be plug-in hooks getting called,
4123 * and we don't want that happening during an S_ROOMS critical
4126 if (num_deleted) for (i=0; i<num_deleted; ++i) {
4127 PerformDeleteHooks(qrbuf.QRname, dellist[i]);
4128 AdjRefCount(dellist[i], -1);
4131 /* Now free the memory we used, and go away. */
4132 if (msglist != NULL) free(msglist);
4133 if (dellist != NULL) free(dellist);
4134 CtdlLogPrintf(CTDL_DEBUG, "%d message(s) deleted.\n", num_deleted);
4135 if (need_to_free_re) regfree(&re);
4136 return (num_deleted);
4142 * Check whether the current user has permission to delete messages from
4143 * the current room (returns 1 for yes, 0 for no)
4145 int CtdlDoIHavePermissionToDeleteMessagesFromThisRoom(void) {
4147 CtdlRoomAccess(&CC->room, &CC->user, &ra, NULL);
4148 if (ra & UA_DELETEALLOWED) return(1);
4156 * Delete message from current room
4158 void cmd_dele(char *args)
4167 extract_token(msgset, args, 0, '|', sizeof msgset);
4168 num_msgs = num_tokens(msgset, ',');
4170 cprintf("%d Nothing to do.\n", CIT_OK);
4174 if (CtdlDoIHavePermissionToDeleteMessagesFromThisRoom() == 0) {
4175 cprintf("%d Higher access required.\n",
4176 ERROR + HIGHER_ACCESS_REQUIRED);
4181 * Build our message set to be moved/copied
4183 msgs = malloc(num_msgs * sizeof(long));
4184 for (i=0; i<num_msgs; ++i) {
4185 extract_token(msgtok, msgset, i, ',', sizeof msgtok);
4186 msgs[i] = atol(msgtok);
4189 num_deleted = CtdlDeleteMessages(CC->room.QRname, msgs, num_msgs, "");
4193 cprintf("%d %d message%s deleted.\n", CIT_OK,
4194 num_deleted, ((num_deleted != 1) ? "s" : ""));
4196 cprintf("%d Message not found.\n", ERROR + MESSAGE_NOT_FOUND);
4204 * move or copy a message to another room
4206 void cmd_move(char *args)
4213 char targ[ROOMNAMELEN];
4214 struct ctdlroom qtemp;
4221 extract_token(msgset, args, 0, '|', sizeof msgset);
4222 num_msgs = num_tokens(msgset, ',');
4224 cprintf("%d Nothing to do.\n", CIT_OK);
4228 extract_token(targ, args, 1, '|', sizeof targ);
4229 convert_room_name_macros(targ, sizeof targ);
4230 targ[ROOMNAMELEN - 1] = 0;
4231 is_copy = extract_int(args, 2);
4233 if (getroom(&qtemp, targ) != 0) {
4234 cprintf("%d '%s' does not exist.\n", ERROR + ROOM_NOT_FOUND, targ);
4238 if (!strcasecmp(qtemp.QRname, CC->room.QRname)) {
4239 cprintf("%d Source and target rooms are the same.\n", ERROR + ALREADY_EXISTS);
4243 getuser(&CC->user, CC->curr_user);
4244 CtdlRoomAccess(&qtemp, &CC->user, &ra, NULL);
4246 /* Check for permission to perform this operation.
4247 * Remember: "CC->room" is source, "qtemp" is target.
4251 /* Aides can move/copy */
4252 if (CC->user.axlevel >= 6) permit = 1;
4254 /* Room aides can move/copy */
4255 if (CC->user.usernum == CC->room.QRroomaide) permit = 1;
4257 /* Permit move/copy from personal rooms */
4258 if ((CC->room.QRflags & QR_MAILBOX)
4259 && (qtemp.QRflags & QR_MAILBOX)) permit = 1;
4261 /* Permit only copy from public to personal room */
4263 && (!(CC->room.QRflags & QR_MAILBOX))
4264 && (qtemp.QRflags & QR_MAILBOX)) permit = 1;
4266 /* Permit message removal from collaborative delete rooms */
4267 if (CC->room.QRflags2 & QR2_COLLABDEL) permit = 1;
4269 /* Users allowed to post into the target room may move into it too. */
4270 if ((CC->room.QRflags & QR_MAILBOX) &&
4271 (qtemp.QRflags & UA_POSTALLOWED)) permit = 1;
4273 /* User must have access to target room */
4274 if (!(ra & UA_KNOWN)) permit = 0;
4277 cprintf("%d Higher access required.\n",
4278 ERROR + HIGHER_ACCESS_REQUIRED);
4283 * Build our message set to be moved/copied
4285 msgs = malloc(num_msgs * sizeof(long));
4286 for (i=0; i<num_msgs; ++i) {
4287 extract_token(msgtok, msgset, i, ',', sizeof msgtok);
4288 msgs[i] = atol(msgtok);
4294 err = CtdlSaveMsgPointersInRoom(targ, msgs, num_msgs, 1, NULL);
4296 cprintf("%d Cannot store message(s) in %s: error %d\n",
4302 /* Now delete the message from the source room,
4303 * if this is a 'move' rather than a 'copy' operation.
4306 CtdlDeleteMessages(CC->room.QRname, msgs, num_msgs, "");
4310 cprintf("%d Message(s) %s.\n", CIT_OK, (is_copy ? "copied" : "moved") );
4316 * GetMetaData() - Get the supplementary record for a message
4318 void GetMetaData(struct MetaData *smibuf, long msgnum)
4321 struct cdbdata *cdbsmi;
4324 memset(smibuf, 0, sizeof(struct MetaData));
4325 smibuf->meta_msgnum = msgnum;
4326 smibuf->meta_refcount = 1; /* Default reference count is 1 */
4328 /* Use the negative of the message number for its supp record index */
4329 TheIndex = (0L - msgnum);
4331 cdbsmi = cdb_fetch(CDB_MSGMAIN, &TheIndex, sizeof(long));
4332 if (cdbsmi == NULL) {
4333 return; /* record not found; go with defaults */
4335 memcpy(smibuf, cdbsmi->ptr,
4336 ((cdbsmi->len > sizeof(struct MetaData)) ?
4337 sizeof(struct MetaData) : cdbsmi->len));
4344 * PutMetaData() - (re)write supplementary record for a message
4346 void PutMetaData(struct MetaData *smibuf)
4350 /* Use the negative of the message number for the metadata db index */
4351 TheIndex = (0L - smibuf->meta_msgnum);
4353 cdb_store(CDB_MSGMAIN,
4354 &TheIndex, (int)sizeof(long),
4355 smibuf, (int)sizeof(struct MetaData));
4360 * AdjRefCount - submit an adjustment to the reference count for a message.
4361 * (These are just queued -- we actually process them later.)
4363 void AdjRefCount(long msgnum, int incr)
4365 struct arcq new_arcq;
4368 begin_critical_section(S_SUPPMSGMAIN);
4369 if (arcfp == NULL) {
4370 arcfp = fopen(file_arcq, "ab+");
4372 end_critical_section(S_SUPPMSGMAIN);
4374 /* msgnum < 0 means that we're trying to close the file */
4376 CtdlLogPrintf(CTDL_DEBUG, "Closing the AdjRefCount queue file\n");
4377 begin_critical_section(S_SUPPMSGMAIN);
4378 if (arcfp != NULL) {
4382 end_critical_section(S_SUPPMSGMAIN);
4387 * If we can't open the queue, perform the operation synchronously.
4389 if (arcfp == NULL) {
4390 TDAP_AdjRefCount(msgnum, incr);
4394 new_arcq.arcq_msgnum = msgnum;
4395 new_arcq.arcq_delta = incr;
4396 rv = fwrite(&new_arcq, sizeof(struct arcq), 1, arcfp);
4404 * TDAP_ProcessAdjRefCountQueue()
4406 * Process the queue of message count adjustments that was created by calls
4407 * to AdjRefCount() ... by reading the queue and calling TDAP_AdjRefCount()
4408 * for each one. This should be an "off hours" operation.
4410 int TDAP_ProcessAdjRefCountQueue(void)
4412 char file_arcq_temp[PATH_MAX];
4415 struct arcq arcq_rec;
4416 int num_records_processed = 0;
4418 snprintf(file_arcq_temp, sizeof file_arcq_temp, "%s.%04x", file_arcq, rand());
4420 begin_critical_section(S_SUPPMSGMAIN);
4421 if (arcfp != NULL) {
4426 r = link(file_arcq, file_arcq_temp);
4428 CtdlLogPrintf(CTDL_CRIT, "%s: %s\n", file_arcq_temp, strerror(errno));
4429 end_critical_section(S_SUPPMSGMAIN);
4430 return(num_records_processed);
4434 end_critical_section(S_SUPPMSGMAIN);
4436 fp = fopen(file_arcq_temp, "rb");
4438 CtdlLogPrintf(CTDL_CRIT, "%s: %s\n", file_arcq_temp, strerror(errno));
4439 return(num_records_processed);
4442 while (fread(&arcq_rec, sizeof(struct arcq), 1, fp) == 1) {
4443 TDAP_AdjRefCount(arcq_rec.arcq_msgnum, arcq_rec.arcq_delta);
4444 ++num_records_processed;
4448 r = unlink(file_arcq_temp);
4450 CtdlLogPrintf(CTDL_CRIT, "%s: %s\n", file_arcq_temp, strerror(errno));
4453 return(num_records_processed);
4459 * TDAP_AdjRefCount - adjust the reference count for a message.
4460 * This one does it "for real" because it's called by
4461 * the autopurger function that processes the queue
4462 * created by AdjRefCount(). If a message's reference
4463 * count becomes zero, we also delete the message from
4464 * disk and de-index it.
4466 void TDAP_AdjRefCount(long msgnum, int incr)
4469 struct MetaData smi;
4472 /* This is a *tight* critical section; please keep it that way, as
4473 * it may get called while nested in other critical sections.
4474 * Complicating this any further will surely cause deadlock!
4476 begin_critical_section(S_SUPPMSGMAIN);
4477 GetMetaData(&smi, msgnum);
4478 smi.meta_refcount += incr;
4480 end_critical_section(S_SUPPMSGMAIN);
4481 CtdlLogPrintf(CTDL_DEBUG, "msg %ld ref count delta %+d, is now %d\n",
4482 msgnum, incr, smi.meta_refcount);
4484 /* If the reference count is now zero, delete the message
4485 * (and its supplementary record as well).
4487 if (smi.meta_refcount == 0) {
4488 CtdlLogPrintf(CTDL_DEBUG, "Deleting message <%ld>\n", msgnum);
4490 /* Call delete hooks with NULL room to show it has gone altogether */
4491 PerformDeleteHooks(NULL, msgnum);
4493 /* Remove from message base */
4495 cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long));
4496 cdb_delete(CDB_BIGMSGS, &delnum, (int)sizeof(long));
4498 /* Remove metadata record */
4499 delnum = (0L - msgnum);
4500 cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long));
4506 * Write a generic object to this room
4508 * Note: this could be much more efficient. Right now we use two temporary
4509 * files, and still pull the message into memory as with all others.
4511 void CtdlWriteObject(char *req_room, /* Room to stuff it in */
4512 char *content_type, /* MIME type of this object */
4513 char *raw_message, /* Data to be written */
4514 off_t raw_length, /* Size of raw_message */
4515 struct ctdluser *is_mailbox, /* Mailbox room? */
4516 int is_binary, /* Is encoding necessary? */
4517 int is_unique, /* Del others of this type? */
4518 unsigned int flags /* Internal save flags */
4522 struct ctdlroom qrbuf;
4523 char roomname[ROOMNAMELEN];
4524 struct CtdlMessage *msg;
4525 char *encoded_message = NULL;
4527 if (is_mailbox != NULL) {
4528 MailboxName(roomname, sizeof roomname, is_mailbox, req_room);
4531 safestrncpy(roomname, req_room, sizeof(roomname));
4534 CtdlLogPrintf(CTDL_DEBUG, "Raw length is %ld\n", (long)raw_length);
4537 encoded_message = malloc((size_t) (((raw_length * 134) / 100) + 4096 ) );
4540 encoded_message = malloc((size_t)(raw_length + 4096));
4543 sprintf(encoded_message, "Content-type: %s\n", content_type);
4546 sprintf(&encoded_message[strlen(encoded_message)],
4547 "Content-transfer-encoding: base64\n\n"
4551 sprintf(&encoded_message[strlen(encoded_message)],
4552 "Content-transfer-encoding: 7bit\n\n"
4558 &encoded_message[strlen(encoded_message)],
4566 &encoded_message[strlen(encoded_message)],
4572 CtdlLogPrintf(CTDL_DEBUG, "Allocating\n");
4573 msg = malloc(sizeof(struct CtdlMessage));
4574 memset(msg, 0, sizeof(struct CtdlMessage));
4575 msg->cm_magic = CTDLMESSAGE_MAGIC;
4576 msg->cm_anon_type = MES_NORMAL;
4577 msg->cm_format_type = 4;
4578 msg->cm_fields['A'] = strdup(CC->user.fullname);
4579 msg->cm_fields['O'] = strdup(req_room);
4580 msg->cm_fields['N'] = strdup(config.c_nodename);
4581 msg->cm_fields['H'] = strdup(config.c_humannode);
4582 msg->cm_flags = flags;
4584 msg->cm_fields['M'] = encoded_message;
4586 /* Create the requested room if we have to. */
4587 if (getroom(&qrbuf, roomname) != 0) {
4588 create_room(roomname,
4589 ( (is_mailbox != NULL) ? 5 : 3 ),
4590 "", 0, 1, 0, VIEW_BBS);
4592 /* If the caller specified this object as unique, delete all
4593 * other objects of this type that are currently in the room.
4596 CtdlLogPrintf(CTDL_DEBUG, "Deleted %d other msgs of this type\n",
4597 CtdlDeleteMessages(roomname, NULL, 0, content_type)
4600 /* Now write the data */
4601 CtdlSubmitMsg(msg, NULL, roomname, 0);
4602 CtdlFreeMessage(msg);
4610 void CtdlGetSysConfigBackend(long msgnum, void *userdata) {
4611 config_msgnum = msgnum;
4615 char *CtdlGetSysConfig(char *sysconfname) {
4616 char hold_rm[ROOMNAMELEN];
4619 struct CtdlMessage *msg;
4622 strcpy(hold_rm, CC->room.QRname);
4623 if (getroom(&CC->room, SYSCONFIGROOM) != 0) {
4624 getroom(&CC->room, hold_rm);
4629 /* We want the last (and probably only) config in this room */
4630 begin_critical_section(S_CONFIG);
4631 config_msgnum = (-1L);
4632 CtdlForEachMessage(MSGS_LAST, 1, NULL, sysconfname, NULL,
4633 CtdlGetSysConfigBackend, NULL);
4634 msgnum = config_msgnum;
4635 end_critical_section(S_CONFIG);
4641 msg = CtdlFetchMessage(msgnum, 1);
4643 conf = strdup(msg->cm_fields['M']);
4644 CtdlFreeMessage(msg);
4651 getroom(&CC->room, hold_rm);
4653 if (conf != NULL) do {
4654 extract_token(buf, conf, 0, '\n', sizeof buf);
4655 strcpy(conf, &conf[strlen(buf)+1]);
4656 } while ( (!IsEmptyStr(conf)) && (!IsEmptyStr(buf)) );
4662 void CtdlPutSysConfig(char *sysconfname, char *sysconfdata) {
4663 CtdlWriteObject(SYSCONFIGROOM, sysconfname, sysconfdata, (strlen(sysconfdata)+1), NULL, 0, 1, 0);
4668 * Determine whether a given Internet address belongs to the current user
4670 int CtdlIsMe(char *addr, int addr_buf_len)
4672 struct recptypes *recp;
4675 recp = validate_recipients(addr, NULL, 0);
4676 if (recp == NULL) return(0);
4678 if (recp->num_local == 0) {
4679 free_recipients(recp);
4683 for (i=0; i<recp->num_local; ++i) {
4684 extract_token(addr, recp->recp_local, i, '|', addr_buf_len);
4685 if (!strcasecmp(addr, CC->user.fullname)) {
4686 free_recipients(recp);
4691 free_recipients(recp);
4697 * Citadel protocol command to do the same
4699 void cmd_isme(char *argbuf) {
4702 if (CtdlAccessCheck(ac_logged_in)) return;
4703 extract_token(addr, argbuf, 0, '|', sizeof addr);
4705 if (CtdlIsMe(addr, sizeof addr)) {
4706 cprintf("%d %s\n", CIT_OK, addr);
4709 cprintf("%d Not you.\n", ERROR + ILLEGAL_VALUE);
4715 /*****************************************************************************/
4716 /* MODULE INITIALIZATION STUFF */
4717 /*****************************************************************************/
4719 CTDL_MODULE_INIT(msgbase)
4721 CtdlRegisterProtoHook(cmd_msgs, "MSGS", "Output a list of messages in the current room");
4722 CtdlRegisterProtoHook(cmd_msg0, "MSG0", "Output a message in plain text format");
4723 CtdlRegisterProtoHook(cmd_msg2, "MSG2", "Output a message in RFC822 format");
4724 CtdlRegisterProtoHook(cmd_msg3, "MSG3", "Output a message in raw format (deprecated)");
4725 CtdlRegisterProtoHook(cmd_msg4, "MSG4", "Output a message in the client's preferred format");
4726 CtdlRegisterProtoHook(cmd_msgp, "MSGP", "Select preferred format for MSG4 output");
4727 CtdlRegisterProtoHook(cmd_opna, "OPNA", "Open an attachment for download");
4728 CtdlRegisterProtoHook(cmd_dlat, "DLAT", "Download an attachment");
4729 CtdlRegisterProtoHook(cmd_ent0, "ENT0", "Enter a message");
4730 CtdlRegisterProtoHook(cmd_dele, "DELE", "Delete a message");
4731 CtdlRegisterProtoHook(cmd_move, "MOVE", "Move or copy a message to another room");
4732 CtdlRegisterProtoHook(cmd_isme, "ISME", "Determine whether an email address belongs to a user");
4734 /* return our Subversion id for the Log */