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;
349 char *is_set; /* actually an array of booleans */
352 char setstr[SIZ], lostr[SIZ], histr[SIZ];
355 /* Don't bother doing *anything* if we were passed a list of zero messages */
356 if (num_target_msgnums < 1) {
360 CtdlLogPrintf(CTDL_DEBUG, "CtdlSetSeen(%d msgs starting with %ld, %d, %d)\n",
361 num_target_msgnums, target_msgnums[0],
362 target_setting, which_set);
364 /* Learn about the user and room in question */
365 CtdlGetRelationship(&vbuf,
366 ((which_user != NULL) ? which_user : &CC->user),
367 ((which_room != NULL) ? which_room : &CC->room)
370 /* Load the message list */
371 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long));
373 msglist = (long *) cdbfr->ptr;
374 cdbfr->ptr = NULL; /* CtdlSetSeen() now owns this memory */
375 num_msgs = cdbfr->len / sizeof(long);
378 return; /* No messages at all? No further action. */
381 is_set = malloc(num_msgs * sizeof(char));
382 memset(is_set, 0, (num_msgs * sizeof(char)) );
384 /* Decide which message set we're manipulating */
386 case ctdlsetseen_seen:
387 safestrncpy(vset, vbuf.v_seen, sizeof vset);
389 case ctdlsetseen_answered:
390 safestrncpy(vset, vbuf.v_answered, sizeof vset);
394 /* CtdlLogPrintf(CTDL_DEBUG, "before optimize: %s\n", vset); */
396 /* Translate the existing sequence set into an array of booleans */
397 num_sets = num_tokens(vset, ',');
398 for (s=0; s<num_sets; ++s) {
399 extract_token(setstr, vset, s, ',', sizeof setstr);
401 extract_token(lostr, setstr, 0, ':', sizeof lostr);
402 if (num_tokens(setstr, ':') >= 2) {
403 extract_token(histr, setstr, 1, ':', sizeof histr);
404 if (!strcmp(histr, "*")) {
405 snprintf(histr, sizeof histr, "%ld", LONG_MAX);
409 strcpy(histr, lostr);
414 for (i = 0; i < num_msgs; ++i) {
415 if ((msglist[i] >= lo) && (msglist[i] <= hi)) {
421 /* Now translate the array of booleans back into a sequence set */
426 for (i=0; i<num_msgs; ++i) {
428 is_seen = is_set[i]; /* Default to existing setting */
430 for (k=0; k<num_target_msgnums; ++k) {
431 if (msglist[i] == target_msgnums[k]) {
432 is_seen = target_setting;
437 if (lo < 0L) lo = msglist[i];
441 if ( ((is_seen == 0) && (was_seen == 1))
442 || ((is_seen == 1) && (i == num_msgs-1)) ) {
444 /* begin trim-o-matic code */
447 while ( (strlen(vset) + 20) > sizeof vset) {
448 remove_token(vset, 0, ',');
450 if (j--) break; /* loop no more than 9 times */
452 if ( (trimming) && (which_set == ctdlsetseen_seen) ) {
456 snprintf(lostr, sizeof lostr,
457 "1:%ld,%s", t, vset);
458 safestrncpy(vset, lostr, sizeof vset);
460 /* end trim-o-matic code */
468 snprintf(&vset[tmp], (sizeof vset) - tmp,
472 snprintf(&vset[tmp], (sizeof vset) - tmp,
481 /* Decide which message set we're manipulating */
483 case ctdlsetseen_seen:
484 safestrncpy(vbuf.v_seen, vset, sizeof vbuf.v_seen);
486 case ctdlsetseen_answered:
487 safestrncpy(vbuf.v_answered, vset,
488 sizeof vbuf.v_answered);
493 /* CtdlLogPrintf(CTDL_DEBUG, " after optimize: %s\n", vset); */
495 CtdlSetRelationship(&vbuf,
496 ((which_user != NULL) ? which_user : &CC->user),
497 ((which_room != NULL) ? which_room : &CC->room)
503 * API function to perform an operation for each qualifying message in the
504 * current room. (Returns the number of messages processed.)
506 int CtdlForEachMessage(int mode, long ref, char *search_string,
508 struct CtdlMessage *compare,
509 void (*CallBack) (long, void *),
515 struct cdbdata *cdbfr;
516 long *msglist = NULL;
518 int num_processed = 0;
521 struct CtdlMessage *msg = NULL;
524 int printed_lastold = 0;
525 int num_search_msgs = 0;
526 long *search_msgs = NULL;
528 int need_to_free_re = 0;
531 if (content_type) if (!IsEmptyStr(content_type)) {
532 regcomp(&re, content_type, 0);
536 /* Learn about the user and room in question */
537 getuser(&CC->user, CC->curr_user);
538 CtdlGetRelationship(&vbuf, &CC->user, &CC->room);
540 /* Load the message list */
541 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long));
543 msglist = (long *) cdbfr->ptr;
544 num_msgs = cdbfr->len / sizeof(long);
546 if (need_to_free_re) regfree(&re);
547 return 0; /* No messages at all? No further action. */
552 * Now begin the traversal.
554 if (num_msgs > 0) for (a = 0; a < num_msgs; ++a) {
556 /* If the caller is looking for a specific MIME type, filter
557 * out all messages which are not of the type requested.
559 if (content_type != NULL) if (!IsEmptyStr(content_type)) {
561 /* This call to GetMetaData() sits inside this loop
562 * so that we only do the extra database read per msg
563 * if we need to. Doing the extra read all the time
564 * really kills the server. If we ever need to use
565 * metadata for another search criterion, we need to
566 * move the read somewhere else -- but still be smart
567 * enough to only do the read if the caller has
568 * specified something that will need it.
570 GetMetaData(&smi, msglist[a]);
572 /* if (strcasecmp(smi.meta_content_type, content_type)) { old non-regex way */
573 if (regexec(&re, smi.meta_content_type, 1, &pm, 0) != 0) {
579 num_msgs = sort_msglist(msglist, num_msgs);
581 /* If a template was supplied, filter out the messages which
582 * don't match. (This could induce some delays!)
585 if (compare != NULL) {
586 for (a = 0; a < num_msgs; ++a) {
587 msg = CtdlFetchMessage(msglist[a], 1);
589 if (CtdlMsgCmp(msg, compare)) {
592 CtdlFreeMessage(msg);
598 /* If a search string was specified, get a message list from
599 * the full text index and remove messages which aren't on both
603 * Since the lists are sorted and strictly ascending, and the
604 * output list is guaranteed to be shorter than or equal to the
605 * input list, we overwrite the bottom of the input list. This
606 * eliminates the need to memmove big chunks of the list over and
609 if ( (num_msgs > 0) && (mode == MSGS_SEARCH) && (search_string) ) {
611 /* Call search module via hook mechanism.
612 * NULL means use any search function available.
613 * otherwise replace with a char * to name of search routine
615 CtdlModuleDoSearch(&num_search_msgs, &search_msgs, search_string, "fulltext");
617 if (num_search_msgs > 0) {
621 orig_num_msgs = num_msgs;
623 for (i=0; i<orig_num_msgs; ++i) {
624 for (j=0; j<num_search_msgs; ++j) {
625 if (msglist[i] == search_msgs[j]) {
626 msglist[num_msgs++] = msglist[i];
632 num_msgs = 0; /* No messages qualify */
634 if (search_msgs != NULL) free(search_msgs);
636 /* Now that we've purged messages which don't contain the search
637 * string, treat a MSGS_SEARCH just like a MSGS_ALL from this
644 * Now iterate through the message list, according to the
645 * criteria supplied by the caller.
648 for (a = 0; a < num_msgs; ++a) {
649 thismsg = msglist[a];
650 if (mode == MSGS_ALL) {
654 is_seen = is_msg_in_sequence_set(
655 vbuf.v_seen, thismsg);
656 if (is_seen) lastold = thismsg;
662 || ((mode == MSGS_OLD) && (is_seen))
663 || ((mode == MSGS_NEW) && (!is_seen))
664 || ((mode == MSGS_LAST) && (a >= (num_msgs - ref)))
665 || ((mode == MSGS_FIRST) && (a < ref))
666 || ((mode == MSGS_GT) && (thismsg > ref))
667 || ((mode == MSGS_EQ) && (thismsg == ref))
670 if ((mode == MSGS_NEW) && (CC->user.flags & US_LASTOLD) && (lastold > 0L) && (printed_lastold == 0) && (!is_seen)) {
672 CallBack(lastold, userdata);
676 if (CallBack) CallBack(thismsg, userdata);
680 cdb_free(cdbfr); /* Clean up */
681 if (need_to_free_re) regfree(&re);
682 return num_processed;
688 * cmd_msgs() - get list of message #'s in this room
689 * implements the MSGS server command using CtdlForEachMessage()
691 void cmd_msgs(char *cmdbuf)
700 int with_template = 0;
701 struct CtdlMessage *template = NULL;
702 int with_headers = 0;
703 char search_string[1024];
705 extract_token(which, cmdbuf, 0, '|', sizeof which);
706 cm_ref = extract_int(cmdbuf, 1);
707 extract_token(search_string, cmdbuf, 1, '|', sizeof search_string);
708 with_template = extract_int(cmdbuf, 2);
709 with_headers = extract_int(cmdbuf, 3);
712 if (!strncasecmp(which, "OLD", 3))
714 else if (!strncasecmp(which, "NEW", 3))
716 else if (!strncasecmp(which, "FIRST", 5))
718 else if (!strncasecmp(which, "LAST", 4))
720 else if (!strncasecmp(which, "GT", 2))
722 else if (!strncasecmp(which, "SEARCH", 6))
727 if ((!(CC->logged_in)) && (!(CC->internal_pgm))) {
728 cprintf("%d not logged in\n", ERROR + NOT_LOGGED_IN);
732 if ( (mode == MSGS_SEARCH) && (!config.c_enable_fulltext) ) {
733 cprintf("%d Full text index is not enabled on this server.\n",
734 ERROR + CMD_NOT_SUPPORTED);
740 cprintf("%d Send template then receive message list\n",
742 template = (struct CtdlMessage *)
743 malloc(sizeof(struct CtdlMessage));
744 memset(template, 0, sizeof(struct CtdlMessage));
745 template->cm_magic = CTDLMESSAGE_MAGIC;
746 template->cm_anon_type = MES_NORMAL;
748 while(client_getln(buf, sizeof buf) >= 0 && strcmp(buf,"000")) {
749 extract_token(tfield, buf, 0, '|', sizeof tfield);
750 extract_token(tvalue, buf, 1, '|', sizeof tvalue);
751 for (i='A'; i<='Z'; ++i) if (msgkeys[i]!=NULL) {
752 if (!strcasecmp(tfield, msgkeys[i])) {
753 template->cm_fields[i] =
761 cprintf("%d \n", LISTING_FOLLOWS);
764 CtdlForEachMessage(mode,
765 ( (mode == MSGS_SEARCH) ? 0 : cm_ref ),
766 ( (mode == MSGS_SEARCH) ? search_string : NULL ),
769 (with_headers ? headers_listing : simple_listing),
772 if (template != NULL) CtdlFreeMessage(template);
780 * help_subst() - support routine for help file viewer
782 void help_subst(char *strbuf, char *source, char *dest)
787 while (p = pattern2(strbuf, source), (p >= 0)) {
788 strcpy(workbuf, &strbuf[p + strlen(source)]);
789 strcpy(&strbuf[p], dest);
790 strcat(strbuf, workbuf);
795 void do_help_subst(char *buffer)
799 help_subst(buffer, "^nodename", config.c_nodename);
800 help_subst(buffer, "^humannode", config.c_humannode);
801 help_subst(buffer, "^fqdn", config.c_fqdn);
802 help_subst(buffer, "^username", CC->user.fullname);
803 snprintf(buf2, sizeof buf2, "%ld", CC->user.usernum);
804 help_subst(buffer, "^usernum", buf2);
805 help_subst(buffer, "^sysadm", config.c_sysadm);
806 help_subst(buffer, "^variantname", CITADEL);
807 snprintf(buf2, sizeof buf2, "%d", config.c_maxsessions);
808 help_subst(buffer, "^maxsessions", buf2);
809 help_subst(buffer, "^bbsdir", ctdl_message_dir);
815 * memfmout() - Citadel text formatter and paginator.
816 * Although the original purpose of this routine was to format
817 * text to the reader's screen width, all we're really using it
818 * for here is to format text out to 80 columns before sending it
819 * to the client. The client software may reformat it again.
822 char *mptr, /* where are we going to get our text from? */
823 char subst, /* nonzero if we should do substitutions */
824 char *nl) /* string to terminate lines with */
832 static int width = 80;
837 c = 1; /* c is the current pos */
841 while (ch = *mptr, ((ch != 0) && (strlen(buffer) < 126))) {
843 buffer[strlen(buffer) + 1] = 0;
844 buffer[strlen(buffer)] = ch;
847 if (buffer[0] == '^')
848 do_help_subst(buffer);
850 buffer[strlen(buffer) + 1] = 0;
852 strcpy(buffer, &buffer[1]);
860 if (((ch == 13) || (ch == 10)) && (old != 13) && (old != 10)) {
863 if (((old == 13) || (old == 10)) && (isspace(real))) {
868 if (((strlen(aaa) + c) > (width - 5)) && (strlen(aaa) > (width - 5))) {
869 cprintf("%s%s", nl, aaa);
878 if ((strlen(aaa) + c) > (width - 5)) {
887 if ((ch == 13) || (ch == 10)) {
888 cprintf("%s%s", aaa, nl);
895 cprintf("%s%s", aaa, nl);
901 * Callback function for mime parser that simply lists the part
903 void list_this_part(char *name, char *filename, char *partnum, char *disp,
904 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
909 ma = (struct ma_info *)cbuserdata;
910 if (ma->is_ma == 0) {
911 cprintf("part=%s|%s|%s|%s|%s|%ld\n",
912 name, filename, partnum, disp, cbtype, (long)length);
917 * Callback function for multipart prefix
919 void list_this_pref(char *name, char *filename, char *partnum, char *disp,
920 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
925 ma = (struct ma_info *)cbuserdata;
926 if (!strcasecmp(cbtype, "multipart/alternative")) {
930 if (ma->is_ma == 0) {
931 cprintf("pref=%s|%s\n", partnum, cbtype);
936 * Callback function for multipart sufffix
938 void list_this_suff(char *name, char *filename, char *partnum, char *disp,
939 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
944 ma = (struct ma_info *)cbuserdata;
945 if (ma->is_ma == 0) {
946 cprintf("suff=%s|%s\n", partnum, cbtype);
948 if (!strcasecmp(cbtype, "multipart/alternative")) {
955 * Callback function for mime parser that opens a section for downloading
957 void mime_download(char *name, char *filename, char *partnum, char *disp,
958 void *content, char *cbtype, char *cbcharset, size_t length,
959 char *encoding, void *cbuserdata)
962 /* Silently go away if there's already a download open... */
963 if (CC->download_fp != NULL)
966 /* ...or if this is not the desired section */
967 if (strcasecmp(CC->download_desired_section, partnum))
970 CC->download_fp = tmpfile();
971 if (CC->download_fp == NULL)
974 fwrite(content, length, 1, CC->download_fp);
975 fflush(CC->download_fp);
976 rewind(CC->download_fp);
978 OpenCmdResult(filename, cbtype);
984 * Callback function for mime parser that outputs a section all at once
986 void mime_spew_section(char *name, char *filename, char *partnum, char *disp,
987 void *content, char *cbtype, char *cbcharset, size_t length,
988 char *encoding, void *cbuserdata)
990 int *found_it = (int *)cbuserdata;
992 /* ...or if this is not the desired section */
993 if (strcasecmp(CC->download_desired_section, partnum))
998 cprintf("%d %d\n", BINARY_FOLLOWS, (int)length);
999 client_write(content, length);
1005 * Load a message from disk into memory.
1006 * This is used by CtdlOutputMsg() and other fetch functions.
1008 * NOTE: Caller is responsible for freeing the returned CtdlMessage struct
1009 * using the CtdlMessageFree() function.
1011 struct CtdlMessage *CtdlFetchMessage(long msgnum, int with_body)
1013 struct cdbdata *dmsgtext;
1014 struct CtdlMessage *ret = NULL;
1018 cit_uint8_t field_header;
1020 CtdlLogPrintf(CTDL_DEBUG, "CtdlFetchMessage(%ld, %d)\n", msgnum, with_body);
1022 dmsgtext = cdb_fetch(CDB_MSGMAIN, &msgnum, sizeof(long));
1023 if (dmsgtext == NULL) {
1026 mptr = dmsgtext->ptr;
1027 upper_bound = mptr + dmsgtext->len;
1029 /* Parse the three bytes that begin EVERY message on disk.
1030 * The first is always 0xFF, the on-disk magic number.
1031 * The second is the anonymous/public type byte.
1032 * The third is the format type byte (vari, fixed, or MIME).
1036 CtdlLogPrintf(CTDL_ERR, "Message %ld appears to be corrupted.\n", msgnum);
1040 ret = (struct CtdlMessage *) malloc(sizeof(struct CtdlMessage));
1041 memset(ret, 0, sizeof(struct CtdlMessage));
1043 ret->cm_magic = CTDLMESSAGE_MAGIC;
1044 ret->cm_anon_type = *mptr++; /* Anon type byte */
1045 ret->cm_format_type = *mptr++; /* Format type byte */
1048 * The rest is zero or more arbitrary fields. Load them in.
1049 * We're done when we encounter either a zero-length field or
1050 * have just processed the 'M' (message text) field.
1053 if (mptr >= upper_bound) {
1056 field_header = *mptr++;
1057 ret->cm_fields[field_header] = strdup(mptr);
1059 while (*mptr++ != 0); /* advance to next field */
1061 } while ((mptr < upper_bound) && (field_header != 'M'));
1065 /* Always make sure there's something in the msg text field. If
1066 * it's NULL, the message text is most likely stored separately,
1067 * so go ahead and fetch that. Failing that, just set a dummy
1068 * body so other code doesn't barf.
1070 if ( (ret->cm_fields['M'] == NULL) && (with_body) ) {
1071 dmsgtext = cdb_fetch(CDB_BIGMSGS, &msgnum, sizeof(long));
1072 if (dmsgtext != NULL) {
1073 ret->cm_fields['M'] = strdup(dmsgtext->ptr);
1077 if (ret->cm_fields['M'] == NULL) {
1078 ret->cm_fields['M'] = strdup("\r\n\r\n (no text)\r\n");
1081 /* Perform "before read" hooks (aborting if any return nonzero) */
1082 if (PerformMessageHooks(ret, EVT_BEFOREREAD) > 0) {
1083 CtdlFreeMessage(ret);
1092 * Returns 1 if the supplied pointer points to a valid Citadel message.
1093 * If the pointer is NULL or the magic number check fails, returns 0.
1095 int is_valid_message(struct CtdlMessage *msg) {
1098 if ((msg->cm_magic) != CTDLMESSAGE_MAGIC) {
1099 CtdlLogPrintf(CTDL_WARNING, "is_valid_message() -- self-check failed\n");
1107 * 'Destructor' for struct CtdlMessage
1109 void CtdlFreeMessage(struct CtdlMessage *msg)
1113 if (is_valid_message(msg) == 0)
1115 if (msg != NULL) free (msg);
1119 for (i = 0; i < 256; ++i)
1120 if (msg->cm_fields[i] != NULL) {
1121 free(msg->cm_fields[i]);
1124 msg->cm_magic = 0; /* just in case */
1130 * Pre callback function for multipart/alternative
1132 * NOTE: this differs from the standard behavior for a reason. Normally when
1133 * displaying multipart/alternative you want to show the _last_ usable
1134 * format in the message. Here we show the _first_ one, because it's
1135 * usually text/plain. Since this set of functions is designed for text
1136 * output to non-MIME-aware clients, this is the desired behavior.
1139 void fixed_output_pre(char *name, char *filename, char *partnum, char *disp,
1140 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
1145 ma = (struct ma_info *)cbuserdata;
1146 CtdlLogPrintf(CTDL_DEBUG, "fixed_output_pre() type=<%s>\n", cbtype);
1147 if (!strcasecmp(cbtype, "multipart/alternative")) {
1151 if (!strcasecmp(cbtype, "message/rfc822")) {
1157 * Post callback function for multipart/alternative
1159 void fixed_output_post(char *name, char *filename, char *partnum, char *disp,
1160 void *content, char *cbtype, char *cbcharset, size_t length,
1161 char *encoding, void *cbuserdata)
1165 ma = (struct ma_info *)cbuserdata;
1166 CtdlLogPrintf(CTDL_DEBUG, "fixed_output_post() type=<%s>\n", cbtype);
1167 if (!strcasecmp(cbtype, "multipart/alternative")) {
1171 if (!strcasecmp(cbtype, "message/rfc822")) {
1177 * Inline callback function for mime parser that wants to display text
1179 void fixed_output(char *name, char *filename, char *partnum, char *disp,
1180 void *content, char *cbtype, char *cbcharset, size_t length,
1181 char *encoding, void *cbuserdata)
1188 ma = (struct ma_info *)cbuserdata;
1190 CtdlLogPrintf(CTDL_DEBUG,
1191 "fixed_output() part %s: %s (%s) (%ld bytes)\n",
1192 partnum, filename, cbtype, (long)length);
1195 * If we're in the middle of a multipart/alternative scope and
1196 * we've already printed another section, skip this one.
1198 if ( (ma->is_ma) && (ma->did_print) ) {
1199 CtdlLogPrintf(CTDL_DEBUG, "Skipping part %s (%s)\n", partnum, cbtype);
1204 if ( (!strcasecmp(cbtype, "text/plain"))
1205 || (IsEmptyStr(cbtype)) ) {
1208 client_write(wptr, length);
1209 if (wptr[length-1] != '\n') {
1216 if (!strcasecmp(cbtype, "text/html")) {
1217 ptr = html_to_ascii(content, length, 80, 0);
1219 client_write(ptr, wlen);
1220 if (ptr[wlen-1] != '\n') {
1227 if (ma->use_fo_hooks) {
1228 if (PerformFixedOutputHooks(cbtype, content, length)) {
1229 /* above function returns nonzero if it handled the part */
1234 if (strncasecmp(cbtype, "multipart/", 10)) {
1235 cprintf("Part %s: %s (%s) (%ld bytes)\r\n",
1236 partnum, filename, cbtype, (long)length);
1242 * The client is elegant and sophisticated and wants to be choosy about
1243 * MIME content types, so figure out which multipart/alternative part
1244 * we're going to send.
1246 * We use a system of weights. When we find a part that matches one of the
1247 * MIME types we've declared as preferential, we can store it in ma->chosen_part
1248 * and then set ma->chosen_pref to that MIME type's position in our preference
1249 * list. If we then hit another match, we only replace the first match if
1250 * the preference value is lower.
1252 void choose_preferred(char *name, char *filename, char *partnum, char *disp,
1253 void *content, char *cbtype, char *cbcharset, size_t length,
1254 char *encoding, void *cbuserdata)
1260 ma = (struct ma_info *)cbuserdata;
1262 // NOTE: REMOVING THIS CONDITIONAL FIXES BUG 220
1263 // http://bugzilla.citadel.org/show_bug.cgi?id=220
1264 // I don't know if there are any side effects! Please TEST TEST TEST
1265 //if (ma->is_ma > 0) {
1267 for (i=0; i<num_tokens(CC->preferred_formats, '|'); ++i) {
1268 extract_token(buf, CC->preferred_formats, i, '|', sizeof buf);
1269 if ( (!strcasecmp(buf, cbtype)) && (!ma->freeze) ) {
1270 if (i < ma->chosen_pref) {
1271 CtdlLogPrintf(CTDL_DEBUG, "Setting chosen part: <%s>\n", partnum);
1272 safestrncpy(ma->chosen_part, partnum, sizeof ma->chosen_part);
1273 ma->chosen_pref = i;
1280 * Now that we've chosen our preferred part, output it.
1282 void output_preferred(char *name, char *filename, char *partnum, char *disp,
1283 void *content, char *cbtype, char *cbcharset, size_t length,
1284 char *encoding, void *cbuserdata)
1288 int add_newline = 0;
1292 ma = (struct ma_info *)cbuserdata;
1294 /* This is not the MIME part you're looking for... */
1295 if (strcasecmp(partnum, ma->chosen_part)) return;
1297 /* If the content-type of this part is in our preferred formats
1298 * list, we can simply output it verbatim.
1300 for (i=0; i<num_tokens(CC->preferred_formats, '|'); ++i) {
1301 extract_token(buf, CC->preferred_formats, i, '|', sizeof buf);
1302 if (!strcasecmp(buf, cbtype)) {
1303 /* Yeah! Go! W00t!! */
1305 text_content = (char *)content;
1306 if (text_content[length-1] != '\n') {
1309 cprintf("Content-type: %s", cbtype);
1310 if (!IsEmptyStr(cbcharset)) {
1311 cprintf("; charset=%s", cbcharset);
1313 cprintf("\nContent-length: %d\n",
1314 (int)(length + add_newline) );
1315 if (!IsEmptyStr(encoding)) {
1316 cprintf("Content-transfer-encoding: %s\n", encoding);
1319 cprintf("Content-transfer-encoding: 7bit\n");
1321 cprintf("X-Citadel-MSG4-Partnum: %s\n", partnum);
1323 client_write(content, length);
1324 if (add_newline) cprintf("\n");
1329 /* No translations required or possible: output as text/plain */
1330 cprintf("Content-type: text/plain\n\n");
1331 fixed_output(name, filename, partnum, disp, content, cbtype, cbcharset,
1332 length, encoding, cbuserdata);
1337 char desired_section[64];
1344 * Callback function for
1346 void extract_encapsulated_message(char *name, char *filename, char *partnum, char *disp,
1347 void *content, char *cbtype, char *cbcharset, size_t length,
1348 char *encoding, void *cbuserdata)
1350 struct encapmsg *encap;
1352 encap = (struct encapmsg *)cbuserdata;
1354 /* Only proceed if this is the desired section... */
1355 if (!strcasecmp(encap->desired_section, partnum)) {
1356 encap->msglen = length;
1357 encap->msg = malloc(length + 2);
1358 memcpy(encap->msg, content, length);
1368 * Get a message off disk. (returns om_* values found in msgbase.h)
1371 int CtdlOutputMsg(long msg_num, /* message number (local) to fetch */
1372 int mode, /* how would you like that message? */
1373 int headers_only, /* eschew the message body? */
1374 int do_proto, /* do Citadel protocol responses? */
1375 int crlf, /* Use CRLF newlines instead of LF? */
1376 char *section, /* NULL or a message/rfc822 section */
1377 int flags /* should the bessage be exported clean? */
1379 struct CtdlMessage *TheMessage = NULL;
1380 int retcode = om_no_such_msg;
1381 struct encapmsg encap;
1383 CtdlLogPrintf(CTDL_DEBUG, "CtdlOutputMsg() msgnum=%ld, mode=%d, section=%s\n",
1385 (section ? section : "<>")
1388 if ((!(CC->logged_in)) && (!(CC->internal_pgm))) {
1389 if (do_proto) cprintf("%d Not logged in.\n",
1390 ERROR + NOT_LOGGED_IN);
1391 return(om_not_logged_in);
1394 /* FIXME: check message id against msglist for this room */
1397 * Fetch the message from disk. If we're in any sort of headers
1398 * only mode, request that we don't even bother loading the body
1401 if ( (headers_only == HEADERS_FAST) || (headers_only == HEADERS_ONLY) ) {
1402 TheMessage = CtdlFetchMessage(msg_num, 0);
1405 TheMessage = CtdlFetchMessage(msg_num, 1);
1408 if (TheMessage == NULL) {
1409 if (do_proto) cprintf("%d Can't locate msg %ld on disk\n",
1410 ERROR + MESSAGE_NOT_FOUND, msg_num);
1411 return(om_no_such_msg);
1414 /* Here is the weird form of this command, to process only an
1415 * encapsulated message/rfc822 section.
1417 if (section) if (!IsEmptyStr(section)) if (strcmp(section, "0")) {
1418 memset(&encap, 0, sizeof encap);
1419 safestrncpy(encap.desired_section, section, sizeof encap.desired_section);
1420 mime_parser(TheMessage->cm_fields['M'],
1422 *extract_encapsulated_message,
1423 NULL, NULL, (void *)&encap, 0
1425 CtdlFreeMessage(TheMessage);
1429 encap.msg[encap.msglen] = 0;
1430 TheMessage = convert_internet_message(encap.msg);
1431 encap.msg = NULL; /* no free() here, TheMessage owns it now */
1433 /* Now we let it fall through to the bottom of this
1434 * function, because TheMessage now contains the
1435 * encapsulated message instead of the top-level
1436 * message. Isn't that neat?
1441 if (do_proto) cprintf("%d msg %ld has no part %s\n",
1442 ERROR + MESSAGE_NOT_FOUND, msg_num, section);
1443 retcode = om_no_such_msg;
1448 /* Ok, output the message now */
1449 retcode = CtdlOutputPreLoadedMsg(TheMessage, mode, headers_only, do_proto, crlf, flags);
1450 CtdlFreeMessage(TheMessage);
1456 char *qp_encode_email_addrs(char *source)
1458 char user[256], node[256], name[256];
1459 const char headerStr[] = "=?UTF-8?Q?";
1463 int need_to_encode = 0;
1469 long nAddrPtrMax = 50;
1474 if (source == NULL) return source;
1475 if (IsEmptyStr(source)) return source;
1477 AddrPtr = malloc (sizeof (long) * nAddrPtrMax);
1478 AddrUtf8 = malloc (sizeof (long) * nAddrPtrMax);
1479 memset(AddrUtf8, 0, sizeof (long) * nAddrPtrMax);
1482 while (!IsEmptyStr (&source[i])) {
1483 if (nColons >= nAddrPtrMax){
1486 ptr = (long *) malloc(sizeof (long) * nAddrPtrMax * 2);
1487 memcpy (ptr, AddrPtr, sizeof (long) * nAddrPtrMax);
1488 free (AddrPtr), AddrPtr = ptr;
1490 ptr = (long *) malloc(sizeof (long) * nAddrPtrMax * 2);
1491 memset(&ptr[nAddrPtrMax], 0,
1492 sizeof (long) * nAddrPtrMax);
1494 memcpy (ptr, AddrUtf8, sizeof (long) * nAddrPtrMax);
1495 free (AddrUtf8), AddrUtf8 = ptr;
1498 if (((unsigned char) source[i] < 32) ||
1499 ((unsigned char) source[i] > 126)) {
1501 AddrUtf8[nColons] = 1;
1503 if (source[i] == '"')
1504 InQuotes = !InQuotes;
1505 if (!InQuotes && source[i] == ',') {
1506 AddrPtr[nColons] = i;
1511 if (need_to_encode == 0) {
1518 EncodedMaxLen = nColons * (sizeof(headerStr) + 3) + SourceLen * 3;
1519 Encoded = (char*) malloc (EncodedMaxLen);
1521 for (i = 0; i < nColons; i++)
1522 source[AddrPtr[i]++] = '\0';
1526 for (i = 0; i < nColons && nPtr != NULL; i++) {
1527 nmax = EncodedMaxLen - (nPtr - Encoded);
1529 process_rfc822_addr(&source[AddrPtr[i]],
1533 /* TODO: libIDN here ! */
1534 if (IsEmptyStr(name)) {
1535 n = snprintf(nPtr, nmax,
1536 (i==0)?"%s@%s" : ",%s@%s",
1540 EncodedName = rfc2047encode(name, strlen(name));
1541 n = snprintf(nPtr, nmax,
1542 (i==0)?"%s <%s@%s>" : ",%s <%s@%s>",
1543 EncodedName, user, node);
1548 n = snprintf(nPtr, nmax,
1549 (i==0)?"%s" : ",%s",
1550 &source[AddrPtr[i]]);
1556 ptr = (char*) malloc(EncodedMaxLen * 2);
1557 memcpy(ptr, Encoded, EncodedMaxLen);
1558 nnPtr = ptr + (nPtr - Encoded), nPtr = nnPtr;
1559 free(Encoded), Encoded = ptr;
1561 i--; /* do it once more with properly lengthened buffer */
1564 for (i = 0; i < nColons; i++)
1565 source[--AddrPtr[i]] = ',';
1573 * Get a message off disk. (returns om_* values found in msgbase.h)
1575 int CtdlOutputPreLoadedMsg(
1576 struct CtdlMessage *TheMessage,
1577 int mode, /* how would you like that message? */
1578 int headers_only, /* eschew the message body? */
1579 int do_proto, /* do Citadel protocol responses? */
1580 int crlf, /* Use CRLF newlines instead of LF? */
1581 int flags /* should the bessage be exported clean? */
1585 cit_uint8_t ch, prev_ch;
1587 char display_name[256];
1589 char *nl; /* newline string */
1591 int subject_found = 0;
1594 /* Buffers needed for RFC822 translation. These are all filled
1595 * using functions that are bounds-checked, and therefore we can
1596 * make them substantially smaller than SIZ.
1604 char datestamp[100];
1606 CtdlLogPrintf(CTDL_DEBUG, "CtdlOutputPreLoadedMsg(TheMessage=%s, %d, %d, %d, %d\n",
1607 ((TheMessage == NULL) ? "NULL" : "not null"),
1608 mode, headers_only, do_proto, crlf);
1610 strcpy(mid, "unknown");
1611 nl = (crlf ? "\r\n" : "\n");
1613 if (!is_valid_message(TheMessage)) {
1614 CtdlLogPrintf(CTDL_ERR,
1615 "ERROR: invalid preloaded message for output\n");
1617 return(om_no_such_msg);
1620 /* Are we downloading a MIME component? */
1621 if (mode == MT_DOWNLOAD) {
1622 if (TheMessage->cm_format_type != FMT_RFC822) {
1624 cprintf("%d This is not a MIME message.\n",
1625 ERROR + ILLEGAL_VALUE);
1626 } else if (CC->download_fp != NULL) {
1627 if (do_proto) cprintf(
1628 "%d You already have a download open.\n",
1629 ERROR + RESOURCE_BUSY);
1631 /* Parse the message text component */
1632 mptr = TheMessage->cm_fields['M'];
1633 mime_parser(mptr, NULL, *mime_download, NULL, NULL, NULL, 0);
1634 /* If there's no file open by this time, the requested
1635 * section wasn't found, so print an error
1637 if (CC->download_fp == NULL) {
1638 if (do_proto) cprintf(
1639 "%d Section %s not found.\n",
1640 ERROR + FILE_NOT_FOUND,
1641 CC->download_desired_section);
1644 return((CC->download_fp != NULL) ? om_ok : om_mime_error);
1647 /* MT_SPEW_SECTION is like MT_DOWNLOAD except it outputs the whole MIME part
1648 * in a single server operation instead of opening a download file.
1650 if (mode == MT_SPEW_SECTION) {
1651 if (TheMessage->cm_format_type != FMT_RFC822) {
1653 cprintf("%d This is not a MIME message.\n",
1654 ERROR + ILLEGAL_VALUE);
1656 /* Parse the message text component */
1659 mptr = TheMessage->cm_fields['M'];
1660 mime_parser(mptr, NULL, *mime_spew_section, NULL, NULL, (void *)&found_it, 0);
1661 /* If section wasn't found, print an error
1664 if (do_proto) cprintf(
1665 "%d Section %s not found.\n",
1666 ERROR + FILE_NOT_FOUND,
1667 CC->download_desired_section);
1670 return((CC->download_fp != NULL) ? om_ok : om_mime_error);
1673 /* now for the user-mode message reading loops */
1674 if (do_proto) cprintf("%d msg:\n", LISTING_FOLLOWS);
1676 /* Does the caller want to skip the headers? */
1677 if (headers_only == HEADERS_NONE) goto START_TEXT;
1679 /* Tell the client which format type we're using. */
1680 if ( (mode == MT_CITADEL) && (do_proto) ) {
1681 cprintf("type=%d\n", TheMessage->cm_format_type);
1684 /* nhdr=yes means that we're only displaying headers, no body */
1685 if ( (TheMessage->cm_anon_type == MES_ANONONLY)
1686 && (mode == MT_CITADEL)
1689 cprintf("nhdr=yes\n");
1692 /* begin header processing loop for Citadel message format */
1694 if ((mode == MT_CITADEL) || (mode == MT_MIME)) {
1696 safestrncpy(display_name, "<unknown>", sizeof display_name);
1697 if (TheMessage->cm_fields['A']) {
1698 strcpy(buf, TheMessage->cm_fields['A']);
1699 if (TheMessage->cm_anon_type == MES_ANONONLY) {
1700 safestrncpy(display_name, "****", sizeof display_name);
1702 else if (TheMessage->cm_anon_type == MES_ANONOPT) {
1703 safestrncpy(display_name, "anonymous", sizeof display_name);
1706 safestrncpy(display_name, buf, sizeof display_name);
1708 if ((is_room_aide())
1709 && ((TheMessage->cm_anon_type == MES_ANONONLY)
1710 || (TheMessage->cm_anon_type == MES_ANONOPT))) {
1711 size_t tmp = strlen(display_name);
1712 snprintf(&display_name[tmp],
1713 sizeof display_name - tmp,
1718 /* Don't show Internet address for users on the
1719 * local Citadel network.
1722 if (TheMessage->cm_fields['N'] != NULL)
1723 if (!IsEmptyStr(TheMessage->cm_fields['N']))
1724 if (haschar(TheMessage->cm_fields['N'], '.') == 0) {
1728 /* Now spew the header fields in the order we like them. */
1729 safestrncpy(allkeys, FORDER, sizeof allkeys);
1730 for (i=0; i<strlen(allkeys); ++i) {
1731 k = (int) allkeys[i];
1733 if ( (TheMessage->cm_fields[k] != NULL)
1734 && (msgkeys[k] != NULL) ) {
1736 if (do_proto) cprintf("%s=%s\n",
1740 else if ((k == 'F') && (suppress_f)) {
1743 /* Masquerade display name if needed */
1745 if (do_proto) cprintf("%s=%s\n",
1747 TheMessage->cm_fields[k]
1756 /* begin header processing loop for RFC822 transfer format */
1761 strcpy(snode, NODENAME);
1762 strcpy(lnode, HUMANNODE);
1763 if (mode == MT_RFC822) {
1764 for (i = 0; i < 256; ++i) {
1765 if (TheMessage->cm_fields[i]) {
1766 mptr = mpptr = TheMessage->cm_fields[i];
1769 safestrncpy(luser, mptr, sizeof luser);
1770 safestrncpy(suser, mptr, sizeof suser);
1772 else if (i == 'Y') {
1773 if ((flags & QP_EADDR) != 0)
1774 mptr = qp_encode_email_addrs(mptr);
1775 cprintf("CC: %s%s", mptr, nl);
1777 else if (i == 'P') {
1778 cprintf("Return-Path: %s%s", mptr, nl);
1780 else if (i == 'L') {
1781 cprintf("List-ID: %s%s", mptr, nl);
1783 else if (i == 'V') {
1784 if ((flags & QP_EADDR) != 0)
1785 mptr = qp_encode_email_addrs(mptr);
1786 cprintf("Envelope-To: %s%s", mptr, nl);
1788 else if (i == 'U') {
1789 cprintf("Subject: %s%s", mptr, nl);
1793 safestrncpy(mid, mptr, sizeof mid);
1795 safestrncpy(lnode, mptr, sizeof lnode);
1797 safestrncpy(fuser, mptr, sizeof fuser);
1798 /* else if (i == 'O')
1799 cprintf("X-Citadel-Room: %s%s",
1802 safestrncpy(snode, mptr, sizeof snode);
1805 if (haschar(mptr, '@') == 0)
1807 cprintf("To: %s@%s%s", mptr, config.c_fqdn, nl);
1811 if ((flags & QP_EADDR) != 0)
1812 mptr = qp_encode_email_addrs(mptr);
1813 cprintf("To: %s%s", mptr, nl);
1816 else if (i == 'T') {
1817 datestring(datestamp, sizeof datestamp,
1818 atol(mptr), DATESTRING_RFC822);
1819 cprintf("Date: %s%s", datestamp, nl);
1821 else if (i == 'W') {
1822 cprintf("References: ");
1823 k = num_tokens(mptr, '|');
1824 for (j=0; j<k; ++j) {
1825 extract_token(buf, mptr, j, '|', sizeof buf);
1826 cprintf("<%s>", buf);
1839 if (subject_found == 0) {
1840 cprintf("Subject: (no subject)%s", nl);
1844 for (i=0; !IsEmptyStr(&suser[i]); ++i) {
1845 suser[i] = tolower(suser[i]);
1846 if (!isalnum(suser[i])) suser[i]='_';
1849 if (mode == MT_RFC822) {
1850 if (!strcasecmp(snode, NODENAME)) {
1851 safestrncpy(snode, FQDN, sizeof snode);
1854 /* Construct a fun message id */
1855 cprintf("Message-ID: <%s", mid);/// todo: this possibly breaks threadding mails.
1856 if (strchr(mid, '@')==NULL) {
1857 cprintf("@%s", snode);
1861 if (!is_room_aide() && (TheMessage->cm_anon_type == MES_ANONONLY)) {
1862 cprintf("From: \"----\" <x@x.org>%s", nl);
1864 else if (!is_room_aide() && (TheMessage->cm_anon_type == MES_ANONOPT)) {
1865 cprintf("From: \"anonymous\" <x@x.org>%s", nl);
1867 else if (!IsEmptyStr(fuser)) {
1868 cprintf("From: \"%s\" <%s>%s", luser, fuser, nl);
1871 cprintf("From: \"%s\" <%s@%s>%s", luser, suser, snode, nl);
1874 cprintf("Organization: %s%s", lnode, nl);
1876 /* Blank line signifying RFC822 end-of-headers */
1877 if (TheMessage->cm_format_type != FMT_RFC822) {
1882 /* end header processing loop ... at this point, we're in the text */
1884 if (headers_only == HEADERS_FAST) goto DONE;
1885 mptr = TheMessage->cm_fields['M'];
1887 /* Tell the client about the MIME parts in this message */
1888 if (TheMessage->cm_format_type == FMT_RFC822) {
1889 if ( (mode == MT_CITADEL) || (mode == MT_MIME) ) {
1890 memset(&ma, 0, sizeof(struct ma_info));
1891 mime_parser(mptr, NULL,
1892 (do_proto ? *list_this_part : NULL),
1893 (do_proto ? *list_this_pref : NULL),
1894 (do_proto ? *list_this_suff : NULL),
1897 else if (mode == MT_RFC822) { /* unparsed RFC822 dump */
1898 char *start_of_text = NULL;
1899 start_of_text = strstr(mptr, "\n\r\n");
1900 if (start_of_text == NULL) start_of_text = strstr(mptr, "\n\n");
1901 if (start_of_text == NULL) start_of_text = mptr;
1903 start_of_text = strstr(start_of_text, "\n");
1908 int nllen = strlen(nl);
1910 while (ch=*mptr, ch!=0) {
1916 ((headers_only == HEADERS_NONE) && (mptr >= start_of_text))
1917 || ((headers_only == HEADERS_ONLY) && (mptr < start_of_text))
1918 || ((headers_only != HEADERS_NONE) && (headers_only != HEADERS_ONLY))
1921 sprintf(&outbuf[outlen], "%s", nl);
1925 outbuf[outlen++] = ch;
1929 if (flags & ESC_DOT)
1931 if ((prev_ch == 10) && (ch == '.') && ((*(mptr+1) == 13) || (*(mptr+1) == 10)))
1933 outbuf[outlen++] = '.';
1938 if (outlen > 1000) {
1939 client_write(outbuf, outlen);
1944 client_write(outbuf, outlen);
1952 if (headers_only == HEADERS_ONLY) {
1956 /* signify start of msg text */
1957 if ( (mode == MT_CITADEL) || (mode == MT_MIME) ) {
1958 if (do_proto) cprintf("text\n");
1961 /* If the format type on disk is 1 (fixed-format), then we want
1962 * everything to be output completely literally ... regardless of
1963 * what message transfer format is in use.
1965 if (TheMessage->cm_format_type == FMT_FIXED) {
1967 if (mode == MT_MIME) {
1968 cprintf("Content-type: text/plain\n\n");
1972 while (ch = *mptr++, ch > 0) {
1975 if ((ch == 10) || (buflen > 250)) {
1977 cprintf("%s%s", buf, nl);
1986 if (!IsEmptyStr(buf))
1987 cprintf("%s%s", buf, nl);
1990 /* If the message on disk is format 0 (Citadel vari-format), we
1991 * output using the formatter at 80 columns. This is the final output
1992 * form if the transfer format is RFC822, but if the transfer format
1993 * is Citadel proprietary, it'll still work, because the indentation
1994 * for new paragraphs is correct and the client will reformat the
1995 * message to the reader's screen width.
1997 if (TheMessage->cm_format_type == FMT_CITADEL) {
1998 if (mode == MT_MIME) {
1999 cprintf("Content-type: text/x-citadel-variformat\n\n");
2001 memfmout(mptr, 0, nl);
2004 /* If the message on disk is format 4 (MIME), we've gotta hand it
2005 * off to the MIME parser. The client has already been told that
2006 * this message is format 1 (fixed format), so the callback function
2007 * we use will display those parts as-is.
2009 if (TheMessage->cm_format_type == FMT_RFC822) {
2010 memset(&ma, 0, sizeof(struct ma_info));
2012 if (mode == MT_MIME) {
2013 ma.use_fo_hooks = 0;
2014 strcpy(ma.chosen_part, "1");
2015 ma.chosen_pref = 9999;
2016 mime_parser(mptr, NULL,
2017 *choose_preferred, *fixed_output_pre,
2018 *fixed_output_post, (void *)&ma, 0);
2019 mime_parser(mptr, NULL,
2020 *output_preferred, NULL, NULL, (void *)&ma, CC->msg4_dont_decode);
2023 ma.use_fo_hooks = 1;
2024 mime_parser(mptr, NULL,
2025 *fixed_output, *fixed_output_pre,
2026 *fixed_output_post, (void *)&ma, 0);
2031 DONE: /* now we're done */
2032 if (do_proto) cprintf("000\n");
2039 * display a message (mode 0 - Citadel proprietary)
2041 void cmd_msg0(char *cmdbuf)
2044 int headers_only = HEADERS_ALL;
2046 msgid = extract_long(cmdbuf, 0);
2047 headers_only = extract_int(cmdbuf, 1);
2049 CtdlOutputMsg(msgid, MT_CITADEL, headers_only, 1, 0, NULL, 0);
2055 * display a message (mode 2 - RFC822)
2057 void cmd_msg2(char *cmdbuf)
2060 int headers_only = HEADERS_ALL;
2062 msgid = extract_long(cmdbuf, 0);
2063 headers_only = extract_int(cmdbuf, 1);
2065 CtdlOutputMsg(msgid, MT_RFC822, headers_only, 1, 1, NULL, 0);
2071 * display a message (mode 3 - IGnet raw format - internal programs only)
2073 void cmd_msg3(char *cmdbuf)
2076 struct CtdlMessage *msg = NULL;
2079 if (CC->internal_pgm == 0) {
2080 cprintf("%d This command is for internal programs only.\n",
2081 ERROR + HIGHER_ACCESS_REQUIRED);
2085 msgnum = extract_long(cmdbuf, 0);
2086 msg = CtdlFetchMessage(msgnum, 1);
2088 cprintf("%d Message %ld not found.\n",
2089 ERROR + MESSAGE_NOT_FOUND, msgnum);
2093 serialize_message(&smr, msg);
2094 CtdlFreeMessage(msg);
2097 cprintf("%d Unable to serialize message\n",
2098 ERROR + INTERNAL_ERROR);
2102 cprintf("%d %ld\n", BINARY_FOLLOWS, (long)smr.len);
2103 client_write((char *)smr.ser, (int)smr.len);
2110 * Display a message using MIME content types
2112 void cmd_msg4(char *cmdbuf)
2117 msgid = extract_long(cmdbuf, 0);
2118 extract_token(section, cmdbuf, 1, '|', sizeof section);
2119 CtdlOutputMsg(msgid, MT_MIME, 0, 1, 0, (section[0] ? section : NULL) , 0);
2125 * Client tells us its preferred message format(s)
2127 void cmd_msgp(char *cmdbuf)
2129 if (!strcasecmp(cmdbuf, "dont_decode")) {
2130 CC->msg4_dont_decode = 1;
2131 cprintf("%d MSG4 will not pre-decode messages.\n", CIT_OK);
2134 safestrncpy(CC->preferred_formats, cmdbuf, sizeof(CC->preferred_formats));
2135 cprintf("%d Preferred MIME formats have been set.\n", CIT_OK);
2141 * Open a component of a MIME message as a download file
2143 void cmd_opna(char *cmdbuf)
2146 char desired_section[128];
2148 msgid = extract_long(cmdbuf, 0);
2149 extract_token(desired_section, cmdbuf, 1, '|', sizeof desired_section);
2150 safestrncpy(CC->download_desired_section, desired_section,
2151 sizeof CC->download_desired_section);
2152 CtdlOutputMsg(msgid, MT_DOWNLOAD, 0, 1, 1, NULL, 0);
2157 * Open a component of a MIME message and transmit it all at once
2159 void cmd_dlat(char *cmdbuf)
2162 char desired_section[128];
2164 msgid = extract_long(cmdbuf, 0);
2165 extract_token(desired_section, cmdbuf, 1, '|', sizeof desired_section);
2166 safestrncpy(CC->download_desired_section, desired_section,
2167 sizeof CC->download_desired_section);
2168 CtdlOutputMsg(msgid, MT_SPEW_SECTION, 0, 1, 1, NULL, 0);
2173 * Save one or more message pointers into a specified room
2174 * (Returns 0 for success, nonzero for failure)
2175 * roomname may be NULL to use the current room
2177 * Note that the 'supplied_msg' field may be set to NULL, in which case
2178 * the message will be fetched from disk, by number, if we need to perform
2179 * replication checks. This adds an additional database read, so if the
2180 * caller already has the message in memory then it should be supplied. (Obviously
2181 * this mode of operation only works if we're saving a single message.)
2183 int CtdlSaveMsgPointersInRoom(char *roomname, long newmsgidlist[], int num_newmsgs,
2184 int do_repl_check, struct CtdlMessage *supplied_msg)
2187 char hold_rm[ROOMNAMELEN];
2188 struct cdbdata *cdbfr;
2191 long highest_msg = 0L;
2194 struct CtdlMessage *msg = NULL;
2196 long *msgs_to_be_merged = NULL;
2197 int num_msgs_to_be_merged = 0;
2199 CtdlLogPrintf(CTDL_DEBUG,
2200 "CtdlSaveMsgPointersInRoom(room=%s, num_msgs=%d, repl=%d)\n",
2201 roomname, num_newmsgs, do_repl_check);
2203 strcpy(hold_rm, CC->room.QRname);
2206 if (newmsgidlist == NULL) return(ERROR + INTERNAL_ERROR);
2207 if (num_newmsgs < 1) return(ERROR + INTERNAL_ERROR);
2208 if (num_newmsgs > 1) supplied_msg = NULL;
2210 /* Now the regular stuff */
2211 if (lgetroom(&CC->room,
2212 ((roomname != NULL) ? roomname : CC->room.QRname) )
2214 CtdlLogPrintf(CTDL_ERR, "No such room <%s>\n", roomname);
2215 return(ERROR + ROOM_NOT_FOUND);
2219 msgs_to_be_merged = malloc(sizeof(long) * num_newmsgs);
2220 num_msgs_to_be_merged = 0;
2223 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long));
2224 if (cdbfr == NULL) {
2228 msglist = (long *) cdbfr->ptr;
2229 cdbfr->ptr = NULL; /* CtdlSaveMsgPointerInRoom() now owns this memory */
2230 num_msgs = cdbfr->len / sizeof(long);
2235 /* Create a list of msgid's which were supplied by the caller, but do
2236 * not already exist in the target room. It is absolutely taboo to
2237 * have more than one reference to the same message in a room.
2239 for (i=0; i<num_newmsgs; ++i) {
2241 if (num_msgs > 0) for (j=0; j<num_msgs; ++j) {
2242 if (msglist[j] == newmsgidlist[i]) {
2247 msgs_to_be_merged[num_msgs_to_be_merged++] = newmsgidlist[i];
2251 CtdlLogPrintf(9, "%d unique messages to be merged\n", num_msgs_to_be_merged);
2254 * Now merge the new messages
2256 msglist = realloc(msglist, (sizeof(long) * (num_msgs + num_msgs_to_be_merged)) );
2257 if (msglist == NULL) {
2258 CtdlLogPrintf(CTDL_ALERT, "ERROR: can't realloc message list!\n");
2260 memcpy(&msglist[num_msgs], msgs_to_be_merged, (sizeof(long) * num_msgs_to_be_merged) );
2261 num_msgs += num_msgs_to_be_merged;
2263 /* Sort the message list, so all the msgid's are in order */
2264 num_msgs = sort_msglist(msglist, num_msgs);
2266 /* Determine the highest message number */
2267 highest_msg = msglist[num_msgs - 1];
2269 /* Write it back to disk. */
2270 cdb_store(CDB_MSGLISTS, &CC->room.QRnumber, (int)sizeof(long),
2271 msglist, (int)(num_msgs * sizeof(long)));
2273 /* Free up the memory we used. */
2276 /* Update the highest-message pointer and unlock the room. */
2277 CC->room.QRhighest = highest_msg;
2278 lputroom(&CC->room);
2280 /* Perform replication checks if necessary */
2281 if ( (DoesThisRoomNeedEuidIndexing(&CC->room)) && (do_repl_check) ) {
2282 CtdlLogPrintf(CTDL_DEBUG, "CtdlSaveMsgPointerInRoom() doing repl checks\n");
2284 for (i=0; i<num_msgs_to_be_merged; ++i) {
2285 msgid = msgs_to_be_merged[i];
2287 if (supplied_msg != NULL) {
2291 msg = CtdlFetchMessage(msgid, 0);
2295 ReplicationChecks(msg);
2297 /* If the message has an Exclusive ID, index that... */
2298 if (msg->cm_fields['E'] != NULL) {
2299 index_message_by_euid(msg->cm_fields['E'], &CC->room, msgid);
2302 /* Free up the memory we may have allocated */
2303 if (msg != supplied_msg) {
2304 CtdlFreeMessage(msg);
2312 CtdlLogPrintf(CTDL_DEBUG, "CtdlSaveMsgPointerInRoom() skips repl checks\n");
2315 /* Submit this room for processing by hooks */
2316 PerformRoomHooks(&CC->room);
2318 /* Go back to the room we were in before we wandered here... */
2319 getroom(&CC->room, hold_rm);
2321 /* Bump the reference count for all messages which were merged */
2322 for (i=0; i<num_msgs_to_be_merged; ++i) {
2323 AdjRefCount(msgs_to_be_merged[i], +1);
2326 /* Free up memory... */
2327 if (msgs_to_be_merged != NULL) {
2328 free(msgs_to_be_merged);
2331 /* Return success. */
2337 * This is the same as CtdlSaveMsgPointersInRoom() but it only accepts
2340 int CtdlSaveMsgPointerInRoom(char *roomname, long msgid,
2341 int do_repl_check, struct CtdlMessage *supplied_msg)
2343 return CtdlSaveMsgPointersInRoom(roomname, &msgid, 1, do_repl_check, supplied_msg);
2350 * Message base operation to save a new message to the message store
2351 * (returns new message number)
2353 * This is the back end for CtdlSubmitMsg() and should not be directly
2354 * called by server-side modules.
2357 long send_message(struct CtdlMessage *msg) {
2365 /* Get a new message number */
2366 newmsgid = get_new_message_number();
2367 snprintf(msgidbuf, sizeof msgidbuf, "%010ld@%s", newmsgid, config.c_fqdn);
2369 /* Generate an ID if we don't have one already */
2370 if (msg->cm_fields['I']==NULL) {
2371 msg->cm_fields['I'] = strdup(msgidbuf);
2374 /* If the message is big, set its body aside for storage elsewhere */
2375 if (msg->cm_fields['M'] != NULL) {
2376 if (strlen(msg->cm_fields['M']) > BIGMSG) {
2378 holdM = msg->cm_fields['M'];
2379 msg->cm_fields['M'] = NULL;
2383 /* Serialize our data structure for storage in the database */
2384 serialize_message(&smr, msg);
2387 msg->cm_fields['M'] = holdM;
2391 cprintf("%d Unable to serialize message\n",
2392 ERROR + INTERNAL_ERROR);
2396 /* Write our little bundle of joy into the message base */
2397 if (cdb_store(CDB_MSGMAIN, &newmsgid, (int)sizeof(long),
2398 smr.ser, smr.len) < 0) {
2399 CtdlLogPrintf(CTDL_ERR, "Can't store message\n");
2403 cdb_store(CDB_BIGMSGS,
2413 /* Free the memory we used for the serialized message */
2416 /* Return the *local* message ID to the caller
2417 * (even if we're storing an incoming network message)
2425 * Serialize a struct CtdlMessage into the format used on disk and network.
2427 * This function loads up a "struct ser_ret" (defined in server.h) which
2428 * contains the length of the serialized message and a pointer to the
2429 * serialized message in memory. THE LATTER MUST BE FREED BY THE CALLER.
2431 void serialize_message(struct ser_ret *ret, /* return values */
2432 struct CtdlMessage *msg) /* unserialized msg */
2434 size_t wlen, fieldlen;
2436 static char *forder = FORDER;
2439 * Check for valid message format
2441 if (is_valid_message(msg) == 0) {
2442 CtdlLogPrintf(CTDL_ERR, "serialize_message() aborting due to invalid message\n");
2449 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL)
2450 ret->len = ret->len +
2451 strlen(msg->cm_fields[(int)forder[i]]) + 2;
2453 ret->ser = malloc(ret->len);
2454 if (ret->ser == NULL) {
2455 CtdlLogPrintf(CTDL_ERR, "serialize_message() malloc(%ld) failed: %s\n",
2456 (long)ret->len, strerror(errno));
2463 ret->ser[1] = msg->cm_anon_type;
2464 ret->ser[2] = msg->cm_format_type;
2467 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL) {
2468 fieldlen = strlen(msg->cm_fields[(int)forder[i]]);
2469 ret->ser[wlen++] = (char)forder[i];
2470 safestrncpy((char *)&ret->ser[wlen], msg->cm_fields[(int)forder[i]], fieldlen+1);
2471 wlen = wlen + fieldlen + 1;
2473 if (ret->len != wlen) CtdlLogPrintf(CTDL_ERR, "ERROR: len=%ld wlen=%ld\n",
2474 (long)ret->len, (long)wlen);
2481 * Serialize a struct CtdlMessage into the format used on disk and network.
2483 * This function loads up a "struct ser_ret" (defined in server.h) which
2484 * contains the length of the serialized message and a pointer to the
2485 * serialized message in memory. THE LATTER MUST BE FREED BY THE CALLER.
2487 void dump_message(struct CtdlMessage *msg, /* unserialized msg */
2488 long Siz) /* how many chars ? */
2492 static char *forder = FORDER;
2496 * Check for valid message format
2498 if (is_valid_message(msg) == 0) {
2499 CtdlLogPrintf(CTDL_ERR, "dump_message() aborting due to invalid message\n");
2503 buf = (char*) malloc (Siz + 1);
2507 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL) {
2508 snprintf (buf, Siz, " msg[%c] = %s ...\n", (char) forder[i],
2509 msg->cm_fields[(int)forder[i]]);
2510 client_write (buf, strlen(buf));
2519 * Check to see if any messages already exist in the current room which
2520 * carry the same Exclusive ID as this one. If any are found, delete them.
2522 void ReplicationChecks(struct CtdlMessage *msg) {
2523 long old_msgnum = (-1L);
2525 if (DoesThisRoomNeedEuidIndexing(&CC->room) == 0) return;
2527 CtdlLogPrintf(CTDL_DEBUG, "Performing replication checks in <%s>\n",
2530 /* No exclusive id? Don't do anything. */
2531 if (msg == NULL) return;
2532 if (msg->cm_fields['E'] == NULL) return;
2533 if (IsEmptyStr(msg->cm_fields['E'])) return;
2534 /*CtdlLogPrintf(CTDL_DEBUG, "Exclusive ID: <%s> for room <%s>\n",
2535 msg->cm_fields['E'], CC->room.QRname);*/
2537 old_msgnum = locate_message_by_euid(msg->cm_fields['E'], &CC->room);
2538 if (old_msgnum > 0L) {
2539 CtdlLogPrintf(CTDL_DEBUG, "ReplicationChecks() replacing message %ld\n", old_msgnum);
2540 CtdlDeleteMessages(CC->room.QRname, &old_msgnum, 1, "");
2547 * Save a message to disk and submit it into the delivery system.
2549 long CtdlSubmitMsg(struct CtdlMessage *msg, /* message to save */
2550 struct recptypes *recps, /* recipients (if mail) */
2551 char *force, /* force a particular room? */
2552 int flags /* should the bessage be exported clean? */
2554 char submit_filename[128];
2555 char generated_timestamp[32];
2556 char hold_rm[ROOMNAMELEN];
2557 char actual_rm[ROOMNAMELEN];
2558 char force_room[ROOMNAMELEN];
2559 char content_type[SIZ]; /* We have to learn this */
2560 char recipient[SIZ];
2563 struct ctdluser userbuf;
2565 struct MetaData smi;
2566 FILE *network_fp = NULL;
2567 static int seqnum = 1;
2568 struct CtdlMessage *imsg = NULL;
2570 size_t instr_alloc = 0;
2572 char *hold_R, *hold_D;
2573 char *collected_addresses = NULL;
2574 struct addresses_to_be_filed *aptr = NULL;
2575 char *saved_rfc822_version = NULL;
2576 int qualified_for_journaling = 0;
2577 struct CitContext *CCC = CC; /* CachedCitContext - performance boost */
2578 char bounce_to[1024] = "";
2580 CtdlLogPrintf(CTDL_DEBUG, "CtdlSubmitMsg() called\n");
2581 if (is_valid_message(msg) == 0) return(-1); /* self check */
2583 /* If this message has no timestamp, we take the liberty of
2584 * giving it one, right now.
2586 if (msg->cm_fields['T'] == NULL) {
2587 snprintf(generated_timestamp, sizeof generated_timestamp, "%ld", (long)time(NULL));
2588 msg->cm_fields['T'] = strdup(generated_timestamp);
2591 /* If this message has no path, we generate one.
2593 if (msg->cm_fields['P'] == NULL) {
2594 if (msg->cm_fields['A'] != NULL) {
2595 msg->cm_fields['P'] = strdup(msg->cm_fields['A']);
2596 for (a=0; !IsEmptyStr(&msg->cm_fields['P'][a]); ++a) {
2597 if (isspace(msg->cm_fields['P'][a])) {
2598 msg->cm_fields['P'][a] = ' ';
2603 msg->cm_fields['P'] = strdup("unknown");
2607 if (force == NULL) {
2608 strcpy(force_room, "");
2611 strcpy(force_room, force);
2614 /* Learn about what's inside, because it's what's inside that counts */
2615 if (msg->cm_fields['M'] == NULL) {
2616 CtdlLogPrintf(CTDL_ERR, "ERROR: attempt to save message with NULL body\n");
2620 switch (msg->cm_format_type) {
2622 strcpy(content_type, "text/x-citadel-variformat");
2625 strcpy(content_type, "text/plain");
2628 strcpy(content_type, "text/plain");
2629 mptr = bmstrcasestr(msg->cm_fields['M'], "Content-type:");
2632 safestrncpy(content_type, &mptr[13], sizeof content_type);
2633 striplt(content_type);
2634 aptr = content_type;
2635 while (!IsEmptyStr(aptr)) {
2647 /* Goto the correct room */
2648 CtdlLogPrintf(CTDL_DEBUG, "Selected room %s\n", (recps) ? CCC->room.QRname : SENTITEMS);
2649 strcpy(hold_rm, CCC->room.QRname);
2650 strcpy(actual_rm, CCC->room.QRname);
2651 if (recps != NULL) {
2652 strcpy(actual_rm, SENTITEMS);
2655 /* If the user is a twit, move to the twit room for posting */
2657 if (CCC->user.axlevel == 2) {
2658 strcpy(hold_rm, actual_rm);
2659 strcpy(actual_rm, config.c_twitroom);
2660 CtdlLogPrintf(CTDL_DEBUG, "Diverting to twit room\n");
2664 /* ...or if this message is destined for Aide> then go there. */
2665 if (!IsEmptyStr(force_room)) {
2666 strcpy(actual_rm, force_room);
2669 CtdlLogPrintf(CTDL_DEBUG, "Final selection: %s\n", actual_rm);
2670 if (strcasecmp(actual_rm, CCC->room.QRname)) {
2671 /* getroom(&CCC->room, actual_rm); */
2672 usergoto(actual_rm, 0, 1, NULL, NULL);
2676 * If this message has no O (room) field, generate one.
2678 if (msg->cm_fields['O'] == NULL) {
2679 msg->cm_fields['O'] = strdup(CCC->room.QRname);
2682 /* Perform "before save" hooks (aborting if any return nonzero) */
2683 CtdlLogPrintf(CTDL_DEBUG, "Performing before-save hooks\n");
2684 if (PerformMessageHooks(msg, EVT_BEFORESAVE) > 0) return(-3);
2687 * If this message has an Exclusive ID, and the room is replication
2688 * checking enabled, then do replication checks.
2690 if (DoesThisRoomNeedEuidIndexing(&CCC->room)) {
2691 ReplicationChecks(msg);
2694 /* Save it to disk */
2695 CtdlLogPrintf(CTDL_DEBUG, "Saving to disk\n");
2696 newmsgid = send_message(msg);
2697 if (newmsgid <= 0L) return(-5);
2699 /* Write a supplemental message info record. This doesn't have to
2700 * be a critical section because nobody else knows about this message
2703 CtdlLogPrintf(CTDL_DEBUG, "Creating MetaData record\n");
2704 memset(&smi, 0, sizeof(struct MetaData));
2705 smi.meta_msgnum = newmsgid;
2706 smi.meta_refcount = 0;
2707 safestrncpy(smi.meta_content_type, content_type,
2708 sizeof smi.meta_content_type);
2711 * Measure how big this message will be when rendered as RFC822.
2712 * We do this for two reasons:
2713 * 1. We need the RFC822 length for the new metadata record, so the
2714 * POP and IMAP services don't have to calculate message lengths
2715 * while the user is waiting (multiplied by potentially hundreds
2716 * or thousands of messages).
2717 * 2. If journaling is enabled, we will need an RFC822 version of the
2718 * message to attach to the journalized copy.
2720 if (CCC->redirect_buffer != NULL) {
2721 CtdlLogPrintf(CTDL_ALERT, "CCC->redirect_buffer is not NULL during message submission!\n");
2724 CCC->redirect_buffer = malloc(SIZ);
2725 CCC->redirect_len = 0;
2726 CCC->redirect_alloc = SIZ;
2727 CtdlOutputPreLoadedMsg(msg, MT_RFC822, HEADERS_ALL, 0, 1, QP_EADDR);
2728 smi.meta_rfc822_length = CCC->redirect_len;
2729 saved_rfc822_version = CCC->redirect_buffer;
2730 CCC->redirect_buffer = NULL;
2731 CCC->redirect_len = 0;
2732 CCC->redirect_alloc = 0;
2736 /* Now figure out where to store the pointers */
2737 CtdlLogPrintf(CTDL_DEBUG, "Storing pointers\n");
2739 /* If this is being done by the networker delivering a private
2740 * message, we want to BYPASS saving the sender's copy (because there
2741 * is no local sender; it would otherwise go to the Trashcan).
2743 if ((!CCC->internal_pgm) || (recps == NULL)) {
2744 if (CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 1, msg) != 0) {
2745 CtdlLogPrintf(CTDL_ERR, "ERROR saving message pointer!\n");
2746 CtdlSaveMsgPointerInRoom(config.c_aideroom, newmsgid, 0, msg);
2750 /* For internet mail, drop a copy in the outbound queue room */
2752 if (recps->num_internet > 0) {
2753 CtdlSaveMsgPointerInRoom(SMTP_SPOOLOUT_ROOM, newmsgid, 0, msg);
2756 /* If other rooms are specified, drop them there too. */
2758 if (recps->num_room > 0)
2759 for (i=0; i<num_tokens(recps->recp_room, '|'); ++i) {
2760 extract_token(recipient, recps->recp_room, i,
2761 '|', sizeof recipient);
2762 CtdlLogPrintf(CTDL_DEBUG, "Delivering to room <%s>\n", recipient);
2763 CtdlSaveMsgPointerInRoom(recipient, newmsgid, 0, msg);
2766 /* Bump this user's messages posted counter. */
2767 CtdlLogPrintf(CTDL_DEBUG, "Updating user\n");
2768 lgetuser(&CCC->user, CCC->curr_user);
2769 CCC->user.posted = CCC->user.posted + 1;
2770 lputuser(&CCC->user);
2772 /* Decide where bounces need to be delivered */
2773 if (CCC->logged_in) {
2774 snprintf(bounce_to, sizeof bounce_to, "%s@%s", CCC->user.fullname, config.c_nodename);
2777 snprintf(bounce_to, sizeof bounce_to, "%s@%s", msg->cm_fields['A'], msg->cm_fields['N']);
2780 /* If this is private, local mail, make a copy in the
2781 * recipient's mailbox and bump the reference count.
2784 if (recps->num_local > 0)
2785 for (i=0; i<num_tokens(recps->recp_local, '|'); ++i) {
2786 extract_token(recipient, recps->recp_local, i,
2787 '|', sizeof recipient);
2788 CtdlLogPrintf(CTDL_DEBUG, "Delivering private local mail to <%s>\n",
2790 if (getuser(&userbuf, recipient) == 0) {
2791 // Add a flag so the Funambol module knows its mail
2792 msg->cm_fields['W'] = strdup(recipient);
2793 MailboxName(actual_rm, sizeof actual_rm, &userbuf, MAILROOM);
2794 CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 0, msg);
2795 BumpNewMailCounter(userbuf.usernum);
2796 if (!IsEmptyStr(config.c_funambol_host) || !IsEmptyStr(config.c_pager_program)) {
2797 /* Generate a instruction message for the Funambol notification
2798 * server, in the same style as the SMTP queue
2801 instr = malloc(instr_alloc);
2802 snprintf(instr, instr_alloc,
2803 "Content-type: %s\n\nmsgid|%ld\nsubmitted|%ld\n"
2805 SPOOLMIME, newmsgid, (long)time(NULL),
2809 imsg = malloc(sizeof(struct CtdlMessage));
2810 memset(imsg, 0, sizeof(struct CtdlMessage));
2811 imsg->cm_magic = CTDLMESSAGE_MAGIC;
2812 imsg->cm_anon_type = MES_NORMAL;
2813 imsg->cm_format_type = FMT_RFC822;
2814 imsg->cm_fields['A'] = strdup("Citadel");
2815 imsg->cm_fields['J'] = strdup("do not journal");
2816 imsg->cm_fields['M'] = instr; /* imsg owns this memory now */
2817 imsg->cm_fields['W'] = strdup(recipient);
2818 CtdlSubmitMsg(imsg, NULL, FNBL_QUEUE_ROOM, 0);
2819 CtdlFreeMessage(imsg);
2823 CtdlLogPrintf(CTDL_DEBUG, "No user <%s>\n", recipient);
2824 CtdlSaveMsgPointerInRoom(config.c_aideroom,
2829 /* Perform "after save" hooks */
2830 CtdlLogPrintf(CTDL_DEBUG, "Performing after-save hooks\n");
2831 PerformMessageHooks(msg, EVT_AFTERSAVE);
2833 /* For IGnet mail, we have to save a new copy into the spooler for
2834 * each recipient, with the R and D fields set to the recipient and
2835 * destination-node. This has two ugly side effects: all other
2836 * recipients end up being unlisted in this recipient's copy of the
2837 * message, and it has to deliver multiple messages to the same
2838 * node. We'll revisit this again in a year or so when everyone has
2839 * a network spool receiver that can handle the new style messages.
2842 if (recps->num_ignet > 0)
2843 for (i=0; i<num_tokens(recps->recp_ignet, '|'); ++i) {
2844 extract_token(recipient, recps->recp_ignet, i,
2845 '|', sizeof recipient);
2847 hold_R = msg->cm_fields['R'];
2848 hold_D = msg->cm_fields['D'];
2849 msg->cm_fields['R'] = malloc(SIZ);
2850 msg->cm_fields['D'] = malloc(128);
2851 extract_token(msg->cm_fields['R'], recipient, 0, '@', SIZ);
2852 extract_token(msg->cm_fields['D'], recipient, 1, '@', 128);
2854 serialize_message(&smr, msg);
2856 snprintf(submit_filename, sizeof submit_filename,
2857 "%s/netmail.%04lx.%04x.%04x",
2859 (long) getpid(), CCC->cs_pid, ++seqnum);
2860 network_fp = fopen(submit_filename, "wb+");
2861 if (network_fp != NULL) {
2862 fwrite(smr.ser, smr.len, 1, network_fp);
2868 free(msg->cm_fields['R']);
2869 free(msg->cm_fields['D']);
2870 msg->cm_fields['R'] = hold_R;
2871 msg->cm_fields['D'] = hold_D;
2874 /* Go back to the room we started from */
2875 CtdlLogPrintf(CTDL_DEBUG, "Returning to original room %s\n", hold_rm);
2876 if (strcasecmp(hold_rm, CCC->room.QRname))
2877 usergoto(hold_rm, 0, 1, NULL, NULL);
2879 /* For internet mail, generate delivery instructions.
2880 * Yes, this is recursive. Deal with it. Infinite recursion does
2881 * not happen because the delivery instructions message does not
2882 * contain a recipient.
2885 if (recps->num_internet > 0) {
2886 CtdlLogPrintf(CTDL_DEBUG, "Generating delivery instructions\n");
2888 instr = malloc(instr_alloc);
2889 snprintf(instr, instr_alloc,
2890 "Content-type: %s\n\nmsgid|%ld\nsubmitted|%ld\n"
2892 SPOOLMIME, newmsgid, (long)time(NULL),
2896 for (i=0; i<num_tokens(recps->recp_internet, '|'); ++i) {
2897 size_t tmp = strlen(instr);
2898 extract_token(recipient, recps->recp_internet, i, '|', sizeof recipient);
2899 if ((tmp + strlen(recipient) + 32) > instr_alloc) {
2900 instr_alloc = instr_alloc * 2;
2901 instr = realloc(instr, instr_alloc);
2903 snprintf(&instr[tmp], instr_alloc - tmp, "remote|%s|0||\n", recipient);
2906 imsg = malloc(sizeof(struct CtdlMessage));
2907 memset(imsg, 0, sizeof(struct CtdlMessage));
2908 imsg->cm_magic = CTDLMESSAGE_MAGIC;
2909 imsg->cm_anon_type = MES_NORMAL;
2910 imsg->cm_format_type = FMT_RFC822;
2911 imsg->cm_fields['A'] = strdup("Citadel");
2912 imsg->cm_fields['J'] = strdup("do not journal");
2913 imsg->cm_fields['M'] = instr; /* imsg owns this memory now */
2914 CtdlSubmitMsg(imsg, NULL, SMTP_SPOOLOUT_ROOM, QP_EADDR);
2915 CtdlFreeMessage(imsg);
2919 * Any addresses to harvest for someone's address book?
2921 if ( (CCC->logged_in) && (recps != NULL) ) {
2922 collected_addresses = harvest_collected_addresses(msg);
2925 if (collected_addresses != NULL) {
2926 aptr = (struct addresses_to_be_filed *)
2927 malloc(sizeof(struct addresses_to_be_filed));
2928 MailboxName(actual_rm, sizeof actual_rm,
2929 &CCC->user, USERCONTACTSROOM);
2930 aptr->roomname = strdup(actual_rm);
2931 aptr->collected_addresses = collected_addresses;
2932 begin_critical_section(S_ATBF);
2935 end_critical_section(S_ATBF);
2939 * Determine whether this message qualifies for journaling.
2941 if (msg->cm_fields['J'] != NULL) {
2942 qualified_for_journaling = 0;
2945 if (recps == NULL) {
2946 qualified_for_journaling = config.c_journal_pubmsgs;
2948 else if (recps->num_local + recps->num_ignet + recps->num_internet > 0) {
2949 qualified_for_journaling = config.c_journal_email;
2952 qualified_for_journaling = config.c_journal_pubmsgs;
2957 * Do we have to perform journaling? If so, hand off the saved
2958 * RFC822 version will be handed off to the journaler for background
2959 * submit. Otherwise, we have to free the memory ourselves.
2961 if (saved_rfc822_version != NULL) {
2962 if (qualified_for_journaling) {
2963 JournalBackgroundSubmit(msg, saved_rfc822_version, recps);
2966 free(saved_rfc822_version);
2979 * Convenience function for generating small administrative messages.
2981 void quickie_message(char *from, char *fromaddr, char *to, char *room, char *text,
2982 int format_type, char *subject)
2984 struct CtdlMessage *msg;
2985 struct recptypes *recp = NULL;
2987 msg = malloc(sizeof(struct CtdlMessage));
2988 memset(msg, 0, sizeof(struct CtdlMessage));
2989 msg->cm_magic = CTDLMESSAGE_MAGIC;
2990 msg->cm_anon_type = MES_NORMAL;
2991 msg->cm_format_type = format_type;
2994 msg->cm_fields['A'] = strdup(from);
2996 else if (fromaddr != NULL) {
2997 msg->cm_fields['A'] = strdup(fromaddr);
2998 if (strchr(msg->cm_fields['A'], '@')) {
2999 *strchr(msg->cm_fields['A'], '@') = 0;
3003 msg->cm_fields['A'] = strdup("Citadel");
3006 if (fromaddr != NULL) msg->cm_fields['F'] = strdup(fromaddr);
3007 if (room != NULL) msg->cm_fields['O'] = strdup(room);
3008 msg->cm_fields['N'] = strdup(NODENAME);
3010 msg->cm_fields['R'] = strdup(to);
3011 recp = validate_recipients(to, NULL, 0);
3013 if (subject != NULL) {
3014 msg->cm_fields['U'] = strdup(subject);
3016 msg->cm_fields['M'] = strdup(text);
3018 CtdlSubmitMsg(msg, recp, room, 0);
3019 CtdlFreeMessage(msg);
3020 if (recp != NULL) free_recipients(recp);
3026 * Back end function used by CtdlMakeMessage() and similar functions
3028 char *CtdlReadMessageBody(char *terminator, /* token signalling EOT */
3029 size_t maxlen, /* maximum message length */
3030 char *exist, /* if non-null, append to it;
3031 exist is ALWAYS freed */
3032 int crlf, /* CRLF newlines instead of LF */
3033 int sock /* socket handle or 0 for this session's client socket */
3037 size_t message_len = 0;
3038 size_t buffer_len = 0;
3045 if (exist == NULL) {
3052 message_len = strlen(exist);
3053 buffer_len = message_len + 4096;
3054 m = realloc(exist, buffer_len);
3061 /* Do we need to change leading ".." to "." for SMTP escaping? */
3062 if (!strcmp(terminator, ".")) {
3066 /* flush the input if we have nowhere to store it */
3071 /* read in the lines of message text one by one */
3074 if (sock_getln(sock, buf, (sizeof buf - 3)) < 0) finished = 1;
3077 if (client_getln(buf, (sizeof buf - 3)) < 1) finished = 1;
3079 if (!strcmp(buf, terminator)) finished = 1;
3081 strcat(buf, "\r\n");
3087 /* Unescape SMTP-style input of two dots at the beginning of the line */
3089 if (!strncmp(buf, "..", 2)) {
3090 strcpy(buf, &buf[1]);
3094 if ( (!flushing) && (!finished) ) {
3095 /* Measure the line */
3096 linelen = strlen(buf);
3098 /* augment the buffer if we have to */
3099 if ((message_len + linelen) >= buffer_len) {
3100 ptr = realloc(m, (buffer_len * 2) );
3101 if (ptr == NULL) { /* flush if can't allocate */
3104 buffer_len = (buffer_len * 2);
3106 CtdlLogPrintf(CTDL_DEBUG, "buffer_len is now %ld\n", (long)buffer_len);
3110 /* Add the new line to the buffer. NOTE: this loop must avoid
3111 * using functions like strcat() and strlen() because they
3112 * traverse the entire buffer upon every call, and doing that
3113 * for a multi-megabyte message slows it down beyond usability.
3115 strcpy(&m[message_len], buf);
3116 message_len += linelen;
3119 /* if we've hit the max msg length, flush the rest */
3120 if (message_len >= maxlen) flushing = 1;
3122 } while (!finished);
3130 * Build a binary message to be saved on disk.
3131 * (NOTE: if you supply 'preformatted_text', the buffer you give it
3132 * will become part of the message. This means you are no longer
3133 * responsible for managing that memory -- it will be freed along with
3134 * the rest of the fields when CtdlFreeMessage() is called.)
3137 struct CtdlMessage *CtdlMakeMessage(
3138 struct ctdluser *author, /* author's user structure */
3139 char *recipient, /* NULL if it's not mail */
3140 char *recp_cc, /* NULL if it's not mail */
3141 char *room, /* room where it's going */
3142 int type, /* see MES_ types in header file */
3143 int format_type, /* variformat, plain text, MIME... */
3144 char *fake_name, /* who we're masquerading as */
3145 char *my_email, /* which of my email addresses to use (empty is ok) */
3146 char *subject, /* Subject (optional) */
3147 char *supplied_euid, /* ...or NULL if this is irrelevant */
3148 char *preformatted_text, /* ...or NULL to read text from client */
3149 char *references /* Thread references */
3151 char dest_node[256];
3153 struct CtdlMessage *msg;
3155 msg = malloc(sizeof(struct CtdlMessage));
3156 memset(msg, 0, sizeof(struct CtdlMessage));
3157 msg->cm_magic = CTDLMESSAGE_MAGIC;
3158 msg->cm_anon_type = type;
3159 msg->cm_format_type = format_type;
3161 /* Don't confuse the poor folks if it's not routed mail. */
3162 strcpy(dest_node, "");
3167 /* Path or Return-Path */
3168 if (my_email == NULL) my_email = "";
3170 if (!IsEmptyStr(my_email)) {
3171 msg->cm_fields['P'] = strdup(my_email);
3174 snprintf(buf, sizeof buf, "%s", author->fullname);
3175 msg->cm_fields['P'] = strdup(buf);
3177 convert_spaces_to_underscores(msg->cm_fields['P']);
3179 snprintf(buf, sizeof buf, "%ld", (long)time(NULL)); /* timestamp */
3180 msg->cm_fields['T'] = strdup(buf);
3182 if (fake_name[0]) /* author */
3183 msg->cm_fields['A'] = strdup(fake_name);
3185 msg->cm_fields['A'] = strdup(author->fullname);
3187 if (CC->room.QRflags & QR_MAILBOX) { /* room */
3188 msg->cm_fields['O'] = strdup(&CC->room.QRname[11]);
3191 msg->cm_fields['O'] = strdup(CC->room.QRname);
3194 msg->cm_fields['N'] = strdup(NODENAME); /* nodename */
3195 msg->cm_fields['H'] = strdup(HUMANNODE); /* hnodename */
3197 if (recipient[0] != 0) {
3198 msg->cm_fields['R'] = strdup(recipient);
3200 if (recp_cc[0] != 0) {
3201 msg->cm_fields['Y'] = strdup(recp_cc);
3203 if (dest_node[0] != 0) {
3204 msg->cm_fields['D'] = strdup(dest_node);
3207 if (!IsEmptyStr(my_email)) {
3208 msg->cm_fields['F'] = strdup(my_email);
3210 else if ( (author == &CC->user) && (!IsEmptyStr(CC->cs_inet_email)) ) {
3211 msg->cm_fields['F'] = strdup(CC->cs_inet_email);
3214 if (subject != NULL) {
3217 length = strlen(subject);
3223 while ((subject[i] != '\0') &&
3224 (IsAscii = isascii(subject[i]) != 0 ))
3227 msg->cm_fields['U'] = strdup(subject);
3228 else /* ok, we've got utf8 in the string. */
3230 msg->cm_fields['U'] = rfc2047encode(subject, length);
3236 if (supplied_euid != NULL) {
3237 msg->cm_fields['E'] = strdup(supplied_euid);
3240 if (references != NULL) {
3241 if (!IsEmptyStr(references)) {
3242 msg->cm_fields['W'] = strdup(references);
3246 if (preformatted_text != NULL) {
3247 msg->cm_fields['M'] = preformatted_text;
3250 msg->cm_fields['M'] = CtdlReadMessageBody("000", config.c_maxmsglen, NULL, 0, 0);
3258 * Check to see whether we have permission to post a message in the current
3259 * room. Returns a *CITADEL ERROR CODE* and puts a message in errmsgbuf, or
3260 * returns 0 on success.
3262 int CtdlDoIHavePermissionToPostInThisRoom(char *errmsgbuf,
3264 const char* RemoteIdentifier,
3268 if (!(CC->logged_in) &&
3269 (PostPublic == POST_LOGGED_IN)) {
3270 snprintf(errmsgbuf, n, "Not logged in.");
3271 return (ERROR + NOT_LOGGED_IN);
3273 else if (PostPublic == CHECK_EXISTANCE) {
3274 return (0); // We're Evaling whether a recipient exists
3276 else if (!(CC->logged_in)) {
3278 if ((CC->room.QRflags & QR_READONLY)) {
3279 snprintf(errmsgbuf, n, "Not logged in.");
3280 return (ERROR + NOT_LOGGED_IN);
3282 if (CC->room.QRflags2 & QR2_MODERATED) {
3283 snprintf(errmsgbuf, n, "Not logged in Moderation feature not yet implemented!");
3284 return (ERROR + NOT_LOGGED_IN);
3286 if ((PostPublic!=POST_LMTP) &&(CC->room.QRflags2 & QR2_SMTP_PUBLIC) == 0) {
3291 if (RemoteIdentifier == NULL)
3293 snprintf(errmsgbuf, n, "Need sender to permit access.");
3294 return (ERROR + USERNAME_REQUIRED);
3297 assoc_file_name(filename, sizeof filename, &CC->room, ctdl_netcfg_dir);
3298 begin_critical_section(S_NETCONFIGS);
3299 if (!read_spoolcontrol_file(&sc, filename))
3301 end_critical_section(S_NETCONFIGS);
3302 snprintf(errmsgbuf, n,
3303 "This mailing list only accepts posts from subscribers.");
3304 return (ERROR + NO_SUCH_USER);
3306 end_critical_section(S_NETCONFIGS);
3307 found = is_recipient (sc, RemoteIdentifier);
3308 free_spoolcontrol_struct(&sc);
3313 snprintf(errmsgbuf, n,
3314 "This mailing list only accepts posts from subscribers.");
3315 return (ERROR + NO_SUCH_USER);
3322 if ((CC->user.axlevel < 2)
3323 && ((CC->room.QRflags & QR_MAILBOX) == 0)) {
3324 snprintf(errmsgbuf, n, "Need to be validated to enter "
3325 "(except in %s> to sysop)", MAILROOM);
3326 return (ERROR + HIGHER_ACCESS_REQUIRED);
3329 CtdlRoomAccess(&CC->room, &CC->user, &ra, NULL);
3330 if (!(ra & UA_POSTALLOWED)) {
3331 snprintf(errmsgbuf, n, "Higher access is required to post in this room.");
3332 return (ERROR + HIGHER_ACCESS_REQUIRED);
3335 strcpy(errmsgbuf, "Ok");
3341 * Check to see if the specified user has Internet mail permission
3342 * (returns nonzero if permission is granted)
3344 int CtdlCheckInternetMailPermission(struct ctdluser *who) {
3346 /* Do not allow twits to send Internet mail */
3347 if (who->axlevel <= 2) return(0);
3349 /* Globally enabled? */
3350 if (config.c_restrict == 0) return(1);
3352 /* User flagged ok? */
3353 if (who->flags & US_INTERNET) return(2);
3355 /* Aide level access? */
3356 if (who->axlevel >= 6) return(3);
3358 /* No mail for you! */
3364 * Validate recipients, count delivery types and errors, and handle aliasing
3365 * FIXME check for dupes!!!!!
3367 * Returns 0 if all addresses are ok, ret->num_error = -1 if no addresses
3368 * were specified, or the number of addresses found invalid.
3370 * Caller needs to free the result using free_recipients()
3372 struct recptypes *validate_recipients(char *supplied_recipients,
3373 const char *RemoteIdentifier,
3375 struct recptypes *ret;
3376 char *recipients = NULL;
3377 char this_recp[256];
3378 char this_recp_cooked[256];
3384 struct ctdluser tempUS;
3385 struct ctdlroom tempQR;
3386 struct ctdlroom tempQR2;
3392 ret = (struct recptypes *) malloc(sizeof(struct recptypes));
3393 if (ret == NULL) return(NULL);
3395 /* Set all strings to null and numeric values to zero */
3396 memset(ret, 0, sizeof(struct recptypes));
3398 if (supplied_recipients == NULL) {
3399 recipients = strdup("");
3402 recipients = strdup(supplied_recipients);
3405 /* Allocate some memory. Yes, this allocates 500% more memory than we will
3406 * actually need, but it's healthier for the heap than doing lots of tiny
3407 * realloc() calls instead.
3410 ret->errormsg = malloc(strlen(recipients) + 1024);
3411 ret->recp_local = malloc(strlen(recipients) + 1024);
3412 ret->recp_internet = malloc(strlen(recipients) + 1024);
3413 ret->recp_ignet = malloc(strlen(recipients) + 1024);
3414 ret->recp_room = malloc(strlen(recipients) + 1024);
3415 ret->display_recp = malloc(strlen(recipients) + 1024);
3417 ret->errormsg[0] = 0;
3418 ret->recp_local[0] = 0;
3419 ret->recp_internet[0] = 0;
3420 ret->recp_ignet[0] = 0;
3421 ret->recp_room[0] = 0;
3422 ret->display_recp[0] = 0;
3424 ret->recptypes_magic = RECPTYPES_MAGIC;
3426 /* Change all valid separator characters to commas */
3427 for (i=0; !IsEmptyStr(&recipients[i]); ++i) {
3428 if ((recipients[i] == ';') || (recipients[i] == '|')) {
3429 recipients[i] = ',';
3433 /* Now start extracting recipients... */
3435 while (!IsEmptyStr(recipients)) {
3437 for (i=0; i<=strlen(recipients); ++i) {
3438 if (recipients[i] == '\"') in_quotes = 1 - in_quotes;
3439 if ( ( (recipients[i] == ',') && (!in_quotes) ) || (recipients[i] == 0) ) {
3440 safestrncpy(this_recp, recipients, i+1);
3442 if (recipients[i] == ',') {
3443 strcpy(recipients, &recipients[i+1]);
3446 strcpy(recipients, "");
3453 if (IsEmptyStr(this_recp))
3455 CtdlLogPrintf(CTDL_DEBUG, "Evaluating recipient #%d: %s\n", num_recps, this_recp);
3457 mailtype = alias(this_recp);
3458 mailtype = alias(this_recp);
3459 mailtype = alias(this_recp);
3461 for (j=0; !IsEmptyStr(&this_recp[j]); ++j) {
3462 if (this_recp[j]=='_') {
3463 this_recp_cooked[j] = ' ';
3466 this_recp_cooked[j] = this_recp[j];
3469 this_recp_cooked[j] = '\0';
3474 if (!strcasecmp(this_recp, "sysop")) {
3476 strcpy(this_recp, config.c_aideroom);
3477 if (!IsEmptyStr(ret->recp_room)) {
3478 strcat(ret->recp_room, "|");
3480 strcat(ret->recp_room, this_recp);
3482 else if ( (!strncasecmp(this_recp, "room_", 5))
3483 && (!getroom(&tempQR, &this_recp_cooked[5])) ) {
3485 /* Save room so we can restore it later */
3489 /* Check permissions to send mail to this room */
3490 err = CtdlDoIHavePermissionToPostInThisRoom(errmsg,
3502 if (!IsEmptyStr(ret->recp_room)) {
3503 strcat(ret->recp_room, "|");
3505 strcat(ret->recp_room, &this_recp_cooked[5]);
3508 /* Restore room in case something needs it */
3512 else if (getuser(&tempUS, this_recp) == 0) {
3514 strcpy(this_recp, tempUS.fullname);
3515 if (!IsEmptyStr(ret->recp_local)) {
3516 strcat(ret->recp_local, "|");
3518 strcat(ret->recp_local, this_recp);
3520 else if (getuser(&tempUS, this_recp_cooked) == 0) {
3522 strcpy(this_recp, tempUS.fullname);
3523 if (!IsEmptyStr(ret->recp_local)) {
3524 strcat(ret->recp_local, "|");
3526 strcat(ret->recp_local, this_recp);
3534 /* Yes, you're reading this correctly: if the target
3535 * domain points back to the local system or an attached
3536 * Citadel directory, the address is invalid. That's
3537 * because if the address were valid, we would have
3538 * already translated it to a local address by now.
3540 if (IsDirectory(this_recp, 0)) {
3545 ++ret->num_internet;
3546 if (!IsEmptyStr(ret->recp_internet)) {
3547 strcat(ret->recp_internet, "|");
3549 strcat(ret->recp_internet, this_recp);
3554 if (!IsEmptyStr(ret->recp_ignet)) {
3555 strcat(ret->recp_ignet, "|");
3557 strcat(ret->recp_ignet, this_recp);
3565 if (IsEmptyStr(errmsg)) {
3566 snprintf(append, sizeof append, "Invalid recipient: %s", this_recp);
3569 snprintf(append, sizeof append, "%s", errmsg);
3571 if ( (strlen(ret->errormsg) + strlen(append) + 3) < SIZ) {
3572 if (!IsEmptyStr(ret->errormsg)) {
3573 strcat(ret->errormsg, "; ");
3575 strcat(ret->errormsg, append);
3579 if (IsEmptyStr(ret->display_recp)) {
3580 strcpy(append, this_recp);
3583 snprintf(append, sizeof append, ", %s", this_recp);
3585 if ( (strlen(ret->display_recp)+strlen(append)) < SIZ) {
3586 strcat(ret->display_recp, append);
3591 if ((ret->num_local + ret->num_internet + ret->num_ignet +
3592 ret->num_room + ret->num_error) == 0) {
3593 ret->num_error = (-1);
3594 strcpy(ret->errormsg, "No recipients specified.");
3597 CtdlLogPrintf(CTDL_DEBUG, "validate_recipients()\n");
3598 CtdlLogPrintf(CTDL_DEBUG, " local: %d <%s>\n", ret->num_local, ret->recp_local);
3599 CtdlLogPrintf(CTDL_DEBUG, " room: %d <%s>\n", ret->num_room, ret->recp_room);
3600 CtdlLogPrintf(CTDL_DEBUG, " inet: %d <%s>\n", ret->num_internet, ret->recp_internet);
3601 CtdlLogPrintf(CTDL_DEBUG, " ignet: %d <%s>\n", ret->num_ignet, ret->recp_ignet);
3602 CtdlLogPrintf(CTDL_DEBUG, " error: %d <%s>\n", ret->num_error, ret->errormsg);
3610 * Destructor for struct recptypes
3612 void free_recipients(struct recptypes *valid) {
3614 if (valid == NULL) {
3618 if (valid->recptypes_magic != RECPTYPES_MAGIC) {
3619 CtdlLogPrintf(CTDL_EMERG, "Attempt to call free_recipients() on some other data type!\n");
3623 if (valid->errormsg != NULL) free(valid->errormsg);
3624 if (valid->recp_local != NULL) free(valid->recp_local);
3625 if (valid->recp_internet != NULL) free(valid->recp_internet);
3626 if (valid->recp_ignet != NULL) free(valid->recp_ignet);
3627 if (valid->recp_room != NULL) free(valid->recp_room);
3628 if (valid->display_recp != NULL) free(valid->display_recp);
3635 * message entry - mode 0 (normal)
3637 void cmd_ent0(char *entargs)
3643 char supplied_euid[128];
3645 int format_type = 0;
3646 char newusername[256];
3647 char newuseremail[256];
3648 struct CtdlMessage *msg;
3652 struct recptypes *valid = NULL;
3653 struct recptypes *valid_to = NULL;
3654 struct recptypes *valid_cc = NULL;
3655 struct recptypes *valid_bcc = NULL;
3657 int subject_required = 0;
3662 int newuseremail_ok = 0;
3663 char references[SIZ];
3668 post = extract_int(entargs, 0);
3669 extract_token(recp, entargs, 1, '|', sizeof recp);
3670 anon_flag = extract_int(entargs, 2);
3671 format_type = extract_int(entargs, 3);
3672 extract_token(subject, entargs, 4, '|', sizeof subject);
3673 extract_token(newusername, entargs, 5, '|', sizeof newusername);
3674 do_confirm = extract_int(entargs, 6);
3675 extract_token(cc, entargs, 7, '|', sizeof cc);
3676 extract_token(bcc, entargs, 8, '|', sizeof bcc);
3677 switch(CC->room.QRdefaultview) {
3680 extract_token(supplied_euid, entargs, 9, '|', sizeof supplied_euid);
3683 supplied_euid[0] = 0;
3686 extract_token(newuseremail, entargs, 10, '|', sizeof newuseremail);
3687 extract_token(references, entargs, 11, '|', sizeof references);
3688 for (ptr=references; *ptr != 0; ++ptr) {
3689 if (*ptr == '!') *ptr = '|';
3692 /* first check to make sure the request is valid. */
3694 err = CtdlDoIHavePermissionToPostInThisRoom(errmsg, sizeof errmsg, NULL, POST_LOGGED_IN);
3697 cprintf("%d %s\n", err, errmsg);
3701 /* Check some other permission type things. */
3703 if (IsEmptyStr(newusername)) {
3704 strcpy(newusername, CC->user.fullname);
3706 if ( (CC->user.axlevel < 6)
3707 && (strcasecmp(newusername, CC->user.fullname))
3708 && (strcasecmp(newusername, CC->cs_inet_fn))
3710 cprintf("%d You don't have permission to author messages as '%s'.\n",
3711 ERROR + HIGHER_ACCESS_REQUIRED,
3718 if (IsEmptyStr(newuseremail)) {
3719 newuseremail_ok = 1;
3722 if (!IsEmptyStr(newuseremail)) {
3723 if (!strcasecmp(newuseremail, CC->cs_inet_email)) {
3724 newuseremail_ok = 1;
3726 else if (!IsEmptyStr(CC->cs_inet_other_emails)) {
3727 j = num_tokens(CC->cs_inet_other_emails, '|');
3728 for (i=0; i<j; ++i) {
3729 extract_token(buf, CC->cs_inet_other_emails, i, '|', sizeof buf);
3730 if (!strcasecmp(newuseremail, buf)) {
3731 newuseremail_ok = 1;
3737 if (!newuseremail_ok) {
3738 cprintf("%d You don't have permission to author messages as '%s'.\n",
3739 ERROR + HIGHER_ACCESS_REQUIRED,
3745 CC->cs_flags |= CS_POSTING;
3747 /* In mailbox rooms we have to behave a little differently --
3748 * make sure the user has specified at least one recipient. Then
3749 * validate the recipient(s). We do this for the Mail> room, as
3750 * well as any room which has the "Mailbox" view set.
3753 if ( ( (CC->room.QRflags & QR_MAILBOX) && (!strcasecmp(&CC->room.QRname[11], MAILROOM)) )
3754 || ( (CC->room.QRflags & QR_MAILBOX) && (CC->curr_view == VIEW_MAILBOX) )
3756 if (CC->user.axlevel < 2) {
3757 strcpy(recp, "sysop");
3762 valid_to = validate_recipients(recp, NULL, 0);
3763 if (valid_to->num_error > 0) {
3764 cprintf("%d %s\n", ERROR + NO_SUCH_USER, valid_to->errormsg);
3765 free_recipients(valid_to);
3769 valid_cc = validate_recipients(cc, NULL, 0);
3770 if (valid_cc->num_error > 0) {
3771 cprintf("%d %s\n", ERROR + NO_SUCH_USER, valid_cc->errormsg);
3772 free_recipients(valid_to);
3773 free_recipients(valid_cc);
3777 valid_bcc = validate_recipients(bcc, NULL, 0);
3778 if (valid_bcc->num_error > 0) {
3779 cprintf("%d %s\n", ERROR + NO_SUCH_USER, valid_bcc->errormsg);
3780 free_recipients(valid_to);
3781 free_recipients(valid_cc);
3782 free_recipients(valid_bcc);
3786 /* Recipient required, but none were specified */
3787 if ( (valid_to->num_error < 0) && (valid_cc->num_error < 0) && (valid_bcc->num_error < 0) ) {
3788 free_recipients(valid_to);
3789 free_recipients(valid_cc);
3790 free_recipients(valid_bcc);
3791 cprintf("%d At least one recipient is required.\n", ERROR + NO_SUCH_USER);
3795 if (valid_to->num_internet + valid_cc->num_internet + valid_bcc->num_internet > 0) {
3796 if (CtdlCheckInternetMailPermission(&CC->user)==0) {
3797 cprintf("%d You do not have permission "
3798 "to send Internet mail.\n",
3799 ERROR + HIGHER_ACCESS_REQUIRED);
3800 free_recipients(valid_to);
3801 free_recipients(valid_cc);
3802 free_recipients(valid_bcc);
3807 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)
3808 && (CC->user.axlevel < 4) ) {
3809 cprintf("%d Higher access required for network mail.\n",
3810 ERROR + HIGHER_ACCESS_REQUIRED);
3811 free_recipients(valid_to);
3812 free_recipients(valid_cc);
3813 free_recipients(valid_bcc);
3817 if ((RESTRICT_INTERNET == 1)
3818 && (valid_to->num_internet + valid_cc->num_internet + valid_bcc->num_internet > 0)
3819 && ((CC->user.flags & US_INTERNET) == 0)
3820 && (!CC->internal_pgm)) {
3821 cprintf("%d You don't have access to Internet mail.\n",
3822 ERROR + HIGHER_ACCESS_REQUIRED);
3823 free_recipients(valid_to);
3824 free_recipients(valid_cc);
3825 free_recipients(valid_bcc);
3831 /* Is this a room which has anonymous-only or anonymous-option? */
3832 anonymous = MES_NORMAL;
3833 if (CC->room.QRflags & QR_ANONONLY) {
3834 anonymous = MES_ANONONLY;
3836 if (CC->room.QRflags & QR_ANONOPT) {
3837 if (anon_flag == 1) { /* only if the user requested it */
3838 anonymous = MES_ANONOPT;
3842 if ((CC->room.QRflags & QR_MAILBOX) == 0) {
3846 /* Recommend to the client that the use of a message subject is
3847 * strongly recommended in this room, if either the SUBJECTREQ flag
3848 * is set, or if there is one or more Internet email recipients.
3850 if (CC->room.QRflags2 & QR2_SUBJECTREQ) subject_required = 1;
3851 if (valid_to) if (valid_to->num_internet > 0) subject_required = 1;
3852 if (valid_cc) if (valid_cc->num_internet > 0) subject_required = 1;
3853 if (valid_bcc) if (valid_bcc->num_internet > 0) subject_required = 1;
3855 /* If we're only checking the validity of the request, return
3856 * success without creating the message.
3859 cprintf("%d %s|%d\n", CIT_OK,
3860 ((valid_to != NULL) ? valid_to->display_recp : ""),
3862 free_recipients(valid_to);
3863 free_recipients(valid_cc);
3864 free_recipients(valid_bcc);
3868 /* We don't need these anymore because we'll do it differently below */
3869 free_recipients(valid_to);
3870 free_recipients(valid_cc);
3871 free_recipients(valid_bcc);
3873 /* Read in the message from the client. */
3875 cprintf("%d send message\n", START_CHAT_MODE);
3877 cprintf("%d send message\n", SEND_LISTING);
3880 msg = CtdlMakeMessage(&CC->user, recp, cc,
3881 CC->room.QRname, anonymous, format_type,
3882 newusername, newuseremail, subject,
3883 ((!IsEmptyStr(supplied_euid)) ? supplied_euid : NULL),
3886 /* Put together one big recipients struct containing to/cc/bcc all in
3887 * one. This is for the envelope.
3889 char *all_recps = malloc(SIZ * 3);
3890 strcpy(all_recps, recp);
3891 if (!IsEmptyStr(cc)) {
3892 if (!IsEmptyStr(all_recps)) {
3893 strcat(all_recps, ",");
3895 strcat(all_recps, cc);
3897 if (!IsEmptyStr(bcc)) {
3898 if (!IsEmptyStr(all_recps)) {
3899 strcat(all_recps, ",");
3901 strcat(all_recps, bcc);
3903 if (!IsEmptyStr(all_recps)) {
3904 valid = validate_recipients(all_recps, NULL, 0);
3912 msgnum = CtdlSubmitMsg(msg, valid, "", QP_EADDR);
3915 cprintf("%ld\n", msgnum);
3917 cprintf("Message accepted.\n");
3920 cprintf("Internal error.\n");
3922 if (msg->cm_fields['E'] != NULL) {
3923 cprintf("%s\n", msg->cm_fields['E']);
3930 CtdlFreeMessage(msg);
3932 if (valid != NULL) {
3933 free_recipients(valid);
3941 * API function to delete messages which match a set of criteria
3942 * (returns the actual number of messages deleted)
3944 int CtdlDeleteMessages(char *room_name, /* which room */
3945 long *dmsgnums, /* array of msg numbers to be deleted */
3946 int num_dmsgnums, /* number of msgs to be deleted, or 0 for "any" */
3947 char *content_type /* or "" for any. regular expressions expected. */
3950 struct ctdlroom qrbuf;
3951 struct cdbdata *cdbfr;
3952 long *msglist = NULL;
3953 long *dellist = NULL;
3956 int num_deleted = 0;
3958 struct MetaData smi;
3961 int need_to_free_re = 0;
3963 if (content_type) if (!IsEmptyStr(content_type)) {
3964 regcomp(&re, content_type, 0);
3965 need_to_free_re = 1;
3967 CtdlLogPrintf(CTDL_DEBUG, "CtdlDeleteMessages(%s, %d msgs, %s)\n",
3968 room_name, num_dmsgnums, content_type);
3970 /* get room record, obtaining a lock... */
3971 if (lgetroom(&qrbuf, room_name) != 0) {
3972 CtdlLogPrintf(CTDL_ERR, "CtdlDeleteMessages(): Room <%s> not found\n",
3974 if (need_to_free_re) regfree(&re);
3975 return (0); /* room not found */
3977 cdbfr = cdb_fetch(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long));
3979 if (cdbfr != NULL) {
3980 dellist = malloc(cdbfr->len);
3981 msglist = (long *) cdbfr->ptr;
3982 cdbfr->ptr = NULL; /* CtdlDeleteMessages() now owns this memory */
3983 num_msgs = cdbfr->len / sizeof(long);
3987 for (i = 0; i < num_msgs; ++i) {
3990 /* Set/clear a bit for each criterion */
3992 /* 0 messages in the list or a null list means that we are
3993 * interested in deleting any messages which meet the other criteria.
3995 if ((num_dmsgnums == 0) || (dmsgnums == NULL)) {
3996 delete_this |= 0x01;
3999 for (j=0; j<num_dmsgnums; ++j) {
4000 if (msglist[i] == dmsgnums[j]) {
4001 delete_this |= 0x01;
4006 if (IsEmptyStr(content_type)) {
4007 delete_this |= 0x02;
4009 GetMetaData(&smi, msglist[i]);
4010 if (regexec(&re, smi.meta_content_type, 1, &pm, 0) == 0) {
4011 delete_this |= 0x02;
4015 /* Delete message only if all bits are set */
4016 if (delete_this == 0x03) {
4017 dellist[num_deleted++] = msglist[i];
4022 num_msgs = sort_msglist(msglist, num_msgs);
4023 cdb_store(CDB_MSGLISTS, &qrbuf.QRnumber, (int)sizeof(long),
4024 msglist, (int)(num_msgs * sizeof(long)));
4026 qrbuf.QRhighest = msglist[num_msgs - 1];
4030 /* Go through the messages we pulled out of the index, and decrement
4031 * their reference counts by 1. If this is the only room the message
4032 * was in, the reference count will reach zero and the message will
4033 * automatically be deleted from the database. We do this in a
4034 * separate pass because there might be plug-in hooks getting called,
4035 * and we don't want that happening during an S_ROOMS critical
4038 if (num_deleted) for (i=0; i<num_deleted; ++i) {
4039 PerformDeleteHooks(qrbuf.QRname, dellist[i]);
4040 AdjRefCount(dellist[i], -1);
4043 /* Now free the memory we used, and go away. */
4044 if (msglist != NULL) free(msglist);
4045 if (dellist != NULL) free(dellist);
4046 CtdlLogPrintf(CTDL_DEBUG, "%d message(s) deleted.\n", num_deleted);
4047 if (need_to_free_re) regfree(&re);
4048 return (num_deleted);
4054 * Check whether the current user has permission to delete messages from
4055 * the current room (returns 1 for yes, 0 for no)
4057 int CtdlDoIHavePermissionToDeleteMessagesFromThisRoom(void) {
4059 CtdlRoomAccess(&CC->room, &CC->user, &ra, NULL);
4060 if (ra & UA_DELETEALLOWED) return(1);
4068 * Delete message from current room
4070 void cmd_dele(char *args)
4079 extract_token(msgset, args, 0, '|', sizeof msgset);
4080 num_msgs = num_tokens(msgset, ',');
4082 cprintf("%d Nothing to do.\n", CIT_OK);
4086 if (CtdlDoIHavePermissionToDeleteMessagesFromThisRoom() == 0) {
4087 cprintf("%d Higher access required.\n",
4088 ERROR + HIGHER_ACCESS_REQUIRED);
4093 * Build our message set to be moved/copied
4095 msgs = malloc(num_msgs * sizeof(long));
4096 for (i=0; i<num_msgs; ++i) {
4097 extract_token(msgtok, msgset, i, ',', sizeof msgtok);
4098 msgs[i] = atol(msgtok);
4101 num_deleted = CtdlDeleteMessages(CC->room.QRname, msgs, num_msgs, "");
4105 cprintf("%d %d message%s deleted.\n", CIT_OK,
4106 num_deleted, ((num_deleted != 1) ? "s" : ""));
4108 cprintf("%d Message not found.\n", ERROR + MESSAGE_NOT_FOUND);
4114 * Back end API function for moves and deletes (multiple messages)
4116 int CtdlCopyMsgsToRoom(long *msgnums, int num_msgs, char *dest) {
4119 err = CtdlSaveMsgPointersInRoom(dest, msgnums, num_msgs, 1, NULL);
4120 if (err != 0) return(err);
4129 * move or copy a message to another room
4131 void cmd_move(char *args)
4138 char targ[ROOMNAMELEN];
4139 struct ctdlroom qtemp;
4146 extract_token(msgset, args, 0, '|', sizeof msgset);
4147 num_msgs = num_tokens(msgset, ',');
4149 cprintf("%d Nothing to do.\n", CIT_OK);
4153 extract_token(targ, args, 1, '|', sizeof targ);
4154 convert_room_name_macros(targ, sizeof targ);
4155 targ[ROOMNAMELEN - 1] = 0;
4156 is_copy = extract_int(args, 2);
4158 if (getroom(&qtemp, targ) != 0) {
4159 cprintf("%d '%s' does not exist.\n",
4160 ERROR + ROOM_NOT_FOUND, targ);
4164 getuser(&CC->user, CC->curr_user);
4165 CtdlRoomAccess(&qtemp, &CC->user, &ra, NULL);
4167 /* Check for permission to perform this operation.
4168 * Remember: "CC->room" is source, "qtemp" is target.
4172 /* Aides can move/copy */
4173 if (CC->user.axlevel >= 6) permit = 1;
4175 /* Room aides can move/copy */
4176 if (CC->user.usernum == CC->room.QRroomaide) permit = 1;
4178 /* Permit move/copy from personal rooms */
4179 if ((CC->room.QRflags & QR_MAILBOX)
4180 && (qtemp.QRflags & QR_MAILBOX)) permit = 1;
4182 /* Permit only copy from public to personal room */
4184 && (!(CC->room.QRflags & QR_MAILBOX))
4185 && (qtemp.QRflags & QR_MAILBOX)) permit = 1;
4187 /* Permit message removal from collaborative delete rooms */
4188 if (CC->room.QRflags2 & QR2_COLLABDEL) permit = 1;
4190 /* Users allowed to post into the target room may move into it too. */
4191 if ((CC->room.QRflags & QR_MAILBOX) &&
4192 (qtemp.QRflags & UA_POSTALLOWED)) permit = 1;
4194 /* User must have access to target room */
4195 if (!(ra & UA_KNOWN)) permit = 0;
4198 cprintf("%d Higher access required.\n",
4199 ERROR + HIGHER_ACCESS_REQUIRED);
4204 * Build our message set to be moved/copied
4206 msgs = malloc(num_msgs * sizeof(long));
4207 for (i=0; i<num_msgs; ++i) {
4208 extract_token(msgtok, msgset, i, ',', sizeof msgtok);
4209 msgs[i] = atol(msgtok);
4215 err = CtdlCopyMsgsToRoom(msgs, num_msgs, targ);
4217 cprintf("%d Cannot store message(s) in %s: error %d\n",
4223 /* Now delete the message from the source room,
4224 * if this is a 'move' rather than a 'copy' operation.
4227 CtdlDeleteMessages(CC->room.QRname, msgs, num_msgs, "");
4231 cprintf("%d Message(s) %s.\n", CIT_OK, (is_copy ? "copied" : "moved") );
4237 * GetMetaData() - Get the supplementary record for a message
4239 void GetMetaData(struct MetaData *smibuf, long msgnum)
4242 struct cdbdata *cdbsmi;
4245 memset(smibuf, 0, sizeof(struct MetaData));
4246 smibuf->meta_msgnum = msgnum;
4247 smibuf->meta_refcount = 1; /* Default reference count is 1 */
4249 /* Use the negative of the message number for its supp record index */
4250 TheIndex = (0L - msgnum);
4252 cdbsmi = cdb_fetch(CDB_MSGMAIN, &TheIndex, sizeof(long));
4253 if (cdbsmi == NULL) {
4254 return; /* record not found; go with defaults */
4256 memcpy(smibuf, cdbsmi->ptr,
4257 ((cdbsmi->len > sizeof(struct MetaData)) ?
4258 sizeof(struct MetaData) : cdbsmi->len));
4265 * PutMetaData() - (re)write supplementary record for a message
4267 void PutMetaData(struct MetaData *smibuf)
4271 /* Use the negative of the message number for the metadata db index */
4272 TheIndex = (0L - smibuf->meta_msgnum);
4274 cdb_store(CDB_MSGMAIN,
4275 &TheIndex, (int)sizeof(long),
4276 smibuf, (int)sizeof(struct MetaData));
4281 * AdjRefCount - submit an adjustment to the reference count for a message.
4282 * (These are just queued -- we actually process them later.)
4284 void AdjRefCount(long msgnum, int incr)
4286 struct arcq new_arcq;
4288 begin_critical_section(S_SUPPMSGMAIN);
4289 if (arcfp == NULL) {
4290 arcfp = fopen(file_arcq, "ab+");
4292 end_critical_section(S_SUPPMSGMAIN);
4294 /* msgnum < 0 means that we're trying to close the file */
4296 CtdlLogPrintf(CTDL_DEBUG, "Closing the AdjRefCount queue file\n");
4297 begin_critical_section(S_SUPPMSGMAIN);
4298 if (arcfp != NULL) {
4302 end_critical_section(S_SUPPMSGMAIN);
4307 * If we can't open the queue, perform the operation synchronously.
4309 if (arcfp == NULL) {
4310 TDAP_AdjRefCount(msgnum, incr);
4314 new_arcq.arcq_msgnum = msgnum;
4315 new_arcq.arcq_delta = incr;
4316 fwrite(&new_arcq, sizeof(struct arcq), 1, arcfp);
4324 * TDAP_ProcessAdjRefCountQueue()
4326 * Process the queue of message count adjustments that was created by calls
4327 * to AdjRefCount() ... by reading the queue and calling TDAP_AdjRefCount()
4328 * for each one. This should be an "off hours" operation.
4330 int TDAP_ProcessAdjRefCountQueue(void)
4332 char file_arcq_temp[PATH_MAX];
4335 struct arcq arcq_rec;
4336 int num_records_processed = 0;
4338 snprintf(file_arcq_temp, sizeof file_arcq_temp, "%s.%04x", file_arcq, rand());
4340 begin_critical_section(S_SUPPMSGMAIN);
4341 if (arcfp != NULL) {
4346 r = link(file_arcq, file_arcq_temp);
4348 CtdlLogPrintf(CTDL_CRIT, "%s: %s\n", file_arcq_temp, strerror(errno));
4349 end_critical_section(S_SUPPMSGMAIN);
4350 return(num_records_processed);
4354 end_critical_section(S_SUPPMSGMAIN);
4356 fp = fopen(file_arcq_temp, "rb");
4358 CtdlLogPrintf(CTDL_CRIT, "%s: %s\n", file_arcq_temp, strerror(errno));
4359 return(num_records_processed);
4362 while (fread(&arcq_rec, sizeof(struct arcq), 1, fp) == 1) {
4363 TDAP_AdjRefCount(arcq_rec.arcq_msgnum, arcq_rec.arcq_delta);
4364 ++num_records_processed;
4368 r = unlink(file_arcq_temp);
4370 CtdlLogPrintf(CTDL_CRIT, "%s: %s\n", file_arcq_temp, strerror(errno));
4373 return(num_records_processed);
4379 * TDAP_AdjRefCount - adjust the reference count for a message.
4380 * This one does it "for real" because it's called by
4381 * the autopurger function that processes the queue
4382 * created by AdjRefCount(). If a message's reference
4383 * count becomes zero, we also delete the message from
4384 * disk and de-index it.
4386 void TDAP_AdjRefCount(long msgnum, int incr)
4389 struct MetaData smi;
4392 /* This is a *tight* critical section; please keep it that way, as
4393 * it may get called while nested in other critical sections.
4394 * Complicating this any further will surely cause deadlock!
4396 begin_critical_section(S_SUPPMSGMAIN);
4397 GetMetaData(&smi, msgnum);
4398 smi.meta_refcount += incr;
4400 end_critical_section(S_SUPPMSGMAIN);
4401 CtdlLogPrintf(CTDL_DEBUG, "msg %ld ref count delta %+d, is now %d\n",
4402 msgnum, incr, smi.meta_refcount);
4404 /* If the reference count is now zero, delete the message
4405 * (and its supplementary record as well).
4407 if (smi.meta_refcount == 0) {
4408 CtdlLogPrintf(CTDL_DEBUG, "Deleting message <%ld>\n", msgnum);
4410 /* Call delete hooks with NULL room to show it has gone altogether */
4411 PerformDeleteHooks(NULL, msgnum);
4413 /* Remove from message base */
4415 cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long));
4416 cdb_delete(CDB_BIGMSGS, &delnum, (int)sizeof(long));
4418 /* Remove metadata record */
4419 delnum = (0L - msgnum);
4420 cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long));
4426 * Write a generic object to this room
4428 * Note: this could be much more efficient. Right now we use two temporary
4429 * files, and still pull the message into memory as with all others.
4431 void CtdlWriteObject(char *req_room, /* Room to stuff it in */
4432 char *content_type, /* MIME type of this object */
4433 char *raw_message, /* Data to be written */
4434 off_t raw_length, /* Size of raw_message */
4435 struct ctdluser *is_mailbox, /* Mailbox room? */
4436 int is_binary, /* Is encoding necessary? */
4437 int is_unique, /* Del others of this type? */
4438 unsigned int flags /* Internal save flags */
4442 struct ctdlroom qrbuf;
4443 char roomname[ROOMNAMELEN];
4444 struct CtdlMessage *msg;
4445 char *encoded_message = NULL;
4447 if (is_mailbox != NULL) {
4448 MailboxName(roomname, sizeof roomname, is_mailbox, req_room);
4451 safestrncpy(roomname, req_room, sizeof(roomname));
4454 CtdlLogPrintf(CTDL_DEBUG, "Raw length is %ld\n", (long)raw_length);
4457 encoded_message = malloc((size_t) (((raw_length * 134) / 100) + 4096 ) );
4460 encoded_message = malloc((size_t)(raw_length + 4096));
4463 sprintf(encoded_message, "Content-type: %s\n", content_type);
4466 sprintf(&encoded_message[strlen(encoded_message)],
4467 "Content-transfer-encoding: base64\n\n"
4471 sprintf(&encoded_message[strlen(encoded_message)],
4472 "Content-transfer-encoding: 7bit\n\n"
4478 &encoded_message[strlen(encoded_message)],
4486 &encoded_message[strlen(encoded_message)],
4492 CtdlLogPrintf(CTDL_DEBUG, "Allocating\n");
4493 msg = malloc(sizeof(struct CtdlMessage));
4494 memset(msg, 0, sizeof(struct CtdlMessage));
4495 msg->cm_magic = CTDLMESSAGE_MAGIC;
4496 msg->cm_anon_type = MES_NORMAL;
4497 msg->cm_format_type = 4;
4498 msg->cm_fields['A'] = strdup(CC->user.fullname);
4499 msg->cm_fields['O'] = strdup(req_room);
4500 msg->cm_fields['N'] = strdup(config.c_nodename);
4501 msg->cm_fields['H'] = strdup(config.c_humannode);
4502 msg->cm_flags = flags;
4504 msg->cm_fields['M'] = encoded_message;
4506 /* Create the requested room if we have to. */
4507 if (getroom(&qrbuf, roomname) != 0) {
4508 create_room(roomname,
4509 ( (is_mailbox != NULL) ? 5 : 3 ),
4510 "", 0, 1, 0, VIEW_BBS);
4512 /* If the caller specified this object as unique, delete all
4513 * other objects of this type that are currently in the room.
4516 CtdlLogPrintf(CTDL_DEBUG, "Deleted %d other msgs of this type\n",
4517 CtdlDeleteMessages(roomname, NULL, 0, content_type)
4520 /* Now write the data */
4521 CtdlSubmitMsg(msg, NULL, roomname, 0);
4522 CtdlFreeMessage(msg);
4530 void CtdlGetSysConfigBackend(long msgnum, void *userdata) {
4531 config_msgnum = msgnum;
4535 char *CtdlGetSysConfig(char *sysconfname) {
4536 char hold_rm[ROOMNAMELEN];
4539 struct CtdlMessage *msg;
4542 strcpy(hold_rm, CC->room.QRname);
4543 if (getroom(&CC->room, SYSCONFIGROOM) != 0) {
4544 getroom(&CC->room, hold_rm);
4549 /* We want the last (and probably only) config in this room */
4550 begin_critical_section(S_CONFIG);
4551 config_msgnum = (-1L);
4552 CtdlForEachMessage(MSGS_LAST, 1, NULL, sysconfname, NULL,
4553 CtdlGetSysConfigBackend, NULL);
4554 msgnum = config_msgnum;
4555 end_critical_section(S_CONFIG);
4561 msg = CtdlFetchMessage(msgnum, 1);
4563 conf = strdup(msg->cm_fields['M']);
4564 CtdlFreeMessage(msg);
4571 getroom(&CC->room, hold_rm);
4573 if (conf != NULL) do {
4574 extract_token(buf, conf, 0, '\n', sizeof buf);
4575 strcpy(conf, &conf[strlen(buf)+1]);
4576 } while ( (!IsEmptyStr(conf)) && (!IsEmptyStr(buf)) );
4582 void CtdlPutSysConfig(char *sysconfname, char *sysconfdata) {
4583 CtdlWriteObject(SYSCONFIGROOM, sysconfname, sysconfdata, (strlen(sysconfdata)+1), NULL, 0, 1, 0);
4588 * Determine whether a given Internet address belongs to the current user
4590 int CtdlIsMe(char *addr, int addr_buf_len)
4592 struct recptypes *recp;
4595 recp = validate_recipients(addr, NULL, 0);
4596 if (recp == NULL) return(0);
4598 if (recp->num_local == 0) {
4599 free_recipients(recp);
4603 for (i=0; i<recp->num_local; ++i) {
4604 extract_token(addr, recp->recp_local, i, '|', addr_buf_len);
4605 if (!strcasecmp(addr, CC->user.fullname)) {
4606 free_recipients(recp);
4611 free_recipients(recp);
4617 * Citadel protocol command to do the same
4619 void cmd_isme(char *argbuf) {
4622 if (CtdlAccessCheck(ac_logged_in)) return;
4623 extract_token(addr, argbuf, 0, '|', sizeof addr);
4625 if (CtdlIsMe(addr, sizeof addr)) {
4626 cprintf("%d %s\n", CIT_OK, addr);
4629 cprintf("%d Not you.\n", ERROR + ILLEGAL_VALUE);