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
)
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
) ) {
1784 if (do_proto
) cprintf("%s=%s\n",
1788 else if ((k
== 'F') && (suppress_f
)) {
1791 /* Masquerade display name if needed */
1793 if (do_proto
) cprintf("%s=%s\n",
1795 TheMessage
->cm_fields
[k
]
1804 /* begin header processing loop for RFC822 transfer format */
1809 strcpy(snode
, NODENAME
);
1810 if (mode
== MT_RFC822
) {
1811 for (i
= 0; i
< 256; ++i
) {
1812 if (TheMessage
->cm_fields
[i
]) {
1813 mptr
= mpptr
= TheMessage
->cm_fields
[i
];
1816 safestrncpy(luser
, mptr
, sizeof luser
);
1817 safestrncpy(suser
, mptr
, sizeof suser
);
1819 else if (i
== 'Y') {
1820 if ((flags
& QP_EADDR
) != 0) {
1821 mptr
= qp_encode_email_addrs(mptr
);
1823 sanitize_truncated_recipient(mptr
);
1824 cprintf("CC: %s%s", mptr
, nl
);
1826 else if (i
== 'P') {
1827 cprintf("Return-Path: %s%s", mptr
, nl
);
1829 else if (i
== 'L') {
1830 cprintf("List-ID: %s%s", mptr
, nl
);
1832 else if (i
== 'V') {
1833 if ((flags
& QP_EADDR
) != 0)
1834 mptr
= qp_encode_email_addrs(mptr
);
1835 cprintf("Envelope-To: %s%s", mptr
, nl
);
1837 else if (i
== 'U') {
1838 cprintf("Subject: %s%s", mptr
, nl
);
1842 safestrncpy(mid
, mptr
, sizeof mid
);
1844 safestrncpy(fuser
, mptr
, sizeof fuser
);
1845 /* else if (i == 'O')
1846 cprintf("X-Citadel-Room: %s%s",
1849 safestrncpy(snode
, mptr
, sizeof snode
);
1852 if (haschar(mptr
, '@') == 0)
1854 sanitize_truncated_recipient(mptr
);
1855 cprintf("To: %s@%s", mptr
, config
.c_fqdn
);
1860 if ((flags
& QP_EADDR
) != 0) {
1861 mptr
= qp_encode_email_addrs(mptr
);
1863 sanitize_truncated_recipient(mptr
);
1864 cprintf("To: %s", mptr
);
1868 else if (i
== 'T') {
1869 datestring(datestamp
, sizeof datestamp
,
1870 atol(mptr
), DATESTRING_RFC822
);
1871 cprintf("Date: %s%s", datestamp
, nl
);
1873 else if (i
== 'W') {
1874 cprintf("References: ");
1875 k
= num_tokens(mptr
, '|');
1876 for (j
=0; j
<k
; ++j
) {
1877 extract_token(buf
, mptr
, j
, '|', sizeof buf
);
1878 cprintf("<%s>", buf
);
1891 if (subject_found
== 0) {
1892 cprintf("Subject: (no subject)%s", nl
);
1896 for (i
=0; !IsEmptyStr(&suser
[i
]); ++i
) {
1897 suser
[i
] = tolower(suser
[i
]);
1898 if (!isalnum(suser
[i
])) suser
[i
]='_';
1901 if (mode
== MT_RFC822
) {
1902 if (!strcasecmp(snode
, NODENAME
)) {
1903 safestrncpy(snode
, FQDN
, sizeof snode
);
1906 /* Construct a fun message id */
1907 cprintf("Message-ID: <%s", mid
);/// todo: this possibly breaks threadding mails.
1908 if (strchr(mid
, '@')==NULL
) {
1909 cprintf("@%s", snode
);
1913 if (!is_room_aide() && (TheMessage
->cm_anon_type
== MES_ANONONLY
)) {
1914 cprintf("From: \"----\" <x@x.org>%s", nl
);
1916 else if (!is_room_aide() && (TheMessage
->cm_anon_type
== MES_ANONOPT
)) {
1917 cprintf("From: \"anonymous\" <x@x.org>%s", nl
);
1919 else if (!IsEmptyStr(fuser
)) {
1920 cprintf("From: \"%s\" <%s>%s", luser
, fuser
, nl
);
1923 cprintf("From: \"%s\" <%s@%s>%s", luser
, suser
, snode
, nl
);
1926 /* Blank line signifying RFC822 end-of-headers */
1927 if (TheMessage
->cm_format_type
!= FMT_RFC822
) {
1932 /* end header processing loop ... at this point, we're in the text */
1934 if (headers_only
== HEADERS_FAST
) goto DONE
;
1935 mptr
= TheMessage
->cm_fields
['M'];
1937 /* Tell the client about the MIME parts in this message */
1938 if (TheMessage
->cm_format_type
== FMT_RFC822
) {
1939 if ( (mode
== MT_CITADEL
) || (mode
== MT_MIME
) ) {
1940 memset(&ma
, 0, sizeof(struct ma_info
));
1941 mime_parser(mptr
, NULL
,
1942 (do_proto
? *list_this_part
: NULL
),
1943 (do_proto
? *list_this_pref
: NULL
),
1944 (do_proto
? *list_this_suff
: NULL
),
1947 else if (mode
== MT_RFC822
) { /* unparsed RFC822 dump */
1948 char *start_of_text
= NULL
;
1949 start_of_text
= strstr(mptr
, "\n\r\n");
1950 if (start_of_text
== NULL
) start_of_text
= strstr(mptr
, "\n\n");
1951 if (start_of_text
== NULL
) start_of_text
= mptr
;
1953 start_of_text
= strstr(start_of_text
, "\n");
1958 int nllen
= strlen(nl
);
1960 while (ch
=*mptr
, ch
!=0) {
1966 ((headers_only
== HEADERS_NONE
) && (mptr
>= start_of_text
))
1967 || ((headers_only
== HEADERS_ONLY
) && (mptr
< start_of_text
))
1968 || ((headers_only
!= HEADERS_NONE
) && (headers_only
!= HEADERS_ONLY
))
1971 sprintf(&outbuf
[outlen
], "%s", nl
);
1975 outbuf
[outlen
++] = ch
;
1979 if (flags
& ESC_DOT
)
1981 if ((prev_ch
== 10) && (ch
== '.') && ((*(mptr
+1) == 13) || (*(mptr
+1) == 10)))
1983 outbuf
[outlen
++] = '.';
1988 if (outlen
> 1000) {
1989 client_write(outbuf
, outlen
);
1994 client_write(outbuf
, outlen
);
2002 if (headers_only
== HEADERS_ONLY
) {
2006 /* signify start of msg text */
2007 if ( (mode
== MT_CITADEL
) || (mode
== MT_MIME
) ) {
2008 if (do_proto
) cprintf("text\n");
2011 /* If the format type on disk is 1 (fixed-format), then we want
2012 * everything to be output completely literally ... regardless of
2013 * what message transfer format is in use.
2015 if (TheMessage
->cm_format_type
== FMT_FIXED
) {
2017 if (mode
== MT_MIME
) {
2018 cprintf("Content-type: text/plain\n\n");
2022 while (ch
= *mptr
++, ch
> 0) {
2025 if ((ch
== 10) || (buflen
> 250)) {
2027 cprintf("%s%s", buf
, nl
);
2036 if (!IsEmptyStr(buf
))
2037 cprintf("%s%s", buf
, nl
);
2040 /* If the message on disk is format 0 (Citadel vari-format), we
2041 * output using the formatter at 80 columns. This is the final output
2042 * form if the transfer format is RFC822, but if the transfer format
2043 * is Citadel proprietary, it'll still work, because the indentation
2044 * for new paragraphs is correct and the client will reformat the
2045 * message to the reader's screen width.
2047 if (TheMessage
->cm_format_type
== FMT_CITADEL
) {
2048 if (mode
== MT_MIME
) {
2049 cprintf("Content-type: text/x-citadel-variformat\n\n");
2051 memfmout(mptr
, 0, nl
);
2054 /* If the message on disk is format 4 (MIME), we've gotta hand it
2055 * off to the MIME parser. The client has already been told that
2056 * this message is format 1 (fixed format), so the callback function
2057 * we use will display those parts as-is.
2059 if (TheMessage
->cm_format_type
== FMT_RFC822
) {
2060 memset(&ma
, 0, sizeof(struct ma_info
));
2062 if (mode
== MT_MIME
) {
2063 ma
.use_fo_hooks
= 0;
2064 strcpy(ma
.chosen_part
, "1");
2065 ma
.chosen_pref
= 9999;
2066 mime_parser(mptr
, NULL
,
2067 *choose_preferred
, *fixed_output_pre
,
2068 *fixed_output_post
, (void *)&ma
, 0);
2069 mime_parser(mptr
, NULL
,
2070 *output_preferred
, NULL
, NULL
, (void *)&ma
, CC
->msg4_dont_decode
);
2073 ma
.use_fo_hooks
= 1;
2074 mime_parser(mptr
, NULL
,
2075 *fixed_output
, *fixed_output_pre
,
2076 *fixed_output_post
, (void *)&ma
, 0);
2081 DONE
: /* now we're done */
2082 if (do_proto
) cprintf("000\n");
2089 * display a message (mode 0 - Citadel proprietary)
2091 void cmd_msg0(char *cmdbuf
)
2094 int headers_only
= HEADERS_ALL
;
2096 msgid
= extract_long(cmdbuf
, 0);
2097 headers_only
= extract_int(cmdbuf
, 1);
2099 CtdlOutputMsg(msgid
, MT_CITADEL
, headers_only
, 1, 0, NULL
, 0);
2105 * display a message (mode 2 - RFC822)
2107 void cmd_msg2(char *cmdbuf
)
2110 int headers_only
= HEADERS_ALL
;
2112 msgid
= extract_long(cmdbuf
, 0);
2113 headers_only
= extract_int(cmdbuf
, 1);
2115 CtdlOutputMsg(msgid
, MT_RFC822
, headers_only
, 1, 1, NULL
, 0);
2121 * display a message (mode 3 - IGnet raw format - internal programs only)
2123 void cmd_msg3(char *cmdbuf
)
2126 struct CtdlMessage
*msg
= NULL
;
2129 if (CC
->internal_pgm
== 0) {
2130 cprintf("%d This command is for internal programs only.\n",
2131 ERROR
+ HIGHER_ACCESS_REQUIRED
);
2135 msgnum
= extract_long(cmdbuf
, 0);
2136 msg
= CtdlFetchMessage(msgnum
, 1);
2138 cprintf("%d Message %ld not found.\n",
2139 ERROR
+ MESSAGE_NOT_FOUND
, msgnum
);
2143 serialize_message(&smr
, msg
);
2144 CtdlFreeMessage(msg
);
2147 cprintf("%d Unable to serialize message\n",
2148 ERROR
+ INTERNAL_ERROR
);
2152 cprintf("%d %ld\n", BINARY_FOLLOWS
, (long)smr
.len
);
2153 client_write((char *)smr
.ser
, (int)smr
.len
);
2160 * Display a message using MIME content types
2162 void cmd_msg4(char *cmdbuf
)
2167 msgid
= extract_long(cmdbuf
, 0);
2168 extract_token(section
, cmdbuf
, 1, '|', sizeof section
);
2169 CtdlOutputMsg(msgid
, MT_MIME
, 0, 1, 0, (section
[0] ? section
: NULL
) , 0);
2175 * Client tells us its preferred message format(s)
2177 void cmd_msgp(char *cmdbuf
)
2179 if (!strcasecmp(cmdbuf
, "dont_decode")) {
2180 CC
->msg4_dont_decode
= 1;
2181 cprintf("%d MSG4 will not pre-decode messages.\n", CIT_OK
);
2184 safestrncpy(CC
->preferred_formats
, cmdbuf
, sizeof(CC
->preferred_formats
));
2185 cprintf("%d Preferred MIME formats have been set.\n", CIT_OK
);
2191 * Open a component of a MIME message as a download file
2193 void cmd_opna(char *cmdbuf
)
2196 char desired_section
[128];
2198 msgid
= extract_long(cmdbuf
, 0);
2199 extract_token(desired_section
, cmdbuf
, 1, '|', sizeof desired_section
);
2200 safestrncpy(CC
->download_desired_section
, desired_section
,
2201 sizeof CC
->download_desired_section
);
2202 CtdlOutputMsg(msgid
, MT_DOWNLOAD
, 0, 1, 1, NULL
, 0);
2207 * Open a component of a MIME message and transmit it all at once
2209 void cmd_dlat(char *cmdbuf
)
2212 char desired_section
[128];
2214 msgid
= extract_long(cmdbuf
, 0);
2215 extract_token(desired_section
, cmdbuf
, 1, '|', sizeof desired_section
);
2216 safestrncpy(CC
->download_desired_section
, desired_section
,
2217 sizeof CC
->download_desired_section
);
2218 CtdlOutputMsg(msgid
, MT_SPEW_SECTION
, 0, 1, 1, NULL
, 0);
2223 * Save one or more message pointers into a specified room
2224 * (Returns 0 for success, nonzero for failure)
2225 * roomname may be NULL to use the current room
2227 * Note that the 'supplied_msg' field may be set to NULL, in which case
2228 * the message will be fetched from disk, by number, if we need to perform
2229 * replication checks. This adds an additional database read, so if the
2230 * caller already has the message in memory then it should be supplied. (Obviously
2231 * this mode of operation only works if we're saving a single message.)
2233 int CtdlSaveMsgPointersInRoom(char *roomname
, long newmsgidlist
[], int num_newmsgs
,
2234 int do_repl_check
, struct CtdlMessage
*supplied_msg
)
2237 char hold_rm
[ROOMNAMELEN
];
2238 struct cdbdata
*cdbfr
;
2241 long highest_msg
= 0L;
2244 struct CtdlMessage
*msg
= NULL
;
2246 long *msgs_to_be_merged
= NULL
;
2247 int num_msgs_to_be_merged
= 0;
2249 CtdlLogPrintf(CTDL_DEBUG
,
2250 "CtdlSaveMsgPointersInRoom(room=%s, num_msgs=%d, repl=%d)\n",
2251 roomname
, num_newmsgs
, do_repl_check
);
2253 strcpy(hold_rm
, CC
->room
.QRname
);
2256 if (newmsgidlist
== NULL
) return(ERROR
+ INTERNAL_ERROR
);
2257 if (num_newmsgs
< 1) return(ERROR
+ INTERNAL_ERROR
);
2258 if (num_newmsgs
> 1) supplied_msg
= NULL
;
2260 /* Now the regular stuff */
2261 if (lgetroom(&CC
->room
,
2262 ((roomname
!= NULL
) ? roomname
: CC
->room
.QRname
) )
2264 CtdlLogPrintf(CTDL_ERR
, "No such room <%s>\n", roomname
);
2265 return(ERROR
+ ROOM_NOT_FOUND
);
2269 msgs_to_be_merged
= malloc(sizeof(long) * num_newmsgs
);
2270 num_msgs_to_be_merged
= 0;
2273 cdbfr
= cdb_fetch(CDB_MSGLISTS
, &CC
->room
.QRnumber
, sizeof(long));
2274 if (cdbfr
== NULL
) {
2278 msglist
= (long *) cdbfr
->ptr
;
2279 cdbfr
->ptr
= NULL
; /* CtdlSaveMsgPointerInRoom() now owns this memory */
2280 num_msgs
= cdbfr
->len
/ sizeof(long);
2285 /* Create a list of msgid's which were supplied by the caller, but do
2286 * not already exist in the target room. It is absolutely taboo to
2287 * have more than one reference to the same message in a room.
2289 for (i
=0; i
<num_newmsgs
; ++i
) {
2291 if (num_msgs
> 0) for (j
=0; j
<num_msgs
; ++j
) {
2292 if (msglist
[j
] == newmsgidlist
[i
]) {
2297 msgs_to_be_merged
[num_msgs_to_be_merged
++] = newmsgidlist
[i
];
2301 CtdlLogPrintf(9, "%d unique messages to be merged\n", num_msgs_to_be_merged
);
2304 * Now merge the new messages
2306 msglist
= realloc(msglist
, (sizeof(long) * (num_msgs
+ num_msgs_to_be_merged
)) );
2307 if (msglist
== NULL
) {
2308 CtdlLogPrintf(CTDL_ALERT
, "ERROR: can't realloc message list!\n");
2310 memcpy(&msglist
[num_msgs
], msgs_to_be_merged
, (sizeof(long) * num_msgs_to_be_merged
) );
2311 num_msgs
+= num_msgs_to_be_merged
;
2313 /* Sort the message list, so all the msgid's are in order */
2314 num_msgs
= sort_msglist(msglist
, num_msgs
);
2316 /* Determine the highest message number */
2317 highest_msg
= msglist
[num_msgs
- 1];
2319 /* Write it back to disk. */
2320 cdb_store(CDB_MSGLISTS
, &CC
->room
.QRnumber
, (int)sizeof(long),
2321 msglist
, (int)(num_msgs
* sizeof(long)));
2323 /* Free up the memory we used. */
2326 /* Update the highest-message pointer and unlock the room. */
2327 CC
->room
.QRhighest
= highest_msg
;
2328 lputroom(&CC
->room
);
2330 /* Perform replication checks if necessary */
2331 if ( (DoesThisRoomNeedEuidIndexing(&CC
->room
)) && (do_repl_check
) ) {
2332 CtdlLogPrintf(CTDL_DEBUG
, "CtdlSaveMsgPointerInRoom() doing repl checks\n");
2334 for (i
=0; i
<num_msgs_to_be_merged
; ++i
) {
2335 msgid
= msgs_to_be_merged
[i
];
2337 if (supplied_msg
!= NULL
) {
2341 msg
= CtdlFetchMessage(msgid
, 0);
2345 ReplicationChecks(msg
);
2347 /* If the message has an Exclusive ID, index that... */
2348 if (msg
->cm_fields
['E'] != NULL
) {
2349 index_message_by_euid(msg
->cm_fields
['E'], &CC
->room
, msgid
);
2352 /* Free up the memory we may have allocated */
2353 if (msg
!= supplied_msg
) {
2354 CtdlFreeMessage(msg
);
2362 CtdlLogPrintf(CTDL_DEBUG
, "CtdlSaveMsgPointerInRoom() skips repl checks\n");
2365 /* Submit this room for processing by hooks */
2366 PerformRoomHooks(&CC
->room
);
2368 /* Go back to the room we were in before we wandered here... */
2369 getroom(&CC
->room
, hold_rm
);
2371 /* Bump the reference count for all messages which were merged */
2372 for (i
=0; i
<num_msgs_to_be_merged
; ++i
) {
2373 AdjRefCount(msgs_to_be_merged
[i
], +1);
2376 /* Free up memory... */
2377 if (msgs_to_be_merged
!= NULL
) {
2378 free(msgs_to_be_merged
);
2381 /* Return success. */
2387 * This is the same as CtdlSaveMsgPointersInRoom() but it only accepts
2390 int CtdlSaveMsgPointerInRoom(char *roomname
, long msgid
,
2391 int do_repl_check
, struct CtdlMessage
*supplied_msg
)
2393 return CtdlSaveMsgPointersInRoom(roomname
, &msgid
, 1, do_repl_check
, supplied_msg
);
2400 * Message base operation to save a new message to the message store
2401 * (returns new message number)
2403 * This is the back end for CtdlSubmitMsg() and should not be directly
2404 * called by server-side modules.
2407 long send_message(struct CtdlMessage
*msg
) {
2415 /* Get a new message number */
2416 newmsgid
= get_new_message_number();
2417 snprintf(msgidbuf
, sizeof msgidbuf
, "%010ld@%s", newmsgid
, config
.c_fqdn
);
2419 /* Generate an ID if we don't have one already */
2420 if (msg
->cm_fields
['I']==NULL
) {
2421 msg
->cm_fields
['I'] = strdup(msgidbuf
);
2424 /* If the message is big, set its body aside for storage elsewhere */
2425 if (msg
->cm_fields
['M'] != NULL
) {
2426 if (strlen(msg
->cm_fields
['M']) > BIGMSG
) {
2428 holdM
= msg
->cm_fields
['M'];
2429 msg
->cm_fields
['M'] = NULL
;
2433 /* Serialize our data structure for storage in the database */
2434 serialize_message(&smr
, msg
);
2437 msg
->cm_fields
['M'] = holdM
;
2441 cprintf("%d Unable to serialize message\n",
2442 ERROR
+ INTERNAL_ERROR
);
2446 /* Write our little bundle of joy into the message base */
2447 if (cdb_store(CDB_MSGMAIN
, &newmsgid
, (int)sizeof(long),
2448 smr
.ser
, smr
.len
) < 0) {
2449 CtdlLogPrintf(CTDL_ERR
, "Can't store message\n");
2453 cdb_store(CDB_BIGMSGS
,
2463 /* Free the memory we used for the serialized message */
2466 /* Return the *local* message ID to the caller
2467 * (even if we're storing an incoming network message)
2475 * Serialize a struct CtdlMessage into the format used on disk and network.
2477 * This function loads up a "struct ser_ret" (defined in server.h) which
2478 * contains the length of the serialized message and a pointer to the
2479 * serialized message in memory. THE LATTER MUST BE FREED BY THE CALLER.
2481 void serialize_message(struct ser_ret
*ret
, /* return values */
2482 struct CtdlMessage
*msg
) /* unserialized msg */
2484 size_t wlen
, fieldlen
;
2486 static char *forder
= FORDER
;
2489 * Check for valid message format
2491 if (is_valid_message(msg
) == 0) {
2492 CtdlLogPrintf(CTDL_ERR
, "serialize_message() aborting due to invalid message\n");
2499 for (i
=0; i
<26; ++i
) if (msg
->cm_fields
[(int)forder
[i
]] != NULL
)
2500 ret
->len
= ret
->len
+
2501 strlen(msg
->cm_fields
[(int)forder
[i
]]) + 2;
2503 ret
->ser
= malloc(ret
->len
);
2504 if (ret
->ser
== NULL
) {
2505 CtdlLogPrintf(CTDL_ERR
, "serialize_message() malloc(%ld) failed: %s\n",
2506 (long)ret
->len
, strerror(errno
));
2513 ret
->ser
[1] = msg
->cm_anon_type
;
2514 ret
->ser
[2] = msg
->cm_format_type
;
2517 for (i
=0; i
<26; ++i
) if (msg
->cm_fields
[(int)forder
[i
]] != NULL
) {
2518 fieldlen
= strlen(msg
->cm_fields
[(int)forder
[i
]]);
2519 ret
->ser
[wlen
++] = (char)forder
[i
];
2520 safestrncpy((char *)&ret
->ser
[wlen
], msg
->cm_fields
[(int)forder
[i
]], fieldlen
+1);
2521 wlen
= wlen
+ fieldlen
+ 1;
2523 if (ret
->len
!= wlen
) CtdlLogPrintf(CTDL_ERR
, "ERROR: len=%ld wlen=%ld\n",
2524 (long)ret
->len
, (long)wlen
);
2531 * Serialize a struct CtdlMessage into the format used on disk and network.
2533 * This function loads up a "struct ser_ret" (defined in server.h) which
2534 * contains the length of the serialized message and a pointer to the
2535 * serialized message in memory. THE LATTER MUST BE FREED BY THE CALLER.
2537 void dump_message(struct CtdlMessage
*msg
, /* unserialized msg */
2538 long Siz
) /* how many chars ? */
2542 static char *forder
= FORDER
;
2546 * Check for valid message format
2548 if (is_valid_message(msg
) == 0) {
2549 CtdlLogPrintf(CTDL_ERR
, "dump_message() aborting due to invalid message\n");
2553 buf
= (char*) malloc (Siz
+ 1);
2557 for (i
=0; i
<26; ++i
) if (msg
->cm_fields
[(int)forder
[i
]] != NULL
) {
2558 snprintf (buf
, Siz
, " msg[%c] = %s ...\n", (char) forder
[i
],
2559 msg
->cm_fields
[(int)forder
[i
]]);
2560 client_write (buf
, strlen(buf
));
2569 * Check to see if any messages already exist in the current room which
2570 * carry the same Exclusive ID as this one. If any are found, delete them.
2572 void ReplicationChecks(struct CtdlMessage
*msg
) {
2573 long old_msgnum
= (-1L);
2575 if (DoesThisRoomNeedEuidIndexing(&CC
->room
) == 0) return;
2577 CtdlLogPrintf(CTDL_DEBUG
, "Performing replication checks in <%s>\n",
2580 /* No exclusive id? Don't do anything. */
2581 if (msg
== NULL
) return;
2582 if (msg
->cm_fields
['E'] == NULL
) return;
2583 if (IsEmptyStr(msg
->cm_fields
['E'])) return;
2584 /*CtdlLogPrintf(CTDL_DEBUG, "Exclusive ID: <%s> for room <%s>\n",
2585 msg->cm_fields['E'], CC->room.QRname);*/
2587 old_msgnum
= locate_message_by_euid(msg
->cm_fields
['E'], &CC
->room
);
2588 if (old_msgnum
> 0L) {
2589 CtdlLogPrintf(CTDL_DEBUG
, "ReplicationChecks() replacing message %ld\n", old_msgnum
);
2590 CtdlDeleteMessages(CC
->room
.QRname
, &old_msgnum
, 1, "");
2597 * Save a message to disk and submit it into the delivery system.
2599 long CtdlSubmitMsg(struct CtdlMessage
*msg
, /* message to save */
2600 struct recptypes
*recps
, /* recipients (if mail) */
2601 char *force
, /* force a particular room? */
2602 int flags
/* should the bessage be exported clean? */
2604 char submit_filename
[128];
2605 char generated_timestamp
[32];
2606 char hold_rm
[ROOMNAMELEN
];
2607 char actual_rm
[ROOMNAMELEN
];
2608 char force_room
[ROOMNAMELEN
];
2609 char content_type
[SIZ
]; /* We have to learn this */
2610 char recipient
[SIZ
];
2613 struct ctdluser userbuf
;
2615 struct MetaData smi
;
2616 FILE *network_fp
= NULL
;
2617 static int seqnum
= 1;
2618 struct CtdlMessage
*imsg
= NULL
;
2620 size_t instr_alloc
= 0;
2622 char *hold_R
, *hold_D
;
2623 char *collected_addresses
= NULL
;
2624 struct addresses_to_be_filed
*aptr
= NULL
;
2625 char *saved_rfc822_version
= NULL
;
2626 int qualified_for_journaling
= 0;
2627 struct CitContext
*CCC
= CC
; /* CachedCitContext - performance boost */
2628 char bounce_to
[1024] = "";
2630 CtdlLogPrintf(CTDL_DEBUG
, "CtdlSubmitMsg() called\n");
2631 if (is_valid_message(msg
) == 0) return(-1); /* self check */
2633 /* If this message has no timestamp, we take the liberty of
2634 * giving it one, right now.
2636 if (msg
->cm_fields
['T'] == NULL
) {
2637 snprintf(generated_timestamp
, sizeof generated_timestamp
, "%ld", (long)time(NULL
));
2638 msg
->cm_fields
['T'] = strdup(generated_timestamp
);
2641 /* If this message has no path, we generate one.
2643 if (msg
->cm_fields
['P'] == NULL
) {
2644 if (msg
->cm_fields
['A'] != NULL
) {
2645 msg
->cm_fields
['P'] = strdup(msg
->cm_fields
['A']);
2646 for (a
=0; !IsEmptyStr(&msg
->cm_fields
['P'][a
]); ++a
) {
2647 if (isspace(msg
->cm_fields
['P'][a
])) {
2648 msg
->cm_fields
['P'][a
] = ' ';
2653 msg
->cm_fields
['P'] = strdup("unknown");
2657 if (force
== NULL
) {
2658 strcpy(force_room
, "");
2661 strcpy(force_room
, force
);
2664 /* Learn about what's inside, because it's what's inside that counts */
2665 if (msg
->cm_fields
['M'] == NULL
) {
2666 CtdlLogPrintf(CTDL_ERR
, "ERROR: attempt to save message with NULL body\n");
2670 switch (msg
->cm_format_type
) {
2672 strcpy(content_type
, "text/x-citadel-variformat");
2675 strcpy(content_type
, "text/plain");
2678 strcpy(content_type
, "text/plain");
2679 mptr
= bmstrcasestr(msg
->cm_fields
['M'], "Content-type:");
2682 safestrncpy(content_type
, &mptr
[13], sizeof content_type
);
2683 striplt(content_type
);
2684 aptr
= content_type
;
2685 while (!IsEmptyStr(aptr
)) {
2697 /* Goto the correct room */
2698 CtdlLogPrintf(CTDL_DEBUG
, "Selected room %s\n", (recps
) ? CCC
->room
.QRname
: SENTITEMS
);
2699 strcpy(hold_rm
, CCC
->room
.QRname
);
2700 strcpy(actual_rm
, CCC
->room
.QRname
);
2701 if (recps
!= NULL
) {
2702 strcpy(actual_rm
, SENTITEMS
);
2705 /* If the user is a twit, move to the twit room for posting */
2707 if (CCC
->user
.axlevel
== 2) {
2708 strcpy(hold_rm
, actual_rm
);
2709 strcpy(actual_rm
, config
.c_twitroom
);
2710 CtdlLogPrintf(CTDL_DEBUG
, "Diverting to twit room\n");
2714 /* ...or if this message is destined for Aide> then go there. */
2715 if (!IsEmptyStr(force_room
)) {
2716 strcpy(actual_rm
, force_room
);
2719 CtdlLogPrintf(CTDL_DEBUG
, "Final selection: %s\n", actual_rm
);
2720 if (strcasecmp(actual_rm
, CCC
->room
.QRname
)) {
2721 /* getroom(&CCC->room, actual_rm); */
2722 usergoto(actual_rm
, 0, 1, NULL
, NULL
);
2726 * If this message has no O (room) field, generate one.
2728 if (msg
->cm_fields
['O'] == NULL
) {
2729 msg
->cm_fields
['O'] = strdup(CCC
->room
.QRname
);
2732 /* Perform "before save" hooks (aborting if any return nonzero) */
2733 CtdlLogPrintf(CTDL_DEBUG
, "Performing before-save hooks\n");
2734 if (PerformMessageHooks(msg
, EVT_BEFORESAVE
) > 0) return(-3);
2737 * If this message has an Exclusive ID, and the room is replication
2738 * checking enabled, then do replication checks.
2740 if (DoesThisRoomNeedEuidIndexing(&CCC
->room
)) {
2741 ReplicationChecks(msg
);
2744 /* Save it to disk */
2745 CtdlLogPrintf(CTDL_DEBUG
, "Saving to disk\n");
2746 newmsgid
= send_message(msg
);
2747 if (newmsgid
<= 0L) return(-5);
2749 /* Write a supplemental message info record. This doesn't have to
2750 * be a critical section because nobody else knows about this message
2753 CtdlLogPrintf(CTDL_DEBUG
, "Creating MetaData record\n");
2754 memset(&smi
, 0, sizeof(struct MetaData
));
2755 smi
.meta_msgnum
= newmsgid
;
2756 smi
.meta_refcount
= 0;
2757 safestrncpy(smi
.meta_content_type
, content_type
,
2758 sizeof smi
.meta_content_type
);
2761 * Measure how big this message will be when rendered as RFC822.
2762 * We do this for two reasons:
2763 * 1. We need the RFC822 length for the new metadata record, so the
2764 * POP and IMAP services don't have to calculate message lengths
2765 * while the user is waiting (multiplied by potentially hundreds
2766 * or thousands of messages).
2767 * 2. If journaling is enabled, we will need an RFC822 version of the
2768 * message to attach to the journalized copy.
2770 if (CCC
->redirect_buffer
!= NULL
) {
2771 CtdlLogPrintf(CTDL_ALERT
, "CCC->redirect_buffer is not NULL during message submission!\n");
2774 CCC
->redirect_buffer
= malloc(SIZ
);
2775 CCC
->redirect_len
= 0;
2776 CCC
->redirect_alloc
= SIZ
;
2777 CtdlOutputPreLoadedMsg(msg
, MT_RFC822
, HEADERS_ALL
, 0, 1, QP_EADDR
);
2778 smi
.meta_rfc822_length
= CCC
->redirect_len
;
2779 saved_rfc822_version
= CCC
->redirect_buffer
;
2780 CCC
->redirect_buffer
= NULL
;
2781 CCC
->redirect_len
= 0;
2782 CCC
->redirect_alloc
= 0;
2786 /* Now figure out where to store the pointers */
2787 CtdlLogPrintf(CTDL_DEBUG
, "Storing pointers\n");
2789 /* If this is being done by the networker delivering a private
2790 * message, we want to BYPASS saving the sender's copy (because there
2791 * is no local sender; it would otherwise go to the Trashcan).
2793 if ((!CCC
->internal_pgm
) || (recps
== NULL
)) {
2794 if (CtdlSaveMsgPointerInRoom(actual_rm
, newmsgid
, 1, msg
) != 0) {
2795 CtdlLogPrintf(CTDL_ERR
, "ERROR saving message pointer!\n");
2796 CtdlSaveMsgPointerInRoom(config
.c_aideroom
, newmsgid
, 0, msg
);
2800 /* For internet mail, drop a copy in the outbound queue room */
2802 if (recps
->num_internet
> 0) {
2803 CtdlSaveMsgPointerInRoom(SMTP_SPOOLOUT_ROOM
, newmsgid
, 0, msg
);
2806 /* If other rooms are specified, drop them there too. */
2808 if (recps
->num_room
> 0)
2809 for (i
=0; i
<num_tokens(recps
->recp_room
, '|'); ++i
) {
2810 extract_token(recipient
, recps
->recp_room
, i
,
2811 '|', sizeof recipient
);
2812 CtdlLogPrintf(CTDL_DEBUG
, "Delivering to room <%s>\n", recipient
);
2813 CtdlSaveMsgPointerInRoom(recipient
, newmsgid
, 0, msg
);
2816 /* Bump this user's messages posted counter. */
2817 CtdlLogPrintf(CTDL_DEBUG
, "Updating user\n");
2818 lgetuser(&CCC
->user
, CCC
->curr_user
);
2819 CCC
->user
.posted
= CCC
->user
.posted
+ 1;
2820 lputuser(&CCC
->user
);
2822 /* Decide where bounces need to be delivered */
2823 if (CCC
->logged_in
) {
2824 snprintf(bounce_to
, sizeof bounce_to
, "%s@%s", CCC
->user
.fullname
, config
.c_nodename
);
2827 snprintf(bounce_to
, sizeof bounce_to
, "%s@%s", msg
->cm_fields
['A'], msg
->cm_fields
['N']);
2830 /* If this is private, local mail, make a copy in the
2831 * recipient's mailbox and bump the reference count.
2834 if (recps
->num_local
> 0)
2835 for (i
=0; i
<num_tokens(recps
->recp_local
, '|'); ++i
) {
2836 extract_token(recipient
, recps
->recp_local
, i
,
2837 '|', sizeof recipient
);
2838 CtdlLogPrintf(CTDL_DEBUG
, "Delivering private local mail to <%s>\n",
2840 if (getuser(&userbuf
, recipient
) == 0) {
2841 // Add a flag so the Funambol module knows its mail
2842 msg
->cm_fields
['W'] = strdup(recipient
);
2843 MailboxName(actual_rm
, sizeof actual_rm
, &userbuf
, MAILROOM
);
2844 CtdlSaveMsgPointerInRoom(actual_rm
, newmsgid
, 0, msg
);
2845 BumpNewMailCounter(userbuf
.usernum
);
2846 if (!IsEmptyStr(config
.c_funambol_host
) || !IsEmptyStr(config
.c_pager_program
)) {
2847 /* Generate a instruction message for the Funambol notification
2848 * server, in the same style as the SMTP queue
2851 instr
= malloc(instr_alloc
);
2852 snprintf(instr
, instr_alloc
,
2853 "Content-type: %s\n\nmsgid|%ld\nsubmitted|%ld\n"
2855 SPOOLMIME
, newmsgid
, (long)time(NULL
),
2859 imsg
= malloc(sizeof(struct CtdlMessage
));
2860 memset(imsg
, 0, sizeof(struct CtdlMessage
));
2861 imsg
->cm_magic
= CTDLMESSAGE_MAGIC
;
2862 imsg
->cm_anon_type
= MES_NORMAL
;
2863 imsg
->cm_format_type
= FMT_RFC822
;
2864 imsg
->cm_fields
['A'] = strdup("Citadel");
2865 imsg
->cm_fields
['J'] = strdup("do not journal");
2866 imsg
->cm_fields
['M'] = instr
; /* imsg owns this memory now */
2867 imsg
->cm_fields
['W'] = strdup(recipient
);
2868 CtdlSubmitMsg(imsg
, NULL
, FNBL_QUEUE_ROOM
, 0);
2869 CtdlFreeMessage(imsg
);
2873 CtdlLogPrintf(CTDL_DEBUG
, "No user <%s>\n", recipient
);
2874 CtdlSaveMsgPointerInRoom(config
.c_aideroom
,
2879 /* Perform "after save" hooks */
2880 CtdlLogPrintf(CTDL_DEBUG
, "Performing after-save hooks\n");
2881 PerformMessageHooks(msg
, EVT_AFTERSAVE
);
2883 /* For IGnet mail, we have to save a new copy into the spooler for
2884 * each recipient, with the R and D fields set to the recipient and
2885 * destination-node. This has two ugly side effects: all other
2886 * recipients end up being unlisted in this recipient's copy of the
2887 * message, and it has to deliver multiple messages to the same
2888 * node. We'll revisit this again in a year or so when everyone has
2889 * a network spool receiver that can handle the new style messages.
2892 if (recps
->num_ignet
> 0)
2893 for (i
=0; i
<num_tokens(recps
->recp_ignet
, '|'); ++i
) {
2894 extract_token(recipient
, recps
->recp_ignet
, i
,
2895 '|', sizeof recipient
);
2897 hold_R
= msg
->cm_fields
['R'];
2898 hold_D
= msg
->cm_fields
['D'];
2899 msg
->cm_fields
['R'] = malloc(SIZ
);
2900 msg
->cm_fields
['D'] = malloc(128);
2901 extract_token(msg
->cm_fields
['R'], recipient
, 0, '@', SIZ
);
2902 extract_token(msg
->cm_fields
['D'], recipient
, 1, '@', 128);
2904 serialize_message(&smr
, msg
);
2906 snprintf(submit_filename
, sizeof submit_filename
,
2907 "%s/netmail.%04lx.%04x.%04x",
2909 (long) getpid(), CCC
->cs_pid
, ++seqnum
);
2910 network_fp
= fopen(submit_filename
, "wb+");
2911 if (network_fp
!= NULL
) {
2912 fwrite(smr
.ser
, smr
.len
, 1, network_fp
);
2918 free(msg
->cm_fields
['R']);
2919 free(msg
->cm_fields
['D']);
2920 msg
->cm_fields
['R'] = hold_R
;
2921 msg
->cm_fields
['D'] = hold_D
;
2924 /* Go back to the room we started from */
2925 CtdlLogPrintf(CTDL_DEBUG
, "Returning to original room %s\n", hold_rm
);
2926 if (strcasecmp(hold_rm
, CCC
->room
.QRname
))
2927 usergoto(hold_rm
, 0, 1, NULL
, NULL
);
2929 /* For internet mail, generate delivery instructions.
2930 * Yes, this is recursive. Deal with it. Infinite recursion does
2931 * not happen because the delivery instructions message does not
2932 * contain a recipient.
2935 if (recps
->num_internet
> 0) {
2936 CtdlLogPrintf(CTDL_DEBUG
, "Generating delivery instructions\n");
2938 instr
= malloc(instr_alloc
);
2939 snprintf(instr
, instr_alloc
,
2940 "Content-type: %s\n\nmsgid|%ld\nsubmitted|%ld\n"
2942 SPOOLMIME
, newmsgid
, (long)time(NULL
),
2946 for (i
=0; i
<num_tokens(recps
->recp_internet
, '|'); ++i
) {
2947 size_t tmp
= strlen(instr
);
2948 extract_token(recipient
, recps
->recp_internet
, i
, '|', sizeof recipient
);
2949 if ((tmp
+ strlen(recipient
) + 32) > instr_alloc
) {
2950 instr_alloc
= instr_alloc
* 2;
2951 instr
= realloc(instr
, instr_alloc
);
2953 snprintf(&instr
[tmp
], instr_alloc
- tmp
, "remote|%s|0||\n", recipient
);
2956 imsg
= malloc(sizeof(struct CtdlMessage
));
2957 memset(imsg
, 0, sizeof(struct CtdlMessage
));
2958 imsg
->cm_magic
= CTDLMESSAGE_MAGIC
;
2959 imsg
->cm_anon_type
= MES_NORMAL
;
2960 imsg
->cm_format_type
= FMT_RFC822
;
2961 imsg
->cm_fields
['A'] = strdup("Citadel");
2962 imsg
->cm_fields
['J'] = strdup("do not journal");
2963 imsg
->cm_fields
['M'] = instr
; /* imsg owns this memory now */
2964 CtdlSubmitMsg(imsg
, NULL
, SMTP_SPOOLOUT_ROOM
, QP_EADDR
);
2965 CtdlFreeMessage(imsg
);
2969 * Any addresses to harvest for someone's address book?
2971 if ( (CCC
->logged_in
) && (recps
!= NULL
) ) {
2972 collected_addresses
= harvest_collected_addresses(msg
);
2975 if (collected_addresses
!= NULL
) {
2976 aptr
= (struct addresses_to_be_filed
*)
2977 malloc(sizeof(struct addresses_to_be_filed
));
2978 MailboxName(actual_rm
, sizeof actual_rm
,
2979 &CCC
->user
, USERCONTACTSROOM
);
2980 aptr
->roomname
= strdup(actual_rm
);
2981 aptr
->collected_addresses
= collected_addresses
;
2982 begin_critical_section(S_ATBF
);
2985 end_critical_section(S_ATBF
);
2989 * Determine whether this message qualifies for journaling.
2991 if (msg
->cm_fields
['J'] != NULL
) {
2992 qualified_for_journaling
= 0;
2995 if (recps
== NULL
) {
2996 qualified_for_journaling
= config
.c_journal_pubmsgs
;
2998 else if (recps
->num_local
+ recps
->num_ignet
+ recps
->num_internet
> 0) {
2999 qualified_for_journaling
= config
.c_journal_email
;
3002 qualified_for_journaling
= config
.c_journal_pubmsgs
;
3007 * Do we have to perform journaling? If so, hand off the saved
3008 * RFC822 version will be handed off to the journaler for background
3009 * submit. Otherwise, we have to free the memory ourselves.
3011 if (saved_rfc822_version
!= NULL
) {
3012 if (qualified_for_journaling
) {
3013 JournalBackgroundSubmit(msg
, saved_rfc822_version
, recps
);
3016 free(saved_rfc822_version
);
3029 * Convenience function for generating small administrative messages.
3031 void quickie_message(char *from
, char *fromaddr
, char *to
, char *room
, char *text
,
3032 int format_type
, char *subject
)
3034 struct CtdlMessage
*msg
;
3035 struct recptypes
*recp
= NULL
;
3037 msg
= malloc(sizeof(struct CtdlMessage
));
3038 memset(msg
, 0, sizeof(struct CtdlMessage
));
3039 msg
->cm_magic
= CTDLMESSAGE_MAGIC
;
3040 msg
->cm_anon_type
= MES_NORMAL
;
3041 msg
->cm_format_type
= format_type
;
3044 msg
->cm_fields
['A'] = strdup(from
);
3046 else if (fromaddr
!= NULL
) {
3047 msg
->cm_fields
['A'] = strdup(fromaddr
);
3048 if (strchr(msg
->cm_fields
['A'], '@')) {
3049 *strchr(msg
->cm_fields
['A'], '@') = 0;
3053 msg
->cm_fields
['A'] = strdup("Citadel");
3056 if (fromaddr
!= NULL
) msg
->cm_fields
['F'] = strdup(fromaddr
);
3057 if (room
!= NULL
) msg
->cm_fields
['O'] = strdup(room
);
3058 msg
->cm_fields
['N'] = strdup(NODENAME
);
3060 msg
->cm_fields
['R'] = strdup(to
);
3061 recp
= validate_recipients(to
, NULL
, 0);
3063 if (subject
!= NULL
) {
3064 msg
->cm_fields
['U'] = strdup(subject
);
3066 msg
->cm_fields
['M'] = strdup(text
);
3068 CtdlSubmitMsg(msg
, recp
, room
, 0);
3069 CtdlFreeMessage(msg
);
3070 if (recp
!= NULL
) free_recipients(recp
);
3076 * Back end function used by CtdlMakeMessage() and similar functions
3078 char *CtdlReadMessageBody(char *terminator
, /* token signalling EOT */
3079 size_t maxlen
, /* maximum message length */
3080 char *exist
, /* if non-null, append to it;
3081 exist is ALWAYS freed */
3082 int crlf
, /* CRLF newlines instead of LF */
3083 int sock
/* socket handle or 0 for this session's client socket */
3087 size_t message_len
= 0;
3088 size_t buffer_len
= 0;
3095 if (exist
== NULL
) {
3102 message_len
= strlen(exist
);
3103 buffer_len
= message_len
+ 4096;
3104 m
= realloc(exist
, buffer_len
);
3111 /* Do we need to change leading ".." to "." for SMTP escaping? */
3112 if (!strcmp(terminator
, ".")) {
3116 /* flush the input if we have nowhere to store it */
3121 /* read in the lines of message text one by one */
3124 if (sock_getln(sock
, buf
, (sizeof buf
- 3)) < 0) finished
= 1;
3127 if (client_getln(buf
, (sizeof buf
- 3)) < 1) finished
= 1;
3129 if (!strcmp(buf
, terminator
)) finished
= 1;
3131 strcat(buf
, "\r\n");
3137 /* Unescape SMTP-style input of two dots at the beginning of the line */
3139 if (!strncmp(buf
, "..", 2)) {
3140 strcpy(buf
, &buf
[1]);
3144 if ( (!flushing
) && (!finished
) ) {
3145 /* Measure the line */
3146 linelen
= strlen(buf
);
3148 /* augment the buffer if we have to */
3149 if ((message_len
+ linelen
) >= buffer_len
) {
3150 ptr
= realloc(m
, (buffer_len
* 2) );
3151 if (ptr
== NULL
) { /* flush if can't allocate */
3154 buffer_len
= (buffer_len
* 2);
3156 CtdlLogPrintf(CTDL_DEBUG
, "buffer_len is now %ld\n", (long)buffer_len
);
3160 /* Add the new line to the buffer. NOTE: this loop must avoid
3161 * using functions like strcat() and strlen() because they
3162 * traverse the entire buffer upon every call, and doing that
3163 * for a multi-megabyte message slows it down beyond usability.
3165 strcpy(&m
[message_len
], buf
);
3166 message_len
+= linelen
;
3169 /* if we've hit the max msg length, flush the rest */
3170 if (message_len
>= maxlen
) flushing
= 1;
3172 } while (!finished
);
3180 * Build a binary message to be saved on disk.
3181 * (NOTE: if you supply 'preformatted_text', the buffer you give it
3182 * will become part of the message. This means you are no longer
3183 * responsible for managing that memory -- it will be freed along with
3184 * the rest of the fields when CtdlFreeMessage() is called.)
3187 struct CtdlMessage
*CtdlMakeMessage(
3188 struct ctdluser
*author
, /* author's user structure */
3189 char *recipient
, /* NULL if it's not mail */
3190 char *recp_cc
, /* NULL if it's not mail */
3191 char *room
, /* room where it's going */
3192 int type
, /* see MES_ types in header file */
3193 int format_type
, /* variformat, plain text, MIME... */
3194 char *fake_name
, /* who we're masquerading as */
3195 char *my_email
, /* which of my email addresses to use (empty is ok) */
3196 char *subject
, /* Subject (optional) */
3197 char *supplied_euid
, /* ...or NULL if this is irrelevant */
3198 char *preformatted_text
, /* ...or NULL to read text from client */
3199 char *references
/* Thread references */
3201 char dest_node
[256];
3203 struct CtdlMessage
*msg
;
3205 msg
= malloc(sizeof(struct CtdlMessage
));
3206 memset(msg
, 0, sizeof(struct CtdlMessage
));
3207 msg
->cm_magic
= CTDLMESSAGE_MAGIC
;
3208 msg
->cm_anon_type
= type
;
3209 msg
->cm_format_type
= format_type
;
3211 /* Don't confuse the poor folks if it's not routed mail. */
3212 strcpy(dest_node
, "");
3217 /* Path or Return-Path */
3218 if (my_email
== NULL
) my_email
= "";
3220 if (!IsEmptyStr(my_email
)) {
3221 msg
->cm_fields
['P'] = strdup(my_email
);
3224 snprintf(buf
, sizeof buf
, "%s", author
->fullname
);
3225 msg
->cm_fields
['P'] = strdup(buf
);
3227 convert_spaces_to_underscores(msg
->cm_fields
['P']);
3229 snprintf(buf
, sizeof buf
, "%ld", (long)time(NULL
)); /* timestamp */
3230 msg
->cm_fields
['T'] = strdup(buf
);
3232 if (fake_name
[0]) /* author */
3233 msg
->cm_fields
['A'] = strdup(fake_name
);
3235 msg
->cm_fields
['A'] = strdup(author
->fullname
);
3237 if (CC
->room
.QRflags
& QR_MAILBOX
) { /* room */
3238 msg
->cm_fields
['O'] = strdup(&CC
->room
.QRname
[11]);
3241 msg
->cm_fields
['O'] = strdup(CC
->room
.QRname
);
3244 msg
->cm_fields
['N'] = strdup(NODENAME
); /* nodename */
3245 msg
->cm_fields
['H'] = strdup(HUMANNODE
); /* hnodename */
3247 if (recipient
[0] != 0) {
3248 msg
->cm_fields
['R'] = strdup(recipient
);
3250 if (recp_cc
[0] != 0) {
3251 msg
->cm_fields
['Y'] = strdup(recp_cc
);
3253 if (dest_node
[0] != 0) {
3254 msg
->cm_fields
['D'] = strdup(dest_node
);
3257 if (!IsEmptyStr(my_email
)) {
3258 msg
->cm_fields
['F'] = strdup(my_email
);
3260 else if ( (author
== &CC
->user
) && (!IsEmptyStr(CC
->cs_inet_email
)) ) {
3261 msg
->cm_fields
['F'] = strdup(CC
->cs_inet_email
);
3264 if (subject
!= NULL
) {
3267 length
= strlen(subject
);
3273 while ((subject
[i
] != '\0') &&
3274 (IsAscii
= isascii(subject
[i
]) != 0 ))
3277 msg
->cm_fields
['U'] = strdup(subject
);
3278 else /* ok, we've got utf8 in the string. */
3280 msg
->cm_fields
['U'] = rfc2047encode(subject
, length
);
3286 if (supplied_euid
!= NULL
) {
3287 msg
->cm_fields
['E'] = strdup(supplied_euid
);
3290 if (references
!= NULL
) {
3291 if (!IsEmptyStr(references
)) {
3292 msg
->cm_fields
['W'] = strdup(references
);
3296 if (preformatted_text
!= NULL
) {
3297 msg
->cm_fields
['M'] = preformatted_text
;
3300 msg
->cm_fields
['M'] = CtdlReadMessageBody("000", config
.c_maxmsglen
, NULL
, 0, 0);
3308 * Check to see whether we have permission to post a message in the current
3309 * room. Returns a *CITADEL ERROR CODE* and puts a message in errmsgbuf, or
3310 * returns 0 on success.
3312 int CtdlDoIHavePermissionToPostInThisRoom(char *errmsgbuf
,
3314 const char* RemoteIdentifier
,
3318 if (!(CC
->logged_in
) &&
3319 (PostPublic
== POST_LOGGED_IN
)) {
3320 snprintf(errmsgbuf
, n
, "Not logged in.");
3321 return (ERROR
+ NOT_LOGGED_IN
);
3323 else if (PostPublic
== CHECK_EXISTANCE
) {
3324 return (0); // We're Evaling whether a recipient exists
3326 else if (!(CC
->logged_in
)) {
3328 if ((CC
->room
.QRflags
& QR_READONLY
)) {
3329 snprintf(errmsgbuf
, n
, "Not logged in.");
3330 return (ERROR
+ NOT_LOGGED_IN
);
3332 if (CC
->room
.QRflags2
& QR2_MODERATED
) {
3333 snprintf(errmsgbuf
, n
, "Not logged in Moderation feature not yet implemented!");
3334 return (ERROR
+ NOT_LOGGED_IN
);
3336 if ((PostPublic
!=POST_LMTP
) &&(CC
->room
.QRflags2
& QR2_SMTP_PUBLIC
) == 0) {
3341 if (RemoteIdentifier
== NULL
)
3343 snprintf(errmsgbuf
, n
, "Need sender to permit access.");
3344 return (ERROR
+ USERNAME_REQUIRED
);
3347 assoc_file_name(filename
, sizeof filename
, &CC
->room
, ctdl_netcfg_dir
);
3348 begin_critical_section(S_NETCONFIGS
);
3349 if (!read_spoolcontrol_file(&sc
, filename
))
3351 end_critical_section(S_NETCONFIGS
);
3352 snprintf(errmsgbuf
, n
,
3353 "This mailing list only accepts posts from subscribers.");
3354 return (ERROR
+ NO_SUCH_USER
);
3356 end_critical_section(S_NETCONFIGS
);
3357 found
= is_recipient (sc
, RemoteIdentifier
);
3358 free_spoolcontrol_struct(&sc
);
3363 snprintf(errmsgbuf
, n
,
3364 "This mailing list only accepts posts from subscribers.");
3365 return (ERROR
+ NO_SUCH_USER
);
3372 if ((CC
->user
.axlevel
< 2)
3373 && ((CC
->room
.QRflags
& QR_MAILBOX
) == 0)) {
3374 snprintf(errmsgbuf
, n
, "Need to be validated to enter "
3375 "(except in %s> to sysop)", MAILROOM
);
3376 return (ERROR
+ HIGHER_ACCESS_REQUIRED
);
3379 CtdlRoomAccess(&CC
->room
, &CC
->user
, &ra
, NULL
);
3380 if (!(ra
& UA_POSTALLOWED
)) {
3381 snprintf(errmsgbuf
, n
, "Higher access is required to post in this room.");
3382 return (ERROR
+ HIGHER_ACCESS_REQUIRED
);
3385 strcpy(errmsgbuf
, "Ok");
3391 * Check to see if the specified user has Internet mail permission
3392 * (returns nonzero if permission is granted)
3394 int CtdlCheckInternetMailPermission(struct ctdluser
*who
) {
3396 /* Do not allow twits to send Internet mail */
3397 if (who
->axlevel
<= 2) return(0);
3399 /* Globally enabled? */
3400 if (config
.c_restrict
== 0) return(1);
3402 /* User flagged ok? */
3403 if (who
->flags
& US_INTERNET
) return(2);
3405 /* Aide level access? */
3406 if (who
->axlevel
>= 6) return(3);
3408 /* No mail for you! */
3414 * Validate recipients, count delivery types and errors, and handle aliasing
3415 * FIXME check for dupes!!!!!
3417 * Returns 0 if all addresses are ok, ret->num_error = -1 if no addresses
3418 * were specified, or the number of addresses found invalid.
3420 * Caller needs to free the result using free_recipients()
3422 struct recptypes
*validate_recipients(char *supplied_recipients
,
3423 const char *RemoteIdentifier
,
3425 struct recptypes
*ret
;
3426 char *recipients
= NULL
;
3427 char this_recp
[256];
3428 char this_recp_cooked
[256];
3434 struct ctdluser tempUS
;
3435 struct ctdlroom tempQR
;
3436 struct ctdlroom tempQR2
;
3442 ret
= (struct recptypes
*) malloc(sizeof(struct recptypes
));
3443 if (ret
== NULL
) return(NULL
);
3445 /* Set all strings to null and numeric values to zero */
3446 memset(ret
, 0, sizeof(struct recptypes
));
3448 if (supplied_recipients
== NULL
) {
3449 recipients
= strdup("");
3452 recipients
= strdup(supplied_recipients
);
3455 /* Allocate some memory. Yes, this allocates 500% more memory than we will
3456 * actually need, but it's healthier for the heap than doing lots of tiny
3457 * realloc() calls instead.
3460 ret
->errormsg
= malloc(strlen(recipients
) + 1024);
3461 ret
->recp_local
= malloc(strlen(recipients
) + 1024);
3462 ret
->recp_internet
= malloc(strlen(recipients
) + 1024);
3463 ret
->recp_ignet
= malloc(strlen(recipients
) + 1024);
3464 ret
->recp_room
= malloc(strlen(recipients
) + 1024);
3465 ret
->display_recp
= malloc(strlen(recipients
) + 1024);
3467 ret
->errormsg
[0] = 0;
3468 ret
->recp_local
[0] = 0;
3469 ret
->recp_internet
[0] = 0;
3470 ret
->recp_ignet
[0] = 0;
3471 ret
->recp_room
[0] = 0;
3472 ret
->display_recp
[0] = 0;
3474 ret
->recptypes_magic
= RECPTYPES_MAGIC
;
3476 /* Change all valid separator characters to commas */
3477 for (i
=0; !IsEmptyStr(&recipients
[i
]); ++i
) {
3478 if ((recipients
[i
] == ';') || (recipients
[i
] == '|')) {
3479 recipients
[i
] = ',';
3483 /* Now start extracting recipients... */
3485 while (!IsEmptyStr(recipients
)) {
3487 for (i
=0; i
<=strlen(recipients
); ++i
) {
3488 if (recipients
[i
] == '\"') in_quotes
= 1 - in_quotes
;
3489 if ( ( (recipients
[i
] == ',') && (!in_quotes
) ) || (recipients
[i
] == 0) ) {
3490 safestrncpy(this_recp
, recipients
, i
+1);
3492 if (recipients
[i
] == ',') {
3493 strcpy(recipients
, &recipients
[i
+1]);
3496 strcpy(recipients
, "");
3503 if (IsEmptyStr(this_recp
))
3505 CtdlLogPrintf(CTDL_DEBUG
, "Evaluating recipient #%d: %s\n", num_recps
, this_recp
);
3507 mailtype
= alias(this_recp
);
3508 mailtype
= alias(this_recp
);
3509 mailtype
= alias(this_recp
);
3511 for (j
=0; !IsEmptyStr(&this_recp
[j
]); ++j
) {
3512 if (this_recp
[j
]=='_') {
3513 this_recp_cooked
[j
] = ' ';
3516 this_recp_cooked
[j
] = this_recp
[j
];
3519 this_recp_cooked
[j
] = '\0';
3524 if (!strcasecmp(this_recp
, "sysop")) {
3526 strcpy(this_recp
, config
.c_aideroom
);
3527 if (!IsEmptyStr(ret
->recp_room
)) {
3528 strcat(ret
->recp_room
, "|");
3530 strcat(ret
->recp_room
, this_recp
);
3532 else if ( (!strncasecmp(this_recp
, "room_", 5))
3533 && (!getroom(&tempQR
, &this_recp_cooked
[5])) ) {
3535 /* Save room so we can restore it later */
3539 /* Check permissions to send mail to this room */
3540 err
= CtdlDoIHavePermissionToPostInThisRoom(errmsg
,
3552 if (!IsEmptyStr(ret
->recp_room
)) {
3553 strcat(ret
->recp_room
, "|");
3555 strcat(ret
->recp_room
, &this_recp_cooked
[5]);
3558 /* Restore room in case something needs it */
3562 else if (getuser(&tempUS
, this_recp
) == 0) {
3564 strcpy(this_recp
, tempUS
.fullname
);
3565 if (!IsEmptyStr(ret
->recp_local
)) {
3566 strcat(ret
->recp_local
, "|");
3568 strcat(ret
->recp_local
, this_recp
);
3570 else if (getuser(&tempUS
, this_recp_cooked
) == 0) {
3572 strcpy(this_recp
, tempUS
.fullname
);
3573 if (!IsEmptyStr(ret
->recp_local
)) {
3574 strcat(ret
->recp_local
, "|");
3576 strcat(ret
->recp_local
, this_recp
);
3584 /* Yes, you're reading this correctly: if the target
3585 * domain points back to the local system or an attached
3586 * Citadel directory, the address is invalid. That's
3587 * because if the address were valid, we would have
3588 * already translated it to a local address by now.
3590 if (IsDirectory(this_recp
, 0)) {
3595 ++ret
->num_internet
;
3596 if (!IsEmptyStr(ret
->recp_internet
)) {
3597 strcat(ret
->recp_internet
, "|");
3599 strcat(ret
->recp_internet
, this_recp
);
3604 if (!IsEmptyStr(ret
->recp_ignet
)) {
3605 strcat(ret
->recp_ignet
, "|");
3607 strcat(ret
->recp_ignet
, this_recp
);
3615 if (IsEmptyStr(errmsg
)) {
3616 snprintf(append
, sizeof append
, "Invalid recipient: %s", this_recp
);
3619 snprintf(append
, sizeof append
, "%s", errmsg
);
3621 if ( (strlen(ret
->errormsg
) + strlen(append
) + 3) < SIZ
) {
3622 if (!IsEmptyStr(ret
->errormsg
)) {
3623 strcat(ret
->errormsg
, "; ");
3625 strcat(ret
->errormsg
, append
);
3629 if (IsEmptyStr(ret
->display_recp
)) {
3630 strcpy(append
, this_recp
);
3633 snprintf(append
, sizeof append
, ", %s", this_recp
);
3635 if ( (strlen(ret
->display_recp
)+strlen(append
)) < SIZ
) {
3636 strcat(ret
->display_recp
, append
);
3641 if ((ret
->num_local
+ ret
->num_internet
+ ret
->num_ignet
+
3642 ret
->num_room
+ ret
->num_error
) == 0) {
3643 ret
->num_error
= (-1);
3644 strcpy(ret
->errormsg
, "No recipients specified.");
3647 CtdlLogPrintf(CTDL_DEBUG
, "validate_recipients()\n");
3648 CtdlLogPrintf(CTDL_DEBUG
, " local: %d <%s>\n", ret
->num_local
, ret
->recp_local
);
3649 CtdlLogPrintf(CTDL_DEBUG
, " room: %d <%s>\n", ret
->num_room
, ret
->recp_room
);
3650 CtdlLogPrintf(CTDL_DEBUG
, " inet: %d <%s>\n", ret
->num_internet
, ret
->recp_internet
);
3651 CtdlLogPrintf(CTDL_DEBUG
, " ignet: %d <%s>\n", ret
->num_ignet
, ret
->recp_ignet
);
3652 CtdlLogPrintf(CTDL_DEBUG
, " error: %d <%s>\n", ret
->num_error
, ret
->errormsg
);
3660 * Destructor for struct recptypes
3662 void free_recipients(struct recptypes
*valid
) {
3664 if (valid
== NULL
) {
3668 if (valid
->recptypes_magic
!= RECPTYPES_MAGIC
) {
3669 CtdlLogPrintf(CTDL_EMERG
, "Attempt to call free_recipients() on some other data type!\n");
3673 if (valid
->errormsg
!= NULL
) free(valid
->errormsg
);
3674 if (valid
->recp_local
!= NULL
) free(valid
->recp_local
);
3675 if (valid
->recp_internet
!= NULL
) free(valid
->recp_internet
);
3676 if (valid
->recp_ignet
!= NULL
) free(valid
->recp_ignet
);
3677 if (valid
->recp_room
!= NULL
) free(valid
->recp_room
);
3678 if (valid
->display_recp
!= NULL
) free(valid
->display_recp
);
3685 * message entry - mode 0 (normal)
3687 void cmd_ent0(char *entargs
)
3693 char supplied_euid
[128];
3695 int format_type
= 0;
3696 char newusername
[256];
3697 char newuseremail
[256];
3698 struct CtdlMessage
*msg
;
3702 struct recptypes
*valid
= NULL
;
3703 struct recptypes
*valid_to
= NULL
;
3704 struct recptypes
*valid_cc
= NULL
;
3705 struct recptypes
*valid_bcc
= NULL
;
3707 int subject_required
= 0;
3712 int newuseremail_ok
= 0;
3713 char references
[SIZ
];
3718 post
= extract_int(entargs
, 0);
3719 extract_token(recp
, entargs
, 1, '|', sizeof recp
);
3720 anon_flag
= extract_int(entargs
, 2);
3721 format_type
= extract_int(entargs
, 3);
3722 extract_token(subject
, entargs
, 4, '|', sizeof subject
);
3723 extract_token(newusername
, entargs
, 5, '|', sizeof newusername
);
3724 do_confirm
= extract_int(entargs
, 6);
3725 extract_token(cc
, entargs
, 7, '|', sizeof cc
);
3726 extract_token(bcc
, entargs
, 8, '|', sizeof bcc
);
3727 switch(CC
->room
.QRdefaultview
) {
3730 extract_token(supplied_euid
, entargs
, 9, '|', sizeof supplied_euid
);
3733 supplied_euid
[0] = 0;
3736 extract_token(newuseremail
, entargs
, 10, '|', sizeof newuseremail
);
3737 extract_token(references
, entargs
, 11, '|', sizeof references
);
3738 for (ptr
=references
; *ptr
!= 0; ++ptr
) {
3739 if (*ptr
== '!') *ptr
= '|';
3742 /* first check to make sure the request is valid. */
3744 err
= CtdlDoIHavePermissionToPostInThisRoom(errmsg
, sizeof errmsg
, NULL
, POST_LOGGED_IN
);
3747 cprintf("%d %s\n", err
, errmsg
);
3751 /* Check some other permission type things. */
3753 if (IsEmptyStr(newusername
)) {
3754 strcpy(newusername
, CC
->user
.fullname
);
3756 if ( (CC
->user
.axlevel
< 6)
3757 && (strcasecmp(newusername
, CC
->user
.fullname
))
3758 && (strcasecmp(newusername
, CC
->cs_inet_fn
))
3760 cprintf("%d You don't have permission to author messages as '%s'.\n",
3761 ERROR
+ HIGHER_ACCESS_REQUIRED
,
3768 if (IsEmptyStr(newuseremail
)) {
3769 newuseremail_ok
= 1;
3772 if (!IsEmptyStr(newuseremail
)) {
3773 if (!strcasecmp(newuseremail
, CC
->cs_inet_email
)) {
3774 newuseremail_ok
= 1;
3776 else if (!IsEmptyStr(CC
->cs_inet_other_emails
)) {
3777 j
= num_tokens(CC
->cs_inet_other_emails
, '|');
3778 for (i
=0; i
<j
; ++i
) {
3779 extract_token(buf
, CC
->cs_inet_other_emails
, i
, '|', sizeof buf
);
3780 if (!strcasecmp(newuseremail
, buf
)) {
3781 newuseremail_ok
= 1;
3787 if (!newuseremail_ok
) {
3788 cprintf("%d You don't have permission to author messages as '%s'.\n",
3789 ERROR
+ HIGHER_ACCESS_REQUIRED
,
3795 CC
->cs_flags
|= CS_POSTING
;
3797 /* In mailbox rooms we have to behave a little differently --
3798 * make sure the user has specified at least one recipient. Then
3799 * validate the recipient(s). We do this for the Mail> room, as
3800 * well as any room which has the "Mailbox" view set.
3803 if ( ( (CC
->room
.QRflags
& QR_MAILBOX
) && (!strcasecmp(&CC
->room
.QRname
[11], MAILROOM
)) )
3804 || ( (CC
->room
.QRflags
& QR_MAILBOX
) && (CC
->curr_view
== VIEW_MAILBOX
) )
3806 if (CC
->user
.axlevel
< 2) {
3807 strcpy(recp
, "sysop");
3812 valid_to
= validate_recipients(recp
, NULL
, 0);
3813 if (valid_to
->num_error
> 0) {
3814 cprintf("%d %s\n", ERROR
+ NO_SUCH_USER
, valid_to
->errormsg
);
3815 free_recipients(valid_to
);
3819 valid_cc
= validate_recipients(cc
, NULL
, 0);
3820 if (valid_cc
->num_error
> 0) {
3821 cprintf("%d %s\n", ERROR
+ NO_SUCH_USER
, valid_cc
->errormsg
);
3822 free_recipients(valid_to
);
3823 free_recipients(valid_cc
);
3827 valid_bcc
= validate_recipients(bcc
, NULL
, 0);
3828 if (valid_bcc
->num_error
> 0) {
3829 cprintf("%d %s\n", ERROR
+ NO_SUCH_USER
, valid_bcc
->errormsg
);
3830 free_recipients(valid_to
);
3831 free_recipients(valid_cc
);
3832 free_recipients(valid_bcc
);
3836 /* Recipient required, but none were specified */
3837 if ( (valid_to
->num_error
< 0) && (valid_cc
->num_error
< 0) && (valid_bcc
->num_error
< 0) ) {
3838 free_recipients(valid_to
);
3839 free_recipients(valid_cc
);
3840 free_recipients(valid_bcc
);
3841 cprintf("%d At least one recipient is required.\n", ERROR
+ NO_SUCH_USER
);
3845 if (valid_to
->num_internet
+ valid_cc
->num_internet
+ valid_bcc
->num_internet
> 0) {
3846 if (CtdlCheckInternetMailPermission(&CC
->user
)==0) {
3847 cprintf("%d You do not have permission "
3848 "to send Internet mail.\n",
3849 ERROR
+ HIGHER_ACCESS_REQUIRED
);
3850 free_recipients(valid_to
);
3851 free_recipients(valid_cc
);
3852 free_recipients(valid_bcc
);
3857 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)
3858 && (CC
->user
.axlevel
< 4) ) {
3859 cprintf("%d Higher access required for network mail.\n",
3860 ERROR
+ HIGHER_ACCESS_REQUIRED
);
3861 free_recipients(valid_to
);
3862 free_recipients(valid_cc
);
3863 free_recipients(valid_bcc
);
3867 if ((RESTRICT_INTERNET
== 1)
3868 && (valid_to
->num_internet
+ valid_cc
->num_internet
+ valid_bcc
->num_internet
> 0)
3869 && ((CC
->user
.flags
& US_INTERNET
) == 0)
3870 && (!CC
->internal_pgm
)) {
3871 cprintf("%d You don't have access to Internet mail.\n",
3872 ERROR
+ HIGHER_ACCESS_REQUIRED
);
3873 free_recipients(valid_to
);
3874 free_recipients(valid_cc
);
3875 free_recipients(valid_bcc
);
3881 /* Is this a room which has anonymous-only or anonymous-option? */
3882 anonymous
= MES_NORMAL
;
3883 if (CC
->room
.QRflags
& QR_ANONONLY
) {
3884 anonymous
= MES_ANONONLY
;
3886 if (CC
->room
.QRflags
& QR_ANONOPT
) {
3887 if (anon_flag
== 1) { /* only if the user requested it */
3888 anonymous
= MES_ANONOPT
;
3892 if ((CC
->room
.QRflags
& QR_MAILBOX
) == 0) {
3896 /* Recommend to the client that the use of a message subject is
3897 * strongly recommended in this room, if either the SUBJECTREQ flag
3898 * is set, or if there is one or more Internet email recipients.
3900 if (CC
->room
.QRflags2
& QR2_SUBJECTREQ
) subject_required
= 1;
3901 if ((valid_to
) && (valid_to
->num_internet
> 0)) subject_required
= 1;
3902 if ((valid_cc
) && (valid_cc
->num_internet
> 0)) subject_required
= 1;
3903 if ((valid_bcc
) && (valid_bcc
->num_internet
> 0)) subject_required
= 1;
3905 /* If we're only checking the validity of the request, return
3906 * success without creating the message.
3909 cprintf("%d %s|%d\n", CIT_OK
,
3910 ((valid_to
!= NULL
) ? valid_to
->display_recp
: ""),
3912 free_recipients(valid_to
);
3913 free_recipients(valid_cc
);
3914 free_recipients(valid_bcc
);
3918 /* We don't need these anymore because we'll do it differently below */
3919 free_recipients(valid_to
);
3920 free_recipients(valid_cc
);
3921 free_recipients(valid_bcc
);
3923 /* Read in the message from the client. */
3925 cprintf("%d send message\n", START_CHAT_MODE
);
3927 cprintf("%d send message\n", SEND_LISTING
);
3930 msg
= CtdlMakeMessage(&CC
->user
, recp
, cc
,
3931 CC
->room
.QRname
, anonymous
, format_type
,
3932 newusername
, newuseremail
, subject
,
3933 ((!IsEmptyStr(supplied_euid
)) ? supplied_euid
: NULL
),
3936 /* Put together one big recipients struct containing to/cc/bcc all in
3937 * one. This is for the envelope.
3939 char *all_recps
= malloc(SIZ
* 3);
3940 strcpy(all_recps
, recp
);
3941 if (!IsEmptyStr(cc
)) {
3942 if (!IsEmptyStr(all_recps
)) {
3943 strcat(all_recps
, ",");
3945 strcat(all_recps
, cc
);
3947 if (!IsEmptyStr(bcc
)) {
3948 if (!IsEmptyStr(all_recps
)) {
3949 strcat(all_recps
, ",");
3951 strcat(all_recps
, bcc
);
3953 if (!IsEmptyStr(all_recps
)) {
3954 valid
= validate_recipients(all_recps
, NULL
, 0);
3962 msgnum
= CtdlSubmitMsg(msg
, valid
, "", QP_EADDR
);
3965 cprintf("%ld\n", msgnum
);
3967 cprintf("Message accepted.\n");
3970 cprintf("Internal error.\n");
3972 if (msg
->cm_fields
['E'] != NULL
) {
3973 cprintf("%s\n", msg
->cm_fields
['E']);
3980 CtdlFreeMessage(msg
);
3982 if (valid
!= NULL
) {
3983 free_recipients(valid
);
3991 * API function to delete messages which match a set of criteria
3992 * (returns the actual number of messages deleted)
3994 int CtdlDeleteMessages(char *room_name
, /* which room */
3995 long *dmsgnums
, /* array of msg numbers to be deleted */
3996 int num_dmsgnums
, /* number of msgs to be deleted, or 0 for "any" */
3997 char *content_type
/* or "" for any. regular expressions expected. */
4000 struct ctdlroom qrbuf
;
4001 struct cdbdata
*cdbfr
;
4002 long *msglist
= NULL
;
4003 long *dellist
= NULL
;
4006 int num_deleted
= 0;
4008 struct MetaData smi
;
4011 int need_to_free_re
= 0;
4013 if (content_type
) if (!IsEmptyStr(content_type
)) {
4014 regcomp(&re
, content_type
, 0);
4015 need_to_free_re
= 1;
4017 CtdlLogPrintf(CTDL_DEBUG
, "CtdlDeleteMessages(%s, %d msgs, %s)\n",
4018 room_name
, num_dmsgnums
, content_type
);
4020 /* get room record, obtaining a lock... */
4021 if (lgetroom(&qrbuf
, room_name
) != 0) {
4022 CtdlLogPrintf(CTDL_ERR
, "CtdlDeleteMessages(): Room <%s> not found\n",
4024 if (need_to_free_re
) regfree(&re
);
4025 return (0); /* room not found */
4027 cdbfr
= cdb_fetch(CDB_MSGLISTS
, &qrbuf
.QRnumber
, sizeof(long));
4029 if (cdbfr
!= NULL
) {
4030 dellist
= malloc(cdbfr
->len
);
4031 msglist
= (long *) cdbfr
->ptr
;
4032 cdbfr
->ptr
= NULL
; /* CtdlDeleteMessages() now owns this memory */
4033 num_msgs
= cdbfr
->len
/ sizeof(long);
4037 for (i
= 0; i
< num_msgs
; ++i
) {
4040 /* Set/clear a bit for each criterion */
4042 /* 0 messages in the list or a null list means that we are
4043 * interested in deleting any messages which meet the other criteria.
4045 if ((num_dmsgnums
== 0) || (dmsgnums
== NULL
)) {
4046 delete_this
|= 0x01;
4049 for (j
=0; j
<num_dmsgnums
; ++j
) {
4050 if (msglist
[i
] == dmsgnums
[j
]) {
4051 delete_this
|= 0x01;
4056 if (IsEmptyStr(content_type
)) {
4057 delete_this
|= 0x02;
4059 GetMetaData(&smi
, msglist
[i
]);
4060 if (regexec(&re
, smi
.meta_content_type
, 1, &pm
, 0) == 0) {
4061 delete_this
|= 0x02;
4065 /* Delete message only if all bits are set */
4066 if (delete_this
== 0x03) {
4067 dellist
[num_deleted
++] = msglist
[i
];
4072 num_msgs
= sort_msglist(msglist
, num_msgs
);
4073 cdb_store(CDB_MSGLISTS
, &qrbuf
.QRnumber
, (int)sizeof(long),
4074 msglist
, (int)(num_msgs
* sizeof(long)));
4076 qrbuf
.QRhighest
= msglist
[num_msgs
- 1];
4080 /* Go through the messages we pulled out of the index, and decrement
4081 * their reference counts by 1. If this is the only room the message
4082 * was in, the reference count will reach zero and the message will
4083 * automatically be deleted from the database. We do this in a
4084 * separate pass because there might be plug-in hooks getting called,
4085 * and we don't want that happening during an S_ROOMS critical
4088 if (num_deleted
) for (i
=0; i
<num_deleted
; ++i
) {
4089 PerformDeleteHooks(qrbuf
.QRname
, dellist
[i
]);
4090 AdjRefCount(dellist
[i
], -1);
4093 /* Now free the memory we used, and go away. */
4094 if (msglist
!= NULL
) free(msglist
);
4095 if (dellist
!= NULL
) free(dellist
);
4096 CtdlLogPrintf(CTDL_DEBUG
, "%d message(s) deleted.\n", num_deleted
);
4097 if (need_to_free_re
) regfree(&re
);
4098 return (num_deleted
);
4104 * Check whether the current user has permission to delete messages from
4105 * the current room (returns 1 for yes, 0 for no)
4107 int CtdlDoIHavePermissionToDeleteMessagesFromThisRoom(void) {
4109 CtdlRoomAccess(&CC
->room
, &CC
->user
, &ra
, NULL
);
4110 if (ra
& UA_DELETEALLOWED
) return(1);
4118 * Delete message from current room
4120 void cmd_dele(char *args
)
4129 extract_token(msgset
, args
, 0, '|', sizeof msgset
);
4130 num_msgs
= num_tokens(msgset
, ',');
4132 cprintf("%d Nothing to do.\n", CIT_OK
);
4136 if (CtdlDoIHavePermissionToDeleteMessagesFromThisRoom() == 0) {
4137 cprintf("%d Higher access required.\n",
4138 ERROR
+ HIGHER_ACCESS_REQUIRED
);
4143 * Build our message set to be moved/copied
4145 msgs
= malloc(num_msgs
* sizeof(long));
4146 for (i
=0; i
<num_msgs
; ++i
) {
4147 extract_token(msgtok
, msgset
, i
, ',', sizeof msgtok
);
4148 msgs
[i
] = atol(msgtok
);
4151 num_deleted
= CtdlDeleteMessages(CC
->room
.QRname
, msgs
, num_msgs
, "");
4155 cprintf("%d %d message%s deleted.\n", CIT_OK
,
4156 num_deleted
, ((num_deleted
!= 1) ? "s" : ""));
4158 cprintf("%d Message not found.\n", ERROR
+ MESSAGE_NOT_FOUND
);
4164 * Back end API function for moves and deletes (multiple messages)
4166 int CtdlCopyMsgsToRoom(long *msgnums
, int num_msgs
, char *dest
) {
4169 err
= CtdlSaveMsgPointersInRoom(dest
, msgnums
, num_msgs
, 1, NULL
);
4170 if (err
!= 0) return(err
);
4179 * move or copy a message to another room
4181 void cmd_move(char *args
)
4188 char targ
[ROOMNAMELEN
];
4189 struct ctdlroom qtemp
;
4196 extract_token(msgset
, args
, 0, '|', sizeof msgset
);
4197 num_msgs
= num_tokens(msgset
, ',');
4199 cprintf("%d Nothing to do.\n", CIT_OK
);
4203 extract_token(targ
, args
, 1, '|', sizeof targ
);
4204 convert_room_name_macros(targ
, sizeof targ
);
4205 targ
[ROOMNAMELEN
- 1] = 0;
4206 is_copy
= extract_int(args
, 2);
4208 if (getroom(&qtemp
, targ
) != 0) {
4209 cprintf("%d '%s' does not exist.\n",
4210 ERROR
+ ROOM_NOT_FOUND
, targ
);
4214 getuser(&CC
->user
, CC
->curr_user
);
4215 CtdlRoomAccess(&qtemp
, &CC
->user
, &ra
, NULL
);
4217 /* Check for permission to perform this operation.
4218 * Remember: "CC->room" is source, "qtemp" is target.
4222 /* Aides can move/copy */
4223 if (CC
->user
.axlevel
>= 6) permit
= 1;
4225 /* Room aides can move/copy */
4226 if (CC
->user
.usernum
== CC
->room
.QRroomaide
) permit
= 1;
4228 /* Permit move/copy from personal rooms */
4229 if ((CC
->room
.QRflags
& QR_MAILBOX
)
4230 && (qtemp
.QRflags
& QR_MAILBOX
)) permit
= 1;
4232 /* Permit only copy from public to personal room */
4234 && (!(CC
->room
.QRflags
& QR_MAILBOX
))
4235 && (qtemp
.QRflags
& QR_MAILBOX
)) permit
= 1;
4237 /* Permit message removal from collaborative delete rooms */
4238 if (CC
->room
.QRflags2
& QR2_COLLABDEL
) permit
= 1;
4240 /* Users allowed to post into the target room may move into it too. */
4241 if ((CC
->room
.QRflags
& QR_MAILBOX
) &&
4242 (qtemp
.QRflags
& UA_POSTALLOWED
)) permit
= 1;
4244 /* User must have access to target room */
4245 if (!(ra
& UA_KNOWN
)) permit
= 0;
4248 cprintf("%d Higher access required.\n",
4249 ERROR
+ HIGHER_ACCESS_REQUIRED
);
4254 * Build our message set to be moved/copied
4256 msgs
= malloc(num_msgs
* sizeof(long));
4257 for (i
=0; i
<num_msgs
; ++i
) {
4258 extract_token(msgtok
, msgset
, i
, ',', sizeof msgtok
);
4259 msgs
[i
] = atol(msgtok
);
4265 err
= CtdlCopyMsgsToRoom(msgs
, num_msgs
, targ
);
4267 cprintf("%d Cannot store message(s) in %s: error %d\n",
4273 /* Now delete the message from the source room,
4274 * if this is a 'move' rather than a 'copy' operation.
4277 CtdlDeleteMessages(CC
->room
.QRname
, msgs
, num_msgs
, "");
4281 cprintf("%d Message(s) %s.\n", CIT_OK
, (is_copy
? "copied" : "moved") );
4287 * GetMetaData() - Get the supplementary record for a message
4289 void GetMetaData(struct MetaData
*smibuf
, long msgnum
)
4292 struct cdbdata
*cdbsmi
;
4295 memset(smibuf
, 0, sizeof(struct MetaData
));
4296 smibuf
->meta_msgnum
= msgnum
;
4297 smibuf
->meta_refcount
= 1; /* Default reference count is 1 */
4299 /* Use the negative of the message number for its supp record index */
4300 TheIndex
= (0L - msgnum
);
4302 cdbsmi
= cdb_fetch(CDB_MSGMAIN
, &TheIndex
, sizeof(long));
4303 if (cdbsmi
== NULL
) {
4304 return; /* record not found; go with defaults */
4306 memcpy(smibuf
, cdbsmi
->ptr
,
4307 ((cdbsmi
->len
> sizeof(struct MetaData
)) ?
4308 sizeof(struct MetaData
) : cdbsmi
->len
));
4315 * PutMetaData() - (re)write supplementary record for a message
4317 void PutMetaData(struct MetaData
*smibuf
)
4321 /* Use the negative of the message number for the metadata db index */
4322 TheIndex
= (0L - smibuf
->meta_msgnum
);
4324 cdb_store(CDB_MSGMAIN
,
4325 &TheIndex
, (int)sizeof(long),
4326 smibuf
, (int)sizeof(struct MetaData
));
4331 * AdjRefCount - submit an adjustment to the reference count for a message.
4332 * (These are just queued -- we actually process them later.)
4334 void AdjRefCount(long msgnum
, int incr
)
4336 struct arcq new_arcq
;
4338 begin_critical_section(S_SUPPMSGMAIN
);
4339 if (arcfp
== NULL
) {
4340 arcfp
= fopen(file_arcq
, "ab+");
4342 end_critical_section(S_SUPPMSGMAIN
);
4344 /* msgnum < 0 means that we're trying to close the file */
4346 CtdlLogPrintf(CTDL_DEBUG
, "Closing the AdjRefCount queue file\n");
4347 begin_critical_section(S_SUPPMSGMAIN
);
4348 if (arcfp
!= NULL
) {
4352 end_critical_section(S_SUPPMSGMAIN
);
4357 * If we can't open the queue, perform the operation synchronously.
4359 if (arcfp
== NULL
) {
4360 TDAP_AdjRefCount(msgnum
, incr
);
4364 new_arcq
.arcq_msgnum
= msgnum
;
4365 new_arcq
.arcq_delta
= incr
;
4366 fwrite(&new_arcq
, sizeof(struct arcq
), 1, arcfp
);
4374 * TDAP_ProcessAdjRefCountQueue()
4376 * Process the queue of message count adjustments that was created by calls
4377 * to AdjRefCount() ... by reading the queue and calling TDAP_AdjRefCount()
4378 * for each one. This should be an "off hours" operation.
4380 int TDAP_ProcessAdjRefCountQueue(void)
4382 char file_arcq_temp
[PATH_MAX
];
4385 struct arcq arcq_rec
;
4386 int num_records_processed
= 0;
4388 snprintf(file_arcq_temp
, sizeof file_arcq_temp
, "%s.%04x", file_arcq
, rand());
4390 begin_critical_section(S_SUPPMSGMAIN
);
4391 if (arcfp
!= NULL
) {
4396 r
= link(file_arcq
, file_arcq_temp
);
4398 CtdlLogPrintf(CTDL_CRIT
, "%s: %s\n", file_arcq_temp
, strerror(errno
));
4399 end_critical_section(S_SUPPMSGMAIN
);
4400 return(num_records_processed
);
4404 end_critical_section(S_SUPPMSGMAIN
);
4406 fp
= fopen(file_arcq_temp
, "rb");
4408 CtdlLogPrintf(CTDL_CRIT
, "%s: %s\n", file_arcq_temp
, strerror(errno
));
4409 return(num_records_processed
);
4412 while (fread(&arcq_rec
, sizeof(struct arcq
), 1, fp
) == 1) {
4413 TDAP_AdjRefCount(arcq_rec
.arcq_msgnum
, arcq_rec
.arcq_delta
);
4414 ++num_records_processed
;
4418 r
= unlink(file_arcq_temp
);
4420 CtdlLogPrintf(CTDL_CRIT
, "%s: %s\n", file_arcq_temp
, strerror(errno
));
4423 return(num_records_processed
);
4429 * TDAP_AdjRefCount - adjust the reference count for a message.
4430 * This one does it "for real" because it's called by
4431 * the autopurger function that processes the queue
4432 * created by AdjRefCount(). If a message's reference
4433 * count becomes zero, we also delete the message from
4434 * disk and de-index it.
4436 void TDAP_AdjRefCount(long msgnum
, int incr
)
4439 struct MetaData smi
;
4442 /* This is a *tight* critical section; please keep it that way, as
4443 * it may get called while nested in other critical sections.
4444 * Complicating this any further will surely cause deadlock!
4446 begin_critical_section(S_SUPPMSGMAIN
);
4447 GetMetaData(&smi
, msgnum
);
4448 smi
.meta_refcount
+= incr
;
4450 end_critical_section(S_SUPPMSGMAIN
);
4451 CtdlLogPrintf(CTDL_DEBUG
, "msg %ld ref count delta %+d, is now %d\n",
4452 msgnum
, incr
, smi
.meta_refcount
);
4454 /* If the reference count is now zero, delete the message
4455 * (and its supplementary record as well).
4457 if (smi
.meta_refcount
== 0) {
4458 CtdlLogPrintf(CTDL_DEBUG
, "Deleting message <%ld>\n", msgnum
);
4460 /* Call delete hooks with NULL room to show it has gone altogether */
4461 PerformDeleteHooks(NULL
, msgnum
);
4463 /* Remove from message base */
4465 cdb_delete(CDB_MSGMAIN
, &delnum
, (int)sizeof(long));
4466 cdb_delete(CDB_BIGMSGS
, &delnum
, (int)sizeof(long));
4468 /* Remove metadata record */
4469 delnum
= (0L - msgnum
);
4470 cdb_delete(CDB_MSGMAIN
, &delnum
, (int)sizeof(long));
4476 * Write a generic object to this room
4478 * Note: this could be much more efficient. Right now we use two temporary
4479 * files, and still pull the message into memory as with all others.
4481 void CtdlWriteObject(char *req_room
, /* Room to stuff it in */
4482 char *content_type
, /* MIME type of this object */
4483 char *raw_message
, /* Data to be written */
4484 off_t raw_length
, /* Size of raw_message */
4485 struct ctdluser
*is_mailbox
, /* Mailbox room? */
4486 int is_binary
, /* Is encoding necessary? */
4487 int is_unique
, /* Del others of this type? */
4488 unsigned int flags
/* Internal save flags */
4492 struct ctdlroom qrbuf
;
4493 char roomname
[ROOMNAMELEN
];
4494 struct CtdlMessage
*msg
;
4495 char *encoded_message
= NULL
;
4497 if (is_mailbox
!= NULL
) {
4498 MailboxName(roomname
, sizeof roomname
, is_mailbox
, req_room
);
4501 safestrncpy(roomname
, req_room
, sizeof(roomname
));
4504 CtdlLogPrintf(CTDL_DEBUG
, "Raw length is %ld\n", (long)raw_length
);
4507 encoded_message
= malloc((size_t) (((raw_length
* 134) / 100) + 4096 ) );
4510 encoded_message
= malloc((size_t)(raw_length
+ 4096));
4513 sprintf(encoded_message
, "Content-type: %s\n", content_type
);
4516 sprintf(&encoded_message
[strlen(encoded_message
)],
4517 "Content-transfer-encoding: base64\n\n"
4521 sprintf(&encoded_message
[strlen(encoded_message
)],
4522 "Content-transfer-encoding: 7bit\n\n"
4528 &encoded_message
[strlen(encoded_message
)],
4536 &encoded_message
[strlen(encoded_message
)],
4542 CtdlLogPrintf(CTDL_DEBUG
, "Allocating\n");
4543 msg
= malloc(sizeof(struct CtdlMessage
));
4544 memset(msg
, 0, sizeof(struct CtdlMessage
));
4545 msg
->cm_magic
= CTDLMESSAGE_MAGIC
;
4546 msg
->cm_anon_type
= MES_NORMAL
;
4547 msg
->cm_format_type
= 4;
4548 msg
->cm_fields
['A'] = strdup(CC
->user
.fullname
);
4549 msg
->cm_fields
['O'] = strdup(req_room
);
4550 msg
->cm_fields
['N'] = strdup(config
.c_nodename
);
4551 msg
->cm_fields
['H'] = strdup(config
.c_humannode
);
4552 msg
->cm_flags
= flags
;
4554 msg
->cm_fields
['M'] = encoded_message
;
4556 /* Create the requested room if we have to. */
4557 if (getroom(&qrbuf
, roomname
) != 0) {
4558 create_room(roomname
,
4559 ( (is_mailbox
!= NULL
) ? 5 : 3 ),
4560 "", 0, 1, 0, VIEW_BBS
);
4562 /* If the caller specified this object as unique, delete all
4563 * other objects of this type that are currently in the room.
4566 CtdlLogPrintf(CTDL_DEBUG
, "Deleted %d other msgs of this type\n",
4567 CtdlDeleteMessages(roomname
, NULL
, 0, content_type
)
4570 /* Now write the data */
4571 CtdlSubmitMsg(msg
, NULL
, roomname
, 0);
4572 CtdlFreeMessage(msg
);
4580 void CtdlGetSysConfigBackend(long msgnum
, void *userdata
) {
4581 config_msgnum
= msgnum
;
4585 char *CtdlGetSysConfig(char *sysconfname
) {
4586 char hold_rm
[ROOMNAMELEN
];
4589 struct CtdlMessage
*msg
;
4592 strcpy(hold_rm
, CC
->room
.QRname
);
4593 if (getroom(&CC
->room
, SYSCONFIGROOM
) != 0) {
4594 getroom(&CC
->room
, hold_rm
);
4599 /* We want the last (and probably only) config in this room */
4600 begin_critical_section(S_CONFIG
);
4601 config_msgnum
= (-1L);
4602 CtdlForEachMessage(MSGS_LAST
, 1, NULL
, sysconfname
, NULL
,
4603 CtdlGetSysConfigBackend
, NULL
);
4604 msgnum
= config_msgnum
;
4605 end_critical_section(S_CONFIG
);
4611 msg
= CtdlFetchMessage(msgnum
, 1);
4613 conf
= strdup(msg
->cm_fields
['M']);
4614 CtdlFreeMessage(msg
);
4621 getroom(&CC
->room
, hold_rm
);
4623 if (conf
!= NULL
) do {
4624 extract_token(buf
, conf
, 0, '\n', sizeof buf
);
4625 strcpy(conf
, &conf
[strlen(buf
)+1]);
4626 } while ( (!IsEmptyStr(conf
)) && (!IsEmptyStr(buf
)) );
4632 void CtdlPutSysConfig(char *sysconfname
, char *sysconfdata
) {
4633 CtdlWriteObject(SYSCONFIGROOM
, sysconfname
, sysconfdata
, (strlen(sysconfdata
)+1), NULL
, 0, 1, 0);
4638 * Determine whether a given Internet address belongs to the current user
4640 int CtdlIsMe(char *addr
, int addr_buf_len
)
4642 struct recptypes
*recp
;
4645 recp
= validate_recipients(addr
, NULL
, 0);
4646 if (recp
== NULL
) return(0);
4648 if (recp
->num_local
== 0) {
4649 free_recipients(recp
);
4653 for (i
=0; i
<recp
->num_local
; ++i
) {
4654 extract_token(addr
, recp
->recp_local
, i
, '|', addr_buf_len
);
4655 if (!strcasecmp(addr
, CC
->user
.fullname
)) {
4656 free_recipients(recp
);
4661 free_recipients(recp
);
4667 * Citadel protocol command to do the same
4669 void cmd_isme(char *argbuf
) {
4672 if (CtdlAccessCheck(ac_logged_in
)) return;
4673 extract_token(addr
, argbuf
, 0, '|', sizeof addr
);
4675 if (CtdlIsMe(addr
, sizeof addr
)) {
4676 cprintf("%d %s\n", CIT_OK
, addr
);
4679 cprintf("%d Not you.\n", ERROR
+ ILLEGAL_VALUE
);