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|%s\n", CIT_OK,
3524 ((valid_to != NULL) ? valid_to->display_recp : ""),
3525 ((CC->room.QRflags2 & QR2_SUBJECTREQ)?
3526 "SUBJECTREQ" : "SUBJECTOPT") );
3527 free_recipients(valid_to);
3528 free_recipients(valid_cc);
3529 free_recipients(valid_bcc);
3533 /* We don't need these anymore because we'll do it differently below */
3534 free_recipients(valid_to);
3535 free_recipients(valid_cc);
3536 free_recipients(valid_bcc);
3538 /* Read in the message from the client. */
3540 cprintf("%d send message\n", START_CHAT_MODE);
3542 cprintf("%d send message\n", SEND_LISTING);
3545 msg = CtdlMakeMessage(&CC->user, recp, cc,
3546 CC->room.QRname, anonymous, format_type,
3547 newusername, newuseremail, subject,
3548 ((strlen(supplied_euid) > 0) ? supplied_euid : NULL),
3551 /* Put together one big recipients struct containing to/cc/bcc all in
3552 * one. This is for the envelope.
3554 char *all_recps = malloc(SIZ * 3);
3555 strcpy(all_recps, recp);
3556 if (strlen(cc) > 0) {
3557 if (strlen(all_recps) > 0) {
3558 strcat(all_recps, ",");
3560 strcat(all_recps, cc);
3562 if (strlen(bcc) > 0) {
3563 if (strlen(all_recps) > 0) {
3564 strcat(all_recps, ",");
3566 strcat(all_recps, bcc);
3568 if (strlen(all_recps) > 0) {
3569 valid = validate_recipients(all_recps);
3577 msgnum = CtdlSubmitMsg(msg, valid, "");
3580 cprintf("%ld\n", msgnum);
3582 cprintf("Message accepted.\n");
3585 cprintf("Internal error.\n");
3587 if (msg->cm_fields['E'] != NULL) {
3588 cprintf("%s\n", msg->cm_fields['E']);
3595 CtdlFreeMessage(msg);
3597 if (valid != NULL) {
3598 free_recipients(valid);
3606 * API function to delete messages which match a set of criteria
3607 * (returns the actual number of messages deleted)
3609 int CtdlDeleteMessages(char *room_name, /* which room */
3610 long *dmsgnums, /* array of msg numbers to be deleted */
3611 int num_dmsgnums, /* number of msgs to be deleted, or 0 for "any" */
3612 char *content_type /* or "" for any. regular expressions expected. */
3615 struct ctdlroom qrbuf;
3616 struct cdbdata *cdbfr;
3617 long *msglist = NULL;
3618 long *dellist = NULL;
3621 int num_deleted = 0;
3623 struct MetaData smi;
3626 int need_to_free_re = 0;
3628 if (content_type) if (strlen(content_type) > 0) {
3629 regcomp(&re, content_type, 0);
3630 need_to_free_re = 1;
3632 lprintf(CTDL_DEBUG, "CtdlDeleteMessages(%s, %d msgs, %s)\n",
3633 room_name, num_dmsgnums, content_type);
3635 /* get room record, obtaining a lock... */
3636 if (lgetroom(&qrbuf, room_name) != 0) {
3637 lprintf(CTDL_ERR, "CtdlDeleteMessages(): Room <%s> not found\n",
3639 if (need_to_free_re) regfree(&re);
3640 return (0); /* room not found */
3642 cdbfr = cdb_fetch(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long));
3644 if (cdbfr != NULL) {
3645 dellist = malloc(cdbfr->len);
3646 msglist = (long *) cdbfr->ptr;
3647 cdbfr->ptr = NULL; /* CtdlDeleteMessages() now owns this memory */
3648 num_msgs = cdbfr->len / sizeof(long);
3652 for (i = 0; i < num_msgs; ++i) {
3655 /* Set/clear a bit for each criterion */
3657 /* 0 messages in the list or a null list means that we are
3658 * interested in deleting any messages which meet the other criteria.
3660 if ((num_dmsgnums == 0) || (dmsgnums == NULL)) {
3661 delete_this |= 0x01;
3664 for (j=0; j<num_dmsgnums; ++j) {
3665 if (msglist[i] == dmsgnums[j]) {
3666 delete_this |= 0x01;
3671 if (strlen(content_type) == 0) {
3672 delete_this |= 0x02;
3674 GetMetaData(&smi, msglist[i]);
3675 if (regexec(&re, smi.meta_content_type, 1, &pm, 0) == 0) {
3676 delete_this |= 0x02;
3680 /* Delete message only if all bits are set */
3681 if (delete_this == 0x03) {
3682 dellist[num_deleted++] = msglist[i];
3687 num_msgs = sort_msglist(msglist, num_msgs);
3688 cdb_store(CDB_MSGLISTS, &qrbuf.QRnumber, (int)sizeof(long),
3689 msglist, (int)(num_msgs * sizeof(long)));
3691 qrbuf.QRhighest = msglist[num_msgs - 1];
3695 /* Go through the messages we pulled out of the index, and decrement
3696 * their reference counts by 1. If this is the only room the message
3697 * was in, the reference count will reach zero and the message will
3698 * automatically be deleted from the database. We do this in a
3699 * separate pass because there might be plug-in hooks getting called,
3700 * and we don't want that happening during an S_ROOMS critical
3703 if (num_deleted) for (i=0; i<num_deleted; ++i) {
3704 PerformDeleteHooks(qrbuf.QRname, dellist[i]);
3705 AdjRefCount(dellist[i], -1);
3708 /* Now free the memory we used, and go away. */
3709 if (msglist != NULL) free(msglist);
3710 if (dellist != NULL) free(dellist);
3711 lprintf(CTDL_DEBUG, "%d message(s) deleted.\n", num_deleted);
3712 if (need_to_free_re) regfree(&re);
3713 return (num_deleted);
3719 * Check whether the current user has permission to delete messages from
3720 * the current room (returns 1 for yes, 0 for no)
3722 int CtdlDoIHavePermissionToDeleteMessagesFromThisRoom(void) {
3724 CtdlRoomAccess(&CC->room, &CC->user, &ra, NULL);
3725 if (ra & UA_DELETEALLOWED) return(1);
3733 * Delete message from current room
3735 void cmd_dele(char *args)
3744 extract_token(msgset, args, 0, '|', sizeof msgset);
3745 num_msgs = num_tokens(msgset, ',');
3747 cprintf("%d Nothing to do.\n", CIT_OK);
3751 if (CtdlDoIHavePermissionToDeleteMessagesFromThisRoom() == 0) {
3752 cprintf("%d Higher access required.\n",
3753 ERROR + HIGHER_ACCESS_REQUIRED);
3758 * Build our message set to be moved/copied
3760 msgs = malloc(num_msgs * sizeof(long));
3761 for (i=0; i<num_msgs; ++i) {
3762 extract_token(msgtok, msgset, i, ',', sizeof msgtok);
3763 msgs[i] = atol(msgtok);
3766 num_deleted = CtdlDeleteMessages(CC->room.QRname, msgs, num_msgs, "");
3770 cprintf("%d %d message%s deleted.\n", CIT_OK,
3771 num_deleted, ((num_deleted != 1) ? "s" : ""));
3773 cprintf("%d Message not found.\n", ERROR + MESSAGE_NOT_FOUND);
3779 * Back end API function for moves and deletes (multiple messages)
3781 int CtdlCopyMsgsToRoom(long *msgnums, int num_msgs, char *dest) {
3784 err = CtdlSaveMsgPointersInRoom(dest, msgnums, num_msgs, 1, NULL);
3785 if (err != 0) return(err);
3794 * move or copy a message to another room
3796 void cmd_move(char *args)
3803 char targ[ROOMNAMELEN];
3804 struct ctdlroom qtemp;
3811 extract_token(msgset, args, 0, '|', sizeof msgset);
3812 num_msgs = num_tokens(msgset, ',');
3814 cprintf("%d Nothing to do.\n", CIT_OK);
3818 extract_token(targ, args, 1, '|', sizeof targ);
3819 convert_room_name_macros(targ, sizeof targ);
3820 targ[ROOMNAMELEN - 1] = 0;
3821 is_copy = extract_int(args, 2);
3823 if (getroom(&qtemp, targ) != 0) {
3824 cprintf("%d '%s' does not exist.\n",
3825 ERROR + ROOM_NOT_FOUND, targ);
3829 getuser(&CC->user, CC->curr_user);
3830 CtdlRoomAccess(&qtemp, &CC->user, &ra, NULL);
3832 /* Check for permission to perform this operation.
3833 * Remember: "CC->room" is source, "qtemp" is target.
3837 /* Aides can move/copy */
3838 if (CC->user.axlevel >= 6) permit = 1;
3840 /* Room aides can move/copy */
3841 if (CC->user.usernum == CC->room.QRroomaide) permit = 1;
3843 /* Permit move/copy from personal rooms */
3844 if ((CC->room.QRflags & QR_MAILBOX)
3845 && (qtemp.QRflags & QR_MAILBOX)) permit = 1;
3847 /* Permit only copy from public to personal room */
3849 && (!(CC->room.QRflags & QR_MAILBOX))
3850 && (qtemp.QRflags & QR_MAILBOX)) permit = 1;
3852 /* Permit message removal from collaborative delete rooms */
3853 if (CC->room.QRflags2 & QR2_COLLABDEL) permit = 1;
3855 /* User must have access to target room */
3856 if (!(ra & UA_KNOWN)) permit = 0;
3859 cprintf("%d Higher access required.\n",
3860 ERROR + HIGHER_ACCESS_REQUIRED);
3865 * Build our message set to be moved/copied
3867 msgs = malloc(num_msgs * sizeof(long));
3868 for (i=0; i<num_msgs; ++i) {
3869 extract_token(msgtok, msgset, i, ',', sizeof msgtok);
3870 msgs[i] = atol(msgtok);
3876 err = CtdlCopyMsgsToRoom(msgs, num_msgs, targ);
3878 cprintf("%d Cannot store message(s) in %s: error %d\n",
3884 /* Now delete the message from the source room,
3885 * if this is a 'move' rather than a 'copy' operation.
3888 CtdlDeleteMessages(CC->room.QRname, msgs, num_msgs, "");
3892 cprintf("%d Message(s) %s.\n", CIT_OK, (is_copy ? "copied" : "moved") );
3898 * GetMetaData() - Get the supplementary record for a message
3900 void GetMetaData(struct MetaData *smibuf, long msgnum)
3903 struct cdbdata *cdbsmi;
3906 memset(smibuf, 0, sizeof(struct MetaData));
3907 smibuf->meta_msgnum = msgnum;
3908 smibuf->meta_refcount = 1; /* Default reference count is 1 */
3910 /* Use the negative of the message number for its supp record index */
3911 TheIndex = (0L - msgnum);
3913 cdbsmi = cdb_fetch(CDB_MSGMAIN, &TheIndex, sizeof(long));
3914 if (cdbsmi == NULL) {
3915 return; /* record not found; go with defaults */
3917 memcpy(smibuf, cdbsmi->ptr,
3918 ((cdbsmi->len > sizeof(struct MetaData)) ?
3919 sizeof(struct MetaData) : cdbsmi->len));
3926 * PutMetaData() - (re)write supplementary record for a message
3928 void PutMetaData(struct MetaData *smibuf)
3932 /* Use the negative of the message number for the metadata db index */
3933 TheIndex = (0L - smibuf->meta_msgnum);
3935 cdb_store(CDB_MSGMAIN,
3936 &TheIndex, (int)sizeof(long),
3937 smibuf, (int)sizeof(struct MetaData));
3942 * AdjRefCount - submit an adjustment to the reference count for a message.
3943 * (These are just queued -- we actually process them later.)
3945 void AdjRefCount(long msgnum, int incr)
3947 struct arcq new_arcq;
3949 begin_critical_section(S_SUPPMSGMAIN);
3950 if (arcfp == NULL) {
3951 arcfp = fopen(file_arcq, "ab+");
3953 end_critical_section(S_SUPPMSGMAIN);
3955 /* msgnum < 0 means that we're trying to close the file */
3957 lprintf(CTDL_DEBUG, "Closing the AdjRefCount queue file\n");
3958 begin_critical_section(S_SUPPMSGMAIN);
3959 if (arcfp != NULL) {
3963 end_critical_section(S_SUPPMSGMAIN);
3968 * If we can't open the queue, perform the operation synchronously.
3970 if (arcfp == NULL) {
3971 TDAP_AdjRefCount(msgnum, incr);
3975 new_arcq.arcq_msgnum = msgnum;
3976 new_arcq.arcq_delta = incr;
3977 fwrite(&new_arcq, sizeof(struct arcq), 1, arcfp);
3985 * TDAP_ProcessAdjRefCountQueue()
3987 * Process the queue of message count adjustments that was created by calls
3988 * to AdjRefCount() ... by reading the queue and calling TDAP_AdjRefCount()
3989 * for each one. This should be an "off hours" operation.
3991 int TDAP_ProcessAdjRefCountQueue(void)
3993 char file_arcq_temp[PATH_MAX];
3996 struct arcq arcq_rec;
3997 int num_records_processed = 0;
3999 snprintf(file_arcq_temp, sizeof file_arcq_temp, "%s2", file_arcq);
4001 begin_critical_section(S_SUPPMSGMAIN);
4002 if (arcfp != NULL) {
4007 r = link(file_arcq, file_arcq_temp);
4009 lprintf(CTDL_CRIT, "%s: %s\n", file_arcq_temp, strerror(errno));
4010 end_critical_section(S_SUPPMSGMAIN);
4011 return(num_records_processed);
4015 end_critical_section(S_SUPPMSGMAIN);
4017 fp = fopen(file_arcq_temp, "rb");
4019 lprintf(CTDL_CRIT, "%s: %s\n", file_arcq_temp, strerror(errno));
4020 return(num_records_processed);
4023 while (fread(&arcq_rec, sizeof(struct arcq), 1, fp) == 1) {
4024 TDAP_AdjRefCount(arcq_rec.arcq_msgnum, arcq_rec.arcq_delta);
4025 ++num_records_processed;
4029 r = unlink(file_arcq_temp);
4031 lprintf(CTDL_CRIT, "%s: %s\n", file_arcq_temp, strerror(errno));
4034 return(num_records_processed);
4040 * TDAP_AdjRefCount - adjust the reference count for a message.
4041 * This one does it "for real" because it's called by
4042 * the autopurger function that processes the queue
4043 * created by AdjRefCount(). If a message's reference
4044 * count becomes zero, we also delete the message from
4045 * disk and de-index it.
4047 void TDAP_AdjRefCount(long msgnum, int incr)
4050 struct MetaData smi;
4053 /* This is a *tight* critical section; please keep it that way, as
4054 * it may get called while nested in other critical sections.
4055 * Complicating this any further will surely cause deadlock!
4057 begin_critical_section(S_SUPPMSGMAIN);
4058 GetMetaData(&smi, msgnum);
4059 smi.meta_refcount += incr;
4061 end_critical_section(S_SUPPMSGMAIN);
4062 lprintf(CTDL_DEBUG, "msg %ld ref count delta %d, is now %d\n",
4063 msgnum, incr, smi.meta_refcount);
4065 /* If the reference count is now zero, delete the message
4066 * (and its supplementary record as well).
4068 if (smi.meta_refcount == 0) {
4069 lprintf(CTDL_DEBUG, "Deleting message <%ld>\n", msgnum);
4071 /* Remove from fulltext index */
4072 if (config.c_enable_fulltext) {
4073 ft_index_message(msgnum, 0);
4076 /* Remove from message base */
4078 cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long));
4079 cdb_delete(CDB_BIGMSGS, &delnum, (int)sizeof(long));
4081 /* Remove metadata record */
4082 delnum = (0L - msgnum);
4083 cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long));
4089 * Write a generic object to this room
4091 * Note: this could be much more efficient. Right now we use two temporary
4092 * files, and still pull the message into memory as with all others.
4094 void CtdlWriteObject(char *req_room, /* Room to stuff it in */
4095 char *content_type, /* MIME type of this object */
4096 char *tempfilename, /* Where to fetch it from */
4097 struct ctdluser *is_mailbox, /* Mailbox room? */
4098 int is_binary, /* Is encoding necessary? */
4099 int is_unique, /* Del others of this type? */
4100 unsigned int flags /* Internal save flags */
4105 struct ctdlroom qrbuf;
4106 char roomname[ROOMNAMELEN];
4107 struct CtdlMessage *msg;
4109 char *raw_message = NULL;
4110 char *encoded_message = NULL;
4111 off_t raw_length = 0;
4113 if (is_mailbox != NULL) {
4114 MailboxName(roomname, sizeof roomname, is_mailbox, req_room);
4117 safestrncpy(roomname, req_room, sizeof(roomname));
4120 fp = fopen(tempfilename, "rb");
4122 lprintf(CTDL_CRIT, "Cannot open %s: %s\n",
4123 tempfilename, strerror(errno));
4126 fseek(fp, 0L, SEEK_END);
4127 raw_length = ftell(fp);
4129 lprintf(CTDL_DEBUG, "Raw length is %ld\n", (long)raw_length);
4131 raw_message = malloc((size_t)raw_length + 2);
4132 fread(raw_message, (size_t)raw_length, 1, fp);
4136 encoded_message = malloc((size_t)
4137 (((raw_length * 134) / 100) + 4096 ) );
4140 encoded_message = malloc((size_t)(raw_length + 4096));
4143 sprintf(encoded_message, "Content-type: %s\n", content_type);
4146 sprintf(&encoded_message[strlen(encoded_message)],
4147 "Content-transfer-encoding: base64\n\n"
4151 sprintf(&encoded_message[strlen(encoded_message)],
4152 "Content-transfer-encoding: 7bit\n\n"
4158 &encoded_message[strlen(encoded_message)],
4164 raw_message[raw_length] = 0;
4166 &encoded_message[strlen(encoded_message)],
4174 lprintf(CTDL_DEBUG, "Allocating\n");
4175 msg = malloc(sizeof(struct CtdlMessage));
4176 memset(msg, 0, sizeof(struct CtdlMessage));
4177 msg->cm_magic = CTDLMESSAGE_MAGIC;
4178 msg->cm_anon_type = MES_NORMAL;
4179 msg->cm_format_type = 4;
4180 msg->cm_fields['A'] = strdup(CC->user.fullname);
4181 msg->cm_fields['O'] = strdup(req_room);
4182 msg->cm_fields['N'] = strdup(config.c_nodename);
4183 msg->cm_fields['H'] = strdup(config.c_humannode);
4184 msg->cm_flags = flags;
4186 msg->cm_fields['M'] = encoded_message;
4188 /* Create the requested room if we have to. */
4189 if (getroom(&qrbuf, roomname) != 0) {
4190 create_room(roomname,
4191 ( (is_mailbox != NULL) ? 5 : 3 ),
4192 "", 0, 1, 0, VIEW_BBS);
4194 /* If the caller specified this object as unique, delete all
4195 * other objects of this type that are currently in the room.
4198 lprintf(CTDL_DEBUG, "Deleted %d other msgs of this type\n",
4199 CtdlDeleteMessages(roomname, NULL, 0, content_type)
4202 /* Now write the data */
4203 CtdlSubmitMsg(msg, NULL, roomname);
4204 CtdlFreeMessage(msg);
4212 void CtdlGetSysConfigBackend(long msgnum, void *userdata) {
4213 config_msgnum = msgnum;
4217 char *CtdlGetSysConfig(char *sysconfname) {
4218 char hold_rm[ROOMNAMELEN];
4221 struct CtdlMessage *msg;
4224 strcpy(hold_rm, CC->room.QRname);
4225 if (getroom(&CC->room, SYSCONFIGROOM) != 0) {
4226 getroom(&CC->room, hold_rm);
4231 /* We want the last (and probably only) config in this room */
4232 begin_critical_section(S_CONFIG);
4233 config_msgnum = (-1L);
4234 CtdlForEachMessage(MSGS_LAST, 1, NULL, sysconfname, NULL,
4235 CtdlGetSysConfigBackend, NULL);
4236 msgnum = config_msgnum;
4237 end_critical_section(S_CONFIG);
4243 msg = CtdlFetchMessage(msgnum, 1);
4245 conf = strdup(msg->cm_fields['M']);
4246 CtdlFreeMessage(msg);
4253 getroom(&CC->room, hold_rm);
4255 if (conf != NULL) do {
4256 extract_token(buf, conf, 0, '\n', sizeof buf);
4257 strcpy(conf, &conf[strlen(buf)+1]);
4258 } while ( (strlen(conf)>0) && (strlen(buf)>0) );
4263 void CtdlPutSysConfig(char *sysconfname, char *sysconfdata) {
4264 char temp[PATH_MAX];
4267 CtdlMakeTempFileName(temp, sizeof temp);
4269 fp = fopen(temp, "w");
4270 if (fp == NULL) return;
4271 fprintf(fp, "%s", sysconfdata);
4274 /* this handy API function does all the work for us */
4275 CtdlWriteObject(SYSCONFIGROOM, sysconfname, temp, NULL, 0, 1, 0);
4281 * Determine whether a given Internet address belongs to the current user
4283 int CtdlIsMe(char *addr, int addr_buf_len)
4285 struct recptypes *recp;
4288 recp = validate_recipients(addr);
4289 if (recp == NULL) return(0);
4291 if (recp->num_local == 0) {
4292 free_recipients(recp);
4296 for (i=0; i<recp->num_local; ++i) {
4297 extract_token(addr, recp->recp_local, i, '|', addr_buf_len);
4298 if (!strcasecmp(addr, CC->user.fullname)) {
4299 free_recipients(recp);
4304 free_recipients(recp);
4310 * Citadel protocol command to do the same
4312 void cmd_isme(char *argbuf) {
4315 if (CtdlAccessCheck(ac_logged_in)) return;
4316 extract_token(addr, argbuf, 0, '|', sizeof addr);
4318 if (CtdlIsMe(addr, sizeof addr)) {
4319 cprintf("%d %s\n", CIT_OK, addr);
4322 cprintf("%d Not you.\n", ERROR + ILLEGAL_VALUE);