4 * Implements the message store.
14 #if TIME_WITH_SYS_TIME
15 # include <sys/time.h>
19 # include <sys/time.h>
34 #include "serv_extensions.h"
38 #include "sysdep_decls.h"
39 #include "citserver.h"
46 #include "mime_parser.h"
49 #include "internet_addressing.h"
50 #include "serv_fulltext.h"
52 #include "euidindex.h"
55 struct addresses_to_be_filed *atbf = NULL;
58 * This really belongs in serv_network.c, but I don't know how to export
59 * symbols between modules.
61 struct FilterList *filterlist = NULL;
65 * These are the four-character field headers we use when outputting
66 * messages in Citadel format (as opposed to RFC822 format).
69 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
70 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
71 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
72 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
73 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
74 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
75 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
76 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
103 * This function is self explanatory.
104 * (What can I say, I'm in a weird mood today...)
106 void remove_any_whitespace_to_the_left_or_right_of_at_symbol(char *name)
110 for (i = 0; i < strlen(name); ++i) {
111 if (name[i] == '@') {
112 while (isspace(name[i - 1]) && i > 0) {
113 strcpy(&name[i - 1], &name[i]);
116 while (isspace(name[i + 1])) {
117 strcpy(&name[i + 1], &name[i + 2]);
125 * Aliasing for network mail.
126 * (Error messages have been commented out, because this is a server.)
128 int alias(char *name)
129 { /* process alias and routing info for mail */
132 char aaa[SIZ], bbb[SIZ];
133 char *ignetcfg = NULL;
134 char *ignetmap = NULL;
141 remove_any_whitespace_to_the_left_or_right_of_at_symbol(name);
142 stripallbut(name, '<', '>');
150 "mail.aliases", "r");
152 fp = fopen("/dev/null", "r");
159 while (fgets(aaa, sizeof aaa, fp) != NULL) {
160 while (isspace(name[0]))
161 strcpy(name, &name[1]);
162 aaa[strlen(aaa) - 1] = 0;
164 for (a = 0; a < strlen(aaa); ++a) {
166 strcpy(bbb, &aaa[a + 1]);
170 if (!strcasecmp(name, aaa))
175 /* Hit the Global Address Book */
176 if (CtdlDirectoryLookup(aaa, name, sizeof aaa) == 0) {
180 lprintf(CTDL_INFO, "Mail is being forwarded to %s\n", name);
182 /* Change "user @ xxx" to "user" if xxx is an alias for this host */
183 for (a=0; a<strlen(name); ++a) {
184 if (name[a] == '@') {
185 if (CtdlHostAlias(&name[a+1]) == hostalias_localhost) {
187 lprintf(CTDL_INFO, "Changed to <%s>\n", name);
192 /* determine local or remote type, see citadel.h */
193 at = haschar(name, '@');
194 if (at == 0) return(MES_LOCAL); /* no @'s - local address */
195 if (at > 1) return(MES_ERROR); /* >1 @'s - invalid address */
196 remove_any_whitespace_to_the_left_or_right_of_at_symbol(name);
198 /* figure out the delivery mode */
199 extract_token(node, name, 1, '@', sizeof node);
201 /* If there are one or more dots in the nodename, we assume that it
202 * is an FQDN and will attempt SMTP delivery to the Internet.
204 if (haschar(node, '.') > 0) {
205 return(MES_INTERNET);
208 /* Otherwise we look in the IGnet maps for a valid Citadel node.
209 * Try directly-connected nodes first...
211 ignetcfg = CtdlGetSysConfig(IGNETCFG);
212 for (i=0; i<num_tokens(ignetcfg, '\n'); ++i) {
213 extract_token(buf, ignetcfg, i, '\n', sizeof buf);
214 extract_token(testnode, buf, 0, '|', sizeof testnode);
215 if (!strcasecmp(node, testnode)) {
223 * Then try nodes that are two or more hops away.
225 ignetmap = CtdlGetSysConfig(IGNETMAP);
226 for (i=0; i<num_tokens(ignetmap, '\n'); ++i) {
227 extract_token(buf, ignetmap, i, '\n', sizeof buf);
228 extract_token(testnode, buf, 0, '|', sizeof testnode);
229 if (!strcasecmp(node, testnode)) {
236 /* If we get to this point it's an invalid node name */
251 "/citadel.control", "r");
253 lprintf(CTDL_CRIT, "Cannot open citadel.control: %s\n",
257 fread((char *) &CitControl, sizeof(struct CitControl), 1, fp);
263 * Back end for the MSGS command: output message number only.
265 void simple_listing(long msgnum, void *userdata)
267 cprintf("%ld\n", msgnum);
273 * Back end for the MSGS command: output header summary.
275 void headers_listing(long msgnum, void *userdata)
277 struct CtdlMessage *msg;
279 msg = CtdlFetchMessage(msgnum, 0);
281 cprintf("%ld|0|||||\n", msgnum);
285 cprintf("%ld|%s|%s|%s|%s|%s|\n",
287 (msg->cm_fields['T'] ? msg->cm_fields['T'] : "0"),
288 (msg->cm_fields['A'] ? msg->cm_fields['A'] : ""),
289 (msg->cm_fields['N'] ? msg->cm_fields['N'] : ""),
290 (msg->cm_fields['F'] ? msg->cm_fields['F'] : ""),
291 (msg->cm_fields['U'] ? msg->cm_fields['U'] : "")
293 CtdlFreeMessage(msg);
298 /* Determine if a given message matches the fields in a message template.
299 * Return 0 for a successful match.
301 int CtdlMsgCmp(struct CtdlMessage *msg, struct CtdlMessage *template) {
304 /* If there aren't any fields in the template, all messages will
307 if (template == NULL) return(0);
309 /* Null messages are bogus. */
310 if (msg == NULL) return(1);
312 for (i='A'; i<='Z'; ++i) {
313 if (template->cm_fields[i] != NULL) {
314 if (msg->cm_fields[i] == NULL) {
317 if (strcasecmp(msg->cm_fields[i],
318 template->cm_fields[i])) return 1;
322 /* All compares succeeded: we have a match! */
329 * Retrieve the "seen" message list for the current room.
331 void CtdlGetSeen(char *buf, int which_set) {
334 /* Learn about the user and room in question */
335 CtdlGetRelationship(&vbuf, &CC->user, &CC->room);
337 if (which_set == ctdlsetseen_seen)
338 safestrncpy(buf, vbuf.v_seen, SIZ);
339 if (which_set == ctdlsetseen_answered)
340 safestrncpy(buf, vbuf.v_answered, SIZ);
346 * Manipulate the "seen msgs" string (or other message set strings)
348 void CtdlSetSeen(long *target_msgnums, int num_target_msgnums,
349 int target_setting, int which_set,
350 struct ctdluser *which_user, struct ctdlroom *which_room) {
351 struct cdbdata *cdbfr;
363 char *is_set; /* actually an array of booleans */
366 char setstr[SIZ], lostr[SIZ], histr[SIZ];
369 lprintf(CTDL_DEBUG, "CtdlSetSeen(%d msgs starting with %ld, %d, %d)\n",
370 num_target_msgnums, target_msgnums[0],
371 target_setting, which_set);
373 /* Learn about the user and room in question */
374 CtdlGetRelationship(&vbuf,
375 ((which_user != NULL) ? which_user : &CC->user),
376 ((which_room != NULL) ? which_room : &CC->room)
379 /* Load the message list */
380 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long));
382 msglist = (long *) cdbfr->ptr;
383 cdbfr->ptr = NULL; /* CtdlSetSeen() now owns this memory */
384 num_msgs = cdbfr->len / sizeof(long);
387 return; /* No messages at all? No further action. */
390 is_set = malloc(num_msgs * sizeof(char));
391 memset(is_set, 0, (num_msgs * sizeof(char)) );
393 /* Decide which message set we're manipulating */
395 case ctdlsetseen_seen:
396 safestrncpy(vset, vbuf.v_seen, sizeof vset);
398 case ctdlsetseen_answered:
399 safestrncpy(vset, vbuf.v_answered, sizeof vset);
403 /* lprintf(CTDL_DEBUG, "before optimize: %s\n", vset); */
405 /* Translate the existing sequence set into an array of booleans */
406 num_sets = num_tokens(vset, ',');
407 for (s=0; s<num_sets; ++s) {
408 extract_token(setstr, vset, s, ',', sizeof setstr);
410 extract_token(lostr, setstr, 0, ':', sizeof lostr);
411 if (num_tokens(setstr, ':') >= 2) {
412 extract_token(histr, setstr, 1, ':', sizeof histr);
413 if (!strcmp(histr, "*")) {
414 snprintf(histr, sizeof histr, "%ld", LONG_MAX);
418 strcpy(histr, lostr);
423 for (i = 0; i < num_msgs; ++i) {
424 if ((msglist[i] >= lo) && (msglist[i] <= hi)) {
430 /* Now translate the array of booleans back into a sequence set */
435 for (i=0; i<num_msgs; ++i) {
437 is_seen = is_set[i]; /* Default to existing setting */
439 for (k=0; k<num_target_msgnums; ++k) {
440 if (msglist[i] == target_msgnums[k]) {
441 is_seen = target_setting;
446 if (lo < 0L) lo = msglist[i];
450 if ( ((is_seen == 0) && (was_seen == 1))
451 || ((is_seen == 1) && (i == num_msgs-1)) ) {
453 /* begin trim-o-matic code */
456 while ( (strlen(vset) + 20) > sizeof vset) {
457 remove_token(vset, 0, ',');
459 if (j--) break; /* loop no more than 9 times */
461 if ( (trimming) && (which_set == ctdlsetseen_seen) ) {
465 snprintf(lostr, sizeof lostr,
466 "1:%ld,%s", t, vset);
467 safestrncpy(vset, lostr, sizeof vset);
469 /* end trim-o-matic code */
477 snprintf(&vset[tmp], (sizeof vset) - tmp,
481 snprintf(&vset[tmp], (sizeof vset) - tmp,
490 /* Decide which message set we're manipulating */
492 case ctdlsetseen_seen:
493 safestrncpy(vbuf.v_seen, vset, sizeof vbuf.v_seen);
495 case ctdlsetseen_answered:
496 safestrncpy(vbuf.v_answered, vset,
497 sizeof vbuf.v_answered);
502 /* lprintf(CTDL_DEBUG, " after optimize: %s\n", vset); */
504 CtdlSetRelationship(&vbuf,
505 ((which_user != NULL) ? which_user : &CC->user),
506 ((which_room != NULL) ? which_room : &CC->room)
512 * API function to perform an operation for each qualifying message in the
513 * current room. (Returns the number of messages processed.)
515 int CtdlForEachMessage(int mode, long ref,
517 struct CtdlMessage *compare,
518 void (*CallBack) (long, void *),
524 struct cdbdata *cdbfr;
525 long *msglist = NULL;
527 int num_processed = 0;
530 struct CtdlMessage *msg;
533 int printed_lastold = 0;
535 /* Learn about the user and room in question */
537 getuser(&CC->user, CC->curr_user);
538 CtdlGetRelationship(&vbuf, &CC->user, &CC->room);
540 /* Load the message list */
541 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long));
543 msglist = (long *) cdbfr->ptr;
544 cdbfr->ptr = NULL; /* CtdlForEachMessage() now owns this memory */
545 num_msgs = cdbfr->len / sizeof(long);
548 return 0; /* No messages at all? No further action. */
553 * Now begin the traversal.
555 if (num_msgs > 0) for (a = 0; a < num_msgs; ++a) {
557 /* If the caller is looking for a specific MIME type, filter
558 * out all messages which are not of the type requested.
560 if (content_type != NULL) if (strlen(content_type) > 0) {
562 /* This call to GetMetaData() sits inside this loop
563 * so that we only do the extra database read per msg
564 * if we need to. Doing the extra read all the time
565 * really kills the server. If we ever need to use
566 * metadata for another search criterion, we need to
567 * move the read somewhere else -- but still be smart
568 * enough to only do the read if the caller has
569 * specified something that will need it.
571 GetMetaData(&smi, msglist[a]);
573 if (strcasecmp(smi.meta_content_type, content_type)) {
579 num_msgs = sort_msglist(msglist, num_msgs);
581 /* If a template was supplied, filter out the messages which
582 * don't match. (This could induce some delays!)
585 if (compare != NULL) {
586 for (a = 0; a < num_msgs; ++a) {
587 msg = CtdlFetchMessage(msglist[a], 1);
589 if (CtdlMsgCmp(msg, compare)) {
592 CtdlFreeMessage(msg);
600 * Now iterate through the message list, according to the
601 * criteria supplied by the caller.
604 for (a = 0; a < num_msgs; ++a) {
605 thismsg = msglist[a];
606 if (mode == MSGS_ALL) {
610 is_seen = is_msg_in_sequence_set(
611 vbuf.v_seen, thismsg);
612 if (is_seen) lastold = thismsg;
618 || ((mode == MSGS_OLD) && (is_seen))
619 || ((mode == MSGS_NEW) && (!is_seen))
620 || ((mode == MSGS_LAST) && (a >= (num_msgs - ref)))
621 || ((mode == MSGS_FIRST) && (a < ref))
622 || ((mode == MSGS_GT) && (thismsg > ref))
623 || ((mode == MSGS_EQ) && (thismsg == ref))
626 if ((mode == MSGS_NEW) && (CC->user.flags & US_LASTOLD) && (lastold > 0L) && (printed_lastold == 0) && (!is_seen)) {
628 CallBack(lastold, userdata);
632 if (CallBack) CallBack(thismsg, userdata);
636 free(msglist); /* Clean up */
637 return num_processed;
643 * cmd_msgs() - get list of message #'s in this room
644 * implements the MSGS server command using CtdlForEachMessage()
646 void cmd_msgs(char *cmdbuf)
655 int with_template = 0;
656 struct CtdlMessage *template = NULL;
657 int with_headers = 0;
659 extract_token(which, cmdbuf, 0, '|', sizeof which);
660 cm_ref = extract_int(cmdbuf, 1);
661 with_template = extract_int(cmdbuf, 2);
662 with_headers = extract_int(cmdbuf, 3);
666 if (!strncasecmp(which, "OLD", 3))
668 else if (!strncasecmp(which, "NEW", 3))
670 else if (!strncasecmp(which, "FIRST", 5))
672 else if (!strncasecmp(which, "LAST", 4))
674 else if (!strncasecmp(which, "GT", 2))
677 if ((!(CC->logged_in)) && (!(CC->internal_pgm))) {
678 cprintf("%d not logged in\n", ERROR + NOT_LOGGED_IN);
684 cprintf("%d Send template then receive message list\n",
686 template = (struct CtdlMessage *)
687 malloc(sizeof(struct CtdlMessage));
688 memset(template, 0, sizeof(struct CtdlMessage));
689 while(client_getln(buf, sizeof buf), strcmp(buf,"000")) {
690 extract_token(tfield, buf, 0, '|', sizeof tfield);
691 extract_token(tvalue, buf, 1, '|', sizeof tvalue);
692 for (i='A'; i<='Z'; ++i) if (msgkeys[i]!=NULL) {
693 if (!strcasecmp(tfield, msgkeys[i])) {
694 template->cm_fields[i] =
702 cprintf("%d \n", LISTING_FOLLOWS);
705 CtdlForEachMessage(mode,
709 (with_headers ? headers_listing : simple_listing),
712 if (template != NULL) CtdlFreeMessage(template);
720 * help_subst() - support routine for help file viewer
722 void help_subst(char *strbuf, char *source, char *dest)
727 while (p = pattern2(strbuf, source), (p >= 0)) {
728 strcpy(workbuf, &strbuf[p + strlen(source)]);
729 strcpy(&strbuf[p], dest);
730 strcat(strbuf, workbuf);
735 void do_help_subst(char *buffer)
739 help_subst(buffer, "^nodename", config.c_nodename);
740 help_subst(buffer, "^humannode", config.c_humannode);
741 help_subst(buffer, "^fqdn", config.c_fqdn);
742 help_subst(buffer, "^username", CC->user.fullname);
743 snprintf(buf2, sizeof buf2, "%ld", CC->user.usernum);
744 help_subst(buffer, "^usernum", buf2);
745 help_subst(buffer, "^sysadm", config.c_sysadm);
746 help_subst(buffer, "^variantname", CITADEL);
747 snprintf(buf2, sizeof buf2, "%d", config.c_maxsessions);
748 help_subst(buffer, "^maxsessions", buf2);
749 help_subst(buffer, "^bbsdir", CTDLDIR);
755 * memfmout() - Citadel text formatter and paginator.
756 * Although the original purpose of this routine was to format
757 * text to the reader's screen width, all we're really using it
758 * for here is to format text out to 80 columns before sending it
759 * to the client. The client software may reformat it again.
762 int width, /* screen width to use */
763 char *mptr, /* where are we going to get our text from? */
764 char subst, /* nonzero if we should do substitutions */
765 char *nl) /* string to terminate lines with */
777 c = 1; /* c is the current pos */
781 while (ch = *mptr, ((ch != 0) && (strlen(buffer) < 126))) {
783 buffer[strlen(buffer) + 1] = 0;
784 buffer[strlen(buffer)] = ch;
787 if (buffer[0] == '^')
788 do_help_subst(buffer);
790 buffer[strlen(buffer) + 1] = 0;
792 strcpy(buffer, &buffer[1]);
800 if (((ch == 13) || (ch == 10)) && (old != 13) && (old != 10))
802 if (((old == 13) || (old == 10)) && (isspace(real))) {
810 if (((strlen(aaa) + c) > (width - 5)) && (strlen(aaa) > (width - 5))) {
811 cprintf("%s%s", nl, aaa);
820 if ((strlen(aaa) + c) > (width - 5)) {
829 if ((ch == 13) || (ch == 10)) {
830 cprintf("%s%s", aaa, nl);
837 cprintf("%s%s", aaa, nl);
843 * Callback function for mime parser that simply lists the part
845 void list_this_part(char *name, char *filename, char *partnum, char *disp,
846 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
851 ma = (struct ma_info *)cbuserdata;
852 if (ma->is_ma == 0) {
853 cprintf("part=%s|%s|%s|%s|%s|%ld\n",
854 name, filename, partnum, disp, cbtype, (long)length);
859 * Callback function for multipart prefix
861 void list_this_pref(char *name, char *filename, char *partnum, char *disp,
862 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
867 ma = (struct ma_info *)cbuserdata;
868 if (!strcasecmp(cbtype, "multipart/alternative")) {
872 if (ma->is_ma == 0) {
873 cprintf("pref=%s|%s\n", partnum, cbtype);
878 * Callback function for multipart sufffix
880 void list_this_suff(char *name, char *filename, char *partnum, char *disp,
881 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
886 ma = (struct ma_info *)cbuserdata;
887 if (ma->is_ma == 0) {
888 cprintf("suff=%s|%s\n", partnum, cbtype);
890 if (!strcasecmp(cbtype, "multipart/alternative")) {
897 * Callback function for mime parser that opens a section for downloading
899 void mime_download(char *name, char *filename, char *partnum, char *disp,
900 void *content, char *cbtype, char *cbcharset, size_t length,
901 char *encoding, void *cbuserdata)
904 /* Silently go away if there's already a download open... */
905 if (CC->download_fp != NULL)
908 /* ...or if this is not the desired section */
909 if (strcasecmp(CC->download_desired_section, partnum))
912 CC->download_fp = tmpfile();
913 if (CC->download_fp == NULL)
916 fwrite(content, length, 1, CC->download_fp);
917 fflush(CC->download_fp);
918 rewind(CC->download_fp);
920 OpenCmdResult(filename, cbtype);
926 * Load a message from disk into memory.
927 * This is used by CtdlOutputMsg() and other fetch functions.
929 * NOTE: Caller is responsible for freeing the returned CtdlMessage struct
930 * using the CtdlMessageFree() function.
932 struct CtdlMessage *CtdlFetchMessage(long msgnum, int with_body)
934 struct cdbdata *dmsgtext;
935 struct CtdlMessage *ret = NULL;
939 cit_uint8_t field_header;
941 lprintf(CTDL_DEBUG, "CtdlFetchMessage(%ld, %d)\n", msgnum, with_body);
943 dmsgtext = cdb_fetch(CDB_MSGMAIN, &msgnum, sizeof(long));
944 if (dmsgtext == NULL) {
947 mptr = dmsgtext->ptr;
948 upper_bound = mptr + dmsgtext->len;
950 /* Parse the three bytes that begin EVERY message on disk.
951 * The first is always 0xFF, the on-disk magic number.
952 * The second is the anonymous/public type byte.
953 * The third is the format type byte (vari, fixed, or MIME).
958 "Message %ld appears to be corrupted.\n",
963 ret = (struct CtdlMessage *) malloc(sizeof(struct CtdlMessage));
964 memset(ret, 0, sizeof(struct CtdlMessage));
966 ret->cm_magic = CTDLMESSAGE_MAGIC;
967 ret->cm_anon_type = *mptr++; /* Anon type byte */
968 ret->cm_format_type = *mptr++; /* Format type byte */
971 * The rest is zero or more arbitrary fields. Load them in.
972 * We're done when we encounter either a zero-length field or
973 * have just processed the 'M' (message text) field.
976 if (mptr >= upper_bound) {
979 field_header = *mptr++;
980 ret->cm_fields[field_header] = strdup(mptr);
982 while (*mptr++ != 0); /* advance to next field */
984 } while ((mptr < upper_bound) && (field_header != 'M'));
988 /* Always make sure there's something in the msg text field. If
989 * it's NULL, the message text is most likely stored separately,
990 * so go ahead and fetch that. Failing that, just set a dummy
991 * body so other code doesn't barf.
993 if ( (ret->cm_fields['M'] == NULL) && (with_body) ) {
994 dmsgtext = cdb_fetch(CDB_BIGMSGS, &msgnum, sizeof(long));
995 if (dmsgtext != NULL) {
996 ret->cm_fields['M'] = strdup(dmsgtext->ptr);
1000 if (ret->cm_fields['M'] == NULL) {
1001 ret->cm_fields['M'] = strdup("<no text>\n");
1004 /* Perform "before read" hooks (aborting if any return nonzero) */
1005 if (PerformMessageHooks(ret, EVT_BEFOREREAD) > 0) {
1006 CtdlFreeMessage(ret);
1015 * Returns 1 if the supplied pointer points to a valid Citadel message.
1016 * If the pointer is NULL or the magic number check fails, returns 0.
1018 int is_valid_message(struct CtdlMessage *msg) {
1021 if ((msg->cm_magic) != CTDLMESSAGE_MAGIC) {
1022 lprintf(CTDL_WARNING, "is_valid_message() -- self-check failed\n");
1030 * 'Destructor' for struct CtdlMessage
1032 void CtdlFreeMessage(struct CtdlMessage *msg)
1036 if (is_valid_message(msg) == 0) return;
1038 for (i = 0; i < 256; ++i)
1039 if (msg->cm_fields[i] != NULL) {
1040 free(msg->cm_fields[i]);
1043 msg->cm_magic = 0; /* just in case */
1049 * Pre callback function for multipart/alternative
1051 * NOTE: this differs from the standard behavior for a reason. Normally when
1052 * displaying multipart/alternative you want to show the _last_ usable
1053 * format in the message. Here we show the _first_ one, because it's
1054 * usually text/plain. Since this set of functions is designed for text
1055 * output to non-MIME-aware clients, this is the desired behavior.
1058 void fixed_output_pre(char *name, char *filename, char *partnum, char *disp,
1059 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
1064 ma = (struct ma_info *)cbuserdata;
1065 lprintf(CTDL_DEBUG, "fixed_output_pre() type=<%s>\n", cbtype);
1066 if (!strcasecmp(cbtype, "multipart/alternative")) {
1074 * Post callback function for multipart/alternative
1076 void fixed_output_post(char *name, char *filename, char *partnum, char *disp,
1077 void *content, char *cbtype, char *cbcharset, size_t length,
1078 char *encoding, void *cbuserdata)
1082 ma = (struct ma_info *)cbuserdata;
1083 lprintf(CTDL_DEBUG, "fixed_output_post() type=<%s>\n", cbtype);
1084 if (!strcasecmp(cbtype, "multipart/alternative")) {
1092 * Inline callback function for mime parser that wants to display text
1094 void fixed_output(char *name, char *filename, char *partnum, char *disp,
1095 void *content, char *cbtype, char *cbcharset, size_t length,
1096 char *encoding, void *cbuserdata)
1103 ma = (struct ma_info *)cbuserdata;
1106 "fixed_output() part %s: %s (%s) (%ld bytes)\n",
1107 partnum, filename, cbtype, (long)length);
1110 * If we're in the middle of a multipart/alternative scope and
1111 * we've already printed another section, skip this one.
1113 if ( (ma->is_ma == 1) && (ma->did_print == 1) ) {
1114 lprintf(CTDL_DEBUG, "Skipping part %s (%s)\n",
1120 if ( (!strcasecmp(cbtype, "text/plain"))
1121 || (strlen(cbtype)==0) ) {
1124 client_write(wptr, length);
1125 if (wptr[length-1] != '\n') {
1130 else if (!strcasecmp(cbtype, "text/html")) {
1131 ptr = html_to_ascii(content, length, 80, 0);
1133 client_write(ptr, wlen);
1134 if (ptr[wlen-1] != '\n') {
1139 else if (PerformFixedOutputHooks(cbtype, content, length)) {
1140 /* above function returns nonzero if it handled the part */
1142 else if (strncasecmp(cbtype, "multipart/", 10)) {
1143 cprintf("Part %s: %s (%s) (%ld bytes)\r\n",
1144 partnum, filename, cbtype, (long)length);
1149 * The client is elegant and sophisticated and wants to be choosy about
1150 * MIME content types, so figure out which multipart/alternative part
1151 * we're going to send.
1153 void choose_preferred(char *name, char *filename, char *partnum, char *disp,
1154 void *content, char *cbtype, char *cbcharset, size_t length,
1155 char *encoding, void *cbuserdata)
1161 ma = (struct ma_info *)cbuserdata;
1163 if (ma->is_ma > 0) {
1164 for (i=0; i<num_tokens(CC->preferred_formats, '|'); ++i) {
1165 extract_token(buf, CC->preferred_formats, i, '|', sizeof buf);
1166 if (!strcasecmp(buf, cbtype)) {
1167 if (num_tokens(partnum, '.') < 3) {
1168 safestrncpy(ma->chosen_part, partnum, sizeof ma->chosen_part);
1176 * Now that we've chosen our preferred part, output it.
1178 void output_preferred(char *name, char *filename, char *partnum, char *disp,
1179 void *content, char *cbtype, char *cbcharset, size_t length,
1180 char *encoding, void *cbuserdata)
1184 int add_newline = 0;
1188 ma = (struct ma_info *)cbuserdata;
1190 /* This is not the MIME part you're looking for... */
1191 if (strcasecmp(partnum, ma->chosen_part)) return;
1193 /* If the content-type of this part is in our preferred formats
1194 * list, we can simply output it verbatim.
1196 for (i=0; i<num_tokens(CC->preferred_formats, '|'); ++i) {
1197 extract_token(buf, CC->preferred_formats, i, '|', sizeof buf);
1198 if (!strcasecmp(buf, cbtype)) {
1199 /* Yeah! Go! W00t!! */
1201 text_content = (char *)content;
1202 if (text_content[length-1] != '\n') {
1206 cprintf("Content-type: %s", cbtype);
1207 if (strlen(cbcharset) > 0) {
1208 cprintf("; charset=%s", cbcharset);
1210 cprintf("\nContent-length: %d\n",
1211 (int)(length + add_newline) );
1212 if (strlen(encoding) > 0) {
1213 cprintf("Content-transfer-encoding: %s\n", encoding);
1216 cprintf("Content-transfer-encoding: 7bit\n");
1219 client_write(content, length);
1220 if (add_newline) cprintf("\n");
1225 /* No translations required or possible: output as text/plain */
1226 cprintf("Content-type: text/plain\n\n");
1227 fixed_output(name, filename, partnum, disp, content, cbtype, cbcharset,
1228 length, encoding, cbuserdata);
1233 char desired_section[64];
1240 * Callback function for
1242 void extract_encapsulated_message(char *name, char *filename, char *partnum, char *disp,
1243 void *content, char *cbtype, char *cbcharset, size_t length,
1244 char *encoding, void *cbuserdata)
1246 struct encapmsg *encap;
1248 encap = (struct encapmsg *)cbuserdata;
1250 /* Only proceed if this is the desired section... */
1251 if (!strcasecmp(encap->desired_section, partnum)) {
1252 encap->msglen = length;
1253 encap->msg = malloc(length + 2);
1254 memcpy(encap->msg, content, length);
1264 * Get a message off disk. (returns om_* values found in msgbase.h)
1267 int CtdlOutputMsg(long msg_num, /* message number (local) to fetch */
1268 int mode, /* how would you like that message? */
1269 int headers_only, /* eschew the message body? */
1270 int do_proto, /* do Citadel protocol responses? */
1271 int crlf, /* Use CRLF newlines instead of LF? */
1272 char *section /* NULL or a message/rfc822 section */
1274 struct CtdlMessage *TheMessage = NULL;
1275 int retcode = om_no_such_msg;
1276 struct encapmsg encap;
1278 lprintf(CTDL_DEBUG, "CtdlOutputMsg() msgnum=%ld, mode=%d, section=%s\n",
1280 (section ? section : "<>")
1283 if ((!(CC->logged_in)) && (!(CC->internal_pgm))) {
1284 if (do_proto) cprintf("%d Not logged in.\n",
1285 ERROR + NOT_LOGGED_IN);
1286 return(om_not_logged_in);
1289 /* FIXME: check message id against msglist for this room */
1292 * Fetch the message from disk. If we're in any sort of headers
1293 * only mode, request that we don't even bother loading the body
1296 if ( (headers_only == HEADERS_FAST) || (headers_only == HEADERS_ONLY) ) {
1297 TheMessage = CtdlFetchMessage(msg_num, 0);
1300 TheMessage = CtdlFetchMessage(msg_num, 1);
1303 if (TheMessage == NULL) {
1304 if (do_proto) cprintf("%d Can't locate msg %ld on disk\n",
1305 ERROR + MESSAGE_NOT_FOUND, msg_num);
1306 return(om_no_such_msg);
1309 /* Here is the weird form of this command, to process only an
1310 * encapsulated message/rfc822 section.
1312 if (section) if (strlen(section)>0) if (strcmp(section, "0")) {
1313 memset(&encap, 0, sizeof encap);
1314 safestrncpy(encap.desired_section, section, sizeof encap.desired_section);
1315 mime_parser(TheMessage->cm_fields['M'],
1317 *extract_encapsulated_message,
1318 NULL, NULL, (void *)&encap, 0
1320 CtdlFreeMessage(TheMessage);
1324 encap.msg[encap.msglen] = 0;
1325 TheMessage = convert_internet_message(encap.msg);
1326 encap.msg = NULL; /* no free() here, TheMessage owns it now */
1328 /* Now we let it fall through to the bottom of this
1329 * function, because TheMessage now contains the
1330 * encapsulated message instead of the top-level
1331 * message. Isn't that neat?
1336 if (do_proto) cprintf("%d msg %ld has no part %s\n",
1337 ERROR + MESSAGE_NOT_FOUND, msg_num, section);
1338 retcode = om_no_such_msg;
1343 /* Ok, output the message now */
1344 retcode = CtdlOutputPreLoadedMsg(
1346 headers_only, do_proto, crlf);
1347 CtdlFreeMessage(TheMessage);
1354 * Get a message off disk. (returns om_* values found in msgbase.h)
1357 int CtdlOutputPreLoadedMsg(
1358 struct CtdlMessage *TheMessage,
1359 int mode, /* how would you like that message? */
1360 int headers_only, /* eschew the message body? */
1361 int do_proto, /* do Citadel protocol responses? */
1362 int crlf /* Use CRLF newlines instead of LF? */
1368 char display_name[256];
1370 char *nl; /* newline string */
1372 int subject_found = 0;
1375 /* Buffers needed for RFC822 translation. These are all filled
1376 * using functions that are bounds-checked, and therefore we can
1377 * make them substantially smaller than SIZ.
1385 char datestamp[100];
1387 lprintf(CTDL_DEBUG, "CtdlOutputPreLoadedMsg(TheMessage=%s, %d, %d, %d, %d\n",
1388 ((TheMessage == NULL) ? "NULL" : "not null"),
1389 mode, headers_only, do_proto, crlf);
1391 strcpy(mid, "unknown");
1392 nl = (crlf ? "\r\n" : "\n");
1394 if (!is_valid_message(TheMessage)) {
1396 "ERROR: invalid preloaded message for output\n");
1397 return(om_no_such_msg);
1400 /* Are we downloading a MIME component? */
1401 if (mode == MT_DOWNLOAD) {
1402 if (TheMessage->cm_format_type != FMT_RFC822) {
1404 cprintf("%d This is not a MIME message.\n",
1405 ERROR + ILLEGAL_VALUE);
1406 } else if (CC->download_fp != NULL) {
1407 if (do_proto) cprintf(
1408 "%d You already have a download open.\n",
1409 ERROR + RESOURCE_BUSY);
1411 /* Parse the message text component */
1412 mptr = TheMessage->cm_fields['M'];
1413 mime_parser(mptr, NULL, *mime_download, NULL, NULL, NULL, 0);
1414 /* If there's no file open by this time, the requested
1415 * section wasn't found, so print an error
1417 if (CC->download_fp == NULL) {
1418 if (do_proto) cprintf(
1419 "%d Section %s not found.\n",
1420 ERROR + FILE_NOT_FOUND,
1421 CC->download_desired_section);
1424 return((CC->download_fp != NULL) ? om_ok : om_mime_error);
1427 /* now for the user-mode message reading loops */
1428 if (do_proto) cprintf("%d msg:\n", LISTING_FOLLOWS);
1430 /* Does the caller want to skip the headers? */
1431 if (headers_only == HEADERS_NONE) goto START_TEXT;
1433 /* Tell the client which format type we're using. */
1434 if ( (mode == MT_CITADEL) && (do_proto) ) {
1435 cprintf("type=%d\n", TheMessage->cm_format_type);
1438 /* nhdr=yes means that we're only displaying headers, no body */
1439 if ( (TheMessage->cm_anon_type == MES_ANONONLY)
1440 && (mode == MT_CITADEL)
1443 cprintf("nhdr=yes\n");
1446 /* begin header processing loop for Citadel message format */
1448 if ((mode == MT_CITADEL) || (mode == MT_MIME)) {
1450 safestrncpy(display_name, "<unknown>", sizeof display_name);
1451 if (TheMessage->cm_fields['A']) {
1452 strcpy(buf, TheMessage->cm_fields['A']);
1453 if (TheMessage->cm_anon_type == MES_ANONONLY) {
1454 safestrncpy(display_name, "****", sizeof display_name);
1456 else if (TheMessage->cm_anon_type == MES_ANONOPT) {
1457 safestrncpy(display_name, "anonymous", sizeof display_name);
1460 safestrncpy(display_name, buf, sizeof display_name);
1462 if ((is_room_aide())
1463 && ((TheMessage->cm_anon_type == MES_ANONONLY)
1464 || (TheMessage->cm_anon_type == MES_ANONOPT))) {
1465 size_t tmp = strlen(display_name);
1466 snprintf(&display_name[tmp],
1467 sizeof display_name - tmp,
1472 /* Don't show Internet address for users on the
1473 * local Citadel network.
1476 if (TheMessage->cm_fields['N'] != NULL)
1477 if (strlen(TheMessage->cm_fields['N']) > 0)
1478 if (haschar(TheMessage->cm_fields['N'], '.') == 0) {
1482 /* Now spew the header fields in the order we like them. */
1483 safestrncpy(allkeys, FORDER, sizeof allkeys);
1484 for (i=0; i<strlen(allkeys); ++i) {
1485 k = (int) allkeys[i];
1487 if ( (TheMessage->cm_fields[k] != NULL)
1488 && (msgkeys[k] != NULL) ) {
1490 if (do_proto) cprintf("%s=%s\n",
1494 else if ((k == 'F') && (suppress_f)) {
1497 /* Masquerade display name if needed */
1499 if (do_proto) cprintf("%s=%s\n",
1501 TheMessage->cm_fields[k]
1510 /* begin header processing loop for RFC822 transfer format */
1515 strcpy(snode, NODENAME);
1516 strcpy(lnode, HUMANNODE);
1517 if (mode == MT_RFC822) {
1518 for (i = 0; i < 256; ++i) {
1519 if (TheMessage->cm_fields[i]) {
1520 mptr = TheMessage->cm_fields[i];
1523 safestrncpy(luser, mptr, sizeof luser);
1524 safestrncpy(suser, mptr, sizeof suser);
1526 else if (i == 'Y') {
1527 cprintf("CC: %s%s", mptr, nl);
1529 else if (i == 'U') {
1530 cprintf("Subject: %s%s", mptr, nl);
1534 safestrncpy(mid, mptr, sizeof mid);
1536 safestrncpy(lnode, mptr, sizeof lnode);
1538 safestrncpy(fuser, mptr, sizeof fuser);
1539 /* else if (i == 'O')
1540 cprintf("X-Citadel-Room: %s%s",
1543 safestrncpy(snode, mptr, sizeof snode);
1545 cprintf("To: %s%s", mptr, nl);
1546 else if (i == 'T') {
1547 datestring(datestamp, sizeof datestamp,
1548 atol(mptr), DATESTRING_RFC822);
1549 cprintf("Date: %s%s", datestamp, nl);
1553 if (subject_found == 0) {
1554 cprintf("Subject: (no subject)%s", nl);
1558 for (i=0; i<strlen(suser); ++i) {
1559 suser[i] = tolower(suser[i]);
1560 if (!isalnum(suser[i])) suser[i]='_';
1563 if (mode == MT_RFC822) {
1564 if (!strcasecmp(snode, NODENAME)) {
1565 safestrncpy(snode, FQDN, sizeof snode);
1568 /* Construct a fun message id */
1569 cprintf("Message-ID: <%s", mid);
1570 if (strchr(mid, '@')==NULL) {
1571 cprintf("@%s", snode);
1575 if (!is_room_aide() && (TheMessage->cm_anon_type == MES_ANONONLY)) {
1576 cprintf("From: \"----\" <x@x.org>%s", nl);
1578 else if (!is_room_aide() && (TheMessage->cm_anon_type == MES_ANONOPT)) {
1579 cprintf("From: \"anonymous\" <x@x.org>%s", nl);
1581 else if (strlen(fuser) > 0) {
1582 cprintf("From: \"%s\" <%s>%s", luser, fuser, nl);
1585 cprintf("From: \"%s\" <%s@%s>%s", luser, suser, snode, nl);
1588 cprintf("Organization: %s%s", lnode, nl);
1590 /* Blank line signifying RFC822 end-of-headers */
1591 if (TheMessage->cm_format_type != FMT_RFC822) {
1596 /* end header processing loop ... at this point, we're in the text */
1598 if (headers_only == HEADERS_FAST) goto DONE;
1599 mptr = TheMessage->cm_fields['M'];
1601 /* Tell the client about the MIME parts in this message */
1602 if (TheMessage->cm_format_type == FMT_RFC822) {
1603 if ( (mode == MT_CITADEL) || (mode == MT_MIME) ) {
1604 memset(&ma, 0, sizeof(struct ma_info));
1605 mime_parser(mptr, NULL,
1606 (do_proto ? *list_this_part : NULL),
1607 (do_proto ? *list_this_pref : NULL),
1608 (do_proto ? *list_this_suff : NULL),
1611 else if (mode == MT_RFC822) { /* unparsed RFC822 dump */
1612 char *start_of_text = NULL;
1613 start_of_text = strstr(mptr, "\n\r\n");
1614 if (start_of_text == NULL) start_of_text = strstr(mptr, "\n\n");
1615 if (start_of_text == NULL) start_of_text = mptr;
1617 start_of_text = strstr(start_of_text, "\n");
1619 while (ch=*mptr, ch!=0) {
1623 else switch(headers_only) {
1625 if (mptr >= start_of_text) {
1626 if (ch == 10) cprintf("%s", nl);
1627 else cprintf("%c", ch);
1631 if (mptr < start_of_text) {
1632 if (ch == 10) cprintf("%s", nl);
1633 else cprintf("%c", ch);
1637 if (ch == 10) cprintf("%s", nl);
1638 else cprintf("%c", ch);
1647 if (headers_only == HEADERS_ONLY) {
1651 /* signify start of msg text */
1652 if ( (mode == MT_CITADEL) || (mode == MT_MIME) ) {
1653 if (do_proto) cprintf("text\n");
1656 /* If the format type on disk is 1 (fixed-format), then we want
1657 * everything to be output completely literally ... regardless of
1658 * what message transfer format is in use.
1660 if (TheMessage->cm_format_type == FMT_FIXED) {
1661 if (mode == MT_MIME) {
1662 cprintf("Content-type: text/plain\n\n");
1665 while (ch = *mptr++, ch > 0) {
1668 if ((ch == 10) || (strlen(buf) > 250)) {
1669 cprintf("%s%s", buf, nl);
1672 buf[strlen(buf) + 1] = 0;
1673 buf[strlen(buf)] = ch;
1676 if (strlen(buf) > 0)
1677 cprintf("%s%s", buf, nl);
1680 /* If the message on disk is format 0 (Citadel vari-format), we
1681 * output using the formatter at 80 columns. This is the final output
1682 * form if the transfer format is RFC822, but if the transfer format
1683 * is Citadel proprietary, it'll still work, because the indentation
1684 * for new paragraphs is correct and the client will reformat the
1685 * message to the reader's screen width.
1687 if (TheMessage->cm_format_type == FMT_CITADEL) {
1688 if (mode == MT_MIME) {
1689 cprintf("Content-type: text/x-citadel-variformat\n\n");
1691 memfmout(80, mptr, 0, nl);
1694 /* If the message on disk is format 4 (MIME), we've gotta hand it
1695 * off to the MIME parser. The client has already been told that
1696 * this message is format 1 (fixed format), so the callback function
1697 * we use will display those parts as-is.
1699 if (TheMessage->cm_format_type == FMT_RFC822) {
1700 memset(&ma, 0, sizeof(struct ma_info));
1702 if (mode == MT_MIME) {
1703 strcpy(ma.chosen_part, "1");
1704 mime_parser(mptr, NULL,
1705 *choose_preferred, *fixed_output_pre,
1706 *fixed_output_post, (void *)&ma, 0);
1707 mime_parser(mptr, NULL,
1708 *output_preferred, NULL, NULL, (void *)&ma, 0);
1711 mime_parser(mptr, NULL,
1712 *fixed_output, *fixed_output_pre,
1713 *fixed_output_post, (void *)&ma, 0);
1718 DONE: /* now we're done */
1719 if (do_proto) cprintf("000\n");
1726 * display a message (mode 0 - Citadel proprietary)
1728 void cmd_msg0(char *cmdbuf)
1731 int headers_only = HEADERS_ALL;
1733 msgid = extract_long(cmdbuf, 0);
1734 headers_only = extract_int(cmdbuf, 1);
1736 CtdlOutputMsg(msgid, MT_CITADEL, headers_only, 1, 0, NULL);
1742 * display a message (mode 2 - RFC822)
1744 void cmd_msg2(char *cmdbuf)
1747 int headers_only = HEADERS_ALL;
1749 msgid = extract_long(cmdbuf, 0);
1750 headers_only = extract_int(cmdbuf, 1);
1752 CtdlOutputMsg(msgid, MT_RFC822, headers_only, 1, 1, NULL);
1758 * display a message (mode 3 - IGnet raw format - internal programs only)
1760 void cmd_msg3(char *cmdbuf)
1763 struct CtdlMessage *msg;
1766 if (CC->internal_pgm == 0) {
1767 cprintf("%d This command is for internal programs only.\n",
1768 ERROR + HIGHER_ACCESS_REQUIRED);
1772 msgnum = extract_long(cmdbuf, 0);
1773 msg = CtdlFetchMessage(msgnum, 1);
1775 cprintf("%d Message %ld not found.\n",
1776 ERROR + MESSAGE_NOT_FOUND, msgnum);
1780 serialize_message(&smr, msg);
1781 CtdlFreeMessage(msg);
1784 cprintf("%d Unable to serialize message\n",
1785 ERROR + INTERNAL_ERROR);
1789 cprintf("%d %ld\n", BINARY_FOLLOWS, (long)smr.len);
1790 client_write((char *)smr.ser, (int)smr.len);
1797 * Display a message using MIME content types
1799 void cmd_msg4(char *cmdbuf)
1804 msgid = extract_long(cmdbuf, 0);
1805 extract_token(section, cmdbuf, 1, '|', sizeof section);
1806 CtdlOutputMsg(msgid, MT_MIME, 0, 1, 0, (section[0] ? section : NULL) );
1812 * Client tells us its preferred message format(s)
1814 void cmd_msgp(char *cmdbuf)
1816 safestrncpy(CC->preferred_formats, cmdbuf,
1817 sizeof(CC->preferred_formats));
1818 cprintf("%d ok\n", CIT_OK);
1823 * Open a component of a MIME message as a download file
1825 void cmd_opna(char *cmdbuf)
1828 char desired_section[128];
1830 msgid = extract_long(cmdbuf, 0);
1831 extract_token(desired_section, cmdbuf, 1, '|', sizeof desired_section);
1832 safestrncpy(CC->download_desired_section, desired_section,
1833 sizeof CC->download_desired_section);
1834 CtdlOutputMsg(msgid, MT_DOWNLOAD, 0, 1, 1, NULL);
1839 * Save a message pointer into a specified room
1840 * (Returns 0 for success, nonzero for failure)
1841 * roomname may be NULL to use the current room
1843 * Note that the 'supplied_msg' field may be set to NULL, in which case
1844 * the message will be fetched from disk, by number, if we need to perform
1845 * replication checks. This adds an additional database read, so if the
1846 * caller already has the message in memory then it should be supplied.
1848 int CtdlSaveMsgPointerInRoom(char *roomname, long msgid, int do_repl_check,
1849 struct CtdlMessage *supplied_msg) {
1851 char hold_rm[ROOMNAMELEN];
1852 struct cdbdata *cdbfr;
1855 long highest_msg = 0L;
1856 struct CtdlMessage *msg = NULL;
1858 /*lprintf(CTDL_DEBUG,
1859 "CtdlSaveMsgPointerInRoom(room=%s, msgid=%ld, repl=%d)\n",
1860 roomname, msgid, do_repl_check);*/
1862 strcpy(hold_rm, CC->room.QRname);
1864 /* Now the regular stuff */
1865 if (lgetroom(&CC->room,
1866 ((roomname != NULL) ? roomname : CC->room.QRname) )
1868 lprintf(CTDL_ERR, "No such room <%s>\n", roomname);
1869 return(ERROR + ROOM_NOT_FOUND);
1872 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long));
1873 if (cdbfr == NULL) {
1877 msglist = (long *) cdbfr->ptr;
1878 cdbfr->ptr = NULL; /* CtdlSaveMsgPointerInRoom() now owns this memory */
1879 num_msgs = cdbfr->len / sizeof(long);
1883 /* Make sure the message doesn't already exist in this room. It
1884 * is absolutely taboo to have more than one reference to the same
1885 * message in a room.
1887 if (num_msgs > 0) for (i=0; i<num_msgs; ++i) {
1888 if (msglist[i] == msgid) {
1889 lputroom(&CC->room); /* unlock the room */
1890 getroom(&CC->room, hold_rm);
1892 return(ERROR + ALREADY_EXISTS);
1896 /* Now add the new message */
1898 msglist = realloc(msglist, (num_msgs * sizeof(long)));
1900 if (msglist == NULL) {
1901 lprintf(CTDL_ALERT, "ERROR: can't realloc message list!\n");
1903 msglist[num_msgs - 1] = msgid;
1905 /* Sort the message list, so all the msgid's are in order */
1906 num_msgs = sort_msglist(msglist, num_msgs);
1908 /* Determine the highest message number */
1909 highest_msg = msglist[num_msgs - 1];
1911 /* Write it back to disk. */
1912 cdb_store(CDB_MSGLISTS, &CC->room.QRnumber, (int)sizeof(long),
1913 msglist, (int)(num_msgs * sizeof(long)));
1915 /* Free up the memory we used. */
1918 /* Update the highest-message pointer and unlock the room. */
1919 CC->room.QRhighest = highest_msg;
1920 lputroom(&CC->room);
1922 /* Perform replication checks if necessary */
1923 if ( (DoesThisRoomNeedEuidIndexing(&CC->room)) && (do_repl_check) ) {
1924 if (supplied_msg != NULL) {
1928 msg = CtdlFetchMessage(msgid, 0);
1932 ReplicationChecks(msg);
1937 /* If the message has an Exclusive ID, index that... */
1939 if (msg->cm_fields['E'] != NULL) {
1940 index_message_by_euid(msg->cm_fields['E'],
1945 /* Free up the memory we may have allocated */
1946 if ( (msg != NULL) && (msg != supplied_msg) ) {
1947 CtdlFreeMessage(msg);
1950 /* Go back to the room we were in before we wandered here... */
1951 getroom(&CC->room, hold_rm);
1953 /* Bump the reference count for this message. */
1954 AdjRefCount(msgid, +1);
1956 /* Return success. */
1963 * Message base operation to save a new message to the message store
1964 * (returns new message number)
1966 * This is the back end for CtdlSubmitMsg() and should not be directly
1967 * called by server-side modules.
1970 long send_message(struct CtdlMessage *msg) {
1978 /* Get a new message number */
1979 newmsgid = get_new_message_number();
1980 snprintf(msgidbuf, sizeof msgidbuf, "%ld@%s", newmsgid, config.c_fqdn);
1982 /* Generate an ID if we don't have one already */
1983 if (msg->cm_fields['I']==NULL) {
1984 msg->cm_fields['I'] = strdup(msgidbuf);
1987 /* If the message is big, set its body aside for storage elsewhere */
1988 if (msg->cm_fields['M'] != NULL) {
1989 if (strlen(msg->cm_fields['M']) > BIGMSG) {
1991 holdM = msg->cm_fields['M'];
1992 msg->cm_fields['M'] = NULL;
1996 /* Serialize our data structure for storage in the database */
1997 serialize_message(&smr, msg);
2000 msg->cm_fields['M'] = holdM;
2004 cprintf("%d Unable to serialize message\n",
2005 ERROR + INTERNAL_ERROR);
2009 /* Write our little bundle of joy into the message base */
2010 if (cdb_store(CDB_MSGMAIN, &newmsgid, (int)sizeof(long),
2011 smr.ser, smr.len) < 0) {
2012 lprintf(CTDL_ERR, "Can't store message\n");
2016 cdb_store(CDB_BIGMSGS,
2026 /* Free the memory we used for the serialized message */
2029 /* Return the *local* message ID to the caller
2030 * (even if we're storing an incoming network message)
2038 * Serialize a struct CtdlMessage into the format used on disk and network.
2040 * This function loads up a "struct ser_ret" (defined in server.h) which
2041 * contains the length of the serialized message and a pointer to the
2042 * serialized message in memory. THE LATTER MUST BE FREED BY THE CALLER.
2044 void serialize_message(struct ser_ret *ret, /* return values */
2045 struct CtdlMessage *msg) /* unserialized msg */
2049 static char *forder = FORDER;
2051 if (is_valid_message(msg) == 0) return; /* self check */
2054 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL)
2055 ret->len = ret->len +
2056 strlen(msg->cm_fields[(int)forder[i]]) + 2;
2058 lprintf(CTDL_DEBUG, "serialize_message() calling malloc(%ld)\n", (long)ret->len);
2059 ret->ser = malloc(ret->len);
2060 if (ret->ser == NULL) {
2066 ret->ser[1] = msg->cm_anon_type;
2067 ret->ser[2] = msg->cm_format_type;
2070 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL) {
2071 ret->ser[wlen++] = (char)forder[i];
2072 strcpy((char *)&ret->ser[wlen], msg->cm_fields[(int)forder[i]]);
2073 wlen = wlen + strlen(msg->cm_fields[(int)forder[i]]) + 1;
2075 if (ret->len != wlen) lprintf(CTDL_ERR, "ERROR: len=%ld wlen=%ld\n",
2076 (long)ret->len, (long)wlen);
2084 * Check to see if any messages already exist in the current room which
2085 * carry the same Exclusive ID as this one. If any are found, delete them.
2087 void ReplicationChecks(struct CtdlMessage *msg) {
2088 long old_msgnum = (-1L);
2090 if (DoesThisRoomNeedEuidIndexing(&CC->room) == 0) return;
2092 lprintf(CTDL_DEBUG, "Performing replication checks in <%s>\n",
2095 /* No exclusive id? Don't do anything. */
2096 if (msg == NULL) return;
2097 if (msg->cm_fields['E'] == NULL) return;
2098 if (strlen(msg->cm_fields['E']) == 0) return;
2099 /*lprintf(CTDL_DEBUG, "Exclusive ID: <%s> for room <%s>\n",
2100 msg->cm_fields['E'], CC->room.QRname);*/
2102 old_msgnum = locate_message_by_euid(msg->cm_fields['E'], &CC->room);
2103 if (old_msgnum > 0L) {
2104 lprintf(CTDL_DEBUG, "ReplicationChecks() replacing message %ld\n", old_msgnum);
2105 CtdlDeleteMessages(CC->room.QRname, old_msgnum, "", 0);
2112 * Save a message to disk and submit it into the delivery system.
2114 long CtdlSubmitMsg(struct CtdlMessage *msg, /* message to save */
2115 struct recptypes *recps, /* recipients (if mail) */
2116 char *force /* force a particular room? */
2118 char submit_filename[128];
2119 char generated_timestamp[32];
2120 char hold_rm[ROOMNAMELEN];
2121 char actual_rm[ROOMNAMELEN];
2122 char force_room[ROOMNAMELEN];
2123 char content_type[SIZ]; /* We have to learn this */
2124 char recipient[SIZ];
2127 struct ctdluser userbuf;
2129 struct MetaData smi;
2130 FILE *network_fp = NULL;
2131 static int seqnum = 1;
2132 struct CtdlMessage *imsg = NULL;
2135 char *hold_R, *hold_D;
2136 char *collected_addresses = NULL;
2137 struct addresses_to_be_filed *aptr = NULL;
2139 lprintf(CTDL_DEBUG, "CtdlSubmitMsg() called\n");
2140 if (is_valid_message(msg) == 0) return(-1); /* self check */
2142 /* If this message has no timestamp, we take the liberty of
2143 * giving it one, right now.
2145 if (msg->cm_fields['T'] == NULL) {
2146 snprintf(generated_timestamp, sizeof generated_timestamp, "%ld", (long)time(NULL));
2147 msg->cm_fields['T'] = strdup(generated_timestamp);
2150 /* If this message has no path, we generate one.
2152 if (msg->cm_fields['P'] == NULL) {
2153 if (msg->cm_fields['A'] != NULL) {
2154 msg->cm_fields['P'] = strdup(msg->cm_fields['A']);
2155 for (a=0; a<strlen(msg->cm_fields['P']); ++a) {
2156 if (isspace(msg->cm_fields['P'][a])) {
2157 msg->cm_fields['P'][a] = ' ';
2162 msg->cm_fields['P'] = strdup("unknown");
2166 if (force == NULL) {
2167 strcpy(force_room, "");
2170 strcpy(force_room, force);
2173 /* Learn about what's inside, because it's what's inside that counts */
2174 if (msg->cm_fields['M'] == NULL) {
2175 lprintf(CTDL_ERR, "ERROR: attempt to save message with NULL body\n");
2179 switch (msg->cm_format_type) {
2181 strcpy(content_type, "text/x-citadel-variformat");
2184 strcpy(content_type, "text/plain");
2187 strcpy(content_type, "text/plain");
2188 mptr = bmstrcasestr(msg->cm_fields['M'], "Content-type: ");
2190 safestrncpy(content_type, &mptr[14],
2191 sizeof content_type);
2192 for (a = 0; a < strlen(content_type); ++a) {
2193 if ((content_type[a] == ';')
2194 || (content_type[a] == ' ')
2195 || (content_type[a] == 13)
2196 || (content_type[a] == 10)) {
2197 content_type[a] = 0;
2203 /* Goto the correct room */
2204 lprintf(CTDL_DEBUG, "Selected room %s\n", (recps) ? CC->room.QRname : SENTITEMS);
2205 strcpy(hold_rm, CC->room.QRname);
2206 strcpy(actual_rm, CC->room.QRname);
2207 if (recps != NULL) {
2208 strcpy(actual_rm, SENTITEMS);
2211 /* If the user is a twit, move to the twit room for posting */
2213 if (CC->user.axlevel == 2) {
2214 strcpy(hold_rm, actual_rm);
2215 strcpy(actual_rm, config.c_twitroom);
2216 lprintf(CTDL_DEBUG, "Diverting to twit room\n");
2220 /* ...or if this message is destined for Aide> then go there. */
2221 if (strlen(force_room) > 0) {
2222 strcpy(actual_rm, force_room);
2225 lprintf(CTDL_DEBUG, "Final selection: %s\n", actual_rm);
2226 if (strcasecmp(actual_rm, CC->room.QRname)) {
2227 /* getroom(&CC->room, actual_rm); */
2228 usergoto(actual_rm, 0, 1, NULL, NULL);
2232 * If this message has no O (room) field, generate one.
2234 if (msg->cm_fields['O'] == NULL) {
2235 msg->cm_fields['O'] = strdup(CC->room.QRname);
2238 /* Perform "before save" hooks (aborting if any return nonzero) */
2239 lprintf(CTDL_DEBUG, "Performing before-save hooks\n");
2240 if (PerformMessageHooks(msg, EVT_BEFORESAVE) > 0) return(-3);
2243 * If this message has an Exclusive ID, and the room is replication
2244 * checking enabled, then do replication checks.
2246 if (DoesThisRoomNeedEuidIndexing(&CC->room)) {
2247 ReplicationChecks(msg);
2250 /* Save it to disk */
2251 lprintf(CTDL_DEBUG, "Saving to disk\n");
2252 newmsgid = send_message(msg);
2253 if (newmsgid <= 0L) return(-5);
2255 /* Write a supplemental message info record. This doesn't have to
2256 * be a critical section because nobody else knows about this message
2259 lprintf(CTDL_DEBUG, "Creating MetaData record\n");
2260 memset(&smi, 0, sizeof(struct MetaData));
2261 smi.meta_msgnum = newmsgid;
2262 smi.meta_refcount = 0;
2263 safestrncpy(smi.meta_content_type, content_type,
2264 sizeof smi.meta_content_type);
2266 /* As part of the new metadata record, measure how
2267 * big this message will be when displayed as RFC822.
2268 * Both POP and IMAP use this, and it's best to just take the hit now
2269 * instead of having to potentially measure thousands of messages when
2270 * a mailbox is opened later.
2273 if (CC->redirect_buffer != NULL) {
2274 lprintf(CTDL_ALERT, "CC->redirect_buffer is not NULL during message submission!\n");
2277 CC->redirect_buffer = malloc(SIZ);
2278 CC->redirect_len = 0;
2279 CC->redirect_alloc = SIZ;
2280 CtdlOutputPreLoadedMsg(msg, MT_RFC822, HEADERS_ALL, 0, 1);
2281 smi.meta_rfc822_length = CC->redirect_len;
2282 free(CC->redirect_buffer);
2283 CC->redirect_buffer = NULL;
2284 CC->redirect_len = 0;
2285 CC->redirect_alloc = 0;
2289 /* Now figure out where to store the pointers */
2290 lprintf(CTDL_DEBUG, "Storing pointers\n");
2292 /* If this is being done by the networker delivering a private
2293 * message, we want to BYPASS saving the sender's copy (because there
2294 * is no local sender; it would otherwise go to the Trashcan).
2296 if ((!CC->internal_pgm) || (recps == NULL)) {
2297 if (CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 1, msg) != 0) {
2298 lprintf(CTDL_ERR, "ERROR saving message pointer!\n");
2299 CtdlSaveMsgPointerInRoom(config.c_aideroom, newmsgid, 0, msg);
2303 /* For internet mail, drop a copy in the outbound queue room */
2305 if (recps->num_internet > 0) {
2306 CtdlSaveMsgPointerInRoom(SMTP_SPOOLOUT_ROOM, newmsgid, 0, msg);
2309 /* If other rooms are specified, drop them there too. */
2311 if (recps->num_room > 0)
2312 for (i=0; i<num_tokens(recps->recp_room, '|'); ++i) {
2313 extract_token(recipient, recps->recp_room, i,
2314 '|', sizeof recipient);
2315 lprintf(CTDL_DEBUG, "Delivering to room <%s>\n", recipient);
2316 CtdlSaveMsgPointerInRoom(recipient, newmsgid, 0, msg);
2319 /* Bump this user's messages posted counter. */
2320 lprintf(CTDL_DEBUG, "Updating user\n");
2321 lgetuser(&CC->user, CC->curr_user);
2322 CC->user.posted = CC->user.posted + 1;
2323 lputuser(&CC->user);
2325 /* If this is private, local mail, make a copy in the
2326 * recipient's mailbox and bump the reference count.
2329 if (recps->num_local > 0)
2330 for (i=0; i<num_tokens(recps->recp_local, '|'); ++i) {
2331 extract_token(recipient, recps->recp_local, i,
2332 '|', sizeof recipient);
2333 lprintf(CTDL_DEBUG, "Delivering private local mail to <%s>\n",
2335 if (getuser(&userbuf, recipient) == 0) {
2336 MailboxName(actual_rm, sizeof actual_rm,
2337 &userbuf, MAILROOM);
2338 CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 0, msg);
2339 BumpNewMailCounter(userbuf.usernum);
2342 lprintf(CTDL_DEBUG, "No user <%s>\n", recipient);
2343 CtdlSaveMsgPointerInRoom(config.c_aideroom,
2348 /* Perform "after save" hooks */
2349 lprintf(CTDL_DEBUG, "Performing after-save hooks\n");
2350 PerformMessageHooks(msg, EVT_AFTERSAVE);
2352 /* For IGnet mail, we have to save a new copy into the spooler for
2353 * each recipient, with the R and D fields set to the recipient and
2354 * destination-node. This has two ugly side effects: all other
2355 * recipients end up being unlisted in this recipient's copy of the
2356 * message, and it has to deliver multiple messages to the same
2357 * node. We'll revisit this again in a year or so when everyone has
2358 * a network spool receiver that can handle the new style messages.
2361 if (recps->num_ignet > 0)
2362 for (i=0; i<num_tokens(recps->recp_ignet, '|'); ++i) {
2363 extract_token(recipient, recps->recp_ignet, i,
2364 '|', sizeof recipient);
2366 hold_R = msg->cm_fields['R'];
2367 hold_D = msg->cm_fields['D'];
2368 msg->cm_fields['R'] = malloc(SIZ);
2369 msg->cm_fields['D'] = malloc(128);
2370 extract_token(msg->cm_fields['R'], recipient, 0, '@', SIZ);
2371 extract_token(msg->cm_fields['D'], recipient, 1, '@', 128);
2373 serialize_message(&smr, msg);
2375 snprintf(submit_filename, sizeof submit_filename,
2376 #ifndef HAVE_SPOOL_DIR
2381 "/network/spoolin/netmail.%04lx.%04x.%04x",
2382 (long) getpid(), CC->cs_pid, ++seqnum);
2383 network_fp = fopen(submit_filename, "wb+");
2384 if (network_fp != NULL) {
2385 fwrite(smr.ser, smr.len, 1, network_fp);
2391 free(msg->cm_fields['R']);
2392 free(msg->cm_fields['D']);
2393 msg->cm_fields['R'] = hold_R;
2394 msg->cm_fields['D'] = hold_D;
2397 /* Go back to the room we started from */
2398 lprintf(CTDL_DEBUG, "Returning to original room %s\n", hold_rm);
2399 if (strcasecmp(hold_rm, CC->room.QRname))
2400 /* getroom(&CC->room, hold_rm); */
2401 usergoto(hold_rm, 0, 1, NULL, NULL);
2403 /* For internet mail, generate delivery instructions.
2404 * Yes, this is recursive. Deal with it. Infinite recursion does
2405 * not happen because the delivery instructions message does not
2406 * contain a recipient.
2409 if (recps->num_internet > 0) {
2410 lprintf(CTDL_DEBUG, "Generating delivery instructions\n");
2411 instr = malloc(SIZ * 2);
2412 snprintf(instr, SIZ * 2,
2413 "Content-type: %s\n\nmsgid|%ld\nsubmitted|%ld\n"
2415 SPOOLMIME, newmsgid, (long)time(NULL),
2416 msg->cm_fields['A'], msg->cm_fields['N']
2419 for (i=0; i<num_tokens(recps->recp_internet, '|'); ++i) {
2420 size_t tmp = strlen(instr);
2421 extract_token(recipient, recps->recp_internet,
2422 i, '|', sizeof recipient);
2423 snprintf(&instr[tmp], SIZ * 2 - tmp,
2424 "remote|%s|0||\n", recipient);
2427 imsg = malloc(sizeof(struct CtdlMessage));
2428 memset(imsg, 0, sizeof(struct CtdlMessage));
2429 imsg->cm_magic = CTDLMESSAGE_MAGIC;
2430 imsg->cm_anon_type = MES_NORMAL;
2431 imsg->cm_format_type = FMT_RFC822;
2432 imsg->cm_fields['A'] = strdup("Citadel");
2433 imsg->cm_fields['M'] = instr;
2434 CtdlSubmitMsg(imsg, NULL, SMTP_SPOOLOUT_ROOM);
2435 CtdlFreeMessage(imsg);
2439 * Any addresses to harvest for someone's address book?
2441 if ( (CC->logged_in) && (recps != NULL) ) {
2442 collected_addresses = harvest_collected_addresses(msg);
2445 if (collected_addresses != NULL) {
2446 begin_critical_section(S_ATBF);
2447 aptr = (struct addresses_to_be_filed *)
2448 malloc(sizeof(struct addresses_to_be_filed));
2450 MailboxName(actual_rm, sizeof actual_rm,
2451 &CC->user, USERCONTACTSROOM);
2452 aptr->roomname = strdup(actual_rm);
2453 aptr->collected_addresses = collected_addresses;
2455 end_critical_section(S_ATBF);
2468 * Convenience function for generating small administrative messages.
2470 void quickie_message(char *from, char *to, char *room, char *text,
2471 int format_type, char *subject)
2473 struct CtdlMessage *msg;
2474 struct recptypes *recp = NULL;
2476 msg = malloc(sizeof(struct CtdlMessage));
2477 memset(msg, 0, sizeof(struct CtdlMessage));
2478 msg->cm_magic = CTDLMESSAGE_MAGIC;
2479 msg->cm_anon_type = MES_NORMAL;
2480 msg->cm_format_type = format_type;
2481 msg->cm_fields['A'] = strdup(from);
2482 if (room != NULL) msg->cm_fields['O'] = strdup(room);
2483 msg->cm_fields['N'] = strdup(NODENAME);
2485 msg->cm_fields['R'] = strdup(to);
2486 recp = validate_recipients(to);
2488 if (subject != NULL) {
2489 msg->cm_fields['U'] = strdup(subject);
2491 msg->cm_fields['M'] = strdup(text);
2493 CtdlSubmitMsg(msg, recp, room);
2494 CtdlFreeMessage(msg);
2495 if (recp != NULL) free(recp);
2501 * Back end function used by CtdlMakeMessage() and similar functions
2503 char *CtdlReadMessageBody(char *terminator, /* token signalling EOT */
2504 size_t maxlen, /* maximum message length */
2505 char *exist, /* if non-null, append to it;
2506 exist is ALWAYS freed */
2507 int crlf /* CRLF newlines instead of LF */
2511 size_t message_len = 0;
2512 size_t buffer_len = 0;
2518 if (exist == NULL) {
2525 message_len = strlen(exist);
2526 buffer_len = message_len + 4096;
2527 m = realloc(exist, buffer_len);
2534 /* flush the input if we have nowhere to store it */
2539 /* read in the lines of message text one by one */
2541 if (client_getln(buf, (sizeof buf - 3)) < 1) finished = 1;
2542 if (!strcmp(buf, terminator)) finished = 1;
2544 strcat(buf, "\r\n");
2550 if ( (!flushing) && (!finished) ) {
2551 /* Measure the line */
2552 linelen = strlen(buf);
2554 /* augment the buffer if we have to */
2555 if ((message_len + linelen) >= buffer_len) {
2556 ptr = realloc(m, (buffer_len * 2) );
2557 if (ptr == NULL) { /* flush if can't allocate */
2560 buffer_len = (buffer_len * 2);
2562 lprintf(CTDL_DEBUG, "buffer_len is now %ld\n", (long)buffer_len);
2566 /* Add the new line to the buffer. NOTE: this loop must avoid
2567 * using functions like strcat() and strlen() because they
2568 * traverse the entire buffer upon every call, and doing that
2569 * for a multi-megabyte message slows it down beyond usability.
2571 strcpy(&m[message_len], buf);
2572 message_len += linelen;
2575 /* if we've hit the max msg length, flush the rest */
2576 if (message_len >= maxlen) flushing = 1;
2578 } while (!finished);
2586 * Build a binary message to be saved on disk.
2587 * (NOTE: if you supply 'preformatted_text', the buffer you give it
2588 * will become part of the message. This means you are no longer
2589 * responsible for managing that memory -- it will be freed along with
2590 * the rest of the fields when CtdlFreeMessage() is called.)
2593 struct CtdlMessage *CtdlMakeMessage(
2594 struct ctdluser *author, /* author's user structure */
2595 char *recipient, /* NULL if it's not mail */
2596 char *recp_cc, /* NULL if it's not mail */
2597 char *room, /* room where it's going */
2598 int type, /* see MES_ types in header file */
2599 int format_type, /* variformat, plain text, MIME... */
2600 char *fake_name, /* who we're masquerading as */
2601 char *subject, /* Subject (optional) */
2602 char *preformatted_text /* ...or NULL to read text from client */
2604 char dest_node[SIZ];
2606 struct CtdlMessage *msg;
2608 msg = malloc(sizeof(struct CtdlMessage));
2609 memset(msg, 0, sizeof(struct CtdlMessage));
2610 msg->cm_magic = CTDLMESSAGE_MAGIC;
2611 msg->cm_anon_type = type;
2612 msg->cm_format_type = format_type;
2614 /* Don't confuse the poor folks if it's not routed mail. */
2615 strcpy(dest_node, "");
2620 snprintf(buf, sizeof buf, "cit%ld", author->usernum); /* Path */
2621 msg->cm_fields['P'] = strdup(buf);
2623 snprintf(buf, sizeof buf, "%ld", (long)time(NULL)); /* timestamp */
2624 msg->cm_fields['T'] = strdup(buf);
2626 if (fake_name[0]) /* author */
2627 msg->cm_fields['A'] = strdup(fake_name);
2629 msg->cm_fields['A'] = strdup(author->fullname);
2631 if (CC->room.QRflags & QR_MAILBOX) { /* room */
2632 msg->cm_fields['O'] = strdup(&CC->room.QRname[11]);
2635 msg->cm_fields['O'] = strdup(CC->room.QRname);
2638 msg->cm_fields['N'] = strdup(NODENAME); /* nodename */
2639 msg->cm_fields['H'] = strdup(HUMANNODE); /* hnodename */
2641 if (recipient[0] != 0) {
2642 msg->cm_fields['R'] = strdup(recipient);
2644 if (recp_cc[0] != 0) {
2645 msg->cm_fields['Y'] = strdup(recp_cc);
2647 if (dest_node[0] != 0) {
2648 msg->cm_fields['D'] = strdup(dest_node);
2651 if ( (author == &CC->user) && (strlen(CC->cs_inet_email) > 0) ) {
2652 msg->cm_fields['F'] = strdup(CC->cs_inet_email);
2655 if (subject != NULL) {
2657 if (strlen(subject) > 0) {
2658 msg->cm_fields['U'] = strdup(subject);
2662 if (preformatted_text != NULL) {
2663 msg->cm_fields['M'] = preformatted_text;
2666 msg->cm_fields['M'] = CtdlReadMessageBody("000",
2667 config.c_maxmsglen, NULL, 0);
2675 * Check to see whether we have permission to post a message in the current
2676 * room. Returns a *CITADEL ERROR CODE* and puts a message in errmsgbuf, or
2677 * returns 0 on success.
2679 int CtdlDoIHavePermissionToPostInThisRoom(char *errmsgbuf, size_t n) {
2681 if (!(CC->logged_in)) {
2682 snprintf(errmsgbuf, n, "Not logged in.");
2683 return (ERROR + NOT_LOGGED_IN);
2686 if ((CC->user.axlevel < 2)
2687 && ((CC->room.QRflags & QR_MAILBOX) == 0)) {
2688 snprintf(errmsgbuf, n, "Need to be validated to enter "
2689 "(except in %s> to sysop)", MAILROOM);
2690 return (ERROR + HIGHER_ACCESS_REQUIRED);
2693 if ((CC->user.axlevel < 4)
2694 && (CC->room.QRflags & QR_NETWORK)) {
2695 snprintf(errmsgbuf, n, "Need net privileges to enter here.");
2696 return (ERROR + HIGHER_ACCESS_REQUIRED);
2699 if ((CC->user.axlevel < 6)
2700 && (CC->room.QRflags & QR_READONLY)) {
2701 snprintf(errmsgbuf, n, "Sorry, this is a read-only room.");
2702 return (ERROR + HIGHER_ACCESS_REQUIRED);
2705 strcpy(errmsgbuf, "Ok");
2711 * Check to see if the specified user has Internet mail permission
2712 * (returns nonzero if permission is granted)
2714 int CtdlCheckInternetMailPermission(struct ctdluser *who) {
2716 /* Do not allow twits to send Internet mail */
2717 if (who->axlevel <= 2) return(0);
2719 /* Globally enabled? */
2720 if (config.c_restrict == 0) return(1);
2722 /* User flagged ok? */
2723 if (who->flags & US_INTERNET) return(2);
2725 /* Aide level access? */
2726 if (who->axlevel >= 6) return(3);
2728 /* No mail for you! */
2734 * Validate recipients, count delivery types and errors, and handle aliasing
2735 * FIXME check for dupes!!!!!
2736 * Returns 0 if all addresses are ok, -1 if no addresses were specified,
2737 * or the number of addresses found invalid.
2739 struct recptypes *validate_recipients(char *supplied_recipients) {
2740 struct recptypes *ret;
2741 char recipients[SIZ];
2742 char this_recp[256];
2743 char this_recp_cooked[256];
2749 struct ctdluser tempUS;
2750 struct ctdlroom tempQR;
2754 ret = (struct recptypes *) malloc(sizeof(struct recptypes));
2755 if (ret == NULL) return(NULL);
2756 memset(ret, 0, sizeof(struct recptypes));
2759 ret->num_internet = 0;
2764 if (supplied_recipients == NULL) {
2765 strcpy(recipients, "");
2768 safestrncpy(recipients, supplied_recipients, sizeof recipients);
2771 /* Change all valid separator characters to commas */
2772 for (i=0; i<strlen(recipients); ++i) {
2773 if ((recipients[i] == ';') || (recipients[i] == '|')) {
2774 recipients[i] = ',';
2778 /* Now start extracting recipients... */
2780 while (strlen(recipients) > 0) {
2782 for (i=0; i<=strlen(recipients); ++i) {
2783 if (recipients[i] == '\"') in_quotes = 1 - in_quotes;
2784 if ( ( (recipients[i] == ',') && (!in_quotes) ) || (recipients[i] == 0) ) {
2785 safestrncpy(this_recp, recipients, i+1);
2787 if (recipients[i] == ',') {
2788 strcpy(recipients, &recipients[i+1]);
2791 strcpy(recipients, "");
2798 lprintf(CTDL_DEBUG, "Evaluating recipient #%d: %s\n", num_recps, this_recp);
2800 mailtype = alias(this_recp);
2801 mailtype = alias(this_recp);
2802 mailtype = alias(this_recp);
2803 for (j=0; j<=strlen(this_recp); ++j) {
2804 if (this_recp[j]=='_') {
2805 this_recp_cooked[j] = ' ';
2808 this_recp_cooked[j] = this_recp[j];
2814 if (!strcasecmp(this_recp, "sysop")) {
2816 strcpy(this_recp, config.c_aideroom);
2817 if (strlen(ret->recp_room) > 0) {
2818 strcat(ret->recp_room, "|");
2820 strcat(ret->recp_room, this_recp);
2822 else if (getuser(&tempUS, this_recp) == 0) {
2824 strcpy(this_recp, tempUS.fullname);
2825 if (strlen(ret->recp_local) > 0) {
2826 strcat(ret->recp_local, "|");
2828 strcat(ret->recp_local, this_recp);
2830 else if (getuser(&tempUS, this_recp_cooked) == 0) {
2832 strcpy(this_recp, tempUS.fullname);
2833 if (strlen(ret->recp_local) > 0) {
2834 strcat(ret->recp_local, "|");
2836 strcat(ret->recp_local, this_recp);
2838 else if ( (!strncasecmp(this_recp, "room_", 5))
2839 && (!getroom(&tempQR, &this_recp_cooked[5])) ) {
2841 if (strlen(ret->recp_room) > 0) {
2842 strcat(ret->recp_room, "|");
2844 strcat(ret->recp_room, &this_recp_cooked[5]);
2852 /* Yes, you're reading this correctly: if the target
2853 * domain points back to the local system or an attached
2854 * Citadel directory, the address is invalid. That's
2855 * because if the address were valid, we would have
2856 * already translated it to a local address by now.
2858 if (IsDirectory(this_recp)) {
2863 ++ret->num_internet;
2864 if (strlen(ret->recp_internet) > 0) {
2865 strcat(ret->recp_internet, "|");
2867 strcat(ret->recp_internet, this_recp);
2872 if (strlen(ret->recp_ignet) > 0) {
2873 strcat(ret->recp_ignet, "|");
2875 strcat(ret->recp_ignet, this_recp);
2883 if (strlen(ret->errormsg) == 0) {
2884 snprintf(append, sizeof append,
2885 "Invalid recipient: %s",
2889 snprintf(append, sizeof append,
2892 if ( (strlen(ret->errormsg) + strlen(append)) < SIZ) {
2893 strcat(ret->errormsg, append);
2897 if (strlen(ret->display_recp) == 0) {
2898 strcpy(append, this_recp);
2901 snprintf(append, sizeof append, ", %s",
2904 if ( (strlen(ret->display_recp)+strlen(append)) < SIZ) {
2905 strcat(ret->display_recp, append);
2910 if ((ret->num_local + ret->num_internet + ret->num_ignet +
2911 ret->num_room + ret->num_error) == 0) {
2912 ret->num_error = (-1);
2913 strcpy(ret->errormsg, "No recipients specified.");
2916 lprintf(CTDL_DEBUG, "validate_recipients()\n");
2917 lprintf(CTDL_DEBUG, " local: %d <%s>\n", ret->num_local, ret->recp_local);
2918 lprintf(CTDL_DEBUG, " room: %d <%s>\n", ret->num_room, ret->recp_room);
2919 lprintf(CTDL_DEBUG, " inet: %d <%s>\n", ret->num_internet, ret->recp_internet);
2920 lprintf(CTDL_DEBUG, " ignet: %d <%s>\n", ret->num_ignet, ret->recp_ignet);
2921 lprintf(CTDL_DEBUG, " error: %d <%s>\n", ret->num_error, ret->errormsg);
2929 * message entry - mode 0 (normal)
2931 void cmd_ent0(char *entargs)
2937 char masquerade_as[SIZ];
2939 int format_type = 0;
2940 char newusername[SIZ];
2941 struct CtdlMessage *msg;
2945 struct recptypes *valid = NULL;
2946 struct recptypes *valid_to = NULL;
2947 struct recptypes *valid_cc = NULL;
2948 struct recptypes *valid_bcc = NULL;
2955 post = extract_int(entargs, 0);
2956 extract_token(recp, entargs, 1, '|', sizeof recp);
2957 anon_flag = extract_int(entargs, 2);
2958 format_type = extract_int(entargs, 3);
2959 extract_token(subject, entargs, 4, '|', sizeof subject);
2960 do_confirm = extract_int(entargs, 6);
2961 extract_token(cc, entargs, 7, '|', sizeof cc);
2962 extract_token(bcc, entargs, 8, '|', sizeof bcc);
2964 /* first check to make sure the request is valid. */
2966 err = CtdlDoIHavePermissionToPostInThisRoom(errmsg, sizeof errmsg);
2968 cprintf("%d %s\n", err, errmsg);
2972 /* Check some other permission type things. */
2975 if (CC->user.axlevel < 6) {
2976 cprintf("%d You don't have permission to masquerade.\n",
2977 ERROR + HIGHER_ACCESS_REQUIRED);
2980 extract_token(newusername, entargs, 5, '|', sizeof newusername);
2981 memset(CC->fake_postname, 0, sizeof(CC->fake_postname) );
2982 safestrncpy(CC->fake_postname, newusername,
2983 sizeof(CC->fake_postname) );
2984 cprintf("%d ok\n", CIT_OK);
2987 CC->cs_flags |= CS_POSTING;
2989 /* In the Mail> room we have to behave a little differently --
2990 * make sure the user has specified at least one recipient. Then
2991 * validate the recipient(s).
2993 if ( (CC->room.QRflags & QR_MAILBOX)
2994 && (!strcasecmp(&CC->room.QRname[11], MAILROOM)) ) {
2996 if (CC->user.axlevel < 2) {
2997 strcpy(recp, "sysop");
3002 valid_to = validate_recipients(recp);
3003 if (valid_to->num_error > 0) {
3004 cprintf("%d Invalid recipient (To)\n", ERROR + NO_SUCH_USER);
3009 valid_cc = validate_recipients(cc);
3010 if (valid_cc->num_error > 0) {
3011 cprintf("%d Invalid recipient (CC)\n", ERROR + NO_SUCH_USER);
3017 valid_bcc = validate_recipients(bcc);
3018 if (valid_bcc->num_error > 0) {
3019 cprintf("%d Invalid recipient (BCC)\n", ERROR + NO_SUCH_USER);
3026 /* Recipient required, but none were specified */
3027 if ( (valid_to->num_error < 0) && (valid_cc->num_error < 0) && (valid_bcc->num_error < 0) ) {
3031 cprintf("%d At least one recipient is required.\n", ERROR + NO_SUCH_USER);
3035 if (valid_to->num_internet + valid_cc->num_internet + valid_bcc->num_internet > 0) {
3036 if (CtdlCheckInternetMailPermission(&CC->user)==0) {
3037 cprintf("%d You do not have permission "
3038 "to send Internet mail.\n",
3039 ERROR + HIGHER_ACCESS_REQUIRED);
3047 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)
3048 && (CC->user.axlevel < 4) ) {
3049 cprintf("%d Higher access required for network mail.\n",
3050 ERROR + HIGHER_ACCESS_REQUIRED);
3057 if ((RESTRICT_INTERNET == 1)
3058 && (valid_to->num_internet + valid_cc->num_internet + valid_bcc->num_internet > 0)
3059 && ((CC->user.flags & US_INTERNET) == 0)
3060 && (!CC->internal_pgm)) {
3061 cprintf("%d You don't have access to Internet mail.\n",
3062 ERROR + HIGHER_ACCESS_REQUIRED);
3071 /* Is this a room which has anonymous-only or anonymous-option? */
3072 anonymous = MES_NORMAL;
3073 if (CC->room.QRflags & QR_ANONONLY) {
3074 anonymous = MES_ANONONLY;
3076 if (CC->room.QRflags & QR_ANONOPT) {
3077 if (anon_flag == 1) { /* only if the user requested it */
3078 anonymous = MES_ANONOPT;
3082 if ((CC->room.QRflags & QR_MAILBOX) == 0) {
3086 /* If we're only checking the validity of the request, return
3087 * success without creating the message.
3090 cprintf("%d %s\n", CIT_OK,
3091 ((valid_to != NULL) ? valid_to->display_recp : "") );
3098 /* We don't need these anymore because we'll do it differently below */
3103 /* Handle author masquerading */
3104 if (CC->fake_postname[0]) {
3105 strcpy(masquerade_as, CC->fake_postname);
3107 else if (CC->fake_username[0]) {
3108 strcpy(masquerade_as, CC->fake_username);
3111 strcpy(masquerade_as, "");
3114 /* Read in the message from the client. */
3116 cprintf("%d send message\n", START_CHAT_MODE);
3118 cprintf("%d send message\n", SEND_LISTING);
3121 msg = CtdlMakeMessage(&CC->user, recp, cc,
3122 CC->room.QRname, anonymous, format_type,
3123 masquerade_as, subject, NULL);
3125 /* Put together one big recipients struct containing to/cc/bcc all in
3126 * one. This is for the envelope.
3128 char *all_recps = malloc(SIZ * 3);
3129 strcpy(all_recps, recp);
3130 if (strlen(cc) > 0) {
3131 if (strlen(all_recps) > 0) {
3132 strcat(all_recps, ",");
3134 strcat(all_recps, cc);
3136 if (strlen(bcc) > 0) {
3137 if (strlen(all_recps) > 0) {
3138 strcat(all_recps, ",");
3140 strcat(all_recps, bcc);
3142 if (strlen(all_recps) > 0) {
3143 valid = validate_recipients(all_recps);
3151 msgnum = CtdlSubmitMsg(msg, valid, "");
3154 cprintf("%ld\n", msgnum);
3156 cprintf("Message accepted.\n");
3159 cprintf("Internal error.\n");
3161 if (msg->cm_fields['E'] != NULL) {
3162 cprintf("%s\n", msg->cm_fields['E']);
3169 CtdlFreeMessage(msg);
3171 CC->fake_postname[0] = '\0';
3172 if (valid != NULL) {
3181 * API function to delete messages which match a set of criteria
3182 * (returns the actual number of messages deleted)
3184 int CtdlDeleteMessages(char *room_name, /* which room */
3185 long dmsgnum, /* or "0" for any */
3186 char *content_type, /* or "" for any */
3187 int deferred /* let TDAP sweep it later */
3191 struct ctdlroom qrbuf;
3192 struct cdbdata *cdbfr;
3193 long *msglist = NULL;
3194 long *dellist = NULL;
3197 int num_deleted = 0;
3199 struct MetaData smi;
3201 lprintf(CTDL_DEBUG, "CtdlDeleteMessages(%s, %ld, %s, %d)\n",
3202 room_name, dmsgnum, content_type, deferred);
3204 /* get room record, obtaining a lock... */
3205 if (lgetroom(&qrbuf, room_name) != 0) {
3206 lprintf(CTDL_ERR, "CtdlDeleteMessages(): Room <%s> not found\n",
3208 return (0); /* room not found */
3210 cdbfr = cdb_fetch(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long));
3212 if (cdbfr != NULL) {
3213 dellist = malloc(cdbfr->len);
3214 msglist = (long *) cdbfr->ptr;
3215 cdbfr->ptr = NULL; /* CtdlDeleteMessages() now owns this memory */
3216 num_msgs = cdbfr->len / sizeof(long);
3220 for (i = 0; i < num_msgs; ++i) {
3223 /* Set/clear a bit for each criterion */
3225 if ((dmsgnum == 0L) || (msglist[i] == dmsgnum)) {
3226 delete_this |= 0x01;
3228 if (strlen(content_type) == 0) {
3229 delete_this |= 0x02;
3231 GetMetaData(&smi, msglist[i]);
3232 if (!strcasecmp(smi.meta_content_type,
3234 delete_this |= 0x02;
3238 /* Delete message only if all bits are set */
3239 if (delete_this == 0x03) {
3240 dellist[num_deleted++] = msglist[i];
3245 num_msgs = sort_msglist(msglist, num_msgs);
3246 cdb_store(CDB_MSGLISTS, &qrbuf.QRnumber, (int)sizeof(long),
3247 msglist, (int)(num_msgs * sizeof(long)));
3249 qrbuf.QRhighest = msglist[num_msgs - 1];
3254 * If the delete operation is "deferred" (and technically, any delete
3255 * operation not performed by THE DREADED AUTO-PURGER ought to be
3256 * a deferred delete) then we save a pointer to the message in the
3257 * DELETED_MSGS_ROOM. This will cause the reference count to remain
3258 * at least 1, which will save the user from having to synchronously
3259 * wait for various disk-intensive operations to complete.
3261 if ( (deferred) && (num_deleted) ) {
3262 for (i=0; i<num_deleted; ++i) {
3263 CtdlCopyMsgToRoom(dellist[i], DELETED_MSGS_ROOM);
3267 /* Go through the messages we pulled out of the index, and decrement
3268 * their reference counts by 1. If this is the only room the message
3269 * was in, the reference count will reach zero and the message will
3270 * automatically be deleted from the database. We do this in a
3271 * separate pass because there might be plug-in hooks getting called,
3272 * and we don't want that happening during an S_ROOMS critical
3275 if (num_deleted) for (i=0; i<num_deleted; ++i) {
3276 PerformDeleteHooks(qrbuf.QRname, dellist[i]);
3277 AdjRefCount(dellist[i], -1);
3280 /* Now free the memory we used, and go away. */
3281 if (msglist != NULL) free(msglist);
3282 if (dellist != NULL) free(dellist);
3283 lprintf(CTDL_DEBUG, "%d message(s) deleted.\n", num_deleted);
3284 return (num_deleted);
3290 * Check whether the current user has permission to delete messages from
3291 * the current room (returns 1 for yes, 0 for no)
3293 int CtdlDoIHavePermissionToDeleteMessagesFromThisRoom(void) {
3294 getuser(&CC->user, CC->curr_user);
3295 if ((CC->user.axlevel < 6)
3296 && (CC->user.usernum != CC->room.QRroomaide)
3297 && ((CC->room.QRflags & QR_MAILBOX) == 0)
3298 && (!(CC->internal_pgm))) {
3307 * Delete message from current room
3309 void cmd_dele(char *delstr)
3314 if (CtdlDoIHavePermissionToDeleteMessagesFromThisRoom() == 0) {
3315 cprintf("%d Higher access required.\n",
3316 ERROR + HIGHER_ACCESS_REQUIRED);
3319 delnum = extract_long(delstr, 0);
3321 num_deleted = CtdlDeleteMessages(CC->room.QRname, delnum, "", 1);
3324 cprintf("%d %d message%s deleted.\n", CIT_OK,
3325 num_deleted, ((num_deleted != 1) ? "s" : ""));
3327 cprintf("%d Message %ld not found.\n", ERROR + MESSAGE_NOT_FOUND, delnum);
3333 * Back end API function for moves and deletes
3335 int CtdlCopyMsgToRoom(long msgnum, char *dest) {
3338 err = CtdlSaveMsgPointerInRoom(dest, msgnum, 1, NULL);
3339 if (err != 0) return(err);
3347 * move or copy a message to another room
3349 void cmd_move(char *args)
3352 char targ[ROOMNAMELEN];
3353 struct ctdlroom qtemp;
3359 num = extract_long(args, 0);
3360 extract_token(targ, args, 1, '|', sizeof targ);
3361 convert_room_name_macros(targ, sizeof targ);
3362 targ[ROOMNAMELEN - 1] = 0;
3363 is_copy = extract_int(args, 2);
3365 if (getroom(&qtemp, targ) != 0) {
3366 cprintf("%d '%s' does not exist.\n",
3367 ERROR + ROOM_NOT_FOUND, targ);
3371 getuser(&CC->user, CC->curr_user);
3372 CtdlRoomAccess(&qtemp, &CC->user, &ra, NULL);
3374 /* Check for permission to perform this operation.
3375 * Remember: "CC->room" is source, "qtemp" is target.
3379 /* Aides can move/copy */
3380 if (CC->user.axlevel >= 6) permit = 1;
3382 /* Room aides can move/copy */
3383 if (CC->user.usernum == CC->room.QRroomaide) permit = 1;
3385 /* Permit move/copy from personal rooms */
3386 if ((CC->room.QRflags & QR_MAILBOX)
3387 && (qtemp.QRflags & QR_MAILBOX)) permit = 1;
3389 /* Permit only copy from public to personal room */
3391 && (!(CC->room.QRflags & QR_MAILBOX))
3392 && (qtemp.QRflags & QR_MAILBOX)) permit = 1;
3394 /* User must have access to target room */
3395 if (!(ra & UA_KNOWN)) permit = 0;
3398 cprintf("%d Higher access required.\n",
3399 ERROR + HIGHER_ACCESS_REQUIRED);
3403 err = CtdlCopyMsgToRoom(num, targ);
3405 cprintf("%d Cannot store message in %s: error %d\n",
3410 /* Now delete the message from the source room,
3411 * if this is a 'move' rather than a 'copy' operation.
3414 CtdlDeleteMessages(CC->room.QRname, num, "", 0);
3417 cprintf("%d Message %s.\n", CIT_OK, (is_copy ? "copied" : "moved") );
3423 * GetMetaData() - Get the supplementary record for a message
3425 void GetMetaData(struct MetaData *smibuf, long msgnum)
3428 struct cdbdata *cdbsmi;
3431 memset(smibuf, 0, sizeof(struct MetaData));
3432 smibuf->meta_msgnum = msgnum;
3433 smibuf->meta_refcount = 1; /* Default reference count is 1 */
3435 /* Use the negative of the message number for its supp record index */
3436 TheIndex = (0L - msgnum);
3438 cdbsmi = cdb_fetch(CDB_MSGMAIN, &TheIndex, sizeof(long));
3439 if (cdbsmi == NULL) {
3440 return; /* record not found; go with defaults */
3442 memcpy(smibuf, cdbsmi->ptr,
3443 ((cdbsmi->len > sizeof(struct MetaData)) ?
3444 sizeof(struct MetaData) : cdbsmi->len));
3451 * PutMetaData() - (re)write supplementary record for a message
3453 void PutMetaData(struct MetaData *smibuf)
3457 /* Use the negative of the message number for the metadata db index */
3458 TheIndex = (0L - smibuf->meta_msgnum);
3460 lprintf(CTDL_DEBUG, "PutMetaData(%ld) - ref count is %d\n",
3461 smibuf->meta_msgnum, smibuf->meta_refcount);
3463 cdb_store(CDB_MSGMAIN,
3464 &TheIndex, (int)sizeof(long),
3465 smibuf, (int)sizeof(struct MetaData));
3470 * AdjRefCount - change the reference count for a message;
3471 * delete the message if it reaches zero
3473 void AdjRefCount(long msgnum, int incr)
3476 struct MetaData smi;
3479 /* This is a *tight* critical section; please keep it that way, as
3480 * it may get called while nested in other critical sections.
3481 * Complicating this any further will surely cause deadlock!
3483 begin_critical_section(S_SUPPMSGMAIN);
3484 GetMetaData(&smi, msgnum);
3485 smi.meta_refcount += incr;
3487 end_critical_section(S_SUPPMSGMAIN);
3488 lprintf(CTDL_DEBUG, "msg %ld ref count incr %d, is now %d\n",
3489 msgnum, incr, smi.meta_refcount);
3491 /* If the reference count is now zero, delete the message
3492 * (and its supplementary record as well).
3494 if (smi.meta_refcount == 0) {
3495 lprintf(CTDL_DEBUG, "Deleting message <%ld>\n", msgnum);
3497 /* Remove from fulltext index */
3498 if (config.c_enable_fulltext) {
3499 ft_index_message(msgnum, 0);
3502 /* Remove from message base */
3504 cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long));
3505 cdb_delete(CDB_BIGMSGS, &delnum, (int)sizeof(long));
3507 /* Remove metadata record */
3508 delnum = (0L - msgnum);
3509 cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long));
3514 * Write a generic object to this room
3516 * Note: this could be much more efficient. Right now we use two temporary
3517 * files, and still pull the message into memory as with all others.
3519 void CtdlWriteObject(char *req_room, /* Room to stuff it in */
3520 char *content_type, /* MIME type of this object */
3521 char *tempfilename, /* Where to fetch it from */
3522 struct ctdluser *is_mailbox, /* Mailbox room? */
3523 int is_binary, /* Is encoding necessary? */
3524 int is_unique, /* Del others of this type? */
3525 unsigned int flags /* Internal save flags */
3530 struct ctdlroom qrbuf;
3531 char roomname[ROOMNAMELEN];
3532 struct CtdlMessage *msg;
3534 char *raw_message = NULL;
3535 char *encoded_message = NULL;
3536 off_t raw_length = 0;
3538 if (is_mailbox != NULL)
3539 MailboxName(roomname, sizeof roomname, is_mailbox, req_room);
3541 safestrncpy(roomname, req_room, sizeof(roomname));
3542 lprintf(CTDL_DEBUG, "CtdlWriteObject() to <%s> (flags=%d)\n", roomname, flags);
3545 fp = fopen(tempfilename, "rb");
3547 lprintf(CTDL_CRIT, "Cannot open %s: %s\n",
3548 tempfilename, strerror(errno));
3551 fseek(fp, 0L, SEEK_END);
3552 raw_length = ftell(fp);
3554 lprintf(CTDL_DEBUG, "Raw length is %ld\n", (long)raw_length);
3556 raw_message = malloc((size_t)raw_length + 2);
3557 fread(raw_message, (size_t)raw_length, 1, fp);
3561 encoded_message = malloc((size_t)
3562 (((raw_length * 134) / 100) + 4096 ) );
3565 encoded_message = malloc((size_t)(raw_length + 4096));
3568 sprintf(encoded_message, "Content-type: %s\n", content_type);
3571 sprintf(&encoded_message[strlen(encoded_message)],
3572 "Content-transfer-encoding: base64\n\n"
3576 sprintf(&encoded_message[strlen(encoded_message)],
3577 "Content-transfer-encoding: 7bit\n\n"
3583 &encoded_message[strlen(encoded_message)],
3589 raw_message[raw_length] = 0;
3591 &encoded_message[strlen(encoded_message)],
3599 lprintf(CTDL_DEBUG, "Allocating\n");
3600 msg = malloc(sizeof(struct CtdlMessage));
3601 memset(msg, 0, sizeof(struct CtdlMessage));
3602 msg->cm_magic = CTDLMESSAGE_MAGIC;
3603 msg->cm_anon_type = MES_NORMAL;
3604 msg->cm_format_type = 4;
3605 msg->cm_fields['A'] = strdup(CC->user.fullname);
3606 msg->cm_fields['O'] = strdup(req_room);
3607 msg->cm_fields['N'] = strdup(config.c_nodename);
3608 msg->cm_fields['H'] = strdup(config.c_humannode);
3609 msg->cm_flags = flags;
3611 msg->cm_fields['M'] = encoded_message;
3613 /* Create the requested room if we have to. */
3614 if (getroom(&qrbuf, roomname) != 0) {
3615 create_room(roomname,
3616 ( (is_mailbox != NULL) ? 5 : 3 ),
3617 "", 0, 1, 0, VIEW_BBS);
3619 /* If the caller specified this object as unique, delete all
3620 * other objects of this type that are currently in the room.
3623 lprintf(CTDL_DEBUG, "Deleted %d other msgs of this type\n",
3624 CtdlDeleteMessages(roomname, 0L, content_type, 0)
3627 /* Now write the data */
3628 CtdlSubmitMsg(msg, NULL, roomname);
3629 CtdlFreeMessage(msg);
3637 void CtdlGetSysConfigBackend(long msgnum, void *userdata) {
3638 config_msgnum = msgnum;
3642 char *CtdlGetSysConfig(char *sysconfname) {
3643 char hold_rm[ROOMNAMELEN];
3646 struct CtdlMessage *msg;
3649 strcpy(hold_rm, CC->room.QRname);
3650 if (getroom(&CC->room, SYSCONFIGROOM) != 0) {
3651 getroom(&CC->room, hold_rm);
3656 /* We want the last (and probably only) config in this room */
3657 begin_critical_section(S_CONFIG);
3658 config_msgnum = (-1L);
3659 CtdlForEachMessage(MSGS_LAST, 1, sysconfname, NULL,
3660 CtdlGetSysConfigBackend, NULL);
3661 msgnum = config_msgnum;
3662 end_critical_section(S_CONFIG);
3668 msg = CtdlFetchMessage(msgnum, 1);
3670 conf = strdup(msg->cm_fields['M']);
3671 CtdlFreeMessage(msg);
3678 getroom(&CC->room, hold_rm);
3680 if (conf != NULL) do {
3681 extract_token(buf, conf, 0, '\n', sizeof buf);
3682 strcpy(conf, &conf[strlen(buf)+1]);
3683 } while ( (strlen(conf)>0) && (strlen(buf)>0) );
3688 void CtdlPutSysConfig(char *sysconfname, char *sysconfdata) {
3689 char temp[PATH_MAX];
3692 strcpy(temp, tmpnam(NULL));
3694 fp = fopen(temp, "w");
3695 if (fp == NULL) return;
3696 fprintf(fp, "%s", sysconfdata);
3699 /* this handy API function does all the work for us */
3700 CtdlWriteObject(SYSCONFIGROOM, sysconfname, temp, NULL, 0, 1, 0);
3706 * Determine whether a given Internet address belongs to the current user
3708 int CtdlIsMe(char *addr, int addr_buf_len)
3710 struct recptypes *recp;
3713 recp = validate_recipients(addr);
3714 if (recp == NULL) return(0);
3716 if (recp->num_local == 0) {
3721 for (i=0; i<recp->num_local; ++i) {
3722 extract_token(addr, recp->recp_local, i, '|', addr_buf_len);
3723 if (!strcasecmp(addr, CC->user.fullname)) {
3735 * Citadel protocol command to do the same
3737 void cmd_isme(char *argbuf) {
3740 if (CtdlAccessCheck(ac_logged_in)) return;
3741 extract_token(addr, argbuf, 0, '|', sizeof addr);
3743 if (CtdlIsMe(addr, sizeof addr)) {
3744 cprintf("%d %s\n", CIT_OK, addr);
3747 cprintf("%d Not you.\n", ERROR + ILLEGAL_VALUE);