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 ctdluser *which_user, struct ctdlroom *which_room) {
311 struct cdbdata *cdbfr;
321 char *is_set; /* actually an array of booleans */
324 char setstr[SIZ], lostr[SIZ], histr[SIZ];
326 lprintf(CTDL_DEBUG, "CtdlSetSeen(%ld, %d, %d)\n",
327 target_msgnum, target_setting, which_set);
329 /* Learn about the user and room in question */
330 CtdlGetRelationship(&vbuf,
331 ((which_user != NULL) ? which_user : &CC->user),
332 ((which_room != NULL) ? which_room : &CC->room)
335 /* Load the message list */
336 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long));
338 msglist = malloc(cdbfr->len);
339 memcpy(msglist, cdbfr->ptr, cdbfr->len);
340 num_msgs = cdbfr->len / sizeof(long);
343 return; /* No messages at all? No further action. */
346 is_set = malloc(num_msgs * sizeof(char));
347 memset(is_set, 0, (num_msgs * sizeof(char)) );
349 /* Decide which message set we're manipulating */
351 case ctdlsetseen_seen:
352 safestrncpy(vset, vbuf.v_seen, sizeof vset);
354 case ctdlsetseen_answered:
355 safestrncpy(vset, vbuf.v_answered, sizeof vset);
359 /* lprintf(CTDL_DEBUG, "before optimize: %s\n", vset); */
361 /* Translate the existing sequence set into an array of booleans */
362 num_sets = num_tokens(vset, ',');
363 for (s=0; s<num_sets; ++s) {
364 extract_token(setstr, vset, s, ',', sizeof setstr);
366 extract_token(lostr, setstr, 0, ':', sizeof lostr);
367 if (num_tokens(setstr, ':') >= 2) {
368 extract_token(histr, setstr, 1, ':', sizeof histr);
369 if (!strcmp(histr, "*")) {
370 snprintf(histr, sizeof histr, "%ld", LONG_MAX);
374 strcpy(histr, lostr);
379 for (i = 0; i < num_msgs; ++i) {
380 if ((msglist[i] >= lo) && (msglist[i] <= hi)) {
386 /* Now translate the array of booleans back into a sequence set */
389 for (i=0; i<num_msgs; ++i) {
392 if (msglist[i] == target_msgnum) {
393 is_seen = target_setting;
402 if (lo < 0L) lo = msglist[i];
405 if ( ((is_seen == 0) && (was_seen == 1))
406 || ((is_seen == 1) && (i == num_msgs-1)) ) {
409 if ( (strlen(vset) + 20) > sizeof vset) {
410 strcpy(vset, &vset[20]);
419 snprintf(&vset[tmp], sizeof vset - tmp,
423 snprintf(&vset[tmp], sizeof vset - tmp,
432 /* Decide which message set we're manipulating */
434 case ctdlsetseen_seen:
435 safestrncpy(vbuf.v_seen, vset, sizeof vbuf.v_seen);
437 case ctdlsetseen_answered:
438 safestrncpy(vbuf.v_answered, vset,
439 sizeof vbuf.v_answered);
444 /* lprintf(CTDL_DEBUG, " after optimize: %s\n", vset); */
446 CtdlSetRelationship(&vbuf,
447 ((which_user != NULL) ? which_user : &CC->user),
448 ((which_room != NULL) ? which_room : &CC->room)
454 * API function to perform an operation for each qualifying message in the
455 * current room. (Returns the number of messages processed.)
457 int CtdlForEachMessage(int mode, long ref,
459 struct CtdlMessage *compare,
460 void (*CallBack) (long, void *),
466 struct cdbdata *cdbfr;
467 long *msglist = NULL;
469 int num_processed = 0;
472 struct CtdlMessage *msg;
475 int printed_lastold = 0;
477 /* Learn about the user and room in question */
479 getuser(&CC->user, CC->curr_user);
480 CtdlGetRelationship(&vbuf, &CC->user, &CC->room);
482 /* Load the message list */
483 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long));
485 msglist = malloc(cdbfr->len);
486 memcpy(msglist, cdbfr->ptr, cdbfr->len);
487 num_msgs = cdbfr->len / sizeof(long);
490 return 0; /* No messages at all? No further action. */
495 * Now begin the traversal.
497 if (num_msgs > 0) for (a = 0; a < num_msgs; ++a) {
499 /* If the caller is looking for a specific MIME type, filter
500 * out all messages which are not of the type requested.
502 if (content_type != NULL) if (strlen(content_type) > 0) {
504 /* This call to GetMetaData() sits inside this loop
505 * so that we only do the extra database read per msg
506 * if we need to. Doing the extra read all the time
507 * really kills the server. If we ever need to use
508 * metadata for another search criterion, we need to
509 * move the read somewhere else -- but still be smart
510 * enough to only do the read if the caller has
511 * specified something that will need it.
513 GetMetaData(&smi, msglist[a]);
515 if (strcasecmp(smi.meta_content_type, content_type)) {
521 num_msgs = sort_msglist(msglist, num_msgs);
523 /* If a template was supplied, filter out the messages which
524 * don't match. (This could induce some delays!)
527 if (compare != NULL) {
528 for (a = 0; a < num_msgs; ++a) {
529 msg = CtdlFetchMessage(msglist[a], 1);
531 if (CtdlMsgCmp(msg, compare)) {
534 CtdlFreeMessage(msg);
542 * Now iterate through the message list, according to the
543 * criteria supplied by the caller.
546 for (a = 0; a < num_msgs; ++a) {
547 thismsg = msglist[a];
548 is_seen = is_msg_in_sequence_set(vbuf.v_seen, thismsg);
549 if (is_seen) lastold = thismsg;
554 || ((mode == MSGS_OLD) && (is_seen))
555 || ((mode == MSGS_NEW) && (!is_seen))
556 || ((mode == MSGS_LAST) && (a >= (num_msgs - ref)))
557 || ((mode == MSGS_FIRST) && (a < ref))
558 || ((mode == MSGS_GT) && (thismsg > ref))
559 || ((mode == MSGS_EQ) && (thismsg == ref))
562 if ((mode == MSGS_NEW) && (CC->user.flags & US_LASTOLD) && (lastold > 0L) && (printed_lastold == 0) && (!is_seen)) {
564 CallBack(lastold, userdata);
568 if (CallBack) CallBack(thismsg, userdata);
572 free(msglist); /* Clean up */
573 return num_processed;
579 * cmd_msgs() - get list of message #'s in this room
580 * implements the MSGS server command using CtdlForEachMessage()
582 void cmd_msgs(char *cmdbuf)
591 int with_template = 0;
592 struct CtdlMessage *template = NULL;
594 extract_token(which, cmdbuf, 0, '|', sizeof which);
595 cm_ref = extract_int(cmdbuf, 1);
596 with_template = extract_int(cmdbuf, 2);
600 if (!strncasecmp(which, "OLD", 3))
602 else if (!strncasecmp(which, "NEW", 3))
604 else if (!strncasecmp(which, "FIRST", 5))
606 else if (!strncasecmp(which, "LAST", 4))
608 else if (!strncasecmp(which, "GT", 2))
611 if ((!(CC->logged_in)) && (!(CC->internal_pgm))) {
612 cprintf("%d not logged in\n", ERROR + NOT_LOGGED_IN);
618 cprintf("%d Send template then receive message list\n",
620 template = (struct CtdlMessage *)
621 malloc(sizeof(struct CtdlMessage));
622 memset(template, 0, sizeof(struct CtdlMessage));
623 while(client_getln(buf, sizeof buf), strcmp(buf,"000")) {
624 extract_token(tfield, buf, 0, '|', sizeof tfield);
625 extract_token(tvalue, buf, 1, '|', sizeof tvalue);
626 for (i='A'; i<='Z'; ++i) if (msgkeys[i]!=NULL) {
627 if (!strcasecmp(tfield, msgkeys[i])) {
628 template->cm_fields[i] =
636 cprintf("%d Message list...\n", LISTING_FOLLOWS);
639 CtdlForEachMessage(mode, cm_ref,
640 NULL, template, simple_listing, NULL);
641 if (template != NULL) CtdlFreeMessage(template);
649 * help_subst() - support routine for help file viewer
651 void help_subst(char *strbuf, char *source, char *dest)
656 while (p = pattern2(strbuf, source), (p >= 0)) {
657 strcpy(workbuf, &strbuf[p + strlen(source)]);
658 strcpy(&strbuf[p], dest);
659 strcat(strbuf, workbuf);
664 void do_help_subst(char *buffer)
668 help_subst(buffer, "^nodename", config.c_nodename);
669 help_subst(buffer, "^humannode", config.c_humannode);
670 help_subst(buffer, "^fqdn", config.c_fqdn);
671 help_subst(buffer, "^username", CC->user.fullname);
672 snprintf(buf2, sizeof buf2, "%ld", CC->user.usernum);
673 help_subst(buffer, "^usernum", buf2);
674 help_subst(buffer, "^sysadm", config.c_sysadm);
675 help_subst(buffer, "^variantname", CITADEL);
676 snprintf(buf2, sizeof buf2, "%d", config.c_maxsessions);
677 help_subst(buffer, "^maxsessions", buf2);
678 help_subst(buffer, "^bbsdir", CTDLDIR);
684 * memfmout() - Citadel text formatter and paginator.
685 * Although the original purpose of this routine was to format
686 * text to the reader's screen width, all we're really using it
687 * for here is to format text out to 80 columns before sending it
688 * to the client. The client software may reformat it again.
691 int width, /* screen width to use */
692 char *mptr, /* where are we going to get our text from? */
693 char subst, /* nonzero if we should do substitutions */
694 char *nl) /* string to terminate lines with */
706 c = 1; /* c is the current pos */
710 while (ch = *mptr, ((ch != 0) && (strlen(buffer) < 126))) {
712 buffer[strlen(buffer) + 1] = 0;
713 buffer[strlen(buffer)] = ch;
716 if (buffer[0] == '^')
717 do_help_subst(buffer);
719 buffer[strlen(buffer) + 1] = 0;
721 strcpy(buffer, &buffer[1]);
729 if (((ch == 13) || (ch == 10)) && (old != 13) && (old != 10))
731 if (((old == 13) || (old == 10)) && (isspace(real))) {
739 if (((strlen(aaa) + c) > (width - 5)) && (strlen(aaa) > (width - 5))) {
740 cprintf("%s%s", nl, aaa);
749 if ((strlen(aaa) + c) > (width - 5)) {
758 if ((ch == 13) || (ch == 10)) {
759 cprintf("%s%s", aaa, nl);
766 cprintf("%s%s", aaa, nl);
772 * Callback function for mime parser that simply lists the part
774 void list_this_part(char *name, char *filename, char *partnum, char *disp,
775 void *content, char *cbtype, size_t length, char *encoding,
779 cprintf("part=%s|%s|%s|%s|%s|%ld\n",
780 name, filename, partnum, disp, cbtype, (long)length);
784 * Callback function for multipart prefix
786 void list_this_pref(char *name, char *filename, char *partnum, char *disp,
787 void *content, char *cbtype, size_t length, char *encoding,
790 cprintf("pref=%s|%s\n", partnum, cbtype);
794 * Callback function for multipart sufffix
796 void list_this_suff(char *name, char *filename, char *partnum, char *disp,
797 void *content, char *cbtype, size_t length, char *encoding,
800 cprintf("suff=%s|%s\n", partnum, cbtype);
805 * Callback function for mime parser that opens a section for downloading
807 void mime_download(char *name, char *filename, char *partnum, char *disp,
808 void *content, char *cbtype, size_t length, char *encoding,
812 /* Silently go away if there's already a download open... */
813 if (CC->download_fp != NULL)
816 /* ...or if this is not the desired section */
817 if (strcasecmp(CC->download_desired_section, partnum))
820 CC->download_fp = tmpfile();
821 if (CC->download_fp == NULL)
824 fwrite(content, length, 1, CC->download_fp);
825 fflush(CC->download_fp);
826 rewind(CC->download_fp);
828 OpenCmdResult(filename, cbtype);
834 * Load a message from disk into memory.
835 * This is used by CtdlOutputMsg() and other fetch functions.
837 * NOTE: Caller is responsible for freeing the returned CtdlMessage struct
838 * using the CtdlMessageFree() function.
840 struct CtdlMessage *CtdlFetchMessage(long msgnum, int with_body)
842 struct cdbdata *dmsgtext;
843 struct CtdlMessage *ret = NULL;
847 cit_uint8_t field_header;
849 lprintf(CTDL_DEBUG, "CtdlFetchMessage(%ld, %d)\n", msgnum, with_body);
851 dmsgtext = cdb_fetch(CDB_MSGMAIN, &msgnum, sizeof(long));
852 if (dmsgtext == NULL) {
855 mptr = dmsgtext->ptr;
856 upper_bound = mptr + dmsgtext->len;
858 /* Parse the three bytes that begin EVERY message on disk.
859 * The first is always 0xFF, the on-disk magic number.
860 * The second is the anonymous/public type byte.
861 * The third is the format type byte (vari, fixed, or MIME).
866 "Message %ld appears to be corrupted.\n",
871 ret = (struct CtdlMessage *) malloc(sizeof(struct CtdlMessage));
872 memset(ret, 0, sizeof(struct CtdlMessage));
874 ret->cm_magic = CTDLMESSAGE_MAGIC;
875 ret->cm_anon_type = *mptr++; /* Anon type byte */
876 ret->cm_format_type = *mptr++; /* Format type byte */
879 * The rest is zero or more arbitrary fields. Load them in.
880 * We're done when we encounter either a zero-length field or
881 * have just processed the 'M' (message text) field.
884 if (mptr >= upper_bound) {
887 field_header = *mptr++;
888 ret->cm_fields[field_header] = strdup(mptr);
890 while (*mptr++ != 0); /* advance to next field */
892 } while ((mptr < upper_bound) && (field_header != 'M'));
896 /* Always make sure there's something in the msg text field. If
897 * it's NULL, the message text is most likely stored separately,
898 * so go ahead and fetch that. Failing that, just set a dummy
899 * body so other code doesn't barf.
901 if ( (ret->cm_fields['M'] == NULL) && (with_body) ) {
902 dmsgtext = cdb_fetch(CDB_BIGMSGS, &msgnum, sizeof(long));
903 if (dmsgtext != NULL) {
904 ret->cm_fields['M'] = strdup(dmsgtext->ptr);
908 if (ret->cm_fields['M'] == NULL) {
909 ret->cm_fields['M'] = strdup("<no text>\n");
912 /* Perform "before read" hooks (aborting if any return nonzero) */
913 if (PerformMessageHooks(ret, EVT_BEFOREREAD) > 0) {
914 CtdlFreeMessage(ret);
923 * Returns 1 if the supplied pointer points to a valid Citadel message.
924 * If the pointer is NULL or the magic number check fails, returns 0.
926 int is_valid_message(struct CtdlMessage *msg) {
929 if ((msg->cm_magic) != CTDLMESSAGE_MAGIC) {
930 lprintf(CTDL_WARNING, "is_valid_message() -- self-check failed\n");
938 * 'Destructor' for struct CtdlMessage
940 void CtdlFreeMessage(struct CtdlMessage *msg)
944 if (is_valid_message(msg) == 0) return;
946 for (i = 0; i < 256; ++i)
947 if (msg->cm_fields[i] != NULL) {
948 free(msg->cm_fields[i]);
951 msg->cm_magic = 0; /* just in case */
957 * Pre callback function for multipart/alternative
959 * NOTE: this differs from the standard behavior for a reason. Normally when
960 * displaying multipart/alternative you want to show the _last_ usable
961 * format in the message. Here we show the _first_ one, because it's
962 * usually text/plain. Since this set of functions is designed for text
963 * output to non-MIME-aware clients, this is the desired behavior.
966 void fixed_output_pre(char *name, char *filename, char *partnum, char *disp,
967 void *content, char *cbtype, size_t length, char *encoding,
972 ma = (struct ma_info *)cbuserdata;
973 lprintf(CTDL_DEBUG, "fixed_output_pre() type=<%s>\n", cbtype);
974 if (!strcasecmp(cbtype, "multipart/alternative")) {
982 * Post callback function for multipart/alternative
984 void fixed_output_post(char *name, char *filename, char *partnum, char *disp,
985 void *content, char *cbtype, size_t length, char *encoding,
990 ma = (struct ma_info *)cbuserdata;
991 lprintf(CTDL_DEBUG, "fixed_output_post() type=<%s>\n", cbtype);
992 if (!strcasecmp(cbtype, "multipart/alternative")) {
1000 * Inline callback function for mime parser that wants to display text
1002 void fixed_output(char *name, char *filename, char *partnum, char *disp,
1003 void *content, char *cbtype, size_t length, char *encoding,
1011 ma = (struct ma_info *)cbuserdata;
1013 lprintf(CTDL_DEBUG, "fixed_output() type=<%s>\n", cbtype);
1016 * If we're in the middle of a multipart/alternative scope and
1017 * we've already printed another section, skip this one.
1019 if ( (ma->is_ma == 1) && (ma->did_print == 1) ) {
1020 lprintf(CTDL_DEBUG, "Skipping part %s (%s)\n", partnum, cbtype);
1025 if ( (!strcasecmp(cbtype, "text/plain"))
1026 || (strlen(cbtype)==0) ) {
1029 client_write(wptr, length);
1030 if (wptr[length-1] != '\n') {
1035 else if (!strcasecmp(cbtype, "text/html")) {
1036 ptr = html_to_ascii(content, 80, 0);
1038 client_write(ptr, wlen);
1039 if (ptr[wlen-1] != '\n') {
1044 else if (strncasecmp(cbtype, "multipart/", 10)) {
1045 cprintf("Part %s: %s (%s) (%ld bytes)\r\n",
1046 partnum, filename, cbtype, (long)length);
1051 * The client is elegant and sophisticated and wants to be choosy about
1052 * MIME content types, so figure out which multipart/alternative part
1053 * we're going to send.
1055 void choose_preferred(char *name, char *filename, char *partnum, char *disp,
1056 void *content, char *cbtype, size_t length, char *encoding,
1063 ma = (struct ma_info *)cbuserdata;
1065 if (ma->is_ma > 0) {
1066 for (i=0; i<num_tokens(CC->preferred_formats, '|'); ++i) {
1067 extract_token(buf, CC->preferred_formats, i, '|', sizeof buf);
1068 if (!strcasecmp(buf, cbtype)) {
1069 strcpy(ma->chosen_part, partnum);
1076 * Now that we've chosen our preferred part, output it.
1078 void output_preferred(char *name, char *filename, char *partnum, char *disp,
1079 void *content, char *cbtype, size_t length, char *encoding,
1084 int add_newline = 0;
1088 ma = (struct ma_info *)cbuserdata;
1090 /* This is not the MIME part you're looking for... */
1091 if (strcasecmp(partnum, ma->chosen_part)) return;
1093 /* If the content-type of this part is in our preferred formats
1094 * list, we can simply output it verbatim.
1096 for (i=0; i<num_tokens(CC->preferred_formats, '|'); ++i) {
1097 extract_token(buf, CC->preferred_formats, i, '|', sizeof buf);
1098 if (!strcasecmp(buf, cbtype)) {
1099 /* Yeah! Go! W00t!! */
1101 text_content = (char *)content;
1102 if (text_content[length-1] != '\n') {
1106 cprintf("Content-type: %s\n", cbtype);
1107 cprintf("Content-length: %d\n",
1108 (int)(length + add_newline) );
1109 if (strlen(encoding) > 0) {
1110 cprintf("Content-transfer-encoding: %s\n", encoding);
1113 cprintf("Content-transfer-encoding: 7bit\n");
1116 client_write(content, length);
1117 if (add_newline) cprintf("\n");
1122 /* No translations required or possible: output as text/plain */
1123 cprintf("Content-type: text/plain\n\n");
1124 fixed_output(name, filename, partnum, disp, content, cbtype,
1125 length, encoding, cbuserdata);
1130 * Get a message off disk. (returns om_* values found in msgbase.h)
1133 int CtdlOutputMsg(long msg_num, /* message number (local) to fetch */
1134 int mode, /* how would you like that message? */
1135 int headers_only, /* eschew the message body? */
1136 int do_proto, /* do Citadel protocol responses? */
1137 int crlf /* Use CRLF newlines instead of LF? */
1139 struct CtdlMessage *TheMessage = NULL;
1140 int retcode = om_no_such_msg;
1142 lprintf(CTDL_DEBUG, "CtdlOutputMsg() msgnum=%ld, mode=%d\n",
1145 if ((!(CC->logged_in)) && (!(CC->internal_pgm))) {
1146 if (do_proto) cprintf("%d Not logged in.\n",
1147 ERROR + NOT_LOGGED_IN);
1148 return(om_not_logged_in);
1151 /* FIXME: check message id against msglist for this room */
1154 * Fetch the message from disk. If we're in any sort of headers
1155 * only mode, request that we don't even bother loading the body
1158 if ( (headers_only == HEADERS_FAST) || (headers_only == HEADERS_ONLY) ) {
1159 TheMessage = CtdlFetchMessage(msg_num, 0);
1162 TheMessage = CtdlFetchMessage(msg_num, 1);
1165 if (TheMessage == NULL) {
1166 if (do_proto) cprintf("%d Can't locate msg %ld on disk\n",
1167 ERROR + MESSAGE_NOT_FOUND, msg_num);
1168 return(om_no_such_msg);
1171 retcode = CtdlOutputPreLoadedMsg(
1172 TheMessage, msg_num, mode,
1173 headers_only, do_proto, crlf);
1175 CtdlFreeMessage(TheMessage);
1182 * Get a message off disk. (returns om_* values found in msgbase.h)
1185 int CtdlOutputPreLoadedMsg(
1186 struct CtdlMessage *TheMessage,
1188 int mode, /* how would you like that message? */
1189 int headers_only, /* eschew the message body? */
1190 int do_proto, /* do Citadel protocol responses? */
1191 int crlf /* Use CRLF newlines instead of LF? */
1197 char display_name[256];
1199 char *nl; /* newline string */
1201 int subject_found = 0;
1204 /* Buffers needed for RFC822 translation. These are all filled
1205 * using functions that are bounds-checked, and therefore we can
1206 * make them substantially smaller than SIZ.
1214 char datestamp[100];
1216 lprintf(CTDL_DEBUG, "CtdlOutputPreLoadedMsg(TheMessage=%s, %ld, %d, %d, %d, %d\n",
1217 ((TheMessage == NULL) ? "NULL" : "not null"),
1219 mode, headers_only, do_proto, crlf);
1221 snprintf(mid, sizeof mid, "%ld", msg_num);
1222 nl = (crlf ? "\r\n" : "\n");
1224 if (!is_valid_message(TheMessage)) {
1226 "ERROR: invalid preloaded message for output\n");
1227 return(om_no_such_msg);
1230 /* Are we downloading a MIME component? */
1231 if (mode == MT_DOWNLOAD) {
1232 if (TheMessage->cm_format_type != FMT_RFC822) {
1234 cprintf("%d This is not a MIME message.\n",
1235 ERROR + ILLEGAL_VALUE);
1236 } else if (CC->download_fp != NULL) {
1237 if (do_proto) cprintf(
1238 "%d You already have a download open.\n",
1239 ERROR + RESOURCE_BUSY);
1241 /* Parse the message text component */
1242 mptr = TheMessage->cm_fields['M'];
1243 ma = malloc(sizeof(struct ma_info));
1244 memset(ma, 0, sizeof(struct ma_info));
1245 mime_parser(mptr, NULL, *mime_download, NULL, NULL, (void *)ma, 0);
1247 /* If there's no file open by this time, the requested
1248 * section wasn't found, so print an error
1250 if (CC->download_fp == NULL) {
1251 if (do_proto) cprintf(
1252 "%d Section %s not found.\n",
1253 ERROR + FILE_NOT_FOUND,
1254 CC->download_desired_section);
1257 return((CC->download_fp != NULL) ? om_ok : om_mime_error);
1260 /* now for the user-mode message reading loops */
1261 if (do_proto) cprintf("%d Message %ld:\n", LISTING_FOLLOWS, msg_num);
1263 /* Does the caller want to skip the headers? */
1264 if (headers_only == HEADERS_NONE) goto START_TEXT;
1266 /* Tell the client which format type we're using. */
1267 if ( (mode == MT_CITADEL) && (do_proto) ) {
1268 cprintf("type=%d\n", TheMessage->cm_format_type);
1271 /* nhdr=yes means that we're only displaying headers, no body */
1272 if ( (TheMessage->cm_anon_type == MES_ANONONLY)
1273 && (mode == MT_CITADEL)
1276 cprintf("nhdr=yes\n");
1279 /* begin header processing loop for Citadel message format */
1281 if ((mode == MT_CITADEL) || (mode == MT_MIME)) {
1283 safestrncpy(display_name, "<unknown>", sizeof display_name);
1284 if (TheMessage->cm_fields['A']) {
1285 strcpy(buf, TheMessage->cm_fields['A']);
1286 if (TheMessage->cm_anon_type == MES_ANONONLY) {
1287 safestrncpy(display_name, "****", sizeof display_name);
1289 else if (TheMessage->cm_anon_type == MES_ANONOPT) {
1290 safestrncpy(display_name, "anonymous", sizeof display_name);
1293 safestrncpy(display_name, buf, sizeof display_name);
1295 if ((is_room_aide())
1296 && ((TheMessage->cm_anon_type == MES_ANONONLY)
1297 || (TheMessage->cm_anon_type == MES_ANONOPT))) {
1298 size_t tmp = strlen(display_name);
1299 snprintf(&display_name[tmp],
1300 sizeof display_name - tmp,
1305 /* Don't show Internet address for users on the
1306 * local Citadel network.
1309 if (TheMessage->cm_fields['N'] != NULL)
1310 if (strlen(TheMessage->cm_fields['N']) > 0)
1311 if (haschar(TheMessage->cm_fields['N'], '.') == 0) {
1315 /* Now spew the header fields in the order we like them. */
1316 safestrncpy(allkeys, FORDER, sizeof allkeys);
1317 for (i=0; i<strlen(allkeys); ++i) {
1318 k = (int) allkeys[i];
1320 if ( (TheMessage->cm_fields[k] != NULL)
1321 && (msgkeys[k] != NULL) ) {
1323 if (do_proto) cprintf("%s=%s\n",
1327 else if ((k == 'F') && (suppress_f)) {
1330 /* Masquerade display name if needed */
1332 if (do_proto) cprintf("%s=%s\n",
1334 TheMessage->cm_fields[k]
1343 /* begin header processing loop for RFC822 transfer format */
1348 strcpy(snode, NODENAME);
1349 strcpy(lnode, HUMANNODE);
1350 if (mode == MT_RFC822) {
1351 for (i = 0; i < 256; ++i) {
1352 if (TheMessage->cm_fields[i]) {
1353 mptr = TheMessage->cm_fields[i];
1356 safestrncpy(luser, mptr, sizeof luser);
1357 safestrncpy(suser, mptr, sizeof suser);
1359 else if (i == 'U') {
1360 cprintf("Subject: %s%s", mptr, nl);
1364 safestrncpy(mid, mptr, sizeof mid);
1366 safestrncpy(lnode, mptr, sizeof lnode);
1368 safestrncpy(fuser, mptr, sizeof fuser);
1369 /* else if (i == 'O')
1370 cprintf("X-Citadel-Room: %s%s",
1373 safestrncpy(snode, mptr, sizeof snode);
1375 cprintf("To: %s%s", mptr, nl);
1376 else if (i == 'T') {
1377 datestring(datestamp, sizeof datestamp,
1378 atol(mptr), DATESTRING_RFC822);
1379 cprintf("Date: %s%s", datestamp, nl);
1383 if (subject_found == 0) {
1384 cprintf("Subject: (no subject)%s", nl);
1388 for (i=0; i<strlen(suser); ++i) {
1389 suser[i] = tolower(suser[i]);
1390 if (!isalnum(suser[i])) suser[i]='_';
1393 if (mode == MT_RFC822) {
1394 if (!strcasecmp(snode, NODENAME)) {
1395 safestrncpy(snode, FQDN, sizeof snode);
1398 /* Construct a fun message id */
1399 cprintf("Message-ID: <%s", mid);
1400 if (strchr(mid, '@')==NULL) {
1401 cprintf("@%s", snode);
1405 if (!is_room_aide() && (TheMessage->cm_anon_type == MES_ANONONLY)) {
1406 // cprintf("From: x@x.org (----)%s", nl);
1407 cprintf("From: \"----\" <x@x.org>%s", nl);
1409 else if (!is_room_aide() && (TheMessage->cm_anon_type == MES_ANONOPT)) {
1410 // cprintf("From: x@x.org (anonymous)%s", nl);
1411 cprintf("From: \"anonymous\" <x@x.org>%s", nl);
1413 else if (strlen(fuser) > 0) {
1414 // cprintf("From: %s (%s)%s", fuser, luser, nl);
1415 cprintf("From: \"%s\" <%s>%s", luser, fuser, nl);
1418 // cprintf("From: %s@%s (%s)%s", suser, snode, luser, nl);
1419 cprintf("From: \"%s\" <%s@%s>%s", luser, suser, snode, nl);
1422 cprintf("Organization: %s%s", lnode, nl);
1424 /* Blank line signifying RFC822 end-of-headers */
1425 if (TheMessage->cm_format_type != FMT_RFC822) {
1430 /* end header processing loop ... at this point, we're in the text */
1432 if (headers_only == HEADERS_FAST) goto DONE;
1433 mptr = TheMessage->cm_fields['M'];
1435 /* Tell the client about the MIME parts in this message */
1436 if (TheMessage->cm_format_type == FMT_RFC822) {
1437 if ( (mode == MT_CITADEL) || (mode == MT_MIME) ) {
1438 mime_parser(mptr, NULL,
1439 (do_proto ? *list_this_part : NULL),
1440 (do_proto ? *list_this_pref : NULL),
1441 (do_proto ? *list_this_suff : NULL),
1444 else if (mode == MT_RFC822) { /* unparsed RFC822 dump */
1445 /* FIXME ... we have to put some code in here to avoid
1446 * printing duplicate header information when both
1447 * Citadel and RFC822 headers exist. Preference should
1448 * probably be given to the RFC822 headers.
1450 int done_rfc822_hdrs = 0;
1451 while (ch=*(mptr++), ch!=0) {
1456 if (!done_rfc822_hdrs) {
1457 if (headers_only != HEADERS_NONE) {
1462 if (headers_only != HEADERS_ONLY) {
1466 if ((*(mptr) == 13) || (*(mptr) == 10)) {
1467 done_rfc822_hdrs = 1;
1471 if (done_rfc822_hdrs) {
1472 if (headers_only != HEADERS_NONE) {
1477 if (headers_only != HEADERS_ONLY) {
1481 if ((*mptr == 13) || (*mptr == 10)) {
1482 done_rfc822_hdrs = 1;
1490 if (headers_only == HEADERS_ONLY) {
1494 /* signify start of msg text */
1495 if ( (mode == MT_CITADEL) || (mode == MT_MIME) ) {
1496 if (do_proto) cprintf("text\n");
1499 /* If the format type on disk is 1 (fixed-format), then we want
1500 * everything to be output completely literally ... regardless of
1501 * what message transfer format is in use.
1503 if (TheMessage->cm_format_type == FMT_FIXED) {
1504 if (mode == MT_MIME) {
1505 cprintf("Content-type: text/plain\n\n");
1508 while (ch = *mptr++, ch > 0) {
1511 if ((ch == 10) || (strlen(buf) > 250)) {
1512 cprintf("%s%s", buf, nl);
1515 buf[strlen(buf) + 1] = 0;
1516 buf[strlen(buf)] = ch;
1519 if (strlen(buf) > 0)
1520 cprintf("%s%s", buf, nl);
1523 /* If the message on disk is format 0 (Citadel vari-format), we
1524 * output using the formatter at 80 columns. This is the final output
1525 * form if the transfer format is RFC822, but if the transfer format
1526 * is Citadel proprietary, it'll still work, because the indentation
1527 * for new paragraphs is correct and the client will reformat the
1528 * message to the reader's screen width.
1530 if (TheMessage->cm_format_type == FMT_CITADEL) {
1531 if (mode == MT_MIME) {
1532 cprintf("Content-type: text/x-citadel-variformat\n\n");
1534 memfmout(80, mptr, 0, nl);
1537 /* If the message on disk is format 4 (MIME), we've gotta hand it
1538 * off to the MIME parser. The client has already been told that
1539 * this message is format 1 (fixed format), so the callback function
1540 * we use will display those parts as-is.
1542 if (TheMessage->cm_format_type == FMT_RFC822) {
1543 ma = malloc(sizeof(struct ma_info));
1544 memset(ma, 0, sizeof(struct ma_info));
1546 if (mode == MT_MIME) {
1547 strcpy(ma->chosen_part, "1");
1548 mime_parser(mptr, NULL,
1549 *choose_preferred, *fixed_output_pre,
1550 *fixed_output_post, (void *)ma, 0);
1551 mime_parser(mptr, NULL,
1552 *output_preferred, NULL, NULL, (void *)ma, 0);
1555 mime_parser(mptr, NULL,
1556 *fixed_output, *fixed_output_pre,
1557 *fixed_output_post, (void *)ma, 0);
1563 DONE: /* now we're done */
1564 if (do_proto) cprintf("000\n");
1571 * display a message (mode 0 - Citadel proprietary)
1573 void cmd_msg0(char *cmdbuf)
1576 int headers_only = HEADERS_ALL;
1578 msgid = extract_long(cmdbuf, 0);
1579 headers_only = extract_int(cmdbuf, 1);
1581 CtdlOutputMsg(msgid, MT_CITADEL, headers_only, 1, 0);
1587 * display a message (mode 2 - RFC822)
1589 void cmd_msg2(char *cmdbuf)
1592 int headers_only = HEADERS_ALL;
1594 msgid = extract_long(cmdbuf, 0);
1595 headers_only = extract_int(cmdbuf, 1);
1597 CtdlOutputMsg(msgid, MT_RFC822, headers_only, 1, 1);
1603 * display a message (mode 3 - IGnet raw format - internal programs only)
1605 void cmd_msg3(char *cmdbuf)
1608 struct CtdlMessage *msg;
1611 if (CC->internal_pgm == 0) {
1612 cprintf("%d This command is for internal programs only.\n",
1613 ERROR + HIGHER_ACCESS_REQUIRED);
1617 msgnum = extract_long(cmdbuf, 0);
1618 msg = CtdlFetchMessage(msgnum, 1);
1620 cprintf("%d Message %ld not found.\n",
1621 ERROR + MESSAGE_NOT_FOUND, msgnum);
1625 serialize_message(&smr, msg);
1626 CtdlFreeMessage(msg);
1629 cprintf("%d Unable to serialize message\n",
1630 ERROR + INTERNAL_ERROR);
1634 cprintf("%d %ld\n", BINARY_FOLLOWS, (long)smr.len);
1635 client_write((char *)smr.ser, (int)smr.len);
1642 * Display a message using MIME content types
1644 void cmd_msg4(char *cmdbuf)
1648 msgid = extract_long(cmdbuf, 0);
1649 CtdlOutputMsg(msgid, MT_MIME, 0, 1, 0);
1655 * Client tells us its preferred message format(s)
1657 void cmd_msgp(char *cmdbuf)
1659 safestrncpy(CC->preferred_formats, cmdbuf,
1660 sizeof(CC->preferred_formats));
1661 cprintf("%d ok\n", CIT_OK);
1666 * Open a component of a MIME message as a download file
1668 void cmd_opna(char *cmdbuf)
1671 char desired_section[128];
1673 msgid = extract_long(cmdbuf, 0);
1674 extract_token(desired_section, cmdbuf, 1, '|', sizeof desired_section);
1675 safestrncpy(CC->download_desired_section, desired_section, sizeof CC->download_desired_section);
1676 CtdlOutputMsg(msgid, MT_DOWNLOAD, 0, 1, 1);
1681 * Save a message pointer into a specified room
1682 * (Returns 0 for success, nonzero for failure)
1683 * roomname may be NULL to use the current room
1685 int CtdlSaveMsgPointerInRoom(char *roomname, long msgid, int flags) {
1687 char hold_rm[ROOMNAMELEN];
1688 struct cdbdata *cdbfr;
1691 long highest_msg = 0L;
1692 struct CtdlMessage *msg = NULL;
1694 lprintf(CTDL_DEBUG, "CtdlSaveMsgPointerInRoom(%s, %ld, %d)\n",
1695 roomname, msgid, flags);
1697 strcpy(hold_rm, CC->room.QRname);
1699 /* We may need to check to see if this message is real */
1700 if ( (flags & SM_VERIFY_GOODNESS)
1701 || (flags & SM_DO_REPL_CHECK)
1703 msg = CtdlFetchMessage(msgid, 1);
1704 if (msg == NULL) return(ERROR + ILLEGAL_VALUE);
1707 /* Perform replication checks if necessary */
1708 if ( (flags & SM_DO_REPL_CHECK) && (msg != NULL) ) {
1710 if (getroom(&CC->room,
1711 ((roomname != NULL) ? roomname : CC->room.QRname) )
1713 lprintf(CTDL_ERR, "No such room <%s>\n", roomname);
1714 if (msg != NULL) CtdlFreeMessage(msg);
1715 return(ERROR + ROOM_NOT_FOUND);
1718 if (ReplicationChecks(msg) != 0) {
1719 getroom(&CC->room, hold_rm);
1720 if (msg != NULL) CtdlFreeMessage(msg);
1722 "Did replication, and newer exists\n");
1727 /* Now the regular stuff */
1728 if (lgetroom(&CC->room,
1729 ((roomname != NULL) ? roomname : CC->room.QRname) )
1731 lprintf(CTDL_ERR, "No such room <%s>\n", roomname);
1732 if (msg != NULL) CtdlFreeMessage(msg);
1733 return(ERROR + ROOM_NOT_FOUND);
1736 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long));
1737 if (cdbfr == NULL) {
1741 msglist = malloc(cdbfr->len);
1742 if (msglist == NULL)
1743 lprintf(CTDL_ALERT, "ERROR malloc msglist!\n");
1744 num_msgs = cdbfr->len / sizeof(long);
1745 memcpy(msglist, cdbfr->ptr, cdbfr->len);
1750 /* Make sure the message doesn't already exist in this room. It
1751 * is absolutely taboo to have more than one reference to the same
1752 * message in a room.
1754 if (num_msgs > 0) for (i=0; i<num_msgs; ++i) {
1755 if (msglist[i] == msgid) {
1756 lputroom(&CC->room); /* unlock the room */
1757 getroom(&CC->room, hold_rm);
1758 if (msg != NULL) CtdlFreeMessage(msg);
1760 return(ERROR + ALREADY_EXISTS);
1764 /* Now add the new message */
1766 msglist = realloc(msglist, (num_msgs * sizeof(long)));
1768 if (msglist == NULL) {
1769 lprintf(CTDL_ALERT, "ERROR: can't realloc message list!\n");
1771 msglist[num_msgs - 1] = msgid;
1773 /* Sort the message list, so all the msgid's are in order */
1774 num_msgs = sort_msglist(msglist, num_msgs);
1776 /* Determine the highest message number */
1777 highest_msg = msglist[num_msgs - 1];
1779 /* Write it back to disk. */
1780 cdb_store(CDB_MSGLISTS, &CC->room.QRnumber, (int)sizeof(long),
1781 msglist, (int)(num_msgs * sizeof(long)));
1783 /* Free up the memory we used. */
1786 /* Update the highest-message pointer and unlock the room. */
1787 CC->room.QRhighest = highest_msg;
1788 lputroom(&CC->room);
1789 getroom(&CC->room, hold_rm);
1791 /* Bump the reference count for this message. */
1792 if ((flags & SM_DONT_BUMP_REF)==0) {
1793 AdjRefCount(msgid, +1);
1796 /* Return success. */
1797 if (msg != NULL) CtdlFreeMessage(msg);
1804 * Message base operation to save a new message to the message store
1805 * (returns new message number)
1807 * This is the back end for CtdlSubmitMsg() and should not be directly
1808 * called by server-side modules.
1811 long send_message(struct CtdlMessage *msg) {
1819 /* Get a new message number */
1820 newmsgid = get_new_message_number();
1821 snprintf(msgidbuf, sizeof msgidbuf, "%ld@%s", newmsgid, config.c_fqdn);
1823 /* Generate an ID if we don't have one already */
1824 if (msg->cm_fields['I']==NULL) {
1825 msg->cm_fields['I'] = strdup(msgidbuf);
1828 /* If the message is big, set its body aside for storage elsewhere */
1829 if (msg->cm_fields['M'] != NULL) {
1830 if (strlen(msg->cm_fields['M']) > BIGMSG) {
1832 holdM = msg->cm_fields['M'];
1833 msg->cm_fields['M'] = NULL;
1837 /* Serialize our data structure for storage in the database */
1838 serialize_message(&smr, msg);
1841 msg->cm_fields['M'] = holdM;
1845 cprintf("%d Unable to serialize message\n",
1846 ERROR + INTERNAL_ERROR);
1850 /* Write our little bundle of joy into the message base */
1851 if (cdb_store(CDB_MSGMAIN, &newmsgid, (int)sizeof(long),
1852 smr.ser, smr.len) < 0) {
1853 lprintf(CTDL_ERR, "Can't store message\n");
1857 cdb_store(CDB_BIGMSGS,
1867 /* Free the memory we used for the serialized message */
1870 /* Return the *local* message ID to the caller
1871 * (even if we're storing an incoming network message)
1879 * Serialize a struct CtdlMessage into the format used on disk and network.
1881 * This function loads up a "struct ser_ret" (defined in server.h) which
1882 * contains the length of the serialized message and a pointer to the
1883 * serialized message in memory. THE LATTER MUST BE FREED BY THE CALLER.
1885 void serialize_message(struct ser_ret *ret, /* return values */
1886 struct CtdlMessage *msg) /* unserialized msg */
1890 static char *forder = FORDER;
1892 if (is_valid_message(msg) == 0) return; /* self check */
1895 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL)
1896 ret->len = ret->len +
1897 strlen(msg->cm_fields[(int)forder[i]]) + 2;
1899 lprintf(CTDL_DEBUG, "serialize_message() calling malloc(%ld)\n", (long)ret->len);
1900 ret->ser = malloc(ret->len);
1901 if (ret->ser == NULL) {
1907 ret->ser[1] = msg->cm_anon_type;
1908 ret->ser[2] = msg->cm_format_type;
1911 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL) {
1912 ret->ser[wlen++] = (char)forder[i];
1913 strcpy((char *)&ret->ser[wlen], msg->cm_fields[(int)forder[i]]);
1914 wlen = wlen + strlen(msg->cm_fields[(int)forder[i]]) + 1;
1916 if (ret->len != wlen) lprintf(CTDL_ERR, "ERROR: len=%ld wlen=%ld\n",
1917 (long)ret->len, (long)wlen);
1925 * Back end for the ReplicationChecks() function
1927 void check_repl(long msgnum, void *userdata) {
1928 lprintf(CTDL_DEBUG, "check_repl() replacing message %ld\n", msgnum);
1929 CtdlDeleteMessages(CC->room.QRname, msgnum, "");
1934 * Check to see if any messages already exist which carry the same Exclusive ID
1935 * as this one. If any are found, delete them.
1938 int ReplicationChecks(struct CtdlMessage *msg) {
1939 struct CtdlMessage *template;
1942 /* No exclusive id? Don't do anything. */
1943 if (msg->cm_fields['E'] == NULL) return 0;
1944 if (strlen(msg->cm_fields['E']) == 0) return 0;
1945 lprintf(CTDL_DEBUG, "Exclusive ID: <%s>\n", msg->cm_fields['E']);
1947 template = (struct CtdlMessage *) malloc(sizeof(struct CtdlMessage));
1948 memset(template, 0, sizeof(struct CtdlMessage));
1949 template->cm_fields['E'] = strdup(msg->cm_fields['E']);
1951 CtdlForEachMessage(MSGS_ALL, 0L, NULL, template, check_repl, NULL);
1953 CtdlFreeMessage(template);
1954 lprintf(CTDL_DEBUG, "ReplicationChecks() returning %d\n", abort_this);
1962 * Save a message to disk and submit it into the delivery system.
1964 long CtdlSubmitMsg(struct CtdlMessage *msg, /* message to save */
1965 struct recptypes *recps, /* recipients (if mail) */
1966 char *force /* force a particular room? */
1968 char submit_filename[128];
1969 char generated_timestamp[32];
1970 char hold_rm[ROOMNAMELEN];
1971 char actual_rm[ROOMNAMELEN];
1972 char force_room[ROOMNAMELEN];
1973 char content_type[SIZ]; /* We have to learn this */
1974 char recipient[SIZ];
1977 struct ctdluser userbuf;
1979 struct MetaData smi;
1980 FILE *network_fp = NULL;
1981 static int seqnum = 1;
1982 struct CtdlMessage *imsg = NULL;
1985 char *hold_R, *hold_D;
1987 lprintf(CTDL_DEBUG, "CtdlSubmitMsg() called\n");
1988 if (is_valid_message(msg) == 0) return(-1); /* self check */
1990 /* If this message has no timestamp, we take the liberty of
1991 * giving it one, right now.
1993 if (msg->cm_fields['T'] == NULL) {
1994 lprintf(CTDL_DEBUG, "Generating timestamp\n");
1995 snprintf(generated_timestamp, sizeof generated_timestamp, "%ld", (long)time(NULL));
1996 msg->cm_fields['T'] = strdup(generated_timestamp);
1999 /* If this message has no path, we generate one.
2001 if (msg->cm_fields['P'] == NULL) {
2002 lprintf(CTDL_DEBUG, "Generating path\n");
2003 if (msg->cm_fields['A'] != NULL) {
2004 msg->cm_fields['P'] = strdup(msg->cm_fields['A']);
2005 for (a=0; a<strlen(msg->cm_fields['P']); ++a) {
2006 if (isspace(msg->cm_fields['P'][a])) {
2007 msg->cm_fields['P'][a] = ' ';
2012 msg->cm_fields['P'] = strdup("unknown");
2016 if (force == NULL) {
2017 strcpy(force_room, "");
2020 strcpy(force_room, force);
2023 /* Learn about what's inside, because it's what's inside that counts */
2024 lprintf(CTDL_DEBUG, "Learning what's inside\n");
2025 if (msg->cm_fields['M'] == NULL) {
2026 lprintf(CTDL_ERR, "ERROR: attempt to save message with NULL body\n");
2030 switch (msg->cm_format_type) {
2032 strcpy(content_type, "text/x-citadel-variformat");
2035 strcpy(content_type, "text/plain");
2038 strcpy(content_type, "text/plain");
2039 mptr = bmstrstr(msg->cm_fields['M'],
2040 "Content-type: ", strncasecmp);
2042 safestrncpy(content_type, &mptr[14],
2043 sizeof content_type);
2044 for (a = 0; a < strlen(content_type); ++a) {
2045 if ((content_type[a] == ';')
2046 || (content_type[a] == ' ')
2047 || (content_type[a] == 13)
2048 || (content_type[a] == 10)) {
2049 content_type[a] = 0;
2055 /* Goto the correct room */
2056 lprintf(CTDL_DEBUG, "Selected room %s\n",
2057 (recps) ? CC->room.QRname : SENTITEMS);
2058 strcpy(hold_rm, CC->room.QRname);
2059 strcpy(actual_rm, CC->room.QRname);
2060 if (recps != NULL) {
2061 strcpy(actual_rm, SENTITEMS);
2064 /* If the user is a twit, move to the twit room for posting */
2065 lprintf(CTDL_DEBUG, "Handling twit stuff: %s\n",
2066 (CC->user.axlevel == 2) ? config.c_twitroom : "OK");
2068 if (CC->user.axlevel == 2) {
2069 strcpy(hold_rm, actual_rm);
2070 strcpy(actual_rm, config.c_twitroom);
2074 /* ...or if this message is destined for Aide> then go there. */
2075 if (strlen(force_room) > 0) {
2076 strcpy(actual_rm, force_room);
2079 lprintf(CTDL_DEBUG, "Final selection: %s\n", actual_rm);
2080 if (strcasecmp(actual_rm, CC->room.QRname)) {
2081 getroom(&CC->room, actual_rm);
2085 * If this message has no O (room) field, generate one.
2087 if (msg->cm_fields['O'] == NULL) {
2088 msg->cm_fields['O'] = strdup(CC->room.QRname);
2091 /* Perform "before save" hooks (aborting if any return nonzero) */
2092 lprintf(CTDL_DEBUG, "Performing before-save hooks\n");
2093 if (PerformMessageHooks(msg, EVT_BEFORESAVE) > 0) return(-3);
2095 /* If this message has an Exclusive ID, perform replication checks */
2096 lprintf(CTDL_DEBUG, "Performing replication checks\n");
2097 if (ReplicationChecks(msg) > 0) return(-4);
2099 /* Save it to disk */
2100 lprintf(CTDL_DEBUG, "Saving to disk\n");
2101 newmsgid = send_message(msg);
2102 if (newmsgid <= 0L) return(-5);
2104 /* Write a supplemental message info record. This doesn't have to
2105 * be a critical section because nobody else knows about this message
2108 lprintf(CTDL_DEBUG, "Creating MetaData record\n");
2109 memset(&smi, 0, sizeof(struct MetaData));
2110 smi.meta_msgnum = newmsgid;
2111 smi.meta_refcount = 0;
2112 safestrncpy(smi.meta_content_type, content_type,
2113 sizeof smi.meta_content_type);
2115 /* As part of the new metadata record, measure how
2116 * big this message will be when displayed as RFC822.
2117 * Both POP and IMAP use this, and it's best to just take the hit now
2118 * instead of having to potentially measure thousands of messages when
2119 * a mailbox is opened later.
2122 if (CC->redirect_buffer != NULL) {
2123 lprintf(CTDL_ALERT, "CC->redirect_buffer is not NULL during message submission!\n");
2126 CC->redirect_buffer = malloc(SIZ);
2127 CC->redirect_len = 0;
2128 CC->redirect_alloc = SIZ;
2129 CtdlOutputPreLoadedMsg(msg, 0L, MT_RFC822, HEADERS_ALL, 0, 1);
2130 smi.meta_rfc822_length = CC->redirect_len;
2131 free(CC->redirect_buffer);
2132 CC->redirect_buffer = NULL;
2133 CC->redirect_len = 0;
2134 CC->redirect_alloc = 0;
2138 /* Now figure out where to store the pointers */
2139 lprintf(CTDL_DEBUG, "Storing pointers\n");
2141 /* If this is being done by the networker delivering a private
2142 * message, we want to BYPASS saving the sender's copy (because there
2143 * is no local sender; it would otherwise go to the Trashcan).
2145 if ((!CC->internal_pgm) || (recps == NULL)) {
2146 if (CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 0) != 0) {
2147 lprintf(CTDL_ERR, "ERROR saving message pointer!\n");
2148 CtdlSaveMsgPointerInRoom(config.c_aideroom,
2153 /* For internet mail, drop a copy in the outbound queue room */
2155 if (recps->num_internet > 0) {
2156 CtdlSaveMsgPointerInRoom(SMTP_SPOOLOUT_ROOM, newmsgid, 0);
2159 /* If other rooms are specified, drop them there too. */
2161 if (recps->num_room > 0)
2162 for (i=0; i<num_tokens(recps->recp_room, '|'); ++i) {
2163 extract_token(recipient, recps->recp_room, i,
2164 '|', sizeof recipient);
2165 lprintf(CTDL_DEBUG, "Delivering to room <%s>\n", recipient);
2166 CtdlSaveMsgPointerInRoom(recipient, newmsgid, 0);
2169 /* Bump this user's messages posted counter. */
2170 lprintf(CTDL_DEBUG, "Updating user\n");
2171 lgetuser(&CC->user, CC->curr_user);
2172 CC->user.posted = CC->user.posted + 1;
2173 lputuser(&CC->user);
2175 /* If this is private, local mail, make a copy in the
2176 * recipient's mailbox and bump the reference count.
2179 if (recps->num_local > 0)
2180 for (i=0; i<num_tokens(recps->recp_local, '|'); ++i) {
2181 extract_token(recipient, recps->recp_local, i,
2182 '|', sizeof recipient);
2183 lprintf(CTDL_DEBUG, "Delivering private local mail to <%s>\n",
2185 if (getuser(&userbuf, recipient) == 0) {
2186 MailboxName(actual_rm, sizeof actual_rm,
2187 &userbuf, MAILROOM);
2188 CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 0);
2189 BumpNewMailCounter(userbuf.usernum);
2192 lprintf(CTDL_DEBUG, "No user <%s>\n", recipient);
2193 CtdlSaveMsgPointerInRoom(config.c_aideroom,
2198 /* Perform "after save" hooks */
2199 lprintf(CTDL_DEBUG, "Performing after-save hooks\n");
2200 PerformMessageHooks(msg, EVT_AFTERSAVE);
2202 /* For IGnet mail, we have to save a new copy into the spooler for
2203 * each recipient, with the R and D fields set to the recipient and
2204 * destination-node. This has two ugly side effects: all other
2205 * recipients end up being unlisted in this recipient's copy of the
2206 * message, and it has to deliver multiple messages to the same
2207 * node. We'll revisit this again in a year or so when everyone has
2208 * a network spool receiver that can handle the new style messages.
2211 if (recps->num_ignet > 0)
2212 for (i=0; i<num_tokens(recps->recp_ignet, '|'); ++i) {
2213 extract_token(recipient, recps->recp_ignet, i,
2214 '|', sizeof recipient);
2216 hold_R = msg->cm_fields['R'];
2217 hold_D = msg->cm_fields['D'];
2218 msg->cm_fields['R'] = malloc(SIZ);
2219 msg->cm_fields['D'] = malloc(128);
2220 extract_token(msg->cm_fields['R'], recipient, 0, '@', SIZ);
2221 extract_token(msg->cm_fields['D'], recipient, 1, '@', 128);
2223 serialize_message(&smr, msg);
2225 snprintf(submit_filename, sizeof submit_filename,
2226 "./network/spoolin/netmail.%04lx.%04x.%04x",
2227 (long) getpid(), CC->cs_pid, ++seqnum);
2228 network_fp = fopen(submit_filename, "wb+");
2229 if (network_fp != NULL) {
2230 fwrite(smr.ser, smr.len, 1, network_fp);
2236 free(msg->cm_fields['R']);
2237 free(msg->cm_fields['D']);
2238 msg->cm_fields['R'] = hold_R;
2239 msg->cm_fields['D'] = hold_D;
2242 /* Go back to the room we started from */
2243 lprintf(CTDL_DEBUG, "Returning to original room %s\n", hold_rm);
2244 if (strcasecmp(hold_rm, CC->room.QRname))
2245 getroom(&CC->room, hold_rm);
2247 /* For internet mail, generate delivery instructions.
2248 * Yes, this is recursive. Deal with it. Infinite recursion does
2249 * not happen because the delivery instructions message does not
2250 * contain a recipient.
2253 if (recps->num_internet > 0) {
2254 lprintf(CTDL_DEBUG, "Generating delivery instructions\n");
2255 instr = malloc(SIZ * 2);
2256 snprintf(instr, SIZ * 2,
2257 "Content-type: %s\n\nmsgid|%ld\nsubmitted|%ld\n"
2259 SPOOLMIME, newmsgid, (long)time(NULL),
2260 msg->cm_fields['A'], msg->cm_fields['N']
2263 for (i=0; i<num_tokens(recps->recp_internet, '|'); ++i) {
2264 size_t tmp = strlen(instr);
2265 extract_token(recipient, recps->recp_internet, i, '|', sizeof recipient);
2266 snprintf(&instr[tmp], SIZ * 2 - tmp,
2267 "remote|%s|0||\n", recipient);
2270 imsg = malloc(sizeof(struct CtdlMessage));
2271 memset(imsg, 0, sizeof(struct CtdlMessage));
2272 imsg->cm_magic = CTDLMESSAGE_MAGIC;
2273 imsg->cm_anon_type = MES_NORMAL;
2274 imsg->cm_format_type = FMT_RFC822;
2275 imsg->cm_fields['A'] = strdup("Citadel");
2276 imsg->cm_fields['M'] = instr;
2277 CtdlSubmitMsg(imsg, NULL, SMTP_SPOOLOUT_ROOM);
2278 CtdlFreeMessage(imsg);
2287 * Convenience function for generating small administrative messages.
2289 void quickie_message(char *from, char *to, char *room, char *text,
2290 int format_type, char *subject)
2292 struct CtdlMessage *msg;
2293 struct recptypes *recp = NULL;
2295 msg = malloc(sizeof(struct CtdlMessage));
2296 memset(msg, 0, sizeof(struct CtdlMessage));
2297 msg->cm_magic = CTDLMESSAGE_MAGIC;
2298 msg->cm_anon_type = MES_NORMAL;
2299 msg->cm_format_type = format_type;
2300 msg->cm_fields['A'] = strdup(from);
2301 if (room != NULL) msg->cm_fields['O'] = strdup(room);
2302 msg->cm_fields['N'] = strdup(NODENAME);
2304 msg->cm_fields['R'] = strdup(to);
2305 recp = validate_recipients(to);
2307 if (subject != NULL) {
2308 msg->cm_fields['U'] = strdup(subject);
2310 msg->cm_fields['M'] = strdup(text);
2312 CtdlSubmitMsg(msg, recp, room);
2313 CtdlFreeMessage(msg);
2314 if (recp != NULL) free(recp);
2320 * Back end function used by CtdlMakeMessage() and similar functions
2322 char *CtdlReadMessageBody(char *terminator, /* token signalling EOT */
2323 size_t maxlen, /* maximum message length */
2324 char *exist, /* if non-null, append to it;
2325 exist is ALWAYS freed */
2326 int crlf /* CRLF newlines instead of LF */
2330 size_t message_len = 0;
2331 size_t buffer_len = 0;
2337 if (exist == NULL) {
2344 message_len = strlen(exist);
2345 buffer_len = message_len + 4096;
2346 m = realloc(exist, buffer_len);
2353 /* flush the input if we have nowhere to store it */
2358 /* read in the lines of message text one by one */
2360 if (client_getln(buf, (sizeof buf - 3)) < 1) finished = 1;
2361 if (!strcmp(buf, terminator)) finished = 1;
2363 strcat(buf, "\r\n");
2369 if ( (!flushing) && (!finished) ) {
2370 /* Measure the line */
2371 linelen = strlen(buf);
2373 /* augment the buffer if we have to */
2374 if ((message_len + linelen) >= buffer_len) {
2375 ptr = realloc(m, (buffer_len * 2) );
2376 if (ptr == NULL) { /* flush if can't allocate */
2379 buffer_len = (buffer_len * 2);
2381 lprintf(CTDL_DEBUG, "buffer_len is now %ld\n", (long)buffer_len);
2385 /* Add the new line to the buffer. NOTE: this loop must avoid
2386 * using functions like strcat() and strlen() because they
2387 * traverse the entire buffer upon every call, and doing that
2388 * for a multi-megabyte message slows it down beyond usability.
2390 strcpy(&m[message_len], buf);
2391 message_len += linelen;
2394 /* if we've hit the max msg length, flush the rest */
2395 if (message_len >= maxlen) flushing = 1;
2397 } while (!finished);
2405 * Build a binary message to be saved on disk.
2406 * (NOTE: if you supply 'preformatted_text', the buffer you give it
2407 * will become part of the message. This means you are no longer
2408 * responsible for managing that memory -- it will be freed along with
2409 * the rest of the fields when CtdlFreeMessage() is called.)
2412 struct CtdlMessage *CtdlMakeMessage(
2413 struct ctdluser *author, /* author's user structure */
2414 char *recipient, /* NULL if it's not mail */
2415 char *room, /* room where it's going */
2416 int type, /* see MES_ types in header file */
2417 int format_type, /* variformat, plain text, MIME... */
2418 char *fake_name, /* who we're masquerading as */
2419 char *subject, /* Subject (optional) */
2420 char *preformatted_text /* ...or NULL to read text from client */
2422 char dest_node[SIZ];
2424 struct CtdlMessage *msg;
2426 msg = malloc(sizeof(struct CtdlMessage));
2427 memset(msg, 0, sizeof(struct CtdlMessage));
2428 msg->cm_magic = CTDLMESSAGE_MAGIC;
2429 msg->cm_anon_type = type;
2430 msg->cm_format_type = format_type;
2432 /* Don't confuse the poor folks if it's not routed mail. */
2433 strcpy(dest_node, "");
2437 snprintf(buf, sizeof buf, "cit%ld", author->usernum); /* Path */
2438 msg->cm_fields['P'] = strdup(buf);
2440 snprintf(buf, sizeof buf, "%ld", (long)time(NULL)); /* timestamp */
2441 msg->cm_fields['T'] = strdup(buf);
2443 if (fake_name[0]) /* author */
2444 msg->cm_fields['A'] = strdup(fake_name);
2446 msg->cm_fields['A'] = strdup(author->fullname);
2448 if (CC->room.QRflags & QR_MAILBOX) { /* room */
2449 msg->cm_fields['O'] = strdup(&CC->room.QRname[11]);
2452 msg->cm_fields['O'] = strdup(CC->room.QRname);
2455 msg->cm_fields['N'] = strdup(NODENAME); /* nodename */
2456 msg->cm_fields['H'] = strdup(HUMANNODE); /* hnodename */
2458 if (recipient[0] != 0) {
2459 msg->cm_fields['R'] = strdup(recipient);
2461 if (dest_node[0] != 0) {
2462 msg->cm_fields['D'] = strdup(dest_node);
2465 if ( (author == &CC->user) && (strlen(CC->cs_inet_email) > 0) ) {
2466 msg->cm_fields['F'] = strdup(CC->cs_inet_email);
2469 if (subject != NULL) {
2471 if (strlen(subject) > 0) {
2472 msg->cm_fields['U'] = strdup(subject);
2476 if (preformatted_text != NULL) {
2477 msg->cm_fields['M'] = preformatted_text;
2480 msg->cm_fields['M'] = CtdlReadMessageBody("000",
2481 config.c_maxmsglen, NULL, 0);
2489 * Check to see whether we have permission to post a message in the current
2490 * room. Returns a *CITADEL ERROR CODE* and puts a message in errmsgbuf, or
2491 * returns 0 on success.
2493 int CtdlDoIHavePermissionToPostInThisRoom(char *errmsgbuf, size_t n) {
2495 if (!(CC->logged_in)) {
2496 snprintf(errmsgbuf, n, "Not logged in.");
2497 return (ERROR + NOT_LOGGED_IN);
2500 if ((CC->user.axlevel < 2)
2501 && ((CC->room.QRflags & QR_MAILBOX) == 0)) {
2502 snprintf(errmsgbuf, n, "Need to be validated to enter "
2503 "(except in %s> to sysop)", MAILROOM);
2504 return (ERROR + HIGHER_ACCESS_REQUIRED);
2507 if ((CC->user.axlevel < 4)
2508 && (CC->room.QRflags & QR_NETWORK)) {
2509 snprintf(errmsgbuf, n, "Need net privileges to enter here.");
2510 return (ERROR + HIGHER_ACCESS_REQUIRED);
2513 if ((CC->user.axlevel < 6)
2514 && (CC->room.QRflags & QR_READONLY)) {
2515 snprintf(errmsgbuf, n, "Sorry, this is a read-only room.");
2516 return (ERROR + HIGHER_ACCESS_REQUIRED);
2519 strcpy(errmsgbuf, "Ok");
2525 * Check to see if the specified user has Internet mail permission
2526 * (returns nonzero if permission is granted)
2528 int CtdlCheckInternetMailPermission(struct ctdluser *who) {
2530 /* Do not allow twits to send Internet mail */
2531 if (who->axlevel <= 2) return(0);
2533 /* Globally enabled? */
2534 if (config.c_restrict == 0) return(1);
2536 /* User flagged ok? */
2537 if (who->flags & US_INTERNET) return(2);
2539 /* Aide level access? */
2540 if (who->axlevel >= 6) return(3);
2542 /* No mail for you! */
2549 * Validate recipients, count delivery types and errors, and handle aliasing
2550 * FIXME check for dupes!!!!!
2552 struct recptypes *validate_recipients(char *recipients) {
2553 struct recptypes *ret;
2554 char this_recp[SIZ];
2555 char this_recp_cooked[SIZ];
2561 struct ctdluser tempUS;
2562 struct ctdlroom tempQR;
2565 ret = (struct recptypes *) malloc(sizeof(struct recptypes));
2566 if (ret == NULL) return(NULL);
2567 memset(ret, 0, sizeof(struct recptypes));
2570 ret->num_internet = 0;
2575 if (recipients == NULL) {
2578 else if (strlen(recipients) == 0) {
2582 /* Change all valid separator characters to commas */
2583 for (i=0; i<strlen(recipients); ++i) {
2584 if ((recipients[i] == ';') || (recipients[i] == '|')) {
2585 recipients[i] = ',';
2590 num_recps = num_tokens(recipients, ',');
2593 if (num_recps > 0) for (i=0; i<num_recps; ++i) {
2594 extract_token(this_recp, recipients, i, ',', sizeof this_recp);
2596 lprintf(CTDL_DEBUG, "Evaluating recipient #%d <%s>\n", i, this_recp);
2597 mailtype = alias(this_recp);
2598 mailtype = alias(this_recp);
2599 mailtype = alias(this_recp);
2600 for (j=0; j<=strlen(this_recp); ++j) {
2601 if (this_recp[j]=='_') {
2602 this_recp_cooked[j] = ' ';
2605 this_recp_cooked[j] = this_recp[j];
2611 if (!strcasecmp(this_recp, "sysop")) {
2613 strcpy(this_recp, config.c_aideroom);
2614 if (strlen(ret->recp_room) > 0) {
2615 strcat(ret->recp_room, "|");
2617 strcat(ret->recp_room, this_recp);
2619 else if (getuser(&tempUS, this_recp) == 0) {
2621 strcpy(this_recp, tempUS.fullname);
2622 if (strlen(ret->recp_local) > 0) {
2623 strcat(ret->recp_local, "|");
2625 strcat(ret->recp_local, this_recp);
2627 else if (getuser(&tempUS, this_recp_cooked) == 0) {
2629 strcpy(this_recp, tempUS.fullname);
2630 if (strlen(ret->recp_local) > 0) {
2631 strcat(ret->recp_local, "|");
2633 strcat(ret->recp_local, this_recp);
2635 else if ( (!strncasecmp(this_recp, "room_", 5))
2636 && (!getroom(&tempQR, &this_recp_cooked[5])) ) {
2638 if (strlen(ret->recp_room) > 0) {
2639 strcat(ret->recp_room, "|");
2641 strcat(ret->recp_room, &this_recp_cooked[5]);
2649 /* Yes, you're reading this correctly: if the target
2650 * domain points back to the local system or an attached
2651 * Citadel directory, the address is invalid. That's
2652 * because if the address were valid, we would have
2653 * already translated it to a local address by now.
2655 if (IsDirectory(this_recp)) {
2660 ++ret->num_internet;
2661 if (strlen(ret->recp_internet) > 0) {
2662 strcat(ret->recp_internet, "|");
2664 strcat(ret->recp_internet, this_recp);
2669 if (strlen(ret->recp_ignet) > 0) {
2670 strcat(ret->recp_ignet, "|");
2672 strcat(ret->recp_ignet, this_recp);
2680 if (strlen(ret->errormsg) == 0) {
2681 snprintf(append, sizeof append,
2682 "Invalid recipient: %s",
2686 snprintf(append, sizeof append,
2689 if ( (strlen(ret->errormsg) + strlen(append)) < SIZ) {
2690 strcat(ret->errormsg, append);
2694 if (strlen(ret->display_recp) == 0) {
2695 strcpy(append, this_recp);
2698 snprintf(append, sizeof append, ", %s",
2701 if ( (strlen(ret->display_recp)+strlen(append)) < SIZ) {
2702 strcat(ret->display_recp, append);
2707 if ((ret->num_local + ret->num_internet + ret->num_ignet +
2708 ret->num_room + ret->num_error) == 0) {
2710 strcpy(ret->errormsg, "No recipients specified.");
2713 lprintf(CTDL_DEBUG, "validate_recipients()\n");
2714 lprintf(CTDL_DEBUG, " local: %d <%s>\n", ret->num_local, ret->recp_local);
2715 lprintf(CTDL_DEBUG, " room: %d <%s>\n", ret->num_room, ret->recp_room);
2716 lprintf(CTDL_DEBUG, " inet: %d <%s>\n", ret->num_internet, ret->recp_internet);
2717 lprintf(CTDL_DEBUG, " ignet: %d <%s>\n", ret->num_ignet, ret->recp_ignet);
2718 lprintf(CTDL_DEBUG, " error: %d <%s>\n", ret->num_error, ret->errormsg);
2726 * message entry - mode 0 (normal)
2728 void cmd_ent0(char *entargs)
2732 char masquerade_as[SIZ];
2734 int format_type = 0;
2735 char newusername[SIZ];
2736 struct CtdlMessage *msg;
2740 struct recptypes *valid = NULL;
2747 post = extract_int(entargs, 0);
2748 extract_token(recp, entargs, 1, '|', sizeof recp);
2749 anon_flag = extract_int(entargs, 2);
2750 format_type = extract_int(entargs, 3);
2751 extract_token(subject, entargs, 4, '|', sizeof subject);
2752 do_confirm = extract_int(entargs, 6);
2754 /* first check to make sure the request is valid. */
2756 err = CtdlDoIHavePermissionToPostInThisRoom(errmsg, sizeof errmsg);
2758 cprintf("%d %s\n", err, errmsg);
2762 /* Check some other permission type things. */
2765 if (CC->user.axlevel < 6) {
2766 cprintf("%d You don't have permission to masquerade.\n",
2767 ERROR + HIGHER_ACCESS_REQUIRED);
2770 extract_token(newusername, entargs, 5, '|', sizeof newusername);
2771 memset(CC->fake_postname, 0, sizeof(CC->fake_postname) );
2772 safestrncpy(CC->fake_postname, newusername,
2773 sizeof(CC->fake_postname) );
2774 cprintf("%d ok\n", CIT_OK);
2777 CC->cs_flags |= CS_POSTING;
2779 /* In the Mail> room we have to behave a little differently --
2780 * make sure the user has specified at least one recipient. Then
2781 * validate the recipient(s).
2783 if ( (CC->room.QRflags & QR_MAILBOX)
2784 && (!strcasecmp(&CC->room.QRname[11], MAILROOM)) ) {
2786 if (CC->user.axlevel < 2) {
2787 strcpy(recp, "sysop");
2790 valid = validate_recipients(recp);
2791 if (valid->num_error > 0) {
2793 ERROR + NO_SUCH_USER, valid->errormsg);
2797 if (valid->num_internet > 0) {
2798 if (CtdlCheckInternetMailPermission(&CC->user)==0) {
2799 cprintf("%d You do not have permission "
2800 "to send Internet mail.\n",
2801 ERROR + HIGHER_ACCESS_REQUIRED);
2807 if ( ( (valid->num_internet + valid->num_ignet) > 0)
2808 && (CC->user.axlevel < 4) ) {
2809 cprintf("%d Higher access required for network mail.\n",
2810 ERROR + HIGHER_ACCESS_REQUIRED);
2815 if ((RESTRICT_INTERNET == 1) && (valid->num_internet > 0)
2816 && ((CC->user.flags & US_INTERNET) == 0)
2817 && (!CC->internal_pgm)) {
2818 cprintf("%d You don't have access to Internet mail.\n",
2819 ERROR + HIGHER_ACCESS_REQUIRED);
2826 /* Is this a room which has anonymous-only or anonymous-option? */
2827 anonymous = MES_NORMAL;
2828 if (CC->room.QRflags & QR_ANONONLY) {
2829 anonymous = MES_ANONONLY;
2831 if (CC->room.QRflags & QR_ANONOPT) {
2832 if (anon_flag == 1) { /* only if the user requested it */
2833 anonymous = MES_ANONOPT;
2837 if ((CC->room.QRflags & QR_MAILBOX) == 0) {
2841 /* If we're only checking the validity of the request, return
2842 * success without creating the message.
2845 cprintf("%d %s\n", CIT_OK,
2846 ((valid != NULL) ? valid->display_recp : "") );
2851 /* Handle author masquerading */
2852 if (CC->fake_postname[0]) {
2853 strcpy(masquerade_as, CC->fake_postname);
2855 else if (CC->fake_username[0]) {
2856 strcpy(masquerade_as, CC->fake_username);
2859 strcpy(masquerade_as, "");
2862 /* Read in the message from the client. */
2864 cprintf("%d send message\n", START_CHAT_MODE);
2866 cprintf("%d send message\n", SEND_LISTING);
2868 msg = CtdlMakeMessage(&CC->user, recp,
2869 CC->room.QRname, anonymous, format_type,
2870 masquerade_as, subject, NULL);
2873 msgnum = CtdlSubmitMsg(msg, valid, "");
2876 cprintf("%ld\n", msgnum);
2878 cprintf("Message accepted.\n");
2881 cprintf("Internal error.\n");
2883 if (msg->cm_fields['E'] != NULL) {
2884 cprintf("%s\n", msg->cm_fields['E']);
2891 CtdlFreeMessage(msg);
2893 CC->fake_postname[0] = '\0';
2901 * API function to delete messages which match a set of criteria
2902 * (returns the actual number of messages deleted)
2904 int CtdlDeleteMessages(char *room_name, /* which room */
2905 long dmsgnum, /* or "0" for any */
2906 char *content_type /* or "" for any */
2910 struct ctdlroom qrbuf;
2911 struct cdbdata *cdbfr;
2912 long *msglist = NULL;
2913 long *dellist = NULL;
2916 int num_deleted = 0;
2918 struct MetaData smi;
2920 lprintf(CTDL_DEBUG, "CtdlDeleteMessages(%s, %ld, %s)\n",
2921 room_name, dmsgnum, content_type);
2923 /* get room record, obtaining a lock... */
2924 if (lgetroom(&qrbuf, room_name) != 0) {
2925 lprintf(CTDL_ERR, "CtdlDeleteMessages(): Room <%s> not found\n",
2927 return (0); /* room not found */
2929 cdbfr = cdb_fetch(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long));
2931 if (cdbfr != NULL) {
2932 msglist = malloc(cdbfr->len);
2933 dellist = malloc(cdbfr->len);
2934 memcpy(msglist, cdbfr->ptr, cdbfr->len);
2935 num_msgs = cdbfr->len / sizeof(long);
2939 for (i = 0; i < num_msgs; ++i) {
2942 /* Set/clear a bit for each criterion */
2944 if ((dmsgnum == 0L) || (msglist[i] == dmsgnum)) {
2945 delete_this |= 0x01;
2947 if (strlen(content_type) == 0) {
2948 delete_this |= 0x02;
2950 GetMetaData(&smi, msglist[i]);
2951 if (!strcasecmp(smi.meta_content_type,
2953 delete_this |= 0x02;
2957 /* Delete message only if all bits are set */
2958 if (delete_this == 0x03) {
2959 dellist[num_deleted++] = msglist[i];
2964 num_msgs = sort_msglist(msglist, num_msgs);
2965 cdb_store(CDB_MSGLISTS, &qrbuf.QRnumber, (int)sizeof(long),
2966 msglist, (int)(num_msgs * sizeof(long)));
2968 qrbuf.QRhighest = msglist[num_msgs - 1];
2972 /* Go through the messages we pulled out of the index, and decrement
2973 * their reference counts by 1. If this is the only room the message
2974 * was in, the reference count will reach zero and the message will
2975 * automatically be deleted from the database. We do this in a
2976 * separate pass because there might be plug-in hooks getting called,
2977 * and we don't want that happening during an S_ROOMS critical
2980 if (num_deleted) for (i=0; i<num_deleted; ++i) {
2981 PerformDeleteHooks(qrbuf.QRname, dellist[i]);
2982 AdjRefCount(dellist[i], -1);
2985 /* Now free the memory we used, and go away. */
2986 if (msglist != NULL) free(msglist);
2987 if (dellist != NULL) free(dellist);
2988 lprintf(CTDL_DEBUG, "%d message(s) deleted.\n", num_deleted);
2989 return (num_deleted);
2995 * Check whether the current user has permission to delete messages from
2996 * the current room (returns 1 for yes, 0 for no)
2998 int CtdlDoIHavePermissionToDeleteMessagesFromThisRoom(void) {
2999 getuser(&CC->user, CC->curr_user);
3000 if ((CC->user.axlevel < 6)
3001 && (CC->user.usernum != CC->room.QRroomaide)
3002 && ((CC->room.QRflags & QR_MAILBOX) == 0)
3003 && (!(CC->internal_pgm))) {
3012 * Delete message from current room
3014 void cmd_dele(char *delstr)
3019 if (CtdlDoIHavePermissionToDeleteMessagesFromThisRoom() == 0) {
3020 cprintf("%d Higher access required.\n",
3021 ERROR + HIGHER_ACCESS_REQUIRED);
3024 delnum = extract_long(delstr, 0);
3026 num_deleted = CtdlDeleteMessages(CC->room.QRname, delnum, "");
3029 cprintf("%d %d message%s deleted.\n", CIT_OK,
3030 num_deleted, ((num_deleted != 1) ? "s" : ""));
3032 cprintf("%d Message %ld not found.\n", ERROR + MESSAGE_NOT_FOUND, delnum);
3038 * Back end API function for moves and deletes
3040 int CtdlCopyMsgToRoom(long msgnum, char *dest) {
3043 err = CtdlSaveMsgPointerInRoom(dest, msgnum,
3044 (SM_VERIFY_GOODNESS | SM_DO_REPL_CHECK) );
3045 if (err != 0) return(err);
3053 * move or copy a message to another room
3055 void cmd_move(char *args)
3058 char targ[ROOMNAMELEN];
3059 struct ctdlroom qtemp;
3065 num = extract_long(args, 0);
3066 extract_token(targ, args, 1, '|', sizeof targ);
3067 targ[ROOMNAMELEN - 1] = 0;
3068 is_copy = extract_int(args, 2);
3070 if (getroom(&qtemp, targ) != 0) {
3071 cprintf("%d '%s' does not exist.\n",
3072 ERROR + ROOM_NOT_FOUND, targ);
3076 getuser(&CC->user, CC->curr_user);
3077 CtdlRoomAccess(&qtemp, &CC->user, &ra, NULL);
3079 /* Check for permission to perform this operation.
3080 * Remember: "CC->room" is source, "qtemp" is target.
3084 /* Aides can move/copy */
3085 if (CC->user.axlevel >= 6) permit = 1;
3087 /* Room aides can move/copy */
3088 if (CC->user.usernum == CC->room.QRroomaide) permit = 1;
3090 /* Permit move/copy from personal rooms */
3091 if ((CC->room.QRflags & QR_MAILBOX)
3092 && (qtemp.QRflags & QR_MAILBOX)) permit = 1;
3094 /* Permit only copy from public to personal room */
3096 && (!(CC->room.QRflags & QR_MAILBOX))
3097 && (qtemp.QRflags & QR_MAILBOX)) permit = 1;
3099 /* User must have access to target room */
3100 if (!(ra & UA_KNOWN)) permit = 0;
3103 cprintf("%d Higher access required.\n",
3104 ERROR + HIGHER_ACCESS_REQUIRED);
3108 err = CtdlCopyMsgToRoom(num, targ);
3110 cprintf("%d Cannot store message in %s: error %d\n",
3115 /* Now delete the message from the source room,
3116 * if this is a 'move' rather than a 'copy' operation.
3119 CtdlDeleteMessages(CC->room.QRname, num, "");
3122 cprintf("%d Message %s.\n", CIT_OK, (is_copy ? "copied" : "moved") );
3128 * GetMetaData() - Get the supplementary record for a message
3130 void GetMetaData(struct MetaData *smibuf, long msgnum)
3133 struct cdbdata *cdbsmi;
3136 memset(smibuf, 0, sizeof(struct MetaData));
3137 smibuf->meta_msgnum = msgnum;
3138 smibuf->meta_refcount = 1; /* Default reference count is 1 */
3140 /* Use the negative of the message number for its supp record index */
3141 TheIndex = (0L - msgnum);
3143 cdbsmi = cdb_fetch(CDB_MSGMAIN, &TheIndex, sizeof(long));
3144 if (cdbsmi == NULL) {
3145 return; /* record not found; go with defaults */
3147 memcpy(smibuf, cdbsmi->ptr,
3148 ((cdbsmi->len > sizeof(struct MetaData)) ?
3149 sizeof(struct MetaData) : cdbsmi->len));
3156 * PutMetaData() - (re)write supplementary record for a message
3158 void PutMetaData(struct MetaData *smibuf)
3162 /* Use the negative of the message number for the metadata db index */
3163 TheIndex = (0L - smibuf->meta_msgnum);
3165 lprintf(CTDL_DEBUG, "PutMetaData(%ld) - ref count is %d\n",
3166 smibuf->meta_msgnum, smibuf->meta_refcount);
3168 cdb_store(CDB_MSGMAIN,
3169 &TheIndex, (int)sizeof(long),
3170 smibuf, (int)sizeof(struct MetaData));
3175 * AdjRefCount - change the reference count for a message;
3176 * delete the message if it reaches zero
3178 void AdjRefCount(long msgnum, int incr)
3181 struct MetaData smi;
3184 /* This is a *tight* critical section; please keep it that way, as
3185 * it may get called while nested in other critical sections.
3186 * Complicating this any further will surely cause deadlock!
3188 begin_critical_section(S_SUPPMSGMAIN);
3189 GetMetaData(&smi, msgnum);
3190 smi.meta_refcount += incr;
3192 end_critical_section(S_SUPPMSGMAIN);
3193 lprintf(CTDL_DEBUG, "msg %ld ref count incr %d, is now %d\n",
3194 msgnum, incr, smi.meta_refcount);
3196 /* If the reference count is now zero, delete the message
3197 * (and its supplementary record as well).
3198 * FIXME ... defer this so it doesn't keep the user waiting.
3200 if (smi.meta_refcount == 0) {
3201 lprintf(CTDL_DEBUG, "Deleting message <%ld>\n", msgnum);
3203 /* Remove from fulltext index */
3204 if (config.c_enable_fulltext) {
3205 ft_index_message(msgnum, 0);
3208 /* Remove from message base */
3210 cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long));
3211 cdb_delete(CDB_BIGMSGS, &delnum, (int)sizeof(long));
3213 /* Remove metadata record */
3214 delnum = (0L - msgnum);
3215 cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long));
3220 * Write a generic object to this room
3222 * Note: this could be much more efficient. Right now we use two temporary
3223 * files, and still pull the message into memory as with all others.
3225 void CtdlWriteObject(char *req_room, /* Room to stuff it in */
3226 char *content_type, /* MIME type of this object */
3227 char *tempfilename, /* Where to fetch it from */
3228 struct ctdluser *is_mailbox, /* Mailbox room? */
3229 int is_binary, /* Is encoding necessary? */
3230 int is_unique, /* Del others of this type? */
3231 unsigned int flags /* Internal save flags */
3236 struct ctdlroom qrbuf;
3237 char roomname[ROOMNAMELEN];
3238 struct CtdlMessage *msg;
3240 char *raw_message = NULL;
3241 char *encoded_message = NULL;
3242 off_t raw_length = 0;
3244 if (is_mailbox != NULL)
3245 MailboxName(roomname, sizeof roomname, is_mailbox, req_room);
3247 safestrncpy(roomname, req_room, sizeof(roomname));
3248 lprintf(CTDL_DEBUG, "CtdlWriteObject() to <%s> (flags=%d)\n", roomname, flags);
3251 fp = fopen(tempfilename, "rb");
3253 lprintf(CTDL_CRIT, "Cannot open %s: %s\n",
3254 tempfilename, strerror(errno));
3257 fseek(fp, 0L, SEEK_END);
3258 raw_length = ftell(fp);
3260 lprintf(CTDL_DEBUG, "Raw length is %ld\n", (long)raw_length);
3262 raw_message = malloc((size_t)raw_length + 2);
3263 fread(raw_message, (size_t)raw_length, 1, fp);
3267 encoded_message = malloc((size_t)
3268 (((raw_length * 134) / 100) + 4096 ) );
3271 encoded_message = malloc((size_t)(raw_length + 4096));
3274 sprintf(encoded_message, "Content-type: %s\n", content_type);
3277 sprintf(&encoded_message[strlen(encoded_message)],
3278 "Content-transfer-encoding: base64\n\n"
3282 sprintf(&encoded_message[strlen(encoded_message)],
3283 "Content-transfer-encoding: 7bit\n\n"
3289 &encoded_message[strlen(encoded_message)],
3295 raw_message[raw_length] = 0;
3297 &encoded_message[strlen(encoded_message)],
3305 lprintf(CTDL_DEBUG, "Allocating\n");
3306 msg = malloc(sizeof(struct CtdlMessage));
3307 memset(msg, 0, sizeof(struct CtdlMessage));
3308 msg->cm_magic = CTDLMESSAGE_MAGIC;
3309 msg->cm_anon_type = MES_NORMAL;
3310 msg->cm_format_type = 4;
3311 msg->cm_fields['A'] = strdup(CC->user.fullname);
3312 msg->cm_fields['O'] = strdup(req_room);
3313 msg->cm_fields['N'] = strdup(config.c_nodename);
3314 msg->cm_fields['H'] = strdup(config.c_humannode);
3315 msg->cm_flags = flags;
3317 msg->cm_fields['M'] = encoded_message;
3319 /* Create the requested room if we have to. */
3320 if (getroom(&qrbuf, roomname) != 0) {
3321 create_room(roomname,
3322 ( (is_mailbox != NULL) ? 5 : 3 ),
3323 "", 0, 1, 0, VIEW_BBS);
3325 /* If the caller specified this object as unique, delete all
3326 * other objects of this type that are currently in the room.
3329 lprintf(CTDL_DEBUG, "Deleted %d other msgs of this type\n",
3330 CtdlDeleteMessages(roomname, 0L, content_type));
3332 /* Now write the data */
3333 CtdlSubmitMsg(msg, NULL, roomname);
3334 CtdlFreeMessage(msg);
3342 void CtdlGetSysConfigBackend(long msgnum, void *userdata) {
3343 config_msgnum = msgnum;
3347 char *CtdlGetSysConfig(char *sysconfname) {
3348 char hold_rm[ROOMNAMELEN];
3351 struct CtdlMessage *msg;
3354 strcpy(hold_rm, CC->room.QRname);
3355 if (getroom(&CC->room, SYSCONFIGROOM) != 0) {
3356 getroom(&CC->room, hold_rm);
3361 /* We want the last (and probably only) config in this room */
3362 begin_critical_section(S_CONFIG);
3363 config_msgnum = (-1L);
3364 CtdlForEachMessage(MSGS_LAST, 1, sysconfname, NULL,
3365 CtdlGetSysConfigBackend, NULL);
3366 msgnum = config_msgnum;
3367 end_critical_section(S_CONFIG);
3373 msg = CtdlFetchMessage(msgnum, 1);
3375 conf = strdup(msg->cm_fields['M']);
3376 CtdlFreeMessage(msg);
3383 getroom(&CC->room, hold_rm);
3385 if (conf != NULL) do {
3386 extract_token(buf, conf, 0, '\n', sizeof buf);
3387 strcpy(conf, &conf[strlen(buf)+1]);
3388 } while ( (strlen(conf)>0) && (strlen(buf)>0) );
3393 void CtdlPutSysConfig(char *sysconfname, char *sysconfdata) {
3394 char temp[PATH_MAX];
3397 strcpy(temp, tmpnam(NULL));
3399 fp = fopen(temp, "w");
3400 if (fp == NULL) return;
3401 fprintf(fp, "%s", sysconfdata);
3404 /* this handy API function does all the work for us */
3405 CtdlWriteObject(SYSCONFIGROOM, sysconfname, temp, NULL, 0, 1, 0);
3411 * Determine whether a given Internet address belongs to the current user
3413 int CtdlIsMe(char *addr, int addr_buf_len)
3415 struct recptypes *recp;
3418 recp = validate_recipients(addr);
3419 if (recp == NULL) return(0);
3421 if (recp->num_local == 0) {
3426 for (i=0; i<recp->num_local; ++i) {
3427 extract_token(addr, recp->recp_local, i, '|', addr_buf_len);
3428 if (!strcasecmp(addr, CC->user.fullname)) {
3440 * Citadel protocol command to do the same
3442 void cmd_isme(char *argbuf) {
3445 if (CtdlAccessCheck(ac_logged_in)) return;
3446 extract_token(addr, argbuf, 0, '|', sizeof addr);
3448 if (CtdlIsMe(addr, sizeof addr)) {
3449 cprintf("%d %s\n", CIT_OK, addr);
3452 cprintf("%d Not you.\n", ERROR + ILLEGAL_VALUE);