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" /* Needed for ft_search and ft_index_message */
54 #include "euidindex.h"
55 #include "journaling.h"
56 #include "citadel_dirs.h"
60 struct addresses_to_be_filed *atbf = NULL;
62 /* This temp file holds the queue of operations for AdjRefCount() */
63 static FILE *arcfp = NULL;
66 * This really belongs in serv_network.c, but I don't know how to export
67 * symbols between modules.
69 struct FilterList *filterlist = NULL;
73 * These are the four-character field headers we use when outputting
74 * messages in Citadel format (as opposed to RFC822 format).
77 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
78 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
79 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
80 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
81 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
82 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
83 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
84 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
112 * This function is self explanatory.
113 * (What can I say, I'm in a weird mood today...)
115 void remove_any_whitespace_to_the_left_or_right_of_at_symbol(char *name)
119 for (i = 0; i < strlen(name); ++i) {
120 if (name[i] == '@') {
121 while (isspace(name[i - 1]) && i > 0) {
122 strcpy(&name[i - 1], &name[i]);
125 while (isspace(name[i + 1])) {
126 strcpy(&name[i + 1], &name[i + 2]);
134 * Aliasing for network mail.
135 * (Error messages have been commented out, because this is a server.)
137 int alias(char *name)
138 { /* process alias and routing info for mail */
141 char aaa[SIZ], bbb[SIZ];
142 char *ignetcfg = NULL;
143 char *ignetmap = NULL;
149 char original_name[256];
150 safestrncpy(original_name, name, sizeof original_name);
153 remove_any_whitespace_to_the_left_or_right_of_at_symbol(name);
154 stripallbut(name, '<', '>');
156 fp = fopen(file_mail_aliases, "r");
158 fp = fopen("/dev/null", "r");
165 while (fgets(aaa, sizeof aaa, fp) != NULL) {
166 while (isspace(name[0]))
167 strcpy(name, &name[1]);
168 aaa[strlen(aaa) - 1] = 0;
170 for (a = 0; a < strlen(aaa); ++a) {
172 strcpy(bbb, &aaa[a + 1]);
176 if (!strcasecmp(name, aaa))
181 /* Hit the Global Address Book */
182 if (CtdlDirectoryLookup(aaa, name, sizeof aaa) == 0) {
186 if (strcasecmp(original_name, name)) {
187 lprintf(CTDL_INFO, "%s is being forwarded to %s\n", original_name, name);
190 /* Change "user @ xxx" to "user" if xxx is an alias for this host */
191 for (a=0; a<strlen(name); ++a) {
192 if (name[a] == '@') {
193 if (CtdlHostAlias(&name[a+1]) == hostalias_localhost) {
195 lprintf(CTDL_INFO, "Changed to <%s>\n", name);
200 /* determine local or remote type, see citadel.h */
201 at = haschar(name, '@');
202 if (at == 0) return(MES_LOCAL); /* no @'s - local address */
203 if (at > 1) return(MES_ERROR); /* >1 @'s - invalid address */
204 remove_any_whitespace_to_the_left_or_right_of_at_symbol(name);
206 /* figure out the delivery mode */
207 extract_token(node, name, 1, '@', sizeof node);
209 /* If there are one or more dots in the nodename, we assume that it
210 * is an FQDN and will attempt SMTP delivery to the Internet.
212 if (haschar(node, '.') > 0) {
213 return(MES_INTERNET);
216 /* Otherwise we look in the IGnet maps for a valid Citadel node.
217 * Try directly-connected nodes first...
219 ignetcfg = CtdlGetSysConfig(IGNETCFG);
220 for (i=0; i<num_tokens(ignetcfg, '\n'); ++i) {
221 extract_token(buf, ignetcfg, i, '\n', sizeof buf);
222 extract_token(testnode, buf, 0, '|', sizeof testnode);
223 if (!strcasecmp(node, testnode)) {
231 * Then try nodes that are two or more hops away.
233 ignetmap = CtdlGetSysConfig(IGNETMAP);
234 for (i=0; i<num_tokens(ignetmap, '\n'); ++i) {
235 extract_token(buf, ignetmap, i, '\n', sizeof buf);
236 extract_token(testnode, buf, 0, '|', sizeof testnode);
237 if (!strcasecmp(node, testnode)) {
244 /* If we get to this point it's an invalid node name */
250 * Back end for the MSGS command: output message number only.
252 void simple_listing(long msgnum, void *userdata)
254 cprintf("%ld\n", msgnum);
260 * Back end for the MSGS command: output header summary.
262 void headers_listing(long msgnum, void *userdata)
264 struct CtdlMessage *msg;
266 msg = CtdlFetchMessage(msgnum, 0);
268 cprintf("%ld|0|||||\n", msgnum);
272 cprintf("%ld|%s|%s|%s|%s|%s|\n",
274 (msg->cm_fields['T'] ? msg->cm_fields['T'] : "0"),
275 (msg->cm_fields['A'] ? msg->cm_fields['A'] : ""),
276 (msg->cm_fields['N'] ? msg->cm_fields['N'] : ""),
277 (msg->cm_fields['F'] ? msg->cm_fields['F'] : ""),
278 (msg->cm_fields['U'] ? msg->cm_fields['U'] : "")
280 CtdlFreeMessage(msg);
285 /* Determine if a given message matches the fields in a message template.
286 * Return 0 for a successful match.
288 int CtdlMsgCmp(struct CtdlMessage *msg, struct CtdlMessage *template) {
291 /* If there aren't any fields in the template, all messages will
294 if (template == NULL) return(0);
296 /* Null messages are bogus. */
297 if (msg == NULL) return(1);
299 for (i='A'; i<='Z'; ++i) {
300 if (template->cm_fields[i] != NULL) {
301 if (msg->cm_fields[i] == NULL) {
304 if (strcasecmp(msg->cm_fields[i],
305 template->cm_fields[i])) return 1;
309 /* All compares succeeded: we have a match! */
316 * Retrieve the "seen" message list for the current room.
318 void CtdlGetSeen(char *buf, int which_set) {
321 /* Learn about the user and room in question */
322 CtdlGetRelationship(&vbuf, &CC->user, &CC->room);
324 if (which_set == ctdlsetseen_seen)
325 safestrncpy(buf, vbuf.v_seen, SIZ);
326 if (which_set == ctdlsetseen_answered)
327 safestrncpy(buf, vbuf.v_answered, SIZ);
333 * Manipulate the "seen msgs" string (or other message set strings)
335 void CtdlSetSeen(long *target_msgnums, int num_target_msgnums,
336 int target_setting, int which_set,
337 struct ctdluser *which_user, struct ctdlroom *which_room) {
338 struct cdbdata *cdbfr;
350 char *is_set; /* actually an array of booleans */
353 char setstr[SIZ], lostr[SIZ], histr[SIZ];
356 /* Don't bother doing *anything* if we were passed a list of zero messages */
357 if (num_target_msgnums < 1) {
361 lprintf(CTDL_DEBUG, "CtdlSetSeen(%d msgs starting with %ld, %d, %d)\n",
362 num_target_msgnums, target_msgnums[0],
363 target_setting, which_set);
365 /* Learn about the user and room in question */
366 CtdlGetRelationship(&vbuf,
367 ((which_user != NULL) ? which_user : &CC->user),
368 ((which_room != NULL) ? which_room : &CC->room)
371 /* Load the message list */
372 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long));
374 msglist = (long *) cdbfr->ptr;
375 cdbfr->ptr = NULL; /* CtdlSetSeen() now owns this memory */
376 num_msgs = cdbfr->len / sizeof(long);
379 return; /* No messages at all? No further action. */
382 is_set = malloc(num_msgs * sizeof(char));
383 memset(is_set, 0, (num_msgs * sizeof(char)) );
385 /* Decide which message set we're manipulating */
387 case ctdlsetseen_seen:
388 safestrncpy(vset, vbuf.v_seen, sizeof vset);
390 case ctdlsetseen_answered:
391 safestrncpy(vset, vbuf.v_answered, sizeof vset);
395 /* lprintf(CTDL_DEBUG, "before optimize: %s\n", vset); */
397 /* Translate the existing sequence set into an array of booleans */
398 num_sets = num_tokens(vset, ',');
399 for (s=0; s<num_sets; ++s) {
400 extract_token(setstr, vset, s, ',', sizeof setstr);
402 extract_token(lostr, setstr, 0, ':', sizeof lostr);
403 if (num_tokens(setstr, ':') >= 2) {
404 extract_token(histr, setstr, 1, ':', sizeof histr);
405 if (!strcmp(histr, "*")) {
406 snprintf(histr, sizeof histr, "%ld", LONG_MAX);
410 strcpy(histr, lostr);
415 for (i = 0; i < num_msgs; ++i) {
416 if ((msglist[i] >= lo) && (msglist[i] <= hi)) {
422 /* Now translate the array of booleans back into a sequence set */
427 for (i=0; i<num_msgs; ++i) {
429 is_seen = is_set[i]; /* Default to existing setting */
431 for (k=0; k<num_target_msgnums; ++k) {
432 if (msglist[i] == target_msgnums[k]) {
433 is_seen = target_setting;
438 if (lo < 0L) lo = msglist[i];
442 if ( ((is_seen == 0) && (was_seen == 1))
443 || ((is_seen == 1) && (i == num_msgs-1)) ) {
445 /* begin trim-o-matic code */
448 while ( (strlen(vset) + 20) > sizeof vset) {
449 remove_token(vset, 0, ',');
451 if (j--) break; /* loop no more than 9 times */
453 if ( (trimming) && (which_set == ctdlsetseen_seen) ) {
457 snprintf(lostr, sizeof lostr,
458 "1:%ld,%s", t, vset);
459 safestrncpy(vset, lostr, sizeof vset);
461 /* end trim-o-matic code */
469 snprintf(&vset[tmp], (sizeof vset) - tmp,
473 snprintf(&vset[tmp], (sizeof vset) - tmp,
482 /* Decide which message set we're manipulating */
484 case ctdlsetseen_seen:
485 safestrncpy(vbuf.v_seen, vset, sizeof vbuf.v_seen);
487 case ctdlsetseen_answered:
488 safestrncpy(vbuf.v_answered, vset,
489 sizeof vbuf.v_answered);
494 /* lprintf(CTDL_DEBUG, " after optimize: %s\n", vset); */
496 CtdlSetRelationship(&vbuf,
497 ((which_user != NULL) ? which_user : &CC->user),
498 ((which_room != NULL) ? which_room : &CC->room)
504 * API function to perform an operation for each qualifying message in the
505 * current room. (Returns the number of messages processed.)
507 int CtdlForEachMessage(int mode, long ref, char *search_string,
509 struct CtdlMessage *compare,
510 void (*CallBack) (long, void *),
516 struct cdbdata *cdbfr;
517 long *msglist = NULL;
519 int num_processed = 0;
522 struct CtdlMessage *msg = NULL;
525 int printed_lastold = 0;
526 int num_search_msgs = 0;
527 long *search_msgs = NULL;
529 int need_to_free_re = 0;
532 if (content_type) if (strlen(content_type) > 0) {
533 regcomp(&re, content_type, 0);
537 /* Learn about the user and room in question */
538 getuser(&CC->user, CC->curr_user);
539 CtdlGetRelationship(&vbuf, &CC->user, &CC->room);
541 /* Load the message list */
542 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long));
544 msglist = (long *) cdbfr->ptr;
545 cdbfr->ptr = NULL; /* CtdlForEachMessage() now owns this memory */
546 num_msgs = cdbfr->len / sizeof(long);
549 if (need_to_free_re) regfree(&re);
550 return 0; /* No messages at all? No further action. */
555 * Now begin the traversal.
557 if (num_msgs > 0) for (a = 0; a < num_msgs; ++a) {
559 /* If the caller is looking for a specific MIME type, filter
560 * out all messages which are not of the type requested.
562 if (content_type != NULL) if (strlen(content_type) > 0) {
564 /* This call to GetMetaData() sits inside this loop
565 * so that we only do the extra database read per msg
566 * if we need to. Doing the extra read all the time
567 * really kills the server. If we ever need to use
568 * metadata for another search criterion, we need to
569 * move the read somewhere else -- but still be smart
570 * enough to only do the read if the caller has
571 * specified something that will need it.
573 GetMetaData(&smi, msglist[a]);
575 /* if (strcasecmp(smi.meta_content_type, content_type)) { old non-regex way */
576 if (regexec(&re, smi.meta_content_type, 1, &pm, 0) != 0) {
582 num_msgs = sort_msglist(msglist, num_msgs);
584 /* If a template was supplied, filter out the messages which
585 * don't match. (This could induce some delays!)
588 if (compare != NULL) {
589 for (a = 0; a < num_msgs; ++a) {
590 msg = CtdlFetchMessage(msglist[a], 1);
592 if (CtdlMsgCmp(msg, compare)) {
595 CtdlFreeMessage(msg);
601 /* If a search string was specified, get a message list from
602 * the full text index and remove messages which aren't on both
606 * Since the lists are sorted and strictly ascending, and the
607 * output list is guaranteed to be shorter than or equal to the
608 * input list, we overwrite the bottom of the input list. This
609 * eliminates the need to memmove big chunks of the list over and
612 if ( (num_msgs > 0) && (mode == MSGS_SEARCH) && (search_string) ) {
613 ft_search(&num_search_msgs, &search_msgs, search_string);
614 if (num_search_msgs > 0) {
618 orig_num_msgs = num_msgs;
620 for (i=0; i<orig_num_msgs; ++i) {
621 for (j=0; j<num_search_msgs; ++j) {
622 if (msglist[i] == search_msgs[j]) {
623 msglist[num_msgs++] = msglist[i];
629 num_msgs = 0; /* No messages qualify */
631 if (search_msgs != NULL) free(search_msgs);
633 /* Now that we've purged messages which don't contain the search
634 * string, treat a MSGS_SEARCH just like a MSGS_ALL from this
641 * Now iterate through the message list, according to the
642 * criteria supplied by the caller.
645 for (a = 0; a < num_msgs; ++a) {
646 thismsg = msglist[a];
647 if (mode == MSGS_ALL) {
651 is_seen = is_msg_in_sequence_set(
652 vbuf.v_seen, thismsg);
653 if (is_seen) lastold = thismsg;
659 || ((mode == MSGS_OLD) && (is_seen))
660 || ((mode == MSGS_NEW) && (!is_seen))
661 || ((mode == MSGS_LAST) && (a >= (num_msgs - ref)))
662 || ((mode == MSGS_FIRST) && (a < ref))
663 || ((mode == MSGS_GT) && (thismsg > ref))
664 || ((mode == MSGS_EQ) && (thismsg == ref))
667 if ((mode == MSGS_NEW) && (CC->user.flags & US_LASTOLD) && (lastold > 0L) && (printed_lastold == 0) && (!is_seen)) {
669 CallBack(lastold, userdata);
673 if (CallBack) CallBack(thismsg, userdata);
677 free(msglist); /* Clean up */
678 if (need_to_free_re) regfree(&re);
679 return num_processed;
685 * cmd_msgs() - get list of message #'s in this room
686 * implements the MSGS server command using CtdlForEachMessage()
688 void cmd_msgs(char *cmdbuf)
697 int with_template = 0;
698 struct CtdlMessage *template = NULL;
699 int with_headers = 0;
700 char search_string[1024];
702 extract_token(which, cmdbuf, 0, '|', sizeof which);
703 cm_ref = extract_int(cmdbuf, 1);
704 extract_token(search_string, cmdbuf, 1, '|', sizeof search_string);
705 with_template = extract_int(cmdbuf, 2);
706 with_headers = extract_int(cmdbuf, 3);
709 if (!strncasecmp(which, "OLD", 3))
711 else if (!strncasecmp(which, "NEW", 3))
713 else if (!strncasecmp(which, "FIRST", 5))
715 else if (!strncasecmp(which, "LAST", 4))
717 else if (!strncasecmp(which, "GT", 2))
719 else if (!strncasecmp(which, "SEARCH", 6))
724 if ((!(CC->logged_in)) && (!(CC->internal_pgm))) {
725 cprintf("%d not logged in\n", ERROR + NOT_LOGGED_IN);
729 if ( (mode == MSGS_SEARCH) && (!config.c_enable_fulltext) ) {
730 cprintf("%d Full text index is not enabled on this server.\n",
731 ERROR + CMD_NOT_SUPPORTED);
737 cprintf("%d Send template then receive message list\n",
739 template = (struct CtdlMessage *)
740 malloc(sizeof(struct CtdlMessage));
741 memset(template, 0, sizeof(struct CtdlMessage));
742 template->cm_magic = CTDLMESSAGE_MAGIC;
743 template->cm_anon_type = MES_NORMAL;
745 while(client_getln(buf, sizeof buf), strcmp(buf,"000")) {
746 extract_token(tfield, buf, 0, '|', sizeof tfield);
747 extract_token(tvalue, buf, 1, '|', sizeof tvalue);
748 for (i='A'; i<='Z'; ++i) if (msgkeys[i]!=NULL) {
749 if (!strcasecmp(tfield, msgkeys[i])) {
750 template->cm_fields[i] =
758 cprintf("%d \n", LISTING_FOLLOWS);
761 CtdlForEachMessage(mode,
762 ( (mode == MSGS_SEARCH) ? 0 : cm_ref ),
763 ( (mode == MSGS_SEARCH) ? search_string : NULL ),
766 (with_headers ? headers_listing : simple_listing),
769 if (template != NULL) CtdlFreeMessage(template);
777 * help_subst() - support routine for help file viewer
779 void help_subst(char *strbuf, char *source, char *dest)
784 while (p = pattern2(strbuf, source), (p >= 0)) {
785 strcpy(workbuf, &strbuf[p + strlen(source)]);
786 strcpy(&strbuf[p], dest);
787 strcat(strbuf, workbuf);
792 void do_help_subst(char *buffer)
796 help_subst(buffer, "^nodename", config.c_nodename);
797 help_subst(buffer, "^humannode", config.c_humannode);
798 help_subst(buffer, "^fqdn", config.c_fqdn);
799 help_subst(buffer, "^username", CC->user.fullname);
800 snprintf(buf2, sizeof buf2, "%ld", CC->user.usernum);
801 help_subst(buffer, "^usernum", buf2);
802 help_subst(buffer, "^sysadm", config.c_sysadm);
803 help_subst(buffer, "^variantname", CITADEL);
804 snprintf(buf2, sizeof buf2, "%d", config.c_maxsessions);
805 help_subst(buffer, "^maxsessions", buf2);
806 help_subst(buffer, "^bbsdir", ctdl_bbsbase_dir);
812 * memfmout() - Citadel text formatter and paginator.
813 * Although the original purpose of this routine was to format
814 * text to the reader's screen width, all we're really using it
815 * for here is to format text out to 80 columns before sending it
816 * to the client. The client software may reformat it again.
819 char *mptr, /* where are we going to get our text from? */
820 char subst, /* nonzero if we should do substitutions */
821 char *nl) /* string to terminate lines with */
829 static int width = 80;
834 c = 1; /* c is the current pos */
838 while (ch = *mptr, ((ch != 0) && (strlen(buffer) < 126))) {
840 buffer[strlen(buffer) + 1] = 0;
841 buffer[strlen(buffer)] = ch;
844 if (buffer[0] == '^')
845 do_help_subst(buffer);
847 buffer[strlen(buffer) + 1] = 0;
849 strcpy(buffer, &buffer[1]);
857 if (((ch == 13) || (ch == 10)) && (old != 13) && (old != 10)) {
860 if (((old == 13) || (old == 10)) && (isspace(real))) {
865 if (((strlen(aaa) + c) > (width - 5)) && (strlen(aaa) > (width - 5))) {
866 cprintf("%s%s", nl, aaa);
875 if ((strlen(aaa) + c) > (width - 5)) {
884 if ((ch == 13) || (ch == 10)) {
885 cprintf("%s%s", aaa, nl);
892 cprintf("%s%s", aaa, nl);
898 * Callback function for mime parser that simply lists the part
900 void list_this_part(char *name, char *filename, char *partnum, char *disp,
901 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
906 ma = (struct ma_info *)cbuserdata;
907 if (ma->is_ma == 0) {
908 cprintf("part=%s|%s|%s|%s|%s|%ld\n",
909 name, filename, partnum, disp, cbtype, (long)length);
914 * Callback function for multipart prefix
916 void list_this_pref(char *name, char *filename, char *partnum, char *disp,
917 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
922 ma = (struct ma_info *)cbuserdata;
923 if (!strcasecmp(cbtype, "multipart/alternative")) {
927 if (ma->is_ma == 0) {
928 cprintf("pref=%s|%s\n", partnum, cbtype);
933 * Callback function for multipart sufffix
935 void list_this_suff(char *name, char *filename, char *partnum, char *disp,
936 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
941 ma = (struct ma_info *)cbuserdata;
942 if (ma->is_ma == 0) {
943 cprintf("suff=%s|%s\n", partnum, cbtype);
945 if (!strcasecmp(cbtype, "multipart/alternative")) {
952 * Callback function for mime parser that opens a section for downloading
954 void mime_download(char *name, char *filename, char *partnum, char *disp,
955 void *content, char *cbtype, char *cbcharset, size_t length,
956 char *encoding, void *cbuserdata)
959 /* Silently go away if there's already a download open... */
960 if (CC->download_fp != NULL)
963 /* ...or if this is not the desired section */
964 if (strcasecmp(CC->download_desired_section, partnum))
967 CC->download_fp = tmpfile();
968 if (CC->download_fp == NULL)
971 fwrite(content, length, 1, CC->download_fp);
972 fflush(CC->download_fp);
973 rewind(CC->download_fp);
975 OpenCmdResult(filename, cbtype);
981 * Callback function for mime parser that outputs a section all at once
983 void mime_spew_section(char *name, char *filename, char *partnum, char *disp,
984 void *content, char *cbtype, char *cbcharset, size_t length,
985 char *encoding, void *cbuserdata)
987 int *found_it = (int *)cbuserdata;
989 /* ...or if this is not the desired section */
990 if (strcasecmp(CC->download_desired_section, partnum))
995 cprintf("%d %d\n", BINARY_FOLLOWS, (int)length);
996 client_write(content, length);
1002 * Load a message from disk into memory.
1003 * This is used by CtdlOutputMsg() and other fetch functions.
1005 * NOTE: Caller is responsible for freeing the returned CtdlMessage struct
1006 * using the CtdlMessageFree() function.
1008 struct CtdlMessage *CtdlFetchMessage(long msgnum, int with_body)
1010 struct cdbdata *dmsgtext;
1011 struct CtdlMessage *ret = NULL;
1015 cit_uint8_t field_header;
1017 lprintf(CTDL_DEBUG, "CtdlFetchMessage(%ld, %d)\n", msgnum, with_body);
1019 dmsgtext = cdb_fetch(CDB_MSGMAIN, &msgnum, sizeof(long));
1020 if (dmsgtext == NULL) {
1023 mptr = dmsgtext->ptr;
1024 upper_bound = mptr + dmsgtext->len;
1026 /* Parse the three bytes that begin EVERY message on disk.
1027 * The first is always 0xFF, the on-disk magic number.
1028 * The second is the anonymous/public type byte.
1029 * The third is the format type byte (vari, fixed, or MIME).
1034 "Message %ld appears to be corrupted.\n",
1039 ret = (struct CtdlMessage *) malloc(sizeof(struct CtdlMessage));
1040 memset(ret, 0, sizeof(struct CtdlMessage));
1042 ret->cm_magic = CTDLMESSAGE_MAGIC;
1043 ret->cm_anon_type = *mptr++; /* Anon type byte */
1044 ret->cm_format_type = *mptr++; /* Format type byte */
1047 * The rest is zero or more arbitrary fields. Load them in.
1048 * We're done when we encounter either a zero-length field or
1049 * have just processed the 'M' (message text) field.
1052 if (mptr >= upper_bound) {
1055 field_header = *mptr++;
1056 ret->cm_fields[field_header] = strdup(mptr);
1058 while (*mptr++ != 0); /* advance to next field */
1060 } while ((mptr < upper_bound) && (field_header != 'M'));
1064 /* Always make sure there's something in the msg text field. If
1065 * it's NULL, the message text is most likely stored separately,
1066 * so go ahead and fetch that. Failing that, just set a dummy
1067 * body so other code doesn't barf.
1069 if ( (ret->cm_fields['M'] == NULL) && (with_body) ) {
1070 dmsgtext = cdb_fetch(CDB_BIGMSGS, &msgnum, sizeof(long));
1071 if (dmsgtext != NULL) {
1072 ret->cm_fields['M'] = strdup(dmsgtext->ptr);
1076 if (ret->cm_fields['M'] == NULL) {
1077 ret->cm_fields['M'] = strdup("\r\n\r\n (no text)\r\n");
1080 /* Perform "before read" hooks (aborting if any return nonzero) */
1081 if (PerformMessageHooks(ret, EVT_BEFOREREAD) > 0) {
1082 CtdlFreeMessage(ret);
1091 * Returns 1 if the supplied pointer points to a valid Citadel message.
1092 * If the pointer is NULL or the magic number check fails, returns 0.
1094 int is_valid_message(struct CtdlMessage *msg) {
1097 if ((msg->cm_magic) != CTDLMESSAGE_MAGIC) {
1098 lprintf(CTDL_WARNING, "is_valid_message() -- self-check failed\n");
1106 * 'Destructor' for struct CtdlMessage
1108 void CtdlFreeMessage(struct CtdlMessage *msg)
1112 if (is_valid_message(msg) == 0)
1114 if (msg != NULL) free (msg);
1118 for (i = 0; i < 256; ++i)
1119 if (msg->cm_fields[i] != NULL) {
1120 free(msg->cm_fields[i]);
1123 msg->cm_magic = 0; /* just in case */
1129 * Pre callback function for multipart/alternative
1131 * NOTE: this differs from the standard behavior for a reason. Normally when
1132 * displaying multipart/alternative you want to show the _last_ usable
1133 * format in the message. Here we show the _first_ one, because it's
1134 * usually text/plain. Since this set of functions is designed for text
1135 * output to non-MIME-aware clients, this is the desired behavior.
1138 void fixed_output_pre(char *name, char *filename, char *partnum, char *disp,
1139 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
1144 ma = (struct ma_info *)cbuserdata;
1145 lprintf(CTDL_DEBUG, "fixed_output_pre() type=<%s>\n", cbtype);
1146 if (!strcasecmp(cbtype, "multipart/alternative")) {
1150 if (!strcasecmp(cbtype, "message/rfc822")) {
1156 * Post callback function for multipart/alternative
1158 void fixed_output_post(char *name, char *filename, char *partnum, char *disp,
1159 void *content, char *cbtype, char *cbcharset, size_t length,
1160 char *encoding, void *cbuserdata)
1164 ma = (struct ma_info *)cbuserdata;
1165 lprintf(CTDL_DEBUG, "fixed_output_post() type=<%s>\n", cbtype);
1166 if (!strcasecmp(cbtype, "multipart/alternative")) {
1170 if (!strcasecmp(cbtype, "message/rfc822")) {
1176 * Inline callback function for mime parser that wants to display text
1178 void fixed_output(char *name, char *filename, char *partnum, char *disp,
1179 void *content, char *cbtype, char *cbcharset, size_t length,
1180 char *encoding, void *cbuserdata)
1187 ma = (struct ma_info *)cbuserdata;
1190 "fixed_output() part %s: %s (%s) (%ld bytes)\n",
1191 partnum, filename, cbtype, (long)length);
1194 * If we're in the middle of a multipart/alternative scope and
1195 * we've already printed another section, skip this one.
1197 if ( (ma->is_ma) && (ma->did_print) ) {
1198 lprintf(CTDL_DEBUG, "Skipping part %s (%s)\n",
1204 if ( (!strcasecmp(cbtype, "text/plain"))
1205 || (strlen(cbtype)==0) ) {
1208 client_write(wptr, length);
1209 if (wptr[length-1] != '\n') {
1216 if (!strcasecmp(cbtype, "text/html")) {
1217 ptr = html_to_ascii(content, length, 80, 0);
1219 client_write(ptr, wlen);
1220 if (ptr[wlen-1] != '\n') {
1227 if (ma->use_fo_hooks) {
1228 if (PerformFixedOutputHooks(cbtype, content, length)) {
1229 /* above function returns nonzero if it handled the part */
1234 if (strncasecmp(cbtype, "multipart/", 10)) {
1235 cprintf("Part %s: %s (%s) (%ld bytes)\r\n",
1236 partnum, filename, cbtype, (long)length);
1242 * The client is elegant and sophisticated and wants to be choosy about
1243 * MIME content types, so figure out which multipart/alternative part
1244 * we're going to send.
1246 * We use a system of weights. When we find a part that matches one of the
1247 * MIME types we've declared as preferential, we can store it in ma->chosen_part
1248 * and then set ma->chosen_pref to that MIME type's position in our preference
1249 * list. If we then hit another match, we only replace the first match if
1250 * the preference value is lower.
1252 void choose_preferred(char *name, char *filename, char *partnum, char *disp,
1253 void *content, char *cbtype, char *cbcharset, size_t length,
1254 char *encoding, void *cbuserdata)
1260 ma = (struct ma_info *)cbuserdata;
1262 if (ma->is_ma > 0) {
1263 for (i=0; i<num_tokens(CC->preferred_formats, '|'); ++i) {
1264 extract_token(buf, CC->preferred_formats, i, '|', sizeof buf);
1265 if ( (!strcasecmp(buf, cbtype)) && (!ma->freeze) ) {
1266 if (i < ma->chosen_pref) {
1267 safestrncpy(ma->chosen_part, partnum, sizeof ma->chosen_part);
1268 ma->chosen_pref = i;
1276 * Now that we've chosen our preferred part, output it.
1278 void output_preferred(char *name, char *filename, char *partnum, char *disp,
1279 void *content, char *cbtype, char *cbcharset, size_t length,
1280 char *encoding, void *cbuserdata)
1284 int add_newline = 0;
1288 ma = (struct ma_info *)cbuserdata;
1290 /* This is not the MIME part you're looking for... */
1291 if (strcasecmp(partnum, ma->chosen_part)) return;
1293 /* If the content-type of this part is in our preferred formats
1294 * list, we can simply output it verbatim.
1296 for (i=0; i<num_tokens(CC->preferred_formats, '|'); ++i) {
1297 extract_token(buf, CC->preferred_formats, i, '|', sizeof buf);
1298 if (!strcasecmp(buf, cbtype)) {
1299 /* Yeah! Go! W00t!! */
1301 text_content = (char *)content;
1302 if (text_content[length-1] != '\n') {
1305 cprintf("Content-type: %s", cbtype);
1306 if (strlen(cbcharset) > 0) {
1307 cprintf("; charset=%s", cbcharset);
1309 cprintf("\nContent-length: %d\n",
1310 (int)(length + add_newline) );
1311 if (strlen(encoding) > 0) {
1312 cprintf("Content-transfer-encoding: %s\n", encoding);
1315 cprintf("Content-transfer-encoding: 7bit\n");
1317 cprintf("X-Citadel-MSG4-Partnum: %s\n", partnum);
1319 client_write(content, length);
1320 if (add_newline) cprintf("\n");
1325 /* No translations required or possible: output as text/plain */
1326 cprintf("Content-type: text/plain\n\n");
1327 fixed_output(name, filename, partnum, disp, content, cbtype, cbcharset,
1328 length, encoding, cbuserdata);
1333 char desired_section[64];
1340 * Callback function for
1342 void extract_encapsulated_message(char *name, char *filename, char *partnum, char *disp,
1343 void *content, char *cbtype, char *cbcharset, size_t length,
1344 char *encoding, void *cbuserdata)
1346 struct encapmsg *encap;
1348 encap = (struct encapmsg *)cbuserdata;
1350 /* Only proceed if this is the desired section... */
1351 if (!strcasecmp(encap->desired_section, partnum)) {
1352 encap->msglen = length;
1353 encap->msg = malloc(length + 2);
1354 memcpy(encap->msg, content, length);
1364 * Get a message off disk. (returns om_* values found in msgbase.h)
1367 int CtdlOutputMsg(long msg_num, /* message number (local) to fetch */
1368 int mode, /* how would you like that message? */
1369 int headers_only, /* eschew the message body? */
1370 int do_proto, /* do Citadel protocol responses? */
1371 int crlf, /* Use CRLF newlines instead of LF? */
1372 char *section /* NULL or a message/rfc822 section */
1374 struct CtdlMessage *TheMessage = NULL;
1375 int retcode = om_no_such_msg;
1376 struct encapmsg encap;
1378 lprintf(CTDL_DEBUG, "CtdlOutputMsg() msgnum=%ld, mode=%d, section=%s\n",
1380 (section ? section : "<>")
1383 if ((!(CC->logged_in)) && (!(CC->internal_pgm))) {
1384 if (do_proto) cprintf("%d Not logged in.\n",
1385 ERROR + NOT_LOGGED_IN);
1386 return(om_not_logged_in);
1389 /* FIXME: check message id against msglist for this room */
1392 * Fetch the message from disk. If we're in any sort of headers
1393 * only mode, request that we don't even bother loading the body
1396 if ( (headers_only == HEADERS_FAST) || (headers_only == HEADERS_ONLY) ) {
1397 TheMessage = CtdlFetchMessage(msg_num, 0);
1400 TheMessage = CtdlFetchMessage(msg_num, 1);
1403 if (TheMessage == NULL) {
1404 if (do_proto) cprintf("%d Can't locate msg %ld on disk\n",
1405 ERROR + MESSAGE_NOT_FOUND, msg_num);
1406 return(om_no_such_msg);
1409 /* Here is the weird form of this command, to process only an
1410 * encapsulated message/rfc822 section.
1412 if (section) if (strlen(section)>0) if (strcmp(section, "0")) {
1413 memset(&encap, 0, sizeof encap);
1414 safestrncpy(encap.desired_section, section, sizeof encap.desired_section);
1415 mime_parser(TheMessage->cm_fields['M'],
1417 *extract_encapsulated_message,
1418 NULL, NULL, (void *)&encap, 0
1420 CtdlFreeMessage(TheMessage);
1424 encap.msg[encap.msglen] = 0;
1425 TheMessage = convert_internet_message(encap.msg);
1426 encap.msg = NULL; /* no free() here, TheMessage owns it now */
1428 /* Now we let it fall through to the bottom of this
1429 * function, because TheMessage now contains the
1430 * encapsulated message instead of the top-level
1431 * message. Isn't that neat?
1436 if (do_proto) cprintf("%d msg %ld has no part %s\n",
1437 ERROR + MESSAGE_NOT_FOUND, msg_num, section);
1438 retcode = om_no_such_msg;
1443 /* Ok, output the message now */
1444 retcode = CtdlOutputPreLoadedMsg(
1446 headers_only, do_proto, crlf);
1447 CtdlFreeMessage(TheMessage);
1454 * Get a message off disk. (returns om_* values found in msgbase.h)
1457 int CtdlOutputPreLoadedMsg(
1458 struct CtdlMessage *TheMessage,
1459 int mode, /* how would you like that message? */
1460 int headers_only, /* eschew the message body? */
1461 int do_proto, /* do Citadel protocol responses? */
1462 int crlf /* Use CRLF newlines instead of LF? */
1468 char display_name[256];
1470 char *nl; /* newline string */
1472 int subject_found = 0;
1475 /* Buffers needed for RFC822 translation. These are all filled
1476 * using functions that are bounds-checked, and therefore we can
1477 * make them substantially smaller than SIZ.
1485 char datestamp[100];
1487 lprintf(CTDL_DEBUG, "CtdlOutputPreLoadedMsg(TheMessage=%s, %d, %d, %d, %d\n",
1488 ((TheMessage == NULL) ? "NULL" : "not null"),
1489 mode, headers_only, do_proto, crlf);
1491 strcpy(mid, "unknown");
1492 nl = (crlf ? "\r\n" : "\n");
1494 if (!is_valid_message(TheMessage)) {
1496 "ERROR: invalid preloaded message for output\n");
1497 return(om_no_such_msg);
1500 /* Are we downloading a MIME component? */
1501 if (mode == MT_DOWNLOAD) {
1502 if (TheMessage->cm_format_type != FMT_RFC822) {
1504 cprintf("%d This is not a MIME message.\n",
1505 ERROR + ILLEGAL_VALUE);
1506 } else if (CC->download_fp != NULL) {
1507 if (do_proto) cprintf(
1508 "%d You already have a download open.\n",
1509 ERROR + RESOURCE_BUSY);
1511 /* Parse the message text component */
1512 mptr = TheMessage->cm_fields['M'];
1513 mime_parser(mptr, NULL, *mime_download, NULL, NULL, NULL, 0);
1514 /* If there's no file open by this time, the requested
1515 * section wasn't found, so print an error
1517 if (CC->download_fp == NULL) {
1518 if (do_proto) cprintf(
1519 "%d Section %s not found.\n",
1520 ERROR + FILE_NOT_FOUND,
1521 CC->download_desired_section);
1524 return((CC->download_fp != NULL) ? om_ok : om_mime_error);
1527 /* MT_SPEW_SECTION is like MT_DOWNLOAD except it outputs the whole MIME part
1528 * in a single server operation instead of opening a download file.
1530 if (mode == MT_SPEW_SECTION) {
1531 if (TheMessage->cm_format_type != FMT_RFC822) {
1533 cprintf("%d This is not a MIME message.\n",
1534 ERROR + ILLEGAL_VALUE);
1536 /* Parse the message text component */
1539 mptr = TheMessage->cm_fields['M'];
1540 mime_parser(mptr, NULL, *mime_spew_section, NULL, NULL, (void *)&found_it, 0);
1541 /* If section wasn't found, print an error
1544 if (do_proto) cprintf(
1545 "%d Section %s not found.\n",
1546 ERROR + FILE_NOT_FOUND,
1547 CC->download_desired_section);
1550 return((CC->download_fp != NULL) ? om_ok : om_mime_error);
1553 /* now for the user-mode message reading loops */
1554 if (do_proto) cprintf("%d msg:\n", LISTING_FOLLOWS);
1556 /* Does the caller want to skip the headers? */
1557 if (headers_only == HEADERS_NONE) goto START_TEXT;
1559 /* Tell the client which format type we're using. */
1560 if ( (mode == MT_CITADEL) && (do_proto) ) {
1561 cprintf("type=%d\n", TheMessage->cm_format_type);
1564 /* nhdr=yes means that we're only displaying headers, no body */
1565 if ( (TheMessage->cm_anon_type == MES_ANONONLY)
1566 && (mode == MT_CITADEL)
1569 cprintf("nhdr=yes\n");
1572 /* begin header processing loop for Citadel message format */
1574 if ((mode == MT_CITADEL) || (mode == MT_MIME)) {
1576 safestrncpy(display_name, "<unknown>", sizeof display_name);
1577 if (TheMessage->cm_fields['A']) {
1578 strcpy(buf, TheMessage->cm_fields['A']);
1579 if (TheMessage->cm_anon_type == MES_ANONONLY) {
1580 safestrncpy(display_name, "****", sizeof display_name);
1582 else if (TheMessage->cm_anon_type == MES_ANONOPT) {
1583 safestrncpy(display_name, "anonymous", sizeof display_name);
1586 safestrncpy(display_name, buf, sizeof display_name);
1588 if ((is_room_aide())
1589 && ((TheMessage->cm_anon_type == MES_ANONONLY)
1590 || (TheMessage->cm_anon_type == MES_ANONOPT))) {
1591 size_t tmp = strlen(display_name);
1592 snprintf(&display_name[tmp],
1593 sizeof display_name - tmp,
1598 /* Don't show Internet address for users on the
1599 * local Citadel network.
1602 if (TheMessage->cm_fields['N'] != NULL)
1603 if (strlen(TheMessage->cm_fields['N']) > 0)
1604 if (haschar(TheMessage->cm_fields['N'], '.') == 0) {
1608 /* Now spew the header fields in the order we like them. */
1609 safestrncpy(allkeys, FORDER, sizeof allkeys);
1610 for (i=0; i<strlen(allkeys); ++i) {
1611 k = (int) allkeys[i];
1613 if ( (TheMessage->cm_fields[k] != NULL)
1614 && (msgkeys[k] != NULL) ) {
1616 if (do_proto) cprintf("%s=%s\n",
1620 else if ((k == 'F') && (suppress_f)) {
1623 /* Masquerade display name if needed */
1625 if (do_proto) cprintf("%s=%s\n",
1627 TheMessage->cm_fields[k]
1636 /* begin header processing loop for RFC822 transfer format */
1641 strcpy(snode, NODENAME);
1642 strcpy(lnode, HUMANNODE);
1643 if (mode == MT_RFC822) {
1644 for (i = 0; i < 256; ++i) {
1645 if (TheMessage->cm_fields[i]) {
1646 mptr = TheMessage->cm_fields[i];
1649 safestrncpy(luser, mptr, sizeof luser);
1650 safestrncpy(suser, mptr, sizeof suser);
1652 else if (i == 'Y') {
1653 cprintf("CC: %s%s", mptr, nl);
1655 else if (i == 'P') {
1656 cprintf("Return-Path: %s%s", mptr, nl);
1658 else if (i == 'V') {
1659 cprintf("Envelope-To: %s%s", mptr, nl);
1661 else if (i == 'U') {
1662 cprintf("Subject: %s%s", mptr, nl);
1666 safestrncpy(mid, mptr, sizeof mid);
1668 safestrncpy(lnode, mptr, sizeof lnode);
1670 safestrncpy(fuser, mptr, sizeof fuser);
1671 /* else if (i == 'O')
1672 cprintf("X-Citadel-Room: %s%s",
1675 safestrncpy(snode, mptr, sizeof snode);
1677 cprintf("To: %s%s", mptr, nl);
1678 else if (i == 'T') {
1679 datestring(datestamp, sizeof datestamp,
1680 atol(mptr), DATESTRING_RFC822);
1681 cprintf("Date: %s%s", datestamp, nl);
1685 if (subject_found == 0) {
1686 cprintf("Subject: (no subject)%s", nl);
1690 for (i=0; i<strlen(suser); ++i) {
1691 suser[i] = tolower(suser[i]);
1692 if (!isalnum(suser[i])) suser[i]='_';
1695 if (mode == MT_RFC822) {
1696 if (!strcasecmp(snode, NODENAME)) {
1697 safestrncpy(snode, FQDN, sizeof snode);
1700 /* Construct a fun message id */
1701 cprintf("Message-ID: <%s", mid);
1702 if (strchr(mid, '@')==NULL) {
1703 cprintf("@%s", snode);
1707 if (!is_room_aide() && (TheMessage->cm_anon_type == MES_ANONONLY)) {
1708 cprintf("From: \"----\" <x@x.org>%s", nl);
1710 else if (!is_room_aide() && (TheMessage->cm_anon_type == MES_ANONOPT)) {
1711 cprintf("From: \"anonymous\" <x@x.org>%s", nl);
1713 else if (strlen(fuser) > 0) {
1714 cprintf("From: \"%s\" <%s>%s", luser, fuser, nl);
1717 cprintf("From: \"%s\" <%s@%s>%s", luser, suser, snode, nl);
1720 cprintf("Organization: %s%s", lnode, nl);
1722 /* Blank line signifying RFC822 end-of-headers */
1723 if (TheMessage->cm_format_type != FMT_RFC822) {
1728 /* end header processing loop ... at this point, we're in the text */
1730 if (headers_only == HEADERS_FAST) goto DONE;
1731 mptr = TheMessage->cm_fields['M'];
1733 /* Tell the client about the MIME parts in this message */
1734 if (TheMessage->cm_format_type == FMT_RFC822) {
1735 if ( (mode == MT_CITADEL) || (mode == MT_MIME) ) {
1736 memset(&ma, 0, sizeof(struct ma_info));
1737 mime_parser(mptr, NULL,
1738 (do_proto ? *list_this_part : NULL),
1739 (do_proto ? *list_this_pref : NULL),
1740 (do_proto ? *list_this_suff : NULL),
1743 else if (mode == MT_RFC822) { /* unparsed RFC822 dump */
1744 char *start_of_text = NULL;
1745 start_of_text = strstr(mptr, "\n\r\n");
1746 if (start_of_text == NULL) start_of_text = strstr(mptr, "\n\n");
1747 if (start_of_text == NULL) start_of_text = mptr;
1749 start_of_text = strstr(start_of_text, "\n");
1754 int nllen = strlen(nl);
1755 while (ch=*mptr, ch!=0) {
1761 ((headers_only == HEADERS_NONE) && (mptr >= start_of_text))
1762 || ((headers_only == HEADERS_ONLY) && (mptr < start_of_text))
1763 || ((headers_only != HEADERS_NONE) && (headers_only != HEADERS_ONLY))
1766 sprintf(&outbuf[outlen], "%s", nl);
1770 outbuf[outlen++] = ch;
1775 if (outlen > 1000) {
1776 client_write(outbuf, outlen);
1781 client_write(outbuf, outlen);
1789 if (headers_only == HEADERS_ONLY) {
1793 /* signify start of msg text */
1794 if ( (mode == MT_CITADEL) || (mode == MT_MIME) ) {
1795 if (do_proto) cprintf("text\n");
1798 /* If the format type on disk is 1 (fixed-format), then we want
1799 * everything to be output completely literally ... regardless of
1800 * what message transfer format is in use.
1802 if (TheMessage->cm_format_type == FMT_FIXED) {
1803 if (mode == MT_MIME) {
1804 cprintf("Content-type: text/plain\n\n");
1807 while (ch = *mptr++, ch > 0) {
1810 if ((ch == 10) || (strlen(buf) > 250)) {
1811 cprintf("%s%s", buf, nl);
1814 buf[strlen(buf) + 1] = 0;
1815 buf[strlen(buf)] = ch;
1818 if (strlen(buf) > 0)
1819 cprintf("%s%s", buf, nl);
1822 /* If the message on disk is format 0 (Citadel vari-format), we
1823 * output using the formatter at 80 columns. This is the final output
1824 * form if the transfer format is RFC822, but if the transfer format
1825 * is Citadel proprietary, it'll still work, because the indentation
1826 * for new paragraphs is correct and the client will reformat the
1827 * message to the reader's screen width.
1829 if (TheMessage->cm_format_type == FMT_CITADEL) {
1830 if (mode == MT_MIME) {
1831 cprintf("Content-type: text/x-citadel-variformat\n\n");
1833 memfmout(mptr, 0, nl);
1836 /* If the message on disk is format 4 (MIME), we've gotta hand it
1837 * off to the MIME parser. The client has already been told that
1838 * this message is format 1 (fixed format), so the callback function
1839 * we use will display those parts as-is.
1841 if (TheMessage->cm_format_type == FMT_RFC822) {
1842 memset(&ma, 0, sizeof(struct ma_info));
1844 if (mode == MT_MIME) {
1845 ma.use_fo_hooks = 0;
1846 strcpy(ma.chosen_part, "1");
1847 ma.chosen_pref = 9999;
1848 mime_parser(mptr, NULL,
1849 *choose_preferred, *fixed_output_pre,
1850 *fixed_output_post, (void *)&ma, 0);
1851 mime_parser(mptr, NULL,
1852 *output_preferred, NULL, NULL, (void *)&ma, 0);
1855 ma.use_fo_hooks = 1;
1856 mime_parser(mptr, NULL,
1857 *fixed_output, *fixed_output_pre,
1858 *fixed_output_post, (void *)&ma, 0);
1863 DONE: /* now we're done */
1864 if (do_proto) cprintf("000\n");
1871 * display a message (mode 0 - Citadel proprietary)
1873 void cmd_msg0(char *cmdbuf)
1876 int headers_only = HEADERS_ALL;
1878 msgid = extract_long(cmdbuf, 0);
1879 headers_only = extract_int(cmdbuf, 1);
1881 CtdlOutputMsg(msgid, MT_CITADEL, headers_only, 1, 0, NULL);
1887 * display a message (mode 2 - RFC822)
1889 void cmd_msg2(char *cmdbuf)
1892 int headers_only = HEADERS_ALL;
1894 msgid = extract_long(cmdbuf, 0);
1895 headers_only = extract_int(cmdbuf, 1);
1897 CtdlOutputMsg(msgid, MT_RFC822, headers_only, 1, 1, NULL);
1903 * display a message (mode 3 - IGnet raw format - internal programs only)
1905 void cmd_msg3(char *cmdbuf)
1908 struct CtdlMessage *msg = NULL;
1911 if (CC->internal_pgm == 0) {
1912 cprintf("%d This command is for internal programs only.\n",
1913 ERROR + HIGHER_ACCESS_REQUIRED);
1917 msgnum = extract_long(cmdbuf, 0);
1918 msg = CtdlFetchMessage(msgnum, 1);
1920 cprintf("%d Message %ld not found.\n",
1921 ERROR + MESSAGE_NOT_FOUND, msgnum);
1925 serialize_message(&smr, msg);
1926 CtdlFreeMessage(msg);
1929 cprintf("%d Unable to serialize message\n",
1930 ERROR + INTERNAL_ERROR);
1934 cprintf("%d %ld\n", BINARY_FOLLOWS, (long)smr.len);
1935 client_write((char *)smr.ser, (int)smr.len);
1942 * Display a message using MIME content types
1944 void cmd_msg4(char *cmdbuf)
1949 msgid = extract_long(cmdbuf, 0);
1950 extract_token(section, cmdbuf, 1, '|', sizeof section);
1951 CtdlOutputMsg(msgid, MT_MIME, 0, 1, 0, (section[0] ? section : NULL) );
1957 * Client tells us its preferred message format(s)
1959 void cmd_msgp(char *cmdbuf)
1961 safestrncpy(CC->preferred_formats, cmdbuf,
1962 sizeof(CC->preferred_formats));
1963 cprintf("%d ok\n", CIT_OK);
1968 * Open a component of a MIME message as a download file
1970 void cmd_opna(char *cmdbuf)
1973 char desired_section[128];
1975 msgid = extract_long(cmdbuf, 0);
1976 extract_token(desired_section, cmdbuf, 1, '|', sizeof desired_section);
1977 safestrncpy(CC->download_desired_section, desired_section,
1978 sizeof CC->download_desired_section);
1979 CtdlOutputMsg(msgid, MT_DOWNLOAD, 0, 1, 1, NULL);
1984 * Open a component of a MIME message and transmit it all at once
1986 void cmd_dlat(char *cmdbuf)
1989 char desired_section[128];
1991 msgid = extract_long(cmdbuf, 0);
1992 extract_token(desired_section, cmdbuf, 1, '|', sizeof desired_section);
1993 safestrncpy(CC->download_desired_section, desired_section,
1994 sizeof CC->download_desired_section);
1995 CtdlOutputMsg(msgid, MT_SPEW_SECTION, 0, 1, 1, NULL);
2000 * Save one or more message pointers into a specified room
2001 * (Returns 0 for success, nonzero for failure)
2002 * roomname may be NULL to use the current room
2004 * Note that the 'supplied_msg' field may be set to NULL, in which case
2005 * the message will be fetched from disk, by number, if we need to perform
2006 * replication checks. This adds an additional database read, so if the
2007 * caller already has the message in memory then it should be supplied. (Obviously
2008 * this mode of operation only works if we're saving a single message.)
2010 int CtdlSaveMsgPointersInRoom(char *roomname, long newmsgidlist[], int num_newmsgs,
2011 int do_repl_check, struct CtdlMessage *supplied_msg)
2014 char hold_rm[ROOMNAMELEN];
2015 struct cdbdata *cdbfr;
2018 long highest_msg = 0L;
2021 struct CtdlMessage *msg = NULL;
2023 long *msgs_to_be_merged = NULL;
2024 int num_msgs_to_be_merged = 0;
2027 "CtdlSaveMsgPointersInRoom(room=%s, num_msgs=%d, repl=%d)\n",
2028 roomname, num_newmsgs, do_repl_check);
2030 strcpy(hold_rm, CC->room.QRname);
2033 if (newmsgidlist == NULL) return(ERROR + INTERNAL_ERROR);
2034 if (num_newmsgs < 1) return(ERROR + INTERNAL_ERROR);
2035 if (num_newmsgs > 1) supplied_msg = NULL;
2037 /* Now the regular stuff */
2038 if (lgetroom(&CC->room,
2039 ((roomname != NULL) ? roomname : CC->room.QRname) )
2041 lprintf(CTDL_ERR, "No such room <%s>\n", roomname);
2042 return(ERROR + ROOM_NOT_FOUND);
2046 msgs_to_be_merged = malloc(sizeof(long) * num_newmsgs);
2047 num_msgs_to_be_merged = 0;
2050 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long));
2051 if (cdbfr == NULL) {
2055 msglist = (long *) cdbfr->ptr;
2056 cdbfr->ptr = NULL; /* CtdlSaveMsgPointerInRoom() now owns this memory */
2057 num_msgs = cdbfr->len / sizeof(long);
2062 /* Create a list of msgid's which were supplied by the caller, but do
2063 * not already exist in the target room. It is absolutely taboo to
2064 * have more than one reference to the same message in a room.
2066 for (i=0; i<num_newmsgs; ++i) {
2068 if (num_msgs > 0) for (j=0; j<num_msgs; ++j) {
2069 if (msglist[j] == newmsgidlist[i]) {
2074 msgs_to_be_merged[num_msgs_to_be_merged++] = newmsgidlist[i];
2078 lprintf(9, "%d unique messages to be merged\n", num_msgs_to_be_merged);
2081 * Now merge the new messages
2083 msglist = realloc(msglist, (sizeof(long) * (num_msgs + num_msgs_to_be_merged)) );
2084 if (msglist == NULL) {
2085 lprintf(CTDL_ALERT, "ERROR: can't realloc message list!\n");
2087 memcpy(&msglist[num_msgs], msgs_to_be_merged, (sizeof(long) * num_msgs_to_be_merged) );
2088 num_msgs += num_msgs_to_be_merged;
2090 /* Sort the message list, so all the msgid's are in order */
2091 num_msgs = sort_msglist(msglist, num_msgs);
2093 /* Determine the highest message number */
2094 highest_msg = msglist[num_msgs - 1];
2096 /* Write it back to disk. */
2097 cdb_store(CDB_MSGLISTS, &CC->room.QRnumber, (int)sizeof(long),
2098 msglist, (int)(num_msgs * sizeof(long)));
2100 /* Free up the memory we used. */
2103 /* Update the highest-message pointer and unlock the room. */
2104 CC->room.QRhighest = highest_msg;
2105 lputroom(&CC->room);
2107 /* Perform replication checks if necessary */
2108 if ( (DoesThisRoomNeedEuidIndexing(&CC->room)) && (do_repl_check) ) {
2109 lprintf(CTDL_DEBUG, "CtdlSaveMsgPointerInRoom() doing repl checks\n");
2111 for (i=0; i<num_msgs_to_be_merged; ++i) {
2112 msgid = msgs_to_be_merged[i];
2114 if (supplied_msg != NULL) {
2118 msg = CtdlFetchMessage(msgid, 0);
2122 ReplicationChecks(msg);
2124 /* If the message has an Exclusive ID, index that... */
2125 if (msg->cm_fields['E'] != NULL) {
2126 index_message_by_euid(msg->cm_fields['E'], &CC->room, msgid);
2129 /* Free up the memory we may have allocated */
2130 if (msg != supplied_msg) {
2131 CtdlFreeMessage(msg);
2139 lprintf(CTDL_DEBUG, "CtdlSaveMsgPointerInRoom() skips repl checks\n");
2142 /* Submit this room for processing by hooks */
2143 PerformRoomHooks(&CC->room);
2145 /* Go back to the room we were in before we wandered here... */
2146 getroom(&CC->room, hold_rm);
2148 /* Bump the reference count for all messages which were merged */
2149 for (i=0; i<num_msgs_to_be_merged; ++i) {
2150 AdjRefCount(msgs_to_be_merged[i], +1);
2153 /* Free up memory... */
2154 if (msgs_to_be_merged != NULL) {
2155 free(msgs_to_be_merged);
2158 /* Return success. */
2164 * This is the same as CtdlSaveMsgPointersInRoom() but it only accepts
2167 int CtdlSaveMsgPointerInRoom(char *roomname, long msgid,
2168 int do_repl_check, struct CtdlMessage *supplied_msg)
2170 return CtdlSaveMsgPointersInRoom(roomname, &msgid, 1, do_repl_check, supplied_msg);
2177 * Message base operation to save a new message to the message store
2178 * (returns new message number)
2180 * This is the back end for CtdlSubmitMsg() and should not be directly
2181 * called by server-side modules.
2184 long send_message(struct CtdlMessage *msg) {
2192 /* Get a new message number */
2193 newmsgid = get_new_message_number();
2194 snprintf(msgidbuf, sizeof msgidbuf, "%010ld@%s", newmsgid, config.c_fqdn);
2196 /* Generate an ID if we don't have one already */
2197 if (msg->cm_fields['I']==NULL) {
2198 msg->cm_fields['I'] = strdup(msgidbuf);
2201 /* If the message is big, set its body aside for storage elsewhere */
2202 if (msg->cm_fields['M'] != NULL) {
2203 if (strlen(msg->cm_fields['M']) > BIGMSG) {
2205 holdM = msg->cm_fields['M'];
2206 msg->cm_fields['M'] = NULL;
2210 /* Serialize our data structure for storage in the database */
2211 serialize_message(&smr, msg);
2214 msg->cm_fields['M'] = holdM;
2218 cprintf("%d Unable to serialize message\n",
2219 ERROR + INTERNAL_ERROR);
2223 /* Write our little bundle of joy into the message base */
2224 if (cdb_store(CDB_MSGMAIN, &newmsgid, (int)sizeof(long),
2225 smr.ser, smr.len) < 0) {
2226 lprintf(CTDL_ERR, "Can't store message\n");
2230 cdb_store(CDB_BIGMSGS,
2240 /* Free the memory we used for the serialized message */
2243 /* Return the *local* message ID to the caller
2244 * (even if we're storing an incoming network message)
2252 * Serialize a struct CtdlMessage into the format used on disk and network.
2254 * This function loads up a "struct ser_ret" (defined in server.h) which
2255 * contains the length of the serialized message and a pointer to the
2256 * serialized message in memory. THE LATTER MUST BE FREED BY THE CALLER.
2258 void serialize_message(struct ser_ret *ret, /* return values */
2259 struct CtdlMessage *msg) /* unserialized msg */
2261 size_t wlen, fieldlen;
2263 static char *forder = FORDER;
2266 * Check for valid message format
2268 if (is_valid_message(msg) == 0) {
2269 lprintf(CTDL_ERR, "serialize_message() aborting due to invalid message\n");
2276 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL)
2277 ret->len = ret->len +
2278 strlen(msg->cm_fields[(int)forder[i]]) + 2;
2280 ret->ser = malloc(ret->len);
2281 if (ret->ser == NULL) {
2282 lprintf(CTDL_ERR, "serialize_message() malloc(%ld) failed: %s\n",
2283 (long)ret->len, strerror(errno));
2290 ret->ser[1] = msg->cm_anon_type;
2291 ret->ser[2] = msg->cm_format_type;
2294 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL) {
2295 fieldlen = strlen(msg->cm_fields[(int)forder[i]]);
2296 ret->ser[wlen++] = (char)forder[i];
2297 safestrncpy((char *)&ret->ser[wlen], msg->cm_fields[(int)forder[i]], fieldlen+1);
2298 wlen = wlen + fieldlen + 1;
2300 if (ret->len != wlen) lprintf(CTDL_ERR, "ERROR: len=%ld wlen=%ld\n",
2301 (long)ret->len, (long)wlen);
2309 * Check to see if any messages already exist in the current room which
2310 * carry the same Exclusive ID as this one. If any are found, delete them.
2312 void ReplicationChecks(struct CtdlMessage *msg) {
2313 long old_msgnum = (-1L);
2315 if (DoesThisRoomNeedEuidIndexing(&CC->room) == 0) return;
2317 lprintf(CTDL_DEBUG, "Performing replication checks in <%s>\n",
2320 /* No exclusive id? Don't do anything. */
2321 if (msg == NULL) return;
2322 if (msg->cm_fields['E'] == NULL) return;
2323 if (strlen(msg->cm_fields['E']) == 0) return;
2324 /*lprintf(CTDL_DEBUG, "Exclusive ID: <%s> for room <%s>\n",
2325 msg->cm_fields['E'], CC->room.QRname);*/
2327 old_msgnum = locate_message_by_euid(msg->cm_fields['E'], &CC->room);
2328 if (old_msgnum > 0L) {
2329 lprintf(CTDL_DEBUG, "ReplicationChecks() replacing message %ld\n", old_msgnum);
2330 CtdlDeleteMessages(CC->room.QRname, &old_msgnum, 1, "");
2337 * Save a message to disk and submit it into the delivery system.
2339 long CtdlSubmitMsg(struct CtdlMessage *msg, /* message to save */
2340 struct recptypes *recps, /* recipients (if mail) */
2341 char *force /* force a particular room? */
2343 char submit_filename[128];
2344 char generated_timestamp[32];
2345 char hold_rm[ROOMNAMELEN];
2346 char actual_rm[ROOMNAMELEN];
2347 char force_room[ROOMNAMELEN];
2348 char content_type[SIZ]; /* We have to learn this */
2349 char recipient[SIZ];
2352 struct ctdluser userbuf;
2354 struct MetaData smi;
2355 FILE *network_fp = NULL;
2356 static int seqnum = 1;
2357 struct CtdlMessage *imsg = NULL;
2359 size_t instr_alloc = 0;
2361 char *hold_R, *hold_D;
2362 char *collected_addresses = NULL;
2363 struct addresses_to_be_filed *aptr = NULL;
2364 char *saved_rfc822_version = NULL;
2365 int qualified_for_journaling = 0;
2367 lprintf(CTDL_DEBUG, "CtdlSubmitMsg() called\n");
2368 if (is_valid_message(msg) == 0) return(-1); /* self check */
2370 /* If this message has no timestamp, we take the liberty of
2371 * giving it one, right now.
2373 if (msg->cm_fields['T'] == NULL) {
2374 snprintf(generated_timestamp, sizeof generated_timestamp, "%ld", (long)time(NULL));
2375 msg->cm_fields['T'] = strdup(generated_timestamp);
2378 /* If this message has no path, we generate one.
2380 if (msg->cm_fields['P'] == NULL) {
2381 if (msg->cm_fields['A'] != NULL) {
2382 msg->cm_fields['P'] = strdup(msg->cm_fields['A']);
2383 for (a=0; a<strlen(msg->cm_fields['P']); ++a) {
2384 if (isspace(msg->cm_fields['P'][a])) {
2385 msg->cm_fields['P'][a] = ' ';
2390 msg->cm_fields['P'] = strdup("unknown");
2394 if (force == NULL) {
2395 strcpy(force_room, "");
2398 strcpy(force_room, force);
2401 /* Learn about what's inside, because it's what's inside that counts */
2402 if (msg->cm_fields['M'] == NULL) {
2403 lprintf(CTDL_ERR, "ERROR: attempt to save message with NULL body\n");
2407 switch (msg->cm_format_type) {
2409 strcpy(content_type, "text/x-citadel-variformat");
2412 strcpy(content_type, "text/plain");
2415 strcpy(content_type, "text/plain");
2416 mptr = bmstrcasestr(msg->cm_fields['M'], "Content-type:");
2418 safestrncpy(content_type, &mptr[13], sizeof content_type);
2419 striplt(content_type);
2420 for (a = 0; a < strlen(content_type); ++a) {
2421 if ((content_type[a] == ';')
2422 || (content_type[a] == ' ')
2423 || (content_type[a] == 13)
2424 || (content_type[a] == 10)) {
2425 content_type[a] = 0;
2431 /* Goto the correct room */
2432 lprintf(CTDL_DEBUG, "Selected room %s\n", (recps) ? CC->room.QRname : SENTITEMS);
2433 strcpy(hold_rm, CC->room.QRname);
2434 strcpy(actual_rm, CC->room.QRname);
2435 if (recps != NULL) {
2436 strcpy(actual_rm, SENTITEMS);
2439 /* If the user is a twit, move to the twit room for posting */
2441 if (CC->user.axlevel == 2) {
2442 strcpy(hold_rm, actual_rm);
2443 strcpy(actual_rm, config.c_twitroom);
2444 lprintf(CTDL_DEBUG, "Diverting to twit room\n");
2448 /* ...or if this message is destined for Aide> then go there. */
2449 if (strlen(force_room) > 0) {
2450 strcpy(actual_rm, force_room);
2453 lprintf(CTDL_DEBUG, "Final selection: %s\n", actual_rm);
2454 if (strcasecmp(actual_rm, CC->room.QRname)) {
2455 /* getroom(&CC->room, actual_rm); */
2456 usergoto(actual_rm, 0, 1, NULL, NULL);
2460 * If this message has no O (room) field, generate one.
2462 if (msg->cm_fields['O'] == NULL) {
2463 msg->cm_fields['O'] = strdup(CC->room.QRname);
2466 /* Perform "before save" hooks (aborting if any return nonzero) */
2467 lprintf(CTDL_DEBUG, "Performing before-save hooks\n");
2468 if (PerformMessageHooks(msg, EVT_BEFORESAVE) > 0) return(-3);
2471 * If this message has an Exclusive ID, and the room is replication
2472 * checking enabled, then do replication checks.
2474 if (DoesThisRoomNeedEuidIndexing(&CC->room)) {
2475 ReplicationChecks(msg);
2478 /* Save it to disk */
2479 lprintf(CTDL_DEBUG, "Saving to disk\n");
2480 newmsgid = send_message(msg);
2481 if (newmsgid <= 0L) return(-5);
2483 /* Write a supplemental message info record. This doesn't have to
2484 * be a critical section because nobody else knows about this message
2487 lprintf(CTDL_DEBUG, "Creating MetaData record\n");
2488 memset(&smi, 0, sizeof(struct MetaData));
2489 smi.meta_msgnum = newmsgid;
2490 smi.meta_refcount = 0;
2491 safestrncpy(smi.meta_content_type, content_type,
2492 sizeof smi.meta_content_type);
2495 * Measure how big this message will be when rendered as RFC822.
2496 * We do this for two reasons:
2497 * 1. We need the RFC822 length for the new metadata record, so the
2498 * POP and IMAP services don't have to calculate message lengths
2499 * while the user is waiting (multiplied by potentially hundreds
2500 * or thousands of messages).
2501 * 2. If journaling is enabled, we will need an RFC822 version of the
2502 * message to attach to the journalized copy.
2504 if (CC->redirect_buffer != NULL) {
2505 lprintf(CTDL_ALERT, "CC->redirect_buffer is not NULL during message submission!\n");
2508 CC->redirect_buffer = malloc(SIZ);
2509 CC->redirect_len = 0;
2510 CC->redirect_alloc = SIZ;
2511 CtdlOutputPreLoadedMsg(msg, MT_RFC822, HEADERS_ALL, 0, 1);
2512 smi.meta_rfc822_length = CC->redirect_len;
2513 saved_rfc822_version = CC->redirect_buffer;
2514 CC->redirect_buffer = NULL;
2515 CC->redirect_len = 0;
2516 CC->redirect_alloc = 0;
2520 /* Now figure out where to store the pointers */
2521 lprintf(CTDL_DEBUG, "Storing pointers\n");
2523 /* If this is being done by the networker delivering a private
2524 * message, we want to BYPASS saving the sender's copy (because there
2525 * is no local sender; it would otherwise go to the Trashcan).
2527 if ((!CC->internal_pgm) || (recps == NULL)) {
2528 if (CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 1, msg) != 0) {
2529 lprintf(CTDL_ERR, "ERROR saving message pointer!\n");
2530 CtdlSaveMsgPointerInRoom(config.c_aideroom, newmsgid, 0, msg);
2534 /* For internet mail, drop a copy in the outbound queue room */
2536 if (recps->num_internet > 0) {
2537 CtdlSaveMsgPointerInRoom(SMTP_SPOOLOUT_ROOM, newmsgid, 0, msg);
2540 /* If other rooms are specified, drop them there too. */
2542 if (recps->num_room > 0)
2543 for (i=0; i<num_tokens(recps->recp_room, '|'); ++i) {
2544 extract_token(recipient, recps->recp_room, i,
2545 '|', sizeof recipient);
2546 lprintf(CTDL_DEBUG, "Delivering to room <%s>\n", recipient);
2547 CtdlSaveMsgPointerInRoom(recipient, newmsgid, 0, msg);
2550 /* Bump this user's messages posted counter. */
2551 lprintf(CTDL_DEBUG, "Updating user\n");
2552 lgetuser(&CC->user, CC->curr_user);
2553 CC->user.posted = CC->user.posted + 1;
2554 lputuser(&CC->user);
2556 /* If this is private, local mail, make a copy in the
2557 * recipient's mailbox and bump the reference count.
2560 if (recps->num_local > 0)
2561 for (i=0; i<num_tokens(recps->recp_local, '|'); ++i) {
2562 extract_token(recipient, recps->recp_local, i,
2563 '|', sizeof recipient);
2564 lprintf(CTDL_DEBUG, "Delivering private local mail to <%s>\n",
2566 if (getuser(&userbuf, recipient) == 0) {
2567 // Add a flag so the Funambol module knows its mail
2568 msg->cm_fields['W'] = strdup(recipient);
2569 MailboxName(actual_rm, sizeof actual_rm, &userbuf, MAILROOM);
2570 CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 0, msg);
2571 BumpNewMailCounter(userbuf.usernum);
2572 if (strlen(config.c_funambol_host) > 0) {
2573 /* Generate a instruction message for the Funambol notification
2574 * server, in the same style as the SMTP queue
2577 instr = malloc(instr_alloc);
2578 snprintf(instr, instr_alloc,
2579 "Content-type: %s\n\nmsgid|%ld\nsubmitted|%ld\n"
2581 SPOOLMIME, newmsgid, (long)time(NULL),
2582 msg->cm_fields['A'], msg->cm_fields['N']
2585 imsg = malloc(sizeof(struct CtdlMessage));
2586 memset(imsg, 0, sizeof(struct CtdlMessage));
2587 imsg->cm_magic = CTDLMESSAGE_MAGIC;
2588 imsg->cm_anon_type = MES_NORMAL;
2589 imsg->cm_format_type = FMT_RFC822;
2590 imsg->cm_fields['A'] = strdup("Citadel");
2591 imsg->cm_fields['J'] = strdup("do not journal");
2592 imsg->cm_fields['M'] = instr; /* imsg owns this memory now */
2593 imsg->cm_fields['W'] = strdup(recipient);
2594 CtdlSubmitMsg(imsg, NULL, FNBL_QUEUE_ROOM);
2595 CtdlFreeMessage(imsg);
2599 lprintf(CTDL_DEBUG, "No user <%s>\n", recipient);
2600 CtdlSaveMsgPointerInRoom(config.c_aideroom,
2605 /* Perform "after save" hooks */
2606 lprintf(CTDL_DEBUG, "Performing after-save hooks\n");
2607 PerformMessageHooks(msg, EVT_AFTERSAVE);
2609 /* For IGnet mail, we have to save a new copy into the spooler for
2610 * each recipient, with the R and D fields set to the recipient and
2611 * destination-node. This has two ugly side effects: all other
2612 * recipients end up being unlisted in this recipient's copy of the
2613 * message, and it has to deliver multiple messages to the same
2614 * node. We'll revisit this again in a year or so when everyone has
2615 * a network spool receiver that can handle the new style messages.
2618 if (recps->num_ignet > 0)
2619 for (i=0; i<num_tokens(recps->recp_ignet, '|'); ++i) {
2620 extract_token(recipient, recps->recp_ignet, i,
2621 '|', sizeof recipient);
2623 hold_R = msg->cm_fields['R'];
2624 hold_D = msg->cm_fields['D'];
2625 msg->cm_fields['R'] = malloc(SIZ);
2626 msg->cm_fields['D'] = malloc(128);
2627 extract_token(msg->cm_fields['R'], recipient, 0, '@', SIZ);
2628 extract_token(msg->cm_fields['D'], recipient, 1, '@', 128);
2630 serialize_message(&smr, msg);
2632 snprintf(submit_filename, sizeof submit_filename,
2633 "%s/netmail.%04lx.%04x.%04x",
2635 (long) getpid(), CC->cs_pid, ++seqnum);
2636 network_fp = fopen(submit_filename, "wb+");
2637 if (network_fp != NULL) {
2638 fwrite(smr.ser, smr.len, 1, network_fp);
2644 free(msg->cm_fields['R']);
2645 free(msg->cm_fields['D']);
2646 msg->cm_fields['R'] = hold_R;
2647 msg->cm_fields['D'] = hold_D;
2650 /* Go back to the room we started from */
2651 lprintf(CTDL_DEBUG, "Returning to original room %s\n", hold_rm);
2652 if (strcasecmp(hold_rm, CC->room.QRname))
2653 usergoto(hold_rm, 0, 1, NULL, NULL);
2655 /* For internet mail, generate delivery instructions.
2656 * Yes, this is recursive. Deal with it. Infinite recursion does
2657 * not happen because the delivery instructions message does not
2658 * contain a recipient.
2661 if (recps->num_internet > 0) {
2662 lprintf(CTDL_DEBUG, "Generating delivery instructions\n");
2664 instr = malloc(instr_alloc);
2665 snprintf(instr, instr_alloc,
2666 "Content-type: %s\n\nmsgid|%ld\nsubmitted|%ld\n"
2668 SPOOLMIME, newmsgid, (long)time(NULL),
2669 msg->cm_fields['A'], msg->cm_fields['N']
2672 for (i=0; i<num_tokens(recps->recp_internet, '|'); ++i) {
2673 size_t tmp = strlen(instr);
2674 extract_token(recipient, recps->recp_internet, i, '|', sizeof recipient);
2675 if ((tmp + strlen(recipient) + 32) > instr_alloc) {
2676 instr_alloc = instr_alloc * 2;
2677 instr = realloc(instr, instr_alloc);
2679 snprintf(&instr[tmp], instr_alloc - tmp, "remote|%s|0||\n", recipient);
2682 imsg = malloc(sizeof(struct CtdlMessage));
2683 memset(imsg, 0, sizeof(struct CtdlMessage));
2684 imsg->cm_magic = CTDLMESSAGE_MAGIC;
2685 imsg->cm_anon_type = MES_NORMAL;
2686 imsg->cm_format_type = FMT_RFC822;
2687 imsg->cm_fields['A'] = strdup("Citadel");
2688 imsg->cm_fields['J'] = strdup("do not journal");
2689 imsg->cm_fields['M'] = instr; /* imsg owns this memory now */
2690 CtdlSubmitMsg(imsg, NULL, SMTP_SPOOLOUT_ROOM);
2691 CtdlFreeMessage(imsg);
2695 * Any addresses to harvest for someone's address book?
2697 if ( (CC->logged_in) && (recps != NULL) ) {
2698 collected_addresses = harvest_collected_addresses(msg);
2701 if (collected_addresses != NULL) {
2702 begin_critical_section(S_ATBF);
2703 aptr = (struct addresses_to_be_filed *)
2704 malloc(sizeof(struct addresses_to_be_filed));
2706 MailboxName(actual_rm, sizeof actual_rm,
2707 &CC->user, USERCONTACTSROOM);
2708 aptr->roomname = strdup(actual_rm);
2709 aptr->collected_addresses = collected_addresses;
2711 end_critical_section(S_ATBF);
2715 * Determine whether this message qualifies for journaling.
2717 if (msg->cm_fields['J'] != NULL) {
2718 qualified_for_journaling = 0;
2721 if (recps == NULL) {
2722 qualified_for_journaling = config.c_journal_pubmsgs;
2724 else if (recps->num_local + recps->num_ignet + recps->num_internet > 0) {
2725 qualified_for_journaling = config.c_journal_email;
2728 qualified_for_journaling = config.c_journal_pubmsgs;
2733 * Do we have to perform journaling? If so, hand off the saved
2734 * RFC822 version will be handed off to the journaler for background
2735 * submit. Otherwise, we have to free the memory ourselves.
2737 if (saved_rfc822_version != NULL) {
2738 if (qualified_for_journaling) {
2739 JournalBackgroundSubmit(msg, saved_rfc822_version, recps);
2742 free(saved_rfc822_version);
2755 * Convenience function for generating small administrative messages.
2757 void quickie_message(char *from, char *fromaddr, char *to, char *room, char *text,
2758 int format_type, char *subject)
2760 struct CtdlMessage *msg;
2761 struct recptypes *recp = NULL;
2763 msg = malloc(sizeof(struct CtdlMessage));
2764 memset(msg, 0, sizeof(struct CtdlMessage));
2765 msg->cm_magic = CTDLMESSAGE_MAGIC;
2766 msg->cm_anon_type = MES_NORMAL;
2767 msg->cm_format_type = format_type;
2770 msg->cm_fields['A'] = strdup(from);
2772 else if (fromaddr != NULL) {
2773 msg->cm_fields['A'] = strdup(fromaddr);
2774 if (strchr(msg->cm_fields['A'], '@')) {
2775 *strchr(msg->cm_fields['A'], '@') = 0;
2779 msg->cm_fields['A'] = strdup("Citadel");
2782 if (fromaddr != NULL) msg->cm_fields['F'] = strdup(fromaddr);
2783 if (room != NULL) msg->cm_fields['O'] = strdup(room);
2784 msg->cm_fields['N'] = strdup(NODENAME);
2786 msg->cm_fields['R'] = strdup(to);
2787 recp = validate_recipients(to);
2789 if (subject != NULL) {
2790 msg->cm_fields['U'] = strdup(subject);
2792 msg->cm_fields['M'] = strdup(text);
2794 CtdlSubmitMsg(msg, recp, room);
2795 CtdlFreeMessage(msg);
2796 if (recp != NULL) free_recipients(recp);
2802 * Back end function used by CtdlMakeMessage() and similar functions
2804 char *CtdlReadMessageBody(char *terminator, /* token signalling EOT */
2805 size_t maxlen, /* maximum message length */
2806 char *exist, /* if non-null, append to it;
2807 exist is ALWAYS freed */
2808 int crlf /* CRLF newlines instead of LF */
2812 size_t message_len = 0;
2813 size_t buffer_len = 0;
2820 if (exist == NULL) {
2827 message_len = strlen(exist);
2828 buffer_len = message_len + 4096;
2829 m = realloc(exist, buffer_len);
2836 /* Do we need to change leading ".." to "." for SMTP escaping? */
2837 if (!strcmp(terminator, ".")) {
2841 /* flush the input if we have nowhere to store it */
2846 /* read in the lines of message text one by one */
2848 if (client_getln(buf, (sizeof buf - 3)) < 1) finished = 1;
2849 if (!strcmp(buf, terminator)) finished = 1;
2851 strcat(buf, "\r\n");
2857 /* Unescape SMTP-style input of two dots at the beginning of the line */
2859 if (!strncmp(buf, "..", 2)) {
2860 strcpy(buf, &buf[1]);
2864 if ( (!flushing) && (!finished) ) {
2865 /* Measure the line */
2866 linelen = strlen(buf);
2868 /* augment the buffer if we have to */
2869 if ((message_len + linelen) >= buffer_len) {
2870 ptr = realloc(m, (buffer_len * 2) );
2871 if (ptr == NULL) { /* flush if can't allocate */
2874 buffer_len = (buffer_len * 2);
2876 lprintf(CTDL_DEBUG, "buffer_len is now %ld\n", (long)buffer_len);
2880 /* Add the new line to the buffer. NOTE: this loop must avoid
2881 * using functions like strcat() and strlen() because they
2882 * traverse the entire buffer upon every call, and doing that
2883 * for a multi-megabyte message slows it down beyond usability.
2885 strcpy(&m[message_len], buf);
2886 message_len += linelen;
2889 /* if we've hit the max msg length, flush the rest */
2890 if (message_len >= maxlen) flushing = 1;
2892 } while (!finished);
2900 * Build a binary message to be saved on disk.
2901 * (NOTE: if you supply 'preformatted_text', the buffer you give it
2902 * will become part of the message. This means you are no longer
2903 * responsible for managing that memory -- it will be freed along with
2904 * the rest of the fields when CtdlFreeMessage() is called.)
2907 struct CtdlMessage *CtdlMakeMessage(
2908 struct ctdluser *author, /* author's user structure */
2909 char *recipient, /* NULL if it's not mail */
2910 char *recp_cc, /* NULL if it's not mail */
2911 char *room, /* room where it's going */
2912 int type, /* see MES_ types in header file */
2913 int format_type, /* variformat, plain text, MIME... */
2914 char *fake_name, /* who we're masquerading as */
2915 char *my_email, /* which of my email addresses to use (empty is ok) */
2916 char *subject, /* Subject (optional) */
2917 char *supplied_euid, /* ...or NULL if this is irrelevant */
2918 char *preformatted_text /* ...or NULL to read text from client */
2920 char dest_node[256];
2922 struct CtdlMessage *msg;
2925 msg = malloc(sizeof(struct CtdlMessage));
2926 memset(msg, 0, sizeof(struct CtdlMessage));
2927 msg->cm_magic = CTDLMESSAGE_MAGIC;
2928 msg->cm_anon_type = type;
2929 msg->cm_format_type = format_type;
2931 /* Don't confuse the poor folks if it's not routed mail. */
2932 strcpy(dest_node, "");
2937 /* Path or Return-Path */
2938 if (my_email == NULL) my_email = "";
2940 if (strlen(my_email) > 0) {
2941 msg->cm_fields['P'] = strdup(my_email);
2944 snprintf(buf, sizeof buf, "%s", author->fullname);
2945 msg->cm_fields['P'] = strdup(buf);
2947 for (i=0; (msg->cm_fields['P'][i]!=0); ++i) {
2948 if (isspace(msg->cm_fields['P'][i])) {
2949 msg->cm_fields['P'][i] = '_';
2953 snprintf(buf, sizeof buf, "%ld", (long)time(NULL)); /* timestamp */
2954 msg->cm_fields['T'] = strdup(buf);
2956 if (fake_name[0]) /* author */
2957 msg->cm_fields['A'] = strdup(fake_name);
2959 msg->cm_fields['A'] = strdup(author->fullname);
2961 if (CC->room.QRflags & QR_MAILBOX) { /* room */
2962 msg->cm_fields['O'] = strdup(&CC->room.QRname[11]);
2965 msg->cm_fields['O'] = strdup(CC->room.QRname);
2968 msg->cm_fields['N'] = strdup(NODENAME); /* nodename */
2969 msg->cm_fields['H'] = strdup(HUMANNODE); /* hnodename */
2971 if (recipient[0] != 0) {
2972 msg->cm_fields['R'] = strdup(recipient);
2974 if (recp_cc[0] != 0) {
2975 msg->cm_fields['Y'] = strdup(recp_cc);
2977 if (dest_node[0] != 0) {
2978 msg->cm_fields['D'] = strdup(dest_node);
2981 if (strlen(my_email) > 0) {
2982 msg->cm_fields['F'] = strdup(my_email);
2984 else if ( (author == &CC->user) && (strlen(CC->cs_inet_email) > 0) ) {
2985 msg->cm_fields['F'] = strdup(CC->cs_inet_email);
2988 if (subject != NULL) {
2991 length = strlen(subject);
2997 while ((subject[i] != '\0') &&
2998 (IsAscii = isascii(subject[i]) != 0 ))
3001 msg->cm_fields['U'] = strdup(subject);
3002 else /* ok, we've got utf8 in the string. */
3004 msg->cm_fields['U'] = rfc2047encode(subject, length);
3010 if (supplied_euid != NULL) {
3011 msg->cm_fields['E'] = strdup(supplied_euid);
3014 if (preformatted_text != NULL) {
3015 msg->cm_fields['M'] = preformatted_text;
3018 msg->cm_fields['M'] = CtdlReadMessageBody("000", config.c_maxmsglen, NULL, 0);
3026 * Check to see whether we have permission to post a message in the current
3027 * room. Returns a *CITADEL ERROR CODE* and puts a message in errmsgbuf, or
3028 * returns 0 on success.
3030 int CtdlDoIHavePermissionToPostInThisRoom(char *errmsgbuf, size_t n) {
3033 if (!(CC->logged_in)) {
3034 snprintf(errmsgbuf, n, "Not logged in.");
3035 return (ERROR + NOT_LOGGED_IN);
3038 if ((CC->user.axlevel < 2)
3039 && ((CC->room.QRflags & QR_MAILBOX) == 0)) {
3040 snprintf(errmsgbuf, n, "Need to be validated to enter "
3041 "(except in %s> to sysop)", MAILROOM);
3042 return (ERROR + HIGHER_ACCESS_REQUIRED);
3045 CtdlRoomAccess(&CC->room, &CC->user, &ra, NULL);
3046 if (!(ra & UA_POSTALLOWED)) {
3047 snprintf(errmsgbuf, n, "Higher access is required to post in this room.");
3048 return (ERROR + HIGHER_ACCESS_REQUIRED);
3051 strcpy(errmsgbuf, "Ok");
3057 * Check to see if the specified user has Internet mail permission
3058 * (returns nonzero if permission is granted)
3060 int CtdlCheckInternetMailPermission(struct ctdluser *who) {
3062 /* Do not allow twits to send Internet mail */
3063 if (who->axlevel <= 2) return(0);
3065 /* Globally enabled? */
3066 if (config.c_restrict == 0) return(1);
3068 /* User flagged ok? */
3069 if (who->flags & US_INTERNET) return(2);
3071 /* Aide level access? */
3072 if (who->axlevel >= 6) return(3);
3074 /* No mail for you! */
3080 * Validate recipients, count delivery types and errors, and handle aliasing
3081 * FIXME check for dupes!!!!!
3083 * Returns 0 if all addresses are ok, ret->num_error = -1 if no addresses
3084 * were specified, or the number of addresses found invalid.
3086 * Caller needs to free the result using free_recipients()
3088 struct recptypes *validate_recipients(char *supplied_recipients) {
3089 struct recptypes *ret;
3090 char *recipients = NULL;
3091 char this_recp[256];
3092 char this_recp_cooked[256];
3098 struct ctdluser tempUS;
3099 struct ctdlroom tempQR;
3103 ret = (struct recptypes *) malloc(sizeof(struct recptypes));
3104 if (ret == NULL) return(NULL);
3106 /* Set all strings to null and numeric values to zero */
3107 memset(ret, 0, sizeof(struct recptypes));
3109 if (supplied_recipients == NULL) {
3110 recipients = strdup("");
3113 recipients = strdup(supplied_recipients);
3116 /* Allocate some memory. Yes, this allocates 500% more memory than we will
3117 * actually need, but it's healthier for the heap than doing lots of tiny
3118 * realloc() calls instead.
3121 ret->errormsg = malloc(strlen(recipients) + 1024);
3122 ret->recp_local = malloc(strlen(recipients) + 1024);
3123 ret->recp_internet = malloc(strlen(recipients) + 1024);
3124 ret->recp_ignet = malloc(strlen(recipients) + 1024);
3125 ret->recp_room = malloc(strlen(recipients) + 1024);
3126 ret->display_recp = malloc(strlen(recipients) + 1024);
3128 ret->errormsg[0] = 0;
3129 ret->recp_local[0] = 0;
3130 ret->recp_internet[0] = 0;
3131 ret->recp_ignet[0] = 0;
3132 ret->recp_room[0] = 0;
3133 ret->display_recp[0] = 0;
3135 ret->recptypes_magic = RECPTYPES_MAGIC;
3137 /* Change all valid separator characters to commas */
3138 for (i=0; i<strlen(recipients); ++i) {
3139 if ((recipients[i] == ';') || (recipients[i] == '|')) {
3140 recipients[i] = ',';
3144 /* Now start extracting recipients... */
3146 while (strlen(recipients) > 0) {
3148 for (i=0; i<=strlen(recipients); ++i) {
3149 if (recipients[i] == '\"') in_quotes = 1 - in_quotes;
3150 if ( ( (recipients[i] == ',') && (!in_quotes) ) || (recipients[i] == 0) ) {
3151 safestrncpy(this_recp, recipients, i+1);
3153 if (recipients[i] == ',') {
3154 strcpy(recipients, &recipients[i+1]);
3157 strcpy(recipients, "");
3164 lprintf(CTDL_DEBUG, "Evaluating recipient #%d: %s\n", num_recps, this_recp);
3166 mailtype = alias(this_recp);
3167 mailtype = alias(this_recp);
3168 mailtype = alias(this_recp);
3169 for (j=0; j<=strlen(this_recp); ++j) {
3170 if (this_recp[j]=='_') {
3171 this_recp_cooked[j] = ' ';
3174 this_recp_cooked[j] = this_recp[j];
3180 if (!strcasecmp(this_recp, "sysop")) {
3182 strcpy(this_recp, config.c_aideroom);
3183 if (strlen(ret->recp_room) > 0) {
3184 strcat(ret->recp_room, "|");
3186 strcat(ret->recp_room, this_recp);
3188 else if (getuser(&tempUS, this_recp) == 0) {
3190 strcpy(this_recp, tempUS.fullname);
3191 if (strlen(ret->recp_local) > 0) {
3192 strcat(ret->recp_local, "|");
3194 strcat(ret->recp_local, this_recp);
3196 else if (getuser(&tempUS, this_recp_cooked) == 0) {
3198 strcpy(this_recp, tempUS.fullname);
3199 if (strlen(ret->recp_local) > 0) {
3200 strcat(ret->recp_local, "|");
3202 strcat(ret->recp_local, this_recp);
3204 else if ( (!strncasecmp(this_recp, "room_", 5))
3205 && (!getroom(&tempQR, &this_recp_cooked[5])) ) {
3207 if (strlen(ret->recp_room) > 0) {
3208 strcat(ret->recp_room, "|");
3210 strcat(ret->recp_room, &this_recp_cooked[5]);
3218 /* Yes, you're reading this correctly: if the target
3219 * domain points back to the local system or an attached
3220 * Citadel directory, the address is invalid. That's
3221 * because if the address were valid, we would have
3222 * already translated it to a local address by now.
3224 if (IsDirectory(this_recp, 0)) {
3229 ++ret->num_internet;
3230 if (strlen(ret->recp_internet) > 0) {
3231 strcat(ret->recp_internet, "|");
3233 strcat(ret->recp_internet, this_recp);
3238 if (strlen(ret->recp_ignet) > 0) {
3239 strcat(ret->recp_ignet, "|");
3241 strcat(ret->recp_ignet, this_recp);
3249 if (strlen(ret->errormsg) == 0) {
3250 snprintf(append, sizeof append,
3251 "Invalid recipient: %s",
3255 snprintf(append, sizeof append, ", %s", this_recp);
3257 if ( (strlen(ret->errormsg) + strlen(append)) < SIZ) {
3258 strcat(ret->errormsg, append);
3262 if (strlen(ret->display_recp) == 0) {
3263 strcpy(append, this_recp);
3266 snprintf(append, sizeof append, ", %s", this_recp);
3268 if ( (strlen(ret->display_recp)+strlen(append)) < SIZ) {
3269 strcat(ret->display_recp, append);
3274 if ((ret->num_local + ret->num_internet + ret->num_ignet +
3275 ret->num_room + ret->num_error) == 0) {
3276 ret->num_error = (-1);
3277 strcpy(ret->errormsg, "No recipients specified.");
3280 lprintf(CTDL_DEBUG, "validate_recipients()\n");
3281 lprintf(CTDL_DEBUG, " local: %d <%s>\n", ret->num_local, ret->recp_local);
3282 lprintf(CTDL_DEBUG, " room: %d <%s>\n", ret->num_room, ret->recp_room);
3283 lprintf(CTDL_DEBUG, " inet: %d <%s>\n", ret->num_internet, ret->recp_internet);
3284 lprintf(CTDL_DEBUG, " ignet: %d <%s>\n", ret->num_ignet, ret->recp_ignet);
3285 lprintf(CTDL_DEBUG, " error: %d <%s>\n", ret->num_error, ret->errormsg);
3293 * Destructor for struct recptypes
3295 void free_recipients(struct recptypes *valid) {
3297 if (valid == NULL) {
3301 if (valid->recptypes_magic != RECPTYPES_MAGIC) {
3302 lprintf(CTDL_EMERG, "Attempt to call free_recipients() on some other data type!\n");
3306 if (valid->errormsg != NULL) free(valid->errormsg);
3307 if (valid->recp_local != NULL) free(valid->recp_local);
3308 if (valid->recp_internet != NULL) free(valid->recp_internet);
3309 if (valid->recp_ignet != NULL) free(valid->recp_ignet);
3310 if (valid->recp_room != NULL) free(valid->recp_room);
3311 if (valid->display_recp != NULL) free(valid->display_recp);
3318 * message entry - mode 0 (normal)
3320 void cmd_ent0(char *entargs)
3326 char supplied_euid[128];
3328 int format_type = 0;
3329 char newusername[256];
3330 char newuseremail[256];
3331 struct CtdlMessage *msg;
3335 struct recptypes *valid = NULL;
3336 struct recptypes *valid_to = NULL;
3337 struct recptypes *valid_cc = NULL;
3338 struct recptypes *valid_bcc = NULL;
3340 int subject_required = 0;
3345 int newuseremail_ok = 0;
3349 post = extract_int(entargs, 0);
3350 extract_token(recp, entargs, 1, '|', sizeof recp);
3351 anon_flag = extract_int(entargs, 2);
3352 format_type = extract_int(entargs, 3);
3353 extract_token(subject, entargs, 4, '|', sizeof subject);
3354 extract_token(newusername, entargs, 5, '|', sizeof newusername);
3355 do_confirm = extract_int(entargs, 6);
3356 extract_token(cc, entargs, 7, '|', sizeof cc);
3357 extract_token(bcc, entargs, 8, '|', sizeof bcc);
3358 switch(CC->room.QRdefaultview) {
3361 extract_token(supplied_euid, entargs, 9, '|', sizeof supplied_euid);
3364 supplied_euid[0] = 0;
3367 extract_token(newuseremail, entargs, 10, '|', sizeof newuseremail);
3369 /* first check to make sure the request is valid. */
3371 err = CtdlDoIHavePermissionToPostInThisRoom(errmsg, sizeof errmsg);
3374 cprintf("%d %s\n", err, errmsg);
3378 /* Check some other permission type things. */
3380 if (strlen(newusername) == 0) {
3381 strcpy(newusername, CC->user.fullname);
3383 if ( (CC->user.axlevel < 6)
3384 && (strcasecmp(newusername, CC->user.fullname))
3385 && (strcasecmp(newusername, CC->cs_inet_fn))
3387 cprintf("%d You don't have permission to author messages as '%s'.\n",
3388 ERROR + HIGHER_ACCESS_REQUIRED,
3395 if (strlen(newuseremail) == 0) {
3396 newuseremail_ok = 1;
3399 if (strlen(newuseremail) > 0) {
3400 if (!strcasecmp(newuseremail, CC->cs_inet_email)) {
3401 newuseremail_ok = 1;
3403 else if (strlen(CC->cs_inet_other_emails) > 0) {
3404 j = num_tokens(CC->cs_inet_other_emails, '|');
3405 for (i=0; i<j; ++i) {
3406 extract_token(buf, CC->cs_inet_other_emails, i, '|', sizeof buf);
3407 if (!strcasecmp(newuseremail, buf)) {
3408 newuseremail_ok = 1;
3414 if (!newuseremail_ok) {
3415 cprintf("%d You don't have permission to author messages as '%s'.\n",
3416 ERROR + HIGHER_ACCESS_REQUIRED,
3422 CC->cs_flags |= CS_POSTING;
3424 /* In mailbox rooms we have to behave a little differently --
3425 * make sure the user has specified at least one recipient. Then
3426 * validate the recipient(s). We do this for the Mail> room, as
3427 * well as any room which has the "Mailbox" view set.
3430 if ( ( (CC->room.QRflags & QR_MAILBOX) && (!strcasecmp(&CC->room.QRname[11], MAILROOM)) )
3431 || ( (CC->room.QRflags & QR_MAILBOX) && (CC->curr_view == VIEW_MAILBOX) )
3433 if (CC->user.axlevel < 2) {
3434 strcpy(recp, "sysop");
3439 valid_to = validate_recipients(recp);
3440 if (valid_to->num_error > 0) {
3441 cprintf("%d Invalid recipient (To)\n", ERROR + NO_SUCH_USER);
3442 free_recipients(valid_to);
3446 valid_cc = validate_recipients(cc);
3447 if (valid_cc->num_error > 0) {
3448 cprintf("%d Invalid recipient (CC)\n", ERROR + NO_SUCH_USER);
3449 free_recipients(valid_to);
3450 free_recipients(valid_cc);
3454 valid_bcc = validate_recipients(bcc);
3455 if (valid_bcc->num_error > 0) {
3456 cprintf("%d Invalid recipient (BCC)\n", ERROR + NO_SUCH_USER);
3457 free_recipients(valid_to);
3458 free_recipients(valid_cc);
3459 free_recipients(valid_bcc);
3463 /* Recipient required, but none were specified */
3464 if ( (valid_to->num_error < 0) && (valid_cc->num_error < 0) && (valid_bcc->num_error < 0) ) {
3465 free_recipients(valid_to);
3466 free_recipients(valid_cc);
3467 free_recipients(valid_bcc);
3468 cprintf("%d At least one recipient is required.\n", ERROR + NO_SUCH_USER);
3472 if (valid_to->num_internet + valid_cc->num_internet + valid_bcc->num_internet > 0) {
3473 if (CtdlCheckInternetMailPermission(&CC->user)==0) {
3474 cprintf("%d You do not have permission "
3475 "to send Internet mail.\n",
3476 ERROR + HIGHER_ACCESS_REQUIRED);
3477 free_recipients(valid_to);
3478 free_recipients(valid_cc);
3479 free_recipients(valid_bcc);
3484 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)
3485 && (CC->user.axlevel < 4) ) {
3486 cprintf("%d Higher access required for network mail.\n",
3487 ERROR + HIGHER_ACCESS_REQUIRED);
3488 free_recipients(valid_to);
3489 free_recipients(valid_cc);
3490 free_recipients(valid_bcc);
3494 if ((RESTRICT_INTERNET == 1)
3495 && (valid_to->num_internet + valid_cc->num_internet + valid_bcc->num_internet > 0)
3496 && ((CC->user.flags & US_INTERNET) == 0)
3497 && (!CC->internal_pgm)) {
3498 cprintf("%d You don't have access to Internet mail.\n",
3499 ERROR + HIGHER_ACCESS_REQUIRED);
3500 free_recipients(valid_to);
3501 free_recipients(valid_cc);
3502 free_recipients(valid_bcc);
3508 /* Is this a room which has anonymous-only or anonymous-option? */
3509 anonymous = MES_NORMAL;
3510 if (CC->room.QRflags & QR_ANONONLY) {
3511 anonymous = MES_ANONONLY;
3513 if (CC->room.QRflags & QR_ANONOPT) {
3514 if (anon_flag == 1) { /* only if the user requested it */
3515 anonymous = MES_ANONOPT;
3519 if ((CC->room.QRflags & QR_MAILBOX) == 0) {
3523 /* Recommend to the client that the use of a message subject is
3524 * strongly recommended in this room, if either the SUBJECTREQ flag
3525 * is set, or if there is one or more Internet email recipients.
3527 if (CC->room.QRflags2 & QR2_SUBJECTREQ) subject_required = 1;
3528 if (valid_to) if (valid_to->num_internet > 0) subject_required = 1;
3529 if (valid_cc) if (valid_cc->num_internet > 0) subject_required = 1;
3530 if (valid_bcc) if (valid_bcc->num_internet > 0) subject_required = 1;
3532 /* If we're only checking the validity of the request, return
3533 * success without creating the message.
3536 cprintf("%d %s|%d\n", CIT_OK,
3537 ((valid_to != NULL) ? valid_to->display_recp : ""),
3539 free_recipients(valid_to);
3540 free_recipients(valid_cc);
3541 free_recipients(valid_bcc);
3545 /* We don't need these anymore because we'll do it differently below */
3546 free_recipients(valid_to);
3547 free_recipients(valid_cc);
3548 free_recipients(valid_bcc);
3550 /* Read in the message from the client. */
3552 cprintf("%d send message\n", START_CHAT_MODE);
3554 cprintf("%d send message\n", SEND_LISTING);
3557 msg = CtdlMakeMessage(&CC->user, recp, cc,
3558 CC->room.QRname, anonymous, format_type,
3559 newusername, newuseremail, subject,
3560 ((strlen(supplied_euid) > 0) ? supplied_euid : NULL),
3563 /* Put together one big recipients struct containing to/cc/bcc all in
3564 * one. This is for the envelope.
3566 char *all_recps = malloc(SIZ * 3);
3567 strcpy(all_recps, recp);
3568 if (strlen(cc) > 0) {
3569 if (strlen(all_recps) > 0) {
3570 strcat(all_recps, ",");
3572 strcat(all_recps, cc);
3574 if (strlen(bcc) > 0) {
3575 if (strlen(all_recps) > 0) {
3576 strcat(all_recps, ",");
3578 strcat(all_recps, bcc);
3580 if (strlen(all_recps) > 0) {
3581 valid = validate_recipients(all_recps);
3589 msgnum = CtdlSubmitMsg(msg, valid, "");
3592 cprintf("%ld\n", msgnum);
3594 cprintf("Message accepted.\n");
3597 cprintf("Internal error.\n");
3599 if (msg->cm_fields['E'] != NULL) {
3600 cprintf("%s\n", msg->cm_fields['E']);
3607 CtdlFreeMessage(msg);
3609 if (valid != NULL) {
3610 free_recipients(valid);
3618 * API function to delete messages which match a set of criteria
3619 * (returns the actual number of messages deleted)
3621 int CtdlDeleteMessages(char *room_name, /* which room */
3622 long *dmsgnums, /* array of msg numbers to be deleted */
3623 int num_dmsgnums, /* number of msgs to be deleted, or 0 for "any" */
3624 char *content_type /* or "" for any. regular expressions expected. */
3627 struct ctdlroom qrbuf;
3628 struct cdbdata *cdbfr;
3629 long *msglist = NULL;
3630 long *dellist = NULL;
3633 int num_deleted = 0;
3635 struct MetaData smi;
3638 int need_to_free_re = 0;
3640 if (content_type) if (strlen(content_type) > 0) {
3641 regcomp(&re, content_type, 0);
3642 need_to_free_re = 1;
3644 lprintf(CTDL_DEBUG, "CtdlDeleteMessages(%s, %d msgs, %s)\n",
3645 room_name, num_dmsgnums, content_type);
3647 /* get room record, obtaining a lock... */
3648 if (lgetroom(&qrbuf, room_name) != 0) {
3649 lprintf(CTDL_ERR, "CtdlDeleteMessages(): Room <%s> not found\n",
3651 if (need_to_free_re) regfree(&re);
3652 return (0); /* room not found */
3654 cdbfr = cdb_fetch(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long));
3656 if (cdbfr != NULL) {
3657 dellist = malloc(cdbfr->len);
3658 msglist = (long *) cdbfr->ptr;
3659 cdbfr->ptr = NULL; /* CtdlDeleteMessages() now owns this memory */
3660 num_msgs = cdbfr->len / sizeof(long);
3664 for (i = 0; i < num_msgs; ++i) {
3667 /* Set/clear a bit for each criterion */
3669 /* 0 messages in the list or a null list means that we are
3670 * interested in deleting any messages which meet the other criteria.
3672 if ((num_dmsgnums == 0) || (dmsgnums == NULL)) {
3673 delete_this |= 0x01;
3676 for (j=0; j<num_dmsgnums; ++j) {
3677 if (msglist[i] == dmsgnums[j]) {
3678 delete_this |= 0x01;
3683 if (strlen(content_type) == 0) {
3684 delete_this |= 0x02;
3686 GetMetaData(&smi, msglist[i]);
3687 if (regexec(&re, smi.meta_content_type, 1, &pm, 0) == 0) {
3688 delete_this |= 0x02;
3692 /* Delete message only if all bits are set */
3693 if (delete_this == 0x03) {
3694 dellist[num_deleted++] = msglist[i];
3699 num_msgs = sort_msglist(msglist, num_msgs);
3700 cdb_store(CDB_MSGLISTS, &qrbuf.QRnumber, (int)sizeof(long),
3701 msglist, (int)(num_msgs * sizeof(long)));
3703 qrbuf.QRhighest = msglist[num_msgs - 1];
3707 /* Go through the messages we pulled out of the index, and decrement
3708 * their reference counts by 1. If this is the only room the message
3709 * was in, the reference count will reach zero and the message will
3710 * automatically be deleted from the database. We do this in a
3711 * separate pass because there might be plug-in hooks getting called,
3712 * and we don't want that happening during an S_ROOMS critical
3715 if (num_deleted) for (i=0; i<num_deleted; ++i) {
3716 PerformDeleteHooks(qrbuf.QRname, dellist[i]);
3717 AdjRefCount(dellist[i], -1);
3720 /* Now free the memory we used, and go away. */
3721 if (msglist != NULL) free(msglist);
3722 if (dellist != NULL) free(dellist);
3723 lprintf(CTDL_DEBUG, "%d message(s) deleted.\n", num_deleted);
3724 if (need_to_free_re) regfree(&re);
3725 return (num_deleted);
3731 * Check whether the current user has permission to delete messages from
3732 * the current room (returns 1 for yes, 0 for no)
3734 int CtdlDoIHavePermissionToDeleteMessagesFromThisRoom(void) {
3736 CtdlRoomAccess(&CC->room, &CC->user, &ra, NULL);
3737 if (ra & UA_DELETEALLOWED) return(1);
3745 * Delete message from current room
3747 void cmd_dele(char *args)
3756 extract_token(msgset, args, 0, '|', sizeof msgset);
3757 num_msgs = num_tokens(msgset, ',');
3759 cprintf("%d Nothing to do.\n", CIT_OK);
3763 if (CtdlDoIHavePermissionToDeleteMessagesFromThisRoom() == 0) {
3764 cprintf("%d Higher access required.\n",
3765 ERROR + HIGHER_ACCESS_REQUIRED);
3770 * Build our message set to be moved/copied
3772 msgs = malloc(num_msgs * sizeof(long));
3773 for (i=0; i<num_msgs; ++i) {
3774 extract_token(msgtok, msgset, i, ',', sizeof msgtok);
3775 msgs[i] = atol(msgtok);
3778 num_deleted = CtdlDeleteMessages(CC->room.QRname, msgs, num_msgs, "");
3782 cprintf("%d %d message%s deleted.\n", CIT_OK,
3783 num_deleted, ((num_deleted != 1) ? "s" : ""));
3785 cprintf("%d Message not found.\n", ERROR + MESSAGE_NOT_FOUND);
3791 * Back end API function for moves and deletes (multiple messages)
3793 int CtdlCopyMsgsToRoom(long *msgnums, int num_msgs, char *dest) {
3796 err = CtdlSaveMsgPointersInRoom(dest, msgnums, num_msgs, 1, NULL);
3797 if (err != 0) return(err);
3806 * move or copy a message to another room
3808 void cmd_move(char *args)
3815 char targ[ROOMNAMELEN];
3816 struct ctdlroom qtemp;
3823 extract_token(msgset, args, 0, '|', sizeof msgset);
3824 num_msgs = num_tokens(msgset, ',');
3826 cprintf("%d Nothing to do.\n", CIT_OK);
3830 extract_token(targ, args, 1, '|', sizeof targ);
3831 convert_room_name_macros(targ, sizeof targ);
3832 targ[ROOMNAMELEN - 1] = 0;
3833 is_copy = extract_int(args, 2);
3835 if (getroom(&qtemp, targ) != 0) {
3836 cprintf("%d '%s' does not exist.\n",
3837 ERROR + ROOM_NOT_FOUND, targ);
3841 getuser(&CC->user, CC->curr_user);
3842 CtdlRoomAccess(&qtemp, &CC->user, &ra, NULL);
3844 /* Check for permission to perform this operation.
3845 * Remember: "CC->room" is source, "qtemp" is target.
3849 /* Aides can move/copy */
3850 if (CC->user.axlevel >= 6) permit = 1;
3852 /* Room aides can move/copy */
3853 if (CC->user.usernum == CC->room.QRroomaide) permit = 1;
3855 /* Permit move/copy from personal rooms */
3856 if ((CC->room.QRflags & QR_MAILBOX)
3857 && (qtemp.QRflags & QR_MAILBOX)) permit = 1;
3859 /* Permit only copy from public to personal room */
3861 && (!(CC->room.QRflags & QR_MAILBOX))
3862 && (qtemp.QRflags & QR_MAILBOX)) permit = 1;
3864 /* Permit message removal from collaborative delete rooms */
3865 if (CC->room.QRflags2 & QR2_COLLABDEL) permit = 1;
3867 /* User must have access to target room */
3868 if (!(ra & UA_KNOWN)) permit = 0;
3871 cprintf("%d Higher access required.\n",
3872 ERROR + HIGHER_ACCESS_REQUIRED);
3877 * Build our message set to be moved/copied
3879 msgs = malloc(num_msgs * sizeof(long));
3880 for (i=0; i<num_msgs; ++i) {
3881 extract_token(msgtok, msgset, i, ',', sizeof msgtok);
3882 msgs[i] = atol(msgtok);
3888 err = CtdlCopyMsgsToRoom(msgs, num_msgs, targ);
3890 cprintf("%d Cannot store message(s) in %s: error %d\n",
3896 /* Now delete the message from the source room,
3897 * if this is a 'move' rather than a 'copy' operation.
3900 CtdlDeleteMessages(CC->room.QRname, msgs, num_msgs, "");
3904 cprintf("%d Message(s) %s.\n", CIT_OK, (is_copy ? "copied" : "moved") );
3910 * GetMetaData() - Get the supplementary record for a message
3912 void GetMetaData(struct MetaData *smibuf, long msgnum)
3915 struct cdbdata *cdbsmi;
3918 memset(smibuf, 0, sizeof(struct MetaData));
3919 smibuf->meta_msgnum = msgnum;
3920 smibuf->meta_refcount = 1; /* Default reference count is 1 */
3922 /* Use the negative of the message number for its supp record index */
3923 TheIndex = (0L - msgnum);
3925 cdbsmi = cdb_fetch(CDB_MSGMAIN, &TheIndex, sizeof(long));
3926 if (cdbsmi == NULL) {
3927 return; /* record not found; go with defaults */
3929 memcpy(smibuf, cdbsmi->ptr,
3930 ((cdbsmi->len > sizeof(struct MetaData)) ?
3931 sizeof(struct MetaData) : cdbsmi->len));
3938 * PutMetaData() - (re)write supplementary record for a message
3940 void PutMetaData(struct MetaData *smibuf)
3944 /* Use the negative of the message number for the metadata db index */
3945 TheIndex = (0L - smibuf->meta_msgnum);
3947 cdb_store(CDB_MSGMAIN,
3948 &TheIndex, (int)sizeof(long),
3949 smibuf, (int)sizeof(struct MetaData));
3954 * AdjRefCount - submit an adjustment to the reference count for a message.
3955 * (These are just queued -- we actually process them later.)
3957 void AdjRefCount(long msgnum, int incr)
3959 struct arcq new_arcq;
3961 begin_critical_section(S_SUPPMSGMAIN);
3962 if (arcfp == NULL) {
3963 arcfp = fopen(file_arcq, "ab+");
3965 end_critical_section(S_SUPPMSGMAIN);
3967 /* msgnum < 0 means that we're trying to close the file */
3969 lprintf(CTDL_DEBUG, "Closing the AdjRefCount queue file\n");
3970 begin_critical_section(S_SUPPMSGMAIN);
3971 if (arcfp != NULL) {
3975 end_critical_section(S_SUPPMSGMAIN);
3980 * If we can't open the queue, perform the operation synchronously.
3982 if (arcfp == NULL) {
3983 TDAP_AdjRefCount(msgnum, incr);
3987 new_arcq.arcq_msgnum = msgnum;
3988 new_arcq.arcq_delta = incr;
3989 fwrite(&new_arcq, sizeof(struct arcq), 1, arcfp);
3997 * TDAP_ProcessAdjRefCountQueue()
3999 * Process the queue of message count adjustments that was created by calls
4000 * to AdjRefCount() ... by reading the queue and calling TDAP_AdjRefCount()
4001 * for each one. This should be an "off hours" operation.
4003 int TDAP_ProcessAdjRefCountQueue(void)
4005 char file_arcq_temp[PATH_MAX];
4008 struct arcq arcq_rec;
4009 int num_records_processed = 0;
4011 snprintf(file_arcq_temp, sizeof file_arcq_temp, "%s2", file_arcq);
4013 begin_critical_section(S_SUPPMSGMAIN);
4014 if (arcfp != NULL) {
4019 r = link(file_arcq, file_arcq_temp);
4021 lprintf(CTDL_CRIT, "%s: %s\n", file_arcq_temp, strerror(errno));
4022 end_critical_section(S_SUPPMSGMAIN);
4023 return(num_records_processed);
4027 end_critical_section(S_SUPPMSGMAIN);
4029 fp = fopen(file_arcq_temp, "rb");
4031 lprintf(CTDL_CRIT, "%s: %s\n", file_arcq_temp, strerror(errno));
4032 return(num_records_processed);
4035 while (fread(&arcq_rec, sizeof(struct arcq), 1, fp) == 1) {
4036 TDAP_AdjRefCount(arcq_rec.arcq_msgnum, arcq_rec.arcq_delta);
4037 ++num_records_processed;
4041 r = unlink(file_arcq_temp);
4043 lprintf(CTDL_CRIT, "%s: %s\n", file_arcq_temp, strerror(errno));
4046 return(num_records_processed);
4052 * TDAP_AdjRefCount - adjust the reference count for a message.
4053 * This one does it "for real" because it's called by
4054 * the autopurger function that processes the queue
4055 * created by AdjRefCount(). If a message's reference
4056 * count becomes zero, we also delete the message from
4057 * disk and de-index it.
4059 void TDAP_AdjRefCount(long msgnum, int incr)
4062 struct MetaData smi;
4065 /* This is a *tight* critical section; please keep it that way, as
4066 * it may get called while nested in other critical sections.
4067 * Complicating this any further will surely cause deadlock!
4069 begin_critical_section(S_SUPPMSGMAIN);
4070 GetMetaData(&smi, msgnum);
4071 smi.meta_refcount += incr;
4073 end_critical_section(S_SUPPMSGMAIN);
4074 lprintf(CTDL_DEBUG, "msg %ld ref count delta %d, is now %d\n",
4075 msgnum, incr, smi.meta_refcount);
4077 /* If the reference count is now zero, delete the message
4078 * (and its supplementary record as well).
4080 if (smi.meta_refcount == 0) {
4081 lprintf(CTDL_DEBUG, "Deleting message <%ld>\n", msgnum);
4083 /* Remove from fulltext index */
4084 if (config.c_enable_fulltext) {
4085 ft_index_message(msgnum, 0);
4088 /* Remove from message base */
4090 cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long));
4091 cdb_delete(CDB_BIGMSGS, &delnum, (int)sizeof(long));
4093 /* Remove metadata record */
4094 delnum = (0L - msgnum);
4095 cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long));
4101 * Write a generic object to this room
4103 * Note: this could be much more efficient. Right now we use two temporary
4104 * files, and still pull the message into memory as with all others.
4106 void CtdlWriteObject(char *req_room, /* Room to stuff it in */
4107 char *content_type, /* MIME type of this object */
4108 char *tempfilename, /* Where to fetch it from */
4109 struct ctdluser *is_mailbox, /* Mailbox room? */
4110 int is_binary, /* Is encoding necessary? */
4111 int is_unique, /* Del others of this type? */
4112 unsigned int flags /* Internal save flags */
4117 struct ctdlroom qrbuf;
4118 char roomname[ROOMNAMELEN];
4119 struct CtdlMessage *msg;
4121 char *raw_message = NULL;
4122 char *encoded_message = NULL;
4123 off_t raw_length = 0;
4125 if (is_mailbox != NULL) {
4126 MailboxName(roomname, sizeof roomname, is_mailbox, req_room);
4129 safestrncpy(roomname, req_room, sizeof(roomname));
4132 fp = fopen(tempfilename, "rb");
4134 lprintf(CTDL_CRIT, "Cannot open %s: %s\n",
4135 tempfilename, strerror(errno));
4138 fseek(fp, 0L, SEEK_END);
4139 raw_length = ftell(fp);
4141 lprintf(CTDL_DEBUG, "Raw length is %ld\n", (long)raw_length);
4143 raw_message = malloc((size_t)raw_length + 2);
4144 fread(raw_message, (size_t)raw_length, 1, fp);
4148 encoded_message = malloc((size_t)
4149 (((raw_length * 134) / 100) + 4096 ) );
4152 encoded_message = malloc((size_t)(raw_length + 4096));
4155 sprintf(encoded_message, "Content-type: %s\n", content_type);
4158 sprintf(&encoded_message[strlen(encoded_message)],
4159 "Content-transfer-encoding: base64\n\n"
4163 sprintf(&encoded_message[strlen(encoded_message)],
4164 "Content-transfer-encoding: 7bit\n\n"
4170 &encoded_message[strlen(encoded_message)],
4176 raw_message[raw_length] = 0;
4178 &encoded_message[strlen(encoded_message)],
4186 lprintf(CTDL_DEBUG, "Allocating\n");
4187 msg = malloc(sizeof(struct CtdlMessage));
4188 memset(msg, 0, sizeof(struct CtdlMessage));
4189 msg->cm_magic = CTDLMESSAGE_MAGIC;
4190 msg->cm_anon_type = MES_NORMAL;
4191 msg->cm_format_type = 4;
4192 msg->cm_fields['A'] = strdup(CC->user.fullname);
4193 msg->cm_fields['O'] = strdup(req_room);
4194 msg->cm_fields['N'] = strdup(config.c_nodename);
4195 msg->cm_fields['H'] = strdup(config.c_humannode);
4196 msg->cm_flags = flags;
4198 msg->cm_fields['M'] = encoded_message;
4200 /* Create the requested room if we have to. */
4201 if (getroom(&qrbuf, roomname) != 0) {
4202 create_room(roomname,
4203 ( (is_mailbox != NULL) ? 5 : 3 ),
4204 "", 0, 1, 0, VIEW_BBS);
4206 /* If the caller specified this object as unique, delete all
4207 * other objects of this type that are currently in the room.
4210 lprintf(CTDL_DEBUG, "Deleted %d other msgs of this type\n",
4211 CtdlDeleteMessages(roomname, NULL, 0, content_type)
4214 /* Now write the data */
4215 CtdlSubmitMsg(msg, NULL, roomname);
4216 CtdlFreeMessage(msg);
4224 void CtdlGetSysConfigBackend(long msgnum, void *userdata) {
4225 config_msgnum = msgnum;
4229 char *CtdlGetSysConfig(char *sysconfname) {
4230 char hold_rm[ROOMNAMELEN];
4233 struct CtdlMessage *msg;
4236 strcpy(hold_rm, CC->room.QRname);
4237 if (getroom(&CC->room, SYSCONFIGROOM) != 0) {
4238 getroom(&CC->room, hold_rm);
4243 /* We want the last (and probably only) config in this room */
4244 begin_critical_section(S_CONFIG);
4245 config_msgnum = (-1L);
4246 CtdlForEachMessage(MSGS_LAST, 1, NULL, sysconfname, NULL,
4247 CtdlGetSysConfigBackend, NULL);
4248 msgnum = config_msgnum;
4249 end_critical_section(S_CONFIG);
4255 msg = CtdlFetchMessage(msgnum, 1);
4257 conf = strdup(msg->cm_fields['M']);
4258 CtdlFreeMessage(msg);
4265 getroom(&CC->room, hold_rm);
4267 if (conf != NULL) do {
4268 extract_token(buf, conf, 0, '\n', sizeof buf);
4269 strcpy(conf, &conf[strlen(buf)+1]);
4270 } while ( (strlen(conf)>0) && (strlen(buf)>0) );
4275 void CtdlPutSysConfig(char *sysconfname, char *sysconfdata) {
4276 char temp[PATH_MAX];
4279 CtdlMakeTempFileName(temp, sizeof temp);
4281 fp = fopen(temp, "w");
4282 if (fp == NULL) return;
4283 fprintf(fp, "%s", sysconfdata);
4286 /* this handy API function does all the work for us */
4287 CtdlWriteObject(SYSCONFIGROOM, sysconfname, temp, NULL, 0, 1, 0);
4293 * Determine whether a given Internet address belongs to the current user
4295 int CtdlIsMe(char *addr, int addr_buf_len)
4297 struct recptypes *recp;
4300 recp = validate_recipients(addr);
4301 if (recp == NULL) return(0);
4303 if (recp->num_local == 0) {
4304 free_recipients(recp);
4308 for (i=0; i<recp->num_local; ++i) {
4309 extract_token(addr, recp->recp_local, i, '|', addr_buf_len);
4310 if (!strcasecmp(addr, CC->user.fullname)) {
4311 free_recipients(recp);
4316 free_recipients(recp);
4322 * Citadel protocol command to do the same
4324 void cmd_isme(char *argbuf) {
4327 if (CtdlAccessCheck(ac_logged_in)) return;
4328 extract_token(addr, argbuf, 0, '|', sizeof addr);
4330 if (CtdlIsMe(addr, sizeof addr)) {
4331 cprintf("%d %s\n", CIT_OK, addr);
4334 cprintf("%d Not you.\n", ERROR + ILLEGAL_VALUE);