4 * Implements the message store.
18 #if TIME_WITH_SYS_TIME
19 # include <sys/time.h>
23 # include <sys/time.h>
38 #include "serv_extensions.h"
42 #include "sysdep_decls.h"
43 #include "citserver.h"
50 #include "mime_parser.h"
53 #include "internet_addressing.h"
54 #include "serv_fulltext.h"
60 * This really belongs in serv_network.c, but I don't know how to export
61 * symbols between modules.
63 struct FilterList *filterlist = NULL;
67 * These are the four-character field headers we use when outputting
68 * messages in Citadel format (as opposed to RFC822 format).
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,
77 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
78 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
105 * This function is self explanatory.
106 * (What can I say, I'm in a weird mood today...)
108 void remove_any_whitespace_to_the_left_or_right_of_at_symbol(char *name)
112 for (i = 0; i < strlen(name); ++i) {
113 if (name[i] == '@') {
114 while (isspace(name[i - 1]) && i > 0) {
115 strcpy(&name[i - 1], &name[i]);
118 while (isspace(name[i + 1])) {
119 strcpy(&name[i + 1], &name[i + 2]);
127 * Aliasing for network mail.
128 * (Error messages have been commented out, because this is a server.)
130 int alias(char *name)
131 { /* process alias and routing info for mail */
134 char aaa[SIZ], bbb[SIZ];
135 char *ignetcfg = NULL;
136 char *ignetmap = NULL;
143 remove_any_whitespace_to_the_left_or_right_of_at_symbol(name);
145 fp = fopen("network/mail.aliases", "r");
147 fp = fopen("/dev/null", "r");
154 while (fgets(aaa, sizeof aaa, fp) != NULL) {
155 while (isspace(name[0]))
156 strcpy(name, &name[1]);
157 aaa[strlen(aaa) - 1] = 0;
159 for (a = 0; a < strlen(aaa); ++a) {
161 strcpy(bbb, &aaa[a + 1]);
165 if (!strcasecmp(name, aaa))
170 /* Hit the Global Address Book */
171 if (CtdlDirectoryLookup(aaa, name) == 0) {
175 lprintf(CTDL_INFO, "Mail is being forwarded to %s\n", name);
177 /* Change "user @ xxx" to "user" if xxx is an alias for this host */
178 for (a=0; a<strlen(name); ++a) {
179 if (name[a] == '@') {
180 if (CtdlHostAlias(&name[a+1]) == hostalias_localhost) {
182 lprintf(CTDL_INFO, "Changed to <%s>\n", name);
187 /* determine local or remote type, see citadel.h */
188 at = haschar(name, '@');
189 if (at == 0) return(MES_LOCAL); /* no @'s - local address */
190 if (at > 1) return(MES_ERROR); /* >1 @'s - invalid address */
191 remove_any_whitespace_to_the_left_or_right_of_at_symbol(name);
193 /* figure out the delivery mode */
194 extract_token(node, name, 1, '@', sizeof node);
196 /* If there are one or more dots in the nodename, we assume that it
197 * is an FQDN and will attempt SMTP delivery to the Internet.
199 if (haschar(node, '.') > 0) {
200 return(MES_INTERNET);
203 /* Otherwise we look in the IGnet maps for a valid Citadel node.
204 * Try directly-connected nodes first...
206 ignetcfg = CtdlGetSysConfig(IGNETCFG);
207 for (i=0; i<num_tokens(ignetcfg, '\n'); ++i) {
208 extract_token(buf, ignetcfg, i, '\n', sizeof buf);
209 extract_token(testnode, buf, 0, '|', sizeof testnode);
210 if (!strcasecmp(node, testnode)) {
218 * Then try nodes that are two or more hops away.
220 ignetmap = CtdlGetSysConfig(IGNETMAP);
221 for (i=0; i<num_tokens(ignetmap, '\n'); ++i) {
222 extract_token(buf, ignetmap, i, '\n', sizeof buf);
223 extract_token(testnode, buf, 0, '|', sizeof testnode);
224 if (!strcasecmp(node, testnode)) {
231 /* If we get to this point it's an invalid node name */
240 fp = fopen("citadel.control", "r");
242 lprintf(CTDL_CRIT, "Cannot open citadel.control: %s\n",
246 fread((char *) &CitControl, sizeof(struct CitControl), 1, fp);
252 void simple_listing(long msgnum, void *userdata)
254 cprintf("%ld\n", msgnum);
259 /* Determine if a given message matches the fields in a message template.
260 * Return 0 for a successful match.
262 int CtdlMsgCmp(struct CtdlMessage *msg, struct CtdlMessage *template) {
265 /* If there aren't any fields in the template, all messages will
268 if (template == NULL) return(0);
270 /* Null messages are bogus. */
271 if (msg == NULL) return(1);
273 for (i='A'; i<='Z'; ++i) {
274 if (template->cm_fields[i] != NULL) {
275 if (msg->cm_fields[i] == NULL) {
278 if (strcasecmp(msg->cm_fields[i],
279 template->cm_fields[i])) return 1;
283 /* All compares succeeded: we have a match! */
290 * Retrieve the "seen" message list for the current room.
292 void CtdlGetSeen(char *buf, int which_set) {
295 /* Learn about the user and room in question */
296 CtdlGetRelationship(&vbuf, &CC->user, &CC->room);
298 if (which_set == ctdlsetseen_seen)
299 safestrncpy(buf, vbuf.v_seen, SIZ);
300 if (which_set == ctdlsetseen_answered)
301 safestrncpy(buf, vbuf.v_answered, SIZ);
307 * Manipulate the "seen msgs" string (or other message set strings)
309 void CtdlSetSeen(long target_msgnum, int target_setting, int which_set) {
310 struct cdbdata *cdbfr;
320 char *is_set; /* actually an array of booleans */
323 char setstr[SIZ], lostr[SIZ], histr[SIZ];
325 lprintf(CTDL_DEBUG, "CtdlSetSeen(%ld, %d, %d)\n",
326 target_msgnum, target_setting, which_set);
328 /* Learn about the user and room in question */
329 CtdlGetRelationship(&vbuf, &CC->user, &CC->room);
331 /* Load the message list */
332 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long));
334 msglist = malloc(cdbfr->len);
335 memcpy(msglist, cdbfr->ptr, cdbfr->len);
336 num_msgs = cdbfr->len / sizeof(long);
339 return; /* No messages at all? No further action. */
342 is_set = malloc(num_msgs * sizeof(char));
343 memset(is_set, 0, (num_msgs * sizeof(char)) );
345 /* Decide which message set we're manipulating */
347 case ctdlsetseen_seen:
348 safestrncpy(vset, vbuf.v_seen, sizeof vset);
350 case ctdlsetseen_answered:
351 safestrncpy(vset, vbuf.v_answered, sizeof vset);
355 /* lprintf(CTDL_DEBUG, "before optimize: %s\n", vset); */
357 /* Translate the existing sequence set into an array of booleans */
358 num_sets = num_tokens(vset, ',');
359 for (s=0; s<num_sets; ++s) {
360 extract_token(setstr, vset, s, ',', sizeof setstr);
362 extract_token(lostr, setstr, 0, ':', sizeof lostr);
363 if (num_tokens(setstr, ':') >= 2) {
364 extract_token(histr, setstr, 1, ':', sizeof histr);
365 if (!strcmp(histr, "*")) {
366 snprintf(histr, sizeof histr, "%ld", LONG_MAX);
370 strcpy(histr, lostr);
375 for (i = 0; i < num_msgs; ++i) {
376 if ((msglist[i] >= lo) && (msglist[i] <= hi)) {
382 /* Now translate the array of booleans back into a sequence set */
385 for (i=0; i<num_msgs; ++i) {
388 if (msglist[i] == target_msgnum) {
389 is_seen = target_setting;
398 if (lo < 0L) lo = msglist[i];
401 if ( ((is_seen == 0) && (was_seen == 1))
402 || ((is_seen == 1) && (i == num_msgs-1)) ) {
405 if ( (strlen(vset) + 20) > sizeof vset) {
406 strcpy(vset, &vset[20]);
415 snprintf(&vset[tmp], sizeof vset - tmp,
419 snprintf(&vset[tmp], sizeof vset - tmp,
428 /* Decide which message set we're manipulating */
430 case ctdlsetseen_seen:
431 safestrncpy(vbuf.v_seen, vset, sizeof vbuf.v_seen);
433 case ctdlsetseen_answered:
434 safestrncpy(vbuf.v_answered, vset,
435 sizeof vbuf.v_answered);
440 /* lprintf(CTDL_DEBUG, " after optimize: %s\n", vset); */
442 CtdlSetRelationship(&vbuf, &CC->user, &CC->room);
447 * API function to perform an operation for each qualifying message in the
448 * current room. (Returns the number of messages processed.)
450 int CtdlForEachMessage(int mode, long ref,
452 struct CtdlMessage *compare,
453 void (*CallBack) (long, void *),
459 struct cdbdata *cdbfr;
460 long *msglist = NULL;
462 int num_processed = 0;
465 struct CtdlMessage *msg;
468 int printed_lastold = 0;
470 /* Learn about the user and room in question */
472 getuser(&CC->user, CC->curr_user);
473 CtdlGetRelationship(&vbuf, &CC->user, &CC->room);
475 /* Load the message list */
476 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long));
478 msglist = malloc(cdbfr->len);
479 memcpy(msglist, cdbfr->ptr, cdbfr->len);
480 num_msgs = cdbfr->len / sizeof(long);
483 return 0; /* No messages at all? No further action. */
488 * Now begin the traversal.
490 if (num_msgs > 0) for (a = 0; a < num_msgs; ++a) {
492 /* If the caller is looking for a specific MIME type, filter
493 * out all messages which are not of the type requested.
495 if (content_type != NULL) if (strlen(content_type) > 0) {
497 /* This call to GetMetaData() sits inside this loop
498 * so that we only do the extra database read per msg
499 * if we need to. Doing the extra read all the time
500 * really kills the server. If we ever need to use
501 * metadata for another search criterion, we need to
502 * move the read somewhere else -- but still be smart
503 * enough to only do the read if the caller has
504 * specified something that will need it.
506 GetMetaData(&smi, msglist[a]);
508 if (strcasecmp(smi.meta_content_type, content_type)) {
514 num_msgs = sort_msglist(msglist, num_msgs);
516 /* If a template was supplied, filter out the messages which
517 * don't match. (This could induce some delays!)
520 if (compare != NULL) {
521 for (a = 0; a < num_msgs; ++a) {
522 msg = CtdlFetchMessage(msglist[a], 1);
524 if (CtdlMsgCmp(msg, compare)) {
527 CtdlFreeMessage(msg);
535 * Now iterate through the message list, according to the
536 * criteria supplied by the caller.
539 for (a = 0; a < num_msgs; ++a) {
540 thismsg = msglist[a];
541 is_seen = is_msg_in_sequence_set(vbuf.v_seen, thismsg);
542 if (is_seen) lastold = thismsg;
547 || ((mode == MSGS_OLD) && (is_seen))
548 || ((mode == MSGS_NEW) && (!is_seen))
549 || ((mode == MSGS_LAST) && (a >= (num_msgs - ref)))
550 || ((mode == MSGS_FIRST) && (a < ref))
551 || ((mode == MSGS_GT) && (thismsg > ref))
552 || ((mode == MSGS_EQ) && (thismsg == ref))
555 if ((mode == MSGS_NEW) && (CC->user.flags & US_LASTOLD) && (lastold > 0L) && (printed_lastold == 0) && (!is_seen)) {
557 CallBack(lastold, userdata);
561 if (CallBack) CallBack(thismsg, userdata);
565 free(msglist); /* Clean up */
566 return num_processed;
572 * cmd_msgs() - get list of message #'s in this room
573 * implements the MSGS server command using CtdlForEachMessage()
575 void cmd_msgs(char *cmdbuf)
584 int with_template = 0;
585 struct CtdlMessage *template = NULL;
587 extract_token(which, cmdbuf, 0, '|', sizeof which);
588 cm_ref = extract_int(cmdbuf, 1);
589 with_template = extract_int(cmdbuf, 2);
593 if (!strncasecmp(which, "OLD", 3))
595 else if (!strncasecmp(which, "NEW", 3))
597 else if (!strncasecmp(which, "FIRST", 5))
599 else if (!strncasecmp(which, "LAST", 4))
601 else if (!strncasecmp(which, "GT", 2))
604 if ((!(CC->logged_in)) && (!(CC->internal_pgm))) {
605 cprintf("%d not logged in\n", ERROR + NOT_LOGGED_IN);
611 cprintf("%d Send template then receive message list\n",
613 template = (struct CtdlMessage *)
614 malloc(sizeof(struct CtdlMessage));
615 memset(template, 0, sizeof(struct CtdlMessage));
616 while(client_getln(buf, sizeof buf), strcmp(buf,"000")) {
617 extract_token(tfield, buf, 0, '|', sizeof tfield);
618 extract_token(tvalue, buf, 1, '|', sizeof tvalue);
619 for (i='A'; i<='Z'; ++i) if (msgkeys[i]!=NULL) {
620 if (!strcasecmp(tfield, msgkeys[i])) {
621 template->cm_fields[i] =
629 cprintf("%d Message list...\n", LISTING_FOLLOWS);
632 CtdlForEachMessage(mode, cm_ref,
633 NULL, template, simple_listing, NULL);
634 if (template != NULL) CtdlFreeMessage(template);
642 * help_subst() - support routine for help file viewer
644 void help_subst(char *strbuf, char *source, char *dest)
649 while (p = pattern2(strbuf, source), (p >= 0)) {
650 strcpy(workbuf, &strbuf[p + strlen(source)]);
651 strcpy(&strbuf[p], dest);
652 strcat(strbuf, workbuf);
657 void do_help_subst(char *buffer)
661 help_subst(buffer, "^nodename", config.c_nodename);
662 help_subst(buffer, "^humannode", config.c_humannode);
663 help_subst(buffer, "^fqdn", config.c_fqdn);
664 help_subst(buffer, "^username", CC->user.fullname);
665 snprintf(buf2, sizeof buf2, "%ld", CC->user.usernum);
666 help_subst(buffer, "^usernum", buf2);
667 help_subst(buffer, "^sysadm", config.c_sysadm);
668 help_subst(buffer, "^variantname", CITADEL);
669 snprintf(buf2, sizeof buf2, "%d", config.c_maxsessions);
670 help_subst(buffer, "^maxsessions", buf2);
671 help_subst(buffer, "^bbsdir", CTDLDIR);
677 * memfmout() - Citadel text formatter and paginator.
678 * Although the original purpose of this routine was to format
679 * text to the reader's screen width, all we're really using it
680 * for here is to format text out to 80 columns before sending it
681 * to the client. The client software may reformat it again.
684 int width, /* screen width to use */
685 char *mptr, /* where are we going to get our text from? */
686 char subst, /* nonzero if we should do substitutions */
687 char *nl) /* string to terminate lines with */
699 c = 1; /* c is the current pos */
703 while (ch = *mptr, ((ch != 0) && (strlen(buffer) < 126))) {
705 buffer[strlen(buffer) + 1] = 0;
706 buffer[strlen(buffer)] = ch;
709 if (buffer[0] == '^')
710 do_help_subst(buffer);
712 buffer[strlen(buffer) + 1] = 0;
714 strcpy(buffer, &buffer[1]);
722 if (((ch == 13) || (ch == 10)) && (old != 13) && (old != 10))
724 if (((old == 13) || (old == 10)) && (isspace(real))) {
732 if (((strlen(aaa) + c) > (width - 5)) && (strlen(aaa) > (width - 5))) {
733 cprintf("%s%s", nl, aaa);
742 if ((strlen(aaa) + c) > (width - 5)) {
751 if ((ch == 13) || (ch == 10)) {
752 cprintf("%s%s", aaa, nl);
759 cprintf("%s%s", aaa, nl);
765 * Callback function for mime parser that simply lists the part
767 void list_this_part(char *name, char *filename, char *partnum, char *disp,
768 void *content, char *cbtype, size_t length, char *encoding,
772 cprintf("part=%s|%s|%s|%s|%s|%ld\n",
773 name, filename, partnum, disp, cbtype, (long)length);
777 * Callback function for multipart prefix
779 void list_this_pref(char *name, char *filename, char *partnum, char *disp,
780 void *content, char *cbtype, size_t length, char *encoding,
783 cprintf("pref=%s|%s\n", partnum, cbtype);
787 * Callback function for multipart sufffix
789 void list_this_suff(char *name, char *filename, char *partnum, char *disp,
790 void *content, char *cbtype, size_t length, char *encoding,
793 cprintf("suff=%s|%s\n", partnum, cbtype);
798 * Callback function for mime parser that opens a section for downloading
800 void mime_download(char *name, char *filename, char *partnum, char *disp,
801 void *content, char *cbtype, size_t length, char *encoding,
805 /* Silently go away if there's already a download open... */
806 if (CC->download_fp != NULL)
809 /* ...or if this is not the desired section */
810 if (strcasecmp(CC->download_desired_section, partnum))
813 CC->download_fp = tmpfile();
814 if (CC->download_fp == NULL)
817 fwrite(content, length, 1, CC->download_fp);
818 fflush(CC->download_fp);
819 rewind(CC->download_fp);
821 OpenCmdResult(filename, cbtype);
827 * Load a message from disk into memory.
828 * This is used by CtdlOutputMsg() and other fetch functions.
830 * NOTE: Caller is responsible for freeing the returned CtdlMessage struct
831 * using the CtdlMessageFree() function.
833 struct CtdlMessage *CtdlFetchMessage(long msgnum, int with_body)
835 struct cdbdata *dmsgtext;
836 struct CtdlMessage *ret = NULL;
840 cit_uint8_t field_header;
842 lprintf(CTDL_DEBUG, "CtdlFetchMessage(%ld, %d)\n", msgnum, with_body);
844 dmsgtext = cdb_fetch(CDB_MSGMAIN, &msgnum, sizeof(long));
845 if (dmsgtext == NULL) {
848 mptr = dmsgtext->ptr;
849 upper_bound = mptr + dmsgtext->len;
851 /* Parse the three bytes that begin EVERY message on disk.
852 * The first is always 0xFF, the on-disk magic number.
853 * The second is the anonymous/public type byte.
854 * The third is the format type byte (vari, fixed, or MIME).
859 "Message %ld appears to be corrupted.\n",
864 ret = (struct CtdlMessage *) malloc(sizeof(struct CtdlMessage));
865 memset(ret, 0, sizeof(struct CtdlMessage));
867 ret->cm_magic = CTDLMESSAGE_MAGIC;
868 ret->cm_anon_type = *mptr++; /* Anon type byte */
869 ret->cm_format_type = *mptr++; /* Format type byte */
872 * The rest is zero or more arbitrary fields. Load them in.
873 * We're done when we encounter either a zero-length field or
874 * have just processed the 'M' (message text) field.
877 if (mptr >= upper_bound) {
880 field_header = *mptr++;
881 ret->cm_fields[field_header] = strdup(mptr);
883 while (*mptr++ != 0); /* advance to next field */
885 } while ((mptr < upper_bound) && (field_header != 'M'));
889 /* Always make sure there's something in the msg text field. If
890 * it's NULL, the message text is most likely stored separately,
891 * so go ahead and fetch that. Failing that, just set a dummy
892 * body so other code doesn't barf.
894 if ( (ret->cm_fields['M'] == NULL) && (with_body) ) {
895 dmsgtext = cdb_fetch(CDB_BIGMSGS, &msgnum, sizeof(long));
896 if (dmsgtext != NULL) {
897 ret->cm_fields['M'] = strdup(dmsgtext->ptr);
901 if (ret->cm_fields['M'] == NULL) {
902 ret->cm_fields['M'] = strdup("<no text>\n");
905 /* Perform "before read" hooks (aborting if any return nonzero) */
906 if (PerformMessageHooks(ret, EVT_BEFOREREAD) > 0) {
907 CtdlFreeMessage(ret);
916 * Returns 1 if the supplied pointer points to a valid Citadel message.
917 * If the pointer is NULL or the magic number check fails, returns 0.
919 int is_valid_message(struct CtdlMessage *msg) {
922 if ((msg->cm_magic) != CTDLMESSAGE_MAGIC) {
923 lprintf(CTDL_WARNING, "is_valid_message() -- self-check failed\n");
931 * 'Destructor' for struct CtdlMessage
933 void CtdlFreeMessage(struct CtdlMessage *msg)
937 if (is_valid_message(msg) == 0) return;
939 for (i = 0; i < 256; ++i)
940 if (msg->cm_fields[i] != NULL) {
941 free(msg->cm_fields[i]);
944 msg->cm_magic = 0; /* just in case */
950 * Pre callback function for multipart/alternative
952 * NOTE: this differs from the standard behavior for a reason. Normally when
953 * displaying multipart/alternative you want to show the _last_ usable
954 * format in the message. Here we show the _first_ one, because it's
955 * usually text/plain. Since this set of functions is designed for text
956 * output to non-MIME-aware clients, this is the desired behavior.
959 void fixed_output_pre(char *name, char *filename, char *partnum, char *disp,
960 void *content, char *cbtype, size_t length, char *encoding,
965 ma = (struct ma_info *)cbuserdata;
966 lprintf(CTDL_DEBUG, "fixed_output_pre() type=<%s>\n", cbtype);
967 if (!strcasecmp(cbtype, "multipart/alternative")) {
975 * Post callback function for multipart/alternative
977 void fixed_output_post(char *name, char *filename, char *partnum, char *disp,
978 void *content, char *cbtype, size_t length, char *encoding,
983 ma = (struct ma_info *)cbuserdata;
984 lprintf(CTDL_DEBUG, "fixed_output_post() type=<%s>\n", cbtype);
985 if (!strcasecmp(cbtype, "multipart/alternative")) {
993 * Inline callback function for mime parser that wants to display text
995 void fixed_output(char *name, char *filename, char *partnum, char *disp,
996 void *content, char *cbtype, size_t length, char *encoding,
1004 ma = (struct ma_info *)cbuserdata;
1006 lprintf(CTDL_DEBUG, "fixed_output() type=<%s>\n", cbtype);
1009 * If we're in the middle of a multipart/alternative scope and
1010 * we've already printed another section, skip this one.
1012 if ( (ma->is_ma == 1) && (ma->did_print == 1) ) {
1013 lprintf(CTDL_DEBUG, "Skipping part %s (%s)\n", partnum, cbtype);
1018 if ( (!strcasecmp(cbtype, "text/plain"))
1019 || (strlen(cbtype)==0) ) {
1022 client_write(wptr, length);
1023 if (wptr[length-1] != '\n') {
1028 else if (!strcasecmp(cbtype, "text/html")) {
1029 ptr = html_to_ascii(content, 80, 0);
1031 client_write(ptr, wlen);
1032 if (ptr[wlen-1] != '\n') {
1037 else if (strncasecmp(cbtype, "multipart/", 10)) {
1038 cprintf("Part %s: %s (%s) (%ld bytes)\r\n",
1039 partnum, filename, cbtype, (long)length);
1044 * The client is elegant and sophisticated and wants to be choosy about
1045 * MIME content types, so figure out which multipart/alternative part
1046 * we're going to send.
1048 void choose_preferred(char *name, char *filename, char *partnum, char *disp,
1049 void *content, char *cbtype, size_t length, char *encoding,
1056 ma = (struct ma_info *)cbuserdata;
1058 if (ma->is_ma > 0) {
1059 for (i=0; i<num_tokens(CC->preferred_formats, '|'); ++i) {
1060 extract_token(buf, CC->preferred_formats, i, '|', sizeof buf);
1061 if (!strcasecmp(buf, cbtype)) {
1062 strcpy(ma->chosen_part, partnum);
1069 * Now that we've chosen our preferred part, output it.
1071 void output_preferred(char *name, char *filename, char *partnum, char *disp,
1072 void *content, char *cbtype, size_t length, char *encoding,
1077 int add_newline = 0;
1081 ma = (struct ma_info *)cbuserdata;
1083 /* This is not the MIME part you're looking for... */
1084 if (strcasecmp(partnum, ma->chosen_part)) return;
1086 /* If the content-type of this part is in our preferred formats
1087 * list, we can simply output it verbatim.
1089 for (i=0; i<num_tokens(CC->preferred_formats, '|'); ++i) {
1090 extract_token(buf, CC->preferred_formats, i, '|', sizeof buf);
1091 if (!strcasecmp(buf, cbtype)) {
1092 /* Yeah! Go! W00t!! */
1094 text_content = (char *)content;
1095 if (text_content[length-1] != '\n') {
1099 cprintf("Content-type: %s\n", cbtype);
1100 cprintf("Content-length: %d\n",
1101 (int)(length + add_newline) );
1102 if (strlen(encoding) > 0) {
1103 cprintf("Content-transfer-encoding: %s\n", encoding);
1106 cprintf("Content-transfer-encoding: 7bit\n");
1109 client_write(content, length);
1110 if (add_newline) cprintf("\n");
1115 /* No translations required or possible: output as text/plain */
1116 cprintf("Content-type: text/plain\n\n");
1117 fixed_output(name, filename, partnum, disp, content, cbtype,
1118 length, encoding, cbuserdata);
1123 * Get a message off disk. (returns om_* values found in msgbase.h)
1126 int CtdlOutputMsg(long msg_num, /* message number (local) to fetch */
1127 int mode, /* how would you like that message? */
1128 int headers_only, /* eschew the message body? */
1129 int do_proto, /* do Citadel protocol responses? */
1130 int crlf /* Use CRLF newlines instead of LF? */
1132 struct CtdlMessage *TheMessage = NULL;
1133 int retcode = om_no_such_msg;
1135 lprintf(CTDL_DEBUG, "CtdlOutputMsg() msgnum=%ld, mode=%d\n",
1138 if ((!(CC->logged_in)) && (!(CC->internal_pgm))) {
1139 if (do_proto) cprintf("%d Not logged in.\n",
1140 ERROR + NOT_LOGGED_IN);
1141 return(om_not_logged_in);
1144 /* FIXME: check message id against msglist for this room */
1147 * Fetch the message from disk. If we're in any sort of headers
1148 * only mode, request that we don't even bother loading the body
1151 if ( (headers_only == HEADERS_FAST) || (headers_only == HEADERS_ONLY) ) {
1152 TheMessage = CtdlFetchMessage(msg_num, 0);
1155 TheMessage = CtdlFetchMessage(msg_num, 1);
1158 if (TheMessage == NULL) {
1159 if (do_proto) cprintf("%d Can't locate msg %ld on disk\n",
1160 ERROR + MESSAGE_NOT_FOUND, msg_num);
1161 return(om_no_such_msg);
1164 retcode = CtdlOutputPreLoadedMsg(
1165 TheMessage, msg_num, mode,
1166 headers_only, do_proto, crlf);
1168 CtdlFreeMessage(TheMessage);
1175 * Get a message off disk. (returns om_* values found in msgbase.h)
1178 int CtdlOutputPreLoadedMsg(
1179 struct CtdlMessage *TheMessage,
1181 int mode, /* how would you like that message? */
1182 int headers_only, /* eschew the message body? */
1183 int do_proto, /* do Citadel protocol responses? */
1184 int crlf /* Use CRLF newlines instead of LF? */
1190 char display_name[256];
1192 char *nl; /* newline string */
1194 int subject_found = 0;
1197 /* Buffers needed for RFC822 translation. These are all filled
1198 * using functions that are bounds-checked, and therefore we can
1199 * make them substantially smaller than SIZ.
1207 char datestamp[100];
1209 lprintf(CTDL_DEBUG, "CtdlOutputPreLoadedMsg(TheMessage=%s, %ld, %d, %d, %d, %d\n",
1210 ((TheMessage == NULL) ? "NULL" : "not null"),
1212 mode, headers_only, do_proto, crlf);
1214 snprintf(mid, sizeof mid, "%ld", msg_num);
1215 nl = (crlf ? "\r\n" : "\n");
1217 if (!is_valid_message(TheMessage)) {
1219 "ERROR: invalid preloaded message for output\n");
1220 return(om_no_such_msg);
1223 /* Are we downloading a MIME component? */
1224 if (mode == MT_DOWNLOAD) {
1225 if (TheMessage->cm_format_type != FMT_RFC822) {
1227 cprintf("%d This is not a MIME message.\n",
1228 ERROR + ILLEGAL_VALUE);
1229 } else if (CC->download_fp != NULL) {
1230 if (do_proto) cprintf(
1231 "%d You already have a download open.\n",
1232 ERROR + RESOURCE_BUSY);
1234 /* Parse the message text component */
1235 mptr = TheMessage->cm_fields['M'];
1236 ma = malloc(sizeof(struct ma_info));
1237 memset(ma, 0, sizeof(struct ma_info));
1238 mime_parser(mptr, NULL, *mime_download, NULL, NULL, (void *)ma, 0);
1240 /* If there's no file open by this time, the requested
1241 * section wasn't found, so print an error
1243 if (CC->download_fp == NULL) {
1244 if (do_proto) cprintf(
1245 "%d Section %s not found.\n",
1246 ERROR + FILE_NOT_FOUND,
1247 CC->download_desired_section);
1250 return((CC->download_fp != NULL) ? om_ok : om_mime_error);
1253 /* now for the user-mode message reading loops */
1254 if (do_proto) cprintf("%d Message %ld:\n", LISTING_FOLLOWS, msg_num);
1256 /* Does the caller want to skip the headers? */
1257 if (headers_only == HEADERS_NONE) goto START_TEXT;
1259 /* Tell the client which format type we're using. */
1260 if ( (mode == MT_CITADEL) && (do_proto) ) {
1261 cprintf("type=%d\n", TheMessage->cm_format_type);
1264 /* nhdr=yes means that we're only displaying headers, no body */
1265 if ( (TheMessage->cm_anon_type == MES_ANONONLY)
1266 && (mode == MT_CITADEL)
1269 cprintf("nhdr=yes\n");
1272 /* begin header processing loop for Citadel message format */
1274 if ((mode == MT_CITADEL) || (mode == MT_MIME)) {
1276 safestrncpy(display_name, "<unknown>", sizeof display_name);
1277 if (TheMessage->cm_fields['A']) {
1278 strcpy(buf, TheMessage->cm_fields['A']);
1279 if (TheMessage->cm_anon_type == MES_ANONONLY) {
1280 safestrncpy(display_name, "****", sizeof display_name);
1282 else if (TheMessage->cm_anon_type == MES_ANONOPT) {
1283 safestrncpy(display_name, "anonymous", sizeof display_name);
1286 safestrncpy(display_name, buf, sizeof display_name);
1288 if ((is_room_aide())
1289 && ((TheMessage->cm_anon_type == MES_ANONONLY)
1290 || (TheMessage->cm_anon_type == MES_ANONOPT))) {
1291 size_t tmp = strlen(display_name);
1292 snprintf(&display_name[tmp],
1293 sizeof display_name - tmp,
1298 /* Don't show Internet address for users on the
1299 * local Citadel network.
1302 if (TheMessage->cm_fields['N'] != NULL)
1303 if (strlen(TheMessage->cm_fields['N']) > 0)
1304 if (haschar(TheMessage->cm_fields['N'], '.') == 0) {
1308 /* Now spew the header fields in the order we like them. */
1309 safestrncpy(allkeys, FORDER, sizeof allkeys);
1310 for (i=0; i<strlen(allkeys); ++i) {
1311 k = (int) allkeys[i];
1313 if ( (TheMessage->cm_fields[k] != NULL)
1314 && (msgkeys[k] != NULL) ) {
1316 if (do_proto) cprintf("%s=%s\n",
1320 else if ((k == 'F') && (suppress_f)) {
1323 /* Masquerade display name if needed */
1325 if (do_proto) cprintf("%s=%s\n",
1327 TheMessage->cm_fields[k]
1336 /* begin header processing loop for RFC822 transfer format */
1341 strcpy(snode, NODENAME);
1342 strcpy(lnode, HUMANNODE);
1343 if (mode == MT_RFC822) {
1344 for (i = 0; i < 256; ++i) {
1345 if (TheMessage->cm_fields[i]) {
1346 mptr = TheMessage->cm_fields[i];
1349 safestrncpy(luser, mptr, sizeof luser);
1350 safestrncpy(suser, mptr, sizeof suser);
1352 else if (i == 'U') {
1353 cprintf("Subject: %s%s", mptr, nl);
1357 safestrncpy(mid, mptr, sizeof mid);
1359 safestrncpy(lnode, mptr, sizeof lnode);
1361 safestrncpy(fuser, mptr, sizeof fuser);
1362 /* else if (i == 'O')
1363 cprintf("X-Citadel-Room: %s%s",
1366 safestrncpy(snode, mptr, sizeof snode);
1368 cprintf("To: %s%s", mptr, nl);
1369 else if (i == 'T') {
1370 datestring(datestamp, sizeof datestamp,
1371 atol(mptr), DATESTRING_RFC822);
1372 cprintf("Date: %s%s", datestamp, nl);
1376 if (subject_found == 0) {
1377 cprintf("Subject: (no subject)%s", nl);
1381 for (i=0; i<strlen(suser); ++i) {
1382 suser[i] = tolower(suser[i]);
1383 if (!isalnum(suser[i])) suser[i]='_';
1386 if (mode == MT_RFC822) {
1387 if (!strcasecmp(snode, NODENAME)) {
1388 safestrncpy(snode, FQDN, sizeof snode);
1391 /* Construct a fun message id */
1392 cprintf("Message-ID: <%s", mid);
1393 if (strchr(mid, '@')==NULL) {
1394 cprintf("@%s", snode);
1398 if (!is_room_aide() && (TheMessage->cm_anon_type == MES_ANONONLY)) {
1399 // cprintf("From: x@x.org (----)%s", nl);
1400 cprintf("From: \"----\" <x@x.org>%s", nl);
1402 else if (!is_room_aide() && (TheMessage->cm_anon_type == MES_ANONOPT)) {
1403 // cprintf("From: x@x.org (anonymous)%s", nl);
1404 cprintf("From: \"anonymous\" <x@x.org>%s", nl);
1406 else if (strlen(fuser) > 0) {
1407 // cprintf("From: %s (%s)%s", fuser, luser, nl);
1408 cprintf("From: \"%s\" <%s>%s", luser, fuser, nl);
1411 // cprintf("From: %s@%s (%s)%s", suser, snode, luser, nl);
1412 cprintf("From: \"%s\" <%s@%s>%s", luser, suser, snode, nl);
1415 cprintf("Organization: %s%s", lnode, nl);
1417 /* Blank line signifying RFC822 end-of-headers */
1418 if (TheMessage->cm_format_type != FMT_RFC822) {
1423 /* end header processing loop ... at this point, we're in the text */
1425 if (headers_only == HEADERS_FAST) goto DONE;
1426 mptr = TheMessage->cm_fields['M'];
1428 /* Tell the client about the MIME parts in this message */
1429 if (TheMessage->cm_format_type == FMT_RFC822) {
1430 if ( (mode == MT_CITADEL) || (mode == MT_MIME) ) {
1431 mime_parser(mptr, NULL,
1432 (do_proto ? *list_this_part : NULL),
1433 (do_proto ? *list_this_pref : NULL),
1434 (do_proto ? *list_this_suff : NULL),
1437 else if (mode == MT_RFC822) { /* unparsed RFC822 dump */
1438 /* FIXME ... we have to put some code in here to avoid
1439 * printing duplicate header information when both
1440 * Citadel and RFC822 headers exist. Preference should
1441 * probably be given to the RFC822 headers.
1443 int done_rfc822_hdrs = 0;
1444 while (ch=*(mptr++), ch!=0) {
1449 if (!done_rfc822_hdrs) {
1450 if (headers_only != HEADERS_NONE) {
1455 if (headers_only != HEADERS_ONLY) {
1459 if ((*(mptr) == 13) || (*(mptr) == 10)) {
1460 done_rfc822_hdrs = 1;
1464 if (done_rfc822_hdrs) {
1465 if (headers_only != HEADERS_NONE) {
1470 if (headers_only != HEADERS_ONLY) {
1474 if ((*mptr == 13) || (*mptr == 10)) {
1475 done_rfc822_hdrs = 1;
1483 if (headers_only == HEADERS_ONLY) {
1487 /* signify start of msg text */
1488 if ( (mode == MT_CITADEL) || (mode == MT_MIME) ) {
1489 if (do_proto) cprintf("text\n");
1492 /* If the format type on disk is 1 (fixed-format), then we want
1493 * everything to be output completely literally ... regardless of
1494 * what message transfer format is in use.
1496 if (TheMessage->cm_format_type == FMT_FIXED) {
1497 if (mode == MT_MIME) {
1498 cprintf("Content-type: text/plain\n\n");
1501 while (ch = *mptr++, ch > 0) {
1504 if ((ch == 10) || (strlen(buf) > 250)) {
1505 cprintf("%s%s", buf, nl);
1508 buf[strlen(buf) + 1] = 0;
1509 buf[strlen(buf)] = ch;
1512 if (strlen(buf) > 0)
1513 cprintf("%s%s", buf, nl);
1516 /* If the message on disk is format 0 (Citadel vari-format), we
1517 * output using the formatter at 80 columns. This is the final output
1518 * form if the transfer format is RFC822, but if the transfer format
1519 * is Citadel proprietary, it'll still work, because the indentation
1520 * for new paragraphs is correct and the client will reformat the
1521 * message to the reader's screen width.
1523 if (TheMessage->cm_format_type == FMT_CITADEL) {
1524 if (mode == MT_MIME) {
1525 cprintf("Content-type: text/x-citadel-variformat\n\n");
1527 memfmout(80, mptr, 0, nl);
1530 /* If the message on disk is format 4 (MIME), we've gotta hand it
1531 * off to the MIME parser. The client has already been told that
1532 * this message is format 1 (fixed format), so the callback function
1533 * we use will display those parts as-is.
1535 if (TheMessage->cm_format_type == FMT_RFC822) {
1536 ma = malloc(sizeof(struct ma_info));
1537 memset(ma, 0, sizeof(struct ma_info));
1539 if (mode == MT_MIME) {
1540 strcpy(ma->chosen_part, "1");
1541 mime_parser(mptr, NULL,
1542 *choose_preferred, *fixed_output_pre,
1543 *fixed_output_post, (void *)ma, 0);
1544 mime_parser(mptr, NULL,
1545 *output_preferred, NULL, NULL, (void *)ma, 0);
1548 mime_parser(mptr, NULL,
1549 *fixed_output, *fixed_output_pre,
1550 *fixed_output_post, (void *)ma, 0);
1556 DONE: /* now we're done */
1557 if (do_proto) cprintf("000\n");
1564 * display a message (mode 0 - Citadel proprietary)
1566 void cmd_msg0(char *cmdbuf)
1569 int headers_only = HEADERS_ALL;
1571 msgid = extract_long(cmdbuf, 0);
1572 headers_only = extract_int(cmdbuf, 1);
1574 CtdlOutputMsg(msgid, MT_CITADEL, headers_only, 1, 0);
1580 * display a message (mode 2 - RFC822)
1582 void cmd_msg2(char *cmdbuf)
1585 int headers_only = HEADERS_ALL;
1587 msgid = extract_long(cmdbuf, 0);
1588 headers_only = extract_int(cmdbuf, 1);
1590 CtdlOutputMsg(msgid, MT_RFC822, headers_only, 1, 1);
1596 * display a message (mode 3 - IGnet raw format - internal programs only)
1598 void cmd_msg3(char *cmdbuf)
1601 struct CtdlMessage *msg;
1604 if (CC->internal_pgm == 0) {
1605 cprintf("%d This command is for internal programs only.\n",
1606 ERROR + HIGHER_ACCESS_REQUIRED);
1610 msgnum = extract_long(cmdbuf, 0);
1611 msg = CtdlFetchMessage(msgnum, 1);
1613 cprintf("%d Message %ld not found.\n",
1614 ERROR + MESSAGE_NOT_FOUND, msgnum);
1618 serialize_message(&smr, msg);
1619 CtdlFreeMessage(msg);
1622 cprintf("%d Unable to serialize message\n",
1623 ERROR + INTERNAL_ERROR);
1627 cprintf("%d %ld\n", BINARY_FOLLOWS, (long)smr.len);
1628 client_write((char *)smr.ser, (int)smr.len);
1635 * Display a message using MIME content types
1637 void cmd_msg4(char *cmdbuf)
1641 msgid = extract_long(cmdbuf, 0);
1642 CtdlOutputMsg(msgid, MT_MIME, 0, 1, 0);
1648 * Client tells us its preferred message format(s)
1650 void cmd_msgp(char *cmdbuf)
1652 safestrncpy(CC->preferred_formats, cmdbuf,
1653 sizeof(CC->preferred_formats));
1654 cprintf("%d ok\n", CIT_OK);
1659 * Open a component of a MIME message as a download file
1661 void cmd_opna(char *cmdbuf)
1664 char desired_section[128];
1666 msgid = extract_long(cmdbuf, 0);
1667 extract_token(desired_section, cmdbuf, 1, '|', sizeof desired_section);
1668 safestrncpy(CC->download_desired_section, desired_section, sizeof CC->download_desired_section);
1669 CtdlOutputMsg(msgid, MT_DOWNLOAD, 0, 1, 1);
1674 * Save a message pointer into a specified room
1675 * (Returns 0 for success, nonzero for failure)
1676 * roomname may be NULL to use the current room
1678 int CtdlSaveMsgPointerInRoom(char *roomname, long msgid, int flags) {
1680 char hold_rm[ROOMNAMELEN];
1681 struct cdbdata *cdbfr;
1684 long highest_msg = 0L;
1685 struct CtdlMessage *msg = NULL;
1687 lprintf(CTDL_DEBUG, "CtdlSaveMsgPointerInRoom(%s, %ld, %d)\n",
1688 roomname, msgid, flags);
1690 strcpy(hold_rm, CC->room.QRname);
1692 /* We may need to check to see if this message is real */
1693 if ( (flags & SM_VERIFY_GOODNESS)
1694 || (flags & SM_DO_REPL_CHECK)
1696 msg = CtdlFetchMessage(msgid, 1);
1697 if (msg == NULL) return(ERROR + ILLEGAL_VALUE);
1700 /* Perform replication checks if necessary */
1701 if ( (flags & SM_DO_REPL_CHECK) && (msg != NULL) ) {
1703 if (getroom(&CC->room,
1704 ((roomname != NULL) ? roomname : CC->room.QRname) )
1706 lprintf(CTDL_ERR, "No such room <%s>\n", roomname);
1707 if (msg != NULL) CtdlFreeMessage(msg);
1708 return(ERROR + ROOM_NOT_FOUND);
1711 if (ReplicationChecks(msg) != 0) {
1712 getroom(&CC->room, hold_rm);
1713 if (msg != NULL) CtdlFreeMessage(msg);
1715 "Did replication, and newer exists\n");
1720 /* Now the regular stuff */
1721 if (lgetroom(&CC->room,
1722 ((roomname != NULL) ? roomname : CC->room.QRname) )
1724 lprintf(CTDL_ERR, "No such room <%s>\n", roomname);
1725 if (msg != NULL) CtdlFreeMessage(msg);
1726 return(ERROR + ROOM_NOT_FOUND);
1729 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long));
1730 if (cdbfr == NULL) {
1734 msglist = malloc(cdbfr->len);
1735 if (msglist == NULL)
1736 lprintf(CTDL_ALERT, "ERROR malloc msglist!\n");
1737 num_msgs = cdbfr->len / sizeof(long);
1738 memcpy(msglist, cdbfr->ptr, cdbfr->len);
1743 /* Make sure the message doesn't already exist in this room. It
1744 * is absolutely taboo to have more than one reference to the same
1745 * message in a room.
1747 if (num_msgs > 0) for (i=0; i<num_msgs; ++i) {
1748 if (msglist[i] == msgid) {
1749 lputroom(&CC->room); /* unlock the room */
1750 getroom(&CC->room, hold_rm);
1751 if (msg != NULL) CtdlFreeMessage(msg);
1753 return(ERROR + ALREADY_EXISTS);
1757 /* Now add the new message */
1759 msglist = realloc(msglist, (num_msgs * sizeof(long)));
1761 if (msglist == NULL) {
1762 lprintf(CTDL_ALERT, "ERROR: can't realloc message list!\n");
1764 msglist[num_msgs - 1] = msgid;
1766 /* Sort the message list, so all the msgid's are in order */
1767 num_msgs = sort_msglist(msglist, num_msgs);
1769 /* Determine the highest message number */
1770 highest_msg = msglist[num_msgs - 1];
1772 /* Write it back to disk. */
1773 cdb_store(CDB_MSGLISTS, &CC->room.QRnumber, (int)sizeof(long),
1774 msglist, (int)(num_msgs * sizeof(long)));
1776 /* Free up the memory we used. */
1779 /* Update the highest-message pointer and unlock the room. */
1780 CC->room.QRhighest = highest_msg;
1781 lputroom(&CC->room);
1782 getroom(&CC->room, hold_rm);
1784 /* Bump the reference count for this message. */
1785 if ((flags & SM_DONT_BUMP_REF)==0) {
1786 AdjRefCount(msgid, +1);
1789 /* Return success. */
1790 if (msg != NULL) CtdlFreeMessage(msg);
1797 * Message base operation to save a new message to the message store
1798 * (returns new message number)
1800 * This is the back end for CtdlSubmitMsg() and should not be directly
1801 * called by server-side modules.
1804 long send_message(struct CtdlMessage *msg) {
1812 /* Get a new message number */
1813 newmsgid = get_new_message_number();
1814 snprintf(msgidbuf, sizeof msgidbuf, "%ld@%s", newmsgid, config.c_fqdn);
1816 /* Generate an ID if we don't have one already */
1817 if (msg->cm_fields['I']==NULL) {
1818 msg->cm_fields['I'] = strdup(msgidbuf);
1821 /* If the message is big, set its body aside for storage elsewhere */
1822 if (msg->cm_fields['M'] != NULL) {
1823 if (strlen(msg->cm_fields['M']) > BIGMSG) {
1825 holdM = msg->cm_fields['M'];
1826 msg->cm_fields['M'] = NULL;
1830 /* Serialize our data structure for storage in the database */
1831 serialize_message(&smr, msg);
1834 msg->cm_fields['M'] = holdM;
1838 cprintf("%d Unable to serialize message\n",
1839 ERROR + INTERNAL_ERROR);
1843 /* Write our little bundle of joy into the message base */
1844 if (cdb_store(CDB_MSGMAIN, &newmsgid, (int)sizeof(long),
1845 smr.ser, smr.len) < 0) {
1846 lprintf(CTDL_ERR, "Can't store message\n");
1850 cdb_store(CDB_BIGMSGS,
1860 /* Free the memory we used for the serialized message */
1863 /* Return the *local* message ID to the caller
1864 * (even if we're storing an incoming network message)
1872 * Serialize a struct CtdlMessage into the format used on disk and network.
1874 * This function loads up a "struct ser_ret" (defined in server.h) which
1875 * contains the length of the serialized message and a pointer to the
1876 * serialized message in memory. THE LATTER MUST BE FREED BY THE CALLER.
1878 void serialize_message(struct ser_ret *ret, /* return values */
1879 struct CtdlMessage *msg) /* unserialized msg */
1883 static char *forder = FORDER;
1885 if (is_valid_message(msg) == 0) return; /* self check */
1888 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL)
1889 ret->len = ret->len +
1890 strlen(msg->cm_fields[(int)forder[i]]) + 2;
1892 lprintf(CTDL_DEBUG, "serialize_message() calling malloc(%ld)\n", (long)ret->len);
1893 ret->ser = malloc(ret->len);
1894 if (ret->ser == NULL) {
1900 ret->ser[1] = msg->cm_anon_type;
1901 ret->ser[2] = msg->cm_format_type;
1904 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL) {
1905 ret->ser[wlen++] = (char)forder[i];
1906 strcpy((char *)&ret->ser[wlen], msg->cm_fields[(int)forder[i]]);
1907 wlen = wlen + strlen(msg->cm_fields[(int)forder[i]]) + 1;
1909 if (ret->len != wlen) lprintf(CTDL_ERR, "ERROR: len=%ld wlen=%ld\n",
1910 (long)ret->len, (long)wlen);
1918 * Back end for the ReplicationChecks() function
1920 void check_repl(long msgnum, void *userdata) {
1921 lprintf(CTDL_DEBUG, "check_repl() replacing message %ld\n", msgnum);
1922 CtdlDeleteMessages(CC->room.QRname, msgnum, "");
1927 * Check to see if any messages already exist which carry the same Exclusive ID
1928 * as this one. If any are found, delete them.
1931 int ReplicationChecks(struct CtdlMessage *msg) {
1932 struct CtdlMessage *template;
1935 /* No exclusive id? Don't do anything. */
1936 if (msg->cm_fields['E'] == NULL) return 0;
1937 if (strlen(msg->cm_fields['E']) == 0) return 0;
1938 lprintf(CTDL_DEBUG, "Exclusive ID: <%s>\n", msg->cm_fields['E']);
1940 template = (struct CtdlMessage *) malloc(sizeof(struct CtdlMessage));
1941 memset(template, 0, sizeof(struct CtdlMessage));
1942 template->cm_fields['E'] = strdup(msg->cm_fields['E']);
1944 CtdlForEachMessage(MSGS_ALL, 0L, NULL, template, check_repl, NULL);
1946 CtdlFreeMessage(template);
1947 lprintf(CTDL_DEBUG, "ReplicationChecks() returning %d\n", abort_this);
1955 * Save a message to disk and submit it into the delivery system.
1957 long CtdlSubmitMsg(struct CtdlMessage *msg, /* message to save */
1958 struct recptypes *recps, /* recipients (if mail) */
1959 char *force /* force a particular room? */
1961 char submit_filename[128];
1962 char generated_timestamp[32];
1963 char hold_rm[ROOMNAMELEN];
1964 char actual_rm[ROOMNAMELEN];
1965 char force_room[ROOMNAMELEN];
1966 char content_type[SIZ]; /* We have to learn this */
1967 char recipient[SIZ];
1970 struct ctdluser userbuf;
1972 struct MetaData smi;
1973 FILE *network_fp = NULL;
1974 static int seqnum = 1;
1975 struct CtdlMessage *imsg = NULL;
1978 char *hold_R, *hold_D;
1980 lprintf(CTDL_DEBUG, "CtdlSubmitMsg() called\n");
1981 if (is_valid_message(msg) == 0) return(-1); /* self check */
1983 /* If this message has no timestamp, we take the liberty of
1984 * giving it one, right now.
1986 if (msg->cm_fields['T'] == NULL) {
1987 lprintf(CTDL_DEBUG, "Generating timestamp\n");
1988 snprintf(generated_timestamp, sizeof generated_timestamp, "%ld", (long)time(NULL));
1989 msg->cm_fields['T'] = strdup(generated_timestamp);
1992 /* If this message has no path, we generate one.
1994 if (msg->cm_fields['P'] == NULL) {
1995 lprintf(CTDL_DEBUG, "Generating path\n");
1996 if (msg->cm_fields['A'] != NULL) {
1997 msg->cm_fields['P'] = strdup(msg->cm_fields['A']);
1998 for (a=0; a<strlen(msg->cm_fields['P']); ++a) {
1999 if (isspace(msg->cm_fields['P'][a])) {
2000 msg->cm_fields['P'][a] = ' ';
2005 msg->cm_fields['P'] = strdup("unknown");
2009 if (force == NULL) {
2010 strcpy(force_room, "");
2013 strcpy(force_room, force);
2016 /* Learn about what's inside, because it's what's inside that counts */
2017 lprintf(CTDL_DEBUG, "Learning what's inside\n");
2018 if (msg->cm_fields['M'] == NULL) {
2019 lprintf(CTDL_ERR, "ERROR: attempt to save message with NULL body\n");
2023 switch (msg->cm_format_type) {
2025 strcpy(content_type, "text/x-citadel-variformat");
2028 strcpy(content_type, "text/plain");
2031 strcpy(content_type, "text/plain");
2032 mptr = bmstrstr(msg->cm_fields['M'],
2033 "Content-type: ", strncasecmp);
2035 safestrncpy(content_type, &mptr[14],
2036 sizeof content_type);
2037 for (a = 0; a < strlen(content_type); ++a) {
2038 if ((content_type[a] == ';')
2039 || (content_type[a] == ' ')
2040 || (content_type[a] == 13)
2041 || (content_type[a] == 10)) {
2042 content_type[a] = 0;
2048 /* Goto the correct room */
2049 lprintf(CTDL_DEBUG, "Selected room %s\n",
2050 (recps) ? CC->room.QRname : SENTITEMS);
2051 strcpy(hold_rm, CC->room.QRname);
2052 strcpy(actual_rm, CC->room.QRname);
2053 if (recps != NULL) {
2054 strcpy(actual_rm, SENTITEMS);
2057 /* If the user is a twit, move to the twit room for posting */
2058 lprintf(CTDL_DEBUG, "Handling twit stuff: %s\n",
2059 (CC->user.axlevel == 2) ? config.c_twitroom : "OK");
2061 if (CC->user.axlevel == 2) {
2062 strcpy(hold_rm, actual_rm);
2063 strcpy(actual_rm, config.c_twitroom);
2067 /* ...or if this message is destined for Aide> then go there. */
2068 if (strlen(force_room) > 0) {
2069 strcpy(actual_rm, force_room);
2072 lprintf(CTDL_DEBUG, "Final selection: %s\n", actual_rm);
2073 if (strcasecmp(actual_rm, CC->room.QRname)) {
2074 getroom(&CC->room, actual_rm);
2078 * If this message has no O (room) field, generate one.
2080 if (msg->cm_fields['O'] == NULL) {
2081 msg->cm_fields['O'] = strdup(CC->room.QRname);
2084 /* Perform "before save" hooks (aborting if any return nonzero) */
2085 lprintf(CTDL_DEBUG, "Performing before-save hooks\n");
2086 if (PerformMessageHooks(msg, EVT_BEFORESAVE) > 0) return(-3);
2088 /* If this message has an Exclusive ID, perform replication checks */
2089 lprintf(CTDL_DEBUG, "Performing replication checks\n");
2090 if (ReplicationChecks(msg) > 0) return(-4);
2092 /* Save it to disk */
2093 lprintf(CTDL_DEBUG, "Saving to disk\n");
2094 newmsgid = send_message(msg);
2095 if (newmsgid <= 0L) return(-5);
2097 /* Write a supplemental message info record. This doesn't have to
2098 * be a critical section because nobody else knows about this message
2101 lprintf(CTDL_DEBUG, "Creating MetaData record\n");
2102 memset(&smi, 0, sizeof(struct MetaData));
2103 smi.meta_msgnum = newmsgid;
2104 smi.meta_refcount = 0;
2105 safestrncpy(smi.meta_content_type, content_type,
2106 sizeof smi.meta_content_type);
2108 /* As part of the new metadata record, measure how
2109 * big this message will be when displayed as RFC822.
2110 * Both POP and IMAP use this, and it's best to just take the hit now
2111 * instead of having to potentially measure thousands of messages when
2112 * a mailbox is opened later.
2115 if (CC->redirect_buffer != NULL) {
2116 lprintf(CTDL_ALERT, "CC->redirect_buffer is not NULL during message submission!\n");
2119 CC->redirect_buffer = malloc(SIZ);
2120 CC->redirect_len = 0;
2121 CC->redirect_alloc = SIZ;
2122 CtdlOutputPreLoadedMsg(msg, 0L, MT_RFC822, HEADERS_ALL, 0, 1);
2123 smi.meta_rfc822_length = CC->redirect_len;
2124 free(CC->redirect_buffer);
2125 CC->redirect_buffer = NULL;
2126 CC->redirect_len = 0;
2127 CC->redirect_alloc = 0;
2131 /* Now figure out where to store the pointers */
2132 lprintf(CTDL_DEBUG, "Storing pointers\n");
2134 /* If this is being done by the networker delivering a private
2135 * message, we want to BYPASS saving the sender's copy (because there
2136 * is no local sender; it would otherwise go to the Trashcan).
2138 if ((!CC->internal_pgm) || (recps == NULL)) {
2139 if (CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 0) != 0) {
2140 lprintf(CTDL_ERR, "ERROR saving message pointer!\n");
2141 CtdlSaveMsgPointerInRoom(config.c_aideroom,
2146 /* For internet mail, drop a copy in the outbound queue room */
2148 if (recps->num_internet > 0) {
2149 CtdlSaveMsgPointerInRoom(SMTP_SPOOLOUT_ROOM, newmsgid, 0);
2152 /* If other rooms are specified, drop them there too. */
2154 if (recps->num_room > 0)
2155 for (i=0; i<num_tokens(recps->recp_room, '|'); ++i) {
2156 extract_token(recipient, recps->recp_room, i,
2157 '|', sizeof recipient);
2158 lprintf(CTDL_DEBUG, "Delivering to room <%s>\n", recipient);
2159 CtdlSaveMsgPointerInRoom(recipient, newmsgid, 0);
2162 /* Bump this user's messages posted counter. */
2163 lprintf(CTDL_DEBUG, "Updating user (FIXME defer this)\n");
2164 lgetuser(&CC->user, CC->curr_user);
2165 CC->user.posted = CC->user.posted + 1;
2166 lputuser(&CC->user);
2168 /* If this is private, local mail, make a copy in the
2169 * recipient's mailbox and bump the reference count.
2172 if (recps->num_local > 0)
2173 for (i=0; i<num_tokens(recps->recp_local, '|'); ++i) {
2174 extract_token(recipient, recps->recp_local, i,
2175 '|', sizeof recipient);
2176 lprintf(CTDL_DEBUG, "Delivering private local mail to <%s>\n",
2178 if (getuser(&userbuf, recipient) == 0) {
2179 MailboxName(actual_rm, sizeof actual_rm,
2180 &userbuf, MAILROOM);
2181 CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 0);
2182 BumpNewMailCounter(userbuf.usernum);
2185 lprintf(CTDL_DEBUG, "No user <%s>\n", recipient);
2186 CtdlSaveMsgPointerInRoom(config.c_aideroom,
2191 /* Perform "after save" hooks */
2192 lprintf(CTDL_DEBUG, "Performing after-save hooks\n");
2193 PerformMessageHooks(msg, EVT_AFTERSAVE);
2195 /* For IGnet mail, we have to save a new copy into the spooler for
2196 * each recipient, with the R and D fields set to the recipient and
2197 * destination-node. This has two ugly side effects: all other
2198 * recipients end up being unlisted in this recipient's copy of the
2199 * message, and it has to deliver multiple messages to the same
2200 * node. We'll revisit this again in a year or so when everyone has
2201 * a network spool receiver that can handle the new style messages.
2204 if (recps->num_ignet > 0)
2205 for (i=0; i<num_tokens(recps->recp_ignet, '|'); ++i) {
2206 extract_token(recipient, recps->recp_ignet, i,
2207 '|', sizeof recipient);
2209 hold_R = msg->cm_fields['R'];
2210 hold_D = msg->cm_fields['D'];
2211 msg->cm_fields['R'] = malloc(SIZ);
2212 msg->cm_fields['D'] = malloc(128);
2213 extract_token(msg->cm_fields['R'], recipient, 0, '@', SIZ);
2214 extract_token(msg->cm_fields['D'], recipient, 1, '@', 128);
2216 serialize_message(&smr, msg);
2218 snprintf(submit_filename, sizeof submit_filename,
2219 "./network/spoolin/netmail.%04lx.%04x.%04x",
2220 (long) getpid(), CC->cs_pid, ++seqnum);
2221 network_fp = fopen(submit_filename, "wb+");
2222 if (network_fp != NULL) {
2223 fwrite(smr.ser, smr.len, 1, network_fp);
2229 free(msg->cm_fields['R']);
2230 free(msg->cm_fields['D']);
2231 msg->cm_fields['R'] = hold_R;
2232 msg->cm_fields['D'] = hold_D;
2235 /* Go back to the room we started from */
2236 lprintf(CTDL_DEBUG, "Returning to original room %s\n", hold_rm);
2237 if (strcasecmp(hold_rm, CC->room.QRname))
2238 getroom(&CC->room, hold_rm);
2240 /* For internet mail, generate delivery instructions.
2241 * Yes, this is recursive. Deal with it. Infinite recursion does
2242 * not happen because the delivery instructions message does not
2243 * contain a recipient.
2246 if (recps->num_internet > 0) {
2247 lprintf(CTDL_DEBUG, "Generating delivery instructions\n");
2248 instr = malloc(SIZ * 2);
2249 snprintf(instr, SIZ * 2,
2250 "Content-type: %s\n\nmsgid|%ld\nsubmitted|%ld\n"
2252 SPOOLMIME, newmsgid, (long)time(NULL),
2253 msg->cm_fields['A'], msg->cm_fields['N']
2256 for (i=0; i<num_tokens(recps->recp_internet, '|'); ++i) {
2257 size_t tmp = strlen(instr);
2258 extract_token(recipient, recps->recp_internet, i, '|', sizeof recipient);
2259 snprintf(&instr[tmp], SIZ * 2 - tmp,
2260 "remote|%s|0||\n", recipient);
2263 imsg = malloc(sizeof(struct CtdlMessage));
2264 memset(imsg, 0, sizeof(struct CtdlMessage));
2265 imsg->cm_magic = CTDLMESSAGE_MAGIC;
2266 imsg->cm_anon_type = MES_NORMAL;
2267 imsg->cm_format_type = FMT_RFC822;
2268 imsg->cm_fields['A'] = strdup("Citadel");
2269 imsg->cm_fields['M'] = instr;
2270 CtdlSubmitMsg(imsg, NULL, SMTP_SPOOLOUT_ROOM);
2271 CtdlFreeMessage(imsg);
2280 * Convenience function for generating small administrative messages.
2282 void quickie_message(char *from, char *to, char *room, char *text,
2283 int format_type, char *subject)
2285 struct CtdlMessage *msg;
2286 struct recptypes *recp = NULL;
2288 msg = malloc(sizeof(struct CtdlMessage));
2289 memset(msg, 0, sizeof(struct CtdlMessage));
2290 msg->cm_magic = CTDLMESSAGE_MAGIC;
2291 msg->cm_anon_type = MES_NORMAL;
2292 msg->cm_format_type = format_type;
2293 msg->cm_fields['A'] = strdup(from);
2294 if (room != NULL) msg->cm_fields['O'] = strdup(room);
2295 msg->cm_fields['N'] = strdup(NODENAME);
2297 msg->cm_fields['R'] = strdup(to);
2298 recp = validate_recipients(to);
2300 if (subject != NULL) {
2301 msg->cm_fields['U'] = strdup(subject);
2303 msg->cm_fields['M'] = strdup(text);
2305 CtdlSubmitMsg(msg, recp, room);
2306 CtdlFreeMessage(msg);
2307 if (recp != NULL) free(recp);
2313 * Back end function used by CtdlMakeMessage() and similar functions
2315 char *CtdlReadMessageBody(char *terminator, /* token signalling EOT */
2316 size_t maxlen, /* maximum message length */
2317 char *exist, /* if non-null, append to it;
2318 exist is ALWAYS freed */
2319 int crlf /* CRLF newlines instead of LF */
2323 size_t message_len = 0;
2324 size_t buffer_len = 0;
2330 if (exist == NULL) {
2337 message_len = strlen(exist);
2338 buffer_len = message_len + 4096;
2339 m = realloc(exist, buffer_len);
2346 /* flush the input if we have nowhere to store it */
2351 /* read in the lines of message text one by one */
2353 if (client_getln(buf, (sizeof buf - 3)) < 1) finished = 1;
2354 if (!strcmp(buf, terminator)) finished = 1;
2356 strcat(buf, "\r\n");
2362 if ( (!flushing) && (!finished) ) {
2363 /* Measure the line */
2364 linelen = strlen(buf);
2366 /* augment the buffer if we have to */
2367 if ((message_len + linelen) >= buffer_len) {
2368 ptr = realloc(m, (buffer_len * 2) );
2369 if (ptr == NULL) { /* flush if can't allocate */
2372 buffer_len = (buffer_len * 2);
2374 lprintf(CTDL_DEBUG, "buffer_len is now %ld\n", (long)buffer_len);
2378 /* Add the new line to the buffer. NOTE: this loop must avoid
2379 * using functions like strcat() and strlen() because they
2380 * traverse the entire buffer upon every call, and doing that
2381 * for a multi-megabyte message slows it down beyond usability.
2383 strcpy(&m[message_len], buf);
2384 message_len += linelen;
2387 /* if we've hit the max msg length, flush the rest */
2388 if (message_len >= maxlen) flushing = 1;
2390 } while (!finished);
2398 * Build a binary message to be saved on disk.
2399 * (NOTE: if you supply 'preformatted_text', the buffer you give it
2400 * will become part of the message. This means you are no longer
2401 * responsible for managing that memory -- it will be freed along with
2402 * the rest of the fields when CtdlFreeMessage() is called.)
2405 struct CtdlMessage *CtdlMakeMessage(
2406 struct ctdluser *author, /* author's user structure */
2407 char *recipient, /* NULL if it's not mail */
2408 char *room, /* room where it's going */
2409 int type, /* see MES_ types in header file */
2410 int format_type, /* variformat, plain text, MIME... */
2411 char *fake_name, /* who we're masquerading as */
2412 char *subject, /* Subject (optional) */
2413 char *preformatted_text /* ...or NULL to read text from client */
2415 char dest_node[SIZ];
2417 struct CtdlMessage *msg;
2419 msg = malloc(sizeof(struct CtdlMessage));
2420 memset(msg, 0, sizeof(struct CtdlMessage));
2421 msg->cm_magic = CTDLMESSAGE_MAGIC;
2422 msg->cm_anon_type = type;
2423 msg->cm_format_type = format_type;
2425 /* Don't confuse the poor folks if it's not routed mail. */
2426 strcpy(dest_node, "");
2430 snprintf(buf, sizeof buf, "cit%ld", author->usernum); /* Path */
2431 msg->cm_fields['P'] = strdup(buf);
2433 snprintf(buf, sizeof buf, "%ld", (long)time(NULL)); /* timestamp */
2434 msg->cm_fields['T'] = strdup(buf);
2436 if (fake_name[0]) /* author */
2437 msg->cm_fields['A'] = strdup(fake_name);
2439 msg->cm_fields['A'] = strdup(author->fullname);
2441 if (CC->room.QRflags & QR_MAILBOX) { /* room */
2442 msg->cm_fields['O'] = strdup(&CC->room.QRname[11]);
2445 msg->cm_fields['O'] = strdup(CC->room.QRname);
2448 msg->cm_fields['N'] = strdup(NODENAME); /* nodename */
2449 msg->cm_fields['H'] = strdup(HUMANNODE); /* hnodename */
2451 if (recipient[0] != 0) {
2452 msg->cm_fields['R'] = strdup(recipient);
2454 if (dest_node[0] != 0) {
2455 msg->cm_fields['D'] = strdup(dest_node);
2458 if ( (author == &CC->user) && (strlen(CC->cs_inet_email) > 0) ) {
2459 msg->cm_fields['F'] = strdup(CC->cs_inet_email);
2462 if (subject != NULL) {
2464 if (strlen(subject) > 0) {
2465 msg->cm_fields['U'] = strdup(subject);
2469 if (preformatted_text != NULL) {
2470 msg->cm_fields['M'] = preformatted_text;
2473 msg->cm_fields['M'] = CtdlReadMessageBody("000",
2474 config.c_maxmsglen, NULL, 0);
2482 * Check to see whether we have permission to post a message in the current
2483 * room. Returns a *CITADEL ERROR CODE* and puts a message in errmsgbuf, or
2484 * returns 0 on success.
2486 int CtdlDoIHavePermissionToPostInThisRoom(char *errmsgbuf, size_t n) {
2488 if (!(CC->logged_in)) {
2489 snprintf(errmsgbuf, n, "Not logged in.");
2490 return (ERROR + NOT_LOGGED_IN);
2493 if ((CC->user.axlevel < 2)
2494 && ((CC->room.QRflags & QR_MAILBOX) == 0)) {
2495 snprintf(errmsgbuf, n, "Need to be validated to enter "
2496 "(except in %s> to sysop)", MAILROOM);
2497 return (ERROR + HIGHER_ACCESS_REQUIRED);
2500 if ((CC->user.axlevel < 4)
2501 && (CC->room.QRflags & QR_NETWORK)) {
2502 snprintf(errmsgbuf, n, "Need net privileges to enter here.");
2503 return (ERROR + HIGHER_ACCESS_REQUIRED);
2506 if ((CC->user.axlevel < 6)
2507 && (CC->room.QRflags & QR_READONLY)) {
2508 snprintf(errmsgbuf, n, "Sorry, this is a read-only room.");
2509 return (ERROR + HIGHER_ACCESS_REQUIRED);
2512 strcpy(errmsgbuf, "Ok");
2518 * Check to see if the specified user has Internet mail permission
2519 * (returns nonzero if permission is granted)
2521 int CtdlCheckInternetMailPermission(struct ctdluser *who) {
2523 /* Do not allow twits to send Internet mail */
2524 if (who->axlevel <= 2) return(0);
2526 /* Globally enabled? */
2527 if (config.c_restrict == 0) return(1);
2529 /* User flagged ok? */
2530 if (who->flags & US_INTERNET) return(2);
2532 /* Aide level access? */
2533 if (who->axlevel >= 6) return(3);
2535 /* No mail for you! */
2542 * Validate recipients, count delivery types and errors, and handle aliasing
2543 * FIXME check for dupes!!!!!
2545 struct recptypes *validate_recipients(char *recipients) {
2546 struct recptypes *ret;
2547 char this_recp[SIZ];
2548 char this_recp_cooked[SIZ];
2554 struct ctdluser tempUS;
2555 struct ctdlroom tempQR;
2558 ret = (struct recptypes *) malloc(sizeof(struct recptypes));
2559 if (ret == NULL) return(NULL);
2560 memset(ret, 0, sizeof(struct recptypes));
2563 ret->num_internet = 0;
2568 if (recipients == NULL) {
2571 else if (strlen(recipients) == 0) {
2575 /* Change all valid separator characters to commas */
2576 for (i=0; i<strlen(recipients); ++i) {
2577 if ((recipients[i] == ';') || (recipients[i] == '|')) {
2578 recipients[i] = ',';
2583 num_recps = num_tokens(recipients, ',');
2586 if (num_recps > 0) for (i=0; i<num_recps; ++i) {
2587 extract_token(this_recp, recipients, i, ',', sizeof this_recp);
2589 lprintf(CTDL_DEBUG, "Evaluating recipient #%d <%s>\n", i, this_recp);
2590 mailtype = alias(this_recp);
2591 mailtype = alias(this_recp);
2592 mailtype = alias(this_recp);
2593 for (j=0; j<=strlen(this_recp); ++j) {
2594 if (this_recp[j]=='_') {
2595 this_recp_cooked[j] = ' ';
2598 this_recp_cooked[j] = this_recp[j];
2604 if (!strcasecmp(this_recp, "sysop")) {
2606 strcpy(this_recp, config.c_aideroom);
2607 if (strlen(ret->recp_room) > 0) {
2608 strcat(ret->recp_room, "|");
2610 strcat(ret->recp_room, this_recp);
2612 else if (getuser(&tempUS, this_recp) == 0) {
2614 strcpy(this_recp, tempUS.fullname);
2615 if (strlen(ret->recp_local) > 0) {
2616 strcat(ret->recp_local, "|");
2618 strcat(ret->recp_local, this_recp);
2620 else if (getuser(&tempUS, this_recp_cooked) == 0) {
2622 strcpy(this_recp, tempUS.fullname);
2623 if (strlen(ret->recp_local) > 0) {
2624 strcat(ret->recp_local, "|");
2626 strcat(ret->recp_local, this_recp);
2628 else if ( (!strncasecmp(this_recp, "room_", 5))
2629 && (!getroom(&tempQR, &this_recp_cooked[5])) ) {
2631 if (strlen(ret->recp_room) > 0) {
2632 strcat(ret->recp_room, "|");
2634 strcat(ret->recp_room, &this_recp_cooked[5]);
2642 /* Yes, you're reading this correctly: if the target
2643 * domain points back to the local system or an attached
2644 * Citadel directory, the address is invalid. That's
2645 * because if the address were valid, we would have
2646 * already translated it to a local address by now.
2648 if (IsDirectory(this_recp)) {
2653 ++ret->num_internet;
2654 if (strlen(ret->recp_internet) > 0) {
2655 strcat(ret->recp_internet, "|");
2657 strcat(ret->recp_internet, this_recp);
2662 if (strlen(ret->recp_ignet) > 0) {
2663 strcat(ret->recp_ignet, "|");
2665 strcat(ret->recp_ignet, this_recp);
2673 if (strlen(ret->errormsg) == 0) {
2674 snprintf(append, sizeof append,
2675 "Invalid recipient: %s",
2679 snprintf(append, sizeof append,
2682 if ( (strlen(ret->errormsg) + strlen(append)) < SIZ) {
2683 strcat(ret->errormsg, append);
2687 if (strlen(ret->display_recp) == 0) {
2688 strcpy(append, this_recp);
2691 snprintf(append, sizeof append, ", %s",
2694 if ( (strlen(ret->display_recp)+strlen(append)) < SIZ) {
2695 strcat(ret->display_recp, append);
2700 if ((ret->num_local + ret->num_internet + ret->num_ignet +
2701 ret->num_room + ret->num_error) == 0) {
2703 strcpy(ret->errormsg, "No recipients specified.");
2706 lprintf(CTDL_DEBUG, "validate_recipients()\n");
2707 lprintf(CTDL_DEBUG, " local: %d <%s>\n", ret->num_local, ret->recp_local);
2708 lprintf(CTDL_DEBUG, " room: %d <%s>\n", ret->num_room, ret->recp_room);
2709 lprintf(CTDL_DEBUG, " inet: %d <%s>\n", ret->num_internet, ret->recp_internet);
2710 lprintf(CTDL_DEBUG, " ignet: %d <%s>\n", ret->num_ignet, ret->recp_ignet);
2711 lprintf(CTDL_DEBUG, " error: %d <%s>\n", ret->num_error, ret->errormsg);
2719 * message entry - mode 0 (normal)
2721 void cmd_ent0(char *entargs)
2725 char masquerade_as[SIZ];
2727 int format_type = 0;
2728 char newusername[SIZ];
2729 struct CtdlMessage *msg;
2733 struct recptypes *valid = NULL;
2740 post = extract_int(entargs, 0);
2741 extract_token(recp, entargs, 1, '|', sizeof recp);
2742 anon_flag = extract_int(entargs, 2);
2743 format_type = extract_int(entargs, 3);
2744 extract_token(subject, entargs, 4, '|', sizeof subject);
2745 do_confirm = extract_int(entargs, 6);
2747 /* first check to make sure the request is valid. */
2749 err = CtdlDoIHavePermissionToPostInThisRoom(errmsg, sizeof errmsg);
2751 cprintf("%d %s\n", err, errmsg);
2755 /* Check some other permission type things. */
2758 if (CC->user.axlevel < 6) {
2759 cprintf("%d You don't have permission to masquerade.\n",
2760 ERROR + HIGHER_ACCESS_REQUIRED);
2763 extract_token(newusername, entargs, 5, '|', sizeof newusername);
2764 memset(CC->fake_postname, 0, sizeof(CC->fake_postname) );
2765 safestrncpy(CC->fake_postname, newusername,
2766 sizeof(CC->fake_postname) );
2767 cprintf("%d ok\n", CIT_OK);
2770 CC->cs_flags |= CS_POSTING;
2772 /* In the Mail> room we have to behave a little differently --
2773 * make sure the user has specified at least one recipient. Then
2774 * validate the recipient(s).
2776 if ( (CC->room.QRflags & QR_MAILBOX)
2777 && (!strcasecmp(&CC->room.QRname[11], MAILROOM)) ) {
2779 if (CC->user.axlevel < 2) {
2780 strcpy(recp, "sysop");
2783 valid = validate_recipients(recp);
2784 if (valid->num_error > 0) {
2786 ERROR + NO_SUCH_USER, valid->errormsg);
2790 if (valid->num_internet > 0) {
2791 if (CtdlCheckInternetMailPermission(&CC->user)==0) {
2792 cprintf("%d You do not have permission "
2793 "to send Internet mail.\n",
2794 ERROR + HIGHER_ACCESS_REQUIRED);
2800 if ( ( (valid->num_internet + valid->num_ignet) > 0)
2801 && (CC->user.axlevel < 4) ) {
2802 cprintf("%d Higher access required for network mail.\n",
2803 ERROR + HIGHER_ACCESS_REQUIRED);
2808 if ((RESTRICT_INTERNET == 1) && (valid->num_internet > 0)
2809 && ((CC->user.flags & US_INTERNET) == 0)
2810 && (!CC->internal_pgm)) {
2811 cprintf("%d You don't have access to Internet mail.\n",
2812 ERROR + HIGHER_ACCESS_REQUIRED);
2819 /* Is this a room which has anonymous-only or anonymous-option? */
2820 anonymous = MES_NORMAL;
2821 if (CC->room.QRflags & QR_ANONONLY) {
2822 anonymous = MES_ANONONLY;
2824 if (CC->room.QRflags & QR_ANONOPT) {
2825 if (anon_flag == 1) { /* only if the user requested it */
2826 anonymous = MES_ANONOPT;
2830 if ((CC->room.QRflags & QR_MAILBOX) == 0) {
2834 /* If we're only checking the validity of the request, return
2835 * success without creating the message.
2838 cprintf("%d %s\n", CIT_OK,
2839 ((valid != NULL) ? valid->display_recp : "") );
2844 /* Handle author masquerading */
2845 if (CC->fake_postname[0]) {
2846 strcpy(masquerade_as, CC->fake_postname);
2848 else if (CC->fake_username[0]) {
2849 strcpy(masquerade_as, CC->fake_username);
2852 strcpy(masquerade_as, "");
2855 /* Read in the message from the client. */
2857 cprintf("%d send message\n", START_CHAT_MODE);
2859 cprintf("%d send message\n", SEND_LISTING);
2861 msg = CtdlMakeMessage(&CC->user, recp,
2862 CC->room.QRname, anonymous, format_type,
2863 masquerade_as, subject, NULL);
2866 msgnum = CtdlSubmitMsg(msg, valid, "");
2869 cprintf("%ld\n", msgnum);
2871 cprintf("Message accepted.\n");
2874 cprintf("Internal error.\n");
2876 if (msg->cm_fields['E'] != NULL) {
2877 cprintf("%s\n", msg->cm_fields['E']);
2884 CtdlFreeMessage(msg);
2886 CC->fake_postname[0] = '\0';
2894 * API function to delete messages which match a set of criteria
2895 * (returns the actual number of messages deleted)
2897 int CtdlDeleteMessages(char *room_name, /* which room */
2898 long dmsgnum, /* or "0" for any */
2899 char *content_type /* or "" for any */
2903 struct ctdlroom qrbuf;
2904 struct cdbdata *cdbfr;
2905 long *msglist = NULL;
2906 long *dellist = NULL;
2909 int num_deleted = 0;
2911 struct MetaData smi;
2913 lprintf(CTDL_DEBUG, "CtdlDeleteMessages(%s, %ld, %s)\n",
2914 room_name, dmsgnum, content_type);
2916 /* get room record, obtaining a lock... */
2917 if (lgetroom(&qrbuf, room_name) != 0) {
2918 lprintf(CTDL_ERR, "CtdlDeleteMessages(): Room <%s> not found\n",
2920 return (0); /* room not found */
2922 cdbfr = cdb_fetch(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long));
2924 if (cdbfr != NULL) {
2925 msglist = malloc(cdbfr->len);
2926 dellist = malloc(cdbfr->len);
2927 memcpy(msglist, cdbfr->ptr, cdbfr->len);
2928 num_msgs = cdbfr->len / sizeof(long);
2932 for (i = 0; i < num_msgs; ++i) {
2935 /* Set/clear a bit for each criterion */
2937 if ((dmsgnum == 0L) || (msglist[i] == dmsgnum)) {
2938 delete_this |= 0x01;
2940 if (strlen(content_type) == 0) {
2941 delete_this |= 0x02;
2943 GetMetaData(&smi, msglist[i]);
2944 if (!strcasecmp(smi.meta_content_type,
2946 delete_this |= 0x02;
2950 /* Delete message only if all bits are set */
2951 if (delete_this == 0x03) {
2952 dellist[num_deleted++] = msglist[i];
2957 num_msgs = sort_msglist(msglist, num_msgs);
2958 cdb_store(CDB_MSGLISTS, &qrbuf.QRnumber, (int)sizeof(long),
2959 msglist, (int)(num_msgs * sizeof(long)));
2961 qrbuf.QRhighest = msglist[num_msgs - 1];
2965 /* Go through the messages we pulled out of the index, and decrement
2966 * their reference counts by 1. If this is the only room the message
2967 * was in, the reference count will reach zero and the message will
2968 * automatically be deleted from the database. We do this in a
2969 * separate pass because there might be plug-in hooks getting called,
2970 * and we don't want that happening during an S_ROOMS critical
2973 if (num_deleted) for (i=0; i<num_deleted; ++i) {
2974 PerformDeleteHooks(qrbuf.QRname, dellist[i]);
2975 AdjRefCount(dellist[i], -1);
2978 /* Now free the memory we used, and go away. */
2979 if (msglist != NULL) free(msglist);
2980 if (dellist != NULL) free(dellist);
2981 lprintf(CTDL_DEBUG, "%d message(s) deleted.\n", num_deleted);
2982 return (num_deleted);
2988 * Check whether the current user has permission to delete messages from
2989 * the current room (returns 1 for yes, 0 for no)
2991 int CtdlDoIHavePermissionToDeleteMessagesFromThisRoom(void) {
2992 getuser(&CC->user, CC->curr_user);
2993 if ((CC->user.axlevel < 6)
2994 && (CC->user.usernum != CC->room.QRroomaide)
2995 && ((CC->room.QRflags & QR_MAILBOX) == 0)
2996 && (!(CC->internal_pgm))) {
3005 * Delete message from current room
3007 void cmd_dele(char *delstr)
3012 if (CtdlDoIHavePermissionToDeleteMessagesFromThisRoom() == 0) {
3013 cprintf("%d Higher access required.\n",
3014 ERROR + HIGHER_ACCESS_REQUIRED);
3017 delnum = extract_long(delstr, 0);
3019 num_deleted = CtdlDeleteMessages(CC->room.QRname, delnum, "");
3022 cprintf("%d %d message%s deleted.\n", CIT_OK,
3023 num_deleted, ((num_deleted != 1) ? "s" : ""));
3025 cprintf("%d Message %ld not found.\n", ERROR + MESSAGE_NOT_FOUND, delnum);
3031 * Back end API function for moves and deletes
3033 int CtdlCopyMsgToRoom(long msgnum, char *dest) {
3036 err = CtdlSaveMsgPointerInRoom(dest, msgnum,
3037 (SM_VERIFY_GOODNESS | SM_DO_REPL_CHECK) );
3038 if (err != 0) return(err);
3046 * move or copy a message to another room
3048 void cmd_move(char *args)
3051 char targ[ROOMNAMELEN];
3052 struct ctdlroom qtemp;
3058 num = extract_long(args, 0);
3059 extract_token(targ, args, 1, '|', sizeof targ);
3060 targ[ROOMNAMELEN - 1] = 0;
3061 is_copy = extract_int(args, 2);
3063 if (getroom(&qtemp, targ) != 0) {
3064 cprintf("%d '%s' does not exist.\n",
3065 ERROR + ROOM_NOT_FOUND, targ);
3069 getuser(&CC->user, CC->curr_user);
3070 CtdlRoomAccess(&qtemp, &CC->user, &ra, NULL);
3072 /* Check for permission to perform this operation.
3073 * Remember: "CC->room" is source, "qtemp" is target.
3077 /* Aides can move/copy */
3078 if (CC->user.axlevel >= 6) permit = 1;
3080 /* Room aides can move/copy */
3081 if (CC->user.usernum == CC->room.QRroomaide) permit = 1;
3083 /* Permit move/copy from personal rooms */
3084 if ((CC->room.QRflags & QR_MAILBOX)
3085 && (qtemp.QRflags & QR_MAILBOX)) permit = 1;
3087 /* Permit only copy from public to personal room */
3089 && (!(CC->room.QRflags & QR_MAILBOX))
3090 && (qtemp.QRflags & QR_MAILBOX)) permit = 1;
3092 /* User must have access to target room */
3093 if (!(ra & UA_KNOWN)) permit = 0;
3096 cprintf("%d Higher access required.\n",
3097 ERROR + HIGHER_ACCESS_REQUIRED);
3101 err = CtdlCopyMsgToRoom(num, targ);
3103 cprintf("%d Cannot store message in %s: error %d\n",
3108 /* Now delete the message from the source room,
3109 * if this is a 'move' rather than a 'copy' operation.
3112 CtdlDeleteMessages(CC->room.QRname, num, "");
3115 cprintf("%d Message %s.\n", CIT_OK, (is_copy ? "copied" : "moved") );
3121 * GetMetaData() - Get the supplementary record for a message
3123 void GetMetaData(struct MetaData *smibuf, long msgnum)
3126 struct cdbdata *cdbsmi;
3129 memset(smibuf, 0, sizeof(struct MetaData));
3130 smibuf->meta_msgnum = msgnum;
3131 smibuf->meta_refcount = 1; /* Default reference count is 1 */
3133 /* Use the negative of the message number for its supp record index */
3134 TheIndex = (0L - msgnum);
3136 cdbsmi = cdb_fetch(CDB_MSGMAIN, &TheIndex, sizeof(long));
3137 if (cdbsmi == NULL) {
3138 return; /* record not found; go with defaults */
3140 memcpy(smibuf, cdbsmi->ptr,
3141 ((cdbsmi->len > sizeof(struct MetaData)) ?
3142 sizeof(struct MetaData) : cdbsmi->len));
3149 * PutMetaData() - (re)write supplementary record for a message
3151 void PutMetaData(struct MetaData *smibuf)
3155 /* Use the negative of the message number for the metadata db index */
3156 TheIndex = (0L - smibuf->meta_msgnum);
3158 lprintf(CTDL_DEBUG, "PutMetaData(%ld) - ref count is %d\n",
3159 smibuf->meta_msgnum, smibuf->meta_refcount);
3161 cdb_store(CDB_MSGMAIN,
3162 &TheIndex, (int)sizeof(long),
3163 smibuf, (int)sizeof(struct MetaData));
3168 * AdjRefCount - change the reference count for a message;
3169 * delete the message if it reaches zero
3171 void AdjRefCount(long msgnum, int incr)
3174 struct MetaData smi;
3177 /* This is a *tight* critical section; please keep it that way, as
3178 * it may get called while nested in other critical sections.
3179 * Complicating this any further will surely cause deadlock!
3181 begin_critical_section(S_SUPPMSGMAIN);
3182 GetMetaData(&smi, msgnum);
3183 smi.meta_refcount += incr;
3185 end_critical_section(S_SUPPMSGMAIN);
3186 lprintf(CTDL_DEBUG, "msg %ld ref count incr %d, is now %d\n",
3187 msgnum, incr, smi.meta_refcount);
3189 /* If the reference count is now zero, delete the message
3190 * (and its supplementary record as well).
3191 * FIXME ... defer this so it doesn't keep the user waiting.
3193 if (smi.meta_refcount == 0) {
3194 lprintf(CTDL_DEBUG, "Deleting message <%ld>\n", msgnum);
3196 /* Remove from fulltext index */
3197 ft_index_message(msgnum, 0);
3199 /* Remove from message base */
3201 cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long));
3202 cdb_delete(CDB_BIGMSGS, &delnum, (int)sizeof(long));
3204 /* Remove metadata record */
3205 delnum = (0L - msgnum);
3206 cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long));
3211 * Write a generic object to this room
3213 * Note: this could be much more efficient. Right now we use two temporary
3214 * files, and still pull the message into memory as with all others.
3216 void CtdlWriteObject(char *req_room, /* Room to stuff it in */
3217 char *content_type, /* MIME type of this object */
3218 char *tempfilename, /* Where to fetch it from */
3219 struct ctdluser *is_mailbox, /* Mailbox room? */
3220 int is_binary, /* Is encoding necessary? */
3221 int is_unique, /* Del others of this type? */
3222 unsigned int flags /* Internal save flags */
3227 struct ctdlroom qrbuf;
3228 char roomname[ROOMNAMELEN];
3229 struct CtdlMessage *msg;
3231 char *raw_message = NULL;
3232 char *encoded_message = NULL;
3233 off_t raw_length = 0;
3235 if (is_mailbox != NULL)
3236 MailboxName(roomname, sizeof roomname, is_mailbox, req_room);
3238 safestrncpy(roomname, req_room, sizeof(roomname));
3239 lprintf(CTDL_DEBUG, "CtdlWriteObject() to <%s> (flags=%d)\n", roomname, flags);
3242 fp = fopen(tempfilename, "rb");
3244 lprintf(CTDL_CRIT, "Cannot open %s: %s\n",
3245 tempfilename, strerror(errno));
3248 fseek(fp, 0L, SEEK_END);
3249 raw_length = ftell(fp);
3251 lprintf(CTDL_DEBUG, "Raw length is %ld\n", (long)raw_length);
3253 raw_message = malloc((size_t)raw_length + 2);
3254 fread(raw_message, (size_t)raw_length, 1, fp);
3258 encoded_message = malloc((size_t)
3259 (((raw_length * 134) / 100) + 4096 ) );
3262 encoded_message = malloc((size_t)(raw_length + 4096));
3265 sprintf(encoded_message, "Content-type: %s\n", content_type);
3268 sprintf(&encoded_message[strlen(encoded_message)],
3269 "Content-transfer-encoding: base64\n\n"
3273 sprintf(&encoded_message[strlen(encoded_message)],
3274 "Content-transfer-encoding: 7bit\n\n"
3280 &encoded_message[strlen(encoded_message)],
3286 raw_message[raw_length] = 0;
3288 &encoded_message[strlen(encoded_message)],
3296 lprintf(CTDL_DEBUG, "Allocating\n");
3297 msg = malloc(sizeof(struct CtdlMessage));
3298 memset(msg, 0, sizeof(struct CtdlMessage));
3299 msg->cm_magic = CTDLMESSAGE_MAGIC;
3300 msg->cm_anon_type = MES_NORMAL;
3301 msg->cm_format_type = 4;
3302 msg->cm_fields['A'] = strdup(CC->user.fullname);
3303 msg->cm_fields['O'] = strdup(req_room);
3304 msg->cm_fields['N'] = strdup(config.c_nodename);
3305 msg->cm_fields['H'] = strdup(config.c_humannode);
3306 msg->cm_flags = flags;
3308 msg->cm_fields['M'] = encoded_message;
3310 /* Create the requested room if we have to. */
3311 if (getroom(&qrbuf, roomname) != 0) {
3312 create_room(roomname,
3313 ( (is_mailbox != NULL) ? 5 : 3 ),
3314 "", 0, 1, 0, VIEW_BBS);
3316 /* If the caller specified this object as unique, delete all
3317 * other objects of this type that are currently in the room.
3320 lprintf(CTDL_DEBUG, "Deleted %d other msgs of this type\n",
3321 CtdlDeleteMessages(roomname, 0L, content_type));
3323 /* Now write the data */
3324 CtdlSubmitMsg(msg, NULL, roomname);
3325 CtdlFreeMessage(msg);
3333 void CtdlGetSysConfigBackend(long msgnum, void *userdata) {
3334 config_msgnum = msgnum;
3338 char *CtdlGetSysConfig(char *sysconfname) {
3339 char hold_rm[ROOMNAMELEN];
3342 struct CtdlMessage *msg;
3345 strcpy(hold_rm, CC->room.QRname);
3346 if (getroom(&CC->room, SYSCONFIGROOM) != 0) {
3347 getroom(&CC->room, hold_rm);
3352 /* We want the last (and probably only) config in this room */
3353 begin_critical_section(S_CONFIG);
3354 config_msgnum = (-1L);
3355 CtdlForEachMessage(MSGS_LAST, 1, sysconfname, NULL,
3356 CtdlGetSysConfigBackend, NULL);
3357 msgnum = config_msgnum;
3358 end_critical_section(S_CONFIG);
3364 msg = CtdlFetchMessage(msgnum, 1);
3366 conf = strdup(msg->cm_fields['M']);
3367 CtdlFreeMessage(msg);
3374 getroom(&CC->room, hold_rm);
3376 if (conf != NULL) do {
3377 extract_token(buf, conf, 0, '\n', sizeof buf);
3378 strcpy(conf, &conf[strlen(buf)+1]);
3379 } while ( (strlen(conf)>0) && (strlen(buf)>0) );
3384 void CtdlPutSysConfig(char *sysconfname, char *sysconfdata) {
3385 char temp[PATH_MAX];
3388 strcpy(temp, tmpnam(NULL));
3390 fp = fopen(temp, "w");
3391 if (fp == NULL) return;
3392 fprintf(fp, "%s", sysconfdata);
3395 /* this handy API function does all the work for us */
3396 CtdlWriteObject(SYSCONFIGROOM, sysconfname, temp, NULL, 0, 1, 0);
3402 * Determine whether a given Internet address belongs to the current user
3404 int CtdlIsMe(char *addr, int addr_buf_len)
3406 struct recptypes *recp;
3409 recp = validate_recipients(addr);
3410 if (recp == NULL) return(0);
3412 if (recp->num_local == 0) {
3417 for (i=0; i<recp->num_local; ++i) {
3418 extract_token(addr, recp->recp_local, i, '|', addr_buf_len);
3419 if (!strcasecmp(addr, CC->user.fullname)) {
3431 * Citadel protocol command to do the same
3433 void cmd_isme(char *argbuf) {
3436 if (CtdlAccessCheck(ac_logged_in)) return;
3437 extract_token(addr, argbuf, 0, '|', sizeof addr);
3439 if (CtdlIsMe(addr, sizeof addr)) {
3440 cprintf("%d %s\n", CIT_OK, addr);
3443 cprintf("%d Not you.\n", ERROR + ILLEGAL_VALUE);