4 * Implements the message store.
14 #if TIME_WITH_SYS_TIME
15 # include <sys/time.h>
19 # include <sys/time.h>
34 #include "serv_extensions.h"
38 #include "sysdep_decls.h"
39 #include "citserver.h"
46 #include "mime_parser.h"
49 #include "internet_addressing.h"
50 #include "serv_fulltext.h"
52 #include "euidindex.h"
53 #include "journaling.h"
54 #include "citadel_dirs.h"
55 #include "serv_network.h"
58 # include "serv_sieve.h"
59 #endif /* HAVE_LIBSIEVE */
62 struct addresses_to_be_filed *atbf = NULL;
65 * This really belongs in serv_network.c, but I don't know how to export
66 * symbols between modules.
68 struct FilterList *filterlist = NULL;
72 * These are the four-character field headers we use when outputting
73 * messages in Citadel format (as opposed to RFC822 format).
76 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
77 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
78 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
79 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
80 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
81 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
82 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
83 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
111 * This function is self explanatory.
112 * (What can I say, I'm in a weird mood today...)
114 void remove_any_whitespace_to_the_left_or_right_of_at_symbol(char *name)
118 for (i = 0; i < strlen(name); ++i) {
119 if (name[i] == '@') {
120 while (isspace(name[i - 1]) && i > 0) {
121 strcpy(&name[i - 1], &name[i]);
124 while (isspace(name[i + 1])) {
125 strcpy(&name[i + 1], &name[i + 2]);
133 * Aliasing for network mail.
134 * (Error messages have been commented out, because this is a server.)
136 int alias(char *name)
137 { /* process alias and routing info for mail */
140 char aaa[SIZ], bbb[SIZ];
141 char *ignetcfg = NULL;
142 char *ignetmap = NULL;
149 remove_any_whitespace_to_the_left_or_right_of_at_symbol(name);
150 stripallbut(name, '<', '>');
152 fp = fopen(file_mail_aliases, "r");
154 fp = fopen("/dev/null", "r");
161 while (fgets(aaa, sizeof aaa, fp) != NULL) {
162 while (isspace(name[0]))
163 strcpy(name, &name[1]);
164 aaa[strlen(aaa) - 1] = 0;
166 for (a = 0; a < strlen(aaa); ++a) {
168 strcpy(bbb, &aaa[a + 1]);
172 if (!strcasecmp(name, aaa))
177 /* Hit the Global Address Book */
178 if (CtdlDirectoryLookup(aaa, name, sizeof aaa) == 0) {
182 lprintf(CTDL_INFO, "Mail is being forwarded to %s\n", name);
184 /* Change "user @ xxx" to "user" if xxx is an alias for this host */
185 for (a=0; a<strlen(name); ++a) {
186 if (name[a] == '@') {
187 if (CtdlHostAlias(&name[a+1]) == hostalias_localhost) {
189 lprintf(CTDL_INFO, "Changed to <%s>\n", name);
194 /* determine local or remote type, see citadel.h */
195 at = haschar(name, '@');
196 if (at == 0) return(MES_LOCAL); /* no @'s - local address */
197 if (at > 1) return(MES_ERROR); /* >1 @'s - invalid address */
198 remove_any_whitespace_to_the_left_or_right_of_at_symbol(name);
200 /* figure out the delivery mode */
201 extract_token(node, name, 1, '@', sizeof node);
203 /* If there are one or more dots in the nodename, we assume that it
204 * is an FQDN and will attempt SMTP delivery to the Internet.
206 if (haschar(node, '.') > 0) {
207 return(MES_INTERNET);
210 /* Otherwise we look in the IGnet maps for a valid Citadel node.
211 * Try directly-connected nodes first...
213 ignetcfg = CtdlGetSysConfig(IGNETCFG);
214 for (i=0; i<num_tokens(ignetcfg, '\n'); ++i) {
215 extract_token(buf, ignetcfg, i, '\n', sizeof buf);
216 extract_token(testnode, buf, 0, '|', sizeof testnode);
217 if (!strcasecmp(node, testnode)) {
225 * Then try nodes that are two or more hops away.
227 ignetmap = CtdlGetSysConfig(IGNETMAP);
228 for (i=0; i<num_tokens(ignetmap, '\n'); ++i) {
229 extract_token(buf, ignetmap, i, '\n', sizeof buf);
230 extract_token(testnode, buf, 0, '|', sizeof testnode);
231 if (!strcasecmp(node, testnode)) {
238 /* If we get to this point it's an invalid node name */
247 fp = fopen(file_citadel_control, "r");
249 lprintf(CTDL_CRIT, "Cannot open %s: %s\n",
250 file_citadel_control,
254 fread((char *) &CitControl, sizeof(struct CitControl), 1, fp);
260 * Back end for the MSGS command: output message number only.
262 void simple_listing(long msgnum, void *userdata)
264 cprintf("%ld\n", msgnum);
270 * Back end for the MSGS command: output header summary.
272 void headers_listing(long msgnum, void *userdata)
274 struct CtdlMessage *msg;
276 msg = CtdlFetchMessage(msgnum, 0);
278 cprintf("%ld|0|||||\n", msgnum);
282 cprintf("%ld|%s|%s|%s|%s|%s|\n",
284 (msg->cm_fields['T'] ? msg->cm_fields['T'] : "0"),
285 (msg->cm_fields['A'] ? msg->cm_fields['A'] : ""),
286 (msg->cm_fields['N'] ? msg->cm_fields['N'] : ""),
287 (msg->cm_fields['F'] ? msg->cm_fields['F'] : ""),
288 (msg->cm_fields['U'] ? msg->cm_fields['U'] : "")
290 CtdlFreeMessage(msg);
295 /* Determine if a given message matches the fields in a message template.
296 * Return 0 for a successful match.
298 int CtdlMsgCmp(struct CtdlMessage *msg, struct CtdlMessage *template) {
301 /* If there aren't any fields in the template, all messages will
304 if (template == NULL) return(0);
306 /* Null messages are bogus. */
307 if (msg == NULL) return(1);
309 for (i='A'; i<='Z'; ++i) {
310 if (template->cm_fields[i] != NULL) {
311 if (msg->cm_fields[i] == NULL) {
314 if (strcasecmp(msg->cm_fields[i],
315 template->cm_fields[i])) return 1;
319 /* All compares succeeded: we have a match! */
326 * Retrieve the "seen" message list for the current room.
328 void CtdlGetSeen(char *buf, int which_set) {
331 /* Learn about the user and room in question */
332 CtdlGetRelationship(&vbuf, &CC->user, &CC->room);
334 if (which_set == ctdlsetseen_seen)
335 safestrncpy(buf, vbuf.v_seen, SIZ);
336 if (which_set == ctdlsetseen_answered)
337 safestrncpy(buf, vbuf.v_answered, SIZ);
343 * Manipulate the "seen msgs" string (or other message set strings)
345 void CtdlSetSeen(long *target_msgnums, int num_target_msgnums,
346 int target_setting, int which_set,
347 struct ctdluser *which_user, struct ctdlroom *which_room) {
348 struct cdbdata *cdbfr;
360 char *is_set; /* actually an array of booleans */
363 char setstr[SIZ], lostr[SIZ], histr[SIZ];
366 /* Don't bother doing *anything* if we were passed a list of zero messages */
367 if (num_target_msgnums < 1) {
371 lprintf(CTDL_DEBUG, "CtdlSetSeen(%d msgs starting with %ld, %d, %d)\n",
372 num_target_msgnums, target_msgnums[0],
373 target_setting, which_set);
375 /* Learn about the user and room in question */
376 CtdlGetRelationship(&vbuf,
377 ((which_user != NULL) ? which_user : &CC->user),
378 ((which_room != NULL) ? which_room : &CC->room)
381 /* Load the message list */
382 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long));
384 msglist = (long *) cdbfr->ptr;
385 cdbfr->ptr = NULL; /* CtdlSetSeen() now owns this memory */
386 num_msgs = cdbfr->len / sizeof(long);
389 return; /* No messages at all? No further action. */
392 is_set = malloc(num_msgs * sizeof(char));
393 memset(is_set, 0, (num_msgs * sizeof(char)) );
395 /* Decide which message set we're manipulating */
397 case ctdlsetseen_seen:
398 safestrncpy(vset, vbuf.v_seen, sizeof vset);
400 case ctdlsetseen_answered:
401 safestrncpy(vset, vbuf.v_answered, sizeof vset);
405 /* lprintf(CTDL_DEBUG, "before optimize: %s\n", vset); */
407 /* Translate the existing sequence set into an array of booleans */
408 num_sets = num_tokens(vset, ',');
409 for (s=0; s<num_sets; ++s) {
410 extract_token(setstr, vset, s, ',', sizeof setstr);
412 extract_token(lostr, setstr, 0, ':', sizeof lostr);
413 if (num_tokens(setstr, ':') >= 2) {
414 extract_token(histr, setstr, 1, ':', sizeof histr);
415 if (!strcmp(histr, "*")) {
416 snprintf(histr, sizeof histr, "%ld", LONG_MAX);
420 strcpy(histr, lostr);
425 for (i = 0; i < num_msgs; ++i) {
426 if ((msglist[i] >= lo) && (msglist[i] <= hi)) {
432 /* Now translate the array of booleans back into a sequence set */
437 for (i=0; i<num_msgs; ++i) {
439 is_seen = is_set[i]; /* Default to existing setting */
441 for (k=0; k<num_target_msgnums; ++k) {
442 if (msglist[i] == target_msgnums[k]) {
443 is_seen = target_setting;
448 if (lo < 0L) lo = msglist[i];
452 if ( ((is_seen == 0) && (was_seen == 1))
453 || ((is_seen == 1) && (i == num_msgs-1)) ) {
455 /* begin trim-o-matic code */
458 while ( (strlen(vset) + 20) > sizeof vset) {
459 remove_token(vset, 0, ',');
461 if (j--) break; /* loop no more than 9 times */
463 if ( (trimming) && (which_set == ctdlsetseen_seen) ) {
467 snprintf(lostr, sizeof lostr,
468 "1:%ld,%s", t, vset);
469 safestrncpy(vset, lostr, sizeof vset);
471 /* end trim-o-matic code */
479 snprintf(&vset[tmp], (sizeof vset) - tmp,
483 snprintf(&vset[tmp], (sizeof vset) - tmp,
492 /* Decide which message set we're manipulating */
494 case ctdlsetseen_seen:
495 safestrncpy(vbuf.v_seen, vset, sizeof vbuf.v_seen);
497 case ctdlsetseen_answered:
498 safestrncpy(vbuf.v_answered, vset,
499 sizeof vbuf.v_answered);
504 /* lprintf(CTDL_DEBUG, " after optimize: %s\n", vset); */
506 CtdlSetRelationship(&vbuf,
507 ((which_user != NULL) ? which_user : &CC->user),
508 ((which_room != NULL) ? which_room : &CC->room)
514 * API function to perform an operation for each qualifying message in the
515 * current room. (Returns the number of messages processed.)
517 int CtdlForEachMessage(int mode, long ref, char *search_string,
519 struct CtdlMessage *compare,
520 void (*CallBack) (long, void *),
526 struct cdbdata *cdbfr;
527 long *msglist = NULL;
529 int num_processed = 0;
532 struct CtdlMessage *msg;
535 int printed_lastold = 0;
536 int num_search_msgs = 0;
537 long *search_msgs = NULL;
539 /* Learn about the user and room in question */
541 getuser(&CC->user, CC->curr_user);
542 CtdlGetRelationship(&vbuf, &CC->user, &CC->room);
544 /* Load the message list */
545 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long));
547 msglist = (long *) cdbfr->ptr;
548 cdbfr->ptr = NULL; /* CtdlForEachMessage() now owns this memory */
549 num_msgs = cdbfr->len / sizeof(long);
552 return 0; /* No messages at all? No further action. */
557 * Now begin the traversal.
559 if (num_msgs > 0) for (a = 0; a < num_msgs; ++a) {
561 /* If the caller is looking for a specific MIME type, filter
562 * out all messages which are not of the type requested.
564 if (content_type != NULL) if (strlen(content_type) > 0) {
566 /* This call to GetMetaData() sits inside this loop
567 * so that we only do the extra database read per msg
568 * if we need to. Doing the extra read all the time
569 * really kills the server. If we ever need to use
570 * metadata for another search criterion, we need to
571 * move the read somewhere else -- but still be smart
572 * enough to only do the read if the caller has
573 * specified something that will need it.
575 GetMetaData(&smi, msglist[a]);
577 if (strcasecmp(smi.meta_content_type, content_type)) {
583 num_msgs = sort_msglist(msglist, num_msgs);
585 /* If a template was supplied, filter out the messages which
586 * don't match. (This could induce some delays!)
589 if (compare != NULL) {
590 for (a = 0; a < num_msgs; ++a) {
591 msg = CtdlFetchMessage(msglist[a], 1);
593 if (CtdlMsgCmp(msg, compare)) {
596 CtdlFreeMessage(msg);
602 /* If a search string was specified, get a message list from
603 * the full text index and remove messages which aren't on both
607 * Since the lists are sorted and strictly ascending, and the
608 * output list is guaranteed to be shorter than or equal to the
609 * input list, we overwrite the bottom of the input list. This
610 * eliminates the need to memmove big chunks of the list over and
613 if ( (num_msgs > 0) && (mode == MSGS_SEARCH) && (search_string) ) {
614 ft_search(&num_search_msgs, &search_msgs, search_string);
615 if (num_search_msgs > 0) {
619 orig_num_msgs = num_msgs;
621 for (i=0; i<orig_num_msgs; ++i) {
622 for (j=0; j<num_search_msgs; ++j) {
623 if (msglist[i] == search_msgs[j]) {
624 msglist[num_msgs++] = msglist[i];
630 num_msgs = 0; /* No messages qualify */
632 if (search_msgs != NULL) free(search_msgs);
634 /* Now that we've purged messages which don't contain the search
635 * string, treat a MSGS_SEARCH just like a MSGS_ALL from this
642 * Now iterate through the message list, according to the
643 * criteria supplied by the caller.
646 for (a = 0; a < num_msgs; ++a) {
647 thismsg = msglist[a];
648 if (mode == MSGS_ALL) {
652 is_seen = is_msg_in_sequence_set(
653 vbuf.v_seen, thismsg);
654 if (is_seen) lastold = thismsg;
660 || ((mode == MSGS_OLD) && (is_seen))
661 || ((mode == MSGS_NEW) && (!is_seen))
662 || ((mode == MSGS_LAST) && (a >= (num_msgs - ref)))
663 || ((mode == MSGS_FIRST) && (a < ref))
664 || ((mode == MSGS_GT) && (thismsg > ref))
665 || ((mode == MSGS_EQ) && (thismsg == ref))
668 if ((mode == MSGS_NEW) && (CC->user.flags & US_LASTOLD) && (lastold > 0L) && (printed_lastold == 0) && (!is_seen)) {
670 CallBack(lastold, userdata);
674 if (CallBack) CallBack(thismsg, userdata);
678 free(msglist); /* Clean up */
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 while(client_getln(buf, sizeof buf), strcmp(buf,"000")) {
743 extract_token(tfield, buf, 0, '|', sizeof tfield);
744 extract_token(tvalue, buf, 1, '|', sizeof tvalue);
745 for (i='A'; i<='Z'; ++i) if (msgkeys[i]!=NULL) {
746 if (!strcasecmp(tfield, msgkeys[i])) {
747 template->cm_fields[i] =
755 cprintf("%d \n", LISTING_FOLLOWS);
758 CtdlForEachMessage(mode,
759 ( (mode == MSGS_SEARCH) ? 0 : cm_ref ),
760 ( (mode == MSGS_SEARCH) ? search_string : NULL ),
763 (with_headers ? headers_listing : simple_listing),
766 if (template != NULL) CtdlFreeMessage(template);
774 * help_subst() - support routine for help file viewer
776 void help_subst(char *strbuf, char *source, char *dest)
781 while (p = pattern2(strbuf, source), (p >= 0)) {
782 strcpy(workbuf, &strbuf[p + strlen(source)]);
783 strcpy(&strbuf[p], dest);
784 strcat(strbuf, workbuf);
789 void do_help_subst(char *buffer)
793 help_subst(buffer, "^nodename", config.c_nodename);
794 help_subst(buffer, "^humannode", config.c_humannode);
795 help_subst(buffer, "^fqdn", config.c_fqdn);
796 help_subst(buffer, "^username", CC->user.fullname);
797 snprintf(buf2, sizeof buf2, "%ld", CC->user.usernum);
798 help_subst(buffer, "^usernum", buf2);
799 help_subst(buffer, "^sysadm", config.c_sysadm);
800 help_subst(buffer, "^variantname", CITADEL);
801 snprintf(buf2, sizeof buf2, "%d", config.c_maxsessions);
802 help_subst(buffer, "^maxsessions", buf2);
803 help_subst(buffer, "^bbsdir", ctdl_bbsbase_dir);
809 * memfmout() - Citadel text formatter and paginator.
810 * Although the original purpose of this routine was to format
811 * text to the reader's screen width, all we're really using it
812 * for here is to format text out to 80 columns before sending it
813 * to the client. The client software may reformat it again.
816 char *mptr, /* where are we going to get our text from? */
817 char subst, /* nonzero if we should do substitutions */
818 char *nl) /* string to terminate lines with */
826 static int width = 80;
831 c = 1; /* c is the current pos */
835 while (ch = *mptr, ((ch != 0) && (strlen(buffer) < 126))) {
837 buffer[strlen(buffer) + 1] = 0;
838 buffer[strlen(buffer)] = ch;
841 if (buffer[0] == '^')
842 do_help_subst(buffer);
844 buffer[strlen(buffer) + 1] = 0;
846 strcpy(buffer, &buffer[1]);
854 if (((ch == 13) || (ch == 10)) && (old != 13) && (old != 10)) {
857 if (((old == 13) || (old == 10)) && (isspace(real))) {
862 if (((strlen(aaa) + c) > (width - 5)) && (strlen(aaa) > (width - 5))) {
863 cprintf("%s%s", nl, aaa);
872 if ((strlen(aaa) + c) > (width - 5)) {
881 if ((ch == 13) || (ch == 10)) {
882 cprintf("%s%s", aaa, nl);
889 cprintf("%s%s", aaa, nl);
895 * Callback function for mime parser that simply lists the part
897 void list_this_part(char *name, char *filename, char *partnum, char *disp,
898 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
903 ma = (struct ma_info *)cbuserdata;
904 if (ma->is_ma == 0) {
905 cprintf("part=%s|%s|%s|%s|%s|%ld\n",
906 name, filename, partnum, disp, cbtype, (long)length);
911 * Callback function for multipart prefix
913 void list_this_pref(char *name, char *filename, char *partnum, char *disp,
914 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
919 ma = (struct ma_info *)cbuserdata;
920 if (!strcasecmp(cbtype, "multipart/alternative")) {
924 if (ma->is_ma == 0) {
925 cprintf("pref=%s|%s\n", partnum, cbtype);
930 * Callback function for multipart sufffix
932 void list_this_suff(char *name, char *filename, char *partnum, char *disp,
933 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
938 ma = (struct ma_info *)cbuserdata;
939 if (ma->is_ma == 0) {
940 cprintf("suff=%s|%s\n", partnum, cbtype);
942 if (!strcasecmp(cbtype, "multipart/alternative")) {
949 * Callback function for mime parser that opens a section for downloading
951 void mime_download(char *name, char *filename, char *partnum, char *disp,
952 void *content, char *cbtype, char *cbcharset, size_t length,
953 char *encoding, void *cbuserdata)
956 /* Silently go away if there's already a download open... */
957 if (CC->download_fp != NULL)
960 /* ...or if this is not the desired section */
961 if (strcasecmp(CC->download_desired_section, partnum))
964 CC->download_fp = tmpfile();
965 if (CC->download_fp == NULL)
968 fwrite(content, length, 1, CC->download_fp);
969 fflush(CC->download_fp);
970 rewind(CC->download_fp);
972 OpenCmdResult(filename, cbtype);
978 * Callback function for mime parser that outputs a section all at once
980 void mime_spew_section(char *name, char *filename, char *partnum, char *disp,
981 void *content, char *cbtype, char *cbcharset, size_t length,
982 char *encoding, void *cbuserdata)
984 int *found_it = (int *)cbuserdata;
986 /* ...or if this is not the desired section */
987 if (strcasecmp(CC->download_desired_section, partnum))
992 cprintf("%d %d\n", BINARY_FOLLOWS, length);
993 client_write(content, length);
999 * Load a message from disk into memory.
1000 * This is used by CtdlOutputMsg() and other fetch functions.
1002 * NOTE: Caller is responsible for freeing the returned CtdlMessage struct
1003 * using the CtdlMessageFree() function.
1005 struct CtdlMessage *CtdlFetchMessage(long msgnum, int with_body)
1007 struct cdbdata *dmsgtext;
1008 struct CtdlMessage *ret = NULL;
1012 cit_uint8_t field_header;
1014 lprintf(CTDL_DEBUG, "CtdlFetchMessage(%ld, %d)\n", msgnum, with_body);
1016 dmsgtext = cdb_fetch(CDB_MSGMAIN, &msgnum, sizeof(long));
1017 if (dmsgtext == NULL) {
1020 mptr = dmsgtext->ptr;
1021 upper_bound = mptr + dmsgtext->len;
1023 /* Parse the three bytes that begin EVERY message on disk.
1024 * The first is always 0xFF, the on-disk magic number.
1025 * The second is the anonymous/public type byte.
1026 * The third is the format type byte (vari, fixed, or MIME).
1031 "Message %ld appears to be corrupted.\n",
1036 ret = (struct CtdlMessage *) malloc(sizeof(struct CtdlMessage));
1037 memset(ret, 0, sizeof(struct CtdlMessage));
1039 ret->cm_magic = CTDLMESSAGE_MAGIC;
1040 ret->cm_anon_type = *mptr++; /* Anon type byte */
1041 ret->cm_format_type = *mptr++; /* Format type byte */
1044 * The rest is zero or more arbitrary fields. Load them in.
1045 * We're done when we encounter either a zero-length field or
1046 * have just processed the 'M' (message text) field.
1049 if (mptr >= upper_bound) {
1052 field_header = *mptr++;
1053 ret->cm_fields[field_header] = strdup(mptr);
1055 while (*mptr++ != 0); /* advance to next field */
1057 } while ((mptr < upper_bound) && (field_header != 'M'));
1061 /* Always make sure there's something in the msg text field. If
1062 * it's NULL, the message text is most likely stored separately,
1063 * so go ahead and fetch that. Failing that, just set a dummy
1064 * body so other code doesn't barf.
1066 if ( (ret->cm_fields['M'] == NULL) && (with_body) ) {
1067 dmsgtext = cdb_fetch(CDB_BIGMSGS, &msgnum, sizeof(long));
1068 if (dmsgtext != NULL) {
1069 ret->cm_fields['M'] = strdup(dmsgtext->ptr);
1073 if (ret->cm_fields['M'] == NULL) {
1074 ret->cm_fields['M'] = strdup("\r\n\r\n (no text)\r\n");
1077 /* Perform "before read" hooks (aborting if any return nonzero) */
1078 if (PerformMessageHooks(ret, EVT_BEFOREREAD) > 0) {
1079 CtdlFreeMessage(ret);
1088 * Returns 1 if the supplied pointer points to a valid Citadel message.
1089 * If the pointer is NULL or the magic number check fails, returns 0.
1091 int is_valid_message(struct CtdlMessage *msg) {
1094 if ((msg->cm_magic) != CTDLMESSAGE_MAGIC) {
1095 lprintf(CTDL_WARNING, "is_valid_message() -- self-check failed\n");
1103 * 'Destructor' for struct CtdlMessage
1105 void CtdlFreeMessage(struct CtdlMessage *msg)
1109 if (is_valid_message(msg) == 0) return;
1111 for (i = 0; i < 256; ++i)
1112 if (msg->cm_fields[i] != NULL) {
1113 free(msg->cm_fields[i]);
1116 msg->cm_magic = 0; /* just in case */
1122 * Pre callback function for multipart/alternative
1124 * NOTE: this differs from the standard behavior for a reason. Normally when
1125 * displaying multipart/alternative you want to show the _last_ usable
1126 * format in the message. Here we show the _first_ one, because it's
1127 * usually text/plain. Since this set of functions is designed for text
1128 * output to non-MIME-aware clients, this is the desired behavior.
1131 void fixed_output_pre(char *name, char *filename, char *partnum, char *disp,
1132 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
1137 ma = (struct ma_info *)cbuserdata;
1138 lprintf(CTDL_DEBUG, "fixed_output_pre() type=<%s>\n", cbtype);
1139 if (!strcasecmp(cbtype, "multipart/alternative")) {
1143 if (!strcasecmp(cbtype, "message/rfc822")) {
1149 * Post callback function for multipart/alternative
1151 void fixed_output_post(char *name, char *filename, char *partnum, char *disp,
1152 void *content, char *cbtype, char *cbcharset, size_t length,
1153 char *encoding, void *cbuserdata)
1157 ma = (struct ma_info *)cbuserdata;
1158 lprintf(CTDL_DEBUG, "fixed_output_post() type=<%s>\n", cbtype);
1159 if (!strcasecmp(cbtype, "multipart/alternative")) {
1163 if (!strcasecmp(cbtype, "message/rfc822")) {
1169 * Inline callback function for mime parser that wants to display text
1171 void fixed_output(char *name, char *filename, char *partnum, char *disp,
1172 void *content, char *cbtype, char *cbcharset, size_t length,
1173 char *encoding, void *cbuserdata)
1180 ma = (struct ma_info *)cbuserdata;
1183 "fixed_output() part %s: %s (%s) (%ld bytes)\n",
1184 partnum, filename, cbtype, (long)length);
1187 * If we're in the middle of a multipart/alternative scope and
1188 * we've already printed another section, skip this one.
1190 if ( (ma->is_ma) && (ma->did_print) ) {
1191 lprintf(CTDL_DEBUG, "Skipping part %s (%s)\n",
1197 if ( (!strcasecmp(cbtype, "text/plain"))
1198 || (strlen(cbtype)==0) ) {
1201 client_write(wptr, length);
1202 if (wptr[length-1] != '\n') {
1209 if (!strcasecmp(cbtype, "text/html")) {
1210 ptr = html_to_ascii(content, length, 80, 0);
1212 client_write(ptr, wlen);
1213 if (ptr[wlen-1] != '\n') {
1220 if (ma->use_fo_hooks) {
1221 if (PerformFixedOutputHooks(cbtype, content, length)) {
1222 /* above function returns nonzero if it handled the part */
1227 if (strncasecmp(cbtype, "multipart/", 10)) {
1228 cprintf("Part %s: %s (%s) (%ld bytes)\r\n",
1229 partnum, filename, cbtype, (long)length);
1235 * The client is elegant and sophisticated and wants to be choosy about
1236 * MIME content types, so figure out which multipart/alternative part
1237 * we're going to send.
1239 * We use a system of weights. When we find a part that matches one of the
1240 * MIME types we've declared as preferential, we can store it in ma->chosen_part
1241 * and then set ma->chosen_pref to that MIME type's position in our preference
1242 * list. If we then hit another match, we only replace the first match if
1243 * the preference value is lower.
1245 void choose_preferred(char *name, char *filename, char *partnum, char *disp,
1246 void *content, char *cbtype, char *cbcharset, size_t length,
1247 char *encoding, void *cbuserdata)
1253 ma = (struct ma_info *)cbuserdata;
1255 if (ma->is_ma > 0) {
1256 for (i=0; i<num_tokens(CC->preferred_formats, '|'); ++i) {
1257 extract_token(buf, CC->preferred_formats, i, '|', sizeof buf);
1258 lprintf(CTDL_DEBUG, "Is <%s> == <%s> ??\n", buf, cbtype);
1259 if ( (!strcasecmp(buf, cbtype)) && (!ma->freeze) ) {
1260 if (i < ma->chosen_pref) {
1261 safestrncpy(ma->chosen_part, partnum, sizeof ma->chosen_part);
1262 ma->chosen_pref = i;
1270 * Now that we've chosen our preferred part, output it.
1272 void output_preferred(char *name, char *filename, char *partnum, char *disp,
1273 void *content, char *cbtype, char *cbcharset, size_t length,
1274 char *encoding, void *cbuserdata)
1278 int add_newline = 0;
1282 ma = (struct ma_info *)cbuserdata;
1284 /* This is not the MIME part you're looking for... */
1285 if (strcasecmp(partnum, ma->chosen_part)) return;
1287 /* If the content-type of this part is in our preferred formats
1288 * list, we can simply output it verbatim.
1290 for (i=0; i<num_tokens(CC->preferred_formats, '|'); ++i) {
1291 extract_token(buf, CC->preferred_formats, i, '|', sizeof buf);
1292 if (!strcasecmp(buf, cbtype)) {
1293 /* Yeah! Go! W00t!! */
1295 text_content = (char *)content;
1296 if (text_content[length-1] != '\n') {
1300 cprintf("Content-type: %s", cbtype);
1301 if (strlen(cbcharset) > 0) {
1302 cprintf("; charset=%s", cbcharset);
1304 cprintf("\nContent-length: %d\n",
1305 (int)(length + add_newline) );
1306 if (strlen(encoding) > 0) {
1307 cprintf("Content-transfer-encoding: %s\n", encoding);
1310 cprintf("Content-transfer-encoding: 7bit\n");
1313 client_write(content, length);
1314 if (add_newline) cprintf("\n");
1319 /* No translations required or possible: output as text/plain */
1320 cprintf("Content-type: text/plain\n\n");
1321 fixed_output(name, filename, partnum, disp, content, cbtype, cbcharset,
1322 length, encoding, cbuserdata);
1327 char desired_section[64];
1334 * Callback function for
1336 void extract_encapsulated_message(char *name, char *filename, char *partnum, char *disp,
1337 void *content, char *cbtype, char *cbcharset, size_t length,
1338 char *encoding, void *cbuserdata)
1340 struct encapmsg *encap;
1342 encap = (struct encapmsg *)cbuserdata;
1344 /* Only proceed if this is the desired section... */
1345 if (!strcasecmp(encap->desired_section, partnum)) {
1346 encap->msglen = length;
1347 encap->msg = malloc(length + 2);
1348 memcpy(encap->msg, content, length);
1358 * Get a message off disk. (returns om_* values found in msgbase.h)
1361 int CtdlOutputMsg(long msg_num, /* message number (local) to fetch */
1362 int mode, /* how would you like that message? */
1363 int headers_only, /* eschew the message body? */
1364 int do_proto, /* do Citadel protocol responses? */
1365 int crlf, /* Use CRLF newlines instead of LF? */
1366 char *section /* NULL or a message/rfc822 section */
1368 struct CtdlMessage *TheMessage = NULL;
1369 int retcode = om_no_such_msg;
1370 struct encapmsg encap;
1372 lprintf(CTDL_DEBUG, "CtdlOutputMsg() msgnum=%ld, mode=%d, section=%s\n",
1374 (section ? section : "<>")
1377 if ((!(CC->logged_in)) && (!(CC->internal_pgm))) {
1378 if (do_proto) cprintf("%d Not logged in.\n",
1379 ERROR + NOT_LOGGED_IN);
1380 return(om_not_logged_in);
1383 /* FIXME: check message id against msglist for this room */
1386 * Fetch the message from disk. If we're in any sort of headers
1387 * only mode, request that we don't even bother loading the body
1390 if ( (headers_only == HEADERS_FAST) || (headers_only == HEADERS_ONLY) ) {
1391 TheMessage = CtdlFetchMessage(msg_num, 0);
1394 TheMessage = CtdlFetchMessage(msg_num, 1);
1397 if (TheMessage == NULL) {
1398 if (do_proto) cprintf("%d Can't locate msg %ld on disk\n",
1399 ERROR + MESSAGE_NOT_FOUND, msg_num);
1400 return(om_no_such_msg);
1403 /* Here is the weird form of this command, to process only an
1404 * encapsulated message/rfc822 section.
1406 if (section) if (strlen(section)>0) if (strcmp(section, "0")) {
1407 memset(&encap, 0, sizeof encap);
1408 safestrncpy(encap.desired_section, section, sizeof encap.desired_section);
1409 mime_parser(TheMessage->cm_fields['M'],
1411 *extract_encapsulated_message,
1412 NULL, NULL, (void *)&encap, 0
1414 CtdlFreeMessage(TheMessage);
1418 encap.msg[encap.msglen] = 0;
1419 TheMessage = convert_internet_message(encap.msg);
1420 encap.msg = NULL; /* no free() here, TheMessage owns it now */
1422 /* Now we let it fall through to the bottom of this
1423 * function, because TheMessage now contains the
1424 * encapsulated message instead of the top-level
1425 * message. Isn't that neat?
1430 if (do_proto) cprintf("%d msg %ld has no part %s\n",
1431 ERROR + MESSAGE_NOT_FOUND, msg_num, section);
1432 retcode = om_no_such_msg;
1437 /* Ok, output the message now */
1438 retcode = CtdlOutputPreLoadedMsg(
1440 headers_only, do_proto, crlf);
1441 CtdlFreeMessage(TheMessage);
1448 * Get a message off disk. (returns om_* values found in msgbase.h)
1451 int CtdlOutputPreLoadedMsg(
1452 struct CtdlMessage *TheMessage,
1453 int mode, /* how would you like that message? */
1454 int headers_only, /* eschew the message body? */
1455 int do_proto, /* do Citadel protocol responses? */
1456 int crlf /* Use CRLF newlines instead of LF? */
1462 char display_name[256];
1464 char *nl; /* newline string */
1466 int subject_found = 0;
1469 /* Buffers needed for RFC822 translation. These are all filled
1470 * using functions that are bounds-checked, and therefore we can
1471 * make them substantially smaller than SIZ.
1479 char datestamp[100];
1481 lprintf(CTDL_DEBUG, "CtdlOutputPreLoadedMsg(TheMessage=%s, %d, %d, %d, %d\n",
1482 ((TheMessage == NULL) ? "NULL" : "not null"),
1483 mode, headers_only, do_proto, crlf);
1485 strcpy(mid, "unknown");
1486 nl = (crlf ? "\r\n" : "\n");
1488 if (!is_valid_message(TheMessage)) {
1490 "ERROR: invalid preloaded message for output\n");
1491 return(om_no_such_msg);
1494 /* Are we downloading a MIME component? */
1495 if (mode == MT_DOWNLOAD) {
1496 if (TheMessage->cm_format_type != FMT_RFC822) {
1498 cprintf("%d This is not a MIME message.\n",
1499 ERROR + ILLEGAL_VALUE);
1500 } else if (CC->download_fp != NULL) {
1501 if (do_proto) cprintf(
1502 "%d You already have a download open.\n",
1503 ERROR + RESOURCE_BUSY);
1505 /* Parse the message text component */
1506 mptr = TheMessage->cm_fields['M'];
1507 mime_parser(mptr, NULL, *mime_download, NULL, NULL, NULL, 0);
1508 /* If there's no file open by this time, the requested
1509 * section wasn't found, so print an error
1511 if (CC->download_fp == NULL) {
1512 if (do_proto) cprintf(
1513 "%d Section %s not found.\n",
1514 ERROR + FILE_NOT_FOUND,
1515 CC->download_desired_section);
1518 return((CC->download_fp != NULL) ? om_ok : om_mime_error);
1521 /* MT_SPEW_SECTION is like MT_DOWNLOAD except it outputs the whole MIME part
1522 * in a single server operation instead of opening a download file.
1524 if (mode == MT_SPEW_SECTION) {
1525 if (TheMessage->cm_format_type != FMT_RFC822) {
1527 cprintf("%d This is not a MIME message.\n",
1528 ERROR + ILLEGAL_VALUE);
1530 /* Parse the message text component */
1533 mptr = TheMessage->cm_fields['M'];
1534 mime_parser(mptr, NULL, *mime_spew_section, NULL, NULL, (void *)&found_it, 0);
1535 /* If section wasn't found, print an error
1538 if (do_proto) cprintf(
1539 "%d Section %s not found.\n",
1540 ERROR + FILE_NOT_FOUND,
1541 CC->download_desired_section);
1544 return((CC->download_fp != NULL) ? om_ok : om_mime_error);
1547 /* now for the user-mode message reading loops */
1548 if (do_proto) cprintf("%d msg:\n", LISTING_FOLLOWS);
1550 /* Does the caller want to skip the headers? */
1551 if (headers_only == HEADERS_NONE) goto START_TEXT;
1553 /* Tell the client which format type we're using. */
1554 if ( (mode == MT_CITADEL) && (do_proto) ) {
1555 cprintf("type=%d\n", TheMessage->cm_format_type);
1558 /* nhdr=yes means that we're only displaying headers, no body */
1559 if ( (TheMessage->cm_anon_type == MES_ANONONLY)
1560 && (mode == MT_CITADEL)
1563 cprintf("nhdr=yes\n");
1566 /* begin header processing loop for Citadel message format */
1568 if ((mode == MT_CITADEL) || (mode == MT_MIME)) {
1570 safestrncpy(display_name, "<unknown>", sizeof display_name);
1571 if (TheMessage->cm_fields['A']) {
1572 strcpy(buf, TheMessage->cm_fields['A']);
1573 if (TheMessage->cm_anon_type == MES_ANONONLY) {
1574 safestrncpy(display_name, "****", sizeof display_name);
1576 else if (TheMessage->cm_anon_type == MES_ANONOPT) {
1577 safestrncpy(display_name, "anonymous", sizeof display_name);
1580 safestrncpy(display_name, buf, sizeof display_name);
1582 if ((is_room_aide())
1583 && ((TheMessage->cm_anon_type == MES_ANONONLY)
1584 || (TheMessage->cm_anon_type == MES_ANONOPT))) {
1585 size_t tmp = strlen(display_name);
1586 snprintf(&display_name[tmp],
1587 sizeof display_name - tmp,
1592 /* Don't show Internet address for users on the
1593 * local Citadel network.
1596 if (TheMessage->cm_fields['N'] != NULL)
1597 if (strlen(TheMessage->cm_fields['N']) > 0)
1598 if (haschar(TheMessage->cm_fields['N'], '.') == 0) {
1602 /* Now spew the header fields in the order we like them. */
1603 safestrncpy(allkeys, FORDER, sizeof allkeys);
1604 for (i=0; i<strlen(allkeys); ++i) {
1605 k = (int) allkeys[i];
1607 if ( (TheMessage->cm_fields[k] != NULL)
1608 && (msgkeys[k] != NULL) ) {
1610 if (do_proto) cprintf("%s=%s\n",
1614 else if ((k == 'F') && (suppress_f)) {
1617 /* Masquerade display name if needed */
1619 if (do_proto) cprintf("%s=%s\n",
1621 TheMessage->cm_fields[k]
1630 /* begin header processing loop for RFC822 transfer format */
1635 strcpy(snode, NODENAME);
1636 strcpy(lnode, HUMANNODE);
1637 if (mode == MT_RFC822) {
1638 for (i = 0; i < 256; ++i) {
1639 if (TheMessage->cm_fields[i]) {
1640 mptr = TheMessage->cm_fields[i];
1643 safestrncpy(luser, mptr, sizeof luser);
1644 safestrncpy(suser, mptr, sizeof suser);
1646 else if (i == 'Y') {
1647 cprintf("CC: %s%s", mptr, nl);
1649 else if (i == 'P') {
1650 cprintf("Return-Path: %s%s", mptr, nl);
1652 else if (i == 'V') {
1653 cprintf("Envelope-To: %s%s", mptr, nl);
1655 else if (i == 'U') {
1656 cprintf("Subject: %s%s", mptr, nl);
1660 safestrncpy(mid, mptr, sizeof mid);
1662 safestrncpy(lnode, mptr, sizeof lnode);
1664 safestrncpy(fuser, mptr, sizeof fuser);
1665 /* else if (i == 'O')
1666 cprintf("X-Citadel-Room: %s%s",
1669 safestrncpy(snode, mptr, sizeof snode);
1671 cprintf("To: %s%s", mptr, nl);
1672 else if (i == 'T') {
1673 datestring(datestamp, sizeof datestamp,
1674 atol(mptr), DATESTRING_RFC822);
1675 cprintf("Date: %s%s", datestamp, nl);
1679 if (subject_found == 0) {
1680 cprintf("Subject: (no subject)%s", nl);
1684 for (i=0; i<strlen(suser); ++i) {
1685 suser[i] = tolower(suser[i]);
1686 if (!isalnum(suser[i])) suser[i]='_';
1689 if (mode == MT_RFC822) {
1690 if (!strcasecmp(snode, NODENAME)) {
1691 safestrncpy(snode, FQDN, sizeof snode);
1694 /* Construct a fun message id */
1695 cprintf("Message-ID: <%s", mid);
1696 if (strchr(mid, '@')==NULL) {
1697 cprintf("@%s", snode);
1701 if (!is_room_aide() && (TheMessage->cm_anon_type == MES_ANONONLY)) {
1702 cprintf("From: \"----\" <x@x.org>%s", nl);
1704 else if (!is_room_aide() && (TheMessage->cm_anon_type == MES_ANONOPT)) {
1705 cprintf("From: \"anonymous\" <x@x.org>%s", nl);
1707 else if (strlen(fuser) > 0) {
1708 cprintf("From: \"%s\" <%s>%s", luser, fuser, nl);
1711 cprintf("From: \"%s\" <%s@%s>%s", luser, suser, snode, nl);
1714 cprintf("Organization: %s%s", lnode, nl);
1716 /* Blank line signifying RFC822 end-of-headers */
1717 if (TheMessage->cm_format_type != FMT_RFC822) {
1722 /* end header processing loop ... at this point, we're in the text */
1724 if (headers_only == HEADERS_FAST) goto DONE;
1725 mptr = TheMessage->cm_fields['M'];
1727 /* Tell the client about the MIME parts in this message */
1728 if (TheMessage->cm_format_type == FMT_RFC822) {
1729 if ( (mode == MT_CITADEL) || (mode == MT_MIME) ) {
1730 memset(&ma, 0, sizeof(struct ma_info));
1731 mime_parser(mptr, NULL,
1732 (do_proto ? *list_this_part : NULL),
1733 (do_proto ? *list_this_pref : NULL),
1734 (do_proto ? *list_this_suff : NULL),
1737 else if (mode == MT_RFC822) { /* unparsed RFC822 dump */
1738 char *start_of_text = NULL;
1739 start_of_text = strstr(mptr, "\n\r\n");
1740 if (start_of_text == NULL) start_of_text = strstr(mptr, "\n\n");
1741 if (start_of_text == NULL) start_of_text = mptr;
1743 start_of_text = strstr(start_of_text, "\n");
1745 while (ch=*mptr, ch!=0) {
1749 else switch(headers_only) {
1751 if (mptr >= start_of_text) {
1752 if (ch == 10) cprintf("%s", nl);
1753 else cprintf("%c", ch);
1757 if (mptr < start_of_text) {
1758 if (ch == 10) cprintf("%s", nl);
1759 else cprintf("%c", ch);
1763 if (ch == 10) cprintf("%s", nl);
1764 else cprintf("%c", ch);
1773 if (headers_only == HEADERS_ONLY) {
1777 /* signify start of msg text */
1778 if ( (mode == MT_CITADEL) || (mode == MT_MIME) ) {
1779 if (do_proto) cprintf("text\n");
1782 /* If the format type on disk is 1 (fixed-format), then we want
1783 * everything to be output completely literally ... regardless of
1784 * what message transfer format is in use.
1786 if (TheMessage->cm_format_type == FMT_FIXED) {
1787 if (mode == MT_MIME) {
1788 cprintf("Content-type: text/plain\n\n");
1791 while (ch = *mptr++, ch > 0) {
1794 if ((ch == 10) || (strlen(buf) > 250)) {
1795 cprintf("%s%s", buf, nl);
1798 buf[strlen(buf) + 1] = 0;
1799 buf[strlen(buf)] = ch;
1802 if (strlen(buf) > 0)
1803 cprintf("%s%s", buf, nl);
1806 /* If the message on disk is format 0 (Citadel vari-format), we
1807 * output using the formatter at 80 columns. This is the final output
1808 * form if the transfer format is RFC822, but if the transfer format
1809 * is Citadel proprietary, it'll still work, because the indentation
1810 * for new paragraphs is correct and the client will reformat the
1811 * message to the reader's screen width.
1813 if (TheMessage->cm_format_type == FMT_CITADEL) {
1814 if (mode == MT_MIME) {
1815 cprintf("Content-type: text/x-citadel-variformat\n\n");
1817 memfmout(mptr, 0, nl);
1820 /* If the message on disk is format 4 (MIME), we've gotta hand it
1821 * off to the MIME parser. The client has already been told that
1822 * this message is format 1 (fixed format), so the callback function
1823 * we use will display those parts as-is.
1825 if (TheMessage->cm_format_type == FMT_RFC822) {
1826 memset(&ma, 0, sizeof(struct ma_info));
1828 if (mode == MT_MIME) {
1829 ma.use_fo_hooks = 0;
1830 strcpy(ma.chosen_part, "1");
1831 ma.chosen_pref = 9999;
1832 mime_parser(mptr, NULL,
1833 *choose_preferred, *fixed_output_pre,
1834 *fixed_output_post, (void *)&ma, 0);
1835 mime_parser(mptr, NULL,
1836 *output_preferred, NULL, NULL, (void *)&ma, 0);
1839 ma.use_fo_hooks = 1;
1840 mime_parser(mptr, NULL,
1841 *fixed_output, *fixed_output_pre,
1842 *fixed_output_post, (void *)&ma, 0);
1847 DONE: /* now we're done */
1848 if (do_proto) cprintf("000\n");
1855 * display a message (mode 0 - Citadel proprietary)
1857 void cmd_msg0(char *cmdbuf)
1860 int headers_only = HEADERS_ALL;
1862 msgid = extract_long(cmdbuf, 0);
1863 headers_only = extract_int(cmdbuf, 1);
1865 CtdlOutputMsg(msgid, MT_CITADEL, headers_only, 1, 0, NULL);
1871 * display a message (mode 2 - RFC822)
1873 void cmd_msg2(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_RFC822, headers_only, 1, 1, NULL);
1887 * display a message (mode 3 - IGnet raw format - internal programs only)
1889 void cmd_msg3(char *cmdbuf)
1892 struct CtdlMessage *msg;
1895 if (CC->internal_pgm == 0) {
1896 cprintf("%d This command is for internal programs only.\n",
1897 ERROR + HIGHER_ACCESS_REQUIRED);
1901 msgnum = extract_long(cmdbuf, 0);
1902 msg = CtdlFetchMessage(msgnum, 1);
1904 cprintf("%d Message %ld not found.\n",
1905 ERROR + MESSAGE_NOT_FOUND, msgnum);
1909 serialize_message(&smr, msg);
1910 CtdlFreeMessage(msg);
1913 cprintf("%d Unable to serialize message\n",
1914 ERROR + INTERNAL_ERROR);
1918 cprintf("%d %ld\n", BINARY_FOLLOWS, (long)smr.len);
1919 client_write((char *)smr.ser, (int)smr.len);
1926 * Display a message using MIME content types
1928 void cmd_msg4(char *cmdbuf)
1933 msgid = extract_long(cmdbuf, 0);
1934 extract_token(section, cmdbuf, 1, '|', sizeof section);
1935 CtdlOutputMsg(msgid, MT_MIME, 0, 1, 0, (section[0] ? section : NULL) );
1941 * Client tells us its preferred message format(s)
1943 void cmd_msgp(char *cmdbuf)
1945 safestrncpy(CC->preferred_formats, cmdbuf,
1946 sizeof(CC->preferred_formats));
1947 cprintf("%d ok\n", CIT_OK);
1952 * Open a component of a MIME message as a download file
1954 void cmd_opna(char *cmdbuf)
1957 char desired_section[128];
1959 msgid = extract_long(cmdbuf, 0);
1960 extract_token(desired_section, cmdbuf, 1, '|', sizeof desired_section);
1961 safestrncpy(CC->download_desired_section, desired_section,
1962 sizeof CC->download_desired_section);
1963 CtdlOutputMsg(msgid, MT_DOWNLOAD, 0, 1, 1, NULL);
1968 * Open a component of a MIME message and transmit it all at once
1970 void cmd_dlat(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_SPEW_SECTION, 0, 1, 1, NULL);
1984 * Save one or more message pointers into a specified room
1985 * (Returns 0 for success, nonzero for failure)
1986 * roomname may be NULL to use the current room
1988 * Note that the 'supplied_msg' field may be set to NULL, in which case
1989 * the message will be fetched from disk, by number, if we need to perform
1990 * replication checks. This adds an additional database read, so if the
1991 * caller already has the message in memory then it should be supplied. (Obviously
1992 * this mode of operation only works if we're saving a single message.)
1994 int CtdlSaveMsgPointersInRoom(char *roomname, long newmsgidlist[], int num_newmsgs,
1995 int do_repl_check, struct CtdlMessage *supplied_msg)
1998 char hold_rm[ROOMNAMELEN];
1999 struct cdbdata *cdbfr;
2002 long highest_msg = 0L;
2005 struct CtdlMessage *msg = NULL;
2007 long *msgs_to_be_merged = NULL;
2008 int num_msgs_to_be_merged = 0;
2011 "CtdlSaveMsgPointersInRoom(room=%s, num_msgs=%d, repl=%d)\n",
2012 roomname, num_newmsgs, do_repl_check);
2014 strcpy(hold_rm, CC->room.QRname);
2017 if (newmsgidlist == NULL) return(ERROR + INTERNAL_ERROR);
2018 if (num_newmsgs < 1) return(ERROR + INTERNAL_ERROR);
2019 if (num_newmsgs > 1) supplied_msg = NULL;
2021 /* Now the regular stuff */
2022 if (lgetroom(&CC->room,
2023 ((roomname != NULL) ? roomname : CC->room.QRname) )
2025 lprintf(CTDL_ERR, "No such room <%s>\n", roomname);
2026 return(ERROR + ROOM_NOT_FOUND);
2030 msgs_to_be_merged = malloc(sizeof(long) * num_newmsgs);
2031 num_msgs_to_be_merged = 0;
2034 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long));
2035 if (cdbfr == NULL) {
2039 msglist = (long *) cdbfr->ptr;
2040 cdbfr->ptr = NULL; /* CtdlSaveMsgPointerInRoom() now owns this memory */
2041 num_msgs = cdbfr->len / sizeof(long);
2046 /* Create a list of msgid's which were supplied by the caller, but do
2047 * not already exist in the target room. It is absolutely taboo to
2048 * have more than one reference to the same message in a room.
2050 for (i=0; i<num_newmsgs; ++i) {
2052 if (num_msgs > 0) for (j=0; j<num_msgs; ++j) {
2053 if (msglist[j] == newmsgidlist[i]) {
2058 msgs_to_be_merged[num_msgs_to_be_merged++] = newmsgidlist[i];
2062 lprintf(9, "%d unique messages to be merged\n", num_msgs_to_be_merged);
2065 * Now merge the new messages
2067 msglist = realloc(msglist, (sizeof(long) * (num_msgs + num_msgs_to_be_merged)) );
2068 if (msglist == NULL) {
2069 lprintf(CTDL_ALERT, "ERROR: can't realloc message list!\n");
2071 memcpy(&msglist[num_msgs], msgs_to_be_merged, (sizeof(long) * num_msgs_to_be_merged) );
2072 num_msgs += num_msgs_to_be_merged;
2074 /* Sort the message list, so all the msgid's are in order */
2075 num_msgs = sort_msglist(msglist, num_msgs);
2077 /* Determine the highest message number */
2078 highest_msg = msglist[num_msgs - 1];
2080 /* Write it back to disk. */
2081 cdb_store(CDB_MSGLISTS, &CC->room.QRnumber, (int)sizeof(long),
2082 msglist, (int)(num_msgs * sizeof(long)));
2084 /* Free up the memory we used. */
2087 /* Update the highest-message pointer and unlock the room. */
2088 CC->room.QRhighest = highest_msg;
2089 lputroom(&CC->room);
2091 /* Perform replication checks if necessary */
2092 if ( (DoesThisRoomNeedEuidIndexing(&CC->room)) && (do_repl_check) ) {
2093 lprintf(CTDL_DEBUG, "CtdlSaveMsgPointerInRoom() doing repl checks\n");
2095 for (i=0; i<num_msgs_to_be_merged; ++i) {
2096 msgid = msgs_to_be_merged[i];
2098 if (supplied_msg != NULL) {
2102 msg = CtdlFetchMessage(msgid, 0);
2106 ReplicationChecks(msg);
2108 /* If the message has an Exclusive ID, index that... */
2109 if (msg->cm_fields['E'] != NULL) {
2110 index_message_by_euid(msg->cm_fields['E'], &CC->room, msgid);
2113 /* Free up the memory we may have allocated */
2114 if (msg != supplied_msg) {
2115 CtdlFreeMessage(msg);
2123 lprintf(CTDL_DEBUG, "CtdlSaveMsgPointerInRoom() skips repl checks\n");
2126 /* Submit this room for net processing */
2127 network_queue_room(&CC->room, NULL);
2129 #ifdef HAVE_LIBSIEVE
2130 /* If this is someone's inbox, submit the room for sieve processing */
2131 if (!strcasecmp(&CC->room.QRname[11], MAILROOM)) {
2132 sieve_queue_room(&CC->room);
2134 #endif /* HAVE_LIBSIEVE */
2136 /* Go back to the room we were in before we wandered here... */
2137 getroom(&CC->room, hold_rm);
2139 /* Bump the reference count for all messages which were merged */
2140 for (i=0; i<num_msgs_to_be_merged; ++i) {
2141 AdjRefCount(msgs_to_be_merged[i], +1);
2144 /* Free up memory... */
2145 if (msgs_to_be_merged != NULL) {
2146 free(msgs_to_be_merged);
2149 /* Return success. */
2155 * This is the same as CtdlSaveMsgPointersInRoom() but it only accepts
2158 int CtdlSaveMsgPointerInRoom(char *roomname, long msgid,
2159 int do_repl_check, struct CtdlMessage *supplied_msg)
2161 return CtdlSaveMsgPointersInRoom(roomname, &msgid, 1, do_repl_check, supplied_msg);
2168 * Message base operation to save a new message to the message store
2169 * (returns new message number)
2171 * This is the back end for CtdlSubmitMsg() and should not be directly
2172 * called by server-side modules.
2175 long send_message(struct CtdlMessage *msg) {
2183 /* Get a new message number */
2184 newmsgid = get_new_message_number();
2185 snprintf(msgidbuf, sizeof msgidbuf, "%010ld@%s", newmsgid, config.c_fqdn);
2187 /* Generate an ID if we don't have one already */
2188 if (msg->cm_fields['I']==NULL) {
2189 msg->cm_fields['I'] = strdup(msgidbuf);
2192 /* If the message is big, set its body aside for storage elsewhere */
2193 if (msg->cm_fields['M'] != NULL) {
2194 if (strlen(msg->cm_fields['M']) > BIGMSG) {
2196 holdM = msg->cm_fields['M'];
2197 msg->cm_fields['M'] = NULL;
2201 /* Serialize our data structure for storage in the database */
2202 serialize_message(&smr, msg);
2205 msg->cm_fields['M'] = holdM;
2209 cprintf("%d Unable to serialize message\n",
2210 ERROR + INTERNAL_ERROR);
2214 /* Write our little bundle of joy into the message base */
2215 if (cdb_store(CDB_MSGMAIN, &newmsgid, (int)sizeof(long),
2216 smr.ser, smr.len) < 0) {
2217 lprintf(CTDL_ERR, "Can't store message\n");
2221 cdb_store(CDB_BIGMSGS,
2231 /* Free the memory we used for the serialized message */
2234 /* Return the *local* message ID to the caller
2235 * (even if we're storing an incoming network message)
2243 * Serialize a struct CtdlMessage into the format used on disk and network.
2245 * This function loads up a "struct ser_ret" (defined in server.h) which
2246 * contains the length of the serialized message and a pointer to the
2247 * serialized message in memory. THE LATTER MUST BE FREED BY THE CALLER.
2249 void serialize_message(struct ser_ret *ret, /* return values */
2250 struct CtdlMessage *msg) /* unserialized msg */
2252 size_t wlen, fieldlen;
2254 static char *forder = FORDER;
2257 * Check for valid message format
2259 if (is_valid_message(msg) == 0) {
2260 lprintf(CTDL_ERR, "serialize_message() aborting due to invalid message\n");
2267 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL)
2268 ret->len = ret->len +
2269 strlen(msg->cm_fields[(int)forder[i]]) + 2;
2271 ret->ser = malloc(ret->len);
2272 if (ret->ser == NULL) {
2273 lprintf(CTDL_ERR, "serialize_message() malloc(%ld) failed: %s\n",
2274 (long)ret->len, strerror(errno));
2281 ret->ser[1] = msg->cm_anon_type;
2282 ret->ser[2] = msg->cm_format_type;
2285 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL) {
2286 fieldlen = strlen(msg->cm_fields[(int)forder[i]]);
2287 ret->ser[wlen++] = (char)forder[i];
2288 safestrncpy((char *)&ret->ser[wlen], msg->cm_fields[(int)forder[i]], fieldlen+1);
2289 wlen = wlen + fieldlen + 1;
2291 if (ret->len != wlen) lprintf(CTDL_ERR, "ERROR: len=%ld wlen=%ld\n",
2292 (long)ret->len, (long)wlen);
2300 * Check to see if any messages already exist in the current room which
2301 * carry the same Exclusive ID as this one. If any are found, delete them.
2303 void ReplicationChecks(struct CtdlMessage *msg) {
2304 long old_msgnum = (-1L);
2306 if (DoesThisRoomNeedEuidIndexing(&CC->room) == 0) return;
2308 lprintf(CTDL_DEBUG, "Performing replication checks in <%s>\n",
2311 /* No exclusive id? Don't do anything. */
2312 if (msg == NULL) return;
2313 if (msg->cm_fields['E'] == NULL) return;
2314 if (strlen(msg->cm_fields['E']) == 0) return;
2315 /*lprintf(CTDL_DEBUG, "Exclusive ID: <%s> for room <%s>\n",
2316 msg->cm_fields['E'], CC->room.QRname);*/
2318 old_msgnum = locate_message_by_euid(msg->cm_fields['E'], &CC->room);
2319 if (old_msgnum > 0L) {
2320 lprintf(CTDL_DEBUG, "ReplicationChecks() replacing message %ld\n", old_msgnum);
2321 CtdlDeleteMessages(CC->room.QRname, &old_msgnum, 1, "", 0);
2328 * Save a message to disk and submit it into the delivery system.
2330 long CtdlSubmitMsg(struct CtdlMessage *msg, /* message to save */
2331 struct recptypes *recps, /* recipients (if mail) */
2332 char *force /* force a particular room? */
2334 char submit_filename[128];
2335 char generated_timestamp[32];
2336 char hold_rm[ROOMNAMELEN];
2337 char actual_rm[ROOMNAMELEN];
2338 char force_room[ROOMNAMELEN];
2339 char content_type[SIZ]; /* We have to learn this */
2340 char recipient[SIZ];
2343 struct ctdluser userbuf;
2345 struct MetaData smi;
2346 FILE *network_fp = NULL;
2347 static int seqnum = 1;
2348 struct CtdlMessage *imsg = NULL;
2351 char *hold_R, *hold_D;
2352 char *collected_addresses = NULL;
2353 struct addresses_to_be_filed *aptr = NULL;
2354 char *saved_rfc822_version = NULL;
2355 int qualified_for_journaling = 0;
2357 lprintf(CTDL_DEBUG, "CtdlSubmitMsg() called\n");
2358 if (is_valid_message(msg) == 0) return(-1); /* self check */
2360 /* If this message has no timestamp, we take the liberty of
2361 * giving it one, right now.
2363 if (msg->cm_fields['T'] == NULL) {
2364 snprintf(generated_timestamp, sizeof generated_timestamp, "%ld", (long)time(NULL));
2365 msg->cm_fields['T'] = strdup(generated_timestamp);
2368 /* If this message has no path, we generate one.
2370 if (msg->cm_fields['P'] == NULL) {
2371 if (msg->cm_fields['A'] != NULL) {
2372 msg->cm_fields['P'] = strdup(msg->cm_fields['A']);
2373 for (a=0; a<strlen(msg->cm_fields['P']); ++a) {
2374 if (isspace(msg->cm_fields['P'][a])) {
2375 msg->cm_fields['P'][a] = ' ';
2380 msg->cm_fields['P'] = strdup("unknown");
2384 if (force == NULL) {
2385 strcpy(force_room, "");
2388 strcpy(force_room, force);
2391 /* Learn about what's inside, because it's what's inside that counts */
2392 if (msg->cm_fields['M'] == NULL) {
2393 lprintf(CTDL_ERR, "ERROR: attempt to save message with NULL body\n");
2397 switch (msg->cm_format_type) {
2399 strcpy(content_type, "text/x-citadel-variformat");
2402 strcpy(content_type, "text/plain");
2405 strcpy(content_type, "text/plain");
2406 mptr = bmstrcasestr(msg->cm_fields['M'], "Content-type: ");
2408 safestrncpy(content_type, &mptr[14],
2409 sizeof content_type);
2410 for (a = 0; a < strlen(content_type); ++a) {
2411 if ((content_type[a] == ';')
2412 || (content_type[a] == ' ')
2413 || (content_type[a] == 13)
2414 || (content_type[a] == 10)) {
2415 content_type[a] = 0;
2421 /* Goto the correct room */
2422 lprintf(CTDL_DEBUG, "Selected room %s\n", (recps) ? CC->room.QRname : SENTITEMS);
2423 strcpy(hold_rm, CC->room.QRname);
2424 strcpy(actual_rm, CC->room.QRname);
2425 if (recps != NULL) {
2426 strcpy(actual_rm, SENTITEMS);
2429 /* If the user is a twit, move to the twit room for posting */
2431 if (CC->user.axlevel == 2) {
2432 strcpy(hold_rm, actual_rm);
2433 strcpy(actual_rm, config.c_twitroom);
2434 lprintf(CTDL_DEBUG, "Diverting to twit room\n");
2438 /* ...or if this message is destined for Aide> then go there. */
2439 if (strlen(force_room) > 0) {
2440 strcpy(actual_rm, force_room);
2443 lprintf(CTDL_DEBUG, "Final selection: %s\n", actual_rm);
2444 if (strcasecmp(actual_rm, CC->room.QRname)) {
2445 /* getroom(&CC->room, actual_rm); */
2446 usergoto(actual_rm, 0, 1, NULL, NULL);
2450 * If this message has no O (room) field, generate one.
2452 if (msg->cm_fields['O'] == NULL) {
2453 msg->cm_fields['O'] = strdup(CC->room.QRname);
2456 /* Perform "before save" hooks (aborting if any return nonzero) */
2457 lprintf(CTDL_DEBUG, "Performing before-save hooks\n");
2458 if (PerformMessageHooks(msg, EVT_BEFORESAVE) > 0) return(-3);
2461 * If this message has an Exclusive ID, and the room is replication
2462 * checking enabled, then do replication checks.
2464 if (DoesThisRoomNeedEuidIndexing(&CC->room)) {
2465 ReplicationChecks(msg);
2468 /* Save it to disk */
2469 lprintf(CTDL_DEBUG, "Saving to disk\n");
2470 newmsgid = send_message(msg);
2471 if (newmsgid <= 0L) return(-5);
2473 /* Write a supplemental message info record. This doesn't have to
2474 * be a critical section because nobody else knows about this message
2477 lprintf(CTDL_DEBUG, "Creating MetaData record\n");
2478 memset(&smi, 0, sizeof(struct MetaData));
2479 smi.meta_msgnum = newmsgid;
2480 smi.meta_refcount = 0;
2481 safestrncpy(smi.meta_content_type, content_type,
2482 sizeof smi.meta_content_type);
2485 * Measure how big this message will be when rendered as RFC822.
2486 * We do this for two reasons:
2487 * 1. We need the RFC822 length for the new metadata record, so the
2488 * POP and IMAP services don't have to calculate message lengths
2489 * while the user is waiting (multiplied by potentially hundreds
2490 * or thousands of messages).
2491 * 2. If journaling is enabled, we will need an RFC822 version of the
2492 * message to attach to the journalized copy.
2494 if (CC->redirect_buffer != NULL) {
2495 lprintf(CTDL_ALERT, "CC->redirect_buffer is not NULL during message submission!\n");
2498 CC->redirect_buffer = malloc(SIZ);
2499 CC->redirect_len = 0;
2500 CC->redirect_alloc = SIZ;
2501 CtdlOutputPreLoadedMsg(msg, MT_RFC822, HEADERS_ALL, 0, 1);
2502 smi.meta_rfc822_length = CC->redirect_len;
2503 saved_rfc822_version = CC->redirect_buffer;
2504 CC->redirect_buffer = NULL;
2505 CC->redirect_len = 0;
2506 CC->redirect_alloc = 0;
2510 /* Now figure out where to store the pointers */
2511 lprintf(CTDL_DEBUG, "Storing pointers\n");
2513 /* If this is being done by the networker delivering a private
2514 * message, we want to BYPASS saving the sender's copy (because there
2515 * is no local sender; it would otherwise go to the Trashcan).
2517 if ((!CC->internal_pgm) || (recps == NULL)) {
2518 if (CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 1, msg) != 0) {
2519 lprintf(CTDL_ERR, "ERROR saving message pointer!\n");
2520 CtdlSaveMsgPointerInRoom(config.c_aideroom, newmsgid, 0, msg);
2524 /* For internet mail, drop a copy in the outbound queue room */
2526 if (recps->num_internet > 0) {
2527 CtdlSaveMsgPointerInRoom(SMTP_SPOOLOUT_ROOM, newmsgid, 0, msg);
2530 /* If other rooms are specified, drop them there too. */
2532 if (recps->num_room > 0)
2533 for (i=0; i<num_tokens(recps->recp_room, '|'); ++i) {
2534 extract_token(recipient, recps->recp_room, i,
2535 '|', sizeof recipient);
2536 lprintf(CTDL_DEBUG, "Delivering to room <%s>\n", recipient);
2537 CtdlSaveMsgPointerInRoom(recipient, newmsgid, 0, msg);
2540 /* Bump this user's messages posted counter. */
2541 lprintf(CTDL_DEBUG, "Updating user\n");
2542 lgetuser(&CC->user, CC->curr_user);
2543 CC->user.posted = CC->user.posted + 1;
2544 lputuser(&CC->user);
2546 /* If this is private, local mail, make a copy in the
2547 * recipient's mailbox and bump the reference count.
2550 if (recps->num_local > 0)
2551 for (i=0; i<num_tokens(recps->recp_local, '|'); ++i) {
2552 extract_token(recipient, recps->recp_local, i,
2553 '|', sizeof recipient);
2554 lprintf(CTDL_DEBUG, "Delivering private local mail to <%s>\n",
2556 if (getuser(&userbuf, recipient) == 0) {
2557 MailboxName(actual_rm, sizeof actual_rm,
2558 &userbuf, MAILROOM);
2559 CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 0, msg);
2560 BumpNewMailCounter(userbuf.usernum);
2563 lprintf(CTDL_DEBUG, "No user <%s>\n", recipient);
2564 CtdlSaveMsgPointerInRoom(config.c_aideroom,
2569 /* Perform "after save" hooks */
2570 lprintf(CTDL_DEBUG, "Performing after-save hooks\n");
2571 PerformMessageHooks(msg, EVT_AFTERSAVE);
2573 /* For IGnet mail, we have to save a new copy into the spooler for
2574 * each recipient, with the R and D fields set to the recipient and
2575 * destination-node. This has two ugly side effects: all other
2576 * recipients end up being unlisted in this recipient's copy of the
2577 * message, and it has to deliver multiple messages to the same
2578 * node. We'll revisit this again in a year or so when everyone has
2579 * a network spool receiver that can handle the new style messages.
2582 if (recps->num_ignet > 0)
2583 for (i=0; i<num_tokens(recps->recp_ignet, '|'); ++i) {
2584 extract_token(recipient, recps->recp_ignet, i,
2585 '|', sizeof recipient);
2587 hold_R = msg->cm_fields['R'];
2588 hold_D = msg->cm_fields['D'];
2589 msg->cm_fields['R'] = malloc(SIZ);
2590 msg->cm_fields['D'] = malloc(128);
2591 extract_token(msg->cm_fields['R'], recipient, 0, '@', SIZ);
2592 extract_token(msg->cm_fields['D'], recipient, 1, '@', 128);
2594 serialize_message(&smr, msg);
2596 snprintf(submit_filename, sizeof submit_filename,
2597 "%s/netmail.%04lx.%04x.%04x",
2599 (long) getpid(), CC->cs_pid, ++seqnum);
2600 network_fp = fopen(submit_filename, "wb+");
2601 if (network_fp != NULL) {
2602 fwrite(smr.ser, smr.len, 1, network_fp);
2608 free(msg->cm_fields['R']);
2609 free(msg->cm_fields['D']);
2610 msg->cm_fields['R'] = hold_R;
2611 msg->cm_fields['D'] = hold_D;
2614 /* Go back to the room we started from */
2615 lprintf(CTDL_DEBUG, "Returning to original room %s\n", hold_rm);
2616 if (strcasecmp(hold_rm, CC->room.QRname))
2617 /* getroom(&CC->room, hold_rm); */
2618 usergoto(hold_rm, 0, 1, NULL, NULL);
2620 /* For internet mail, generate delivery instructions.
2621 * Yes, this is recursive. Deal with it. Infinite recursion does
2622 * not happen because the delivery instructions message does not
2623 * contain a recipient.
2626 if (recps->num_internet > 0) {
2627 lprintf(CTDL_DEBUG, "Generating delivery instructions\n");
2628 instr = malloc(SIZ * 2);
2629 snprintf(instr, SIZ * 2,
2630 "Content-type: %s\n\nmsgid|%ld\nsubmitted|%ld\n"
2632 SPOOLMIME, newmsgid, (long)time(NULL),
2633 msg->cm_fields['A'], msg->cm_fields['N']
2636 for (i=0; i<num_tokens(recps->recp_internet, '|'); ++i) {
2637 size_t tmp = strlen(instr);
2638 extract_token(recipient, recps->recp_internet,
2639 i, '|', sizeof recipient);
2640 snprintf(&instr[tmp], SIZ * 2 - tmp,
2641 "remote|%s|0||\n", recipient);
2644 imsg = malloc(sizeof(struct CtdlMessage));
2645 memset(imsg, 0, sizeof(struct CtdlMessage));
2646 imsg->cm_magic = CTDLMESSAGE_MAGIC;
2647 imsg->cm_anon_type = MES_NORMAL;
2648 imsg->cm_format_type = FMT_RFC822;
2649 imsg->cm_fields['A'] = strdup("Citadel");
2650 imsg->cm_fields['J'] = strdup("do not journal");
2651 imsg->cm_fields['M'] = instr;
2652 CtdlSubmitMsg(imsg, NULL, SMTP_SPOOLOUT_ROOM);
2653 CtdlFreeMessage(imsg);
2657 * Any addresses to harvest for someone's address book?
2659 if ( (CC->logged_in) && (recps != NULL) ) {
2660 collected_addresses = harvest_collected_addresses(msg);
2663 if (collected_addresses != NULL) {
2664 begin_critical_section(S_ATBF);
2665 aptr = (struct addresses_to_be_filed *)
2666 malloc(sizeof(struct addresses_to_be_filed));
2668 MailboxName(actual_rm, sizeof actual_rm,
2669 &CC->user, USERCONTACTSROOM);
2670 aptr->roomname = strdup(actual_rm);
2671 aptr->collected_addresses = collected_addresses;
2673 end_critical_section(S_ATBF);
2677 * Determine whether this message qualifies for journaling.
2679 if (msg->cm_fields['J'] != NULL) {
2680 qualified_for_journaling = 0;
2683 if (recps == NULL) {
2684 qualified_for_journaling = config.c_journal_pubmsgs;
2686 else if (recps->num_local + recps->num_ignet + recps->num_internet > 0) {
2687 qualified_for_journaling = config.c_journal_email;
2690 qualified_for_journaling = config.c_journal_pubmsgs;
2695 * Do we have to perform journaling? If so, hand off the saved
2696 * RFC822 version will be handed off to the journaler for background
2697 * submit. Otherwise, we have to free the memory ourselves.
2699 if (saved_rfc822_version != NULL) {
2700 if (qualified_for_journaling) {
2701 JournalBackgroundSubmit(msg, saved_rfc822_version, recps);
2704 free(saved_rfc822_version);
2717 * Convenience function for generating small administrative messages.
2719 void quickie_message(char *from, char *fromaddr, char *to, char *room, char *text,
2720 int format_type, char *subject)
2722 struct CtdlMessage *msg;
2723 struct recptypes *recp = NULL;
2725 msg = malloc(sizeof(struct CtdlMessage));
2726 memset(msg, 0, sizeof(struct CtdlMessage));
2727 msg->cm_magic = CTDLMESSAGE_MAGIC;
2728 msg->cm_anon_type = MES_NORMAL;
2729 msg->cm_format_type = format_type;
2732 msg->cm_fields['A'] = strdup(from);
2734 else if (fromaddr != NULL) {
2735 msg->cm_fields['A'] = strdup(fromaddr);
2736 if (strchr(msg->cm_fields['A'], '@')) {
2737 *strchr(msg->cm_fields['A'], '@') = 0;
2741 msg->cm_fields['A'] = strdup("Citadel");
2744 if (fromaddr != NULL) msg->cm_fields['F'] = strdup(fromaddr);
2745 if (room != NULL) msg->cm_fields['O'] = strdup(room);
2746 msg->cm_fields['N'] = strdup(NODENAME);
2748 msg->cm_fields['R'] = strdup(to);
2749 recp = validate_recipients(to);
2751 if (subject != NULL) {
2752 msg->cm_fields['U'] = strdup(subject);
2754 msg->cm_fields['M'] = strdup(text);
2756 CtdlSubmitMsg(msg, recp, room);
2757 CtdlFreeMessage(msg);
2758 if (recp != NULL) free(recp);
2764 * Back end function used by CtdlMakeMessage() and similar functions
2766 char *CtdlReadMessageBody(char *terminator, /* token signalling EOT */
2767 size_t maxlen, /* maximum message length */
2768 char *exist, /* if non-null, append to it;
2769 exist is ALWAYS freed */
2770 int crlf /* CRLF newlines instead of LF */
2774 size_t message_len = 0;
2775 size_t buffer_len = 0;
2782 if (exist == NULL) {
2789 message_len = strlen(exist);
2790 buffer_len = message_len + 4096;
2791 m = realloc(exist, buffer_len);
2798 /* Do we need to change leading ".." to "." for SMTP escaping? */
2799 if (!strcmp(terminator, ".")) {
2803 /* flush the input if we have nowhere to store it */
2808 /* read in the lines of message text one by one */
2810 if (client_getln(buf, (sizeof buf - 3)) < 1) finished = 1;
2811 if (!strcmp(buf, terminator)) finished = 1;
2813 strcat(buf, "\r\n");
2819 /* Unescape SMTP-style input of two dots at the beginning of the line */
2821 if (!strncmp(buf, "..", 2)) {
2822 strcpy(buf, &buf[1]);
2826 if ( (!flushing) && (!finished) ) {
2827 /* Measure the line */
2828 linelen = strlen(buf);
2830 /* augment the buffer if we have to */
2831 if ((message_len + linelen) >= buffer_len) {
2832 ptr = realloc(m, (buffer_len * 2) );
2833 if (ptr == NULL) { /* flush if can't allocate */
2836 buffer_len = (buffer_len * 2);
2838 lprintf(CTDL_DEBUG, "buffer_len is now %ld\n", (long)buffer_len);
2842 /* Add the new line to the buffer. NOTE: this loop must avoid
2843 * using functions like strcat() and strlen() because they
2844 * traverse the entire buffer upon every call, and doing that
2845 * for a multi-megabyte message slows it down beyond usability.
2847 strcpy(&m[message_len], buf);
2848 message_len += linelen;
2851 /* if we've hit the max msg length, flush the rest */
2852 if (message_len >= maxlen) flushing = 1;
2854 } while (!finished);
2862 * Build a binary message to be saved on disk.
2863 * (NOTE: if you supply 'preformatted_text', the buffer you give it
2864 * will become part of the message. This means you are no longer
2865 * responsible for managing that memory -- it will be freed along with
2866 * the rest of the fields when CtdlFreeMessage() is called.)
2869 struct CtdlMessage *CtdlMakeMessage(
2870 struct ctdluser *author, /* author's user structure */
2871 char *recipient, /* NULL if it's not mail */
2872 char *recp_cc, /* NULL if it's not mail */
2873 char *room, /* room where it's going */
2874 int type, /* see MES_ types in header file */
2875 int format_type, /* variformat, plain text, MIME... */
2876 char *fake_name, /* who we're masquerading as */
2877 char *subject, /* Subject (optional) */
2878 char *supplied_euid, /* ...or NULL if this is irrelevant */
2879 char *preformatted_text /* ...or NULL to read text from client */
2881 char dest_node[SIZ];
2883 struct CtdlMessage *msg;
2885 msg = malloc(sizeof(struct CtdlMessage));
2886 memset(msg, 0, sizeof(struct CtdlMessage));
2887 msg->cm_magic = CTDLMESSAGE_MAGIC;
2888 msg->cm_anon_type = type;
2889 msg->cm_format_type = format_type;
2891 /* Don't confuse the poor folks if it's not routed mail. */
2892 strcpy(dest_node, "");
2897 snprintf(buf, sizeof buf, "cit%ld", author->usernum); /* Path */
2898 msg->cm_fields['P'] = strdup(buf);
2900 snprintf(buf, sizeof buf, "%ld", (long)time(NULL)); /* timestamp */
2901 msg->cm_fields['T'] = strdup(buf);
2903 if (fake_name[0]) /* author */
2904 msg->cm_fields['A'] = strdup(fake_name);
2906 msg->cm_fields['A'] = strdup(author->fullname);
2908 if (CC->room.QRflags & QR_MAILBOX) { /* room */
2909 msg->cm_fields['O'] = strdup(&CC->room.QRname[11]);
2912 msg->cm_fields['O'] = strdup(CC->room.QRname);
2915 msg->cm_fields['N'] = strdup(NODENAME); /* nodename */
2916 msg->cm_fields['H'] = strdup(HUMANNODE); /* hnodename */
2918 if (recipient[0] != 0) {
2919 msg->cm_fields['R'] = strdup(recipient);
2921 if (recp_cc[0] != 0) {
2922 msg->cm_fields['Y'] = strdup(recp_cc);
2924 if (dest_node[0] != 0) {
2925 msg->cm_fields['D'] = strdup(dest_node);
2928 if ( (author == &CC->user) && (strlen(CC->cs_inet_email) > 0) ) {
2929 msg->cm_fields['F'] = strdup(CC->cs_inet_email);
2932 if (subject != NULL) {
2934 if (strlen(subject) > 0) {
2935 msg->cm_fields['U'] = strdup(subject);
2939 if (supplied_euid != NULL) {
2940 msg->cm_fields['E'] = strdup(supplied_euid);
2943 if (preformatted_text != NULL) {
2944 msg->cm_fields['M'] = preformatted_text;
2947 msg->cm_fields['M'] = CtdlReadMessageBody("000",
2948 config.c_maxmsglen, NULL, 0);
2956 * Check to see whether we have permission to post a message in the current
2957 * room. Returns a *CITADEL ERROR CODE* and puts a message in errmsgbuf, or
2958 * returns 0 on success.
2960 int CtdlDoIHavePermissionToPostInThisRoom(char *errmsgbuf, size_t n) {
2962 if (!(CC->logged_in)) {
2963 snprintf(errmsgbuf, n, "Not logged in.");
2964 return (ERROR + NOT_LOGGED_IN);
2967 if ((CC->user.axlevel < 2)
2968 && ((CC->room.QRflags & QR_MAILBOX) == 0)) {
2969 snprintf(errmsgbuf, n, "Need to be validated to enter "
2970 "(except in %s> to sysop)", MAILROOM);
2971 return (ERROR + HIGHER_ACCESS_REQUIRED);
2974 if ((CC->user.axlevel < 4)
2975 && (CC->room.QRflags & QR_NETWORK)) {
2976 snprintf(errmsgbuf, n, "Need net privileges to enter here.");
2977 return (ERROR + HIGHER_ACCESS_REQUIRED);
2980 if ((CC->user.axlevel < 6)
2981 && (CC->room.QRflags & QR_READONLY)) {
2982 snprintf(errmsgbuf, n, "Sorry, this is a read-only room.");
2983 return (ERROR + HIGHER_ACCESS_REQUIRED);
2986 strcpy(errmsgbuf, "Ok");
2992 * Check to see if the specified user has Internet mail permission
2993 * (returns nonzero if permission is granted)
2995 int CtdlCheckInternetMailPermission(struct ctdluser *who) {
2997 /* Do not allow twits to send Internet mail */
2998 if (who->axlevel <= 2) return(0);
3000 /* Globally enabled? */
3001 if (config.c_restrict == 0) return(1);
3003 /* User flagged ok? */
3004 if (who->flags & US_INTERNET) return(2);
3006 /* Aide level access? */
3007 if (who->axlevel >= 6) return(3);
3009 /* No mail for you! */
3015 * Validate recipients, count delivery types and errors, and handle aliasing
3016 * FIXME check for dupes!!!!!
3017 * Returns 0 if all addresses are ok, ret->num_error = -1 if no addresses
3018 * were specified, or the number of addresses found invalid.
3019 * caller needs to free the result.
3021 struct recptypes *validate_recipients(char *supplied_recipients) {
3022 struct recptypes *ret;
3023 char recipients[SIZ];
3024 char this_recp[256];
3025 char this_recp_cooked[256];
3031 struct ctdluser tempUS;
3032 struct ctdlroom tempQR;
3036 ret = (struct recptypes *) malloc(sizeof(struct recptypes));
3037 if (ret == NULL) return(NULL);
3038 memset(ret, 0, sizeof(struct recptypes));
3041 ret->num_internet = 0;
3046 if (supplied_recipients == NULL) {
3047 strcpy(recipients, "");
3050 safestrncpy(recipients, supplied_recipients, sizeof recipients);
3053 /* Change all valid separator characters to commas */
3054 for (i=0; i<strlen(recipients); ++i) {
3055 if ((recipients[i] == ';') || (recipients[i] == '|')) {
3056 recipients[i] = ',';
3060 /* Now start extracting recipients... */
3062 while (strlen(recipients) > 0) {
3064 for (i=0; i<=strlen(recipients); ++i) {
3065 if (recipients[i] == '\"') in_quotes = 1 - in_quotes;
3066 if ( ( (recipients[i] == ',') && (!in_quotes) ) || (recipients[i] == 0) ) {
3067 safestrncpy(this_recp, recipients, i+1);
3069 if (recipients[i] == ',') {
3070 strcpy(recipients, &recipients[i+1]);
3073 strcpy(recipients, "");
3080 lprintf(CTDL_DEBUG, "Evaluating recipient #%d: %s\n", num_recps, this_recp);
3082 mailtype = alias(this_recp);
3083 mailtype = alias(this_recp);
3084 mailtype = alias(this_recp);
3085 for (j=0; j<=strlen(this_recp); ++j) {
3086 if (this_recp[j]=='_') {
3087 this_recp_cooked[j] = ' ';
3090 this_recp_cooked[j] = this_recp[j];
3096 if (!strcasecmp(this_recp, "sysop")) {
3098 strcpy(this_recp, config.c_aideroom);
3099 if (strlen(ret->recp_room) > 0) {
3100 strcat(ret->recp_room, "|");
3102 strcat(ret->recp_room, this_recp);
3104 else if (getuser(&tempUS, this_recp) == 0) {
3106 strcpy(this_recp, tempUS.fullname);
3107 if (strlen(ret->recp_local) > 0) {
3108 strcat(ret->recp_local, "|");
3110 strcat(ret->recp_local, this_recp);
3112 else if (getuser(&tempUS, this_recp_cooked) == 0) {
3114 strcpy(this_recp, tempUS.fullname);
3115 if (strlen(ret->recp_local) > 0) {
3116 strcat(ret->recp_local, "|");
3118 strcat(ret->recp_local, this_recp);
3120 else if ( (!strncasecmp(this_recp, "room_", 5))
3121 && (!getroom(&tempQR, &this_recp_cooked[5])) ) {
3123 if (strlen(ret->recp_room) > 0) {
3124 strcat(ret->recp_room, "|");
3126 strcat(ret->recp_room, &this_recp_cooked[5]);
3134 /* Yes, you're reading this correctly: if the target
3135 * domain points back to the local system or an attached
3136 * Citadel directory, the address is invalid. That's
3137 * because if the address were valid, we would have
3138 * already translated it to a local address by now.
3140 if (IsDirectory(this_recp)) {
3145 ++ret->num_internet;
3146 if (strlen(ret->recp_internet) > 0) {
3147 strcat(ret->recp_internet, "|");
3149 strcat(ret->recp_internet, this_recp);
3154 if (strlen(ret->recp_ignet) > 0) {
3155 strcat(ret->recp_ignet, "|");
3157 strcat(ret->recp_ignet, this_recp);
3165 if (strlen(ret->errormsg) == 0) {
3166 snprintf(append, sizeof append,
3167 "Invalid recipient: %s",
3171 snprintf(append, sizeof append,
3174 if ( (strlen(ret->errormsg) + strlen(append)) < SIZ) {
3175 strcat(ret->errormsg, append);
3179 if (strlen(ret->display_recp) == 0) {
3180 strcpy(append, this_recp);
3183 snprintf(append, sizeof append, ", %s",
3186 if ( (strlen(ret->display_recp)+strlen(append)) < SIZ) {
3187 strcat(ret->display_recp, append);
3192 if ((ret->num_local + ret->num_internet + ret->num_ignet +
3193 ret->num_room + ret->num_error) == 0) {
3194 ret->num_error = (-1);
3195 strcpy(ret->errormsg, "No recipients specified.");
3198 lprintf(CTDL_DEBUG, "validate_recipients()\n");
3199 lprintf(CTDL_DEBUG, " local: %d <%s>\n", ret->num_local, ret->recp_local);
3200 lprintf(CTDL_DEBUG, " room: %d <%s>\n", ret->num_room, ret->recp_room);
3201 lprintf(CTDL_DEBUG, " inet: %d <%s>\n", ret->num_internet, ret->recp_internet);
3202 lprintf(CTDL_DEBUG, " ignet: %d <%s>\n", ret->num_ignet, ret->recp_ignet);
3203 lprintf(CTDL_DEBUG, " error: %d <%s>\n", ret->num_error, ret->errormsg);
3211 * message entry - mode 0 (normal)
3213 void cmd_ent0(char *entargs)
3219 char supplied_euid[128];
3220 char masquerade_as[SIZ];
3222 int format_type = 0;
3223 char newusername[SIZ];
3224 struct CtdlMessage *msg;
3228 struct recptypes *valid = NULL;
3229 struct recptypes *valid_to = NULL;
3230 struct recptypes *valid_cc = NULL;
3231 struct recptypes *valid_bcc = NULL;
3238 post = extract_int(entargs, 0);
3239 extract_token(recp, entargs, 1, '|', sizeof recp);
3240 anon_flag = extract_int(entargs, 2);
3241 format_type = extract_int(entargs, 3);
3242 extract_token(subject, entargs, 4, '|', sizeof subject);
3243 do_confirm = extract_int(entargs, 6);
3244 extract_token(cc, entargs, 7, '|', sizeof cc);
3245 extract_token(bcc, entargs, 8, '|', sizeof bcc);
3246 switch(CC->room.QRdefaultview) {
3249 extract_token(supplied_euid, entargs, 9, '|', sizeof supplied_euid);
3252 supplied_euid[0] = 0;
3256 /* first check to make sure the request is valid. */
3258 err = CtdlDoIHavePermissionToPostInThisRoom(errmsg, sizeof errmsg);
3260 cprintf("%d %s\n", err, errmsg);
3264 /* Check some other permission type things. */
3267 if (CC->user.axlevel < 6) {
3268 cprintf("%d You don't have permission to masquerade.\n",
3269 ERROR + HIGHER_ACCESS_REQUIRED);
3272 extract_token(newusername, entargs, 5, '|', sizeof newusername);
3273 memset(CC->fake_postname, 0, sizeof(CC->fake_postname) );
3274 safestrncpy(CC->fake_postname, newusername,
3275 sizeof(CC->fake_postname) );
3276 cprintf("%d ok\n", CIT_OK);
3279 CC->cs_flags |= CS_POSTING;
3281 /* In the Mail> room we have to behave a little differently --
3282 * make sure the user has specified at least one recipient. Then
3283 * validate the recipient(s).
3285 if ( (CC->room.QRflags & QR_MAILBOX)
3286 && (!strcasecmp(&CC->room.QRname[11], MAILROOM)) ) {
3288 if (CC->user.axlevel < 2) {
3289 strcpy(recp, "sysop");
3294 valid_to = validate_recipients(recp);
3295 if (valid_to->num_error > 0) {
3296 cprintf("%d Invalid recipient (To)\n", ERROR + NO_SUCH_USER);
3301 valid_cc = validate_recipients(cc);
3302 if (valid_cc->num_error > 0) {
3303 cprintf("%d Invalid recipient (CC)\n", ERROR + NO_SUCH_USER);
3309 valid_bcc = validate_recipients(bcc);
3310 if (valid_bcc->num_error > 0) {
3311 cprintf("%d Invalid recipient (BCC)\n", ERROR + NO_SUCH_USER);
3318 /* Recipient required, but none were specified */
3319 if ( (valid_to->num_error < 0) && (valid_cc->num_error < 0) && (valid_bcc->num_error < 0) ) {
3323 cprintf("%d At least one recipient is required.\n", ERROR + NO_SUCH_USER);
3327 if (valid_to->num_internet + valid_cc->num_internet + valid_bcc->num_internet > 0) {
3328 if (CtdlCheckInternetMailPermission(&CC->user)==0) {
3329 cprintf("%d You do not have permission "
3330 "to send Internet mail.\n",
3331 ERROR + HIGHER_ACCESS_REQUIRED);
3339 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)
3340 && (CC->user.axlevel < 4) ) {
3341 cprintf("%d Higher access required for network mail.\n",
3342 ERROR + HIGHER_ACCESS_REQUIRED);
3349 if ((RESTRICT_INTERNET == 1)
3350 && (valid_to->num_internet + valid_cc->num_internet + valid_bcc->num_internet > 0)
3351 && ((CC->user.flags & US_INTERNET) == 0)
3352 && (!CC->internal_pgm)) {
3353 cprintf("%d You don't have access to Internet mail.\n",
3354 ERROR + HIGHER_ACCESS_REQUIRED);
3363 /* Is this a room which has anonymous-only or anonymous-option? */
3364 anonymous = MES_NORMAL;
3365 if (CC->room.QRflags & QR_ANONONLY) {
3366 anonymous = MES_ANONONLY;
3368 if (CC->room.QRflags & QR_ANONOPT) {
3369 if (anon_flag == 1) { /* only if the user requested it */
3370 anonymous = MES_ANONOPT;
3374 if ((CC->room.QRflags & QR_MAILBOX) == 0) {
3378 /* If we're only checking the validity of the request, return
3379 * success without creating the message.
3382 cprintf("%d %s\n", CIT_OK,
3383 ((valid_to != NULL) ? valid_to->display_recp : "") );
3390 /* We don't need these anymore because we'll do it differently below */
3395 /* Handle author masquerading */
3396 if (CC->fake_postname[0]) {
3397 strcpy(masquerade_as, CC->fake_postname);
3399 else if (CC->fake_username[0]) {
3400 strcpy(masquerade_as, CC->fake_username);
3403 strcpy(masquerade_as, "");
3406 /* Read in the message from the client. */
3408 cprintf("%d send message\n", START_CHAT_MODE);
3410 cprintf("%d send message\n", SEND_LISTING);
3413 msg = CtdlMakeMessage(&CC->user, recp, cc,
3414 CC->room.QRname, anonymous, format_type,
3415 masquerade_as, subject,
3416 ((strlen(supplied_euid) > 0) ? supplied_euid : NULL),
3419 /* Put together one big recipients struct containing to/cc/bcc all in
3420 * one. This is for the envelope.
3422 char *all_recps = malloc(SIZ * 3);
3423 strcpy(all_recps, recp);
3424 if (strlen(cc) > 0) {
3425 if (strlen(all_recps) > 0) {
3426 strcat(all_recps, ",");
3428 strcat(all_recps, cc);
3430 if (strlen(bcc) > 0) {
3431 if (strlen(all_recps) > 0) {
3432 strcat(all_recps, ",");
3434 strcat(all_recps, bcc);
3436 if (strlen(all_recps) > 0) {
3437 valid = validate_recipients(all_recps);
3445 msgnum = CtdlSubmitMsg(msg, valid, "");
3448 cprintf("%ld\n", msgnum);
3450 cprintf("Message accepted.\n");
3453 cprintf("Internal error.\n");
3455 if (msg->cm_fields['E'] != NULL) {
3456 cprintf("%s\n", msg->cm_fields['E']);
3463 CtdlFreeMessage(msg);
3465 CC->fake_postname[0] = '\0';
3466 if (valid != NULL) {
3475 * API function to delete messages which match a set of criteria
3476 * (returns the actual number of messages deleted)
3478 int CtdlDeleteMessages(char *room_name, /* which room */
3479 long *dmsgnums, /* array of msg numbers to be deleted */
3480 int num_dmsgnums, /* number of msgs to be deleted, or 0 for "any" */
3481 char *content_type, /* or "" for any */
3482 int deferred /* let TDAP sweep it later */
3486 struct ctdlroom qrbuf;
3487 struct cdbdata *cdbfr;
3488 long *msglist = NULL;
3489 long *dellist = NULL;
3492 int num_deleted = 0;
3494 struct MetaData smi;
3496 lprintf(CTDL_DEBUG, "CtdlDeleteMessages(%s, %d msgs, %s, %d)\n",
3497 room_name, num_dmsgnums, content_type, deferred);
3499 /* get room record, obtaining a lock... */
3500 if (lgetroom(&qrbuf, room_name) != 0) {
3501 lprintf(CTDL_ERR, "CtdlDeleteMessages(): Room <%s> not found\n",
3503 return (0); /* room not found */
3505 cdbfr = cdb_fetch(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long));
3507 if (cdbfr != NULL) {
3508 dellist = malloc(cdbfr->len);
3509 msglist = (long *) cdbfr->ptr;
3510 cdbfr->ptr = NULL; /* CtdlDeleteMessages() now owns this memory */
3511 num_msgs = cdbfr->len / sizeof(long);
3515 for (i = 0; i < num_msgs; ++i) {
3518 /* Set/clear a bit for each criterion */
3520 /* 0 messages in the list or a null list means that we are
3521 * interested in deleting any messages which meet the other criteria.
3523 if ((num_dmsgnums == 0) || (dmsgnums == NULL)) {
3524 delete_this |= 0x01;
3527 for (j=0; j<num_dmsgnums; ++j) {
3528 if (msglist[i] == dmsgnums[j]) {
3529 delete_this |= 0x01;
3534 if (strlen(content_type) == 0) {
3535 delete_this |= 0x02;
3537 GetMetaData(&smi, msglist[i]);
3538 if (!strcasecmp(smi.meta_content_type,
3540 delete_this |= 0x02;
3544 /* Delete message only if all bits are set */
3545 if (delete_this == 0x03) {
3546 dellist[num_deleted++] = msglist[i];
3551 num_msgs = sort_msglist(msglist, num_msgs);
3552 cdb_store(CDB_MSGLISTS, &qrbuf.QRnumber, (int)sizeof(long),
3553 msglist, (int)(num_msgs * sizeof(long)));
3555 qrbuf.QRhighest = msglist[num_msgs - 1];
3560 * If the delete operation is "deferred" (and technically, any delete
3561 * operation not performed by THE DREADED AUTO-PURGER ought to be
3562 * a deferred delete) then we save a pointer to the message in the
3563 * DELETED_MSGS_ROOM. This will cause the reference count to remain
3564 * at least 1, which will save the user from having to synchronously
3565 * wait for various disk-intensive operations to complete.
3567 * Slick -- we now use the new bulk API for moving messages.
3569 if ( (deferred) && (num_deleted) ) {
3570 CtdlCopyMsgsToRoom(dellist, num_deleted, DELETED_MSGS_ROOM);
3573 /* Go through the messages we pulled out of the index, and decrement
3574 * their reference counts by 1. If this is the only room the message
3575 * was in, the reference count will reach zero and the message will
3576 * automatically be deleted from the database. We do this in a
3577 * separate pass because there might be plug-in hooks getting called,
3578 * and we don't want that happening during an S_ROOMS critical
3581 if (num_deleted) for (i=0; i<num_deleted; ++i) {
3582 PerformDeleteHooks(qrbuf.QRname, dellist[i]);
3583 AdjRefCount(dellist[i], -1);
3586 /* Now free the memory we used, and go away. */
3587 if (msglist != NULL) free(msglist);
3588 if (dellist != NULL) free(dellist);
3589 lprintf(CTDL_DEBUG, "%d message(s) deleted.\n", num_deleted);
3590 return (num_deleted);
3596 * Check whether the current user has permission to delete messages from
3597 * the current room (returns 1 for yes, 0 for no)
3599 int CtdlDoIHavePermissionToDeleteMessagesFromThisRoom(void) {
3600 getuser(&CC->user, CC->curr_user);
3601 if ((CC->user.axlevel < 6)
3602 && (CC->user.usernum != CC->room.QRroomaide)
3603 && ((CC->room.QRflags & QR_MAILBOX) == 0)
3604 && (!(CC->internal_pgm))) {
3613 * Delete message from current room
3615 void cmd_dele(char *args)
3624 extract_token(msgset, args, 0, '|', sizeof msgset);
3625 num_msgs = num_tokens(msgset, ',');
3627 cprintf("%d Nothing to do.\n", CIT_OK);
3631 if (CtdlDoIHavePermissionToDeleteMessagesFromThisRoom() == 0) {
3632 cprintf("%d Higher access required.\n",
3633 ERROR + HIGHER_ACCESS_REQUIRED);
3638 * Build our message set to be moved/copied
3640 msgs = malloc(num_msgs * sizeof(long));
3641 for (i=0; i<num_msgs; ++i) {
3642 extract_token(msgtok, msgset, i, ',', sizeof msgtok);
3643 msgs[i] = atol(msgtok);
3646 num_deleted = CtdlDeleteMessages(CC->room.QRname, msgs, num_msgs, "", 1);
3650 cprintf("%d %d message%s deleted.\n", CIT_OK,
3651 num_deleted, ((num_deleted != 1) ? "s" : ""));
3653 cprintf("%d Message not found.\n", ERROR + MESSAGE_NOT_FOUND);
3659 * Back end API function for moves and deletes (multiple messages)
3661 int CtdlCopyMsgsToRoom(long *msgnums, int num_msgs, char *dest) {
3664 err = CtdlSaveMsgPointersInRoom(dest, msgnums, num_msgs, 1, NULL);
3665 if (err != 0) return(err);
3674 * move or copy a message to another room
3676 void cmd_move(char *args)
3683 char targ[ROOMNAMELEN];
3684 struct ctdlroom qtemp;
3691 extract_token(msgset, args, 0, '|', sizeof msgset);
3692 num_msgs = num_tokens(msgset, ',');
3694 cprintf("%d Nothing to do.\n", CIT_OK);
3698 extract_token(targ, args, 1, '|', sizeof targ);
3699 convert_room_name_macros(targ, sizeof targ);
3700 targ[ROOMNAMELEN - 1] = 0;
3701 is_copy = extract_int(args, 2);
3703 if (getroom(&qtemp, targ) != 0) {
3704 cprintf("%d '%s' does not exist.\n",
3705 ERROR + ROOM_NOT_FOUND, targ);
3709 getuser(&CC->user, CC->curr_user);
3710 CtdlRoomAccess(&qtemp, &CC->user, &ra, NULL);
3712 /* Check for permission to perform this operation.
3713 * Remember: "CC->room" is source, "qtemp" is target.
3717 /* Aides can move/copy */
3718 if (CC->user.axlevel >= 6) permit = 1;
3720 /* Room aides can move/copy */
3721 if (CC->user.usernum == CC->room.QRroomaide) permit = 1;
3723 /* Permit move/copy from personal rooms */
3724 if ((CC->room.QRflags & QR_MAILBOX)
3725 && (qtemp.QRflags & QR_MAILBOX)) permit = 1;
3727 /* Permit only copy from public to personal room */
3729 && (!(CC->room.QRflags & QR_MAILBOX))
3730 && (qtemp.QRflags & QR_MAILBOX)) permit = 1;
3732 /* User must have access to target room */
3733 if (!(ra & UA_KNOWN)) permit = 0;
3736 cprintf("%d Higher access required.\n",
3737 ERROR + HIGHER_ACCESS_REQUIRED);
3742 * Build our message set to be moved/copied
3744 msgs = malloc(num_msgs * sizeof(long));
3745 for (i=0; i<num_msgs; ++i) {
3746 extract_token(msgtok, msgset, i, ',', sizeof msgtok);
3747 msgs[i] = atol(msgtok);
3753 err = CtdlCopyMsgsToRoom(msgs, num_msgs, targ);
3755 cprintf("%d Cannot store message(s) in %s: error %d\n",
3761 /* Now delete the message from the source room,
3762 * if this is a 'move' rather than a 'copy' operation.
3765 CtdlDeleteMessages(CC->room.QRname, msgs, num_msgs, "", 0);
3769 cprintf("%d Message(s) %s.\n", CIT_OK, (is_copy ? "copied" : "moved") );
3775 * GetMetaData() - Get the supplementary record for a message
3777 void GetMetaData(struct MetaData *smibuf, long msgnum)
3780 struct cdbdata *cdbsmi;
3783 memset(smibuf, 0, sizeof(struct MetaData));
3784 smibuf->meta_msgnum = msgnum;
3785 smibuf->meta_refcount = 1; /* Default reference count is 1 */
3787 /* Use the negative of the message number for its supp record index */
3788 TheIndex = (0L - msgnum);
3790 cdbsmi = cdb_fetch(CDB_MSGMAIN, &TheIndex, sizeof(long));
3791 if (cdbsmi == NULL) {
3792 return; /* record not found; go with defaults */
3794 memcpy(smibuf, cdbsmi->ptr,
3795 ((cdbsmi->len > sizeof(struct MetaData)) ?
3796 sizeof(struct MetaData) : cdbsmi->len));
3803 * PutMetaData() - (re)write supplementary record for a message
3805 void PutMetaData(struct MetaData *smibuf)
3809 /* Use the negative of the message number for the metadata db index */
3810 TheIndex = (0L - smibuf->meta_msgnum);
3812 lprintf(CTDL_DEBUG, "PutMetaData(%ld) - ref count is %d\n",
3813 smibuf->meta_msgnum, smibuf->meta_refcount);
3815 cdb_store(CDB_MSGMAIN,
3816 &TheIndex, (int)sizeof(long),
3817 smibuf, (int)sizeof(struct MetaData));
3822 * AdjRefCount - change the reference count for a message;
3823 * delete the message if it reaches zero
3825 void AdjRefCount(long msgnum, int incr)
3828 struct MetaData smi;
3831 /* This is a *tight* critical section; please keep it that way, as
3832 * it may get called while nested in other critical sections.
3833 * Complicating this any further will surely cause deadlock!
3835 begin_critical_section(S_SUPPMSGMAIN);
3836 GetMetaData(&smi, msgnum);
3837 smi.meta_refcount += incr;
3839 end_critical_section(S_SUPPMSGMAIN);
3840 lprintf(CTDL_DEBUG, "msg %ld ref count incr %d, is now %d\n",
3841 msgnum, incr, smi.meta_refcount);
3843 /* If the reference count is now zero, delete the message
3844 * (and its supplementary record as well).
3846 if (smi.meta_refcount == 0) {
3847 lprintf(CTDL_DEBUG, "Deleting message <%ld>\n", msgnum);
3849 /* Remove from fulltext index */
3850 if (config.c_enable_fulltext) {
3851 ft_index_message(msgnum, 0);
3854 /* Remove from message base */
3856 cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long));
3857 cdb_delete(CDB_BIGMSGS, &delnum, (int)sizeof(long));
3859 /* Remove metadata record */
3860 delnum = (0L - msgnum);
3861 cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long));
3866 * Write a generic object to this room
3868 * Note: this could be much more efficient. Right now we use two temporary
3869 * files, and still pull the message into memory as with all others.
3871 void CtdlWriteObject(char *req_room, /* Room to stuff it in */
3872 char *content_type, /* MIME type of this object */
3873 char *tempfilename, /* Where to fetch it from */
3874 struct ctdluser *is_mailbox, /* Mailbox room? */
3875 int is_binary, /* Is encoding necessary? */
3876 int is_unique, /* Del others of this type? */
3877 unsigned int flags /* Internal save flags */
3882 struct ctdlroom qrbuf;
3883 char roomname[ROOMNAMELEN];
3884 struct CtdlMessage *msg;
3886 char *raw_message = NULL;
3887 char *encoded_message = NULL;
3888 off_t raw_length = 0;
3890 if (is_mailbox != NULL) {
3891 MailboxName(roomname, sizeof roomname, is_mailbox, req_room);
3894 safestrncpy(roomname, req_room, sizeof(roomname));
3897 fp = fopen(tempfilename, "rb");
3899 lprintf(CTDL_CRIT, "Cannot open %s: %s\n",
3900 tempfilename, strerror(errno));
3903 fseek(fp, 0L, SEEK_END);
3904 raw_length = ftell(fp);
3906 lprintf(CTDL_DEBUG, "Raw length is %ld\n", (long)raw_length);
3908 raw_message = malloc((size_t)raw_length + 2);
3909 fread(raw_message, (size_t)raw_length, 1, fp);
3913 encoded_message = malloc((size_t)
3914 (((raw_length * 134) / 100) + 4096 ) );
3917 encoded_message = malloc((size_t)(raw_length + 4096));
3920 sprintf(encoded_message, "Content-type: %s\n", content_type);
3923 sprintf(&encoded_message[strlen(encoded_message)],
3924 "Content-transfer-encoding: base64\n\n"
3928 sprintf(&encoded_message[strlen(encoded_message)],
3929 "Content-transfer-encoding: 7bit\n\n"
3935 &encoded_message[strlen(encoded_message)],
3941 raw_message[raw_length] = 0;
3943 &encoded_message[strlen(encoded_message)],
3951 lprintf(CTDL_DEBUG, "Allocating\n");
3952 msg = malloc(sizeof(struct CtdlMessage));
3953 memset(msg, 0, sizeof(struct CtdlMessage));
3954 msg->cm_magic = CTDLMESSAGE_MAGIC;
3955 msg->cm_anon_type = MES_NORMAL;
3956 msg->cm_format_type = 4;
3957 msg->cm_fields['A'] = strdup(CC->user.fullname);
3958 msg->cm_fields['O'] = strdup(req_room);
3959 msg->cm_fields['N'] = strdup(config.c_nodename);
3960 msg->cm_fields['H'] = strdup(config.c_humannode);
3961 msg->cm_flags = flags;
3963 msg->cm_fields['M'] = encoded_message;
3965 /* Create the requested room if we have to. */
3966 if (getroom(&qrbuf, roomname) != 0) {
3967 create_room(roomname,
3968 ( (is_mailbox != NULL) ? 5 : 3 ),
3969 "", 0, 1, 0, VIEW_BBS);
3971 /* If the caller specified this object as unique, delete all
3972 * other objects of this type that are currently in the room.
3975 lprintf(CTDL_DEBUG, "Deleted %d other msgs of this type\n",
3976 CtdlDeleteMessages(roomname, NULL, 0, content_type, 0)
3979 /* Now write the data */
3980 CtdlSubmitMsg(msg, NULL, roomname);
3981 CtdlFreeMessage(msg);
3989 void CtdlGetSysConfigBackend(long msgnum, void *userdata) {
3990 config_msgnum = msgnum;
3994 char *CtdlGetSysConfig(char *sysconfname) {
3995 char hold_rm[ROOMNAMELEN];
3998 struct CtdlMessage *msg;
4001 strcpy(hold_rm, CC->room.QRname);
4002 if (getroom(&CC->room, SYSCONFIGROOM) != 0) {
4003 getroom(&CC->room, hold_rm);
4008 /* We want the last (and probably only) config in this room */
4009 begin_critical_section(S_CONFIG);
4010 config_msgnum = (-1L);
4011 CtdlForEachMessage(MSGS_LAST, 1, NULL, sysconfname, NULL,
4012 CtdlGetSysConfigBackend, NULL);
4013 msgnum = config_msgnum;
4014 end_critical_section(S_CONFIG);
4020 msg = CtdlFetchMessage(msgnum, 1);
4022 conf = strdup(msg->cm_fields['M']);
4023 CtdlFreeMessage(msg);
4030 getroom(&CC->room, hold_rm);
4032 if (conf != NULL) do {
4033 extract_token(buf, conf, 0, '\n', sizeof buf);
4034 strcpy(conf, &conf[strlen(buf)+1]);
4035 } while ( (strlen(conf)>0) && (strlen(buf)>0) );
4040 void CtdlPutSysConfig(char *sysconfname, char *sysconfdata) {
4041 char temp[PATH_MAX];
4044 CtdlMakeTempFileName(temp, sizeof temp);
4046 fp = fopen(temp, "w");
4047 if (fp == NULL) return;
4048 fprintf(fp, "%s", sysconfdata);
4051 /* this handy API function does all the work for us */
4052 CtdlWriteObject(SYSCONFIGROOM, sysconfname, temp, NULL, 0, 1, 0);
4058 * Determine whether a given Internet address belongs to the current user
4060 int CtdlIsMe(char *addr, int addr_buf_len)
4062 struct recptypes *recp;
4065 recp = validate_recipients(addr);
4066 if (recp == NULL) return(0);
4068 if (recp->num_local == 0) {
4073 for (i=0; i<recp->num_local; ++i) {
4074 extract_token(addr, recp->recp_local, i, '|', addr_buf_len);
4075 if (!strcasecmp(addr, CC->user.fullname)) {
4087 * Citadel protocol command to do the same
4089 void cmd_isme(char *argbuf) {
4092 if (CtdlAccessCheck(ac_logged_in)) return;
4093 extract_token(addr, argbuf, 0, '|', sizeof addr);
4095 if (CtdlIsMe(addr, sizeof addr)) {
4096 cprintf("%d %s\n", CIT_OK, addr);
4099 cprintf("%d Not you.\n", ERROR + ILLEGAL_VALUE);