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"
49 #include "mime_parser.h"
52 #include "internet_addressing.h"
53 #include "serv_fulltext.h"
55 extern struct config config;
60 * This really belongs in serv_network.c, but I don't know how to export
61 * symbols between modules.
63 struct FilterList *filterlist = NULL;
67 * These are the four-character field headers we use when outputting
68 * messages in Citadel format (as opposed to RFC822 format).
71 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
72 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
73 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
74 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
75 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
76 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
77 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
78 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
105 * This function is self explanatory.
106 * (What can I say, I'm in a weird mood today...)
108 void remove_any_whitespace_to_the_left_or_right_of_at_symbol(char *name)
112 for (i = 0; i < strlen(name); ++i) {
113 if (name[i] == '@') {
114 while (isspace(name[i - 1]) && i > 0) {
115 strcpy(&name[i - 1], &name[i]);
118 while (isspace(name[i + 1])) {
119 strcpy(&name[i + 1], &name[i + 2]);
127 * Aliasing for network mail.
128 * (Error messages have been commented out, because this is a server.)
130 int alias(char *name)
131 { /* process alias and routing info for mail */
134 char aaa[SIZ], bbb[SIZ];
135 char *ignetcfg = NULL;
136 char *ignetmap = NULL;
143 remove_any_whitespace_to_the_left_or_right_of_at_symbol(name);
145 fp = fopen("network/mail.aliases", "r");
147 fp = fopen("/dev/null", "r");
154 while (fgets(aaa, sizeof aaa, fp) != NULL) {
155 while (isspace(name[0]))
156 strcpy(name, &name[1]);
157 aaa[strlen(aaa) - 1] = 0;
159 for (a = 0; a < strlen(aaa); ++a) {
161 strcpy(bbb, &aaa[a + 1]);
165 if (!strcasecmp(name, aaa))
170 /* Hit the Global Address Book */
171 if (CtdlDirectoryLookup(aaa, name) == 0) {
175 lprintf(CTDL_INFO, "Mail is being forwarded to %s\n", name);
177 /* Change "user @ xxx" to "user" if xxx is an alias for this host */
178 for (a=0; a<strlen(name); ++a) {
179 if (name[a] == '@') {
180 if (CtdlHostAlias(&name[a+1]) == hostalias_localhost) {
182 lprintf(CTDL_INFO, "Changed to <%s>\n", name);
187 /* determine local or remote type, see citadel.h */
188 at = haschar(name, '@');
189 if (at == 0) return(MES_LOCAL); /* no @'s - local address */
190 if (at > 1) return(MES_ERROR); /* >1 @'s - invalid address */
191 remove_any_whitespace_to_the_left_or_right_of_at_symbol(name);
193 /* figure out the delivery mode */
194 extract_token(node, name, 1, '@', sizeof node);
196 /* If there are one or more dots in the nodename, we assume that it
197 * is an FQDN and will attempt SMTP delivery to the Internet.
199 if (haschar(node, '.') > 0) {
200 return(MES_INTERNET);
203 /* Otherwise we look in the IGnet maps for a valid Citadel node.
204 * Try directly-connected nodes first...
206 ignetcfg = CtdlGetSysConfig(IGNETCFG);
207 for (i=0; i<num_tokens(ignetcfg, '\n'); ++i) {
208 extract_token(buf, ignetcfg, i, '\n', sizeof buf);
209 extract_token(testnode, buf, 0, '|', sizeof testnode);
210 if (!strcasecmp(node, testnode)) {
218 * Then try nodes that are two or more hops away.
220 ignetmap = CtdlGetSysConfig(IGNETMAP);
221 for (i=0; i<num_tokens(ignetmap, '\n'); ++i) {
222 extract_token(buf, ignetmap, i, '\n', sizeof buf);
223 extract_token(testnode, buf, 0, '|', sizeof testnode);
224 if (!strcasecmp(node, testnode)) {
231 /* If we get to this point it's an invalid node name */
240 fp = fopen("citadel.control", "r");
242 lprintf(CTDL_CRIT, "Cannot open citadel.control: %s\n",
246 fread((char *) &CitControl, sizeof(struct CitControl), 1, fp);
252 void simple_listing(long msgnum, void *userdata)
254 cprintf("%ld\n", msgnum);
259 /* Determine if a given message matches the fields in a message template.
260 * Return 0 for a successful match.
262 int CtdlMsgCmp(struct CtdlMessage *msg, struct CtdlMessage *template) {
265 /* If there aren't any fields in the template, all messages will
268 if (template == NULL) return(0);
270 /* Null messages are bogus. */
271 if (msg == NULL) return(1);
273 for (i='A'; i<='Z'; ++i) {
274 if (template->cm_fields[i] != NULL) {
275 if (msg->cm_fields[i] == NULL) {
278 if (strcasecmp(msg->cm_fields[i],
279 template->cm_fields[i])) return 1;
283 /* All compares succeeded: we have a match! */
290 * Retrieve the "seen" message list for the current room.
292 void CtdlGetSeen(char *buf, int which_set) {
295 /* Learn about the user and room in question */
296 CtdlGetRelationship(&vbuf, &CC->user, &CC->room);
298 if (which_set == ctdlsetseen_seen)
299 safestrncpy(buf, vbuf.v_seen, SIZ);
300 if (which_set == ctdlsetseen_answered)
301 safestrncpy(buf, vbuf.v_answered, SIZ);
307 * Manipulate the "seen msgs" string (or other message set strings)
309 void CtdlSetSeen(long target_msgnum, int target_setting, int which_set) {
310 struct cdbdata *cdbfr;
320 char *is_set; /* actually an array of booleans */
323 char setstr[SIZ], lostr[SIZ], histr[SIZ];
325 lprintf(CTDL_DEBUG, "CtdlSetSeen(%ld, %d, %d)\n",
326 target_msgnum, target_setting, which_set);
328 /* Learn about the user and room in question */
329 CtdlGetRelationship(&vbuf, &CC->user, &CC->room);
331 /* Load the message list */
332 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long));
334 msglist = malloc(cdbfr->len);
335 memcpy(msglist, cdbfr->ptr, cdbfr->len);
336 num_msgs = cdbfr->len / sizeof(long);
339 return; /* No messages at all? No further action. */
342 is_set = malloc(num_msgs * sizeof(char));
343 memset(is_set, 0, (num_msgs * sizeof(char)) );
345 /* Decide which message set we're manipulating */
346 if (which_set == ctdlsetseen_seen) safestrncpy(vset, vbuf.v_seen, sizeof vset);
347 if (which_set == ctdlsetseen_answered) safestrncpy(vset, vbuf.v_answered, sizeof vset);
349 lprintf(CTDL_DEBUG, "before optimize: %s\n", vset);
351 /* Translate the existing sequence set into an array of booleans */
352 num_sets = num_tokens(vset, ',');
353 for (s=0; s<num_sets; ++s) {
354 extract_token(setstr, vset, s, ',', sizeof setstr);
356 extract_token(lostr, setstr, 0, ':', sizeof lostr);
357 if (num_tokens(setstr, ':') >= 2) {
358 extract_token(histr, setstr, 1, ':', sizeof histr);
359 if (!strcmp(histr, "*")) {
360 snprintf(histr, sizeof histr, "%ld", LONG_MAX);
364 strcpy(histr, lostr);
369 for (i = 0; i < num_msgs; ++i) {
370 if ((msglist[i] >= lo) && (msglist[i] <= hi)) {
376 /* Now translate the array of booleans back into a sequence set */
379 for (i=0; i<num_msgs; ++i) {
382 if (msglist[i] == target_msgnum) {
383 is_seen = target_setting;
392 if (lo < 0L) lo = msglist[i];
395 if ( ((is_seen == 0) && (was_seen == 1))
396 || ((is_seen == 1) && (i == num_msgs-1)) ) {
399 if ( (strlen(vset) + 20) > sizeof vset) {
400 strcpy(vset, &vset[20]);
409 snprintf(&vset[tmp], sizeof vset - tmp,
413 snprintf(&vset[tmp], sizeof vset - tmp,
422 /* Decide which message set we're manipulating */
423 if (which_set == ctdlsetseen_seen) safestrncpy(vbuf.v_seen, vset, sizeof vbuf.v_seen);
424 if (which_set == ctdlsetseen_answered) safestrncpy(vbuf.v_answered, vset, sizeof vbuf.v_answered);
427 lprintf(CTDL_DEBUG, " after optimize: %s\n", vset);
429 CtdlSetRelationship(&vbuf, &CC->user, &CC->room);
434 * API function to perform an operation for each qualifying message in the
435 * current room. (Returns the number of messages processed.)
437 int CtdlForEachMessage(int mode, long ref,
439 struct CtdlMessage *compare,
440 void (*CallBack) (long, void *),
446 struct cdbdata *cdbfr;
447 long *msglist = NULL;
449 int num_processed = 0;
452 struct CtdlMessage *msg;
455 int printed_lastold = 0;
457 /* Learn about the user and room in question */
459 getuser(&CC->user, CC->curr_user);
460 CtdlGetRelationship(&vbuf, &CC->user, &CC->room);
462 /* Load the message list */
463 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long));
465 msglist = malloc(cdbfr->len);
466 memcpy(msglist, cdbfr->ptr, cdbfr->len);
467 num_msgs = cdbfr->len / sizeof(long);
470 return 0; /* No messages at all? No further action. */
475 * Now begin the traversal.
477 if (num_msgs > 0) for (a = 0; a < num_msgs; ++a) {
479 /* If the caller is looking for a specific MIME type, filter
480 * out all messages which are not of the type requested.
482 if (content_type != NULL) if (strlen(content_type) > 0) {
484 /* This call to GetMetaData() sits inside this loop
485 * so that we only do the extra database read per msg
486 * if we need to. Doing the extra read all the time
487 * really kills the server. If we ever need to use
488 * metadata for another search criterion, we need to
489 * move the read somewhere else -- but still be smart
490 * enough to only do the read if the caller has
491 * specified something that will need it.
493 GetMetaData(&smi, msglist[a]);
495 if (strcasecmp(smi.meta_content_type, content_type)) {
501 num_msgs = sort_msglist(msglist, num_msgs);
503 /* If a template was supplied, filter out the messages which
504 * don't match. (This could induce some delays!)
507 if (compare != NULL) {
508 for (a = 0; a < num_msgs; ++a) {
509 msg = CtdlFetchMessage(msglist[a], 1);
511 if (CtdlMsgCmp(msg, compare)) {
514 CtdlFreeMessage(msg);
522 * Now iterate through the message list, according to the
523 * criteria supplied by the caller.
526 for (a = 0; a < num_msgs; ++a) {
527 thismsg = msglist[a];
528 is_seen = is_msg_in_sequence_set(vbuf.v_seen, thismsg);
529 if (is_seen) lastold = thismsg;
534 || ((mode == MSGS_OLD) && (is_seen))
535 || ((mode == MSGS_NEW) && (!is_seen))
536 || ((mode == MSGS_LAST) && (a >= (num_msgs - ref)))
537 || ((mode == MSGS_FIRST) && (a < ref))
538 || ((mode == MSGS_GT) && (thismsg > ref))
539 || ((mode == MSGS_EQ) && (thismsg == ref))
542 if ((mode == MSGS_NEW) && (CC->user.flags & US_LASTOLD) && (lastold > 0L) && (printed_lastold == 0) && (!is_seen)) {
544 CallBack(lastold, userdata);
548 if (CallBack) CallBack(thismsg, userdata);
552 free(msglist); /* Clean up */
553 return num_processed;
559 * cmd_msgs() - get list of message #'s in this room
560 * implements the MSGS server command using CtdlForEachMessage()
562 void cmd_msgs(char *cmdbuf)
571 int with_template = 0;
572 struct CtdlMessage *template = NULL;
574 extract_token(which, cmdbuf, 0, '|', sizeof which);
575 cm_ref = extract_int(cmdbuf, 1);
576 with_template = extract_int(cmdbuf, 2);
580 if (!strncasecmp(which, "OLD", 3))
582 else if (!strncasecmp(which, "NEW", 3))
584 else if (!strncasecmp(which, "FIRST", 5))
586 else if (!strncasecmp(which, "LAST", 4))
588 else if (!strncasecmp(which, "GT", 2))
591 if ((!(CC->logged_in)) && (!(CC->internal_pgm))) {
592 cprintf("%d not logged in\n", ERROR + NOT_LOGGED_IN);
598 cprintf("%d Send template then receive message list\n",
600 template = (struct CtdlMessage *)
601 malloc(sizeof(struct CtdlMessage));
602 memset(template, 0, sizeof(struct CtdlMessage));
603 while(client_getln(buf, sizeof buf), strcmp(buf,"000")) {
604 extract_token(tfield, buf, 0, '|', sizeof tfield);
605 extract_token(tvalue, buf, 1, '|', sizeof tvalue);
606 for (i='A'; i<='Z'; ++i) if (msgkeys[i]!=NULL) {
607 if (!strcasecmp(tfield, msgkeys[i])) {
608 template->cm_fields[i] =
616 cprintf("%d Message list...\n", LISTING_FOLLOWS);
619 CtdlForEachMessage(mode, cm_ref,
620 NULL, template, simple_listing, NULL);
621 if (template != NULL) CtdlFreeMessage(template);
629 * help_subst() - support routine for help file viewer
631 void help_subst(char *strbuf, char *source, char *dest)
636 while (p = pattern2(strbuf, source), (p >= 0)) {
637 strcpy(workbuf, &strbuf[p + strlen(source)]);
638 strcpy(&strbuf[p], dest);
639 strcat(strbuf, workbuf);
644 void do_help_subst(char *buffer)
648 help_subst(buffer, "^nodename", config.c_nodename);
649 help_subst(buffer, "^humannode", config.c_humannode);
650 help_subst(buffer, "^fqdn", config.c_fqdn);
651 help_subst(buffer, "^username", CC->user.fullname);
652 snprintf(buf2, sizeof buf2, "%ld", CC->user.usernum);
653 help_subst(buffer, "^usernum", buf2);
654 help_subst(buffer, "^sysadm", config.c_sysadm);
655 help_subst(buffer, "^variantname", CITADEL);
656 snprintf(buf2, sizeof buf2, "%d", config.c_maxsessions);
657 help_subst(buffer, "^maxsessions", buf2);
658 help_subst(buffer, "^bbsdir", CTDLDIR);
664 * memfmout() - Citadel text formatter and paginator.
665 * Although the original purpose of this routine was to format
666 * text to the reader's screen width, all we're really using it
667 * for here is to format text out to 80 columns before sending it
668 * to the client. The client software may reformat it again.
671 int width, /* screen width to use */
672 char *mptr, /* where are we going to get our text from? */
673 char subst, /* nonzero if we should do substitutions */
674 char *nl) /* string to terminate lines with */
686 c = 1; /* c is the current pos */
690 while (ch = *mptr, ((ch != 0) && (strlen(buffer) < 126))) {
692 buffer[strlen(buffer) + 1] = 0;
693 buffer[strlen(buffer)] = ch;
696 if (buffer[0] == '^')
697 do_help_subst(buffer);
699 buffer[strlen(buffer) + 1] = 0;
701 strcpy(buffer, &buffer[1]);
709 if (((ch == 13) || (ch == 10)) && (old != 13) && (old != 10))
711 if (((old == 13) || (old == 10)) && (isspace(real))) {
719 if (((strlen(aaa) + c) > (width - 5)) && (strlen(aaa) > (width - 5))) {
720 cprintf("%s%s", nl, aaa);
729 if ((strlen(aaa) + c) > (width - 5)) {
738 if ((ch == 13) || (ch == 10)) {
739 cprintf("%s%s", aaa, nl);
746 cprintf("%s%s", aaa, nl);
752 * Callback function for mime parser that simply lists the part
754 void list_this_part(char *name, char *filename, char *partnum, char *disp,
755 void *content, char *cbtype, size_t length, char *encoding,
759 cprintf("part=%s|%s|%s|%s|%s|%ld\n",
760 name, filename, partnum, disp, cbtype, (long)length);
764 * Callback function for multipart prefix
766 void list_this_pref(char *name, char *filename, char *partnum, char *disp,
767 void *content, char *cbtype, size_t length, char *encoding,
770 cprintf("pref=%s|%s\n", partnum, cbtype);
774 * Callback function for multipart sufffix
776 void list_this_suff(char *name, char *filename, char *partnum, char *disp,
777 void *content, char *cbtype, size_t length, char *encoding,
780 cprintf("suff=%s|%s\n", partnum, cbtype);
785 * Callback function for mime parser that opens a section for downloading
787 void mime_download(char *name, char *filename, char *partnum, char *disp,
788 void *content, char *cbtype, size_t length, char *encoding,
792 /* Silently go away if there's already a download open... */
793 if (CC->download_fp != NULL)
796 /* ...or if this is not the desired section */
797 if (strcasecmp(CC->download_desired_section, partnum))
800 CC->download_fp = tmpfile();
801 if (CC->download_fp == NULL)
804 fwrite(content, length, 1, CC->download_fp);
805 fflush(CC->download_fp);
806 rewind(CC->download_fp);
808 OpenCmdResult(filename, cbtype);
814 * Load a message from disk into memory.
815 * This is used by CtdlOutputMsg() and other fetch functions.
817 * NOTE: Caller is responsible for freeing the returned CtdlMessage struct
818 * using the CtdlMessageFree() function.
820 struct CtdlMessage *CtdlFetchMessage(long msgnum, int with_body)
822 struct cdbdata *dmsgtext;
823 struct CtdlMessage *ret = NULL;
827 cit_uint8_t field_header;
829 lprintf(CTDL_DEBUG, "CtdlFetchMessage(%ld, %d)\n", msgnum, with_body);
831 dmsgtext = cdb_fetch(CDB_MSGMAIN, &msgnum, sizeof(long));
832 if (dmsgtext == NULL) {
835 mptr = dmsgtext->ptr;
836 upper_bound = mptr + dmsgtext->len;
838 /* Parse the three bytes that begin EVERY message on disk.
839 * The first is always 0xFF, the on-disk magic number.
840 * The second is the anonymous/public type byte.
841 * The third is the format type byte (vari, fixed, or MIME).
846 "Message %ld appears to be corrupted.\n",
851 ret = (struct CtdlMessage *) malloc(sizeof(struct CtdlMessage));
852 memset(ret, 0, sizeof(struct CtdlMessage));
854 ret->cm_magic = CTDLMESSAGE_MAGIC;
855 ret->cm_anon_type = *mptr++; /* Anon type byte */
856 ret->cm_format_type = *mptr++; /* Format type byte */
859 * The rest is zero or more arbitrary fields. Load them in.
860 * We're done when we encounter either a zero-length field or
861 * have just processed the 'M' (message text) field.
864 if (mptr >= upper_bound) {
867 field_header = *mptr++;
868 ret->cm_fields[field_header] = strdup(mptr);
870 while (*mptr++ != 0); /* advance to next field */
872 } while ((mptr < upper_bound) && (field_header != 'M'));
876 /* Always make sure there's something in the msg text field. If
877 * it's NULL, the message text is most likely stored separately,
878 * so go ahead and fetch that. Failing that, just set a dummy
879 * body so other code doesn't barf.
881 if ( (ret->cm_fields['M'] == NULL) && (with_body) ) {
882 dmsgtext = cdb_fetch(CDB_BIGMSGS, &msgnum, sizeof(long));
883 if (dmsgtext != NULL) {
884 ret->cm_fields['M'] = strdup(dmsgtext->ptr);
888 if (ret->cm_fields['M'] == NULL) {
889 ret->cm_fields['M'] = strdup("<no text>\n");
892 /* Perform "before read" hooks (aborting if any return nonzero) */
893 if (PerformMessageHooks(ret, EVT_BEFOREREAD) > 0) {
894 CtdlFreeMessage(ret);
903 * Returns 1 if the supplied pointer points to a valid Citadel message.
904 * If the pointer is NULL or the magic number check fails, returns 0.
906 int is_valid_message(struct CtdlMessage *msg) {
909 if ((msg->cm_magic) != CTDLMESSAGE_MAGIC) {
910 lprintf(CTDL_WARNING, "is_valid_message() -- self-check failed\n");
918 * 'Destructor' for struct CtdlMessage
920 void CtdlFreeMessage(struct CtdlMessage *msg)
924 if (is_valid_message(msg) == 0) return;
926 for (i = 0; i < 256; ++i)
927 if (msg->cm_fields[i] != NULL) {
928 free(msg->cm_fields[i]);
931 msg->cm_magic = 0; /* just in case */
937 * Pre callback function for multipart/alternative
939 * NOTE: this differs from the standard behavior for a reason. Normally when
940 * displaying multipart/alternative you want to show the _last_ usable
941 * format in the message. Here we show the _first_ one, because it's
942 * usually text/plain. Since this set of functions is designed for text
943 * output to non-MIME-aware clients, this is the desired behavior.
946 void fixed_output_pre(char *name, char *filename, char *partnum, char *disp,
947 void *content, char *cbtype, size_t length, char *encoding,
952 ma = (struct ma_info *)cbuserdata;
953 lprintf(CTDL_DEBUG, "fixed_output_pre() type=<%s>\n", cbtype);
954 if (!strcasecmp(cbtype, "multipart/alternative")) {
962 * Post callback function for multipart/alternative
964 void fixed_output_post(char *name, char *filename, char *partnum, char *disp,
965 void *content, char *cbtype, size_t length, char *encoding,
970 ma = (struct ma_info *)cbuserdata;
971 lprintf(CTDL_DEBUG, "fixed_output_post() type=<%s>\n", cbtype);
972 if (!strcasecmp(cbtype, "multipart/alternative")) {
980 * Inline callback function for mime parser that wants to display text
982 void fixed_output(char *name, char *filename, char *partnum, char *disp,
983 void *content, char *cbtype, size_t length, char *encoding,
991 ma = (struct ma_info *)cbuserdata;
993 lprintf(CTDL_DEBUG, "fixed_output() type=<%s>\n", cbtype);
996 * If we're in the middle of a multipart/alternative scope and
997 * we've already printed another section, skip this one.
999 if ( (ma->is_ma == 1) && (ma->did_print == 1) ) {
1000 lprintf(CTDL_DEBUG, "Skipping part %s (%s)\n", partnum, cbtype);
1005 if ( (!strcasecmp(cbtype, "text/plain"))
1006 || (strlen(cbtype)==0) ) {
1009 client_write(wptr, length);
1010 if (wptr[length-1] != '\n') {
1015 else if (!strcasecmp(cbtype, "text/html")) {
1016 ptr = html_to_ascii(content, 80, 0);
1018 client_write(ptr, wlen);
1019 if (ptr[wlen-1] != '\n') {
1024 else if (strncasecmp(cbtype, "multipart/", 10)) {
1025 cprintf("Part %s: %s (%s) (%ld bytes)\r\n",
1026 partnum, filename, cbtype, (long)length);
1031 * The client is elegant and sophisticated and wants to be choosy about
1032 * MIME content types, so figure out which multipart/alternative part
1033 * we're going to send.
1035 void choose_preferred(char *name, char *filename, char *partnum, char *disp,
1036 void *content, char *cbtype, size_t length, char *encoding,
1043 ma = (struct ma_info *)cbuserdata;
1045 if (ma->is_ma > 0) {
1046 for (i=0; i<num_tokens(CC->preferred_formats, '|'); ++i) {
1047 extract_token(buf, CC->preferred_formats, i, '|', sizeof buf);
1048 if (!strcasecmp(buf, cbtype)) {
1049 strcpy(ma->chosen_part, partnum);
1056 * Now that we've chosen our preferred part, output it.
1058 void output_preferred(char *name, char *filename, char *partnum, char *disp,
1059 void *content, char *cbtype, size_t length, char *encoding,
1064 int add_newline = 0;
1068 ma = (struct ma_info *)cbuserdata;
1070 /* This is not the MIME part you're looking for... */
1071 if (strcasecmp(partnum, ma->chosen_part)) return;
1073 /* If the content-type of this part is in our preferred formats
1074 * list, we can simply output it verbatim.
1076 for (i=0; i<num_tokens(CC->preferred_formats, '|'); ++i) {
1077 extract_token(buf, CC->preferred_formats, i, '|', sizeof buf);
1078 if (!strcasecmp(buf, cbtype)) {
1079 /* Yeah! Go! W00t!! */
1081 text_content = (char *)content;
1082 if (text_content[length-1] != '\n') {
1086 cprintf("Content-type: %s\n", cbtype);
1087 cprintf("Content-length: %d\n",
1088 (int)(length + add_newline) );
1089 if (strlen(encoding) > 0) {
1090 cprintf("Content-transfer-encoding: %s\n", encoding);
1093 cprintf("Content-transfer-encoding: 7bit\n");
1096 client_write(content, length);
1097 if (add_newline) cprintf("\n");
1102 /* No translations required or possible: output as text/plain */
1103 cprintf("Content-type: text/plain\n\n");
1104 fixed_output(name, filename, partnum, disp, content, cbtype,
1105 length, encoding, cbuserdata);
1110 * Get a message off disk. (returns om_* values found in msgbase.h)
1113 int CtdlOutputMsg(long msg_num, /* message number (local) to fetch */
1114 int mode, /* how would you like that message? */
1115 int headers_only, /* eschew the message body? */
1116 int do_proto, /* do Citadel protocol responses? */
1117 int crlf /* Use CRLF newlines instead of LF? */
1119 struct CtdlMessage *TheMessage = NULL;
1120 int retcode = om_no_such_msg;
1122 lprintf(CTDL_DEBUG, "CtdlOutputMsg() msgnum=%ld, mode=%d\n",
1125 if ((!(CC->logged_in)) && (!(CC->internal_pgm))) {
1126 if (do_proto) cprintf("%d Not logged in.\n",
1127 ERROR + NOT_LOGGED_IN);
1128 return(om_not_logged_in);
1131 /* FIXME: check message id against msglist for this room */
1134 * Fetch the message from disk. If we're in any sort of headers
1135 * only mode, request that we don't even bother loading the body
1138 if ( (headers_only == HEADERS_FAST) || (headers_only == HEADERS_ONLY) ) {
1139 TheMessage = CtdlFetchMessage(msg_num, 0);
1142 TheMessage = CtdlFetchMessage(msg_num, 1);
1145 if (TheMessage == NULL) {
1146 if (do_proto) cprintf("%d Can't locate msg %ld on disk\n",
1147 ERROR + MESSAGE_NOT_FOUND, msg_num);
1148 return(om_no_such_msg);
1151 retcode = CtdlOutputPreLoadedMsg(
1152 TheMessage, msg_num, mode,
1153 headers_only, do_proto, crlf);
1155 CtdlFreeMessage(TheMessage);
1162 * Get a message off disk. (returns om_* values found in msgbase.h)
1165 int CtdlOutputPreLoadedMsg(
1166 struct CtdlMessage *TheMessage,
1168 int mode, /* how would you like that message? */
1169 int headers_only, /* eschew the message body? */
1170 int do_proto, /* do Citadel protocol responses? */
1171 int crlf /* Use CRLF newlines instead of LF? */
1177 char display_name[256];
1179 char *nl; /* newline string */
1181 int subject_found = 0;
1184 /* Buffers needed for RFC822 translation. These are all filled
1185 * using functions that are bounds-checked, and therefore we can
1186 * make them substantially smaller than SIZ.
1194 char datestamp[100];
1196 lprintf(CTDL_DEBUG, "CtdlOutputPreLoadedMsg(TheMessage=%s, %ld, %d, %d, %d, %d\n",
1197 ((TheMessage == NULL) ? "NULL" : "not null"),
1199 mode, headers_only, do_proto, crlf);
1201 snprintf(mid, sizeof mid, "%ld", msg_num);
1202 nl = (crlf ? "\r\n" : "\n");
1204 if (!is_valid_message(TheMessage)) {
1206 "ERROR: invalid preloaded message for output\n");
1207 return(om_no_such_msg);
1210 /* Are we downloading a MIME component? */
1211 if (mode == MT_DOWNLOAD) {
1212 if (TheMessage->cm_format_type != FMT_RFC822) {
1214 cprintf("%d This is not a MIME message.\n",
1215 ERROR + ILLEGAL_VALUE);
1216 } else if (CC->download_fp != NULL) {
1217 if (do_proto) cprintf(
1218 "%d You already have a download open.\n",
1219 ERROR + RESOURCE_BUSY);
1221 /* Parse the message text component */
1222 mptr = TheMessage->cm_fields['M'];
1223 ma = malloc(sizeof(struct ma_info));
1224 memset(ma, 0, sizeof(struct ma_info));
1225 mime_parser(mptr, NULL, *mime_download, NULL, NULL, (void *)ma, 0);
1227 /* If there's no file open by this time, the requested
1228 * section wasn't found, so print an error
1230 if (CC->download_fp == NULL) {
1231 if (do_proto) cprintf(
1232 "%d Section %s not found.\n",
1233 ERROR + FILE_NOT_FOUND,
1234 CC->download_desired_section);
1237 return((CC->download_fp != NULL) ? om_ok : om_mime_error);
1240 /* now for the user-mode message reading loops */
1241 if (do_proto) cprintf("%d Message %ld:\n", LISTING_FOLLOWS, msg_num);
1243 /* Does the caller want to skip the headers? */
1244 if (headers_only == HEADERS_NONE) goto START_TEXT;
1246 /* Tell the client which format type we're using. */
1247 if ( (mode == MT_CITADEL) && (do_proto) ) {
1248 cprintf("type=%d\n", TheMessage->cm_format_type);
1251 /* nhdr=yes means that we're only displaying headers, no body */
1252 if ( (TheMessage->cm_anon_type == MES_ANONONLY)
1253 && (mode == MT_CITADEL)
1256 cprintf("nhdr=yes\n");
1259 /* begin header processing loop for Citadel message format */
1261 if ((mode == MT_CITADEL) || (mode == MT_MIME)) {
1263 safestrncpy(display_name, "<unknown>", sizeof display_name);
1264 if (TheMessage->cm_fields['A']) {
1265 strcpy(buf, TheMessage->cm_fields['A']);
1266 if (TheMessage->cm_anon_type == MES_ANONONLY) {
1267 safestrncpy(display_name, "****", sizeof display_name);
1269 else if (TheMessage->cm_anon_type == MES_ANONOPT) {
1270 safestrncpy(display_name, "anonymous", sizeof display_name);
1273 safestrncpy(display_name, buf, sizeof display_name);
1275 if ((is_room_aide())
1276 && ((TheMessage->cm_anon_type == MES_ANONONLY)
1277 || (TheMessage->cm_anon_type == MES_ANONOPT))) {
1278 size_t tmp = strlen(display_name);
1279 snprintf(&display_name[tmp],
1280 sizeof display_name - tmp,
1285 /* Don't show Internet address for users on the
1286 * local Citadel network.
1289 if (TheMessage->cm_fields['N'] != NULL)
1290 if (strlen(TheMessage->cm_fields['N']) > 0)
1291 if (haschar(TheMessage->cm_fields['N'], '.') == 0) {
1295 /* Now spew the header fields in the order we like them. */
1296 safestrncpy(allkeys, FORDER, sizeof allkeys);
1297 for (i=0; i<strlen(allkeys); ++i) {
1298 k = (int) allkeys[i];
1300 if ( (TheMessage->cm_fields[k] != NULL)
1301 && (msgkeys[k] != NULL) ) {
1303 if (do_proto) cprintf("%s=%s\n",
1307 else if ((k == 'F') && (suppress_f)) {
1310 /* Masquerade display name if needed */
1312 if (do_proto) cprintf("%s=%s\n",
1314 TheMessage->cm_fields[k]
1323 /* begin header processing loop for RFC822 transfer format */
1328 strcpy(snode, NODENAME);
1329 strcpy(lnode, HUMANNODE);
1330 if (mode == MT_RFC822) {
1331 for (i = 0; i < 256; ++i) {
1332 if (TheMessage->cm_fields[i]) {
1333 mptr = TheMessage->cm_fields[i];
1336 safestrncpy(luser, mptr, sizeof luser);
1337 safestrncpy(suser, mptr, sizeof suser);
1339 else if (i == 'U') {
1340 cprintf("Subject: %s%s", mptr, nl);
1344 safestrncpy(mid, mptr, sizeof mid);
1346 safestrncpy(lnode, mptr, sizeof lnode);
1348 safestrncpy(fuser, mptr, sizeof fuser);
1349 /* else if (i == 'O')
1350 cprintf("X-Citadel-Room: %s%s",
1353 safestrncpy(snode, mptr, sizeof snode);
1355 cprintf("To: %s%s", mptr, nl);
1356 else if (i == 'T') {
1357 datestring(datestamp, sizeof datestamp,
1358 atol(mptr), DATESTRING_RFC822);
1359 cprintf("Date: %s%s", datestamp, nl);
1363 if (subject_found == 0) {
1364 cprintf("Subject: (no subject)%s", nl);
1368 for (i=0; i<strlen(suser); ++i) {
1369 suser[i] = tolower(suser[i]);
1370 if (!isalnum(suser[i])) suser[i]='_';
1373 if (mode == MT_RFC822) {
1374 if (!strcasecmp(snode, NODENAME)) {
1375 safestrncpy(snode, FQDN, sizeof snode);
1378 /* Construct a fun message id */
1379 cprintf("Message-ID: <%s", mid);
1380 if (strchr(mid, '@')==NULL) {
1381 cprintf("@%s", snode);
1385 if (!is_room_aide() && (TheMessage->cm_anon_type == MES_ANONONLY)) {
1386 // cprintf("From: x@x.org (----)%s", nl);
1387 cprintf("From: \"----\" <x@x.org>%s", nl);
1389 else if (!is_room_aide() && (TheMessage->cm_anon_type == MES_ANONOPT)) {
1390 // cprintf("From: x@x.org (anonymous)%s", nl);
1391 cprintf("From: \"anonymous\" <x@x.org>%s", nl);
1393 else if (strlen(fuser) > 0) {
1394 // cprintf("From: %s (%s)%s", fuser, luser, nl);
1395 cprintf("From: \"%s\" <%s>%s", luser, fuser, nl);
1398 // cprintf("From: %s@%s (%s)%s", suser, snode, luser, nl);
1399 cprintf("From: \"%s\" <%s@%s>%s", luser, suser, snode, nl);
1402 cprintf("Organization: %s%s", lnode, nl);
1404 /* Blank line signifying RFC822 end-of-headers */
1405 if (TheMessage->cm_format_type != FMT_RFC822) {
1410 /* end header processing loop ... at this point, we're in the text */
1412 if (headers_only == HEADERS_FAST) goto DONE;
1413 mptr = TheMessage->cm_fields['M'];
1415 /* Tell the client about the MIME parts in this message */
1416 if (TheMessage->cm_format_type == FMT_RFC822) {
1417 if ( (mode == MT_CITADEL) || (mode == MT_MIME) ) {
1418 mime_parser(mptr, NULL,
1419 (do_proto ? *list_this_part : NULL),
1420 (do_proto ? *list_this_pref : NULL),
1421 (do_proto ? *list_this_suff : NULL),
1424 else if (mode == MT_RFC822) { /* unparsed RFC822 dump */
1425 /* FIXME ... we have to put some code in here to avoid
1426 * printing duplicate header information when both
1427 * Citadel and RFC822 headers exist. Preference should
1428 * probably be given to the RFC822 headers.
1430 int done_rfc822_hdrs = 0;
1431 while (ch=*(mptr++), ch!=0) {
1436 if (!done_rfc822_hdrs) {
1437 if (headers_only != HEADERS_NONE) {
1442 if (headers_only != HEADERS_ONLY) {
1446 if ((*(mptr) == 13) || (*(mptr) == 10)) {
1447 done_rfc822_hdrs = 1;
1451 if (done_rfc822_hdrs) {
1452 if (headers_only != HEADERS_NONE) {
1457 if (headers_only != HEADERS_ONLY) {
1461 if ((*mptr == 13) || (*mptr == 10)) {
1462 done_rfc822_hdrs = 1;
1470 if (headers_only == HEADERS_ONLY) {
1474 /* signify start of msg text */
1475 if ( (mode == MT_CITADEL) || (mode == MT_MIME) ) {
1476 if (do_proto) cprintf("text\n");
1479 /* If the format type on disk is 1 (fixed-format), then we want
1480 * everything to be output completely literally ... regardless of
1481 * what message transfer format is in use.
1483 if (TheMessage->cm_format_type == FMT_FIXED) {
1484 if (mode == MT_MIME) {
1485 cprintf("Content-type: text/plain\n\n");
1488 while (ch = *mptr++, ch > 0) {
1491 if ((ch == 10) || (strlen(buf) > 250)) {
1492 cprintf("%s%s", buf, nl);
1495 buf[strlen(buf) + 1] = 0;
1496 buf[strlen(buf)] = ch;
1499 if (strlen(buf) > 0)
1500 cprintf("%s%s", buf, nl);
1503 /* If the message on disk is format 0 (Citadel vari-format), we
1504 * output using the formatter at 80 columns. This is the final output
1505 * form if the transfer format is RFC822, but if the transfer format
1506 * is Citadel proprietary, it'll still work, because the indentation
1507 * for new paragraphs is correct and the client will reformat the
1508 * message to the reader's screen width.
1510 if (TheMessage->cm_format_type == FMT_CITADEL) {
1511 if (mode == MT_MIME) {
1512 cprintf("Content-type: text/x-citadel-variformat\n\n");
1514 memfmout(80, mptr, 0, nl);
1517 /* If the message on disk is format 4 (MIME), we've gotta hand it
1518 * off to the MIME parser. The client has already been told that
1519 * this message is format 1 (fixed format), so the callback function
1520 * we use will display those parts as-is.
1522 if (TheMessage->cm_format_type == FMT_RFC822) {
1523 ma = malloc(sizeof(struct ma_info));
1524 memset(ma, 0, sizeof(struct ma_info));
1526 if (mode == MT_MIME) {
1527 strcpy(ma->chosen_part, "1");
1528 mime_parser(mptr, NULL,
1529 *choose_preferred, *fixed_output_pre,
1530 *fixed_output_post, (void *)ma, 0);
1531 mime_parser(mptr, NULL,
1532 *output_preferred, NULL, NULL, (void *)ma, 0);
1535 mime_parser(mptr, NULL,
1536 *fixed_output, *fixed_output_pre,
1537 *fixed_output_post, (void *)ma, 0);
1543 DONE: /* now we're done */
1544 if (do_proto) cprintf("000\n");
1551 * display a message (mode 0 - Citadel proprietary)
1553 void cmd_msg0(char *cmdbuf)
1556 int headers_only = HEADERS_ALL;
1558 msgid = extract_long(cmdbuf, 0);
1559 headers_only = extract_int(cmdbuf, 1);
1561 CtdlOutputMsg(msgid, MT_CITADEL, headers_only, 1, 0);
1567 * display a message (mode 2 - RFC822)
1569 void cmd_msg2(char *cmdbuf)
1572 int headers_only = HEADERS_ALL;
1574 msgid = extract_long(cmdbuf, 0);
1575 headers_only = extract_int(cmdbuf, 1);
1577 CtdlOutputMsg(msgid, MT_RFC822, headers_only, 1, 1);
1583 * display a message (mode 3 - IGnet raw format - internal programs only)
1585 void cmd_msg3(char *cmdbuf)
1588 struct CtdlMessage *msg;
1591 if (CC->internal_pgm == 0) {
1592 cprintf("%d This command is for internal programs only.\n",
1593 ERROR + HIGHER_ACCESS_REQUIRED);
1597 msgnum = extract_long(cmdbuf, 0);
1598 msg = CtdlFetchMessage(msgnum, 1);
1600 cprintf("%d Message %ld not found.\n",
1601 ERROR + MESSAGE_NOT_FOUND, msgnum);
1605 serialize_message(&smr, msg);
1606 CtdlFreeMessage(msg);
1609 cprintf("%d Unable to serialize message\n",
1610 ERROR + INTERNAL_ERROR);
1614 cprintf("%d %ld\n", BINARY_FOLLOWS, (long)smr.len);
1615 client_write((char *)smr.ser, (int)smr.len);
1622 * Display a message using MIME content types
1624 void cmd_msg4(char *cmdbuf)
1628 msgid = extract_long(cmdbuf, 0);
1629 CtdlOutputMsg(msgid, MT_MIME, 0, 1, 0);
1635 * Client tells us its preferred message format(s)
1637 void cmd_msgp(char *cmdbuf)
1639 safestrncpy(CC->preferred_formats, cmdbuf,
1640 sizeof(CC->preferred_formats));
1641 cprintf("%d ok\n", CIT_OK);
1646 * Open a component of a MIME message as a download file
1648 void cmd_opna(char *cmdbuf)
1651 char desired_section[128];
1653 msgid = extract_long(cmdbuf, 0);
1654 extract_token(desired_section, cmdbuf, 1, '|', sizeof desired_section);
1655 safestrncpy(CC->download_desired_section, desired_section, sizeof CC->download_desired_section);
1656 CtdlOutputMsg(msgid, MT_DOWNLOAD, 0, 1, 1);
1661 * Save a message pointer into a specified room
1662 * (Returns 0 for success, nonzero for failure)
1663 * roomname may be NULL to use the current room
1665 int CtdlSaveMsgPointerInRoom(char *roomname, long msgid, int flags) {
1667 char hold_rm[ROOMNAMELEN];
1668 struct cdbdata *cdbfr;
1671 long highest_msg = 0L;
1672 struct CtdlMessage *msg = NULL;
1674 lprintf(CTDL_DEBUG, "CtdlSaveMsgPointerInRoom(%s, %ld, %d)\n",
1675 roomname, msgid, flags);
1677 strcpy(hold_rm, CC->room.QRname);
1679 /* We may need to check to see if this message is real */
1680 if ( (flags & SM_VERIFY_GOODNESS)
1681 || (flags & SM_DO_REPL_CHECK)
1683 msg = CtdlFetchMessage(msgid, 1);
1684 if (msg == NULL) return(ERROR + ILLEGAL_VALUE);
1687 /* Perform replication checks if necessary */
1688 if ( (flags & SM_DO_REPL_CHECK) && (msg != NULL) ) {
1690 if (getroom(&CC->room,
1691 ((roomname != NULL) ? roomname : CC->room.QRname) )
1693 lprintf(CTDL_ERR, "No such room <%s>\n", roomname);
1694 if (msg != NULL) CtdlFreeMessage(msg);
1695 return(ERROR + ROOM_NOT_FOUND);
1698 if (ReplicationChecks(msg) != 0) {
1699 getroom(&CC->room, hold_rm);
1700 if (msg != NULL) CtdlFreeMessage(msg);
1702 "Did replication, and newer exists\n");
1707 /* Now the regular stuff */
1708 if (lgetroom(&CC->room,
1709 ((roomname != NULL) ? roomname : CC->room.QRname) )
1711 lprintf(CTDL_ERR, "No such room <%s>\n", roomname);
1712 if (msg != NULL) CtdlFreeMessage(msg);
1713 return(ERROR + ROOM_NOT_FOUND);
1716 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long));
1717 if (cdbfr == NULL) {
1721 msglist = malloc(cdbfr->len);
1722 if (msglist == NULL)
1723 lprintf(CTDL_ALERT, "ERROR malloc msglist!\n");
1724 num_msgs = cdbfr->len / sizeof(long);
1725 memcpy(msglist, cdbfr->ptr, cdbfr->len);
1730 /* Make sure the message doesn't already exist in this room. It
1731 * is absolutely taboo to have more than one reference to the same
1732 * message in a room.
1734 if (num_msgs > 0) for (i=0; i<num_msgs; ++i) {
1735 if (msglist[i] == msgid) {
1736 lputroom(&CC->room); /* unlock the room */
1737 getroom(&CC->room, hold_rm);
1738 if (msg != NULL) CtdlFreeMessage(msg);
1740 return(ERROR + ALREADY_EXISTS);
1744 /* Now add the new message */
1746 msglist = realloc(msglist, (num_msgs * sizeof(long)));
1748 if (msglist == NULL) {
1749 lprintf(CTDL_ALERT, "ERROR: can't realloc message list!\n");
1751 msglist[num_msgs - 1] = msgid;
1753 /* Sort the message list, so all the msgid's are in order */
1754 num_msgs = sort_msglist(msglist, num_msgs);
1756 /* Determine the highest message number */
1757 highest_msg = msglist[num_msgs - 1];
1759 /* Write it back to disk. */
1760 cdb_store(CDB_MSGLISTS, &CC->room.QRnumber, (int)sizeof(long),
1761 msglist, (int)(num_msgs * sizeof(long)));
1763 /* Free up the memory we used. */
1766 /* Update the highest-message pointer and unlock the room. */
1767 CC->room.QRhighest = highest_msg;
1768 lputroom(&CC->room);
1769 getroom(&CC->room, hold_rm);
1771 /* Bump the reference count for this message. */
1772 if ((flags & SM_DONT_BUMP_REF)==0) {
1773 AdjRefCount(msgid, +1);
1776 /* Return success. */
1777 if (msg != NULL) CtdlFreeMessage(msg);
1784 * Message base operation to save a new message to the message store
1785 * (returns new message number)
1787 * This is the back end for CtdlSubmitMsg() and should not be directly
1788 * called by server-side modules.
1791 long send_message(struct CtdlMessage *msg) {
1799 /* Get a new message number */
1800 newmsgid = get_new_message_number();
1801 snprintf(msgidbuf, sizeof msgidbuf, "%ld@%s", newmsgid, config.c_fqdn);
1803 /* Generate an ID if we don't have one already */
1804 if (msg->cm_fields['I']==NULL) {
1805 msg->cm_fields['I'] = strdup(msgidbuf);
1808 /* If the message is big, set its body aside for storage elsewhere */
1809 if (msg->cm_fields['M'] != NULL) {
1810 if (strlen(msg->cm_fields['M']) > BIGMSG) {
1812 holdM = msg->cm_fields['M'];
1813 msg->cm_fields['M'] = NULL;
1817 /* Serialize our data structure for storage in the database */
1818 serialize_message(&smr, msg);
1821 msg->cm_fields['M'] = holdM;
1825 cprintf("%d Unable to serialize message\n",
1826 ERROR + INTERNAL_ERROR);
1830 /* Write our little bundle of joy into the message base */
1831 if (cdb_store(CDB_MSGMAIN, &newmsgid, (int)sizeof(long),
1832 smr.ser, smr.len) < 0) {
1833 lprintf(CTDL_ERR, "Can't store message\n");
1837 cdb_store(CDB_BIGMSGS,
1847 /* Free the memory we used for the serialized message */
1850 /* Return the *local* message ID to the caller
1851 * (even if we're storing an incoming network message)
1859 * Serialize a struct CtdlMessage into the format used on disk and network.
1861 * This function loads up a "struct ser_ret" (defined in server.h) which
1862 * contains the length of the serialized message and a pointer to the
1863 * serialized message in memory. THE LATTER MUST BE FREED BY THE CALLER.
1865 void serialize_message(struct ser_ret *ret, /* return values */
1866 struct CtdlMessage *msg) /* unserialized msg */
1870 static char *forder = FORDER;
1872 if (is_valid_message(msg) == 0) return; /* self check */
1875 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL)
1876 ret->len = ret->len +
1877 strlen(msg->cm_fields[(int)forder[i]]) + 2;
1879 lprintf(CTDL_DEBUG, "serialize_message() calling malloc(%ld)\n", (long)ret->len);
1880 ret->ser = malloc(ret->len);
1881 if (ret->ser == NULL) {
1887 ret->ser[1] = msg->cm_anon_type;
1888 ret->ser[2] = msg->cm_format_type;
1891 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL) {
1892 ret->ser[wlen++] = (char)forder[i];
1893 strcpy((char *)&ret->ser[wlen], msg->cm_fields[(int)forder[i]]);
1894 wlen = wlen + strlen(msg->cm_fields[(int)forder[i]]) + 1;
1896 if (ret->len != wlen) lprintf(CTDL_ERR, "ERROR: len=%ld wlen=%ld\n",
1897 (long)ret->len, (long)wlen);
1905 * Back end for the ReplicationChecks() function
1907 void check_repl(long msgnum, void *userdata) {
1908 lprintf(CTDL_DEBUG, "check_repl() replacing message %ld\n", msgnum);
1909 CtdlDeleteMessages(CC->room.QRname, msgnum, "");
1914 * Check to see if any messages already exist which carry the same Exclusive ID
1915 * as this one. If any are found, delete them.
1918 int ReplicationChecks(struct CtdlMessage *msg) {
1919 struct CtdlMessage *template;
1922 /* No exclusive id? Don't do anything. */
1923 if (msg->cm_fields['E'] == NULL) return 0;
1924 if (strlen(msg->cm_fields['E']) == 0) return 0;
1925 lprintf(CTDL_DEBUG, "Exclusive ID: <%s>\n", msg->cm_fields['E']);
1927 template = (struct CtdlMessage *) malloc(sizeof(struct CtdlMessage));
1928 memset(template, 0, sizeof(struct CtdlMessage));
1929 template->cm_fields['E'] = strdup(msg->cm_fields['E']);
1931 CtdlForEachMessage(MSGS_ALL, 0L, NULL, template, check_repl, NULL);
1933 CtdlFreeMessage(template);
1934 lprintf(CTDL_DEBUG, "ReplicationChecks() returning %d\n", abort_this);
1942 * Save a message to disk and submit it into the delivery system.
1944 long CtdlSubmitMsg(struct CtdlMessage *msg, /* message to save */
1945 struct recptypes *recps, /* recipients (if mail) */
1946 char *force /* force a particular room? */
1948 char submit_filename[128];
1949 char generated_timestamp[32];
1950 char hold_rm[ROOMNAMELEN];
1951 char actual_rm[ROOMNAMELEN];
1952 char force_room[ROOMNAMELEN];
1953 char content_type[SIZ]; /* We have to learn this */
1954 char recipient[SIZ];
1957 struct ctdluser userbuf;
1959 struct MetaData smi;
1960 FILE *network_fp = NULL;
1961 static int seqnum = 1;
1962 struct CtdlMessage *imsg = NULL;
1965 char *hold_R, *hold_D;
1967 lprintf(CTDL_DEBUG, "CtdlSubmitMsg() called\n");
1968 if (is_valid_message(msg) == 0) return(-1); /* self check */
1970 /* If this message has no timestamp, we take the liberty of
1971 * giving it one, right now.
1973 if (msg->cm_fields['T'] == NULL) {
1974 lprintf(CTDL_DEBUG, "Generating timestamp\n");
1975 snprintf(generated_timestamp, sizeof generated_timestamp, "%ld", (long)time(NULL));
1976 msg->cm_fields['T'] = strdup(generated_timestamp);
1979 /* If this message has no path, we generate one.
1981 if (msg->cm_fields['P'] == NULL) {
1982 lprintf(CTDL_DEBUG, "Generating path\n");
1983 if (msg->cm_fields['A'] != NULL) {
1984 msg->cm_fields['P'] = strdup(msg->cm_fields['A']);
1985 for (a=0; a<strlen(msg->cm_fields['P']); ++a) {
1986 if (isspace(msg->cm_fields['P'][a])) {
1987 msg->cm_fields['P'][a] = ' ';
1992 msg->cm_fields['P'] = strdup("unknown");
1996 if (force == NULL) {
1997 strcpy(force_room, "");
2000 strcpy(force_room, force);
2003 /* Learn about what's inside, because it's what's inside that counts */
2004 lprintf(CTDL_DEBUG, "Learning what's inside\n");
2005 if (msg->cm_fields['M'] == NULL) {
2006 lprintf(CTDL_ERR, "ERROR: attempt to save message with NULL body\n");
2010 switch (msg->cm_format_type) {
2012 strcpy(content_type, "text/x-citadel-variformat");
2015 strcpy(content_type, "text/plain");
2018 strcpy(content_type, "text/plain");
2019 mptr = bmstrstr(msg->cm_fields['M'],
2020 "Content-type: ", strncasecmp);
2022 safestrncpy(content_type, &mptr[14],
2023 sizeof content_type);
2024 for (a = 0; a < strlen(content_type); ++a) {
2025 if ((content_type[a] == ';')
2026 || (content_type[a] == ' ')
2027 || (content_type[a] == 13)
2028 || (content_type[a] == 10)) {
2029 content_type[a] = 0;
2035 /* Goto the correct room */
2036 lprintf(CTDL_DEBUG, "Selected room %s\n",
2037 (recps) ? CC->room.QRname : SENTITEMS);
2038 strcpy(hold_rm, CC->room.QRname);
2039 strcpy(actual_rm, CC->room.QRname);
2040 if (recps != NULL) {
2041 strcpy(actual_rm, SENTITEMS);
2044 /* If the user is a twit, move to the twit room for posting */
2045 lprintf(CTDL_DEBUG, "Handling twit stuff: %s\n",
2046 (CC->user.axlevel == 2) ? config.c_twitroom : "OK");
2048 if (CC->user.axlevel == 2) {
2049 strcpy(hold_rm, actual_rm);
2050 strcpy(actual_rm, config.c_twitroom);
2054 /* ...or if this message is destined for Aide> then go there. */
2055 if (strlen(force_room) > 0) {
2056 strcpy(actual_rm, force_room);
2059 lprintf(CTDL_DEBUG, "Final selection: %s\n", actual_rm);
2060 if (strcasecmp(actual_rm, CC->room.QRname)) {
2061 getroom(&CC->room, actual_rm);
2065 * If this message has no O (room) field, generate one.
2067 if (msg->cm_fields['O'] == NULL) {
2068 msg->cm_fields['O'] = strdup(CC->room.QRname);
2071 /* Perform "before save" hooks (aborting if any return nonzero) */
2072 lprintf(CTDL_DEBUG, "Performing before-save hooks\n");
2073 if (PerformMessageHooks(msg, EVT_BEFORESAVE) > 0) return(-3);
2075 /* If this message has an Exclusive ID, perform replication checks */
2076 lprintf(CTDL_DEBUG, "Performing replication checks\n");
2077 if (ReplicationChecks(msg) > 0) return(-4);
2079 /* Save it to disk */
2080 lprintf(CTDL_DEBUG, "Saving to disk\n");
2081 newmsgid = send_message(msg);
2082 if (newmsgid <= 0L) return(-5);
2084 /* Write a supplemental message info record. This doesn't have to
2085 * be a critical section because nobody else knows about this message
2088 lprintf(CTDL_DEBUG, "Creating MetaData record\n");
2089 memset(&smi, 0, sizeof(struct MetaData));
2090 smi.meta_msgnum = newmsgid;
2091 smi.meta_refcount = 0;
2092 safestrncpy(smi.meta_content_type, content_type,
2093 sizeof smi.meta_content_type);
2095 /* As part of the new metadata record, measure how
2096 * big this message will be when displayed as RFC822.
2097 * Both POP and IMAP use this, and it's best to just take the hit now
2098 * instead of having to potentially measure thousands of messages when
2099 * a mailbox is opened later.
2102 if (CC->redirect_buffer != NULL) {
2103 lprintf(CTDL_ALERT, "CC->redirect_buffer is not NULL during message submission!\n");
2106 CC->redirect_buffer = malloc(SIZ);
2107 CC->redirect_len = 0;
2108 CC->redirect_alloc = SIZ;
2109 CtdlOutputPreLoadedMsg(msg, 0L, MT_RFC822, HEADERS_ALL, 0, 1);
2110 smi.meta_rfc822_length = CC->redirect_len;
2111 free(CC->redirect_buffer);
2112 CC->redirect_buffer = NULL;
2113 CC->redirect_len = 0;
2114 CC->redirect_alloc = 0;
2118 /* Now figure out where to store the pointers */
2119 lprintf(CTDL_DEBUG, "Storing pointers\n");
2121 /* If this is being done by the networker delivering a private
2122 * message, we want to BYPASS saving the sender's copy (because there
2123 * is no local sender; it would otherwise go to the Trashcan).
2125 if ((!CC->internal_pgm) || (recps == NULL)) {
2126 if (CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 0) != 0) {
2127 lprintf(CTDL_ERR, "ERROR saving message pointer!\n");
2128 CtdlSaveMsgPointerInRoom(config.c_aideroom,
2133 /* For internet mail, drop a copy in the outbound queue room */
2135 if (recps->num_internet > 0) {
2136 CtdlSaveMsgPointerInRoom(SMTP_SPOOLOUT_ROOM, newmsgid, 0);
2139 /* If other rooms are specified, drop them there too. */
2141 if (recps->num_room > 0)
2142 for (i=0; i<num_tokens(recps->recp_room, '|'); ++i) {
2143 extract_token(recipient, recps->recp_room, i,
2144 '|', sizeof recipient);
2145 lprintf(CTDL_DEBUG, "Delivering to room <%s>\n", recipient);
2146 CtdlSaveMsgPointerInRoom(recipient, newmsgid, 0);
2149 /* Bump this user's messages posted counter. */
2150 lprintf(CTDL_DEBUG, "Updating user\n");
2151 lgetuser(&CC->user, CC->curr_user);
2152 CC->user.posted = CC->user.posted + 1;
2153 lputuser(&CC->user);
2155 /* If this is private, local mail, make a copy in the
2156 * recipient's mailbox and bump the reference count.
2159 if (recps->num_local > 0)
2160 for (i=0; i<num_tokens(recps->recp_local, '|'); ++i) {
2161 extract_token(recipient, recps->recp_local, i,
2162 '|', sizeof recipient);
2163 lprintf(CTDL_DEBUG, "Delivering private local mail to <%s>\n",
2165 if (getuser(&userbuf, recipient) == 0) {
2166 MailboxName(actual_rm, sizeof actual_rm,
2167 &userbuf, MAILROOM);
2168 CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 0);
2169 BumpNewMailCounter(userbuf.usernum);
2172 lprintf(CTDL_DEBUG, "No user <%s>\n", recipient);
2173 CtdlSaveMsgPointerInRoom(config.c_aideroom,
2178 /* Perform "after save" hooks */
2179 lprintf(CTDL_DEBUG, "Performing after-save hooks\n");
2180 PerformMessageHooks(msg, EVT_AFTERSAVE);
2182 /* For IGnet mail, we have to save a new copy into the spooler for
2183 * each recipient, with the R and D fields set to the recipient and
2184 * destination-node. This has two ugly side effects: all other
2185 * recipients end up being unlisted in this recipient's copy of the
2186 * message, and it has to deliver multiple messages to the same
2187 * node. We'll revisit this again in a year or so when everyone has
2188 * a network spool receiver that can handle the new style messages.
2191 if (recps->num_ignet > 0)
2192 for (i=0; i<num_tokens(recps->recp_ignet, '|'); ++i) {
2193 extract_token(recipient, recps->recp_ignet, i,
2194 '|', sizeof recipient);
2196 hold_R = msg->cm_fields['R'];
2197 hold_D = msg->cm_fields['D'];
2198 msg->cm_fields['R'] = malloc(SIZ);
2199 msg->cm_fields['D'] = malloc(128);
2200 extract_token(msg->cm_fields['R'], recipient, 0, '@', SIZ);
2201 extract_token(msg->cm_fields['D'], recipient, 1, '@', 128);
2203 serialize_message(&smr, msg);
2205 snprintf(submit_filename, sizeof submit_filename,
2206 "./network/spoolin/netmail.%04lx.%04x.%04x",
2207 (long) getpid(), CC->cs_pid, ++seqnum);
2208 network_fp = fopen(submit_filename, "wb+");
2209 if (network_fp != NULL) {
2210 fwrite(smr.ser, smr.len, 1, network_fp);
2216 free(msg->cm_fields['R']);
2217 free(msg->cm_fields['D']);
2218 msg->cm_fields['R'] = hold_R;
2219 msg->cm_fields['D'] = hold_D;
2222 /* Go back to the room we started from */
2223 lprintf(CTDL_DEBUG, "Returning to original room %s\n", hold_rm);
2224 if (strcasecmp(hold_rm, CC->room.QRname))
2225 getroom(&CC->room, hold_rm);
2227 /* For internet mail, generate delivery instructions.
2228 * Yes, this is recursive. Deal with it. Infinite recursion does
2229 * not happen because the delivery instructions message does not
2230 * contain a recipient.
2233 if (recps->num_internet > 0) {
2234 lprintf(CTDL_DEBUG, "Generating delivery instructions\n");
2235 instr = malloc(SIZ * 2);
2236 snprintf(instr, SIZ * 2,
2237 "Content-type: %s\n\nmsgid|%ld\nsubmitted|%ld\n"
2239 SPOOLMIME, newmsgid, (long)time(NULL),
2240 msg->cm_fields['A'], msg->cm_fields['N']
2243 for (i=0; i<num_tokens(recps->recp_internet, '|'); ++i) {
2244 size_t tmp = strlen(instr);
2245 extract_token(recipient, recps->recp_internet, i, '|', sizeof recipient);
2246 snprintf(&instr[tmp], SIZ * 2 - tmp,
2247 "remote|%s|0||\n", recipient);
2250 imsg = malloc(sizeof(struct CtdlMessage));
2251 memset(imsg, 0, sizeof(struct CtdlMessage));
2252 imsg->cm_magic = CTDLMESSAGE_MAGIC;
2253 imsg->cm_anon_type = MES_NORMAL;
2254 imsg->cm_format_type = FMT_RFC822;
2255 imsg->cm_fields['A'] = strdup("Citadel");
2256 imsg->cm_fields['M'] = instr;
2257 CtdlSubmitMsg(imsg, NULL, SMTP_SPOOLOUT_ROOM);
2258 CtdlFreeMessage(imsg);
2267 * Convenience function for generating small administrative messages.
2269 void quickie_message(char *from, char *to, char *room, char *text,
2270 int format_type, char *subject)
2272 struct CtdlMessage *msg;
2273 struct recptypes *recp = NULL;
2275 msg = malloc(sizeof(struct CtdlMessage));
2276 memset(msg, 0, sizeof(struct CtdlMessage));
2277 msg->cm_magic = CTDLMESSAGE_MAGIC;
2278 msg->cm_anon_type = MES_NORMAL;
2279 msg->cm_format_type = format_type;
2280 msg->cm_fields['A'] = strdup(from);
2281 if (room != NULL) msg->cm_fields['O'] = strdup(room);
2282 msg->cm_fields['N'] = strdup(NODENAME);
2284 msg->cm_fields['R'] = strdup(to);
2285 recp = validate_recipients(to);
2287 if (subject != NULL) {
2288 msg->cm_fields['U'] = strdup(subject);
2290 msg->cm_fields['M'] = strdup(text);
2292 CtdlSubmitMsg(msg, recp, room);
2293 CtdlFreeMessage(msg);
2294 if (recp != NULL) free(recp);
2300 * Back end function used by CtdlMakeMessage() and similar functions
2302 char *CtdlReadMessageBody(char *terminator, /* token signalling EOT */
2303 size_t maxlen, /* maximum message length */
2304 char *exist, /* if non-null, append to it;
2305 exist is ALWAYS freed */
2306 int crlf /* CRLF newlines instead of LF */
2310 size_t message_len = 0;
2311 size_t buffer_len = 0;
2317 if (exist == NULL) {
2324 message_len = strlen(exist);
2325 buffer_len = message_len + 4096;
2326 m = realloc(exist, buffer_len);
2333 /* flush the input if we have nowhere to store it */
2338 /* read in the lines of message text one by one */
2340 if (client_getln(buf, (sizeof buf - 3)) < 1) finished = 1;
2341 if (!strcmp(buf, terminator)) finished = 1;
2343 strcat(buf, "\r\n");
2349 if ( (!flushing) && (!finished) ) {
2350 /* Measure the line */
2351 linelen = strlen(buf);
2353 /* augment the buffer if we have to */
2354 if ((message_len + linelen) >= buffer_len) {
2355 ptr = realloc(m, (buffer_len * 2) );
2356 if (ptr == NULL) { /* flush if can't allocate */
2359 buffer_len = (buffer_len * 2);
2361 lprintf(CTDL_DEBUG, "buffer_len is now %ld\n", (long)buffer_len);
2365 /* Add the new line to the buffer. NOTE: this loop must avoid
2366 * using functions like strcat() and strlen() because they
2367 * traverse the entire buffer upon every call, and doing that
2368 * for a multi-megabyte message slows it down beyond usability.
2370 strcpy(&m[message_len], buf);
2371 message_len += linelen;
2374 /* if we've hit the max msg length, flush the rest */
2375 if (message_len >= maxlen) flushing = 1;
2377 } while (!finished);
2385 * Build a binary message to be saved on disk.
2386 * (NOTE: if you supply 'preformatted_text', the buffer you give it
2387 * will become part of the message. This means you are no longer
2388 * responsible for managing that memory -- it will be freed along with
2389 * the rest of the fields when CtdlFreeMessage() is called.)
2392 struct CtdlMessage *CtdlMakeMessage(
2393 struct ctdluser *author, /* author's user structure */
2394 char *recipient, /* NULL if it's not mail */
2395 char *room, /* room where it's going */
2396 int type, /* see MES_ types in header file */
2397 int format_type, /* variformat, plain text, MIME... */
2398 char *fake_name, /* who we're masquerading as */
2399 char *subject, /* Subject (optional) */
2400 char *preformatted_text /* ...or NULL to read text from client */
2402 char dest_node[SIZ];
2404 struct CtdlMessage *msg;
2406 msg = malloc(sizeof(struct CtdlMessage));
2407 memset(msg, 0, sizeof(struct CtdlMessage));
2408 msg->cm_magic = CTDLMESSAGE_MAGIC;
2409 msg->cm_anon_type = type;
2410 msg->cm_format_type = format_type;
2412 /* Don't confuse the poor folks if it's not routed mail. */
2413 strcpy(dest_node, "");
2417 snprintf(buf, sizeof buf, "cit%ld", author->usernum); /* Path */
2418 msg->cm_fields['P'] = strdup(buf);
2420 snprintf(buf, sizeof buf, "%ld", (long)time(NULL)); /* timestamp */
2421 msg->cm_fields['T'] = strdup(buf);
2423 if (fake_name[0]) /* author */
2424 msg->cm_fields['A'] = strdup(fake_name);
2426 msg->cm_fields['A'] = strdup(author->fullname);
2428 if (CC->room.QRflags & QR_MAILBOX) { /* room */
2429 msg->cm_fields['O'] = strdup(&CC->room.QRname[11]);
2432 msg->cm_fields['O'] = strdup(CC->room.QRname);
2435 msg->cm_fields['N'] = strdup(NODENAME); /* nodename */
2436 msg->cm_fields['H'] = strdup(HUMANNODE); /* hnodename */
2438 if (recipient[0] != 0) {
2439 msg->cm_fields['R'] = strdup(recipient);
2441 if (dest_node[0] != 0) {
2442 msg->cm_fields['D'] = strdup(dest_node);
2445 if ( (author == &CC->user) && (strlen(CC->cs_inet_email) > 0) ) {
2446 msg->cm_fields['F'] = strdup(CC->cs_inet_email);
2449 if (subject != NULL) {
2451 if (strlen(subject) > 0) {
2452 msg->cm_fields['U'] = strdup(subject);
2456 if (preformatted_text != NULL) {
2457 msg->cm_fields['M'] = preformatted_text;
2460 msg->cm_fields['M'] = CtdlReadMessageBody("000",
2461 config.c_maxmsglen, NULL, 0);
2469 * Check to see whether we have permission to post a message in the current
2470 * room. Returns a *CITADEL ERROR CODE* and puts a message in errmsgbuf, or
2471 * returns 0 on success.
2473 int CtdlDoIHavePermissionToPostInThisRoom(char *errmsgbuf, size_t n) {
2475 if (!(CC->logged_in)) {
2476 snprintf(errmsgbuf, n, "Not logged in.");
2477 return (ERROR + NOT_LOGGED_IN);
2480 if ((CC->user.axlevel < 2)
2481 && ((CC->room.QRflags & QR_MAILBOX) == 0)) {
2482 snprintf(errmsgbuf, n, "Need to be validated to enter "
2483 "(except in %s> to sysop)", MAILROOM);
2484 return (ERROR + HIGHER_ACCESS_REQUIRED);
2487 if ((CC->user.axlevel < 4)
2488 && (CC->room.QRflags & QR_NETWORK)) {
2489 snprintf(errmsgbuf, n, "Need net privileges to enter here.");
2490 return (ERROR + HIGHER_ACCESS_REQUIRED);
2493 if ((CC->user.axlevel < 6)
2494 && (CC->room.QRflags & QR_READONLY)) {
2495 snprintf(errmsgbuf, n, "Sorry, this is a read-only room.");
2496 return (ERROR + HIGHER_ACCESS_REQUIRED);
2499 strcpy(errmsgbuf, "Ok");
2505 * Check to see if the specified user has Internet mail permission
2506 * (returns nonzero if permission is granted)
2508 int CtdlCheckInternetMailPermission(struct ctdluser *who) {
2510 /* Do not allow twits to send Internet mail */
2511 if (who->axlevel <= 2) return(0);
2513 /* Globally enabled? */
2514 if (config.c_restrict == 0) return(1);
2516 /* User flagged ok? */
2517 if (who->flags & US_INTERNET) return(2);
2519 /* Aide level access? */
2520 if (who->axlevel >= 6) return(3);
2522 /* No mail for you! */
2529 * Validate recipients, count delivery types and errors, and handle aliasing
2530 * FIXME check for dupes!!!!!
2532 struct recptypes *validate_recipients(char *recipients) {
2533 struct recptypes *ret;
2534 char this_recp[SIZ];
2535 char this_recp_cooked[SIZ];
2541 struct ctdluser tempUS;
2542 struct ctdlroom tempQR;
2545 ret = (struct recptypes *) malloc(sizeof(struct recptypes));
2546 if (ret == NULL) return(NULL);
2547 memset(ret, 0, sizeof(struct recptypes));
2550 ret->num_internet = 0;
2555 if (recipients == NULL) {
2558 else if (strlen(recipients) == 0) {
2562 /* Change all valid separator characters to commas */
2563 for (i=0; i<strlen(recipients); ++i) {
2564 if ((recipients[i] == ';') || (recipients[i] == '|')) {
2565 recipients[i] = ',';
2570 num_recps = num_tokens(recipients, ',');
2573 if (num_recps > 0) for (i=0; i<num_recps; ++i) {
2574 extract_token(this_recp, recipients, i, ',', sizeof this_recp);
2576 lprintf(CTDL_DEBUG, "Evaluating recipient #%d <%s>\n", i, this_recp);
2577 mailtype = alias(this_recp);
2578 mailtype = alias(this_recp);
2579 mailtype = alias(this_recp);
2580 for (j=0; j<=strlen(this_recp); ++j) {
2581 if (this_recp[j]=='_') {
2582 this_recp_cooked[j] = ' ';
2585 this_recp_cooked[j] = this_recp[j];
2591 if (!strcasecmp(this_recp, "sysop")) {
2593 strcpy(this_recp, config.c_aideroom);
2594 if (strlen(ret->recp_room) > 0) {
2595 strcat(ret->recp_room, "|");
2597 strcat(ret->recp_room, this_recp);
2599 else if (getuser(&tempUS, this_recp) == 0) {
2601 strcpy(this_recp, tempUS.fullname);
2602 if (strlen(ret->recp_local) > 0) {
2603 strcat(ret->recp_local, "|");
2605 strcat(ret->recp_local, this_recp);
2607 else if (getuser(&tempUS, this_recp_cooked) == 0) {
2609 strcpy(this_recp, tempUS.fullname);
2610 if (strlen(ret->recp_local) > 0) {
2611 strcat(ret->recp_local, "|");
2613 strcat(ret->recp_local, this_recp);
2615 else if ( (!strncasecmp(this_recp, "room_", 5))
2616 && (!getroom(&tempQR, &this_recp_cooked[5])) ) {
2618 if (strlen(ret->recp_room) > 0) {
2619 strcat(ret->recp_room, "|");
2621 strcat(ret->recp_room, &this_recp_cooked[5]);
2629 /* Yes, you're reading this correctly: if the target
2630 * domain points back to the local system or an attached
2631 * Citadel directory, the address is invalid. That's
2632 * because if the address were valid, we would have
2633 * already translated it to a local address by now.
2635 if (IsDirectory(this_recp)) {
2640 ++ret->num_internet;
2641 if (strlen(ret->recp_internet) > 0) {
2642 strcat(ret->recp_internet, "|");
2644 strcat(ret->recp_internet, this_recp);
2649 if (strlen(ret->recp_ignet) > 0) {
2650 strcat(ret->recp_ignet, "|");
2652 strcat(ret->recp_ignet, this_recp);
2660 if (strlen(ret->errormsg) == 0) {
2661 snprintf(append, sizeof append,
2662 "Invalid recipient: %s",
2666 snprintf(append, sizeof append,
2669 if ( (strlen(ret->errormsg) + strlen(append)) < SIZ) {
2670 strcat(ret->errormsg, append);
2674 if (strlen(ret->display_recp) == 0) {
2675 strcpy(append, this_recp);
2678 snprintf(append, sizeof append, ", %s",
2681 if ( (strlen(ret->display_recp)+strlen(append)) < SIZ) {
2682 strcat(ret->display_recp, append);
2687 if ((ret->num_local + ret->num_internet + ret->num_ignet +
2688 ret->num_room + ret->num_error) == 0) {
2690 strcpy(ret->errormsg, "No recipients specified.");
2693 lprintf(CTDL_DEBUG, "validate_recipients()\n");
2694 lprintf(CTDL_DEBUG, " local: %d <%s>\n", ret->num_local, ret->recp_local);
2695 lprintf(CTDL_DEBUG, " room: %d <%s>\n", ret->num_room, ret->recp_room);
2696 lprintf(CTDL_DEBUG, " inet: %d <%s>\n", ret->num_internet, ret->recp_internet);
2697 lprintf(CTDL_DEBUG, " ignet: %d <%s>\n", ret->num_ignet, ret->recp_ignet);
2698 lprintf(CTDL_DEBUG, " error: %d <%s>\n", ret->num_error, ret->errormsg);
2706 * message entry - mode 0 (normal)
2708 void cmd_ent0(char *entargs)
2712 char masquerade_as[SIZ];
2714 int format_type = 0;
2715 char newusername[SIZ];
2716 struct CtdlMessage *msg;
2720 struct recptypes *valid = NULL;
2727 post = extract_int(entargs, 0);
2728 extract_token(recp, entargs, 1, '|', sizeof recp);
2729 anon_flag = extract_int(entargs, 2);
2730 format_type = extract_int(entargs, 3);
2731 extract_token(subject, entargs, 4, '|', sizeof subject);
2732 do_confirm = extract_int(entargs, 6);
2734 /* first check to make sure the request is valid. */
2736 err = CtdlDoIHavePermissionToPostInThisRoom(errmsg, sizeof errmsg);
2738 cprintf("%d %s\n", err, errmsg);
2742 /* Check some other permission type things. */
2745 if (CC->user.axlevel < 6) {
2746 cprintf("%d You don't have permission to masquerade.\n",
2747 ERROR + HIGHER_ACCESS_REQUIRED);
2750 extract_token(newusername, entargs, 5, '|', sizeof newusername);
2751 memset(CC->fake_postname, 0, sizeof(CC->fake_postname) );
2752 safestrncpy(CC->fake_postname, newusername,
2753 sizeof(CC->fake_postname) );
2754 cprintf("%d ok\n", CIT_OK);
2757 CC->cs_flags |= CS_POSTING;
2759 /* In the Mail> room we have to behave a little differently --
2760 * make sure the user has specified at least one recipient. Then
2761 * validate the recipient(s).
2763 if ( (CC->room.QRflags & QR_MAILBOX)
2764 && (!strcasecmp(&CC->room.QRname[11], MAILROOM)) ) {
2766 if (CC->user.axlevel < 2) {
2767 strcpy(recp, "sysop");
2770 valid = validate_recipients(recp);
2771 if (valid->num_error > 0) {
2773 ERROR + NO_SUCH_USER, valid->errormsg);
2777 if (valid->num_internet > 0) {
2778 if (CtdlCheckInternetMailPermission(&CC->user)==0) {
2779 cprintf("%d You do not have permission "
2780 "to send Internet mail.\n",
2781 ERROR + HIGHER_ACCESS_REQUIRED);
2787 if ( ( (valid->num_internet + valid->num_ignet) > 0)
2788 && (CC->user.axlevel < 4) ) {
2789 cprintf("%d Higher access required for network mail.\n",
2790 ERROR + HIGHER_ACCESS_REQUIRED);
2795 if ((RESTRICT_INTERNET == 1) && (valid->num_internet > 0)
2796 && ((CC->user.flags & US_INTERNET) == 0)
2797 && (!CC->internal_pgm)) {
2798 cprintf("%d You don't have access to Internet mail.\n",
2799 ERROR + HIGHER_ACCESS_REQUIRED);
2806 /* Is this a room which has anonymous-only or anonymous-option? */
2807 anonymous = MES_NORMAL;
2808 if (CC->room.QRflags & QR_ANONONLY) {
2809 anonymous = MES_ANONONLY;
2811 if (CC->room.QRflags & QR_ANONOPT) {
2812 if (anon_flag == 1) { /* only if the user requested it */
2813 anonymous = MES_ANONOPT;
2817 if ((CC->room.QRflags & QR_MAILBOX) == 0) {
2821 /* If we're only checking the validity of the request, return
2822 * success without creating the message.
2825 cprintf("%d %s\n", CIT_OK,
2826 ((valid != NULL) ? valid->display_recp : "") );
2831 /* Handle author masquerading */
2832 if (CC->fake_postname[0]) {
2833 strcpy(masquerade_as, CC->fake_postname);
2835 else if (CC->fake_username[0]) {
2836 strcpy(masquerade_as, CC->fake_username);
2839 strcpy(masquerade_as, "");
2842 /* Read in the message from the client. */
2844 cprintf("%d send message\n", START_CHAT_MODE);
2846 cprintf("%d send message\n", SEND_LISTING);
2848 msg = CtdlMakeMessage(&CC->user, recp,
2849 CC->room.QRname, anonymous, format_type,
2850 masquerade_as, subject, NULL);
2853 msgnum = CtdlSubmitMsg(msg, valid, "");
2856 cprintf("%ld\n", msgnum);
2858 cprintf("Message accepted.\n");
2861 cprintf("Internal error.\n");
2863 if (msg->cm_fields['E'] != NULL) {
2864 cprintf("%s\n", msg->cm_fields['E']);
2871 CtdlFreeMessage(msg);
2873 CC->fake_postname[0] = '\0';
2881 * API function to delete messages which match a set of criteria
2882 * (returns the actual number of messages deleted)
2884 int CtdlDeleteMessages(char *room_name, /* which room */
2885 long dmsgnum, /* or "0" for any */
2886 char *content_type /* or "" for any */
2890 struct ctdlroom qrbuf;
2891 struct cdbdata *cdbfr;
2892 long *msglist = NULL;
2893 long *dellist = NULL;
2896 int num_deleted = 0;
2898 struct MetaData smi;
2900 lprintf(CTDL_DEBUG, "CtdlDeleteMessages(%s, %ld, %s)\n",
2901 room_name, dmsgnum, content_type);
2903 /* get room record, obtaining a lock... */
2904 if (lgetroom(&qrbuf, room_name) != 0) {
2905 lprintf(CTDL_ERR, "CtdlDeleteMessages(): Room <%s> not found\n",
2907 return (0); /* room not found */
2909 cdbfr = cdb_fetch(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long));
2911 if (cdbfr != NULL) {
2912 msglist = malloc(cdbfr->len);
2913 dellist = malloc(cdbfr->len);
2914 memcpy(msglist, cdbfr->ptr, cdbfr->len);
2915 num_msgs = cdbfr->len / sizeof(long);
2919 for (i = 0; i < num_msgs; ++i) {
2922 /* Set/clear a bit for each criterion */
2924 if ((dmsgnum == 0L) || (msglist[i] == dmsgnum)) {
2925 delete_this |= 0x01;
2927 if (strlen(content_type) == 0) {
2928 delete_this |= 0x02;
2930 GetMetaData(&smi, msglist[i]);
2931 if (!strcasecmp(smi.meta_content_type,
2933 delete_this |= 0x02;
2937 /* Delete message only if all bits are set */
2938 if (delete_this == 0x03) {
2939 dellist[num_deleted++] = msglist[i];
2944 num_msgs = sort_msglist(msglist, num_msgs);
2945 cdb_store(CDB_MSGLISTS, &qrbuf.QRnumber, (int)sizeof(long),
2946 msglist, (int)(num_msgs * sizeof(long)));
2948 qrbuf.QRhighest = msglist[num_msgs - 1];
2952 /* Go through the messages we pulled out of the index, and decrement
2953 * their reference counts by 1. If this is the only room the message
2954 * was in, the reference count will reach zero and the message will
2955 * automatically be deleted from the database. We do this in a
2956 * separate pass because there might be plug-in hooks getting called,
2957 * and we don't want that happening during an S_ROOMS critical
2960 if (num_deleted) for (i=0; i<num_deleted; ++i) {
2961 PerformDeleteHooks(qrbuf.QRname, dellist[i]);
2962 AdjRefCount(dellist[i], -1);
2965 /* Now free the memory we used, and go away. */
2966 if (msglist != NULL) free(msglist);
2967 if (dellist != NULL) free(dellist);
2968 lprintf(CTDL_DEBUG, "%d message(s) deleted.\n", num_deleted);
2969 return (num_deleted);
2975 * Check whether the current user has permission to delete messages from
2976 * the current room (returns 1 for yes, 0 for no)
2978 int CtdlDoIHavePermissionToDeleteMessagesFromThisRoom(void) {
2979 getuser(&CC->user, CC->curr_user);
2980 if ((CC->user.axlevel < 6)
2981 && (CC->user.usernum != CC->room.QRroomaide)
2982 && ((CC->room.QRflags & QR_MAILBOX) == 0)
2983 && (!(CC->internal_pgm))) {
2992 * Delete message from current room
2994 void cmd_dele(char *delstr)
2999 if (CtdlDoIHavePermissionToDeleteMessagesFromThisRoom() == 0) {
3000 cprintf("%d Higher access required.\n",
3001 ERROR + HIGHER_ACCESS_REQUIRED);
3004 delnum = extract_long(delstr, 0);
3006 num_deleted = CtdlDeleteMessages(CC->room.QRname, delnum, "");
3009 cprintf("%d %d message%s deleted.\n", CIT_OK,
3010 num_deleted, ((num_deleted != 1) ? "s" : ""));
3012 cprintf("%d Message %ld not found.\n", ERROR + MESSAGE_NOT_FOUND, delnum);
3018 * Back end API function for moves and deletes
3020 int CtdlCopyMsgToRoom(long msgnum, char *dest) {
3023 err = CtdlSaveMsgPointerInRoom(dest, msgnum,
3024 (SM_VERIFY_GOODNESS | SM_DO_REPL_CHECK) );
3025 if (err != 0) return(err);
3033 * move or copy a message to another room
3035 void cmd_move(char *args)
3038 char targ[ROOMNAMELEN];
3039 struct ctdlroom qtemp;
3045 num = extract_long(args, 0);
3046 extract_token(targ, args, 1, '|', sizeof targ);
3047 targ[ROOMNAMELEN - 1] = 0;
3048 is_copy = extract_int(args, 2);
3050 if (getroom(&qtemp, targ) != 0) {
3051 cprintf("%d '%s' does not exist.\n",
3052 ERROR + ROOM_NOT_FOUND, targ);
3056 getuser(&CC->user, CC->curr_user);
3057 CtdlRoomAccess(&qtemp, &CC->user, &ra, NULL);
3059 /* Check for permission to perform this operation.
3060 * Remember: "CC->room" is source, "qtemp" is target.
3064 /* Aides can move/copy */
3065 if (CC->user.axlevel >= 6) permit = 1;
3067 /* Room aides can move/copy */
3068 if (CC->user.usernum == CC->room.QRroomaide) permit = 1;
3070 /* Permit move/copy from personal rooms */
3071 if ((CC->room.QRflags & QR_MAILBOX)
3072 && (qtemp.QRflags & QR_MAILBOX)) permit = 1;
3074 /* Permit only copy from public to personal room */
3076 && (!(CC->room.QRflags & QR_MAILBOX))
3077 && (qtemp.QRflags & QR_MAILBOX)) permit = 1;
3079 /* User must have access to target room */
3080 if (!(ra & UA_KNOWN)) permit = 0;
3083 cprintf("%d Higher access required.\n",
3084 ERROR + HIGHER_ACCESS_REQUIRED);
3088 err = CtdlCopyMsgToRoom(num, targ);
3090 cprintf("%d Cannot store message in %s: error %d\n",
3095 /* Now delete the message from the source room,
3096 * if this is a 'move' rather than a 'copy' operation.
3099 CtdlDeleteMessages(CC->room.QRname, num, "");
3102 cprintf("%d Message %s.\n", CIT_OK, (is_copy ? "copied" : "moved") );
3108 * GetMetaData() - Get the supplementary record for a message
3110 void GetMetaData(struct MetaData *smibuf, long msgnum)
3113 struct cdbdata *cdbsmi;
3116 memset(smibuf, 0, sizeof(struct MetaData));
3117 smibuf->meta_msgnum = msgnum;
3118 smibuf->meta_refcount = 1; /* Default reference count is 1 */
3120 /* Use the negative of the message number for its supp record index */
3121 TheIndex = (0L - msgnum);
3123 cdbsmi = cdb_fetch(CDB_MSGMAIN, &TheIndex, sizeof(long));
3124 if (cdbsmi == NULL) {
3125 return; /* record not found; go with defaults */
3127 memcpy(smibuf, cdbsmi->ptr,
3128 ((cdbsmi->len > sizeof(struct MetaData)) ?
3129 sizeof(struct MetaData) : cdbsmi->len));
3136 * PutMetaData() - (re)write supplementary record for a message
3138 void PutMetaData(struct MetaData *smibuf)
3142 /* Use the negative of the message number for the metadata db index */
3143 TheIndex = (0L - smibuf->meta_msgnum);
3145 lprintf(CTDL_DEBUG, "PutMetaData(%ld) - ref count is %d\n",
3146 smibuf->meta_msgnum, smibuf->meta_refcount);
3148 cdb_store(CDB_MSGMAIN,
3149 &TheIndex, (int)sizeof(long),
3150 smibuf, (int)sizeof(struct MetaData));
3155 * AdjRefCount - change the reference count for a message;
3156 * delete the message if it reaches zero
3158 void AdjRefCount(long msgnum, int incr)
3161 struct MetaData smi;
3164 /* This is a *tight* critical section; please keep it that way, as
3165 * it may get called while nested in other critical sections.
3166 * Complicating this any further will surely cause deadlock!
3168 begin_critical_section(S_SUPPMSGMAIN);
3169 GetMetaData(&smi, msgnum);
3170 lprintf(CTDL_DEBUG, "Ref count for message <%ld> before write is <%d>\n",
3171 msgnum, smi.meta_refcount);
3172 smi.meta_refcount += incr;
3174 end_critical_section(S_SUPPMSGMAIN);
3175 lprintf(CTDL_DEBUG, "Ref count for message <%ld> after write is <%d>\n",
3176 msgnum, smi.meta_refcount);
3178 /* If the reference count is now zero, delete the message
3179 * (and its supplementary record as well).
3180 * FIXME ... defer this so it doesn't keep the user waiting.
3182 if (smi.meta_refcount == 0) {
3183 lprintf(CTDL_DEBUG, "Deleting message <%ld>\n", msgnum);
3185 /* Remove from fulltext index */
3186 ft_index_message(msgnum, 0);
3188 /* Remove from message base */
3190 cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long));
3191 cdb_delete(CDB_BIGMSGS, &delnum, (int)sizeof(long));
3193 /* Remove metadata record */
3194 delnum = (0L - msgnum);
3195 cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long));
3200 * Write a generic object to this room
3202 * Note: this could be much more efficient. Right now we use two temporary
3203 * files, and still pull the message into memory as with all others.
3205 void CtdlWriteObject(char *req_room, /* Room to stuff it in */
3206 char *content_type, /* MIME type of this object */
3207 char *tempfilename, /* Where to fetch it from */
3208 struct ctdluser *is_mailbox, /* Mailbox room? */
3209 int is_binary, /* Is encoding necessary? */
3210 int is_unique, /* Del others of this type? */
3211 unsigned int flags /* Internal save flags */
3216 struct ctdlroom qrbuf;
3217 char roomname[ROOMNAMELEN];
3218 struct CtdlMessage *msg;
3220 char *raw_message = NULL;
3221 char *encoded_message = NULL;
3222 off_t raw_length = 0;
3224 if (is_mailbox != NULL)
3225 MailboxName(roomname, sizeof roomname, is_mailbox, req_room);
3227 safestrncpy(roomname, req_room, sizeof(roomname));
3228 lprintf(CTDL_DEBUG, "CtdlWriteObject() to <%s> (flags=%d)\n", roomname, flags);
3231 fp = fopen(tempfilename, "rb");
3233 lprintf(CTDL_CRIT, "Cannot open %s: %s\n",
3234 tempfilename, strerror(errno));
3237 fseek(fp, 0L, SEEK_END);
3238 raw_length = ftell(fp);
3240 lprintf(CTDL_DEBUG, "Raw length is %ld\n", (long)raw_length);
3242 raw_message = malloc((size_t)raw_length + 2);
3243 fread(raw_message, (size_t)raw_length, 1, fp);
3247 encoded_message = malloc((size_t)
3248 (((raw_length * 134) / 100) + 4096 ) );
3251 encoded_message = malloc((size_t)(raw_length + 4096));
3254 sprintf(encoded_message, "Content-type: %s\n", content_type);
3257 sprintf(&encoded_message[strlen(encoded_message)],
3258 "Content-transfer-encoding: base64\n\n"
3262 sprintf(&encoded_message[strlen(encoded_message)],
3263 "Content-transfer-encoding: 7bit\n\n"
3269 &encoded_message[strlen(encoded_message)],
3275 raw_message[raw_length] = 0;
3277 &encoded_message[strlen(encoded_message)],
3285 lprintf(CTDL_DEBUG, "Allocating\n");
3286 msg = malloc(sizeof(struct CtdlMessage));
3287 memset(msg, 0, sizeof(struct CtdlMessage));
3288 msg->cm_magic = CTDLMESSAGE_MAGIC;
3289 msg->cm_anon_type = MES_NORMAL;
3290 msg->cm_format_type = 4;
3291 msg->cm_fields['A'] = strdup(CC->user.fullname);
3292 msg->cm_fields['O'] = strdup(req_room);
3293 msg->cm_fields['N'] = strdup(config.c_nodename);
3294 msg->cm_fields['H'] = strdup(config.c_humannode);
3295 msg->cm_flags = flags;
3297 msg->cm_fields['M'] = encoded_message;
3299 /* Create the requested room if we have to. */
3300 if (getroom(&qrbuf, roomname) != 0) {
3301 create_room(roomname,
3302 ( (is_mailbox != NULL) ? 5 : 3 ),
3303 "", 0, 1, 0, VIEW_BBS);
3305 /* If the caller specified this object as unique, delete all
3306 * other objects of this type that are currently in the room.
3309 lprintf(CTDL_DEBUG, "Deleted %d other msgs of this type\n",
3310 CtdlDeleteMessages(roomname, 0L, content_type));
3312 /* Now write the data */
3313 CtdlSubmitMsg(msg, NULL, roomname);
3314 CtdlFreeMessage(msg);
3322 void CtdlGetSysConfigBackend(long msgnum, void *userdata) {
3323 config_msgnum = msgnum;
3327 char *CtdlGetSysConfig(char *sysconfname) {
3328 char hold_rm[ROOMNAMELEN];
3331 struct CtdlMessage *msg;
3334 strcpy(hold_rm, CC->room.QRname);
3335 if (getroom(&CC->room, SYSCONFIGROOM) != 0) {
3336 getroom(&CC->room, hold_rm);
3341 /* We want the last (and probably only) config in this room */
3342 begin_critical_section(S_CONFIG);
3343 config_msgnum = (-1L);
3344 CtdlForEachMessage(MSGS_LAST, 1, sysconfname, NULL,
3345 CtdlGetSysConfigBackend, NULL);
3346 msgnum = config_msgnum;
3347 end_critical_section(S_CONFIG);
3353 msg = CtdlFetchMessage(msgnum, 1);
3355 conf = strdup(msg->cm_fields['M']);
3356 CtdlFreeMessage(msg);
3363 getroom(&CC->room, hold_rm);
3365 if (conf != NULL) do {
3366 extract_token(buf, conf, 0, '\n', sizeof buf);
3367 strcpy(conf, &conf[strlen(buf)+1]);
3368 } while ( (strlen(conf)>0) && (strlen(buf)>0) );
3373 void CtdlPutSysConfig(char *sysconfname, char *sysconfdata) {
3374 char temp[PATH_MAX];
3377 strcpy(temp, tmpnam(NULL));
3379 fp = fopen(temp, "w");
3380 if (fp == NULL) return;
3381 fprintf(fp, "%s", sysconfdata);
3384 /* this handy API function does all the work for us */
3385 CtdlWriteObject(SYSCONFIGROOM, sysconfname, temp, NULL, 0, 1, 0);
3391 * Determine whether a given Internet address belongs to the current user
3393 int CtdlIsMe(char *addr, int addr_buf_len)
3395 struct recptypes *recp;
3398 recp = validate_recipients(addr);
3399 if (recp == NULL) return(0);
3401 if (recp->num_local == 0) {
3406 for (i=0; i<recp->num_local; ++i) {
3407 extract_token(addr, recp->recp_local, i, '|', addr_buf_len);
3408 if (!strcasecmp(addr, CC->user.fullname)) {
3420 * Citadel protocol command to do the same
3422 void cmd_isme(char *argbuf) {
3425 if (CtdlAccessCheck(ac_logged_in)) return;
3426 extract_token(addr, argbuf, 0, '|', sizeof addr);
3428 if (CtdlIsMe(addr, sizeof addr)) {
3429 cprintf("%d %s\n", CIT_OK, addr);
3432 cprintf("%d Not you.\n", ERROR + ILLEGAL_VALUE);