4 * Implements the message store.
14 #if TIME_WITH_SYS_TIME
15 # include <sys/time.h>
19 # include <sys/time.h>
32 #include <sys/types.h>
34 #include <libcitadel.h>
37 #include "serv_extensions.h"
41 #include "sysdep_decls.h"
42 #include "citserver.h"
49 #include "internet_addressing.h"
50 #include "euidindex.h"
51 #include "journaling.h"
52 #include "citadel_dirs.h"
53 #include "clientsocket.h"
54 #include "serv_network.h"
58 struct addresses_to_be_filed
*atbf
= NULL
;
60 /* This temp file holds the queue of operations for AdjRefCount() */
61 static FILE *arcfp
= NULL
;
64 * This really belongs in serv_network.c, but I don't know how to export
65 * symbols between modules.
67 struct FilterList
*filterlist
= NULL
;
71 * These are the four-character field headers we use when outputting
72 * messages in Citadel format (as opposed to RFC822 format).
75 NULL
, NULL
, NULL
, NULL
, NULL
, NULL
, NULL
, NULL
,
76 NULL
, NULL
, NULL
, NULL
, NULL
, NULL
, NULL
, NULL
,
77 NULL
, NULL
, NULL
, NULL
, NULL
, NULL
, NULL
, NULL
,
78 NULL
, NULL
, NULL
, NULL
, NULL
, NULL
, NULL
, NULL
,
79 NULL
, NULL
, NULL
, NULL
, NULL
, NULL
, NULL
, NULL
,
80 NULL
, NULL
, NULL
, NULL
, NULL
, NULL
, NULL
, NULL
,
81 NULL
, NULL
, NULL
, NULL
, NULL
, NULL
, NULL
, NULL
,
82 NULL
, NULL
, NULL
, NULL
, NULL
, NULL
, NULL
, NULL
,
111 * This function is self explanatory.
112 * (What can I say, I'm in a weird mood today...)
114 void remove_any_whitespace_to_the_left_or_right_of_at_symbol(char *name
)
118 for (i
= 0; i
< strlen(name
); ++i
) {
119 if (name
[i
] == '@') {
120 while (isspace(name
[i
- 1]) && i
> 0) {
121 strcpy(&name
[i
- 1], &name
[i
]);
124 while (isspace(name
[i
+ 1])) {
125 strcpy(&name
[i
+ 1], &name
[i
+ 2]);
133 * Aliasing for network mail.
134 * (Error messages have been commented out, because this is a server.)
136 int alias(char *name
)
137 { /* process alias and routing info for mail */
140 char aaa
[SIZ
], bbb
[SIZ
];
141 char *ignetcfg
= NULL
;
142 char *ignetmap
= NULL
;
148 char original_name
[256];
149 safestrncpy(original_name
, name
, sizeof original_name
);
152 remove_any_whitespace_to_the_left_or_right_of_at_symbol(name
);
153 stripallbut(name
, '<', '>');
155 fp
= fopen(file_mail_aliases
, "r");
157 fp
= fopen("/dev/null", "r");
164 while (fgets(aaa
, sizeof aaa
, fp
) != NULL
) {
165 while (isspace(name
[0]))
166 strcpy(name
, &name
[1]);
167 aaa
[strlen(aaa
) - 1] = 0;
169 for (a
= 0; a
< strlen(aaa
); ++a
) {
171 strcpy(bbb
, &aaa
[a
+ 1]);
175 if (!strcasecmp(name
, aaa
))
180 /* Hit the Global Address Book */
181 if (CtdlDirectoryLookup(aaa
, name
, sizeof aaa
) == 0) {
185 if (strcasecmp(original_name
, name
)) {
186 CtdlLogPrintf(CTDL_INFO
, "%s is being forwarded to %s\n", original_name
, name
);
189 /* Change "user @ xxx" to "user" if xxx is an alias for this host */
190 for (a
=0; a
<strlen(name
); ++a
) {
191 if (name
[a
] == '@') {
192 if (CtdlHostAlias(&name
[a
+1]) == hostalias_localhost
) {
194 CtdlLogPrintf(CTDL_INFO
, "Changed to <%s>\n", name
);
199 /* determine local or remote type, see citadel.h */
200 at
= haschar(name
, '@');
201 if (at
== 0) return(MES_LOCAL
); /* no @'s - local address */
202 if (at
> 1) return(MES_ERROR
); /* >1 @'s - invalid address */
203 remove_any_whitespace_to_the_left_or_right_of_at_symbol(name
);
205 /* figure out the delivery mode */
206 extract_token(node
, name
, 1, '@', sizeof node
);
208 /* If there are one or more dots in the nodename, we assume that it
209 * is an FQDN and will attempt SMTP delivery to the Internet.
211 if (haschar(node
, '.') > 0) {
212 return(MES_INTERNET
);
215 /* Otherwise we look in the IGnet maps for a valid Citadel node.
216 * Try directly-connected nodes first...
218 ignetcfg
= CtdlGetSysConfig(IGNETCFG
);
219 for (i
=0; i
<num_tokens(ignetcfg
, '\n'); ++i
) {
220 extract_token(buf
, ignetcfg
, i
, '\n', sizeof buf
);
221 extract_token(testnode
, buf
, 0, '|', sizeof testnode
);
222 if (!strcasecmp(node
, testnode
)) {
230 * Then try nodes that are two or more hops away.
232 ignetmap
= CtdlGetSysConfig(IGNETMAP
);
233 for (i
=0; i
<num_tokens(ignetmap
, '\n'); ++i
) {
234 extract_token(buf
, ignetmap
, i
, '\n', sizeof buf
);
235 extract_token(testnode
, buf
, 0, '|', sizeof testnode
);
236 if (!strcasecmp(node
, testnode
)) {
243 /* If we get to this point it's an invalid node name */
249 * Back end for the MSGS command: output message number only.
251 void simple_listing(long msgnum
, void *userdata
)
253 cprintf("%ld\n", msgnum
);
259 * Back end for the MSGS command: output header summary.
261 void headers_listing(long msgnum
, void *userdata
)
263 struct CtdlMessage
*msg
;
265 msg
= CtdlFetchMessage(msgnum
, 0);
267 cprintf("%ld|0|||||\n", msgnum
);
271 cprintf("%ld|%s|%s|%s|%s|%s|\n",
273 (msg
->cm_fields
['T'] ? msg
->cm_fields
['T'] : "0"),
274 (msg
->cm_fields
['A'] ? msg
->cm_fields
['A'] : ""),
275 (msg
->cm_fields
['N'] ? msg
->cm_fields
['N'] : ""),
276 (msg
->cm_fields
['F'] ? msg
->cm_fields
['F'] : ""),
277 (msg
->cm_fields
['U'] ? msg
->cm_fields
['U'] : "")
279 CtdlFreeMessage(msg
);
284 /* Determine if a given message matches the fields in a message template.
285 * Return 0 for a successful match.
287 int CtdlMsgCmp(struct CtdlMessage
*msg
, struct CtdlMessage
*template) {
290 /* If there aren't any fields in the template, all messages will
293 if (template == NULL
) return(0);
295 /* Null messages are bogus. */
296 if (msg
== NULL
) return(1);
298 for (i
='A'; i
<='Z'; ++i
) {
299 if (template->cm_fields
[i
] != NULL
) {
300 if (msg
->cm_fields
[i
] == NULL
) {
303 if (strcasecmp(msg
->cm_fields
[i
],
304 template->cm_fields
[i
])) return 1;
308 /* All compares succeeded: we have a match! */
315 * Retrieve the "seen" message list for the current room.
317 void CtdlGetSeen(char *buf
, int which_set
) {
320 /* Learn about the user and room in question */
321 CtdlGetRelationship(&vbuf
, &CC
->user
, &CC
->room
);
323 if (which_set
== ctdlsetseen_seen
)
324 safestrncpy(buf
, vbuf
.v_seen
, SIZ
);
325 if (which_set
== ctdlsetseen_answered
)
326 safestrncpy(buf
, vbuf
.v_answered
, SIZ
);
332 * Manipulate the "seen msgs" string (or other message set strings)
334 void CtdlSetSeen(long *target_msgnums
, int num_target_msgnums
,
335 int target_setting
, int which_set
,
336 struct ctdluser
*which_user
, struct ctdlroom
*which_room
) {
337 struct cdbdata
*cdbfr
;
347 char *is_set
; /* actually an array of booleans */
351 char setstr
[SIZ
], lostr
[SIZ
], histr
[SIZ
];
353 /* Don't bother doing *anything* if we were passed a list of zero messages */
354 if (num_target_msgnums
< 1) {
358 /* If no room was specified, we go with the current room. */
360 which_room
= &CC
->room
;
363 /* If no user was specified, we go with the current user. */
365 which_user
= &CC
->user
;
368 CtdlLogPrintf(CTDL_DEBUG
, "CtdlSetSeen(%d msgs starting with %ld, %s, %d) in <%s>\n",
369 num_target_msgnums
, target_msgnums
[0],
370 (target_setting
? "SET" : "CLEAR"),
374 /* Learn about the user and room in question */
375 CtdlGetRelationship(&vbuf
, which_user
, which_room
);
377 /* Load the message list */
378 cdbfr
= cdb_fetch(CDB_MSGLISTS
, &which_room
->QRnumber
, sizeof(long));
380 msglist
= (long *) cdbfr
->ptr
;
381 cdbfr
->ptr
= NULL
; /* CtdlSetSeen() now owns this memory */
382 num_msgs
= cdbfr
->len
/ sizeof(long);
385 return; /* No messages at all? No further action. */
388 is_set
= malloc(num_msgs
* sizeof(char));
389 memset(is_set
, 0, (num_msgs
* sizeof(char)) );
391 /* Decide which message set we're manipulating */
393 case ctdlsetseen_seen
:
394 safestrncpy(vset
, vbuf
.v_seen
, sizeof vset
);
396 case ctdlsetseen_answered
:
397 safestrncpy(vset
, vbuf
.v_answered
, sizeof vset
);
402 #if 0 /* This is a special diagnostic section. Do not allow it to run during normal operation. */
403 CtdlLogPrintf(CTDL_DEBUG
, "There are %d messages in the room.\n", num_msgs
);
404 for (i
=0; i
<num_msgs
; ++i
) {
405 if ((i
> 0) && (msglist
[i
] <= msglist
[i
-1])) abort();
407 CtdlLogPrintf(CTDL_DEBUG
, "We are twiddling %d of them.\n", num_target_msgnums
);
408 for (k
=0; k
<num_target_msgnums
; ++k
) {
409 if ((k
> 0) && (target_msgnums
[k
] <= target_msgnums
[k
-1])) abort();
413 CtdlLogPrintf(CTDL_DEBUG
, "before update: %s\n", vset
);
415 /* Translate the existing sequence set into an array of booleans */
416 num_sets
= num_tokens(vset
, ',');
417 for (s
=0; s
<num_sets
; ++s
) {
418 extract_token(setstr
, vset
, s
, ',', sizeof setstr
);
420 extract_token(lostr
, setstr
, 0, ':', sizeof lostr
);
421 if (num_tokens(setstr
, ':') >= 2) {
422 extract_token(histr
, setstr
, 1, ':', sizeof histr
);
425 strcpy(histr
, lostr
);
428 if (!strcmp(histr
, "*")) {
435 for (i
= 0; i
< num_msgs
; ++i
) {
436 if ((msglist
[i
] >= lo
) && (msglist
[i
] <= hi
)) {
443 /* Now translate the array of booleans back into a sequence set */
449 for (i
=0; i
<num_msgs
; ++i
) {
453 for (k
=0; k
<num_target_msgnums
; ++k
) {
454 if (msglist
[i
] == target_msgnums
[k
]) {
455 is_seen
= target_setting
;
459 w
= 0; /* set to 1 if we write something to the string */
461 if ((was_seen
== 0) && (is_seen
== 1)) {
464 else if ((was_seen
== 1) && (is_seen
== 0)) {
468 if (!IsEmptyStr(vset
)) {
472 sprintf(&vset
[strlen(vset
)], "%ld", hi
);
475 sprintf(&vset
[strlen(vset
)], "%ld:%ld", lo
, hi
);
478 else if ((is_seen
) && (i
== num_msgs
- 1)) {
480 if (!IsEmptyStr(vset
)) {
483 if ((i
==0) || (was_seen
== 0)) {
484 sprintf(&vset
[strlen(vset
)], "%ld", msglist
[i
]);
487 sprintf(&vset
[strlen(vset
)], "%ld:%ld", lo
, msglist
[i
]);
491 /* If the string is getting too long, truncate it at the beginning; repeat up to 9 times */
492 if (w
) for (j
=0; j
<9; ++j
) {
493 if ((strlen(vset
) + 20) > sizeof vset
) {
494 remove_token(vset
, 0, ',');
495 if (which_set
== ctdlsetseen_seen
) {
497 sprintf(temp
, "1:%ld,", atol(vset
)-1L);
507 CtdlLogPrintf(CTDL_DEBUG
, " after update: %s\n", vset
);
509 /* Decide which message set we're manipulating */
511 case ctdlsetseen_seen
:
512 safestrncpy(vbuf
.v_seen
, vset
, sizeof vbuf
.v_seen
);
514 case ctdlsetseen_answered
:
515 safestrncpy(vbuf
.v_answered
, vset
, sizeof vbuf
.v_answered
);
521 CtdlSetRelationship(&vbuf
, which_user
, which_room
);
526 * API function to perform an operation for each qualifying message in the
527 * current room. (Returns the number of messages processed.)
529 int CtdlForEachMessage(int mode
, long ref
, char *search_string
,
531 struct CtdlMessage
*compare
,
532 void (*CallBack
) (long, void *),
538 struct cdbdata
*cdbfr
;
539 long *msglist
= NULL
;
541 int num_processed
= 0;
544 struct CtdlMessage
*msg
= NULL
;
547 int printed_lastold
= 0;
548 int num_search_msgs
= 0;
549 long *search_msgs
= NULL
;
551 int need_to_free_re
= 0;
554 if ((content_type
) && (!IsEmptyStr(content_type
))) {
555 regcomp(&re
, content_type
, 0);
559 /* Learn about the user and room in question */
560 getuser(&CC
->user
, CC
->curr_user
);
561 CtdlGetRelationship(&vbuf
, &CC
->user
, &CC
->room
);
563 /* Load the message list */
564 cdbfr
= cdb_fetch(CDB_MSGLISTS
, &CC
->room
.QRnumber
, sizeof(long));
566 msglist
= (long *) cdbfr
->ptr
;
567 num_msgs
= cdbfr
->len
/ sizeof(long);
569 if (need_to_free_re
) regfree(&re
);
570 return 0; /* No messages at all? No further action. */
575 * Now begin the traversal.
577 if (num_msgs
> 0) for (a
= 0; a
< num_msgs
; ++a
) {
579 /* If the caller is looking for a specific MIME type, filter
580 * out all messages which are not of the type requested.
582 if ((content_type
!= NULL
) && (!IsEmptyStr(content_type
))) {
584 /* This call to GetMetaData() sits inside this loop
585 * so that we only do the extra database read per msg
586 * if we need to. Doing the extra read all the time
587 * really kills the server. If we ever need to use
588 * metadata for another search criterion, we need to
589 * move the read somewhere else -- but still be smart
590 * enough to only do the read if the caller has
591 * specified something that will need it.
593 GetMetaData(&smi
, msglist
[a
]);
595 /* if (strcasecmp(smi.meta_content_type, content_type)) { old non-regex way */
596 if (regexec(&re
, smi
.meta_content_type
, 1, &pm
, 0) != 0) {
602 num_msgs
= sort_msglist(msglist
, num_msgs
);
604 /* If a template was supplied, filter out the messages which
605 * don't match. (This could induce some delays!)
608 if (compare
!= NULL
) {
609 for (a
= 0; a
< num_msgs
; ++a
) {
610 msg
= CtdlFetchMessage(msglist
[a
], 1);
612 if (CtdlMsgCmp(msg
, compare
)) {
615 CtdlFreeMessage(msg
);
621 /* If a search string was specified, get a message list from
622 * the full text index and remove messages which aren't on both
626 * Since the lists are sorted and strictly ascending, and the
627 * output list is guaranteed to be shorter than or equal to the
628 * input list, we overwrite the bottom of the input list. This
629 * eliminates the need to memmove big chunks of the list over and
632 if ( (num_msgs
> 0) && (mode
== MSGS_SEARCH
) && (search_string
) ) {
634 /* Call search module via hook mechanism.
635 * NULL means use any search function available.
636 * otherwise replace with a char * to name of search routine
638 CtdlModuleDoSearch(&num_search_msgs
, &search_msgs
, search_string
, "fulltext");
640 if (num_search_msgs
> 0) {
644 orig_num_msgs
= num_msgs
;
646 for (i
=0; i
<orig_num_msgs
; ++i
) {
647 for (j
=0; j
<num_search_msgs
; ++j
) {
648 if (msglist
[i
] == search_msgs
[j
]) {
649 msglist
[num_msgs
++] = msglist
[i
];
655 num_msgs
= 0; /* No messages qualify */
657 if (search_msgs
!= NULL
) free(search_msgs
);
659 /* Now that we've purged messages which don't contain the search
660 * string, treat a MSGS_SEARCH just like a MSGS_ALL from this
667 * Now iterate through the message list, according to the
668 * criteria supplied by the caller.
671 for (a
= 0; a
< num_msgs
; ++a
) {
672 thismsg
= msglist
[a
];
673 if (mode
== MSGS_ALL
) {
677 is_seen
= is_msg_in_sequence_set(
678 vbuf
.v_seen
, thismsg
);
679 if (is_seen
) lastold
= thismsg
;
685 || ((mode
== MSGS_OLD
) && (is_seen
))
686 || ((mode
== MSGS_NEW
) && (!is_seen
))
687 || ((mode
== MSGS_LAST
) && (a
>= (num_msgs
- ref
)))
688 || ((mode
== MSGS_FIRST
) && (a
< ref
))
689 || ((mode
== MSGS_GT
) && (thismsg
> ref
))
690 || ((mode
== MSGS_EQ
) && (thismsg
== ref
))
693 if ((mode
== MSGS_NEW
) && (CC
->user
.flags
& US_LASTOLD
) && (lastold
> 0L) && (printed_lastold
== 0) && (!is_seen
)) {
695 CallBack(lastold
, userdata
);
699 if (CallBack
) CallBack(thismsg
, userdata
);
703 cdb_free(cdbfr
); /* Clean up */
704 if (need_to_free_re
) regfree(&re
);
705 return num_processed
;
711 * cmd_msgs() - get list of message #'s in this room
712 * implements the MSGS server command using CtdlForEachMessage()
714 void cmd_msgs(char *cmdbuf
)
723 int with_template
= 0;
724 struct CtdlMessage
*template = NULL
;
725 int with_headers
= 0;
726 char search_string
[1024];
728 extract_token(which
, cmdbuf
, 0, '|', sizeof which
);
729 cm_ref
= extract_int(cmdbuf
, 1);
730 extract_token(search_string
, cmdbuf
, 1, '|', sizeof search_string
);
731 with_template
= extract_int(cmdbuf
, 2);
732 with_headers
= extract_int(cmdbuf
, 3);
735 if (!strncasecmp(which
, "OLD", 3))
737 else if (!strncasecmp(which
, "NEW", 3))
739 else if (!strncasecmp(which
, "FIRST", 5))
741 else if (!strncasecmp(which
, "LAST", 4))
743 else if (!strncasecmp(which
, "GT", 2))
745 else if (!strncasecmp(which
, "SEARCH", 6))
750 if ((!(CC
->logged_in
)) && (!(CC
->internal_pgm
))) {
751 cprintf("%d not logged in\n", ERROR
+ NOT_LOGGED_IN
);
755 if ( (mode
== MSGS_SEARCH
) && (!config
.c_enable_fulltext
) ) {
756 cprintf("%d Full text index is not enabled on this server.\n",
757 ERROR
+ CMD_NOT_SUPPORTED
);
763 cprintf("%d Send template then receive message list\n",
765 template = (struct CtdlMessage
*)
766 malloc(sizeof(struct CtdlMessage
));
767 memset(template, 0, sizeof(struct CtdlMessage
));
768 template->cm_magic
= CTDLMESSAGE_MAGIC
;
769 template->cm_anon_type
= MES_NORMAL
;
771 while(client_getln(buf
, sizeof buf
) >= 0 && strcmp(buf
,"000")) {
772 extract_token(tfield
, buf
, 0, '|', sizeof tfield
);
773 extract_token(tvalue
, buf
, 1, '|', sizeof tvalue
);
774 for (i
='A'; i
<='Z'; ++i
) if (msgkeys
[i
]!=NULL
) {
775 if (!strcasecmp(tfield
, msgkeys
[i
])) {
776 template->cm_fields
[i
] =
784 cprintf("%d \n", LISTING_FOLLOWS
);
787 CtdlForEachMessage(mode
,
788 ( (mode
== MSGS_SEARCH
) ? 0 : cm_ref
),
789 ( (mode
== MSGS_SEARCH
) ? search_string
: NULL
),
792 (with_headers
? headers_listing
: simple_listing
),
795 if (template != NULL
) CtdlFreeMessage(template);
803 * help_subst() - support routine for help file viewer
805 void help_subst(char *strbuf
, char *source
, char *dest
)
810 while (p
= pattern2(strbuf
, source
), (p
>= 0)) {
811 strcpy(workbuf
, &strbuf
[p
+ strlen(source
)]);
812 strcpy(&strbuf
[p
], dest
);
813 strcat(strbuf
, workbuf
);
818 void do_help_subst(char *buffer
)
822 help_subst(buffer
, "^nodename", config
.c_nodename
);
823 help_subst(buffer
, "^humannode", config
.c_humannode
);
824 help_subst(buffer
, "^fqdn", config
.c_fqdn
);
825 help_subst(buffer
, "^username", CC
->user
.fullname
);
826 snprintf(buf2
, sizeof buf2
, "%ld", CC
->user
.usernum
);
827 help_subst(buffer
, "^usernum", buf2
);
828 help_subst(buffer
, "^sysadm", config
.c_sysadm
);
829 help_subst(buffer
, "^variantname", CITADEL
);
830 snprintf(buf2
, sizeof buf2
, "%d", config
.c_maxsessions
);
831 help_subst(buffer
, "^maxsessions", buf2
);
832 help_subst(buffer
, "^bbsdir", ctdl_message_dir
);
838 * memfmout() - Citadel text formatter and paginator.
839 * Although the original purpose of this routine was to format
840 * text to the reader's screen width, all we're really using it
841 * for here is to format text out to 80 columns before sending it
842 * to the client. The client software may reformat it again.
845 char *mptr
, /* where are we going to get our text from? */
846 char subst
, /* nonzero if we should do substitutions */
847 char *nl
) /* string to terminate lines with */
855 static int width
= 80;
860 c
= 1; /* c is the current pos */
864 while (ch
= *mptr
, ((ch
!= 0) && (strlen(buffer
) < 126))) {
866 buffer
[strlen(buffer
) + 1] = 0;
867 buffer
[strlen(buffer
)] = ch
;
870 if (buffer
[0] == '^')
871 do_help_subst(buffer
);
873 buffer
[strlen(buffer
) + 1] = 0;
875 strcpy(buffer
, &buffer
[1]);
883 if (((ch
== 13) || (ch
== 10)) && (old
!= 13) && (old
!= 10)) {
886 if (((old
== 13) || (old
== 10)) && (isspace(real
))) {
891 if (((strlen(aaa
) + c
) > (width
- 5)) && (strlen(aaa
) > (width
- 5))) {
892 cprintf("%s%s", nl
, aaa
);
901 if ((strlen(aaa
) + c
) > (width
- 5)) {
910 if ((ch
== 13) || (ch
== 10)) {
911 cprintf("%s%s", aaa
, nl
);
918 cprintf("%s%s", aaa
, nl
);
924 * Callback function for mime parser that simply lists the part
926 void list_this_part(char *name
, char *filename
, char *partnum
, char *disp
,
927 void *content
, char *cbtype
, char *cbcharset
, size_t length
, char *encoding
,
928 char *cbid
, void *cbuserdata
)
932 ma
= (struct ma_info
*)cbuserdata
;
933 if (ma
->is_ma
== 0) {
934 cprintf("part=%s|%s|%s|%s|%s|%ld|%s\n",
935 name
, filename
, partnum
, disp
, cbtype
, (long)length
, cbid
);
940 * Callback function for multipart prefix
942 void list_this_pref(char *name
, char *filename
, char *partnum
, char *disp
,
943 void *content
, char *cbtype
, char *cbcharset
, size_t length
, char *encoding
,
944 char *cbid
, void *cbuserdata
)
948 ma
= (struct ma_info
*)cbuserdata
;
949 if (!strcasecmp(cbtype
, "multipart/alternative")) {
953 if (ma
->is_ma
== 0) {
954 cprintf("pref=%s|%s\n", partnum
, cbtype
);
959 * Callback function for multipart sufffix
961 void list_this_suff(char *name
, char *filename
, char *partnum
, char *disp
,
962 void *content
, char *cbtype
, char *cbcharset
, size_t length
, char *encoding
,
963 char *cbid
, void *cbuserdata
)
967 ma
= (struct ma_info
*)cbuserdata
;
968 if (ma
->is_ma
== 0) {
969 cprintf("suff=%s|%s\n", partnum
, cbtype
);
971 if (!strcasecmp(cbtype
, "multipart/alternative")) {
978 * Callback function for mime parser that opens a section for downloading
980 void mime_download(char *name
, char *filename
, char *partnum
, char *disp
,
981 void *content
, char *cbtype
, char *cbcharset
, size_t length
,
982 char *encoding
, char *cbid
, void *cbuserdata
)
985 /* Silently go away if there's already a download open. */
986 if (CC
->download_fp
!= NULL
)
990 (!IsEmptyStr(partnum
) && (!strcasecmp(CC
->download_desired_section
, partnum
)))
991 || (!IsEmptyStr(cbid
) && (!strcasecmp(CC
->download_desired_section
, cbid
)))
993 CC
->download_fp
= tmpfile();
994 if (CC
->download_fp
== NULL
)
997 fwrite(content
, length
, 1, CC
->download_fp
);
998 fflush(CC
->download_fp
);
999 rewind(CC
->download_fp
);
1001 OpenCmdResult(filename
, cbtype
);
1008 * Callback function for mime parser that outputs a section all at once.
1009 * We can specify the desired section by part number *or* content-id.
1011 void mime_spew_section(char *name
, char *filename
, char *partnum
, char *disp
,
1012 void *content
, char *cbtype
, char *cbcharset
, size_t length
,
1013 char *encoding
, char *cbid
, void *cbuserdata
)
1015 int *found_it
= (int *)cbuserdata
;
1018 (!IsEmptyStr(partnum
) && (!strcasecmp(CC
->download_desired_section
, partnum
)))
1019 || (!IsEmptyStr(cbid
) && (!strcasecmp(CC
->download_desired_section
, cbid
)))
1022 cprintf("%d %d\n", BINARY_FOLLOWS
, (int)length
);
1023 client_write(content
, length
);
1030 * Load a message from disk into memory.
1031 * This is used by CtdlOutputMsg() and other fetch functions.
1033 * NOTE: Caller is responsible for freeing the returned CtdlMessage struct
1034 * using the CtdlMessageFree() function.
1036 struct CtdlMessage
*CtdlFetchMessage(long msgnum
, int with_body
)
1038 struct cdbdata
*dmsgtext
;
1039 struct CtdlMessage
*ret
= NULL
;
1043 cit_uint8_t field_header
;
1045 CtdlLogPrintf(CTDL_DEBUG
, "CtdlFetchMessage(%ld, %d)\n", msgnum
, with_body
);
1047 dmsgtext
= cdb_fetch(CDB_MSGMAIN
, &msgnum
, sizeof(long));
1048 if (dmsgtext
== NULL
) {
1051 mptr
= dmsgtext
->ptr
;
1052 upper_bound
= mptr
+ dmsgtext
->len
;
1054 /* Parse the three bytes that begin EVERY message on disk.
1055 * The first is always 0xFF, the on-disk magic number.
1056 * The second is the anonymous/public type byte.
1057 * The third is the format type byte (vari, fixed, or MIME).
1061 CtdlLogPrintf(CTDL_ERR
, "Message %ld appears to be corrupted.\n", msgnum
);
1065 ret
= (struct CtdlMessage
*) malloc(sizeof(struct CtdlMessage
));
1066 memset(ret
, 0, sizeof(struct CtdlMessage
));
1068 ret
->cm_magic
= CTDLMESSAGE_MAGIC
;
1069 ret
->cm_anon_type
= *mptr
++; /* Anon type byte */
1070 ret
->cm_format_type
= *mptr
++; /* Format type byte */
1073 * The rest is zero or more arbitrary fields. Load them in.
1074 * We're done when we encounter either a zero-length field or
1075 * have just processed the 'M' (message text) field.
1078 if (mptr
>= upper_bound
) {
1081 field_header
= *mptr
++;
1082 ret
->cm_fields
[field_header
] = strdup(mptr
);
1084 while (*mptr
++ != 0); /* advance to next field */
1086 } while ((mptr
< upper_bound
) && (field_header
!= 'M'));
1090 /* Always make sure there's something in the msg text field. If
1091 * it's NULL, the message text is most likely stored separately,
1092 * so go ahead and fetch that. Failing that, just set a dummy
1093 * body so other code doesn't barf.
1095 if ( (ret
->cm_fields
['M'] == NULL
) && (with_body
) ) {
1096 dmsgtext
= cdb_fetch(CDB_BIGMSGS
, &msgnum
, sizeof(long));
1097 if (dmsgtext
!= NULL
) {
1098 ret
->cm_fields
['M'] = strdup(dmsgtext
->ptr
);
1102 if (ret
->cm_fields
['M'] == NULL
) {
1103 ret
->cm_fields
['M'] = strdup("\r\n\r\n (no text)\r\n");
1106 /* Perform "before read" hooks (aborting if any return nonzero) */
1107 if (PerformMessageHooks(ret
, EVT_BEFOREREAD
) > 0) {
1108 CtdlFreeMessage(ret
);
1117 * Returns 1 if the supplied pointer points to a valid Citadel message.
1118 * If the pointer is NULL or the magic number check fails, returns 0.
1120 int is_valid_message(struct CtdlMessage
*msg
) {
1123 if ((msg
->cm_magic
) != CTDLMESSAGE_MAGIC
) {
1124 CtdlLogPrintf(CTDL_WARNING
, "is_valid_message() -- self-check failed\n");
1132 * 'Destructor' for struct CtdlMessage
1134 void CtdlFreeMessage(struct CtdlMessage
*msg
)
1138 if (is_valid_message(msg
) == 0)
1140 if (msg
!= NULL
) free (msg
);
1144 for (i
= 0; i
< 256; ++i
)
1145 if (msg
->cm_fields
[i
] != NULL
) {
1146 free(msg
->cm_fields
[i
]);
1149 msg
->cm_magic
= 0; /* just in case */
1155 * Pre callback function for multipart/alternative
1157 * NOTE: this differs from the standard behavior for a reason. Normally when
1158 * displaying multipart/alternative you want to show the _last_ usable
1159 * format in the message. Here we show the _first_ one, because it's
1160 * usually text/plain. Since this set of functions is designed for text
1161 * output to non-MIME-aware clients, this is the desired behavior.
1164 void fixed_output_pre(char *name
, char *filename
, char *partnum
, char *disp
,
1165 void *content
, char *cbtype
, char *cbcharset
, size_t length
, char *encoding
,
1166 char *cbid
, void *cbuserdata
)
1170 ma
= (struct ma_info
*)cbuserdata
;
1171 CtdlLogPrintf(CTDL_DEBUG
, "fixed_output_pre() type=<%s>\n", cbtype
);
1172 if (!strcasecmp(cbtype
, "multipart/alternative")) {
1176 if (!strcasecmp(cbtype
, "message/rfc822")) {
1182 * Post callback function for multipart/alternative
1184 void fixed_output_post(char *name
, char *filename
, char *partnum
, char *disp
,
1185 void *content
, char *cbtype
, char *cbcharset
, size_t length
,
1186 char *encoding
, char *cbid
, void *cbuserdata
)
1190 ma
= (struct ma_info
*)cbuserdata
;
1191 CtdlLogPrintf(CTDL_DEBUG
, "fixed_output_post() type=<%s>\n", cbtype
);
1192 if (!strcasecmp(cbtype
, "multipart/alternative")) {
1196 if (!strcasecmp(cbtype
, "message/rfc822")) {
1202 * Inline callback function for mime parser that wants to display text
1204 void fixed_output(char *name
, char *filename
, char *partnum
, char *disp
,
1205 void *content
, char *cbtype
, char *cbcharset
, size_t length
,
1206 char *encoding
, char *cbid
, void *cbuserdata
)
1213 ma
= (struct ma_info
*)cbuserdata
;
1215 CtdlLogPrintf(CTDL_DEBUG
,
1216 "fixed_output() part %s: %s (%s) (%ld bytes)\n",
1217 partnum
, filename
, cbtype
, (long)length
);
1220 * If we're in the middle of a multipart/alternative scope and
1221 * we've already printed another section, skip this one.
1223 if ( (ma
->is_ma
) && (ma
->did_print
) ) {
1224 CtdlLogPrintf(CTDL_DEBUG
, "Skipping part %s (%s)\n", partnum
, cbtype
);
1229 if ( (!strcasecmp(cbtype
, "text/plain"))
1230 || (IsEmptyStr(cbtype
)) ) {
1233 client_write(wptr
, length
);
1234 if (wptr
[length
-1] != '\n') {
1241 if (!strcasecmp(cbtype
, "text/html")) {
1242 ptr
= html_to_ascii(content
, length
, 80, 0);
1244 client_write(ptr
, wlen
);
1245 if (ptr
[wlen
-1] != '\n') {
1252 if (ma
->use_fo_hooks
) {
1253 if (PerformFixedOutputHooks(cbtype
, content
, length
)) {
1254 /* above function returns nonzero if it handled the part */
1259 if (strncasecmp(cbtype
, "multipart/", 10)) {
1260 cprintf("Part %s: %s (%s) (%ld bytes)\r\n",
1261 partnum
, filename
, cbtype
, (long)length
);
1267 * The client is elegant and sophisticated and wants to be choosy about
1268 * MIME content types, so figure out which multipart/alternative part
1269 * we're going to send.
1271 * We use a system of weights. When we find a part that matches one of the
1272 * MIME types we've declared as preferential, we can store it in ma->chosen_part
1273 * and then set ma->chosen_pref to that MIME type's position in our preference
1274 * list. If we then hit another match, we only replace the first match if
1275 * the preference value is lower.
1277 void choose_preferred(char *name
, char *filename
, char *partnum
, char *disp
,
1278 void *content
, char *cbtype
, char *cbcharset
, size_t length
,
1279 char *encoding
, char *cbid
, void *cbuserdata
)
1285 ma
= (struct ma_info
*)cbuserdata
;
1287 // NOTE: REMOVING THIS CONDITIONAL FIXES BUG 220
1288 // http://bugzilla.citadel.org/show_bug.cgi?id=220
1289 // I don't know if there are any side effects! Please TEST TEST TEST
1290 //if (ma->is_ma > 0) {
1292 for (i
=0; i
<num_tokens(CC
->preferred_formats
, '|'); ++i
) {
1293 extract_token(buf
, CC
->preferred_formats
, i
, '|', sizeof buf
);
1294 if ( (!strcasecmp(buf
, cbtype
)) && (!ma
->freeze
) ) {
1295 if (i
< ma
->chosen_pref
) {
1296 CtdlLogPrintf(CTDL_DEBUG
, "Setting chosen part: <%s>\n", partnum
);
1297 safestrncpy(ma
->chosen_part
, partnum
, sizeof ma
->chosen_part
);
1298 ma
->chosen_pref
= i
;
1305 * Now that we've chosen our preferred part, output it.
1307 void output_preferred(char *name
, char *filename
, char *partnum
, char *disp
,
1308 void *content
, char *cbtype
, char *cbcharset
, size_t length
,
1309 char *encoding
, char *cbid
, void *cbuserdata
)
1313 int add_newline
= 0;
1317 ma
= (struct ma_info
*)cbuserdata
;
1319 /* This is not the MIME part you're looking for... */
1320 if (strcasecmp(partnum
, ma
->chosen_part
)) return;
1322 /* If the content-type of this part is in our preferred formats
1323 * list, we can simply output it verbatim.
1325 for (i
=0; i
<num_tokens(CC
->preferred_formats
, '|'); ++i
) {
1326 extract_token(buf
, CC
->preferred_formats
, i
, '|', sizeof buf
);
1327 if (!strcasecmp(buf
, cbtype
)) {
1328 /* Yeah! Go! W00t!! */
1330 text_content
= (char *)content
;
1331 if (text_content
[length
-1] != '\n') {
1334 cprintf("Content-type: %s", cbtype
);
1335 if (!IsEmptyStr(cbcharset
)) {
1336 cprintf("; charset=%s", cbcharset
);
1338 cprintf("\nContent-length: %d\n",
1339 (int)(length
+ add_newline
) );
1340 if (!IsEmptyStr(encoding
)) {
1341 cprintf("Content-transfer-encoding: %s\n", encoding
);
1344 cprintf("Content-transfer-encoding: 7bit\n");
1346 cprintf("X-Citadel-MSG4-Partnum: %s\n", partnum
);
1348 client_write(content
, length
);
1349 if (add_newline
) cprintf("\n");
1354 /* No translations required or possible: output as text/plain */
1355 cprintf("Content-type: text/plain\n\n");
1356 fixed_output(name
, filename
, partnum
, disp
, content
, cbtype
, cbcharset
,
1357 length
, encoding
, cbid
, cbuserdata
);
1362 char desired_section
[64];
1369 * Callback function for
1371 void extract_encapsulated_message(char *name
, char *filename
, char *partnum
, char *disp
,
1372 void *content
, char *cbtype
, char *cbcharset
, size_t length
,
1373 char *encoding
, char *cbid
, void *cbuserdata
)
1375 struct encapmsg
*encap
;
1377 encap
= (struct encapmsg
*)cbuserdata
;
1379 /* Only proceed if this is the desired section... */
1380 if (!strcasecmp(encap
->desired_section
, partnum
)) {
1381 encap
->msglen
= length
;
1382 encap
->msg
= malloc(length
+ 2);
1383 memcpy(encap
->msg
, content
, length
);
1393 * Get a message off disk. (returns om_* values found in msgbase.h)
1396 int CtdlOutputMsg(long msg_num
, /* message number (local) to fetch */
1397 int mode
, /* how would you like that message? */
1398 int headers_only
, /* eschew the message body? */
1399 int do_proto
, /* do Citadel protocol responses? */
1400 int crlf
, /* Use CRLF newlines instead of LF? */
1401 char *section
, /* NULL or a message/rfc822 section */
1402 int flags
/* should the bessage be exported clean? */
1404 struct CtdlMessage
*TheMessage
= NULL
;
1405 int retcode
= om_no_such_msg
;
1406 struct encapmsg encap
;
1408 CtdlLogPrintf(CTDL_DEBUG
, "CtdlOutputMsg() msgnum=%ld, mode=%d, section=%s\n",
1410 (section
? section
: "<>")
1413 if ((!(CC
->logged_in
)) && (!(CC
->internal_pgm
))) {
1414 if (do_proto
) cprintf("%d Not logged in.\n",
1415 ERROR
+ NOT_LOGGED_IN
);
1416 return(om_not_logged_in
);
1419 /* FIXME: check message id against msglist for this room */
1422 * Fetch the message from disk. If we're in HEADERS_FAST mode,
1423 * request that we don't even bother loading the body into memory.
1425 if (headers_only
== HEADERS_FAST
) {
1426 TheMessage
= CtdlFetchMessage(msg_num
, 0);
1429 TheMessage
= CtdlFetchMessage(msg_num
, 1);
1432 if (TheMessage
== NULL
) {
1433 if (do_proto
) cprintf("%d Can't locate msg %ld on disk\n",
1434 ERROR
+ MESSAGE_NOT_FOUND
, msg_num
);
1435 return(om_no_such_msg
);
1438 /* Here is the weird form of this command, to process only an
1439 * encapsulated message/rfc822 section.
1441 if (section
) if (!IsEmptyStr(section
)) if (strcmp(section
, "0")) {
1442 memset(&encap
, 0, sizeof encap
);
1443 safestrncpy(encap
.desired_section
, section
, sizeof encap
.desired_section
);
1444 mime_parser(TheMessage
->cm_fields
['M'],
1446 *extract_encapsulated_message
,
1447 NULL
, NULL
, (void *)&encap
, 0
1449 CtdlFreeMessage(TheMessage
);
1453 encap
.msg
[encap
.msglen
] = 0;
1454 TheMessage
= convert_internet_message(encap
.msg
);
1455 encap
.msg
= NULL
; /* no free() here, TheMessage owns it now */
1457 /* Now we let it fall through to the bottom of this
1458 * function, because TheMessage now contains the
1459 * encapsulated message instead of the top-level
1460 * message. Isn't that neat?
1465 if (do_proto
) cprintf("%d msg %ld has no part %s\n",
1466 ERROR
+ MESSAGE_NOT_FOUND
, msg_num
, section
);
1467 retcode
= om_no_such_msg
;
1472 /* Ok, output the message now */
1473 retcode
= CtdlOutputPreLoadedMsg(TheMessage
, mode
, headers_only
, do_proto
, crlf
, flags
);
1474 CtdlFreeMessage(TheMessage
);
1480 char *qp_encode_email_addrs(char *source
)
1482 char user
[256], node
[256], name
[256];
1483 const char headerStr
[] = "=?UTF-8?Q?";
1487 int need_to_encode
= 0;
1493 long nAddrPtrMax
= 50;
1498 if (source
== NULL
) return source
;
1499 if (IsEmptyStr(source
)) return source
;
1501 AddrPtr
= malloc (sizeof (long) * nAddrPtrMax
);
1502 AddrUtf8
= malloc (sizeof (long) * nAddrPtrMax
);
1503 memset(AddrUtf8
, 0, sizeof (long) * nAddrPtrMax
);
1506 while (!IsEmptyStr (&source
[i
])) {
1507 if (nColons
>= nAddrPtrMax
){
1510 ptr
= (long *) malloc(sizeof (long) * nAddrPtrMax
* 2);
1511 memcpy (ptr
, AddrPtr
, sizeof (long) * nAddrPtrMax
);
1512 free (AddrPtr
), AddrPtr
= ptr
;
1514 ptr
= (long *) malloc(sizeof (long) * nAddrPtrMax
* 2);
1515 memset(&ptr
[nAddrPtrMax
], 0,
1516 sizeof (long) * nAddrPtrMax
);
1518 memcpy (ptr
, AddrUtf8
, sizeof (long) * nAddrPtrMax
);
1519 free (AddrUtf8
), AddrUtf8
= ptr
;
1522 if (((unsigned char) source
[i
] < 32) ||
1523 ((unsigned char) source
[i
] > 126)) {
1525 AddrUtf8
[nColons
] = 1;
1527 if (source
[i
] == '"')
1528 InQuotes
= !InQuotes
;
1529 if (!InQuotes
&& source
[i
] == ',') {
1530 AddrPtr
[nColons
] = i
;
1535 if (need_to_encode
== 0) {
1542 EncodedMaxLen
= nColons
* (sizeof(headerStr
) + 3) + SourceLen
* 3;
1543 Encoded
= (char*) malloc (EncodedMaxLen
);
1545 for (i
= 0; i
< nColons
; i
++)
1546 source
[AddrPtr
[i
]++] = '\0';
1550 for (i
= 0; i
< nColons
&& nPtr
!= NULL
; i
++) {
1551 nmax
= EncodedMaxLen
- (nPtr
- Encoded
);
1553 process_rfc822_addr(&source
[AddrPtr
[i
]],
1557 /* TODO: libIDN here ! */
1558 if (IsEmptyStr(name
)) {
1559 n
= snprintf(nPtr
, nmax
,
1560 (i
==0)?"%s@%s" : ",%s@%s",
1564 EncodedName
= rfc2047encode(name
, strlen(name
));
1565 n
= snprintf(nPtr
, nmax
,
1566 (i
==0)?"%s <%s@%s>" : ",%s <%s@%s>",
1567 EncodedName
, user
, node
);
1572 n
= snprintf(nPtr
, nmax
,
1573 (i
==0)?"%s" : ",%s",
1574 &source
[AddrPtr
[i
]]);
1580 ptr
= (char*) malloc(EncodedMaxLen
* 2);
1581 memcpy(ptr
, Encoded
, EncodedMaxLen
);
1582 nnPtr
= ptr
+ (nPtr
- Encoded
), nPtr
= nnPtr
;
1583 free(Encoded
), Encoded
= ptr
;
1585 i
--; /* do it once more with properly lengthened buffer */
1588 for (i
= 0; i
< nColons
; i
++)
1589 source
[--AddrPtr
[i
]] = ',';
1596 /* If the last item in a list of recipients was truncated to a partial address,
1597 * remove it completely in order to avoid choking libSieve
1599 void sanitize_truncated_recipient(char *str
)
1602 if (num_tokens(str
, ',') < 2) return;
1604 int len
= strlen(str
);
1605 if (len
< 900) return;
1606 if (len
> 998) str
[998] = 0;
1608 char *cptr
= strrchr(str
, ',');
1611 char *lptr
= strchr(cptr
, '<');
1612 char *rptr
= strchr(cptr
, '>');
1614 if ( (lptr
) && (rptr
) && (rptr
> lptr
) ) return;
1622 * Get a message off disk. (returns om_* values found in msgbase.h)
1624 int CtdlOutputPreLoadedMsg(
1625 struct CtdlMessage
*TheMessage
,
1626 int mode
, /* how would you like that message? */
1627 int headers_only
, /* eschew the message body? */
1628 int do_proto
, /* do Citadel protocol responses? */
1629 int crlf
, /* Use CRLF newlines instead of LF? */
1630 int flags
/* should the bessage be exported clean? */
1634 cit_uint8_t ch
, prev_ch
;
1636 char display_name
[256];
1638 char *nl
; /* newline string */
1640 int subject_found
= 0;
1643 /* Buffers needed for RFC822 translation. These are all filled
1644 * using functions that are bounds-checked, and therefore we can
1645 * make them substantially smaller than SIZ.
1652 char datestamp
[100];
1654 CtdlLogPrintf(CTDL_DEBUG
, "CtdlOutputPreLoadedMsg(TheMessage=%s, %d, %d, %d, %d\n",
1655 ((TheMessage
== NULL
) ? "NULL" : "not null"),
1656 mode
, headers_only
, do_proto
, crlf
);
1658 strcpy(mid
, "unknown");
1659 nl
= (crlf
? "\r\n" : "\n");
1661 if (!is_valid_message(TheMessage
)) {
1662 CtdlLogPrintf(CTDL_ERR
,
1663 "ERROR: invalid preloaded message for output\n");
1665 return(om_no_such_msg
);
1668 /* Are we downloading a MIME component? */
1669 if (mode
== MT_DOWNLOAD
) {
1670 if (TheMessage
->cm_format_type
!= FMT_RFC822
) {
1672 cprintf("%d This is not a MIME message.\n",
1673 ERROR
+ ILLEGAL_VALUE
);
1674 } else if (CC
->download_fp
!= NULL
) {
1675 if (do_proto
) cprintf(
1676 "%d You already have a download open.\n",
1677 ERROR
+ RESOURCE_BUSY
);
1679 /* Parse the message text component */
1680 mptr
= TheMessage
->cm_fields
['M'];
1681 mime_parser(mptr
, NULL
, *mime_download
, NULL
, NULL
, NULL
, 0);
1682 /* If there's no file open by this time, the requested
1683 * section wasn't found, so print an error
1685 if (CC
->download_fp
== NULL
) {
1686 if (do_proto
) cprintf(
1687 "%d Section %s not found.\n",
1688 ERROR
+ FILE_NOT_FOUND
,
1689 CC
->download_desired_section
);
1692 return((CC
->download_fp
!= NULL
) ? om_ok
: om_mime_error
);
1695 /* MT_SPEW_SECTION is like MT_DOWNLOAD except it outputs the whole MIME part
1696 * in a single server operation instead of opening a download file.
1698 if (mode
== MT_SPEW_SECTION
) {
1699 if (TheMessage
->cm_format_type
!= FMT_RFC822
) {
1701 cprintf("%d This is not a MIME message.\n",
1702 ERROR
+ ILLEGAL_VALUE
);
1704 /* Parse the message text component */
1707 mptr
= TheMessage
->cm_fields
['M'];
1708 mime_parser(mptr
, NULL
, *mime_spew_section
, NULL
, NULL
, (void *)&found_it
, 0);
1709 /* If section wasn't found, print an error
1712 if (do_proto
) cprintf(
1713 "%d Section %s not found.\n",
1714 ERROR
+ FILE_NOT_FOUND
,
1715 CC
->download_desired_section
);
1718 return((CC
->download_fp
!= NULL
) ? om_ok
: om_mime_error
);
1721 /* now for the user-mode message reading loops */
1722 if (do_proto
) cprintf("%d msg:\n", LISTING_FOLLOWS
);
1724 /* Does the caller want to skip the headers? */
1725 if (headers_only
== HEADERS_NONE
) goto START_TEXT
;
1727 /* Tell the client which format type we're using. */
1728 if ( (mode
== MT_CITADEL
) && (do_proto
) ) {
1729 cprintf("type=%d\n", TheMessage
->cm_format_type
);
1732 /* nhdr=yes means that we're only displaying headers, no body */
1733 if ( (TheMessage
->cm_anon_type
== MES_ANONONLY
)
1734 && ((mode
== MT_CITADEL
) || (mode
== MT_MIME
))
1737 cprintf("nhdr=yes\n");
1740 /* begin header processing loop for Citadel message format */
1742 if ((mode
== MT_CITADEL
) || (mode
== MT_MIME
)) {
1744 safestrncpy(display_name
, "<unknown>", sizeof display_name
);
1745 if (TheMessage
->cm_fields
['A']) {
1746 strcpy(buf
, TheMessage
->cm_fields
['A']);
1747 if (TheMessage
->cm_anon_type
== MES_ANONONLY
) {
1748 safestrncpy(display_name
, "****", sizeof display_name
);
1750 else if (TheMessage
->cm_anon_type
== MES_ANONOPT
) {
1751 safestrncpy(display_name
, "anonymous", sizeof display_name
);
1754 safestrncpy(display_name
, buf
, sizeof display_name
);
1756 if ((is_room_aide())
1757 && ((TheMessage
->cm_anon_type
== MES_ANONONLY
)
1758 || (TheMessage
->cm_anon_type
== MES_ANONOPT
))) {
1759 size_t tmp
= strlen(display_name
);
1760 snprintf(&display_name
[tmp
],
1761 sizeof display_name
- tmp
,
1766 /* Don't show Internet address for users on the
1767 * local Citadel network.
1770 if (TheMessage
->cm_fields
['N'] != NULL
)
1771 if (!IsEmptyStr(TheMessage
->cm_fields
['N']))
1772 if (haschar(TheMessage
->cm_fields
['N'], '.') == 0) {
1776 /* Now spew the header fields in the order we like them. */
1777 safestrncpy(allkeys
, FORDER
, sizeof allkeys
);
1778 for (i
=0; i
<strlen(allkeys
); ++i
) {
1779 k
= (int) allkeys
[i
];
1781 if ( (TheMessage
->cm_fields
[k
] != NULL
)
1782 && (msgkeys
[k
] != NULL
) ) {
1783 if ((k
== 'V') || (k
== 'R') || (k
== 'Y')) {
1784 sanitize_truncated_recipient(TheMessage
->cm_fields
[k
]);
1787 if (do_proto
) cprintf("%s=%s\n",
1791 else if ((k
== 'F') && (suppress_f
)) {
1794 /* Masquerade display name if needed */
1796 if (do_proto
) cprintf("%s=%s\n",
1798 TheMessage
->cm_fields
[k
]
1807 /* begin header processing loop for RFC822 transfer format */
1812 strcpy(snode
, NODENAME
);
1813 if (mode
== MT_RFC822
) {
1814 for (i
= 0; i
< 256; ++i
) {
1815 if (TheMessage
->cm_fields
[i
]) {
1816 mptr
= mpptr
= TheMessage
->cm_fields
[i
];
1819 safestrncpy(luser
, mptr
, sizeof luser
);
1820 safestrncpy(suser
, mptr
, sizeof suser
);
1822 else if (i
== 'Y') {
1823 if ((flags
& QP_EADDR
) != 0) {
1824 mptr
= qp_encode_email_addrs(mptr
);
1826 sanitize_truncated_recipient(mptr
);
1827 cprintf("CC: %s%s", mptr
, nl
);
1829 else if (i
== 'P') {
1830 cprintf("Return-Path: %s%s", mptr
, nl
);
1832 else if (i
== 'L') {
1833 cprintf("List-ID: %s%s", mptr
, nl
);
1835 else if (i
== 'V') {
1836 if ((flags
& QP_EADDR
) != 0)
1837 mptr
= qp_encode_email_addrs(mptr
);
1838 cprintf("Envelope-To: %s%s", mptr
, nl
);
1840 else if (i
== 'U') {
1841 cprintf("Subject: %s%s", mptr
, nl
);
1845 safestrncpy(mid
, mptr
, sizeof mid
);
1847 safestrncpy(fuser
, mptr
, sizeof fuser
);
1848 /* else if (i == 'O')
1849 cprintf("X-Citadel-Room: %s%s",
1852 safestrncpy(snode
, mptr
, sizeof snode
);
1855 if (haschar(mptr
, '@') == 0)
1857 sanitize_truncated_recipient(mptr
);
1858 cprintf("To: %s@%s", mptr
, config
.c_fqdn
);
1863 if ((flags
& QP_EADDR
) != 0) {
1864 mptr
= qp_encode_email_addrs(mptr
);
1866 sanitize_truncated_recipient(mptr
);
1867 cprintf("To: %s", mptr
);
1871 else if (i
== 'T') {
1872 datestring(datestamp
, sizeof datestamp
,
1873 atol(mptr
), DATESTRING_RFC822
);
1874 cprintf("Date: %s%s", datestamp
, nl
);
1876 else if (i
== 'W') {
1877 cprintf("References: ");
1878 k
= num_tokens(mptr
, '|');
1879 for (j
=0; j
<k
; ++j
) {
1880 extract_token(buf
, mptr
, j
, '|', sizeof buf
);
1881 cprintf("<%s>", buf
);
1894 if (subject_found
== 0) {
1895 cprintf("Subject: (no subject)%s", nl
);
1899 for (i
=0; !IsEmptyStr(&suser
[i
]); ++i
) {
1900 suser
[i
] = tolower(suser
[i
]);
1901 if (!isalnum(suser
[i
])) suser
[i
]='_';
1904 if (mode
== MT_RFC822
) {
1905 if (!strcasecmp(snode
, NODENAME
)) {
1906 safestrncpy(snode
, FQDN
, sizeof snode
);
1909 /* Construct a fun message id */
1910 cprintf("Message-ID: <%s", mid
);/// todo: this possibly breaks threadding mails.
1911 if (strchr(mid
, '@')==NULL
) {
1912 cprintf("@%s", snode
);
1916 if (!is_room_aide() && (TheMessage
->cm_anon_type
== MES_ANONONLY
)) {
1917 cprintf("From: \"----\" <x@x.org>%s", nl
);
1919 else if (!is_room_aide() && (TheMessage
->cm_anon_type
== MES_ANONOPT
)) {
1920 cprintf("From: \"anonymous\" <x@x.org>%s", nl
);
1922 else if (!IsEmptyStr(fuser
)) {
1923 cprintf("From: \"%s\" <%s>%s", luser
, fuser
, nl
);
1926 cprintf("From: \"%s\" <%s@%s>%s", luser
, suser
, snode
, nl
);
1929 /* Blank line signifying RFC822 end-of-headers */
1930 if (TheMessage
->cm_format_type
!= FMT_RFC822
) {
1935 /* end header processing loop ... at this point, we're in the text */
1937 if (headers_only
== HEADERS_FAST
) goto DONE
;
1938 mptr
= TheMessage
->cm_fields
['M'];
1940 /* Tell the client about the MIME parts in this message */
1941 if (TheMessage
->cm_format_type
== FMT_RFC822
) {
1942 if ( (mode
== MT_CITADEL
) || (mode
== MT_MIME
) ) {
1943 memset(&ma
, 0, sizeof(struct ma_info
));
1944 mime_parser(mptr
, NULL
,
1945 (do_proto
? *list_this_part
: NULL
),
1946 (do_proto
? *list_this_pref
: NULL
),
1947 (do_proto
? *list_this_suff
: NULL
),
1950 else if (mode
== MT_RFC822
) { /* unparsed RFC822 dump */
1951 char *start_of_text
= NULL
;
1952 start_of_text
= strstr(mptr
, "\n\r\n");
1953 if (start_of_text
== NULL
) start_of_text
= strstr(mptr
, "\n\n");
1954 if (start_of_text
== NULL
) start_of_text
= mptr
;
1956 start_of_text
= strstr(start_of_text
, "\n");
1961 int nllen
= strlen(nl
);
1963 while (ch
=*mptr
, ch
!=0) {
1969 ((headers_only
== HEADERS_NONE
) && (mptr
>= start_of_text
))
1970 || ((headers_only
== HEADERS_ONLY
) && (mptr
< start_of_text
))
1971 || ((headers_only
!= HEADERS_NONE
) && (headers_only
!= HEADERS_ONLY
))
1974 sprintf(&outbuf
[outlen
], "%s", nl
);
1978 outbuf
[outlen
++] = ch
;
1982 if (flags
& ESC_DOT
)
1984 if ((prev_ch
== 10) && (ch
== '.') && ((*(mptr
+1) == 13) || (*(mptr
+1) == 10)))
1986 outbuf
[outlen
++] = '.';
1991 if (outlen
> 1000) {
1992 client_write(outbuf
, outlen
);
1997 client_write(outbuf
, outlen
);
2005 if (headers_only
== HEADERS_ONLY
) {
2009 /* signify start of msg text */
2010 if ( (mode
== MT_CITADEL
) || (mode
== MT_MIME
) ) {
2011 if (do_proto
) cprintf("text\n");
2014 /* If the format type on disk is 1 (fixed-format), then we want
2015 * everything to be output completely literally ... regardless of
2016 * what message transfer format is in use.
2018 if (TheMessage
->cm_format_type
== FMT_FIXED
) {
2020 if (mode
== MT_MIME
) {
2021 cprintf("Content-type: text/plain\n\n");
2025 while (ch
= *mptr
++, ch
> 0) {
2028 if ((ch
== 10) || (buflen
> 250)) {
2030 cprintf("%s%s", buf
, nl
);
2039 if (!IsEmptyStr(buf
))
2040 cprintf("%s%s", buf
, nl
);
2043 /* If the message on disk is format 0 (Citadel vari-format), we
2044 * output using the formatter at 80 columns. This is the final output
2045 * form if the transfer format is RFC822, but if the transfer format
2046 * is Citadel proprietary, it'll still work, because the indentation
2047 * for new paragraphs is correct and the client will reformat the
2048 * message to the reader's screen width.
2050 if (TheMessage
->cm_format_type
== FMT_CITADEL
) {
2051 if (mode
== MT_MIME
) {
2052 cprintf("Content-type: text/x-citadel-variformat\n\n");
2054 memfmout(mptr
, 0, nl
);
2057 /* If the message on disk is format 4 (MIME), we've gotta hand it
2058 * off to the MIME parser. The client has already been told that
2059 * this message is format 1 (fixed format), so the callback function
2060 * we use will display those parts as-is.
2062 if (TheMessage
->cm_format_type
== FMT_RFC822
) {
2063 memset(&ma
, 0, sizeof(struct ma_info
));
2065 if (mode
== MT_MIME
) {
2066 ma
.use_fo_hooks
= 0;
2067 strcpy(ma
.chosen_part
, "1");
2068 ma
.chosen_pref
= 9999;
2069 mime_parser(mptr
, NULL
,
2070 *choose_preferred
, *fixed_output_pre
,
2071 *fixed_output_post
, (void *)&ma
, 0);
2072 mime_parser(mptr
, NULL
,
2073 *output_preferred
, NULL
, NULL
, (void *)&ma
, CC
->msg4_dont_decode
);
2076 ma
.use_fo_hooks
= 1;
2077 mime_parser(mptr
, NULL
,
2078 *fixed_output
, *fixed_output_pre
,
2079 *fixed_output_post
, (void *)&ma
, 0);
2084 DONE
: /* now we're done */
2085 if (do_proto
) cprintf("000\n");
2092 * display a message (mode 0 - Citadel proprietary)
2094 void cmd_msg0(char *cmdbuf
)
2097 int headers_only
= HEADERS_ALL
;
2099 msgid
= extract_long(cmdbuf
, 0);
2100 headers_only
= extract_int(cmdbuf
, 1);
2102 CtdlOutputMsg(msgid
, MT_CITADEL
, headers_only
, 1, 0, NULL
, 0);
2108 * display a message (mode 2 - RFC822)
2110 void cmd_msg2(char *cmdbuf
)
2113 int headers_only
= HEADERS_ALL
;
2115 msgid
= extract_long(cmdbuf
, 0);
2116 headers_only
= extract_int(cmdbuf
, 1);
2118 CtdlOutputMsg(msgid
, MT_RFC822
, headers_only
, 1, 1, NULL
, 0);
2124 * display a message (mode 3 - IGnet raw format - internal programs only)
2126 void cmd_msg3(char *cmdbuf
)
2129 struct CtdlMessage
*msg
= NULL
;
2132 if (CC
->internal_pgm
== 0) {
2133 cprintf("%d This command is for internal programs only.\n",
2134 ERROR
+ HIGHER_ACCESS_REQUIRED
);
2138 msgnum
= extract_long(cmdbuf
, 0);
2139 msg
= CtdlFetchMessage(msgnum
, 1);
2141 cprintf("%d Message %ld not found.\n",
2142 ERROR
+ MESSAGE_NOT_FOUND
, msgnum
);
2146 serialize_message(&smr
, msg
);
2147 CtdlFreeMessage(msg
);
2150 cprintf("%d Unable to serialize message\n",
2151 ERROR
+ INTERNAL_ERROR
);
2155 cprintf("%d %ld\n", BINARY_FOLLOWS
, (long)smr
.len
);
2156 client_write((char *)smr
.ser
, (int)smr
.len
);
2163 * Display a message using MIME content types
2165 void cmd_msg4(char *cmdbuf
)
2170 msgid
= extract_long(cmdbuf
, 0);
2171 extract_token(section
, cmdbuf
, 1, '|', sizeof section
);
2172 CtdlOutputMsg(msgid
, MT_MIME
, 0, 1, 0, (section
[0] ? section
: NULL
) , 0);
2178 * Client tells us its preferred message format(s)
2180 void cmd_msgp(char *cmdbuf
)
2182 if (!strcasecmp(cmdbuf
, "dont_decode")) {
2183 CC
->msg4_dont_decode
= 1;
2184 cprintf("%d MSG4 will not pre-decode messages.\n", CIT_OK
);
2187 safestrncpy(CC
->preferred_formats
, cmdbuf
, sizeof(CC
->preferred_formats
));
2188 cprintf("%d Preferred MIME formats have been set.\n", CIT_OK
);
2194 * Open a component of a MIME message as a download file
2196 void cmd_opna(char *cmdbuf
)
2199 char desired_section
[128];
2201 msgid
= extract_long(cmdbuf
, 0);
2202 extract_token(desired_section
, cmdbuf
, 1, '|', sizeof desired_section
);
2203 safestrncpy(CC
->download_desired_section
, desired_section
,
2204 sizeof CC
->download_desired_section
);
2205 CtdlOutputMsg(msgid
, MT_DOWNLOAD
, 0, 1, 1, NULL
, 0);
2210 * Open a component of a MIME message and transmit it all at once
2212 void cmd_dlat(char *cmdbuf
)
2215 char desired_section
[128];
2217 msgid
= extract_long(cmdbuf
, 0);
2218 extract_token(desired_section
, cmdbuf
, 1, '|', sizeof desired_section
);
2219 safestrncpy(CC
->download_desired_section
, desired_section
,
2220 sizeof CC
->download_desired_section
);
2221 CtdlOutputMsg(msgid
, MT_SPEW_SECTION
, 0, 1, 1, NULL
, 0);
2226 * Save one or more message pointers into a specified room
2227 * (Returns 0 for success, nonzero for failure)
2228 * roomname may be NULL to use the current room
2230 * Note that the 'supplied_msg' field may be set to NULL, in which case
2231 * the message will be fetched from disk, by number, if we need to perform
2232 * replication checks. This adds an additional database read, so if the
2233 * caller already has the message in memory then it should be supplied. (Obviously
2234 * this mode of operation only works if we're saving a single message.)
2236 int CtdlSaveMsgPointersInRoom(char *roomname
, long newmsgidlist
[], int num_newmsgs
,
2237 int do_repl_check
, struct CtdlMessage
*supplied_msg
)
2240 char hold_rm
[ROOMNAMELEN
];
2241 struct cdbdata
*cdbfr
;
2244 long highest_msg
= 0L;
2247 struct CtdlMessage
*msg
= NULL
;
2249 long *msgs_to_be_merged
= NULL
;
2250 int num_msgs_to_be_merged
= 0;
2252 CtdlLogPrintf(CTDL_DEBUG
,
2253 "CtdlSaveMsgPointersInRoom(room=%s, num_msgs=%d, repl=%d)\n",
2254 roomname
, num_newmsgs
, do_repl_check
);
2256 strcpy(hold_rm
, CC
->room
.QRname
);
2259 if (newmsgidlist
== NULL
) return(ERROR
+ INTERNAL_ERROR
);
2260 if (num_newmsgs
< 1) return(ERROR
+ INTERNAL_ERROR
);
2261 if (num_newmsgs
> 1) supplied_msg
= NULL
;
2263 /* Now the regular stuff */
2264 if (lgetroom(&CC
->room
,
2265 ((roomname
!= NULL
) ? roomname
: CC
->room
.QRname
) )
2267 CtdlLogPrintf(CTDL_ERR
, "No such room <%s>\n", roomname
);
2268 return(ERROR
+ ROOM_NOT_FOUND
);
2272 msgs_to_be_merged
= malloc(sizeof(long) * num_newmsgs
);
2273 num_msgs_to_be_merged
= 0;
2276 cdbfr
= cdb_fetch(CDB_MSGLISTS
, &CC
->room
.QRnumber
, sizeof(long));
2277 if (cdbfr
== NULL
) {
2281 msglist
= (long *) cdbfr
->ptr
;
2282 cdbfr
->ptr
= NULL
; /* CtdlSaveMsgPointerInRoom() now owns this memory */
2283 num_msgs
= cdbfr
->len
/ sizeof(long);
2288 /* Create a list of msgid's which were supplied by the caller, but do
2289 * not already exist in the target room. It is absolutely taboo to
2290 * have more than one reference to the same message in a room.
2292 for (i
=0; i
<num_newmsgs
; ++i
) {
2294 if (num_msgs
> 0) for (j
=0; j
<num_msgs
; ++j
) {
2295 if (msglist
[j
] == newmsgidlist
[i
]) {
2300 msgs_to_be_merged
[num_msgs_to_be_merged
++] = newmsgidlist
[i
];
2304 CtdlLogPrintf(9, "%d unique messages to be merged\n", num_msgs_to_be_merged
);
2307 * Now merge the new messages
2309 msglist
= realloc(msglist
, (sizeof(long) * (num_msgs
+ num_msgs_to_be_merged
)) );
2310 if (msglist
== NULL
) {
2311 CtdlLogPrintf(CTDL_ALERT
, "ERROR: can't realloc message list!\n");
2313 memcpy(&msglist
[num_msgs
], msgs_to_be_merged
, (sizeof(long) * num_msgs_to_be_merged
) );
2314 num_msgs
+= num_msgs_to_be_merged
;
2316 /* Sort the message list, so all the msgid's are in order */
2317 num_msgs
= sort_msglist(msglist
, num_msgs
);
2319 /* Determine the highest message number */
2320 highest_msg
= msglist
[num_msgs
- 1];
2322 /* Write it back to disk. */
2323 cdb_store(CDB_MSGLISTS
, &CC
->room
.QRnumber
, (int)sizeof(long),
2324 msglist
, (int)(num_msgs
* sizeof(long)));
2326 /* Free up the memory we used. */
2329 /* Update the highest-message pointer and unlock the room. */
2330 CC
->room
.QRhighest
= highest_msg
;
2331 lputroom(&CC
->room
);
2333 /* Perform replication checks if necessary */
2334 if ( (DoesThisRoomNeedEuidIndexing(&CC
->room
)) && (do_repl_check
) ) {
2335 CtdlLogPrintf(CTDL_DEBUG
, "CtdlSaveMsgPointerInRoom() doing repl checks\n");
2337 for (i
=0; i
<num_msgs_to_be_merged
; ++i
) {
2338 msgid
= msgs_to_be_merged
[i
];
2340 if (supplied_msg
!= NULL
) {
2344 msg
= CtdlFetchMessage(msgid
, 0);
2348 ReplicationChecks(msg
);
2350 /* If the message has an Exclusive ID, index that... */
2351 if (msg
->cm_fields
['E'] != NULL
) {
2352 index_message_by_euid(msg
->cm_fields
['E'], &CC
->room
, msgid
);
2355 /* Free up the memory we may have allocated */
2356 if (msg
!= supplied_msg
) {
2357 CtdlFreeMessage(msg
);
2365 CtdlLogPrintf(CTDL_DEBUG
, "CtdlSaveMsgPointerInRoom() skips repl checks\n");
2368 /* Submit this room for processing by hooks */
2369 PerformRoomHooks(&CC
->room
);
2371 /* Go back to the room we were in before we wandered here... */
2372 getroom(&CC
->room
, hold_rm
);
2374 /* Bump the reference count for all messages which were merged */
2375 for (i
=0; i
<num_msgs_to_be_merged
; ++i
) {
2376 AdjRefCount(msgs_to_be_merged
[i
], +1);
2379 /* Free up memory... */
2380 if (msgs_to_be_merged
!= NULL
) {
2381 free(msgs_to_be_merged
);
2384 /* Return success. */
2390 * This is the same as CtdlSaveMsgPointersInRoom() but it only accepts
2393 int CtdlSaveMsgPointerInRoom(char *roomname
, long msgid
,
2394 int do_repl_check
, struct CtdlMessage
*supplied_msg
)
2396 return CtdlSaveMsgPointersInRoom(roomname
, &msgid
, 1, do_repl_check
, supplied_msg
);
2403 * Message base operation to save a new message to the message store
2404 * (returns new message number)
2406 * This is the back end for CtdlSubmitMsg() and should not be directly
2407 * called by server-side modules.
2410 long send_message(struct CtdlMessage
*msg
) {
2418 /* Get a new message number */
2419 newmsgid
= get_new_message_number();
2420 snprintf(msgidbuf
, sizeof msgidbuf
, "%010ld@%s", newmsgid
, config
.c_fqdn
);
2422 /* Generate an ID if we don't have one already */
2423 if (msg
->cm_fields
['I']==NULL
) {
2424 msg
->cm_fields
['I'] = strdup(msgidbuf
);
2427 /* If the message is big, set its body aside for storage elsewhere */
2428 if (msg
->cm_fields
['M'] != NULL
) {
2429 if (strlen(msg
->cm_fields
['M']) > BIGMSG
) {
2431 holdM
= msg
->cm_fields
['M'];
2432 msg
->cm_fields
['M'] = NULL
;
2436 /* Serialize our data structure for storage in the database */
2437 serialize_message(&smr
, msg
);
2440 msg
->cm_fields
['M'] = holdM
;
2444 cprintf("%d Unable to serialize message\n",
2445 ERROR
+ INTERNAL_ERROR
);
2449 /* Write our little bundle of joy into the message base */
2450 if (cdb_store(CDB_MSGMAIN
, &newmsgid
, (int)sizeof(long),
2451 smr
.ser
, smr
.len
) < 0) {
2452 CtdlLogPrintf(CTDL_ERR
, "Can't store message\n");
2456 cdb_store(CDB_BIGMSGS
,
2466 /* Free the memory we used for the serialized message */
2469 /* Return the *local* message ID to the caller
2470 * (even if we're storing an incoming network message)
2478 * Serialize a struct CtdlMessage into the format used on disk and network.
2480 * This function loads up a "struct ser_ret" (defined in server.h) which
2481 * contains the length of the serialized message and a pointer to the
2482 * serialized message in memory. THE LATTER MUST BE FREED BY THE CALLER.
2484 void serialize_message(struct ser_ret
*ret
, /* return values */
2485 struct CtdlMessage
*msg
) /* unserialized msg */
2487 size_t wlen
, fieldlen
;
2489 static char *forder
= FORDER
;
2492 * Check for valid message format
2494 if (is_valid_message(msg
) == 0) {
2495 CtdlLogPrintf(CTDL_ERR
, "serialize_message() aborting due to invalid message\n");
2502 for (i
=0; i
<26; ++i
) if (msg
->cm_fields
[(int)forder
[i
]] != NULL
)
2503 ret
->len
= ret
->len
+
2504 strlen(msg
->cm_fields
[(int)forder
[i
]]) + 2;
2506 ret
->ser
= malloc(ret
->len
);
2507 if (ret
->ser
== NULL
) {
2508 CtdlLogPrintf(CTDL_ERR
, "serialize_message() malloc(%ld) failed: %s\n",
2509 (long)ret
->len
, strerror(errno
));
2516 ret
->ser
[1] = msg
->cm_anon_type
;
2517 ret
->ser
[2] = msg
->cm_format_type
;
2520 for (i
=0; i
<26; ++i
) if (msg
->cm_fields
[(int)forder
[i
]] != NULL
) {
2521 fieldlen
= strlen(msg
->cm_fields
[(int)forder
[i
]]);
2522 ret
->ser
[wlen
++] = (char)forder
[i
];
2523 safestrncpy((char *)&ret
->ser
[wlen
], msg
->cm_fields
[(int)forder
[i
]], fieldlen
+1);
2524 wlen
= wlen
+ fieldlen
+ 1;
2526 if (ret
->len
!= wlen
) CtdlLogPrintf(CTDL_ERR
, "ERROR: len=%ld wlen=%ld\n",
2527 (long)ret
->len
, (long)wlen
);
2534 * Serialize a struct CtdlMessage into the format used on disk and network.
2536 * This function loads up a "struct ser_ret" (defined in server.h) which
2537 * contains the length of the serialized message and a pointer to the
2538 * serialized message in memory. THE LATTER MUST BE FREED BY THE CALLER.
2540 void dump_message(struct CtdlMessage
*msg
, /* unserialized msg */
2541 long Siz
) /* how many chars ? */
2545 static char *forder
= FORDER
;
2549 * Check for valid message format
2551 if (is_valid_message(msg
) == 0) {
2552 CtdlLogPrintf(CTDL_ERR
, "dump_message() aborting due to invalid message\n");
2556 buf
= (char*) malloc (Siz
+ 1);
2560 for (i
=0; i
<26; ++i
) if (msg
->cm_fields
[(int)forder
[i
]] != NULL
) {
2561 snprintf (buf
, Siz
, " msg[%c] = %s ...\n", (char) forder
[i
],
2562 msg
->cm_fields
[(int)forder
[i
]]);
2563 client_write (buf
, strlen(buf
));
2572 * Check to see if any messages already exist in the current room which
2573 * carry the same Exclusive ID as this one. If any are found, delete them.
2575 void ReplicationChecks(struct CtdlMessage
*msg
) {
2576 long old_msgnum
= (-1L);
2578 if (DoesThisRoomNeedEuidIndexing(&CC
->room
) == 0) return;
2580 CtdlLogPrintf(CTDL_DEBUG
, "Performing replication checks in <%s>\n",
2583 /* No exclusive id? Don't do anything. */
2584 if (msg
== NULL
) return;
2585 if (msg
->cm_fields
['E'] == NULL
) return;
2586 if (IsEmptyStr(msg
->cm_fields
['E'])) return;
2587 /*CtdlLogPrintf(CTDL_DEBUG, "Exclusive ID: <%s> for room <%s>\n",
2588 msg->cm_fields['E'], CC->room.QRname);*/
2590 old_msgnum
= locate_message_by_euid(msg
->cm_fields
['E'], &CC
->room
);
2591 if (old_msgnum
> 0L) {
2592 CtdlLogPrintf(CTDL_DEBUG
, "ReplicationChecks() replacing message %ld\n", old_msgnum
);
2593 CtdlDeleteMessages(CC
->room
.QRname
, &old_msgnum
, 1, "");
2600 * Save a message to disk and submit it into the delivery system.
2602 long CtdlSubmitMsg(struct CtdlMessage
*msg
, /* message to save */
2603 struct recptypes
*recps
, /* recipients (if mail) */
2604 char *force
, /* force a particular room? */
2605 int flags
/* should the bessage be exported clean? */
2607 char submit_filename
[128];
2608 char generated_timestamp
[32];
2609 char hold_rm
[ROOMNAMELEN
];
2610 char actual_rm
[ROOMNAMELEN
];
2611 char force_room
[ROOMNAMELEN
];
2612 char content_type
[SIZ
]; /* We have to learn this */
2613 char recipient
[SIZ
];
2616 struct ctdluser userbuf
;
2618 struct MetaData smi
;
2619 FILE *network_fp
= NULL
;
2620 static int seqnum
= 1;
2621 struct CtdlMessage
*imsg
= NULL
;
2623 size_t instr_alloc
= 0;
2625 char *hold_R
, *hold_D
;
2626 char *collected_addresses
= NULL
;
2627 struct addresses_to_be_filed
*aptr
= NULL
;
2628 char *saved_rfc822_version
= NULL
;
2629 int qualified_for_journaling
= 0;
2630 struct CitContext
*CCC
= CC
; /* CachedCitContext - performance boost */
2631 char bounce_to
[1024] = "";
2633 CtdlLogPrintf(CTDL_DEBUG
, "CtdlSubmitMsg() called\n");
2634 if (is_valid_message(msg
) == 0) return(-1); /* self check */
2636 /* If this message has no timestamp, we take the liberty of
2637 * giving it one, right now.
2639 if (msg
->cm_fields
['T'] == NULL
) {
2640 snprintf(generated_timestamp
, sizeof generated_timestamp
, "%ld", (long)time(NULL
));
2641 msg
->cm_fields
['T'] = strdup(generated_timestamp
);
2644 /* If this message has no path, we generate one.
2646 if (msg
->cm_fields
['P'] == NULL
) {
2647 if (msg
->cm_fields
['A'] != NULL
) {
2648 msg
->cm_fields
['P'] = strdup(msg
->cm_fields
['A']);
2649 for (a
=0; !IsEmptyStr(&msg
->cm_fields
['P'][a
]); ++a
) {
2650 if (isspace(msg
->cm_fields
['P'][a
])) {
2651 msg
->cm_fields
['P'][a
] = ' ';
2656 msg
->cm_fields
['P'] = strdup("unknown");
2660 if (force
== NULL
) {
2661 strcpy(force_room
, "");
2664 strcpy(force_room
, force
);
2667 /* Learn about what's inside, because it's what's inside that counts */
2668 if (msg
->cm_fields
['M'] == NULL
) {
2669 CtdlLogPrintf(CTDL_ERR
, "ERROR: attempt to save message with NULL body\n");
2673 switch (msg
->cm_format_type
) {
2675 strcpy(content_type
, "text/x-citadel-variformat");
2678 strcpy(content_type
, "text/plain");
2681 strcpy(content_type
, "text/plain");
2682 mptr
= bmstrcasestr(msg
->cm_fields
['M'], "Content-type:");
2685 safestrncpy(content_type
, &mptr
[13], sizeof content_type
);
2686 striplt(content_type
);
2687 aptr
= content_type
;
2688 while (!IsEmptyStr(aptr
)) {
2700 /* Goto the correct room */
2701 CtdlLogPrintf(CTDL_DEBUG
, "Selected room %s\n", (recps
) ? CCC
->room
.QRname
: SENTITEMS
);
2702 strcpy(hold_rm
, CCC
->room
.QRname
);
2703 strcpy(actual_rm
, CCC
->room
.QRname
);
2704 if (recps
!= NULL
) {
2705 strcpy(actual_rm
, SENTITEMS
);
2708 /* If the user is a twit, move to the twit room for posting */
2710 if (CCC
->user
.axlevel
== 2) {
2711 strcpy(hold_rm
, actual_rm
);
2712 strcpy(actual_rm
, config
.c_twitroom
);
2713 CtdlLogPrintf(CTDL_DEBUG
, "Diverting to twit room\n");
2717 /* ...or if this message is destined for Aide> then go there. */
2718 if (!IsEmptyStr(force_room
)) {
2719 strcpy(actual_rm
, force_room
);
2722 CtdlLogPrintf(CTDL_DEBUG
, "Final selection: %s\n", actual_rm
);
2723 if (strcasecmp(actual_rm
, CCC
->room
.QRname
)) {
2724 /* getroom(&CCC->room, actual_rm); */
2725 usergoto(actual_rm
, 0, 1, NULL
, NULL
);
2729 * If this message has no O (room) field, generate one.
2731 if (msg
->cm_fields
['O'] == NULL
) {
2732 msg
->cm_fields
['O'] = strdup(CCC
->room
.QRname
);
2735 /* Perform "before save" hooks (aborting if any return nonzero) */
2736 CtdlLogPrintf(CTDL_DEBUG
, "Performing before-save hooks\n");
2737 if (PerformMessageHooks(msg
, EVT_BEFORESAVE
) > 0) return(-3);
2740 * If this message has an Exclusive ID, and the room is replication
2741 * checking enabled, then do replication checks.
2743 if (DoesThisRoomNeedEuidIndexing(&CCC
->room
)) {
2744 ReplicationChecks(msg
);
2747 /* Save it to disk */
2748 CtdlLogPrintf(CTDL_DEBUG
, "Saving to disk\n");
2749 newmsgid
= send_message(msg
);
2750 if (newmsgid
<= 0L) return(-5);
2752 /* Write a supplemental message info record. This doesn't have to
2753 * be a critical section because nobody else knows about this message
2756 CtdlLogPrintf(CTDL_DEBUG
, "Creating MetaData record\n");
2757 memset(&smi
, 0, sizeof(struct MetaData
));
2758 smi
.meta_msgnum
= newmsgid
;
2759 smi
.meta_refcount
= 0;
2760 safestrncpy(smi
.meta_content_type
, content_type
,
2761 sizeof smi
.meta_content_type
);
2764 * Measure how big this message will be when rendered as RFC822.
2765 * We do this for two reasons:
2766 * 1. We need the RFC822 length for the new metadata record, so the
2767 * POP and IMAP services don't have to calculate message lengths
2768 * while the user is waiting (multiplied by potentially hundreds
2769 * or thousands of messages).
2770 * 2. If journaling is enabled, we will need an RFC822 version of the
2771 * message to attach to the journalized copy.
2773 if (CCC
->redirect_buffer
!= NULL
) {
2774 CtdlLogPrintf(CTDL_ALERT
, "CCC->redirect_buffer is not NULL during message submission!\n");
2777 CCC
->redirect_buffer
= malloc(SIZ
);
2778 CCC
->redirect_len
= 0;
2779 CCC
->redirect_alloc
= SIZ
;
2780 CtdlOutputPreLoadedMsg(msg
, MT_RFC822
, HEADERS_ALL
, 0, 1, QP_EADDR
);
2781 smi
.meta_rfc822_length
= CCC
->redirect_len
;
2782 saved_rfc822_version
= CCC
->redirect_buffer
;
2783 CCC
->redirect_buffer
= NULL
;
2784 CCC
->redirect_len
= 0;
2785 CCC
->redirect_alloc
= 0;
2789 /* Now figure out where to store the pointers */
2790 CtdlLogPrintf(CTDL_DEBUG
, "Storing pointers\n");
2792 /* If this is being done by the networker delivering a private
2793 * message, we want to BYPASS saving the sender's copy (because there
2794 * is no local sender; it would otherwise go to the Trashcan).
2796 if ((!CCC
->internal_pgm
) || (recps
== NULL
)) {
2797 if (CtdlSaveMsgPointerInRoom(actual_rm
, newmsgid
, 1, msg
) != 0) {
2798 CtdlLogPrintf(CTDL_ERR
, "ERROR saving message pointer!\n");
2799 CtdlSaveMsgPointerInRoom(config
.c_aideroom
, newmsgid
, 0, msg
);
2803 /* For internet mail, drop a copy in the outbound queue room */
2805 if (recps
->num_internet
> 0) {
2806 CtdlSaveMsgPointerInRoom(SMTP_SPOOLOUT_ROOM
, newmsgid
, 0, msg
);
2809 /* If other rooms are specified, drop them there too. */
2811 if (recps
->num_room
> 0)
2812 for (i
=0; i
<num_tokens(recps
->recp_room
, '|'); ++i
) {
2813 extract_token(recipient
, recps
->recp_room
, i
,
2814 '|', sizeof recipient
);
2815 CtdlLogPrintf(CTDL_DEBUG
, "Delivering to room <%s>\n", recipient
);
2816 CtdlSaveMsgPointerInRoom(recipient
, newmsgid
, 0, msg
);
2819 /* Bump this user's messages posted counter. */
2820 CtdlLogPrintf(CTDL_DEBUG
, "Updating user\n");
2821 lgetuser(&CCC
->user
, CCC
->curr_user
);
2822 CCC
->user
.posted
= CCC
->user
.posted
+ 1;
2823 lputuser(&CCC
->user
);
2825 /* Decide where bounces need to be delivered */
2826 if (CCC
->logged_in
) {
2827 snprintf(bounce_to
, sizeof bounce_to
, "%s@%s", CCC
->user
.fullname
, config
.c_nodename
);
2830 snprintf(bounce_to
, sizeof bounce_to
, "%s@%s", msg
->cm_fields
['A'], msg
->cm_fields
['N']);
2833 /* If this is private, local mail, make a copy in the
2834 * recipient's mailbox and bump the reference count.
2837 if (recps
->num_local
> 0)
2838 for (i
=0; i
<num_tokens(recps
->recp_local
, '|'); ++i
) {
2839 extract_token(recipient
, recps
->recp_local
, i
,
2840 '|', sizeof recipient
);
2841 CtdlLogPrintf(CTDL_DEBUG
, "Delivering private local mail to <%s>\n",
2843 if (getuser(&userbuf
, recipient
) == 0) {
2844 // Add a flag so the Funambol module knows its mail
2845 msg
->cm_fields
['W'] = strdup(recipient
);
2846 MailboxName(actual_rm
, sizeof actual_rm
, &userbuf
, MAILROOM
);
2847 CtdlSaveMsgPointerInRoom(actual_rm
, newmsgid
, 0, msg
);
2848 BumpNewMailCounter(userbuf
.usernum
);
2849 if (!IsEmptyStr(config
.c_funambol_host
) || !IsEmptyStr(config
.c_pager_program
)) {
2850 /* Generate a instruction message for the Funambol notification
2851 * server, in the same style as the SMTP queue
2854 instr
= malloc(instr_alloc
);
2855 snprintf(instr
, instr_alloc
,
2856 "Content-type: %s\n\nmsgid|%ld\nsubmitted|%ld\n"
2858 SPOOLMIME
, newmsgid
, (long)time(NULL
),
2862 imsg
= malloc(sizeof(struct CtdlMessage
));
2863 memset(imsg
, 0, sizeof(struct CtdlMessage
));
2864 imsg
->cm_magic
= CTDLMESSAGE_MAGIC
;
2865 imsg
->cm_anon_type
= MES_NORMAL
;
2866 imsg
->cm_format_type
= FMT_RFC822
;
2867 imsg
->cm_fields
['A'] = strdup("Citadel");
2868 imsg
->cm_fields
['J'] = strdup("do not journal");
2869 imsg
->cm_fields
['M'] = instr
; /* imsg owns this memory now */
2870 imsg
->cm_fields
['W'] = strdup(recipient
);
2871 CtdlSubmitMsg(imsg
, NULL
, FNBL_QUEUE_ROOM
, 0);
2872 CtdlFreeMessage(imsg
);
2876 CtdlLogPrintf(CTDL_DEBUG
, "No user <%s>\n", recipient
);
2877 CtdlSaveMsgPointerInRoom(config
.c_aideroom
,
2882 /* Perform "after save" hooks */
2883 CtdlLogPrintf(CTDL_DEBUG
, "Performing after-save hooks\n");
2884 PerformMessageHooks(msg
, EVT_AFTERSAVE
);
2886 /* For IGnet mail, we have to save a new copy into the spooler for
2887 * each recipient, with the R and D fields set to the recipient and
2888 * destination-node. This has two ugly side effects: all other
2889 * recipients end up being unlisted in this recipient's copy of the
2890 * message, and it has to deliver multiple messages to the same
2891 * node. We'll revisit this again in a year or so when everyone has
2892 * a network spool receiver that can handle the new style messages.
2895 if (recps
->num_ignet
> 0)
2896 for (i
=0; i
<num_tokens(recps
->recp_ignet
, '|'); ++i
) {
2897 extract_token(recipient
, recps
->recp_ignet
, i
,
2898 '|', sizeof recipient
);
2900 hold_R
= msg
->cm_fields
['R'];
2901 hold_D
= msg
->cm_fields
['D'];
2902 msg
->cm_fields
['R'] = malloc(SIZ
);
2903 msg
->cm_fields
['D'] = malloc(128);
2904 extract_token(msg
->cm_fields
['R'], recipient
, 0, '@', SIZ
);
2905 extract_token(msg
->cm_fields
['D'], recipient
, 1, '@', 128);
2907 serialize_message(&smr
, msg
);
2909 snprintf(submit_filename
, sizeof submit_filename
,
2910 "%s/netmail.%04lx.%04x.%04x",
2912 (long) getpid(), CCC
->cs_pid
, ++seqnum
);
2913 network_fp
= fopen(submit_filename
, "wb+");
2914 if (network_fp
!= NULL
) {
2915 fwrite(smr
.ser
, smr
.len
, 1, network_fp
);
2921 free(msg
->cm_fields
['R']);
2922 free(msg
->cm_fields
['D']);
2923 msg
->cm_fields
['R'] = hold_R
;
2924 msg
->cm_fields
['D'] = hold_D
;
2927 /* Go back to the room we started from */
2928 CtdlLogPrintf(CTDL_DEBUG
, "Returning to original room %s\n", hold_rm
);
2929 if (strcasecmp(hold_rm
, CCC
->room
.QRname
))
2930 usergoto(hold_rm
, 0, 1, NULL
, NULL
);
2932 /* For internet mail, generate delivery instructions.
2933 * Yes, this is recursive. Deal with it. Infinite recursion does
2934 * not happen because the delivery instructions message does not
2935 * contain a recipient.
2938 if (recps
->num_internet
> 0) {
2939 CtdlLogPrintf(CTDL_DEBUG
, "Generating delivery instructions\n");
2941 instr
= malloc(instr_alloc
);
2942 snprintf(instr
, instr_alloc
,
2943 "Content-type: %s\n\nmsgid|%ld\nsubmitted|%ld\n"
2945 SPOOLMIME
, newmsgid
, (long)time(NULL
),
2949 for (i
=0; i
<num_tokens(recps
->recp_internet
, '|'); ++i
) {
2950 size_t tmp
= strlen(instr
);
2951 extract_token(recipient
, recps
->recp_internet
, i
, '|', sizeof recipient
);
2952 if ((tmp
+ strlen(recipient
) + 32) > instr_alloc
) {
2953 instr_alloc
= instr_alloc
* 2;
2954 instr
= realloc(instr
, instr_alloc
);
2956 snprintf(&instr
[tmp
], instr_alloc
- tmp
, "remote|%s|0||\n", recipient
);
2959 imsg
= malloc(sizeof(struct CtdlMessage
));
2960 memset(imsg
, 0, sizeof(struct CtdlMessage
));
2961 imsg
->cm_magic
= CTDLMESSAGE_MAGIC
;
2962 imsg
->cm_anon_type
= MES_NORMAL
;
2963 imsg
->cm_format_type
= FMT_RFC822
;
2964 imsg
->cm_fields
['A'] = strdup("Citadel");
2965 imsg
->cm_fields
['J'] = strdup("do not journal");
2966 imsg
->cm_fields
['M'] = instr
; /* imsg owns this memory now */
2967 CtdlSubmitMsg(imsg
, NULL
, SMTP_SPOOLOUT_ROOM
, QP_EADDR
);
2968 CtdlFreeMessage(imsg
);
2972 * Any addresses to harvest for someone's address book?
2974 if ( (CCC
->logged_in
) && (recps
!= NULL
) ) {
2975 collected_addresses
= harvest_collected_addresses(msg
);
2978 if (collected_addresses
!= NULL
) {
2979 aptr
= (struct addresses_to_be_filed
*)
2980 malloc(sizeof(struct addresses_to_be_filed
));
2981 MailboxName(actual_rm
, sizeof actual_rm
,
2982 &CCC
->user
, USERCONTACTSROOM
);
2983 aptr
->roomname
= strdup(actual_rm
);
2984 aptr
->collected_addresses
= collected_addresses
;
2985 begin_critical_section(S_ATBF
);
2988 end_critical_section(S_ATBF
);
2992 * Determine whether this message qualifies for journaling.
2994 if (msg
->cm_fields
['J'] != NULL
) {
2995 qualified_for_journaling
= 0;
2998 if (recps
== NULL
) {
2999 qualified_for_journaling
= config
.c_journal_pubmsgs
;
3001 else if (recps
->num_local
+ recps
->num_ignet
+ recps
->num_internet
> 0) {
3002 qualified_for_journaling
= config
.c_journal_email
;
3005 qualified_for_journaling
= config
.c_journal_pubmsgs
;
3010 * Do we have to perform journaling? If so, hand off the saved
3011 * RFC822 version will be handed off to the journaler for background
3012 * submit. Otherwise, we have to free the memory ourselves.
3014 if (saved_rfc822_version
!= NULL
) {
3015 if (qualified_for_journaling
) {
3016 JournalBackgroundSubmit(msg
, saved_rfc822_version
, recps
);
3019 free(saved_rfc822_version
);
3032 * Convenience function for generating small administrative messages.
3034 void quickie_message(char *from
, char *fromaddr
, char *to
, char *room
, char *text
,
3035 int format_type
, char *subject
)
3037 struct CtdlMessage
*msg
;
3038 struct recptypes
*recp
= NULL
;
3040 msg
= malloc(sizeof(struct CtdlMessage
));
3041 memset(msg
, 0, sizeof(struct CtdlMessage
));
3042 msg
->cm_magic
= CTDLMESSAGE_MAGIC
;
3043 msg
->cm_anon_type
= MES_NORMAL
;
3044 msg
->cm_format_type
= format_type
;
3047 msg
->cm_fields
['A'] = strdup(from
);
3049 else if (fromaddr
!= NULL
) {
3050 msg
->cm_fields
['A'] = strdup(fromaddr
);
3051 if (strchr(msg
->cm_fields
['A'], '@')) {
3052 *strchr(msg
->cm_fields
['A'], '@') = 0;
3056 msg
->cm_fields
['A'] = strdup("Citadel");
3059 if (fromaddr
!= NULL
) msg
->cm_fields
['F'] = strdup(fromaddr
);
3060 if (room
!= NULL
) msg
->cm_fields
['O'] = strdup(room
);
3061 msg
->cm_fields
['N'] = strdup(NODENAME
);
3063 msg
->cm_fields
['R'] = strdup(to
);
3064 recp
= validate_recipients(to
, NULL
, 0);
3066 if (subject
!= NULL
) {
3067 msg
->cm_fields
['U'] = strdup(subject
);
3069 msg
->cm_fields
['M'] = strdup(text
);
3071 CtdlSubmitMsg(msg
, recp
, room
, 0);
3072 CtdlFreeMessage(msg
);
3073 if (recp
!= NULL
) free_recipients(recp
);
3079 * Back end function used by CtdlMakeMessage() and similar functions
3081 char *CtdlReadMessageBody(char *terminator
, /* token signalling EOT */
3082 size_t maxlen
, /* maximum message length */
3083 char *exist
, /* if non-null, append to it;
3084 exist is ALWAYS freed */
3085 int crlf
, /* CRLF newlines instead of LF */
3086 int sock
/* socket handle or 0 for this session's client socket */
3090 size_t message_len
= 0;
3091 size_t buffer_len
= 0;
3098 if (exist
== NULL
) {
3105 message_len
= strlen(exist
);
3106 buffer_len
= message_len
+ 4096;
3107 m
= realloc(exist
, buffer_len
);
3114 /* Do we need to change leading ".." to "." for SMTP escaping? */
3115 if (!strcmp(terminator
, ".")) {
3119 /* flush the input if we have nowhere to store it */
3124 /* read in the lines of message text one by one */
3127 if (sock_getln(sock
, buf
, (sizeof buf
- 3)) < 0) finished
= 1;
3130 if (client_getln(buf
, (sizeof buf
- 3)) < 1) finished
= 1;
3132 if (!strcmp(buf
, terminator
)) finished
= 1;
3134 strcat(buf
, "\r\n");
3140 /* Unescape SMTP-style input of two dots at the beginning of the line */
3142 if (!strncmp(buf
, "..", 2)) {
3143 strcpy(buf
, &buf
[1]);
3147 if ( (!flushing
) && (!finished
) ) {
3148 /* Measure the line */
3149 linelen
= strlen(buf
);
3151 /* augment the buffer if we have to */
3152 if ((message_len
+ linelen
) >= buffer_len
) {
3153 ptr
= realloc(m
, (buffer_len
* 2) );
3154 if (ptr
== NULL
) { /* flush if can't allocate */
3157 buffer_len
= (buffer_len
* 2);
3159 CtdlLogPrintf(CTDL_DEBUG
, "buffer_len is now %ld\n", (long)buffer_len
);
3163 /* Add the new line to the buffer. NOTE: this loop must avoid
3164 * using functions like strcat() and strlen() because they
3165 * traverse the entire buffer upon every call, and doing that
3166 * for a multi-megabyte message slows it down beyond usability.
3168 strcpy(&m
[message_len
], buf
);
3169 message_len
+= linelen
;
3172 /* if we've hit the max msg length, flush the rest */
3173 if (message_len
>= maxlen
) flushing
= 1;
3175 } while (!finished
);
3183 * Build a binary message to be saved on disk.
3184 * (NOTE: if you supply 'preformatted_text', the buffer you give it
3185 * will become part of the message. This means you are no longer
3186 * responsible for managing that memory -- it will be freed along with
3187 * the rest of the fields when CtdlFreeMessage() is called.)
3190 struct CtdlMessage
*CtdlMakeMessage(
3191 struct ctdluser
*author
, /* author's user structure */
3192 char *recipient
, /* NULL if it's not mail */
3193 char *recp_cc
, /* NULL if it's not mail */
3194 char *room
, /* room where it's going */
3195 int type
, /* see MES_ types in header file */
3196 int format_type
, /* variformat, plain text, MIME... */
3197 char *fake_name
, /* who we're masquerading as */
3198 char *my_email
, /* which of my email addresses to use (empty is ok) */
3199 char *subject
, /* Subject (optional) */
3200 char *supplied_euid
, /* ...or NULL if this is irrelevant */
3201 char *preformatted_text
, /* ...or NULL to read text from client */
3202 char *references
/* Thread references */
3204 char dest_node
[256];
3206 struct CtdlMessage
*msg
;
3208 msg
= malloc(sizeof(struct CtdlMessage
));
3209 memset(msg
, 0, sizeof(struct CtdlMessage
));
3210 msg
->cm_magic
= CTDLMESSAGE_MAGIC
;
3211 msg
->cm_anon_type
= type
;
3212 msg
->cm_format_type
= format_type
;
3214 /* Don't confuse the poor folks if it's not routed mail. */
3215 strcpy(dest_node
, "");
3220 /* Path or Return-Path */
3221 if (my_email
== NULL
) my_email
= "";
3223 if (!IsEmptyStr(my_email
)) {
3224 msg
->cm_fields
['P'] = strdup(my_email
);
3227 snprintf(buf
, sizeof buf
, "%s", author
->fullname
);
3228 msg
->cm_fields
['P'] = strdup(buf
);
3230 convert_spaces_to_underscores(msg
->cm_fields
['P']);
3232 snprintf(buf
, sizeof buf
, "%ld", (long)time(NULL
)); /* timestamp */
3233 msg
->cm_fields
['T'] = strdup(buf
);
3235 if (fake_name
[0]) /* author */
3236 msg
->cm_fields
['A'] = strdup(fake_name
);
3238 msg
->cm_fields
['A'] = strdup(author
->fullname
);
3240 if (CC
->room
.QRflags
& QR_MAILBOX
) { /* room */
3241 msg
->cm_fields
['O'] = strdup(&CC
->room
.QRname
[11]);
3244 msg
->cm_fields
['O'] = strdup(CC
->room
.QRname
);
3247 msg
->cm_fields
['N'] = strdup(NODENAME
); /* nodename */
3248 msg
->cm_fields
['H'] = strdup(HUMANNODE
); /* hnodename */
3250 if (recipient
[0] != 0) {
3251 msg
->cm_fields
['R'] = strdup(recipient
);
3253 if (recp_cc
[0] != 0) {
3254 msg
->cm_fields
['Y'] = strdup(recp_cc
);
3256 if (dest_node
[0] != 0) {
3257 msg
->cm_fields
['D'] = strdup(dest_node
);
3260 if (!IsEmptyStr(my_email
)) {
3261 msg
->cm_fields
['F'] = strdup(my_email
);
3263 else if ( (author
== &CC
->user
) && (!IsEmptyStr(CC
->cs_inet_email
)) ) {
3264 msg
->cm_fields
['F'] = strdup(CC
->cs_inet_email
);
3267 if (subject
!= NULL
) {
3270 length
= strlen(subject
);
3276 while ((subject
[i
] != '\0') &&
3277 (IsAscii
= isascii(subject
[i
]) != 0 ))
3280 msg
->cm_fields
['U'] = strdup(subject
);
3281 else /* ok, we've got utf8 in the string. */
3283 msg
->cm_fields
['U'] = rfc2047encode(subject
, length
);
3289 if (supplied_euid
!= NULL
) {
3290 msg
->cm_fields
['E'] = strdup(supplied_euid
);
3293 if (references
!= NULL
) {
3294 if (!IsEmptyStr(references
)) {
3295 msg
->cm_fields
['W'] = strdup(references
);
3299 if (preformatted_text
!= NULL
) {
3300 msg
->cm_fields
['M'] = preformatted_text
;
3303 msg
->cm_fields
['M'] = CtdlReadMessageBody("000", config
.c_maxmsglen
, NULL
, 0, 0);
3311 * Check to see whether we have permission to post a message in the current
3312 * room. Returns a *CITADEL ERROR CODE* and puts a message in errmsgbuf, or
3313 * returns 0 on success.
3315 int CtdlDoIHavePermissionToPostInThisRoom(char *errmsgbuf
,
3317 const char* RemoteIdentifier
,
3321 if (!(CC
->logged_in
) &&
3322 (PostPublic
== POST_LOGGED_IN
)) {
3323 snprintf(errmsgbuf
, n
, "Not logged in.");
3324 return (ERROR
+ NOT_LOGGED_IN
);
3326 else if (PostPublic
== CHECK_EXISTANCE
) {
3327 return (0); // We're Evaling whether a recipient exists
3329 else if (!(CC
->logged_in
)) {
3331 if ((CC
->room
.QRflags
& QR_READONLY
)) {
3332 snprintf(errmsgbuf
, n
, "Not logged in.");
3333 return (ERROR
+ NOT_LOGGED_IN
);
3335 if (CC
->room
.QRflags2
& QR2_MODERATED
) {
3336 snprintf(errmsgbuf
, n
, "Not logged in Moderation feature not yet implemented!");
3337 return (ERROR
+ NOT_LOGGED_IN
);
3339 if ((PostPublic
!=POST_LMTP
) &&(CC
->room
.QRflags2
& QR2_SMTP_PUBLIC
) == 0) {
3344 if (RemoteIdentifier
== NULL
)
3346 snprintf(errmsgbuf
, n
, "Need sender to permit access.");
3347 return (ERROR
+ USERNAME_REQUIRED
);
3350 assoc_file_name(filename
, sizeof filename
, &CC
->room
, ctdl_netcfg_dir
);
3351 begin_critical_section(S_NETCONFIGS
);
3352 if (!read_spoolcontrol_file(&sc
, filename
))
3354 end_critical_section(S_NETCONFIGS
);
3355 snprintf(errmsgbuf
, n
,
3356 "This mailing list only accepts posts from subscribers.");
3357 return (ERROR
+ NO_SUCH_USER
);
3359 end_critical_section(S_NETCONFIGS
);
3360 found
= is_recipient (sc
, RemoteIdentifier
);
3361 free_spoolcontrol_struct(&sc
);
3366 snprintf(errmsgbuf
, n
,
3367 "This mailing list only accepts posts from subscribers.");
3368 return (ERROR
+ NO_SUCH_USER
);
3375 if ((CC
->user
.axlevel
< 2)
3376 && ((CC
->room
.QRflags
& QR_MAILBOX
) == 0)) {
3377 snprintf(errmsgbuf
, n
, "Need to be validated to enter "
3378 "(except in %s> to sysop)", MAILROOM
);
3379 return (ERROR
+ HIGHER_ACCESS_REQUIRED
);
3382 CtdlRoomAccess(&CC
->room
, &CC
->user
, &ra
, NULL
);
3383 if (!(ra
& UA_POSTALLOWED
)) {
3384 snprintf(errmsgbuf
, n
, "Higher access is required to post in this room.");
3385 return (ERROR
+ HIGHER_ACCESS_REQUIRED
);
3388 strcpy(errmsgbuf
, "Ok");
3394 * Check to see if the specified user has Internet mail permission
3395 * (returns nonzero if permission is granted)
3397 int CtdlCheckInternetMailPermission(struct ctdluser
*who
) {
3399 /* Do not allow twits to send Internet mail */
3400 if (who
->axlevel
<= 2) return(0);
3402 /* Globally enabled? */
3403 if (config
.c_restrict
== 0) return(1);
3405 /* User flagged ok? */
3406 if (who
->flags
& US_INTERNET
) return(2);
3408 /* Aide level access? */
3409 if (who
->axlevel
>= 6) return(3);
3411 /* No mail for you! */
3417 * Validate recipients, count delivery types and errors, and handle aliasing
3418 * FIXME check for dupes!!!!!
3420 * Returns 0 if all addresses are ok, ret->num_error = -1 if no addresses
3421 * were specified, or the number of addresses found invalid.
3423 * Caller needs to free the result using free_recipients()
3425 struct recptypes
*validate_recipients(char *supplied_recipients
,
3426 const char *RemoteIdentifier
,
3428 struct recptypes
*ret
;
3429 char *recipients
= NULL
;
3430 char this_recp
[256];
3431 char this_recp_cooked
[256];
3437 struct ctdluser tempUS
;
3438 struct ctdlroom tempQR
;
3439 struct ctdlroom tempQR2
;
3445 ret
= (struct recptypes
*) malloc(sizeof(struct recptypes
));
3446 if (ret
== NULL
) return(NULL
);
3448 /* Set all strings to null and numeric values to zero */
3449 memset(ret
, 0, sizeof(struct recptypes
));
3451 if (supplied_recipients
== NULL
) {
3452 recipients
= strdup("");
3455 recipients
= strdup(supplied_recipients
);
3458 /* Allocate some memory. Yes, this allocates 500% more memory than we will
3459 * actually need, but it's healthier for the heap than doing lots of tiny
3460 * realloc() calls instead.
3463 ret
->errormsg
= malloc(strlen(recipients
) + 1024);
3464 ret
->recp_local
= malloc(strlen(recipients
) + 1024);
3465 ret
->recp_internet
= malloc(strlen(recipients
) + 1024);
3466 ret
->recp_ignet
= malloc(strlen(recipients
) + 1024);
3467 ret
->recp_room
= malloc(strlen(recipients
) + 1024);
3468 ret
->display_recp
= malloc(strlen(recipients
) + 1024);
3470 ret
->errormsg
[0] = 0;
3471 ret
->recp_local
[0] = 0;
3472 ret
->recp_internet
[0] = 0;
3473 ret
->recp_ignet
[0] = 0;
3474 ret
->recp_room
[0] = 0;
3475 ret
->display_recp
[0] = 0;
3477 ret
->recptypes_magic
= RECPTYPES_MAGIC
;
3479 /* Change all valid separator characters to commas */
3480 for (i
=0; !IsEmptyStr(&recipients
[i
]); ++i
) {
3481 if ((recipients
[i
] == ';') || (recipients
[i
] == '|')) {
3482 recipients
[i
] = ',';
3486 /* Now start extracting recipients... */
3488 while (!IsEmptyStr(recipients
)) {
3490 for (i
=0; i
<=strlen(recipients
); ++i
) {
3491 if (recipients
[i
] == '\"') in_quotes
= 1 - in_quotes
;
3492 if ( ( (recipients
[i
] == ',') && (!in_quotes
) ) || (recipients
[i
] == 0) ) {
3493 safestrncpy(this_recp
, recipients
, i
+1);
3495 if (recipients
[i
] == ',') {
3496 strcpy(recipients
, &recipients
[i
+1]);
3499 strcpy(recipients
, "");
3506 if (IsEmptyStr(this_recp
))
3508 CtdlLogPrintf(CTDL_DEBUG
, "Evaluating recipient #%d: %s\n", num_recps
, this_recp
);
3510 mailtype
= alias(this_recp
);
3511 mailtype
= alias(this_recp
);
3512 mailtype
= alias(this_recp
);
3514 for (j
=0; !IsEmptyStr(&this_recp
[j
]); ++j
) {
3515 if (this_recp
[j
]=='_') {
3516 this_recp_cooked
[j
] = ' ';
3519 this_recp_cooked
[j
] = this_recp
[j
];
3522 this_recp_cooked
[j
] = '\0';
3527 if (!strcasecmp(this_recp
, "sysop")) {
3529 strcpy(this_recp
, config
.c_aideroom
);
3530 if (!IsEmptyStr(ret
->recp_room
)) {
3531 strcat(ret
->recp_room
, "|");
3533 strcat(ret
->recp_room
, this_recp
);
3535 else if ( (!strncasecmp(this_recp
, "room_", 5))
3536 && (!getroom(&tempQR
, &this_recp_cooked
[5])) ) {
3538 /* Save room so we can restore it later */
3542 /* Check permissions to send mail to this room */
3543 err
= CtdlDoIHavePermissionToPostInThisRoom(errmsg
,
3555 if (!IsEmptyStr(ret
->recp_room
)) {
3556 strcat(ret
->recp_room
, "|");
3558 strcat(ret
->recp_room
, &this_recp_cooked
[5]);
3561 /* Restore room in case something needs it */
3565 else if (getuser(&tempUS
, this_recp
) == 0) {
3567 strcpy(this_recp
, tempUS
.fullname
);
3568 if (!IsEmptyStr(ret
->recp_local
)) {
3569 strcat(ret
->recp_local
, "|");
3571 strcat(ret
->recp_local
, this_recp
);
3573 else if (getuser(&tempUS
, this_recp_cooked
) == 0) {
3575 strcpy(this_recp
, tempUS
.fullname
);
3576 if (!IsEmptyStr(ret
->recp_local
)) {
3577 strcat(ret
->recp_local
, "|");
3579 strcat(ret
->recp_local
, this_recp
);
3587 /* Yes, you're reading this correctly: if the target
3588 * domain points back to the local system or an attached
3589 * Citadel directory, the address is invalid. That's
3590 * because if the address were valid, we would have
3591 * already translated it to a local address by now.
3593 if (IsDirectory(this_recp
, 0)) {
3598 ++ret
->num_internet
;
3599 if (!IsEmptyStr(ret
->recp_internet
)) {
3600 strcat(ret
->recp_internet
, "|");
3602 strcat(ret
->recp_internet
, this_recp
);
3607 if (!IsEmptyStr(ret
->recp_ignet
)) {
3608 strcat(ret
->recp_ignet
, "|");
3610 strcat(ret
->recp_ignet
, this_recp
);
3618 if (IsEmptyStr(errmsg
)) {
3619 snprintf(append
, sizeof append
, "Invalid recipient: %s", this_recp
);
3622 snprintf(append
, sizeof append
, "%s", errmsg
);
3624 if ( (strlen(ret
->errormsg
) + strlen(append
) + 3) < SIZ
) {
3625 if (!IsEmptyStr(ret
->errormsg
)) {
3626 strcat(ret
->errormsg
, "; ");
3628 strcat(ret
->errormsg
, append
);
3632 if (IsEmptyStr(ret
->display_recp
)) {
3633 strcpy(append
, this_recp
);
3636 snprintf(append
, sizeof append
, ", %s", this_recp
);
3638 if ( (strlen(ret
->display_recp
)+strlen(append
)) < SIZ
) {
3639 strcat(ret
->display_recp
, append
);
3644 if ((ret
->num_local
+ ret
->num_internet
+ ret
->num_ignet
+
3645 ret
->num_room
+ ret
->num_error
) == 0) {
3646 ret
->num_error
= (-1);
3647 strcpy(ret
->errormsg
, "No recipients specified.");
3650 CtdlLogPrintf(CTDL_DEBUG
, "validate_recipients()\n");
3651 CtdlLogPrintf(CTDL_DEBUG
, " local: %d <%s>\n", ret
->num_local
, ret
->recp_local
);
3652 CtdlLogPrintf(CTDL_DEBUG
, " room: %d <%s>\n", ret
->num_room
, ret
->recp_room
);
3653 CtdlLogPrintf(CTDL_DEBUG
, " inet: %d <%s>\n", ret
->num_internet
, ret
->recp_internet
);
3654 CtdlLogPrintf(CTDL_DEBUG
, " ignet: %d <%s>\n", ret
->num_ignet
, ret
->recp_ignet
);
3655 CtdlLogPrintf(CTDL_DEBUG
, " error: %d <%s>\n", ret
->num_error
, ret
->errormsg
);
3663 * Destructor for struct recptypes
3665 void free_recipients(struct recptypes
*valid
) {
3667 if (valid
== NULL
) {
3671 if (valid
->recptypes_magic
!= RECPTYPES_MAGIC
) {
3672 CtdlLogPrintf(CTDL_EMERG
, "Attempt to call free_recipients() on some other data type!\n");
3676 if (valid
->errormsg
!= NULL
) free(valid
->errormsg
);
3677 if (valid
->recp_local
!= NULL
) free(valid
->recp_local
);
3678 if (valid
->recp_internet
!= NULL
) free(valid
->recp_internet
);
3679 if (valid
->recp_ignet
!= NULL
) free(valid
->recp_ignet
);
3680 if (valid
->recp_room
!= NULL
) free(valid
->recp_room
);
3681 if (valid
->display_recp
!= NULL
) free(valid
->display_recp
);
3688 * message entry - mode 0 (normal)
3690 void cmd_ent0(char *entargs
)
3696 char supplied_euid
[128];
3698 int format_type
= 0;
3699 char newusername
[256];
3700 char newuseremail
[256];
3701 struct CtdlMessage
*msg
;
3705 struct recptypes
*valid
= NULL
;
3706 struct recptypes
*valid_to
= NULL
;
3707 struct recptypes
*valid_cc
= NULL
;
3708 struct recptypes
*valid_bcc
= NULL
;
3710 int subject_required
= 0;
3715 int newuseremail_ok
= 0;
3716 char references
[SIZ
];
3721 post
= extract_int(entargs
, 0);
3722 extract_token(recp
, entargs
, 1, '|', sizeof recp
);
3723 anon_flag
= extract_int(entargs
, 2);
3724 format_type
= extract_int(entargs
, 3);
3725 extract_token(subject
, entargs
, 4, '|', sizeof subject
);
3726 extract_token(newusername
, entargs
, 5, '|', sizeof newusername
);
3727 do_confirm
= extract_int(entargs
, 6);
3728 extract_token(cc
, entargs
, 7, '|', sizeof cc
);
3729 extract_token(bcc
, entargs
, 8, '|', sizeof bcc
);
3730 switch(CC
->room
.QRdefaultview
) {
3733 extract_token(supplied_euid
, entargs
, 9, '|', sizeof supplied_euid
);
3736 supplied_euid
[0] = 0;
3739 extract_token(newuseremail
, entargs
, 10, '|', sizeof newuseremail
);
3740 extract_token(references
, entargs
, 11, '|', sizeof references
);
3741 for (ptr
=references
; *ptr
!= 0; ++ptr
) {
3742 if (*ptr
== '!') *ptr
= '|';
3745 /* first check to make sure the request is valid. */
3747 err
= CtdlDoIHavePermissionToPostInThisRoom(errmsg
, sizeof errmsg
, NULL
, POST_LOGGED_IN
);
3750 cprintf("%d %s\n", err
, errmsg
);
3754 /* Check some other permission type things. */
3756 if (IsEmptyStr(newusername
)) {
3757 strcpy(newusername
, CC
->user
.fullname
);
3759 if ( (CC
->user
.axlevel
< 6)
3760 && (strcasecmp(newusername
, CC
->user
.fullname
))
3761 && (strcasecmp(newusername
, CC
->cs_inet_fn
))
3763 cprintf("%d You don't have permission to author messages as '%s'.\n",
3764 ERROR
+ HIGHER_ACCESS_REQUIRED
,
3771 if (IsEmptyStr(newuseremail
)) {
3772 newuseremail_ok
= 1;
3775 if (!IsEmptyStr(newuseremail
)) {
3776 if (!strcasecmp(newuseremail
, CC
->cs_inet_email
)) {
3777 newuseremail_ok
= 1;
3779 else if (!IsEmptyStr(CC
->cs_inet_other_emails
)) {
3780 j
= num_tokens(CC
->cs_inet_other_emails
, '|');
3781 for (i
=0; i
<j
; ++i
) {
3782 extract_token(buf
, CC
->cs_inet_other_emails
, i
, '|', sizeof buf
);
3783 if (!strcasecmp(newuseremail
, buf
)) {
3784 newuseremail_ok
= 1;
3790 if (!newuseremail_ok
) {
3791 cprintf("%d You don't have permission to author messages as '%s'.\n",
3792 ERROR
+ HIGHER_ACCESS_REQUIRED
,
3798 CC
->cs_flags
|= CS_POSTING
;
3800 /* In mailbox rooms we have to behave a little differently --
3801 * make sure the user has specified at least one recipient. Then
3802 * validate the recipient(s). We do this for the Mail> room, as
3803 * well as any room which has the "Mailbox" view set.
3806 if ( ( (CC
->room
.QRflags
& QR_MAILBOX
) && (!strcasecmp(&CC
->room
.QRname
[11], MAILROOM
)) )
3807 || ( (CC
->room
.QRflags
& QR_MAILBOX
) && (CC
->curr_view
== VIEW_MAILBOX
) )
3809 if (CC
->user
.axlevel
< 2) {
3810 strcpy(recp
, "sysop");
3815 valid_to
= validate_recipients(recp
, NULL
, 0);
3816 if (valid_to
->num_error
> 0) {
3817 cprintf("%d %s\n", ERROR
+ NO_SUCH_USER
, valid_to
->errormsg
);
3818 free_recipients(valid_to
);
3822 valid_cc
= validate_recipients(cc
, NULL
, 0);
3823 if (valid_cc
->num_error
> 0) {
3824 cprintf("%d %s\n", ERROR
+ NO_SUCH_USER
, valid_cc
->errormsg
);
3825 free_recipients(valid_to
);
3826 free_recipients(valid_cc
);
3830 valid_bcc
= validate_recipients(bcc
, NULL
, 0);
3831 if (valid_bcc
->num_error
> 0) {
3832 cprintf("%d %s\n", ERROR
+ NO_SUCH_USER
, valid_bcc
->errormsg
);
3833 free_recipients(valid_to
);
3834 free_recipients(valid_cc
);
3835 free_recipients(valid_bcc
);
3839 /* Recipient required, but none were specified */
3840 if ( (valid_to
->num_error
< 0) && (valid_cc
->num_error
< 0) && (valid_bcc
->num_error
< 0) ) {
3841 free_recipients(valid_to
);
3842 free_recipients(valid_cc
);
3843 free_recipients(valid_bcc
);
3844 cprintf("%d At least one recipient is required.\n", ERROR
+ NO_SUCH_USER
);
3848 if (valid_to
->num_internet
+ valid_cc
->num_internet
+ valid_bcc
->num_internet
> 0) {
3849 if (CtdlCheckInternetMailPermission(&CC
->user
)==0) {
3850 cprintf("%d You do not have permission "
3851 "to send Internet mail.\n",
3852 ERROR
+ HIGHER_ACCESS_REQUIRED
);
3853 free_recipients(valid_to
);
3854 free_recipients(valid_cc
);
3855 free_recipients(valid_bcc
);
3860 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)
3861 && (CC
->user
.axlevel
< 4) ) {
3862 cprintf("%d Higher access required for network mail.\n",
3863 ERROR
+ HIGHER_ACCESS_REQUIRED
);
3864 free_recipients(valid_to
);
3865 free_recipients(valid_cc
);
3866 free_recipients(valid_bcc
);
3870 if ((RESTRICT_INTERNET
== 1)
3871 && (valid_to
->num_internet
+ valid_cc
->num_internet
+ valid_bcc
->num_internet
> 0)
3872 && ((CC
->user
.flags
& US_INTERNET
) == 0)
3873 && (!CC
->internal_pgm
)) {
3874 cprintf("%d You don't have access to Internet mail.\n",
3875 ERROR
+ HIGHER_ACCESS_REQUIRED
);
3876 free_recipients(valid_to
);
3877 free_recipients(valid_cc
);
3878 free_recipients(valid_bcc
);
3884 /* Is this a room which has anonymous-only or anonymous-option? */
3885 anonymous
= MES_NORMAL
;
3886 if (CC
->room
.QRflags
& QR_ANONONLY
) {
3887 anonymous
= MES_ANONONLY
;
3889 if (CC
->room
.QRflags
& QR_ANONOPT
) {
3890 if (anon_flag
== 1) { /* only if the user requested it */
3891 anonymous
= MES_ANONOPT
;
3895 if ((CC
->room
.QRflags
& QR_MAILBOX
) == 0) {
3899 /* Recommend to the client that the use of a message subject is
3900 * strongly recommended in this room, if either the SUBJECTREQ flag
3901 * is set, or if there is one or more Internet email recipients.
3903 if (CC
->room
.QRflags2
& QR2_SUBJECTREQ
) subject_required
= 1;
3904 if ((valid_to
) && (valid_to
->num_internet
> 0)) subject_required
= 1;
3905 if ((valid_cc
) && (valid_cc
->num_internet
> 0)) subject_required
= 1;
3906 if ((valid_bcc
) && (valid_bcc
->num_internet
> 0)) subject_required
= 1;
3908 /* If we're only checking the validity of the request, return
3909 * success without creating the message.
3912 cprintf("%d %s|%d\n", CIT_OK
,
3913 ((valid_to
!= NULL
) ? valid_to
->display_recp
: ""),
3915 free_recipients(valid_to
);
3916 free_recipients(valid_cc
);
3917 free_recipients(valid_bcc
);
3921 /* We don't need these anymore because we'll do it differently below */
3922 free_recipients(valid_to
);
3923 free_recipients(valid_cc
);
3924 free_recipients(valid_bcc
);
3926 /* Read in the message from the client. */
3928 cprintf("%d send message\n", START_CHAT_MODE
);
3930 cprintf("%d send message\n", SEND_LISTING
);
3933 msg
= CtdlMakeMessage(&CC
->user
, recp
, cc
,
3934 CC
->room
.QRname
, anonymous
, format_type
,
3935 newusername
, newuseremail
, subject
,
3936 ((!IsEmptyStr(supplied_euid
)) ? supplied_euid
: NULL
),
3939 /* Put together one big recipients struct containing to/cc/bcc all in
3940 * one. This is for the envelope.
3942 char *all_recps
= malloc(SIZ
* 3);
3943 strcpy(all_recps
, recp
);
3944 if (!IsEmptyStr(cc
)) {
3945 if (!IsEmptyStr(all_recps
)) {
3946 strcat(all_recps
, ",");
3948 strcat(all_recps
, cc
);
3950 if (!IsEmptyStr(bcc
)) {
3951 if (!IsEmptyStr(all_recps
)) {
3952 strcat(all_recps
, ",");
3954 strcat(all_recps
, bcc
);
3956 if (!IsEmptyStr(all_recps
)) {
3957 valid
= validate_recipients(all_recps
, NULL
, 0);
3965 msgnum
= CtdlSubmitMsg(msg
, valid
, "", QP_EADDR
);
3968 cprintf("%ld\n", msgnum
);
3970 cprintf("Message accepted.\n");
3973 cprintf("Internal error.\n");
3975 if (msg
->cm_fields
['E'] != NULL
) {
3976 cprintf("%s\n", msg
->cm_fields
['E']);
3983 CtdlFreeMessage(msg
);
3985 if (valid
!= NULL
) {
3986 free_recipients(valid
);
3994 * API function to delete messages which match a set of criteria
3995 * (returns the actual number of messages deleted)
3997 int CtdlDeleteMessages(char *room_name
, /* which room */
3998 long *dmsgnums
, /* array of msg numbers to be deleted */
3999 int num_dmsgnums
, /* number of msgs to be deleted, or 0 for "any" */
4000 char *content_type
/* or "" for any. regular expressions expected. */
4003 struct ctdlroom qrbuf
;
4004 struct cdbdata
*cdbfr
;
4005 long *msglist
= NULL
;
4006 long *dellist
= NULL
;
4009 int num_deleted
= 0;
4011 struct MetaData smi
;
4014 int need_to_free_re
= 0;
4016 if (content_type
) if (!IsEmptyStr(content_type
)) {
4017 regcomp(&re
, content_type
, 0);
4018 need_to_free_re
= 1;
4020 CtdlLogPrintf(CTDL_DEBUG
, "CtdlDeleteMessages(%s, %d msgs, %s)\n",
4021 room_name
, num_dmsgnums
, content_type
);
4023 /* get room record, obtaining a lock... */
4024 if (lgetroom(&qrbuf
, room_name
) != 0) {
4025 CtdlLogPrintf(CTDL_ERR
, "CtdlDeleteMessages(): Room <%s> not found\n",
4027 if (need_to_free_re
) regfree(&re
);
4028 return (0); /* room not found */
4030 cdbfr
= cdb_fetch(CDB_MSGLISTS
, &qrbuf
.QRnumber
, sizeof(long));
4032 if (cdbfr
!= NULL
) {
4033 dellist
= malloc(cdbfr
->len
);
4034 msglist
= (long *) cdbfr
->ptr
;
4035 cdbfr
->ptr
= NULL
; /* CtdlDeleteMessages() now owns this memory */
4036 num_msgs
= cdbfr
->len
/ sizeof(long);
4040 for (i
= 0; i
< num_msgs
; ++i
) {
4043 /* Set/clear a bit for each criterion */
4045 /* 0 messages in the list or a null list means that we are
4046 * interested in deleting any messages which meet the other criteria.
4048 if ((num_dmsgnums
== 0) || (dmsgnums
== NULL
)) {
4049 delete_this
|= 0x01;
4052 for (j
=0; j
<num_dmsgnums
; ++j
) {
4053 if (msglist
[i
] == dmsgnums
[j
]) {
4054 delete_this
|= 0x01;
4059 if (IsEmptyStr(content_type
)) {
4060 delete_this
|= 0x02;
4062 GetMetaData(&smi
, msglist
[i
]);
4063 if (regexec(&re
, smi
.meta_content_type
, 1, &pm
, 0) == 0) {
4064 delete_this
|= 0x02;
4068 /* Delete message only if all bits are set */
4069 if (delete_this
== 0x03) {
4070 dellist
[num_deleted
++] = msglist
[i
];
4075 num_msgs
= sort_msglist(msglist
, num_msgs
);
4076 cdb_store(CDB_MSGLISTS
, &qrbuf
.QRnumber
, (int)sizeof(long),
4077 msglist
, (int)(num_msgs
* sizeof(long)));
4079 qrbuf
.QRhighest
= msglist
[num_msgs
- 1];
4083 /* Go through the messages we pulled out of the index, and decrement
4084 * their reference counts by 1. If this is the only room the message
4085 * was in, the reference count will reach zero and the message will
4086 * automatically be deleted from the database. We do this in a
4087 * separate pass because there might be plug-in hooks getting called,
4088 * and we don't want that happening during an S_ROOMS critical
4091 if (num_deleted
) for (i
=0; i
<num_deleted
; ++i
) {
4092 PerformDeleteHooks(qrbuf
.QRname
, dellist
[i
]);
4093 AdjRefCount(dellist
[i
], -1);
4096 /* Now free the memory we used, and go away. */
4097 if (msglist
!= NULL
) free(msglist
);
4098 if (dellist
!= NULL
) free(dellist
);
4099 CtdlLogPrintf(CTDL_DEBUG
, "%d message(s) deleted.\n", num_deleted
);
4100 if (need_to_free_re
) regfree(&re
);
4101 return (num_deleted
);
4107 * Check whether the current user has permission to delete messages from
4108 * the current room (returns 1 for yes, 0 for no)
4110 int CtdlDoIHavePermissionToDeleteMessagesFromThisRoom(void) {
4112 CtdlRoomAccess(&CC
->room
, &CC
->user
, &ra
, NULL
);
4113 if (ra
& UA_DELETEALLOWED
) return(1);
4121 * Delete message from current room
4123 void cmd_dele(char *args
)
4132 extract_token(msgset
, args
, 0, '|', sizeof msgset
);
4133 num_msgs
= num_tokens(msgset
, ',');
4135 cprintf("%d Nothing to do.\n", CIT_OK
);
4139 if (CtdlDoIHavePermissionToDeleteMessagesFromThisRoom() == 0) {
4140 cprintf("%d Higher access required.\n",
4141 ERROR
+ HIGHER_ACCESS_REQUIRED
);
4146 * Build our message set to be moved/copied
4148 msgs
= malloc(num_msgs
* sizeof(long));
4149 for (i
=0; i
<num_msgs
; ++i
) {
4150 extract_token(msgtok
, msgset
, i
, ',', sizeof msgtok
);
4151 msgs
[i
] = atol(msgtok
);
4154 num_deleted
= CtdlDeleteMessages(CC
->room
.QRname
, msgs
, num_msgs
, "");
4158 cprintf("%d %d message%s deleted.\n", CIT_OK
,
4159 num_deleted
, ((num_deleted
!= 1) ? "s" : ""));
4161 cprintf("%d Message not found.\n", ERROR
+ MESSAGE_NOT_FOUND
);
4167 * Back end API function for moves and deletes (multiple messages)
4169 int CtdlCopyMsgsToRoom(long *msgnums
, int num_msgs
, char *dest
) {
4172 err
= CtdlSaveMsgPointersInRoom(dest
, msgnums
, num_msgs
, 1, NULL
);
4173 if (err
!= 0) return(err
);
4182 * move or copy a message to another room
4184 void cmd_move(char *args
)
4191 char targ
[ROOMNAMELEN
];
4192 struct ctdlroom qtemp
;
4199 extract_token(msgset
, args
, 0, '|', sizeof msgset
);
4200 num_msgs
= num_tokens(msgset
, ',');
4202 cprintf("%d Nothing to do.\n", CIT_OK
);
4206 extract_token(targ
, args
, 1, '|', sizeof targ
);
4207 convert_room_name_macros(targ
, sizeof targ
);
4208 targ
[ROOMNAMELEN
- 1] = 0;
4209 is_copy
= extract_int(args
, 2);
4211 if (getroom(&qtemp
, targ
) != 0) {
4212 cprintf("%d '%s' does not exist.\n",
4213 ERROR
+ ROOM_NOT_FOUND
, targ
);
4217 getuser(&CC
->user
, CC
->curr_user
);
4218 CtdlRoomAccess(&qtemp
, &CC
->user
, &ra
, NULL
);
4220 /* Check for permission to perform this operation.
4221 * Remember: "CC->room" is source, "qtemp" is target.
4225 /* Aides can move/copy */
4226 if (CC
->user
.axlevel
>= 6) permit
= 1;
4228 /* Room aides can move/copy */
4229 if (CC
->user
.usernum
== CC
->room
.QRroomaide
) permit
= 1;
4231 /* Permit move/copy from personal rooms */
4232 if ((CC
->room
.QRflags
& QR_MAILBOX
)
4233 && (qtemp
.QRflags
& QR_MAILBOX
)) permit
= 1;
4235 /* Permit only copy from public to personal room */
4237 && (!(CC
->room
.QRflags
& QR_MAILBOX
))
4238 && (qtemp
.QRflags
& QR_MAILBOX
)) permit
= 1;
4240 /* Permit message removal from collaborative delete rooms */
4241 if (CC
->room
.QRflags2
& QR2_COLLABDEL
) permit
= 1;
4243 /* Users allowed to post into the target room may move into it too. */
4244 if ((CC
->room
.QRflags
& QR_MAILBOX
) &&
4245 (qtemp
.QRflags
& UA_POSTALLOWED
)) permit
= 1;
4247 /* User must have access to target room */
4248 if (!(ra
& UA_KNOWN
)) permit
= 0;
4251 cprintf("%d Higher access required.\n",
4252 ERROR
+ HIGHER_ACCESS_REQUIRED
);
4257 * Build our message set to be moved/copied
4259 msgs
= malloc(num_msgs
* sizeof(long));
4260 for (i
=0; i
<num_msgs
; ++i
) {
4261 extract_token(msgtok
, msgset
, i
, ',', sizeof msgtok
);
4262 msgs
[i
] = atol(msgtok
);
4268 err
= CtdlCopyMsgsToRoom(msgs
, num_msgs
, targ
);
4270 cprintf("%d Cannot store message(s) in %s: error %d\n",
4276 /* Now delete the message from the source room,
4277 * if this is a 'move' rather than a 'copy' operation.
4280 CtdlDeleteMessages(CC
->room
.QRname
, msgs
, num_msgs
, "");
4284 cprintf("%d Message(s) %s.\n", CIT_OK
, (is_copy
? "copied" : "moved") );
4290 * GetMetaData() - Get the supplementary record for a message
4292 void GetMetaData(struct MetaData
*smibuf
, long msgnum
)
4295 struct cdbdata
*cdbsmi
;
4298 memset(smibuf
, 0, sizeof(struct MetaData
));
4299 smibuf
->meta_msgnum
= msgnum
;
4300 smibuf
->meta_refcount
= 1; /* Default reference count is 1 */
4302 /* Use the negative of the message number for its supp record index */
4303 TheIndex
= (0L - msgnum
);
4305 cdbsmi
= cdb_fetch(CDB_MSGMAIN
, &TheIndex
, sizeof(long));
4306 if (cdbsmi
== NULL
) {
4307 return; /* record not found; go with defaults */
4309 memcpy(smibuf
, cdbsmi
->ptr
,
4310 ((cdbsmi
->len
> sizeof(struct MetaData
)) ?
4311 sizeof(struct MetaData
) : cdbsmi
->len
));
4318 * PutMetaData() - (re)write supplementary record for a message
4320 void PutMetaData(struct MetaData
*smibuf
)
4324 /* Use the negative of the message number for the metadata db index */
4325 TheIndex
= (0L - smibuf
->meta_msgnum
);
4327 cdb_store(CDB_MSGMAIN
,
4328 &TheIndex
, (int)sizeof(long),
4329 smibuf
, (int)sizeof(struct MetaData
));
4334 * AdjRefCount - submit an adjustment to the reference count for a message.
4335 * (These are just queued -- we actually process them later.)
4337 void AdjRefCount(long msgnum
, int incr
)
4339 struct arcq new_arcq
;
4341 begin_critical_section(S_SUPPMSGMAIN
);
4342 if (arcfp
== NULL
) {
4343 arcfp
= fopen(file_arcq
, "ab+");
4345 end_critical_section(S_SUPPMSGMAIN
);
4347 /* msgnum < 0 means that we're trying to close the file */
4349 CtdlLogPrintf(CTDL_DEBUG
, "Closing the AdjRefCount queue file\n");
4350 begin_critical_section(S_SUPPMSGMAIN
);
4351 if (arcfp
!= NULL
) {
4355 end_critical_section(S_SUPPMSGMAIN
);
4360 * If we can't open the queue, perform the operation synchronously.
4362 if (arcfp
== NULL
) {
4363 TDAP_AdjRefCount(msgnum
, incr
);
4367 new_arcq
.arcq_msgnum
= msgnum
;
4368 new_arcq
.arcq_delta
= incr
;
4369 fwrite(&new_arcq
, sizeof(struct arcq
), 1, arcfp
);
4377 * TDAP_ProcessAdjRefCountQueue()
4379 * Process the queue of message count adjustments that was created by calls
4380 * to AdjRefCount() ... by reading the queue and calling TDAP_AdjRefCount()
4381 * for each one. This should be an "off hours" operation.
4383 int TDAP_ProcessAdjRefCountQueue(void)
4385 char file_arcq_temp
[PATH_MAX
];
4388 struct arcq arcq_rec
;
4389 int num_records_processed
= 0;
4391 snprintf(file_arcq_temp
, sizeof file_arcq_temp
, "%s.%04x", file_arcq
, rand());
4393 begin_critical_section(S_SUPPMSGMAIN
);
4394 if (arcfp
!= NULL
) {
4399 r
= link(file_arcq
, file_arcq_temp
);
4401 CtdlLogPrintf(CTDL_CRIT
, "%s: %s\n", file_arcq_temp
, strerror(errno
));
4402 end_critical_section(S_SUPPMSGMAIN
);
4403 return(num_records_processed
);
4407 end_critical_section(S_SUPPMSGMAIN
);
4409 fp
= fopen(file_arcq_temp
, "rb");
4411 CtdlLogPrintf(CTDL_CRIT
, "%s: %s\n", file_arcq_temp
, strerror(errno
));
4412 return(num_records_processed
);
4415 while (fread(&arcq_rec
, sizeof(struct arcq
), 1, fp
) == 1) {
4416 TDAP_AdjRefCount(arcq_rec
.arcq_msgnum
, arcq_rec
.arcq_delta
);
4417 ++num_records_processed
;
4421 r
= unlink(file_arcq_temp
);
4423 CtdlLogPrintf(CTDL_CRIT
, "%s: %s\n", file_arcq_temp
, strerror(errno
));
4426 return(num_records_processed
);
4432 * TDAP_AdjRefCount - adjust the reference count for a message.
4433 * This one does it "for real" because it's called by
4434 * the autopurger function that processes the queue
4435 * created by AdjRefCount(). If a message's reference
4436 * count becomes zero, we also delete the message from
4437 * disk and de-index it.
4439 void TDAP_AdjRefCount(long msgnum
, int incr
)
4442 struct MetaData smi
;
4445 /* This is a *tight* critical section; please keep it that way, as
4446 * it may get called while nested in other critical sections.
4447 * Complicating this any further will surely cause deadlock!
4449 begin_critical_section(S_SUPPMSGMAIN
);
4450 GetMetaData(&smi
, msgnum
);
4451 smi
.meta_refcount
+= incr
;
4453 end_critical_section(S_SUPPMSGMAIN
);
4454 CtdlLogPrintf(CTDL_DEBUG
, "msg %ld ref count delta %+d, is now %d\n",
4455 msgnum
, incr
, smi
.meta_refcount
);
4457 /* If the reference count is now zero, delete the message
4458 * (and its supplementary record as well).
4460 if (smi
.meta_refcount
== 0) {
4461 CtdlLogPrintf(CTDL_DEBUG
, "Deleting message <%ld>\n", msgnum
);
4463 /* Call delete hooks with NULL room to show it has gone altogether */
4464 PerformDeleteHooks(NULL
, msgnum
);
4466 /* Remove from message base */
4468 cdb_delete(CDB_MSGMAIN
, &delnum
, (int)sizeof(long));
4469 cdb_delete(CDB_BIGMSGS
, &delnum
, (int)sizeof(long));
4471 /* Remove metadata record */
4472 delnum
= (0L - msgnum
);
4473 cdb_delete(CDB_MSGMAIN
, &delnum
, (int)sizeof(long));
4479 * Write a generic object to this room
4481 * Note: this could be much more efficient. Right now we use two temporary
4482 * files, and still pull the message into memory as with all others.
4484 void CtdlWriteObject(char *req_room
, /* Room to stuff it in */
4485 char *content_type
, /* MIME type of this object */
4486 char *raw_message
, /* Data to be written */
4487 off_t raw_length
, /* Size of raw_message */
4488 struct ctdluser
*is_mailbox
, /* Mailbox room? */
4489 int is_binary
, /* Is encoding necessary? */
4490 int is_unique
, /* Del others of this type? */
4491 unsigned int flags
/* Internal save flags */
4495 struct ctdlroom qrbuf
;
4496 char roomname
[ROOMNAMELEN
];
4497 struct CtdlMessage
*msg
;
4498 char *encoded_message
= NULL
;
4500 if (is_mailbox
!= NULL
) {
4501 MailboxName(roomname
, sizeof roomname
, is_mailbox
, req_room
);
4504 safestrncpy(roomname
, req_room
, sizeof(roomname
));
4507 CtdlLogPrintf(CTDL_DEBUG
, "Raw length is %ld\n", (long)raw_length
);
4510 encoded_message
= malloc((size_t) (((raw_length
* 134) / 100) + 4096 ) );
4513 encoded_message
= malloc((size_t)(raw_length
+ 4096));
4516 sprintf(encoded_message
, "Content-type: %s\n", content_type
);
4519 sprintf(&encoded_message
[strlen(encoded_message
)],
4520 "Content-transfer-encoding: base64\n\n"
4524 sprintf(&encoded_message
[strlen(encoded_message
)],
4525 "Content-transfer-encoding: 7bit\n\n"
4531 &encoded_message
[strlen(encoded_message
)],
4539 &encoded_message
[strlen(encoded_message
)],
4545 CtdlLogPrintf(CTDL_DEBUG
, "Allocating\n");
4546 msg
= malloc(sizeof(struct CtdlMessage
));
4547 memset(msg
, 0, sizeof(struct CtdlMessage
));
4548 msg
->cm_magic
= CTDLMESSAGE_MAGIC
;
4549 msg
->cm_anon_type
= MES_NORMAL
;
4550 msg
->cm_format_type
= 4;
4551 msg
->cm_fields
['A'] = strdup(CC
->user
.fullname
);
4552 msg
->cm_fields
['O'] = strdup(req_room
);
4553 msg
->cm_fields
['N'] = strdup(config
.c_nodename
);
4554 msg
->cm_fields
['H'] = strdup(config
.c_humannode
);
4555 msg
->cm_flags
= flags
;
4557 msg
->cm_fields
['M'] = encoded_message
;
4559 /* Create the requested room if we have to. */
4560 if (getroom(&qrbuf
, roomname
) != 0) {
4561 create_room(roomname
,
4562 ( (is_mailbox
!= NULL
) ? 5 : 3 ),
4563 "", 0, 1, 0, VIEW_BBS
);
4565 /* If the caller specified this object as unique, delete all
4566 * other objects of this type that are currently in the room.
4569 CtdlLogPrintf(CTDL_DEBUG
, "Deleted %d other msgs of this type\n",
4570 CtdlDeleteMessages(roomname
, NULL
, 0, content_type
)
4573 /* Now write the data */
4574 CtdlSubmitMsg(msg
, NULL
, roomname
, 0);
4575 CtdlFreeMessage(msg
);
4583 void CtdlGetSysConfigBackend(long msgnum
, void *userdata
) {
4584 config_msgnum
= msgnum
;
4588 char *CtdlGetSysConfig(char *sysconfname
) {
4589 char hold_rm
[ROOMNAMELEN
];
4592 struct CtdlMessage
*msg
;
4595 strcpy(hold_rm
, CC
->room
.QRname
);
4596 if (getroom(&CC
->room
, SYSCONFIGROOM
) != 0) {
4597 getroom(&CC
->room
, hold_rm
);
4602 /* We want the last (and probably only) config in this room */
4603 begin_critical_section(S_CONFIG
);
4604 config_msgnum
= (-1L);
4605 CtdlForEachMessage(MSGS_LAST
, 1, NULL
, sysconfname
, NULL
,
4606 CtdlGetSysConfigBackend
, NULL
);
4607 msgnum
= config_msgnum
;
4608 end_critical_section(S_CONFIG
);
4614 msg
= CtdlFetchMessage(msgnum
, 1);
4616 conf
= strdup(msg
->cm_fields
['M']);
4617 CtdlFreeMessage(msg
);
4624 getroom(&CC
->room
, hold_rm
);
4626 if (conf
!= NULL
) do {
4627 extract_token(buf
, conf
, 0, '\n', sizeof buf
);
4628 strcpy(conf
, &conf
[strlen(buf
)+1]);
4629 } while ( (!IsEmptyStr(conf
)) && (!IsEmptyStr(buf
)) );
4635 void CtdlPutSysConfig(char *sysconfname
, char *sysconfdata
) {
4636 CtdlWriteObject(SYSCONFIGROOM
, sysconfname
, sysconfdata
, (strlen(sysconfdata
)+1), NULL
, 0, 1, 0);
4641 * Determine whether a given Internet address belongs to the current user
4643 int CtdlIsMe(char *addr
, int addr_buf_len
)
4645 struct recptypes
*recp
;
4648 recp
= validate_recipients(addr
, NULL
, 0);
4649 if (recp
== NULL
) return(0);
4651 if (recp
->num_local
== 0) {
4652 free_recipients(recp
);
4656 for (i
=0; i
<recp
->num_local
; ++i
) {
4657 extract_token(addr
, recp
->recp_local
, i
, '|', addr_buf_len
);
4658 if (!strcasecmp(addr
, CC
->user
.fullname
)) {
4659 free_recipients(recp
);
4664 free_recipients(recp
);
4670 * Citadel protocol command to do the same
4672 void cmd_isme(char *argbuf
) {
4675 if (CtdlAccessCheck(ac_logged_in
)) return;
4676 extract_token(addr
, argbuf
, 0, '|', sizeof addr
);
4678 if (CtdlIsMe(addr
, sizeof addr
)) {
4679 cprintf("%d %s\n", CIT_OK
, addr
);
4682 cprintf("%d Not you.\n", ERROR
+ ILLEGAL_VALUE
);