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); */
2082 usergoto(actual_rm, 0, 1, NULL, NULL);
2086 * If this message has no O (room) field, generate one.
2088 if (msg->cm_fields['O'] == NULL) {
2089 msg->cm_fields['O'] = strdup(CC->room.QRname);
2092 /* Perform "before save" hooks (aborting if any return nonzero) */
2093 lprintf(CTDL_DEBUG, "Performing before-save hooks\n");
2094 if (PerformMessageHooks(msg, EVT_BEFORESAVE) > 0) return(-3);
2096 /* If this message has an Exclusive ID, perform replication checks */
2097 lprintf(CTDL_DEBUG, "Performing replication checks\n");
2098 if (ReplicationChecks(msg) > 0) return(-4);
2100 /* Save it to disk */
2101 lprintf(CTDL_DEBUG, "Saving to disk\n");
2102 newmsgid = send_message(msg);
2103 if (newmsgid <= 0L) return(-5);
2105 /* Write a supplemental message info record. This doesn't have to
2106 * be a critical section because nobody else knows about this message
2109 lprintf(CTDL_DEBUG, "Creating MetaData record\n");
2110 memset(&smi, 0, sizeof(struct MetaData));
2111 smi.meta_msgnum = newmsgid;
2112 smi.meta_refcount = 0;
2113 safestrncpy(smi.meta_content_type, content_type,
2114 sizeof smi.meta_content_type);
2116 /* As part of the new metadata record, measure how
2117 * big this message will be when displayed as RFC822.
2118 * Both POP and IMAP use this, and it's best to just take the hit now
2119 * instead of having to potentially measure thousands of messages when
2120 * a mailbox is opened later.
2123 if (CC->redirect_buffer != NULL) {
2124 lprintf(CTDL_ALERT, "CC->redirect_buffer is not NULL during message submission!\n");
2127 CC->redirect_buffer = malloc(SIZ);
2128 CC->redirect_len = 0;
2129 CC->redirect_alloc = SIZ;
2130 CtdlOutputPreLoadedMsg(msg, 0L, MT_RFC822, HEADERS_ALL, 0, 1);
2131 smi.meta_rfc822_length = CC->redirect_len;
2132 free(CC->redirect_buffer);
2133 CC->redirect_buffer = NULL;
2134 CC->redirect_len = 0;
2135 CC->redirect_alloc = 0;
2139 /* Now figure out where to store the pointers */
2140 lprintf(CTDL_DEBUG, "Storing pointers\n");
2142 /* If this is being done by the networker delivering a private
2143 * message, we want to BYPASS saving the sender's copy (because there
2144 * is no local sender; it would otherwise go to the Trashcan).
2146 if ((!CC->internal_pgm) || (recps == NULL)) {
2147 if (CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 0) != 0) {
2148 lprintf(CTDL_ERR, "ERROR saving message pointer!\n");
2149 CtdlSaveMsgPointerInRoom(config.c_aideroom,
2154 /* For internet mail, drop a copy in the outbound queue room */
2156 if (recps->num_internet > 0) {
2157 CtdlSaveMsgPointerInRoom(SMTP_SPOOLOUT_ROOM, newmsgid, 0);
2160 /* If other rooms are specified, drop them there too. */
2162 if (recps->num_room > 0)
2163 for (i=0; i<num_tokens(recps->recp_room, '|'); ++i) {
2164 extract_token(recipient, recps->recp_room, i,
2165 '|', sizeof recipient);
2166 lprintf(CTDL_DEBUG, "Delivering to room <%s>\n", recipient);
2167 CtdlSaveMsgPointerInRoom(recipient, newmsgid, 0);
2170 /* Bump this user's messages posted counter. */
2171 lprintf(CTDL_DEBUG, "Updating user\n");
2172 lgetuser(&CC->user, CC->curr_user);
2173 CC->user.posted = CC->user.posted + 1;
2174 lputuser(&CC->user);
2176 /* If this is private, local mail, make a copy in the
2177 * recipient's mailbox and bump the reference count.
2180 if (recps->num_local > 0)
2181 for (i=0; i<num_tokens(recps->recp_local, '|'); ++i) {
2182 extract_token(recipient, recps->recp_local, i,
2183 '|', sizeof recipient);
2184 lprintf(CTDL_DEBUG, "Delivering private local mail to <%s>\n",
2186 if (getuser(&userbuf, recipient) == 0) {
2187 MailboxName(actual_rm, sizeof actual_rm,
2188 &userbuf, MAILROOM);
2189 CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 0);
2190 BumpNewMailCounter(userbuf.usernum);
2193 lprintf(CTDL_DEBUG, "No user <%s>\n", recipient);
2194 CtdlSaveMsgPointerInRoom(config.c_aideroom,
2199 /* Perform "after save" hooks */
2200 lprintf(CTDL_DEBUG, "Performing after-save hooks\n");
2201 PerformMessageHooks(msg, EVT_AFTERSAVE);
2203 /* For IGnet mail, we have to save a new copy into the spooler for
2204 * each recipient, with the R and D fields set to the recipient and
2205 * destination-node. This has two ugly side effects: all other
2206 * recipients end up being unlisted in this recipient's copy of the
2207 * message, and it has to deliver multiple messages to the same
2208 * node. We'll revisit this again in a year or so when everyone has
2209 * a network spool receiver that can handle the new style messages.
2212 if (recps->num_ignet > 0)
2213 for (i=0; i<num_tokens(recps->recp_ignet, '|'); ++i) {
2214 extract_token(recipient, recps->recp_ignet, i,
2215 '|', sizeof recipient);
2217 hold_R = msg->cm_fields['R'];
2218 hold_D = msg->cm_fields['D'];
2219 msg->cm_fields['R'] = malloc(SIZ);
2220 msg->cm_fields['D'] = malloc(128);
2221 extract_token(msg->cm_fields['R'], recipient, 0, '@', SIZ);
2222 extract_token(msg->cm_fields['D'], recipient, 1, '@', 128);
2224 serialize_message(&smr, msg);
2226 snprintf(submit_filename, sizeof submit_filename,
2227 "./network/spoolin/netmail.%04lx.%04x.%04x",
2228 (long) getpid(), CC->cs_pid, ++seqnum);
2229 network_fp = fopen(submit_filename, "wb+");
2230 if (network_fp != NULL) {
2231 fwrite(smr.ser, smr.len, 1, network_fp);
2237 free(msg->cm_fields['R']);
2238 free(msg->cm_fields['D']);
2239 msg->cm_fields['R'] = hold_R;
2240 msg->cm_fields['D'] = hold_D;
2243 /* Go back to the room we started from */
2244 lprintf(CTDL_DEBUG, "Returning to original room %s\n", hold_rm);
2245 if (strcasecmp(hold_rm, CC->room.QRname))
2246 /* getroom(&CC->room, hold_rm); */
2247 usergoto(hold_rm, 0, 1, NULL, NULL);
2249 /* For internet mail, generate delivery instructions.
2250 * Yes, this is recursive. Deal with it. Infinite recursion does
2251 * not happen because the delivery instructions message does not
2252 * contain a recipient.
2255 if (recps->num_internet > 0) {
2256 lprintf(CTDL_DEBUG, "Generating delivery instructions\n");
2257 instr = malloc(SIZ * 2);
2258 snprintf(instr, SIZ * 2,
2259 "Content-type: %s\n\nmsgid|%ld\nsubmitted|%ld\n"
2261 SPOOLMIME, newmsgid, (long)time(NULL),
2262 msg->cm_fields['A'], msg->cm_fields['N']
2265 for (i=0; i<num_tokens(recps->recp_internet, '|'); ++i) {
2266 size_t tmp = strlen(instr);
2267 extract_token(recipient, recps->recp_internet, i, '|', sizeof recipient);
2268 snprintf(&instr[tmp], SIZ * 2 - tmp,
2269 "remote|%s|0||\n", recipient);
2272 imsg = malloc(sizeof(struct CtdlMessage));
2273 memset(imsg, 0, sizeof(struct CtdlMessage));
2274 imsg->cm_magic = CTDLMESSAGE_MAGIC;
2275 imsg->cm_anon_type = MES_NORMAL;
2276 imsg->cm_format_type = FMT_RFC822;
2277 imsg->cm_fields['A'] = strdup("Citadel");
2278 imsg->cm_fields['M'] = instr;
2279 CtdlSubmitMsg(imsg, NULL, SMTP_SPOOLOUT_ROOM);
2280 CtdlFreeMessage(imsg);
2289 * Convenience function for generating small administrative messages.
2291 void quickie_message(char *from, char *to, char *room, char *text,
2292 int format_type, char *subject)
2294 struct CtdlMessage *msg;
2295 struct recptypes *recp = NULL;
2297 msg = malloc(sizeof(struct CtdlMessage));
2298 memset(msg, 0, sizeof(struct CtdlMessage));
2299 msg->cm_magic = CTDLMESSAGE_MAGIC;
2300 msg->cm_anon_type = MES_NORMAL;
2301 msg->cm_format_type = format_type;
2302 msg->cm_fields['A'] = strdup(from);
2303 if (room != NULL) msg->cm_fields['O'] = strdup(room);
2304 msg->cm_fields['N'] = strdup(NODENAME);
2306 msg->cm_fields['R'] = strdup(to);
2307 recp = validate_recipients(to);
2309 if (subject != NULL) {
2310 msg->cm_fields['U'] = strdup(subject);
2312 msg->cm_fields['M'] = strdup(text);
2314 CtdlSubmitMsg(msg, recp, room);
2315 CtdlFreeMessage(msg);
2316 if (recp != NULL) free(recp);
2322 * Back end function used by CtdlMakeMessage() and similar functions
2324 char *CtdlReadMessageBody(char *terminator, /* token signalling EOT */
2325 size_t maxlen, /* maximum message length */
2326 char *exist, /* if non-null, append to it;
2327 exist is ALWAYS freed */
2328 int crlf /* CRLF newlines instead of LF */
2332 size_t message_len = 0;
2333 size_t buffer_len = 0;
2339 if (exist == NULL) {
2346 message_len = strlen(exist);
2347 buffer_len = message_len + 4096;
2348 m = realloc(exist, buffer_len);
2355 /* flush the input if we have nowhere to store it */
2360 /* read in the lines of message text one by one */
2362 if (client_getln(buf, (sizeof buf - 3)) < 1) finished = 1;
2363 if (!strcmp(buf, terminator)) finished = 1;
2365 strcat(buf, "\r\n");
2371 if ( (!flushing) && (!finished) ) {
2372 /* Measure the line */
2373 linelen = strlen(buf);
2375 /* augment the buffer if we have to */
2376 if ((message_len + linelen) >= buffer_len) {
2377 ptr = realloc(m, (buffer_len * 2) );
2378 if (ptr == NULL) { /* flush if can't allocate */
2381 buffer_len = (buffer_len * 2);
2383 lprintf(CTDL_DEBUG, "buffer_len is now %ld\n", (long)buffer_len);
2387 /* Add the new line to the buffer. NOTE: this loop must avoid
2388 * using functions like strcat() and strlen() because they
2389 * traverse the entire buffer upon every call, and doing that
2390 * for a multi-megabyte message slows it down beyond usability.
2392 strcpy(&m[message_len], buf);
2393 message_len += linelen;
2396 /* if we've hit the max msg length, flush the rest */
2397 if (message_len >= maxlen) flushing = 1;
2399 } while (!finished);
2407 * Build a binary message to be saved on disk.
2408 * (NOTE: if you supply 'preformatted_text', the buffer you give it
2409 * will become part of the message. This means you are no longer
2410 * responsible for managing that memory -- it will be freed along with
2411 * the rest of the fields when CtdlFreeMessage() is called.)
2414 struct CtdlMessage *CtdlMakeMessage(
2415 struct ctdluser *author, /* author's user structure */
2416 char *recipient, /* NULL if it's not mail */
2417 char *room, /* room where it's going */
2418 int type, /* see MES_ types in header file */
2419 int format_type, /* variformat, plain text, MIME... */
2420 char *fake_name, /* who we're masquerading as */
2421 char *subject, /* Subject (optional) */
2422 char *preformatted_text /* ...or NULL to read text from client */
2424 char dest_node[SIZ];
2426 struct CtdlMessage *msg;
2428 msg = malloc(sizeof(struct CtdlMessage));
2429 memset(msg, 0, sizeof(struct CtdlMessage));
2430 msg->cm_magic = CTDLMESSAGE_MAGIC;
2431 msg->cm_anon_type = type;
2432 msg->cm_format_type = format_type;
2434 /* Don't confuse the poor folks if it's not routed mail. */
2435 strcpy(dest_node, "");
2439 snprintf(buf, sizeof buf, "cit%ld", author->usernum); /* Path */
2440 msg->cm_fields['P'] = strdup(buf);
2442 snprintf(buf, sizeof buf, "%ld", (long)time(NULL)); /* timestamp */
2443 msg->cm_fields['T'] = strdup(buf);
2445 if (fake_name[0]) /* author */
2446 msg->cm_fields['A'] = strdup(fake_name);
2448 msg->cm_fields['A'] = strdup(author->fullname);
2450 if (CC->room.QRflags & QR_MAILBOX) { /* room */
2451 msg->cm_fields['O'] = strdup(&CC->room.QRname[11]);
2454 msg->cm_fields['O'] = strdup(CC->room.QRname);
2457 msg->cm_fields['N'] = strdup(NODENAME); /* nodename */
2458 msg->cm_fields['H'] = strdup(HUMANNODE); /* hnodename */
2460 if (recipient[0] != 0) {
2461 msg->cm_fields['R'] = strdup(recipient);
2463 if (dest_node[0] != 0) {
2464 msg->cm_fields['D'] = strdup(dest_node);
2467 if ( (author == &CC->user) && (strlen(CC->cs_inet_email) > 0) ) {
2468 msg->cm_fields['F'] = strdup(CC->cs_inet_email);
2471 if (subject != NULL) {
2473 if (strlen(subject) > 0) {
2474 msg->cm_fields['U'] = strdup(subject);
2478 if (preformatted_text != NULL) {
2479 msg->cm_fields['M'] = preformatted_text;
2482 msg->cm_fields['M'] = CtdlReadMessageBody("000",
2483 config.c_maxmsglen, NULL, 0);
2491 * Check to see whether we have permission to post a message in the current
2492 * room. Returns a *CITADEL ERROR CODE* and puts a message in errmsgbuf, or
2493 * returns 0 on success.
2495 int CtdlDoIHavePermissionToPostInThisRoom(char *errmsgbuf, size_t n) {
2497 if (!(CC->logged_in)) {
2498 snprintf(errmsgbuf, n, "Not logged in.");
2499 return (ERROR + NOT_LOGGED_IN);
2502 if ((CC->user.axlevel < 2)
2503 && ((CC->room.QRflags & QR_MAILBOX) == 0)) {
2504 snprintf(errmsgbuf, n, "Need to be validated to enter "
2505 "(except in %s> to sysop)", MAILROOM);
2506 return (ERROR + HIGHER_ACCESS_REQUIRED);
2509 if ((CC->user.axlevel < 4)
2510 && (CC->room.QRflags & QR_NETWORK)) {
2511 snprintf(errmsgbuf, n, "Need net privileges to enter here.");
2512 return (ERROR + HIGHER_ACCESS_REQUIRED);
2515 if ((CC->user.axlevel < 6)
2516 && (CC->room.QRflags & QR_READONLY)) {
2517 snprintf(errmsgbuf, n, "Sorry, this is a read-only room.");
2518 return (ERROR + HIGHER_ACCESS_REQUIRED);
2521 strcpy(errmsgbuf, "Ok");
2527 * Check to see if the specified user has Internet mail permission
2528 * (returns nonzero if permission is granted)
2530 int CtdlCheckInternetMailPermission(struct ctdluser *who) {
2532 /* Do not allow twits to send Internet mail */
2533 if (who->axlevel <= 2) return(0);
2535 /* Globally enabled? */
2536 if (config.c_restrict == 0) return(1);
2538 /* User flagged ok? */
2539 if (who->flags & US_INTERNET) return(2);
2541 /* Aide level access? */
2542 if (who->axlevel >= 6) return(3);
2544 /* No mail for you! */
2551 * Validate recipients, count delivery types and errors, and handle aliasing
2552 * FIXME check for dupes!!!!!
2554 struct recptypes *validate_recipients(char *recipients) {
2555 struct recptypes *ret;
2556 char this_recp[SIZ];
2557 char this_recp_cooked[SIZ];
2563 struct ctdluser tempUS;
2564 struct ctdlroom tempQR;
2567 ret = (struct recptypes *) malloc(sizeof(struct recptypes));
2568 if (ret == NULL) return(NULL);
2569 memset(ret, 0, sizeof(struct recptypes));
2572 ret->num_internet = 0;
2577 if (recipients == NULL) {
2580 else if (strlen(recipients) == 0) {
2584 /* Change all valid separator characters to commas */
2585 for (i=0; i<strlen(recipients); ++i) {
2586 if ((recipients[i] == ';') || (recipients[i] == '|')) {
2587 recipients[i] = ',';
2592 num_recps = num_tokens(recipients, ',');
2595 if (num_recps > 0) for (i=0; i<num_recps; ++i) {
2596 extract_token(this_recp, recipients, i, ',', sizeof this_recp);
2598 lprintf(CTDL_DEBUG, "Evaluating recipient #%d <%s>\n", i, this_recp);
2599 mailtype = alias(this_recp);
2600 mailtype = alias(this_recp);
2601 mailtype = alias(this_recp);
2602 for (j=0; j<=strlen(this_recp); ++j) {
2603 if (this_recp[j]=='_') {
2604 this_recp_cooked[j] = ' ';
2607 this_recp_cooked[j] = this_recp[j];
2613 if (!strcasecmp(this_recp, "sysop")) {
2615 strcpy(this_recp, config.c_aideroom);
2616 if (strlen(ret->recp_room) > 0) {
2617 strcat(ret->recp_room, "|");
2619 strcat(ret->recp_room, this_recp);
2621 else if (getuser(&tempUS, this_recp) == 0) {
2623 strcpy(this_recp, tempUS.fullname);
2624 if (strlen(ret->recp_local) > 0) {
2625 strcat(ret->recp_local, "|");
2627 strcat(ret->recp_local, this_recp);
2629 else if (getuser(&tempUS, this_recp_cooked) == 0) {
2631 strcpy(this_recp, tempUS.fullname);
2632 if (strlen(ret->recp_local) > 0) {
2633 strcat(ret->recp_local, "|");
2635 strcat(ret->recp_local, this_recp);
2637 else if ( (!strncasecmp(this_recp, "room_", 5))
2638 && (!getroom(&tempQR, &this_recp_cooked[5])) ) {
2640 if (strlen(ret->recp_room) > 0) {
2641 strcat(ret->recp_room, "|");
2643 strcat(ret->recp_room, &this_recp_cooked[5]);
2651 /* Yes, you're reading this correctly: if the target
2652 * domain points back to the local system or an attached
2653 * Citadel directory, the address is invalid. That's
2654 * because if the address were valid, we would have
2655 * already translated it to a local address by now.
2657 if (IsDirectory(this_recp)) {
2662 ++ret->num_internet;
2663 if (strlen(ret->recp_internet) > 0) {
2664 strcat(ret->recp_internet, "|");
2666 strcat(ret->recp_internet, this_recp);
2671 if (strlen(ret->recp_ignet) > 0) {
2672 strcat(ret->recp_ignet, "|");
2674 strcat(ret->recp_ignet, this_recp);
2682 if (strlen(ret->errormsg) == 0) {
2683 snprintf(append, sizeof append,
2684 "Invalid recipient: %s",
2688 snprintf(append, sizeof append,
2691 if ( (strlen(ret->errormsg) + strlen(append)) < SIZ) {
2692 strcat(ret->errormsg, append);
2696 if (strlen(ret->display_recp) == 0) {
2697 strcpy(append, this_recp);
2700 snprintf(append, sizeof append, ", %s",
2703 if ( (strlen(ret->display_recp)+strlen(append)) < SIZ) {
2704 strcat(ret->display_recp, append);
2709 if ((ret->num_local + ret->num_internet + ret->num_ignet +
2710 ret->num_room + ret->num_error) == 0) {
2712 strcpy(ret->errormsg, "No recipients specified.");
2715 lprintf(CTDL_DEBUG, "validate_recipients()\n");
2716 lprintf(CTDL_DEBUG, " local: %d <%s>\n", ret->num_local, ret->recp_local);
2717 lprintf(CTDL_DEBUG, " room: %d <%s>\n", ret->num_room, ret->recp_room);
2718 lprintf(CTDL_DEBUG, " inet: %d <%s>\n", ret->num_internet, ret->recp_internet);
2719 lprintf(CTDL_DEBUG, " ignet: %d <%s>\n", ret->num_ignet, ret->recp_ignet);
2720 lprintf(CTDL_DEBUG, " error: %d <%s>\n", ret->num_error, ret->errormsg);
2728 * message entry - mode 0 (normal)
2730 void cmd_ent0(char *entargs)
2734 char masquerade_as[SIZ];
2736 int format_type = 0;
2737 char newusername[SIZ];
2738 struct CtdlMessage *msg;
2742 struct recptypes *valid = NULL;
2749 post = extract_int(entargs, 0);
2750 extract_token(recp, entargs, 1, '|', sizeof recp);
2751 anon_flag = extract_int(entargs, 2);
2752 format_type = extract_int(entargs, 3);
2753 extract_token(subject, entargs, 4, '|', sizeof subject);
2754 do_confirm = extract_int(entargs, 6);
2756 /* first check to make sure the request is valid. */
2758 err = CtdlDoIHavePermissionToPostInThisRoom(errmsg, sizeof errmsg);
2760 cprintf("%d %s\n", err, errmsg);
2764 /* Check some other permission type things. */
2767 if (CC->user.axlevel < 6) {
2768 cprintf("%d You don't have permission to masquerade.\n",
2769 ERROR + HIGHER_ACCESS_REQUIRED);
2772 extract_token(newusername, entargs, 5, '|', sizeof newusername);
2773 memset(CC->fake_postname, 0, sizeof(CC->fake_postname) );
2774 safestrncpy(CC->fake_postname, newusername,
2775 sizeof(CC->fake_postname) );
2776 cprintf("%d ok\n", CIT_OK);
2779 CC->cs_flags |= CS_POSTING;
2781 /* In the Mail> room we have to behave a little differently --
2782 * make sure the user has specified at least one recipient. Then
2783 * validate the recipient(s).
2785 if ( (CC->room.QRflags & QR_MAILBOX)
2786 && (!strcasecmp(&CC->room.QRname[11], MAILROOM)) ) {
2788 if (CC->user.axlevel < 2) {
2789 strcpy(recp, "sysop");
2792 valid = validate_recipients(recp);
2793 if (valid->num_error > 0) {
2795 ERROR + NO_SUCH_USER, valid->errormsg);
2799 if (valid->num_internet > 0) {
2800 if (CtdlCheckInternetMailPermission(&CC->user)==0) {
2801 cprintf("%d You do not have permission "
2802 "to send Internet mail.\n",
2803 ERROR + HIGHER_ACCESS_REQUIRED);
2809 if ( ( (valid->num_internet + valid->num_ignet) > 0)
2810 && (CC->user.axlevel < 4) ) {
2811 cprintf("%d Higher access required for network mail.\n",
2812 ERROR + HIGHER_ACCESS_REQUIRED);
2817 if ((RESTRICT_INTERNET == 1) && (valid->num_internet > 0)
2818 && ((CC->user.flags & US_INTERNET) == 0)
2819 && (!CC->internal_pgm)) {
2820 cprintf("%d You don't have access to Internet mail.\n",
2821 ERROR + HIGHER_ACCESS_REQUIRED);
2828 /* Is this a room which has anonymous-only or anonymous-option? */
2829 anonymous = MES_NORMAL;
2830 if (CC->room.QRflags & QR_ANONONLY) {
2831 anonymous = MES_ANONONLY;
2833 if (CC->room.QRflags & QR_ANONOPT) {
2834 if (anon_flag == 1) { /* only if the user requested it */
2835 anonymous = MES_ANONOPT;
2839 if ((CC->room.QRflags & QR_MAILBOX) == 0) {
2843 /* If we're only checking the validity of the request, return
2844 * success without creating the message.
2847 cprintf("%d %s\n", CIT_OK,
2848 ((valid != NULL) ? valid->display_recp : "") );
2853 /* Handle author masquerading */
2854 if (CC->fake_postname[0]) {
2855 strcpy(masquerade_as, CC->fake_postname);
2857 else if (CC->fake_username[0]) {
2858 strcpy(masquerade_as, CC->fake_username);
2861 strcpy(masquerade_as, "");
2864 /* Read in the message from the client. */
2866 cprintf("%d send message\n", START_CHAT_MODE);
2868 cprintf("%d send message\n", SEND_LISTING);
2870 msg = CtdlMakeMessage(&CC->user, recp,
2871 CC->room.QRname, anonymous, format_type,
2872 masquerade_as, subject, NULL);
2875 msgnum = CtdlSubmitMsg(msg, valid, "");
2878 cprintf("%ld\n", msgnum);
2880 cprintf("Message accepted.\n");
2883 cprintf("Internal error.\n");
2885 if (msg->cm_fields['E'] != NULL) {
2886 cprintf("%s\n", msg->cm_fields['E']);
2893 CtdlFreeMessage(msg);
2895 CC->fake_postname[0] = '\0';
2903 * API function to delete messages which match a set of criteria
2904 * (returns the actual number of messages deleted)
2906 int CtdlDeleteMessages(char *room_name, /* which room */
2907 long dmsgnum, /* or "0" for any */
2908 char *content_type /* or "" for any */
2912 struct ctdlroom qrbuf;
2913 struct cdbdata *cdbfr;
2914 long *msglist = NULL;
2915 long *dellist = NULL;
2918 int num_deleted = 0;
2920 struct MetaData smi;
2922 lprintf(CTDL_DEBUG, "CtdlDeleteMessages(%s, %ld, %s)\n",
2923 room_name, dmsgnum, content_type);
2925 /* get room record, obtaining a lock... */
2926 if (lgetroom(&qrbuf, room_name) != 0) {
2927 lprintf(CTDL_ERR, "CtdlDeleteMessages(): Room <%s> not found\n",
2929 return (0); /* room not found */
2931 cdbfr = cdb_fetch(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long));
2933 if (cdbfr != NULL) {
2934 msglist = malloc(cdbfr->len);
2935 dellist = malloc(cdbfr->len);
2936 memcpy(msglist, cdbfr->ptr, cdbfr->len);
2937 num_msgs = cdbfr->len / sizeof(long);
2941 for (i = 0; i < num_msgs; ++i) {
2944 /* Set/clear a bit for each criterion */
2946 if ((dmsgnum == 0L) || (msglist[i] == dmsgnum)) {
2947 delete_this |= 0x01;
2949 if (strlen(content_type) == 0) {
2950 delete_this |= 0x02;
2952 GetMetaData(&smi, msglist[i]);
2953 if (!strcasecmp(smi.meta_content_type,
2955 delete_this |= 0x02;
2959 /* Delete message only if all bits are set */
2960 if (delete_this == 0x03) {
2961 dellist[num_deleted++] = msglist[i];
2966 num_msgs = sort_msglist(msglist, num_msgs);
2967 cdb_store(CDB_MSGLISTS, &qrbuf.QRnumber, (int)sizeof(long),
2968 msglist, (int)(num_msgs * sizeof(long)));
2970 qrbuf.QRhighest = msglist[num_msgs - 1];
2974 /* Go through the messages we pulled out of the index, and decrement
2975 * their reference counts by 1. If this is the only room the message
2976 * was in, the reference count will reach zero and the message will
2977 * automatically be deleted from the database. We do this in a
2978 * separate pass because there might be plug-in hooks getting called,
2979 * and we don't want that happening during an S_ROOMS critical
2982 if (num_deleted) for (i=0; i<num_deleted; ++i) {
2983 PerformDeleteHooks(qrbuf.QRname, dellist[i]);
2984 AdjRefCount(dellist[i], -1);
2987 /* Now free the memory we used, and go away. */
2988 if (msglist != NULL) free(msglist);
2989 if (dellist != NULL) free(dellist);
2990 lprintf(CTDL_DEBUG, "%d message(s) deleted.\n", num_deleted);
2991 return (num_deleted);
2997 * Check whether the current user has permission to delete messages from
2998 * the current room (returns 1 for yes, 0 for no)
3000 int CtdlDoIHavePermissionToDeleteMessagesFromThisRoom(void) {
3001 getuser(&CC->user, CC->curr_user);
3002 if ((CC->user.axlevel < 6)
3003 && (CC->user.usernum != CC->room.QRroomaide)
3004 && ((CC->room.QRflags & QR_MAILBOX) == 0)
3005 && (!(CC->internal_pgm))) {
3014 * Delete message from current room
3016 void cmd_dele(char *delstr)
3021 if (CtdlDoIHavePermissionToDeleteMessagesFromThisRoom() == 0) {
3022 cprintf("%d Higher access required.\n",
3023 ERROR + HIGHER_ACCESS_REQUIRED);
3026 delnum = extract_long(delstr, 0);
3028 num_deleted = CtdlDeleteMessages(CC->room.QRname, delnum, "");
3031 cprintf("%d %d message%s deleted.\n", CIT_OK,
3032 num_deleted, ((num_deleted != 1) ? "s" : ""));
3034 cprintf("%d Message %ld not found.\n", ERROR + MESSAGE_NOT_FOUND, delnum);
3040 * Back end API function for moves and deletes
3042 int CtdlCopyMsgToRoom(long msgnum, char *dest) {
3045 err = CtdlSaveMsgPointerInRoom(dest, msgnum,
3046 (SM_VERIFY_GOODNESS | SM_DO_REPL_CHECK) );
3047 if (err != 0) return(err);
3055 * move or copy a message to another room
3057 void cmd_move(char *args)
3060 char targ[ROOMNAMELEN];
3061 struct ctdlroom qtemp;
3067 num = extract_long(args, 0);
3068 extract_token(targ, args, 1, '|', sizeof targ);
3069 targ[ROOMNAMELEN - 1] = 0;
3070 is_copy = extract_int(args, 2);
3072 if (getroom(&qtemp, targ) != 0) {
3073 cprintf("%d '%s' does not exist.\n",
3074 ERROR + ROOM_NOT_FOUND, targ);
3078 getuser(&CC->user, CC->curr_user);
3079 CtdlRoomAccess(&qtemp, &CC->user, &ra, NULL);
3081 /* Check for permission to perform this operation.
3082 * Remember: "CC->room" is source, "qtemp" is target.
3086 /* Aides can move/copy */
3087 if (CC->user.axlevel >= 6) permit = 1;
3089 /* Room aides can move/copy */
3090 if (CC->user.usernum == CC->room.QRroomaide) permit = 1;
3092 /* Permit move/copy from personal rooms */
3093 if ((CC->room.QRflags & QR_MAILBOX)
3094 && (qtemp.QRflags & QR_MAILBOX)) permit = 1;
3096 /* Permit only copy from public to personal room */
3098 && (!(CC->room.QRflags & QR_MAILBOX))
3099 && (qtemp.QRflags & QR_MAILBOX)) permit = 1;
3101 /* User must have access to target room */
3102 if (!(ra & UA_KNOWN)) permit = 0;
3105 cprintf("%d Higher access required.\n",
3106 ERROR + HIGHER_ACCESS_REQUIRED);
3110 err = CtdlCopyMsgToRoom(num, targ);
3112 cprintf("%d Cannot store message in %s: error %d\n",
3117 /* Now delete the message from the source room,
3118 * if this is a 'move' rather than a 'copy' operation.
3121 CtdlDeleteMessages(CC->room.QRname, num, "");
3124 cprintf("%d Message %s.\n", CIT_OK, (is_copy ? "copied" : "moved") );
3130 * GetMetaData() - Get the supplementary record for a message
3132 void GetMetaData(struct MetaData *smibuf, long msgnum)
3135 struct cdbdata *cdbsmi;
3138 memset(smibuf, 0, sizeof(struct MetaData));
3139 smibuf->meta_msgnum = msgnum;
3140 smibuf->meta_refcount = 1; /* Default reference count is 1 */
3142 /* Use the negative of the message number for its supp record index */
3143 TheIndex = (0L - msgnum);
3145 cdbsmi = cdb_fetch(CDB_MSGMAIN, &TheIndex, sizeof(long));
3146 if (cdbsmi == NULL) {
3147 return; /* record not found; go with defaults */
3149 memcpy(smibuf, cdbsmi->ptr,
3150 ((cdbsmi->len > sizeof(struct MetaData)) ?
3151 sizeof(struct MetaData) : cdbsmi->len));
3158 * PutMetaData() - (re)write supplementary record for a message
3160 void PutMetaData(struct MetaData *smibuf)
3164 /* Use the negative of the message number for the metadata db index */
3165 TheIndex = (0L - smibuf->meta_msgnum);
3167 lprintf(CTDL_DEBUG, "PutMetaData(%ld) - ref count is %d\n",
3168 smibuf->meta_msgnum, smibuf->meta_refcount);
3170 cdb_store(CDB_MSGMAIN,
3171 &TheIndex, (int)sizeof(long),
3172 smibuf, (int)sizeof(struct MetaData));
3177 * AdjRefCount - change the reference count for a message;
3178 * delete the message if it reaches zero
3180 void AdjRefCount(long msgnum, int incr)
3183 struct MetaData smi;
3186 /* This is a *tight* critical section; please keep it that way, as
3187 * it may get called while nested in other critical sections.
3188 * Complicating this any further will surely cause deadlock!
3190 begin_critical_section(S_SUPPMSGMAIN);
3191 GetMetaData(&smi, msgnum);
3192 smi.meta_refcount += incr;
3194 end_critical_section(S_SUPPMSGMAIN);
3195 lprintf(CTDL_DEBUG, "msg %ld ref count incr %d, is now %d\n",
3196 msgnum, incr, smi.meta_refcount);
3198 /* If the reference count is now zero, delete the message
3199 * (and its supplementary record as well).
3200 * FIXME ... defer this so it doesn't keep the user waiting.
3202 if (smi.meta_refcount == 0) {
3203 lprintf(CTDL_DEBUG, "Deleting message <%ld>\n", msgnum);
3205 /* Remove from fulltext index */
3206 if (config.c_enable_fulltext) {
3207 ft_index_message(msgnum, 0);
3210 /* Remove from message base */
3212 cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long));
3213 cdb_delete(CDB_BIGMSGS, &delnum, (int)sizeof(long));
3215 /* Remove metadata record */
3216 delnum = (0L - msgnum);
3217 cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long));
3222 * Write a generic object to this room
3224 * Note: this could be much more efficient. Right now we use two temporary
3225 * files, and still pull the message into memory as with all others.
3227 void CtdlWriteObject(char *req_room, /* Room to stuff it in */
3228 char *content_type, /* MIME type of this object */
3229 char *tempfilename, /* Where to fetch it from */
3230 struct ctdluser *is_mailbox, /* Mailbox room? */
3231 int is_binary, /* Is encoding necessary? */
3232 int is_unique, /* Del others of this type? */
3233 unsigned int flags /* Internal save flags */
3238 struct ctdlroom qrbuf;
3239 char roomname[ROOMNAMELEN];
3240 struct CtdlMessage *msg;
3242 char *raw_message = NULL;
3243 char *encoded_message = NULL;
3244 off_t raw_length = 0;
3246 if (is_mailbox != NULL)
3247 MailboxName(roomname, sizeof roomname, is_mailbox, req_room);
3249 safestrncpy(roomname, req_room, sizeof(roomname));
3250 lprintf(CTDL_DEBUG, "CtdlWriteObject() to <%s> (flags=%d)\n", roomname, flags);
3253 fp = fopen(tempfilename, "rb");
3255 lprintf(CTDL_CRIT, "Cannot open %s: %s\n",
3256 tempfilename, strerror(errno));
3259 fseek(fp, 0L, SEEK_END);
3260 raw_length = ftell(fp);
3262 lprintf(CTDL_DEBUG, "Raw length is %ld\n", (long)raw_length);
3264 raw_message = malloc((size_t)raw_length + 2);
3265 fread(raw_message, (size_t)raw_length, 1, fp);
3269 encoded_message = malloc((size_t)
3270 (((raw_length * 134) / 100) + 4096 ) );
3273 encoded_message = malloc((size_t)(raw_length + 4096));
3276 sprintf(encoded_message, "Content-type: %s\n", content_type);
3279 sprintf(&encoded_message[strlen(encoded_message)],
3280 "Content-transfer-encoding: base64\n\n"
3284 sprintf(&encoded_message[strlen(encoded_message)],
3285 "Content-transfer-encoding: 7bit\n\n"
3291 &encoded_message[strlen(encoded_message)],
3297 raw_message[raw_length] = 0;
3299 &encoded_message[strlen(encoded_message)],
3307 lprintf(CTDL_DEBUG, "Allocating\n");
3308 msg = malloc(sizeof(struct CtdlMessage));
3309 memset(msg, 0, sizeof(struct CtdlMessage));
3310 msg->cm_magic = CTDLMESSAGE_MAGIC;
3311 msg->cm_anon_type = MES_NORMAL;
3312 msg->cm_format_type = 4;
3313 msg->cm_fields['A'] = strdup(CC->user.fullname);
3314 msg->cm_fields['O'] = strdup(req_room);
3315 msg->cm_fields['N'] = strdup(config.c_nodename);
3316 msg->cm_fields['H'] = strdup(config.c_humannode);
3317 msg->cm_flags = flags;
3319 msg->cm_fields['M'] = encoded_message;
3321 /* Create the requested room if we have to. */
3322 if (getroom(&qrbuf, roomname) != 0) {
3323 create_room(roomname,
3324 ( (is_mailbox != NULL) ? 5 : 3 ),
3325 "", 0, 1, 0, VIEW_BBS);
3327 /* If the caller specified this object as unique, delete all
3328 * other objects of this type that are currently in the room.
3331 lprintf(CTDL_DEBUG, "Deleted %d other msgs of this type\n",
3332 CtdlDeleteMessages(roomname, 0L, content_type));
3334 /* Now write the data */
3335 CtdlSubmitMsg(msg, NULL, roomname);
3336 CtdlFreeMessage(msg);
3344 void CtdlGetSysConfigBackend(long msgnum, void *userdata) {
3345 config_msgnum = msgnum;
3349 char *CtdlGetSysConfig(char *sysconfname) {
3350 char hold_rm[ROOMNAMELEN];
3353 struct CtdlMessage *msg;
3356 strcpy(hold_rm, CC->room.QRname);
3357 if (getroom(&CC->room, SYSCONFIGROOM) != 0) {
3358 getroom(&CC->room, hold_rm);
3363 /* We want the last (and probably only) config in this room */
3364 begin_critical_section(S_CONFIG);
3365 config_msgnum = (-1L);
3366 CtdlForEachMessage(MSGS_LAST, 1, sysconfname, NULL,
3367 CtdlGetSysConfigBackend, NULL);
3368 msgnum = config_msgnum;
3369 end_critical_section(S_CONFIG);
3375 msg = CtdlFetchMessage(msgnum, 1);
3377 conf = strdup(msg->cm_fields['M']);
3378 CtdlFreeMessage(msg);
3385 getroom(&CC->room, hold_rm);
3387 if (conf != NULL) do {
3388 extract_token(buf, conf, 0, '\n', sizeof buf);
3389 strcpy(conf, &conf[strlen(buf)+1]);
3390 } while ( (strlen(conf)>0) && (strlen(buf)>0) );
3395 void CtdlPutSysConfig(char *sysconfname, char *sysconfdata) {
3396 char temp[PATH_MAX];
3399 strcpy(temp, tmpnam(NULL));
3401 fp = fopen(temp, "w");
3402 if (fp == NULL) return;
3403 fprintf(fp, "%s", sysconfdata);
3406 /* this handy API function does all the work for us */
3407 CtdlWriteObject(SYSCONFIGROOM, sysconfname, temp, NULL, 0, 1, 0);
3413 * Determine whether a given Internet address belongs to the current user
3415 int CtdlIsMe(char *addr, int addr_buf_len)
3417 struct recptypes *recp;
3420 recp = validate_recipients(addr);
3421 if (recp == NULL) return(0);
3423 if (recp->num_local == 0) {
3428 for (i=0; i<recp->num_local; ++i) {
3429 extract_token(addr, recp->recp_local, i, '|', addr_buf_len);
3430 if (!strcasecmp(addr, CC->user.fullname)) {
3442 * Citadel protocol command to do the same
3444 void cmd_isme(char *argbuf) {
3447 if (CtdlAccessCheck(ac_logged_in)) return;
3448 extract_token(addr, argbuf, 0, '|', sizeof addr);
3450 if (CtdlIsMe(addr, sizeof addr)) {
3451 cprintf("%d %s\n", CIT_OK, addr);
3454 cprintf("%d Not you.\n", ERROR + ILLEGAL_VALUE);