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>
36 #include "serv_extensions.h"
40 #include "sysdep_decls.h"
41 #include "citserver.h"
48 #include "mime_parser.h"
51 #include "internet_addressing.h"
52 #include "serv_fulltext.h"
54 #include "euidindex.h"
55 #include "journaling.h"
56 #include "citadel_dirs.h"
57 #include "serv_network.h"
60 # include "serv_sieve.h"
61 #endif /* HAVE_LIBSIEVE */
64 struct addresses_to_be_filed *atbf = NULL;
66 /* This temp file holds the queue of operations for AdjRefCount() */
67 static FILE *arcfp = NULL;
70 * This really belongs in serv_network.c, but I don't know how to export
71 * symbols between modules.
73 struct FilterList *filterlist = NULL;
77 * These are the four-character field headers we use when outputting
78 * messages in Citadel format (as opposed to RFC822 format).
81 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
82 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
83 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
84 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
85 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
86 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
87 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
88 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
116 * This function is self explanatory.
117 * (What can I say, I'm in a weird mood today...)
119 void remove_any_whitespace_to_the_left_or_right_of_at_symbol(char *name)
123 for (i = 0; i < strlen(name); ++i) {
124 if (name[i] == '@') {
125 while (isspace(name[i - 1]) && i > 0) {
126 strcpy(&name[i - 1], &name[i]);
129 while (isspace(name[i + 1])) {
130 strcpy(&name[i + 1], &name[i + 2]);
138 * Aliasing for network mail.
139 * (Error messages have been commented out, because this is a server.)
141 int alias(char *name)
142 { /* process alias and routing info for mail */
145 char aaa[SIZ], bbb[SIZ];
146 char *ignetcfg = NULL;
147 char *ignetmap = NULL;
153 char original_name[256];
154 safestrncpy(original_name, name, sizeof original_name);
157 remove_any_whitespace_to_the_left_or_right_of_at_symbol(name);
158 stripallbut(name, '<', '>');
160 fp = fopen(file_mail_aliases, "r");
162 fp = fopen("/dev/null", "r");
169 while (fgets(aaa, sizeof aaa, fp) != NULL) {
170 while (isspace(name[0]))
171 strcpy(name, &name[1]);
172 aaa[strlen(aaa) - 1] = 0;
174 for (a = 0; a < strlen(aaa); ++a) {
176 strcpy(bbb, &aaa[a + 1]);
180 if (!strcasecmp(name, aaa))
185 /* Hit the Global Address Book */
186 if (CtdlDirectoryLookup(aaa, name, sizeof aaa) == 0) {
190 if (strcasecmp(original_name, name)) {
191 lprintf(CTDL_INFO, "%s is being forwarded to %s\n", original_name, name);
194 /* Change "user @ xxx" to "user" if xxx is an alias for this host */
195 for (a=0; a<strlen(name); ++a) {
196 if (name[a] == '@') {
197 if (CtdlHostAlias(&name[a+1]) == hostalias_localhost) {
199 lprintf(CTDL_INFO, "Changed to <%s>\n", name);
204 /* determine local or remote type, see citadel.h */
205 at = haschar(name, '@');
206 if (at == 0) return(MES_LOCAL); /* no @'s - local address */
207 if (at > 1) return(MES_ERROR); /* >1 @'s - invalid address */
208 remove_any_whitespace_to_the_left_or_right_of_at_symbol(name);
210 /* figure out the delivery mode */
211 extract_token(node, name, 1, '@', sizeof node);
213 /* If there are one or more dots in the nodename, we assume that it
214 * is an FQDN and will attempt SMTP delivery to the Internet.
216 if (haschar(node, '.') > 0) {
217 return(MES_INTERNET);
220 /* Otherwise we look in the IGnet maps for a valid Citadel node.
221 * Try directly-connected nodes first...
223 ignetcfg = CtdlGetSysConfig(IGNETCFG);
224 for (i=0; i<num_tokens(ignetcfg, '\n'); ++i) {
225 extract_token(buf, ignetcfg, i, '\n', sizeof buf);
226 extract_token(testnode, buf, 0, '|', sizeof testnode);
227 if (!strcasecmp(node, testnode)) {
235 * Then try nodes that are two or more hops away.
237 ignetmap = CtdlGetSysConfig(IGNETMAP);
238 for (i=0; i<num_tokens(ignetmap, '\n'); ++i) {
239 extract_token(buf, ignetmap, i, '\n', sizeof buf);
240 extract_token(testnode, buf, 0, '|', sizeof testnode);
241 if (!strcasecmp(node, testnode)) {
248 /* If we get to this point it's an invalid node name */
254 * Back end for the MSGS command: output message number only.
256 void simple_listing(long msgnum, void *userdata)
258 cprintf("%ld\n", msgnum);
264 * Back end for the MSGS command: output header summary.
266 void headers_listing(long msgnum, void *userdata)
268 struct CtdlMessage *msg;
270 msg = CtdlFetchMessage(msgnum, 0);
272 cprintf("%ld|0|||||\n", msgnum);
276 cprintf("%ld|%s|%s|%s|%s|%s|\n",
278 (msg->cm_fields['T'] ? msg->cm_fields['T'] : "0"),
279 (msg->cm_fields['A'] ? msg->cm_fields['A'] : ""),
280 (msg->cm_fields['N'] ? msg->cm_fields['N'] : ""),
281 (msg->cm_fields['F'] ? msg->cm_fields['F'] : ""),
282 (msg->cm_fields['U'] ? msg->cm_fields['U'] : "")
284 CtdlFreeMessage(msg);
289 /* Determine if a given message matches the fields in a message template.
290 * Return 0 for a successful match.
292 int CtdlMsgCmp(struct CtdlMessage *msg, struct CtdlMessage *template) {
295 /* If there aren't any fields in the template, all messages will
298 if (template == NULL) return(0);
300 /* Null messages are bogus. */
301 if (msg == NULL) return(1);
303 for (i='A'; i<='Z'; ++i) {
304 if (template->cm_fields[i] != NULL) {
305 if (msg->cm_fields[i] == NULL) {
308 if (strcasecmp(msg->cm_fields[i],
309 template->cm_fields[i])) return 1;
313 /* All compares succeeded: we have a match! */
320 * Retrieve the "seen" message list for the current room.
322 void CtdlGetSeen(char *buf, int which_set) {
325 /* Learn about the user and room in question */
326 CtdlGetRelationship(&vbuf, &CC->user, &CC->room);
328 if (which_set == ctdlsetseen_seen)
329 safestrncpy(buf, vbuf.v_seen, SIZ);
330 if (which_set == ctdlsetseen_answered)
331 safestrncpy(buf, vbuf.v_answered, SIZ);
337 * Manipulate the "seen msgs" string (or other message set strings)
339 void CtdlSetSeen(long *target_msgnums, int num_target_msgnums,
340 int target_setting, int which_set,
341 struct ctdluser *which_user, struct ctdlroom *which_room) {
342 struct cdbdata *cdbfr;
354 char *is_set; /* actually an array of booleans */
357 char setstr[SIZ], lostr[SIZ], histr[SIZ];
360 /* Don't bother doing *anything* if we were passed a list of zero messages */
361 if (num_target_msgnums < 1) {
365 lprintf(CTDL_DEBUG, "CtdlSetSeen(%d msgs starting with %ld, %d, %d)\n",
366 num_target_msgnums, target_msgnums[0],
367 target_setting, which_set);
369 /* Learn about the user and room in question */
370 CtdlGetRelationship(&vbuf,
371 ((which_user != NULL) ? which_user : &CC->user),
372 ((which_room != NULL) ? which_room : &CC->room)
375 /* Load the message list */
376 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long));
378 msglist = (long *) cdbfr->ptr;
379 cdbfr->ptr = NULL; /* CtdlSetSeen() now owns this memory */
380 num_msgs = cdbfr->len / sizeof(long);
383 return; /* No messages at all? No further action. */
386 is_set = malloc(num_msgs * sizeof(char));
387 memset(is_set, 0, (num_msgs * sizeof(char)) );
389 /* Decide which message set we're manipulating */
391 case ctdlsetseen_seen:
392 safestrncpy(vset, vbuf.v_seen, sizeof vset);
394 case ctdlsetseen_answered:
395 safestrncpy(vset, vbuf.v_answered, sizeof vset);
399 /* lprintf(CTDL_DEBUG, "before optimize: %s\n", vset); */
401 /* Translate the existing sequence set into an array of booleans */
402 num_sets = num_tokens(vset, ',');
403 for (s=0; s<num_sets; ++s) {
404 extract_token(setstr, vset, s, ',', sizeof setstr);
406 extract_token(lostr, setstr, 0, ':', sizeof lostr);
407 if (num_tokens(setstr, ':') >= 2) {
408 extract_token(histr, setstr, 1, ':', sizeof histr);
409 if (!strcmp(histr, "*")) {
410 snprintf(histr, sizeof histr, "%ld", LONG_MAX);
414 strcpy(histr, lostr);
419 for (i = 0; i < num_msgs; ++i) {
420 if ((msglist[i] >= lo) && (msglist[i] <= hi)) {
426 /* Now translate the array of booleans back into a sequence set */
431 for (i=0; i<num_msgs; ++i) {
433 is_seen = is_set[i]; /* Default to existing setting */
435 for (k=0; k<num_target_msgnums; ++k) {
436 if (msglist[i] == target_msgnums[k]) {
437 is_seen = target_setting;
442 if (lo < 0L) lo = msglist[i];
446 if ( ((is_seen == 0) && (was_seen == 1))
447 || ((is_seen == 1) && (i == num_msgs-1)) ) {
449 /* begin trim-o-matic code */
452 while ( (strlen(vset) + 20) > sizeof vset) {
453 remove_token(vset, 0, ',');
455 if (j--) break; /* loop no more than 9 times */
457 if ( (trimming) && (which_set == ctdlsetseen_seen) ) {
461 snprintf(lostr, sizeof lostr,
462 "1:%ld,%s", t, vset);
463 safestrncpy(vset, lostr, sizeof vset);
465 /* end trim-o-matic code */
473 snprintf(&vset[tmp], (sizeof vset) - tmp,
477 snprintf(&vset[tmp], (sizeof vset) - tmp,
486 /* Decide which message set we're manipulating */
488 case ctdlsetseen_seen:
489 safestrncpy(vbuf.v_seen, vset, sizeof vbuf.v_seen);
491 case ctdlsetseen_answered:
492 safestrncpy(vbuf.v_answered, vset,
493 sizeof vbuf.v_answered);
498 /* lprintf(CTDL_DEBUG, " after optimize: %s\n", vset); */
500 CtdlSetRelationship(&vbuf,
501 ((which_user != NULL) ? which_user : &CC->user),
502 ((which_room != NULL) ? which_room : &CC->room)
508 * API function to perform an operation for each qualifying message in the
509 * current room. (Returns the number of messages processed.)
511 int CtdlForEachMessage(int mode, long ref, char *search_string,
513 struct CtdlMessage *compare,
514 void (*CallBack) (long, void *),
520 struct cdbdata *cdbfr;
521 long *msglist = NULL;
523 int num_processed = 0;
526 struct CtdlMessage *msg = NULL;
529 int printed_lastold = 0;
530 int num_search_msgs = 0;
531 long *search_msgs = NULL;
533 int need_to_free_re = 0;
536 if (content_type) if (strlen(content_type) > 0) {
537 regcomp(&re, content_type, 0);
541 /* Learn about the user and room in question */
542 getuser(&CC->user, CC->curr_user);
543 CtdlGetRelationship(&vbuf, &CC->user, &CC->room);
545 /* Load the message list */
546 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long));
548 msglist = (long *) cdbfr->ptr;
549 cdbfr->ptr = NULL; /* CtdlForEachMessage() now owns this memory */
550 num_msgs = cdbfr->len / sizeof(long);
553 if (need_to_free_re) regfree(&re);
554 return 0; /* No messages at all? No further action. */
559 * Now begin the traversal.
561 if (num_msgs > 0) for (a = 0; a < num_msgs; ++a) {
563 /* If the caller is looking for a specific MIME type, filter
564 * out all messages which are not of the type requested.
566 if (content_type != NULL) if (strlen(content_type) > 0) {
568 /* This call to GetMetaData() sits inside this loop
569 * so that we only do the extra database read per msg
570 * if we need to. Doing the extra read all the time
571 * really kills the server. If we ever need to use
572 * metadata for another search criterion, we need to
573 * move the read somewhere else -- but still be smart
574 * enough to only do the read if the caller has
575 * specified something that will need it.
577 GetMetaData(&smi, msglist[a]);
579 /* if (strcasecmp(smi.meta_content_type, content_type)) { old non-regex way */
580 if (regexec(&re, smi.meta_content_type, 1, &pm, 0) != 0) {
586 num_msgs = sort_msglist(msglist, num_msgs);
588 /* If a template was supplied, filter out the messages which
589 * don't match. (This could induce some delays!)
592 if (compare != NULL) {
593 for (a = 0; a < num_msgs; ++a) {
594 msg = CtdlFetchMessage(msglist[a], 1);
596 if (CtdlMsgCmp(msg, compare)) {
599 CtdlFreeMessage(msg);
605 /* If a search string was specified, get a message list from
606 * the full text index and remove messages which aren't on both
610 * Since the lists are sorted and strictly ascending, and the
611 * output list is guaranteed to be shorter than or equal to the
612 * input list, we overwrite the bottom of the input list. This
613 * eliminates the need to memmove big chunks of the list over and
616 if ( (num_msgs > 0) && (mode == MSGS_SEARCH) && (search_string) ) {
617 ft_search(&num_search_msgs, &search_msgs, search_string);
618 if (num_search_msgs > 0) {
622 orig_num_msgs = num_msgs;
624 for (i=0; i<orig_num_msgs; ++i) {
625 for (j=0; j<num_search_msgs; ++j) {
626 if (msglist[i] == search_msgs[j]) {
627 msglist[num_msgs++] = msglist[i];
633 num_msgs = 0; /* No messages qualify */
635 if (search_msgs != NULL) free(search_msgs);
637 /* Now that we've purged messages which don't contain the search
638 * string, treat a MSGS_SEARCH just like a MSGS_ALL from this
645 * Now iterate through the message list, according to the
646 * criteria supplied by the caller.
649 for (a = 0; a < num_msgs; ++a) {
650 thismsg = msglist[a];
651 if (mode == MSGS_ALL) {
655 is_seen = is_msg_in_sequence_set(
656 vbuf.v_seen, thismsg);
657 if (is_seen) lastold = thismsg;
663 || ((mode == MSGS_OLD) && (is_seen))
664 || ((mode == MSGS_NEW) && (!is_seen))
665 || ((mode == MSGS_LAST) && (a >= (num_msgs - ref)))
666 || ((mode == MSGS_FIRST) && (a < ref))
667 || ((mode == MSGS_GT) && (thismsg > ref))
668 || ((mode == MSGS_EQ) && (thismsg == ref))
671 if ((mode == MSGS_NEW) && (CC->user.flags & US_LASTOLD) && (lastold > 0L) && (printed_lastold == 0) && (!is_seen)) {
673 CallBack(lastold, userdata);
677 if (CallBack) CallBack(thismsg, userdata);
681 free(msglist); /* Clean up */
682 if (need_to_free_re) regfree(&re);
683 return num_processed;
689 * cmd_msgs() - get list of message #'s in this room
690 * implements the MSGS server command using CtdlForEachMessage()
692 void cmd_msgs(char *cmdbuf)
701 int with_template = 0;
702 struct CtdlMessage *template = NULL;
703 int with_headers = 0;
704 char search_string[1024];
706 extract_token(which, cmdbuf, 0, '|', sizeof which);
707 cm_ref = extract_int(cmdbuf, 1);
708 extract_token(search_string, cmdbuf, 1, '|', sizeof search_string);
709 with_template = extract_int(cmdbuf, 2);
710 with_headers = extract_int(cmdbuf, 3);
713 if (!strncasecmp(which, "OLD", 3))
715 else if (!strncasecmp(which, "NEW", 3))
717 else if (!strncasecmp(which, "FIRST", 5))
719 else if (!strncasecmp(which, "LAST", 4))
721 else if (!strncasecmp(which, "GT", 2))
723 else if (!strncasecmp(which, "SEARCH", 6))
728 if ((!(CC->logged_in)) && (!(CC->internal_pgm))) {
729 cprintf("%d not logged in\n", ERROR + NOT_LOGGED_IN);
733 if ( (mode == MSGS_SEARCH) && (!config.c_enable_fulltext) ) {
734 cprintf("%d Full text index is not enabled on this server.\n",
735 ERROR + CMD_NOT_SUPPORTED);
741 cprintf("%d Send template then receive message list\n",
743 template = (struct CtdlMessage *)
744 malloc(sizeof(struct CtdlMessage));
745 memset(template, 0, sizeof(struct CtdlMessage));
746 template->cm_magic = CTDLMESSAGE_MAGIC;
747 template->cm_anon_type = MES_NORMAL;
749 while(client_getln(buf, sizeof buf), strcmp(buf,"000")) {
750 extract_token(tfield, buf, 0, '|', sizeof tfield);
751 extract_token(tvalue, buf, 1, '|', sizeof tvalue);
752 for (i='A'; i<='Z'; ++i) if (msgkeys[i]!=NULL) {
753 if (!strcasecmp(tfield, msgkeys[i])) {
754 template->cm_fields[i] =
762 cprintf("%d \n", LISTING_FOLLOWS);
765 CtdlForEachMessage(mode,
766 ( (mode == MSGS_SEARCH) ? 0 : cm_ref ),
767 ( (mode == MSGS_SEARCH) ? search_string : NULL ),
770 (with_headers ? headers_listing : simple_listing),
773 if (template != NULL) CtdlFreeMessage(template);
781 * help_subst() - support routine for help file viewer
783 void help_subst(char *strbuf, char *source, char *dest)
788 while (p = pattern2(strbuf, source), (p >= 0)) {
789 strcpy(workbuf, &strbuf[p + strlen(source)]);
790 strcpy(&strbuf[p], dest);
791 strcat(strbuf, workbuf);
796 void do_help_subst(char *buffer)
800 help_subst(buffer, "^nodename", config.c_nodename);
801 help_subst(buffer, "^humannode", config.c_humannode);
802 help_subst(buffer, "^fqdn", config.c_fqdn);
803 help_subst(buffer, "^username", CC->user.fullname);
804 snprintf(buf2, sizeof buf2, "%ld", CC->user.usernum);
805 help_subst(buffer, "^usernum", buf2);
806 help_subst(buffer, "^sysadm", config.c_sysadm);
807 help_subst(buffer, "^variantname", CITADEL);
808 snprintf(buf2, sizeof buf2, "%d", config.c_maxsessions);
809 help_subst(buffer, "^maxsessions", buf2);
810 help_subst(buffer, "^bbsdir", ctdl_bbsbase_dir);
816 * memfmout() - Citadel text formatter and paginator.
817 * Although the original purpose of this routine was to format
818 * text to the reader's screen width, all we're really using it
819 * for here is to format text out to 80 columns before sending it
820 * to the client. The client software may reformat it again.
823 char *mptr, /* where are we going to get our text from? */
824 char subst, /* nonzero if we should do substitutions */
825 char *nl) /* string to terminate lines with */
833 static int width = 80;
838 c = 1; /* c is the current pos */
842 while (ch = *mptr, ((ch != 0) && (strlen(buffer) < 126))) {
844 buffer[strlen(buffer) + 1] = 0;
845 buffer[strlen(buffer)] = ch;
848 if (buffer[0] == '^')
849 do_help_subst(buffer);
851 buffer[strlen(buffer) + 1] = 0;
853 strcpy(buffer, &buffer[1]);
861 if (((ch == 13) || (ch == 10)) && (old != 13) && (old != 10)) {
864 if (((old == 13) || (old == 10)) && (isspace(real))) {
869 if (((strlen(aaa) + c) > (width - 5)) && (strlen(aaa) > (width - 5))) {
870 cprintf("%s%s", nl, aaa);
879 if ((strlen(aaa) + c) > (width - 5)) {
888 if ((ch == 13) || (ch == 10)) {
889 cprintf("%s%s", aaa, nl);
896 cprintf("%s%s", aaa, nl);
902 * Callback function for mime parser that simply lists the part
904 void list_this_part(char *name, char *filename, char *partnum, char *disp,
905 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
910 ma = (struct ma_info *)cbuserdata;
911 if (ma->is_ma == 0) {
912 cprintf("part=%s|%s|%s|%s|%s|%ld\n",
913 name, filename, partnum, disp, cbtype, (long)length);
918 * Callback function for multipart prefix
920 void list_this_pref(char *name, char *filename, char *partnum, char *disp,
921 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
926 ma = (struct ma_info *)cbuserdata;
927 if (!strcasecmp(cbtype, "multipart/alternative")) {
931 if (ma->is_ma == 0) {
932 cprintf("pref=%s|%s\n", partnum, cbtype);
937 * Callback function for multipart sufffix
939 void list_this_suff(char *name, char *filename, char *partnum, char *disp,
940 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
945 ma = (struct ma_info *)cbuserdata;
946 if (ma->is_ma == 0) {
947 cprintf("suff=%s|%s\n", partnum, cbtype);
949 if (!strcasecmp(cbtype, "multipart/alternative")) {
956 * Callback function for mime parser that opens a section for downloading
958 void mime_download(char *name, char *filename, char *partnum, char *disp,
959 void *content, char *cbtype, char *cbcharset, size_t length,
960 char *encoding, void *cbuserdata)
963 /* Silently go away if there's already a download open... */
964 if (CC->download_fp != NULL)
967 /* ...or if this is not the desired section */
968 if (strcasecmp(CC->download_desired_section, partnum))
971 CC->download_fp = tmpfile();
972 if (CC->download_fp == NULL)
975 fwrite(content, length, 1, CC->download_fp);
976 fflush(CC->download_fp);
977 rewind(CC->download_fp);
979 OpenCmdResult(filename, cbtype);
985 * Callback function for mime parser that outputs a section all at once
987 void mime_spew_section(char *name, char *filename, char *partnum, char *disp,
988 void *content, char *cbtype, char *cbcharset, size_t length,
989 char *encoding, void *cbuserdata)
991 int *found_it = (int *)cbuserdata;
993 /* ...or if this is not the desired section */
994 if (strcasecmp(CC->download_desired_section, partnum))
999 cprintf("%d %d\n", BINARY_FOLLOWS, (int)length);
1000 client_write(content, length);
1006 * Load a message from disk into memory.
1007 * This is used by CtdlOutputMsg() and other fetch functions.
1009 * NOTE: Caller is responsible for freeing the returned CtdlMessage struct
1010 * using the CtdlMessageFree() function.
1012 struct CtdlMessage *CtdlFetchMessage(long msgnum, int with_body)
1014 struct cdbdata *dmsgtext;
1015 struct CtdlMessage *ret = NULL;
1019 cit_uint8_t field_header;
1021 lprintf(CTDL_DEBUG, "CtdlFetchMessage(%ld, %d)\n", msgnum, with_body);
1023 dmsgtext = cdb_fetch(CDB_MSGMAIN, &msgnum, sizeof(long));
1024 if (dmsgtext == NULL) {
1027 mptr = dmsgtext->ptr;
1028 upper_bound = mptr + dmsgtext->len;
1030 /* Parse the three bytes that begin EVERY message on disk.
1031 * The first is always 0xFF, the on-disk magic number.
1032 * The second is the anonymous/public type byte.
1033 * The third is the format type byte (vari, fixed, or MIME).
1038 "Message %ld appears to be corrupted.\n",
1043 ret = (struct CtdlMessage *) malloc(sizeof(struct CtdlMessage));
1044 memset(ret, 0, sizeof(struct CtdlMessage));
1046 ret->cm_magic = CTDLMESSAGE_MAGIC;
1047 ret->cm_anon_type = *mptr++; /* Anon type byte */
1048 ret->cm_format_type = *mptr++; /* Format type byte */
1051 * The rest is zero or more arbitrary fields. Load them in.
1052 * We're done when we encounter either a zero-length field or
1053 * have just processed the 'M' (message text) field.
1056 if (mptr >= upper_bound) {
1059 field_header = *mptr++;
1060 ret->cm_fields[field_header] = strdup(mptr);
1062 while (*mptr++ != 0); /* advance to next field */
1064 } while ((mptr < upper_bound) && (field_header != 'M'));
1068 /* Always make sure there's something in the msg text field. If
1069 * it's NULL, the message text is most likely stored separately,
1070 * so go ahead and fetch that. Failing that, just set a dummy
1071 * body so other code doesn't barf.
1073 if ( (ret->cm_fields['M'] == NULL) && (with_body) ) {
1074 dmsgtext = cdb_fetch(CDB_BIGMSGS, &msgnum, sizeof(long));
1075 if (dmsgtext != NULL) {
1076 ret->cm_fields['M'] = strdup(dmsgtext->ptr);
1080 if (ret->cm_fields['M'] == NULL) {
1081 ret->cm_fields['M'] = strdup("\r\n\r\n (no text)\r\n");
1084 /* Perform "before read" hooks (aborting if any return nonzero) */
1085 if (PerformMessageHooks(ret, EVT_BEFOREREAD) > 0) {
1086 CtdlFreeMessage(ret);
1095 * Returns 1 if the supplied pointer points to a valid Citadel message.
1096 * If the pointer is NULL or the magic number check fails, returns 0.
1098 int is_valid_message(struct CtdlMessage *msg) {
1101 if ((msg->cm_magic) != CTDLMESSAGE_MAGIC) {
1102 lprintf(CTDL_WARNING, "is_valid_message() -- self-check failed\n");
1110 * 'Destructor' for struct CtdlMessage
1112 void CtdlFreeMessage(struct CtdlMessage *msg)
1116 if (is_valid_message(msg) == 0)
1118 if (msg != NULL) free (msg);
1122 for (i = 0; i < 256; ++i)
1123 if (msg->cm_fields[i] != NULL) {
1124 free(msg->cm_fields[i]);
1127 msg->cm_magic = 0; /* just in case */
1133 * Pre callback function for multipart/alternative
1135 * NOTE: this differs from the standard behavior for a reason. Normally when
1136 * displaying multipart/alternative you want to show the _last_ usable
1137 * format in the message. Here we show the _first_ one, because it's
1138 * usually text/plain. Since this set of functions is designed for text
1139 * output to non-MIME-aware clients, this is the desired behavior.
1142 void fixed_output_pre(char *name, char *filename, char *partnum, char *disp,
1143 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
1148 ma = (struct ma_info *)cbuserdata;
1149 lprintf(CTDL_DEBUG, "fixed_output_pre() type=<%s>\n", cbtype);
1150 if (!strcasecmp(cbtype, "multipart/alternative")) {
1154 if (!strcasecmp(cbtype, "message/rfc822")) {
1160 * Post callback function for multipart/alternative
1162 void fixed_output_post(char *name, char *filename, char *partnum, char *disp,
1163 void *content, char *cbtype, char *cbcharset, size_t length,
1164 char *encoding, void *cbuserdata)
1168 ma = (struct ma_info *)cbuserdata;
1169 lprintf(CTDL_DEBUG, "fixed_output_post() type=<%s>\n", cbtype);
1170 if (!strcasecmp(cbtype, "multipart/alternative")) {
1174 if (!strcasecmp(cbtype, "message/rfc822")) {
1180 * Inline callback function for mime parser that wants to display text
1182 void fixed_output(char *name, char *filename, char *partnum, char *disp,
1183 void *content, char *cbtype, char *cbcharset, size_t length,
1184 char *encoding, void *cbuserdata)
1191 ma = (struct ma_info *)cbuserdata;
1194 "fixed_output() part %s: %s (%s) (%ld bytes)\n",
1195 partnum, filename, cbtype, (long)length);
1198 * If we're in the middle of a multipart/alternative scope and
1199 * we've already printed another section, skip this one.
1201 if ( (ma->is_ma) && (ma->did_print) ) {
1202 lprintf(CTDL_DEBUG, "Skipping part %s (%s)\n",
1208 if ( (!strcasecmp(cbtype, "text/plain"))
1209 || (strlen(cbtype)==0) ) {
1212 client_write(wptr, length);
1213 if (wptr[length-1] != '\n') {
1220 if (!strcasecmp(cbtype, "text/html")) {
1221 ptr = html_to_ascii(content, length, 80, 0);
1223 client_write(ptr, wlen);
1224 if (ptr[wlen-1] != '\n') {
1231 if (ma->use_fo_hooks) {
1232 if (PerformFixedOutputHooks(cbtype, content, length)) {
1233 /* above function returns nonzero if it handled the part */
1238 if (strncasecmp(cbtype, "multipart/", 10)) {
1239 cprintf("Part %s: %s (%s) (%ld bytes)\r\n",
1240 partnum, filename, cbtype, (long)length);
1246 * The client is elegant and sophisticated and wants to be choosy about
1247 * MIME content types, so figure out which multipart/alternative part
1248 * we're going to send.
1250 * We use a system of weights. When we find a part that matches one of the
1251 * MIME types we've declared as preferential, we can store it in ma->chosen_part
1252 * and then set ma->chosen_pref to that MIME type's position in our preference
1253 * list. If we then hit another match, we only replace the first match if
1254 * the preference value is lower.
1256 void choose_preferred(char *name, char *filename, char *partnum, char *disp,
1257 void *content, char *cbtype, char *cbcharset, size_t length,
1258 char *encoding, void *cbuserdata)
1264 ma = (struct ma_info *)cbuserdata;
1266 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 safestrncpy(ma->chosen_part, partnum, sizeof ma->chosen_part);
1272 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 (strlen(cbcharset) > 0) {
1311 cprintf("; charset=%s", cbcharset);
1313 cprintf("\nContent-length: %d\n",
1314 (int)(length + add_newline) );
1315 if (strlen(encoding) > 0) {
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 */
1378 struct CtdlMessage *TheMessage = NULL;
1379 int retcode = om_no_such_msg;
1380 struct encapmsg encap;
1382 lprintf(CTDL_DEBUG, "CtdlOutputMsg() msgnum=%ld, mode=%d, section=%s\n",
1384 (section ? section : "<>")
1387 if ((!(CC->logged_in)) && (!(CC->internal_pgm))) {
1388 if (do_proto) cprintf("%d Not logged in.\n",
1389 ERROR + NOT_LOGGED_IN);
1390 return(om_not_logged_in);
1393 /* FIXME: check message id against msglist for this room */
1396 * Fetch the message from disk. If we're in any sort of headers
1397 * only mode, request that we don't even bother loading the body
1400 if ( (headers_only == HEADERS_FAST) || (headers_only == HEADERS_ONLY) ) {
1401 TheMessage = CtdlFetchMessage(msg_num, 0);
1404 TheMessage = CtdlFetchMessage(msg_num, 1);
1407 if (TheMessage == NULL) {
1408 if (do_proto) cprintf("%d Can't locate msg %ld on disk\n",
1409 ERROR + MESSAGE_NOT_FOUND, msg_num);
1410 return(om_no_such_msg);
1413 /* Here is the weird form of this command, to process only an
1414 * encapsulated message/rfc822 section.
1416 if (section) if (strlen(section)>0) if (strcmp(section, "0")) {
1417 memset(&encap, 0, sizeof encap);
1418 safestrncpy(encap.desired_section, section, sizeof encap.desired_section);
1419 mime_parser(TheMessage->cm_fields['M'],
1421 *extract_encapsulated_message,
1422 NULL, NULL, (void *)&encap, 0
1424 CtdlFreeMessage(TheMessage);
1428 encap.msg[encap.msglen] = 0;
1429 TheMessage = convert_internet_message(encap.msg);
1430 encap.msg = NULL; /* no free() here, TheMessage owns it now */
1432 /* Now we let it fall through to the bottom of this
1433 * function, because TheMessage now contains the
1434 * encapsulated message instead of the top-level
1435 * message. Isn't that neat?
1440 if (do_proto) cprintf("%d msg %ld has no part %s\n",
1441 ERROR + MESSAGE_NOT_FOUND, msg_num, section);
1442 retcode = om_no_such_msg;
1447 /* Ok, output the message now */
1448 retcode = CtdlOutputPreLoadedMsg(
1450 headers_only, do_proto, crlf);
1451 CtdlFreeMessage(TheMessage);
1458 * Get a message off disk. (returns om_* values found in msgbase.h)
1461 int CtdlOutputPreLoadedMsg(
1462 struct CtdlMessage *TheMessage,
1463 int mode, /* how would you like that message? */
1464 int headers_only, /* eschew the message body? */
1465 int do_proto, /* do Citadel protocol responses? */
1466 int crlf /* Use CRLF newlines instead of LF? */
1472 char display_name[256];
1474 char *nl; /* newline string */
1476 int subject_found = 0;
1479 /* Buffers needed for RFC822 translation. These are all filled
1480 * using functions that are bounds-checked, and therefore we can
1481 * make them substantially smaller than SIZ.
1489 char datestamp[100];
1491 lprintf(CTDL_DEBUG, "CtdlOutputPreLoadedMsg(TheMessage=%s, %d, %d, %d, %d\n",
1492 ((TheMessage == NULL) ? "NULL" : "not null"),
1493 mode, headers_only, do_proto, crlf);
1495 strcpy(mid, "unknown");
1496 nl = (crlf ? "\r\n" : "\n");
1498 if (!is_valid_message(TheMessage)) {
1500 "ERROR: invalid preloaded message for output\n");
1501 return(om_no_such_msg);
1504 /* Are we downloading a MIME component? */
1505 if (mode == MT_DOWNLOAD) {
1506 if (TheMessage->cm_format_type != FMT_RFC822) {
1508 cprintf("%d This is not a MIME message.\n",
1509 ERROR + ILLEGAL_VALUE);
1510 } else if (CC->download_fp != NULL) {
1511 if (do_proto) cprintf(
1512 "%d You already have a download open.\n",
1513 ERROR + RESOURCE_BUSY);
1515 /* Parse the message text component */
1516 mptr = TheMessage->cm_fields['M'];
1517 mime_parser(mptr, NULL, *mime_download, NULL, NULL, NULL, 0);
1518 /* If there's no file open by this time, the requested
1519 * section wasn't found, so print an error
1521 if (CC->download_fp == NULL) {
1522 if (do_proto) cprintf(
1523 "%d Section %s not found.\n",
1524 ERROR + FILE_NOT_FOUND,
1525 CC->download_desired_section);
1528 return((CC->download_fp != NULL) ? om_ok : om_mime_error);
1531 /* MT_SPEW_SECTION is like MT_DOWNLOAD except it outputs the whole MIME part
1532 * in a single server operation instead of opening a download file.
1534 if (mode == MT_SPEW_SECTION) {
1535 if (TheMessage->cm_format_type != FMT_RFC822) {
1537 cprintf("%d This is not a MIME message.\n",
1538 ERROR + ILLEGAL_VALUE);
1540 /* Parse the message text component */
1543 mptr = TheMessage->cm_fields['M'];
1544 mime_parser(mptr, NULL, *mime_spew_section, NULL, NULL, (void *)&found_it, 0);
1545 /* If section wasn't found, print an error
1548 if (do_proto) cprintf(
1549 "%d Section %s not found.\n",
1550 ERROR + FILE_NOT_FOUND,
1551 CC->download_desired_section);
1554 return((CC->download_fp != NULL) ? om_ok : om_mime_error);
1557 /* now for the user-mode message reading loops */
1558 if (do_proto) cprintf("%d msg:\n", LISTING_FOLLOWS);
1560 /* Does the caller want to skip the headers? */
1561 if (headers_only == HEADERS_NONE) goto START_TEXT;
1563 /* Tell the client which format type we're using. */
1564 if ( (mode == MT_CITADEL) && (do_proto) ) {
1565 cprintf("type=%d\n", TheMessage->cm_format_type);
1568 /* nhdr=yes means that we're only displaying headers, no body */
1569 if ( (TheMessage->cm_anon_type == MES_ANONONLY)
1570 && (mode == MT_CITADEL)
1573 cprintf("nhdr=yes\n");
1576 /* begin header processing loop for Citadel message format */
1578 if ((mode == MT_CITADEL) || (mode == MT_MIME)) {
1580 safestrncpy(display_name, "<unknown>", sizeof display_name);
1581 if (TheMessage->cm_fields['A']) {
1582 strcpy(buf, TheMessage->cm_fields['A']);
1583 if (TheMessage->cm_anon_type == MES_ANONONLY) {
1584 safestrncpy(display_name, "****", sizeof display_name);
1586 else if (TheMessage->cm_anon_type == MES_ANONOPT) {
1587 safestrncpy(display_name, "anonymous", sizeof display_name);
1590 safestrncpy(display_name, buf, sizeof display_name);
1592 if ((is_room_aide())
1593 && ((TheMessage->cm_anon_type == MES_ANONONLY)
1594 || (TheMessage->cm_anon_type == MES_ANONOPT))) {
1595 size_t tmp = strlen(display_name);
1596 snprintf(&display_name[tmp],
1597 sizeof display_name - tmp,
1602 /* Don't show Internet address for users on the
1603 * local Citadel network.
1606 if (TheMessage->cm_fields['N'] != NULL)
1607 if (strlen(TheMessage->cm_fields['N']) > 0)
1608 if (haschar(TheMessage->cm_fields['N'], '.') == 0) {
1612 /* Now spew the header fields in the order we like them. */
1613 safestrncpy(allkeys, FORDER, sizeof allkeys);
1614 for (i=0; i<strlen(allkeys); ++i) {
1615 k = (int) allkeys[i];
1617 if ( (TheMessage->cm_fields[k] != NULL)
1618 && (msgkeys[k] != NULL) ) {
1620 if (do_proto) cprintf("%s=%s\n",
1624 else if ((k == 'F') && (suppress_f)) {
1627 /* Masquerade display name if needed */
1629 if (do_proto) cprintf("%s=%s\n",
1631 TheMessage->cm_fields[k]
1640 /* begin header processing loop for RFC822 transfer format */
1645 strcpy(snode, NODENAME);
1646 strcpy(lnode, HUMANNODE);
1647 if (mode == MT_RFC822) {
1648 for (i = 0; i < 256; ++i) {
1649 if (TheMessage->cm_fields[i]) {
1650 mptr = TheMessage->cm_fields[i];
1653 safestrncpy(luser, mptr, sizeof luser);
1654 safestrncpy(suser, mptr, sizeof suser);
1656 else if (i == 'Y') {
1657 cprintf("CC: %s%s", mptr, nl);
1659 else if (i == 'P') {
1660 cprintf("Return-Path: %s%s", mptr, nl);
1662 else if (i == 'V') {
1663 cprintf("Envelope-To: %s%s", mptr, nl);
1665 else if (i == 'U') {
1666 cprintf("Subject: %s%s", mptr, nl);
1670 safestrncpy(mid, mptr, sizeof mid);
1672 safestrncpy(lnode, mptr, sizeof lnode);
1674 safestrncpy(fuser, mptr, sizeof fuser);
1675 /* else if (i == 'O')
1676 cprintf("X-Citadel-Room: %s%s",
1679 safestrncpy(snode, mptr, sizeof snode);
1681 cprintf("To: %s%s", mptr, nl);
1682 else if (i == 'T') {
1683 datestring(datestamp, sizeof datestamp,
1684 atol(mptr), DATESTRING_RFC822);
1685 cprintf("Date: %s%s", datestamp, nl);
1689 if (subject_found == 0) {
1690 cprintf("Subject: (no subject)%s", nl);
1694 for (i=0; i<strlen(suser); ++i) {
1695 suser[i] = tolower(suser[i]);
1696 if (!isalnum(suser[i])) suser[i]='_';
1699 if (mode == MT_RFC822) {
1700 if (!strcasecmp(snode, NODENAME)) {
1701 safestrncpy(snode, FQDN, sizeof snode);
1704 /* Construct a fun message id */
1705 cprintf("Message-ID: <%s", mid);
1706 if (strchr(mid, '@')==NULL) {
1707 cprintf("@%s", snode);
1711 if (!is_room_aide() && (TheMessage->cm_anon_type == MES_ANONONLY)) {
1712 cprintf("From: \"----\" <x@x.org>%s", nl);
1714 else if (!is_room_aide() && (TheMessage->cm_anon_type == MES_ANONOPT)) {
1715 cprintf("From: \"anonymous\" <x@x.org>%s", nl);
1717 else if (strlen(fuser) > 0) {
1718 cprintf("From: \"%s\" <%s>%s", luser, fuser, nl);
1721 cprintf("From: \"%s\" <%s@%s>%s", luser, suser, snode, nl);
1724 cprintf("Organization: %s%s", lnode, nl);
1726 /* Blank line signifying RFC822 end-of-headers */
1727 if (TheMessage->cm_format_type != FMT_RFC822) {
1732 /* end header processing loop ... at this point, we're in the text */
1734 if (headers_only == HEADERS_FAST) goto DONE;
1735 mptr = TheMessage->cm_fields['M'];
1737 /* Tell the client about the MIME parts in this message */
1738 if (TheMessage->cm_format_type == FMT_RFC822) {
1739 if ( (mode == MT_CITADEL) || (mode == MT_MIME) ) {
1740 memset(&ma, 0, sizeof(struct ma_info));
1741 mime_parser(mptr, NULL,
1742 (do_proto ? *list_this_part : NULL),
1743 (do_proto ? *list_this_pref : NULL),
1744 (do_proto ? *list_this_suff : NULL),
1747 else if (mode == MT_RFC822) { /* unparsed RFC822 dump */
1748 char *start_of_text = NULL;
1749 start_of_text = strstr(mptr, "\n\r\n");
1750 if (start_of_text == NULL) start_of_text = strstr(mptr, "\n\n");
1751 if (start_of_text == NULL) start_of_text = mptr;
1753 start_of_text = strstr(start_of_text, "\n");
1758 int nllen = strlen(nl);
1759 while (ch=*mptr, ch!=0) {
1765 ((headers_only == HEADERS_NONE) && (mptr >= start_of_text))
1766 || ((headers_only == HEADERS_ONLY) && (mptr < start_of_text))
1767 || ((headers_only != HEADERS_NONE) && (headers_only != HEADERS_ONLY))
1770 sprintf(&outbuf[outlen], "%s", nl);
1774 outbuf[outlen++] = ch;
1779 if (outlen > 1000) {
1780 client_write(outbuf, outlen);
1785 client_write(outbuf, outlen);
1793 if (headers_only == HEADERS_ONLY) {
1797 /* signify start of msg text */
1798 if ( (mode == MT_CITADEL) || (mode == MT_MIME) ) {
1799 if (do_proto) cprintf("text\n");
1802 /* If the format type on disk is 1 (fixed-format), then we want
1803 * everything to be output completely literally ... regardless of
1804 * what message transfer format is in use.
1806 if (TheMessage->cm_format_type == FMT_FIXED) {
1807 if (mode == MT_MIME) {
1808 cprintf("Content-type: text/plain\n\n");
1811 while (ch = *mptr++, ch > 0) {
1814 if ((ch == 10) || (strlen(buf) > 250)) {
1815 cprintf("%s%s", buf, nl);
1818 buf[strlen(buf) + 1] = 0;
1819 buf[strlen(buf)] = ch;
1822 if (strlen(buf) > 0)
1823 cprintf("%s%s", buf, nl);
1826 /* If the message on disk is format 0 (Citadel vari-format), we
1827 * output using the formatter at 80 columns. This is the final output
1828 * form if the transfer format is RFC822, but if the transfer format
1829 * is Citadel proprietary, it'll still work, because the indentation
1830 * for new paragraphs is correct and the client will reformat the
1831 * message to the reader's screen width.
1833 if (TheMessage->cm_format_type == FMT_CITADEL) {
1834 if (mode == MT_MIME) {
1835 cprintf("Content-type: text/x-citadel-variformat\n\n");
1837 memfmout(mptr, 0, nl);
1840 /* If the message on disk is format 4 (MIME), we've gotta hand it
1841 * off to the MIME parser. The client has already been told that
1842 * this message is format 1 (fixed format), so the callback function
1843 * we use will display those parts as-is.
1845 if (TheMessage->cm_format_type == FMT_RFC822) {
1846 memset(&ma, 0, sizeof(struct ma_info));
1848 if (mode == MT_MIME) {
1849 ma.use_fo_hooks = 0;
1850 strcpy(ma.chosen_part, "1");
1851 ma.chosen_pref = 9999;
1852 mime_parser(mptr, NULL,
1853 *choose_preferred, *fixed_output_pre,
1854 *fixed_output_post, (void *)&ma, 0);
1855 mime_parser(mptr, NULL,
1856 *output_preferred, NULL, NULL, (void *)&ma, 0);
1859 ma.use_fo_hooks = 1;
1860 mime_parser(mptr, NULL,
1861 *fixed_output, *fixed_output_pre,
1862 *fixed_output_post, (void *)&ma, 0);
1867 DONE: /* now we're done */
1868 if (do_proto) cprintf("000\n");
1875 * display a message (mode 0 - Citadel proprietary)
1877 void cmd_msg0(char *cmdbuf)
1880 int headers_only = HEADERS_ALL;
1882 msgid = extract_long(cmdbuf, 0);
1883 headers_only = extract_int(cmdbuf, 1);
1885 CtdlOutputMsg(msgid, MT_CITADEL, headers_only, 1, 0, NULL);
1891 * display a message (mode 2 - RFC822)
1893 void cmd_msg2(char *cmdbuf)
1896 int headers_only = HEADERS_ALL;
1898 msgid = extract_long(cmdbuf, 0);
1899 headers_only = extract_int(cmdbuf, 1);
1901 CtdlOutputMsg(msgid, MT_RFC822, headers_only, 1, 1, NULL);
1907 * display a message (mode 3 - IGnet raw format - internal programs only)
1909 void cmd_msg3(char *cmdbuf)
1912 struct CtdlMessage *msg = NULL;
1915 if (CC->internal_pgm == 0) {
1916 cprintf("%d This command is for internal programs only.\n",
1917 ERROR + HIGHER_ACCESS_REQUIRED);
1921 msgnum = extract_long(cmdbuf, 0);
1922 msg = CtdlFetchMessage(msgnum, 1);
1924 cprintf("%d Message %ld not found.\n",
1925 ERROR + MESSAGE_NOT_FOUND, msgnum);
1929 serialize_message(&smr, msg);
1930 CtdlFreeMessage(msg);
1933 cprintf("%d Unable to serialize message\n",
1934 ERROR + INTERNAL_ERROR);
1938 cprintf("%d %ld\n", BINARY_FOLLOWS, (long)smr.len);
1939 client_write((char *)smr.ser, (int)smr.len);
1946 * Display a message using MIME content types
1948 void cmd_msg4(char *cmdbuf)
1953 msgid = extract_long(cmdbuf, 0);
1954 extract_token(section, cmdbuf, 1, '|', sizeof section);
1955 CtdlOutputMsg(msgid, MT_MIME, 0, 1, 0, (section[0] ? section : NULL) );
1961 * Client tells us its preferred message format(s)
1963 void cmd_msgp(char *cmdbuf)
1965 safestrncpy(CC->preferred_formats, cmdbuf,
1966 sizeof(CC->preferred_formats));
1967 cprintf("%d ok\n", CIT_OK);
1972 * Open a component of a MIME message as a download file
1974 void cmd_opna(char *cmdbuf)
1977 char desired_section[128];
1979 msgid = extract_long(cmdbuf, 0);
1980 extract_token(desired_section, cmdbuf, 1, '|', sizeof desired_section);
1981 safestrncpy(CC->download_desired_section, desired_section,
1982 sizeof CC->download_desired_section);
1983 CtdlOutputMsg(msgid, MT_DOWNLOAD, 0, 1, 1, NULL);
1988 * Open a component of a MIME message and transmit it all at once
1990 void cmd_dlat(char *cmdbuf)
1993 char desired_section[128];
1995 msgid = extract_long(cmdbuf, 0);
1996 extract_token(desired_section, cmdbuf, 1, '|', sizeof desired_section);
1997 safestrncpy(CC->download_desired_section, desired_section,
1998 sizeof CC->download_desired_section);
1999 CtdlOutputMsg(msgid, MT_SPEW_SECTION, 0, 1, 1, NULL);
2004 * Save one or more message pointers into a specified room
2005 * (Returns 0 for success, nonzero for failure)
2006 * roomname may be NULL to use the current room
2008 * Note that the 'supplied_msg' field may be set to NULL, in which case
2009 * the message will be fetched from disk, by number, if we need to perform
2010 * replication checks. This adds an additional database read, so if the
2011 * caller already has the message in memory then it should be supplied. (Obviously
2012 * this mode of operation only works if we're saving a single message.)
2014 int CtdlSaveMsgPointersInRoom(char *roomname, long newmsgidlist[], int num_newmsgs,
2015 int do_repl_check, struct CtdlMessage *supplied_msg)
2018 char hold_rm[ROOMNAMELEN];
2019 struct cdbdata *cdbfr;
2022 long highest_msg = 0L;
2025 struct CtdlMessage *msg = NULL;
2027 long *msgs_to_be_merged = NULL;
2028 int num_msgs_to_be_merged = 0;
2031 "CtdlSaveMsgPointersInRoom(room=%s, num_msgs=%d, repl=%d)\n",
2032 roomname, num_newmsgs, do_repl_check);
2034 strcpy(hold_rm, CC->room.QRname);
2037 if (newmsgidlist == NULL) return(ERROR + INTERNAL_ERROR);
2038 if (num_newmsgs < 1) return(ERROR + INTERNAL_ERROR);
2039 if (num_newmsgs > 1) supplied_msg = NULL;
2041 /* Now the regular stuff */
2042 if (lgetroom(&CC->room,
2043 ((roomname != NULL) ? roomname : CC->room.QRname) )
2045 lprintf(CTDL_ERR, "No such room <%s>\n", roomname);
2046 return(ERROR + ROOM_NOT_FOUND);
2050 msgs_to_be_merged = malloc(sizeof(long) * num_newmsgs);
2051 num_msgs_to_be_merged = 0;
2054 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long));
2055 if (cdbfr == NULL) {
2059 msglist = (long *) cdbfr->ptr;
2060 cdbfr->ptr = NULL; /* CtdlSaveMsgPointerInRoom() now owns this memory */
2061 num_msgs = cdbfr->len / sizeof(long);
2066 /* Create a list of msgid's which were supplied by the caller, but do
2067 * not already exist in the target room. It is absolutely taboo to
2068 * have more than one reference to the same message in a room.
2070 for (i=0; i<num_newmsgs; ++i) {
2072 if (num_msgs > 0) for (j=0; j<num_msgs; ++j) {
2073 if (msglist[j] == newmsgidlist[i]) {
2078 msgs_to_be_merged[num_msgs_to_be_merged++] = newmsgidlist[i];
2082 lprintf(9, "%d unique messages to be merged\n", num_msgs_to_be_merged);
2085 * Now merge the new messages
2087 msglist = realloc(msglist, (sizeof(long) * (num_msgs + num_msgs_to_be_merged)) );
2088 if (msglist == NULL) {
2089 lprintf(CTDL_ALERT, "ERROR: can't realloc message list!\n");
2091 memcpy(&msglist[num_msgs], msgs_to_be_merged, (sizeof(long) * num_msgs_to_be_merged) );
2092 num_msgs += num_msgs_to_be_merged;
2094 /* Sort the message list, so all the msgid's are in order */
2095 num_msgs = sort_msglist(msglist, num_msgs);
2097 /* Determine the highest message number */
2098 highest_msg = msglist[num_msgs - 1];
2100 /* Write it back to disk. */
2101 cdb_store(CDB_MSGLISTS, &CC->room.QRnumber, (int)sizeof(long),
2102 msglist, (int)(num_msgs * sizeof(long)));
2104 /* Free up the memory we used. */
2107 /* Update the highest-message pointer and unlock the room. */
2108 CC->room.QRhighest = highest_msg;
2109 lputroom(&CC->room);
2111 /* Perform replication checks if necessary */
2112 if ( (DoesThisRoomNeedEuidIndexing(&CC->room)) && (do_repl_check) ) {
2113 lprintf(CTDL_DEBUG, "CtdlSaveMsgPointerInRoom() doing repl checks\n");
2115 for (i=0; i<num_msgs_to_be_merged; ++i) {
2116 msgid = msgs_to_be_merged[i];
2118 if (supplied_msg != NULL) {
2122 msg = CtdlFetchMessage(msgid, 0);
2126 ReplicationChecks(msg);
2128 /* If the message has an Exclusive ID, index that... */
2129 if (msg->cm_fields['E'] != NULL) {
2130 index_message_by_euid(msg->cm_fields['E'], &CC->room, msgid);
2133 /* Free up the memory we may have allocated */
2134 if (msg != supplied_msg) {
2135 CtdlFreeMessage(msg);
2143 lprintf(CTDL_DEBUG, "CtdlSaveMsgPointerInRoom() skips repl checks\n");
2146 /* Submit this room for net processing */
2147 network_queue_room(&CC->room, NULL);
2149 #ifdef HAVE_LIBSIEVE
2150 /* If this is someone's inbox, submit the room for sieve processing */
2151 if (!strcasecmp(&CC->room.QRname[11], MAILROOM)) {
2152 sieve_queue_room(&CC->room);
2154 #endif /* HAVE_LIBSIEVE */
2156 /* Go back to the room we were in before we wandered here... */
2157 getroom(&CC->room, hold_rm);
2159 /* Bump the reference count for all messages which were merged */
2160 for (i=0; i<num_msgs_to_be_merged; ++i) {
2161 AdjRefCount(msgs_to_be_merged[i], +1);
2164 /* Free up memory... */
2165 if (msgs_to_be_merged != NULL) {
2166 free(msgs_to_be_merged);
2169 /* Return success. */
2175 * This is the same as CtdlSaveMsgPointersInRoom() but it only accepts
2178 int CtdlSaveMsgPointerInRoom(char *roomname, long msgid,
2179 int do_repl_check, struct CtdlMessage *supplied_msg)
2181 return CtdlSaveMsgPointersInRoom(roomname, &msgid, 1, do_repl_check, supplied_msg);
2188 * Message base operation to save a new message to the message store
2189 * (returns new message number)
2191 * This is the back end for CtdlSubmitMsg() and should not be directly
2192 * called by server-side modules.
2195 long send_message(struct CtdlMessage *msg) {
2203 /* Get a new message number */
2204 newmsgid = get_new_message_number();
2205 snprintf(msgidbuf, sizeof msgidbuf, "%010ld@%s", newmsgid, config.c_fqdn);
2207 /* Generate an ID if we don't have one already */
2208 if (msg->cm_fields['I']==NULL) {
2209 msg->cm_fields['I'] = strdup(msgidbuf);
2212 /* If the message is big, set its body aside for storage elsewhere */
2213 if (msg->cm_fields['M'] != NULL) {
2214 if (strlen(msg->cm_fields['M']) > BIGMSG) {
2216 holdM = msg->cm_fields['M'];
2217 msg->cm_fields['M'] = NULL;
2221 /* Serialize our data structure for storage in the database */
2222 serialize_message(&smr, msg);
2225 msg->cm_fields['M'] = holdM;
2229 cprintf("%d Unable to serialize message\n",
2230 ERROR + INTERNAL_ERROR);
2234 /* Write our little bundle of joy into the message base */
2235 if (cdb_store(CDB_MSGMAIN, &newmsgid, (int)sizeof(long),
2236 smr.ser, smr.len) < 0) {
2237 lprintf(CTDL_ERR, "Can't store message\n");
2241 cdb_store(CDB_BIGMSGS,
2251 /* Free the memory we used for the serialized message */
2254 /* Return the *local* message ID to the caller
2255 * (even if we're storing an incoming network message)
2263 * Serialize a struct CtdlMessage into the format used on disk and network.
2265 * This function loads up a "struct ser_ret" (defined in server.h) which
2266 * contains the length of the serialized message and a pointer to the
2267 * serialized message in memory. THE LATTER MUST BE FREED BY THE CALLER.
2269 void serialize_message(struct ser_ret *ret, /* return values */
2270 struct CtdlMessage *msg) /* unserialized msg */
2272 size_t wlen, fieldlen;
2274 static char *forder = FORDER;
2277 * Check for valid message format
2279 if (is_valid_message(msg) == 0) {
2280 lprintf(CTDL_ERR, "serialize_message() aborting due to invalid message\n");
2287 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL)
2288 ret->len = ret->len +
2289 strlen(msg->cm_fields[(int)forder[i]]) + 2;
2291 ret->ser = malloc(ret->len);
2292 if (ret->ser == NULL) {
2293 lprintf(CTDL_ERR, "serialize_message() malloc(%ld) failed: %s\n",
2294 (long)ret->len, strerror(errno));
2301 ret->ser[1] = msg->cm_anon_type;
2302 ret->ser[2] = msg->cm_format_type;
2305 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL) {
2306 fieldlen = strlen(msg->cm_fields[(int)forder[i]]);
2307 ret->ser[wlen++] = (char)forder[i];
2308 safestrncpy((char *)&ret->ser[wlen], msg->cm_fields[(int)forder[i]], fieldlen+1);
2309 wlen = wlen + fieldlen + 1;
2311 if (ret->len != wlen) lprintf(CTDL_ERR, "ERROR: len=%ld wlen=%ld\n",
2312 (long)ret->len, (long)wlen);
2320 * Check to see if any messages already exist in the current room which
2321 * carry the same Exclusive ID as this one. If any are found, delete them.
2323 void ReplicationChecks(struct CtdlMessage *msg) {
2324 long old_msgnum = (-1L);
2326 if (DoesThisRoomNeedEuidIndexing(&CC->room) == 0) return;
2328 lprintf(CTDL_DEBUG, "Performing replication checks in <%s>\n",
2331 /* No exclusive id? Don't do anything. */
2332 if (msg == NULL) return;
2333 if (msg->cm_fields['E'] == NULL) return;
2334 if (strlen(msg->cm_fields['E']) == 0) return;
2335 /*lprintf(CTDL_DEBUG, "Exclusive ID: <%s> for room <%s>\n",
2336 msg->cm_fields['E'], CC->room.QRname);*/
2338 old_msgnum = locate_message_by_euid(msg->cm_fields['E'], &CC->room);
2339 if (old_msgnum > 0L) {
2340 lprintf(CTDL_DEBUG, "ReplicationChecks() replacing message %ld\n", old_msgnum);
2341 CtdlDeleteMessages(CC->room.QRname, &old_msgnum, 1, "");
2348 * Save a message to disk and submit it into the delivery system.
2350 long CtdlSubmitMsg(struct CtdlMessage *msg, /* message to save */
2351 struct recptypes *recps, /* recipients (if mail) */
2352 char *force /* force a particular room? */
2354 char submit_filename[128];
2355 char generated_timestamp[32];
2356 char hold_rm[ROOMNAMELEN];
2357 char actual_rm[ROOMNAMELEN];
2358 char force_room[ROOMNAMELEN];
2359 char content_type[SIZ]; /* We have to learn this */
2360 char recipient[SIZ];
2363 struct ctdluser userbuf;
2365 struct MetaData smi;
2366 FILE *network_fp = NULL;
2367 static int seqnum = 1;
2368 struct CtdlMessage *imsg = NULL;
2370 size_t instr_alloc = 0;
2372 char *hold_R, *hold_D;
2373 char *collected_addresses = NULL;
2374 struct addresses_to_be_filed *aptr = NULL;
2375 char *saved_rfc822_version = NULL;
2376 int qualified_for_journaling = 0;
2378 lprintf(CTDL_DEBUG, "CtdlSubmitMsg() called\n");
2379 if (is_valid_message(msg) == 0) return(-1); /* self check */
2381 /* If this message has no timestamp, we take the liberty of
2382 * giving it one, right now.
2384 if (msg->cm_fields['T'] == NULL) {
2385 snprintf(generated_timestamp, sizeof generated_timestamp, "%ld", (long)time(NULL));
2386 msg->cm_fields['T'] = strdup(generated_timestamp);
2389 /* If this message has no path, we generate one.
2391 if (msg->cm_fields['P'] == NULL) {
2392 if (msg->cm_fields['A'] != NULL) {
2393 msg->cm_fields['P'] = strdup(msg->cm_fields['A']);
2394 for (a=0; a<strlen(msg->cm_fields['P']); ++a) {
2395 if (isspace(msg->cm_fields['P'][a])) {
2396 msg->cm_fields['P'][a] = ' ';
2401 msg->cm_fields['P'] = strdup("unknown");
2405 if (force == NULL) {
2406 strcpy(force_room, "");
2409 strcpy(force_room, force);
2412 /* Learn about what's inside, because it's what's inside that counts */
2413 if (msg->cm_fields['M'] == NULL) {
2414 lprintf(CTDL_ERR, "ERROR: attempt to save message with NULL body\n");
2418 switch (msg->cm_format_type) {
2420 strcpy(content_type, "text/x-citadel-variformat");
2423 strcpy(content_type, "text/plain");
2426 strcpy(content_type, "text/plain");
2427 mptr = bmstrcasestr(msg->cm_fields['M'], "Content-type:");
2429 safestrncpy(content_type, &mptr[13], sizeof content_type);
2430 striplt(content_type);
2431 for (a = 0; a < strlen(content_type); ++a) {
2432 if ((content_type[a] == ';')
2433 || (content_type[a] == ' ')
2434 || (content_type[a] == 13)
2435 || (content_type[a] == 10)) {
2436 content_type[a] = 0;
2442 /* Goto the correct room */
2443 lprintf(CTDL_DEBUG, "Selected room %s\n", (recps) ? CC->room.QRname : SENTITEMS);
2444 strcpy(hold_rm, CC->room.QRname);
2445 strcpy(actual_rm, CC->room.QRname);
2446 if (recps != NULL) {
2447 strcpy(actual_rm, SENTITEMS);
2450 /* If the user is a twit, move to the twit room for posting */
2452 if (CC->user.axlevel == 2) {
2453 strcpy(hold_rm, actual_rm);
2454 strcpy(actual_rm, config.c_twitroom);
2455 lprintf(CTDL_DEBUG, "Diverting to twit room\n");
2459 /* ...or if this message is destined for Aide> then go there. */
2460 if (strlen(force_room) > 0) {
2461 strcpy(actual_rm, force_room);
2464 lprintf(CTDL_DEBUG, "Final selection: %s\n", actual_rm);
2465 if (strcasecmp(actual_rm, CC->room.QRname)) {
2466 /* getroom(&CC->room, actual_rm); */
2467 usergoto(actual_rm, 0, 1, NULL, NULL);
2471 * If this message has no O (room) field, generate one.
2473 if (msg->cm_fields['O'] == NULL) {
2474 msg->cm_fields['O'] = strdup(CC->room.QRname);
2477 /* Perform "before save" hooks (aborting if any return nonzero) */
2478 lprintf(CTDL_DEBUG, "Performing before-save hooks\n");
2479 if (PerformMessageHooks(msg, EVT_BEFORESAVE) > 0) return(-3);
2482 * If this message has an Exclusive ID, and the room is replication
2483 * checking enabled, then do replication checks.
2485 if (DoesThisRoomNeedEuidIndexing(&CC->room)) {
2486 ReplicationChecks(msg);
2489 /* Save it to disk */
2490 lprintf(CTDL_DEBUG, "Saving to disk\n");
2491 newmsgid = send_message(msg);
2492 if (newmsgid <= 0L) return(-5);
2494 /* Write a supplemental message info record. This doesn't have to
2495 * be a critical section because nobody else knows about this message
2498 lprintf(CTDL_DEBUG, "Creating MetaData record\n");
2499 memset(&smi, 0, sizeof(struct MetaData));
2500 smi.meta_msgnum = newmsgid;
2501 smi.meta_refcount = 0;
2502 safestrncpy(smi.meta_content_type, content_type,
2503 sizeof smi.meta_content_type);
2506 * Measure how big this message will be when rendered as RFC822.
2507 * We do this for two reasons:
2508 * 1. We need the RFC822 length for the new metadata record, so the
2509 * POP and IMAP services don't have to calculate message lengths
2510 * while the user is waiting (multiplied by potentially hundreds
2511 * or thousands of messages).
2512 * 2. If journaling is enabled, we will need an RFC822 version of the
2513 * message to attach to the journalized copy.
2515 if (CC->redirect_buffer != NULL) {
2516 lprintf(CTDL_ALERT, "CC->redirect_buffer is not NULL during message submission!\n");
2519 CC->redirect_buffer = malloc(SIZ);
2520 CC->redirect_len = 0;
2521 CC->redirect_alloc = SIZ;
2522 CtdlOutputPreLoadedMsg(msg, MT_RFC822, HEADERS_ALL, 0, 1);
2523 smi.meta_rfc822_length = CC->redirect_len;
2524 saved_rfc822_version = CC->redirect_buffer;
2525 CC->redirect_buffer = NULL;
2526 CC->redirect_len = 0;
2527 CC->redirect_alloc = 0;
2531 /* Now figure out where to store the pointers */
2532 lprintf(CTDL_DEBUG, "Storing pointers\n");
2534 /* If this is being done by the networker delivering a private
2535 * message, we want to BYPASS saving the sender's copy (because there
2536 * is no local sender; it would otherwise go to the Trashcan).
2538 if ((!CC->internal_pgm) || (recps == NULL)) {
2539 if (CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 1, msg) != 0) {
2540 lprintf(CTDL_ERR, "ERROR saving message pointer!\n");
2541 CtdlSaveMsgPointerInRoom(config.c_aideroom, newmsgid, 0, msg);
2545 /* For internet mail, drop a copy in the outbound queue room */
2547 if (recps->num_internet > 0) {
2548 CtdlSaveMsgPointerInRoom(SMTP_SPOOLOUT_ROOM, newmsgid, 0, msg);
2551 /* If other rooms are specified, drop them there too. */
2553 if (recps->num_room > 0)
2554 for (i=0; i<num_tokens(recps->recp_room, '|'); ++i) {
2555 extract_token(recipient, recps->recp_room, i,
2556 '|', sizeof recipient);
2557 lprintf(CTDL_DEBUG, "Delivering to room <%s>\n", recipient);
2558 CtdlSaveMsgPointerInRoom(recipient, newmsgid, 0, msg);
2561 /* Bump this user's messages posted counter. */
2562 lprintf(CTDL_DEBUG, "Updating user\n");
2563 lgetuser(&CC->user, CC->curr_user);
2564 CC->user.posted = CC->user.posted + 1;
2565 lputuser(&CC->user);
2567 /* If this is private, local mail, make a copy in the
2568 * recipient's mailbox and bump the reference count.
2571 if (recps->num_local > 0)
2572 for (i=0; i<num_tokens(recps->recp_local, '|'); ++i) {
2573 extract_token(recipient, recps->recp_local, i,
2574 '|', sizeof recipient);
2575 lprintf(CTDL_DEBUG, "Delivering private local mail to <%s>\n",
2577 if (getuser(&userbuf, recipient) == 0) {
2578 // Add a flag so the Funambol module knows its mail
2579 msg->cm_fields['W'] = strdup(recipient);
2580 MailboxName(actual_rm, sizeof actual_rm, &userbuf, MAILROOM);
2581 CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 0, msg);
2582 BumpNewMailCounter(userbuf.usernum);
2583 if (strlen(config.c_funambol_host) > 0) {
2584 /* Generate a instruction message for the Funambol notification
2585 * server, in the same style as the SMTP queue
2588 instr = malloc(instr_alloc);
2589 snprintf(instr, instr_alloc,
2590 "Content-type: %s\n\nmsgid|%ld\nsubmitted|%ld\n"
2592 SPOOLMIME, newmsgid, (long)time(NULL),
2593 msg->cm_fields['A'], msg->cm_fields['N']
2596 imsg = malloc(sizeof(struct CtdlMessage));
2597 memset(imsg, 0, sizeof(struct CtdlMessage));
2598 imsg->cm_magic = CTDLMESSAGE_MAGIC;
2599 imsg->cm_anon_type = MES_NORMAL;
2600 imsg->cm_format_type = FMT_RFC822;
2601 imsg->cm_fields['A'] = strdup("Citadel");
2602 imsg->cm_fields['J'] = strdup("do not journal");
2603 imsg->cm_fields['M'] = instr; /* imsg owns this memory now */
2604 imsg->cm_fields['W'] = strdup(recipient);
2605 CtdlSubmitMsg(imsg, NULL, FNBL_QUEUE_ROOM);
2606 CtdlFreeMessage(imsg);
2610 lprintf(CTDL_DEBUG, "No user <%s>\n", recipient);
2611 CtdlSaveMsgPointerInRoom(config.c_aideroom,
2616 /* Perform "after save" hooks */
2617 lprintf(CTDL_DEBUG, "Performing after-save hooks\n");
2618 PerformMessageHooks(msg, EVT_AFTERSAVE);
2620 /* For IGnet mail, we have to save a new copy into the spooler for
2621 * each recipient, with the R and D fields set to the recipient and
2622 * destination-node. This has two ugly side effects: all other
2623 * recipients end up being unlisted in this recipient's copy of the
2624 * message, and it has to deliver multiple messages to the same
2625 * node. We'll revisit this again in a year or so when everyone has
2626 * a network spool receiver that can handle the new style messages.
2629 if (recps->num_ignet > 0)
2630 for (i=0; i<num_tokens(recps->recp_ignet, '|'); ++i) {
2631 extract_token(recipient, recps->recp_ignet, i,
2632 '|', sizeof recipient);
2634 hold_R = msg->cm_fields['R'];
2635 hold_D = msg->cm_fields['D'];
2636 msg->cm_fields['R'] = malloc(SIZ);
2637 msg->cm_fields['D'] = malloc(128);
2638 extract_token(msg->cm_fields['R'], recipient, 0, '@', SIZ);
2639 extract_token(msg->cm_fields['D'], recipient, 1, '@', 128);
2641 serialize_message(&smr, msg);
2643 snprintf(submit_filename, sizeof submit_filename,
2644 "%s/netmail.%04lx.%04x.%04x",
2646 (long) getpid(), CC->cs_pid, ++seqnum);
2647 network_fp = fopen(submit_filename, "wb+");
2648 if (network_fp != NULL) {
2649 fwrite(smr.ser, smr.len, 1, network_fp);
2655 free(msg->cm_fields['R']);
2656 free(msg->cm_fields['D']);
2657 msg->cm_fields['R'] = hold_R;
2658 msg->cm_fields['D'] = hold_D;
2661 /* Go back to the room we started from */
2662 lprintf(CTDL_DEBUG, "Returning to original room %s\n", hold_rm);
2663 if (strcasecmp(hold_rm, CC->room.QRname))
2664 usergoto(hold_rm, 0, 1, NULL, NULL);
2666 /* For internet mail, generate delivery instructions.
2667 * Yes, this is recursive. Deal with it. Infinite recursion does
2668 * not happen because the delivery instructions message does not
2669 * contain a recipient.
2672 if (recps->num_internet > 0) {
2673 lprintf(CTDL_DEBUG, "Generating delivery instructions\n");
2675 instr = malloc(instr_alloc);
2676 snprintf(instr, instr_alloc,
2677 "Content-type: %s\n\nmsgid|%ld\nsubmitted|%ld\n"
2679 SPOOLMIME, newmsgid, (long)time(NULL),
2680 msg->cm_fields['A'], msg->cm_fields['N']
2683 for (i=0; i<num_tokens(recps->recp_internet, '|'); ++i) {
2684 size_t tmp = strlen(instr);
2685 extract_token(recipient, recps->recp_internet, i, '|', sizeof recipient);
2686 if ((tmp + strlen(recipient) + 32) > instr_alloc) {
2687 instr_alloc = instr_alloc * 2;
2688 instr = realloc(instr, instr_alloc);
2690 snprintf(&instr[tmp], instr_alloc - tmp, "remote|%s|0||\n", recipient);
2693 imsg = malloc(sizeof(struct CtdlMessage));
2694 memset(imsg, 0, sizeof(struct CtdlMessage));
2695 imsg->cm_magic = CTDLMESSAGE_MAGIC;
2696 imsg->cm_anon_type = MES_NORMAL;
2697 imsg->cm_format_type = FMT_RFC822;
2698 imsg->cm_fields['A'] = strdup("Citadel");
2699 imsg->cm_fields['J'] = strdup("do not journal");
2700 imsg->cm_fields['M'] = instr; /* imsg owns this memory now */
2701 CtdlSubmitMsg(imsg, NULL, SMTP_SPOOLOUT_ROOM);
2702 CtdlFreeMessage(imsg);
2706 * Any addresses to harvest for someone's address book?
2708 if ( (CC->logged_in) && (recps != NULL) ) {
2709 collected_addresses = harvest_collected_addresses(msg);
2712 if (collected_addresses != NULL) {
2713 begin_critical_section(S_ATBF);
2714 aptr = (struct addresses_to_be_filed *)
2715 malloc(sizeof(struct addresses_to_be_filed));
2717 MailboxName(actual_rm, sizeof actual_rm,
2718 &CC->user, USERCONTACTSROOM);
2719 aptr->roomname = strdup(actual_rm);
2720 aptr->collected_addresses = collected_addresses;
2722 end_critical_section(S_ATBF);
2726 * Determine whether this message qualifies for journaling.
2728 if (msg->cm_fields['J'] != NULL) {
2729 qualified_for_journaling = 0;
2732 if (recps == NULL) {
2733 qualified_for_journaling = config.c_journal_pubmsgs;
2735 else if (recps->num_local + recps->num_ignet + recps->num_internet > 0) {
2736 qualified_for_journaling = config.c_journal_email;
2739 qualified_for_journaling = config.c_journal_pubmsgs;
2744 * Do we have to perform journaling? If so, hand off the saved
2745 * RFC822 version will be handed off to the journaler for background
2746 * submit. Otherwise, we have to free the memory ourselves.
2748 if (saved_rfc822_version != NULL) {
2749 if (qualified_for_journaling) {
2750 JournalBackgroundSubmit(msg, saved_rfc822_version, recps);
2753 free(saved_rfc822_version);
2766 * Convenience function for generating small administrative messages.
2768 void quickie_message(char *from, char *fromaddr, char *to, char *room, char *text,
2769 int format_type, char *subject)
2771 struct CtdlMessage *msg;
2772 struct recptypes *recp = NULL;
2774 msg = malloc(sizeof(struct CtdlMessage));
2775 memset(msg, 0, sizeof(struct CtdlMessage));
2776 msg->cm_magic = CTDLMESSAGE_MAGIC;
2777 msg->cm_anon_type = MES_NORMAL;
2778 msg->cm_format_type = format_type;
2781 msg->cm_fields['A'] = strdup(from);
2783 else if (fromaddr != NULL) {
2784 msg->cm_fields['A'] = strdup(fromaddr);
2785 if (strchr(msg->cm_fields['A'], '@')) {
2786 *strchr(msg->cm_fields['A'], '@') = 0;
2790 msg->cm_fields['A'] = strdup("Citadel");
2793 if (fromaddr != NULL) msg->cm_fields['F'] = strdup(fromaddr);
2794 if (room != NULL) msg->cm_fields['O'] = strdup(room);
2795 msg->cm_fields['N'] = strdup(NODENAME);
2797 msg->cm_fields['R'] = strdup(to);
2798 recp = validate_recipients(to);
2800 if (subject != NULL) {
2801 msg->cm_fields['U'] = strdup(subject);
2803 msg->cm_fields['M'] = strdup(text);
2805 CtdlSubmitMsg(msg, recp, room);
2806 CtdlFreeMessage(msg);
2807 if (recp != NULL) free_recipients(recp);
2813 * Back end function used by CtdlMakeMessage() and similar functions
2815 char *CtdlReadMessageBody(char *terminator, /* token signalling EOT */
2816 size_t maxlen, /* maximum message length */
2817 char *exist, /* if non-null, append to it;
2818 exist is ALWAYS freed */
2819 int crlf /* CRLF newlines instead of LF */
2823 size_t message_len = 0;
2824 size_t buffer_len = 0;
2831 if (exist == NULL) {
2838 message_len = strlen(exist);
2839 buffer_len = message_len + 4096;
2840 m = realloc(exist, buffer_len);
2847 /* Do we need to change leading ".." to "." for SMTP escaping? */
2848 if (!strcmp(terminator, ".")) {
2852 /* flush the input if we have nowhere to store it */
2857 /* read in the lines of message text one by one */
2859 if (client_getln(buf, (sizeof buf - 3)) < 1) finished = 1;
2860 if (!strcmp(buf, terminator)) finished = 1;
2862 strcat(buf, "\r\n");
2868 /* Unescape SMTP-style input of two dots at the beginning of the line */
2870 if (!strncmp(buf, "..", 2)) {
2871 strcpy(buf, &buf[1]);
2875 if ( (!flushing) && (!finished) ) {
2876 /* Measure the line */
2877 linelen = strlen(buf);
2879 /* augment the buffer if we have to */
2880 if ((message_len + linelen) >= buffer_len) {
2881 ptr = realloc(m, (buffer_len * 2) );
2882 if (ptr == NULL) { /* flush if can't allocate */
2885 buffer_len = (buffer_len * 2);
2887 lprintf(CTDL_DEBUG, "buffer_len is now %ld\n", (long)buffer_len);
2891 /* Add the new line to the buffer. NOTE: this loop must avoid
2892 * using functions like strcat() and strlen() because they
2893 * traverse the entire buffer upon every call, and doing that
2894 * for a multi-megabyte message slows it down beyond usability.
2896 strcpy(&m[message_len], buf);
2897 message_len += linelen;
2900 /* if we've hit the max msg length, flush the rest */
2901 if (message_len >= maxlen) flushing = 1;
2903 } while (!finished);
2911 * Build a binary message to be saved on disk.
2912 * (NOTE: if you supply 'preformatted_text', the buffer you give it
2913 * will become part of the message. This means you are no longer
2914 * responsible for managing that memory -- it will be freed along with
2915 * the rest of the fields when CtdlFreeMessage() is called.)
2918 struct CtdlMessage *CtdlMakeMessage(
2919 struct ctdluser *author, /* author's user structure */
2920 char *recipient, /* NULL if it's not mail */
2921 char *recp_cc, /* NULL if it's not mail */
2922 char *room, /* room where it's going */
2923 int type, /* see MES_ types in header file */
2924 int format_type, /* variformat, plain text, MIME... */
2925 char *fake_name, /* who we're masquerading as */
2926 char *my_email, /* which of my email addresses to use (empty is ok) */
2927 char *subject, /* Subject (optional) */
2928 char *supplied_euid, /* ...or NULL if this is irrelevant */
2929 char *preformatted_text /* ...or NULL to read text from client */
2931 char dest_node[256];
2933 struct CtdlMessage *msg;
2935 msg = malloc(sizeof(struct CtdlMessage));
2936 memset(msg, 0, sizeof(struct CtdlMessage));
2937 msg->cm_magic = CTDLMESSAGE_MAGIC;
2938 msg->cm_anon_type = type;
2939 msg->cm_format_type = format_type;
2941 /* Don't confuse the poor folks if it's not routed mail. */
2942 strcpy(dest_node, "");
2947 snprintf(buf, sizeof buf, "cit%ld", author->usernum); /* Path */
2948 msg->cm_fields['P'] = strdup(buf);
2950 snprintf(buf, sizeof buf, "%ld", (long)time(NULL)); /* timestamp */
2951 msg->cm_fields['T'] = strdup(buf);
2953 if (fake_name[0]) /* author */
2954 msg->cm_fields['A'] = strdup(fake_name);
2956 msg->cm_fields['A'] = strdup(author->fullname);
2958 if (CC->room.QRflags & QR_MAILBOX) { /* room */
2959 msg->cm_fields['O'] = strdup(&CC->room.QRname[11]);
2962 msg->cm_fields['O'] = strdup(CC->room.QRname);
2965 msg->cm_fields['N'] = strdup(NODENAME); /* nodename */
2966 msg->cm_fields['H'] = strdup(HUMANNODE); /* hnodename */
2968 if (recipient[0] != 0) {
2969 msg->cm_fields['R'] = strdup(recipient);
2971 if (recp_cc[0] != 0) {
2972 msg->cm_fields['Y'] = strdup(recp_cc);
2974 if (dest_node[0] != 0) {
2975 msg->cm_fields['D'] = strdup(dest_node);
2978 if (strlen(my_email) > 0) {
2979 msg->cm_fields['F'] = strdup(my_email);
2981 else if ( (author == &CC->user) && (strlen(CC->cs_inet_email) > 0) ) {
2982 msg->cm_fields['F'] = strdup(CC->cs_inet_email);
2985 if (subject != NULL) {
2988 length = strlen(subject);
2994 while ((subject[i] != '\0') &&
2995 (IsAscii = isascii(subject[i]) != 0 ))
2998 msg->cm_fields['U'] = strdup(subject);
2999 else /* ok, we've got utf8 in the string. */
3001 msg->cm_fields['U'] = rfc2047encode(subject, length);
3007 if (supplied_euid != NULL) {
3008 msg->cm_fields['E'] = strdup(supplied_euid);
3011 if (preformatted_text != NULL) {
3012 msg->cm_fields['M'] = preformatted_text;
3015 msg->cm_fields['M'] = CtdlReadMessageBody("000", config.c_maxmsglen, NULL, 0);
3023 * Check to see whether we have permission to post a message in the current
3024 * room. Returns a *CITADEL ERROR CODE* and puts a message in errmsgbuf, or
3025 * returns 0 on success.
3027 int CtdlDoIHavePermissionToPostInThisRoom(char *errmsgbuf, size_t n) {
3030 if (!(CC->logged_in)) {
3031 snprintf(errmsgbuf, n, "Not logged in.");
3032 return (ERROR + NOT_LOGGED_IN);
3035 if ((CC->user.axlevel < 2)
3036 && ((CC->room.QRflags & QR_MAILBOX) == 0)) {
3037 snprintf(errmsgbuf, n, "Need to be validated to enter "
3038 "(except in %s> to sysop)", MAILROOM);
3039 return (ERROR + HIGHER_ACCESS_REQUIRED);
3042 CtdlRoomAccess(&CC->room, &CC->user, &ra, NULL);
3043 if (!(ra & UA_POSTALLOWED)) {
3044 snprintf(errmsgbuf, n, "Higher access is required to post in this room.");
3045 return (ERROR + HIGHER_ACCESS_REQUIRED);
3048 strcpy(errmsgbuf, "Ok");
3054 * Check to see if the specified user has Internet mail permission
3055 * (returns nonzero if permission is granted)
3057 int CtdlCheckInternetMailPermission(struct ctdluser *who) {
3059 /* Do not allow twits to send Internet mail */
3060 if (who->axlevel <= 2) return(0);
3062 /* Globally enabled? */
3063 if (config.c_restrict == 0) return(1);
3065 /* User flagged ok? */
3066 if (who->flags & US_INTERNET) return(2);
3068 /* Aide level access? */
3069 if (who->axlevel >= 6) return(3);
3071 /* No mail for you! */
3077 * Validate recipients, count delivery types and errors, and handle aliasing
3078 * FIXME check for dupes!!!!!
3080 * Returns 0 if all addresses are ok, ret->num_error = -1 if no addresses
3081 * were specified, or the number of addresses found invalid.
3083 * Caller needs to free the result using free_recipients()
3085 struct recptypes *validate_recipients(char *supplied_recipients) {
3086 struct recptypes *ret;
3087 char *recipients = NULL;
3088 char this_recp[256];
3089 char this_recp_cooked[256];
3095 struct ctdluser tempUS;
3096 struct ctdlroom tempQR;
3100 ret = (struct recptypes *) malloc(sizeof(struct recptypes));
3101 if (ret == NULL) return(NULL);
3103 /* Set all strings to null and numeric values to zero */
3104 memset(ret, 0, sizeof(struct recptypes));
3106 if (supplied_recipients == NULL) {
3107 recipients = strdup("");
3110 recipients = strdup(supplied_recipients);
3113 /* Allocate some memory. Yes, this allocates 500% more memory than we will
3114 * actually need, but it's healthier for the heap than doing lots of tiny
3115 * realloc() calls instead.
3118 ret->errormsg = malloc(strlen(recipients) + 1024);
3119 ret->recp_local = malloc(strlen(recipients) + 1024);
3120 ret->recp_internet = malloc(strlen(recipients) + 1024);
3121 ret->recp_ignet = malloc(strlen(recipients) + 1024);
3122 ret->recp_room = malloc(strlen(recipients) + 1024);
3123 ret->display_recp = malloc(strlen(recipients) + 1024);
3125 ret->errormsg[0] = 0;
3126 ret->recp_local[0] = 0;
3127 ret->recp_internet[0] = 0;
3128 ret->recp_ignet[0] = 0;
3129 ret->recp_room[0] = 0;
3130 ret->display_recp[0] = 0;
3132 ret->recptypes_magic = RECPTYPES_MAGIC;
3134 /* Change all valid separator characters to commas */
3135 for (i=0; i<strlen(recipients); ++i) {
3136 if ((recipients[i] == ';') || (recipients[i] == '|')) {
3137 recipients[i] = ',';
3141 /* Now start extracting recipients... */
3143 while (strlen(recipients) > 0) {
3145 for (i=0; i<=strlen(recipients); ++i) {
3146 if (recipients[i] == '\"') in_quotes = 1 - in_quotes;
3147 if ( ( (recipients[i] == ',') && (!in_quotes) ) || (recipients[i] == 0) ) {
3148 safestrncpy(this_recp, recipients, i+1);
3150 if (recipients[i] == ',') {
3151 strcpy(recipients, &recipients[i+1]);
3154 strcpy(recipients, "");
3161 lprintf(CTDL_DEBUG, "Evaluating recipient #%d: %s\n", num_recps, this_recp);
3163 mailtype = alias(this_recp);
3164 mailtype = alias(this_recp);
3165 mailtype = alias(this_recp);
3166 for (j=0; j<=strlen(this_recp); ++j) {
3167 if (this_recp[j]=='_') {
3168 this_recp_cooked[j] = ' ';
3171 this_recp_cooked[j] = this_recp[j];
3177 if (!strcasecmp(this_recp, "sysop")) {
3179 strcpy(this_recp, config.c_aideroom);
3180 if (strlen(ret->recp_room) > 0) {
3181 strcat(ret->recp_room, "|");
3183 strcat(ret->recp_room, this_recp);
3185 else if (getuser(&tempUS, this_recp) == 0) {
3187 strcpy(this_recp, tempUS.fullname);
3188 if (strlen(ret->recp_local) > 0) {
3189 strcat(ret->recp_local, "|");
3191 strcat(ret->recp_local, this_recp);
3193 else if (getuser(&tempUS, this_recp_cooked) == 0) {
3195 strcpy(this_recp, tempUS.fullname);
3196 if (strlen(ret->recp_local) > 0) {
3197 strcat(ret->recp_local, "|");
3199 strcat(ret->recp_local, this_recp);
3201 else if ( (!strncasecmp(this_recp, "room_", 5))
3202 && (!getroom(&tempQR, &this_recp_cooked[5])) ) {
3204 if (strlen(ret->recp_room) > 0) {
3205 strcat(ret->recp_room, "|");
3207 strcat(ret->recp_room, &this_recp_cooked[5]);
3215 /* Yes, you're reading this correctly: if the target
3216 * domain points back to the local system or an attached
3217 * Citadel directory, the address is invalid. That's
3218 * because if the address were valid, we would have
3219 * already translated it to a local address by now.
3221 if (IsDirectory(this_recp, 0)) {
3226 ++ret->num_internet;
3227 if (strlen(ret->recp_internet) > 0) {
3228 strcat(ret->recp_internet, "|");
3230 strcat(ret->recp_internet, this_recp);
3235 if (strlen(ret->recp_ignet) > 0) {
3236 strcat(ret->recp_ignet, "|");
3238 strcat(ret->recp_ignet, this_recp);
3246 if (strlen(ret->errormsg) == 0) {
3247 snprintf(append, sizeof append,
3248 "Invalid recipient: %s",
3252 snprintf(append, sizeof append, ", %s", this_recp);
3254 if ( (strlen(ret->errormsg) + strlen(append)) < SIZ) {
3255 strcat(ret->errormsg, append);
3259 if (strlen(ret->display_recp) == 0) {
3260 strcpy(append, this_recp);
3263 snprintf(append, sizeof append, ", %s", this_recp);
3265 if ( (strlen(ret->display_recp)+strlen(append)) < SIZ) {
3266 strcat(ret->display_recp, append);
3271 if ((ret->num_local + ret->num_internet + ret->num_ignet +
3272 ret->num_room + ret->num_error) == 0) {
3273 ret->num_error = (-1);
3274 strcpy(ret->errormsg, "No recipients specified.");
3277 lprintf(CTDL_DEBUG, "validate_recipients()\n");
3278 lprintf(CTDL_DEBUG, " local: %d <%s>\n", ret->num_local, ret->recp_local);
3279 lprintf(CTDL_DEBUG, " room: %d <%s>\n", ret->num_room, ret->recp_room);
3280 lprintf(CTDL_DEBUG, " inet: %d <%s>\n", ret->num_internet, ret->recp_internet);
3281 lprintf(CTDL_DEBUG, " ignet: %d <%s>\n", ret->num_ignet, ret->recp_ignet);
3282 lprintf(CTDL_DEBUG, " error: %d <%s>\n", ret->num_error, ret->errormsg);
3290 * Destructor for struct recptypes
3292 void free_recipients(struct recptypes *valid) {
3294 if (valid == NULL) {
3298 if (valid->recptypes_magic != RECPTYPES_MAGIC) {
3299 lprintf(CTDL_EMERG, "Attempt to call free_recipients() on some other data type!\n");
3303 if (valid->errormsg != NULL) free(valid->errormsg);
3304 if (valid->recp_local != NULL) free(valid->recp_local);
3305 if (valid->recp_internet != NULL) free(valid->recp_internet);
3306 if (valid->recp_ignet != NULL) free(valid->recp_ignet);
3307 if (valid->recp_room != NULL) free(valid->recp_room);
3308 if (valid->display_recp != NULL) free(valid->display_recp);
3315 * message entry - mode 0 (normal)
3317 void cmd_ent0(char *entargs)
3323 char supplied_euid[128];
3325 int format_type = 0;
3326 char newusername[256];
3327 char newuseremail[256];
3328 struct CtdlMessage *msg;
3332 struct recptypes *valid = NULL;
3333 struct recptypes *valid_to = NULL;
3334 struct recptypes *valid_cc = NULL;
3335 struct recptypes *valid_bcc = NULL;
3337 int subject_required = 0;
3342 int newuseremail_ok = 0;
3346 post = extract_int(entargs, 0);
3347 extract_token(recp, entargs, 1, '|', sizeof recp);
3348 anon_flag = extract_int(entargs, 2);
3349 format_type = extract_int(entargs, 3);
3350 extract_token(subject, entargs, 4, '|', sizeof subject);
3351 extract_token(newusername, entargs, 5, '|', sizeof newusername);
3352 do_confirm = extract_int(entargs, 6);
3353 extract_token(cc, entargs, 7, '|', sizeof cc);
3354 extract_token(bcc, entargs, 8, '|', sizeof bcc);
3355 switch(CC->room.QRdefaultview) {
3358 extract_token(supplied_euid, entargs, 9, '|', sizeof supplied_euid);
3361 supplied_euid[0] = 0;
3364 extract_token(newuseremail, entargs, 10, '|', sizeof newuseremail);
3366 /* first check to make sure the request is valid. */
3368 err = CtdlDoIHavePermissionToPostInThisRoom(errmsg, sizeof errmsg);
3371 cprintf("%d %s\n", err, errmsg);
3375 /* Check some other permission type things. */
3377 if (strlen(newusername) == 0) {
3378 strcpy(newusername, CC->user.fullname);
3380 if ( (CC->user.axlevel < 6)
3381 && (strcasecmp(newusername, CC->user.fullname))
3382 && (strcasecmp(newusername, CC->cs_inet_fn))
3384 cprintf("%d You don't have permission to author messages as '%s'.\n",
3385 ERROR + HIGHER_ACCESS_REQUIRED,
3392 if (strlen(newuseremail) == 0) {
3393 newuseremail_ok = 1;
3396 if (strlen(newuseremail) > 0) {
3397 if (!strcasecmp(newuseremail, CC->cs_inet_email)) {
3398 newuseremail_ok = 1;
3400 else if (strlen(CC->cs_inet_other_emails) > 0) {
3401 j = num_tokens(CC->cs_inet_other_emails, '|');
3402 for (i=0; i<j; ++i) {
3403 extract_token(buf, CC->cs_inet_other_emails, i, '|', sizeof buf);
3404 if (!strcasecmp(newuseremail, buf)) {
3405 newuseremail_ok = 1;
3411 if (!newuseremail_ok) {
3412 cprintf("%d You don't have permission to author messages as '%s'.\n",
3413 ERROR + HIGHER_ACCESS_REQUIRED,
3419 CC->cs_flags |= CS_POSTING;
3421 /* In mailbox rooms we have to behave a little differently --
3422 * make sure the user has specified at least one recipient. Then
3423 * validate the recipient(s). We do this for the Mail> room, as
3424 * well as any room which has the "Mailbox" view set.
3427 if ( ( (CC->room.QRflags & QR_MAILBOX) && (!strcasecmp(&CC->room.QRname[11], MAILROOM)) )
3428 || ( (CC->room.QRflags & QR_MAILBOX) && (CC->curr_view == VIEW_MAILBOX) )
3430 if (CC->user.axlevel < 2) {
3431 strcpy(recp, "sysop");
3436 valid_to = validate_recipients(recp);
3437 if (valid_to->num_error > 0) {
3438 cprintf("%d Invalid recipient (To)\n", ERROR + NO_SUCH_USER);
3439 free_recipients(valid_to);
3443 valid_cc = validate_recipients(cc);
3444 if (valid_cc->num_error > 0) {
3445 cprintf("%d Invalid recipient (CC)\n", ERROR + NO_SUCH_USER);
3446 free_recipients(valid_to);
3447 free_recipients(valid_cc);
3451 valid_bcc = validate_recipients(bcc);
3452 if (valid_bcc->num_error > 0) {
3453 cprintf("%d Invalid recipient (BCC)\n", ERROR + NO_SUCH_USER);
3454 free_recipients(valid_to);
3455 free_recipients(valid_cc);
3456 free_recipients(valid_bcc);
3460 /* Recipient required, but none were specified */
3461 if ( (valid_to->num_error < 0) && (valid_cc->num_error < 0) && (valid_bcc->num_error < 0) ) {
3462 free_recipients(valid_to);
3463 free_recipients(valid_cc);
3464 free_recipients(valid_bcc);
3465 cprintf("%d At least one recipient is required.\n", ERROR + NO_SUCH_USER);
3469 if (valid_to->num_internet + valid_cc->num_internet + valid_bcc->num_internet > 0) {
3470 if (CtdlCheckInternetMailPermission(&CC->user)==0) {
3471 cprintf("%d You do not have permission "
3472 "to send Internet mail.\n",
3473 ERROR + HIGHER_ACCESS_REQUIRED);
3474 free_recipients(valid_to);
3475 free_recipients(valid_cc);
3476 free_recipients(valid_bcc);
3481 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)
3482 && (CC->user.axlevel < 4) ) {
3483 cprintf("%d Higher access required for network mail.\n",
3484 ERROR + HIGHER_ACCESS_REQUIRED);
3485 free_recipients(valid_to);
3486 free_recipients(valid_cc);
3487 free_recipients(valid_bcc);
3491 if ((RESTRICT_INTERNET == 1)
3492 && (valid_to->num_internet + valid_cc->num_internet + valid_bcc->num_internet > 0)
3493 && ((CC->user.flags & US_INTERNET) == 0)
3494 && (!CC->internal_pgm)) {
3495 cprintf("%d You don't have access to Internet mail.\n",
3496 ERROR + HIGHER_ACCESS_REQUIRED);
3497 free_recipients(valid_to);
3498 free_recipients(valid_cc);
3499 free_recipients(valid_bcc);
3505 /* Is this a room which has anonymous-only or anonymous-option? */
3506 anonymous = MES_NORMAL;
3507 if (CC->room.QRflags & QR_ANONONLY) {
3508 anonymous = MES_ANONONLY;
3510 if (CC->room.QRflags & QR_ANONOPT) {
3511 if (anon_flag == 1) { /* only if the user requested it */
3512 anonymous = MES_ANONOPT;
3516 if ((CC->room.QRflags & QR_MAILBOX) == 0) {
3520 /* Recommend to the client that the use of a message subject is
3521 * strongly recommended in this room, if either the SUBJECTREQ flag
3522 * is set, or if there is one or more Internet email recipients.
3524 if (CC->room.QRflags2 & QR2_SUBJECTREQ) subject_required = 1;
3525 if (valid_to) if (valid_to->num_internet > 0) subject_required = 1;
3526 if (valid_cc) if (valid_cc->num_internet > 0) subject_required = 1;
3527 if (valid_bcc) if (valid_bcc->num_internet > 0) subject_required = 1;
3529 /* If we're only checking the validity of the request, return
3530 * success without creating the message.
3533 cprintf("%d %s|%d\n", CIT_OK,
3534 ((valid_to != NULL) ? valid_to->display_recp : ""),
3536 free_recipients(valid_to);
3537 free_recipients(valid_cc);
3538 free_recipients(valid_bcc);
3542 /* We don't need these anymore because we'll do it differently below */
3543 free_recipients(valid_to);
3544 free_recipients(valid_cc);
3545 free_recipients(valid_bcc);
3547 /* Read in the message from the client. */
3549 cprintf("%d send message\n", START_CHAT_MODE);
3551 cprintf("%d send message\n", SEND_LISTING);
3554 msg = CtdlMakeMessage(&CC->user, recp, cc,
3555 CC->room.QRname, anonymous, format_type,
3556 newusername, newuseremail, subject,
3557 ((strlen(supplied_euid) > 0) ? supplied_euid : NULL),
3560 /* Put together one big recipients struct containing to/cc/bcc all in
3561 * one. This is for the envelope.
3563 char *all_recps = malloc(SIZ * 3);
3564 strcpy(all_recps, recp);
3565 if (strlen(cc) > 0) {
3566 if (strlen(all_recps) > 0) {
3567 strcat(all_recps, ",");
3569 strcat(all_recps, cc);
3571 if (strlen(bcc) > 0) {
3572 if (strlen(all_recps) > 0) {
3573 strcat(all_recps, ",");
3575 strcat(all_recps, bcc);
3577 if (strlen(all_recps) > 0) {
3578 valid = validate_recipients(all_recps);
3586 msgnum = CtdlSubmitMsg(msg, valid, "");
3589 cprintf("%ld\n", msgnum);
3591 cprintf("Message accepted.\n");
3594 cprintf("Internal error.\n");
3596 if (msg->cm_fields['E'] != NULL) {
3597 cprintf("%s\n", msg->cm_fields['E']);
3604 CtdlFreeMessage(msg);
3606 if (valid != NULL) {
3607 free_recipients(valid);
3615 * API function to delete messages which match a set of criteria
3616 * (returns the actual number of messages deleted)
3618 int CtdlDeleteMessages(char *room_name, /* which room */
3619 long *dmsgnums, /* array of msg numbers to be deleted */
3620 int num_dmsgnums, /* number of msgs to be deleted, or 0 for "any" */
3621 char *content_type /* or "" for any. regular expressions expected. */
3624 struct ctdlroom qrbuf;
3625 struct cdbdata *cdbfr;
3626 long *msglist = NULL;
3627 long *dellist = NULL;
3630 int num_deleted = 0;
3632 struct MetaData smi;
3635 int need_to_free_re = 0;
3637 if (content_type) if (strlen(content_type) > 0) {
3638 regcomp(&re, content_type, 0);
3639 need_to_free_re = 1;
3641 lprintf(CTDL_DEBUG, "CtdlDeleteMessages(%s, %d msgs, %s)\n",
3642 room_name, num_dmsgnums, content_type);
3644 /* get room record, obtaining a lock... */
3645 if (lgetroom(&qrbuf, room_name) != 0) {
3646 lprintf(CTDL_ERR, "CtdlDeleteMessages(): Room <%s> not found\n",
3648 if (need_to_free_re) regfree(&re);
3649 return (0); /* room not found */
3651 cdbfr = cdb_fetch(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long));
3653 if (cdbfr != NULL) {
3654 dellist = malloc(cdbfr->len);
3655 msglist = (long *) cdbfr->ptr;
3656 cdbfr->ptr = NULL; /* CtdlDeleteMessages() now owns this memory */
3657 num_msgs = cdbfr->len / sizeof(long);
3661 for (i = 0; i < num_msgs; ++i) {
3664 /* Set/clear a bit for each criterion */
3666 /* 0 messages in the list or a null list means that we are
3667 * interested in deleting any messages which meet the other criteria.
3669 if ((num_dmsgnums == 0) || (dmsgnums == NULL)) {
3670 delete_this |= 0x01;
3673 for (j=0; j<num_dmsgnums; ++j) {
3674 if (msglist[i] == dmsgnums[j]) {
3675 delete_this |= 0x01;
3680 if (strlen(content_type) == 0) {
3681 delete_this |= 0x02;
3683 GetMetaData(&smi, msglist[i]);
3684 if (regexec(&re, smi.meta_content_type, 1, &pm, 0) == 0) {
3685 delete_this |= 0x02;
3689 /* Delete message only if all bits are set */
3690 if (delete_this == 0x03) {
3691 dellist[num_deleted++] = msglist[i];
3696 num_msgs = sort_msglist(msglist, num_msgs);
3697 cdb_store(CDB_MSGLISTS, &qrbuf.QRnumber, (int)sizeof(long),
3698 msglist, (int)(num_msgs * sizeof(long)));
3700 qrbuf.QRhighest = msglist[num_msgs - 1];
3704 /* Go through the messages we pulled out of the index, and decrement
3705 * their reference counts by 1. If this is the only room the message
3706 * was in, the reference count will reach zero and the message will
3707 * automatically be deleted from the database. We do this in a
3708 * separate pass because there might be plug-in hooks getting called,
3709 * and we don't want that happening during an S_ROOMS critical
3712 if (num_deleted) for (i=0; i<num_deleted; ++i) {
3713 PerformDeleteHooks(qrbuf.QRname, dellist[i]);
3714 AdjRefCount(dellist[i], -1);
3717 /* Now free the memory we used, and go away. */
3718 if (msglist != NULL) free(msglist);
3719 if (dellist != NULL) free(dellist);
3720 lprintf(CTDL_DEBUG, "%d message(s) deleted.\n", num_deleted);
3721 if (need_to_free_re) regfree(&re);
3722 return (num_deleted);
3728 * Check whether the current user has permission to delete messages from
3729 * the current room (returns 1 for yes, 0 for no)
3731 int CtdlDoIHavePermissionToDeleteMessagesFromThisRoom(void) {
3733 CtdlRoomAccess(&CC->room, &CC->user, &ra, NULL);
3734 if (ra & UA_DELETEALLOWED) return(1);
3742 * Delete message from current room
3744 void cmd_dele(char *args)
3753 extract_token(msgset, args, 0, '|', sizeof msgset);
3754 num_msgs = num_tokens(msgset, ',');
3756 cprintf("%d Nothing to do.\n", CIT_OK);
3760 if (CtdlDoIHavePermissionToDeleteMessagesFromThisRoom() == 0) {
3761 cprintf("%d Higher access required.\n",
3762 ERROR + HIGHER_ACCESS_REQUIRED);
3767 * Build our message set to be moved/copied
3769 msgs = malloc(num_msgs * sizeof(long));
3770 for (i=0; i<num_msgs; ++i) {
3771 extract_token(msgtok, msgset, i, ',', sizeof msgtok);
3772 msgs[i] = atol(msgtok);
3775 num_deleted = CtdlDeleteMessages(CC->room.QRname, msgs, num_msgs, "");
3779 cprintf("%d %d message%s deleted.\n", CIT_OK,
3780 num_deleted, ((num_deleted != 1) ? "s" : ""));
3782 cprintf("%d Message not found.\n", ERROR + MESSAGE_NOT_FOUND);
3788 * Back end API function for moves and deletes (multiple messages)
3790 int CtdlCopyMsgsToRoom(long *msgnums, int num_msgs, char *dest) {
3793 err = CtdlSaveMsgPointersInRoom(dest, msgnums, num_msgs, 1, NULL);
3794 if (err != 0) return(err);
3803 * move or copy a message to another room
3805 void cmd_move(char *args)
3812 char targ[ROOMNAMELEN];
3813 struct ctdlroom qtemp;
3820 extract_token(msgset, args, 0, '|', sizeof msgset);
3821 num_msgs = num_tokens(msgset, ',');
3823 cprintf("%d Nothing to do.\n", CIT_OK);
3827 extract_token(targ, args, 1, '|', sizeof targ);
3828 convert_room_name_macros(targ, sizeof targ);
3829 targ[ROOMNAMELEN - 1] = 0;
3830 is_copy = extract_int(args, 2);
3832 if (getroom(&qtemp, targ) != 0) {
3833 cprintf("%d '%s' does not exist.\n",
3834 ERROR + ROOM_NOT_FOUND, targ);
3838 getuser(&CC->user, CC->curr_user);
3839 CtdlRoomAccess(&qtemp, &CC->user, &ra, NULL);
3841 /* Check for permission to perform this operation.
3842 * Remember: "CC->room" is source, "qtemp" is target.
3846 /* Aides can move/copy */
3847 if (CC->user.axlevel >= 6) permit = 1;
3849 /* Room aides can move/copy */
3850 if (CC->user.usernum == CC->room.QRroomaide) permit = 1;
3852 /* Permit move/copy from personal rooms */
3853 if ((CC->room.QRflags & QR_MAILBOX)
3854 && (qtemp.QRflags & QR_MAILBOX)) permit = 1;
3856 /* Permit only copy from public to personal room */
3858 && (!(CC->room.QRflags & QR_MAILBOX))
3859 && (qtemp.QRflags & QR_MAILBOX)) permit = 1;
3861 /* Permit message removal from collaborative delete rooms */
3862 if (CC->room.QRflags2 & QR2_COLLABDEL) permit = 1;
3864 /* User must have access to target room */
3865 if (!(ra & UA_KNOWN)) permit = 0;
3868 cprintf("%d Higher access required.\n",
3869 ERROR + HIGHER_ACCESS_REQUIRED);
3874 * Build our message set to be moved/copied
3876 msgs = malloc(num_msgs * sizeof(long));
3877 for (i=0; i<num_msgs; ++i) {
3878 extract_token(msgtok, msgset, i, ',', sizeof msgtok);
3879 msgs[i] = atol(msgtok);
3885 err = CtdlCopyMsgsToRoom(msgs, num_msgs, targ);
3887 cprintf("%d Cannot store message(s) in %s: error %d\n",
3893 /* Now delete the message from the source room,
3894 * if this is a 'move' rather than a 'copy' operation.
3897 CtdlDeleteMessages(CC->room.QRname, msgs, num_msgs, "");
3901 cprintf("%d Message(s) %s.\n", CIT_OK, (is_copy ? "copied" : "moved") );
3907 * GetMetaData() - Get the supplementary record for a message
3909 void GetMetaData(struct MetaData *smibuf, long msgnum)
3912 struct cdbdata *cdbsmi;
3915 memset(smibuf, 0, sizeof(struct MetaData));
3916 smibuf->meta_msgnum = msgnum;
3917 smibuf->meta_refcount = 1; /* Default reference count is 1 */
3919 /* Use the negative of the message number for its supp record index */
3920 TheIndex = (0L - msgnum);
3922 cdbsmi = cdb_fetch(CDB_MSGMAIN, &TheIndex, sizeof(long));
3923 if (cdbsmi == NULL) {
3924 return; /* record not found; go with defaults */
3926 memcpy(smibuf, cdbsmi->ptr,
3927 ((cdbsmi->len > sizeof(struct MetaData)) ?
3928 sizeof(struct MetaData) : cdbsmi->len));
3935 * PutMetaData() - (re)write supplementary record for a message
3937 void PutMetaData(struct MetaData *smibuf)
3941 /* Use the negative of the message number for the metadata db index */
3942 TheIndex = (0L - smibuf->meta_msgnum);
3944 cdb_store(CDB_MSGMAIN,
3945 &TheIndex, (int)sizeof(long),
3946 smibuf, (int)sizeof(struct MetaData));
3951 * AdjRefCount - submit an adjustment to the reference count for a message.
3952 * (These are just queued -- we actually process them later.)
3954 void AdjRefCount(long msgnum, int incr)
3956 struct arcq new_arcq;
3958 begin_critical_section(S_SUPPMSGMAIN);
3959 if (arcfp == NULL) {
3960 arcfp = fopen(file_arcq, "ab+");
3962 end_critical_section(S_SUPPMSGMAIN);
3964 /* msgnum < 0 means that we're trying to close the file */
3966 lprintf(CTDL_DEBUG, "Closing the AdjRefCount queue file\n");
3967 begin_critical_section(S_SUPPMSGMAIN);
3968 if (arcfp != NULL) {
3972 end_critical_section(S_SUPPMSGMAIN);
3977 * If we can't open the queue, perform the operation synchronously.
3979 if (arcfp == NULL) {
3980 TDAP_AdjRefCount(msgnum, incr);
3984 new_arcq.arcq_msgnum = msgnum;
3985 new_arcq.arcq_delta = incr;
3986 fwrite(&new_arcq, sizeof(struct arcq), 1, arcfp);
3994 * TDAP_ProcessAdjRefCountQueue()
3996 * Process the queue of message count adjustments that was created by calls
3997 * to AdjRefCount() ... by reading the queue and calling TDAP_AdjRefCount()
3998 * for each one. This should be an "off hours" operation.
4000 int TDAP_ProcessAdjRefCountQueue(void)
4002 char file_arcq_temp[PATH_MAX];
4005 struct arcq arcq_rec;
4006 int num_records_processed = 0;
4008 snprintf(file_arcq_temp, sizeof file_arcq_temp, "%s2", file_arcq);
4010 begin_critical_section(S_SUPPMSGMAIN);
4011 if (arcfp != NULL) {
4016 r = link(file_arcq, file_arcq_temp);
4018 lprintf(CTDL_CRIT, "%s: %s\n", file_arcq_temp, strerror(errno));
4019 end_critical_section(S_SUPPMSGMAIN);
4020 return(num_records_processed);
4024 end_critical_section(S_SUPPMSGMAIN);
4026 fp = fopen(file_arcq_temp, "rb");
4028 lprintf(CTDL_CRIT, "%s: %s\n", file_arcq_temp, strerror(errno));
4029 return(num_records_processed);
4032 while (fread(&arcq_rec, sizeof(struct arcq), 1, fp) == 1) {
4033 TDAP_AdjRefCount(arcq_rec.arcq_msgnum, arcq_rec.arcq_delta);
4034 ++num_records_processed;
4038 r = unlink(file_arcq_temp);
4040 lprintf(CTDL_CRIT, "%s: %s\n", file_arcq_temp, strerror(errno));
4043 return(num_records_processed);
4049 * TDAP_AdjRefCount - adjust the reference count for a message.
4050 * This one does it "for real" because it's called by
4051 * the autopurger function that processes the queue
4052 * created by AdjRefCount(). If a message's reference
4053 * count becomes zero, we also delete the message from
4054 * disk and de-index it.
4056 void TDAP_AdjRefCount(long msgnum, int incr)
4059 struct MetaData smi;
4062 /* This is a *tight* critical section; please keep it that way, as
4063 * it may get called while nested in other critical sections.
4064 * Complicating this any further will surely cause deadlock!
4066 begin_critical_section(S_SUPPMSGMAIN);
4067 GetMetaData(&smi, msgnum);
4068 smi.meta_refcount += incr;
4070 end_critical_section(S_SUPPMSGMAIN);
4071 lprintf(CTDL_DEBUG, "msg %ld ref count delta %d, is now %d\n",
4072 msgnum, incr, smi.meta_refcount);
4074 /* If the reference count is now zero, delete the message
4075 * (and its supplementary record as well).
4077 if (smi.meta_refcount == 0) {
4078 lprintf(CTDL_DEBUG, "Deleting message <%ld>\n", msgnum);
4080 /* Remove from fulltext index */
4081 if (config.c_enable_fulltext) {
4082 ft_index_message(msgnum, 0);
4085 /* Remove from message base */
4087 cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long));
4088 cdb_delete(CDB_BIGMSGS, &delnum, (int)sizeof(long));
4090 /* Remove metadata record */
4091 delnum = (0L - msgnum);
4092 cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long));
4098 * Write a generic object to this room
4100 * Note: this could be much more efficient. Right now we use two temporary
4101 * files, and still pull the message into memory as with all others.
4103 void CtdlWriteObject(char *req_room, /* Room to stuff it in */
4104 char *content_type, /* MIME type of this object */
4105 char *tempfilename, /* Where to fetch it from */
4106 struct ctdluser *is_mailbox, /* Mailbox room? */
4107 int is_binary, /* Is encoding necessary? */
4108 int is_unique, /* Del others of this type? */
4109 unsigned int flags /* Internal save flags */
4114 struct ctdlroom qrbuf;
4115 char roomname[ROOMNAMELEN];
4116 struct CtdlMessage *msg;
4118 char *raw_message = NULL;
4119 char *encoded_message = NULL;
4120 off_t raw_length = 0;
4122 if (is_mailbox != NULL) {
4123 MailboxName(roomname, sizeof roomname, is_mailbox, req_room);
4126 safestrncpy(roomname, req_room, sizeof(roomname));
4129 fp = fopen(tempfilename, "rb");
4131 lprintf(CTDL_CRIT, "Cannot open %s: %s\n",
4132 tempfilename, strerror(errno));
4135 fseek(fp, 0L, SEEK_END);
4136 raw_length = ftell(fp);
4138 lprintf(CTDL_DEBUG, "Raw length is %ld\n", (long)raw_length);
4140 raw_message = malloc((size_t)raw_length + 2);
4141 fread(raw_message, (size_t)raw_length, 1, fp);
4145 encoded_message = malloc((size_t)
4146 (((raw_length * 134) / 100) + 4096 ) );
4149 encoded_message = malloc((size_t)(raw_length + 4096));
4152 sprintf(encoded_message, "Content-type: %s\n", content_type);
4155 sprintf(&encoded_message[strlen(encoded_message)],
4156 "Content-transfer-encoding: base64\n\n"
4160 sprintf(&encoded_message[strlen(encoded_message)],
4161 "Content-transfer-encoding: 7bit\n\n"
4167 &encoded_message[strlen(encoded_message)],
4173 raw_message[raw_length] = 0;
4175 &encoded_message[strlen(encoded_message)],
4183 lprintf(CTDL_DEBUG, "Allocating\n");
4184 msg = malloc(sizeof(struct CtdlMessage));
4185 memset(msg, 0, sizeof(struct CtdlMessage));
4186 msg->cm_magic = CTDLMESSAGE_MAGIC;
4187 msg->cm_anon_type = MES_NORMAL;
4188 msg->cm_format_type = 4;
4189 msg->cm_fields['A'] = strdup(CC->user.fullname);
4190 msg->cm_fields['O'] = strdup(req_room);
4191 msg->cm_fields['N'] = strdup(config.c_nodename);
4192 msg->cm_fields['H'] = strdup(config.c_humannode);
4193 msg->cm_flags = flags;
4195 msg->cm_fields['M'] = encoded_message;
4197 /* Create the requested room if we have to. */
4198 if (getroom(&qrbuf, roomname) != 0) {
4199 create_room(roomname,
4200 ( (is_mailbox != NULL) ? 5 : 3 ),
4201 "", 0, 1, 0, VIEW_BBS);
4203 /* If the caller specified this object as unique, delete all
4204 * other objects of this type that are currently in the room.
4207 lprintf(CTDL_DEBUG, "Deleted %d other msgs of this type\n",
4208 CtdlDeleteMessages(roomname, NULL, 0, content_type)
4211 /* Now write the data */
4212 CtdlSubmitMsg(msg, NULL, roomname);
4213 CtdlFreeMessage(msg);
4221 void CtdlGetSysConfigBackend(long msgnum, void *userdata) {
4222 config_msgnum = msgnum;
4226 char *CtdlGetSysConfig(char *sysconfname) {
4227 char hold_rm[ROOMNAMELEN];
4230 struct CtdlMessage *msg;
4233 strcpy(hold_rm, CC->room.QRname);
4234 if (getroom(&CC->room, SYSCONFIGROOM) != 0) {
4235 getroom(&CC->room, hold_rm);
4240 /* We want the last (and probably only) config in this room */
4241 begin_critical_section(S_CONFIG);
4242 config_msgnum = (-1L);
4243 CtdlForEachMessage(MSGS_LAST, 1, NULL, sysconfname, NULL,
4244 CtdlGetSysConfigBackend, NULL);
4245 msgnum = config_msgnum;
4246 end_critical_section(S_CONFIG);
4252 msg = CtdlFetchMessage(msgnum, 1);
4254 conf = strdup(msg->cm_fields['M']);
4255 CtdlFreeMessage(msg);
4262 getroom(&CC->room, hold_rm);
4264 if (conf != NULL) do {
4265 extract_token(buf, conf, 0, '\n', sizeof buf);
4266 strcpy(conf, &conf[strlen(buf)+1]);
4267 } while ( (strlen(conf)>0) && (strlen(buf)>0) );
4272 void CtdlPutSysConfig(char *sysconfname, char *sysconfdata) {
4273 char temp[PATH_MAX];
4276 CtdlMakeTempFileName(temp, sizeof temp);
4278 fp = fopen(temp, "w");
4279 if (fp == NULL) return;
4280 fprintf(fp, "%s", sysconfdata);
4283 /* this handy API function does all the work for us */
4284 CtdlWriteObject(SYSCONFIGROOM, sysconfname, temp, NULL, 0, 1, 0);
4290 * Determine whether a given Internet address belongs to the current user
4292 int CtdlIsMe(char *addr, int addr_buf_len)
4294 struct recptypes *recp;
4297 recp = validate_recipients(addr);
4298 if (recp == NULL) return(0);
4300 if (recp->num_local == 0) {
4301 free_recipients(recp);
4305 for (i=0; i<recp->num_local; ++i) {
4306 extract_token(addr, recp->recp_local, i, '|', addr_buf_len);
4307 if (!strcasecmp(addr, CC->user.fullname)) {
4308 free_recipients(recp);
4313 free_recipients(recp);
4319 * Citadel protocol command to do the same
4321 void cmd_isme(char *argbuf) {
4324 if (CtdlAccessCheck(ac_logged_in)) return;
4325 extract_token(addr, argbuf, 0, '|', sizeof addr);
4327 if (CtdlIsMe(addr, sizeof addr)) {
4328 cprintf("%d %s\n", CIT_OK, addr);
4331 cprintf("%d Not you.\n", ERROR + ILLEGAL_VALUE);