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 lprintf(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 lprintf(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 lprintf(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 /* lprintf(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 /* lprintf(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), 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 lprintf(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 lprintf(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 lprintf(CTDL_WARNING, "is_valid_message() -- self-check failed\n");
1106 * 'Destructor' for struct CtdlMessage
1108 void CtdlFreeMessage(struct CtdlMessage *msg)
1112 if (is_valid_message(msg) == 0)
1114 if (msg != NULL) free (msg);
1118 for (i = 0; i < 256; ++i)
1119 if (msg->cm_fields[i] != NULL) {
1120 free(msg->cm_fields[i]);
1123 msg->cm_magic = 0; /* just in case */
1129 * Pre callback function for multipart/alternative
1131 * NOTE: this differs from the standard behavior for a reason. Normally when
1132 * displaying multipart/alternative you want to show the _last_ usable
1133 * format in the message. Here we show the _first_ one, because it's
1134 * usually text/plain. Since this set of functions is designed for text
1135 * output to non-MIME-aware clients, this is the desired behavior.
1138 void fixed_output_pre(char *name, char *filename, char *partnum, char *disp,
1139 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
1144 ma = (struct ma_info *)cbuserdata;
1145 lprintf(CTDL_DEBUG, "fixed_output_pre() type=<%s>\n", cbtype);
1146 if (!strcasecmp(cbtype, "multipart/alternative")) {
1150 if (!strcasecmp(cbtype, "message/rfc822")) {
1156 * Post callback function for multipart/alternative
1158 void fixed_output_post(char *name, char *filename, char *partnum, char *disp,
1159 void *content, char *cbtype, char *cbcharset, size_t length,
1160 char *encoding, void *cbuserdata)
1164 ma = (struct ma_info *)cbuserdata;
1165 lprintf(CTDL_DEBUG, "fixed_output_post() type=<%s>\n", cbtype);
1166 if (!strcasecmp(cbtype, "multipart/alternative")) {
1170 if (!strcasecmp(cbtype, "message/rfc822")) {
1176 * Inline callback function for mime parser that wants to display text
1178 void fixed_output(char *name, char *filename, char *partnum, char *disp,
1179 void *content, char *cbtype, char *cbcharset, size_t length,
1180 char *encoding, void *cbuserdata)
1187 ma = (struct ma_info *)cbuserdata;
1190 "fixed_output() part %s: %s (%s) (%ld bytes)\n",
1191 partnum, filename, cbtype, (long)length);
1194 * If we're in the middle of a multipart/alternative scope and
1195 * we've already printed another section, skip this one.
1197 if ( (ma->is_ma) && (ma->did_print) ) {
1198 lprintf(CTDL_DEBUG, "Skipping part %s (%s)\n", 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 lprintf(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 lprintf(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 lprintf(CTDL_DEBUG, "CtdlOutputPreLoadedMsg(TheMessage=%s, %d, %d, %d, %d\n",
1488 ((TheMessage == NULL) ? "NULL" : "not null"),
1489 mode, headers_only, do_proto, crlf);
1491 strcpy(mid, "unknown");
1492 nl = (crlf ? "\r\n" : "\n");
1494 if (!is_valid_message(TheMessage)) {
1496 "ERROR: invalid preloaded message for output\n");
1497 return(om_no_such_msg);
1500 /* Are we downloading a MIME component? */
1501 if (mode == MT_DOWNLOAD) {
1502 if (TheMessage->cm_format_type != FMT_RFC822) {
1504 cprintf("%d This is not a MIME message.\n",
1505 ERROR + ILLEGAL_VALUE);
1506 } else if (CC->download_fp != NULL) {
1507 if (do_proto) cprintf(
1508 "%d You already have a download open.\n",
1509 ERROR + RESOURCE_BUSY);
1511 /* Parse the message text component */
1512 mptr = TheMessage->cm_fields['M'];
1513 mime_parser(mptr, NULL, *mime_download, NULL, NULL, NULL, 0);
1514 /* If there's no file open by this time, the requested
1515 * section wasn't found, so print an error
1517 if (CC->download_fp == NULL) {
1518 if (do_proto) cprintf(
1519 "%d Section %s not found.\n",
1520 ERROR + FILE_NOT_FOUND,
1521 CC->download_desired_section);
1524 return((CC->download_fp != NULL) ? om_ok : om_mime_error);
1527 /* MT_SPEW_SECTION is like MT_DOWNLOAD except it outputs the whole MIME part
1528 * in a single server operation instead of opening a download file.
1530 if (mode == MT_SPEW_SECTION) {
1531 if (TheMessage->cm_format_type != FMT_RFC822) {
1533 cprintf("%d This is not a MIME message.\n",
1534 ERROR + ILLEGAL_VALUE);
1536 /* Parse the message text component */
1539 mptr = TheMessage->cm_fields['M'];
1540 mime_parser(mptr, NULL, *mime_spew_section, NULL, NULL, (void *)&found_it, 0);
1541 /* If section wasn't found, print an error
1544 if (do_proto) cprintf(
1545 "%d Section %s not found.\n",
1546 ERROR + FILE_NOT_FOUND,
1547 CC->download_desired_section);
1550 return((CC->download_fp != NULL) ? om_ok : om_mime_error);
1553 /* now for the user-mode message reading loops */
1554 if (do_proto) cprintf("%d msg:\n", LISTING_FOLLOWS);
1556 /* Does the caller want to skip the headers? */
1557 if (headers_only == HEADERS_NONE) goto START_TEXT;
1559 /* Tell the client which format type we're using. */
1560 if ( (mode == MT_CITADEL) && (do_proto) ) {
1561 cprintf("type=%d\n", TheMessage->cm_format_type);
1564 /* nhdr=yes means that we're only displaying headers, no body */
1565 if ( (TheMessage->cm_anon_type == MES_ANONONLY)
1566 && (mode == MT_CITADEL)
1569 cprintf("nhdr=yes\n");
1572 /* begin header processing loop for Citadel message format */
1574 if ((mode == MT_CITADEL) || (mode == MT_MIME)) {
1576 safestrncpy(display_name, "<unknown>", sizeof display_name);
1577 if (TheMessage->cm_fields['A']) {
1578 strcpy(buf, TheMessage->cm_fields['A']);
1579 if (TheMessage->cm_anon_type == MES_ANONONLY) {
1580 safestrncpy(display_name, "****", sizeof display_name);
1582 else if (TheMessage->cm_anon_type == MES_ANONOPT) {
1583 safestrncpy(display_name, "anonymous", sizeof display_name);
1586 safestrncpy(display_name, buf, sizeof display_name);
1588 if ((is_room_aide())
1589 && ((TheMessage->cm_anon_type == MES_ANONONLY)
1590 || (TheMessage->cm_anon_type == MES_ANONOPT))) {
1591 size_t tmp = strlen(display_name);
1592 snprintf(&display_name[tmp],
1593 sizeof display_name - tmp,
1598 /* Don't show Internet address for users on the
1599 * local Citadel network.
1602 if (TheMessage->cm_fields['N'] != NULL)
1603 if (!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);
1694 if (subject_found == 0) {
1695 cprintf("Subject: (no subject)%s", nl);
1699 for (i=0; !IsEmptyStr(&suser[i]); ++i) {
1700 suser[i] = tolower(suser[i]);
1701 if (!isalnum(suser[i])) suser[i]='_';
1704 if (mode == MT_RFC822) {
1705 if (!strcasecmp(snode, NODENAME)) {
1706 safestrncpy(snode, FQDN, sizeof snode);
1709 /* Construct a fun message id */
1710 cprintf("Message-ID: <%s", mid);/// todo: this possibly breaks threadding mails.
1711 if (strchr(mid, '@')==NULL) {
1712 cprintf("@%s", snode);
1716 if (!is_room_aide() && (TheMessage->cm_anon_type == MES_ANONONLY)) {
1717 cprintf("From: \"----\" <x@x.org>%s", nl);
1719 else if (!is_room_aide() && (TheMessage->cm_anon_type == MES_ANONOPT)) {
1720 cprintf("From: \"anonymous\" <x@x.org>%s", nl);
1722 else if (!IsEmptyStr(fuser)) {
1723 cprintf("From: \"%s\" <%s>%s", luser, fuser, nl);
1726 cprintf("From: \"%s\" <%s@%s>%s", luser, suser, snode, nl);
1729 cprintf("Organization: %s%s", lnode, nl);
1731 /* Blank line signifying RFC822 end-of-headers */
1732 if (TheMessage->cm_format_type != FMT_RFC822) {
1737 /* end header processing loop ... at this point, we're in the text */
1739 if (headers_only == HEADERS_FAST) goto DONE;
1740 mptr = TheMessage->cm_fields['M'];
1742 /* Tell the client about the MIME parts in this message */
1743 if (TheMessage->cm_format_type == FMT_RFC822) {
1744 if ( (mode == MT_CITADEL) || (mode == MT_MIME) ) {
1745 memset(&ma, 0, sizeof(struct ma_info));
1746 mime_parser(mptr, NULL,
1747 (do_proto ? *list_this_part : NULL),
1748 (do_proto ? *list_this_pref : NULL),
1749 (do_proto ? *list_this_suff : NULL),
1752 else if (mode == MT_RFC822) { /* unparsed RFC822 dump */
1753 char *start_of_text = NULL;
1754 start_of_text = strstr(mptr, "\n\r\n");
1755 if (start_of_text == NULL) start_of_text = strstr(mptr, "\n\n");
1756 if (start_of_text == NULL) start_of_text = mptr;
1758 start_of_text = strstr(start_of_text, "\n");
1763 int nllen = strlen(nl);
1764 while (ch=*mptr, ch!=0) {
1770 ((headers_only == HEADERS_NONE) && (mptr >= start_of_text))
1771 || ((headers_only == HEADERS_ONLY) && (mptr < start_of_text))
1772 || ((headers_only != HEADERS_NONE) && (headers_only != HEADERS_ONLY))
1775 sprintf(&outbuf[outlen], "%s", nl);
1779 outbuf[outlen++] = ch;
1784 if (outlen > 1000) {
1785 client_write(outbuf, outlen);
1790 client_write(outbuf, outlen);
1798 if (headers_only == HEADERS_ONLY) {
1802 /* signify start of msg text */
1803 if ( (mode == MT_CITADEL) || (mode == MT_MIME) ) {
1804 if (do_proto) cprintf("text\n");
1807 /* If the format type on disk is 1 (fixed-format), then we want
1808 * everything to be output completely literally ... regardless of
1809 * what message transfer format is in use.
1811 if (TheMessage->cm_format_type == FMT_FIXED) {
1813 if (mode == MT_MIME) {
1814 cprintf("Content-type: text/plain\n\n");
1818 while (ch = *mptr++, ch > 0) {
1821 if ((ch == 10) || (buflen > 250)) {
1823 cprintf("%s%s", buf, nl);
1832 if (!IsEmptyStr(buf))
1833 cprintf("%s%s", buf, nl);
1836 /* If the message on disk is format 0 (Citadel vari-format), we
1837 * output using the formatter at 80 columns. This is the final output
1838 * form if the transfer format is RFC822, but if the transfer format
1839 * is Citadel proprietary, it'll still work, because the indentation
1840 * for new paragraphs is correct and the client will reformat the
1841 * message to the reader's screen width.
1843 if (TheMessage->cm_format_type == FMT_CITADEL) {
1844 if (mode == MT_MIME) {
1845 cprintf("Content-type: text/x-citadel-variformat\n\n");
1847 memfmout(mptr, 0, nl);
1850 /* If the message on disk is format 4 (MIME), we've gotta hand it
1851 * off to the MIME parser. The client has already been told that
1852 * this message is format 1 (fixed format), so the callback function
1853 * we use will display those parts as-is.
1855 if (TheMessage->cm_format_type == FMT_RFC822) {
1856 memset(&ma, 0, sizeof(struct ma_info));
1858 if (mode == MT_MIME) {
1859 ma.use_fo_hooks = 0;
1860 strcpy(ma.chosen_part, "1");
1861 ma.chosen_pref = 9999;
1862 mime_parser(mptr, NULL,
1863 *choose_preferred, *fixed_output_pre,
1864 *fixed_output_post, (void *)&ma, 0);
1865 mime_parser(mptr, NULL,
1866 *output_preferred, NULL, NULL, (void *)&ma, CC->msg4_dont_decode);
1869 ma.use_fo_hooks = 1;
1870 mime_parser(mptr, NULL,
1871 *fixed_output, *fixed_output_pre,
1872 *fixed_output_post, (void *)&ma, 0);
1877 DONE: /* now we're done */
1878 if (do_proto) cprintf("000\n");
1885 * display a message (mode 0 - Citadel proprietary)
1887 void cmd_msg0(char *cmdbuf)
1890 int headers_only = HEADERS_ALL;
1892 msgid = extract_long(cmdbuf, 0);
1893 headers_only = extract_int(cmdbuf, 1);
1895 CtdlOutputMsg(msgid, MT_CITADEL, headers_only, 1, 0, NULL);
1901 * display a message (mode 2 - RFC822)
1903 void cmd_msg2(char *cmdbuf)
1906 int headers_only = HEADERS_ALL;
1908 msgid = extract_long(cmdbuf, 0);
1909 headers_only = extract_int(cmdbuf, 1);
1911 CtdlOutputMsg(msgid, MT_RFC822, headers_only, 1, 1, NULL);
1917 * display a message (mode 3 - IGnet raw format - internal programs only)
1919 void cmd_msg3(char *cmdbuf)
1922 struct CtdlMessage *msg = NULL;
1925 if (CC->internal_pgm == 0) {
1926 cprintf("%d This command is for internal programs only.\n",
1927 ERROR + HIGHER_ACCESS_REQUIRED);
1931 msgnum = extract_long(cmdbuf, 0);
1932 msg = CtdlFetchMessage(msgnum, 1);
1934 cprintf("%d Message %ld not found.\n",
1935 ERROR + MESSAGE_NOT_FOUND, msgnum);
1939 serialize_message(&smr, msg);
1940 CtdlFreeMessage(msg);
1943 cprintf("%d Unable to serialize message\n",
1944 ERROR + INTERNAL_ERROR);
1948 cprintf("%d %ld\n", BINARY_FOLLOWS, (long)smr.len);
1949 client_write((char *)smr.ser, (int)smr.len);
1956 * Display a message using MIME content types
1958 void cmd_msg4(char *cmdbuf)
1963 msgid = extract_long(cmdbuf, 0);
1964 extract_token(section, cmdbuf, 1, '|', sizeof section);
1965 CtdlOutputMsg(msgid, MT_MIME, 0, 1, 0, (section[0] ? section : NULL) );
1971 * Client tells us its preferred message format(s)
1973 void cmd_msgp(char *cmdbuf)
1975 if (!strcasecmp(cmdbuf, "dont_decode")) {
1976 CC->msg4_dont_decode = 1;
1977 cprintf("%d MSG4 will not pre-decode messages.\n", CIT_OK);
1980 safestrncpy(CC->preferred_formats, cmdbuf, sizeof(CC->preferred_formats));
1981 cprintf("%d Preferred MIME formats have been set.\n", CIT_OK);
1987 * Open a component of a MIME message as a download file
1989 void cmd_opna(char *cmdbuf)
1992 char desired_section[128];
1994 msgid = extract_long(cmdbuf, 0);
1995 extract_token(desired_section, cmdbuf, 1, '|', sizeof desired_section);
1996 safestrncpy(CC->download_desired_section, desired_section,
1997 sizeof CC->download_desired_section);
1998 CtdlOutputMsg(msgid, MT_DOWNLOAD, 0, 1, 1, NULL);
2003 * Open a component of a MIME message and transmit it all at once
2005 void cmd_dlat(char *cmdbuf)
2008 char desired_section[128];
2010 msgid = extract_long(cmdbuf, 0);
2011 extract_token(desired_section, cmdbuf, 1, '|', sizeof desired_section);
2012 safestrncpy(CC->download_desired_section, desired_section,
2013 sizeof CC->download_desired_section);
2014 CtdlOutputMsg(msgid, MT_SPEW_SECTION, 0, 1, 1, NULL);
2019 * Save one or more message pointers into a specified room
2020 * (Returns 0 for success, nonzero for failure)
2021 * roomname may be NULL to use the current room
2023 * Note that the 'supplied_msg' field may be set to NULL, in which case
2024 * the message will be fetched from disk, by number, if we need to perform
2025 * replication checks. This adds an additional database read, so if the
2026 * caller already has the message in memory then it should be supplied. (Obviously
2027 * this mode of operation only works if we're saving a single message.)
2029 int CtdlSaveMsgPointersInRoom(char *roomname, long newmsgidlist[], int num_newmsgs,
2030 int do_repl_check, struct CtdlMessage *supplied_msg)
2033 char hold_rm[ROOMNAMELEN];
2034 struct cdbdata *cdbfr;
2037 long highest_msg = 0L;
2040 struct CtdlMessage *msg = NULL;
2042 long *msgs_to_be_merged = NULL;
2043 int num_msgs_to_be_merged = 0;
2046 "CtdlSaveMsgPointersInRoom(room=%s, num_msgs=%d, repl=%d)\n",
2047 roomname, num_newmsgs, do_repl_check);
2049 strcpy(hold_rm, CC->room.QRname);
2052 if (newmsgidlist == NULL) return(ERROR + INTERNAL_ERROR);
2053 if (num_newmsgs < 1) return(ERROR + INTERNAL_ERROR);
2054 if (num_newmsgs > 1) supplied_msg = NULL;
2056 /* Now the regular stuff */
2057 if (lgetroom(&CC->room,
2058 ((roomname != NULL) ? roomname : CC->room.QRname) )
2060 lprintf(CTDL_ERR, "No such room <%s>\n", roomname);
2061 return(ERROR + ROOM_NOT_FOUND);
2065 msgs_to_be_merged = malloc(sizeof(long) * num_newmsgs);
2066 num_msgs_to_be_merged = 0;
2069 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long));
2070 if (cdbfr == NULL) {
2074 msglist = (long *) cdbfr->ptr;
2075 cdbfr->ptr = NULL; /* CtdlSaveMsgPointerInRoom() now owns this memory */
2076 num_msgs = cdbfr->len / sizeof(long);
2081 /* Create a list of msgid's which were supplied by the caller, but do
2082 * not already exist in the target room. It is absolutely taboo to
2083 * have more than one reference to the same message in a room.
2085 for (i=0; i<num_newmsgs; ++i) {
2087 if (num_msgs > 0) for (j=0; j<num_msgs; ++j) {
2088 if (msglist[j] == newmsgidlist[i]) {
2093 msgs_to_be_merged[num_msgs_to_be_merged++] = newmsgidlist[i];
2097 lprintf(9, "%d unique messages to be merged\n", num_msgs_to_be_merged);
2100 * Now merge the new messages
2102 msglist = realloc(msglist, (sizeof(long) * (num_msgs + num_msgs_to_be_merged)) );
2103 if (msglist == NULL) {
2104 lprintf(CTDL_ALERT, "ERROR: can't realloc message list!\n");
2106 memcpy(&msglist[num_msgs], msgs_to_be_merged, (sizeof(long) * num_msgs_to_be_merged) );
2107 num_msgs += num_msgs_to_be_merged;
2109 /* Sort the message list, so all the msgid's are in order */
2110 num_msgs = sort_msglist(msglist, num_msgs);
2112 /* Determine the highest message number */
2113 highest_msg = msglist[num_msgs - 1];
2115 /* Write it back to disk. */
2116 cdb_store(CDB_MSGLISTS, &CC->room.QRnumber, (int)sizeof(long),
2117 msglist, (int)(num_msgs * sizeof(long)));
2119 /* Free up the memory we used. */
2122 /* Update the highest-message pointer and unlock the room. */
2123 CC->room.QRhighest = highest_msg;
2124 lputroom(&CC->room);
2126 /* Perform replication checks if necessary */
2127 if ( (DoesThisRoomNeedEuidIndexing(&CC->room)) && (do_repl_check) ) {
2128 lprintf(CTDL_DEBUG, "CtdlSaveMsgPointerInRoom() doing repl checks\n");
2130 for (i=0; i<num_msgs_to_be_merged; ++i) {
2131 msgid = msgs_to_be_merged[i];
2133 if (supplied_msg != NULL) {
2137 msg = CtdlFetchMessage(msgid, 0);
2141 ReplicationChecks(msg);
2143 /* If the message has an Exclusive ID, index that... */
2144 if (msg->cm_fields['E'] != NULL) {
2145 index_message_by_euid(msg->cm_fields['E'], &CC->room, msgid);
2148 /* Free up the memory we may have allocated */
2149 if (msg != supplied_msg) {
2150 CtdlFreeMessage(msg);
2158 lprintf(CTDL_DEBUG, "CtdlSaveMsgPointerInRoom() skips repl checks\n");
2161 /* Submit this room for processing by hooks */
2162 PerformRoomHooks(&CC->room);
2164 /* Go back to the room we were in before we wandered here... */
2165 getroom(&CC->room, hold_rm);
2167 /* Bump the reference count for all messages which were merged */
2168 for (i=0; i<num_msgs_to_be_merged; ++i) {
2169 AdjRefCount(msgs_to_be_merged[i], +1);
2172 /* Free up memory... */
2173 if (msgs_to_be_merged != NULL) {
2174 free(msgs_to_be_merged);
2177 /* Return success. */
2183 * This is the same as CtdlSaveMsgPointersInRoom() but it only accepts
2186 int CtdlSaveMsgPointerInRoom(char *roomname, long msgid,
2187 int do_repl_check, struct CtdlMessage *supplied_msg)
2189 return CtdlSaveMsgPointersInRoom(roomname, &msgid, 1, do_repl_check, supplied_msg);
2196 * Message base operation to save a new message to the message store
2197 * (returns new message number)
2199 * This is the back end for CtdlSubmitMsg() and should not be directly
2200 * called by server-side modules.
2203 long send_message(struct CtdlMessage *msg) {
2211 /* Get a new message number */
2212 newmsgid = get_new_message_number();
2213 snprintf(msgidbuf, sizeof msgidbuf, "%010ld@%s", newmsgid, config.c_fqdn);
2215 /* Generate an ID if we don't have one already */
2216 if (msg->cm_fields['I']==NULL) {
2217 msg->cm_fields['I'] = strdup(msgidbuf);
2220 /* If the message is big, set its body aside for storage elsewhere */
2221 if (msg->cm_fields['M'] != NULL) {
2222 if (strlen(msg->cm_fields['M']) > BIGMSG) {
2224 holdM = msg->cm_fields['M'];
2225 msg->cm_fields['M'] = NULL;
2229 /* Serialize our data structure for storage in the database */
2230 serialize_message(&smr, msg);
2233 msg->cm_fields['M'] = holdM;
2237 cprintf("%d Unable to serialize message\n",
2238 ERROR + INTERNAL_ERROR);
2242 /* Write our little bundle of joy into the message base */
2243 if (cdb_store(CDB_MSGMAIN, &newmsgid, (int)sizeof(long),
2244 smr.ser, smr.len) < 0) {
2245 lprintf(CTDL_ERR, "Can't store message\n");
2249 cdb_store(CDB_BIGMSGS,
2259 /* Free the memory we used for the serialized message */
2262 /* Return the *local* message ID to the caller
2263 * (even if we're storing an incoming network message)
2271 * Serialize a struct CtdlMessage into the format used on disk and network.
2273 * This function loads up a "struct ser_ret" (defined in server.h) which
2274 * contains the length of the serialized message and a pointer to the
2275 * serialized message in memory. THE LATTER MUST BE FREED BY THE CALLER.
2277 void serialize_message(struct ser_ret *ret, /* return values */
2278 struct CtdlMessage *msg) /* unserialized msg */
2280 size_t wlen, fieldlen;
2282 static char *forder = FORDER;
2285 * Check for valid message format
2287 if (is_valid_message(msg) == 0) {
2288 lprintf(CTDL_ERR, "serialize_message() aborting due to invalid message\n");
2295 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL)
2296 ret->len = ret->len +
2297 strlen(msg->cm_fields[(int)forder[i]]) + 2;
2299 ret->ser = malloc(ret->len);
2300 if (ret->ser == NULL) {
2301 lprintf(CTDL_ERR, "serialize_message() malloc(%ld) failed: %s\n",
2302 (long)ret->len, strerror(errno));
2309 ret->ser[1] = msg->cm_anon_type;
2310 ret->ser[2] = msg->cm_format_type;
2313 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL) {
2314 fieldlen = strlen(msg->cm_fields[(int)forder[i]]);
2315 ret->ser[wlen++] = (char)forder[i];
2316 safestrncpy((char *)&ret->ser[wlen], msg->cm_fields[(int)forder[i]], fieldlen+1);
2317 wlen = wlen + fieldlen + 1;
2319 if (ret->len != wlen) lprintf(CTDL_ERR, "ERROR: len=%ld wlen=%ld\n",
2320 (long)ret->len, (long)wlen);
2327 * Serialize a struct CtdlMessage into the format used on disk and network.
2329 * This function loads up a "struct ser_ret" (defined in server.h) which
2330 * contains the length of the serialized message and a pointer to the
2331 * serialized message in memory. THE LATTER MUST BE FREED BY THE CALLER.
2333 void dump_message(struct CtdlMessage *msg, /* unserialized msg */
2334 long Siz) /* how many chars ? */
2338 static char *forder = FORDER;
2342 * Check for valid message format
2344 if (is_valid_message(msg) == 0) {
2345 lprintf(CTDL_ERR, "dump_message() aborting due to invalid message\n");
2349 buf = (char*) malloc (Siz + 1);
2353 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL) {
2354 snprintf (buf, Siz, " msg[%c] = %s ...\n", (char) forder[i],
2355 msg->cm_fields[(int)forder[i]]);
2356 client_write (buf, strlen(buf));
2365 * Check to see if any messages already exist in the current room which
2366 * carry the same Exclusive ID as this one. If any are found, delete them.
2368 void ReplicationChecks(struct CtdlMessage *msg) {
2369 long old_msgnum = (-1L);
2371 if (DoesThisRoomNeedEuidIndexing(&CC->room) == 0) return;
2373 lprintf(CTDL_DEBUG, "Performing replication checks in <%s>\n",
2376 /* No exclusive id? Don't do anything. */
2377 if (msg == NULL) return;
2378 if (msg->cm_fields['E'] == NULL) return;
2379 if (IsEmptyStr(msg->cm_fields['E'])) return;
2380 /*lprintf(CTDL_DEBUG, "Exclusive ID: <%s> for room <%s>\n",
2381 msg->cm_fields['E'], CC->room.QRname);*/
2383 old_msgnum = locate_message_by_euid(msg->cm_fields['E'], &CC->room);
2384 if (old_msgnum > 0L) {
2385 lprintf(CTDL_DEBUG, "ReplicationChecks() replacing message %ld\n", old_msgnum);
2386 CtdlDeleteMessages(CC->room.QRname, &old_msgnum, 1, "");
2393 * Save a message to disk and submit it into the delivery system.
2395 long CtdlSubmitMsg(struct CtdlMessage *msg, /* message to save */
2396 struct recptypes *recps, /* recipients (if mail) */
2397 char *force /* force a particular room? */
2399 char submit_filename[128];
2400 char generated_timestamp[32];
2401 char hold_rm[ROOMNAMELEN];
2402 char actual_rm[ROOMNAMELEN];
2403 char force_room[ROOMNAMELEN];
2404 char content_type[SIZ]; /* We have to learn this */
2405 char recipient[SIZ];
2408 struct ctdluser userbuf;
2410 struct MetaData smi;
2411 FILE *network_fp = NULL;
2412 static int seqnum = 1;
2413 struct CtdlMessage *imsg = NULL;
2415 size_t instr_alloc = 0;
2417 char *hold_R, *hold_D;
2418 char *collected_addresses = NULL;
2419 struct addresses_to_be_filed *aptr = NULL;
2420 char *saved_rfc822_version = NULL;
2421 int qualified_for_journaling = 0;
2422 struct CitContext *CCC = CC; /* CachedCitContext - performance boost */
2424 lprintf(CTDL_DEBUG, "CtdlSubmitMsg() called\n");
2425 if (is_valid_message(msg) == 0) return(-1); /* self check */
2427 /* If this message has no timestamp, we take the liberty of
2428 * giving it one, right now.
2430 if (msg->cm_fields['T'] == NULL) {
2431 snprintf(generated_timestamp, sizeof generated_timestamp, "%ld", (long)time(NULL));
2432 msg->cm_fields['T'] = strdup(generated_timestamp);
2435 /* If this message has no path, we generate one.
2437 if (msg->cm_fields['P'] == NULL) {
2438 if (msg->cm_fields['A'] != NULL) {
2439 msg->cm_fields['P'] = strdup(msg->cm_fields['A']);
2440 for (a=0; !IsEmptyStr(&msg->cm_fields['P'][a]); ++a) {
2441 if (isspace(msg->cm_fields['P'][a])) {
2442 msg->cm_fields['P'][a] = ' ';
2447 msg->cm_fields['P'] = strdup("unknown");
2451 if (force == NULL) {
2452 strcpy(force_room, "");
2455 strcpy(force_room, force);
2458 /* Learn about what's inside, because it's what's inside that counts */
2459 if (msg->cm_fields['M'] == NULL) {
2460 lprintf(CTDL_ERR, "ERROR: attempt to save message with NULL body\n");
2464 switch (msg->cm_format_type) {
2466 strcpy(content_type, "text/x-citadel-variformat");
2469 strcpy(content_type, "text/plain");
2472 strcpy(content_type, "text/plain");
2473 mptr = bmstrcasestr(msg->cm_fields['M'], "Content-type:");
2476 safestrncpy(content_type, &mptr[13], sizeof content_type);
2477 striplt(content_type);
2478 aptr = content_type;
2479 while (!IsEmptyStr(aptr)) {
2491 /* Goto the correct room */
2492 lprintf(CTDL_DEBUG, "Selected room %s\n", (recps) ? CCC->room.QRname : SENTITEMS);
2493 strcpy(hold_rm, CCC->room.QRname);
2494 strcpy(actual_rm, CCC->room.QRname);
2495 if (recps != NULL) {
2496 strcpy(actual_rm, SENTITEMS);
2499 /* If the user is a twit, move to the twit room for posting */
2501 if (CCC->user.axlevel == 2) {
2502 strcpy(hold_rm, actual_rm);
2503 strcpy(actual_rm, config.c_twitroom);
2504 lprintf(CTDL_DEBUG, "Diverting to twit room\n");
2508 /* ...or if this message is destined for Aide> then go there. */
2509 if (!IsEmptyStr(force_room)) {
2510 strcpy(actual_rm, force_room);
2513 lprintf(CTDL_DEBUG, "Final selection: %s\n", actual_rm);
2514 if (strcasecmp(actual_rm, CCC->room.QRname)) {
2515 /* getroom(&CCC->room, actual_rm); */
2516 usergoto(actual_rm, 0, 1, NULL, NULL);
2520 * If this message has no O (room) field, generate one.
2522 if (msg->cm_fields['O'] == NULL) {
2523 msg->cm_fields['O'] = strdup(CCC->room.QRname);
2526 /* Perform "before save" hooks (aborting if any return nonzero) */
2527 lprintf(CTDL_DEBUG, "Performing before-save hooks\n");
2528 if (PerformMessageHooks(msg, EVT_BEFORESAVE) > 0) return(-3);
2531 * If this message has an Exclusive ID, and the room is replication
2532 * checking enabled, then do replication checks.
2534 if (DoesThisRoomNeedEuidIndexing(&CCC->room)) {
2535 ReplicationChecks(msg);
2538 /* Save it to disk */
2539 lprintf(CTDL_DEBUG, "Saving to disk\n");
2540 newmsgid = send_message(msg);
2541 if (newmsgid <= 0L) return(-5);
2543 /* Write a supplemental message info record. This doesn't have to
2544 * be a critical section because nobody else knows about this message
2547 lprintf(CTDL_DEBUG, "Creating MetaData record\n");
2548 memset(&smi, 0, sizeof(struct MetaData));
2549 smi.meta_msgnum = newmsgid;
2550 smi.meta_refcount = 0;
2551 safestrncpy(smi.meta_content_type, content_type,
2552 sizeof smi.meta_content_type);
2555 * Measure how big this message will be when rendered as RFC822.
2556 * We do this for two reasons:
2557 * 1. We need the RFC822 length for the new metadata record, so the
2558 * POP and IMAP services don't have to calculate message lengths
2559 * while the user is waiting (multiplied by potentially hundreds
2560 * or thousands of messages).
2561 * 2. If journaling is enabled, we will need an RFC822 version of the
2562 * message to attach to the journalized copy.
2564 if (CCC->redirect_buffer != NULL) {
2565 lprintf(CTDL_ALERT, "CCC->redirect_buffer is not NULL during message submission!\n");
2568 CCC->redirect_buffer = malloc(SIZ);
2569 CCC->redirect_len = 0;
2570 CCC->redirect_alloc = SIZ;
2571 CtdlOutputPreLoadedMsg(msg, MT_RFC822, HEADERS_ALL, 0, 1);
2572 smi.meta_rfc822_length = CCC->redirect_len;
2573 saved_rfc822_version = CCC->redirect_buffer;
2574 CCC->redirect_buffer = NULL;
2575 CCC->redirect_len = 0;
2576 CCC->redirect_alloc = 0;
2580 /* Now figure out where to store the pointers */
2581 lprintf(CTDL_DEBUG, "Storing pointers\n");
2583 /* If this is being done by the networker delivering a private
2584 * message, we want to BYPASS saving the sender's copy (because there
2585 * is no local sender; it would otherwise go to the Trashcan).
2587 if ((!CCC->internal_pgm) || (recps == NULL)) {
2588 if (CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 1, msg) != 0) {
2589 lprintf(CTDL_ERR, "ERROR saving message pointer!\n");
2590 CtdlSaveMsgPointerInRoom(config.c_aideroom, newmsgid, 0, msg);
2594 /* For internet mail, drop a copy in the outbound queue room */
2596 if (recps->num_internet > 0) {
2597 CtdlSaveMsgPointerInRoom(SMTP_SPOOLOUT_ROOM, newmsgid, 0, msg);
2600 /* If other rooms are specified, drop them there too. */
2602 if (recps->num_room > 0)
2603 for (i=0; i<num_tokens(recps->recp_room, '|'); ++i) {
2604 extract_token(recipient, recps->recp_room, i,
2605 '|', sizeof recipient);
2606 lprintf(CTDL_DEBUG, "Delivering to room <%s>\n", recipient);
2607 CtdlSaveMsgPointerInRoom(recipient, newmsgid, 0, msg);
2610 /* Bump this user's messages posted counter. */
2611 lprintf(CTDL_DEBUG, "Updating user\n");
2612 lgetuser(&CCC->user, CCC->curr_user);
2613 CCC->user.posted = CCC->user.posted + 1;
2614 lputuser(&CCC->user);
2616 /* If this is private, local mail, make a copy in the
2617 * recipient's mailbox and bump the reference count.
2620 if (recps->num_local > 0)
2621 for (i=0; i<num_tokens(recps->recp_local, '|'); ++i) {
2622 extract_token(recipient, recps->recp_local, i,
2623 '|', sizeof recipient);
2624 lprintf(CTDL_DEBUG, "Delivering private local mail to <%s>\n",
2626 if (getuser(&userbuf, recipient) == 0) {
2627 // Add a flag so the Funambol module knows its mail
2628 msg->cm_fields['W'] = strdup(recipient);
2629 MailboxName(actual_rm, sizeof actual_rm, &userbuf, MAILROOM);
2630 CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 0, msg);
2631 BumpNewMailCounter(userbuf.usernum);
2632 if (!IsEmptyStr(config.c_funambol_host) || !IsEmptyStr(config.c_pager_program)) {
2633 /* Generate a instruction message for the Funambol notification
2634 * server, in the same style as the SMTP queue
2637 instr = malloc(instr_alloc);
2638 snprintf(instr, instr_alloc,
2639 "Content-type: %s\n\nmsgid|%ld\nsubmitted|%ld\n"
2641 SPOOLMIME, newmsgid, (long)time(NULL),
2642 msg->cm_fields['A'], msg->cm_fields['N']
2645 imsg = malloc(sizeof(struct CtdlMessage));
2646 memset(imsg, 0, sizeof(struct CtdlMessage));
2647 imsg->cm_magic = CTDLMESSAGE_MAGIC;
2648 imsg->cm_anon_type = MES_NORMAL;
2649 imsg->cm_format_type = FMT_RFC822;
2650 imsg->cm_fields['A'] = strdup("Citadel");
2651 imsg->cm_fields['J'] = strdup("do not journal");
2652 imsg->cm_fields['M'] = instr; /* imsg owns this memory now */
2653 imsg->cm_fields['W'] = strdup(recipient);
2654 CtdlSubmitMsg(imsg, NULL, FNBL_QUEUE_ROOM);
2655 CtdlFreeMessage(imsg);
2659 lprintf(CTDL_DEBUG, "No user <%s>\n", recipient);
2660 CtdlSaveMsgPointerInRoom(config.c_aideroom,
2665 /* Perform "after save" hooks */
2666 lprintf(CTDL_DEBUG, "Performing after-save hooks\n");
2667 PerformMessageHooks(msg, EVT_AFTERSAVE);
2669 /* For IGnet mail, we have to save a new copy into the spooler for
2670 * each recipient, with the R and D fields set to the recipient and
2671 * destination-node. This has two ugly side effects: all other
2672 * recipients end up being unlisted in this recipient's copy of the
2673 * message, and it has to deliver multiple messages to the same
2674 * node. We'll revisit this again in a year or so when everyone has
2675 * a network spool receiver that can handle the new style messages.
2678 if (recps->num_ignet > 0)
2679 for (i=0; i<num_tokens(recps->recp_ignet, '|'); ++i) {
2680 extract_token(recipient, recps->recp_ignet, i,
2681 '|', sizeof recipient);
2683 hold_R = msg->cm_fields['R'];
2684 hold_D = msg->cm_fields['D'];
2685 msg->cm_fields['R'] = malloc(SIZ);
2686 msg->cm_fields['D'] = malloc(128);
2687 extract_token(msg->cm_fields['R'], recipient, 0, '@', SIZ);
2688 extract_token(msg->cm_fields['D'], recipient, 1, '@', 128);
2690 serialize_message(&smr, msg);
2692 snprintf(submit_filename, sizeof submit_filename,
2693 "%s/netmail.%04lx.%04x.%04x",
2695 (long) getpid(), CCC->cs_pid, ++seqnum);
2696 network_fp = fopen(submit_filename, "wb+");
2697 if (network_fp != NULL) {
2698 fwrite(smr.ser, smr.len, 1, network_fp);
2704 free(msg->cm_fields['R']);
2705 free(msg->cm_fields['D']);
2706 msg->cm_fields['R'] = hold_R;
2707 msg->cm_fields['D'] = hold_D;
2710 /* Go back to the room we started from */
2711 lprintf(CTDL_DEBUG, "Returning to original room %s\n", hold_rm);
2712 if (strcasecmp(hold_rm, CCC->room.QRname))
2713 usergoto(hold_rm, 0, 1, NULL, NULL);
2715 /* For internet mail, generate delivery instructions.
2716 * Yes, this is recursive. Deal with it. Infinite recursion does
2717 * not happen because the delivery instructions message does not
2718 * contain a recipient.
2721 if (recps->num_internet > 0) {
2722 lprintf(CTDL_DEBUG, "Generating delivery instructions\n");
2724 instr = malloc(instr_alloc);
2725 snprintf(instr, instr_alloc,
2726 "Content-type: %s\n\nmsgid|%ld\nsubmitted|%ld\n"
2728 SPOOLMIME, newmsgid, (long)time(NULL),
2729 msg->cm_fields['A'], msg->cm_fields['N']
2732 for (i=0; i<num_tokens(recps->recp_internet, '|'); ++i) {
2733 size_t tmp = strlen(instr);
2734 extract_token(recipient, recps->recp_internet, i, '|', sizeof recipient);
2735 if ((tmp + strlen(recipient) + 32) > instr_alloc) {
2736 instr_alloc = instr_alloc * 2;
2737 instr = realloc(instr, instr_alloc);
2739 snprintf(&instr[tmp], instr_alloc - tmp, "remote|%s|0||\n", recipient);
2742 imsg = malloc(sizeof(struct CtdlMessage));
2743 memset(imsg, 0, sizeof(struct CtdlMessage));
2744 imsg->cm_magic = CTDLMESSAGE_MAGIC;
2745 imsg->cm_anon_type = MES_NORMAL;
2746 imsg->cm_format_type = FMT_RFC822;
2747 imsg->cm_fields['A'] = strdup("Citadel");
2748 imsg->cm_fields['J'] = strdup("do not journal");
2749 imsg->cm_fields['M'] = instr; /* imsg owns this memory now */
2750 CtdlSubmitMsg(imsg, NULL, SMTP_SPOOLOUT_ROOM);
2751 CtdlFreeMessage(imsg);
2755 * Any addresses to harvest for someone's address book?
2757 if ( (CCC->logged_in) && (recps != NULL) ) {
2758 collected_addresses = harvest_collected_addresses(msg);
2761 if (collected_addresses != NULL) {
2762 begin_critical_section(S_ATBF);
2763 aptr = (struct addresses_to_be_filed *)
2764 malloc(sizeof(struct addresses_to_be_filed));
2766 MailboxName(actual_rm, sizeof actual_rm,
2767 &CCC->user, USERCONTACTSROOM);
2768 aptr->roomname = strdup(actual_rm);
2769 aptr->collected_addresses = collected_addresses;
2771 end_critical_section(S_ATBF);
2775 * Determine whether this message qualifies for journaling.
2777 if (msg->cm_fields['J'] != NULL) {
2778 qualified_for_journaling = 0;
2781 if (recps == NULL) {
2782 qualified_for_journaling = config.c_journal_pubmsgs;
2784 else if (recps->num_local + recps->num_ignet + recps->num_internet > 0) {
2785 qualified_for_journaling = config.c_journal_email;
2788 qualified_for_journaling = config.c_journal_pubmsgs;
2793 * Do we have to perform journaling? If so, hand off the saved
2794 * RFC822 version will be handed off to the journaler for background
2795 * submit. Otherwise, we have to free the memory ourselves.
2797 if (saved_rfc822_version != NULL) {
2798 if (qualified_for_journaling) {
2799 JournalBackgroundSubmit(msg, saved_rfc822_version, recps);
2802 free(saved_rfc822_version);
2815 * Convenience function for generating small administrative messages.
2817 void quickie_message(char *from, char *fromaddr, char *to, char *room, char *text,
2818 int format_type, char *subject)
2820 struct CtdlMessage *msg;
2821 struct recptypes *recp = NULL;
2823 msg = malloc(sizeof(struct CtdlMessage));
2824 memset(msg, 0, sizeof(struct CtdlMessage));
2825 msg->cm_magic = CTDLMESSAGE_MAGIC;
2826 msg->cm_anon_type = MES_NORMAL;
2827 msg->cm_format_type = format_type;
2830 msg->cm_fields['A'] = strdup(from);
2832 else if (fromaddr != NULL) {
2833 msg->cm_fields['A'] = strdup(fromaddr);
2834 if (strchr(msg->cm_fields['A'], '@')) {
2835 *strchr(msg->cm_fields['A'], '@') = 0;
2839 msg->cm_fields['A'] = strdup("Citadel");
2842 if (fromaddr != NULL) msg->cm_fields['F'] = strdup(fromaddr);
2843 if (room != NULL) msg->cm_fields['O'] = strdup(room);
2844 msg->cm_fields['N'] = strdup(NODENAME);
2846 msg->cm_fields['R'] = strdup(to);
2847 recp = validate_recipients(to, NULL, 0);
2849 if (subject != NULL) {
2850 msg->cm_fields['U'] = strdup(subject);
2852 msg->cm_fields['M'] = strdup(text);
2854 CtdlSubmitMsg(msg, recp, room);
2855 CtdlFreeMessage(msg);
2856 if (recp != NULL) free_recipients(recp);
2862 * Back end function used by CtdlMakeMessage() and similar functions
2864 char *CtdlReadMessageBody(char *terminator, /* token signalling EOT */
2865 size_t maxlen, /* maximum message length */
2866 char *exist, /* if non-null, append to it;
2867 exist is ALWAYS freed */
2868 int crlf, /* CRLF newlines instead of LF */
2869 int sock /* socket handle or 0 for this session's client socket */
2873 size_t message_len = 0;
2874 size_t buffer_len = 0;
2881 if (exist == NULL) {
2888 message_len = strlen(exist);
2889 buffer_len = message_len + 4096;
2890 m = realloc(exist, buffer_len);
2897 /* Do we need to change leading ".." to "." for SMTP escaping? */
2898 if (!strcmp(terminator, ".")) {
2902 /* flush the input if we have nowhere to store it */
2907 /* read in the lines of message text one by one */
2910 if (sock_getln(sock, buf, (sizeof buf - 3)) < 0) finished = 1;
2913 if (client_getln(buf, (sizeof buf - 3)) < 1) finished = 1;
2915 if (!strcmp(buf, terminator)) finished = 1;
2917 strcat(buf, "\r\n");
2923 /* Unescape SMTP-style input of two dots at the beginning of the line */
2925 if (!strncmp(buf, "..", 2)) {
2926 strcpy(buf, &buf[1]);
2930 if ( (!flushing) && (!finished) ) {
2931 /* Measure the line */
2932 linelen = strlen(buf);
2934 /* augment the buffer if we have to */
2935 if ((message_len + linelen) >= buffer_len) {
2936 ptr = realloc(m, (buffer_len * 2) );
2937 if (ptr == NULL) { /* flush if can't allocate */
2940 buffer_len = (buffer_len * 2);
2942 lprintf(CTDL_DEBUG, "buffer_len is now %ld\n", (long)buffer_len);
2946 /* Add the new line to the buffer. NOTE: this loop must avoid
2947 * using functions like strcat() and strlen() because they
2948 * traverse the entire buffer upon every call, and doing that
2949 * for a multi-megabyte message slows it down beyond usability.
2951 strcpy(&m[message_len], buf);
2952 message_len += linelen;
2955 /* if we've hit the max msg length, flush the rest */
2956 if (message_len >= maxlen) flushing = 1;
2958 } while (!finished);
2966 * Build a binary message to be saved on disk.
2967 * (NOTE: if you supply 'preformatted_text', the buffer you give it
2968 * will become part of the message. This means you are no longer
2969 * responsible for managing that memory -- it will be freed along with
2970 * the rest of the fields when CtdlFreeMessage() is called.)
2973 struct CtdlMessage *CtdlMakeMessage(
2974 struct ctdluser *author, /* author's user structure */
2975 char *recipient, /* NULL if it's not mail */
2976 char *recp_cc, /* NULL if it's not mail */
2977 char *room, /* room where it's going */
2978 int type, /* see MES_ types in header file */
2979 int format_type, /* variformat, plain text, MIME... */
2980 char *fake_name, /* who we're masquerading as */
2981 char *my_email, /* which of my email addresses to use (empty is ok) */
2982 char *subject, /* Subject (optional) */
2983 char *supplied_euid, /* ...or NULL if this is irrelevant */
2984 char *preformatted_text /* ...or NULL to read text from client */
2986 char dest_node[256];
2988 struct CtdlMessage *msg;
2991 msg = malloc(sizeof(struct CtdlMessage));
2992 memset(msg, 0, sizeof(struct CtdlMessage));
2993 msg->cm_magic = CTDLMESSAGE_MAGIC;
2994 msg->cm_anon_type = type;
2995 msg->cm_format_type = format_type;
2997 /* Don't confuse the poor folks if it's not routed mail. */
2998 strcpy(dest_node, "");
3003 /* Path or Return-Path */
3004 if (my_email == NULL) my_email = "";
3006 if (!IsEmptyStr(my_email)) {
3007 msg->cm_fields['P'] = strdup(my_email);
3010 snprintf(buf, sizeof buf, "%s", author->fullname);
3011 msg->cm_fields['P'] = strdup(buf);
3013 for (i=0; (msg->cm_fields['P'][i]!=0); ++i) {
3014 if (isspace(msg->cm_fields['P'][i])) {
3015 msg->cm_fields['P'][i] = '_';
3019 snprintf(buf, sizeof buf, "%ld", (long)time(NULL)); /* timestamp */
3020 msg->cm_fields['T'] = strdup(buf);
3022 if (fake_name[0]) /* author */
3023 msg->cm_fields['A'] = strdup(fake_name);
3025 msg->cm_fields['A'] = strdup(author->fullname);
3027 if (CC->room.QRflags & QR_MAILBOX) { /* room */
3028 msg->cm_fields['O'] = strdup(&CC->room.QRname[11]);
3031 msg->cm_fields['O'] = strdup(CC->room.QRname);
3034 msg->cm_fields['N'] = strdup(NODENAME); /* nodename */
3035 msg->cm_fields['H'] = strdup(HUMANNODE); /* hnodename */
3037 if (recipient[0] != 0) {
3038 msg->cm_fields['R'] = strdup(recipient);
3040 if (recp_cc[0] != 0) {
3041 msg->cm_fields['Y'] = strdup(recp_cc);
3043 if (dest_node[0] != 0) {
3044 msg->cm_fields['D'] = strdup(dest_node);
3047 if (!IsEmptyStr(my_email)) {
3048 msg->cm_fields['F'] = strdup(my_email);
3050 else if ( (author == &CC->user) && (!IsEmptyStr(CC->cs_inet_email)) ) {
3051 msg->cm_fields['F'] = strdup(CC->cs_inet_email);
3054 if (subject != NULL) {
3057 length = strlen(subject);
3063 while ((subject[i] != '\0') &&
3064 (IsAscii = isascii(subject[i]) != 0 ))
3067 msg->cm_fields['U'] = strdup(subject);
3068 else /* ok, we've got utf8 in the string. */
3070 msg->cm_fields['U'] = rfc2047encode(subject, length);
3076 if (supplied_euid != NULL) {
3077 msg->cm_fields['E'] = strdup(supplied_euid);
3080 if (preformatted_text != NULL) {
3081 msg->cm_fields['M'] = preformatted_text;
3084 msg->cm_fields['M'] = CtdlReadMessageBody("000", config.c_maxmsglen, NULL, 0, 0);
3092 * Check to see whether we have permission to post a message in the current
3093 * room. Returns a *CITADEL ERROR CODE* and puts a message in errmsgbuf, or
3094 * returns 0 on success.
3096 int CtdlDoIHavePermissionToPostInThisRoom(char *errmsgbuf,
3098 const char* RemoteIdentifier,
3102 if (!(CC->logged_in) &&
3103 (PostPublic == POST_LOGGED_IN)) {
3104 snprintf(errmsgbuf, n, "Not logged in.");
3105 return (ERROR + NOT_LOGGED_IN);
3107 else if (PostPublic == CHECK_EXISTANCE) {
3108 return (0); // We're Evaling whether a recipient exists
3110 else if (!(CC->logged_in)) {
3112 if ((CC->room.QRflags & QR_READONLY)) {
3113 snprintf(errmsgbuf, n, "Not logged in.");
3114 return (ERROR + NOT_LOGGED_IN);
3116 if (CC->room.QRflags2 & QR2_MODERATED) {
3117 snprintf(errmsgbuf, n, "Not logged in Moderation feature not yet implemented!");
3118 return (ERROR + NOT_LOGGED_IN);
3120 if ((PostPublic!=POST_LMTP) &&(CC->room.QRflags2 & QR2_SMTP_PUBLIC) == 0) {
3125 if (RemoteIdentifier == NULL)
3127 snprintf(errmsgbuf, n, "Need sender to permit access.");
3128 return (ERROR + USERNAME_REQUIRED);
3131 assoc_file_name(filename, sizeof filename, &CC->room, ctdl_netcfg_dir);
3132 begin_critical_section(S_NETCONFIGS);
3133 if (!read_spoolcontrol_file(&sc, filename))
3135 end_critical_section(S_NETCONFIGS);
3136 snprintf(errmsgbuf, n,
3137 "This mailing list only accepts posts from subscribers.");
3138 return (ERROR + NO_SUCH_USER);
3140 end_critical_section(S_NETCONFIGS);
3141 found = is_recipient (sc, RemoteIdentifier);
3142 free_spoolcontrol_struct(&sc);
3147 snprintf(errmsgbuf, n,
3148 "This mailing list only accepts posts from subscribers.");
3149 return (ERROR + NO_SUCH_USER);
3156 if ((CC->user.axlevel < 2)
3157 && ((CC->room.QRflags & QR_MAILBOX) == 0)) {
3158 snprintf(errmsgbuf, n, "Need to be validated to enter "
3159 "(except in %s> to sysop)", MAILROOM);
3160 return (ERROR + HIGHER_ACCESS_REQUIRED);
3163 CtdlRoomAccess(&CC->room, &CC->user, &ra, NULL);
3164 if (!(ra & UA_POSTALLOWED)) {
3165 snprintf(errmsgbuf, n, "Higher access is required to post in this room.");
3166 return (ERROR + HIGHER_ACCESS_REQUIRED);
3169 strcpy(errmsgbuf, "Ok");
3175 * Check to see if the specified user has Internet mail permission
3176 * (returns nonzero if permission is granted)
3178 int CtdlCheckInternetMailPermission(struct ctdluser *who) {
3180 /* Do not allow twits to send Internet mail */
3181 if (who->axlevel <= 2) return(0);
3183 /* Globally enabled? */
3184 if (config.c_restrict == 0) return(1);
3186 /* User flagged ok? */
3187 if (who->flags & US_INTERNET) return(2);
3189 /* Aide level access? */
3190 if (who->axlevel >= 6) return(3);
3192 /* No mail for you! */
3198 * Validate recipients, count delivery types and errors, and handle aliasing
3199 * FIXME check for dupes!!!!!
3201 * Returns 0 if all addresses are ok, ret->num_error = -1 if no addresses
3202 * were specified, or the number of addresses found invalid.
3204 * Caller needs to free the result using free_recipients()
3206 struct recptypes *validate_recipients(char *supplied_recipients,
3207 const char *RemoteIdentifier,
3209 struct recptypes *ret;
3210 char *recipients = NULL;
3211 char this_recp[256];
3212 char this_recp_cooked[256];
3218 struct ctdluser tempUS;
3219 struct ctdlroom tempQR;
3220 struct ctdlroom tempQR2;
3226 ret = (struct recptypes *) malloc(sizeof(struct recptypes));
3227 if (ret == NULL) return(NULL);
3229 /* Set all strings to null and numeric values to zero */
3230 memset(ret, 0, sizeof(struct recptypes));
3232 if (supplied_recipients == NULL) {
3233 recipients = strdup("");
3236 recipients = strdup(supplied_recipients);
3239 /* Allocate some memory. Yes, this allocates 500% more memory than we will
3240 * actually need, but it's healthier for the heap than doing lots of tiny
3241 * realloc() calls instead.
3244 ret->errormsg = malloc(strlen(recipients) + 1024);
3245 ret->recp_local = malloc(strlen(recipients) + 1024);
3246 ret->recp_internet = malloc(strlen(recipients) + 1024);
3247 ret->recp_ignet = malloc(strlen(recipients) + 1024);
3248 ret->recp_room = malloc(strlen(recipients) + 1024);
3249 ret->display_recp = malloc(strlen(recipients) + 1024);
3251 ret->errormsg[0] = 0;
3252 ret->recp_local[0] = 0;
3253 ret->recp_internet[0] = 0;
3254 ret->recp_ignet[0] = 0;
3255 ret->recp_room[0] = 0;
3256 ret->display_recp[0] = 0;
3258 ret->recptypes_magic = RECPTYPES_MAGIC;
3260 /* Change all valid separator characters to commas */
3261 for (i=0; !IsEmptyStr(&recipients[i]); ++i) {
3262 if ((recipients[i] == ';') || (recipients[i] == '|')) {
3263 recipients[i] = ',';
3267 /* Now start extracting recipients... */
3269 while (!IsEmptyStr(recipients)) {
3271 for (i=0; i<=strlen(recipients); ++i) {
3272 if (recipients[i] == '\"') in_quotes = 1 - in_quotes;
3273 if ( ( (recipients[i] == ',') && (!in_quotes) ) || (recipients[i] == 0) ) {
3274 safestrncpy(this_recp, recipients, i+1);
3276 if (recipients[i] == ',') {
3277 strcpy(recipients, &recipients[i+1]);
3280 strcpy(recipients, "");
3287 if (IsEmptyStr(this_recp))
3289 lprintf(CTDL_DEBUG, "Evaluating recipient #%d: %s\n", num_recps, this_recp);
3291 mailtype = alias(this_recp);
3292 mailtype = alias(this_recp);
3293 mailtype = alias(this_recp);
3295 for (j=0; !IsEmptyStr(&this_recp[j]); ++j) {
3296 if (this_recp[j]=='_') {
3297 this_recp_cooked[j] = ' ';
3300 this_recp_cooked[j] = this_recp[j];
3303 this_recp_cooked[j] = '\0';
3308 if (!strcasecmp(this_recp, "sysop")) {
3310 strcpy(this_recp, config.c_aideroom);
3311 if (!IsEmptyStr(ret->recp_room)) {
3312 strcat(ret->recp_room, "|");
3314 strcat(ret->recp_room, this_recp);
3316 else if (getuser(&tempUS, this_recp) == 0) {
3318 strcpy(this_recp, tempUS.fullname);
3319 if (!IsEmptyStr(ret->recp_local)) {
3320 strcat(ret->recp_local, "|");
3322 strcat(ret->recp_local, this_recp);
3324 else if (getuser(&tempUS, this_recp_cooked) == 0) {
3326 strcpy(this_recp, tempUS.fullname);
3327 if (!IsEmptyStr(ret->recp_local)) {
3328 strcat(ret->recp_local, "|");
3330 strcat(ret->recp_local, this_recp);
3332 else if ( (!strncasecmp(this_recp, "room_", 5))
3333 && (!getroom(&tempQR, &this_recp_cooked[5])) ) {
3335 /* Save room so we can restore it later */
3339 /* Check permissions to send mail to this room */
3340 err = CtdlDoIHavePermissionToPostInThisRoom(errmsg,
3352 if (!IsEmptyStr(ret->recp_room)) {
3353 strcat(ret->recp_room, "|");
3355 strcat(ret->recp_room, &this_recp_cooked[5]);
3358 /* Restore room in case something needs it */
3368 /* Yes, you're reading this correctly: if the target
3369 * domain points back to the local system or an attached
3370 * Citadel directory, the address is invalid. That's
3371 * because if the address were valid, we would have
3372 * already translated it to a local address by now.
3374 if (IsDirectory(this_recp, 0)) {
3379 ++ret->num_internet;
3380 if (!IsEmptyStr(ret->recp_internet)) {
3381 strcat(ret->recp_internet, "|");
3383 strcat(ret->recp_internet, this_recp);
3388 if (!IsEmptyStr(ret->recp_ignet)) {
3389 strcat(ret->recp_ignet, "|");
3391 strcat(ret->recp_ignet, this_recp);
3399 if (IsEmptyStr(errmsg)) {
3400 snprintf(append, sizeof append, "Invalid recipient: %s", this_recp);
3403 snprintf(append, sizeof append, "%s", errmsg);
3405 if ( (strlen(ret->errormsg) + strlen(append) + 3) < SIZ) {
3406 if (!IsEmptyStr(ret->errormsg)) {
3407 strcat(ret->errormsg, "; ");
3409 strcat(ret->errormsg, append);
3413 if (IsEmptyStr(ret->display_recp)) {
3414 strcpy(append, this_recp);
3417 snprintf(append, sizeof append, ", %s", this_recp);
3419 if ( (strlen(ret->display_recp)+strlen(append)) < SIZ) {
3420 strcat(ret->display_recp, append);
3425 if ((ret->num_local + ret->num_internet + ret->num_ignet +
3426 ret->num_room + ret->num_error) == 0) {
3427 ret->num_error = (-1);
3428 strcpy(ret->errormsg, "No recipients specified.");
3431 lprintf(CTDL_DEBUG, "validate_recipients()\n");
3432 lprintf(CTDL_DEBUG, " local: %d <%s>\n", ret->num_local, ret->recp_local);
3433 lprintf(CTDL_DEBUG, " room: %d <%s>\n", ret->num_room, ret->recp_room);
3434 lprintf(CTDL_DEBUG, " inet: %d <%s>\n", ret->num_internet, ret->recp_internet);
3435 lprintf(CTDL_DEBUG, " ignet: %d <%s>\n", ret->num_ignet, ret->recp_ignet);
3436 lprintf(CTDL_DEBUG, " error: %d <%s>\n", ret->num_error, ret->errormsg);
3444 * Destructor for struct recptypes
3446 void free_recipients(struct recptypes *valid) {
3448 if (valid == NULL) {
3452 if (valid->recptypes_magic != RECPTYPES_MAGIC) {
3453 lprintf(CTDL_EMERG, "Attempt to call free_recipients() on some other data type!\n");
3457 if (valid->errormsg != NULL) free(valid->errormsg);
3458 if (valid->recp_local != NULL) free(valid->recp_local);
3459 if (valid->recp_internet != NULL) free(valid->recp_internet);
3460 if (valid->recp_ignet != NULL) free(valid->recp_ignet);
3461 if (valid->recp_room != NULL) free(valid->recp_room);
3462 if (valid->display_recp != NULL) free(valid->display_recp);
3469 * message entry - mode 0 (normal)
3471 void cmd_ent0(char *entargs)
3477 char supplied_euid[128];
3479 int format_type = 0;
3480 char newusername[256];
3481 char newuseremail[256];
3482 struct CtdlMessage *msg;
3486 struct recptypes *valid = NULL;
3487 struct recptypes *valid_to = NULL;
3488 struct recptypes *valid_cc = NULL;
3489 struct recptypes *valid_bcc = NULL;
3491 int subject_required = 0;
3496 int newuseremail_ok = 0;
3500 post = extract_int(entargs, 0);
3501 extract_token(recp, entargs, 1, '|', sizeof recp);
3502 anon_flag = extract_int(entargs, 2);
3503 format_type = extract_int(entargs, 3);
3504 extract_token(subject, entargs, 4, '|', sizeof subject);
3505 extract_token(newusername, entargs, 5, '|', sizeof newusername);
3506 do_confirm = extract_int(entargs, 6);
3507 extract_token(cc, entargs, 7, '|', sizeof cc);
3508 extract_token(bcc, entargs, 8, '|', sizeof bcc);
3509 switch(CC->room.QRdefaultview) {
3512 extract_token(supplied_euid, entargs, 9, '|', sizeof supplied_euid);
3515 supplied_euid[0] = 0;
3518 extract_token(newuseremail, entargs, 10, '|', sizeof newuseremail);
3520 /* first check to make sure the request is valid. */
3522 err = CtdlDoIHavePermissionToPostInThisRoom(errmsg, sizeof errmsg, NULL, POST_LOGGED_IN);
3525 cprintf("%d %s\n", err, errmsg);
3529 /* Check some other permission type things. */
3531 if (IsEmptyStr(newusername)) {
3532 strcpy(newusername, CC->user.fullname);
3534 if ( (CC->user.axlevel < 6)
3535 && (strcasecmp(newusername, CC->user.fullname))
3536 && (strcasecmp(newusername, CC->cs_inet_fn))
3538 cprintf("%d You don't have permission to author messages as '%s'.\n",
3539 ERROR + HIGHER_ACCESS_REQUIRED,
3546 if (IsEmptyStr(newuseremail)) {
3547 newuseremail_ok = 1;
3550 if (!IsEmptyStr(newuseremail)) {
3551 if (!strcasecmp(newuseremail, CC->cs_inet_email)) {
3552 newuseremail_ok = 1;
3554 else if (!IsEmptyStr(CC->cs_inet_other_emails)) {
3555 j = num_tokens(CC->cs_inet_other_emails, '|');
3556 for (i=0; i<j; ++i) {
3557 extract_token(buf, CC->cs_inet_other_emails, i, '|', sizeof buf);
3558 if (!strcasecmp(newuseremail, buf)) {
3559 newuseremail_ok = 1;
3565 if (!newuseremail_ok) {
3566 cprintf("%d You don't have permission to author messages as '%s'.\n",
3567 ERROR + HIGHER_ACCESS_REQUIRED,
3573 CC->cs_flags |= CS_POSTING;
3575 /* In mailbox rooms we have to behave a little differently --
3576 * make sure the user has specified at least one recipient. Then
3577 * validate the recipient(s). We do this for the Mail> room, as
3578 * well as any room which has the "Mailbox" view set.
3581 if ( ( (CC->room.QRflags & QR_MAILBOX) && (!strcasecmp(&CC->room.QRname[11], MAILROOM)) )
3582 || ( (CC->room.QRflags & QR_MAILBOX) && (CC->curr_view == VIEW_MAILBOX) )
3584 if (CC->user.axlevel < 2) {
3585 strcpy(recp, "sysop");
3590 valid_to = validate_recipients(recp, NULL, 0);
3591 if (valid_to->num_error > 0) {
3592 cprintf("%d %s\n", ERROR + NO_SUCH_USER, valid_to->errormsg);
3593 free_recipients(valid_to);
3597 valid_cc = validate_recipients(cc, NULL, 0);
3598 if (valid_cc->num_error > 0) {
3599 cprintf("%d %s\n", ERROR + NO_SUCH_USER, valid_cc->errormsg);
3600 free_recipients(valid_to);
3601 free_recipients(valid_cc);
3605 valid_bcc = validate_recipients(bcc, NULL, 0);
3606 if (valid_bcc->num_error > 0) {
3607 cprintf("%d %s\n", ERROR + NO_SUCH_USER, valid_bcc->errormsg);
3608 free_recipients(valid_to);
3609 free_recipients(valid_cc);
3610 free_recipients(valid_bcc);
3614 /* Recipient required, but none were specified */
3615 if ( (valid_to->num_error < 0) && (valid_cc->num_error < 0) && (valid_bcc->num_error < 0) ) {
3616 free_recipients(valid_to);
3617 free_recipients(valid_cc);
3618 free_recipients(valid_bcc);
3619 cprintf("%d At least one recipient is required.\n", ERROR + NO_SUCH_USER);
3623 if (valid_to->num_internet + valid_cc->num_internet + valid_bcc->num_internet > 0) {
3624 if (CtdlCheckInternetMailPermission(&CC->user)==0) {
3625 cprintf("%d You do not have permission "
3626 "to send Internet mail.\n",
3627 ERROR + HIGHER_ACCESS_REQUIRED);
3628 free_recipients(valid_to);
3629 free_recipients(valid_cc);
3630 free_recipients(valid_bcc);
3635 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)
3636 && (CC->user.axlevel < 4) ) {
3637 cprintf("%d Higher access required for network mail.\n",
3638 ERROR + HIGHER_ACCESS_REQUIRED);
3639 free_recipients(valid_to);
3640 free_recipients(valid_cc);
3641 free_recipients(valid_bcc);
3645 if ((RESTRICT_INTERNET == 1)
3646 && (valid_to->num_internet + valid_cc->num_internet + valid_bcc->num_internet > 0)
3647 && ((CC->user.flags & US_INTERNET) == 0)
3648 && (!CC->internal_pgm)) {
3649 cprintf("%d You don't have access to Internet mail.\n",
3650 ERROR + HIGHER_ACCESS_REQUIRED);
3651 free_recipients(valid_to);
3652 free_recipients(valid_cc);
3653 free_recipients(valid_bcc);
3659 /* Is this a room which has anonymous-only or anonymous-option? */
3660 anonymous = MES_NORMAL;
3661 if (CC->room.QRflags & QR_ANONONLY) {
3662 anonymous = MES_ANONONLY;
3664 if (CC->room.QRflags & QR_ANONOPT) {
3665 if (anon_flag == 1) { /* only if the user requested it */
3666 anonymous = MES_ANONOPT;
3670 if ((CC->room.QRflags & QR_MAILBOX) == 0) {
3674 /* Recommend to the client that the use of a message subject is
3675 * strongly recommended in this room, if either the SUBJECTREQ flag
3676 * is set, or if there is one or more Internet email recipients.
3678 if (CC->room.QRflags2 & QR2_SUBJECTREQ) subject_required = 1;
3679 if (valid_to) if (valid_to->num_internet > 0) subject_required = 1;
3680 if (valid_cc) if (valid_cc->num_internet > 0) subject_required = 1;
3681 if (valid_bcc) if (valid_bcc->num_internet > 0) subject_required = 1;
3683 /* If we're only checking the validity of the request, return
3684 * success without creating the message.
3687 cprintf("%d %s|%d\n", CIT_OK,
3688 ((valid_to != NULL) ? valid_to->display_recp : ""),
3690 free_recipients(valid_to);
3691 free_recipients(valid_cc);
3692 free_recipients(valid_bcc);
3696 /* We don't need these anymore because we'll do it differently below */
3697 free_recipients(valid_to);
3698 free_recipients(valid_cc);
3699 free_recipients(valid_bcc);
3701 /* Read in the message from the client. */
3703 cprintf("%d send message\n", START_CHAT_MODE);
3705 cprintf("%d send message\n", SEND_LISTING);
3708 msg = CtdlMakeMessage(&CC->user, recp, cc,
3709 CC->room.QRname, anonymous, format_type,
3710 newusername, newuseremail, subject,
3711 ((!IsEmptyStr(supplied_euid)) ? supplied_euid : NULL),
3714 /* Put together one big recipients struct containing to/cc/bcc all in
3715 * one. This is for the envelope.
3717 char *all_recps = malloc(SIZ * 3);
3718 strcpy(all_recps, recp);
3719 if (!IsEmptyStr(cc)) {
3720 if (!IsEmptyStr(all_recps)) {
3721 strcat(all_recps, ",");
3723 strcat(all_recps, cc);
3725 if (!IsEmptyStr(bcc)) {
3726 if (!IsEmptyStr(all_recps)) {
3727 strcat(all_recps, ",");
3729 strcat(all_recps, bcc);
3731 if (!IsEmptyStr(all_recps)) {
3732 valid = validate_recipients(all_recps, NULL, 0);
3740 msgnum = CtdlSubmitMsg(msg, valid, "");
3743 cprintf("%ld\n", msgnum);
3745 cprintf("Message accepted.\n");
3748 cprintf("Internal error.\n");
3750 if (msg->cm_fields['E'] != NULL) {
3751 cprintf("%s\n", msg->cm_fields['E']);
3758 CtdlFreeMessage(msg);
3760 if (valid != NULL) {
3761 free_recipients(valid);
3769 * API function to delete messages which match a set of criteria
3770 * (returns the actual number of messages deleted)
3772 int CtdlDeleteMessages(char *room_name, /* which room */
3773 long *dmsgnums, /* array of msg numbers to be deleted */
3774 int num_dmsgnums, /* number of msgs to be deleted, or 0 for "any" */
3775 char *content_type /* or "" for any. regular expressions expected. */
3778 struct ctdlroom qrbuf;
3779 struct cdbdata *cdbfr;
3780 long *msglist = NULL;
3781 long *dellist = NULL;
3784 int num_deleted = 0;
3786 struct MetaData smi;
3789 int need_to_free_re = 0;
3791 if (content_type) if (!IsEmptyStr(content_type)) {
3792 regcomp(&re, content_type, 0);
3793 need_to_free_re = 1;
3795 lprintf(CTDL_DEBUG, "CtdlDeleteMessages(%s, %d msgs, %s)\n",
3796 room_name, num_dmsgnums, content_type);
3798 /* get room record, obtaining a lock... */
3799 if (lgetroom(&qrbuf, room_name) != 0) {
3800 lprintf(CTDL_ERR, "CtdlDeleteMessages(): Room <%s> not found\n",
3802 if (need_to_free_re) regfree(&re);
3803 return (0); /* room not found */
3805 cdbfr = cdb_fetch(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long));
3807 if (cdbfr != NULL) {
3808 dellist = malloc(cdbfr->len);
3809 msglist = (long *) cdbfr->ptr;
3810 cdbfr->ptr = NULL; /* CtdlDeleteMessages() now owns this memory */
3811 num_msgs = cdbfr->len / sizeof(long);
3815 for (i = 0; i < num_msgs; ++i) {
3818 /* Set/clear a bit for each criterion */
3820 /* 0 messages in the list or a null list means that we are
3821 * interested in deleting any messages which meet the other criteria.
3823 if ((num_dmsgnums == 0) || (dmsgnums == NULL)) {
3824 delete_this |= 0x01;
3827 for (j=0; j<num_dmsgnums; ++j) {
3828 if (msglist[i] == dmsgnums[j]) {
3829 delete_this |= 0x01;
3834 if (IsEmptyStr(content_type)) {
3835 delete_this |= 0x02;
3837 GetMetaData(&smi, msglist[i]);
3838 if (regexec(&re, smi.meta_content_type, 1, &pm, 0) == 0) {
3839 delete_this |= 0x02;
3843 /* Delete message only if all bits are set */
3844 if (delete_this == 0x03) {
3845 dellist[num_deleted++] = msglist[i];
3850 num_msgs = sort_msglist(msglist, num_msgs);
3851 cdb_store(CDB_MSGLISTS, &qrbuf.QRnumber, (int)sizeof(long),
3852 msglist, (int)(num_msgs * sizeof(long)));
3854 qrbuf.QRhighest = msglist[num_msgs - 1];
3858 /* Go through the messages we pulled out of the index, and decrement
3859 * their reference counts by 1. If this is the only room the message
3860 * was in, the reference count will reach zero and the message will
3861 * automatically be deleted from the database. We do this in a
3862 * separate pass because there might be plug-in hooks getting called,
3863 * and we don't want that happening during an S_ROOMS critical
3866 if (num_deleted) for (i=0; i<num_deleted; ++i) {
3867 PerformDeleteHooks(qrbuf.QRname, dellist[i]);
3868 AdjRefCount(dellist[i], -1);
3871 /* Now free the memory we used, and go away. */
3872 if (msglist != NULL) free(msglist);
3873 if (dellist != NULL) free(dellist);
3874 lprintf(CTDL_DEBUG, "%d message(s) deleted.\n", num_deleted);
3875 if (need_to_free_re) regfree(&re);
3876 return (num_deleted);
3882 * Check whether the current user has permission to delete messages from
3883 * the current room (returns 1 for yes, 0 for no)
3885 int CtdlDoIHavePermissionToDeleteMessagesFromThisRoom(void) {
3887 CtdlRoomAccess(&CC->room, &CC->user, &ra, NULL);
3888 if (ra & UA_DELETEALLOWED) return(1);
3896 * Delete message from current room
3898 void cmd_dele(char *args)
3907 extract_token(msgset, args, 0, '|', sizeof msgset);
3908 num_msgs = num_tokens(msgset, ',');
3910 cprintf("%d Nothing to do.\n", CIT_OK);
3914 if (CtdlDoIHavePermissionToDeleteMessagesFromThisRoom() == 0) {
3915 cprintf("%d Higher access required.\n",
3916 ERROR + HIGHER_ACCESS_REQUIRED);
3921 * Build our message set to be moved/copied
3923 msgs = malloc(num_msgs * sizeof(long));
3924 for (i=0; i<num_msgs; ++i) {
3925 extract_token(msgtok, msgset, i, ',', sizeof msgtok);
3926 msgs[i] = atol(msgtok);
3929 num_deleted = CtdlDeleteMessages(CC->room.QRname, msgs, num_msgs, "");
3933 cprintf("%d %d message%s deleted.\n", CIT_OK,
3934 num_deleted, ((num_deleted != 1) ? "s" : ""));
3936 cprintf("%d Message not found.\n", ERROR + MESSAGE_NOT_FOUND);
3942 * Back end API function for moves and deletes (multiple messages)
3944 int CtdlCopyMsgsToRoom(long *msgnums, int num_msgs, char *dest) {
3947 err = CtdlSaveMsgPointersInRoom(dest, msgnums, num_msgs, 1, NULL);
3948 if (err != 0) return(err);
3957 * move or copy a message to another room
3959 void cmd_move(char *args)
3966 char targ[ROOMNAMELEN];
3967 struct ctdlroom qtemp;
3974 extract_token(msgset, args, 0, '|', sizeof msgset);
3975 num_msgs = num_tokens(msgset, ',');
3977 cprintf("%d Nothing to do.\n", CIT_OK);
3981 extract_token(targ, args, 1, '|', sizeof targ);
3982 convert_room_name_macros(targ, sizeof targ);
3983 targ[ROOMNAMELEN - 1] = 0;
3984 is_copy = extract_int(args, 2);
3986 if (getroom(&qtemp, targ) != 0) {
3987 cprintf("%d '%s' does not exist.\n",
3988 ERROR + ROOM_NOT_FOUND, targ);
3992 getuser(&CC->user, CC->curr_user);
3993 CtdlRoomAccess(&qtemp, &CC->user, &ra, NULL);
3995 /* Check for permission to perform this operation.
3996 * Remember: "CC->room" is source, "qtemp" is target.
4000 /* Aides can move/copy */
4001 if (CC->user.axlevel >= 6) permit = 1;
4003 /* Room aides can move/copy */
4004 if (CC->user.usernum == CC->room.QRroomaide) permit = 1;
4006 /* Permit move/copy from personal rooms */
4007 if ((CC->room.QRflags & QR_MAILBOX)
4008 && (qtemp.QRflags & QR_MAILBOX)) permit = 1;
4010 /* Permit only copy from public to personal room */
4012 && (!(CC->room.QRflags & QR_MAILBOX))
4013 && (qtemp.QRflags & QR_MAILBOX)) permit = 1;
4015 /* Permit message removal from collaborative delete rooms */
4016 if (CC->room.QRflags2 & QR2_COLLABDEL) permit = 1;
4018 /* Users allowed to post into the target room may move into it too. */
4019 if ((CC->room.QRflags & QR_MAILBOX) &&
4020 (qtemp.QRflags & UA_POSTALLOWED)) permit = 1;
4022 /* User must have access to target room */
4023 if (!(ra & UA_KNOWN)) permit = 0;
4026 cprintf("%d Higher access required.\n",
4027 ERROR + HIGHER_ACCESS_REQUIRED);
4032 * Build our message set to be moved/copied
4034 msgs = malloc(num_msgs * sizeof(long));
4035 for (i=0; i<num_msgs; ++i) {
4036 extract_token(msgtok, msgset, i, ',', sizeof msgtok);
4037 msgs[i] = atol(msgtok);
4043 err = CtdlCopyMsgsToRoom(msgs, num_msgs, targ);
4045 cprintf("%d Cannot store message(s) in %s: error %d\n",
4051 /* Now delete the message from the source room,
4052 * if this is a 'move' rather than a 'copy' operation.
4055 CtdlDeleteMessages(CC->room.QRname, msgs, num_msgs, "");
4059 cprintf("%d Message(s) %s.\n", CIT_OK, (is_copy ? "copied" : "moved") );
4065 * GetMetaData() - Get the supplementary record for a message
4067 void GetMetaData(struct MetaData *smibuf, long msgnum)
4070 struct cdbdata *cdbsmi;
4073 memset(smibuf, 0, sizeof(struct MetaData));
4074 smibuf->meta_msgnum = msgnum;
4075 smibuf->meta_refcount = 1; /* Default reference count is 1 */
4077 /* Use the negative of the message number for its supp record index */
4078 TheIndex = (0L - msgnum);
4080 cdbsmi = cdb_fetch(CDB_MSGMAIN, &TheIndex, sizeof(long));
4081 if (cdbsmi == NULL) {
4082 return; /* record not found; go with defaults */
4084 memcpy(smibuf, cdbsmi->ptr,
4085 ((cdbsmi->len > sizeof(struct MetaData)) ?
4086 sizeof(struct MetaData) : cdbsmi->len));
4093 * PutMetaData() - (re)write supplementary record for a message
4095 void PutMetaData(struct MetaData *smibuf)
4099 /* Use the negative of the message number for the metadata db index */
4100 TheIndex = (0L - smibuf->meta_msgnum);
4102 cdb_store(CDB_MSGMAIN,
4103 &TheIndex, (int)sizeof(long),
4104 smibuf, (int)sizeof(struct MetaData));
4109 * AdjRefCount - submit an adjustment to the reference count for a message.
4110 * (These are just queued -- we actually process them later.)
4112 void AdjRefCount(long msgnum, int incr)
4114 struct arcq new_arcq;
4116 begin_critical_section(S_SUPPMSGMAIN);
4117 if (arcfp == NULL) {
4118 arcfp = fopen(file_arcq, "ab+");
4120 end_critical_section(S_SUPPMSGMAIN);
4122 /* msgnum < 0 means that we're trying to close the file */
4124 lprintf(CTDL_DEBUG, "Closing the AdjRefCount queue file\n");
4125 begin_critical_section(S_SUPPMSGMAIN);
4126 if (arcfp != NULL) {
4130 end_critical_section(S_SUPPMSGMAIN);
4135 * If we can't open the queue, perform the operation synchronously.
4137 if (arcfp == NULL) {
4138 TDAP_AdjRefCount(msgnum, incr);
4142 new_arcq.arcq_msgnum = msgnum;
4143 new_arcq.arcq_delta = incr;
4144 fwrite(&new_arcq, sizeof(struct arcq), 1, arcfp);
4152 * TDAP_ProcessAdjRefCountQueue()
4154 * Process the queue of message count adjustments that was created by calls
4155 * to AdjRefCount() ... by reading the queue and calling TDAP_AdjRefCount()
4156 * for each one. This should be an "off hours" operation.
4158 int TDAP_ProcessAdjRefCountQueue(void)
4160 char file_arcq_temp[PATH_MAX];
4163 struct arcq arcq_rec;
4164 int num_records_processed = 0;
4166 snprintf(file_arcq_temp, sizeof file_arcq_temp, "%s2", file_arcq);
4168 begin_critical_section(S_SUPPMSGMAIN);
4169 if (arcfp != NULL) {
4174 r = link(file_arcq, file_arcq_temp);
4176 lprintf(CTDL_CRIT, "%s: %s\n", file_arcq_temp, strerror(errno));
4177 end_critical_section(S_SUPPMSGMAIN);
4178 return(num_records_processed);
4182 end_critical_section(S_SUPPMSGMAIN);
4184 fp = fopen(file_arcq_temp, "rb");
4186 lprintf(CTDL_CRIT, "%s: %s\n", file_arcq_temp, strerror(errno));
4187 return(num_records_processed);
4190 while (fread(&arcq_rec, sizeof(struct arcq), 1, fp) == 1) {
4191 TDAP_AdjRefCount(arcq_rec.arcq_msgnum, arcq_rec.arcq_delta);
4192 ++num_records_processed;
4196 r = unlink(file_arcq_temp);
4198 lprintf(CTDL_CRIT, "%s: %s\n", file_arcq_temp, strerror(errno));
4201 return(num_records_processed);
4207 * TDAP_AdjRefCount - adjust the reference count for a message.
4208 * This one does it "for real" because it's called by
4209 * the autopurger function that processes the queue
4210 * created by AdjRefCount(). If a message's reference
4211 * count becomes zero, we also delete the message from
4212 * disk and de-index it.
4214 void TDAP_AdjRefCount(long msgnum, int incr)
4217 struct MetaData smi;
4220 /* This is a *tight* critical section; please keep it that way, as
4221 * it may get called while nested in other critical sections.
4222 * Complicating this any further will surely cause deadlock!
4224 begin_critical_section(S_SUPPMSGMAIN);
4225 GetMetaData(&smi, msgnum);
4226 smi.meta_refcount += incr;
4228 end_critical_section(S_SUPPMSGMAIN);
4229 lprintf(CTDL_DEBUG, "msg %ld ref count delta %d, is now %d\n",
4230 msgnum, incr, smi.meta_refcount);
4232 /* If the reference count is now zero, delete the message
4233 * (and its supplementary record as well).
4235 if (smi.meta_refcount == 0) {
4236 lprintf(CTDL_DEBUG, "Deleting message <%ld>\n", msgnum);
4238 /* Call delete hooks with NULL room to show it has gone altogether */
4239 PerformDeleteHooks(NULL, msgnum);
4241 /* Remove from message base */
4243 cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long));
4244 cdb_delete(CDB_BIGMSGS, &delnum, (int)sizeof(long));
4246 /* Remove metadata record */
4247 delnum = (0L - msgnum);
4248 cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long));
4254 * Write a generic object to this room
4256 * Note: this could be much more efficient. Right now we use two temporary
4257 * files, and still pull the message into memory as with all others.
4259 void CtdlWriteObject(char *req_room, /* Room to stuff it in */
4260 char *content_type, /* MIME type of this object */
4261 char *tempfilename, /* Where to fetch it from */
4262 struct ctdluser *is_mailbox, /* Mailbox room? */
4263 int is_binary, /* Is encoding necessary? */
4264 int is_unique, /* Del others of this type? */
4265 unsigned int flags /* Internal save flags */
4270 struct ctdlroom qrbuf;
4271 char roomname[ROOMNAMELEN];
4272 struct CtdlMessage *msg;
4274 char *raw_message = NULL;
4275 char *encoded_message = NULL;
4276 off_t raw_length = 0;
4278 if (is_mailbox != NULL) {
4279 MailboxName(roomname, sizeof roomname, is_mailbox, req_room);
4282 safestrncpy(roomname, req_room, sizeof(roomname));
4285 fp = fopen(tempfilename, "rb");
4287 lprintf(CTDL_CRIT, "Cannot open %s: %s\n",
4288 tempfilename, strerror(errno));
4291 fseek(fp, 0L, SEEK_END);
4292 raw_length = ftell(fp);
4294 lprintf(CTDL_DEBUG, "Raw length is %ld\n", (long)raw_length);
4296 raw_message = malloc((size_t)raw_length + 2);
4297 fread(raw_message, (size_t)raw_length, 1, fp);
4301 encoded_message = malloc((size_t)
4302 (((raw_length * 134) / 100) + 4096 ) );
4305 encoded_message = malloc((size_t)(raw_length + 4096));
4308 sprintf(encoded_message, "Content-type: %s\n", content_type);
4311 sprintf(&encoded_message[strlen(encoded_message)],
4312 "Content-transfer-encoding: base64\n\n"
4316 sprintf(&encoded_message[strlen(encoded_message)],
4317 "Content-transfer-encoding: 7bit\n\n"
4323 &encoded_message[strlen(encoded_message)],
4330 raw_message[raw_length] = 0;
4332 &encoded_message[strlen(encoded_message)],
4340 lprintf(CTDL_DEBUG, "Allocating\n");
4341 msg = malloc(sizeof(struct CtdlMessage));
4342 memset(msg, 0, sizeof(struct CtdlMessage));
4343 msg->cm_magic = CTDLMESSAGE_MAGIC;
4344 msg->cm_anon_type = MES_NORMAL;
4345 msg->cm_format_type = 4;
4346 msg->cm_fields['A'] = strdup(CC->user.fullname);
4347 msg->cm_fields['O'] = strdup(req_room);
4348 msg->cm_fields['N'] = strdup(config.c_nodename);
4349 msg->cm_fields['H'] = strdup(config.c_humannode);
4350 msg->cm_flags = flags;
4352 msg->cm_fields['M'] = encoded_message;
4354 /* Create the requested room if we have to. */
4355 if (getroom(&qrbuf, roomname) != 0) {
4356 create_room(roomname,
4357 ( (is_mailbox != NULL) ? 5 : 3 ),
4358 "", 0, 1, 0, VIEW_BBS);
4360 /* If the caller specified this object as unique, delete all
4361 * other objects of this type that are currently in the room.
4364 lprintf(CTDL_DEBUG, "Deleted %d other msgs of this type\n",
4365 CtdlDeleteMessages(roomname, NULL, 0, content_type)
4368 /* Now write the data */
4369 CtdlSubmitMsg(msg, NULL, roomname);
4370 CtdlFreeMessage(msg);
4378 void CtdlGetSysConfigBackend(long msgnum, void *userdata) {
4379 config_msgnum = msgnum;
4383 char *CtdlGetSysConfig(char *sysconfname) {
4384 char hold_rm[ROOMNAMELEN];
4387 struct CtdlMessage *msg;
4390 strcpy(hold_rm, CC->room.QRname);
4391 if (getroom(&CC->room, SYSCONFIGROOM) != 0) {
4392 getroom(&CC->room, hold_rm);
4397 /* We want the last (and probably only) config in this room */
4398 begin_critical_section(S_CONFIG);
4399 config_msgnum = (-1L);
4400 CtdlForEachMessage(MSGS_LAST, 1, NULL, sysconfname, NULL,
4401 CtdlGetSysConfigBackend, NULL);
4402 msgnum = config_msgnum;
4403 end_critical_section(S_CONFIG);
4409 msg = CtdlFetchMessage(msgnum, 1);
4411 conf = strdup(msg->cm_fields['M']);
4412 CtdlFreeMessage(msg);
4419 getroom(&CC->room, hold_rm);
4421 if (conf != NULL) do {
4422 extract_token(buf, conf, 0, '\n', sizeof buf);
4423 strcpy(conf, &conf[strlen(buf)+1]);
4424 } while ( (!IsEmptyStr(conf)) && (!IsEmptyStr(buf)) );
4429 void CtdlPutSysConfig(char *sysconfname, char *sysconfdata) {
4430 char temp[PATH_MAX];
4433 CtdlMakeTempFileName(temp, sizeof temp);
4435 fp = fopen(temp, "w");
4436 if (fp == NULL) return;
4437 fprintf(fp, "%s", sysconfdata);
4440 /* this handy API function does all the work for us */
4441 CtdlWriteObject(SYSCONFIGROOM, sysconfname, temp, NULL, 0, 1, 0);
4447 * Determine whether a given Internet address belongs to the current user
4449 int CtdlIsMe(char *addr, int addr_buf_len)
4451 struct recptypes *recp;
4454 recp = validate_recipients(addr, NULL, 0);
4455 if (recp == NULL) return(0);
4457 if (recp->num_local == 0) {
4458 free_recipients(recp);
4462 for (i=0; i<recp->num_local; ++i) {
4463 extract_token(addr, recp->recp_local, i, '|', addr_buf_len);
4464 if (!strcasecmp(addr, CC->user.fullname)) {
4465 free_recipients(recp);
4470 free_recipients(recp);
4476 * Citadel protocol command to do the same
4478 void cmd_isme(char *argbuf) {
4481 if (CtdlAccessCheck(ac_logged_in)) return;
4482 extract_token(addr, argbuf, 0, '|', sizeof addr);
4484 if (CtdlIsMe(addr, sizeof addr)) {
4485 cprintf("%d %s\n", CIT_OK, addr);
4488 cprintf("%d Not you.\n", ERROR + ILLEGAL_VALUE);