4 * Implements the message store.
14 #if TIME_WITH_SYS_TIME
15 # include <sys/time.h>
19 # include <sys/time.h>
32 #include <sys/types.h>
36 #include "serv_extensions.h"
40 #include "sysdep_decls.h"
41 #include "citserver.h"
48 #include "mime_parser.h"
51 #include "internet_addressing.h"
53 #include "euidindex.h"
54 #include "journaling.h"
55 #include "citadel_dirs.h"
56 #include "clientsocket.h"
60 struct addresses_to_be_filed *atbf = NULL;
62 /* This temp file holds the queue of operations for AdjRefCount() */
63 static FILE *arcfp = NULL;
66 * This really belongs in serv_network.c, but I don't know how to export
67 * symbols between modules.
69 struct FilterList *filterlist = NULL;
73 * These are the four-character field headers we use when outputting
74 * messages in Citadel format (as opposed to RFC822 format).
77 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
78 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
79 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
80 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
81 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
82 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
83 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
84 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
112 * This function is self explanatory.
113 * (What can I say, I'm in a weird mood today...)
115 void remove_any_whitespace_to_the_left_or_right_of_at_symbol(char *name)
119 for (i = 0; i < strlen(name); ++i) {
120 if (name[i] == '@') {
121 while (isspace(name[i - 1]) && i > 0) {
122 strcpy(&name[i - 1], &name[i]);
125 while (isspace(name[i + 1])) {
126 strcpy(&name[i + 1], &name[i + 2]);
134 * Aliasing for network mail.
135 * (Error messages have been commented out, because this is a server.)
137 int alias(char *name)
138 { /* process alias and routing info for mail */
141 char aaa[SIZ], bbb[SIZ];
142 char *ignetcfg = NULL;
143 char *ignetmap = NULL;
149 char original_name[256];
150 safestrncpy(original_name, name, sizeof original_name);
153 remove_any_whitespace_to_the_left_or_right_of_at_symbol(name);
154 stripallbut(name, '<', '>');
156 fp = fopen(file_mail_aliases, "r");
158 fp = fopen("/dev/null", "r");
165 while (fgets(aaa, sizeof aaa, fp) != NULL) {
166 while (isspace(name[0]))
167 strcpy(name, &name[1]);
168 aaa[strlen(aaa) - 1] = 0;
170 for (a = 0; a < strlen(aaa); ++a) {
172 strcpy(bbb, &aaa[a + 1]);
176 if (!strcasecmp(name, aaa))
181 /* Hit the Global Address Book */
182 if (CtdlDirectoryLookup(aaa, name, sizeof aaa) == 0) {
186 if (strcasecmp(original_name, name)) {
187 lprintf(CTDL_INFO, "%s is being forwarded to %s\n", original_name, name);
190 /* Change "user @ xxx" to "user" if xxx is an alias for this host */
191 for (a=0; a<strlen(name); ++a) {
192 if (name[a] == '@') {
193 if (CtdlHostAlias(&name[a+1]) == hostalias_localhost) {
195 lprintf(CTDL_INFO, "Changed to <%s>\n", name);
200 /* determine local or remote type, see citadel.h */
201 at = haschar(name, '@');
202 if (at == 0) return(MES_LOCAL); /* no @'s - local address */
203 if (at > 1) return(MES_ERROR); /* >1 @'s - invalid address */
204 remove_any_whitespace_to_the_left_or_right_of_at_symbol(name);
206 /* figure out the delivery mode */
207 extract_token(node, name, 1, '@', sizeof node);
209 /* If there are one or more dots in the nodename, we assume that it
210 * is an FQDN and will attempt SMTP delivery to the Internet.
212 if (haschar(node, '.') > 0) {
213 return(MES_INTERNET);
216 /* Otherwise we look in the IGnet maps for a valid Citadel node.
217 * Try directly-connected nodes first...
219 ignetcfg = CtdlGetSysConfig(IGNETCFG);
220 for (i=0; i<num_tokens(ignetcfg, '\n'); ++i) {
221 extract_token(buf, ignetcfg, i, '\n', sizeof buf);
222 extract_token(testnode, buf, 0, '|', sizeof testnode);
223 if (!strcasecmp(node, testnode)) {
231 * Then try nodes that are two or more hops away.
233 ignetmap = CtdlGetSysConfig(IGNETMAP);
234 for (i=0; i<num_tokens(ignetmap, '\n'); ++i) {
235 extract_token(buf, ignetmap, i, '\n', sizeof buf);
236 extract_token(testnode, buf, 0, '|', sizeof testnode);
237 if (!strcasecmp(node, testnode)) {
244 /* If we get to this point it's an invalid node name */
250 * Back end for the MSGS command: output message number only.
252 void simple_listing(long msgnum, void *userdata)
254 cprintf("%ld\n", msgnum);
260 * Back end for the MSGS command: output header summary.
262 void headers_listing(long msgnum, void *userdata)
264 struct CtdlMessage *msg;
266 msg = CtdlFetchMessage(msgnum, 0);
268 cprintf("%ld|0|||||\n", msgnum);
272 cprintf("%ld|%s|%s|%s|%s|%s|\n",
274 (msg->cm_fields['T'] ? msg->cm_fields['T'] : "0"),
275 (msg->cm_fields['A'] ? msg->cm_fields['A'] : ""),
276 (msg->cm_fields['N'] ? msg->cm_fields['N'] : ""),
277 (msg->cm_fields['F'] ? msg->cm_fields['F'] : ""),
278 (msg->cm_fields['U'] ? msg->cm_fields['U'] : "")
280 CtdlFreeMessage(msg);
285 /* Determine if a given message matches the fields in a message template.
286 * Return 0 for a successful match.
288 int CtdlMsgCmp(struct CtdlMessage *msg, struct CtdlMessage *template) {
291 /* If there aren't any fields in the template, all messages will
294 if (template == NULL) return(0);
296 /* Null messages are bogus. */
297 if (msg == NULL) return(1);
299 for (i='A'; i<='Z'; ++i) {
300 if (template->cm_fields[i] != NULL) {
301 if (msg->cm_fields[i] == NULL) {
304 if (strcasecmp(msg->cm_fields[i],
305 template->cm_fields[i])) return 1;
309 /* All compares succeeded: we have a match! */
316 * Retrieve the "seen" message list for the current room.
318 void CtdlGetSeen(char *buf, int which_set) {
321 /* Learn about the user and room in question */
322 CtdlGetRelationship(&vbuf, &CC->user, &CC->room);
324 if (which_set == ctdlsetseen_seen)
325 safestrncpy(buf, vbuf.v_seen, SIZ);
326 if (which_set == ctdlsetseen_answered)
327 safestrncpy(buf, vbuf.v_answered, SIZ);
333 * Manipulate the "seen msgs" string (or other message set strings)
335 void CtdlSetSeen(long *target_msgnums, int num_target_msgnums,
336 int target_setting, int which_set,
337 struct ctdluser *which_user, struct ctdlroom *which_room) {
338 struct cdbdata *cdbfr;
350 char *is_set; /* actually an array of booleans */
353 char setstr[SIZ], lostr[SIZ], histr[SIZ];
356 /* Don't bother doing *anything* if we were passed a list of zero messages */
357 if (num_target_msgnums < 1) {
361 lprintf(CTDL_DEBUG, "CtdlSetSeen(%d msgs starting with %ld, %d, %d)\n",
362 num_target_msgnums, target_msgnums[0],
363 target_setting, which_set);
365 /* Learn about the user and room in question */
366 CtdlGetRelationship(&vbuf,
367 ((which_user != NULL) ? which_user : &CC->user),
368 ((which_room != NULL) ? which_room : &CC->room)
371 /* Load the message list */
372 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long));
374 msglist = (long *) cdbfr->ptr;
375 cdbfr->ptr = NULL; /* CtdlSetSeen() now owns this memory */
376 num_msgs = cdbfr->len / sizeof(long);
379 return; /* No messages at all? No further action. */
382 is_set = malloc(num_msgs * sizeof(char));
383 memset(is_set, 0, (num_msgs * sizeof(char)) );
385 /* Decide which message set we're manipulating */
387 case ctdlsetseen_seen:
388 safestrncpy(vset, vbuf.v_seen, sizeof vset);
390 case ctdlsetseen_answered:
391 safestrncpy(vset, vbuf.v_answered, sizeof vset);
395 /* lprintf(CTDL_DEBUG, "before optimize: %s\n", vset); */
397 /* Translate the existing sequence set into an array of booleans */
398 num_sets = num_tokens(vset, ',');
399 for (s=0; s<num_sets; ++s) {
400 extract_token(setstr, vset, s, ',', sizeof setstr);
402 extract_token(lostr, setstr, 0, ':', sizeof lostr);
403 if (num_tokens(setstr, ':') >= 2) {
404 extract_token(histr, setstr, 1, ':', sizeof histr);
405 if (!strcmp(histr, "*")) {
406 snprintf(histr, sizeof histr, "%ld", LONG_MAX);
410 strcpy(histr, lostr);
415 for (i = 0; i < num_msgs; ++i) {
416 if ((msglist[i] >= lo) && (msglist[i] <= hi)) {
422 /* Now translate the array of booleans back into a sequence set */
427 for (i=0; i<num_msgs; ++i) {
429 is_seen = is_set[i]; /* Default to existing setting */
431 for (k=0; k<num_target_msgnums; ++k) {
432 if (msglist[i] == target_msgnums[k]) {
433 is_seen = target_setting;
438 if (lo < 0L) lo = msglist[i];
442 if ( ((is_seen == 0) && (was_seen == 1))
443 || ((is_seen == 1) && (i == num_msgs-1)) ) {
445 /* begin trim-o-matic code */
448 while ( (strlen(vset) + 20) > sizeof vset) {
449 remove_token(vset, 0, ',');
451 if (j--) break; /* loop no more than 9 times */
453 if ( (trimming) && (which_set == ctdlsetseen_seen) ) {
457 snprintf(lostr, sizeof lostr,
458 "1:%ld,%s", t, vset);
459 safestrncpy(vset, lostr, sizeof vset);
461 /* end trim-o-matic code */
469 snprintf(&vset[tmp], (sizeof vset) - tmp,
473 snprintf(&vset[tmp], (sizeof vset) - tmp,
482 /* Decide which message set we're manipulating */
484 case ctdlsetseen_seen:
485 safestrncpy(vbuf.v_seen, vset, sizeof vbuf.v_seen);
487 case ctdlsetseen_answered:
488 safestrncpy(vbuf.v_answered, vset,
489 sizeof vbuf.v_answered);
494 /* lprintf(CTDL_DEBUG, " after optimize: %s\n", vset); */
496 CtdlSetRelationship(&vbuf,
497 ((which_user != NULL) ? which_user : &CC->user),
498 ((which_room != NULL) ? which_room : &CC->room)
504 * API function to perform an operation for each qualifying message in the
505 * current room. (Returns the number of messages processed.)
507 int CtdlForEachMessage(int mode, long ref, char *search_string,
509 struct CtdlMessage *compare,
510 void (*CallBack) (long, void *),
516 struct cdbdata *cdbfr;
517 long *msglist = NULL;
519 int num_processed = 0;
522 struct CtdlMessage *msg = NULL;
525 int printed_lastold = 0;
526 int num_search_msgs = 0;
527 long *search_msgs = NULL;
529 int need_to_free_re = 0;
532 if (content_type) if (!IsEmptyStr(content_type)) {
533 regcomp(&re, content_type, 0);
537 /* Learn about the user and room in question */
538 getuser(&CC->user, CC->curr_user);
539 CtdlGetRelationship(&vbuf, &CC->user, &CC->room);
541 /* Load the message list */
542 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long));
544 msglist = (long *) cdbfr->ptr;
545 num_msgs = cdbfr->len / sizeof(long);
547 if (need_to_free_re) regfree(&re);
548 return 0; /* No messages at all? No further action. */
553 * Now begin the traversal.
555 if (num_msgs > 0) for (a = 0; a < num_msgs; ++a) {
557 /* If the caller is looking for a specific MIME type, filter
558 * out all messages which are not of the type requested.
560 if (content_type != NULL) if (!IsEmptyStr(content_type)) {
562 /* This call to GetMetaData() sits inside this loop
563 * so that we only do the extra database read per msg
564 * if we need to. Doing the extra read all the time
565 * really kills the server. If we ever need to use
566 * metadata for another search criterion, we need to
567 * move the read somewhere else -- but still be smart
568 * enough to only do the read if the caller has
569 * specified something that will need it.
571 GetMetaData(&smi, msglist[a]);
573 /* if (strcasecmp(smi.meta_content_type, content_type)) { old non-regex way */
574 if (regexec(&re, smi.meta_content_type, 1, &pm, 0) != 0) {
580 num_msgs = sort_msglist(msglist, num_msgs);
582 /* If a template was supplied, filter out the messages which
583 * don't match. (This could induce some delays!)
586 if (compare != NULL) {
587 for (a = 0; a < num_msgs; ++a) {
588 msg = CtdlFetchMessage(msglist[a], 1);
590 if (CtdlMsgCmp(msg, compare)) {
593 CtdlFreeMessage(msg);
599 /* If a search string was specified, get a message list from
600 * the full text index and remove messages which aren't on both
604 * Since the lists are sorted and strictly ascending, and the
605 * output list is guaranteed to be shorter than or equal to the
606 * input list, we overwrite the bottom of the input list. This
607 * eliminates the need to memmove big chunks of the list over and
610 if ( (num_msgs > 0) && (mode == MSGS_SEARCH) && (search_string) ) {
612 /* Call search module via hook mechanism.
613 * NULL means use any search function available.
614 * otherwise replace with a char * to name of search routine
616 CtdlModuleDoSearch(&num_search_msgs, &search_msgs, search_string, "fulltext");
618 if (num_search_msgs > 0) {
622 orig_num_msgs = num_msgs;
624 for (i=0; i<orig_num_msgs; ++i) {
625 for (j=0; j<num_search_msgs; ++j) {
626 if (msglist[i] == search_msgs[j]) {
627 msglist[num_msgs++] = msglist[i];
633 num_msgs = 0; /* No messages qualify */
635 if (search_msgs != NULL) free(search_msgs);
637 /* Now that we've purged messages which don't contain the search
638 * string, treat a MSGS_SEARCH just like a MSGS_ALL from this
645 * Now iterate through the message list, according to the
646 * criteria supplied by the caller.
649 for (a = 0; a < num_msgs; ++a) {
650 thismsg = msglist[a];
651 if (mode == MSGS_ALL) {
655 is_seen = is_msg_in_sequence_set(
656 vbuf.v_seen, thismsg);
657 if (is_seen) lastold = thismsg;
663 || ((mode == MSGS_OLD) && (is_seen))
664 || ((mode == MSGS_NEW) && (!is_seen))
665 || ((mode == MSGS_LAST) && (a >= (num_msgs - ref)))
666 || ((mode == MSGS_FIRST) && (a < ref))
667 || ((mode == MSGS_GT) && (thismsg > ref))
668 || ((mode == MSGS_EQ) && (thismsg == ref))
671 if ((mode == MSGS_NEW) && (CC->user.flags & US_LASTOLD) && (lastold > 0L) && (printed_lastold == 0) && (!is_seen)) {
673 CallBack(lastold, userdata);
677 if (CallBack) CallBack(thismsg, userdata);
681 cdb_free(cdbfr); /* Clean up */
682 if (need_to_free_re) regfree(&re);
683 return num_processed;
689 * cmd_msgs() - get list of message #'s in this room
690 * implements the MSGS server command using CtdlForEachMessage()
692 void cmd_msgs(char *cmdbuf)
701 int with_template = 0;
702 struct CtdlMessage *template = NULL;
703 int with_headers = 0;
704 char search_string[1024];
706 extract_token(which, cmdbuf, 0, '|', sizeof which);
707 cm_ref = extract_int(cmdbuf, 1);
708 extract_token(search_string, cmdbuf, 1, '|', sizeof search_string);
709 with_template = extract_int(cmdbuf, 2);
710 with_headers = extract_int(cmdbuf, 3);
713 if (!strncasecmp(which, "OLD", 3))
715 else if (!strncasecmp(which, "NEW", 3))
717 else if (!strncasecmp(which, "FIRST", 5))
719 else if (!strncasecmp(which, "LAST", 4))
721 else if (!strncasecmp(which, "GT", 2))
723 else if (!strncasecmp(which, "SEARCH", 6))
728 if ((!(CC->logged_in)) && (!(CC->internal_pgm))) {
729 cprintf("%d not logged in\n", ERROR + NOT_LOGGED_IN);
733 if ( (mode == MSGS_SEARCH) && (!config.c_enable_fulltext) ) {
734 cprintf("%d Full text index is not enabled on this server.\n",
735 ERROR + CMD_NOT_SUPPORTED);
741 cprintf("%d Send template then receive message list\n",
743 template = (struct CtdlMessage *)
744 malloc(sizeof(struct CtdlMessage));
745 memset(template, 0, sizeof(struct CtdlMessage));
746 template->cm_magic = CTDLMESSAGE_MAGIC;
747 template->cm_anon_type = MES_NORMAL;
749 while(client_getln(buf, sizeof buf), strcmp(buf,"000")) {
750 extract_token(tfield, buf, 0, '|', sizeof tfield);
751 extract_token(tvalue, buf, 1, '|', sizeof tvalue);
752 for (i='A'; i<='Z'; ++i) if (msgkeys[i]!=NULL) {
753 if (!strcasecmp(tfield, msgkeys[i])) {
754 template->cm_fields[i] =
762 cprintf("%d \n", LISTING_FOLLOWS);
765 CtdlForEachMessage(mode,
766 ( (mode == MSGS_SEARCH) ? 0 : cm_ref ),
767 ( (mode == MSGS_SEARCH) ? search_string : NULL ),
770 (with_headers ? headers_listing : simple_listing),
773 if (template != NULL) CtdlFreeMessage(template);
781 * help_subst() - support routine for help file viewer
783 void help_subst(char *strbuf, char *source, char *dest)
788 while (p = pattern2(strbuf, source), (p >= 0)) {
789 strcpy(workbuf, &strbuf[p + strlen(source)]);
790 strcpy(&strbuf[p], dest);
791 strcat(strbuf, workbuf);
796 void do_help_subst(char *buffer)
800 help_subst(buffer, "^nodename", config.c_nodename);
801 help_subst(buffer, "^humannode", config.c_humannode);
802 help_subst(buffer, "^fqdn", config.c_fqdn);
803 help_subst(buffer, "^username", CC->user.fullname);
804 snprintf(buf2, sizeof buf2, "%ld", CC->user.usernum);
805 help_subst(buffer, "^usernum", buf2);
806 help_subst(buffer, "^sysadm", config.c_sysadm);
807 help_subst(buffer, "^variantname", CITADEL);
808 snprintf(buf2, sizeof buf2, "%d", config.c_maxsessions);
809 help_subst(buffer, "^maxsessions", buf2);
810 help_subst(buffer, "^bbsdir", ctdl_bbsbase_dir);
816 * memfmout() - Citadel text formatter and paginator.
817 * Although the original purpose of this routine was to format
818 * text to the reader's screen width, all we're really using it
819 * for here is to format text out to 80 columns before sending it
820 * to the client. The client software may reformat it again.
823 char *mptr, /* where are we going to get our text from? */
824 char subst, /* nonzero if we should do substitutions */
825 char *nl) /* string to terminate lines with */
833 static int width = 80;
838 c = 1; /* c is the current pos */
842 while (ch = *mptr, ((ch != 0) && (strlen(buffer) < 126))) {
844 buffer[strlen(buffer) + 1] = 0;
845 buffer[strlen(buffer)] = ch;
848 if (buffer[0] == '^')
849 do_help_subst(buffer);
851 buffer[strlen(buffer) + 1] = 0;
853 strcpy(buffer, &buffer[1]);
861 if (((ch == 13) || (ch == 10)) && (old != 13) && (old != 10)) {
864 if (((old == 13) || (old == 10)) && (isspace(real))) {
869 if (((strlen(aaa) + c) > (width - 5)) && (strlen(aaa) > (width - 5))) {
870 cprintf("%s%s", nl, aaa);
879 if ((strlen(aaa) + c) > (width - 5)) {
888 if ((ch == 13) || (ch == 10)) {
889 cprintf("%s%s", aaa, nl);
896 cprintf("%s%s", aaa, nl);
902 * Callback function for mime parser that simply lists the part
904 void list_this_part(char *name, char *filename, char *partnum, char *disp,
905 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
910 ma = (struct ma_info *)cbuserdata;
911 if (ma->is_ma == 0) {
912 cprintf("part=%s|%s|%s|%s|%s|%ld\n",
913 name, filename, partnum, disp, cbtype, (long)length);
918 * Callback function for multipart prefix
920 void list_this_pref(char *name, char *filename, char *partnum, char *disp,
921 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
926 ma = (struct ma_info *)cbuserdata;
927 if (!strcasecmp(cbtype, "multipart/alternative")) {
931 if (ma->is_ma == 0) {
932 cprintf("pref=%s|%s\n", partnum, cbtype);
937 * Callback function for multipart sufffix
939 void list_this_suff(char *name, char *filename, char *partnum, char *disp,
940 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
945 ma = (struct ma_info *)cbuserdata;
946 if (ma->is_ma == 0) {
947 cprintf("suff=%s|%s\n", partnum, cbtype);
949 if (!strcasecmp(cbtype, "multipart/alternative")) {
956 * Callback function for mime parser that opens a section for downloading
958 void mime_download(char *name, char *filename, char *partnum, char *disp,
959 void *content, char *cbtype, char *cbcharset, size_t length,
960 char *encoding, void *cbuserdata)
963 /* Silently go away if there's already a download open... */
964 if (CC->download_fp != NULL)
967 /* ...or if this is not the desired section */
968 if (strcasecmp(CC->download_desired_section, partnum))
971 CC->download_fp = tmpfile();
972 if (CC->download_fp == NULL)
975 fwrite(content, length, 1, CC->download_fp);
976 fflush(CC->download_fp);
977 rewind(CC->download_fp);
979 OpenCmdResult(filename, cbtype);
985 * Callback function for mime parser that outputs a section all at once
987 void mime_spew_section(char *name, char *filename, char *partnum, char *disp,
988 void *content, char *cbtype, char *cbcharset, size_t length,
989 char *encoding, void *cbuserdata)
991 int *found_it = (int *)cbuserdata;
993 /* ...or if this is not the desired section */
994 if (strcasecmp(CC->download_desired_section, partnum))
999 cprintf("%d %d\n", BINARY_FOLLOWS, (int)length);
1000 client_write(content, length);
1006 * Load a message from disk into memory.
1007 * This is used by CtdlOutputMsg() and other fetch functions.
1009 * NOTE: Caller is responsible for freeing the returned CtdlMessage struct
1010 * using the CtdlMessageFree() function.
1012 struct CtdlMessage *CtdlFetchMessage(long msgnum, int with_body)
1014 struct cdbdata *dmsgtext;
1015 struct CtdlMessage *ret = NULL;
1019 cit_uint8_t field_header;
1021 lprintf(CTDL_DEBUG, "CtdlFetchMessage(%ld, %d)\n", msgnum, with_body);
1023 dmsgtext = cdb_fetch(CDB_MSGMAIN, &msgnum, sizeof(long));
1024 if (dmsgtext == NULL) {
1027 mptr = dmsgtext->ptr;
1028 upper_bound = mptr + dmsgtext->len;
1030 /* Parse the three bytes that begin EVERY message on disk.
1031 * The first is always 0xFF, the on-disk magic number.
1032 * The second is the anonymous/public type byte.
1033 * The third is the format type byte (vari, fixed, or MIME).
1037 lprintf(CTDL_ERR, "Message %ld appears to be corrupted.\n", msgnum);
1041 ret = (struct CtdlMessage *) malloc(sizeof(struct CtdlMessage));
1042 memset(ret, 0, sizeof(struct CtdlMessage));
1044 ret->cm_magic = CTDLMESSAGE_MAGIC;
1045 ret->cm_anon_type = *mptr++; /* Anon type byte */
1046 ret->cm_format_type = *mptr++; /* Format type byte */
1049 * The rest is zero or more arbitrary fields. Load them in.
1050 * We're done when we encounter either a zero-length field or
1051 * have just processed the 'M' (message text) field.
1054 if (mptr >= upper_bound) {
1057 field_header = *mptr++;
1058 ret->cm_fields[field_header] = strdup(mptr);
1060 while (*mptr++ != 0); /* advance to next field */
1062 } while ((mptr < upper_bound) && (field_header != 'M'));
1066 /* Always make sure there's something in the msg text field. If
1067 * it's NULL, the message text is most likely stored separately,
1068 * so go ahead and fetch that. Failing that, just set a dummy
1069 * body so other code doesn't barf.
1071 if ( (ret->cm_fields['M'] == NULL) && (with_body) ) {
1072 dmsgtext = cdb_fetch(CDB_BIGMSGS, &msgnum, sizeof(long));
1073 if (dmsgtext != NULL) {
1074 ret->cm_fields['M'] = strdup(dmsgtext->ptr);
1078 if (ret->cm_fields['M'] == NULL) {
1079 ret->cm_fields['M'] = strdup("\r\n\r\n (no text)\r\n");
1082 /* Perform "before read" hooks (aborting if any return nonzero) */
1083 if (PerformMessageHooks(ret, EVT_BEFOREREAD) > 0) {
1084 CtdlFreeMessage(ret);
1093 * Returns 1 if the supplied pointer points to a valid Citadel message.
1094 * If the pointer is NULL or the magic number check fails, returns 0.
1096 int is_valid_message(struct CtdlMessage *msg) {
1099 if ((msg->cm_magic) != CTDLMESSAGE_MAGIC) {
1100 lprintf(CTDL_WARNING, "is_valid_message() -- self-check failed\n");
1108 * 'Destructor' for struct CtdlMessage
1110 void CtdlFreeMessage(struct CtdlMessage *msg)
1114 if (is_valid_message(msg) == 0)
1116 if (msg != NULL) free (msg);
1120 for (i = 0; i < 256; ++i)
1121 if (msg->cm_fields[i] != NULL) {
1122 free(msg->cm_fields[i]);
1125 msg->cm_magic = 0; /* just in case */
1131 * Pre callback function for multipart/alternative
1133 * NOTE: this differs from the standard behavior for a reason. Normally when
1134 * displaying multipart/alternative you want to show the _last_ usable
1135 * format in the message. Here we show the _first_ one, because it's
1136 * usually text/plain. Since this set of functions is designed for text
1137 * output to non-MIME-aware clients, this is the desired behavior.
1140 void fixed_output_pre(char *name, char *filename, char *partnum, char *disp,
1141 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
1146 ma = (struct ma_info *)cbuserdata;
1147 lprintf(CTDL_DEBUG, "fixed_output_pre() type=<%s>\n", cbtype);
1148 if (!strcasecmp(cbtype, "multipart/alternative")) {
1152 if (!strcasecmp(cbtype, "message/rfc822")) {
1158 * Post callback function for multipart/alternative
1160 void fixed_output_post(char *name, char *filename, char *partnum, char *disp,
1161 void *content, char *cbtype, char *cbcharset, size_t length,
1162 char *encoding, void *cbuserdata)
1166 ma = (struct ma_info *)cbuserdata;
1167 lprintf(CTDL_DEBUG, "fixed_output_post() type=<%s>\n", cbtype);
1168 if (!strcasecmp(cbtype, "multipart/alternative")) {
1172 if (!strcasecmp(cbtype, "message/rfc822")) {
1178 * Inline callback function for mime parser that wants to display text
1180 void fixed_output(char *name, char *filename, char *partnum, char *disp,
1181 void *content, char *cbtype, char *cbcharset, size_t length,
1182 char *encoding, void *cbuserdata)
1189 ma = (struct ma_info *)cbuserdata;
1192 "fixed_output() part %s: %s (%s) (%ld bytes)\n",
1193 partnum, filename, cbtype, (long)length);
1196 * If we're in the middle of a multipart/alternative scope and
1197 * we've already printed another section, skip this one.
1199 if ( (ma->is_ma) && (ma->did_print) ) {
1200 lprintf(CTDL_DEBUG, "Skipping part %s (%s)\n", partnum, cbtype);
1205 if ( (!strcasecmp(cbtype, "text/plain"))
1206 || (IsEmptyStr(cbtype)) ) {
1209 client_write(wptr, length);
1210 if (wptr[length-1] != '\n') {
1217 if (!strcasecmp(cbtype, "text/html")) {
1218 ptr = html_to_ascii(content, length, 80, 0);
1220 client_write(ptr, wlen);
1221 if (ptr[wlen-1] != '\n') {
1228 if (ma->use_fo_hooks) {
1229 if (PerformFixedOutputHooks(cbtype, content, length)) {
1230 /* above function returns nonzero if it handled the part */
1235 if (strncasecmp(cbtype, "multipart/", 10)) {
1236 cprintf("Part %s: %s (%s) (%ld bytes)\r\n",
1237 partnum, filename, cbtype, (long)length);
1243 * The client is elegant and sophisticated and wants to be choosy about
1244 * MIME content types, so figure out which multipart/alternative part
1245 * we're going to send.
1247 * We use a system of weights. When we find a part that matches one of the
1248 * MIME types we've declared as preferential, we can store it in ma->chosen_part
1249 * and then set ma->chosen_pref to that MIME type's position in our preference
1250 * list. If we then hit another match, we only replace the first match if
1251 * the preference value is lower.
1253 void choose_preferred(char *name, char *filename, char *partnum, char *disp,
1254 void *content, char *cbtype, char *cbcharset, size_t length,
1255 char *encoding, void *cbuserdata)
1261 ma = (struct ma_info *)cbuserdata;
1263 // NOTE: REMOVING THIS CONDITIONAL FIXES BUG 220
1264 // http://bugzilla.citadel.org/show_bug.cgi?id=220
1265 // I don't know if there are any side effects! Please TEST TEST TEST
1266 //if (ma->is_ma > 0) {
1268 for (i=0; i<num_tokens(CC->preferred_formats, '|'); ++i) {
1269 extract_token(buf, CC->preferred_formats, i, '|', sizeof buf);
1270 if ( (!strcasecmp(buf, cbtype)) && (!ma->freeze) ) {
1271 if (i < ma->chosen_pref) {
1272 lprintf(CTDL_DEBUG, "Setting chosen part: <%s>\n", partnum);
1273 safestrncpy(ma->chosen_part, partnum, sizeof ma->chosen_part);
1274 ma->chosen_pref = i;
1281 * Now that we've chosen our preferred part, output it.
1283 void output_preferred(char *name, char *filename, char *partnum, char *disp,
1284 void *content, char *cbtype, char *cbcharset, size_t length,
1285 char *encoding, void *cbuserdata)
1289 int add_newline = 0;
1293 ma = (struct ma_info *)cbuserdata;
1295 /* This is not the MIME part you're looking for... */
1296 if (strcasecmp(partnum, ma->chosen_part)) return;
1298 /* If the content-type of this part is in our preferred formats
1299 * list, we can simply output it verbatim.
1301 for (i=0; i<num_tokens(CC->preferred_formats, '|'); ++i) {
1302 extract_token(buf, CC->preferred_formats, i, '|', sizeof buf);
1303 if (!strcasecmp(buf, cbtype)) {
1304 /* Yeah! Go! W00t!! */
1306 text_content = (char *)content;
1307 if (text_content[length-1] != '\n') {
1310 cprintf("Content-type: %s", cbtype);
1311 if (!IsEmptyStr(cbcharset)) {
1312 cprintf("; charset=%s", cbcharset);
1314 cprintf("\nContent-length: %d\n",
1315 (int)(length + add_newline) );
1316 if (!IsEmptyStr(encoding)) {
1317 cprintf("Content-transfer-encoding: %s\n", encoding);
1320 cprintf("Content-transfer-encoding: 7bit\n");
1322 cprintf("X-Citadel-MSG4-Partnum: %s\n", partnum);
1324 client_write(content, length);
1325 if (add_newline) cprintf("\n");
1330 /* No translations required or possible: output as text/plain */
1331 cprintf("Content-type: text/plain\n\n");
1332 fixed_output(name, filename, partnum, disp, content, cbtype, cbcharset,
1333 length, encoding, cbuserdata);
1338 char desired_section[64];
1345 * Callback function for
1347 void extract_encapsulated_message(char *name, char *filename, char *partnum, char *disp,
1348 void *content, char *cbtype, char *cbcharset, size_t length,
1349 char *encoding, void *cbuserdata)
1351 struct encapmsg *encap;
1353 encap = (struct encapmsg *)cbuserdata;
1355 /* Only proceed if this is the desired section... */
1356 if (!strcasecmp(encap->desired_section, partnum)) {
1357 encap->msglen = length;
1358 encap->msg = malloc(length + 2);
1359 memcpy(encap->msg, content, length);
1369 * Get a message off disk. (returns om_* values found in msgbase.h)
1372 int CtdlOutputMsg(long msg_num, /* message number (local) to fetch */
1373 int mode, /* how would you like that message? */
1374 int headers_only, /* eschew the message body? */
1375 int do_proto, /* do Citadel protocol responses? */
1376 int crlf, /* Use CRLF newlines instead of LF? */
1377 char *section /* NULL or a message/rfc822 section */
1379 struct CtdlMessage *TheMessage = NULL;
1380 int retcode = om_no_such_msg;
1381 struct encapmsg encap;
1383 lprintf(CTDL_DEBUG, "CtdlOutputMsg() msgnum=%ld, mode=%d, section=%s\n",
1385 (section ? section : "<>")
1388 if ((!(CC->logged_in)) && (!(CC->internal_pgm))) {
1389 if (do_proto) cprintf("%d Not logged in.\n",
1390 ERROR + NOT_LOGGED_IN);
1391 return(om_not_logged_in);
1394 /* FIXME: check message id against msglist for this room */
1397 * Fetch the message from disk. If we're in any sort of headers
1398 * only mode, request that we don't even bother loading the body
1401 if ( (headers_only == HEADERS_FAST) || (headers_only == HEADERS_ONLY) ) {
1402 TheMessage = CtdlFetchMessage(msg_num, 0);
1405 TheMessage = CtdlFetchMessage(msg_num, 1);
1408 if (TheMessage == NULL) {
1409 if (do_proto) cprintf("%d Can't locate msg %ld on disk\n",
1410 ERROR + MESSAGE_NOT_FOUND, msg_num);
1411 return(om_no_such_msg);
1414 /* Here is the weird form of this command, to process only an
1415 * encapsulated message/rfc822 section.
1417 if (section) if (!IsEmptyStr(section)) if (strcmp(section, "0")) {
1418 memset(&encap, 0, sizeof encap);
1419 safestrncpy(encap.desired_section, section, sizeof encap.desired_section);
1420 mime_parser(TheMessage->cm_fields['M'],
1422 *extract_encapsulated_message,
1423 NULL, NULL, (void *)&encap, 0
1425 CtdlFreeMessage(TheMessage);
1429 encap.msg[encap.msglen] = 0;
1430 TheMessage = convert_internet_message(encap.msg);
1431 encap.msg = NULL; /* no free() here, TheMessage owns it now */
1433 /* Now we let it fall through to the bottom of this
1434 * function, because TheMessage now contains the
1435 * encapsulated message instead of the top-level
1436 * message. Isn't that neat?
1441 if (do_proto) cprintf("%d msg %ld has no part %s\n",
1442 ERROR + MESSAGE_NOT_FOUND, msg_num, section);
1443 retcode = om_no_such_msg;
1448 /* Ok, output the message now */
1449 retcode = CtdlOutputPreLoadedMsg(TheMessage, mode, headers_only, do_proto, crlf);
1450 CtdlFreeMessage(TheMessage);
1457 * Get a message off disk. (returns om_* values found in msgbase.h)
1459 int CtdlOutputPreLoadedMsg(
1460 struct CtdlMessage *TheMessage,
1461 int mode, /* how would you like that message? */
1462 int headers_only, /* eschew the message body? */
1463 int do_proto, /* do Citadel protocol responses? */
1464 int crlf /* Use CRLF newlines instead of LF? */
1470 char display_name[256];
1472 char *nl; /* newline string */
1474 int subject_found = 0;
1477 /* Buffers needed for RFC822 translation. These are all filled
1478 * using functions that are bounds-checked, and therefore we can
1479 * make them substantially smaller than SIZ.
1487 char datestamp[100];
1489 lprintf(CTDL_DEBUG, "CtdlOutputPreLoadedMsg(TheMessage=%s, %d, %d, %d, %d\n",
1490 ((TheMessage == NULL) ? "NULL" : "not null"),
1491 mode, headers_only, do_proto, crlf);
1493 strcpy(mid, "unknown");
1494 nl = (crlf ? "\r\n" : "\n");
1496 if (!is_valid_message(TheMessage)) {
1498 "ERROR: invalid preloaded message for output\n");
1499 return(om_no_such_msg);
1502 /* Are we downloading a MIME component? */
1503 if (mode == MT_DOWNLOAD) {
1504 if (TheMessage->cm_format_type != FMT_RFC822) {
1506 cprintf("%d This is not a MIME message.\n",
1507 ERROR + ILLEGAL_VALUE);
1508 } else if (CC->download_fp != NULL) {
1509 if (do_proto) cprintf(
1510 "%d You already have a download open.\n",
1511 ERROR + RESOURCE_BUSY);
1513 /* Parse the message text component */
1514 mptr = TheMessage->cm_fields['M'];
1515 mime_parser(mptr, NULL, *mime_download, NULL, NULL, NULL, 0);
1516 /* If there's no file open by this time, the requested
1517 * section wasn't found, so print an error
1519 if (CC->download_fp == NULL) {
1520 if (do_proto) cprintf(
1521 "%d Section %s not found.\n",
1522 ERROR + FILE_NOT_FOUND,
1523 CC->download_desired_section);
1526 return((CC->download_fp != NULL) ? om_ok : om_mime_error);
1529 /* MT_SPEW_SECTION is like MT_DOWNLOAD except it outputs the whole MIME part
1530 * in a single server operation instead of opening a download file.
1532 if (mode == MT_SPEW_SECTION) {
1533 if (TheMessage->cm_format_type != FMT_RFC822) {
1535 cprintf("%d This is not a MIME message.\n",
1536 ERROR + ILLEGAL_VALUE);
1538 /* Parse the message text component */
1541 mptr = TheMessage->cm_fields['M'];
1542 mime_parser(mptr, NULL, *mime_spew_section, NULL, NULL, (void *)&found_it, 0);
1543 /* If section wasn't found, print an error
1546 if (do_proto) cprintf(
1547 "%d Section %s not found.\n",
1548 ERROR + FILE_NOT_FOUND,
1549 CC->download_desired_section);
1552 return((CC->download_fp != NULL) ? om_ok : om_mime_error);
1555 /* now for the user-mode message reading loops */
1556 if (do_proto) cprintf("%d msg:\n", LISTING_FOLLOWS);
1558 /* Does the caller want to skip the headers? */
1559 if (headers_only == HEADERS_NONE) goto START_TEXT;
1561 /* Tell the client which format type we're using. */
1562 if ( (mode == MT_CITADEL) && (do_proto) ) {
1563 cprintf("type=%d\n", TheMessage->cm_format_type);
1566 /* nhdr=yes means that we're only displaying headers, no body */
1567 if ( (TheMessage->cm_anon_type == MES_ANONONLY)
1568 && (mode == MT_CITADEL)
1571 cprintf("nhdr=yes\n");
1574 /* begin header processing loop for Citadel message format */
1576 if ((mode == MT_CITADEL) || (mode == MT_MIME)) {
1578 safestrncpy(display_name, "<unknown>", sizeof display_name);
1579 if (TheMessage->cm_fields['A']) {
1580 strcpy(buf, TheMessage->cm_fields['A']);
1581 if (TheMessage->cm_anon_type == MES_ANONONLY) {
1582 safestrncpy(display_name, "****", sizeof display_name);
1584 else if (TheMessage->cm_anon_type == MES_ANONOPT) {
1585 safestrncpy(display_name, "anonymous", sizeof display_name);
1588 safestrncpy(display_name, buf, sizeof display_name);
1590 if ((is_room_aide())
1591 && ((TheMessage->cm_anon_type == MES_ANONONLY)
1592 || (TheMessage->cm_anon_type == MES_ANONOPT))) {
1593 size_t tmp = strlen(display_name);
1594 snprintf(&display_name[tmp],
1595 sizeof display_name - tmp,
1600 /* Don't show Internet address for users on the
1601 * local Citadel network.
1604 if (TheMessage->cm_fields['N'] != NULL)
1605 if (!IsEmptyStr(TheMessage->cm_fields['N']))
1606 if (haschar(TheMessage->cm_fields['N'], '.') == 0) {
1610 /* Now spew the header fields in the order we like them. */
1611 safestrncpy(allkeys, FORDER, sizeof allkeys);
1612 for (i=0; i<strlen(allkeys); ++i) {
1613 k = (int) allkeys[i];
1615 if ( (TheMessage->cm_fields[k] != NULL)
1616 && (msgkeys[k] != NULL) ) {
1618 if (do_proto) cprintf("%s=%s\n",
1622 else if ((k == 'F') && (suppress_f)) {
1625 /* Masquerade display name if needed */
1627 if (do_proto) cprintf("%s=%s\n",
1629 TheMessage->cm_fields[k]
1638 /* begin header processing loop for RFC822 transfer format */
1643 strcpy(snode, NODENAME);
1644 strcpy(lnode, HUMANNODE);
1645 if (mode == MT_RFC822) {
1646 for (i = 0; i < 256; ++i) {
1647 if (TheMessage->cm_fields[i]) {
1648 mptr = TheMessage->cm_fields[i];
1651 safestrncpy(luser, mptr, sizeof luser);
1652 safestrncpy(suser, mptr, sizeof suser);
1654 else if (i == 'Y') {
1655 cprintf("CC: %s%s", mptr, nl);
1657 else if (i == 'P') {
1658 cprintf("Return-Path: %s%s", mptr, nl);
1660 else if (i == 'V') {
1661 cprintf("Envelope-To: %s%s", mptr, nl);
1663 else if (i == 'U') {
1664 cprintf("Subject: %s%s", mptr, nl);
1668 safestrncpy(mid, mptr, sizeof mid);
1670 safestrncpy(lnode, mptr, sizeof lnode);
1672 safestrncpy(fuser, mptr, sizeof fuser);
1673 /* else if (i == 'O')
1674 cprintf("X-Citadel-Room: %s%s",
1677 safestrncpy(snode, mptr, sizeof snode);
1680 if (haschar(mptr, '@') == 0)
1682 cprintf("To: %s@%s%s", mptr, config.c_fqdn, nl);
1686 cprintf("To: %s%s", mptr, nl);
1689 else if (i == 'T') {
1690 datestring(datestamp, sizeof datestamp,
1691 atol(mptr), DATESTRING_RFC822);
1692 cprintf("Date: %s%s", datestamp, nl);
1696 if (subject_found == 0) {
1697 cprintf("Subject: (no subject)%s", nl);
1701 for (i=0; !IsEmptyStr(&suser[i]); ++i) {
1702 suser[i] = tolower(suser[i]);
1703 if (!isalnum(suser[i])) suser[i]='_';
1706 if (mode == MT_RFC822) {
1707 if (!strcasecmp(snode, NODENAME)) {
1708 safestrncpy(snode, FQDN, sizeof snode);
1711 /* Construct a fun message id */
1712 cprintf("Message-ID: <%s", mid);
1713 if (strchr(mid, '@')==NULL) {
1714 cprintf("@%s", snode);
1718 if (!is_room_aide() && (TheMessage->cm_anon_type == MES_ANONONLY)) {
1719 cprintf("From: \"----\" <x@x.org>%s", nl);
1721 else if (!is_room_aide() && (TheMessage->cm_anon_type == MES_ANONOPT)) {
1722 cprintf("From: \"anonymous\" <x@x.org>%s", nl);
1724 else if (!IsEmptyStr(fuser)) {
1725 cprintf("From: \"%s\" <%s>%s", luser, fuser, nl);
1728 cprintf("From: \"%s\" <%s@%s>%s", luser, suser, snode, nl);
1731 cprintf("Organization: %s%s", lnode, nl);
1733 /* Blank line signifying RFC822 end-of-headers */
1734 if (TheMessage->cm_format_type != FMT_RFC822) {
1739 /* end header processing loop ... at this point, we're in the text */
1741 if (headers_only == HEADERS_FAST) goto DONE;
1742 mptr = TheMessage->cm_fields['M'];
1744 /* Tell the client about the MIME parts in this message */
1745 if (TheMessage->cm_format_type == FMT_RFC822) {
1746 if ( (mode == MT_CITADEL) || (mode == MT_MIME) ) {
1747 memset(&ma, 0, sizeof(struct ma_info));
1748 mime_parser(mptr, NULL,
1749 (do_proto ? *list_this_part : NULL),
1750 (do_proto ? *list_this_pref : NULL),
1751 (do_proto ? *list_this_suff : NULL),
1754 else if (mode == MT_RFC822) { /* unparsed RFC822 dump */
1755 char *start_of_text = NULL;
1756 start_of_text = strstr(mptr, "\n\r\n");
1757 if (start_of_text == NULL) start_of_text = strstr(mptr, "\n\n");
1758 if (start_of_text == NULL) start_of_text = mptr;
1760 start_of_text = strstr(start_of_text, "\n");
1765 int nllen = strlen(nl);
1766 while (ch=*mptr, ch!=0) {
1772 ((headers_only == HEADERS_NONE) && (mptr >= start_of_text))
1773 || ((headers_only == HEADERS_ONLY) && (mptr < start_of_text))
1774 || ((headers_only != HEADERS_NONE) && (headers_only != HEADERS_ONLY))
1777 sprintf(&outbuf[outlen], "%s", nl);
1781 outbuf[outlen++] = ch;
1786 if (outlen > 1000) {
1787 client_write(outbuf, outlen);
1792 client_write(outbuf, outlen);
1800 if (headers_only == HEADERS_ONLY) {
1804 /* signify start of msg text */
1805 if ( (mode == MT_CITADEL) || (mode == MT_MIME) ) {
1806 if (do_proto) cprintf("text\n");
1809 /* If the format type on disk is 1 (fixed-format), then we want
1810 * everything to be output completely literally ... regardless of
1811 * what message transfer format is in use.
1813 if (TheMessage->cm_format_type == FMT_FIXED) {
1815 if (mode == MT_MIME) {
1816 cprintf("Content-type: text/plain\n\n");
1820 while (ch = *mptr++, ch > 0) {
1823 if ((ch == 10) || (buflen > 250)) {
1825 cprintf("%s%s", buf, nl);
1834 if (!IsEmptyStr(buf))
1835 cprintf("%s%s", buf, nl);
1838 /* If the message on disk is format 0 (Citadel vari-format), we
1839 * output using the formatter at 80 columns. This is the final output
1840 * form if the transfer format is RFC822, but if the transfer format
1841 * is Citadel proprietary, it'll still work, because the indentation
1842 * for new paragraphs is correct and the client will reformat the
1843 * message to the reader's screen width.
1845 if (TheMessage->cm_format_type == FMT_CITADEL) {
1846 if (mode == MT_MIME) {
1847 cprintf("Content-type: text/x-citadel-variformat\n\n");
1849 memfmout(mptr, 0, nl);
1852 /* If the message on disk is format 4 (MIME), we've gotta hand it
1853 * off to the MIME parser. The client has already been told that
1854 * this message is format 1 (fixed format), so the callback function
1855 * we use will display those parts as-is.
1857 if (TheMessage->cm_format_type == FMT_RFC822) {
1858 memset(&ma, 0, sizeof(struct ma_info));
1860 if (mode == MT_MIME) {
1861 ma.use_fo_hooks = 0;
1862 strcpy(ma.chosen_part, "1");
1863 ma.chosen_pref = 9999;
1864 mime_parser(mptr, NULL,
1865 *choose_preferred, *fixed_output_pre,
1866 *fixed_output_post, (void *)&ma, 0);
1867 mime_parser(mptr, NULL,
1868 *output_preferred, NULL, NULL, (void *)&ma, CC->msg4_dont_decode);
1871 ma.use_fo_hooks = 1;
1872 mime_parser(mptr, NULL,
1873 *fixed_output, *fixed_output_pre,
1874 *fixed_output_post, (void *)&ma, 0);
1879 DONE: /* now we're done */
1880 if (do_proto) cprintf("000\n");
1887 * display a message (mode 0 - Citadel proprietary)
1889 void cmd_msg0(char *cmdbuf)
1892 int headers_only = HEADERS_ALL;
1894 msgid = extract_long(cmdbuf, 0);
1895 headers_only = extract_int(cmdbuf, 1);
1897 CtdlOutputMsg(msgid, MT_CITADEL, headers_only, 1, 0, NULL);
1903 * display a message (mode 2 - RFC822)
1905 void cmd_msg2(char *cmdbuf)
1908 int headers_only = HEADERS_ALL;
1910 msgid = extract_long(cmdbuf, 0);
1911 headers_only = extract_int(cmdbuf, 1);
1913 CtdlOutputMsg(msgid, MT_RFC822, headers_only, 1, 1, NULL);
1919 * display a message (mode 3 - IGnet raw format - internal programs only)
1921 void cmd_msg3(char *cmdbuf)
1924 struct CtdlMessage *msg = NULL;
1927 if (CC->internal_pgm == 0) {
1928 cprintf("%d This command is for internal programs only.\n",
1929 ERROR + HIGHER_ACCESS_REQUIRED);
1933 msgnum = extract_long(cmdbuf, 0);
1934 msg = CtdlFetchMessage(msgnum, 1);
1936 cprintf("%d Message %ld not found.\n",
1937 ERROR + MESSAGE_NOT_FOUND, msgnum);
1941 serialize_message(&smr, msg);
1942 CtdlFreeMessage(msg);
1945 cprintf("%d Unable to serialize message\n",
1946 ERROR + INTERNAL_ERROR);
1950 cprintf("%d %ld\n", BINARY_FOLLOWS, (long)smr.len);
1951 client_write((char *)smr.ser, (int)smr.len);
1958 * Display a message using MIME content types
1960 void cmd_msg4(char *cmdbuf)
1965 msgid = extract_long(cmdbuf, 0);
1966 extract_token(section, cmdbuf, 1, '|', sizeof section);
1967 CtdlOutputMsg(msgid, MT_MIME, 0, 1, 0, (section[0] ? section : NULL) );
1973 * Client tells us its preferred message format(s)
1975 void cmd_msgp(char *cmdbuf)
1977 if (!strcasecmp(cmdbuf, "dont_decode")) {
1978 CC->msg4_dont_decode = 1;
1979 cprintf("%d MSG4 will not pre-decode messages.\n", CIT_OK);
1982 safestrncpy(CC->preferred_formats, cmdbuf, sizeof(CC->preferred_formats));
1983 cprintf("%d Preferred MIME formats have been set.\n", CIT_OK);
1989 * Open a component of a MIME message as a download file
1991 void cmd_opna(char *cmdbuf)
1994 char desired_section[128];
1996 msgid = extract_long(cmdbuf, 0);
1997 extract_token(desired_section, cmdbuf, 1, '|', sizeof desired_section);
1998 safestrncpy(CC->download_desired_section, desired_section,
1999 sizeof CC->download_desired_section);
2000 CtdlOutputMsg(msgid, MT_DOWNLOAD, 0, 1, 1, NULL);
2005 * Open a component of a MIME message and transmit it all at once
2007 void cmd_dlat(char *cmdbuf)
2010 char desired_section[128];
2012 msgid = extract_long(cmdbuf, 0);
2013 extract_token(desired_section, cmdbuf, 1, '|', sizeof desired_section);
2014 safestrncpy(CC->download_desired_section, desired_section,
2015 sizeof CC->download_desired_section);
2016 CtdlOutputMsg(msgid, MT_SPEW_SECTION, 0, 1, 1, NULL);
2021 * Save one or more message pointers into a specified room
2022 * (Returns 0 for success, nonzero for failure)
2023 * roomname may be NULL to use the current room
2025 * Note that the 'supplied_msg' field may be set to NULL, in which case
2026 * the message will be fetched from disk, by number, if we need to perform
2027 * replication checks. This adds an additional database read, so if the
2028 * caller already has the message in memory then it should be supplied. (Obviously
2029 * this mode of operation only works if we're saving a single message.)
2031 int CtdlSaveMsgPointersInRoom(char *roomname, long newmsgidlist[], int num_newmsgs,
2032 int do_repl_check, struct CtdlMessage *supplied_msg)
2035 char hold_rm[ROOMNAMELEN];
2036 struct cdbdata *cdbfr;
2039 long highest_msg = 0L;
2042 struct CtdlMessage *msg = NULL;
2044 long *msgs_to_be_merged = NULL;
2045 int num_msgs_to_be_merged = 0;
2048 "CtdlSaveMsgPointersInRoom(room=%s, num_msgs=%d, repl=%d)\n",
2049 roomname, num_newmsgs, do_repl_check);
2051 strcpy(hold_rm, CC->room.QRname);
2054 if (newmsgidlist == NULL) return(ERROR + INTERNAL_ERROR);
2055 if (num_newmsgs < 1) return(ERROR + INTERNAL_ERROR);
2056 if (num_newmsgs > 1) supplied_msg = NULL;
2058 /* Now the regular stuff */
2059 if (lgetroom(&CC->room,
2060 ((roomname != NULL) ? roomname : CC->room.QRname) )
2062 lprintf(CTDL_ERR, "No such room <%s>\n", roomname);
2063 return(ERROR + ROOM_NOT_FOUND);
2067 msgs_to_be_merged = malloc(sizeof(long) * num_newmsgs);
2068 num_msgs_to_be_merged = 0;
2071 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long));
2072 if (cdbfr == NULL) {
2076 msglist = (long *) cdbfr->ptr;
2077 cdbfr->ptr = NULL; /* CtdlSaveMsgPointerInRoom() now owns this memory */
2078 num_msgs = cdbfr->len / sizeof(long);
2083 /* Create a list of msgid's which were supplied by the caller, but do
2084 * not already exist in the target room. It is absolutely taboo to
2085 * have more than one reference to the same message in a room.
2087 for (i=0; i<num_newmsgs; ++i) {
2089 if (num_msgs > 0) for (j=0; j<num_msgs; ++j) {
2090 if (msglist[j] == newmsgidlist[i]) {
2095 msgs_to_be_merged[num_msgs_to_be_merged++] = newmsgidlist[i];
2099 lprintf(9, "%d unique messages to be merged\n", num_msgs_to_be_merged);
2102 * Now merge the new messages
2104 msglist = realloc(msglist, (sizeof(long) * (num_msgs + num_msgs_to_be_merged)) );
2105 if (msglist == NULL) {
2106 lprintf(CTDL_ALERT, "ERROR: can't realloc message list!\n");
2108 memcpy(&msglist[num_msgs], msgs_to_be_merged, (sizeof(long) * num_msgs_to_be_merged) );
2109 num_msgs += num_msgs_to_be_merged;
2111 /* Sort the message list, so all the msgid's are in order */
2112 num_msgs = sort_msglist(msglist, num_msgs);
2114 /* Determine the highest message number */
2115 highest_msg = msglist[num_msgs - 1];
2117 /* Write it back to disk. */
2118 cdb_store(CDB_MSGLISTS, &CC->room.QRnumber, (int)sizeof(long),
2119 msglist, (int)(num_msgs * sizeof(long)));
2121 /* Free up the memory we used. */
2124 /* Update the highest-message pointer and unlock the room. */
2125 CC->room.QRhighest = highest_msg;
2126 lputroom(&CC->room);
2128 /* Perform replication checks if necessary */
2129 if ( (DoesThisRoomNeedEuidIndexing(&CC->room)) && (do_repl_check) ) {
2130 lprintf(CTDL_DEBUG, "CtdlSaveMsgPointerInRoom() doing repl checks\n");
2132 for (i=0; i<num_msgs_to_be_merged; ++i) {
2133 msgid = msgs_to_be_merged[i];
2135 if (supplied_msg != NULL) {
2139 msg = CtdlFetchMessage(msgid, 0);
2143 ReplicationChecks(msg);
2145 /* If the message has an Exclusive ID, index that... */
2146 if (msg->cm_fields['E'] != NULL) {
2147 index_message_by_euid(msg->cm_fields['E'], &CC->room, msgid);
2150 /* Free up the memory we may have allocated */
2151 if (msg != supplied_msg) {
2152 CtdlFreeMessage(msg);
2160 lprintf(CTDL_DEBUG, "CtdlSaveMsgPointerInRoom() skips repl checks\n");
2163 /* Submit this room for processing by hooks */
2164 PerformRoomHooks(&CC->room);
2166 /* Go back to the room we were in before we wandered here... */
2167 getroom(&CC->room, hold_rm);
2169 /* Bump the reference count for all messages which were merged */
2170 for (i=0; i<num_msgs_to_be_merged; ++i) {
2171 AdjRefCount(msgs_to_be_merged[i], +1);
2174 /* Free up memory... */
2175 if (msgs_to_be_merged != NULL) {
2176 free(msgs_to_be_merged);
2179 /* Return success. */
2185 * This is the same as CtdlSaveMsgPointersInRoom() but it only accepts
2188 int CtdlSaveMsgPointerInRoom(char *roomname, long msgid,
2189 int do_repl_check, struct CtdlMessage *supplied_msg)
2191 return CtdlSaveMsgPointersInRoom(roomname, &msgid, 1, do_repl_check, supplied_msg);
2198 * Message base operation to save a new message to the message store
2199 * (returns new message number)
2201 * This is the back end for CtdlSubmitMsg() and should not be directly
2202 * called by server-side modules.
2205 long send_message(struct CtdlMessage *msg) {
2213 /* Get a new message number */
2214 newmsgid = get_new_message_number();
2215 snprintf(msgidbuf, sizeof msgidbuf, "%010ld@%s", newmsgid, config.c_fqdn);
2217 /* Generate an ID if we don't have one already */
2218 if (msg->cm_fields['I']==NULL) {
2219 msg->cm_fields['I'] = strdup(msgidbuf);
2222 /* If the message is big, set its body aside for storage elsewhere */
2223 if (msg->cm_fields['M'] != NULL) {
2224 if (strlen(msg->cm_fields['M']) > BIGMSG) {
2226 holdM = msg->cm_fields['M'];
2227 msg->cm_fields['M'] = NULL;
2231 /* Serialize our data structure for storage in the database */
2232 serialize_message(&smr, msg);
2235 msg->cm_fields['M'] = holdM;
2239 cprintf("%d Unable to serialize message\n",
2240 ERROR + INTERNAL_ERROR);
2244 /* Write our little bundle of joy into the message base */
2245 if (cdb_store(CDB_MSGMAIN, &newmsgid, (int)sizeof(long),
2246 smr.ser, smr.len) < 0) {
2247 lprintf(CTDL_ERR, "Can't store message\n");
2251 cdb_store(CDB_BIGMSGS,
2261 /* Free the memory we used for the serialized message */
2264 /* Return the *local* message ID to the caller
2265 * (even if we're storing an incoming network message)
2273 * Serialize a struct CtdlMessage into the format used on disk and network.
2275 * This function loads up a "struct ser_ret" (defined in server.h) which
2276 * contains the length of the serialized message and a pointer to the
2277 * serialized message in memory. THE LATTER MUST BE FREED BY THE CALLER.
2279 void serialize_message(struct ser_ret *ret, /* return values */
2280 struct CtdlMessage *msg) /* unserialized msg */
2282 size_t wlen, fieldlen;
2284 static char *forder = FORDER;
2287 * Check for valid message format
2289 if (is_valid_message(msg) == 0) {
2290 lprintf(CTDL_ERR, "serialize_message() aborting due to invalid message\n");
2297 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL)
2298 ret->len = ret->len +
2299 strlen(msg->cm_fields[(int)forder[i]]) + 2;
2301 ret->ser = malloc(ret->len);
2302 if (ret->ser == NULL) {
2303 lprintf(CTDL_ERR, "serialize_message() malloc(%ld) failed: %s\n",
2304 (long)ret->len, strerror(errno));
2311 ret->ser[1] = msg->cm_anon_type;
2312 ret->ser[2] = msg->cm_format_type;
2315 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL) {
2316 fieldlen = strlen(msg->cm_fields[(int)forder[i]]);
2317 ret->ser[wlen++] = (char)forder[i];
2318 safestrncpy((char *)&ret->ser[wlen], msg->cm_fields[(int)forder[i]], fieldlen+1);
2319 wlen = wlen + fieldlen + 1;
2321 if (ret->len != wlen) lprintf(CTDL_ERR, "ERROR: len=%ld wlen=%ld\n",
2322 (long)ret->len, (long)wlen);
2329 * Serialize a struct CtdlMessage into the format used on disk and network.
2331 * This function loads up a "struct ser_ret" (defined in server.h) which
2332 * contains the length of the serialized message and a pointer to the
2333 * serialized message in memory. THE LATTER MUST BE FREED BY THE CALLER.
2335 void dump_message(struct CtdlMessage *msg, /* unserialized msg */
2336 long Siz) /* how many chars ? */
2340 static char *forder = FORDER;
2344 * Check for valid message format
2346 if (is_valid_message(msg) == 0) {
2347 lprintf(CTDL_ERR, "dump_message() aborting due to invalid message\n");
2351 buf = (char*) malloc (Siz + 1);
2355 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL) {
2356 snprintf (buf, Siz, " msg[%c] = %s ...\n", (char) forder[i],
2357 msg->cm_fields[(int)forder[i]]);
2358 client_write (buf, strlen(buf));
2367 * Check to see if any messages already exist in the current room which
2368 * carry the same Exclusive ID as this one. If any are found, delete them.
2370 void ReplicationChecks(struct CtdlMessage *msg) {
2371 long old_msgnum = (-1L);
2373 if (DoesThisRoomNeedEuidIndexing(&CC->room) == 0) return;
2375 lprintf(CTDL_DEBUG, "Performing replication checks in <%s>\n",
2378 /* No exclusive id? Don't do anything. */
2379 if (msg == NULL) return;
2380 if (msg->cm_fields['E'] == NULL) return;
2381 if (IsEmptyStr(msg->cm_fields['E'])) return;
2382 /*lprintf(CTDL_DEBUG, "Exclusive ID: <%s> for room <%s>\n",
2383 msg->cm_fields['E'], CC->room.QRname);*/
2385 old_msgnum = locate_message_by_euid(msg->cm_fields['E'], &CC->room);
2386 if (old_msgnum > 0L) {
2387 lprintf(CTDL_DEBUG, "ReplicationChecks() replacing message %ld\n", old_msgnum);
2388 CtdlDeleteMessages(CC->room.QRname, &old_msgnum, 1, "");
2395 * Save a message to disk and submit it into the delivery system.
2397 long CtdlSubmitMsg(struct CtdlMessage *msg, /* message to save */
2398 struct recptypes *recps, /* recipients (if mail) */
2399 char *force /* force a particular room? */
2401 char submit_filename[128];
2402 char generated_timestamp[32];
2403 char hold_rm[ROOMNAMELEN];
2404 char actual_rm[ROOMNAMELEN];
2405 char force_room[ROOMNAMELEN];
2406 char content_type[SIZ]; /* We have to learn this */
2407 char recipient[SIZ];
2410 struct ctdluser userbuf;
2412 struct MetaData smi;
2413 FILE *network_fp = NULL;
2414 static int seqnum = 1;
2415 struct CtdlMessage *imsg = NULL;
2417 size_t instr_alloc = 0;
2419 char *hold_R, *hold_D;
2420 char *collected_addresses = NULL;
2421 struct addresses_to_be_filed *aptr = NULL;
2422 char *saved_rfc822_version = NULL;
2423 int qualified_for_journaling = 0;
2424 struct CitContext *CCC = CC; /* CachedCitContext - performance boost */
2426 lprintf(CTDL_DEBUG, "CtdlSubmitMsg() called\n");
2427 if (is_valid_message(msg) == 0) return(-1); /* self check */
2429 /* If this message has no timestamp, we take the liberty of
2430 * giving it one, right now.
2432 if (msg->cm_fields['T'] == NULL) {
2433 snprintf(generated_timestamp, sizeof generated_timestamp, "%ld", (long)time(NULL));
2434 msg->cm_fields['T'] = strdup(generated_timestamp);
2437 /* If this message has no path, we generate one.
2439 if (msg->cm_fields['P'] == NULL) {
2440 if (msg->cm_fields['A'] != NULL) {
2441 msg->cm_fields['P'] = strdup(msg->cm_fields['A']);
2442 for (a=0; !IsEmptyStr(&msg->cm_fields['P'][a]); ++a) {
2443 if (isspace(msg->cm_fields['P'][a])) {
2444 msg->cm_fields['P'][a] = ' ';
2449 msg->cm_fields['P'] = strdup("unknown");
2453 if (force == NULL) {
2454 strcpy(force_room, "");
2457 strcpy(force_room, force);
2460 /* Learn about what's inside, because it's what's inside that counts */
2461 if (msg->cm_fields['M'] == NULL) {
2462 lprintf(CTDL_ERR, "ERROR: attempt to save message with NULL body\n");
2466 switch (msg->cm_format_type) {
2468 strcpy(content_type, "text/x-citadel-variformat");
2471 strcpy(content_type, "text/plain");
2474 strcpy(content_type, "text/plain");
2475 mptr = bmstrcasestr(msg->cm_fields['M'], "Content-type:");
2477 safestrncpy(content_type, &mptr[13], sizeof content_type);
2478 striplt(content_type);
2479 for (a = 0; a < strlen(content_type); ++a) {
2480 if ((content_type[a] == ';')
2481 || (content_type[a] == ' ')
2482 || (content_type[a] == 13)
2483 || (content_type[a] == 10)) {
2484 content_type[a] = 0;
2490 /* Goto the correct room */
2491 lprintf(CTDL_DEBUG, "Selected room %s\n", (recps) ? CCC->room.QRname : SENTITEMS);
2492 strcpy(hold_rm, CCC->room.QRname);
2493 strcpy(actual_rm, CCC->room.QRname);
2494 if (recps != NULL) {
2495 strcpy(actual_rm, SENTITEMS);
2498 /* If the user is a twit, move to the twit room for posting */
2500 if (CCC->user.axlevel == 2) {
2501 strcpy(hold_rm, actual_rm);
2502 strcpy(actual_rm, config.c_twitroom);
2503 lprintf(CTDL_DEBUG, "Diverting to twit room\n");
2507 /* ...or if this message is destined for Aide> then go there. */
2508 if (!IsEmptyStr(force_room)) {
2509 strcpy(actual_rm, force_room);
2512 lprintf(CTDL_DEBUG, "Final selection: %s\n", actual_rm);
2513 if (strcasecmp(actual_rm, CCC->room.QRname)) {
2514 /* getroom(&CCC->room, actual_rm); */
2515 usergoto(actual_rm, 0, 1, NULL, NULL);
2519 * If this message has no O (room) field, generate one.
2521 if (msg->cm_fields['O'] == NULL) {
2522 msg->cm_fields['O'] = strdup(CCC->room.QRname);
2525 /* Perform "before save" hooks (aborting if any return nonzero) */
2526 lprintf(CTDL_DEBUG, "Performing before-save hooks\n");
2527 if (PerformMessageHooks(msg, EVT_BEFORESAVE) > 0) return(-3);
2530 * If this message has an Exclusive ID, and the room is replication
2531 * checking enabled, then do replication checks.
2533 if (DoesThisRoomNeedEuidIndexing(&CCC->room)) {
2534 ReplicationChecks(msg);
2537 /* Save it to disk */
2538 lprintf(CTDL_DEBUG, "Saving to disk\n");
2539 newmsgid = send_message(msg);
2540 if (newmsgid <= 0L) return(-5);
2542 /* Write a supplemental message info record. This doesn't have to
2543 * be a critical section because nobody else knows about this message
2546 lprintf(CTDL_DEBUG, "Creating MetaData record\n");
2547 memset(&smi, 0, sizeof(struct MetaData));
2548 smi.meta_msgnum = newmsgid;
2549 smi.meta_refcount = 0;
2550 safestrncpy(smi.meta_content_type, content_type,
2551 sizeof smi.meta_content_type);
2554 * Measure how big this message will be when rendered as RFC822.
2555 * We do this for two reasons:
2556 * 1. We need the RFC822 length for the new metadata record, so the
2557 * POP and IMAP services don't have to calculate message lengths
2558 * while the user is waiting (multiplied by potentially hundreds
2559 * or thousands of messages).
2560 * 2. If journaling is enabled, we will need an RFC822 version of the
2561 * message to attach to the journalized copy.
2563 if (CCC->redirect_buffer != NULL) {
2564 lprintf(CTDL_ALERT, "CCC->redirect_buffer is not NULL during message submission!\n");
2567 CCC->redirect_buffer = malloc(SIZ);
2568 CCC->redirect_len = 0;
2569 CCC->redirect_alloc = SIZ;
2570 CtdlOutputPreLoadedMsg(msg, MT_RFC822, HEADERS_ALL, 0, 1);
2571 smi.meta_rfc822_length = CCC->redirect_len;
2572 saved_rfc822_version = CCC->redirect_buffer;
2573 CCC->redirect_buffer = NULL;
2574 CCC->redirect_len = 0;
2575 CCC->redirect_alloc = 0;
2579 /* Now figure out where to store the pointers */
2580 lprintf(CTDL_DEBUG, "Storing pointers\n");
2582 /* If this is being done by the networker delivering a private
2583 * message, we want to BYPASS saving the sender's copy (because there
2584 * is no local sender; it would otherwise go to the Trashcan).
2586 if ((!CCC->internal_pgm) || (recps == NULL)) {
2587 if (CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 1, msg) != 0) {
2588 lprintf(CTDL_ERR, "ERROR saving message pointer!\n");
2589 CtdlSaveMsgPointerInRoom(config.c_aideroom, newmsgid, 0, msg);
2593 /* For internet mail, drop a copy in the outbound queue room */
2595 if (recps->num_internet > 0) {
2596 CtdlSaveMsgPointerInRoom(SMTP_SPOOLOUT_ROOM, newmsgid, 0, msg);
2599 /* If other rooms are specified, drop them there too. */
2601 if (recps->num_room > 0)
2602 for (i=0; i<num_tokens(recps->recp_room, '|'); ++i) {
2603 extract_token(recipient, recps->recp_room, i,
2604 '|', sizeof recipient);
2605 lprintf(CTDL_DEBUG, "Delivering to room <%s>\n", recipient);
2606 CtdlSaveMsgPointerInRoom(recipient, newmsgid, 0, msg);
2609 /* Bump this user's messages posted counter. */
2610 lprintf(CTDL_DEBUG, "Updating user\n");
2611 lgetuser(&CCC->user, CCC->curr_user);
2612 CCC->user.posted = CCC->user.posted + 1;
2613 lputuser(&CCC->user);
2615 /* If this is private, local mail, make a copy in the
2616 * recipient's mailbox and bump the reference count.
2619 if (recps->num_local > 0)
2620 for (i=0; i<num_tokens(recps->recp_local, '|'); ++i) {
2621 extract_token(recipient, recps->recp_local, i,
2622 '|', sizeof recipient);
2623 lprintf(CTDL_DEBUG, "Delivering private local mail to <%s>\n",
2625 if (getuser(&userbuf, recipient) == 0) {
2626 // Add a flag so the Funambol module knows its mail
2627 msg->cm_fields['W'] = strdup(recipient);
2628 MailboxName(actual_rm, sizeof actual_rm, &userbuf, MAILROOM);
2629 CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 0, msg);
2630 BumpNewMailCounter(userbuf.usernum);
2631 if (!IsEmptyStr(config.c_funambol_host) || !IsEmptyStr(config.c_pager_program)) {
2632 /* Generate a instruction message for the Funambol notification
2633 * server, in the same style as the SMTP queue
2636 instr = malloc(instr_alloc);
2637 snprintf(instr, instr_alloc,
2638 "Content-type: %s\n\nmsgid|%ld\nsubmitted|%ld\n"
2640 SPOOLMIME, newmsgid, (long)time(NULL),
2641 msg->cm_fields['A'], msg->cm_fields['N']
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; /* imsg owns this memory now */
2652 imsg->cm_fields['W'] = strdup(recipient);
2653 CtdlSubmitMsg(imsg, NULL, FNBL_QUEUE_ROOM);
2654 CtdlFreeMessage(imsg);
2658 lprintf(CTDL_DEBUG, "No user <%s>\n", recipient);
2659 CtdlSaveMsgPointerInRoom(config.c_aideroom,
2664 /* Perform "after save" hooks */
2665 lprintf(CTDL_DEBUG, "Performing after-save hooks\n");
2666 PerformMessageHooks(msg, EVT_AFTERSAVE);
2668 /* For IGnet mail, we have to save a new copy into the spooler for
2669 * each recipient, with the R and D fields set to the recipient and
2670 * destination-node. This has two ugly side effects: all other
2671 * recipients end up being unlisted in this recipient's copy of the
2672 * message, and it has to deliver multiple messages to the same
2673 * node. We'll revisit this again in a year or so when everyone has
2674 * a network spool receiver that can handle the new style messages.
2677 if (recps->num_ignet > 0)
2678 for (i=0; i<num_tokens(recps->recp_ignet, '|'); ++i) {
2679 extract_token(recipient, recps->recp_ignet, i,
2680 '|', sizeof recipient);
2682 hold_R = msg->cm_fields['R'];
2683 hold_D = msg->cm_fields['D'];
2684 msg->cm_fields['R'] = malloc(SIZ);
2685 msg->cm_fields['D'] = malloc(128);
2686 extract_token(msg->cm_fields['R'], recipient, 0, '@', SIZ);
2687 extract_token(msg->cm_fields['D'], recipient, 1, '@', 128);
2689 serialize_message(&smr, msg);
2691 snprintf(submit_filename, sizeof submit_filename,
2692 "%s/netmail.%04lx.%04x.%04x",
2694 (long) getpid(), CCC->cs_pid, ++seqnum);
2695 network_fp = fopen(submit_filename, "wb+");
2696 if (network_fp != NULL) {
2697 fwrite(smr.ser, smr.len, 1, network_fp);
2703 free(msg->cm_fields['R']);
2704 free(msg->cm_fields['D']);
2705 msg->cm_fields['R'] = hold_R;
2706 msg->cm_fields['D'] = hold_D;
2709 /* Go back to the room we started from */
2710 lprintf(CTDL_DEBUG, "Returning to original room %s\n", hold_rm);
2711 if (strcasecmp(hold_rm, CCC->room.QRname))
2712 usergoto(hold_rm, 0, 1, NULL, NULL);
2714 /* For internet mail, generate delivery instructions.
2715 * Yes, this is recursive. Deal with it. Infinite recursion does
2716 * not happen because the delivery instructions message does not
2717 * contain a recipient.
2720 if (recps->num_internet > 0) {
2721 lprintf(CTDL_DEBUG, "Generating delivery instructions\n");
2723 instr = malloc(instr_alloc);
2724 snprintf(instr, instr_alloc,
2725 "Content-type: %s\n\nmsgid|%ld\nsubmitted|%ld\n"
2727 SPOOLMIME, newmsgid, (long)time(NULL),
2728 msg->cm_fields['A'], msg->cm_fields['N']
2731 for (i=0; i<num_tokens(recps->recp_internet, '|'); ++i) {
2732 size_t tmp = strlen(instr);
2733 extract_token(recipient, recps->recp_internet, i, '|', sizeof recipient);
2734 if ((tmp + strlen(recipient) + 32) > instr_alloc) {
2735 instr_alloc = instr_alloc * 2;
2736 instr = realloc(instr, instr_alloc);
2738 snprintf(&instr[tmp], instr_alloc - tmp, "remote|%s|0||\n", recipient);
2741 imsg = malloc(sizeof(struct CtdlMessage));
2742 memset(imsg, 0, sizeof(struct CtdlMessage));
2743 imsg->cm_magic = CTDLMESSAGE_MAGIC;
2744 imsg->cm_anon_type = MES_NORMAL;
2745 imsg->cm_format_type = FMT_RFC822;
2746 imsg->cm_fields['A'] = strdup("Citadel");
2747 imsg->cm_fields['J'] = strdup("do not journal");
2748 imsg->cm_fields['M'] = instr; /* imsg owns this memory now */
2749 CtdlSubmitMsg(imsg, NULL, SMTP_SPOOLOUT_ROOM);
2750 CtdlFreeMessage(imsg);
2754 * Any addresses to harvest for someone's address book?
2756 if ( (CCC->logged_in) && (recps != NULL) ) {
2757 collected_addresses = harvest_collected_addresses(msg);
2760 if (collected_addresses != NULL) {
2761 begin_critical_section(S_ATBF);
2762 aptr = (struct addresses_to_be_filed *)
2763 malloc(sizeof(struct addresses_to_be_filed));
2765 MailboxName(actual_rm, sizeof actual_rm,
2766 &CCC->user, USERCONTACTSROOM);
2767 aptr->roomname = strdup(actual_rm);
2768 aptr->collected_addresses = collected_addresses;
2770 end_critical_section(S_ATBF);
2774 * Determine whether this message qualifies for journaling.
2776 if (msg->cm_fields['J'] != NULL) {
2777 qualified_for_journaling = 0;
2780 if (recps == NULL) {
2781 qualified_for_journaling = config.c_journal_pubmsgs;
2783 else if (recps->num_local + recps->num_ignet + recps->num_internet > 0) {
2784 qualified_for_journaling = config.c_journal_email;
2787 qualified_for_journaling = config.c_journal_pubmsgs;
2792 * Do we have to perform journaling? If so, hand off the saved
2793 * RFC822 version will be handed off to the journaler for background
2794 * submit. Otherwise, we have to free the memory ourselves.
2796 if (saved_rfc822_version != NULL) {
2797 if (qualified_for_journaling) {
2798 JournalBackgroundSubmit(msg, saved_rfc822_version, recps);
2801 free(saved_rfc822_version);
2814 * Convenience function for generating small administrative messages.
2816 void quickie_message(char *from, char *fromaddr, char *to, char *room, char *text,
2817 int format_type, char *subject)
2819 struct CtdlMessage *msg;
2820 struct recptypes *recp = NULL;
2822 msg = malloc(sizeof(struct CtdlMessage));
2823 memset(msg, 0, sizeof(struct CtdlMessage));
2824 msg->cm_magic = CTDLMESSAGE_MAGIC;
2825 msg->cm_anon_type = MES_NORMAL;
2826 msg->cm_format_type = format_type;
2829 msg->cm_fields['A'] = strdup(from);
2831 else if (fromaddr != NULL) {
2832 msg->cm_fields['A'] = strdup(fromaddr);
2833 if (strchr(msg->cm_fields['A'], '@')) {
2834 *strchr(msg->cm_fields['A'], '@') = 0;
2838 msg->cm_fields['A'] = strdup("Citadel");
2841 if (fromaddr != NULL) msg->cm_fields['F'] = strdup(fromaddr);
2842 if (room != NULL) msg->cm_fields['O'] = strdup(room);
2843 msg->cm_fields['N'] = strdup(NODENAME);
2845 msg->cm_fields['R'] = strdup(to);
2846 recp = validate_recipients(to);
2848 if (subject != NULL) {
2849 msg->cm_fields['U'] = strdup(subject);
2851 msg->cm_fields['M'] = strdup(text);
2853 CtdlSubmitMsg(msg, recp, room);
2854 CtdlFreeMessage(msg);
2855 if (recp != NULL) free_recipients(recp);
2861 * Back end function used by CtdlMakeMessage() and similar functions
2863 char *CtdlReadMessageBody(char *terminator, /* token signalling EOT */
2864 size_t maxlen, /* maximum message length */
2865 char *exist, /* if non-null, append to it;
2866 exist is ALWAYS freed */
2867 int crlf, /* CRLF newlines instead of LF */
2868 int sock /* socket handle or 0 for this session's client socket */
2872 size_t message_len = 0;
2873 size_t buffer_len = 0;
2880 if (exist == NULL) {
2887 message_len = strlen(exist);
2888 buffer_len = message_len + 4096;
2889 m = realloc(exist, buffer_len);
2896 /* Do we need to change leading ".." to "." for SMTP escaping? */
2897 if (!strcmp(terminator, ".")) {
2901 /* flush the input if we have nowhere to store it */
2906 /* read in the lines of message text one by one */
2909 if (sock_getln(sock, buf, (sizeof buf - 3)) < 0) finished = 1;
2912 if (client_getln(buf, (sizeof buf - 3)) < 1) finished = 1;
2914 if (!strcmp(buf, terminator)) finished = 1;
2916 strcat(buf, "\r\n");
2922 /* Unescape SMTP-style input of two dots at the beginning of the line */
2924 if (!strncmp(buf, "..", 2)) {
2925 strcpy(buf, &buf[1]);
2929 if ( (!flushing) && (!finished) ) {
2930 /* Measure the line */
2931 linelen = strlen(buf);
2933 /* augment the buffer if we have to */
2934 if ((message_len + linelen) >= buffer_len) {
2935 ptr = realloc(m, (buffer_len * 2) );
2936 if (ptr == NULL) { /* flush if can't allocate */
2939 buffer_len = (buffer_len * 2);
2941 lprintf(CTDL_DEBUG, "buffer_len is now %ld\n", (long)buffer_len);
2945 /* Add the new line to the buffer. NOTE: this loop must avoid
2946 * using functions like strcat() and strlen() because they
2947 * traverse the entire buffer upon every call, and doing that
2948 * for a multi-megabyte message slows it down beyond usability.
2950 strcpy(&m[message_len], buf);
2951 message_len += linelen;
2954 /* if we've hit the max msg length, flush the rest */
2955 if (message_len >= maxlen) flushing = 1;
2957 } while (!finished);
2965 * Build a binary message to be saved on disk.
2966 * (NOTE: if you supply 'preformatted_text', the buffer you give it
2967 * will become part of the message. This means you are no longer
2968 * responsible for managing that memory -- it will be freed along with
2969 * the rest of the fields when CtdlFreeMessage() is called.)
2972 struct CtdlMessage *CtdlMakeMessage(
2973 struct ctdluser *author, /* author's user structure */
2974 char *recipient, /* NULL if it's not mail */
2975 char *recp_cc, /* NULL if it's not mail */
2976 char *room, /* room where it's going */
2977 int type, /* see MES_ types in header file */
2978 int format_type, /* variformat, plain text, MIME... */
2979 char *fake_name, /* who we're masquerading as */
2980 char *my_email, /* which of my email addresses to use (empty is ok) */
2981 char *subject, /* Subject (optional) */
2982 char *supplied_euid, /* ...or NULL if this is irrelevant */
2983 char *preformatted_text /* ...or NULL to read text from client */
2985 char dest_node[256];
2987 struct CtdlMessage *msg;
2990 msg = malloc(sizeof(struct CtdlMessage));
2991 memset(msg, 0, sizeof(struct CtdlMessage));
2992 msg->cm_magic = CTDLMESSAGE_MAGIC;
2993 msg->cm_anon_type = type;
2994 msg->cm_format_type = format_type;
2996 /* Don't confuse the poor folks if it's not routed mail. */
2997 strcpy(dest_node, "");
3002 /* Path or Return-Path */
3003 if (my_email == NULL) my_email = "";
3005 if (!IsEmptyStr(my_email)) {
3006 msg->cm_fields['P'] = strdup(my_email);
3009 snprintf(buf, sizeof buf, "%s", author->fullname);
3010 msg->cm_fields['P'] = strdup(buf);
3012 for (i=0; (msg->cm_fields['P'][i]!=0); ++i) {
3013 if (isspace(msg->cm_fields['P'][i])) {
3014 msg->cm_fields['P'][i] = '_';
3018 snprintf(buf, sizeof buf, "%ld", (long)time(NULL)); /* timestamp */
3019 msg->cm_fields['T'] = strdup(buf);
3021 if (fake_name[0]) /* author */
3022 msg->cm_fields['A'] = strdup(fake_name);
3024 msg->cm_fields['A'] = strdup(author->fullname);
3026 if (CC->room.QRflags & QR_MAILBOX) { /* room */
3027 msg->cm_fields['O'] = strdup(&CC->room.QRname[11]);
3030 msg->cm_fields['O'] = strdup(CC->room.QRname);
3033 msg->cm_fields['N'] = strdup(NODENAME); /* nodename */
3034 msg->cm_fields['H'] = strdup(HUMANNODE); /* hnodename */
3036 if (recipient[0] != 0) {
3037 msg->cm_fields['R'] = strdup(recipient);
3039 if (recp_cc[0] != 0) {
3040 msg->cm_fields['Y'] = strdup(recp_cc);
3042 if (dest_node[0] != 0) {
3043 msg->cm_fields['D'] = strdup(dest_node);
3046 if (!IsEmptyStr(my_email)) {
3047 msg->cm_fields['F'] = strdup(my_email);
3049 else if ( (author == &CC->user) && (!IsEmptyStr(CC->cs_inet_email)) ) {
3050 msg->cm_fields['F'] = strdup(CC->cs_inet_email);
3053 if (subject != NULL) {
3056 length = strlen(subject);
3062 while ((subject[i] != '\0') &&
3063 (IsAscii = isascii(subject[i]) != 0 ))
3066 msg->cm_fields['U'] = strdup(subject);
3067 else /* ok, we've got utf8 in the string. */
3069 msg->cm_fields['U'] = rfc2047encode(subject, length);
3075 if (supplied_euid != NULL) {
3076 msg->cm_fields['E'] = strdup(supplied_euid);
3079 if (preformatted_text != NULL) {
3080 msg->cm_fields['M'] = preformatted_text;
3083 msg->cm_fields['M'] = CtdlReadMessageBody("000", config.c_maxmsglen, NULL, 0, 0);
3091 * Check to see whether we have permission to post a message in the current
3092 * room. Returns a *CITADEL ERROR CODE* and puts a message in errmsgbuf, or
3093 * returns 0 on success.
3095 int CtdlDoIHavePermissionToPostInThisRoom(char *errmsgbuf, size_t n) {
3098 if (!(CC->logged_in)) {
3099 snprintf(errmsgbuf, n, "Not logged in.");
3100 return (ERROR + NOT_LOGGED_IN);
3103 if ((CC->user.axlevel < 2)
3104 && ((CC->room.QRflags & QR_MAILBOX) == 0)) {
3105 snprintf(errmsgbuf, n, "Need to be validated to enter "
3106 "(except in %s> to sysop)", MAILROOM);
3107 return (ERROR + HIGHER_ACCESS_REQUIRED);
3110 CtdlRoomAccess(&CC->room, &CC->user, &ra, NULL);
3111 if (!(ra & UA_POSTALLOWED)) {
3112 snprintf(errmsgbuf, n, "Higher access is required to post in this room.");
3113 return (ERROR + HIGHER_ACCESS_REQUIRED);
3116 strcpy(errmsgbuf, "Ok");
3122 * Check to see if the specified user has Internet mail permission
3123 * (returns nonzero if permission is granted)
3125 int CtdlCheckInternetMailPermission(struct ctdluser *who) {
3127 /* Do not allow twits to send Internet mail */
3128 if (who->axlevel <= 2) return(0);
3130 /* Globally enabled? */
3131 if (config.c_restrict == 0) return(1);
3133 /* User flagged ok? */
3134 if (who->flags & US_INTERNET) return(2);
3136 /* Aide level access? */
3137 if (who->axlevel >= 6) return(3);
3139 /* No mail for you! */
3145 * Validate recipients, count delivery types and errors, and handle aliasing
3146 * FIXME check for dupes!!!!!
3148 * Returns 0 if all addresses are ok, ret->num_error = -1 if no addresses
3149 * were specified, or the number of addresses found invalid.
3151 * Caller needs to free the result using free_recipients()
3153 struct recptypes *validate_recipients(char *supplied_recipients) {
3154 struct recptypes *ret;
3155 char *recipients = NULL;
3156 char this_recp[256];
3157 char this_recp_cooked[256];
3163 struct ctdluser tempUS;
3164 struct ctdlroom tempQR;
3168 ret = (struct recptypes *) malloc(sizeof(struct recptypes));
3169 if (ret == NULL) return(NULL);
3171 /* Set all strings to null and numeric values to zero */
3172 memset(ret, 0, sizeof(struct recptypes));
3174 if (supplied_recipients == NULL) {
3175 recipients = strdup("");
3178 recipients = strdup(supplied_recipients);
3181 /* Allocate some memory. Yes, this allocates 500% more memory than we will
3182 * actually need, but it's healthier for the heap than doing lots of tiny
3183 * realloc() calls instead.
3186 ret->errormsg = malloc(strlen(recipients) + 1024);
3187 ret->recp_local = malloc(strlen(recipients) + 1024);
3188 ret->recp_internet = malloc(strlen(recipients) + 1024);
3189 ret->recp_ignet = malloc(strlen(recipients) + 1024);
3190 ret->recp_room = malloc(strlen(recipients) + 1024);
3191 ret->display_recp = malloc(strlen(recipients) + 1024);
3193 ret->errormsg[0] = 0;
3194 ret->recp_local[0] = 0;
3195 ret->recp_internet[0] = 0;
3196 ret->recp_ignet[0] = 0;
3197 ret->recp_room[0] = 0;
3198 ret->display_recp[0] = 0;
3200 ret->recptypes_magic = RECPTYPES_MAGIC;
3202 /* Change all valid separator characters to commas */
3203 for (i=0; !IsEmptyStr(&recipients[i]); ++i) {
3204 if ((recipients[i] == ';') || (recipients[i] == '|')) {
3205 recipients[i] = ',';
3209 /* Now start extracting recipients... */
3211 while (!IsEmptyStr(recipients)) {
3213 for (i=0; i<=strlen(recipients); ++i) {
3214 if (recipients[i] == '\"') in_quotes = 1 - in_quotes;
3215 if ( ( (recipients[i] == ',') && (!in_quotes) ) || (recipients[i] == 0) ) {
3216 safestrncpy(this_recp, recipients, i+1);
3218 if (recipients[i] == ',') {
3219 strcpy(recipients, &recipients[i+1]);
3222 strcpy(recipients, "");
3229 lprintf(CTDL_DEBUG, "Evaluating recipient #%d: %s\n", num_recps, this_recp);
3231 mailtype = alias(this_recp);
3232 mailtype = alias(this_recp);
3233 mailtype = alias(this_recp);
3235 for (j=0; !IsEmptyStr(&this_recp[j]); ++j) {
3236 if (this_recp[j]=='_') {
3237 this_recp_cooked[j] = ' ';
3240 this_recp_cooked[j] = this_recp[j];
3243 this_recp_cooked[j] = '\0';
3247 if (!strcasecmp(this_recp, "sysop")) {
3249 strcpy(this_recp, config.c_aideroom);
3250 if (!IsEmptyStr(ret->recp_room)) {
3251 strcat(ret->recp_room, "|");
3253 strcat(ret->recp_room, this_recp);
3255 else if (getuser(&tempUS, this_recp) == 0) {
3257 strcpy(this_recp, tempUS.fullname);
3258 if (!IsEmptyStr(ret->recp_local)) {
3259 strcat(ret->recp_local, "|");
3261 strcat(ret->recp_local, this_recp);
3263 else if (getuser(&tempUS, this_recp_cooked) == 0) {
3265 strcpy(this_recp, tempUS.fullname);
3266 if (!IsEmptyStr(ret->recp_local)) {
3267 strcat(ret->recp_local, "|");
3269 strcat(ret->recp_local, this_recp);
3271 else if ( (!strncasecmp(this_recp, "room_", 5))
3272 && (!getroom(&tempQR, &this_recp_cooked[5])) ) {
3274 if (!IsEmptyStr(ret->recp_room)) {
3275 strcat(ret->recp_room, "|");
3277 strcat(ret->recp_room, &this_recp_cooked[5]);
3285 /* Yes, you're reading this correctly: if the target
3286 * domain points back to the local system or an attached
3287 * Citadel directory, the address is invalid. That's
3288 * because if the address were valid, we would have
3289 * already translated it to a local address by now.
3291 if (IsDirectory(this_recp, 0)) {
3296 ++ret->num_internet;
3297 if (!IsEmptyStr(ret->recp_internet)) {
3298 strcat(ret->recp_internet, "|");
3300 strcat(ret->recp_internet, this_recp);
3305 if (!IsEmptyStr(ret->recp_ignet)) {
3306 strcat(ret->recp_ignet, "|");
3308 strcat(ret->recp_ignet, this_recp);
3316 if (IsEmptyStr(ret->errormsg)) {
3317 snprintf(append, sizeof append,
3318 "Invalid recipient: %s",
3322 snprintf(append, sizeof append, ", %s", this_recp);
3324 if ( (strlen(ret->errormsg) + strlen(append)) < SIZ) {
3325 strcat(ret->errormsg, append);
3329 if (IsEmptyStr(ret->display_recp)) {
3330 strcpy(append, this_recp);
3333 snprintf(append, sizeof append, ", %s", this_recp);
3335 if ( (strlen(ret->display_recp)+strlen(append)) < SIZ) {
3336 strcat(ret->display_recp, append);
3341 if ((ret->num_local + ret->num_internet + ret->num_ignet +
3342 ret->num_room + ret->num_error) == 0) {
3343 ret->num_error = (-1);
3344 strcpy(ret->errormsg, "No recipients specified.");
3347 lprintf(CTDL_DEBUG, "validate_recipients()\n");
3348 lprintf(CTDL_DEBUG, " local: %d <%s>\n", ret->num_local, ret->recp_local);
3349 lprintf(CTDL_DEBUG, " room: %d <%s>\n", ret->num_room, ret->recp_room);
3350 lprintf(CTDL_DEBUG, " inet: %d <%s>\n", ret->num_internet, ret->recp_internet);
3351 lprintf(CTDL_DEBUG, " ignet: %d <%s>\n", ret->num_ignet, ret->recp_ignet);
3352 lprintf(CTDL_DEBUG, " error: %d <%s>\n", ret->num_error, ret->errormsg);
3360 * Destructor for struct recptypes
3362 void free_recipients(struct recptypes *valid) {
3364 if (valid == NULL) {
3368 if (valid->recptypes_magic != RECPTYPES_MAGIC) {
3369 lprintf(CTDL_EMERG, "Attempt to call free_recipients() on some other data type!\n");
3373 if (valid->errormsg != NULL) free(valid->errormsg);
3374 if (valid->recp_local != NULL) free(valid->recp_local);
3375 if (valid->recp_internet != NULL) free(valid->recp_internet);
3376 if (valid->recp_ignet != NULL) free(valid->recp_ignet);
3377 if (valid->recp_room != NULL) free(valid->recp_room);
3378 if (valid->display_recp != NULL) free(valid->display_recp);
3385 * message entry - mode 0 (normal)
3387 void cmd_ent0(char *entargs)
3393 char supplied_euid[128];
3395 int format_type = 0;
3396 char newusername[256];
3397 char newuseremail[256];
3398 struct CtdlMessage *msg;
3402 struct recptypes *valid = NULL;
3403 struct recptypes *valid_to = NULL;
3404 struct recptypes *valid_cc = NULL;
3405 struct recptypes *valid_bcc = NULL;
3407 int subject_required = 0;
3412 int newuseremail_ok = 0;
3416 post = extract_int(entargs, 0);
3417 extract_token(recp, entargs, 1, '|', sizeof recp);
3418 anon_flag = extract_int(entargs, 2);
3419 format_type = extract_int(entargs, 3);
3420 extract_token(subject, entargs, 4, '|', sizeof subject);
3421 extract_token(newusername, entargs, 5, '|', sizeof newusername);
3422 do_confirm = extract_int(entargs, 6);
3423 extract_token(cc, entargs, 7, '|', sizeof cc);
3424 extract_token(bcc, entargs, 8, '|', sizeof bcc);
3425 switch(CC->room.QRdefaultview) {
3428 extract_token(supplied_euid, entargs, 9, '|', sizeof supplied_euid);
3431 supplied_euid[0] = 0;
3434 extract_token(newuseremail, entargs, 10, '|', sizeof newuseremail);
3436 /* first check to make sure the request is valid. */
3438 err = CtdlDoIHavePermissionToPostInThisRoom(errmsg, sizeof errmsg);
3441 cprintf("%d %s\n", err, errmsg);
3445 /* Check some other permission type things. */
3447 if (IsEmptyStr(newusername)) {
3448 strcpy(newusername, CC->user.fullname);
3450 if ( (CC->user.axlevel < 6)
3451 && (strcasecmp(newusername, CC->user.fullname))
3452 && (strcasecmp(newusername, CC->cs_inet_fn))
3454 cprintf("%d You don't have permission to author messages as '%s'.\n",
3455 ERROR + HIGHER_ACCESS_REQUIRED,
3462 if (IsEmptyStr(newuseremail)) {
3463 newuseremail_ok = 1;
3466 if (!IsEmptyStr(newuseremail)) {
3467 if (!strcasecmp(newuseremail, CC->cs_inet_email)) {
3468 newuseremail_ok = 1;
3470 else if (!IsEmptyStr(CC->cs_inet_other_emails)) {
3471 j = num_tokens(CC->cs_inet_other_emails, '|');
3472 for (i=0; i<j; ++i) {
3473 extract_token(buf, CC->cs_inet_other_emails, i, '|', sizeof buf);
3474 if (!strcasecmp(newuseremail, buf)) {
3475 newuseremail_ok = 1;
3481 if (!newuseremail_ok) {
3482 cprintf("%d You don't have permission to author messages as '%s'.\n",
3483 ERROR + HIGHER_ACCESS_REQUIRED,
3489 CC->cs_flags |= CS_POSTING;
3491 /* In mailbox rooms we have to behave a little differently --
3492 * make sure the user has specified at least one recipient. Then
3493 * validate the recipient(s). We do this for the Mail> room, as
3494 * well as any room which has the "Mailbox" view set.
3497 if ( ( (CC->room.QRflags & QR_MAILBOX) && (!strcasecmp(&CC->room.QRname[11], MAILROOM)) )
3498 || ( (CC->room.QRflags & QR_MAILBOX) && (CC->curr_view == VIEW_MAILBOX) )
3500 if (CC->user.axlevel < 2) {
3501 strcpy(recp, "sysop");
3506 valid_to = validate_recipients(recp);
3507 if (valid_to->num_error > 0) {
3508 cprintf("%d Invalid recipient (To)\n", ERROR + NO_SUCH_USER);
3509 free_recipients(valid_to);
3513 valid_cc = validate_recipients(cc);
3514 if (valid_cc->num_error > 0) {
3515 cprintf("%d Invalid recipient (CC)\n", ERROR + NO_SUCH_USER);
3516 free_recipients(valid_to);
3517 free_recipients(valid_cc);
3521 valid_bcc = validate_recipients(bcc);
3522 if (valid_bcc->num_error > 0) {
3523 cprintf("%d Invalid recipient (BCC)\n", ERROR + NO_SUCH_USER);
3524 free_recipients(valid_to);
3525 free_recipients(valid_cc);
3526 free_recipients(valid_bcc);
3530 /* Recipient required, but none were specified */
3531 if ( (valid_to->num_error < 0) && (valid_cc->num_error < 0) && (valid_bcc->num_error < 0) ) {
3532 free_recipients(valid_to);
3533 free_recipients(valid_cc);
3534 free_recipients(valid_bcc);
3535 cprintf("%d At least one recipient is required.\n", ERROR + NO_SUCH_USER);
3539 if (valid_to->num_internet + valid_cc->num_internet + valid_bcc->num_internet > 0) {
3540 if (CtdlCheckInternetMailPermission(&CC->user)==0) {
3541 cprintf("%d You do not have permission "
3542 "to send Internet mail.\n",
3543 ERROR + HIGHER_ACCESS_REQUIRED);
3544 free_recipients(valid_to);
3545 free_recipients(valid_cc);
3546 free_recipients(valid_bcc);
3551 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)
3552 && (CC->user.axlevel < 4) ) {
3553 cprintf("%d Higher access required for network mail.\n",
3554 ERROR + HIGHER_ACCESS_REQUIRED);
3555 free_recipients(valid_to);
3556 free_recipients(valid_cc);
3557 free_recipients(valid_bcc);
3561 if ((RESTRICT_INTERNET == 1)
3562 && (valid_to->num_internet + valid_cc->num_internet + valid_bcc->num_internet > 0)
3563 && ((CC->user.flags & US_INTERNET) == 0)
3564 && (!CC->internal_pgm)) {
3565 cprintf("%d You don't have access to Internet mail.\n",
3566 ERROR + HIGHER_ACCESS_REQUIRED);
3567 free_recipients(valid_to);
3568 free_recipients(valid_cc);
3569 free_recipients(valid_bcc);
3575 /* Is this a room which has anonymous-only or anonymous-option? */
3576 anonymous = MES_NORMAL;
3577 if (CC->room.QRflags & QR_ANONONLY) {
3578 anonymous = MES_ANONONLY;
3580 if (CC->room.QRflags & QR_ANONOPT) {
3581 if (anon_flag == 1) { /* only if the user requested it */
3582 anonymous = MES_ANONOPT;
3586 if ((CC->room.QRflags & QR_MAILBOX) == 0) {
3590 /* Recommend to the client that the use of a message subject is
3591 * strongly recommended in this room, if either the SUBJECTREQ flag
3592 * is set, or if there is one or more Internet email recipients.
3594 if (CC->room.QRflags2 & QR2_SUBJECTREQ) subject_required = 1;
3595 if (valid_to) if (valid_to->num_internet > 0) subject_required = 1;
3596 if (valid_cc) if (valid_cc->num_internet > 0) subject_required = 1;
3597 if (valid_bcc) if (valid_bcc->num_internet > 0) subject_required = 1;
3599 /* If we're only checking the validity of the request, return
3600 * success without creating the message.
3603 cprintf("%d %s|%d\n", CIT_OK,
3604 ((valid_to != NULL) ? valid_to->display_recp : ""),
3606 free_recipients(valid_to);
3607 free_recipients(valid_cc);
3608 free_recipients(valid_bcc);
3612 /* We don't need these anymore because we'll do it differently below */
3613 free_recipients(valid_to);
3614 free_recipients(valid_cc);
3615 free_recipients(valid_bcc);
3617 /* Read in the message from the client. */
3619 cprintf("%d send message\n", START_CHAT_MODE);
3621 cprintf("%d send message\n", SEND_LISTING);
3624 msg = CtdlMakeMessage(&CC->user, recp, cc,
3625 CC->room.QRname, anonymous, format_type,
3626 newusername, newuseremail, subject,
3627 ((!IsEmptyStr(supplied_euid)) ? supplied_euid : NULL),
3630 /* Put together one big recipients struct containing to/cc/bcc all in
3631 * one. This is for the envelope.
3633 char *all_recps = malloc(SIZ * 3);
3634 strcpy(all_recps, recp);
3635 if (!IsEmptyStr(cc)) {
3636 if (!IsEmptyStr(all_recps)) {
3637 strcat(all_recps, ",");
3639 strcat(all_recps, cc);
3641 if (!IsEmptyStr(bcc)) {
3642 if (!IsEmptyStr(all_recps)) {
3643 strcat(all_recps, ",");
3645 strcat(all_recps, bcc);
3647 if (!IsEmptyStr(all_recps)) {
3648 valid = validate_recipients(all_recps);
3656 msgnum = CtdlSubmitMsg(msg, valid, "");
3659 cprintf("%ld\n", msgnum);
3661 cprintf("Message accepted.\n");
3664 cprintf("Internal error.\n");
3666 if (msg->cm_fields['E'] != NULL) {
3667 cprintf("%s\n", msg->cm_fields['E']);
3674 CtdlFreeMessage(msg);
3676 if (valid != NULL) {
3677 free_recipients(valid);
3685 * API function to delete messages which match a set of criteria
3686 * (returns the actual number of messages deleted)
3688 int CtdlDeleteMessages(char *room_name, /* which room */
3689 long *dmsgnums, /* array of msg numbers to be deleted */
3690 int num_dmsgnums, /* number of msgs to be deleted, or 0 for "any" */
3691 char *content_type /* or "" for any. regular expressions expected. */
3694 struct ctdlroom qrbuf;
3695 struct cdbdata *cdbfr;
3696 long *msglist = NULL;
3697 long *dellist = NULL;
3700 int num_deleted = 0;
3702 struct MetaData smi;
3705 int need_to_free_re = 0;
3707 if (content_type) if (!IsEmptyStr(content_type)) {
3708 regcomp(&re, content_type, 0);
3709 need_to_free_re = 1;
3711 lprintf(CTDL_DEBUG, "CtdlDeleteMessages(%s, %d msgs, %s)\n",
3712 room_name, num_dmsgnums, content_type);
3714 /* get room record, obtaining a lock... */
3715 if (lgetroom(&qrbuf, room_name) != 0) {
3716 lprintf(CTDL_ERR, "CtdlDeleteMessages(): Room <%s> not found\n",
3718 if (need_to_free_re) regfree(&re);
3719 return (0); /* room not found */
3721 cdbfr = cdb_fetch(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long));
3723 if (cdbfr != NULL) {
3724 dellist = malloc(cdbfr->len);
3725 msglist = (long *) cdbfr->ptr;
3726 cdbfr->ptr = NULL; /* CtdlDeleteMessages() now owns this memory */
3727 num_msgs = cdbfr->len / sizeof(long);
3731 for (i = 0; i < num_msgs; ++i) {
3734 /* Set/clear a bit for each criterion */
3736 /* 0 messages in the list or a null list means that we are
3737 * interested in deleting any messages which meet the other criteria.
3739 if ((num_dmsgnums == 0) || (dmsgnums == NULL)) {
3740 delete_this |= 0x01;
3743 for (j=0; j<num_dmsgnums; ++j) {
3744 if (msglist[i] == dmsgnums[j]) {
3745 delete_this |= 0x01;
3750 if (IsEmptyStr(content_type)) {
3751 delete_this |= 0x02;
3753 GetMetaData(&smi, msglist[i]);
3754 if (regexec(&re, smi.meta_content_type, 1, &pm, 0) == 0) {
3755 delete_this |= 0x02;
3759 /* Delete message only if all bits are set */
3760 if (delete_this == 0x03) {
3761 dellist[num_deleted++] = msglist[i];
3766 num_msgs = sort_msglist(msglist, num_msgs);
3767 cdb_store(CDB_MSGLISTS, &qrbuf.QRnumber, (int)sizeof(long),
3768 msglist, (int)(num_msgs * sizeof(long)));
3770 qrbuf.QRhighest = msglist[num_msgs - 1];
3774 /* Go through the messages we pulled out of the index, and decrement
3775 * their reference counts by 1. If this is the only room the message
3776 * was in, the reference count will reach zero and the message will
3777 * automatically be deleted from the database. We do this in a
3778 * separate pass because there might be plug-in hooks getting called,
3779 * and we don't want that happening during an S_ROOMS critical
3782 if (num_deleted) for (i=0; i<num_deleted; ++i) {
3783 PerformDeleteHooks(qrbuf.QRname, dellist[i]);
3784 AdjRefCount(dellist[i], -1);
3787 /* Now free the memory we used, and go away. */
3788 if (msglist != NULL) free(msglist);
3789 if (dellist != NULL) free(dellist);
3790 lprintf(CTDL_DEBUG, "%d message(s) deleted.\n", num_deleted);
3791 if (need_to_free_re) regfree(&re);
3792 return (num_deleted);
3798 * Check whether the current user has permission to delete messages from
3799 * the current room (returns 1 for yes, 0 for no)
3801 int CtdlDoIHavePermissionToDeleteMessagesFromThisRoom(void) {
3803 CtdlRoomAccess(&CC->room, &CC->user, &ra, NULL);
3804 if (ra & UA_DELETEALLOWED) return(1);
3812 * Delete message from current room
3814 void cmd_dele(char *args)
3823 extract_token(msgset, args, 0, '|', sizeof msgset);
3824 num_msgs = num_tokens(msgset, ',');
3826 cprintf("%d Nothing to do.\n", CIT_OK);
3830 if (CtdlDoIHavePermissionToDeleteMessagesFromThisRoom() == 0) {
3831 cprintf("%d Higher access required.\n",
3832 ERROR + HIGHER_ACCESS_REQUIRED);
3837 * Build our message set to be moved/copied
3839 msgs = malloc(num_msgs * sizeof(long));
3840 for (i=0; i<num_msgs; ++i) {
3841 extract_token(msgtok, msgset, i, ',', sizeof msgtok);
3842 msgs[i] = atol(msgtok);
3845 num_deleted = CtdlDeleteMessages(CC->room.QRname, msgs, num_msgs, "");
3849 cprintf("%d %d message%s deleted.\n", CIT_OK,
3850 num_deleted, ((num_deleted != 1) ? "s" : ""));
3852 cprintf("%d Message not found.\n", ERROR + MESSAGE_NOT_FOUND);
3858 * Back end API function for moves and deletes (multiple messages)
3860 int CtdlCopyMsgsToRoom(long *msgnums, int num_msgs, char *dest) {
3863 err = CtdlSaveMsgPointersInRoom(dest, msgnums, num_msgs, 1, NULL);
3864 if (err != 0) return(err);
3873 * move or copy a message to another room
3875 void cmd_move(char *args)
3882 char targ[ROOMNAMELEN];
3883 struct ctdlroom qtemp;
3890 extract_token(msgset, args, 0, '|', sizeof msgset);
3891 num_msgs = num_tokens(msgset, ',');
3893 cprintf("%d Nothing to do.\n", CIT_OK);
3897 extract_token(targ, args, 1, '|', sizeof targ);
3898 convert_room_name_macros(targ, sizeof targ);
3899 targ[ROOMNAMELEN - 1] = 0;
3900 is_copy = extract_int(args, 2);
3902 if (getroom(&qtemp, targ) != 0) {
3903 cprintf("%d '%s' does not exist.\n",
3904 ERROR + ROOM_NOT_FOUND, targ);
3908 getuser(&CC->user, CC->curr_user);
3909 CtdlRoomAccess(&qtemp, &CC->user, &ra, NULL);
3911 /* Check for permission to perform this operation.
3912 * Remember: "CC->room" is source, "qtemp" is target.
3916 /* Aides can move/copy */
3917 if (CC->user.axlevel >= 6) permit = 1;
3919 /* Room aides can move/copy */
3920 if (CC->user.usernum == CC->room.QRroomaide) permit = 1;
3922 /* Permit move/copy from personal rooms */
3923 if ((CC->room.QRflags & QR_MAILBOX)
3924 && (qtemp.QRflags & QR_MAILBOX)) permit = 1;
3926 /* Permit only copy from public to personal room */
3928 && (!(CC->room.QRflags & QR_MAILBOX))
3929 && (qtemp.QRflags & QR_MAILBOX)) permit = 1;
3931 /* Permit message removal from collaborative delete rooms */
3932 if (CC->room.QRflags2 & QR2_COLLABDEL) permit = 1;
3934 /* User must have access to target room */
3935 if (!(ra & UA_KNOWN)) permit = 0;
3938 cprintf("%d Higher access required.\n",
3939 ERROR + HIGHER_ACCESS_REQUIRED);
3944 * Build our message set to be moved/copied
3946 msgs = malloc(num_msgs * sizeof(long));
3947 for (i=0; i<num_msgs; ++i) {
3948 extract_token(msgtok, msgset, i, ',', sizeof msgtok);
3949 msgs[i] = atol(msgtok);
3955 err = CtdlCopyMsgsToRoom(msgs, num_msgs, targ);
3957 cprintf("%d Cannot store message(s) in %s: error %d\n",
3963 /* Now delete the message from the source room,
3964 * if this is a 'move' rather than a 'copy' operation.
3967 CtdlDeleteMessages(CC->room.QRname, msgs, num_msgs, "");
3971 cprintf("%d Message(s) %s.\n", CIT_OK, (is_copy ? "copied" : "moved") );
3977 * GetMetaData() - Get the supplementary record for a message
3979 void GetMetaData(struct MetaData *smibuf, long msgnum)
3982 struct cdbdata *cdbsmi;
3985 memset(smibuf, 0, sizeof(struct MetaData));
3986 smibuf->meta_msgnum = msgnum;
3987 smibuf->meta_refcount = 1; /* Default reference count is 1 */
3989 /* Use the negative of the message number for its supp record index */
3990 TheIndex = (0L - msgnum);
3992 cdbsmi = cdb_fetch(CDB_MSGMAIN, &TheIndex, sizeof(long));
3993 if (cdbsmi == NULL) {
3994 return; /* record not found; go with defaults */
3996 memcpy(smibuf, cdbsmi->ptr,
3997 ((cdbsmi->len > sizeof(struct MetaData)) ?
3998 sizeof(struct MetaData) : cdbsmi->len));
4005 * PutMetaData() - (re)write supplementary record for a message
4007 void PutMetaData(struct MetaData *smibuf)
4011 /* Use the negative of the message number for the metadata db index */
4012 TheIndex = (0L - smibuf->meta_msgnum);
4014 cdb_store(CDB_MSGMAIN,
4015 &TheIndex, (int)sizeof(long),
4016 smibuf, (int)sizeof(struct MetaData));
4021 * AdjRefCount - submit an adjustment to the reference count for a message.
4022 * (These are just queued -- we actually process them later.)
4024 void AdjRefCount(long msgnum, int incr)
4026 struct arcq new_arcq;
4028 begin_critical_section(S_SUPPMSGMAIN);
4029 if (arcfp == NULL) {
4030 arcfp = fopen(file_arcq, "ab+");
4032 end_critical_section(S_SUPPMSGMAIN);
4034 /* msgnum < 0 means that we're trying to close the file */
4036 lprintf(CTDL_DEBUG, "Closing the AdjRefCount queue file\n");
4037 begin_critical_section(S_SUPPMSGMAIN);
4038 if (arcfp != NULL) {
4042 end_critical_section(S_SUPPMSGMAIN);
4047 * If we can't open the queue, perform the operation synchronously.
4049 if (arcfp == NULL) {
4050 TDAP_AdjRefCount(msgnum, incr);
4054 new_arcq.arcq_msgnum = msgnum;
4055 new_arcq.arcq_delta = incr;
4056 fwrite(&new_arcq, sizeof(struct arcq), 1, arcfp);
4064 * TDAP_ProcessAdjRefCountQueue()
4066 * Process the queue of message count adjustments that was created by calls
4067 * to AdjRefCount() ... by reading the queue and calling TDAP_AdjRefCount()
4068 * for each one. This should be an "off hours" operation.
4070 int TDAP_ProcessAdjRefCountQueue(void)
4072 char file_arcq_temp[PATH_MAX];
4075 struct arcq arcq_rec;
4076 int num_records_processed = 0;
4078 snprintf(file_arcq_temp, sizeof file_arcq_temp, "%s2", file_arcq);
4080 begin_critical_section(S_SUPPMSGMAIN);
4081 if (arcfp != NULL) {
4086 r = link(file_arcq, file_arcq_temp);
4088 lprintf(CTDL_CRIT, "%s: %s\n", file_arcq_temp, strerror(errno));
4089 end_critical_section(S_SUPPMSGMAIN);
4090 return(num_records_processed);
4094 end_critical_section(S_SUPPMSGMAIN);
4096 fp = fopen(file_arcq_temp, "rb");
4098 lprintf(CTDL_CRIT, "%s: %s\n", file_arcq_temp, strerror(errno));
4099 return(num_records_processed);
4102 while (fread(&arcq_rec, sizeof(struct arcq), 1, fp) == 1) {
4103 TDAP_AdjRefCount(arcq_rec.arcq_msgnum, arcq_rec.arcq_delta);
4104 ++num_records_processed;
4108 r = unlink(file_arcq_temp);
4110 lprintf(CTDL_CRIT, "%s: %s\n", file_arcq_temp, strerror(errno));
4113 return(num_records_processed);
4119 * TDAP_AdjRefCount - adjust the reference count for a message.
4120 * This one does it "for real" because it's called by
4121 * the autopurger function that processes the queue
4122 * created by AdjRefCount(). If a message's reference
4123 * count becomes zero, we also delete the message from
4124 * disk and de-index it.
4126 void TDAP_AdjRefCount(long msgnum, int incr)
4129 struct MetaData smi;
4132 /* This is a *tight* critical section; please keep it that way, as
4133 * it may get called while nested in other critical sections.
4134 * Complicating this any further will surely cause deadlock!
4136 begin_critical_section(S_SUPPMSGMAIN);
4137 GetMetaData(&smi, msgnum);
4138 smi.meta_refcount += incr;
4140 end_critical_section(S_SUPPMSGMAIN);
4141 lprintf(CTDL_DEBUG, "msg %ld ref count delta %d, is now %d\n",
4142 msgnum, incr, smi.meta_refcount);
4144 /* If the reference count is now zero, delete the message
4145 * (and its supplementary record as well).
4147 if (smi.meta_refcount == 0) {
4148 lprintf(CTDL_DEBUG, "Deleting message <%ld>\n", msgnum);
4150 /* Call delete hooks with NULL room to show it has gone altogether */
4151 PerformDeleteHooks(NULL, msgnum);
4153 /* Remove from message base */
4155 cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long));
4156 cdb_delete(CDB_BIGMSGS, &delnum, (int)sizeof(long));
4158 /* Remove metadata record */
4159 delnum = (0L - msgnum);
4160 cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long));
4166 * Write a generic object to this room
4168 * Note: this could be much more efficient. Right now we use two temporary
4169 * files, and still pull the message into memory as with all others.
4171 void CtdlWriteObject(char *req_room, /* Room to stuff it in */
4172 char *content_type, /* MIME type of this object */
4173 char *tempfilename, /* Where to fetch it from */
4174 struct ctdluser *is_mailbox, /* Mailbox room? */
4175 int is_binary, /* Is encoding necessary? */
4176 int is_unique, /* Del others of this type? */
4177 unsigned int flags /* Internal save flags */
4182 struct ctdlroom qrbuf;
4183 char roomname[ROOMNAMELEN];
4184 struct CtdlMessage *msg;
4186 char *raw_message = NULL;
4187 char *encoded_message = NULL;
4188 off_t raw_length = 0;
4190 if (is_mailbox != NULL) {
4191 MailboxName(roomname, sizeof roomname, is_mailbox, req_room);
4194 safestrncpy(roomname, req_room, sizeof(roomname));
4197 fp = fopen(tempfilename, "rb");
4199 lprintf(CTDL_CRIT, "Cannot open %s: %s\n",
4200 tempfilename, strerror(errno));
4203 fseek(fp, 0L, SEEK_END);
4204 raw_length = ftell(fp);
4206 lprintf(CTDL_DEBUG, "Raw length is %ld\n", (long)raw_length);
4208 raw_message = malloc((size_t)raw_length + 2);
4209 fread(raw_message, (size_t)raw_length, 1, fp);
4213 encoded_message = malloc((size_t)
4214 (((raw_length * 134) / 100) + 4096 ) );
4217 encoded_message = malloc((size_t)(raw_length + 4096));
4220 sprintf(encoded_message, "Content-type: %s\n", content_type);
4223 sprintf(&encoded_message[strlen(encoded_message)],
4224 "Content-transfer-encoding: base64\n\n"
4228 sprintf(&encoded_message[strlen(encoded_message)],
4229 "Content-transfer-encoding: 7bit\n\n"
4235 &encoded_message[strlen(encoded_message)],
4241 raw_message[raw_length] = 0;
4243 &encoded_message[strlen(encoded_message)],
4251 lprintf(CTDL_DEBUG, "Allocating\n");
4252 msg = malloc(sizeof(struct CtdlMessage));
4253 memset(msg, 0, sizeof(struct CtdlMessage));
4254 msg->cm_magic = CTDLMESSAGE_MAGIC;
4255 msg->cm_anon_type = MES_NORMAL;
4256 msg->cm_format_type = 4;
4257 msg->cm_fields['A'] = strdup(CC->user.fullname);
4258 msg->cm_fields['O'] = strdup(req_room);
4259 msg->cm_fields['N'] = strdup(config.c_nodename);
4260 msg->cm_fields['H'] = strdup(config.c_humannode);
4261 msg->cm_flags = flags;
4263 msg->cm_fields['M'] = encoded_message;
4265 /* Create the requested room if we have to. */
4266 if (getroom(&qrbuf, roomname) != 0) {
4267 create_room(roomname,
4268 ( (is_mailbox != NULL) ? 5 : 3 ),
4269 "", 0, 1, 0, VIEW_BBS);
4271 /* If the caller specified this object as unique, delete all
4272 * other objects of this type that are currently in the room.
4275 lprintf(CTDL_DEBUG, "Deleted %d other msgs of this type\n",
4276 CtdlDeleteMessages(roomname, NULL, 0, content_type)
4279 /* Now write the data */
4280 CtdlSubmitMsg(msg, NULL, roomname);
4281 CtdlFreeMessage(msg);
4289 void CtdlGetSysConfigBackend(long msgnum, void *userdata) {
4290 config_msgnum = msgnum;
4294 char *CtdlGetSysConfig(char *sysconfname) {
4295 char hold_rm[ROOMNAMELEN];
4298 struct CtdlMessage *msg;
4301 strcpy(hold_rm, CC->room.QRname);
4302 if (getroom(&CC->room, SYSCONFIGROOM) != 0) {
4303 getroom(&CC->room, hold_rm);
4308 /* We want the last (and probably only) config in this room */
4309 begin_critical_section(S_CONFIG);
4310 config_msgnum = (-1L);
4311 CtdlForEachMessage(MSGS_LAST, 1, NULL, sysconfname, NULL,
4312 CtdlGetSysConfigBackend, NULL);
4313 msgnum = config_msgnum;
4314 end_critical_section(S_CONFIG);
4320 msg = CtdlFetchMessage(msgnum, 1);
4322 conf = strdup(msg->cm_fields['M']);
4323 CtdlFreeMessage(msg);
4330 getroom(&CC->room, hold_rm);
4332 if (conf != NULL) do {
4333 extract_token(buf, conf, 0, '\n', sizeof buf);
4334 strcpy(conf, &conf[strlen(buf)+1]);
4335 } while ( (!IsEmptyStr(conf)) && (!IsEmptyStr(buf)) );
4340 void CtdlPutSysConfig(char *sysconfname, char *sysconfdata) {
4341 char temp[PATH_MAX];
4344 CtdlMakeTempFileName(temp, sizeof temp);
4346 fp = fopen(temp, "w");
4347 if (fp == NULL) return;
4348 fprintf(fp, "%s", sysconfdata);
4351 /* this handy API function does all the work for us */
4352 CtdlWriteObject(SYSCONFIGROOM, sysconfname, temp, NULL, 0, 1, 0);
4358 * Determine whether a given Internet address belongs to the current user
4360 int CtdlIsMe(char *addr, int addr_buf_len)
4362 struct recptypes *recp;
4365 recp = validate_recipients(addr);
4366 if (recp == NULL) return(0);
4368 if (recp->num_local == 0) {
4369 free_recipients(recp);
4373 for (i=0; i<recp->num_local; ++i) {
4374 extract_token(addr, recp->recp_local, i, '|', addr_buf_len);
4375 if (!strcasecmp(addr, CC->user.fullname)) {
4376 free_recipients(recp);
4381 free_recipients(recp);
4387 * Citadel protocol command to do the same
4389 void cmd_isme(char *argbuf) {
4392 if (CtdlAccessCheck(ac_logged_in)) return;
4393 extract_token(addr, argbuf, 0, '|', sizeof addr);
4395 if (CtdlIsMe(addr, sizeof addr)) {
4396 cprintf("%d %s\n", CIT_OK, addr);
4399 cprintf("%d Not you.\n", ERROR + ILLEGAL_VALUE);