2 * Implements the message store.
4 * Copyright (c) 1987-2012 by the citadel.org team
6 * This program is open source software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License version 3.
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
21 #if TIME_WITH_SYS_TIME
22 # include <sys/time.h>
26 # include <sys/time.h>
39 #include <sys/types.h>
44 #include <libcitadel.h>
47 #include "serv_extensions.h"
51 #include "sysdep_decls.h"
52 #include "citserver.h"
59 #include "internet_addressing.h"
60 #include "euidindex.h"
61 #include "journaling.h"
62 #include "citadel_dirs.h"
63 #include "clientsocket.h"
66 #include "ctdl_module.h"
69 struct addresses_to_be_filed *atbf = NULL;
71 /* This temp file holds the queue of operations for AdjRefCount() */
72 static FILE *arcfp = NULL;
73 void AdjRefCountList(long *msgnum, long nmsg, int incr);
75 int MessageDebugEnabled = 0;
78 * These are the four-character field headers we use when outputting
79 * messages in Citadel format (as opposed to RFC822 format).
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,
85 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
86 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
87 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
88 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
89 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
119 eMsgField FieldOrder[] = {
120 /* Important fields */
131 /* Semi-important fields */
137 /* G is not used yet, may become virus signature*/
140 /* Q is not used yet */
143 /* X is not used yet */
144 /* Z is not used yet */
151 /* Message text (MUST be last) */
153 /* Not saved to disk:
158 static const long NDiskFields = sizeof(FieldOrder) / sizeof(eMsgField);
159 void CM_SetField(struct CtdlMessage *Msg, eMsgField which, const char *buf, long length)
161 if (Msg->cm_fields[which] != NULL)
162 free (Msg->cm_fields[which]);
163 Msg->cm_fields[which] = malloc(length + 1);
164 memcpy(Msg->cm_fields[which], buf, length);
165 Msg->cm_fields[which][length] = '\0';
168 void CM_SetFieldLONG(struct CtdlMessage *Msg, eMsgField which, long lvalue)
172 len = snprintf(buf, sizeof(buf), "%ld", lvalue);
173 CM_SetField(Msg, which, buf, len);
175 void CM_CutFieldAt(struct CtdlMessage *Msg, eMsgField WhichToCut, long maxlen)
177 if (Msg->cm_fields[WhichToCut] == NULL)
180 if (strlen(Msg->cm_fields[WhichToCut]) > maxlen)
181 Msg->cm_fields[WhichToCut][maxlen] = '\0';
184 void CM_FlushField(struct CtdlMessage *Msg, eMsgField which)
186 if (Msg->cm_fields[which] != NULL)
187 free (Msg->cm_fields[which]);
188 Msg->cm_fields[which] = NULL;
191 void CM_CopyField(struct CtdlMessage *Msg, eMsgField WhichToPutTo, eMsgField WhichtToCopy)
194 if (Msg->cm_fields[WhichToPutTo] != NULL)
195 free (Msg->cm_fields[WhichToPutTo]);
197 if (Msg->cm_fields[WhichtToCopy] != NULL)
199 len = strlen(Msg->cm_fields[WhichtToCopy]);
200 Msg->cm_fields[WhichToPutTo] = malloc(len + 1);
201 memcpy(Msg->cm_fields[WhichToPutTo], Msg->cm_fields[WhichToPutTo], len);
202 Msg->cm_fields[WhichToPutTo][len] = '\0';
205 Msg->cm_fields[WhichToPutTo] = NULL;
209 void CM_PrependToField(struct CtdlMessage *Msg, eMsgField which, const char *buf, long length)
211 if (Msg->cm_fields[which] != NULL) {
216 oldmsgsize = strlen(Msg->cm_fields[which]) + 1;
217 newmsgsize = length + oldmsgsize;
219 new = malloc(newmsgsize);
220 memcpy(new, buf, length);
221 memcpy(new + length, Msg->cm_fields[which], oldmsgsize);
222 free(Msg->cm_fields[which]);
223 Msg->cm_fields[which] = new;
226 Msg->cm_fields[which] = malloc(length + 1);
227 memcpy(Msg->cm_fields[which], buf, length);
228 Msg->cm_fields[which][length] = '\0';
232 void CM_SetAsField(struct CtdlMessage *Msg, eMsgField which, char **buf, long length)
234 if (Msg->cm_fields[which] != NULL)
235 free (Msg->cm_fields[which]);
237 Msg->cm_fields[which] = *buf;
241 void CM_SetAsFieldSB(struct CtdlMessage *Msg, eMsgField which, StrBuf **buf)
243 if (Msg->cm_fields[which] != NULL)
244 free (Msg->cm_fields[which]);
246 Msg->cm_fields[which] = SmashStrBuf(buf);
249 void CM_GetAsField(struct CtdlMessage *Msg, eMsgField which, char **ret, long *retlen)
251 if (Msg->cm_fields[which] != NULL)
253 *retlen = strlen(Msg->cm_fields[which]);
254 *ret = Msg->cm_fields[which];
255 Msg->cm_fields[which] = NULL;
265 * This function is self explanatory.
266 * (What can I say, I'm in a weird mood today...)
268 void remove_any_whitespace_to_the_left_or_right_of_at_symbol(char *name)
272 for (i = 0; i < strlen(name); ++i) {
273 if (name[i] == '@') {
274 while (isspace(name[i - 1]) && i > 0) {
275 strcpy(&name[i - 1], &name[i]);
278 while (isspace(name[i + 1])) {
279 strcpy(&name[i + 1], &name[i + 2]);
287 * Aliasing for network mail.
288 * (Error messages have been commented out, because this is a server.)
290 int alias(char *name)
291 { /* process alias and routing info for mail */
292 struct CitContext *CCC = CC;
295 char aaa[SIZ], bbb[SIZ];
296 char *ignetcfg = NULL;
297 char *ignetmap = NULL;
303 char original_name[256];
304 safestrncpy(original_name, name, sizeof original_name);
307 remove_any_whitespace_to_the_left_or_right_of_at_symbol(name);
308 stripallbut(name, '<', '>');
310 fp = fopen(file_mail_aliases, "r");
312 fp = fopen("/dev/null", "r");
319 while (fgets(aaa, sizeof aaa, fp) != NULL) {
320 while (isspace(name[0]))
321 strcpy(name, &name[1]);
322 aaa[strlen(aaa) - 1] = 0;
324 for (a = 0; a < strlen(aaa); ++a) {
326 strcpy(bbb, &aaa[a + 1]);
330 if (!strcasecmp(name, aaa))
335 /* Hit the Global Address Book */
336 if (CtdlDirectoryLookup(aaa, name, sizeof aaa) == 0) {
340 if (strcasecmp(original_name, name)) {
341 MSG_syslog(LOG_INFO, "%s is being forwarded to %s\n", original_name, name);
344 /* Change "user @ xxx" to "user" if xxx is an alias for this host */
345 for (a=0; a<strlen(name); ++a) {
346 if (name[a] == '@') {
347 if (CtdlHostAlias(&name[a+1]) == hostalias_localhost) {
349 MSG_syslog(LOG_INFO, "Changed to <%s>\n", name);
354 /* determine local or remote type, see citadel.h */
355 at = haschar(name, '@');
356 if (at == 0) return(MES_LOCAL); /* no @'s - local address */
357 if (at > 1) return(MES_ERROR); /* >1 @'s - invalid address */
358 remove_any_whitespace_to_the_left_or_right_of_at_symbol(name);
360 /* figure out the delivery mode */
361 extract_token(node, name, 1, '@', sizeof node);
363 /* If there are one or more dots in the nodename, we assume that it
364 * is an FQDN and will attempt SMTP delivery to the Internet.
366 if (haschar(node, '.') > 0) {
367 return(MES_INTERNET);
370 /* Otherwise we look in the IGnet maps for a valid Citadel node.
371 * Try directly-connected nodes first...
373 ignetcfg = CtdlGetSysConfig(IGNETCFG);
374 for (i=0; i<num_tokens(ignetcfg, '\n'); ++i) {
375 extract_token(buf, ignetcfg, i, '\n', sizeof buf);
376 extract_token(testnode, buf, 0, '|', sizeof testnode);
377 if (!strcasecmp(node, testnode)) {
385 * Then try nodes that are two or more hops away.
387 ignetmap = CtdlGetSysConfig(IGNETMAP);
388 for (i=0; i<num_tokens(ignetmap, '\n'); ++i) {
389 extract_token(buf, ignetmap, i, '\n', sizeof buf);
390 extract_token(testnode, buf, 0, '|', sizeof testnode);
391 if (!strcasecmp(node, testnode)) {
398 /* If we get to this point it's an invalid node name */
404 * Back end for the MSGS command: output message number only.
406 void simple_listing(long msgnum, void *userdata)
408 cprintf("%ld\n", msgnum);
414 * Back end for the MSGS command: output header summary.
416 void headers_listing(long msgnum, void *userdata)
418 struct CtdlMessage *msg;
420 msg = CtdlFetchMessage(msgnum, 0);
422 cprintf("%ld|0|||||\n", msgnum);
426 cprintf("%ld|%s|%s|%s|%s|%s|\n",
428 (msg->cm_fields[eTimestamp] ? msg->cm_fields[eTimestamp] : "0"),
429 (msg->cm_fields[eAuthor] ? msg->cm_fields[eAuthor] : ""),
430 (msg->cm_fields[eNodeName] ? msg->cm_fields[eNodeName] : ""),
431 (msg->cm_fields[erFc822Addr] ? msg->cm_fields[erFc822Addr] : ""),
432 (msg->cm_fields[eMsgSubject] ? msg->cm_fields[eMsgSubject] : "")
434 CtdlFreeMessage(msg);
438 * Back end for the MSGS command: output EUID header.
440 void headers_euid(long msgnum, void *userdata)
442 struct CtdlMessage *msg;
444 msg = CtdlFetchMessage(msgnum, 0);
446 cprintf("%ld||\n", msgnum);
450 cprintf("%ld|%s|%s\n",
452 (msg->cm_fields[eExclusiveID] ? msg->cm_fields[eExclusiveID] : ""),
453 (msg->cm_fields[eTimestamp] ? msg->cm_fields[eTimestamp] : "0"));
454 CtdlFreeMessage(msg);
461 /* Determine if a given message matches the fields in a message template.
462 * Return 0 for a successful match.
464 int CtdlMsgCmp(struct CtdlMessage *msg, struct CtdlMessage *template) {
467 /* If there aren't any fields in the template, all messages will
470 if (template == NULL) return(0);
472 /* Null messages are bogus. */
473 if (msg == NULL) return(1);
475 for (i='A'; i<='Z'; ++i) {
476 if (template->cm_fields[i] != NULL) {
477 if (msg->cm_fields[i] == NULL) {
478 /* Considered equal if temmplate is empty string */
479 if (IsEmptyStr(template->cm_fields[i])) continue;
482 if (strcasecmp(msg->cm_fields[i],
483 template->cm_fields[i])) return 1;
487 /* All compares succeeded: we have a match! */
494 * Retrieve the "seen" message list for the current room.
496 void CtdlGetSeen(char *buf, int which_set) {
497 struct CitContext *CCC = CC;
500 /* Learn about the user and room in question */
501 CtdlGetRelationship(&vbuf, &CCC->user, &CCC->room);
503 if (which_set == ctdlsetseen_seen)
504 safestrncpy(buf, vbuf.v_seen, SIZ);
505 if (which_set == ctdlsetseen_answered)
506 safestrncpy(buf, vbuf.v_answered, SIZ);
512 * Manipulate the "seen msgs" string (or other message set strings)
514 void CtdlSetSeen(long *target_msgnums, int num_target_msgnums,
515 int target_setting, int which_set,
516 struct ctdluser *which_user, struct ctdlroom *which_room) {
517 struct CitContext *CCC = CC;
518 struct cdbdata *cdbfr;
523 long hi = (-1L); /// TODO: we just write here. y?
532 char *is_set; /* actually an array of booleans */
534 /* Don't bother doing *anything* if we were passed a list of zero messages */
535 if (num_target_msgnums < 1) {
539 /* If no room was specified, we go with the current room. */
541 which_room = &CCC->room;
544 /* If no user was specified, we go with the current user. */
546 which_user = &CCC->user;
549 MSG_syslog(LOG_DEBUG, "CtdlSetSeen(%d msgs starting with %ld, %s, %d) in <%s>\n",
550 num_target_msgnums, target_msgnums[0],
551 (target_setting ? "SET" : "CLEAR"),
555 /* Learn about the user and room in question */
556 CtdlGetRelationship(&vbuf, which_user, which_room);
558 /* Load the message list */
559 cdbfr = cdb_fetch(CDB_MSGLISTS, &which_room->QRnumber, sizeof(long));
561 msglist = (long *) cdbfr->ptr;
562 cdbfr->ptr = NULL; /* CtdlSetSeen() now owns this memory */
563 num_msgs = cdbfr->len / sizeof(long);
566 return; /* No messages at all? No further action. */
569 is_set = malloc(num_msgs * sizeof(char));
570 memset(is_set, 0, (num_msgs * sizeof(char)) );
572 /* Decide which message set we're manipulating */
574 case ctdlsetseen_seen:
575 vset = NewStrBufPlain(vbuf.v_seen, -1);
577 case ctdlsetseen_answered:
578 vset = NewStrBufPlain(vbuf.v_answered, -1);
585 #if 0 /* This is a special diagnostic section. Do not allow it to run during normal operation. */
586 MSG_syslog(LOG_DEBUG, "There are %d messages in the room.\n", num_msgs);
587 for (i=0; i<num_msgs; ++i) {
588 if ((i > 0) && (msglist[i] <= msglist[i-1])) abort();
590 MSG_syslog(LOG_DEBUG, "We are twiddling %d of them.\n", num_target_msgnums);
591 for (k=0; k<num_target_msgnums; ++k) {
592 if ((k > 0) && (target_msgnums[k] <= target_msgnums[k-1])) abort();
596 MSG_syslog(LOG_DEBUG, "before update: %s\n", ChrPtr(vset));
598 /* Translate the existing sequence set into an array of booleans */
599 setstr = NewStrBuf();
603 while (StrBufExtract_NextToken(setstr, vset, &pvset, ',') >= 0) {
605 StrBufExtract_token(lostr, setstr, 0, ':');
606 if (StrBufNum_tokens(setstr, ':') >= 2) {
607 StrBufExtract_token(histr, setstr, 1, ':');
611 StrBufAppendBuf(histr, lostr, 0);
614 if (!strcmp(ChrPtr(histr), "*")) {
621 for (i = 0; i < num_msgs; ++i) {
622 if ((msglist[i] >= lo) && (msglist[i] <= hi)) {
632 /* Now translate the array of booleans back into a sequence set */
638 for (i=0; i<num_msgs; ++i) {
642 for (k=0; k<num_target_msgnums; ++k) {
643 if (msglist[i] == target_msgnums[k]) {
644 is_seen = target_setting;
648 if ((was_seen == 0) && (is_seen == 1)) {
651 else if ((was_seen == 1) && (is_seen == 0)) {
654 if (StrLength(vset) > 0) {
655 StrBufAppendBufPlain(vset, HKEY(","), 0);
658 StrBufAppendPrintf(vset, "%ld", hi);
661 StrBufAppendPrintf(vset, "%ld:%ld", lo, hi);
665 if ((is_seen) && (i == num_msgs - 1)) {
666 if (StrLength(vset) > 0) {
667 StrBufAppendBufPlain(vset, HKEY(","), 0);
669 if ((i==0) || (was_seen == 0)) {
670 StrBufAppendPrintf(vset, "%ld", msglist[i]);
673 StrBufAppendPrintf(vset, "%ld:%ld", lo, msglist[i]);
681 * We will have to stuff this string back into a 4096 byte buffer, so if it's
682 * larger than that now, truncate it by removing tokens from the beginning.
683 * The limit of 100 iterations is there to prevent an infinite loop in case
684 * something unexpected happens.
686 int number_of_truncations = 0;
687 while ( (StrLength(vset) > SIZ) && (number_of_truncations < 100) ) {
688 StrBufRemove_token(vset, 0, ',');
689 ++number_of_truncations;
693 * If we're truncating the sequence set of messages marked with the 'seen' flag,
694 * we want the earliest messages (the truncated ones) to be marked, not unmarked.
695 * Otherwise messages at the beginning will suddenly appear to be 'unseen'.
697 if ( (which_set == ctdlsetseen_seen) && (number_of_truncations > 0) ) {
699 first_tok = NewStrBuf();
700 StrBufExtract_token(first_tok, vset, 0, ',');
701 StrBufRemove_token(vset, 0, ',');
703 if (StrBufNum_tokens(first_tok, ':') > 1) {
704 StrBufRemove_token(first_tok, 0, ':');
708 new_set = NewStrBuf();
709 StrBufAppendBufPlain(new_set, HKEY("1:"), 0);
710 StrBufAppendBuf(new_set, first_tok, 0);
711 StrBufAppendBufPlain(new_set, HKEY(":"), 0);
712 StrBufAppendBuf(new_set, vset, 0);
715 FreeStrBuf(&first_tok);
719 MSG_syslog(LOG_DEBUG, " after update: %s\n", ChrPtr(vset));
721 /* Decide which message set we're manipulating */
723 case ctdlsetseen_seen:
724 safestrncpy(vbuf.v_seen, ChrPtr(vset), sizeof vbuf.v_seen);
726 case ctdlsetseen_answered:
727 safestrncpy(vbuf.v_answered, ChrPtr(vset), sizeof vbuf.v_answered);
733 CtdlSetRelationship(&vbuf, which_user, which_room);
739 * API function to perform an operation for each qualifying message in the
740 * current room. (Returns the number of messages processed.)
742 int CtdlForEachMessage(int mode, long ref, char *search_string,
744 struct CtdlMessage *compare,
745 ForEachMsgCallback CallBack,
748 struct CitContext *CCC = CC;
751 struct cdbdata *cdbfr;
752 long *msglist = NULL;
754 int num_processed = 0;
757 struct CtdlMessage *msg = NULL;
760 int printed_lastold = 0;
761 int num_search_msgs = 0;
762 long *search_msgs = NULL;
764 int need_to_free_re = 0;
767 if ((content_type) && (!IsEmptyStr(content_type))) {
768 regcomp(&re, content_type, 0);
772 /* Learn about the user and room in question */
773 if (server_shutting_down) {
774 if (need_to_free_re) regfree(&re);
777 CtdlGetUser(&CCC->user, CCC->curr_user);
779 if (server_shutting_down) {
780 if (need_to_free_re) regfree(&re);
783 CtdlGetRelationship(&vbuf, &CCC->user, &CCC->room);
785 if (server_shutting_down) {
786 if (need_to_free_re) regfree(&re);
790 /* Load the message list */
791 cdbfr = cdb_fetch(CDB_MSGLISTS, &CCC->room.QRnumber, sizeof(long));
793 if (need_to_free_re) regfree(&re);
794 return 0; /* No messages at all? No further action. */
797 msglist = (long *) cdbfr->ptr;
798 num_msgs = cdbfr->len / sizeof(long);
800 cdbfr->ptr = NULL; /* clear this so that cdb_free() doesn't free it */
801 cdb_free(cdbfr); /* we own this memory now */
804 * Now begin the traversal.
806 if (num_msgs > 0) for (a = 0; a < num_msgs; ++a) {
808 /* If the caller is looking for a specific MIME type, filter
809 * out all messages which are not of the type requested.
811 if ((content_type != NULL) && (!IsEmptyStr(content_type))) {
813 /* This call to GetMetaData() sits inside this loop
814 * so that we only do the extra database read per msg
815 * if we need to. Doing the extra read all the time
816 * really kills the server. If we ever need to use
817 * metadata for another search criterion, we need to
818 * move the read somewhere else -- but still be smart
819 * enough to only do the read if the caller has
820 * specified something that will need it.
822 if (server_shutting_down) {
823 if (need_to_free_re) regfree(&re);
827 GetMetaData(&smi, msglist[a]);
829 /* if (strcasecmp(smi.meta_content_type, content_type)) { old non-regex way */
830 if (regexec(&re, smi.meta_content_type, 1, &pm, 0) != 0) {
836 num_msgs = sort_msglist(msglist, num_msgs);
838 /* If a template was supplied, filter out the messages which
839 * don't match. (This could induce some delays!)
842 if (compare != NULL) {
843 for (a = 0; a < num_msgs; ++a) {
844 if (server_shutting_down) {
845 if (need_to_free_re) regfree(&re);
849 msg = CtdlFetchMessage(msglist[a], 1);
851 if (CtdlMsgCmp(msg, compare)) {
854 CtdlFreeMessage(msg);
860 /* If a search string was specified, get a message list from
861 * the full text index and remove messages which aren't on both
865 * Since the lists are sorted and strictly ascending, and the
866 * output list is guaranteed to be shorter than or equal to the
867 * input list, we overwrite the bottom of the input list. This
868 * eliminates the need to memmove big chunks of the list over and
871 if ( (num_msgs > 0) && (mode == MSGS_SEARCH) && (search_string) ) {
873 /* Call search module via hook mechanism.
874 * NULL means use any search function available.
875 * otherwise replace with a char * to name of search routine
877 CtdlModuleDoSearch(&num_search_msgs, &search_msgs, search_string, "fulltext");
879 if (num_search_msgs > 0) {
883 orig_num_msgs = num_msgs;
885 for (i=0; i<orig_num_msgs; ++i) {
886 for (j=0; j<num_search_msgs; ++j) {
887 if (msglist[i] == search_msgs[j]) {
888 msglist[num_msgs++] = msglist[i];
894 num_msgs = 0; /* No messages qualify */
896 if (search_msgs != NULL) free(search_msgs);
898 /* Now that we've purged messages which don't contain the search
899 * string, treat a MSGS_SEARCH just like a MSGS_ALL from this
906 * Now iterate through the message list, according to the
907 * criteria supplied by the caller.
910 for (a = 0; a < num_msgs; ++a) {
911 if (server_shutting_down) {
912 if (need_to_free_re) regfree(&re);
914 return num_processed;
916 thismsg = msglist[a];
917 if (mode == MSGS_ALL) {
921 is_seen = is_msg_in_sequence_set(
922 vbuf.v_seen, thismsg);
923 if (is_seen) lastold = thismsg;
929 || ((mode == MSGS_OLD) && (is_seen))
930 || ((mode == MSGS_NEW) && (!is_seen))
931 || ((mode == MSGS_LAST) && (a >= (num_msgs - ref)))
932 || ((mode == MSGS_FIRST) && (a < ref))
933 || ((mode == MSGS_GT) && (thismsg > ref))
934 || ((mode == MSGS_LT) && (thismsg < ref))
935 || ((mode == MSGS_EQ) && (thismsg == ref))
938 if ((mode == MSGS_NEW) && (CCC->user.flags & US_LASTOLD) && (lastold > 0L) && (printed_lastold == 0) && (!is_seen)) {
940 CallBack(lastold, userdata);
944 if (CallBack) CallBack(thismsg, userdata);
948 if (need_to_free_re) regfree(&re);
951 * We cache the most recent msglist in order to do security checks later
953 if (CCC->client_socket > 0) {
954 if (CCC->cached_msglist != NULL) {
955 free(CCC->cached_msglist);
957 CCC->cached_msglist = msglist;
958 CCC->cached_num_msgs = num_msgs;
964 return num_processed;
970 * cmd_msgs() - get list of message #'s in this room
971 * implements the MSGS server command using CtdlForEachMessage()
973 void cmd_msgs(char *cmdbuf)
982 int with_template = 0;
983 struct CtdlMessage *template = NULL;
984 char search_string[1024];
985 ForEachMsgCallback CallBack;
987 if (CtdlAccessCheck(ac_logged_in_or_guest)) return;
989 extract_token(which, cmdbuf, 0, '|', sizeof which);
990 cm_ref = extract_int(cmdbuf, 1);
991 extract_token(search_string, cmdbuf, 1, '|', sizeof search_string);
992 with_template = extract_int(cmdbuf, 2);
993 switch (extract_int(cmdbuf, 3))
997 CallBack = simple_listing;
1000 CallBack = headers_listing;
1003 CallBack = headers_euid;
1008 if (!strncasecmp(which, "OLD", 3))
1010 else if (!strncasecmp(which, "NEW", 3))
1012 else if (!strncasecmp(which, "FIRST", 5))
1014 else if (!strncasecmp(which, "LAST", 4))
1016 else if (!strncasecmp(which, "GT", 2))
1018 else if (!strncasecmp(which, "LT", 2))
1020 else if (!strncasecmp(which, "SEARCH", 6))
1025 if ( (mode == MSGS_SEARCH) && (!config.c_enable_fulltext) ) {
1026 cprintf("%d Full text index is not enabled on this server.\n",
1027 ERROR + CMD_NOT_SUPPORTED);
1031 if (with_template) {
1033 cprintf("%d Send template then receive message list\n",
1035 template = (struct CtdlMessage *)
1036 malloc(sizeof(struct CtdlMessage));
1037 memset(template, 0, sizeof(struct CtdlMessage));
1038 template->cm_magic = CTDLMESSAGE_MAGIC;
1039 template->cm_anon_type = MES_NORMAL;
1041 while(client_getln(buf, sizeof buf) >= 0 && strcmp(buf,"000")) {
1043 extract_token(tfield, buf, 0, '|', sizeof tfield);
1044 tValueLen = extract_token(tvalue, buf, 1, '|', sizeof tvalue);
1045 for (i='A'; i<='Z'; ++i) if (msgkeys[i]!=NULL) {
1046 if (!strcasecmp(tfield, msgkeys[i])) {
1047 CM_SetField(template, i, tvalue, tValueLen);
1054 cprintf("%d \n", LISTING_FOLLOWS);
1057 CtdlForEachMessage(mode,
1058 ( (mode == MSGS_SEARCH) ? 0 : cm_ref ),
1059 ( (mode == MSGS_SEARCH) ? search_string : NULL ),
1064 if (template != NULL) CtdlFreeMessage(template);
1072 * help_subst() - support routine for help file viewer
1074 void help_subst(char *strbuf, char *source, char *dest)
1079 while (p = pattern2(strbuf, source), (p >= 0)) {
1080 strcpy(workbuf, &strbuf[p + strlen(source)]);
1081 strcpy(&strbuf[p], dest);
1082 strcat(strbuf, workbuf);
1087 void do_help_subst(char *buffer)
1091 help_subst(buffer, "^nodename", config.c_nodename);
1092 help_subst(buffer, "^humannode", config.c_humannode);
1093 help_subst(buffer, "^fqdn", config.c_fqdn);
1094 help_subst(buffer, "^username", CC->user.fullname);
1095 snprintf(buf2, sizeof buf2, "%ld", CC->user.usernum);
1096 help_subst(buffer, "^usernum", buf2);
1097 help_subst(buffer, "^sysadm", config.c_sysadm);
1098 help_subst(buffer, "^variantname", CITADEL);
1099 snprintf(buf2, sizeof buf2, "%d", config.c_maxsessions);
1100 help_subst(buffer, "^maxsessions", buf2);
1101 help_subst(buffer, "^bbsdir", ctdl_message_dir);
1107 * memfmout() - Citadel text formatter and paginator.
1108 * Although the original purpose of this routine was to format
1109 * text to the reader's screen width, all we're really using it
1110 * for here is to format text out to 80 columns before sending it
1111 * to the client. The client software may reformat it again.
1114 char *mptr, /* where are we going to get our text from? */
1115 const char *nl /* string to terminate lines with */
1117 struct CitContext *CCC = CC;
1119 unsigned char ch = 0;
1126 while (ch=*(mptr++), ch != 0) {
1129 if (client_write(outbuf, len) == -1)
1131 MSGM_syslog(LOG_ERR, "memfmout(): aborting due to write failure.\n");
1135 if (client_write(nl, nllen) == -1)
1137 MSGM_syslog(LOG_ERR, "memfmout(): aborting due to write failure.\n");
1142 else if (ch == '\r') {
1143 /* Ignore carriage returns. Newlines are always LF or CRLF but never CR. */
1145 else if (isspace(ch)) {
1146 if (column > 72) { /* Beyond 72 columns, break on the next space */
1147 if (client_write(outbuf, len) == -1)
1149 MSGM_syslog(LOG_ERR, "memfmout(): aborting due to write failure.\n");
1153 if (client_write(nl, nllen) == -1)
1155 MSGM_syslog(LOG_ERR, "memfmout(): aborting due to write failure.\n");
1168 if (column > 1000) { /* Beyond 1000 columns, break anywhere */
1169 if (client_write(outbuf, len) == -1)
1171 MSGM_syslog(LOG_ERR, "memfmout(): aborting due to write failure.\n");
1175 if (client_write(nl, nllen) == -1)
1177 MSGM_syslog(LOG_ERR, "memfmout(): aborting due to write failure.\n");
1185 if (client_write(outbuf, len) == -1)
1187 MSGM_syslog(LOG_ERR, "memfmout(): aborting due to write failure.\n");
1191 client_write(nl, nllen);
1199 * Callback function for mime parser that simply lists the part
1201 void list_this_part(char *name, char *filename, char *partnum, char *disp,
1202 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
1203 char *cbid, void *cbuserdata)
1207 ma = (struct ma_info *)cbuserdata;
1208 if (ma->is_ma == 0) {
1209 cprintf("part=%s|%s|%s|%s|%s|%ld|%s|%s\n",
1222 * Callback function for multipart prefix
1224 void list_this_pref(char *name, char *filename, char *partnum, char *disp,
1225 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
1226 char *cbid, void *cbuserdata)
1230 ma = (struct ma_info *)cbuserdata;
1231 if (!strcasecmp(cbtype, "multipart/alternative")) {
1235 if (ma->is_ma == 0) {
1236 cprintf("pref=%s|%s\n", partnum, cbtype);
1241 * Callback function for multipart sufffix
1243 void list_this_suff(char *name, char *filename, char *partnum, char *disp,
1244 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
1245 char *cbid, void *cbuserdata)
1249 ma = (struct ma_info *)cbuserdata;
1250 if (ma->is_ma == 0) {
1251 cprintf("suff=%s|%s\n", partnum, cbtype);
1253 if (!strcasecmp(cbtype, "multipart/alternative")) {
1260 * Callback function for mime parser that opens a section for downloading
1262 void mime_download(char *name, char *filename, char *partnum, char *disp,
1263 void *content, char *cbtype, char *cbcharset, size_t length,
1264 char *encoding, char *cbid, void *cbuserdata)
1267 CitContext *CCC = MyContext();
1269 /* Silently go away if there's already a download open. */
1270 if (CCC->download_fp != NULL)
1274 (!IsEmptyStr(partnum) && (!strcasecmp(CCC->download_desired_section, partnum)))
1275 || (!IsEmptyStr(cbid) && (!strcasecmp(CCC->download_desired_section, cbid)))
1277 CCC->download_fp = tmpfile();
1278 if (CCC->download_fp == NULL) {
1279 MSG_syslog(LOG_EMERG, "mime_download(): Couldn't write: %s\n",
1281 cprintf("%d cannot open temporary file: %s\n",
1282 ERROR + INTERNAL_ERROR, strerror(errno));
1286 rv = fwrite(content, length, 1, CCC->download_fp);
1288 MSG_syslog(LOG_EMERG, "mime_download(): Couldn't write: %s\n",
1290 cprintf("%d unable to write tempfile.\n",
1292 fclose(CCC->download_fp);
1293 CCC->download_fp = NULL;
1296 fflush(CCC->download_fp);
1297 rewind(CCC->download_fp);
1299 OpenCmdResult(filename, cbtype);
1306 * Callback function for mime parser that outputs a section all at once.
1307 * We can specify the desired section by part number *or* content-id.
1309 void mime_spew_section(char *name, char *filename, char *partnum, char *disp,
1310 void *content, char *cbtype, char *cbcharset, size_t length,
1311 char *encoding, char *cbid, void *cbuserdata)
1313 int *found_it = (int *)cbuserdata;
1316 (!IsEmptyStr(partnum) && (!strcasecmp(CC->download_desired_section, partnum)))
1317 || (!IsEmptyStr(cbid) && (!strcasecmp(CC->download_desired_section, cbid)))
1320 cprintf("%d %d|-1|%s|%s|%s\n",
1327 client_write(content, length);
1333 * Load a message from disk into memory.
1334 * This is used by CtdlOutputMsg() and other fetch functions.
1336 * NOTE: Caller is responsible for freeing the returned CtdlMessage struct
1337 * using the CtdlMessageFree() function.
1339 struct CtdlMessage *CtdlFetchMessage(long msgnum, int with_body)
1341 struct CitContext *CCC = CC;
1342 struct cdbdata *dmsgtext;
1343 struct CtdlMessage *ret = NULL;
1347 cit_uint8_t field_header;
1349 MSG_syslog(LOG_DEBUG, "CtdlFetchMessage(%ld, %d)\n", msgnum, with_body);
1350 dmsgtext = cdb_fetch(CDB_MSGMAIN, &msgnum, sizeof(long));
1351 if (dmsgtext == NULL) {
1352 MSG_syslog(LOG_ERR, "CtdlFetchMessage(%ld, %d) Failed!\n", msgnum, with_body);
1355 mptr = dmsgtext->ptr;
1356 upper_bound = mptr + dmsgtext->len;
1358 /* Parse the three bytes that begin EVERY message on disk.
1359 * The first is always 0xFF, the on-disk magic number.
1360 * The second is the anonymous/public type byte.
1361 * The third is the format type byte (vari, fixed, or MIME).
1365 MSG_syslog(LOG_ERR, "Message %ld appears to be corrupted.\n", msgnum);
1369 ret = (struct CtdlMessage *) malloc(sizeof(struct CtdlMessage));
1370 memset(ret, 0, sizeof(struct CtdlMessage));
1372 ret->cm_magic = CTDLMESSAGE_MAGIC;
1373 ret->cm_anon_type = *mptr++; /* Anon type byte */
1374 ret->cm_format_type = *mptr++; /* Format type byte */
1377 * The rest is zero or more arbitrary fields. Load them in.
1378 * We're done when we encounter either a zero-length field or
1379 * have just processed the 'M' (message text) field.
1383 if (mptr >= upper_bound) {
1386 field_header = *mptr++;
1388 CM_SetField(ret, field_header, mptr, len);
1390 mptr += len + 1; /* advance to next field */
1392 } while ((mptr < upper_bound) && (field_header != 'M'));
1396 /* Always make sure there's something in the msg text field. If
1397 * it's NULL, the message text is most likely stored separately,
1398 * so go ahead and fetch that. Failing that, just set a dummy
1399 * body so other code doesn't barf.
1401 if ( (ret->cm_fields[eMesageText] == NULL) && (with_body) ) {
1402 dmsgtext = cdb_fetch(CDB_BIGMSGS, &msgnum, sizeof(long));
1403 if (dmsgtext != NULL) {
1404 CM_SetAsField(ret, eMesageText, &dmsgtext->ptr, dmsgtext->len);
1408 if (ret->cm_fields[eMesageText] == NULL) {
1409 CM_SetField(ret, eMesageText, HKEY("\r\n\r\n (no text)\r\n"));
1412 /* Perform "before read" hooks (aborting if any return nonzero) */
1413 if (PerformMessageHooks(ret, EVT_BEFOREREAD) > 0) {
1414 CtdlFreeMessage(ret);
1423 * Returns 1 if the supplied pointer points to a valid Citadel message.
1424 * If the pointer is NULL or the magic number check fails, returns 0.
1426 int is_valid_message(struct CtdlMessage *msg) {
1429 if ((msg->cm_magic) != CTDLMESSAGE_MAGIC) {
1430 struct CitContext *CCC = CC;
1431 MSGM_syslog(LOG_WARNING, "is_valid_message() -- self-check failed\n");
1437 void CtdlFreeMessageContents(struct CtdlMessage *msg)
1441 for (i = 0; i < 256; ++i)
1442 if (msg->cm_fields[i] != NULL) {
1443 free(msg->cm_fields[i]);
1446 msg->cm_magic = 0; /* just in case */
1449 * 'Destructor' for struct CtdlMessage
1451 void CtdlFreeMessage(struct CtdlMessage *msg)
1453 if (is_valid_message(msg) == 0)
1455 if (msg != NULL) free (msg);
1458 CtdlFreeMessageContents(msg);
1462 int DupCMField(eMsgField i, struct CtdlMessage *OrgMsg, struct CtdlMessage *NewMsg)
1465 len = strlen(OrgMsg->cm_fields[i]);
1466 NewMsg->cm_fields[i] = malloc(len + 1);
1467 if (NewMsg->cm_fields[i] == NULL)
1469 memcpy(NewMsg->cm_fields[i], OrgMsg->cm_fields[i], len);
1470 NewMsg->cm_fields[i][len] = '\0';
1474 struct CtdlMessage * CtdlDuplicateMessage(struct CtdlMessage *OrgMsg)
1477 struct CtdlMessage *NewMsg;
1479 if (is_valid_message(OrgMsg) == 0)
1481 NewMsg = (struct CtdlMessage *)malloc(sizeof(struct CtdlMessage));
1485 memcpy(NewMsg, OrgMsg, sizeof(struct CtdlMessage));
1487 memset(&NewMsg->cm_fields, 0, sizeof(char*) * 256);
1489 for (i = 0; i < 256; ++i)
1491 if (OrgMsg->cm_fields[i] != NULL)
1493 if (!DupCMField(i, OrgMsg, NewMsg))
1495 CtdlFreeMessage(NewMsg);
1507 * Pre callback function for multipart/alternative
1509 * NOTE: this differs from the standard behavior for a reason. Normally when
1510 * displaying multipart/alternative you want to show the _last_ usable
1511 * format in the message. Here we show the _first_ one, because it's
1512 * usually text/plain. Since this set of functions is designed for text
1513 * output to non-MIME-aware clients, this is the desired behavior.
1516 void fixed_output_pre(char *name, char *filename, char *partnum, char *disp,
1517 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
1518 char *cbid, void *cbuserdata)
1520 struct CitContext *CCC = CC;
1523 ma = (struct ma_info *)cbuserdata;
1524 MSG_syslog(LOG_DEBUG, "fixed_output_pre() type=<%s>\n", cbtype);
1525 if (!strcasecmp(cbtype, "multipart/alternative")) {
1529 if (!strcasecmp(cbtype, "message/rfc822")) {
1535 * Post callback function for multipart/alternative
1537 void fixed_output_post(char *name, char *filename, char *partnum, char *disp,
1538 void *content, char *cbtype, char *cbcharset, size_t length,
1539 char *encoding, char *cbid, void *cbuserdata)
1541 struct CitContext *CCC = CC;
1544 ma = (struct ma_info *)cbuserdata;
1545 MSG_syslog(LOG_DEBUG, "fixed_output_post() type=<%s>\n", cbtype);
1546 if (!strcasecmp(cbtype, "multipart/alternative")) {
1550 if (!strcasecmp(cbtype, "message/rfc822")) {
1556 * Inline callback function for mime parser that wants to display text
1558 void fixed_output(char *name, char *filename, char *partnum, char *disp,
1559 void *content, char *cbtype, char *cbcharset, size_t length,
1560 char *encoding, char *cbid, void *cbuserdata)
1562 struct CitContext *CCC = CC;
1568 ma = (struct ma_info *)cbuserdata;
1570 MSG_syslog(LOG_DEBUG,
1571 "fixed_output() part %s: %s (%s) (%ld bytes)\n",
1572 partnum, filename, cbtype, (long)length);
1575 * If we're in the middle of a multipart/alternative scope and
1576 * we've already printed another section, skip this one.
1578 if ( (ma->is_ma) && (ma->did_print) ) {
1579 MSG_syslog(LOG_DEBUG, "Skipping part %s (%s)\n", partnum, cbtype);
1584 if ( (!strcasecmp(cbtype, "text/plain"))
1585 || (IsEmptyStr(cbtype)) ) {
1588 client_write(wptr, length);
1589 if (wptr[length-1] != '\n') {
1596 if (!strcasecmp(cbtype, "text/html")) {
1597 ptr = html_to_ascii(content, length, 80, 0);
1599 client_write(ptr, wlen);
1600 if ((wlen > 0) && (ptr[wlen-1] != '\n')) {
1607 if (ma->use_fo_hooks) {
1608 if (PerformFixedOutputHooks(cbtype, content, length)) {
1609 /* above function returns nonzero if it handled the part */
1614 if (strncasecmp(cbtype, "multipart/", 10)) {
1615 cprintf("Part %s: %s (%s) (%ld bytes)\r\n",
1616 partnum, filename, cbtype, (long)length);
1622 * The client is elegant and sophisticated and wants to be choosy about
1623 * MIME content types, so figure out which multipart/alternative part
1624 * we're going to send.
1626 * We use a system of weights. When we find a part that matches one of the
1627 * MIME types we've declared as preferential, we can store it in ma->chosen_part
1628 * and then set ma->chosen_pref to that MIME type's position in our preference
1629 * list. If we then hit another match, we only replace the first match if
1630 * the preference value is lower.
1632 void choose_preferred(char *name, char *filename, char *partnum, char *disp,
1633 void *content, char *cbtype, char *cbcharset, size_t length,
1634 char *encoding, char *cbid, void *cbuserdata)
1636 struct CitContext *CCC = CC;
1641 ma = (struct ma_info *)cbuserdata;
1643 // NOTE: REMOVING THIS CONDITIONAL FIXES BUG 220
1644 // http://bugzilla.citadel.org/show_bug.cgi?id=220
1645 // I don't know if there are any side effects! Please TEST TEST TEST
1646 //if (ma->is_ma > 0) {
1648 for (i=0; i<num_tokens(CCC->preferred_formats, '|'); ++i) {
1649 extract_token(buf, CCC->preferred_formats, i, '|', sizeof buf);
1650 if ( (!strcasecmp(buf, cbtype)) && (!ma->freeze) ) {
1651 if (i < ma->chosen_pref) {
1652 MSG_syslog(LOG_DEBUG, "Setting chosen part: <%s>\n", partnum);
1653 safestrncpy(ma->chosen_part, partnum, sizeof ma->chosen_part);
1654 ma->chosen_pref = i;
1661 * Now that we've chosen our preferred part, output it.
1663 void output_preferred(char *name,
1675 struct CitContext *CCC = CC;
1678 int add_newline = 0;
1681 char *decoded = NULL;
1682 size_t bytes_decoded;
1685 ma = (struct ma_info *)cbuserdata;
1687 /* This is not the MIME part you're looking for... */
1688 if (strcasecmp(partnum, ma->chosen_part)) return;
1690 /* If the content-type of this part is in our preferred formats
1691 * list, we can simply output it verbatim.
1693 for (i=0; i<num_tokens(CCC->preferred_formats, '|'); ++i) {
1694 extract_token(buf, CCC->preferred_formats, i, '|', sizeof buf);
1695 if (!strcasecmp(buf, cbtype)) {
1696 /* Yeah! Go! W00t!! */
1697 if (ma->dont_decode == 0)
1698 rc = mime_decode_now (content,
1704 break; /* Give us the chance, maybe theres another one. */
1706 if (rc == 0) text_content = (char *)content;
1708 text_content = decoded;
1709 length = bytes_decoded;
1712 if (text_content[length-1] != '\n') {
1715 cprintf("Content-type: %s", cbtype);
1716 if (!IsEmptyStr(cbcharset)) {
1717 cprintf("; charset=%s", cbcharset);
1719 cprintf("\nContent-length: %d\n",
1720 (int)(length + add_newline) );
1721 if (!IsEmptyStr(encoding)) {
1722 cprintf("Content-transfer-encoding: %s\n", encoding);
1725 cprintf("Content-transfer-encoding: 7bit\n");
1727 cprintf("X-Citadel-MSG4-Partnum: %s\n", partnum);
1729 if (client_write(text_content, length) == -1)
1731 MSGM_syslog(LOG_ERR, "output_preferred(): aborting due to write failure.\n");
1734 if (add_newline) cprintf("\n");
1735 if (decoded != NULL) free(decoded);
1740 /* No translations required or possible: output as text/plain */
1741 cprintf("Content-type: text/plain\n\n");
1743 if (ma->dont_decode == 0)
1744 rc = mime_decode_now (content,
1750 return; /* Give us the chance, maybe theres another one. */
1752 if (rc == 0) text_content = (char *)content;
1754 text_content = decoded;
1755 length = bytes_decoded;
1758 fixed_output(name, filename, partnum, disp, text_content, cbtype, cbcharset,
1759 length, encoding, cbid, cbuserdata);
1760 if (decoded != NULL) free(decoded);
1765 char desired_section[64];
1772 * Callback function for
1774 void extract_encapsulated_message(char *name, char *filename, char *partnum, char *disp,
1775 void *content, char *cbtype, char *cbcharset, size_t length,
1776 char *encoding, char *cbid, void *cbuserdata)
1778 struct encapmsg *encap;
1780 encap = (struct encapmsg *)cbuserdata;
1782 /* Only proceed if this is the desired section... */
1783 if (!strcasecmp(encap->desired_section, partnum)) {
1784 encap->msglen = length;
1785 encap->msg = malloc(length + 2);
1786 memcpy(encap->msg, content, length);
1793 * Determine whether the specified message exists in the cached_msglist
1794 * (This is a security check)
1796 int check_cached_msglist(long msgnum) {
1797 struct CitContext *CCC = CC;
1799 /* cases in which we skip the check */
1800 if (!CCC) return om_ok; /* not a session */
1801 if (CCC->client_socket <= 0) return om_ok; /* not a client session */
1802 if (CCC->cached_msglist == NULL) return om_access_denied; /* no msglist fetched */
1803 if (CCC->cached_num_msgs == 0) return om_access_denied; /* nothing to check */
1806 /* Do a binary search within the cached_msglist for the requested msgnum */
1808 int max = (CC->cached_num_msgs - 1);
1810 while (max >= min) {
1811 int middle = min + (max-min) / 2 ;
1812 if (msgnum == CCC->cached_msglist[middle]) {
1815 if (msgnum > CC->cached_msglist[middle]) {
1823 return om_access_denied;
1828 * Determine whether the currently logged in session has permission to read
1829 * messages in the current room.
1831 int CtdlDoIHavePermissionToReadMessagesInThisRoom(void) {
1832 if ( (!(CC->logged_in))
1833 && (!(CC->internal_pgm))
1834 && (!config.c_guest_logins)
1836 return(om_not_logged_in);
1843 * Get a message off disk. (returns om_* values found in msgbase.h)
1846 int CtdlOutputMsg(long msg_num, /* message number (local) to fetch */
1847 int mode, /* how would you like that message? */
1848 int headers_only, /* eschew the message body? */
1849 int do_proto, /* do Citadel protocol responses? */
1850 int crlf, /* Use CRLF newlines instead of LF? */
1851 char *section, /* NULL or a message/rfc822 section */
1852 int flags, /* various flags; see msgbase.h */
1856 struct CitContext *CCC = CC;
1857 struct CtdlMessage *TheMessage = NULL;
1858 int retcode = CIT_OK;
1859 struct encapmsg encap;
1862 MSG_syslog(LOG_DEBUG, "CtdlOutputMsg(msgnum=%ld, mode=%d, section=%s)\n",
1864 (section ? section : "<>")
1867 r = CtdlDoIHavePermissionToReadMessagesInThisRoom();
1870 if (r == om_not_logged_in) {
1871 cprintf("%d Not logged in.\n", ERROR + NOT_LOGGED_IN);
1874 cprintf("%d An unknown error has occurred.\n", ERROR);
1881 * Check to make sure the message is actually IN this room
1883 r = check_cached_msglist(msg_num);
1884 if (r == om_access_denied) {
1885 /* Not in the cache? We get ONE shot to check it again. */
1886 CtdlForEachMessage(MSGS_ALL, 0L, NULL, NULL, NULL, NULL, NULL);
1887 r = check_cached_msglist(msg_num);
1890 MSG_syslog(LOG_DEBUG, "Security check fail: message %ld is not in %s\n",
1891 msg_num, CCC->room.QRname
1894 if (r == om_access_denied) {
1895 cprintf("%d message %ld was not found in this room\n",
1896 ERROR + HIGHER_ACCESS_REQUIRED,
1905 * Fetch the message from disk. If we're in HEADERS_FAST mode,
1906 * request that we don't even bother loading the body into memory.
1908 if (headers_only == HEADERS_FAST) {
1909 TheMessage = CtdlFetchMessage(msg_num, 0);
1912 TheMessage = CtdlFetchMessage(msg_num, 1);
1915 if (TheMessage == NULL) {
1916 if (do_proto) cprintf("%d Can't locate msg %ld on disk\n",
1917 ERROR + MESSAGE_NOT_FOUND, msg_num);
1918 return(om_no_such_msg);
1921 /* Here is the weird form of this command, to process only an
1922 * encapsulated message/rfc822 section.
1924 if (section) if (!IsEmptyStr(section)) if (strcmp(section, "0")) {
1925 memset(&encap, 0, sizeof encap);
1926 safestrncpy(encap.desired_section, section, sizeof encap.desired_section);
1927 mime_parser(TheMessage->cm_fields[eMesageText],
1929 *extract_encapsulated_message,
1930 NULL, NULL, (void *)&encap, 0
1933 if ((Author != NULL) && (*Author == NULL))
1935 *Author = TheMessage->cm_fields[eAuthor];
1936 TheMessage->cm_fields[eAuthor] = NULL;
1938 if ((Address != NULL) && (*Address == NULL))
1940 *Address = TheMessage->cm_fields[erFc822Addr];
1941 TheMessage->cm_fields[erFc822Addr] = NULL;
1943 CtdlFreeMessage(TheMessage);
1947 encap.msg[encap.msglen] = 0;
1948 TheMessage = convert_internet_message(encap.msg);
1949 encap.msg = NULL; /* no free() here, TheMessage owns it now */
1951 /* Now we let it fall through to the bottom of this
1952 * function, because TheMessage now contains the
1953 * encapsulated message instead of the top-level
1954 * message. Isn't that neat?
1959 cprintf("%d msg %ld has no part %s\n",
1960 ERROR + MESSAGE_NOT_FOUND,
1964 retcode = om_no_such_msg;
1969 /* Ok, output the message now */
1970 if (retcode == CIT_OK)
1971 retcode = CtdlOutputPreLoadedMsg(TheMessage, mode, headers_only, do_proto, crlf, flags);
1972 if ((Author != NULL) && (*Author == NULL))
1974 *Author = TheMessage->cm_fields[eAuthor];
1975 TheMessage->cm_fields[eAuthor] = NULL;
1977 if ((Address != NULL) && (*Address == NULL))
1979 *Address = TheMessage->cm_fields[erFc822Addr];
1980 TheMessage->cm_fields[erFc822Addr] = NULL;
1983 CtdlFreeMessage(TheMessage);
1989 char *qp_encode_email_addrs(char *source)
1991 struct CitContext *CCC = CC;
1992 char *user, *node, *name;
1993 const char headerStr[] = "=?UTF-8?Q?";
1997 int need_to_encode = 0;
2003 long nAddrPtrMax = 50;
2008 if (source == NULL) return source;
2009 if (IsEmptyStr(source)) return source;
2010 if (MessageDebugEnabled != 0) cit_backtrace();
2011 MSG_syslog(LOG_DEBUG, "qp_encode_email_addrs: [%s]\n", source);
2013 AddrPtr = malloc (sizeof (long) * nAddrPtrMax);
2014 AddrUtf8 = malloc (sizeof (long) * nAddrPtrMax);
2015 memset(AddrUtf8, 0, sizeof (long) * nAddrPtrMax);
2018 while (!IsEmptyStr (&source[i])) {
2019 if (nColons >= nAddrPtrMax){
2022 ptr = (long *) malloc(sizeof (long) * nAddrPtrMax * 2);
2023 memcpy (ptr, AddrPtr, sizeof (long) * nAddrPtrMax);
2024 free (AddrPtr), AddrPtr = ptr;
2026 ptr = (long *) malloc(sizeof (long) * nAddrPtrMax * 2);
2027 memset(&ptr[nAddrPtrMax], 0,
2028 sizeof (long) * nAddrPtrMax);
2030 memcpy (ptr, AddrUtf8, sizeof (long) * nAddrPtrMax);
2031 free (AddrUtf8), AddrUtf8 = ptr;
2034 if (((unsigned char) source[i] < 32) ||
2035 ((unsigned char) source[i] > 126)) {
2037 AddrUtf8[nColons] = 1;
2039 if (source[i] == '"')
2040 InQuotes = !InQuotes;
2041 if (!InQuotes && source[i] == ',') {
2042 AddrPtr[nColons] = i;
2047 if (need_to_encode == 0) {
2054 EncodedMaxLen = nColons * (sizeof(headerStr) + 3) + SourceLen * 3;
2055 Encoded = (char*) malloc (EncodedMaxLen);
2057 for (i = 0; i < nColons; i++)
2058 source[AddrPtr[i]++] = '\0';
2059 /* TODO: if libidn, this might get larger*/
2060 user = malloc(SourceLen + 1);
2061 node = malloc(SourceLen + 1);
2062 name = malloc(SourceLen + 1);
2066 for (i = 0; i < nColons && nPtr != NULL; i++) {
2067 nmax = EncodedMaxLen - (nPtr - Encoded);
2069 process_rfc822_addr(&source[AddrPtr[i]],
2073 /* TODO: libIDN here ! */
2074 if (IsEmptyStr(name)) {
2075 n = snprintf(nPtr, nmax,
2076 (i==0)?"%s@%s" : ",%s@%s",
2080 EncodedName = rfc2047encode(name, strlen(name));
2081 n = snprintf(nPtr, nmax,
2082 (i==0)?"%s <%s@%s>" : ",%s <%s@%s>",
2083 EncodedName, user, node);
2088 n = snprintf(nPtr, nmax,
2089 (i==0)?"%s" : ",%s",
2090 &source[AddrPtr[i]]);
2096 ptr = (char*) malloc(EncodedMaxLen * 2);
2097 memcpy(ptr, Encoded, EncodedMaxLen);
2098 nnPtr = ptr + (nPtr - Encoded), nPtr = nnPtr;
2099 free(Encoded), Encoded = ptr;
2101 i--; /* do it once more with properly lengthened buffer */
2104 for (i = 0; i < nColons; i++)
2105 source[--AddrPtr[i]] = ',';
2116 /* If the last item in a list of recipients was truncated to a partial address,
2117 * remove it completely in order to avoid choking libSieve
2119 void sanitize_truncated_recipient(char *str)
2122 if (num_tokens(str, ',') < 2) return;
2124 int len = strlen(str);
2125 if (len < 900) return;
2126 if (len > 998) str[998] = 0;
2128 char *cptr = strrchr(str, ',');
2131 char *lptr = strchr(cptr, '<');
2132 char *rptr = strchr(cptr, '>');
2134 if ( (lptr) && (rptr) && (rptr > lptr) ) return;
2140 void OutputCtdlMsgHeaders(
2141 struct CtdlMessage *TheMessage,
2142 int do_proto) /* do Citadel protocol responses? */
2147 char display_name[256];
2149 /* begin header processing loop for Citadel message format */
2150 safestrncpy(display_name, "<unknown>", sizeof display_name);
2151 if (TheMessage->cm_fields[eAuthor]) {
2152 strcpy(buf, TheMessage->cm_fields[eAuthor]);
2153 if (TheMessage->cm_anon_type == MES_ANONONLY) {
2154 safestrncpy(display_name, "****", sizeof display_name);
2156 else if (TheMessage->cm_anon_type == MES_ANONOPT) {
2157 safestrncpy(display_name, "anonymous", sizeof display_name);
2160 safestrncpy(display_name, buf, sizeof display_name);
2162 if ((is_room_aide())
2163 && ((TheMessage->cm_anon_type == MES_ANONONLY)
2164 || (TheMessage->cm_anon_type == MES_ANONOPT))) {
2165 size_t tmp = strlen(display_name);
2166 snprintf(&display_name[tmp],
2167 sizeof display_name - tmp,
2172 /* Don't show Internet address for users on the
2173 * local Citadel network.
2176 if (TheMessage->cm_fields[eNodeName] != NULL)
2177 if (!IsEmptyStr(TheMessage->cm_fields[eNodeName]))
2178 if (haschar(TheMessage->cm_fields[eNodeName], '.') == 0) {
2182 /* Now spew the header fields in the order we like them. */
2183 for (i=0; i< NDiskFields; ++i) {
2185 Field = FieldOrder[i];
2186 if (Field != eMesageText) {
2187 if ( (TheMessage->cm_fields[Field] != NULL)
2188 && (msgkeys[Field] != NULL) ) {
2189 if ((Field == eenVelopeTo) ||
2190 (Field == eRecipient) ||
2191 (Field == eCarbonCopY)) {
2192 sanitize_truncated_recipient(TheMessage->cm_fields[Field]);
2194 if (Field == eAuthor) {
2195 if (do_proto) cprintf("%s=%s\n",
2199 else if ((Field == erFc822Addr) && (suppress_f)) {
2202 /* Masquerade display name if needed */
2204 if (do_proto) cprintf("%s=%s\n",
2206 TheMessage->cm_fields[Field]
2215 void OutputRFC822MsgHeaders(
2216 struct CtdlMessage *TheMessage,
2217 int flags, /* should the bessage be exported clean */
2219 char *mid, long sizeof_mid,
2220 char *suser, long sizeof_suser,
2221 char *luser, long sizeof_luser,
2222 char *fuser, long sizeof_fuser,
2223 char *snode, long sizeof_snode)
2225 char datestamp[100];
2226 int subject_found = 0;
2233 for (i = 0; i < 256; ++i) {
2234 if (TheMessage->cm_fields[i]) {
2235 mptr = mpptr = TheMessage->cm_fields[i];
2238 safestrncpy(luser, mptr, sizeof_luser);
2239 safestrncpy(suser, mptr, sizeof_suser);
2241 else if (i == 'Y') {
2242 if ((flags & QP_EADDR) != 0) {
2243 mptr = qp_encode_email_addrs(mptr);
2245 sanitize_truncated_recipient(mptr);
2246 cprintf("CC: %s%s", mptr, nl);
2248 else if (i == 'P') {
2249 cprintf("Return-Path: %s%s", mptr, nl);
2251 else if (i == eListID) {
2252 cprintf("List-ID: %s%s", mptr, nl);
2254 else if (i == 'V') {
2255 if ((flags & QP_EADDR) != 0)
2256 mptr = qp_encode_email_addrs(mptr);
2258 while ((*hptr != '\0') && isspace(*hptr))
2260 if (!IsEmptyStr(hptr))
2261 cprintf("Envelope-To: %s%s", hptr, nl);
2263 else if (i == 'U') {
2264 cprintf("Subject: %s%s", mptr, nl);
2268 safestrncpy(mid, mptr, sizeof_mid); /// TODO: detect @ here and copy @nodename in if not found.
2269 else if (i == erFc822Addr)
2270 safestrncpy(fuser, mptr, sizeof_fuser);
2271 /* else if (i == 'O')
2272 cprintf("X-Citadel-Room: %s%s",
2275 safestrncpy(snode, mptr, sizeof_snode);
2278 if (haschar(mptr, '@') == 0)
2280 sanitize_truncated_recipient(mptr);
2281 cprintf("To: %s@%s", mptr, config.c_fqdn);
2286 if ((flags & QP_EADDR) != 0) {
2287 mptr = qp_encode_email_addrs(mptr);
2289 sanitize_truncated_recipient(mptr);
2290 cprintf("To: %s", mptr);
2294 else if (i == 'T') {
2295 datestring(datestamp, sizeof datestamp,
2296 atol(mptr), DATESTRING_RFC822);
2297 cprintf("Date: %s%s", datestamp, nl);
2299 else if (i == 'W') {
2300 cprintf("References: ");
2301 k = num_tokens(mptr, '|');
2302 for (j=0; j<k; ++j) {
2303 extract_token(buf, mptr, j, '|', sizeof buf);
2304 cprintf("<%s>", buf);
2313 else if (i == eReplyTo) {
2315 while ((*hptr != '\0') && isspace(*hptr))
2317 if (!IsEmptyStr(hptr))
2318 cprintf("Reply-To: %s%s", mptr, nl);
2324 if (subject_found == 0) {
2325 cprintf("Subject: (no subject)%s", nl);
2330 void Dump_RFC822HeadersBody(
2331 struct CtdlMessage *TheMessage,
2332 int headers_only, /* eschew the message body? */
2333 int flags, /* should the bessage be exported clean? */
2337 cit_uint8_t prev_ch;
2339 const char *StartOfText = StrBufNOTNULL;
2342 int nllen = strlen(nl);
2345 mptr = TheMessage->cm_fields[eMesageText];
2349 while (*mptr != '\0') {
2350 if (*mptr == '\r') {
2357 eoh = (*(mptr+1) == '\r') && (*(mptr+2) == '\n');
2359 eoh = *(mptr+1) == '\n';
2363 StartOfText = strchr(StartOfText, '\n');
2364 StartOfText = strchr(StartOfText, '\n');
2367 if (((headers_only == HEADERS_NONE) && (mptr >= StartOfText)) ||
2368 ((headers_only == HEADERS_ONLY) && (mptr < StartOfText)) ||
2369 ((headers_only != HEADERS_NONE) &&
2370 (headers_only != HEADERS_ONLY))
2372 if (*mptr == '\n') {
2373 memcpy(&outbuf[outlen], nl, nllen);
2375 outbuf[outlen] = '\0';
2378 outbuf[outlen++] = *mptr;
2382 if (flags & ESC_DOT)
2384 if ((prev_ch == '\n') &&
2386 ((*(mptr+1) == '\r') || (*(mptr+1) == '\n')))
2388 outbuf[outlen++] = '.';
2393 if (outlen > 1000) {
2394 if (client_write(outbuf, outlen) == -1)
2396 struct CitContext *CCC = CC;
2397 MSGM_syslog(LOG_ERR, "Dump_RFC822HeadersBody(): aborting due to write failure.\n");
2404 client_write(outbuf, outlen);
2410 /* If the format type on disk is 1 (fixed-format), then we want
2411 * everything to be output completely literally ... regardless of
2412 * what message transfer format is in use.
2414 void DumpFormatFixed(
2415 struct CtdlMessage *TheMessage,
2416 int mode, /* how would you like that message? */
2423 int nllen = strlen (nl);
2426 mptr = TheMessage->cm_fields[eMesageText];
2428 if (mode == MT_MIME) {
2429 cprintf("Content-type: text/plain\n\n");
2433 while (ch = *mptr++, ch > 0) {
2437 if ((buflen > 250) && (!xlline)){
2441 while ((buflen > 0) &&
2442 (!isspace(buf[buflen])))
2448 mptr -= tbuflen - buflen;
2453 /* if we reach the outer bounds of our buffer,
2454 abort without respect what whe purge. */
2457 (buflen > SIZ - nllen - 2)))
2461 memcpy (&buf[buflen], nl, nllen);
2465 if (client_write(buf, buflen) == -1)
2467 struct CitContext *CCC = CC;
2468 MSGM_syslog(LOG_ERR, "DumpFormatFixed(): aborting due to write failure.\n");
2480 if (!IsEmptyStr(buf))
2481 cprintf("%s%s", buf, nl);
2485 * Get a message off disk. (returns om_* values found in msgbase.h)
2487 int CtdlOutputPreLoadedMsg(
2488 struct CtdlMessage *TheMessage,
2489 int mode, /* how would you like that message? */
2490 int headers_only, /* eschew the message body? */
2491 int do_proto, /* do Citadel protocol responses? */
2492 int crlf, /* Use CRLF newlines instead of LF? */
2493 int flags /* should the bessage be exported clean? */
2495 struct CitContext *CCC = CC;
2498 const char *nl; /* newline string */
2501 /* Buffers needed for RFC822 translation. These are all filled
2502 * using functions that are bounds-checked, and therefore we can
2503 * make them substantially smaller than SIZ.
2511 MSG_syslog(LOG_DEBUG, "CtdlOutputPreLoadedMsg(TheMessage=%s, %d, %d, %d, %d\n",
2512 ((TheMessage == NULL) ? "NULL" : "not null"),
2513 mode, headers_only, do_proto, crlf);
2515 strcpy(mid, "unknown");
2516 nl = (crlf ? "\r\n" : "\n");
2518 if (!is_valid_message(TheMessage)) {
2519 MSGM_syslog(LOG_ERR,
2520 "ERROR: invalid preloaded message for output\n");
2522 return(om_no_such_msg);
2525 /* Suppress envelope recipients if required to avoid disclosing BCC addresses.
2526 * Pad it with spaces in order to avoid changing the RFC822 length of the message.
2528 if ( (flags & SUPPRESS_ENV_TO) && (TheMessage->cm_fields[eenVelopeTo] != NULL) ) {
2529 memset(TheMessage->cm_fields[eenVelopeTo], ' ', strlen(TheMessage->cm_fields[eenVelopeTo]));
2532 /* Are we downloading a MIME component? */
2533 if (mode == MT_DOWNLOAD) {
2534 if (TheMessage->cm_format_type != FMT_RFC822) {
2536 cprintf("%d This is not a MIME message.\n",
2537 ERROR + ILLEGAL_VALUE);
2538 } else if (CCC->download_fp != NULL) {
2539 if (do_proto) cprintf(
2540 "%d You already have a download open.\n",
2541 ERROR + RESOURCE_BUSY);
2543 /* Parse the message text component */
2544 mptr = TheMessage->cm_fields[eMesageText];
2545 mime_parser(mptr, NULL, *mime_download, NULL, NULL, NULL, 0);
2546 /* If there's no file open by this time, the requested
2547 * section wasn't found, so print an error
2549 if (CCC->download_fp == NULL) {
2550 if (do_proto) cprintf(
2551 "%d Section %s not found.\n",
2552 ERROR + FILE_NOT_FOUND,
2553 CCC->download_desired_section);
2556 return((CCC->download_fp != NULL) ? om_ok : om_mime_error);
2559 /* MT_SPEW_SECTION is like MT_DOWNLOAD except it outputs the whole MIME part
2560 * in a single server operation instead of opening a download file.
2562 if (mode == MT_SPEW_SECTION) {
2563 if (TheMessage->cm_format_type != FMT_RFC822) {
2565 cprintf("%d This is not a MIME message.\n",
2566 ERROR + ILLEGAL_VALUE);
2568 /* Parse the message text component */
2571 mptr = TheMessage->cm_fields[eMesageText];
2572 mime_parser(mptr, NULL, *mime_spew_section, NULL, NULL, (void *)&found_it, 0);
2573 /* If section wasn't found, print an error
2576 if (do_proto) cprintf(
2577 "%d Section %s not found.\n",
2578 ERROR + FILE_NOT_FOUND,
2579 CCC->download_desired_section);
2582 return((CCC->download_fp != NULL) ? om_ok : om_mime_error);
2585 /* now for the user-mode message reading loops */
2586 if (do_proto) cprintf("%d msg:\n", LISTING_FOLLOWS);
2588 /* Does the caller want to skip the headers? */
2589 if (headers_only == HEADERS_NONE) goto START_TEXT;
2591 /* Tell the client which format type we're using. */
2592 if ( (mode == MT_CITADEL) && (do_proto) ) {
2593 cprintf("type=%d\n", TheMessage->cm_format_type);
2596 /* nhdr=yes means that we're only displaying headers, no body */
2597 if ( (TheMessage->cm_anon_type == MES_ANONONLY)
2598 && ((mode == MT_CITADEL) || (mode == MT_MIME))
2601 cprintf("nhdr=yes\n");
2604 if ((mode == MT_CITADEL) || (mode == MT_MIME))
2605 OutputCtdlMsgHeaders(TheMessage, do_proto);
2608 /* begin header processing loop for RFC822 transfer format */
2612 strcpy(snode, NODENAME);
2613 if (mode == MT_RFC822)
2614 OutputRFC822MsgHeaders(
2619 suser, sizeof(suser),
2620 luser, sizeof(luser),
2621 fuser, sizeof(fuser),
2622 snode, sizeof(snode)
2626 for (i=0; !IsEmptyStr(&suser[i]); ++i) {
2627 suser[i] = tolower(suser[i]);
2628 if (!isalnum(suser[i])) suser[i]='_';
2631 if (mode == MT_RFC822) {
2632 if (!strcasecmp(snode, NODENAME)) {
2633 safestrncpy(snode, FQDN, sizeof snode);
2636 /* Construct a fun message id */
2637 cprintf("Message-ID: <%s", mid);/// todo: this possibly breaks threadding mails.
2638 if (strchr(mid, '@')==NULL) {
2639 cprintf("@%s", snode);
2643 if (!is_room_aide() && (TheMessage->cm_anon_type == MES_ANONONLY)) {
2644 cprintf("From: \"----\" <x@x.org>%s", nl);
2646 else if (!is_room_aide() && (TheMessage->cm_anon_type == MES_ANONOPT)) {
2647 cprintf("From: \"anonymous\" <x@x.org>%s", nl);
2649 else if (!IsEmptyStr(fuser)) {
2650 cprintf("From: \"%s\" <%s>%s", luser, fuser, nl);
2653 cprintf("From: \"%s\" <%s@%s>%s", luser, suser, snode, nl);
2656 /* Blank line signifying RFC822 end-of-headers */
2657 if (TheMessage->cm_format_type != FMT_RFC822) {
2662 /* end header processing loop ... at this point, we're in the text */
2664 if (headers_only == HEADERS_FAST) goto DONE;
2666 /* Tell the client about the MIME parts in this message */
2667 if (TheMessage->cm_format_type == FMT_RFC822) {
2668 if ( (mode == MT_CITADEL) || (mode == MT_MIME) ) {
2669 mptr = TheMessage->cm_fields[eMesageText];
2670 memset(&ma, 0, sizeof(struct ma_info));
2671 mime_parser(mptr, NULL,
2672 (do_proto ? *list_this_part : NULL),
2673 (do_proto ? *list_this_pref : NULL),
2674 (do_proto ? *list_this_suff : NULL),
2677 else if (mode == MT_RFC822) { /* unparsed RFC822 dump */
2678 Dump_RFC822HeadersBody(
2687 if (headers_only == HEADERS_ONLY) {
2691 /* signify start of msg text */
2692 if ( (mode == MT_CITADEL) || (mode == MT_MIME) ) {
2693 if (do_proto) cprintf("text\n");
2696 if (TheMessage->cm_format_type == FMT_FIXED)
2699 mode, /* how would you like that message? */
2702 /* If the message on disk is format 0 (Citadel vari-format), we
2703 * output using the formatter at 80 columns. This is the final output
2704 * form if the transfer format is RFC822, but if the transfer format
2705 * is Citadel proprietary, it'll still work, because the indentation
2706 * for new paragraphs is correct and the client will reformat the
2707 * message to the reader's screen width.
2709 if (TheMessage->cm_format_type == FMT_CITADEL) {
2710 mptr = TheMessage->cm_fields[eMesageText];
2712 if (mode == MT_MIME) {
2713 cprintf("Content-type: text/x-citadel-variformat\n\n");
2718 /* If the message on disk is format 4 (MIME), we've gotta hand it
2719 * off to the MIME parser. The client has already been told that
2720 * this message is format 1 (fixed format), so the callback function
2721 * we use will display those parts as-is.
2723 if (TheMessage->cm_format_type == FMT_RFC822) {
2724 memset(&ma, 0, sizeof(struct ma_info));
2726 if (mode == MT_MIME) {
2727 ma.use_fo_hooks = 0;
2728 strcpy(ma.chosen_part, "1");
2729 ma.chosen_pref = 9999;
2730 ma.dont_decode = CCC->msg4_dont_decode;
2731 mime_parser(mptr, NULL,
2732 *choose_preferred, *fixed_output_pre,
2733 *fixed_output_post, (void *)&ma, 1);
2734 mime_parser(mptr, NULL,
2735 *output_preferred, NULL, NULL, (void *)&ma, 1);
2738 ma.use_fo_hooks = 1;
2739 mime_parser(mptr, NULL,
2740 *fixed_output, *fixed_output_pre,
2741 *fixed_output_post, (void *)&ma, 0);
2746 DONE: /* now we're done */
2747 if (do_proto) cprintf("000\n");
2753 * display a message (mode 0 - Citadel proprietary)
2755 void cmd_msg0(char *cmdbuf)
2758 int headers_only = HEADERS_ALL;
2760 msgid = extract_long(cmdbuf, 0);
2761 headers_only = extract_int(cmdbuf, 1);
2763 CtdlOutputMsg(msgid, MT_CITADEL, headers_only, 1, 0, NULL, 0, NULL, NULL);
2769 * display a message (mode 2 - RFC822)
2771 void cmd_msg2(char *cmdbuf)
2774 int headers_only = HEADERS_ALL;
2776 msgid = extract_long(cmdbuf, 0);
2777 headers_only = extract_int(cmdbuf, 1);
2779 CtdlOutputMsg(msgid, MT_RFC822, headers_only, 1, 1, NULL, 0, NULL, NULL);
2785 * display a message (mode 3 - IGnet raw format - internal programs only)
2787 void cmd_msg3(char *cmdbuf)
2790 struct CtdlMessage *msg = NULL;
2793 if (CC->internal_pgm == 0) {
2794 cprintf("%d This command is for internal programs only.\n",
2795 ERROR + HIGHER_ACCESS_REQUIRED);
2799 msgnum = extract_long(cmdbuf, 0);
2800 msg = CtdlFetchMessage(msgnum, 1);
2802 cprintf("%d Message %ld not found.\n",
2803 ERROR + MESSAGE_NOT_FOUND, msgnum);
2807 serialize_message(&smr, msg);
2808 CtdlFreeMessage(msg);
2811 cprintf("%d Unable to serialize message\n",
2812 ERROR + INTERNAL_ERROR);
2816 cprintf("%d %ld\n", BINARY_FOLLOWS, (long)smr.len);
2817 client_write((char *)smr.ser, (int)smr.len);
2824 * Display a message using MIME content types
2826 void cmd_msg4(char *cmdbuf)
2831 msgid = extract_long(cmdbuf, 0);
2832 extract_token(section, cmdbuf, 1, '|', sizeof section);
2833 CtdlOutputMsg(msgid, MT_MIME, 0, 1, 0, (section[0] ? section : NULL) , 0, NULL, NULL);
2839 * Client tells us its preferred message format(s)
2841 void cmd_msgp(char *cmdbuf)
2843 if (!strcasecmp(cmdbuf, "dont_decode")) {
2844 CC->msg4_dont_decode = 1;
2845 cprintf("%d MSG4 will not pre-decode messages.\n", CIT_OK);
2848 safestrncpy(CC->preferred_formats, cmdbuf, sizeof(CC->preferred_formats));
2849 cprintf("%d Preferred MIME formats have been set.\n", CIT_OK);
2855 * Open a component of a MIME message as a download file
2857 void cmd_opna(char *cmdbuf)
2860 char desired_section[128];
2862 msgid = extract_long(cmdbuf, 0);
2863 extract_token(desired_section, cmdbuf, 1, '|', sizeof desired_section);
2864 safestrncpy(CC->download_desired_section, desired_section,
2865 sizeof CC->download_desired_section);
2866 CtdlOutputMsg(msgid, MT_DOWNLOAD, 0, 1, 1, NULL, 0, NULL, NULL);
2871 * Open a component of a MIME message and transmit it all at once
2873 void cmd_dlat(char *cmdbuf)
2876 char desired_section[128];
2878 msgid = extract_long(cmdbuf, 0);
2879 extract_token(desired_section, cmdbuf, 1, '|', sizeof desired_section);
2880 safestrncpy(CC->download_desired_section, desired_section,
2881 sizeof CC->download_desired_section);
2882 CtdlOutputMsg(msgid, MT_SPEW_SECTION, 0, 1, 1, NULL, 0, NULL, NULL);
2887 * Save one or more message pointers into a specified room
2888 * (Returns 0 for success, nonzero for failure)
2889 * roomname may be NULL to use the current room
2891 * Note that the 'supplied_msg' field may be set to NULL, in which case
2892 * the message will be fetched from disk, by number, if we need to perform
2893 * replication checks. This adds an additional database read, so if the
2894 * caller already has the message in memory then it should be supplied. (Obviously
2895 * this mode of operation only works if we're saving a single message.)
2897 int CtdlSaveMsgPointersInRoom(char *roomname, long newmsgidlist[], int num_newmsgs,
2898 int do_repl_check, struct CtdlMessage *supplied_msg, int suppress_refcount_adj
2900 struct CitContext *CCC = CC;
2902 char hold_rm[ROOMNAMELEN];
2903 struct cdbdata *cdbfr;
2906 long highest_msg = 0L;
2909 struct CtdlMessage *msg = NULL;
2911 long *msgs_to_be_merged = NULL;
2912 int num_msgs_to_be_merged = 0;
2914 MSG_syslog(LOG_DEBUG,
2915 "CtdlSaveMsgPointersInRoom(room=%s, num_msgs=%d, repl=%d, suppress_rca=%d)\n",
2916 roomname, num_newmsgs, do_repl_check, suppress_refcount_adj
2919 strcpy(hold_rm, CCC->room.QRname);
2922 if (newmsgidlist == NULL) return(ERROR + INTERNAL_ERROR);
2923 if (num_newmsgs < 1) return(ERROR + INTERNAL_ERROR);
2924 if (num_newmsgs > 1) supplied_msg = NULL;
2926 /* Now the regular stuff */
2927 if (CtdlGetRoomLock(&CCC->room,
2928 ((roomname != NULL) ? roomname : CCC->room.QRname) )
2930 MSG_syslog(LOG_ERR, "No such room <%s>\n", roomname);
2931 return(ERROR + ROOM_NOT_FOUND);
2935 msgs_to_be_merged = malloc(sizeof(long) * num_newmsgs);
2936 num_msgs_to_be_merged = 0;
2939 cdbfr = cdb_fetch(CDB_MSGLISTS, &CCC->room.QRnumber, sizeof(long));
2940 if (cdbfr == NULL) {
2944 msglist = (long *) cdbfr->ptr;
2945 cdbfr->ptr = NULL; /* CtdlSaveMsgPointerInRoom() now owns this memory */
2946 num_msgs = cdbfr->len / sizeof(long);
2951 /* Create a list of msgid's which were supplied by the caller, but do
2952 * not already exist in the target room. It is absolutely taboo to
2953 * have more than one reference to the same message in a room.
2955 for (i=0; i<num_newmsgs; ++i) {
2957 if (num_msgs > 0) for (j=0; j<num_msgs; ++j) {
2958 if (msglist[j] == newmsgidlist[i]) {
2963 msgs_to_be_merged[num_msgs_to_be_merged++] = newmsgidlist[i];
2967 MSG_syslog(LOG_DEBUG, "%d unique messages to be merged\n", num_msgs_to_be_merged);
2970 * Now merge the new messages
2972 msglist = realloc(msglist, (sizeof(long) * (num_msgs + num_msgs_to_be_merged)) );
2973 if (msglist == NULL) {
2974 MSGM_syslog(LOG_ALERT, "ERROR: can't realloc message list!\n");
2975 free(msgs_to_be_merged);
2976 return (ERROR + INTERNAL_ERROR);
2978 memcpy(&msglist[num_msgs], msgs_to_be_merged, (sizeof(long) * num_msgs_to_be_merged) );
2979 num_msgs += num_msgs_to_be_merged;
2981 /* Sort the message list, so all the msgid's are in order */
2982 num_msgs = sort_msglist(msglist, num_msgs);
2984 /* Determine the highest message number */
2985 highest_msg = msglist[num_msgs - 1];
2987 /* Write it back to disk. */
2988 cdb_store(CDB_MSGLISTS, &CCC->room.QRnumber, (int)sizeof(long),
2989 msglist, (int)(num_msgs * sizeof(long)));
2991 /* Free up the memory we used. */
2994 /* Update the highest-message pointer and unlock the room. */
2995 CCC->room.QRhighest = highest_msg;
2996 CtdlPutRoomLock(&CCC->room);
2998 /* Perform replication checks if necessary */
2999 if ( (DoesThisRoomNeedEuidIndexing(&CCC->room)) && (do_repl_check) ) {
3000 MSGM_syslog(LOG_DEBUG, "CtdlSaveMsgPointerInRoom() doing repl checks\n");
3002 for (i=0; i<num_msgs_to_be_merged; ++i) {
3003 msgid = msgs_to_be_merged[i];
3005 if (supplied_msg != NULL) {
3009 msg = CtdlFetchMessage(msgid, 0);
3013 ReplicationChecks(msg);
3015 /* If the message has an Exclusive ID, index that... */
3016 if (msg->cm_fields[eExclusiveID] != NULL) {
3017 index_message_by_euid(msg->cm_fields[eExclusiveID], &CCC->room, msgid);
3020 /* Free up the memory we may have allocated */
3021 if (msg != supplied_msg) {
3022 CtdlFreeMessage(msg);
3030 MSGM_syslog(LOG_DEBUG, "CtdlSaveMsgPointerInRoom() skips repl checks\n");
3033 /* Submit this room for processing by hooks */
3034 PerformRoomHooks(&CCC->room);
3036 /* Go back to the room we were in before we wandered here... */
3037 CtdlGetRoom(&CCC->room, hold_rm);
3039 /* Bump the reference count for all messages which were merged */
3040 if (!suppress_refcount_adj) {
3041 AdjRefCountList(msgs_to_be_merged, num_msgs_to_be_merged, +1);
3044 /* Free up memory... */
3045 if (msgs_to_be_merged != NULL) {
3046 free(msgs_to_be_merged);
3049 /* Return success. */
3055 * This is the same as CtdlSaveMsgPointersInRoom() but it only accepts
3058 int CtdlSaveMsgPointerInRoom(char *roomname, long msgid,
3059 int do_repl_check, struct CtdlMessage *supplied_msg)
3061 return CtdlSaveMsgPointersInRoom(roomname, &msgid, 1, do_repl_check, supplied_msg, 0);
3068 * Message base operation to save a new message to the message store
3069 * (returns new message number)
3071 * This is the back end for CtdlSubmitMsg() and should not be directly
3072 * called by server-side modules.
3075 long send_message(struct CtdlMessage *msg) {
3076 struct CitContext *CCC = CC;
3085 /* Get a new message number */
3086 newmsgid = get_new_message_number();
3087 msgidbuflen = snprintf(msgidbuf, sizeof msgidbuf, "%08lX-%08lX@%s",
3088 (long unsigned int) time(NULL),
3089 (long unsigned int) newmsgid,
3093 /* Generate an ID if we don't have one already */
3094 if (msg->cm_fields[emessageId]==NULL) {
3095 CM_SetField(msg, emessageId, msgidbuf, msgidbuflen);
3098 /* If the message is big, set its body aside for storage elsewhere */
3099 if (msg->cm_fields[eMesageText] != NULL) {
3100 if (strlen(msg->cm_fields[eMesageText]) > BIGMSG) {
3102 holdM = msg->cm_fields[eMesageText];
3103 msg->cm_fields[eMesageText] = NULL;
3107 /* Serialize our data structure for storage in the database */
3108 serialize_message(&smr, msg);
3111 msg->cm_fields[eMesageText] = holdM;
3115 cprintf("%d Unable to serialize message\n",
3116 ERROR + INTERNAL_ERROR);
3120 /* Write our little bundle of joy into the message base */
3121 if (cdb_store(CDB_MSGMAIN, &newmsgid, (int)sizeof(long),
3122 smr.ser, smr.len) < 0) {
3123 MSGM_syslog(LOG_ERR, "Can't store message\n");
3127 cdb_store(CDB_BIGMSGS,
3137 /* Free the memory we used for the serialized message */
3140 /* Return the *local* message ID to the caller
3141 * (even if we're storing an incoming network message)
3149 * Serialize a struct CtdlMessage into the format used on disk and network.
3151 * This function loads up a "struct ser_ret" (defined in server.h) which
3152 * contains the length of the serialized message and a pointer to the
3153 * serialized message in memory. THE LATTER MUST BE FREED BY THE CALLER.
3155 void serialize_message(struct ser_ret *ret, /* return values */
3156 struct CtdlMessage *msg) /* unserialized msg */
3158 struct CitContext *CCC = CC;
3159 size_t wlen, fieldlen;
3161 long lengths[NDiskFields];
3163 memset(lengths, 0, sizeof(lengths));
3166 * Check for valid message format
3168 if (is_valid_message(msg) == 0) {
3169 MSGM_syslog(LOG_ERR, "serialize_message() aborting due to invalid message\n");
3176 for (i=0; i < NDiskFields; ++i)
3177 if (msg->cm_fields[FieldOrder[i]] != NULL)
3179 lengths[i] = strlen(msg->cm_fields[FieldOrder[i]]);
3180 ret->len += lengths[i] + 2;
3183 ret->ser = malloc(ret->len);
3184 if (ret->ser == NULL) {
3185 MSG_syslog(LOG_ERR, "serialize_message() malloc(%ld) failed: %s\n",
3186 (long)ret->len, strerror(errno));
3193 ret->ser[1] = msg->cm_anon_type;
3194 ret->ser[2] = msg->cm_format_type;
3197 for (i=0; i < NDiskFields; ++i)
3198 if (msg->cm_fields[FieldOrder[i]] != NULL)
3200 fieldlen = lengths[i];
3201 ret->ser[wlen++] = (char)FieldOrder[i];
3203 memcpy(&ret->ser[wlen],
3204 msg->cm_fields[FieldOrder[i]],
3207 wlen = wlen + fieldlen + 1;
3210 if (ret->len != wlen) {
3211 MSG_syslog(LOG_ERR, "ERROR: len=%ld wlen=%ld\n",
3212 (long)ret->len, (long)wlen);
3220 * Check to see if any messages already exist in the current room which
3221 * carry the same Exclusive ID as this one. If any are found, delete them.
3223 void ReplicationChecks(struct CtdlMessage *msg) {
3224 struct CitContext *CCC = CC;
3225 long old_msgnum = (-1L);
3227 if (DoesThisRoomNeedEuidIndexing(&CCC->room) == 0) return;
3229 MSG_syslog(LOG_DEBUG, "Performing replication checks in <%s>\n",
3232 /* No exclusive id? Don't do anything. */
3233 if (msg == NULL) return;
3234 if (msg->cm_fields[eExclusiveID] == NULL) return;
3235 if (IsEmptyStr(msg->cm_fields[eExclusiveID])) return;
3236 /*MSG_syslog(LOG_DEBUG, "Exclusive ID: <%s> for room <%s>\n",
3237 msg->cm_fields[eExclusiveID], CCC->room.QRname);*/
3239 old_msgnum = CtdlLocateMessageByEuid(msg->cm_fields[eExclusiveID], &CCC->room);
3240 if (old_msgnum > 0L) {
3241 MSG_syslog(LOG_DEBUG, "ReplicationChecks() replacing message %ld\n", old_msgnum);
3242 CtdlDeleteMessages(CCC->room.QRname, &old_msgnum, 1, "");
3249 * Save a message to disk and submit it into the delivery system.
3251 long CtdlSubmitMsg(struct CtdlMessage *msg, /* message to save */
3252 struct recptypes *recps, /* recipients (if mail) */
3253 const char *force, /* force a particular room? */
3254 int flags /* should the message be exported clean? */
3257 char submit_filename[128];
3258 char hold_rm[ROOMNAMELEN];
3259 char actual_rm[ROOMNAMELEN];
3260 char force_room[ROOMNAMELEN];
3261 char content_type[SIZ]; /* We have to learn this */
3262 char recipient[SIZ];
3265 const char *mptr = NULL;
3266 struct ctdluser userbuf;
3268 struct MetaData smi;
3269 FILE *network_fp = NULL;
3270 static int seqnum = 1;
3271 struct CtdlMessage *imsg = NULL;
3273 size_t instr_alloc = 0;
3275 char *hold_R, *hold_D;
3276 char *collected_addresses = NULL;
3277 struct addresses_to_be_filed *aptr = NULL;
3278 StrBuf *saved_rfc822_version = NULL;
3279 int qualified_for_journaling = 0;
3280 CitContext *CCC = MyContext();
3281 char bounce_to[1024] = "";
3284 MSGM_syslog(LOG_DEBUG, "CtdlSubmitMsg() called\n");
3285 if (is_valid_message(msg) == 0) return(-1); /* self check */
3287 /* If this message has no timestamp, we take the liberty of
3288 * giving it one, right now.
3290 if (msg->cm_fields[eTimestamp] == NULL) {
3291 CM_SetFieldLONG(msg, eTimestamp, time(NULL));
3294 /* If this message has no path, we generate one.
3296 if (msg->cm_fields[eMessagePath] == NULL) {
3297 if (msg->cm_fields[eAuthor] != NULL) {
3298 CM_CopyField(msg, eMessagePath, eAuthor);
3299 for (a=0; !IsEmptyStr(&msg->cm_fields[eMessagePath][a]); ++a) {
3300 if (isspace(msg->cm_fields[eMessagePath][a])) {
3301 msg->cm_fields[eMessagePath][a] = ' ';
3306 CM_SetField(msg,eMessagePath, HKEY("unknown"));
3310 if (force == NULL) {
3311 force_room[0] = '\0';
3314 strcpy(force_room, force);
3317 /* Learn about what's inside, because it's what's inside that counts */
3318 if (msg->cm_fields[eMesageText] == NULL) {
3319 MSGM_syslog(LOG_ERR, "ERROR: attempt to save message with NULL body\n");
3323 switch (msg->cm_format_type) {
3325 strcpy(content_type, "text/x-citadel-variformat");
3328 strcpy(content_type, "text/plain");
3331 strcpy(content_type, "text/plain");
3332 mptr = bmstrcasestr(msg->cm_fields[eMesageText], "Content-type:");
3335 safestrncpy(content_type, &mptr[13], sizeof content_type);
3336 striplt(content_type);
3337 aptr = content_type;
3338 while (!IsEmptyStr(aptr)) {
3350 /* Goto the correct room */
3351 room = (recps) ? CCC->room.QRname : SENTITEMS;
3352 MSG_syslog(LOG_DEBUG, "Selected room %s\n", room);
3353 strcpy(hold_rm, CCC->room.QRname);
3354 strcpy(actual_rm, CCC->room.QRname);
3355 if (recps != NULL) {
3356 strcpy(actual_rm, SENTITEMS);
3359 /* If the user is a twit, move to the twit room for posting */
3361 if (CCC->user.axlevel == AxProbU) {
3362 strcpy(hold_rm, actual_rm);
3363 strcpy(actual_rm, config.c_twitroom);
3364 MSGM_syslog(LOG_DEBUG, "Diverting to twit room\n");
3368 /* ...or if this message is destined for Aide> then go there. */
3369 if (!IsEmptyStr(force_room)) {
3370 strcpy(actual_rm, force_room);
3373 MSG_syslog(LOG_INFO, "Final selection: %s (%s)\n", actual_rm, room);
3374 if (strcasecmp(actual_rm, CCC->room.QRname)) {
3375 /* CtdlGetRoom(&CCC->room, actual_rm); */
3376 CtdlUserGoto(actual_rm, 0, 1, NULL, NULL);
3380 * If this message has no O (room) field, generate one.
3382 if (msg->cm_fields[eOriginalRoom] == NULL) {
3383 CM_SetField(msg, eOriginalRoom, CCC->room.QRname, strlen(CCC->room.QRname));
3386 /* Perform "before save" hooks (aborting if any return nonzero) */
3387 MSGM_syslog(LOG_DEBUG, "Performing before-save hooks\n");
3388 if (PerformMessageHooks(msg, EVT_BEFORESAVE) > 0) return(-3);
3391 * If this message has an Exclusive ID, and the room is replication
3392 * checking enabled, then do replication checks.
3394 if (DoesThisRoomNeedEuidIndexing(&CCC->room)) {
3395 ReplicationChecks(msg);
3398 /* Save it to disk */
3399 MSGM_syslog(LOG_DEBUG, "Saving to disk\n");
3400 newmsgid = send_message(msg);
3401 if (newmsgid <= 0L) return(-5);
3403 /* Write a supplemental message info record. This doesn't have to
3404 * be a critical section because nobody else knows about this message
3407 MSGM_syslog(LOG_DEBUG, "Creating MetaData record\n");
3408 memset(&smi, 0, sizeof(struct MetaData));
3409 smi.meta_msgnum = newmsgid;
3410 smi.meta_refcount = 0;
3411 safestrncpy(smi.meta_content_type, content_type,
3412 sizeof smi.meta_content_type);
3415 * Measure how big this message will be when rendered as RFC822.
3416 * We do this for two reasons:
3417 * 1. We need the RFC822 length for the new metadata record, so the
3418 * POP and IMAP services don't have to calculate message lengths
3419 * while the user is waiting (multiplied by potentially hundreds
3420 * or thousands of messages).
3421 * 2. If journaling is enabled, we will need an RFC822 version of the
3422 * message to attach to the journalized copy.
3424 if (CCC->redirect_buffer != NULL) {
3425 MSGM_syslog(LOG_ALERT, "CCC->redirect_buffer is not NULL during message submission!\n");
3428 CCC->redirect_buffer = NewStrBufPlain(NULL, SIZ);
3429 CtdlOutputPreLoadedMsg(msg, MT_RFC822, HEADERS_ALL, 0, 1, QP_EADDR);
3430 smi.meta_rfc822_length = StrLength(CCC->redirect_buffer);
3431 saved_rfc822_version = CCC->redirect_buffer;
3432 CCC->redirect_buffer = NULL;
3436 /* Now figure out where to store the pointers */
3437 MSGM_syslog(LOG_DEBUG, "Storing pointers\n");
3439 /* If this is being done by the networker delivering a private
3440 * message, we want to BYPASS saving the sender's copy (because there
3441 * is no local sender; it would otherwise go to the Trashcan).
3443 if ((!CCC->internal_pgm) || (recps == NULL)) {
3444 if (CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 1, msg) != 0) {
3445 MSGM_syslog(LOG_ERR, "ERROR saving message pointer!\n");
3446 CtdlSaveMsgPointerInRoom(config.c_aideroom, newmsgid, 0, msg);
3450 /* For internet mail, drop a copy in the outbound queue room */
3451 if ((recps != NULL) && (recps->num_internet > 0)) {
3452 CtdlSaveMsgPointerInRoom(SMTP_SPOOLOUT_ROOM, newmsgid, 0, msg);
3455 /* If other rooms are specified, drop them there too. */
3456 if ((recps != NULL) && (recps->num_room > 0))
3457 for (i=0; i<num_tokens(recps->recp_room, '|'); ++i) {
3458 extract_token(recipient, recps->recp_room, i,
3459 '|', sizeof recipient);
3460 MSG_syslog(LOG_DEBUG, "Delivering to room <%s>\n", recipient);///// xxxx
3461 CtdlSaveMsgPointerInRoom(recipient, newmsgid, 0, msg);
3464 /* Bump this user's messages posted counter. */
3465 MSGM_syslog(LOG_DEBUG, "Updating user\n");
3466 CtdlGetUserLock(&CCC->user, CCC->curr_user);
3467 CCC->user.posted = CCC->user.posted + 1;
3468 CtdlPutUserLock(&CCC->user);
3470 /* Decide where bounces need to be delivered */
3471 if ((recps != NULL) && (recps->bounce_to != NULL)) {
3472 safestrncpy(bounce_to, recps->bounce_to, sizeof bounce_to);
3474 else if (CCC->logged_in) {
3475 snprintf(bounce_to, sizeof bounce_to, "%s@%s", CCC->user.fullname, config.c_nodename);
3478 snprintf(bounce_to, sizeof bounce_to, "%s@%s", msg->cm_fields[eAuthor], msg->cm_fields[eNodeName]);
3481 /* If this is private, local mail, make a copy in the
3482 * recipient's mailbox and bump the reference count.
3484 if ((recps != NULL) && (recps->num_local > 0))
3485 for (i=0; i<num_tokens(recps->recp_local, '|'); ++i) {
3487 recipientlen = extract_token(recipient,
3488 recps->recp_local, i,
3489 '|', sizeof recipient);
3490 MSG_syslog(LOG_DEBUG, "Delivering private local mail to <%s>\n",
3492 if (CtdlGetUser(&userbuf, recipient) == 0) {
3493 CtdlMailboxName(actual_rm, sizeof actual_rm, &userbuf, MAILROOM);
3494 CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 0, msg);
3495 CtdlBumpNewMailCounter(userbuf.usernum);
3496 if (!IsEmptyStr(config.c_funambol_host) || !IsEmptyStr(config.c_pager_program)) {
3497 /* Generate a instruction message for the Funambol notification
3498 * server, in the same style as the SMTP queue
3502 instr = malloc(instr_alloc);
3503 instrlen = snprintf(
3505 "Content-type: %s\n\nmsgid|%ld\nsubmitted|%ld\n"
3509 (long)time(NULL), //todo: time() is expensive!
3513 imsg = malloc(sizeof(struct CtdlMessage));
3514 memset(imsg, 0, sizeof(struct CtdlMessage));
3515 imsg->cm_magic = CTDLMESSAGE_MAGIC;
3516 imsg->cm_anon_type = MES_NORMAL;
3517 imsg->cm_format_type = FMT_RFC822;
3518 CM_SetField(imsg, eMsgSubject, HKEY("QMSG"));
3519 CM_SetField(imsg, eAuthor, HKEY("Citadel"));
3520 CM_SetField(imsg, eJournal, HKEY("do not journal"));
3521 CM_SetAsField(imsg, eMesageText, &instr, instrlen);
3522 CM_SetField(imsg, eExtnotify, recipient, recipientlen);
3523 CtdlSubmitMsg(imsg, NULL, FNBL_QUEUE_ROOM, 0);
3524 CtdlFreeMessage(imsg);
3528 MSG_syslog(LOG_DEBUG, "No user <%s>\n", recipient);
3529 CtdlSaveMsgPointerInRoom(config.c_aideroom, newmsgid, 0, msg);
3533 /* Perform "after save" hooks */
3534 MSGM_syslog(LOG_DEBUG, "Performing after-save hooks\n");
3535 if (msg->cm_fields[eVltMsgNum] != NULL) free(msg->cm_fields[eVltMsgNum]);
3536 msg->cm_fields[eVltMsgNum] = malloc(20);
3537 snprintf(msg->cm_fields[eVltMsgNum], 20, "%ld", newmsgid);
3538 PerformMessageHooks(msg, EVT_AFTERSAVE);
3539 free(msg->cm_fields[eVltMsgNum]);
3540 msg->cm_fields[eVltMsgNum] = NULL;
3542 /* For IGnet mail, we have to save a new copy into the spooler for
3543 * each recipient, with the R and D fields set to the recipient and
3544 * destination-node. This has two ugly side effects: all other
3545 * recipients end up being unlisted in this recipient's copy of the
3546 * message, and it has to deliver multiple messages to the same
3547 * node. We'll revisit this again in a year or so when everyone has
3548 * a network spool receiver that can handle the new style messages.
3550 if ((recps != NULL) && (recps->num_ignet > 0))
3551 for (i=0; i<num_tokens(recps->recp_ignet, '|'); ++i) {
3552 extract_token(recipient, recps->recp_ignet, i,
3553 '|', sizeof recipient);
3555 hold_R = msg->cm_fields[eRecipient];
3556 hold_D = msg->cm_fields[eDestination];
3557 msg->cm_fields[eRecipient] = malloc(SIZ);
3558 msg->cm_fields[eDestination] = malloc(128);
3559 extract_token(msg->cm_fields[eRecipient], recipient, 0, '@', SIZ);
3560 extract_token(msg->cm_fields[eDestination], recipient, 1, '@', 128);
3562 serialize_message(&smr, msg);
3564 snprintf(submit_filename, sizeof submit_filename,
3565 "%s/netmail.%04lx.%04x.%04x",
3567 (long) getpid(), CCC->cs_pid, ++seqnum);
3568 network_fp = fopen(submit_filename, "wb+");
3569 if (network_fp != NULL) {
3570 rv = fwrite(smr.ser, smr.len, 1, network_fp);
3572 MSG_syslog(LOG_EMERG, "CtdlSubmitMsg(): Couldn't write network spool file: %s\n",
3580 free(msg->cm_fields[eRecipient]);
3581 free(msg->cm_fields[eDestination]);
3582 msg->cm_fields[eRecipient] = hold_R;
3583 msg->cm_fields[eDestination] = hold_D;
3586 /* Go back to the room we started from */
3587 MSG_syslog(LOG_DEBUG, "Returning to original room %s\n", hold_rm);
3588 if (strcasecmp(hold_rm, CCC->room.QRname))
3589 CtdlUserGoto(hold_rm, 0, 1, NULL, NULL);
3591 /* For internet mail, generate delivery instructions.
3592 * Yes, this is recursive. Deal with it. Infinite recursion does
3593 * not happen because the delivery instructions message does not
3594 * contain a recipient.
3596 if ((recps != NULL) && (recps->num_internet > 0)) {
3597 StrBuf *SpoolMsg = NewStrBuf();
3600 MSGM_syslog(LOG_DEBUG, "Generating delivery instructions\n");
3602 StrBufPrintf(SpoolMsg,
3603 "Content-type: "SPOOLMIME"\n"
3612 if (recps->envelope_from != NULL) {
3613 StrBufAppendBufPlain(SpoolMsg, HKEY("envelope_from|"), 0);
3614 StrBufAppendBufPlain(SpoolMsg, recps->envelope_from, -1, 0);
3615 StrBufAppendBufPlain(SpoolMsg, HKEY("\n"), 0);
3617 if (recps->sending_room != NULL) {
3618 StrBufAppendBufPlain(SpoolMsg, HKEY("source_room|"), 0);
3619 StrBufAppendBufPlain(SpoolMsg, recps->sending_room, -1, 0);
3620 StrBufAppendBufPlain(SpoolMsg, HKEY("\n"), 0);
3623 nTokens = num_tokens(recps->recp_internet, '|');
3624 for (i = 0; i < nTokens; i++) {
3626 len = extract_token(recipient, recps->recp_internet, i, '|', sizeof recipient);
3628 StrBufAppendBufPlain(SpoolMsg, HKEY("remote|"), 0);
3629 StrBufAppendBufPlain(SpoolMsg, recipient, len, 0);
3630 StrBufAppendBufPlain(SpoolMsg, HKEY("|0||\n"), 0);
3634 imsg = malloc(sizeof(struct CtdlMessage));
3635 memset(imsg, 0, sizeof(struct CtdlMessage));
3636 imsg->cm_magic = CTDLMESSAGE_MAGIC;
3637 imsg->cm_anon_type = MES_NORMAL;
3638 imsg->cm_format_type = FMT_RFC822;
3639 imsg->cm_fields[eMsgSubject] = strdup("QMSG");
3640 imsg->cm_fields[eAuthor] = strdup("Citadel");
3641 imsg->cm_fields[eJournal] = strdup("do not journal");
3642 imsg->cm_fields[eMesageText] = SmashStrBuf(&SpoolMsg); /* imsg owns this memory now */
3643 CtdlSubmitMsg(imsg, NULL, SMTP_SPOOLOUT_ROOM, QP_EADDR);
3644 CtdlFreeMessage(imsg);
3648 * Any addresses to harvest for someone's address book?
3650 if ( (CCC->logged_in) && (recps != NULL) ) {
3651 collected_addresses = harvest_collected_addresses(msg);
3654 if (collected_addresses != NULL) {
3655 aptr = (struct addresses_to_be_filed *)
3656 malloc(sizeof(struct addresses_to_be_filed));
3657 CtdlMailboxName(actual_rm, sizeof actual_rm,
3658 &CCC->user, USERCONTACTSROOM);
3659 aptr->roomname = strdup(actual_rm);
3660 aptr->collected_addresses = collected_addresses;
3661 begin_critical_section(S_ATBF);
3664 end_critical_section(S_ATBF);
3668 * Determine whether this message qualifies for journaling.
3670 if (msg->cm_fields[eJournal] != NULL) {
3671 qualified_for_journaling = 0;
3674 if (recps == NULL) {
3675 qualified_for_journaling = config.c_journal_pubmsgs;
3677 else if (recps->num_local + recps->num_ignet + recps->num_internet > 0) {
3678 qualified_for_journaling = config.c_journal_email;
3681 qualified_for_journaling = config.c_journal_pubmsgs;
3686 * Do we have to perform journaling? If so, hand off the saved
3687 * RFC822 version will be handed off to the journaler for background
3688 * submit. Otherwise, we have to free the memory ourselves.
3690 if (saved_rfc822_version != NULL) {
3691 if (qualified_for_journaling) {
3692 JournalBackgroundSubmit(msg, saved_rfc822_version, recps);
3695 FreeStrBuf(&saved_rfc822_version);
3705 * Convenience function for generating small administrative messages.
3707 void quickie_message(const char *from,
3708 const char *fromaddr,
3713 const char *subject)
3715 struct CtdlMessage *msg;
3716 struct recptypes *recp = NULL;
3718 msg = malloc(sizeof(struct CtdlMessage));
3719 memset(msg, 0, sizeof(struct CtdlMessage));
3720 msg->cm_magic = CTDLMESSAGE_MAGIC;
3721 msg->cm_anon_type = MES_NORMAL;
3722 msg->cm_format_type = format_type;
3725 msg->cm_fields[eAuthor] = strdup(from);
3727 else if (fromaddr != NULL) {
3728 msg->cm_fields[eAuthor] = strdup(fromaddr);
3729 if (strchr(msg->cm_fields[eAuthor], '@')) {
3730 *strchr(msg->cm_fields[eAuthor], '@') = 0;
3734 msg->cm_fields[eAuthor] = strdup("Citadel");
3737 if (fromaddr != NULL) msg->cm_fields[erFc822Addr] = strdup(fromaddr);
3738 if (room != NULL) msg->cm_fields[eOriginalRoom] = strdup(room);
3739 msg->cm_fields[eNodeName] = strdup(NODENAME);
3741 msg->cm_fields[eRecipient] = strdup(to);
3742 recp = validate_recipients(to, NULL, 0);
3744 if (subject != NULL) {
3745 msg->cm_fields[eMsgSubject] = strdup(subject);
3747 msg->cm_fields[eMesageText] = strdup(text);
3749 CtdlSubmitMsg(msg, recp, room, 0);
3750 CtdlFreeMessage(msg);
3751 if (recp != NULL) free_recipients(recp);
3754 void flood_protect_quickie_message(const char *from,
3755 const char *fromaddr,
3760 const char *subject,
3762 const char **CritStr,
3769 u_char rawdigest[MD5_DIGEST_LEN];
3770 struct MD5Context md5context;
3774 time_t tsday = NOW / (8*60*60); /* just care for a day... */
3776 tslen = snprintf(timestamp, sizeof(timestamp), "%ld", tsday);
3777 MD5Init(&md5context);
3779 for (i = 0; i < nCriterions; i++)
3780 MD5Update(&md5context,
3781 (const unsigned char*)CritStr[i], CritStrLen[i]);
3782 MD5Update(&md5context,
3783 (const unsigned char*)timestamp, tslen);
3784 MD5Final(rawdigest, &md5context);
3786 guid = NewStrBufPlain(NULL,
3787 MD5_DIGEST_LEN * 2 + 12);
3788 StrBufHexEscAppend(guid, NULL, rawdigest, MD5_DIGEST_LEN);
3789 StrBufAppendBufPlain(guid, HKEY("_fldpt"), 0);
3790 if (StrLength(guid) > 40)
3791 StrBufCutAt(guid, 40, NULL);
3793 if (CheckIfAlreadySeen("FPAideMessage",
3802 /* yes, we did. flood protection kicks in. */
3804 "not sending message again\n");
3808 /* no, this message isn't sent recently; go ahead. */
3809 quickie_message(from,
3820 * Back end function used by CtdlMakeMessage() and similar functions
3822 StrBuf *CtdlReadMessageBodyBuf(char *terminator, /* token signalling EOT */
3824 size_t maxlen, /* maximum message length */
3825 StrBuf *exist, /* if non-null, append to it;
3826 exist is ALWAYS freed */
3827 int crlf, /* CRLF newlines instead of LF */
3828 int *sock /* socket handle or 0 for this session's client socket */
3837 LineBuf = NewStrBufPlain(NULL, SIZ);
3838 if (exist == NULL) {
3839 Message = NewStrBufPlain(NULL, 4 * SIZ);
3842 Message = NewStrBufDup(exist);
3845 /* Do we need to change leading ".." to "." for SMTP escaping? */
3846 if ((tlen == 1) && (*terminator == '.')) {
3850 /* read in the lines of message text one by one */
3853 if ((CtdlSockGetLine(sock, LineBuf, 5) < 0) ||
3858 if (CtdlClientGetLine(LineBuf) < 0) finished = 1;
3860 if ((StrLength(LineBuf) == tlen) &&
3861 (!strcmp(ChrPtr(LineBuf), terminator)))
3864 if ( (!flushing) && (!finished) ) {
3866 StrBufAppendBufPlain(LineBuf, HKEY("\r\n"), 0);
3869 StrBufAppendBufPlain(LineBuf, HKEY("\n"), 0);
3872 /* Unescape SMTP-style input of two dots at the beginning of the line */
3874 (StrLength(LineBuf) == 2) &&
3875 (!strcmp(ChrPtr(LineBuf), "..")))
3877 StrBufCutLeft(LineBuf, 1);
3880 StrBufAppendBuf(Message, LineBuf, 0);
3883 /* if we've hit the max msg length, flush the rest */
3884 if (StrLength(Message) >= maxlen) flushing = 1;
3886 } while (!finished);
3887 FreeStrBuf(&LineBuf);
3891 void DeleteAsyncMsg(ReadAsyncMsg **Msg)
3895 FreeStrBuf(&(*Msg)->MsgBuf);
3901 ReadAsyncMsg *NewAsyncMsg(const char *terminator, /* token signalling EOT */
3903 size_t maxlen, /* maximum message length */
3904 size_t expectlen, /* if we expect a message, how long should it be? */
3905 StrBuf *exist, /* if non-null, append to it;
3906 exist is ALWAYS freed */
3907 long eLen, /* length of exist */
3908 int crlf /* CRLF newlines instead of LF */
3911 ReadAsyncMsg *NewMsg;
3913 NewMsg = (ReadAsyncMsg *)malloc(sizeof(ReadAsyncMsg));
3914 memset(NewMsg, 0, sizeof(ReadAsyncMsg));
3916 if (exist == NULL) {
3919 if (expectlen == 0) {
3923 len = expectlen + 10;
3925 NewMsg->MsgBuf = NewStrBufPlain(NULL, len);
3928 NewMsg->MsgBuf = NewStrBufDup(exist);
3930 /* Do we need to change leading ".." to "." for SMTP escaping? */
3931 if ((tlen == 1) && (*terminator == '.')) {
3935 NewMsg->terminator = terminator;
3936 NewMsg->tlen = tlen;
3938 NewMsg->maxlen = maxlen;
3940 NewMsg->crlf = crlf;
3946 * Back end function used by CtdlMakeMessage() and similar functions
3948 eReadState CtdlReadMessageBodyAsync(AsyncIO *IO)
3950 ReadAsyncMsg *ReadMsg;
3951 int MsgFinished = 0;
3952 eReadState Finished = eMustReadMore;
3957 const char *pch = ChrPtr(IO->SendBuf.Buf);
3958 const char *pchh = IO->SendBuf.ReadWritePointer;
3964 nbytes = StrLength(IO->SendBuf.Buf) - (pchh - pch);
3965 snprintf(fn, SIZ, "/tmp/foolog_ev_%s.%d",
3966 ((CitContext*)(IO->CitContext))->ServiceName,
3969 fd = fopen(fn, "a+");
3972 ReadMsg = IO->ReadMsg;
3974 /* read in the lines of message text one by one */
3976 Finished = StrBufChunkSipLine(IO->IOBuf, &IO->RecvBuf);
3979 case eMustReadMore: /// read new from socket...
3981 if (IO->RecvBuf.ReadWritePointer != NULL) {
3982 nbytes = StrLength(IO->RecvBuf.Buf) - (IO->RecvBuf.ReadWritePointer - ChrPtr(IO->RecvBuf.Buf));
3983 fprintf(fd, "Read; Line unfinished: %ld Bytes still in buffer [", nbytes);
3985 fwrite(IO->RecvBuf.ReadWritePointer, nbytes, 1, fd);
3989 fprintf(fd, "BufferEmpty! \n");
3995 case eBufferNotEmpty: /* shouldn't happen... */
3996 case eReadSuccess: /// done for now...
3998 case eReadFail: /// WHUT?
4004 if ((StrLength(IO->IOBuf) == ReadMsg->tlen) &&
4005 (!strcmp(ChrPtr(IO->IOBuf), ReadMsg->terminator))) {
4008 fprintf(fd, "found Terminator; Message Size: %d\n", StrLength(ReadMsg->MsgBuf));
4011 else if (!ReadMsg->flushing) {
4014 fprintf(fd, "Read Line: [%d][%s]\n", StrLength(IO->IOBuf), ChrPtr(IO->IOBuf));
4017 /* Unescape SMTP-style input of two dots at the beginning of the line */
4018 if ((ReadMsg->dodot) &&
4019 (StrLength(IO->IOBuf) == 2) && /* TODO: do we just unescape lines with two dots or any line? */
4020 (!strcmp(ChrPtr(IO->IOBuf), "..")))
4023 fprintf(fd, "UnEscaped!\n");
4025 StrBufCutLeft(IO->IOBuf, 1);
4028 if (ReadMsg->crlf) {
4029 StrBufAppendBufPlain(IO->IOBuf, HKEY("\r\n"), 0);
4032 StrBufAppendBufPlain(IO->IOBuf, HKEY("\n"), 0);
4035 StrBufAppendBuf(ReadMsg->MsgBuf, IO->IOBuf, 0);
4038 /* if we've hit the max msg length, flush the rest */
4039 if (StrLength(ReadMsg->MsgBuf) >= ReadMsg->maxlen) ReadMsg->flushing = 1;
4041 } while (!MsgFinished);
4044 fprintf(fd, "Done with reading; %s.\n, ",
4045 (MsgFinished)?"Message Finished": "FAILED");
4049 return eReadSuccess;
4056 * Back end function used by CtdlMakeMessage() and similar functions
4058 char *CtdlReadMessageBody(char *terminator, /* token signalling EOT */
4060 size_t maxlen, /* maximum message length */
4061 StrBuf *exist, /* if non-null, append to it;
4062 exist is ALWAYS freed */
4063 int crlf, /* CRLF newlines instead of LF */
4064 int *sock /* socket handle or 0 for this session's client socket */
4069 Message = CtdlReadMessageBodyBuf(terminator,
4075 if (Message == NULL)
4078 return SmashStrBuf(&Message);
4083 * Build a binary message to be saved on disk.
4084 * (NOTE: if you supply 'preformatted_text', the buffer you give it
4085 * will become part of the message. This means you are no longer
4086 * responsible for managing that memory -- it will be freed along with
4087 * the rest of the fields when CtdlFreeMessage() is called.)
4090 struct CtdlMessage *CtdlMakeMessage(
4091 struct ctdluser *author, /* author's user structure */
4092 char *recipient, /* NULL if it's not mail */
4093 char *recp_cc, /* NULL if it's not mail */
4094 char *room, /* room where it's going */
4095 int type, /* see MES_ types in header file */
4096 int format_type, /* variformat, plain text, MIME... */
4097 char *fake_name, /* who we're masquerading as */
4098 char *my_email, /* which of my email addresses to use (empty is ok) */
4099 char *subject, /* Subject (optional) */
4100 char *supplied_euid, /* ...or NULL if this is irrelevant */
4101 char *preformatted_text, /* ...or NULL to read text from client */
4102 char *references /* Thread references */
4104 char dest_node[256];
4106 struct CtdlMessage *msg;
4108 StrBuf *FakeEncAuthor = NULL;
4110 msg = malloc(sizeof(struct CtdlMessage));
4111 memset(msg, 0, sizeof(struct CtdlMessage));
4112 msg->cm_magic = CTDLMESSAGE_MAGIC;
4113 msg->cm_anon_type = type;
4114 msg->cm_format_type = format_type;
4116 /* Don't confuse the poor folks if it's not routed mail. */
4117 strcpy(dest_node, "");
4119 if (recipient != NULL) striplt(recipient);
4120 if (recp_cc != NULL) striplt(recp_cc);
4122 /* Path or Return-Path */
4123 if (my_email == NULL) my_email = "";
4125 if (!IsEmptyStr(my_email)) {
4126 msg->cm_fields[eMessagePath] = strdup(my_email);
4129 snprintf(buf, sizeof buf, "%s", author->fullname);
4130 msg->cm_fields[eMessagePath] = strdup(buf);
4132 convert_spaces_to_underscores(msg->cm_fields[eMessagePath]);
4134 snprintf(buf, sizeof buf, "%ld", (long)time(NULL)); /* timestamp */
4135 msg->cm_fields[eTimestamp] = strdup(buf);
4137 if ((fake_name != NULL) && (fake_name[0])) { /* author */
4138 FakeAuthor = NewStrBufPlain (fake_name, -1);
4141 FakeAuthor = NewStrBufPlain (author->fullname, -1);
4143 StrBufRFC2047encode(&FakeEncAuthor, FakeAuthor);
4144 msg->cm_fields[eAuthor] = SmashStrBuf(&FakeEncAuthor);
4145 FreeStrBuf(&FakeAuthor);
4147 if (CC->room.QRflags & QR_MAILBOX) { /* room */
4148 msg->cm_fields[eOriginalRoom] = strdup(&CC->room.QRname[11]);
4151 msg->cm_fields[eOriginalRoom] = strdup(CC->room.QRname);
4154 msg->cm_fields[eNodeName] = strdup(NODENAME); /* nodename */
4155 msg->cm_fields[eHumanNode] = strdup(HUMANNODE); /* hnodename */
4157 if ((recipient != NULL) && (recipient[0] != 0)) {
4158 msg->cm_fields[eRecipient] = strdup(recipient);
4160 if ((recp_cc != NULL) && (recp_cc[0] != 0)) {
4161 msg->cm_fields[eCarbonCopY] = strdup(recp_cc);
4163 if (dest_node[0] != 0) {
4164 msg->cm_fields[eDestination] = strdup(dest_node);
4167 if (!IsEmptyStr(my_email)) {
4168 msg->cm_fields[erFc822Addr] = strdup(my_email);
4170 else if ( (author == &CC->user) && (!IsEmptyStr(CC->cs_inet_email)) ) {
4171 msg->cm_fields[erFc822Addr] = strdup(CC->cs_inet_email);
4174 if (subject != NULL) {
4177 length = strlen(subject);
4183 while ((subject[i] != '\0') &&
4184 (IsAscii = isascii(subject[i]) != 0 ))
4187 msg->cm_fields[eMsgSubject] = strdup(subject);
4188 else /* ok, we've got utf8 in the string. */
4190 msg->cm_fields[eMsgSubject] = rfc2047encode(subject, length);
4196 if (supplied_euid != NULL) {
4197 msg->cm_fields[eExclusiveID] = strdup(supplied_euid);
4200 if ((references != NULL) && (!IsEmptyStr(references))) {
4201 if (msg->cm_fields[eWeferences] != NULL)
4202 free(msg->cm_fields[eWeferences]);
4203 msg->cm_fields[eWeferences] = strdup(references);
4206 if (preformatted_text != NULL) {
4207 msg->cm_fields[eMesageText] = preformatted_text;
4210 msg->cm_fields[eMesageText] = CtdlReadMessageBody(HKEY("000"), config.c_maxmsglen, NULL, 0, 0);
4217 * Check to see whether we have permission to post a message in the current
4218 * room. Returns a *CITADEL ERROR CODE* and puts a message in errmsgbuf, or
4219 * returns 0 on success.
4221 int CtdlDoIHavePermissionToPostInThisRoom(
4224 const char* RemoteIdentifier,
4230 if (!(CC->logged_in) &&
4231 (PostPublic == POST_LOGGED_IN)) {
4232 snprintf(errmsgbuf, n, "Not logged in.");
4233 return (ERROR + NOT_LOGGED_IN);
4235 else if (PostPublic == CHECK_EXISTANCE) {
4236 return (0); // We're Evaling whether a recipient exists
4238 else if (!(CC->logged_in)) {
4240 if ((CC->room.QRflags & QR_READONLY)) {
4241 snprintf(errmsgbuf, n, "Not logged in.");
4242 return (ERROR + NOT_LOGGED_IN);
4244 if (CC->room.QRflags2 & QR2_MODERATED) {
4245 snprintf(errmsgbuf, n, "Not logged in Moderation feature not yet implemented!");
4246 return (ERROR + NOT_LOGGED_IN);
4248 if ((PostPublic!=POST_LMTP) &&(CC->room.QRflags2 & QR2_SMTP_PUBLIC) == 0) {
4250 return CtdlNetconfigCheckRoomaccess(errmsgbuf, n, RemoteIdentifier);
4256 if ((CC->user.axlevel < AxProbU)
4257 && ((CC->room.QRflags & QR_MAILBOX) == 0)) {
4258 snprintf(errmsgbuf, n, "Need to be validated to enter (except in %s> to sysop)", MAILROOM);
4259 return (ERROR + HIGHER_ACCESS_REQUIRED);
4262 CtdlRoomAccess(&CC->room, &CC->user, &ra, NULL);
4264 if (ra & UA_POSTALLOWED) {
4265 strcpy(errmsgbuf, "OK to post or reply here");
4269 if ( (ra & UA_REPLYALLOWED) && (is_reply) ) {
4271 * To be thorough, we ought to check to see if the message they are
4272 * replying to is actually a valid one in this room, but unless this
4273 * actually becomes a problem we'll go with high performance instead.
4275 strcpy(errmsgbuf, "OK to reply here");
4279 if ( (ra & UA_REPLYALLOWED) && (!is_reply) ) {
4280 /* Clarify what happened with a better error message */
4281 snprintf(errmsgbuf, n, "You may only reply to existing messages here.");
4282 return (ERROR + HIGHER_ACCESS_REQUIRED);
4285 snprintf(errmsgbuf, n, "Higher access is required to post in this room.");
4286 return (ERROR + HIGHER_ACCESS_REQUIRED);
4292 * Check to see if the specified user has Internet mail permission
4293 * (returns nonzero if permission is granted)
4295 int CtdlCheckInternetMailPermission(struct ctdluser *who) {
4297 /* Do not allow twits to send Internet mail */
4298 if (who->axlevel <= AxProbU) return(0);
4300 /* Globally enabled? */
4301 if (config.c_restrict == 0) return(1);
4303 /* User flagged ok? */
4304 if (who->flags & US_INTERNET) return(2);
4306 /* Admin level access? */
4307 if (who->axlevel >= AxAideU) return(3);
4309 /* No mail for you! */
4315 * Validate recipients, count delivery types and errors, and handle aliasing
4316 * FIXME check for dupes!!!!!
4318 * Returns 0 if all addresses are ok, ret->num_error = -1 if no addresses
4319 * were specified, or the number of addresses found invalid.
4321 * Caller needs to free the result using free_recipients()
4323 struct recptypes *validate_recipients(const char *supplied_recipients,
4324 const char *RemoteIdentifier,
4326 struct CitContext *CCC = CC;
4327 struct recptypes *ret;
4328 char *recipients = NULL;
4330 char this_recp[256];
4331 char this_recp_cooked[256];
4338 struct ctdluser tempUS;
4339 struct ctdlroom tempQR;
4340 struct ctdlroom tempQR2;
4346 ret = (struct recptypes *) malloc(sizeof(struct recptypes));
4347 if (ret == NULL) return(NULL);
4349 /* Set all strings to null and numeric values to zero */
4350 memset(ret, 0, sizeof(struct recptypes));
4352 if (supplied_recipients == NULL) {
4353 recipients = strdup("");
4356 recipients = strdup(supplied_recipients);
4359 /* Allocate some memory. Yes, this allocates 500% more memory than we will
4360 * actually need, but it's healthier for the heap than doing lots of tiny
4361 * realloc() calls instead.
4363 len = strlen(recipients) + 1024;
4364 ret->errormsg = malloc(len);
4365 ret->recp_local = malloc(len);
4366 ret->recp_internet = malloc(len);
4367 ret->recp_ignet = malloc(len);
4368 ret->recp_room = malloc(len);
4369 ret->display_recp = malloc(len);
4370 ret->recp_orgroom = malloc(len);
4371 org_recp = malloc(len);
4373 ret->errormsg[0] = 0;
4374 ret->recp_local[0] = 0;
4375 ret->recp_internet[0] = 0;
4376 ret->recp_ignet[0] = 0;
4377 ret->recp_room[0] = 0;
4378 ret->recp_orgroom[0] = 0;
4379 ret->display_recp[0] = 0;
4381 ret->recptypes_magic = RECPTYPES_MAGIC;
4383 /* Change all valid separator characters to commas */
4384 for (i=0; !IsEmptyStr(&recipients[i]); ++i) {
4385 if ((recipients[i] == ';') || (recipients[i] == '|')) {
4386 recipients[i] = ',';
4390 /* Now start extracting recipients... */
4392 while (!IsEmptyStr(recipients)) {
4393 for (i=0; i<=strlen(recipients); ++i) {
4394 if (recipients[i] == '\"') in_quotes = 1 - in_quotes;
4395 if ( ( (recipients[i] == ',') && (!in_quotes) ) || (recipients[i] == 0) ) {
4396 safestrncpy(this_recp, recipients, i+1);
4398 if (recipients[i] == ',') {
4399 strcpy(recipients, &recipients[i+1]);
4402 strcpy(recipients, "");
4409 if (IsEmptyStr(this_recp))
4411 MSG_syslog(LOG_DEBUG, "Evaluating recipient #%d: %s\n", num_recps, this_recp);
4414 strcpy(org_recp, this_recp);
4417 mailtype = alias(this_recp);
4419 for (j = 0; !IsEmptyStr(&this_recp[j]); ++j) {
4420 if (this_recp[j]=='_') {
4421 this_recp_cooked[j] = ' ';
4424 this_recp_cooked[j] = this_recp[j];
4427 this_recp_cooked[j] = '\0';
4432 if (!strcasecmp(this_recp, "sysop")) {
4434 strcpy(this_recp, config.c_aideroom);
4435 if (!IsEmptyStr(ret->recp_room)) {
4436 strcat(ret->recp_room, "|");
4438 strcat(ret->recp_room, this_recp);
4440 else if ( (!strncasecmp(this_recp, "room_", 5))
4441 && (!CtdlGetRoom(&tempQR, &this_recp_cooked[5])) ) {
4443 /* Save room so we can restore it later */
4444 tempQR2 = CCC->room;
4447 /* Check permissions to send mail to this room */
4448 err = CtdlDoIHavePermissionToPostInThisRoom(
4453 0 /* 0 = not a reply */
4462 if (!IsEmptyStr(ret->recp_room)) {
4463 strcat(ret->recp_room, "|");
4465 strcat(ret->recp_room, &this_recp_cooked[5]);
4467 if (!IsEmptyStr(ret->recp_orgroom)) {
4468 strcat(ret->recp_orgroom, "|");
4470 strcat(ret->recp_orgroom, org_recp);
4474 /* Restore room in case something needs it */
4475 CCC->room = tempQR2;
4478 else if (CtdlGetUser(&tempUS, this_recp) == 0) {
4480 strcpy(this_recp, tempUS.fullname);
4481 if (!IsEmptyStr(ret->recp_local)) {
4482 strcat(ret->recp_local, "|");
4484 strcat(ret->recp_local, this_recp);
4486 else if (CtdlGetUser(&tempUS, this_recp_cooked) == 0) {
4488 strcpy(this_recp, tempUS.fullname);
4489 if (!IsEmptyStr(ret->recp_local)) {
4490 strcat(ret->recp_local, "|");
4492 strcat(ret->recp_local, this_recp);
4500 /* Yes, you're reading this correctly: if the target
4501 * domain points back to the local system or an attached
4502 * Citadel directory, the address is invalid. That's
4503 * because if the address were valid, we would have
4504 * already translated it to a local address by now.
4506 if (IsDirectory(this_recp, 0)) {
4511 ++ret->num_internet;
4512 if (!IsEmptyStr(ret->recp_internet)) {
4513 strcat(ret->recp_internet, "|");
4515 strcat(ret->recp_internet, this_recp);
4520 if (!IsEmptyStr(ret->recp_ignet)) {
4521 strcat(ret->recp_ignet, "|");
4523 strcat(ret->recp_ignet, this_recp);
4531 if (IsEmptyStr(errmsg)) {
4532 snprintf(append, sizeof append, "Invalid recipient: %s", this_recp);
4535 snprintf(append, sizeof append, "%s", errmsg);
4537 if ( (strlen(ret->errormsg) + strlen(append) + 3) < SIZ) {
4538 if (!IsEmptyStr(ret->errormsg)) {
4539 strcat(ret->errormsg, "; ");
4541 strcat(ret->errormsg, append);
4545 if (IsEmptyStr(ret->display_recp)) {
4546 strcpy(append, this_recp);
4549 snprintf(append, sizeof append, ", %s", this_recp);
4551 if ( (strlen(ret->display_recp)+strlen(append)) < SIZ) {
4552 strcat(ret->display_recp, append);
4558 if ((ret->num_local + ret->num_internet + ret->num_ignet +
4559 ret->num_room + ret->num_error) == 0) {
4560 ret->num_error = (-1);
4561 strcpy(ret->errormsg, "No recipients specified.");
4564 MSGM_syslog(LOG_DEBUG, "validate_recipients()\n");
4565 MSG_syslog(LOG_DEBUG, " local: %d <%s>\n", ret->num_local, ret->recp_local);
4566 MSG_syslog(LOG_DEBUG, " room: %d <%s>\n", ret->num_room, ret->recp_room);
4567 MSG_syslog(LOG_DEBUG, " inet: %d <%s>\n", ret->num_internet, ret->recp_internet);
4568 MSG_syslog(LOG_DEBUG, " ignet: %d <%s>\n", ret->num_ignet, ret->recp_ignet);
4569 MSG_syslog(LOG_DEBUG, " error: %d <%s>\n", ret->num_error, ret->errormsg);
4577 * Destructor for struct recptypes
4579 void free_recipients(struct recptypes *valid) {
4581 if (valid == NULL) {
4585 if (valid->recptypes_magic != RECPTYPES_MAGIC) {
4586 struct CitContext *CCC = CC;
4587 MSGM_syslog(LOG_EMERG, "Attempt to call free_recipients() on some other data type!\n");
4591 if (valid->errormsg != NULL) free(valid->errormsg);
4592 if (valid->recp_local != NULL) free(valid->recp_local);
4593 if (valid->recp_internet != NULL) free(valid->recp_internet);
4594 if (valid->recp_ignet != NULL) free(valid->recp_ignet);
4595 if (valid->recp_room != NULL) free(valid->recp_room);
4596 if (valid->recp_orgroom != NULL) free(valid->recp_orgroom);
4597 if (valid->display_recp != NULL) free(valid->display_recp);
4598 if (valid->bounce_to != NULL) free(valid->bounce_to);
4599 if (valid->envelope_from != NULL) free(valid->envelope_from);
4600 if (valid->sending_room != NULL) free(valid->sending_room);
4607 * message entry - mode 0 (normal)
4609 void cmd_ent0(char *entargs)
4611 struct CitContext *CCC = CC;
4616 char supplied_euid[128];
4618 int format_type = 0;
4619 char newusername[256];
4620 char newuseremail[256];
4621 struct CtdlMessage *msg;
4625 struct recptypes *valid = NULL;
4626 struct recptypes *valid_to = NULL;
4627 struct recptypes *valid_cc = NULL;
4628 struct recptypes *valid_bcc = NULL;
4630 int subject_required = 0;
4635 int newuseremail_ok = 0;
4636 char references[SIZ];
4641 post = extract_int(entargs, 0);
4642 extract_token(recp, entargs, 1, '|', sizeof recp);
4643 anon_flag = extract_int(entargs, 2);
4644 format_type = extract_int(entargs, 3);
4645 extract_token(subject, entargs, 4, '|', sizeof subject);
4646 extract_token(newusername, entargs, 5, '|', sizeof newusername);
4647 do_confirm = extract_int(entargs, 6);
4648 extract_token(cc, entargs, 7, '|', sizeof cc);
4649 extract_token(bcc, entargs, 8, '|', sizeof bcc);
4650 switch(CC->room.QRdefaultview) {
4653 extract_token(supplied_euid, entargs, 9, '|', sizeof supplied_euid);
4656 supplied_euid[0] = 0;
4659 extract_token(newuseremail, entargs, 10, '|', sizeof newuseremail);
4660 extract_token(references, entargs, 11, '|', sizeof references);
4661 for (ptr=references; *ptr != 0; ++ptr) {
4662 if (*ptr == '!') *ptr = '|';
4665 /* first check to make sure the request is valid. */
4667 err = CtdlDoIHavePermissionToPostInThisRoom(
4672 (!IsEmptyStr(references)) /* is this a reply? or a top-level post? */
4676 cprintf("%d %s\n", err, errmsg);
4680 /* Check some other permission type things. */
4682 if (IsEmptyStr(newusername)) {
4683 strcpy(newusername, CCC->user.fullname);
4685 if ( (CCC->user.axlevel < AxAideU)
4686 && (strcasecmp(newusername, CCC->user.fullname))
4687 && (strcasecmp(newusername, CCC->cs_inet_fn))
4689 cprintf("%d You don't have permission to author messages as '%s'.\n",
4690 ERROR + HIGHER_ACCESS_REQUIRED,
4697 if (IsEmptyStr(newuseremail)) {
4698 newuseremail_ok = 1;
4701 if (!IsEmptyStr(newuseremail)) {
4702 if (!strcasecmp(newuseremail, CCC->cs_inet_email)) {
4703 newuseremail_ok = 1;
4705 else if (!IsEmptyStr(CCC->cs_inet_other_emails)) {
4706 j = num_tokens(CCC->cs_inet_other_emails, '|');
4707 for (i=0; i<j; ++i) {
4708 extract_token(buf, CCC->cs_inet_other_emails, i, '|', sizeof buf);
4709 if (!strcasecmp(newuseremail, buf)) {
4710 newuseremail_ok = 1;
4716 if (!newuseremail_ok) {
4717 cprintf("%d You don't have permission to author messages as '%s'.\n",
4718 ERROR + HIGHER_ACCESS_REQUIRED,
4724 CCC->cs_flags |= CS_POSTING;
4726 /* In mailbox rooms we have to behave a little differently --
4727 * make sure the user has specified at least one recipient. Then
4728 * validate the recipient(s). We do this for the Mail> room, as
4729 * well as any room which has the "Mailbox" view set - unless it
4730 * is the DRAFTS room which does not require recipients
4733 if ( ( ( (CCC->room.QRflags & QR_MAILBOX) && (!strcasecmp(&CCC->room.QRname[11], MAILROOM)) )
4734 || ( (CCC->room.QRflags & QR_MAILBOX) && (CCC->curr_view == VIEW_MAILBOX) )
4735 ) && (strcasecmp(&CCC->room.QRname[11], USERDRAFTROOM)) !=0 ) {
4736 if (CCC->user.axlevel < AxProbU) {
4737 strcpy(recp, "sysop");
4742 valid_to = validate_recipients(recp, NULL, 0);
4743 if (valid_to->num_error > 0) {
4744 cprintf("%d %s\n", ERROR + NO_SUCH_USER, valid_to->errormsg);
4745 free_recipients(valid_to);
4749 valid_cc = validate_recipients(cc, NULL, 0);
4750 if (valid_cc->num_error > 0) {
4751 cprintf("%d %s\n", ERROR + NO_SUCH_USER, valid_cc->errormsg);
4752 free_recipients(valid_to);
4753 free_recipients(valid_cc);
4757 valid_bcc = validate_recipients(bcc, NULL, 0);
4758 if (valid_bcc->num_error > 0) {
4759 cprintf("%d %s\n", ERROR + NO_SUCH_USER, valid_bcc->errormsg);
4760 free_recipients(valid_to);
4761 free_recipients(valid_cc);
4762 free_recipients(valid_bcc);
4766 /* Recipient required, but none were specified */
4767 if ( (valid_to->num_error < 0) && (valid_cc->num_error < 0) && (valid_bcc->num_error < 0) ) {
4768 free_recipients(valid_to);
4769 free_recipients(valid_cc);
4770 free_recipients(valid_bcc);
4771 cprintf("%d At least one recipient is required.\n", ERROR + NO_SUCH_USER);
4775 if (valid_to->num_internet + valid_cc->num_internet + valid_bcc->num_internet > 0) {
4776 if (CtdlCheckInternetMailPermission(&CCC->user)==0) {
4777 cprintf("%d You do not have permission "
4778 "to send Internet mail.\n",
4779 ERROR + HIGHER_ACCESS_REQUIRED);
4780 free_recipients(valid_to);
4781 free_recipients(valid_cc);
4782 free_recipients(valid_bcc);
4787 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)
4788 && (CCC->user.axlevel < AxNetU) ) {
4789 cprintf("%d Higher access required for network mail.\n",
4790 ERROR + HIGHER_ACCESS_REQUIRED);
4791 free_recipients(valid_to);
4792 free_recipients(valid_cc);
4793 free_recipients(valid_bcc);
4797 if ((RESTRICT_INTERNET == 1)
4798 && (valid_to->num_internet + valid_cc->num_internet + valid_bcc->num_internet > 0)
4799 && ((CCC->user.flags & US_INTERNET) == 0)
4800 && (!CCC->internal_pgm)) {
4801 cprintf("%d You don't have access to Internet mail.\n",
4802 ERROR + HIGHER_ACCESS_REQUIRED);
4803 free_recipients(valid_to);
4804 free_recipients(valid_cc);
4805 free_recipients(valid_bcc);
4811 /* Is this a room which has anonymous-only or anonymous-option? */
4812 anonymous = MES_NORMAL;
4813 if (CCC->room.QRflags & QR_ANONONLY) {
4814 anonymous = MES_ANONONLY;
4816 if (CCC->room.QRflags & QR_ANONOPT) {
4817 if (anon_flag == 1) { /* only if the user requested it */
4818 anonymous = MES_ANONOPT;
4822 if ((CCC->room.QRflags & QR_MAILBOX) == 0) {
4826 /* Recommend to the client that the use of a message subject is
4827 * strongly recommended in this room, if either the SUBJECTREQ flag
4828 * is set, or if there is one or more Internet email recipients.
4830 if (CCC->room.QRflags2 & QR2_SUBJECTREQ) subject_required = 1;
4831 if ((valid_to) && (valid_to->num_internet > 0)) subject_required = 1;
4832 if ((valid_cc) && (valid_cc->num_internet > 0)) subject_required = 1;
4833 if ((valid_bcc) && (valid_bcc->num_internet > 0)) subject_required = 1;
4835 /* If we're only checking the validity of the request, return
4836 * success without creating the message.
4839 cprintf("%d %s|%d\n", CIT_OK,
4840 ((valid_to != NULL) ? valid_to->display_recp : ""),
4842 free_recipients(valid_to);
4843 free_recipients(valid_cc);
4844 free_recipients(valid_bcc);
4848 /* We don't need these anymore because we'll do it differently below */
4849 free_recipients(valid_to);
4850 free_recipients(valid_cc);
4851 free_recipients(valid_bcc);
4853 /* Read in the message from the client. */
4855 cprintf("%d send message\n", START_CHAT_MODE);
4857 cprintf("%d send message\n", SEND_LISTING);
4860 msg = CtdlMakeMessage(&CCC->user, recp, cc,
4861 CCC->room.QRname, anonymous, format_type,
4862 newusername, newuseremail, subject,
4863 ((!IsEmptyStr(supplied_euid)) ? supplied_euid : NULL),
4866 /* Put together one big recipients struct containing to/cc/bcc all in
4867 * one. This is for the envelope.
4869 char *all_recps = malloc(SIZ * 3);
4870 strcpy(all_recps, recp);
4871 if (!IsEmptyStr(cc)) {
4872 if (!IsEmptyStr(all_recps)) {
4873 strcat(all_recps, ",");
4875 strcat(all_recps, cc);
4877 if (!IsEmptyStr(bcc)) {
4878 if (!IsEmptyStr(all_recps)) {
4879 strcat(all_recps, ",");
4881 strcat(all_recps, bcc);
4883 if (!IsEmptyStr(all_recps)) {
4884 valid = validate_recipients(all_recps, NULL, 0);
4891 if ((valid != NULL) && (valid->num_room == 1))
4893 /* posting into an ML room? set the envelope from
4894 * to the actual mail address so others get a valid
4897 msg->cm_fields[eenVelopeTo] = strdup(valid->recp_orgroom);
4901 msgnum = CtdlSubmitMsg(msg, valid, "", QP_EADDR);
4903 cprintf("%ld\n", msgnum);
4905 if (StrLength(CCC->StatusMessage) > 0) {
4906 cprintf("%s\n", ChrPtr(CCC->StatusMessage));
4908 else if (msgnum >= 0L) {
4909 client_write(HKEY("Message accepted.\n"));
4912 client_write(HKEY("Internal error.\n"));
4915 if (msg->cm_fields[eExclusiveID] != NULL) {
4916 cprintf("%s\n", msg->cm_fields[eExclusiveID]);
4923 CtdlFreeMessage(msg);
4925 if (valid != NULL) {
4926 free_recipients(valid);
4934 * API function to delete messages which match a set of criteria
4935 * (returns the actual number of messages deleted)
4937 int CtdlDeleteMessages(char *room_name, /* which room */
4938 long *dmsgnums, /* array of msg numbers to be deleted */
4939 int num_dmsgnums, /* number of msgs to be deleted, or 0 for "any" */
4940 char *content_type /* or "" for any. regular expressions expected. */
4943 struct CitContext *CCC = CC;
4944 struct ctdlroom qrbuf;
4945 struct cdbdata *cdbfr;
4946 long *msglist = NULL;
4947 long *dellist = NULL;
4950 int num_deleted = 0;
4952 struct MetaData smi;
4955 int need_to_free_re = 0;
4957 if (content_type) if (!IsEmptyStr(content_type)) {
4958 regcomp(&re, content_type, 0);
4959 need_to_free_re = 1;
4961 MSG_syslog(LOG_DEBUG, " CtdlDeleteMessages(%s, %d msgs, %s)\n",
4962 room_name, num_dmsgnums, content_type);
4964 /* get room record, obtaining a lock... */
4965 if (CtdlGetRoomLock(&qrbuf, room_name) != 0) {
4966 MSG_syslog(LOG_ERR, " CtdlDeleteMessages(): Room <%s> not found\n",
4968 if (need_to_free_re) regfree(&re);
4969 return (0); /* room not found */
4971 cdbfr = cdb_fetch(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long));
4973 if (cdbfr != NULL) {
4974 dellist = malloc(cdbfr->len);
4975 msglist = (long *) cdbfr->ptr;
4976 cdbfr->ptr = NULL; /* CtdlDeleteMessages() now owns this memory */
4977 num_msgs = cdbfr->len / sizeof(long);
4981 int have_contenttype = (content_type != NULL) && !IsEmptyStr(content_type);
4982 int have_delmsgs = (num_dmsgnums == 0) || (dmsgnums == NULL);
4983 int have_more_del = 1;
4985 num_msgs = sort_msglist(msglist, num_msgs);
4986 if (num_dmsgnums > 1)
4987 num_dmsgnums = sort_msglist(dmsgnums, num_dmsgnums);
4990 StrBuf *dbg = NewStrBuf();
4991 for (i = 0; i < num_dmsgnums; i++)
4992 StrBufAppendPrintf(dbg, ", %ld", dmsgnums[i]);
4993 MSG_syslog(LOG_DEBUG, " Deleting before: %s", ChrPtr(dbg));
4998 while ((i < num_msgs) && (have_more_del)) {
5001 /* Set/clear a bit for each criterion */
5003 /* 0 messages in the list or a null list means that we are
5004 * interested in deleting any messages which meet the other criteria.
5007 delete_this |= 0x01;
5010 while ((i < num_msgs) && (msglist[i] < dmsgnums[j])) i++;
5015 if (msglist[i] == dmsgnums[j]) {
5016 delete_this |= 0x01;
5019 have_more_del = (j < num_dmsgnums);
5022 if (have_contenttype) {
5023 GetMetaData(&smi, msglist[i]);
5024 if (regexec(&re, smi.meta_content_type, 1, &pm, 0) == 0) {
5025 delete_this |= 0x02;
5028 delete_this |= 0x02;
5031 /* Delete message only if all bits are set */
5032 if (delete_this == 0x03) {
5033 dellist[num_deleted++] = msglist[i];
5040 StrBuf *dbg = NewStrBuf();
5041 for (i = 0; i < num_deleted; i++)
5042 StrBufAppendPrintf(dbg, ", %ld", dellist[i]);
5043 MSG_syslog(LOG_DEBUG, " Deleting: %s", ChrPtr(dbg));
5047 num_msgs = sort_msglist(msglist, num_msgs);
5048 cdb_store(CDB_MSGLISTS, &qrbuf.QRnumber, (int)sizeof(long),
5049 msglist, (int)(num_msgs * sizeof(long)));
5052 qrbuf.QRhighest = msglist[num_msgs - 1];
5054 qrbuf.QRhighest = 0;
5056 CtdlPutRoomLock(&qrbuf);
5058 /* Go through the messages we pulled out of the index, and decrement
5059 * their reference counts by 1. If this is the only room the message
5060 * was in, the reference count will reach zero and the message will
5061 * automatically be deleted from the database. We do this in a
5062 * separate pass because there might be plug-in hooks getting called,
5063 * and we don't want that happening during an S_ROOMS critical
5067 for (i=0; i<num_deleted; ++i) {
5068 PerformDeleteHooks(qrbuf.QRname, dellist[i]);
5070 AdjRefCountList(dellist, num_deleted, -1);
5072 /* Now free the memory we used, and go away. */
5073 if (msglist != NULL) free(msglist);
5074 if (dellist != NULL) free(dellist);
5075 MSG_syslog(LOG_DEBUG, " %d message(s) deleted.\n", num_deleted);
5076 if (need_to_free_re) regfree(&re);
5077 return (num_deleted);
5083 * Check whether the current user has permission to delete messages from
5084 * the current room (returns 1 for yes, 0 for no)
5086 int CtdlDoIHavePermissionToDeleteMessagesFromThisRoom(void) {
5088 CtdlRoomAccess(&CC->room, &CC->user, &ra, NULL);
5089 if (ra & UA_DELETEALLOWED) return(1);
5097 * Delete message from current room
5099 void cmd_dele(char *args)
5108 extract_token(msgset, args, 0, '|', sizeof msgset);
5109 num_msgs = num_tokens(msgset, ',');
5111 cprintf("%d Nothing to do.\n", CIT_OK);
5115 if (CtdlDoIHavePermissionToDeleteMessagesFromThisRoom() == 0) {
5116 cprintf("%d Higher access required.\n",
5117 ERROR + HIGHER_ACCESS_REQUIRED);
5122 * Build our message set to be moved/copied
5124 msgs = malloc(num_msgs * sizeof(long));
5125 for (i=0; i<num_msgs; ++i) {
5126 extract_token(msgtok, msgset, i, ',', sizeof msgtok);
5127 msgs[i] = atol(msgtok);
5130 num_deleted = CtdlDeleteMessages(CC->room.QRname, msgs, num_msgs, "");
5134 cprintf("%d %d message%s deleted.\n", CIT_OK,
5135 num_deleted, ((num_deleted != 1) ? "s" : ""));
5137 cprintf("%d Message not found.\n", ERROR + MESSAGE_NOT_FOUND);
5145 * move or copy a message to another room
5147 void cmd_move(char *args)
5154 char targ[ROOMNAMELEN];
5155 struct ctdlroom qtemp;
5162 extract_token(msgset, args, 0, '|', sizeof msgset);
5163 num_msgs = num_tokens(msgset, ',');
5165 cprintf("%d Nothing to do.\n", CIT_OK);
5169 extract_token(targ, args, 1, '|', sizeof targ);
5170 convert_room_name_macros(targ, sizeof targ);
5171 targ[ROOMNAMELEN - 1] = 0;
5172 is_copy = extract_int(args, 2);
5174 if (CtdlGetRoom(&qtemp, targ) != 0) {
5175 cprintf("%d '%s' does not exist.\n", ERROR + ROOM_NOT_FOUND, targ);
5179 if (!strcasecmp(qtemp.QRname, CC->room.QRname)) {
5180 cprintf("%d Source and target rooms are the same.\n", ERROR + ALREADY_EXISTS);
5184 CtdlGetUser(&CC->user, CC->curr_user);
5185 CtdlRoomAccess(&qtemp, &CC->user, &ra, NULL);
5187 /* Check for permission to perform this operation.
5188 * Remember: "CC->room" is source, "qtemp" is target.
5192 /* Admins can move/copy */
5193 if (CC->user.axlevel >= AxAideU) permit = 1;
5195 /* Room aides can move/copy */
5196 if (CC->user.usernum == CC->room.QRroomaide) permit = 1;
5198 /* Permit move/copy from personal rooms */
5199 if ((CC->room.QRflags & QR_MAILBOX)
5200 && (qtemp.QRflags & QR_MAILBOX)) permit = 1;
5202 /* Permit only copy from public to personal room */
5204 && (!(CC->room.QRflags & QR_MAILBOX))
5205 && (qtemp.QRflags & QR_MAILBOX)) permit = 1;
5207 /* Permit message removal from collaborative delete rooms */
5208 if (CC->room.QRflags2 & QR2_COLLABDEL) permit = 1;
5210 /* Users allowed to post into the target room may move into it too. */
5211 if ((CC->room.QRflags & QR_MAILBOX) &&
5212 (qtemp.QRflags & UA_POSTALLOWED)) permit = 1;
5214 /* User must have access to target room */
5215 if (!(ra & UA_KNOWN)) permit = 0;
5218 cprintf("%d Higher access required.\n",
5219 ERROR + HIGHER_ACCESS_REQUIRED);
5224 * Build our message set to be moved/copied
5226 msgs = malloc(num_msgs * sizeof(long));
5227 for (i=0; i<num_msgs; ++i) {
5228 extract_token(msgtok, msgset, i, ',', sizeof msgtok);
5229 msgs[i] = atol(msgtok);
5235 err = CtdlSaveMsgPointersInRoom(targ, msgs, num_msgs, 1, NULL, 0);
5237 cprintf("%d Cannot store message(s) in %s: error %d\n",
5243 /* Now delete the message from the source room,
5244 * if this is a 'move' rather than a 'copy' operation.
5247 CtdlDeleteMessages(CC->room.QRname, msgs, num_msgs, "");
5251 cprintf("%d Message(s) %s.\n", CIT_OK, (is_copy ? "copied" : "moved") );
5257 * GetMetaData() - Get the supplementary record for a message
5259 void GetMetaData(struct MetaData *smibuf, long msgnum)
5262 struct cdbdata *cdbsmi;
5265 memset(smibuf, 0, sizeof(struct MetaData));
5266 smibuf->meta_msgnum = msgnum;
5267 smibuf->meta_refcount = 1; /* Default reference count is 1 */
5269 /* Use the negative of the message number for its supp record index */
5270 TheIndex = (0L - msgnum);
5272 cdbsmi = cdb_fetch(CDB_MSGMAIN, &TheIndex, sizeof(long));
5273 if (cdbsmi == NULL) {
5274 return; /* record not found; go with defaults */
5276 memcpy(smibuf, cdbsmi->ptr,
5277 ((cdbsmi->len > sizeof(struct MetaData)) ?
5278 sizeof(struct MetaData) : cdbsmi->len));
5285 * PutMetaData() - (re)write supplementary record for a message
5287 void PutMetaData(struct MetaData *smibuf)
5291 /* Use the negative of the message number for the metadata db index */
5292 TheIndex = (0L - smibuf->meta_msgnum);
5294 cdb_store(CDB_MSGMAIN,
5295 &TheIndex, (int)sizeof(long),
5296 smibuf, (int)sizeof(struct MetaData));
5301 * AdjRefCount - submit an adjustment to the reference count for a message.
5302 * (These are just queued -- we actually process them later.)
5304 void AdjRefCount(long msgnum, int incr)
5306 struct CitContext *CCC = CC;
5307 struct arcq new_arcq;
5310 MSG_syslog(LOG_DEBUG, "AdjRefCount() msg %ld ref count delta %+d\n", msgnum, incr);
5312 begin_critical_section(S_SUPPMSGMAIN);
5313 if (arcfp == NULL) {
5314 arcfp = fopen(file_arcq, "ab+");
5315 chown(file_arcq, CTDLUID, (-1));
5316 chmod(file_arcq, 0600);
5318 end_critical_section(S_SUPPMSGMAIN);
5320 /* msgnum < 0 means that we're trying to close the file */
5322 MSGM_syslog(LOG_DEBUG, "Closing the AdjRefCount queue file\n");
5323 begin_critical_section(S_SUPPMSGMAIN);
5324 if (arcfp != NULL) {
5328 end_critical_section(S_SUPPMSGMAIN);
5333 * If we can't open the queue, perform the operation synchronously.
5335 if (arcfp == NULL) {
5336 TDAP_AdjRefCount(msgnum, incr);
5340 new_arcq.arcq_msgnum = msgnum;
5341 new_arcq.arcq_delta = incr;
5342 rv = fwrite(&new_arcq, sizeof(struct arcq), 1, arcfp);
5344 MSG_syslog(LOG_EMERG, "Couldn't write Refcount Queue File %s: %s\n",
5353 void AdjRefCountList(long *msgnum, long nmsg, int incr)
5355 struct CitContext *CCC = CC;
5356 long i, the_size, offset;
5357 struct arcq *new_arcq;
5360 MSG_syslog(LOG_DEBUG, "AdjRefCountList() msg %ld ref count delta %+d\n", nmsg, incr);
5362 begin_critical_section(S_SUPPMSGMAIN);
5363 if (arcfp == NULL) {
5364 arcfp = fopen(file_arcq, "ab+");
5365 chown(file_arcq, CTDLUID, (-1));
5366 chmod(file_arcq, 0600);
5368 end_critical_section(S_SUPPMSGMAIN);
5371 * If we can't open the queue, perform the operation synchronously.
5373 if (arcfp == NULL) {
5374 for (i = 0; i < nmsg; i++)
5375 TDAP_AdjRefCount(msgnum[i], incr);
5379 the_size = sizeof(struct arcq) * nmsg;
5380 new_arcq = malloc(the_size);
5381 for (i = 0; i < nmsg; i++) {
5382 new_arcq[i].arcq_msgnum = msgnum[i];
5383 new_arcq[i].arcq_delta = incr;
5387 while ((rv >= 0) && (offset < the_size))
5389 rv = fwrite(new_arcq + offset, 1, the_size - offset, arcfp);
5391 MSG_syslog(LOG_EMERG, "Couldn't write Refcount Queue File %s: %s\n",
5407 * TDAP_ProcessAdjRefCountQueue()
5409 * Process the queue of message count adjustments that was created by calls
5410 * to AdjRefCount() ... by reading the queue and calling TDAP_AdjRefCount()
5411 * for each one. This should be an "off hours" operation.
5413 int TDAP_ProcessAdjRefCountQueue(void)
5415 struct CitContext *CCC = CC;
5416 char file_arcq_temp[PATH_MAX];
5419 struct arcq arcq_rec;
5420 int num_records_processed = 0;
5422 snprintf(file_arcq_temp, sizeof file_arcq_temp, "%s.%04x", file_arcq, rand());
5424 begin_critical_section(S_SUPPMSGMAIN);
5425 if (arcfp != NULL) {
5430 r = link(file_arcq, file_arcq_temp);
5432 MSG_syslog(LOG_CRIT, "%s: %s\n", file_arcq_temp, strerror(errno));
5433 end_critical_section(S_SUPPMSGMAIN);
5434 return(num_records_processed);
5438 end_critical_section(S_SUPPMSGMAIN);
5440 fp = fopen(file_arcq_temp, "rb");
5442 MSG_syslog(LOG_CRIT, "%s: %s\n", file_arcq_temp, strerror(errno));
5443 return(num_records_processed);
5446 while (fread(&arcq_rec, sizeof(struct arcq), 1, fp) == 1) {
5447 TDAP_AdjRefCount(arcq_rec.arcq_msgnum, arcq_rec.arcq_delta);
5448 ++num_records_processed;
5452 r = unlink(file_arcq_temp);
5454 MSG_syslog(LOG_CRIT, "%s: %s\n", file_arcq_temp, strerror(errno));
5457 return(num_records_processed);
5463 * TDAP_AdjRefCount - adjust the reference count for a message.
5464 * This one does it "for real" because it's called by
5465 * the autopurger function that processes the queue
5466 * created by AdjRefCount(). If a message's reference
5467 * count becomes zero, we also delete the message from
5468 * disk and de-index it.
5470 void TDAP_AdjRefCount(long msgnum, int incr)
5472 struct CitContext *CCC = CC;
5474 struct MetaData smi;
5477 /* This is a *tight* critical section; please keep it that way, as
5478 * it may get called while nested in other critical sections.
5479 * Complicating this any further will surely cause deadlock!
5481 begin_critical_section(S_SUPPMSGMAIN);
5482 GetMetaData(&smi, msgnum);
5483 smi.meta_refcount += incr;
5485 end_critical_section(S_SUPPMSGMAIN);
5486 MSG_syslog(LOG_DEBUG, "TDAP_AdjRefCount() msg %ld ref count delta %+d, is now %d\n",
5487 msgnum, incr, smi.meta_refcount
5490 /* If the reference count is now zero, delete the message
5491 * (and its supplementary record as well).
5493 if (smi.meta_refcount == 0) {
5494 MSG_syslog(LOG_DEBUG, "Deleting message <%ld>\n", msgnum);
5496 /* Call delete hooks with NULL room to show it has gone altogether */
5497 PerformDeleteHooks(NULL, msgnum);
5499 /* Remove from message base */
5501 cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long));
5502 cdb_delete(CDB_BIGMSGS, &delnum, (int)sizeof(long));
5504 /* Remove metadata record */
5505 delnum = (0L - msgnum);
5506 cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long));
5512 * Write a generic object to this room
5514 * Note: this could be much more efficient. Right now we use two temporary
5515 * files, and still pull the message into memory as with all others.
5517 void CtdlWriteObject(char *req_room, /* Room to stuff it in */
5518 char *content_type, /* MIME type of this object */
5519 char *raw_message, /* Data to be written */
5520 off_t raw_length, /* Size of raw_message */
5521 struct ctdluser *is_mailbox, /* Mailbox room? */
5522 int is_binary, /* Is encoding necessary? */
5523 int is_unique, /* Del others of this type? */
5524 unsigned int flags /* Internal save flags */
5527 struct CitContext *CCC = CC;
5528 struct ctdlroom qrbuf;
5529 char roomname[ROOMNAMELEN];
5530 struct CtdlMessage *msg;
5531 char *encoded_message = NULL;
5533 if (is_mailbox != NULL) {
5534 CtdlMailboxName(roomname, sizeof roomname, is_mailbox, req_room);
5537 safestrncpy(roomname, req_room, sizeof(roomname));
5540 MSG_syslog(LOG_DEBUG, "Raw length is %ld\n", (long)raw_length);
5543 encoded_message = malloc((size_t) (((raw_length * 134) / 100) + 4096 ) );
5546 encoded_message = malloc((size_t)(raw_length + 4096));
5549 sprintf(encoded_message, "Content-type: %s\n", content_type);
5552 sprintf(&encoded_message[strlen(encoded_message)],
5553 "Content-transfer-encoding: base64\n\n"
5557 sprintf(&encoded_message[strlen(encoded_message)],
5558 "Content-transfer-encoding: 7bit\n\n"
5564 &encoded_message[strlen(encoded_message)],
5572 &encoded_message[strlen(encoded_message)],
5578 MSGM_syslog(LOG_DEBUG, "Allocating\n");
5579 msg = malloc(sizeof(struct CtdlMessage));
5580 memset(msg, 0, sizeof(struct CtdlMessage));
5581 msg->cm_magic = CTDLMESSAGE_MAGIC;
5582 msg->cm_anon_type = MES_NORMAL;
5583 msg->cm_format_type = 4;
5584 msg->cm_fields[eAuthor] = strdup(CCC->user.fullname);
5585 msg->cm_fields[eOriginalRoom] = strdup(req_room);
5586 msg->cm_fields[eNodeName] = strdup(config.c_nodename);
5587 msg->cm_fields[eHumanNode] = strdup(config.c_humannode);
5588 msg->cm_flags = flags;
5590 msg->cm_fields[eMesageText] = encoded_message;
5592 /* Create the requested room if we have to. */
5593 if (CtdlGetRoom(&qrbuf, roomname) != 0) {
5594 CtdlCreateRoom(roomname,
5595 ( (is_mailbox != NULL) ? 5 : 3 ),
5596 "", 0, 1, 0, VIEW_BBS);
5598 /* If the caller specified this object as unique, delete all
5599 * other objects of this type that are currently in the room.
5602 MSG_syslog(LOG_DEBUG, "Deleted %d other msgs of this type\n",
5603 CtdlDeleteMessages(roomname, NULL, 0, content_type)
5606 /* Now write the data */
5607 CtdlSubmitMsg(msg, NULL, roomname, 0);
5608 CtdlFreeMessage(msg);
5616 void CtdlGetSysConfigBackend(long msgnum, void *userdata) {
5617 config_msgnum = msgnum;
5621 char *CtdlGetSysConfig(char *sysconfname) {
5622 char hold_rm[ROOMNAMELEN];
5625 struct CtdlMessage *msg;
5628 strcpy(hold_rm, CC->room.QRname);
5629 if (CtdlGetRoom(&CC->room, SYSCONFIGROOM) != 0) {
5630 CtdlGetRoom(&CC->room, hold_rm);
5635 /* We want the last (and probably only) config in this room */
5636 begin_critical_section(S_CONFIG);
5637 config_msgnum = (-1L);
5638 CtdlForEachMessage(MSGS_LAST, 1, NULL, sysconfname, NULL,
5639 CtdlGetSysConfigBackend, NULL);
5640 msgnum = config_msgnum;
5641 end_critical_section(S_CONFIG);
5647 msg = CtdlFetchMessage(msgnum, 1);
5649 conf = strdup(msg->cm_fields[eMesageText]);
5650 CtdlFreeMessage(msg);
5657 CtdlGetRoom(&CC->room, hold_rm);
5659 if (conf != NULL) do {
5660 extract_token(buf, conf, 0, '\n', sizeof buf);
5661 strcpy(conf, &conf[strlen(buf)+1]);
5662 } while ( (!IsEmptyStr(conf)) && (!IsEmptyStr(buf)) );
5668 void CtdlPutSysConfig(char *sysconfname, char *sysconfdata) {
5669 CtdlWriteObject(SYSCONFIGROOM, sysconfname, sysconfdata, (strlen(sysconfdata)+1), NULL, 0, 1, 0);
5674 * Determine whether a given Internet address belongs to the current user
5676 int CtdlIsMe(char *addr, int addr_buf_len)
5678 struct recptypes *recp;
5681 recp = validate_recipients(addr, NULL, 0);
5682 if (recp == NULL) return(0);
5684 if (recp->num_local == 0) {
5685 free_recipients(recp);
5689 for (i=0; i<recp->num_local; ++i) {
5690 extract_token(addr, recp->recp_local, i, '|', addr_buf_len);
5691 if (!strcasecmp(addr, CC->user.fullname)) {
5692 free_recipients(recp);
5697 free_recipients(recp);
5703 * Citadel protocol command to do the same
5705 void cmd_isme(char *argbuf) {
5708 if (CtdlAccessCheck(ac_logged_in)) return;
5709 extract_token(addr, argbuf, 0, '|', sizeof addr);
5711 if (CtdlIsMe(addr, sizeof addr)) {
5712 cprintf("%d %s\n", CIT_OK, addr);
5715 cprintf("%d Not you.\n", ERROR + ILLEGAL_VALUE);
5721 /*****************************************************************************/
5722 /* MODULE INITIALIZATION STUFF */
5723 /*****************************************************************************/
5724 void SetMessageDebugEnabled(const int n)
5726 MessageDebugEnabled = n;
5728 CTDL_MODULE_INIT(msgbase)
5731 CtdlRegisterDebugFlagHook(HKEY("messages"), SetMessageDebugEnabled, &MessageDebugEnabled);
5733 CtdlRegisterProtoHook(cmd_msgs, "MSGS", "Output a list of messages in the current room");
5734 CtdlRegisterProtoHook(cmd_msg0, "MSG0", "Output a message in plain text format");
5735 CtdlRegisterProtoHook(cmd_msg2, "MSG2", "Output a message in RFC822 format");
5736 CtdlRegisterProtoHook(cmd_msg3, "MSG3", "Output a message in raw format (deprecated)");
5737 CtdlRegisterProtoHook(cmd_msg4, "MSG4", "Output a message in the client's preferred format");
5738 CtdlRegisterProtoHook(cmd_msgp, "MSGP", "Select preferred format for MSG4 output");
5739 CtdlRegisterProtoHook(cmd_opna, "OPNA", "Open an attachment for download");
5740 CtdlRegisterProtoHook(cmd_dlat, "DLAT", "Download an attachment");
5741 CtdlRegisterProtoHook(cmd_ent0, "ENT0", "Enter a message");
5742 CtdlRegisterProtoHook(cmd_dele, "DELE", "Delete a message");
5743 CtdlRegisterProtoHook(cmd_move, "MOVE", "Move or copy a message to another room");
5744 CtdlRegisterProtoHook(cmd_isme, "ISME", "Determine whether an email address belongs to a user");
5747 /* return our Subversion id for the Log */