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;
3341 int newuseremail_ok = 0;
3345 post = extract_int(entargs, 0);
3346 extract_token(recp, entargs, 1, '|', sizeof recp);
3347 anon_flag = extract_int(entargs, 2);
3348 format_type = extract_int(entargs, 3);
3349 extract_token(subject, entargs, 4, '|', sizeof subject);
3350 extract_token(newusername, entargs, 5, '|', sizeof newusername);
3351 do_confirm = extract_int(entargs, 6);
3352 extract_token(cc, entargs, 7, '|', sizeof cc);
3353 extract_token(bcc, entargs, 8, '|', sizeof bcc);
3354 switch(CC->room.QRdefaultview) {
3357 extract_token(supplied_euid, entargs, 9, '|', sizeof supplied_euid);
3360 supplied_euid[0] = 0;
3363 extract_token(newuseremail, entargs, 10, '|', sizeof newuseremail);
3365 /* first check to make sure the request is valid. */
3367 err = CtdlDoIHavePermissionToPostInThisRoom(errmsg, sizeof errmsg);
3370 cprintf("%d %s\n", err, errmsg);
3374 /* Check some other permission type things. */
3376 if (strlen(newusername) == 0) {
3377 strcpy(newusername, CC->user.fullname);
3379 if ( (CC->user.axlevel < 6)
3380 && (strcasecmp(newusername, CC->user.fullname))
3381 && (strcasecmp(newusername, CC->cs_inet_fn))
3383 cprintf("%d You don't have permission to author messages as '%s'.\n",
3384 ERROR + HIGHER_ACCESS_REQUIRED,
3391 if (strlen(newuseremail) == 0) {
3392 newuseremail_ok = 1;
3395 if (strlen(newuseremail) > 0) {
3396 if (!strcasecmp(newuseremail, CC->cs_inet_email)) {
3397 newuseremail_ok = 1;
3399 else if (strlen(CC->cs_inet_other_emails) > 0) {
3400 j = num_tokens(CC->cs_inet_other_emails, '|');
3401 for (i=0; i<j; ++i) {
3402 extract_token(buf, CC->cs_inet_other_emails, i, '|', sizeof buf);
3403 if (!strcasecmp(newuseremail, buf)) {
3404 newuseremail_ok = 1;
3410 if (!newuseremail_ok) {
3411 cprintf("%d You don't have permission to author messages as '%s'.\n",
3412 ERROR + HIGHER_ACCESS_REQUIRED,
3418 CC->cs_flags |= CS_POSTING;
3420 /* In mailbox rooms we have to behave a little differently --
3421 * make sure the user has specified at least one recipient. Then
3422 * validate the recipient(s). We do this for the Mail> room, as
3423 * well as any room which has the "Mailbox" view set.
3426 if ( ( (CC->room.QRflags & QR_MAILBOX) && (!strcasecmp(&CC->room.QRname[11], MAILROOM)) )
3427 || ( (CC->room.QRflags & QR_MAILBOX) && (CC->curr_view == VIEW_MAILBOX) )
3429 if (CC->user.axlevel < 2) {
3430 strcpy(recp, "sysop");
3435 valid_to = validate_recipients(recp);
3436 if (valid_to->num_error > 0) {
3437 cprintf("%d Invalid recipient (To)\n", ERROR + NO_SUCH_USER);
3438 free_recipients(valid_to);
3442 valid_cc = validate_recipients(cc);
3443 if (valid_cc->num_error > 0) {
3444 cprintf("%d Invalid recipient (CC)\n", ERROR + NO_SUCH_USER);
3445 free_recipients(valid_to);
3446 free_recipients(valid_cc);
3450 valid_bcc = validate_recipients(bcc);
3451 if (valid_bcc->num_error > 0) {
3452 cprintf("%d Invalid recipient (BCC)\n", ERROR + NO_SUCH_USER);
3453 free_recipients(valid_to);
3454 free_recipients(valid_cc);
3455 free_recipients(valid_bcc);
3459 /* Recipient required, but none were specified */
3460 if ( (valid_to->num_error < 0) && (valid_cc->num_error < 0) && (valid_bcc->num_error < 0) ) {
3461 free_recipients(valid_to);
3462 free_recipients(valid_cc);
3463 free_recipients(valid_bcc);
3464 cprintf("%d At least one recipient is required.\n", ERROR + NO_SUCH_USER);
3468 if (valid_to->num_internet + valid_cc->num_internet + valid_bcc->num_internet > 0) {
3469 if (CtdlCheckInternetMailPermission(&CC->user)==0) {
3470 cprintf("%d You do not have permission "
3471 "to send Internet mail.\n",
3472 ERROR + HIGHER_ACCESS_REQUIRED);
3473 free_recipients(valid_to);
3474 free_recipients(valid_cc);
3475 free_recipients(valid_bcc);
3480 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)
3481 && (CC->user.axlevel < 4) ) {
3482 cprintf("%d Higher access required for network mail.\n",
3483 ERROR + HIGHER_ACCESS_REQUIRED);
3484 free_recipients(valid_to);
3485 free_recipients(valid_cc);
3486 free_recipients(valid_bcc);
3490 if ((RESTRICT_INTERNET == 1)
3491 && (valid_to->num_internet + valid_cc->num_internet + valid_bcc->num_internet > 0)
3492 && ((CC->user.flags & US_INTERNET) == 0)
3493 && (!CC->internal_pgm)) {
3494 cprintf("%d You don't have access to Internet mail.\n",
3495 ERROR + HIGHER_ACCESS_REQUIRED);
3496 free_recipients(valid_to);
3497 free_recipients(valid_cc);
3498 free_recipients(valid_bcc);
3504 /* Is this a room which has anonymous-only or anonymous-option? */
3505 anonymous = MES_NORMAL;
3506 if (CC->room.QRflags & QR_ANONONLY) {
3507 anonymous = MES_ANONONLY;
3509 if (CC->room.QRflags & QR_ANONOPT) {
3510 if (anon_flag == 1) { /* only if the user requested it */
3511 anonymous = MES_ANONOPT;
3515 if ((CC->room.QRflags & QR_MAILBOX) == 0) {
3519 /* If we're only checking the validity of the request, return
3520 * success without creating the message.
3523 cprintf("%d %s\n", CIT_OK,
3524 ((valid_to != NULL) ? valid_to->display_recp : "") );
3525 free_recipients(valid_to);
3526 free_recipients(valid_cc);
3527 free_recipients(valid_bcc);
3531 /* We don't need these anymore because we'll do it differently below */
3532 free_recipients(valid_to);
3533 free_recipients(valid_cc);
3534 free_recipients(valid_bcc);
3536 /* Read in the message from the client. */
3538 cprintf("%d send message\n", START_CHAT_MODE);
3540 cprintf("%d send message\n", SEND_LISTING);
3543 msg = CtdlMakeMessage(&CC->user, recp, cc,
3544 CC->room.QRname, anonymous, format_type,
3545 newusername, newuseremail, subject,
3546 ((strlen(supplied_euid) > 0) ? supplied_euid : NULL),
3549 /* Put together one big recipients struct containing to/cc/bcc all in
3550 * one. This is for the envelope.
3552 char *all_recps = malloc(SIZ * 3);
3553 strcpy(all_recps, recp);
3554 if (strlen(cc) > 0) {
3555 if (strlen(all_recps) > 0) {
3556 strcat(all_recps, ",");
3558 strcat(all_recps, cc);
3560 if (strlen(bcc) > 0) {
3561 if (strlen(all_recps) > 0) {
3562 strcat(all_recps, ",");
3564 strcat(all_recps, bcc);
3566 if (strlen(all_recps) > 0) {
3567 valid = validate_recipients(all_recps);
3575 msgnum = CtdlSubmitMsg(msg, valid, "");
3578 cprintf("%ld\n", msgnum);
3580 cprintf("Message accepted.\n");
3583 cprintf("Internal error.\n");
3585 if (msg->cm_fields['E'] != NULL) {
3586 cprintf("%s\n", msg->cm_fields['E']);
3593 CtdlFreeMessage(msg);
3595 if (valid != NULL) {
3596 free_recipients(valid);
3604 * API function to delete messages which match a set of criteria
3605 * (returns the actual number of messages deleted)
3607 int CtdlDeleteMessages(char *room_name, /* which room */
3608 long *dmsgnums, /* array of msg numbers to be deleted */
3609 int num_dmsgnums, /* number of msgs to be deleted, or 0 for "any" */
3610 char *content_type /* or "" for any. regular expressions expected. */
3613 struct ctdlroom qrbuf;
3614 struct cdbdata *cdbfr;
3615 long *msglist = NULL;
3616 long *dellist = NULL;
3619 int num_deleted = 0;
3621 struct MetaData smi;
3624 int need_to_free_re = 0;
3626 if (content_type) if (strlen(content_type) > 0) {
3627 regcomp(&re, content_type, 0);
3628 need_to_free_re = 1;
3630 lprintf(CTDL_DEBUG, "CtdlDeleteMessages(%s, %d msgs, %s)\n",
3631 room_name, num_dmsgnums, content_type);
3633 /* get room record, obtaining a lock... */
3634 if (lgetroom(&qrbuf, room_name) != 0) {
3635 lprintf(CTDL_ERR, "CtdlDeleteMessages(): Room <%s> not found\n",
3637 if (need_to_free_re) regfree(&re);
3638 return (0); /* room not found */
3640 cdbfr = cdb_fetch(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long));
3642 if (cdbfr != NULL) {
3643 dellist = malloc(cdbfr->len);
3644 msglist = (long *) cdbfr->ptr;
3645 cdbfr->ptr = NULL; /* CtdlDeleteMessages() now owns this memory */
3646 num_msgs = cdbfr->len / sizeof(long);
3650 for (i = 0; i < num_msgs; ++i) {
3653 /* Set/clear a bit for each criterion */
3655 /* 0 messages in the list or a null list means that we are
3656 * interested in deleting any messages which meet the other criteria.
3658 if ((num_dmsgnums == 0) || (dmsgnums == NULL)) {
3659 delete_this |= 0x01;
3662 for (j=0; j<num_dmsgnums; ++j) {
3663 if (msglist[i] == dmsgnums[j]) {
3664 delete_this |= 0x01;
3669 if (strlen(content_type) == 0) {
3670 delete_this |= 0x02;
3672 GetMetaData(&smi, msglist[i]);
3673 if (regexec(&re, smi.meta_content_type, 1, &pm, 0) == 0) {
3674 delete_this |= 0x02;
3678 /* Delete message only if all bits are set */
3679 if (delete_this == 0x03) {
3680 dellist[num_deleted++] = msglist[i];
3685 num_msgs = sort_msglist(msglist, num_msgs);
3686 cdb_store(CDB_MSGLISTS, &qrbuf.QRnumber, (int)sizeof(long),
3687 msglist, (int)(num_msgs * sizeof(long)));
3689 qrbuf.QRhighest = msglist[num_msgs - 1];
3693 /* Go through the messages we pulled out of the index, and decrement
3694 * their reference counts by 1. If this is the only room the message
3695 * was in, the reference count will reach zero and the message will
3696 * automatically be deleted from the database. We do this in a
3697 * separate pass because there might be plug-in hooks getting called,
3698 * and we don't want that happening during an S_ROOMS critical
3701 if (num_deleted) for (i=0; i<num_deleted; ++i) {
3702 PerformDeleteHooks(qrbuf.QRname, dellist[i]);
3703 AdjRefCount(dellist[i], -1);
3706 /* Now free the memory we used, and go away. */
3707 if (msglist != NULL) free(msglist);
3708 if (dellist != NULL) free(dellist);
3709 lprintf(CTDL_DEBUG, "%d message(s) deleted.\n", num_deleted);
3710 if (need_to_free_re) regfree(&re);
3711 return (num_deleted);
3717 * Check whether the current user has permission to delete messages from
3718 * the current room (returns 1 for yes, 0 for no)
3720 int CtdlDoIHavePermissionToDeleteMessagesFromThisRoom(void) {
3722 CtdlRoomAccess(&CC->room, &CC->user, &ra, NULL);
3723 if (ra & UA_DELETEALLOWED) return(1);
3731 * Delete message from current room
3733 void cmd_dele(char *args)
3742 extract_token(msgset, args, 0, '|', sizeof msgset);
3743 num_msgs = num_tokens(msgset, ',');
3745 cprintf("%d Nothing to do.\n", CIT_OK);
3749 if (CtdlDoIHavePermissionToDeleteMessagesFromThisRoom() == 0) {
3750 cprintf("%d Higher access required.\n",
3751 ERROR + HIGHER_ACCESS_REQUIRED);
3756 * Build our message set to be moved/copied
3758 msgs = malloc(num_msgs * sizeof(long));
3759 for (i=0; i<num_msgs; ++i) {
3760 extract_token(msgtok, msgset, i, ',', sizeof msgtok);
3761 msgs[i] = atol(msgtok);
3764 num_deleted = CtdlDeleteMessages(CC->room.QRname, msgs, num_msgs, "");
3768 cprintf("%d %d message%s deleted.\n", CIT_OK,
3769 num_deleted, ((num_deleted != 1) ? "s" : ""));
3771 cprintf("%d Message not found.\n", ERROR + MESSAGE_NOT_FOUND);
3777 * Back end API function for moves and deletes (multiple messages)
3779 int CtdlCopyMsgsToRoom(long *msgnums, int num_msgs, char *dest) {
3782 err = CtdlSaveMsgPointersInRoom(dest, msgnums, num_msgs, 1, NULL);
3783 if (err != 0) return(err);
3792 * move or copy a message to another room
3794 void cmd_move(char *args)
3801 char targ[ROOMNAMELEN];
3802 struct ctdlroom qtemp;
3809 extract_token(msgset, args, 0, '|', sizeof msgset);
3810 num_msgs = num_tokens(msgset, ',');
3812 cprintf("%d Nothing to do.\n", CIT_OK);
3816 extract_token(targ, args, 1, '|', sizeof targ);
3817 convert_room_name_macros(targ, sizeof targ);
3818 targ[ROOMNAMELEN - 1] = 0;
3819 is_copy = extract_int(args, 2);
3821 if (getroom(&qtemp, targ) != 0) {
3822 cprintf("%d '%s' does not exist.\n",
3823 ERROR + ROOM_NOT_FOUND, targ);
3827 getuser(&CC->user, CC->curr_user);
3828 CtdlRoomAccess(&qtemp, &CC->user, &ra, NULL);
3830 /* Check for permission to perform this operation.
3831 * Remember: "CC->room" is source, "qtemp" is target.
3835 /* Aides can move/copy */
3836 if (CC->user.axlevel >= 6) permit = 1;
3838 /* Room aides can move/copy */
3839 if (CC->user.usernum == CC->room.QRroomaide) permit = 1;
3841 /* Permit move/copy from personal rooms */
3842 if ((CC->room.QRflags & QR_MAILBOX)
3843 && (qtemp.QRflags & QR_MAILBOX)) permit = 1;
3845 /* Permit only copy from public to personal room */
3847 && (!(CC->room.QRflags & QR_MAILBOX))
3848 && (qtemp.QRflags & QR_MAILBOX)) permit = 1;
3850 /* Permit message removal from collaborative delete rooms */
3851 if (CC->room.QRflags2 & QR2_COLLABDEL) permit = 1;
3853 /* User must have access to target room */
3854 if (!(ra & UA_KNOWN)) permit = 0;
3857 cprintf("%d Higher access required.\n",
3858 ERROR + HIGHER_ACCESS_REQUIRED);
3863 * Build our message set to be moved/copied
3865 msgs = malloc(num_msgs * sizeof(long));
3866 for (i=0; i<num_msgs; ++i) {
3867 extract_token(msgtok, msgset, i, ',', sizeof msgtok);
3868 msgs[i] = atol(msgtok);
3874 err = CtdlCopyMsgsToRoom(msgs, num_msgs, targ);
3876 cprintf("%d Cannot store message(s) in %s: error %d\n",
3882 /* Now delete the message from the source room,
3883 * if this is a 'move' rather than a 'copy' operation.
3886 CtdlDeleteMessages(CC->room.QRname, msgs, num_msgs, "");
3890 cprintf("%d Message(s) %s.\n", CIT_OK, (is_copy ? "copied" : "moved") );
3896 * GetMetaData() - Get the supplementary record for a message
3898 void GetMetaData(struct MetaData *smibuf, long msgnum)
3901 struct cdbdata *cdbsmi;
3904 memset(smibuf, 0, sizeof(struct MetaData));
3905 smibuf->meta_msgnum = msgnum;
3906 smibuf->meta_refcount = 1; /* Default reference count is 1 */
3908 /* Use the negative of the message number for its supp record index */
3909 TheIndex = (0L - msgnum);
3911 cdbsmi = cdb_fetch(CDB_MSGMAIN, &TheIndex, sizeof(long));
3912 if (cdbsmi == NULL) {
3913 return; /* record not found; go with defaults */
3915 memcpy(smibuf, cdbsmi->ptr,
3916 ((cdbsmi->len > sizeof(struct MetaData)) ?
3917 sizeof(struct MetaData) : cdbsmi->len));
3924 * PutMetaData() - (re)write supplementary record for a message
3926 void PutMetaData(struct MetaData *smibuf)
3930 /* Use the negative of the message number for the metadata db index */
3931 TheIndex = (0L - smibuf->meta_msgnum);
3933 cdb_store(CDB_MSGMAIN,
3934 &TheIndex, (int)sizeof(long),
3935 smibuf, (int)sizeof(struct MetaData));
3940 * AdjRefCount - submit an adjustment to the reference count for a message.
3941 * (These are just queued -- we actually process them later.)
3943 void AdjRefCount(long msgnum, int incr)
3945 struct arcq new_arcq;
3947 begin_critical_section(S_SUPPMSGMAIN);
3948 if (arcfp == NULL) {
3949 arcfp = fopen(file_arcq, "ab+");
3951 end_critical_section(S_SUPPMSGMAIN);
3953 /* msgnum < 0 means that we're trying to close the file */
3955 lprintf(CTDL_DEBUG, "Closing the AdjRefCount queue file\n");
3956 begin_critical_section(S_SUPPMSGMAIN);
3957 if (arcfp != NULL) {
3961 end_critical_section(S_SUPPMSGMAIN);
3966 * If we can't open the queue, perform the operation synchronously.
3968 if (arcfp == NULL) {
3969 TDAP_AdjRefCount(msgnum, incr);
3973 new_arcq.arcq_msgnum = msgnum;
3974 new_arcq.arcq_delta = incr;
3975 fwrite(&new_arcq, sizeof(struct arcq), 1, arcfp);
3983 * TDAP_ProcessAdjRefCountQueue()
3985 * Process the queue of message count adjustments that was created by calls
3986 * to AdjRefCount() ... by reading the queue and calling TDAP_AdjRefCount()
3987 * for each one. This should be an "off hours" operation.
3989 int TDAP_ProcessAdjRefCountQueue(void)
3991 char file_arcq_temp[PATH_MAX];
3994 struct arcq arcq_rec;
3995 int num_records_processed = 0;
3997 snprintf(file_arcq_temp, sizeof file_arcq_temp, "%s2", file_arcq);
3999 begin_critical_section(S_SUPPMSGMAIN);
4000 if (arcfp != NULL) {
4005 r = link(file_arcq, file_arcq_temp);
4007 lprintf(CTDL_CRIT, "%s: %s\n", file_arcq_temp, strerror(errno));
4008 end_critical_section(S_SUPPMSGMAIN);
4009 return(num_records_processed);
4013 end_critical_section(S_SUPPMSGMAIN);
4015 fp = fopen(file_arcq_temp, "rb");
4017 lprintf(CTDL_CRIT, "%s: %s\n", file_arcq_temp, strerror(errno));
4018 return(num_records_processed);
4021 while (fread(&arcq_rec, sizeof(struct arcq), 1, fp) == 1) {
4022 TDAP_AdjRefCount(arcq_rec.arcq_msgnum, arcq_rec.arcq_delta);
4023 ++num_records_processed;
4027 r = unlink(file_arcq_temp);
4029 lprintf(CTDL_CRIT, "%s: %s\n", file_arcq_temp, strerror(errno));
4032 return(num_records_processed);
4038 * TDAP_AdjRefCount - adjust the reference count for a message.
4039 * This one does it "for real" because it's called by
4040 * the autopurger function that processes the queue
4041 * created by AdjRefCount(). If a message's reference
4042 * count becomes zero, we also delete the message from
4043 * disk and de-index it.
4045 void TDAP_AdjRefCount(long msgnum, int incr)
4048 struct MetaData smi;
4051 /* This is a *tight* critical section; please keep it that way, as
4052 * it may get called while nested in other critical sections.
4053 * Complicating this any further will surely cause deadlock!
4055 begin_critical_section(S_SUPPMSGMAIN);
4056 GetMetaData(&smi, msgnum);
4057 smi.meta_refcount += incr;
4059 end_critical_section(S_SUPPMSGMAIN);
4060 lprintf(CTDL_DEBUG, "msg %ld ref count delta %d, is now %d\n",
4061 msgnum, incr, smi.meta_refcount);
4063 /* If the reference count is now zero, delete the message
4064 * (and its supplementary record as well).
4066 if (smi.meta_refcount == 0) {
4067 lprintf(CTDL_DEBUG, "Deleting message <%ld>\n", msgnum);
4069 /* Remove from fulltext index */
4070 if (config.c_enable_fulltext) {
4071 ft_index_message(msgnum, 0);
4074 /* Remove from message base */
4076 cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long));
4077 cdb_delete(CDB_BIGMSGS, &delnum, (int)sizeof(long));
4079 /* Remove metadata record */
4080 delnum = (0L - msgnum);
4081 cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long));
4087 * Write a generic object to this room
4089 * Note: this could be much more efficient. Right now we use two temporary
4090 * files, and still pull the message into memory as with all others.
4092 void CtdlWriteObject(char *req_room, /* Room to stuff it in */
4093 char *content_type, /* MIME type of this object */
4094 char *tempfilename, /* Where to fetch it from */
4095 struct ctdluser *is_mailbox, /* Mailbox room? */
4096 int is_binary, /* Is encoding necessary? */
4097 int is_unique, /* Del others of this type? */
4098 unsigned int flags /* Internal save flags */
4103 struct ctdlroom qrbuf;
4104 char roomname[ROOMNAMELEN];
4105 struct CtdlMessage *msg;
4107 char *raw_message = NULL;
4108 char *encoded_message = NULL;
4109 off_t raw_length = 0;
4111 if (is_mailbox != NULL) {
4112 MailboxName(roomname, sizeof roomname, is_mailbox, req_room);
4115 safestrncpy(roomname, req_room, sizeof(roomname));
4118 fp = fopen(tempfilename, "rb");
4120 lprintf(CTDL_CRIT, "Cannot open %s: %s\n",
4121 tempfilename, strerror(errno));
4124 fseek(fp, 0L, SEEK_END);
4125 raw_length = ftell(fp);
4127 lprintf(CTDL_DEBUG, "Raw length is %ld\n", (long)raw_length);
4129 raw_message = malloc((size_t)raw_length + 2);
4130 fread(raw_message, (size_t)raw_length, 1, fp);
4134 encoded_message = malloc((size_t)
4135 (((raw_length * 134) / 100) + 4096 ) );
4138 encoded_message = malloc((size_t)(raw_length + 4096));
4141 sprintf(encoded_message, "Content-type: %s\n", content_type);
4144 sprintf(&encoded_message[strlen(encoded_message)],
4145 "Content-transfer-encoding: base64\n\n"
4149 sprintf(&encoded_message[strlen(encoded_message)],
4150 "Content-transfer-encoding: 7bit\n\n"
4156 &encoded_message[strlen(encoded_message)],
4162 raw_message[raw_length] = 0;
4164 &encoded_message[strlen(encoded_message)],
4172 lprintf(CTDL_DEBUG, "Allocating\n");
4173 msg = malloc(sizeof(struct CtdlMessage));
4174 memset(msg, 0, sizeof(struct CtdlMessage));
4175 msg->cm_magic = CTDLMESSAGE_MAGIC;
4176 msg->cm_anon_type = MES_NORMAL;
4177 msg->cm_format_type = 4;
4178 msg->cm_fields['A'] = strdup(CC->user.fullname);
4179 msg->cm_fields['O'] = strdup(req_room);
4180 msg->cm_fields['N'] = strdup(config.c_nodename);
4181 msg->cm_fields['H'] = strdup(config.c_humannode);
4182 msg->cm_flags = flags;
4184 msg->cm_fields['M'] = encoded_message;
4186 /* Create the requested room if we have to. */
4187 if (getroom(&qrbuf, roomname) != 0) {
4188 create_room(roomname,
4189 ( (is_mailbox != NULL) ? 5 : 3 ),
4190 "", 0, 1, 0, VIEW_BBS);
4192 /* If the caller specified this object as unique, delete all
4193 * other objects of this type that are currently in the room.
4196 lprintf(CTDL_DEBUG, "Deleted %d other msgs of this type\n",
4197 CtdlDeleteMessages(roomname, NULL, 0, content_type)
4200 /* Now write the data */
4201 CtdlSubmitMsg(msg, NULL, roomname);
4202 CtdlFreeMessage(msg);
4210 void CtdlGetSysConfigBackend(long msgnum, void *userdata) {
4211 config_msgnum = msgnum;
4215 char *CtdlGetSysConfig(char *sysconfname) {
4216 char hold_rm[ROOMNAMELEN];
4219 struct CtdlMessage *msg;
4222 strcpy(hold_rm, CC->room.QRname);
4223 if (getroom(&CC->room, SYSCONFIGROOM) != 0) {
4224 getroom(&CC->room, hold_rm);
4229 /* We want the last (and probably only) config in this room */
4230 begin_critical_section(S_CONFIG);
4231 config_msgnum = (-1L);
4232 CtdlForEachMessage(MSGS_LAST, 1, NULL, sysconfname, NULL,
4233 CtdlGetSysConfigBackend, NULL);
4234 msgnum = config_msgnum;
4235 end_critical_section(S_CONFIG);
4241 msg = CtdlFetchMessage(msgnum, 1);
4243 conf = strdup(msg->cm_fields['M']);
4244 CtdlFreeMessage(msg);
4251 getroom(&CC->room, hold_rm);
4253 if (conf != NULL) do {
4254 extract_token(buf, conf, 0, '\n', sizeof buf);
4255 strcpy(conf, &conf[strlen(buf)+1]);
4256 } while ( (strlen(conf)>0) && (strlen(buf)>0) );
4261 void CtdlPutSysConfig(char *sysconfname, char *sysconfdata) {
4262 char temp[PATH_MAX];
4265 CtdlMakeTempFileName(temp, sizeof temp);
4267 fp = fopen(temp, "w");
4268 if (fp == NULL) return;
4269 fprintf(fp, "%s", sysconfdata);
4272 /* this handy API function does all the work for us */
4273 CtdlWriteObject(SYSCONFIGROOM, sysconfname, temp, NULL, 0, 1, 0);
4279 * Determine whether a given Internet address belongs to the current user
4281 int CtdlIsMe(char *addr, int addr_buf_len)
4283 struct recptypes *recp;
4286 recp = validate_recipients(addr);
4287 if (recp == NULL) return(0);
4289 if (recp->num_local == 0) {
4290 free_recipients(recp);
4294 for (i=0; i<recp->num_local; ++i) {
4295 extract_token(addr, recp->recp_local, i, '|', addr_buf_len);
4296 if (!strcasecmp(addr, CC->user.fullname)) {
4297 free_recipients(recp);
4302 free_recipients(recp);
4308 * Citadel protocol command to do the same
4310 void cmd_isme(char *argbuf) {
4313 if (CtdlAccessCheck(ac_logged_in)) return;
4314 extract_token(addr, argbuf, 0, '|', sizeof addr);
4316 if (CtdlIsMe(addr, sizeof addr)) {
4317 cprintf("%d %s\n", CIT_OK, addr);
4320 cprintf("%d Not you.\n", ERROR + ILLEGAL_VALUE);