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>
34 #include <libcitadel.h>
37 #include "serv_extensions.h"
41 #include "sysdep_decls.h"
42 #include "citserver.h"
49 #include "internet_addressing.h"
50 #include "euidindex.h"
51 #include "journaling.h"
52 #include "citadel_dirs.h"
53 #include "clientsocket.h"
54 #include "serv_network.h"
58 struct addresses_to_be_filed *atbf = NULL;
60 /* This temp file holds the queue of operations for AdjRefCount() */
61 static FILE *arcfp = NULL;
64 * This really belongs in serv_network.c, but I don't know how to export
65 * symbols between modules.
67 struct FilterList *filterlist = NULL;
71 * These are the four-character field headers we use when outputting
72 * messages in Citadel format (as opposed to RFC822 format).
75 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
76 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
77 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
78 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
79 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
80 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
81 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
82 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
110 * This function is self explanatory.
111 * (What can I say, I'm in a weird mood today...)
113 void remove_any_whitespace_to_the_left_or_right_of_at_symbol(char *name)
117 for (i = 0; i < strlen(name); ++i) {
118 if (name[i] == '@') {
119 while (isspace(name[i - 1]) && i > 0) {
120 strcpy(&name[i - 1], &name[i]);
123 while (isspace(name[i + 1])) {
124 strcpy(&name[i + 1], &name[i + 2]);
132 * Aliasing for network mail.
133 * (Error messages have been commented out, because this is a server.)
135 int alias(char *name)
136 { /* process alias and routing info for mail */
139 char aaa[SIZ], bbb[SIZ];
140 char *ignetcfg = NULL;
141 char *ignetmap = NULL;
147 char original_name[256];
148 safestrncpy(original_name, name, sizeof original_name);
151 remove_any_whitespace_to_the_left_or_right_of_at_symbol(name);
152 stripallbut(name, '<', '>');
154 fp = fopen(file_mail_aliases, "r");
156 fp = fopen("/dev/null", "r");
163 while (fgets(aaa, sizeof aaa, fp) != NULL) {
164 while (isspace(name[0]))
165 strcpy(name, &name[1]);
166 aaa[strlen(aaa) - 1] = 0;
168 for (a = 0; a < strlen(aaa); ++a) {
170 strcpy(bbb, &aaa[a + 1]);
174 if (!strcasecmp(name, aaa))
179 /* Hit the Global Address Book */
180 if (CtdlDirectoryLookup(aaa, name, sizeof aaa) == 0) {
184 if (strcasecmp(original_name, name)) {
185 CtdlLogPrintf(CTDL_INFO, "%s is being forwarded to %s\n", original_name, name);
188 /* Change "user @ xxx" to "user" if xxx is an alias for this host */
189 for (a=0; a<strlen(name); ++a) {
190 if (name[a] == '@') {
191 if (CtdlHostAlias(&name[a+1]) == hostalias_localhost) {
193 CtdlLogPrintf(CTDL_INFO, "Changed to <%s>\n", name);
198 /* determine local or remote type, see citadel.h */
199 at = haschar(name, '@');
200 if (at == 0) return(MES_LOCAL); /* no @'s - local address */
201 if (at > 1) return(MES_ERROR); /* >1 @'s - invalid address */
202 remove_any_whitespace_to_the_left_or_right_of_at_symbol(name);
204 /* figure out the delivery mode */
205 extract_token(node, name, 1, '@', sizeof node);
207 /* If there are one or more dots in the nodename, we assume that it
208 * is an FQDN and will attempt SMTP delivery to the Internet.
210 if (haschar(node, '.') > 0) {
211 return(MES_INTERNET);
214 /* Otherwise we look in the IGnet maps for a valid Citadel node.
215 * Try directly-connected nodes first...
217 ignetcfg = CtdlGetSysConfig(IGNETCFG);
218 for (i=0; i<num_tokens(ignetcfg, '\n'); ++i) {
219 extract_token(buf, ignetcfg, i, '\n', sizeof buf);
220 extract_token(testnode, buf, 0, '|', sizeof testnode);
221 if (!strcasecmp(node, testnode)) {
229 * Then try nodes that are two or more hops away.
231 ignetmap = CtdlGetSysConfig(IGNETMAP);
232 for (i=0; i<num_tokens(ignetmap, '\n'); ++i) {
233 extract_token(buf, ignetmap, i, '\n', sizeof buf);
234 extract_token(testnode, buf, 0, '|', sizeof testnode);
235 if (!strcasecmp(node, testnode)) {
242 /* If we get to this point it's an invalid node name */
248 * Back end for the MSGS command: output message number only.
250 void simple_listing(long msgnum, void *userdata)
252 cprintf("%ld\n", msgnum);
258 * Back end for the MSGS command: output header summary.
260 void headers_listing(long msgnum, void *userdata)
262 struct CtdlMessage *msg;
264 msg = CtdlFetchMessage(msgnum, 0);
266 cprintf("%ld|0|||||\n", msgnum);
270 cprintf("%ld|%s|%s|%s|%s|%s|\n",
272 (msg->cm_fields['T'] ? msg->cm_fields['T'] : "0"),
273 (msg->cm_fields['A'] ? msg->cm_fields['A'] : ""),
274 (msg->cm_fields['N'] ? msg->cm_fields['N'] : ""),
275 (msg->cm_fields['F'] ? msg->cm_fields['F'] : ""),
276 (msg->cm_fields['U'] ? msg->cm_fields['U'] : "")
278 CtdlFreeMessage(msg);
283 /* Determine if a given message matches the fields in a message template.
284 * Return 0 for a successful match.
286 int CtdlMsgCmp(struct CtdlMessage *msg, struct CtdlMessage *template) {
289 /* If there aren't any fields in the template, all messages will
292 if (template == NULL) return(0);
294 /* Null messages are bogus. */
295 if (msg == NULL) return(1);
297 for (i='A'; i<='Z'; ++i) {
298 if (template->cm_fields[i] != NULL) {
299 if (msg->cm_fields[i] == NULL) {
302 if (strcasecmp(msg->cm_fields[i],
303 template->cm_fields[i])) return 1;
307 /* All compares succeeded: we have a match! */
314 * Retrieve the "seen" message list for the current room.
316 void CtdlGetSeen(char *buf, int which_set) {
319 /* Learn about the user and room in question */
320 CtdlGetRelationship(&vbuf, &CC->user, &CC->room);
322 if (which_set == ctdlsetseen_seen)
323 safestrncpy(buf, vbuf.v_seen, SIZ);
324 if (which_set == ctdlsetseen_answered)
325 safestrncpy(buf, vbuf.v_answered, SIZ);
331 * Manipulate the "seen msgs" string (or other message set strings)
333 void CtdlSetSeen(long *target_msgnums, int num_target_msgnums,
334 int target_setting, int which_set,
335 struct ctdluser *which_user, struct ctdlroom *which_room) {
336 struct cdbdata *cdbfr;
348 char *is_set; /* actually an array of booleans */
351 char setstr[SIZ], lostr[SIZ], histr[SIZ];
354 /* Don't bother doing *anything* if we were passed a list of zero messages */
355 if (num_target_msgnums < 1) {
359 CtdlLogPrintf(CTDL_DEBUG, "CtdlSetSeen(%d msgs starting with %ld, %d, %d)\n",
360 num_target_msgnums, target_msgnums[0],
361 target_setting, which_set);
363 /* Learn about the user and room in question */
364 CtdlGetRelationship(&vbuf,
365 ((which_user != NULL) ? which_user : &CC->user),
366 ((which_room != NULL) ? which_room : &CC->room)
369 /* Load the message list */
370 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long));
372 msglist = (long *) cdbfr->ptr;
373 cdbfr->ptr = NULL; /* CtdlSetSeen() now owns this memory */
374 num_msgs = cdbfr->len / sizeof(long);
377 return; /* No messages at all? No further action. */
380 is_set = malloc(num_msgs * sizeof(char));
381 memset(is_set, 0, (num_msgs * sizeof(char)) );
383 /* Decide which message set we're manipulating */
385 case ctdlsetseen_seen:
386 safestrncpy(vset, vbuf.v_seen, sizeof vset);
388 case ctdlsetseen_answered:
389 safestrncpy(vset, vbuf.v_answered, sizeof vset);
393 /* CtdlLogPrintf(CTDL_DEBUG, "before optimize: %s\n", vset); */
395 /* Translate the existing sequence set into an array of booleans */
396 num_sets = num_tokens(vset, ',');
397 for (s=0; s<num_sets; ++s) {
398 extract_token(setstr, vset, s, ',', sizeof setstr);
400 extract_token(lostr, setstr, 0, ':', sizeof lostr);
401 if (num_tokens(setstr, ':') >= 2) {
402 extract_token(histr, setstr, 1, ':', sizeof histr);
403 if (!strcmp(histr, "*")) {
404 snprintf(histr, sizeof histr, "%ld", LONG_MAX);
408 strcpy(histr, lostr);
413 for (i = 0; i < num_msgs; ++i) {
414 if ((msglist[i] >= lo) && (msglist[i] <= hi)) {
420 /* Now translate the array of booleans back into a sequence set */
425 for (i=0; i<num_msgs; ++i) {
427 is_seen = is_set[i]; /* Default to existing setting */
429 for (k=0; k<num_target_msgnums; ++k) {
430 if (msglist[i] == target_msgnums[k]) {
431 is_seen = target_setting;
436 if (lo < 0L) lo = msglist[i];
440 if ( ((is_seen == 0) && (was_seen == 1))
441 || ((is_seen == 1) && (i == num_msgs-1)) ) {
443 /* begin trim-o-matic code */
446 while ( (strlen(vset) + 20) > sizeof vset) {
447 remove_token(vset, 0, ',');
449 if (j--) break; /* loop no more than 9 times */
451 if ( (trimming) && (which_set == ctdlsetseen_seen) ) {
455 snprintf(lostr, sizeof lostr,
456 "1:%ld,%s", t, vset);
457 safestrncpy(vset, lostr, sizeof vset);
459 /* end trim-o-matic code */
467 snprintf(&vset[tmp], (sizeof vset) - tmp,
471 snprintf(&vset[tmp], (sizeof vset) - tmp,
480 /* Decide which message set we're manipulating */
482 case ctdlsetseen_seen:
483 safestrncpy(vbuf.v_seen, vset, sizeof vbuf.v_seen);
485 case ctdlsetseen_answered:
486 safestrncpy(vbuf.v_answered, vset,
487 sizeof vbuf.v_answered);
492 /* CtdlLogPrintf(CTDL_DEBUG, " after optimize: %s\n", vset); */
494 CtdlSetRelationship(&vbuf,
495 ((which_user != NULL) ? which_user : &CC->user),
496 ((which_room != NULL) ? which_room : &CC->room)
502 * API function to perform an operation for each qualifying message in the
503 * current room. (Returns the number of messages processed.)
505 int CtdlForEachMessage(int mode, long ref, char *search_string,
507 struct CtdlMessage *compare,
508 void (*CallBack) (long, void *),
514 struct cdbdata *cdbfr;
515 long *msglist = NULL;
517 int num_processed = 0;
520 struct CtdlMessage *msg = NULL;
523 int printed_lastold = 0;
524 int num_search_msgs = 0;
525 long *search_msgs = NULL;
527 int need_to_free_re = 0;
530 if (content_type) if (!IsEmptyStr(content_type)) {
531 regcomp(&re, content_type, 0);
535 /* Learn about the user and room in question */
536 getuser(&CC->user, CC->curr_user);
537 CtdlGetRelationship(&vbuf, &CC->user, &CC->room);
539 /* Load the message list */
540 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long));
542 msglist = (long *) cdbfr->ptr;
543 num_msgs = cdbfr->len / sizeof(long);
545 if (need_to_free_re) regfree(&re);
546 return 0; /* No messages at all? No further action. */
551 * Now begin the traversal.
553 if (num_msgs > 0) for (a = 0; a < num_msgs; ++a) {
555 /* If the caller is looking for a specific MIME type, filter
556 * out all messages which are not of the type requested.
558 if (content_type != NULL) if (!IsEmptyStr(content_type)) {
560 /* This call to GetMetaData() sits inside this loop
561 * so that we only do the extra database read per msg
562 * if we need to. Doing the extra read all the time
563 * really kills the server. If we ever need to use
564 * metadata for another search criterion, we need to
565 * move the read somewhere else -- but still be smart
566 * enough to only do the read if the caller has
567 * specified something that will need it.
569 GetMetaData(&smi, msglist[a]);
571 /* if (strcasecmp(smi.meta_content_type, content_type)) { old non-regex way */
572 if (regexec(&re, smi.meta_content_type, 1, &pm, 0) != 0) {
578 num_msgs = sort_msglist(msglist, num_msgs);
580 /* If a template was supplied, filter out the messages which
581 * don't match. (This could induce some delays!)
584 if (compare != NULL) {
585 for (a = 0; a < num_msgs; ++a) {
586 msg = CtdlFetchMessage(msglist[a], 1);
588 if (CtdlMsgCmp(msg, compare)) {
591 CtdlFreeMessage(msg);
597 /* If a search string was specified, get a message list from
598 * the full text index and remove messages which aren't on both
602 * Since the lists are sorted and strictly ascending, and the
603 * output list is guaranteed to be shorter than or equal to the
604 * input list, we overwrite the bottom of the input list. This
605 * eliminates the need to memmove big chunks of the list over and
608 if ( (num_msgs > 0) && (mode == MSGS_SEARCH) && (search_string) ) {
610 /* Call search module via hook mechanism.
611 * NULL means use any search function available.
612 * otherwise replace with a char * to name of search routine
614 CtdlModuleDoSearch(&num_search_msgs, &search_msgs, search_string, "fulltext");
616 if (num_search_msgs > 0) {
620 orig_num_msgs = num_msgs;
622 for (i=0; i<orig_num_msgs; ++i) {
623 for (j=0; j<num_search_msgs; ++j) {
624 if (msglist[i] == search_msgs[j]) {
625 msglist[num_msgs++] = msglist[i];
631 num_msgs = 0; /* No messages qualify */
633 if (search_msgs != NULL) free(search_msgs);
635 /* Now that we've purged messages which don't contain the search
636 * string, treat a MSGS_SEARCH just like a MSGS_ALL from this
643 * Now iterate through the message list, according to the
644 * criteria supplied by the caller.
647 for (a = 0; a < num_msgs; ++a) {
648 thismsg = msglist[a];
649 if (mode == MSGS_ALL) {
653 is_seen = is_msg_in_sequence_set(
654 vbuf.v_seen, thismsg);
655 if (is_seen) lastold = thismsg;
661 || ((mode == MSGS_OLD) && (is_seen))
662 || ((mode == MSGS_NEW) && (!is_seen))
663 || ((mode == MSGS_LAST) && (a >= (num_msgs - ref)))
664 || ((mode == MSGS_FIRST) && (a < ref))
665 || ((mode == MSGS_GT) && (thismsg > ref))
666 || ((mode == MSGS_EQ) && (thismsg == ref))
669 if ((mode == MSGS_NEW) && (CC->user.flags & US_LASTOLD) && (lastold > 0L) && (printed_lastold == 0) && (!is_seen)) {
671 CallBack(lastold, userdata);
675 if (CallBack) CallBack(thismsg, userdata);
679 cdb_free(cdbfr); /* Clean up */
680 if (need_to_free_re) regfree(&re);
681 return num_processed;
687 * cmd_msgs() - get list of message #'s in this room
688 * implements the MSGS server command using CtdlForEachMessage()
690 void cmd_msgs(char *cmdbuf)
699 int with_template = 0;
700 struct CtdlMessage *template = NULL;
701 int with_headers = 0;
702 char search_string[1024];
704 extract_token(which, cmdbuf, 0, '|', sizeof which);
705 cm_ref = extract_int(cmdbuf, 1);
706 extract_token(search_string, cmdbuf, 1, '|', sizeof search_string);
707 with_template = extract_int(cmdbuf, 2);
708 with_headers = extract_int(cmdbuf, 3);
711 if (!strncasecmp(which, "OLD", 3))
713 else if (!strncasecmp(which, "NEW", 3))
715 else if (!strncasecmp(which, "FIRST", 5))
717 else if (!strncasecmp(which, "LAST", 4))
719 else if (!strncasecmp(which, "GT", 2))
721 else if (!strncasecmp(which, "SEARCH", 6))
726 if ((!(CC->logged_in)) && (!(CC->internal_pgm))) {
727 cprintf("%d not logged in\n", ERROR + NOT_LOGGED_IN);
731 if ( (mode == MSGS_SEARCH) && (!config.c_enable_fulltext) ) {
732 cprintf("%d Full text index is not enabled on this server.\n",
733 ERROR + CMD_NOT_SUPPORTED);
739 cprintf("%d Send template then receive message list\n",
741 template = (struct CtdlMessage *)
742 malloc(sizeof(struct CtdlMessage));
743 memset(template, 0, sizeof(struct CtdlMessage));
744 template->cm_magic = CTDLMESSAGE_MAGIC;
745 template->cm_anon_type = MES_NORMAL;
747 while(client_getln(buf, sizeof buf) >= 0 && strcmp(buf,"000")) {
748 extract_token(tfield, buf, 0, '|', sizeof tfield);
749 extract_token(tvalue, buf, 1, '|', sizeof tvalue);
750 for (i='A'; i<='Z'; ++i) if (msgkeys[i]!=NULL) {
751 if (!strcasecmp(tfield, msgkeys[i])) {
752 template->cm_fields[i] =
760 cprintf("%d \n", LISTING_FOLLOWS);
763 CtdlForEachMessage(mode,
764 ( (mode == MSGS_SEARCH) ? 0 : cm_ref ),
765 ( (mode == MSGS_SEARCH) ? search_string : NULL ),
768 (with_headers ? headers_listing : simple_listing),
771 if (template != NULL) CtdlFreeMessage(template);
779 * help_subst() - support routine for help file viewer
781 void help_subst(char *strbuf, char *source, char *dest)
786 while (p = pattern2(strbuf, source), (p >= 0)) {
787 strcpy(workbuf, &strbuf[p + strlen(source)]);
788 strcpy(&strbuf[p], dest);
789 strcat(strbuf, workbuf);
794 void do_help_subst(char *buffer)
798 help_subst(buffer, "^nodename", config.c_nodename);
799 help_subst(buffer, "^humannode", config.c_humannode);
800 help_subst(buffer, "^fqdn", config.c_fqdn);
801 help_subst(buffer, "^username", CC->user.fullname);
802 snprintf(buf2, sizeof buf2, "%ld", CC->user.usernum);
803 help_subst(buffer, "^usernum", buf2);
804 help_subst(buffer, "^sysadm", config.c_sysadm);
805 help_subst(buffer, "^variantname", CITADEL);
806 snprintf(buf2, sizeof buf2, "%d", config.c_maxsessions);
807 help_subst(buffer, "^maxsessions", buf2);
808 help_subst(buffer, "^bbsdir", ctdl_message_dir);
814 * memfmout() - Citadel text formatter and paginator.
815 * Although the original purpose of this routine was to format
816 * text to the reader's screen width, all we're really using it
817 * for here is to format text out to 80 columns before sending it
818 * to the client. The client software may reformat it again.
821 char *mptr, /* where are we going to get our text from? */
822 char subst, /* nonzero if we should do substitutions */
823 char *nl) /* string to terminate lines with */
831 static int width = 80;
836 c = 1; /* c is the current pos */
840 while (ch = *mptr, ((ch != 0) && (strlen(buffer) < 126))) {
842 buffer[strlen(buffer) + 1] = 0;
843 buffer[strlen(buffer)] = ch;
846 if (buffer[0] == '^')
847 do_help_subst(buffer);
849 buffer[strlen(buffer) + 1] = 0;
851 strcpy(buffer, &buffer[1]);
859 if (((ch == 13) || (ch == 10)) && (old != 13) && (old != 10)) {
862 if (((old == 13) || (old == 10)) && (isspace(real))) {
867 if (((strlen(aaa) + c) > (width - 5)) && (strlen(aaa) > (width - 5))) {
868 cprintf("%s%s", nl, aaa);
877 if ((strlen(aaa) + c) > (width - 5)) {
886 if ((ch == 13) || (ch == 10)) {
887 cprintf("%s%s", aaa, nl);
894 cprintf("%s%s", aaa, nl);
900 * Callback function for mime parser that simply lists the part
902 void list_this_part(char *name, char *filename, char *partnum, char *disp,
903 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
908 ma = (struct ma_info *)cbuserdata;
909 if (ma->is_ma == 0) {
910 cprintf("part=%s|%s|%s|%s|%s|%ld\n",
911 name, filename, partnum, disp, cbtype, (long)length);
916 * Callback function for multipart prefix
918 void list_this_pref(char *name, char *filename, char *partnum, char *disp,
919 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
924 ma = (struct ma_info *)cbuserdata;
925 if (!strcasecmp(cbtype, "multipart/alternative")) {
929 if (ma->is_ma == 0) {
930 cprintf("pref=%s|%s\n", partnum, cbtype);
935 * Callback function for multipart sufffix
937 void list_this_suff(char *name, char *filename, char *partnum, char *disp,
938 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
943 ma = (struct ma_info *)cbuserdata;
944 if (ma->is_ma == 0) {
945 cprintf("suff=%s|%s\n", partnum, cbtype);
947 if (!strcasecmp(cbtype, "multipart/alternative")) {
954 * Callback function for mime parser that opens a section for downloading
956 void mime_download(char *name, char *filename, char *partnum, char *disp,
957 void *content, char *cbtype, char *cbcharset, size_t length,
958 char *encoding, void *cbuserdata)
961 /* Silently go away if there's already a download open... */
962 if (CC->download_fp != NULL)
965 /* ...or if this is not the desired section */
966 if (strcasecmp(CC->download_desired_section, partnum))
969 CC->download_fp = tmpfile();
970 if (CC->download_fp == NULL)
973 fwrite(content, length, 1, CC->download_fp);
974 fflush(CC->download_fp);
975 rewind(CC->download_fp);
977 OpenCmdResult(filename, cbtype);
983 * Callback function for mime parser that outputs a section all at once
985 void mime_spew_section(char *name, char *filename, char *partnum, char *disp,
986 void *content, char *cbtype, char *cbcharset, size_t length,
987 char *encoding, void *cbuserdata)
989 int *found_it = (int *)cbuserdata;
991 /* ...or if this is not the desired section */
992 if (strcasecmp(CC->download_desired_section, partnum))
997 cprintf("%d %d\n", BINARY_FOLLOWS, (int)length);
998 client_write(content, length);
1004 * Load a message from disk into memory.
1005 * This is used by CtdlOutputMsg() and other fetch functions.
1007 * NOTE: Caller is responsible for freeing the returned CtdlMessage struct
1008 * using the CtdlMessageFree() function.
1010 struct CtdlMessage *CtdlFetchMessage(long msgnum, int with_body)
1012 struct cdbdata *dmsgtext;
1013 struct CtdlMessage *ret = NULL;
1017 cit_uint8_t field_header;
1019 CtdlLogPrintf(CTDL_DEBUG, "CtdlFetchMessage(%ld, %d)\n", msgnum, with_body);
1021 dmsgtext = cdb_fetch(CDB_MSGMAIN, &msgnum, sizeof(long));
1022 if (dmsgtext == NULL) {
1025 mptr = dmsgtext->ptr;
1026 upper_bound = mptr + dmsgtext->len;
1028 /* Parse the three bytes that begin EVERY message on disk.
1029 * The first is always 0xFF, the on-disk magic number.
1030 * The second is the anonymous/public type byte.
1031 * The third is the format type byte (vari, fixed, or MIME).
1035 CtdlLogPrintf(CTDL_ERR, "Message %ld appears to be corrupted.\n", msgnum);
1039 ret = (struct CtdlMessage *) malloc(sizeof(struct CtdlMessage));
1040 memset(ret, 0, sizeof(struct CtdlMessage));
1042 ret->cm_magic = CTDLMESSAGE_MAGIC;
1043 ret->cm_anon_type = *mptr++; /* Anon type byte */
1044 ret->cm_format_type = *mptr++; /* Format type byte */
1047 * The rest is zero or more arbitrary fields. Load them in.
1048 * We're done when we encounter either a zero-length field or
1049 * have just processed the 'M' (message text) field.
1052 if (mptr >= upper_bound) {
1055 field_header = *mptr++;
1056 ret->cm_fields[field_header] = strdup(mptr);
1058 while (*mptr++ != 0); /* advance to next field */
1060 } while ((mptr < upper_bound) && (field_header != 'M'));
1064 /* Always make sure there's something in the msg text field. If
1065 * it's NULL, the message text is most likely stored separately,
1066 * so go ahead and fetch that. Failing that, just set a dummy
1067 * body so other code doesn't barf.
1069 if ( (ret->cm_fields['M'] == NULL) && (with_body) ) {
1070 dmsgtext = cdb_fetch(CDB_BIGMSGS, &msgnum, sizeof(long));
1071 if (dmsgtext != NULL) {
1072 ret->cm_fields['M'] = strdup(dmsgtext->ptr);
1076 if (ret->cm_fields['M'] == NULL) {
1077 ret->cm_fields['M'] = strdup("\r\n\r\n (no text)\r\n");
1080 /* Perform "before read" hooks (aborting if any return nonzero) */
1081 if (PerformMessageHooks(ret, EVT_BEFOREREAD) > 0) {
1082 CtdlFreeMessage(ret);
1091 * Returns 1 if the supplied pointer points to a valid Citadel message.
1092 * If the pointer is NULL or the magic number check fails, returns 0.
1094 int is_valid_message(struct CtdlMessage *msg) {
1097 if ((msg->cm_magic) != CTDLMESSAGE_MAGIC) {
1098 CtdlLogPrintf(CTDL_WARNING, "is_valid_message() -- self-check failed\n");
1106 * 'Destructor' for struct CtdlMessage
1108 void CtdlFreeMessage(struct CtdlMessage *msg)
1112 if (is_valid_message(msg) == 0)
1114 if (msg != NULL) free (msg);
1118 for (i = 0; i < 256; ++i)
1119 if (msg->cm_fields[i] != NULL) {
1120 free(msg->cm_fields[i]);
1123 msg->cm_magic = 0; /* just in case */
1129 * Pre callback function for multipart/alternative
1131 * NOTE: this differs from the standard behavior for a reason. Normally when
1132 * displaying multipart/alternative you want to show the _last_ usable
1133 * format in the message. Here we show the _first_ one, because it's
1134 * usually text/plain. Since this set of functions is designed for text
1135 * output to non-MIME-aware clients, this is the desired behavior.
1138 void fixed_output_pre(char *name, char *filename, char *partnum, char *disp,
1139 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
1144 ma = (struct ma_info *)cbuserdata;
1145 CtdlLogPrintf(CTDL_DEBUG, "fixed_output_pre() type=<%s>\n", cbtype);
1146 if (!strcasecmp(cbtype, "multipart/alternative")) {
1150 if (!strcasecmp(cbtype, "message/rfc822")) {
1156 * Post callback function for multipart/alternative
1158 void fixed_output_post(char *name, char *filename, char *partnum, char *disp,
1159 void *content, char *cbtype, char *cbcharset, size_t length,
1160 char *encoding, void *cbuserdata)
1164 ma = (struct ma_info *)cbuserdata;
1165 CtdlLogPrintf(CTDL_DEBUG, "fixed_output_post() type=<%s>\n", cbtype);
1166 if (!strcasecmp(cbtype, "multipart/alternative")) {
1170 if (!strcasecmp(cbtype, "message/rfc822")) {
1176 * Inline callback function for mime parser that wants to display text
1178 void fixed_output(char *name, char *filename, char *partnum, char *disp,
1179 void *content, char *cbtype, char *cbcharset, size_t length,
1180 char *encoding, void *cbuserdata)
1187 ma = (struct ma_info *)cbuserdata;
1189 CtdlLogPrintf(CTDL_DEBUG,
1190 "fixed_output() part %s: %s (%s) (%ld bytes)\n",
1191 partnum, filename, cbtype, (long)length);
1194 * If we're in the middle of a multipart/alternative scope and
1195 * we've already printed another section, skip this one.
1197 if ( (ma->is_ma) && (ma->did_print) ) {
1198 CtdlLogPrintf(CTDL_DEBUG, "Skipping part %s (%s)\n", partnum, cbtype);
1203 if ( (!strcasecmp(cbtype, "text/plain"))
1204 || (IsEmptyStr(cbtype)) ) {
1207 client_write(wptr, length);
1208 if (wptr[length-1] != '\n') {
1215 if (!strcasecmp(cbtype, "text/html")) {
1216 ptr = html_to_ascii(content, length, 80, 0);
1218 client_write(ptr, wlen);
1219 if (ptr[wlen-1] != '\n') {
1226 if (ma->use_fo_hooks) {
1227 if (PerformFixedOutputHooks(cbtype, content, length)) {
1228 /* above function returns nonzero if it handled the part */
1233 if (strncasecmp(cbtype, "multipart/", 10)) {
1234 cprintf("Part %s: %s (%s) (%ld bytes)\r\n",
1235 partnum, filename, cbtype, (long)length);
1241 * The client is elegant and sophisticated and wants to be choosy about
1242 * MIME content types, so figure out which multipart/alternative part
1243 * we're going to send.
1245 * We use a system of weights. When we find a part that matches one of the
1246 * MIME types we've declared as preferential, we can store it in ma->chosen_part
1247 * and then set ma->chosen_pref to that MIME type's position in our preference
1248 * list. If we then hit another match, we only replace the first match if
1249 * the preference value is lower.
1251 void choose_preferred(char *name, char *filename, char *partnum, char *disp,
1252 void *content, char *cbtype, char *cbcharset, size_t length,
1253 char *encoding, void *cbuserdata)
1259 ma = (struct ma_info *)cbuserdata;
1261 // NOTE: REMOVING THIS CONDITIONAL FIXES BUG 220
1262 // http://bugzilla.citadel.org/show_bug.cgi?id=220
1263 // I don't know if there are any side effects! Please TEST TEST TEST
1264 //if (ma->is_ma > 0) {
1266 for (i=0; i<num_tokens(CC->preferred_formats, '|'); ++i) {
1267 extract_token(buf, CC->preferred_formats, i, '|', sizeof buf);
1268 if ( (!strcasecmp(buf, cbtype)) && (!ma->freeze) ) {
1269 if (i < ma->chosen_pref) {
1270 CtdlLogPrintf(CTDL_DEBUG, "Setting chosen part: <%s>\n", partnum);
1271 safestrncpy(ma->chosen_part, partnum, sizeof ma->chosen_part);
1272 ma->chosen_pref = i;
1279 * Now that we've chosen our preferred part, output it.
1281 void output_preferred(char *name, char *filename, char *partnum, char *disp,
1282 void *content, char *cbtype, char *cbcharset, size_t length,
1283 char *encoding, void *cbuserdata)
1287 int add_newline = 0;
1291 ma = (struct ma_info *)cbuserdata;
1293 /* This is not the MIME part you're looking for... */
1294 if (strcasecmp(partnum, ma->chosen_part)) return;
1296 /* If the content-type of this part is in our preferred formats
1297 * list, we can simply output it verbatim.
1299 for (i=0; i<num_tokens(CC->preferred_formats, '|'); ++i) {
1300 extract_token(buf, CC->preferred_formats, i, '|', sizeof buf);
1301 if (!strcasecmp(buf, cbtype)) {
1302 /* Yeah! Go! W00t!! */
1304 text_content = (char *)content;
1305 if (text_content[length-1] != '\n') {
1308 cprintf("Content-type: %s", cbtype);
1309 if (!IsEmptyStr(cbcharset)) {
1310 cprintf("; charset=%s", cbcharset);
1312 cprintf("\nContent-length: %d\n",
1313 (int)(length + add_newline) );
1314 if (!IsEmptyStr(encoding)) {
1315 cprintf("Content-transfer-encoding: %s\n", encoding);
1318 cprintf("Content-transfer-encoding: 7bit\n");
1320 cprintf("X-Citadel-MSG4-Partnum: %s\n", partnum);
1322 client_write(content, length);
1323 if (add_newline) cprintf("\n");
1328 /* No translations required or possible: output as text/plain */
1329 cprintf("Content-type: text/plain\n\n");
1330 fixed_output(name, filename, partnum, disp, content, cbtype, cbcharset,
1331 length, encoding, cbuserdata);
1336 char desired_section[64];
1343 * Callback function for
1345 void extract_encapsulated_message(char *name, char *filename, char *partnum, char *disp,
1346 void *content, char *cbtype, char *cbcharset, size_t length,
1347 char *encoding, void *cbuserdata)
1349 struct encapmsg *encap;
1351 encap = (struct encapmsg *)cbuserdata;
1353 /* Only proceed if this is the desired section... */
1354 if (!strcasecmp(encap->desired_section, partnum)) {
1355 encap->msglen = length;
1356 encap->msg = malloc(length + 2);
1357 memcpy(encap->msg, content, length);
1367 * Get a message off disk. (returns om_* values found in msgbase.h)
1370 int CtdlOutputMsg(long msg_num, /* message number (local) to fetch */
1371 int mode, /* how would you like that message? */
1372 int headers_only, /* eschew the message body? */
1373 int do_proto, /* do Citadel protocol responses? */
1374 int crlf, /* Use CRLF newlines instead of LF? */
1375 char *section /* NULL or a message/rfc822 section */
1377 struct CtdlMessage *TheMessage = NULL;
1378 int retcode = om_no_such_msg;
1379 struct encapmsg encap;
1381 CtdlLogPrintf(CTDL_DEBUG, "CtdlOutputMsg() msgnum=%ld, mode=%d, section=%s\n",
1383 (section ? section : "<>")
1386 if ((!(CC->logged_in)) && (!(CC->internal_pgm))) {
1387 if (do_proto) cprintf("%d Not logged in.\n",
1388 ERROR + NOT_LOGGED_IN);
1389 return(om_not_logged_in);
1392 /* FIXME: check message id against msglist for this room */
1395 * Fetch the message from disk. If we're in any sort of headers
1396 * only mode, request that we don't even bother loading the body
1399 if ( (headers_only == HEADERS_FAST) || (headers_only == HEADERS_ONLY) ) {
1400 TheMessage = CtdlFetchMessage(msg_num, 0);
1403 TheMessage = CtdlFetchMessage(msg_num, 1);
1406 if (TheMessage == NULL) {
1407 if (do_proto) cprintf("%d Can't locate msg %ld on disk\n",
1408 ERROR + MESSAGE_NOT_FOUND, msg_num);
1409 return(om_no_such_msg);
1412 /* Here is the weird form of this command, to process only an
1413 * encapsulated message/rfc822 section.
1415 if (section) if (!IsEmptyStr(section)) if (strcmp(section, "0")) {
1416 memset(&encap, 0, sizeof encap);
1417 safestrncpy(encap.desired_section, section, sizeof encap.desired_section);
1418 mime_parser(TheMessage->cm_fields['M'],
1420 *extract_encapsulated_message,
1421 NULL, NULL, (void *)&encap, 0
1423 CtdlFreeMessage(TheMessage);
1427 encap.msg[encap.msglen] = 0;
1428 TheMessage = convert_internet_message(encap.msg);
1429 encap.msg = NULL; /* no free() here, TheMessage owns it now */
1431 /* Now we let it fall through to the bottom of this
1432 * function, because TheMessage now contains the
1433 * encapsulated message instead of the top-level
1434 * message. Isn't that neat?
1439 if (do_proto) cprintf("%d msg %ld has no part %s\n",
1440 ERROR + MESSAGE_NOT_FOUND, msg_num, section);
1441 retcode = om_no_such_msg;
1446 /* Ok, output the message now */
1447 retcode = CtdlOutputPreLoadedMsg(TheMessage, mode, headers_only, do_proto, crlf);
1448 CtdlFreeMessage(TheMessage);
1455 * Get a message off disk. (returns om_* values found in msgbase.h)
1457 int CtdlOutputPreLoadedMsg(
1458 struct CtdlMessage *TheMessage,
1459 int mode, /* how would you like that message? */
1460 int headers_only, /* eschew the message body? */
1461 int do_proto, /* do Citadel protocol responses? */
1462 int crlf /* Use CRLF newlines instead of LF? */
1468 char display_name[256];
1470 char *nl; /* newline string */
1472 int subject_found = 0;
1475 /* Buffers needed for RFC822 translation. These are all filled
1476 * using functions that are bounds-checked, and therefore we can
1477 * make them substantially smaller than SIZ.
1485 char datestamp[100];
1487 CtdlLogPrintf(CTDL_DEBUG, "CtdlOutputPreLoadedMsg(TheMessage=%s, %d, %d, %d, %d\n",
1488 ((TheMessage == NULL) ? "NULL" : "not null"),
1489 mode, headers_only, do_proto, crlf);
1491 strcpy(mid, "unknown");
1492 nl = (crlf ? "\r\n" : "\n");
1494 if (!is_valid_message(TheMessage)) {
1495 CtdlLogPrintf(CTDL_ERR,
1496 "ERROR: invalid preloaded message for output\n");
1497 return(om_no_such_msg);
1500 /* Are we downloading a MIME component? */
1501 if (mode == MT_DOWNLOAD) {
1502 if (TheMessage->cm_format_type != FMT_RFC822) {
1504 cprintf("%d This is not a MIME message.\n",
1505 ERROR + ILLEGAL_VALUE);
1506 } else if (CC->download_fp != NULL) {
1507 if (do_proto) cprintf(
1508 "%d You already have a download open.\n",
1509 ERROR + RESOURCE_BUSY);
1511 /* Parse the message text component */
1512 mptr = TheMessage->cm_fields['M'];
1513 mime_parser(mptr, NULL, *mime_download, NULL, NULL, NULL, 0);
1514 /* If there's no file open by this time, the requested
1515 * section wasn't found, so print an error
1517 if (CC->download_fp == NULL) {
1518 if (do_proto) cprintf(
1519 "%d Section %s not found.\n",
1520 ERROR + FILE_NOT_FOUND,
1521 CC->download_desired_section);
1524 return((CC->download_fp != NULL) ? om_ok : om_mime_error);
1527 /* MT_SPEW_SECTION is like MT_DOWNLOAD except it outputs the whole MIME part
1528 * in a single server operation instead of opening a download file.
1530 if (mode == MT_SPEW_SECTION) {
1531 if (TheMessage->cm_format_type != FMT_RFC822) {
1533 cprintf("%d This is not a MIME message.\n",
1534 ERROR + ILLEGAL_VALUE);
1536 /* Parse the message text component */
1539 mptr = TheMessage->cm_fields['M'];
1540 mime_parser(mptr, NULL, *mime_spew_section, NULL, NULL, (void *)&found_it, 0);
1541 /* If section wasn't found, print an error
1544 if (do_proto) cprintf(
1545 "%d Section %s not found.\n",
1546 ERROR + FILE_NOT_FOUND,
1547 CC->download_desired_section);
1550 return((CC->download_fp != NULL) ? om_ok : om_mime_error);
1553 /* now for the user-mode message reading loops */
1554 if (do_proto) cprintf("%d msg:\n", LISTING_FOLLOWS);
1556 /* Does the caller want to skip the headers? */
1557 if (headers_only == HEADERS_NONE) goto START_TEXT;
1559 /* Tell the client which format type we're using. */
1560 if ( (mode == MT_CITADEL) && (do_proto) ) {
1561 cprintf("type=%d\n", TheMessage->cm_format_type);
1564 /* nhdr=yes means that we're only displaying headers, no body */
1565 if ( (TheMessage->cm_anon_type == MES_ANONONLY)
1566 && (mode == MT_CITADEL)
1569 cprintf("nhdr=yes\n");
1572 /* begin header processing loop for Citadel message format */
1574 if ((mode == MT_CITADEL) || (mode == MT_MIME)) {
1576 safestrncpy(display_name, "<unknown>", sizeof display_name);
1577 if (TheMessage->cm_fields['A']) {
1578 strcpy(buf, TheMessage->cm_fields['A']);
1579 if (TheMessage->cm_anon_type == MES_ANONONLY) {
1580 safestrncpy(display_name, "****", sizeof display_name);
1582 else if (TheMessage->cm_anon_type == MES_ANONOPT) {
1583 safestrncpy(display_name, "anonymous", sizeof display_name);
1586 safestrncpy(display_name, buf, sizeof display_name);
1588 if ((is_room_aide())
1589 && ((TheMessage->cm_anon_type == MES_ANONONLY)
1590 || (TheMessage->cm_anon_type == MES_ANONOPT))) {
1591 size_t tmp = strlen(display_name);
1592 snprintf(&display_name[tmp],
1593 sizeof display_name - tmp,
1598 /* Don't show Internet address for users on the
1599 * local Citadel network.
1602 if (TheMessage->cm_fields['N'] != NULL)
1603 if (!IsEmptyStr(TheMessage->cm_fields['N']))
1604 if (haschar(TheMessage->cm_fields['N'], '.') == 0) {
1608 /* Now spew the header fields in the order we like them. */
1609 safestrncpy(allkeys, FORDER, sizeof allkeys);
1610 for (i=0; i<strlen(allkeys); ++i) {
1611 k = (int) allkeys[i];
1613 if ( (TheMessage->cm_fields[k] != NULL)
1614 && (msgkeys[k] != NULL) ) {
1616 if (do_proto) cprintf("%s=%s\n",
1620 else if ((k == 'F') && (suppress_f)) {
1623 /* Masquerade display name if needed */
1625 if (do_proto) cprintf("%s=%s\n",
1627 TheMessage->cm_fields[k]
1636 /* begin header processing loop for RFC822 transfer format */
1641 strcpy(snode, NODENAME);
1642 strcpy(lnode, HUMANNODE);
1643 if (mode == MT_RFC822) {
1644 for (i = 0; i < 256; ++i) {
1645 if (TheMessage->cm_fields[i]) {
1646 mptr = TheMessage->cm_fields[i];
1649 safestrncpy(luser, mptr, sizeof luser);
1650 safestrncpy(suser, mptr, sizeof suser);
1652 else if (i == 'Y') {
1653 cprintf("CC: %s%s", mptr, nl);
1655 else if (i == 'P') {
1656 cprintf("Return-Path: %s%s", mptr, nl);
1658 else if (i == 'V') {
1659 cprintf("Envelope-To: %s%s", mptr, nl);
1661 else if (i == 'U') {
1662 cprintf("Subject: %s%s", mptr, nl);
1666 safestrncpy(mid, mptr, sizeof mid);
1668 safestrncpy(lnode, mptr, sizeof lnode);
1670 safestrncpy(fuser, mptr, sizeof fuser);
1671 /* else if (i == 'O')
1672 cprintf("X-Citadel-Room: %s%s",
1675 safestrncpy(snode, mptr, sizeof snode);
1678 if (haschar(mptr, '@') == 0)
1680 cprintf("To: %s@%s%s", mptr, config.c_fqdn, nl);
1684 cprintf("To: %s%s", mptr, nl);
1687 else if (i == 'T') {
1688 datestring(datestamp, sizeof datestamp,
1689 atol(mptr), DATESTRING_RFC822);
1690 cprintf("Date: %s%s", datestamp, nl);
1692 else if (i == 'W') {
1693 cprintf("References: ");
1694 k = num_tokens(mptr, '|');
1695 for (j=0; j<k; ++j) {
1696 extract_token(buf, mptr, j, '|', sizeof buf);
1697 cprintf("<%s>", buf);
1708 if (subject_found == 0) {
1709 cprintf("Subject: (no subject)%s", nl);
1713 for (i=0; !IsEmptyStr(&suser[i]); ++i) {
1714 suser[i] = tolower(suser[i]);
1715 if (!isalnum(suser[i])) suser[i]='_';
1718 if (mode == MT_RFC822) {
1719 if (!strcasecmp(snode, NODENAME)) {
1720 safestrncpy(snode, FQDN, sizeof snode);
1723 /* Construct a fun message id */
1724 cprintf("Message-ID: <%s", mid);/// todo: this possibly breaks threadding mails.
1725 if (strchr(mid, '@')==NULL) {
1726 cprintf("@%s", snode);
1730 if (!is_room_aide() && (TheMessage->cm_anon_type == MES_ANONONLY)) {
1731 cprintf("From: \"----\" <x@x.org>%s", nl);
1733 else if (!is_room_aide() && (TheMessage->cm_anon_type == MES_ANONOPT)) {
1734 cprintf("From: \"anonymous\" <x@x.org>%s", nl);
1736 else if (!IsEmptyStr(fuser)) {
1737 cprintf("From: \"%s\" <%s>%s", luser, fuser, nl);
1740 cprintf("From: \"%s\" <%s@%s>%s", luser, suser, snode, nl);
1743 cprintf("Organization: %s%s", lnode, nl);
1745 /* Blank line signifying RFC822 end-of-headers */
1746 if (TheMessage->cm_format_type != FMT_RFC822) {
1751 /* end header processing loop ... at this point, we're in the text */
1753 if (headers_only == HEADERS_FAST) goto DONE;
1754 mptr = TheMessage->cm_fields['M'];
1756 /* Tell the client about the MIME parts in this message */
1757 if (TheMessage->cm_format_type == FMT_RFC822) {
1758 if ( (mode == MT_CITADEL) || (mode == MT_MIME) ) {
1759 memset(&ma, 0, sizeof(struct ma_info));
1760 mime_parser(mptr, NULL,
1761 (do_proto ? *list_this_part : NULL),
1762 (do_proto ? *list_this_pref : NULL),
1763 (do_proto ? *list_this_suff : NULL),
1766 else if (mode == MT_RFC822) { /* unparsed RFC822 dump */
1767 char *start_of_text = NULL;
1768 start_of_text = strstr(mptr, "\n\r\n");
1769 if (start_of_text == NULL) start_of_text = strstr(mptr, "\n\n");
1770 if (start_of_text == NULL) start_of_text = mptr;
1772 start_of_text = strstr(start_of_text, "\n");
1777 int nllen = strlen(nl);
1778 while (ch=*mptr, ch!=0) {
1784 ((headers_only == HEADERS_NONE) && (mptr >= start_of_text))
1785 || ((headers_only == HEADERS_ONLY) && (mptr < start_of_text))
1786 || ((headers_only != HEADERS_NONE) && (headers_only != HEADERS_ONLY))
1789 sprintf(&outbuf[outlen], "%s", nl);
1793 outbuf[outlen++] = ch;
1798 if (outlen > 1000) {
1799 client_write(outbuf, outlen);
1804 client_write(outbuf, outlen);
1812 if (headers_only == HEADERS_ONLY) {
1816 /* signify start of msg text */
1817 if ( (mode == MT_CITADEL) || (mode == MT_MIME) ) {
1818 if (do_proto) cprintf("text\n");
1821 /* If the format type on disk is 1 (fixed-format), then we want
1822 * everything to be output completely literally ... regardless of
1823 * what message transfer format is in use.
1825 if (TheMessage->cm_format_type == FMT_FIXED) {
1827 if (mode == MT_MIME) {
1828 cprintf("Content-type: text/plain\n\n");
1832 while (ch = *mptr++, ch > 0) {
1835 if ((ch == 10) || (buflen > 250)) {
1837 cprintf("%s%s", buf, nl);
1846 if (!IsEmptyStr(buf))
1847 cprintf("%s%s", buf, nl);
1850 /* If the message on disk is format 0 (Citadel vari-format), we
1851 * output using the formatter at 80 columns. This is the final output
1852 * form if the transfer format is RFC822, but if the transfer format
1853 * is Citadel proprietary, it'll still work, because the indentation
1854 * for new paragraphs is correct and the client will reformat the
1855 * message to the reader's screen width.
1857 if (TheMessage->cm_format_type == FMT_CITADEL) {
1858 if (mode == MT_MIME) {
1859 cprintf("Content-type: text/x-citadel-variformat\n\n");
1861 memfmout(mptr, 0, nl);
1864 /* If the message on disk is format 4 (MIME), we've gotta hand it
1865 * off to the MIME parser. The client has already been told that
1866 * this message is format 1 (fixed format), so the callback function
1867 * we use will display those parts as-is.
1869 if (TheMessage->cm_format_type == FMT_RFC822) {
1870 memset(&ma, 0, sizeof(struct ma_info));
1872 if (mode == MT_MIME) {
1873 ma.use_fo_hooks = 0;
1874 strcpy(ma.chosen_part, "1");
1875 ma.chosen_pref = 9999;
1876 mime_parser(mptr, NULL,
1877 *choose_preferred, *fixed_output_pre,
1878 *fixed_output_post, (void *)&ma, 0);
1879 mime_parser(mptr, NULL,
1880 *output_preferred, NULL, NULL, (void *)&ma, CC->msg4_dont_decode);
1883 ma.use_fo_hooks = 1;
1884 mime_parser(mptr, NULL,
1885 *fixed_output, *fixed_output_pre,
1886 *fixed_output_post, (void *)&ma, 0);
1891 DONE: /* now we're done */
1892 if (do_proto) cprintf("000\n");
1899 * display a message (mode 0 - Citadel proprietary)
1901 void cmd_msg0(char *cmdbuf)
1904 int headers_only = HEADERS_ALL;
1906 msgid = extract_long(cmdbuf, 0);
1907 headers_only = extract_int(cmdbuf, 1);
1909 CtdlOutputMsg(msgid, MT_CITADEL, headers_only, 1, 0, NULL);
1915 * display a message (mode 2 - RFC822)
1917 void cmd_msg2(char *cmdbuf)
1920 int headers_only = HEADERS_ALL;
1922 msgid = extract_long(cmdbuf, 0);
1923 headers_only = extract_int(cmdbuf, 1);
1925 CtdlOutputMsg(msgid, MT_RFC822, headers_only, 1, 1, NULL);
1931 * display a message (mode 3 - IGnet raw format - internal programs only)
1933 void cmd_msg3(char *cmdbuf)
1936 struct CtdlMessage *msg = NULL;
1939 if (CC->internal_pgm == 0) {
1940 cprintf("%d This command is for internal programs only.\n",
1941 ERROR + HIGHER_ACCESS_REQUIRED);
1945 msgnum = extract_long(cmdbuf, 0);
1946 msg = CtdlFetchMessage(msgnum, 1);
1948 cprintf("%d Message %ld not found.\n",
1949 ERROR + MESSAGE_NOT_FOUND, msgnum);
1953 serialize_message(&smr, msg);
1954 CtdlFreeMessage(msg);
1957 cprintf("%d Unable to serialize message\n",
1958 ERROR + INTERNAL_ERROR);
1962 cprintf("%d %ld\n", BINARY_FOLLOWS, (long)smr.len);
1963 client_write((char *)smr.ser, (int)smr.len);
1970 * Display a message using MIME content types
1972 void cmd_msg4(char *cmdbuf)
1977 msgid = extract_long(cmdbuf, 0);
1978 extract_token(section, cmdbuf, 1, '|', sizeof section);
1979 CtdlOutputMsg(msgid, MT_MIME, 0, 1, 0, (section[0] ? section : NULL) );
1985 * Client tells us its preferred message format(s)
1987 void cmd_msgp(char *cmdbuf)
1989 if (!strcasecmp(cmdbuf, "dont_decode")) {
1990 CC->msg4_dont_decode = 1;
1991 cprintf("%d MSG4 will not pre-decode messages.\n", CIT_OK);
1994 safestrncpy(CC->preferred_formats, cmdbuf, sizeof(CC->preferred_formats));
1995 cprintf("%d Preferred MIME formats have been set.\n", CIT_OK);
2001 * Open a component of a MIME message as a download file
2003 void cmd_opna(char *cmdbuf)
2006 char desired_section[128];
2008 msgid = extract_long(cmdbuf, 0);
2009 extract_token(desired_section, cmdbuf, 1, '|', sizeof desired_section);
2010 safestrncpy(CC->download_desired_section, desired_section,
2011 sizeof CC->download_desired_section);
2012 CtdlOutputMsg(msgid, MT_DOWNLOAD, 0, 1, 1, NULL);
2017 * Open a component of a MIME message and transmit it all at once
2019 void cmd_dlat(char *cmdbuf)
2022 char desired_section[128];
2024 msgid = extract_long(cmdbuf, 0);
2025 extract_token(desired_section, cmdbuf, 1, '|', sizeof desired_section);
2026 safestrncpy(CC->download_desired_section, desired_section,
2027 sizeof CC->download_desired_section);
2028 CtdlOutputMsg(msgid, MT_SPEW_SECTION, 0, 1, 1, NULL);
2033 * Save one or more message pointers into a specified room
2034 * (Returns 0 for success, nonzero for failure)
2035 * roomname may be NULL to use the current room
2037 * Note that the 'supplied_msg' field may be set to NULL, in which case
2038 * the message will be fetched from disk, by number, if we need to perform
2039 * replication checks. This adds an additional database read, so if the
2040 * caller already has the message in memory then it should be supplied. (Obviously
2041 * this mode of operation only works if we're saving a single message.)
2043 int CtdlSaveMsgPointersInRoom(char *roomname, long newmsgidlist[], int num_newmsgs,
2044 int do_repl_check, struct CtdlMessage *supplied_msg)
2047 char hold_rm[ROOMNAMELEN];
2048 struct cdbdata *cdbfr;
2051 long highest_msg = 0L;
2054 struct CtdlMessage *msg = NULL;
2056 long *msgs_to_be_merged = NULL;
2057 int num_msgs_to_be_merged = 0;
2059 CtdlLogPrintf(CTDL_DEBUG,
2060 "CtdlSaveMsgPointersInRoom(room=%s, num_msgs=%d, repl=%d)\n",
2061 roomname, num_newmsgs, do_repl_check);
2063 strcpy(hold_rm, CC->room.QRname);
2066 if (newmsgidlist == NULL) return(ERROR + INTERNAL_ERROR);
2067 if (num_newmsgs < 1) return(ERROR + INTERNAL_ERROR);
2068 if (num_newmsgs > 1) supplied_msg = NULL;
2070 /* Now the regular stuff */
2071 if (lgetroom(&CC->room,
2072 ((roomname != NULL) ? roomname : CC->room.QRname) )
2074 CtdlLogPrintf(CTDL_ERR, "No such room <%s>\n", roomname);
2075 return(ERROR + ROOM_NOT_FOUND);
2079 msgs_to_be_merged = malloc(sizeof(long) * num_newmsgs);
2080 num_msgs_to_be_merged = 0;
2083 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long));
2084 if (cdbfr == NULL) {
2088 msglist = (long *) cdbfr->ptr;
2089 cdbfr->ptr = NULL; /* CtdlSaveMsgPointerInRoom() now owns this memory */
2090 num_msgs = cdbfr->len / sizeof(long);
2095 /* Create a list of msgid's which were supplied by the caller, but do
2096 * not already exist in the target room. It is absolutely taboo to
2097 * have more than one reference to the same message in a room.
2099 for (i=0; i<num_newmsgs; ++i) {
2101 if (num_msgs > 0) for (j=0; j<num_msgs; ++j) {
2102 if (msglist[j] == newmsgidlist[i]) {
2107 msgs_to_be_merged[num_msgs_to_be_merged++] = newmsgidlist[i];
2111 CtdlLogPrintf(9, "%d unique messages to be merged\n", num_msgs_to_be_merged);
2114 * Now merge the new messages
2116 msglist = realloc(msglist, (sizeof(long) * (num_msgs + num_msgs_to_be_merged)) );
2117 if (msglist == NULL) {
2118 CtdlLogPrintf(CTDL_ALERT, "ERROR: can't realloc message list!\n");
2120 memcpy(&msglist[num_msgs], msgs_to_be_merged, (sizeof(long) * num_msgs_to_be_merged) );
2121 num_msgs += num_msgs_to_be_merged;
2123 /* Sort the message list, so all the msgid's are in order */
2124 num_msgs = sort_msglist(msglist, num_msgs);
2126 /* Determine the highest message number */
2127 highest_msg = msglist[num_msgs - 1];
2129 /* Write it back to disk. */
2130 cdb_store(CDB_MSGLISTS, &CC->room.QRnumber, (int)sizeof(long),
2131 msglist, (int)(num_msgs * sizeof(long)));
2133 /* Free up the memory we used. */
2136 /* Update the highest-message pointer and unlock the room. */
2137 CC->room.QRhighest = highest_msg;
2138 lputroom(&CC->room);
2140 /* Perform replication checks if necessary */
2141 if ( (DoesThisRoomNeedEuidIndexing(&CC->room)) && (do_repl_check) ) {
2142 CtdlLogPrintf(CTDL_DEBUG, "CtdlSaveMsgPointerInRoom() doing repl checks\n");
2144 for (i=0; i<num_msgs_to_be_merged; ++i) {
2145 msgid = msgs_to_be_merged[i];
2147 if (supplied_msg != NULL) {
2151 msg = CtdlFetchMessage(msgid, 0);
2155 ReplicationChecks(msg);
2157 /* If the message has an Exclusive ID, index that... */
2158 if (msg->cm_fields['E'] != NULL) {
2159 index_message_by_euid(msg->cm_fields['E'], &CC->room, msgid);
2162 /* Free up the memory we may have allocated */
2163 if (msg != supplied_msg) {
2164 CtdlFreeMessage(msg);
2172 CtdlLogPrintf(CTDL_DEBUG, "CtdlSaveMsgPointerInRoom() skips repl checks\n");
2175 /* Submit this room for processing by hooks */
2176 PerformRoomHooks(&CC->room);
2178 /* Go back to the room we were in before we wandered here... */
2179 getroom(&CC->room, hold_rm);
2181 /* Bump the reference count for all messages which were merged */
2182 for (i=0; i<num_msgs_to_be_merged; ++i) {
2183 AdjRefCount(msgs_to_be_merged[i], +1);
2186 /* Free up memory... */
2187 if (msgs_to_be_merged != NULL) {
2188 free(msgs_to_be_merged);
2191 /* Return success. */
2197 * This is the same as CtdlSaveMsgPointersInRoom() but it only accepts
2200 int CtdlSaveMsgPointerInRoom(char *roomname, long msgid,
2201 int do_repl_check, struct CtdlMessage *supplied_msg)
2203 return CtdlSaveMsgPointersInRoom(roomname, &msgid, 1, do_repl_check, supplied_msg);
2210 * Message base operation to save a new message to the message store
2211 * (returns new message number)
2213 * This is the back end for CtdlSubmitMsg() and should not be directly
2214 * called by server-side modules.
2217 long send_message(struct CtdlMessage *msg) {
2225 /* Get a new message number */
2226 newmsgid = get_new_message_number();
2227 snprintf(msgidbuf, sizeof msgidbuf, "%010ld@%s", newmsgid, config.c_fqdn);
2229 /* Generate an ID if we don't have one already */
2230 if (msg->cm_fields['I']==NULL) {
2231 msg->cm_fields['I'] = strdup(msgidbuf);
2234 /* If the message is big, set its body aside for storage elsewhere */
2235 if (msg->cm_fields['M'] != NULL) {
2236 if (strlen(msg->cm_fields['M']) > BIGMSG) {
2238 holdM = msg->cm_fields['M'];
2239 msg->cm_fields['M'] = NULL;
2243 /* Serialize our data structure for storage in the database */
2244 serialize_message(&smr, msg);
2247 msg->cm_fields['M'] = holdM;
2251 cprintf("%d Unable to serialize message\n",
2252 ERROR + INTERNAL_ERROR);
2256 /* Write our little bundle of joy into the message base */
2257 if (cdb_store(CDB_MSGMAIN, &newmsgid, (int)sizeof(long),
2258 smr.ser, smr.len) < 0) {
2259 CtdlLogPrintf(CTDL_ERR, "Can't store message\n");
2263 cdb_store(CDB_BIGMSGS,
2273 /* Free the memory we used for the serialized message */
2276 /* Return the *local* message ID to the caller
2277 * (even if we're storing an incoming network message)
2285 * Serialize a struct CtdlMessage into the format used on disk and network.
2287 * This function loads up a "struct ser_ret" (defined in server.h) which
2288 * contains the length of the serialized message and a pointer to the
2289 * serialized message in memory. THE LATTER MUST BE FREED BY THE CALLER.
2291 void serialize_message(struct ser_ret *ret, /* return values */
2292 struct CtdlMessage *msg) /* unserialized msg */
2294 size_t wlen, fieldlen;
2296 static char *forder = FORDER;
2299 * Check for valid message format
2301 if (is_valid_message(msg) == 0) {
2302 CtdlLogPrintf(CTDL_ERR, "serialize_message() aborting due to invalid message\n");
2309 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL)
2310 ret->len = ret->len +
2311 strlen(msg->cm_fields[(int)forder[i]]) + 2;
2313 ret->ser = malloc(ret->len);
2314 if (ret->ser == NULL) {
2315 CtdlLogPrintf(CTDL_ERR, "serialize_message() malloc(%ld) failed: %s\n",
2316 (long)ret->len, strerror(errno));
2323 ret->ser[1] = msg->cm_anon_type;
2324 ret->ser[2] = msg->cm_format_type;
2327 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL) {
2328 fieldlen = strlen(msg->cm_fields[(int)forder[i]]);
2329 ret->ser[wlen++] = (char)forder[i];
2330 safestrncpy((char *)&ret->ser[wlen], msg->cm_fields[(int)forder[i]], fieldlen+1);
2331 wlen = wlen + fieldlen + 1;
2333 if (ret->len != wlen) CtdlLogPrintf(CTDL_ERR, "ERROR: len=%ld wlen=%ld\n",
2334 (long)ret->len, (long)wlen);
2341 * Serialize a struct CtdlMessage into the format used on disk and network.
2343 * This function loads up a "struct ser_ret" (defined in server.h) which
2344 * contains the length of the serialized message and a pointer to the
2345 * serialized message in memory. THE LATTER MUST BE FREED BY THE CALLER.
2347 void dump_message(struct CtdlMessage *msg, /* unserialized msg */
2348 long Siz) /* how many chars ? */
2352 static char *forder = FORDER;
2356 * Check for valid message format
2358 if (is_valid_message(msg) == 0) {
2359 CtdlLogPrintf(CTDL_ERR, "dump_message() aborting due to invalid message\n");
2363 buf = (char*) malloc (Siz + 1);
2367 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL) {
2368 snprintf (buf, Siz, " msg[%c] = %s ...\n", (char) forder[i],
2369 msg->cm_fields[(int)forder[i]]);
2370 client_write (buf, strlen(buf));
2379 * Check to see if any messages already exist in the current room which
2380 * carry the same Exclusive ID as this one. If any are found, delete them.
2382 void ReplicationChecks(struct CtdlMessage *msg) {
2383 long old_msgnum = (-1L);
2385 if (DoesThisRoomNeedEuidIndexing(&CC->room) == 0) return;
2387 CtdlLogPrintf(CTDL_DEBUG, "Performing replication checks in <%s>\n",
2390 /* No exclusive id? Don't do anything. */
2391 if (msg == NULL) return;
2392 if (msg->cm_fields['E'] == NULL) return;
2393 if (IsEmptyStr(msg->cm_fields['E'])) return;
2394 /*CtdlLogPrintf(CTDL_DEBUG, "Exclusive ID: <%s> for room <%s>\n",
2395 msg->cm_fields['E'], CC->room.QRname);*/
2397 old_msgnum = locate_message_by_euid(msg->cm_fields['E'], &CC->room);
2398 if (old_msgnum > 0L) {
2399 CtdlLogPrintf(CTDL_DEBUG, "ReplicationChecks() replacing message %ld\n", old_msgnum);
2400 CtdlDeleteMessages(CC->room.QRname, &old_msgnum, 1, "");
2407 * Save a message to disk and submit it into the delivery system.
2409 long CtdlSubmitMsg(struct CtdlMessage *msg, /* message to save */
2410 struct recptypes *recps, /* recipients (if mail) */
2411 char *force /* force a particular room? */
2413 char submit_filename[128];
2414 char generated_timestamp[32];
2415 char hold_rm[ROOMNAMELEN];
2416 char actual_rm[ROOMNAMELEN];
2417 char force_room[ROOMNAMELEN];
2418 char content_type[SIZ]; /* We have to learn this */
2419 char recipient[SIZ];
2422 struct ctdluser userbuf;
2424 struct MetaData smi;
2425 FILE *network_fp = NULL;
2426 static int seqnum = 1;
2427 struct CtdlMessage *imsg = NULL;
2429 size_t instr_alloc = 0;
2431 char *hold_R, *hold_D;
2432 char *collected_addresses = NULL;
2433 struct addresses_to_be_filed *aptr = NULL;
2434 char *saved_rfc822_version = NULL;
2435 int qualified_for_journaling = 0;
2436 struct CitContext *CCC = CC; /* CachedCitContext - performance boost */
2438 CtdlLogPrintf(CTDL_DEBUG, "CtdlSubmitMsg() called\n");
2439 if (is_valid_message(msg) == 0) return(-1); /* self check */
2441 /* If this message has no timestamp, we take the liberty of
2442 * giving it one, right now.
2444 if (msg->cm_fields['T'] == NULL) {
2445 snprintf(generated_timestamp, sizeof generated_timestamp, "%ld", (long)time(NULL));
2446 msg->cm_fields['T'] = strdup(generated_timestamp);
2449 /* If this message has no path, we generate one.
2451 if (msg->cm_fields['P'] == NULL) {
2452 if (msg->cm_fields['A'] != NULL) {
2453 msg->cm_fields['P'] = strdup(msg->cm_fields['A']);
2454 for (a=0; !IsEmptyStr(&msg->cm_fields['P'][a]); ++a) {
2455 if (isspace(msg->cm_fields['P'][a])) {
2456 msg->cm_fields['P'][a] = ' ';
2461 msg->cm_fields['P'] = strdup("unknown");
2465 if (force == NULL) {
2466 strcpy(force_room, "");
2469 strcpy(force_room, force);
2472 /* Learn about what's inside, because it's what's inside that counts */
2473 if (msg->cm_fields['M'] == NULL) {
2474 CtdlLogPrintf(CTDL_ERR, "ERROR: attempt to save message with NULL body\n");
2478 switch (msg->cm_format_type) {
2480 strcpy(content_type, "text/x-citadel-variformat");
2483 strcpy(content_type, "text/plain");
2486 strcpy(content_type, "text/plain");
2487 mptr = bmstrcasestr(msg->cm_fields['M'], "Content-type:");
2490 safestrncpy(content_type, &mptr[13], sizeof content_type);
2491 striplt(content_type);
2492 aptr = content_type;
2493 while (!IsEmptyStr(aptr)) {
2505 /* Goto the correct room */
2506 CtdlLogPrintf(CTDL_DEBUG, "Selected room %s\n", (recps) ? CCC->room.QRname : SENTITEMS);
2507 strcpy(hold_rm, CCC->room.QRname);
2508 strcpy(actual_rm, CCC->room.QRname);
2509 if (recps != NULL) {
2510 strcpy(actual_rm, SENTITEMS);
2513 /* If the user is a twit, move to the twit room for posting */
2515 if (CCC->user.axlevel == 2) {
2516 strcpy(hold_rm, actual_rm);
2517 strcpy(actual_rm, config.c_twitroom);
2518 CtdlLogPrintf(CTDL_DEBUG, "Diverting to twit room\n");
2522 /* ...or if this message is destined for Aide> then go there. */
2523 if (!IsEmptyStr(force_room)) {
2524 strcpy(actual_rm, force_room);
2527 CtdlLogPrintf(CTDL_DEBUG, "Final selection: %s\n", actual_rm);
2528 if (strcasecmp(actual_rm, CCC->room.QRname)) {
2529 /* getroom(&CCC->room, actual_rm); */
2530 usergoto(actual_rm, 0, 1, NULL, NULL);
2534 * If this message has no O (room) field, generate one.
2536 if (msg->cm_fields['O'] == NULL) {
2537 msg->cm_fields['O'] = strdup(CCC->room.QRname);
2540 /* Perform "before save" hooks (aborting if any return nonzero) */
2541 CtdlLogPrintf(CTDL_DEBUG, "Performing before-save hooks\n");
2542 if (PerformMessageHooks(msg, EVT_BEFORESAVE) > 0) return(-3);
2545 * If this message has an Exclusive ID, and the room is replication
2546 * checking enabled, then do replication checks.
2548 if (DoesThisRoomNeedEuidIndexing(&CCC->room)) {
2549 ReplicationChecks(msg);
2552 /* Save it to disk */
2553 CtdlLogPrintf(CTDL_DEBUG, "Saving to disk\n");
2554 newmsgid = send_message(msg);
2555 if (newmsgid <= 0L) return(-5);
2557 /* Write a supplemental message info record. This doesn't have to
2558 * be a critical section because nobody else knows about this message
2561 CtdlLogPrintf(CTDL_DEBUG, "Creating MetaData record\n");
2562 memset(&smi, 0, sizeof(struct MetaData));
2563 smi.meta_msgnum = newmsgid;
2564 smi.meta_refcount = 0;
2565 safestrncpy(smi.meta_content_type, content_type,
2566 sizeof smi.meta_content_type);
2569 * Measure how big this message will be when rendered as RFC822.
2570 * We do this for two reasons:
2571 * 1. We need the RFC822 length for the new metadata record, so the
2572 * POP and IMAP services don't have to calculate message lengths
2573 * while the user is waiting (multiplied by potentially hundreds
2574 * or thousands of messages).
2575 * 2. If journaling is enabled, we will need an RFC822 version of the
2576 * message to attach to the journalized copy.
2578 if (CCC->redirect_buffer != NULL) {
2579 CtdlLogPrintf(CTDL_ALERT, "CCC->redirect_buffer is not NULL during message submission!\n");
2582 CCC->redirect_buffer = malloc(SIZ);
2583 CCC->redirect_len = 0;
2584 CCC->redirect_alloc = SIZ;
2585 CtdlOutputPreLoadedMsg(msg, MT_RFC822, HEADERS_ALL, 0, 1);
2586 smi.meta_rfc822_length = CCC->redirect_len;
2587 saved_rfc822_version = CCC->redirect_buffer;
2588 CCC->redirect_buffer = NULL;
2589 CCC->redirect_len = 0;
2590 CCC->redirect_alloc = 0;
2594 /* Now figure out where to store the pointers */
2595 CtdlLogPrintf(CTDL_DEBUG, "Storing pointers\n");
2597 /* If this is being done by the networker delivering a private
2598 * message, we want to BYPASS saving the sender's copy (because there
2599 * is no local sender; it would otherwise go to the Trashcan).
2601 if ((!CCC->internal_pgm) || (recps == NULL)) {
2602 if (CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 1, msg) != 0) {
2603 CtdlLogPrintf(CTDL_ERR, "ERROR saving message pointer!\n");
2604 CtdlSaveMsgPointerInRoom(config.c_aideroom, newmsgid, 0, msg);
2608 /* For internet mail, drop a copy in the outbound queue room */
2610 if (recps->num_internet > 0) {
2611 CtdlSaveMsgPointerInRoom(SMTP_SPOOLOUT_ROOM, newmsgid, 0, msg);
2614 /* If other rooms are specified, drop them there too. */
2616 if (recps->num_room > 0)
2617 for (i=0; i<num_tokens(recps->recp_room, '|'); ++i) {
2618 extract_token(recipient, recps->recp_room, i,
2619 '|', sizeof recipient);
2620 CtdlLogPrintf(CTDL_DEBUG, "Delivering to room <%s>\n", recipient);
2621 CtdlSaveMsgPointerInRoom(recipient, newmsgid, 0, msg);
2624 /* Bump this user's messages posted counter. */
2625 CtdlLogPrintf(CTDL_DEBUG, "Updating user\n");
2626 lgetuser(&CCC->user, CCC->curr_user);
2627 CCC->user.posted = CCC->user.posted + 1;
2628 lputuser(&CCC->user);
2630 /* If this is private, local mail, make a copy in the
2631 * recipient's mailbox and bump the reference count.
2634 if (recps->num_local > 0)
2635 for (i=0; i<num_tokens(recps->recp_local, '|'); ++i) {
2636 extract_token(recipient, recps->recp_local, i,
2637 '|', sizeof recipient);
2638 CtdlLogPrintf(CTDL_DEBUG, "Delivering private local mail to <%s>\n",
2640 if (getuser(&userbuf, recipient) == 0) {
2641 // Add a flag so the Funambol module knows its mail
2642 msg->cm_fields['W'] = strdup(recipient);
2643 MailboxName(actual_rm, sizeof actual_rm, &userbuf, MAILROOM);
2644 CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 0, msg);
2645 BumpNewMailCounter(userbuf.usernum);
2646 if (!IsEmptyStr(config.c_funambol_host) || !IsEmptyStr(config.c_pager_program)) {
2647 /* Generate a instruction message for the Funambol notification
2648 * server, in the same style as the SMTP queue
2651 instr = malloc(instr_alloc);
2652 snprintf(instr, instr_alloc,
2653 "Content-type: %s\n\nmsgid|%ld\nsubmitted|%ld\n"
2655 SPOOLMIME, newmsgid, (long)time(NULL),
2656 msg->cm_fields['A'], msg->cm_fields['N']
2659 imsg = malloc(sizeof(struct CtdlMessage));
2660 memset(imsg, 0, sizeof(struct CtdlMessage));
2661 imsg->cm_magic = CTDLMESSAGE_MAGIC;
2662 imsg->cm_anon_type = MES_NORMAL;
2663 imsg->cm_format_type = FMT_RFC822;
2664 imsg->cm_fields['A'] = strdup("Citadel");
2665 imsg->cm_fields['J'] = strdup("do not journal");
2666 imsg->cm_fields['M'] = instr; /* imsg owns this memory now */
2667 imsg->cm_fields['W'] = strdup(recipient);
2668 CtdlSubmitMsg(imsg, NULL, FNBL_QUEUE_ROOM);
2669 CtdlFreeMessage(imsg);
2673 CtdlLogPrintf(CTDL_DEBUG, "No user <%s>\n", recipient);
2674 CtdlSaveMsgPointerInRoom(config.c_aideroom,
2679 /* Perform "after save" hooks */
2680 CtdlLogPrintf(CTDL_DEBUG, "Performing after-save hooks\n");
2681 PerformMessageHooks(msg, EVT_AFTERSAVE);
2683 /* For IGnet mail, we have to save a new copy into the spooler for
2684 * each recipient, with the R and D fields set to the recipient and
2685 * destination-node. This has two ugly side effects: all other
2686 * recipients end up being unlisted in this recipient's copy of the
2687 * message, and it has to deliver multiple messages to the same
2688 * node. We'll revisit this again in a year or so when everyone has
2689 * a network spool receiver that can handle the new style messages.
2692 if (recps->num_ignet > 0)
2693 for (i=0; i<num_tokens(recps->recp_ignet, '|'); ++i) {
2694 extract_token(recipient, recps->recp_ignet, i,
2695 '|', sizeof recipient);
2697 hold_R = msg->cm_fields['R'];
2698 hold_D = msg->cm_fields['D'];
2699 msg->cm_fields['R'] = malloc(SIZ);
2700 msg->cm_fields['D'] = malloc(128);
2701 extract_token(msg->cm_fields['R'], recipient, 0, '@', SIZ);
2702 extract_token(msg->cm_fields['D'], recipient, 1, '@', 128);
2704 serialize_message(&smr, msg);
2706 snprintf(submit_filename, sizeof submit_filename,
2707 "%s/netmail.%04lx.%04x.%04x",
2709 (long) getpid(), CCC->cs_pid, ++seqnum);
2710 network_fp = fopen(submit_filename, "wb+");
2711 if (network_fp != NULL) {
2712 fwrite(smr.ser, smr.len, 1, network_fp);
2718 free(msg->cm_fields['R']);
2719 free(msg->cm_fields['D']);
2720 msg->cm_fields['R'] = hold_R;
2721 msg->cm_fields['D'] = hold_D;
2724 /* Go back to the room we started from */
2725 CtdlLogPrintf(CTDL_DEBUG, "Returning to original room %s\n", hold_rm);
2726 if (strcasecmp(hold_rm, CCC->room.QRname))
2727 usergoto(hold_rm, 0, 1, NULL, NULL);
2729 /* For internet mail, generate delivery instructions.
2730 * Yes, this is recursive. Deal with it. Infinite recursion does
2731 * not happen because the delivery instructions message does not
2732 * contain a recipient.
2735 if (recps->num_internet > 0) {
2736 CtdlLogPrintf(CTDL_DEBUG, "Generating delivery instructions\n");
2738 instr = malloc(instr_alloc);
2739 snprintf(instr, instr_alloc,
2740 "Content-type: %s\n\nmsgid|%ld\nsubmitted|%ld\n"
2742 SPOOLMIME, newmsgid, (long)time(NULL),
2743 msg->cm_fields['A'], msg->cm_fields['N']
2746 for (i=0; i<num_tokens(recps->recp_internet, '|'); ++i) {
2747 size_t tmp = strlen(instr);
2748 extract_token(recipient, recps->recp_internet, i, '|', sizeof recipient);
2749 if ((tmp + strlen(recipient) + 32) > instr_alloc) {
2750 instr_alloc = instr_alloc * 2;
2751 instr = realloc(instr, instr_alloc);
2753 snprintf(&instr[tmp], instr_alloc - tmp, "remote|%s|0||\n", recipient);
2756 imsg = malloc(sizeof(struct CtdlMessage));
2757 memset(imsg, 0, sizeof(struct CtdlMessage));
2758 imsg->cm_magic = CTDLMESSAGE_MAGIC;
2759 imsg->cm_anon_type = MES_NORMAL;
2760 imsg->cm_format_type = FMT_RFC822;
2761 imsg->cm_fields['A'] = strdup("Citadel");
2762 imsg->cm_fields['J'] = strdup("do not journal");
2763 imsg->cm_fields['M'] = instr; /* imsg owns this memory now */
2764 CtdlSubmitMsg(imsg, NULL, SMTP_SPOOLOUT_ROOM);
2765 CtdlFreeMessage(imsg);
2769 * Any addresses to harvest for someone's address book?
2771 if ( (CCC->logged_in) && (recps != NULL) ) {
2772 collected_addresses = harvest_collected_addresses(msg);
2775 if (collected_addresses != NULL) {
2776 begin_critical_section(S_ATBF);
2777 aptr = (struct addresses_to_be_filed *)
2778 malloc(sizeof(struct addresses_to_be_filed));
2780 MailboxName(actual_rm, sizeof actual_rm,
2781 &CCC->user, USERCONTACTSROOM);
2782 aptr->roomname = strdup(actual_rm);
2783 aptr->collected_addresses = collected_addresses;
2785 end_critical_section(S_ATBF);
2789 * Determine whether this message qualifies for journaling.
2791 if (msg->cm_fields['J'] != NULL) {
2792 qualified_for_journaling = 0;
2795 if (recps == NULL) {
2796 qualified_for_journaling = config.c_journal_pubmsgs;
2798 else if (recps->num_local + recps->num_ignet + recps->num_internet > 0) {
2799 qualified_for_journaling = config.c_journal_email;
2802 qualified_for_journaling = config.c_journal_pubmsgs;
2807 * Do we have to perform journaling? If so, hand off the saved
2808 * RFC822 version will be handed off to the journaler for background
2809 * submit. Otherwise, we have to free the memory ourselves.
2811 if (saved_rfc822_version != NULL) {
2812 if (qualified_for_journaling) {
2813 JournalBackgroundSubmit(msg, saved_rfc822_version, recps);
2816 free(saved_rfc822_version);
2829 * Convenience function for generating small administrative messages.
2831 void quickie_message(char *from, char *fromaddr, char *to, char *room, char *text,
2832 int format_type, char *subject)
2834 struct CtdlMessage *msg;
2835 struct recptypes *recp = NULL;
2837 msg = malloc(sizeof(struct CtdlMessage));
2838 memset(msg, 0, sizeof(struct CtdlMessage));
2839 msg->cm_magic = CTDLMESSAGE_MAGIC;
2840 msg->cm_anon_type = MES_NORMAL;
2841 msg->cm_format_type = format_type;
2844 msg->cm_fields['A'] = strdup(from);
2846 else if (fromaddr != NULL) {
2847 msg->cm_fields['A'] = strdup(fromaddr);
2848 if (strchr(msg->cm_fields['A'], '@')) {
2849 *strchr(msg->cm_fields['A'], '@') = 0;
2853 msg->cm_fields['A'] = strdup("Citadel");
2856 if (fromaddr != NULL) msg->cm_fields['F'] = strdup(fromaddr);
2857 if (room != NULL) msg->cm_fields['O'] = strdup(room);
2858 msg->cm_fields['N'] = strdup(NODENAME);
2860 msg->cm_fields['R'] = strdup(to);
2861 recp = validate_recipients(to, NULL, 0);
2863 if (subject != NULL) {
2864 msg->cm_fields['U'] = strdup(subject);
2866 msg->cm_fields['M'] = strdup(text);
2868 CtdlSubmitMsg(msg, recp, room);
2869 CtdlFreeMessage(msg);
2870 if (recp != NULL) free_recipients(recp);
2876 * Back end function used by CtdlMakeMessage() and similar functions
2878 char *CtdlReadMessageBody(char *terminator, /* token signalling EOT */
2879 size_t maxlen, /* maximum message length */
2880 char *exist, /* if non-null, append to it;
2881 exist is ALWAYS freed */
2882 int crlf, /* CRLF newlines instead of LF */
2883 int sock /* socket handle or 0 for this session's client socket */
2887 size_t message_len = 0;
2888 size_t buffer_len = 0;
2895 if (exist == NULL) {
2902 message_len = strlen(exist);
2903 buffer_len = message_len + 4096;
2904 m = realloc(exist, buffer_len);
2911 /* Do we need to change leading ".." to "." for SMTP escaping? */
2912 if (!strcmp(terminator, ".")) {
2916 /* flush the input if we have nowhere to store it */
2921 /* read in the lines of message text one by one */
2924 if (sock_getln(sock, buf, (sizeof buf - 3)) < 0) finished = 1;
2927 if (client_getln(buf, (sizeof buf - 3)) < 1) finished = 1;
2929 if (!strcmp(buf, terminator)) finished = 1;
2931 strcat(buf, "\r\n");
2937 /* Unescape SMTP-style input of two dots at the beginning of the line */
2939 if (!strncmp(buf, "..", 2)) {
2940 strcpy(buf, &buf[1]);
2944 if ( (!flushing) && (!finished) ) {
2945 /* Measure the line */
2946 linelen = strlen(buf);
2948 /* augment the buffer if we have to */
2949 if ((message_len + linelen) >= buffer_len) {
2950 ptr = realloc(m, (buffer_len * 2) );
2951 if (ptr == NULL) { /* flush if can't allocate */
2954 buffer_len = (buffer_len * 2);
2956 CtdlLogPrintf(CTDL_DEBUG, "buffer_len is now %ld\n", (long)buffer_len);
2960 /* Add the new line to the buffer. NOTE: this loop must avoid
2961 * using functions like strcat() and strlen() because they
2962 * traverse the entire buffer upon every call, and doing that
2963 * for a multi-megabyte message slows it down beyond usability.
2965 strcpy(&m[message_len], buf);
2966 message_len += linelen;
2969 /* if we've hit the max msg length, flush the rest */
2970 if (message_len >= maxlen) flushing = 1;
2972 } while (!finished);
2980 * Build a binary message to be saved on disk.
2981 * (NOTE: if you supply 'preformatted_text', the buffer you give it
2982 * will become part of the message. This means you are no longer
2983 * responsible for managing that memory -- it will be freed along with
2984 * the rest of the fields when CtdlFreeMessage() is called.)
2987 struct CtdlMessage *CtdlMakeMessage(
2988 struct ctdluser *author, /* author's user structure */
2989 char *recipient, /* NULL if it's not mail */
2990 char *recp_cc, /* NULL if it's not mail */
2991 char *room, /* room where it's going */
2992 int type, /* see MES_ types in header file */
2993 int format_type, /* variformat, plain text, MIME... */
2994 char *fake_name, /* who we're masquerading as */
2995 char *my_email, /* which of my email addresses to use (empty is ok) */
2996 char *subject, /* Subject (optional) */
2997 char *supplied_euid, /* ...or NULL if this is irrelevant */
2998 char *preformatted_text /* ...or NULL to read text from client */
3000 char dest_node[256];
3002 struct CtdlMessage *msg;
3004 msg = malloc(sizeof(struct CtdlMessage));
3005 memset(msg, 0, sizeof(struct CtdlMessage));
3006 msg->cm_magic = CTDLMESSAGE_MAGIC;
3007 msg->cm_anon_type = type;
3008 msg->cm_format_type = format_type;
3010 /* Don't confuse the poor folks if it's not routed mail. */
3011 strcpy(dest_node, "");
3016 /* Path or Return-Path */
3017 if (my_email == NULL) my_email = "";
3019 if (!IsEmptyStr(my_email)) {
3020 msg->cm_fields['P'] = strdup(my_email);
3023 snprintf(buf, sizeof buf, "%s", author->fullname);
3024 msg->cm_fields['P'] = strdup(buf);
3026 convert_spaces_to_underscores(msg->cm_fields['P']);
3028 snprintf(buf, sizeof buf, "%ld", (long)time(NULL)); /* timestamp */
3029 msg->cm_fields['T'] = strdup(buf);
3031 if (fake_name[0]) /* author */
3032 msg->cm_fields['A'] = strdup(fake_name);
3034 msg->cm_fields['A'] = strdup(author->fullname);
3036 if (CC->room.QRflags & QR_MAILBOX) { /* room */
3037 msg->cm_fields['O'] = strdup(&CC->room.QRname[11]);
3040 msg->cm_fields['O'] = strdup(CC->room.QRname);
3043 msg->cm_fields['N'] = strdup(NODENAME); /* nodename */
3044 msg->cm_fields['H'] = strdup(HUMANNODE); /* hnodename */
3046 if (recipient[0] != 0) {
3047 msg->cm_fields['R'] = strdup(recipient);
3049 if (recp_cc[0] != 0) {
3050 msg->cm_fields['Y'] = strdup(recp_cc);
3052 if (dest_node[0] != 0) {
3053 msg->cm_fields['D'] = strdup(dest_node);
3056 if (!IsEmptyStr(my_email)) {
3057 msg->cm_fields['F'] = strdup(my_email);
3059 else if ( (author == &CC->user) && (!IsEmptyStr(CC->cs_inet_email)) ) {
3060 msg->cm_fields['F'] = strdup(CC->cs_inet_email);
3063 if (subject != NULL) {
3066 length = strlen(subject);
3072 while ((subject[i] != '\0') &&
3073 (IsAscii = isascii(subject[i]) != 0 ))
3076 msg->cm_fields['U'] = strdup(subject);
3077 else /* ok, we've got utf8 in the string. */
3079 msg->cm_fields['U'] = rfc2047encode(subject, length);
3085 if (supplied_euid != NULL) {
3086 msg->cm_fields['E'] = strdup(supplied_euid);
3089 if (preformatted_text != NULL) {
3090 msg->cm_fields['M'] = preformatted_text;
3093 msg->cm_fields['M'] = CtdlReadMessageBody("000", config.c_maxmsglen, NULL, 0, 0);
3101 * Check to see whether we have permission to post a message in the current
3102 * room. Returns a *CITADEL ERROR CODE* and puts a message in errmsgbuf, or
3103 * returns 0 on success.
3105 int CtdlDoIHavePermissionToPostInThisRoom(char *errmsgbuf,
3107 const char* RemoteIdentifier,
3111 if (!(CC->logged_in) &&
3112 (PostPublic == POST_LOGGED_IN)) {
3113 snprintf(errmsgbuf, n, "Not logged in.");
3114 return (ERROR + NOT_LOGGED_IN);
3116 else if (PostPublic == CHECK_EXISTANCE) {
3117 return (0); // We're Evaling whether a recipient exists
3119 else if (!(CC->logged_in)) {
3121 if ((CC->room.QRflags & QR_READONLY)) {
3122 snprintf(errmsgbuf, n, "Not logged in.");
3123 return (ERROR + NOT_LOGGED_IN);
3125 if (CC->room.QRflags2 & QR2_MODERATED) {
3126 snprintf(errmsgbuf, n, "Not logged in Moderation feature not yet implemented!");
3127 return (ERROR + NOT_LOGGED_IN);
3129 if ((PostPublic!=POST_LMTP) &&(CC->room.QRflags2 & QR2_SMTP_PUBLIC) == 0) {
3134 if (RemoteIdentifier == NULL)
3136 snprintf(errmsgbuf, n, "Need sender to permit access.");
3137 return (ERROR + USERNAME_REQUIRED);
3140 assoc_file_name(filename, sizeof filename, &CC->room, ctdl_netcfg_dir);
3141 begin_critical_section(S_NETCONFIGS);
3142 if (!read_spoolcontrol_file(&sc, filename))
3144 end_critical_section(S_NETCONFIGS);
3145 snprintf(errmsgbuf, n,
3146 "This mailing list only accepts posts from subscribers.");
3147 return (ERROR + NO_SUCH_USER);
3149 end_critical_section(S_NETCONFIGS);
3150 found = is_recipient (sc, RemoteIdentifier);
3151 free_spoolcontrol_struct(&sc);
3156 snprintf(errmsgbuf, n,
3157 "This mailing list only accepts posts from subscribers.");
3158 return (ERROR + NO_SUCH_USER);
3165 if ((CC->user.axlevel < 2)
3166 && ((CC->room.QRflags & QR_MAILBOX) == 0)) {
3167 snprintf(errmsgbuf, n, "Need to be validated to enter "
3168 "(except in %s> to sysop)", MAILROOM);
3169 return (ERROR + HIGHER_ACCESS_REQUIRED);
3172 CtdlRoomAccess(&CC->room, &CC->user, &ra, NULL);
3173 if (!(ra & UA_POSTALLOWED)) {
3174 snprintf(errmsgbuf, n, "Higher access is required to post in this room.");
3175 return (ERROR + HIGHER_ACCESS_REQUIRED);
3178 strcpy(errmsgbuf, "Ok");
3184 * Check to see if the specified user has Internet mail permission
3185 * (returns nonzero if permission is granted)
3187 int CtdlCheckInternetMailPermission(struct ctdluser *who) {
3189 /* Do not allow twits to send Internet mail */
3190 if (who->axlevel <= 2) return(0);
3192 /* Globally enabled? */
3193 if (config.c_restrict == 0) return(1);
3195 /* User flagged ok? */
3196 if (who->flags & US_INTERNET) return(2);
3198 /* Aide level access? */
3199 if (who->axlevel >= 6) return(3);
3201 /* No mail for you! */
3207 * Validate recipients, count delivery types and errors, and handle aliasing
3208 * FIXME check for dupes!!!!!
3210 * Returns 0 if all addresses are ok, ret->num_error = -1 if no addresses
3211 * were specified, or the number of addresses found invalid.
3213 * Caller needs to free the result using free_recipients()
3215 struct recptypes *validate_recipients(char *supplied_recipients,
3216 const char *RemoteIdentifier,
3218 struct recptypes *ret;
3219 char *recipients = NULL;
3220 char this_recp[256];
3221 char this_recp_cooked[256];
3227 struct ctdluser tempUS;
3228 struct ctdlroom tempQR;
3229 struct ctdlroom tempQR2;
3235 ret = (struct recptypes *) malloc(sizeof(struct recptypes));
3236 if (ret == NULL) return(NULL);
3238 /* Set all strings to null and numeric values to zero */
3239 memset(ret, 0, sizeof(struct recptypes));
3241 if (supplied_recipients == NULL) {
3242 recipients = strdup("");
3245 recipients = strdup(supplied_recipients);
3248 /* Allocate some memory. Yes, this allocates 500% more memory than we will
3249 * actually need, but it's healthier for the heap than doing lots of tiny
3250 * realloc() calls instead.
3253 ret->errormsg = malloc(strlen(recipients) + 1024);
3254 ret->recp_local = malloc(strlen(recipients) + 1024);
3255 ret->recp_internet = malloc(strlen(recipients) + 1024);
3256 ret->recp_ignet = malloc(strlen(recipients) + 1024);
3257 ret->recp_room = malloc(strlen(recipients) + 1024);
3258 ret->display_recp = malloc(strlen(recipients) + 1024);
3260 ret->errormsg[0] = 0;
3261 ret->recp_local[0] = 0;
3262 ret->recp_internet[0] = 0;
3263 ret->recp_ignet[0] = 0;
3264 ret->recp_room[0] = 0;
3265 ret->display_recp[0] = 0;
3267 ret->recptypes_magic = RECPTYPES_MAGIC;
3269 /* Change all valid separator characters to commas */
3270 for (i=0; !IsEmptyStr(&recipients[i]); ++i) {
3271 if ((recipients[i] == ';') || (recipients[i] == '|')) {
3272 recipients[i] = ',';
3276 /* Now start extracting recipients... */
3278 while (!IsEmptyStr(recipients)) {
3280 for (i=0; i<=strlen(recipients); ++i) {
3281 if (recipients[i] == '\"') in_quotes = 1 - in_quotes;
3282 if ( ( (recipients[i] == ',') && (!in_quotes) ) || (recipients[i] == 0) ) {
3283 safestrncpy(this_recp, recipients, i+1);
3285 if (recipients[i] == ',') {
3286 strcpy(recipients, &recipients[i+1]);
3289 strcpy(recipients, "");
3296 if (IsEmptyStr(this_recp))
3298 CtdlLogPrintf(CTDL_DEBUG, "Evaluating recipient #%d: %s\n", num_recps, this_recp);
3300 mailtype = alias(this_recp);
3301 mailtype = alias(this_recp);
3302 mailtype = alias(this_recp);
3304 for (j=0; !IsEmptyStr(&this_recp[j]); ++j) {
3305 if (this_recp[j]=='_') {
3306 this_recp_cooked[j] = ' ';
3309 this_recp_cooked[j] = this_recp[j];
3312 this_recp_cooked[j] = '\0';
3317 if (!strcasecmp(this_recp, "sysop")) {
3319 strcpy(this_recp, config.c_aideroom);
3320 if (!IsEmptyStr(ret->recp_room)) {
3321 strcat(ret->recp_room, "|");
3323 strcat(ret->recp_room, this_recp);
3325 else if ( (!strncasecmp(this_recp, "room_", 5))
3326 && (!getroom(&tempQR, &this_recp_cooked[5])) ) {
3328 /* Save room so we can restore it later */
3332 /* Check permissions to send mail to this room */
3333 err = CtdlDoIHavePermissionToPostInThisRoom(errmsg,
3345 if (!IsEmptyStr(ret->recp_room)) {
3346 strcat(ret->recp_room, "|");
3348 strcat(ret->recp_room, &this_recp_cooked[5]);
3351 /* Restore room in case something needs it */
3355 else if (getuser(&tempUS, this_recp) == 0) {
3357 strcpy(this_recp, tempUS.fullname);
3358 if (!IsEmptyStr(ret->recp_local)) {
3359 strcat(ret->recp_local, "|");
3361 strcat(ret->recp_local, this_recp);
3363 else if (getuser(&tempUS, this_recp_cooked) == 0) {
3365 strcpy(this_recp, tempUS.fullname);
3366 if (!IsEmptyStr(ret->recp_local)) {
3367 strcat(ret->recp_local, "|");
3369 strcat(ret->recp_local, this_recp);
3377 /* Yes, you're reading this correctly: if the target
3378 * domain points back to the local system or an attached
3379 * Citadel directory, the address is invalid. That's
3380 * because if the address were valid, we would have
3381 * already translated it to a local address by now.
3383 if (IsDirectory(this_recp, 0)) {
3388 ++ret->num_internet;
3389 if (!IsEmptyStr(ret->recp_internet)) {
3390 strcat(ret->recp_internet, "|");
3392 strcat(ret->recp_internet, this_recp);
3397 if (!IsEmptyStr(ret->recp_ignet)) {
3398 strcat(ret->recp_ignet, "|");
3400 strcat(ret->recp_ignet, this_recp);
3408 if (IsEmptyStr(errmsg)) {
3409 snprintf(append, sizeof append, "Invalid recipient: %s", this_recp);
3412 snprintf(append, sizeof append, "%s", errmsg);
3414 if ( (strlen(ret->errormsg) + strlen(append) + 3) < SIZ) {
3415 if (!IsEmptyStr(ret->errormsg)) {
3416 strcat(ret->errormsg, "; ");
3418 strcat(ret->errormsg, append);
3422 if (IsEmptyStr(ret->display_recp)) {
3423 strcpy(append, this_recp);
3426 snprintf(append, sizeof append, ", %s", this_recp);
3428 if ( (strlen(ret->display_recp)+strlen(append)) < SIZ) {
3429 strcat(ret->display_recp, append);
3434 if ((ret->num_local + ret->num_internet + ret->num_ignet +
3435 ret->num_room + ret->num_error) == 0) {
3436 ret->num_error = (-1);
3437 strcpy(ret->errormsg, "No recipients specified.");
3440 CtdlLogPrintf(CTDL_DEBUG, "validate_recipients()\n");
3441 CtdlLogPrintf(CTDL_DEBUG, " local: %d <%s>\n", ret->num_local, ret->recp_local);
3442 CtdlLogPrintf(CTDL_DEBUG, " room: %d <%s>\n", ret->num_room, ret->recp_room);
3443 CtdlLogPrintf(CTDL_DEBUG, " inet: %d <%s>\n", ret->num_internet, ret->recp_internet);
3444 CtdlLogPrintf(CTDL_DEBUG, " ignet: %d <%s>\n", ret->num_ignet, ret->recp_ignet);
3445 CtdlLogPrintf(CTDL_DEBUG, " error: %d <%s>\n", ret->num_error, ret->errormsg);
3453 * Destructor for struct recptypes
3455 void free_recipients(struct recptypes *valid) {
3457 if (valid == NULL) {
3461 if (valid->recptypes_magic != RECPTYPES_MAGIC) {
3462 CtdlLogPrintf(CTDL_EMERG, "Attempt to call free_recipients() on some other data type!\n");
3466 if (valid->errormsg != NULL) free(valid->errormsg);
3467 if (valid->recp_local != NULL) free(valid->recp_local);
3468 if (valid->recp_internet != NULL) free(valid->recp_internet);
3469 if (valid->recp_ignet != NULL) free(valid->recp_ignet);
3470 if (valid->recp_room != NULL) free(valid->recp_room);
3471 if (valid->display_recp != NULL) free(valid->display_recp);
3478 * message entry - mode 0 (normal)
3480 void cmd_ent0(char *entargs)
3486 char supplied_euid[128];
3488 int format_type = 0;
3489 char newusername[256];
3490 char newuseremail[256];
3491 struct CtdlMessage *msg;
3495 struct recptypes *valid = NULL;
3496 struct recptypes *valid_to = NULL;
3497 struct recptypes *valid_cc = NULL;
3498 struct recptypes *valid_bcc = NULL;
3500 int subject_required = 0;
3505 int newuseremail_ok = 0;
3509 post = extract_int(entargs, 0);
3510 extract_token(recp, entargs, 1, '|', sizeof recp);
3511 anon_flag = extract_int(entargs, 2);
3512 format_type = extract_int(entargs, 3);
3513 extract_token(subject, entargs, 4, '|', sizeof subject);
3514 extract_token(newusername, entargs, 5, '|', sizeof newusername);
3515 do_confirm = extract_int(entargs, 6);
3516 extract_token(cc, entargs, 7, '|', sizeof cc);
3517 extract_token(bcc, entargs, 8, '|', sizeof bcc);
3518 switch(CC->room.QRdefaultview) {
3521 extract_token(supplied_euid, entargs, 9, '|', sizeof supplied_euid);
3524 supplied_euid[0] = 0;
3527 extract_token(newuseremail, entargs, 10, '|', sizeof newuseremail);
3529 /* first check to make sure the request is valid. */
3531 err = CtdlDoIHavePermissionToPostInThisRoom(errmsg, sizeof errmsg, NULL, POST_LOGGED_IN);
3534 cprintf("%d %s\n", err, errmsg);
3538 /* Check some other permission type things. */
3540 if (IsEmptyStr(newusername)) {
3541 strcpy(newusername, CC->user.fullname);
3543 if ( (CC->user.axlevel < 6)
3544 && (strcasecmp(newusername, CC->user.fullname))
3545 && (strcasecmp(newusername, CC->cs_inet_fn))
3547 cprintf("%d You don't have permission to author messages as '%s'.\n",
3548 ERROR + HIGHER_ACCESS_REQUIRED,
3555 if (IsEmptyStr(newuseremail)) {
3556 newuseremail_ok = 1;
3559 if (!IsEmptyStr(newuseremail)) {
3560 if (!strcasecmp(newuseremail, CC->cs_inet_email)) {
3561 newuseremail_ok = 1;
3563 else if (!IsEmptyStr(CC->cs_inet_other_emails)) {
3564 j = num_tokens(CC->cs_inet_other_emails, '|');
3565 for (i=0; i<j; ++i) {
3566 extract_token(buf, CC->cs_inet_other_emails, i, '|', sizeof buf);
3567 if (!strcasecmp(newuseremail, buf)) {
3568 newuseremail_ok = 1;
3574 if (!newuseremail_ok) {
3575 cprintf("%d You don't have permission to author messages as '%s'.\n",
3576 ERROR + HIGHER_ACCESS_REQUIRED,
3582 CC->cs_flags |= CS_POSTING;
3584 /* In mailbox rooms we have to behave a little differently --
3585 * make sure the user has specified at least one recipient. Then
3586 * validate the recipient(s). We do this for the Mail> room, as
3587 * well as any room which has the "Mailbox" view set.
3590 if ( ( (CC->room.QRflags & QR_MAILBOX) && (!strcasecmp(&CC->room.QRname[11], MAILROOM)) )
3591 || ( (CC->room.QRflags & QR_MAILBOX) && (CC->curr_view == VIEW_MAILBOX) )
3593 if (CC->user.axlevel < 2) {
3594 strcpy(recp, "sysop");
3599 valid_to = validate_recipients(recp, NULL, 0);
3600 if (valid_to->num_error > 0) {
3601 cprintf("%d %s\n", ERROR + NO_SUCH_USER, valid_to->errormsg);
3602 free_recipients(valid_to);
3606 valid_cc = validate_recipients(cc, NULL, 0);
3607 if (valid_cc->num_error > 0) {
3608 cprintf("%d %s\n", ERROR + NO_SUCH_USER, valid_cc->errormsg);
3609 free_recipients(valid_to);
3610 free_recipients(valid_cc);
3614 valid_bcc = validate_recipients(bcc, NULL, 0);
3615 if (valid_bcc->num_error > 0) {
3616 cprintf("%d %s\n", ERROR + NO_SUCH_USER, valid_bcc->errormsg);
3617 free_recipients(valid_to);
3618 free_recipients(valid_cc);
3619 free_recipients(valid_bcc);
3623 /* Recipient required, but none were specified */
3624 if ( (valid_to->num_error < 0) && (valid_cc->num_error < 0) && (valid_bcc->num_error < 0) ) {
3625 free_recipients(valid_to);
3626 free_recipients(valid_cc);
3627 free_recipients(valid_bcc);
3628 cprintf("%d At least one recipient is required.\n", ERROR + NO_SUCH_USER);
3632 if (valid_to->num_internet + valid_cc->num_internet + valid_bcc->num_internet > 0) {
3633 if (CtdlCheckInternetMailPermission(&CC->user)==0) {
3634 cprintf("%d You do not have permission "
3635 "to send Internet mail.\n",
3636 ERROR + HIGHER_ACCESS_REQUIRED);
3637 free_recipients(valid_to);
3638 free_recipients(valid_cc);
3639 free_recipients(valid_bcc);
3644 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)
3645 && (CC->user.axlevel < 4) ) {
3646 cprintf("%d Higher access required for network mail.\n",
3647 ERROR + HIGHER_ACCESS_REQUIRED);
3648 free_recipients(valid_to);
3649 free_recipients(valid_cc);
3650 free_recipients(valid_bcc);
3654 if ((RESTRICT_INTERNET == 1)
3655 && (valid_to->num_internet + valid_cc->num_internet + valid_bcc->num_internet > 0)
3656 && ((CC->user.flags & US_INTERNET) == 0)
3657 && (!CC->internal_pgm)) {
3658 cprintf("%d You don't have access to Internet mail.\n",
3659 ERROR + HIGHER_ACCESS_REQUIRED);
3660 free_recipients(valid_to);
3661 free_recipients(valid_cc);
3662 free_recipients(valid_bcc);
3668 /* Is this a room which has anonymous-only or anonymous-option? */
3669 anonymous = MES_NORMAL;
3670 if (CC->room.QRflags & QR_ANONONLY) {
3671 anonymous = MES_ANONONLY;
3673 if (CC->room.QRflags & QR_ANONOPT) {
3674 if (anon_flag == 1) { /* only if the user requested it */
3675 anonymous = MES_ANONOPT;
3679 if ((CC->room.QRflags & QR_MAILBOX) == 0) {
3683 /* Recommend to the client that the use of a message subject is
3684 * strongly recommended in this room, if either the SUBJECTREQ flag
3685 * is set, or if there is one or more Internet email recipients.
3687 if (CC->room.QRflags2 & QR2_SUBJECTREQ) subject_required = 1;
3688 if (valid_to) if (valid_to->num_internet > 0) subject_required = 1;
3689 if (valid_cc) if (valid_cc->num_internet > 0) subject_required = 1;
3690 if (valid_bcc) if (valid_bcc->num_internet > 0) subject_required = 1;
3692 /* If we're only checking the validity of the request, return
3693 * success without creating the message.
3696 cprintf("%d %s|%d\n", CIT_OK,
3697 ((valid_to != NULL) ? valid_to->display_recp : ""),
3699 free_recipients(valid_to);
3700 free_recipients(valid_cc);
3701 free_recipients(valid_bcc);
3705 /* We don't need these anymore because we'll do it differently below */
3706 free_recipients(valid_to);
3707 free_recipients(valid_cc);
3708 free_recipients(valid_bcc);
3710 /* Read in the message from the client. */
3712 cprintf("%d send message\n", START_CHAT_MODE);
3714 cprintf("%d send message\n", SEND_LISTING);
3717 msg = CtdlMakeMessage(&CC->user, recp, cc,
3718 CC->room.QRname, anonymous, format_type,
3719 newusername, newuseremail, subject,
3720 ((!IsEmptyStr(supplied_euid)) ? supplied_euid : NULL),
3723 /* Put together one big recipients struct containing to/cc/bcc all in
3724 * one. This is for the envelope.
3726 char *all_recps = malloc(SIZ * 3);
3727 strcpy(all_recps, recp);
3728 if (!IsEmptyStr(cc)) {
3729 if (!IsEmptyStr(all_recps)) {
3730 strcat(all_recps, ",");
3732 strcat(all_recps, cc);
3734 if (!IsEmptyStr(bcc)) {
3735 if (!IsEmptyStr(all_recps)) {
3736 strcat(all_recps, ",");
3738 strcat(all_recps, bcc);
3740 if (!IsEmptyStr(all_recps)) {
3741 valid = validate_recipients(all_recps, NULL, 0);
3749 msgnum = CtdlSubmitMsg(msg, valid, "");
3752 cprintf("%ld\n", msgnum);
3754 cprintf("Message accepted.\n");
3757 cprintf("Internal error.\n");
3759 if (msg->cm_fields['E'] != NULL) {
3760 cprintf("%s\n", msg->cm_fields['E']);
3767 CtdlFreeMessage(msg);
3769 if (valid != NULL) {
3770 free_recipients(valid);
3778 * API function to delete messages which match a set of criteria
3779 * (returns the actual number of messages deleted)
3781 int CtdlDeleteMessages(char *room_name, /* which room */
3782 long *dmsgnums, /* array of msg numbers to be deleted */
3783 int num_dmsgnums, /* number of msgs to be deleted, or 0 for "any" */
3784 char *content_type /* or "" for any. regular expressions expected. */
3787 struct ctdlroom qrbuf;
3788 struct cdbdata *cdbfr;
3789 long *msglist = NULL;
3790 long *dellist = NULL;
3793 int num_deleted = 0;
3795 struct MetaData smi;
3798 int need_to_free_re = 0;
3800 if (content_type) if (!IsEmptyStr(content_type)) {
3801 regcomp(&re, content_type, 0);
3802 need_to_free_re = 1;
3804 CtdlLogPrintf(CTDL_DEBUG, "CtdlDeleteMessages(%s, %d msgs, %s)\n",
3805 room_name, num_dmsgnums, content_type);
3807 /* get room record, obtaining a lock... */
3808 if (lgetroom(&qrbuf, room_name) != 0) {
3809 CtdlLogPrintf(CTDL_ERR, "CtdlDeleteMessages(): Room <%s> not found\n",
3811 if (need_to_free_re) regfree(&re);
3812 return (0); /* room not found */
3814 cdbfr = cdb_fetch(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long));
3816 if (cdbfr != NULL) {
3817 dellist = malloc(cdbfr->len);
3818 msglist = (long *) cdbfr->ptr;
3819 cdbfr->ptr = NULL; /* CtdlDeleteMessages() now owns this memory */
3820 num_msgs = cdbfr->len / sizeof(long);
3824 for (i = 0; i < num_msgs; ++i) {
3827 /* Set/clear a bit for each criterion */
3829 /* 0 messages in the list or a null list means that we are
3830 * interested in deleting any messages which meet the other criteria.
3832 if ((num_dmsgnums == 0) || (dmsgnums == NULL)) {
3833 delete_this |= 0x01;
3836 for (j=0; j<num_dmsgnums; ++j) {
3837 if (msglist[i] == dmsgnums[j]) {
3838 delete_this |= 0x01;
3843 if (IsEmptyStr(content_type)) {
3844 delete_this |= 0x02;
3846 GetMetaData(&smi, msglist[i]);
3847 if (regexec(&re, smi.meta_content_type, 1, &pm, 0) == 0) {
3848 delete_this |= 0x02;
3852 /* Delete message only if all bits are set */
3853 if (delete_this == 0x03) {
3854 dellist[num_deleted++] = msglist[i];
3859 num_msgs = sort_msglist(msglist, num_msgs);
3860 cdb_store(CDB_MSGLISTS, &qrbuf.QRnumber, (int)sizeof(long),
3861 msglist, (int)(num_msgs * sizeof(long)));
3863 qrbuf.QRhighest = msglist[num_msgs - 1];
3867 /* Go through the messages we pulled out of the index, and decrement
3868 * their reference counts by 1. If this is the only room the message
3869 * was in, the reference count will reach zero and the message will
3870 * automatically be deleted from the database. We do this in a
3871 * separate pass because there might be plug-in hooks getting called,
3872 * and we don't want that happening during an S_ROOMS critical
3875 if (num_deleted) for (i=0; i<num_deleted; ++i) {
3876 PerformDeleteHooks(qrbuf.QRname, dellist[i]);
3877 AdjRefCount(dellist[i], -1);
3880 /* Now free the memory we used, and go away. */
3881 if (msglist != NULL) free(msglist);
3882 if (dellist != NULL) free(dellist);
3883 CtdlLogPrintf(CTDL_DEBUG, "%d message(s) deleted.\n", num_deleted);
3884 if (need_to_free_re) regfree(&re);
3885 return (num_deleted);
3891 * Check whether the current user has permission to delete messages from
3892 * the current room (returns 1 for yes, 0 for no)
3894 int CtdlDoIHavePermissionToDeleteMessagesFromThisRoom(void) {
3896 CtdlRoomAccess(&CC->room, &CC->user, &ra, NULL);
3897 if (ra & UA_DELETEALLOWED) return(1);
3905 * Delete message from current room
3907 void cmd_dele(char *args)
3916 extract_token(msgset, args, 0, '|', sizeof msgset);
3917 num_msgs = num_tokens(msgset, ',');
3919 cprintf("%d Nothing to do.\n", CIT_OK);
3923 if (CtdlDoIHavePermissionToDeleteMessagesFromThisRoom() == 0) {
3924 cprintf("%d Higher access required.\n",
3925 ERROR + HIGHER_ACCESS_REQUIRED);
3930 * Build our message set to be moved/copied
3932 msgs = malloc(num_msgs * sizeof(long));
3933 for (i=0; i<num_msgs; ++i) {
3934 extract_token(msgtok, msgset, i, ',', sizeof msgtok);
3935 msgs[i] = atol(msgtok);
3938 num_deleted = CtdlDeleteMessages(CC->room.QRname, msgs, num_msgs, "");
3942 cprintf("%d %d message%s deleted.\n", CIT_OK,
3943 num_deleted, ((num_deleted != 1) ? "s" : ""));
3945 cprintf("%d Message not found.\n", ERROR + MESSAGE_NOT_FOUND);
3951 * Back end API function for moves and deletes (multiple messages)
3953 int CtdlCopyMsgsToRoom(long *msgnums, int num_msgs, char *dest) {
3956 err = CtdlSaveMsgPointersInRoom(dest, msgnums, num_msgs, 1, NULL);
3957 if (err != 0) return(err);
3966 * move or copy a message to another room
3968 void cmd_move(char *args)
3975 char targ[ROOMNAMELEN];
3976 struct ctdlroom qtemp;
3983 extract_token(msgset, args, 0, '|', sizeof msgset);
3984 num_msgs = num_tokens(msgset, ',');
3986 cprintf("%d Nothing to do.\n", CIT_OK);
3990 extract_token(targ, args, 1, '|', sizeof targ);
3991 convert_room_name_macros(targ, sizeof targ);
3992 targ[ROOMNAMELEN - 1] = 0;
3993 is_copy = extract_int(args, 2);
3995 if (getroom(&qtemp, targ) != 0) {
3996 cprintf("%d '%s' does not exist.\n",
3997 ERROR + ROOM_NOT_FOUND, targ);
4001 getuser(&CC->user, CC->curr_user);
4002 CtdlRoomAccess(&qtemp, &CC->user, &ra, NULL);
4004 /* Check for permission to perform this operation.
4005 * Remember: "CC->room" is source, "qtemp" is target.
4009 /* Aides can move/copy */
4010 if (CC->user.axlevel >= 6) permit = 1;
4012 /* Room aides can move/copy */
4013 if (CC->user.usernum == CC->room.QRroomaide) permit = 1;
4015 /* Permit move/copy from personal rooms */
4016 if ((CC->room.QRflags & QR_MAILBOX)
4017 && (qtemp.QRflags & QR_MAILBOX)) permit = 1;
4019 /* Permit only copy from public to personal room */
4021 && (!(CC->room.QRflags & QR_MAILBOX))
4022 && (qtemp.QRflags & QR_MAILBOX)) permit = 1;
4024 /* Permit message removal from collaborative delete rooms */
4025 if (CC->room.QRflags2 & QR2_COLLABDEL) permit = 1;
4027 /* Users allowed to post into the target room may move into it too. */
4028 if ((CC->room.QRflags & QR_MAILBOX) &&
4029 (qtemp.QRflags & UA_POSTALLOWED)) permit = 1;
4031 /* User must have access to target room */
4032 if (!(ra & UA_KNOWN)) permit = 0;
4035 cprintf("%d Higher access required.\n",
4036 ERROR + HIGHER_ACCESS_REQUIRED);
4041 * Build our message set to be moved/copied
4043 msgs = malloc(num_msgs * sizeof(long));
4044 for (i=0; i<num_msgs; ++i) {
4045 extract_token(msgtok, msgset, i, ',', sizeof msgtok);
4046 msgs[i] = atol(msgtok);
4052 err = CtdlCopyMsgsToRoom(msgs, num_msgs, targ);
4054 cprintf("%d Cannot store message(s) in %s: error %d\n",
4060 /* Now delete the message from the source room,
4061 * if this is a 'move' rather than a 'copy' operation.
4064 CtdlDeleteMessages(CC->room.QRname, msgs, num_msgs, "");
4068 cprintf("%d Message(s) %s.\n", CIT_OK, (is_copy ? "copied" : "moved") );
4074 * GetMetaData() - Get the supplementary record for a message
4076 void GetMetaData(struct MetaData *smibuf, long msgnum)
4079 struct cdbdata *cdbsmi;
4082 memset(smibuf, 0, sizeof(struct MetaData));
4083 smibuf->meta_msgnum = msgnum;
4084 smibuf->meta_refcount = 1; /* Default reference count is 1 */
4086 /* Use the negative of the message number for its supp record index */
4087 TheIndex = (0L - msgnum);
4089 cdbsmi = cdb_fetch(CDB_MSGMAIN, &TheIndex, sizeof(long));
4090 if (cdbsmi == NULL) {
4091 return; /* record not found; go with defaults */
4093 memcpy(smibuf, cdbsmi->ptr,
4094 ((cdbsmi->len > sizeof(struct MetaData)) ?
4095 sizeof(struct MetaData) : cdbsmi->len));
4102 * PutMetaData() - (re)write supplementary record for a message
4104 void PutMetaData(struct MetaData *smibuf)
4108 /* Use the negative of the message number for the metadata db index */
4109 TheIndex = (0L - smibuf->meta_msgnum);
4111 cdb_store(CDB_MSGMAIN,
4112 &TheIndex, (int)sizeof(long),
4113 smibuf, (int)sizeof(struct MetaData));
4118 * AdjRefCount - submit an adjustment to the reference count for a message.
4119 * (These are just queued -- we actually process them later.)
4121 void AdjRefCount(long msgnum, int incr)
4123 struct arcq new_arcq;
4125 begin_critical_section(S_SUPPMSGMAIN);
4126 if (arcfp == NULL) {
4127 arcfp = fopen(file_arcq, "ab+");
4129 end_critical_section(S_SUPPMSGMAIN);
4131 /* msgnum < 0 means that we're trying to close the file */
4133 CtdlLogPrintf(CTDL_DEBUG, "Closing the AdjRefCount queue file\n");
4134 begin_critical_section(S_SUPPMSGMAIN);
4135 if (arcfp != NULL) {
4139 end_critical_section(S_SUPPMSGMAIN);
4144 * If we can't open the queue, perform the operation synchronously.
4146 if (arcfp == NULL) {
4147 TDAP_AdjRefCount(msgnum, incr);
4151 new_arcq.arcq_msgnum = msgnum;
4152 new_arcq.arcq_delta = incr;
4153 fwrite(&new_arcq, sizeof(struct arcq), 1, arcfp);
4161 * TDAP_ProcessAdjRefCountQueue()
4163 * Process the queue of message count adjustments that was created by calls
4164 * to AdjRefCount() ... by reading the queue and calling TDAP_AdjRefCount()
4165 * for each one. This should be an "off hours" operation.
4167 int TDAP_ProcessAdjRefCountQueue(void)
4169 char file_arcq_temp[PATH_MAX];
4172 struct arcq arcq_rec;
4173 int num_records_processed = 0;
4175 snprintf(file_arcq_temp, sizeof file_arcq_temp, "%s2", file_arcq);
4177 begin_critical_section(S_SUPPMSGMAIN);
4178 if (arcfp != NULL) {
4183 r = link(file_arcq, file_arcq_temp);
4185 CtdlLogPrintf(CTDL_CRIT, "%s: %s\n", file_arcq_temp, strerror(errno));
4186 end_critical_section(S_SUPPMSGMAIN);
4187 return(num_records_processed);
4191 end_critical_section(S_SUPPMSGMAIN);
4193 fp = fopen(file_arcq_temp, "rb");
4195 CtdlLogPrintf(CTDL_CRIT, "%s: %s\n", file_arcq_temp, strerror(errno));
4196 return(num_records_processed);
4199 while (fread(&arcq_rec, sizeof(struct arcq), 1, fp) == 1) {
4200 TDAP_AdjRefCount(arcq_rec.arcq_msgnum, arcq_rec.arcq_delta);
4201 ++num_records_processed;
4205 r = unlink(file_arcq_temp);
4207 CtdlLogPrintf(CTDL_CRIT, "%s: %s\n", file_arcq_temp, strerror(errno));
4210 return(num_records_processed);
4216 * TDAP_AdjRefCount - adjust the reference count for a message.
4217 * This one does it "for real" because it's called by
4218 * the autopurger function that processes the queue
4219 * created by AdjRefCount(). If a message's reference
4220 * count becomes zero, we also delete the message from
4221 * disk and de-index it.
4223 void TDAP_AdjRefCount(long msgnum, int incr)
4226 struct MetaData smi;
4229 /* This is a *tight* critical section; please keep it that way, as
4230 * it may get called while nested in other critical sections.
4231 * Complicating this any further will surely cause deadlock!
4233 begin_critical_section(S_SUPPMSGMAIN);
4234 GetMetaData(&smi, msgnum);
4235 smi.meta_refcount += incr;
4237 end_critical_section(S_SUPPMSGMAIN);
4238 CtdlLogPrintf(CTDL_DEBUG, "msg %ld ref count delta %d, is now %d\n",
4239 msgnum, incr, smi.meta_refcount);
4241 /* If the reference count is now zero, delete the message
4242 * (and its supplementary record as well).
4244 if (smi.meta_refcount == 0) {
4245 CtdlLogPrintf(CTDL_DEBUG, "Deleting message <%ld>\n", msgnum);
4247 /* Call delete hooks with NULL room to show it has gone altogether */
4248 PerformDeleteHooks(NULL, msgnum);
4250 /* Remove from message base */
4252 cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long));
4253 cdb_delete(CDB_BIGMSGS, &delnum, (int)sizeof(long));
4255 /* Remove metadata record */
4256 delnum = (0L - msgnum);
4257 cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long));
4263 * Write a generic object to this room
4265 * Note: this could be much more efficient. Right now we use two temporary
4266 * files, and still pull the message into memory as with all others.
4268 void CtdlWriteObject(char *req_room, /* Room to stuff it in */
4269 char *content_type, /* MIME type of this object */
4270 char *tempfilename, /* Where to fetch it from */
4271 struct ctdluser *is_mailbox, /* Mailbox room? */
4272 int is_binary, /* Is encoding necessary? */
4273 int is_unique, /* Del others of this type? */
4274 unsigned int flags /* Internal save flags */
4279 struct ctdlroom qrbuf;
4280 char roomname[ROOMNAMELEN];
4281 struct CtdlMessage *msg;
4283 char *raw_message = NULL;
4284 char *encoded_message = NULL;
4285 off_t raw_length = 0;
4287 if (is_mailbox != NULL) {
4288 MailboxName(roomname, sizeof roomname, is_mailbox, req_room);
4291 safestrncpy(roomname, req_room, sizeof(roomname));
4294 fp = fopen(tempfilename, "rb");
4296 CtdlLogPrintf(CTDL_CRIT, "Cannot open %s: %s\n",
4297 tempfilename, strerror(errno));
4300 fseek(fp, 0L, SEEK_END);
4301 raw_length = ftell(fp);
4303 CtdlLogPrintf(CTDL_DEBUG, "Raw length is %ld\n", (long)raw_length);
4305 raw_message = malloc((size_t)raw_length + 2);
4306 fread(raw_message, (size_t)raw_length, 1, fp);
4310 encoded_message = malloc((size_t)
4311 (((raw_length * 134) / 100) + 4096 ) );
4314 encoded_message = malloc((size_t)(raw_length + 4096));
4317 sprintf(encoded_message, "Content-type: %s\n", content_type);
4320 sprintf(&encoded_message[strlen(encoded_message)],
4321 "Content-transfer-encoding: base64\n\n"
4325 sprintf(&encoded_message[strlen(encoded_message)],
4326 "Content-transfer-encoding: 7bit\n\n"
4332 &encoded_message[strlen(encoded_message)],
4339 raw_message[raw_length] = 0;
4341 &encoded_message[strlen(encoded_message)],
4349 CtdlLogPrintf(CTDL_DEBUG, "Allocating\n");
4350 msg = malloc(sizeof(struct CtdlMessage));
4351 memset(msg, 0, sizeof(struct CtdlMessage));
4352 msg->cm_magic = CTDLMESSAGE_MAGIC;
4353 msg->cm_anon_type = MES_NORMAL;
4354 msg->cm_format_type = 4;
4355 msg->cm_fields['A'] = strdup(CC->user.fullname);
4356 msg->cm_fields['O'] = strdup(req_room);
4357 msg->cm_fields['N'] = strdup(config.c_nodename);
4358 msg->cm_fields['H'] = strdup(config.c_humannode);
4359 msg->cm_flags = flags;
4361 msg->cm_fields['M'] = encoded_message;
4363 /* Create the requested room if we have to. */
4364 if (getroom(&qrbuf, roomname) != 0) {
4365 create_room(roomname,
4366 ( (is_mailbox != NULL) ? 5 : 3 ),
4367 "", 0, 1, 0, VIEW_BBS);
4369 /* If the caller specified this object as unique, delete all
4370 * other objects of this type that are currently in the room.
4373 CtdlLogPrintf(CTDL_DEBUG, "Deleted %d other msgs of this type\n",
4374 CtdlDeleteMessages(roomname, NULL, 0, content_type)
4377 /* Now write the data */
4378 CtdlSubmitMsg(msg, NULL, roomname);
4379 CtdlFreeMessage(msg);
4387 void CtdlGetSysConfigBackend(long msgnum, void *userdata) {
4388 config_msgnum = msgnum;
4392 char *CtdlGetSysConfig(char *sysconfname) {
4393 char hold_rm[ROOMNAMELEN];
4396 struct CtdlMessage *msg;
4399 strcpy(hold_rm, CC->room.QRname);
4400 if (getroom(&CC->room, SYSCONFIGROOM) != 0) {
4401 getroom(&CC->room, hold_rm);
4406 /* We want the last (and probably only) config in this room */
4407 begin_critical_section(S_CONFIG);
4408 config_msgnum = (-1L);
4409 CtdlForEachMessage(MSGS_LAST, 1, NULL, sysconfname, NULL,
4410 CtdlGetSysConfigBackend, NULL);
4411 msgnum = config_msgnum;
4412 end_critical_section(S_CONFIG);
4418 msg = CtdlFetchMessage(msgnum, 1);
4420 conf = strdup(msg->cm_fields['M']);
4421 CtdlFreeMessage(msg);
4428 getroom(&CC->room, hold_rm);
4430 if (conf != NULL) do {
4431 extract_token(buf, conf, 0, '\n', sizeof buf);
4432 strcpy(conf, &conf[strlen(buf)+1]);
4433 } while ( (!IsEmptyStr(conf)) && (!IsEmptyStr(buf)) );
4438 void CtdlPutSysConfig(char *sysconfname, char *sysconfdata) {
4439 char temp[PATH_MAX];
4442 CtdlMakeTempFileName(temp, sizeof temp);
4444 fp = fopen(temp, "w");
4445 if (fp == NULL) return;
4446 fprintf(fp, "%s", sysconfdata);
4449 /* this handy API function does all the work for us */
4450 CtdlWriteObject(SYSCONFIGROOM, sysconfname, temp, NULL, 0, 1, 0);
4456 * Determine whether a given Internet address belongs to the current user
4458 int CtdlIsMe(char *addr, int addr_buf_len)
4460 struct recptypes *recp;
4463 recp = validate_recipients(addr, NULL, 0);
4464 if (recp == NULL) return(0);
4466 if (recp->num_local == 0) {
4467 free_recipients(recp);
4471 for (i=0; i<recp->num_local; ++i) {
4472 extract_token(addr, recp->recp_local, i, '|', addr_buf_len);
4473 if (!strcasecmp(addr, CC->user.fullname)) {
4474 free_recipients(recp);
4479 free_recipients(recp);
4485 * Citadel protocol command to do the same
4487 void cmd_isme(char *argbuf) {
4490 if (CtdlAccessCheck(ac_logged_in)) return;
4491 extract_token(addr, argbuf, 0, '|', sizeof addr);
4493 if (CtdlIsMe(addr, sizeof addr)) {
4494 cprintf("%d %s\n", CIT_OK, addr);
4497 cprintf("%d Not you.\n", ERROR + ILLEGAL_VALUE);