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"
54 #define desired_section ((char *)CtdlGetUserData(SYM_DESIRED_SECTION))
55 #define ma ((struct ma_info *)CtdlGetUserData(SYM_MA_INFO))
57 extern struct config config;
62 * This really belongs in serv_network.c, but I don't know how to export
63 * symbols between modules.
65 struct FilterList *filterlist = NULL;
69 * These are the four-character field headers we use when outputting
70 * messages in Citadel format (as opposed to RFC822 format).
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,
79 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
80 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
107 * This function is self explanatory.
108 * (What can I say, I'm in a weird mood today...)
110 void remove_any_whitespace_to_the_left_or_right_of_at_symbol(char *name)
114 for (i = 0; i < strlen(name); ++i) {
115 if (name[i] == '@') {
116 while (isspace(name[i - 1]) && i > 0) {
117 strcpy(&name[i - 1], &name[i]);
120 while (isspace(name[i + 1])) {
121 strcpy(&name[i + 1], &name[i + 2]);
129 * Aliasing for network mail.
130 * (Error messages have been commented out, because this is a server.)
132 int alias(char *name)
133 { /* process alias and routing info for mail */
136 char aaa[SIZ], bbb[SIZ];
137 char *ignetcfg = NULL;
138 char *ignetmap = NULL;
145 remove_any_whitespace_to_the_left_or_right_of_at_symbol(name);
147 fp = fopen("network/mail.aliases", "r");
149 fp = fopen("/dev/null", "r");
156 while (fgets(aaa, sizeof aaa, fp) != NULL) {
157 while (isspace(name[0]))
158 strcpy(name, &name[1]);
159 aaa[strlen(aaa) - 1] = 0;
161 for (a = 0; a < strlen(aaa); ++a) {
163 strcpy(bbb, &aaa[a + 1]);
167 if (!strcasecmp(name, aaa))
172 /* Hit the Global Address Book */
173 if (CtdlDirectoryLookup(aaa, name) == 0) {
177 lprintf(CTDL_INFO, "Mail is being forwarded to %s\n", name);
179 /* Change "user @ xxx" to "user" if xxx is an alias for this host */
180 for (a=0; a<strlen(name); ++a) {
181 if (name[a] == '@') {
182 if (CtdlHostAlias(&name[a+1]) == hostalias_localhost) {
184 lprintf(CTDL_INFO, "Changed to <%s>\n", name);
189 /* determine local or remote type, see citadel.h */
190 at = haschar(name, '@');
191 if (at == 0) return(MES_LOCAL); /* no @'s - local address */
192 if (at > 1) return(MES_ERROR); /* >1 @'s - invalid address */
193 remove_any_whitespace_to_the_left_or_right_of_at_symbol(name);
195 /* figure out the delivery mode */
196 extract_token(node, name, 1, '@');
198 /* If there are one or more dots in the nodename, we assume that it
199 * is an FQDN and will attempt SMTP delivery to the Internet.
201 if (haschar(node, '.') > 0) {
202 return(MES_INTERNET);
205 /* Otherwise we look in the IGnet maps for a valid Citadel node.
206 * Try directly-connected nodes first...
208 ignetcfg = CtdlGetSysConfig(IGNETCFG);
209 for (i=0; i<num_tokens(ignetcfg, '\n'); ++i) {
210 extract_token(buf, ignetcfg, i, '\n');
211 extract_token(testnode, buf, 0, '|');
212 if (!strcasecmp(node, testnode)) {
220 * Then try nodes that are two or more hops away.
222 ignetmap = CtdlGetSysConfig(IGNETMAP);
223 for (i=0; i<num_tokens(ignetmap, '\n'); ++i) {
224 extract_token(buf, ignetmap, i, '\n');
225 extract_token(testnode, buf, 0, '|');
226 if (!strcasecmp(node, testnode)) {
233 /* If we get to this point it's an invalid node name */
242 fp = fopen("citadel.control", "r");
244 lprintf(CTDL_CRIT, "Cannot open citadel.control: %s\n",
248 fread((char *) &CitControl, sizeof(struct CitControl), 1, fp);
254 void simple_listing(long msgnum, void *userdata)
256 cprintf("%ld\n", msgnum);
261 /* Determine if a given message matches the fields in a message template.
262 * Return 0 for a successful match.
264 int CtdlMsgCmp(struct CtdlMessage *msg, struct CtdlMessage *template) {
267 /* If there aren't any fields in the template, all messages will
270 if (template == NULL) return(0);
272 /* Null messages are bogus. */
273 if (msg == NULL) return(1);
275 for (i='A'; i<='Z'; ++i) {
276 if (template->cm_fields[i] != NULL) {
277 if (msg->cm_fields[i] == NULL) {
280 if (strcasecmp(msg->cm_fields[i],
281 template->cm_fields[i])) return 1;
285 /* All compares succeeded: we have a match! */
292 * Retrieve the "seen" message list for the current room.
294 void CtdlGetSeen(char *buf, int which_set) {
297 /* Learn about the user and room in question */
298 CtdlGetRelationship(&vbuf, &CC->user, &CC->room);
300 if (which_set == ctdlsetseen_seen)
301 safestrncpy(buf, vbuf.v_seen, SIZ);
302 if (which_set == ctdlsetseen_answered)
303 safestrncpy(buf, vbuf.v_answered, SIZ);
309 * Manipulate the "seen msgs" string (or other message set strings)
311 void CtdlSetSeen(long target_msgnum, int target_setting, int which_set) {
313 struct cdbdata *cdbfr;
324 lprintf(CTDL_DEBUG, "CtdlSetSeen(%ld, %d, %d)\n",
325 target_msgnum, target_setting, which_set);
327 /* Learn about the user and room in question */
328 CtdlGetRelationship(&vbuf, &CC->user, &CC->room);
330 /* Load the message list */
331 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long));
333 msglist = malloc(cdbfr->len);
334 memcpy(msglist, cdbfr->ptr, cdbfr->len);
335 num_msgs = cdbfr->len / sizeof(long);
338 return; /* No messages at all? No further action. */
341 /* Decide which message set we're manipulating */
342 if (which_set == ctdlsetseen_seen) strcpy(vset, vbuf.v_seen);
343 if (which_set == ctdlsetseen_answered) strcpy(vset, vbuf.v_answered);
345 lprintf(CTDL_DEBUG, "before optimize: %s\n", vset);
348 for (i=0; i<num_msgs; ++i) {
351 if (msglist[i] == target_msgnum) {
352 is_seen = target_setting;
355 if (is_msg_in_mset(vset, msglist[i])) {
361 if (lo < 0L) lo = msglist[i];
364 if ( ((is_seen == 0) && (was_seen == 1))
365 || ((is_seen == 1) && (i == num_msgs-1)) ) {
368 if ( (strlen(newseen) + 20) > SIZ) {
369 strcpy(newseen, &newseen[20]);
372 tmp = strlen(newseen);
374 strcat(newseen, ",");
378 snprintf(&newseen[tmp], sizeof newseen - tmp,
382 snprintf(&newseen[tmp], sizeof newseen - tmp,
391 /* Decide which message set we're manipulating */
392 if (which_set == ctdlsetseen_seen) strcpy(vbuf.v_seen, newseen);
393 if (which_set == ctdlsetseen_answered) strcpy(vbuf.v_answered, newseen);
395 lprintf(CTDL_DEBUG, " after optimize: %s\n", newseen);
397 CtdlSetRelationship(&vbuf, &CC->user, &CC->room);
402 * API function to perform an operation for each qualifying message in the
403 * current room. (Returns the number of messages processed.)
405 int CtdlForEachMessage(int mode, long ref,
407 struct CtdlMessage *compare,
408 void (*CallBack) (long, void *),
414 struct cdbdata *cdbfr;
415 long *msglist = NULL;
417 int num_processed = 0;
420 struct CtdlMessage *msg;
423 int printed_lastold = 0;
425 /* Learn about the user and room in question */
427 getuser(&CC->user, CC->curr_user);
428 CtdlGetRelationship(&vbuf, &CC->user, &CC->room);
430 /* Load the message list */
431 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long));
433 msglist = malloc(cdbfr->len);
434 memcpy(msglist, cdbfr->ptr, cdbfr->len);
435 num_msgs = cdbfr->len / sizeof(long);
438 return 0; /* No messages at all? No further action. */
443 * Now begin the traversal.
445 if (num_msgs > 0) for (a = 0; a < num_msgs; ++a) {
447 /* If the caller is looking for a specific MIME type, filter
448 * out all messages which are not of the type requested.
450 if (content_type != NULL) if (strlen(content_type) > 0) {
452 /* This call to GetMetaData() sits inside this loop
453 * so that we only do the extra database read per msg
454 * if we need to. Doing the extra read all the time
455 * really kills the server. If we ever need to use
456 * metadata for another search criterion, we need to
457 * move the read somewhere else -- but still be smart
458 * enough to only do the read if the caller has
459 * specified something that will need it.
461 GetMetaData(&smi, msglist[a]);
463 if (strcasecmp(smi.meta_content_type, content_type)) {
469 num_msgs = sort_msglist(msglist, num_msgs);
471 /* If a template was supplied, filter out the messages which
472 * don't match. (This could induce some delays!)
475 if (compare != NULL) {
476 for (a = 0; a < num_msgs; ++a) {
477 msg = CtdlFetchMessage(msglist[a], 1);
479 if (CtdlMsgCmp(msg, compare)) {
482 CtdlFreeMessage(msg);
490 * Now iterate through the message list, according to the
491 * criteria supplied by the caller.
494 for (a = 0; a < num_msgs; ++a) {
495 thismsg = msglist[a];
496 is_seen = is_msg_in_mset(vbuf.v_seen, thismsg);
497 if (is_seen) lastold = thismsg;
502 || ((mode == MSGS_OLD) && (is_seen))
503 || ((mode == MSGS_NEW) && (!is_seen))
504 || ((mode == MSGS_LAST) && (a >= (num_msgs - ref)))
505 || ((mode == MSGS_FIRST) && (a < ref))
506 || ((mode == MSGS_GT) && (thismsg > ref))
507 || ((mode == MSGS_EQ) && (thismsg == ref))
510 if ((mode == MSGS_NEW) && (CC->user.flags & US_LASTOLD) && (lastold > 0L) && (printed_lastold == 0) && (!is_seen)) {
512 CallBack(lastold, userdata);
516 if (CallBack) CallBack(thismsg, userdata);
520 free(msglist); /* Clean up */
521 return num_processed;
527 * cmd_msgs() - get list of message #'s in this room
528 * implements the MSGS server command using CtdlForEachMessage()
530 void cmd_msgs(char *cmdbuf)
539 int with_template = 0;
540 struct CtdlMessage *template = NULL;
542 extract(which, cmdbuf, 0);
543 cm_ref = extract_int(cmdbuf, 1);
544 with_template = extract_int(cmdbuf, 2);
548 if (!strncasecmp(which, "OLD", 3))
550 else if (!strncasecmp(which, "NEW", 3))
552 else if (!strncasecmp(which, "FIRST", 5))
554 else if (!strncasecmp(which, "LAST", 4))
556 else if (!strncasecmp(which, "GT", 2))
559 if ((!(CC->logged_in)) && (!(CC->internal_pgm))) {
560 cprintf("%d not logged in\n", ERROR + NOT_LOGGED_IN);
566 cprintf("%d Send template then receive message list\n",
568 template = (struct CtdlMessage *)
569 malloc(sizeof(struct CtdlMessage));
570 memset(template, 0, sizeof(struct CtdlMessage));
571 while(client_getln(buf, sizeof buf), strcmp(buf,"000")) {
572 extract(tfield, buf, 0);
573 extract(tvalue, buf, 1);
574 for (i='A'; i<='Z'; ++i) if (msgkeys[i]!=NULL) {
575 if (!strcasecmp(tfield, msgkeys[i])) {
576 template->cm_fields[i] =
584 cprintf("%d Message list...\n", LISTING_FOLLOWS);
587 CtdlForEachMessage(mode, cm_ref,
588 NULL, template, simple_listing, NULL);
589 if (template != NULL) CtdlFreeMessage(template);
597 * help_subst() - support routine for help file viewer
599 void help_subst(char *strbuf, char *source, char *dest)
604 while (p = pattern2(strbuf, source), (p >= 0)) {
605 strcpy(workbuf, &strbuf[p + strlen(source)]);
606 strcpy(&strbuf[p], dest);
607 strcat(strbuf, workbuf);
612 void do_help_subst(char *buffer)
616 help_subst(buffer, "^nodename", config.c_nodename);
617 help_subst(buffer, "^humannode", config.c_humannode);
618 help_subst(buffer, "^fqdn", config.c_fqdn);
619 help_subst(buffer, "^username", CC->user.fullname);
620 snprintf(buf2, sizeof buf2, "%ld", CC->user.usernum);
621 help_subst(buffer, "^usernum", buf2);
622 help_subst(buffer, "^sysadm", config.c_sysadm);
623 help_subst(buffer, "^variantname", CITADEL);
624 snprintf(buf2, sizeof buf2, "%d", config.c_maxsessions);
625 help_subst(buffer, "^maxsessions", buf2);
626 help_subst(buffer, "^bbsdir", BBSDIR);
632 * memfmout() - Citadel text formatter and paginator.
633 * Although the original purpose of this routine was to format
634 * text to the reader's screen width, all we're really using it
635 * for here is to format text out to 80 columns before sending it
636 * to the client. The client software may reformat it again.
639 int width, /* screen width to use */
640 char *mptr, /* where are we going to get our text from? */
641 char subst, /* nonzero if we should do substitutions */
642 char *nl) /* string to terminate lines with */
654 c = 1; /* c is the current pos */
658 while (ch = *mptr, ((ch != 0) && (strlen(buffer) < 126))) {
660 buffer[strlen(buffer) + 1] = 0;
661 buffer[strlen(buffer)] = ch;
664 if (buffer[0] == '^')
665 do_help_subst(buffer);
667 buffer[strlen(buffer) + 1] = 0;
669 strcpy(buffer, &buffer[1]);
677 if (((ch == 13) || (ch == 10)) && (old != 13) && (old != 10))
679 if (((old == 13) || (old == 10)) && (isspace(real))) {
687 if (((strlen(aaa) + c) > (width - 5)) && (strlen(aaa) > (width - 5))) {
688 cprintf("%s%s", nl, aaa);
697 if ((strlen(aaa) + c) > (width - 5)) {
706 if ((ch == 13) || (ch == 10)) {
707 cprintf("%s%s", aaa, nl);
714 cprintf("%s%s", aaa, nl);
720 * Callback function for mime parser that simply lists the part
722 void list_this_part(char *name, char *filename, char *partnum, char *disp,
723 void *content, char *cbtype, size_t length, char *encoding,
727 cprintf("part=%s|%s|%s|%s|%s|%ld\n",
728 name, filename, partnum, disp, cbtype, (long)length);
732 * Callback function for multipart prefix
734 void list_this_pref(char *name, char *filename, char *partnum, char *disp,
735 void *content, char *cbtype, size_t length, char *encoding,
738 cprintf("pref=%s|%s\n", partnum, cbtype);
742 * Callback function for multipart sufffix
744 void list_this_suff(char *name, char *filename, char *partnum, char *disp,
745 void *content, char *cbtype, size_t length, char *encoding,
748 cprintf("suff=%s|%s\n", partnum, cbtype);
753 * Callback function for mime parser that opens a section for downloading
755 void mime_download(char *name, char *filename, char *partnum, char *disp,
756 void *content, char *cbtype, size_t length, char *encoding,
760 /* Silently go away if there's already a download open... */
761 if (CC->download_fp != NULL)
764 /* ...or if this is not the desired section */
765 if (strcasecmp(desired_section, partnum))
768 CC->download_fp = tmpfile();
769 if (CC->download_fp == NULL)
772 fwrite(content, length, 1, CC->download_fp);
773 fflush(CC->download_fp);
774 rewind(CC->download_fp);
776 OpenCmdResult(filename, cbtype);
782 * Load a message from disk into memory.
783 * This is used by CtdlOutputMsg() and other fetch functions.
785 * NOTE: Caller is responsible for freeing the returned CtdlMessage struct
786 * using the CtdlMessageFree() function.
788 struct CtdlMessage *CtdlFetchMessage(long msgnum, int with_body)
790 struct cdbdata *dmsgtext;
791 struct CtdlMessage *ret = NULL;
794 cit_uint8_t field_header;
797 lprintf(CTDL_DEBUG, "CtdlFetchMessage(%ld, %d)\n", msgnum, with_body);
799 dmsgtext = cdb_fetch(CDB_MSGMAIN, &msgnum, sizeof(long));
800 if (dmsgtext == NULL) {
803 mptr = dmsgtext->ptr;
805 /* Parse the three bytes that begin EVERY message on disk.
806 * The first is always 0xFF, the on-disk magic number.
807 * The second is the anonymous/public type byte.
808 * The third is the format type byte (vari, fixed, or MIME).
813 "Message %ld appears to be corrupted.\n",
818 ret = (struct CtdlMessage *) malloc(sizeof(struct CtdlMessage));
819 memset(ret, 0, sizeof(struct CtdlMessage));
821 ret->cm_magic = CTDLMESSAGE_MAGIC;
822 ret->cm_anon_type = *mptr++; /* Anon type byte */
823 ret->cm_format_type = *mptr++; /* Format type byte */
826 * The rest is zero or more arbitrary fields. Load them in.
827 * We're done when we encounter either a zero-length field or
828 * have just processed the 'M' (message text) field.
831 field_length = strlen(mptr);
832 if (field_length == 0)
834 field_header = *mptr++;
835 ret->cm_fields[field_header] = malloc(field_length + 1);
836 strcpy(ret->cm_fields[field_header], mptr);
838 while (*mptr++ != 0); /* advance to next field */
840 } while ((field_length > 0) && (field_header != 'M'));
844 /* Always make sure there's something in the msg text field. If
845 * it's NULL, the message text is most likely stored separately,
846 * so go ahead and fetch that. Failing that, just set a dummy
847 * body so other code doesn't barf.
849 if ( (ret->cm_fields['M'] == NULL) && (with_body) ) {
850 dmsgtext = cdb_fetch(CDB_BIGMSGS, &msgnum, sizeof(long));
851 if (dmsgtext != NULL) {
852 ret->cm_fields['M'] = strdup(dmsgtext->ptr);
856 if (ret->cm_fields['M'] == NULL) {
857 ret->cm_fields['M'] = strdup("<no text>\n");
860 /* Perform "before read" hooks (aborting if any return nonzero) */
861 if (PerformMessageHooks(ret, EVT_BEFOREREAD) > 0) {
862 CtdlFreeMessage(ret);
871 * Returns 1 if the supplied pointer points to a valid Citadel message.
872 * If the pointer is NULL or the magic number check fails, returns 0.
874 int is_valid_message(struct CtdlMessage *msg) {
877 if ((msg->cm_magic) != CTDLMESSAGE_MAGIC) {
878 lprintf(CTDL_WARNING, "is_valid_message() -- self-check failed\n");
886 * 'Destructor' for struct CtdlMessage
888 void CtdlFreeMessage(struct CtdlMessage *msg)
892 if (is_valid_message(msg) == 0) return;
894 for (i = 0; i < 256; ++i)
895 if (msg->cm_fields[i] != NULL) {
896 free(msg->cm_fields[i]);
899 msg->cm_magic = 0; /* just in case */
905 * Pre callback function for multipart/alternative
907 * NOTE: this differs from the standard behavior for a reason. Normally when
908 * displaying multipart/alternative you want to show the _last_ usable
909 * format in the message. Here we show the _first_ one, because it's
910 * usually text/plain. Since this set of functions is designed for text
911 * output to non-MIME-aware clients, this is the desired behavior.
914 void fixed_output_pre(char *name, char *filename, char *partnum, char *disp,
915 void *content, char *cbtype, size_t length, char *encoding,
918 lprintf(CTDL_DEBUG, "fixed_output_pre() type=<%s>\n", cbtype);
919 if (!strcasecmp(cbtype, "multipart/alternative")) {
927 * Post callback function for multipart/alternative
929 void fixed_output_post(char *name, char *filename, char *partnum, char *disp,
930 void *content, char *cbtype, size_t length, char *encoding,
933 lprintf(CTDL_DEBUG, "fixed_output_post() type=<%s>\n", cbtype);
934 if (!strcasecmp(cbtype, "multipart/alternative")) {
942 * Inline callback function for mime parser that wants to display text
944 void fixed_output(char *name, char *filename, char *partnum, char *disp,
945 void *content, char *cbtype, size_t length, char *encoding,
952 lprintf(CTDL_DEBUG, "fixed_output() type=<%s>\n", cbtype);
955 * If we're in the middle of a multipart/alternative scope and
956 * we've already printed another section, skip this one.
958 if ( (ma->is_ma == 1) && (ma->did_print == 1) ) {
959 lprintf(CTDL_DEBUG, "Skipping part %s (%s)\n", partnum, cbtype);
964 if ( (!strcasecmp(cbtype, "text/plain"))
965 || (strlen(cbtype)==0) ) {
968 client_write(wptr, length);
969 if (wptr[length-1] != '\n') {
974 else if (!strcasecmp(cbtype, "text/html")) {
975 ptr = html_to_ascii(content, 80, 0);
977 client_write(ptr, wlen);
978 if (ptr[wlen-1] != '\n') {
983 else if (strncasecmp(cbtype, "multipart/", 10)) {
984 cprintf("Part %s: %s (%s) (%ld bytes)\r\n",
985 partnum, filename, cbtype, (long)length);
990 * The client is elegant and sophisticated and wants to be choosy about
991 * MIME content types, so figure out which multipart/alternative part
992 * we're going to send.
994 void choose_preferred(char *name, char *filename, char *partnum, char *disp,
995 void *content, char *cbtype, size_t length, char *encoding,
1001 if (ma->is_ma > 0) {
1002 for (i=0; i<num_tokens(CC->preferred_formats, '|'); ++i) {
1003 extract(buf, CC->preferred_formats, i);
1004 if (!strcasecmp(buf, cbtype)) {
1005 strcpy(ma->chosen_part, partnum);
1012 * Now that we've chosen our preferred part, output it.
1014 void output_preferred(char *name, char *filename, char *partnum, char *disp,
1015 void *content, char *cbtype, size_t length, char *encoding,
1020 int add_newline = 0;
1023 /* This is not the MIME part you're looking for... */
1024 if (strcasecmp(partnum, ma->chosen_part)) return;
1026 /* If the content-type of this part is in our preferred formats
1027 * list, we can simply output it verbatim.
1029 for (i=0; i<num_tokens(CC->preferred_formats, '|'); ++i) {
1030 extract(buf, CC->preferred_formats, i);
1031 if (!strcasecmp(buf, cbtype)) {
1032 /* Yeah! Go! W00t!! */
1034 text_content = (char *)content;
1035 if (text_content[length-1] != '\n') {
1039 cprintf("Content-type: %s\n", cbtype);
1040 cprintf("Content-length: %d\n",
1041 (int)(length + add_newline) );
1042 if (strlen(encoding) > 0) {
1043 cprintf("Content-transfer-encoding: %s\n", encoding);
1046 cprintf("Content-transfer-encoding: 7bit\n");
1049 client_write(content, length);
1050 if (add_newline) cprintf("\n");
1055 /* No translations required or possible: output as text/plain */
1056 cprintf("Content-type: text/plain\n\n");
1057 fixed_output(name, filename, partnum, disp, content, cbtype,
1058 length, encoding, cbuserdata);
1063 * Get a message off disk. (returns om_* values found in msgbase.h)
1066 int CtdlOutputMsg(long msg_num, /* message number (local) to fetch */
1067 int mode, /* how would you like that message? */
1068 int headers_only, /* eschew the message body? */
1069 int do_proto, /* do Citadel protocol responses? */
1070 int crlf /* Use CRLF newlines instead of LF? */
1072 struct CtdlMessage *TheMessage = NULL;
1073 int retcode = om_no_such_msg;
1075 lprintf(CTDL_DEBUG, "CtdlOutputMsg() msgnum=%ld, mode=%d\n",
1078 if ((!(CC->logged_in)) && (!(CC->internal_pgm))) {
1079 if (do_proto) cprintf("%d Not logged in.\n",
1080 ERROR + NOT_LOGGED_IN);
1081 return(om_not_logged_in);
1084 /* FIXME: check message id against msglist for this room */
1087 * Fetch the message from disk. If we're in sooper-fast headers
1088 * only mode, request that we don't even bother loading the body
1091 if (headers_only == HEADERS_FAST) {
1092 TheMessage = CtdlFetchMessage(msg_num, 0);
1095 TheMessage = CtdlFetchMessage(msg_num, 1);
1098 if (TheMessage == NULL) {
1099 if (do_proto) cprintf("%d Can't locate msg %ld on disk\n",
1100 ERROR + MESSAGE_NOT_FOUND, msg_num);
1101 return(om_no_such_msg);
1104 retcode = CtdlOutputPreLoadedMsg(
1105 TheMessage, msg_num, mode,
1106 headers_only, do_proto, crlf);
1108 CtdlFreeMessage(TheMessage);
1115 * Get a message off disk. (returns om_* values found in msgbase.h)
1118 int CtdlOutputPreLoadedMsg(
1119 struct CtdlMessage *TheMessage,
1121 int mode, /* how would you like that message? */
1122 int headers_only, /* eschew the message body? */
1123 int do_proto, /* do Citadel protocol responses? */
1124 int crlf /* Use CRLF newlines instead of LF? */
1130 char display_name[SIZ];
1132 char *nl; /* newline string */
1134 int subject_found = 0;
1136 /* buffers needed for RFC822 translation */
1143 char datestamp[SIZ];
1146 lprintf(CTDL_DEBUG, "CtdlOutputPreLoadedMsg(TheMessage=%s, %ld, %d, %d, %d, %d\n",
1147 ((TheMessage == NULL) ? "NULL" : "not null"),
1149 mode, headers_only, do_proto, crlf);
1151 snprintf(mid, sizeof mid, "%ld", msg_num);
1152 nl = (crlf ? "\r\n" : "\n");
1154 if (!is_valid_message(TheMessage)) {
1156 "ERROR: invalid preloaded message for output\n");
1157 return(om_no_such_msg);
1160 /* Are we downloading a MIME component? */
1161 if (mode == MT_DOWNLOAD) {
1162 if (TheMessage->cm_format_type != FMT_RFC822) {
1164 cprintf("%d This is not a MIME message.\n",
1165 ERROR + ILLEGAL_VALUE);
1166 } else if (CC->download_fp != NULL) {
1167 if (do_proto) cprintf(
1168 "%d You already have a download open.\n",
1169 ERROR + RESOURCE_BUSY);
1171 /* Parse the message text component */
1172 mptr = TheMessage->cm_fields['M'];
1173 mime_parser(mptr, NULL,
1174 *mime_download, NULL, NULL,
1176 /* If there's no file open by this time, the requested
1177 * section wasn't found, so print an error
1179 if (CC->download_fp == NULL) {
1180 if (do_proto) cprintf(
1181 "%d Section %s not found.\n",
1182 ERROR + FILE_NOT_FOUND,
1186 return((CC->download_fp != NULL) ? om_ok : om_mime_error);
1189 /* now for the user-mode message reading loops */
1190 if (do_proto) cprintf("%d Message %ld:\n", LISTING_FOLLOWS, msg_num);
1192 /* Does the caller want to skip the headers? */
1193 if (headers_only == HEADERS_NONE) goto START_TEXT;
1195 /* Tell the client which format type we're using. */
1196 if ( (mode == MT_CITADEL) && (do_proto) ) {
1197 cprintf("type=%d\n", TheMessage->cm_format_type);
1200 /* nhdr=yes means that we're only displaying headers, no body */
1201 if ( (TheMessage->cm_anon_type == MES_ANONONLY)
1202 && (mode == MT_CITADEL)
1205 cprintf("nhdr=yes\n");
1208 /* begin header processing loop for Citadel message format */
1210 if ((mode == MT_CITADEL) || (mode == MT_MIME)) {
1212 strcpy(display_name, "<unknown>");
1213 if (TheMessage->cm_fields['A']) {
1214 strcpy(buf, TheMessage->cm_fields['A']);
1215 if (TheMessage->cm_anon_type == MES_ANONONLY) {
1216 strcpy(display_name, "****");
1218 else if (TheMessage->cm_anon_type == MES_ANONOPT) {
1219 strcpy(display_name, "anonymous");
1222 strcpy(display_name, buf);
1224 if ((is_room_aide())
1225 && ((TheMessage->cm_anon_type == MES_ANONONLY)
1226 || (TheMessage->cm_anon_type == MES_ANONOPT))) {
1227 size_t tmp = strlen(display_name);
1228 snprintf(&display_name[tmp],
1229 sizeof display_name - tmp,
1234 /* Don't show Internet address for users on the
1235 * local Citadel network.
1238 if (TheMessage->cm_fields['N'] != NULL)
1239 if (strlen(TheMessage->cm_fields['N']) > 0)
1240 if (haschar(TheMessage->cm_fields['N'], '.') == 0) {
1244 /* Now spew the header fields in the order we like them. */
1245 strcpy(allkeys, FORDER);
1246 for (i=0; i<strlen(allkeys); ++i) {
1247 k = (int) allkeys[i];
1249 if ( (TheMessage->cm_fields[k] != NULL)
1250 && (msgkeys[k] != NULL) ) {
1252 if (do_proto) cprintf("%s=%s\n",
1256 else if ((k == 'F') && (suppress_f)) {
1259 /* Masquerade display name if needed */
1261 if (do_proto) cprintf("%s=%s\n",
1263 TheMessage->cm_fields[k]
1272 /* begin header processing loop for RFC822 transfer format */
1277 strcpy(snode, NODENAME);
1278 strcpy(lnode, HUMANNODE);
1279 if (mode == MT_RFC822) {
1280 for (i = 0; i < 256; ++i) {
1281 if (TheMessage->cm_fields[i]) {
1282 mptr = TheMessage->cm_fields[i];
1285 safestrncpy(luser, mptr, sizeof luser);
1286 safestrncpy(suser, mptr, sizeof suser);
1288 else if (i == 'U') {
1289 cprintf("Subject: %s%s", mptr, nl);
1293 safestrncpy(mid, mptr, sizeof mid);
1295 safestrncpy(lnode, mptr, sizeof lnode);
1297 safestrncpy(fuser, mptr, sizeof fuser);
1299 cprintf("X-Citadel-Room: %s%s",
1302 safestrncpy(snode, mptr, sizeof snode);
1304 cprintf("To: %s%s", mptr, nl);
1305 else if (i == 'T') {
1306 datestring(datestamp, sizeof datestamp,
1307 atol(mptr), DATESTRING_RFC822);
1308 cprintf("Date: %s%s", datestamp, nl);
1312 if (subject_found == 0) {
1313 cprintf("Subject: (no subject)%s", nl);
1317 for (i=0; i<strlen(suser); ++i) {
1318 suser[i] = tolower(suser[i]);
1319 if (!isalnum(suser[i])) suser[i]='_';
1322 if (mode == MT_RFC822) {
1323 if (!strcasecmp(snode, NODENAME)) {
1324 safestrncpy(snode, FQDN, sizeof snode);
1327 /* Construct a fun message id */
1328 cprintf("Message-ID: <%s", mid);
1329 if (strchr(mid, '@')==NULL) {
1330 cprintf("@%s", snode);
1334 if (!is_room_aide() && (TheMessage->cm_anon_type == MES_ANONONLY)) {
1335 cprintf("From: x@x.org (----)%s", nl);
1337 else if (!is_room_aide() && (TheMessage->cm_anon_type == MES_ANONOPT)) {
1338 cprintf("From: x@x.org (anonymous)%s", nl);
1340 else if (strlen(fuser) > 0) {
1341 cprintf("From: %s (%s)%s", fuser, luser, nl);
1344 cprintf("From: %s@%s (%s)%s", suser, snode, luser, nl);
1347 cprintf("Organization: %s%s", lnode, nl);
1349 /* Blank line signifying RFC822 end-of-headers */
1350 if (TheMessage->cm_format_type != FMT_RFC822) {
1355 /* end header processing loop ... at this point, we're in the text */
1357 if (headers_only == HEADERS_FAST) goto DONE;
1358 mptr = TheMessage->cm_fields['M'];
1360 /* Tell the client about the MIME parts in this message */
1361 if (TheMessage->cm_format_type == FMT_RFC822) {
1362 if ( (mode == MT_CITADEL) || (mode == MT_MIME) ) {
1363 mime_parser(mptr, NULL,
1369 else if (mode == MT_RFC822) { /* unparsed RFC822 dump */
1370 /* FIXME ... we have to put some code in here to avoid
1371 * printing duplicate header information when both
1372 * Citadel and RFC822 headers exist. Preference should
1373 * probably be given to the RFC822 headers.
1375 int done_rfc822_hdrs = 0;
1376 while (ch=*(mptr++), ch!=0) {
1381 if (!done_rfc822_hdrs) {
1382 if (headers_only != HEADERS_NONE) {
1387 if (headers_only != HEADERS_ONLY) {
1391 if ((*(mptr) == 13) || (*(mptr) == 10)) {
1392 done_rfc822_hdrs = 1;
1396 if (done_rfc822_hdrs) {
1397 if (headers_only != HEADERS_NONE) {
1402 if (headers_only != HEADERS_ONLY) {
1406 if ((*mptr == 13) || (*mptr == 10)) {
1407 done_rfc822_hdrs = 1;
1415 if (headers_only == HEADERS_ONLY) {
1419 /* signify start of msg text */
1420 if ( (mode == MT_CITADEL) || (mode == MT_MIME) ) {
1421 if (do_proto) cprintf("text\n");
1424 /* If the format type on disk is 1 (fixed-format), then we want
1425 * everything to be output completely literally ... regardless of
1426 * what message transfer format is in use.
1428 if (TheMessage->cm_format_type == FMT_FIXED) {
1429 if (mode == MT_MIME) {
1430 cprintf("Content-type: text/plain\n\n");
1433 while (ch = *mptr++, ch > 0) {
1436 if ((ch == 10) || (strlen(buf) > 250)) {
1437 cprintf("%s%s", buf, nl);
1440 buf[strlen(buf) + 1] = 0;
1441 buf[strlen(buf)] = ch;
1444 if (strlen(buf) > 0)
1445 cprintf("%s%s", buf, nl);
1448 /* If the message on disk is format 0 (Citadel vari-format), we
1449 * output using the formatter at 80 columns. This is the final output
1450 * form if the transfer format is RFC822, but if the transfer format
1451 * is Citadel proprietary, it'll still work, because the indentation
1452 * for new paragraphs is correct and the client will reformat the
1453 * message to the reader's screen width.
1455 if (TheMessage->cm_format_type == FMT_CITADEL) {
1456 if (mode == MT_MIME) {
1457 cprintf("Content-type: text/x-citadel-variformat\n\n");
1459 memfmout(80, mptr, 0, nl);
1462 /* If the message on disk is format 4 (MIME), we've gotta hand it
1463 * off to the MIME parser. The client has already been told that
1464 * this message is format 1 (fixed format), so the callback function
1465 * we use will display those parts as-is.
1467 if (TheMessage->cm_format_type == FMT_RFC822) {
1468 CtdlAllocUserData(SYM_MA_INFO, sizeof(struct ma_info));
1469 memset(ma, 0, sizeof(struct ma_info));
1471 if (mode == MT_MIME) {
1472 strcpy(ma->chosen_part, "1");
1473 mime_parser(mptr, NULL,
1474 *choose_preferred, *fixed_output_pre,
1475 *fixed_output_post, NULL, 0);
1476 mime_parser(mptr, NULL,
1477 *output_preferred, NULL, NULL, NULL, 0);
1480 mime_parser(mptr, NULL,
1481 *fixed_output, *fixed_output_pre,
1482 *fixed_output_post, NULL, 0);
1486 DONE: /* now we're done */
1487 if (do_proto) cprintf("000\n");
1494 * display a message (mode 0 - Citadel proprietary)
1496 void cmd_msg0(char *cmdbuf)
1499 int headers_only = HEADERS_ALL;
1501 msgid = extract_long(cmdbuf, 0);
1502 headers_only = extract_int(cmdbuf, 1);
1504 CtdlOutputMsg(msgid, MT_CITADEL, headers_only, 1, 0);
1510 * display a message (mode 2 - RFC822)
1512 void cmd_msg2(char *cmdbuf)
1515 int headers_only = HEADERS_ALL;
1517 msgid = extract_long(cmdbuf, 0);
1518 headers_only = extract_int(cmdbuf, 1);
1520 CtdlOutputMsg(msgid, MT_RFC822, headers_only, 1, 1);
1526 * display a message (mode 3 - IGnet raw format - internal programs only)
1528 void cmd_msg3(char *cmdbuf)
1531 struct CtdlMessage *msg;
1534 if (CC->internal_pgm == 0) {
1535 cprintf("%d This command is for internal programs only.\n",
1536 ERROR + HIGHER_ACCESS_REQUIRED);
1540 msgnum = extract_long(cmdbuf, 0);
1541 msg = CtdlFetchMessage(msgnum, 1);
1543 cprintf("%d Message %ld not found.\n",
1544 ERROR + MESSAGE_NOT_FOUND, msgnum);
1548 serialize_message(&smr, msg);
1549 CtdlFreeMessage(msg);
1552 cprintf("%d Unable to serialize message\n",
1553 ERROR + INTERNAL_ERROR);
1557 cprintf("%d %ld\n", BINARY_FOLLOWS, (long)smr.len);
1558 client_write((char *)smr.ser, (int)smr.len);
1565 * Display a message using MIME content types
1567 void cmd_msg4(char *cmdbuf)
1571 msgid = extract_long(cmdbuf, 0);
1572 CtdlOutputMsg(msgid, MT_MIME, 0, 1, 0);
1578 * Client tells us its preferred message format(s)
1580 void cmd_msgp(char *cmdbuf)
1582 safestrncpy(CC->preferred_formats, cmdbuf,
1583 sizeof(CC->preferred_formats));
1584 cprintf("%d ok\n", CIT_OK);
1589 * Open a component of a MIME message as a download file
1591 void cmd_opna(char *cmdbuf)
1595 CtdlAllocUserData(SYM_DESIRED_SECTION, SIZ);
1597 msgid = extract_long(cmdbuf, 0);
1598 extract(desired_section, cmdbuf, 1);
1600 CtdlOutputMsg(msgid, MT_DOWNLOAD, 0, 1, 1);
1605 * Save a message pointer into a specified room
1606 * (Returns 0 for success, nonzero for failure)
1607 * roomname may be NULL to use the current room
1609 int CtdlSaveMsgPointerInRoom(char *roomname, long msgid, int flags) {
1611 char hold_rm[ROOMNAMELEN];
1612 struct cdbdata *cdbfr;
1615 long highest_msg = 0L;
1616 struct CtdlMessage *msg = NULL;
1618 lprintf(CTDL_DEBUG, "CtdlSaveMsgPointerInRoom(%s, %ld, %d)\n",
1619 roomname, msgid, flags);
1621 strcpy(hold_rm, CC->room.QRname);
1623 /* We may need to check to see if this message is real */
1624 if ( (flags & SM_VERIFY_GOODNESS)
1625 || (flags & SM_DO_REPL_CHECK)
1627 msg = CtdlFetchMessage(msgid, 1);
1628 if (msg == NULL) return(ERROR + ILLEGAL_VALUE);
1631 /* Perform replication checks if necessary */
1632 if ( (flags & SM_DO_REPL_CHECK) && (msg != NULL) ) {
1634 if (getroom(&CC->room,
1635 ((roomname != NULL) ? roomname : CC->room.QRname) )
1637 lprintf(CTDL_ERR, "No such room <%s>\n", roomname);
1638 if (msg != NULL) CtdlFreeMessage(msg);
1639 return(ERROR + ROOM_NOT_FOUND);
1642 if (ReplicationChecks(msg) != 0) {
1643 getroom(&CC->room, hold_rm);
1644 if (msg != NULL) CtdlFreeMessage(msg);
1646 "Did replication, and newer exists\n");
1651 /* Now the regular stuff */
1652 if (lgetroom(&CC->room,
1653 ((roomname != NULL) ? roomname : CC->room.QRname) )
1655 lprintf(CTDL_ERR, "No such room <%s>\n", roomname);
1656 if (msg != NULL) CtdlFreeMessage(msg);
1657 return(ERROR + ROOM_NOT_FOUND);
1660 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long));
1661 if (cdbfr == NULL) {
1665 msglist = malloc(cdbfr->len);
1666 if (msglist == NULL)
1667 lprintf(CTDL_ALERT, "ERROR malloc msglist!\n");
1668 num_msgs = cdbfr->len / sizeof(long);
1669 memcpy(msglist, cdbfr->ptr, cdbfr->len);
1674 /* Make sure the message doesn't already exist in this room. It
1675 * is absolutely taboo to have more than one reference to the same
1676 * message in a room.
1678 if (num_msgs > 0) for (i=0; i<num_msgs; ++i) {
1679 if (msglist[i] == msgid) {
1680 lputroom(&CC->room); /* unlock the room */
1681 getroom(&CC->room, hold_rm);
1682 if (msg != NULL) CtdlFreeMessage(msg);
1684 return(ERROR + ALREADY_EXISTS);
1688 /* Now add the new message */
1690 msglist = realloc(msglist, (num_msgs * sizeof(long)));
1692 if (msglist == NULL) {
1693 lprintf(CTDL_ALERT, "ERROR: can't realloc message list!\n");
1695 msglist[num_msgs - 1] = msgid;
1697 /* Sort the message list, so all the msgid's are in order */
1698 num_msgs = sort_msglist(msglist, num_msgs);
1700 /* Determine the highest message number */
1701 highest_msg = msglist[num_msgs - 1];
1703 /* Write it back to disk. */
1704 cdb_store(CDB_MSGLISTS, &CC->room.QRnumber, (int)sizeof(long),
1705 msglist, (int)(num_msgs * sizeof(long)));
1707 /* Free up the memory we used. */
1710 /* Update the highest-message pointer and unlock the room. */
1711 CC->room.QRhighest = highest_msg;
1712 lputroom(&CC->room);
1713 getroom(&CC->room, hold_rm);
1715 /* Bump the reference count for this message. */
1716 if ((flags & SM_DONT_BUMP_REF)==0) {
1717 AdjRefCount(msgid, +1);
1720 /* Return success. */
1721 if (msg != NULL) CtdlFreeMessage(msg);
1728 * Message base operation to save a new message to the message store
1729 * (returns new message number)
1731 * This is the back end for CtdlSubmitMsg() and should not be directly
1732 * called by server-side modules.
1735 long send_message(struct CtdlMessage *msg) {
1743 /* Get a new message number */
1744 newmsgid = get_new_message_number();
1745 snprintf(msgidbuf, sizeof msgidbuf, "%ld@%s", newmsgid, config.c_fqdn);
1747 /* Generate an ID if we don't have one already */
1748 if (msg->cm_fields['I']==NULL) {
1749 msg->cm_fields['I'] = strdup(msgidbuf);
1752 /* If the message is big, set its body aside for storage elsewhere */
1753 if (msg->cm_fields['M'] != NULL) {
1754 if (strlen(msg->cm_fields['M']) > BIGMSG) {
1756 holdM = msg->cm_fields['M'];
1757 msg->cm_fields['M'] = NULL;
1761 /* Serialize our data structure for storage in the database */
1762 serialize_message(&smr, msg);
1765 msg->cm_fields['M'] = holdM;
1769 cprintf("%d Unable to serialize message\n",
1770 ERROR + INTERNAL_ERROR);
1774 /* Write our little bundle of joy into the message base */
1775 if (cdb_store(CDB_MSGMAIN, &newmsgid, (int)sizeof(long),
1776 smr.ser, smr.len) < 0) {
1777 lprintf(CTDL_ERR, "Can't store message\n");
1781 cdb_store(CDB_BIGMSGS,
1791 /* Free the memory we used for the serialized message */
1794 /* Return the *local* message ID to the caller
1795 * (even if we're storing an incoming network message)
1803 * Serialize a struct CtdlMessage into the format used on disk and network.
1805 * This function loads up a "struct ser_ret" (defined in server.h) which
1806 * contains the length of the serialized message and a pointer to the
1807 * serialized message in memory. THE LATTER MUST BE FREED BY THE CALLER.
1809 void serialize_message(struct ser_ret *ret, /* return values */
1810 struct CtdlMessage *msg) /* unserialized msg */
1814 static char *forder = FORDER;
1816 if (is_valid_message(msg) == 0) return; /* self check */
1819 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL)
1820 ret->len = ret->len +
1821 strlen(msg->cm_fields[(int)forder[i]]) + 2;
1823 lprintf(CTDL_DEBUG, "serialize_message() calling malloc(%ld)\n", (long)ret->len);
1824 ret->ser = malloc(ret->len);
1825 if (ret->ser == NULL) {
1831 ret->ser[1] = msg->cm_anon_type;
1832 ret->ser[2] = msg->cm_format_type;
1835 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL) {
1836 ret->ser[wlen++] = (char)forder[i];
1837 strcpy((char *)&ret->ser[wlen], msg->cm_fields[(int)forder[i]]);
1838 wlen = wlen + strlen(msg->cm_fields[(int)forder[i]]) + 1;
1840 if (ret->len != wlen) lprintf(CTDL_ERR, "ERROR: len=%ld wlen=%ld\n",
1841 (long)ret->len, (long)wlen);
1849 * Back end for the ReplicationChecks() function
1851 void check_repl(long msgnum, void *userdata) {
1852 lprintf(CTDL_DEBUG, "check_repl() replacing message %ld\n", msgnum);
1853 CtdlDeleteMessages(CC->room.QRname, msgnum, "");
1858 * Check to see if any messages already exist which carry the same Exclusive ID
1859 * as this one. If any are found, delete them.
1862 int ReplicationChecks(struct CtdlMessage *msg) {
1863 struct CtdlMessage *template;
1866 /* No exclusive id? Don't do anything. */
1867 if (msg->cm_fields['E'] == NULL) return 0;
1868 if (strlen(msg->cm_fields['E']) == 0) return 0;
1869 lprintf(CTDL_DEBUG, "Exclusive ID: <%s>\n", msg->cm_fields['E']);
1871 template = (struct CtdlMessage *) malloc(sizeof(struct CtdlMessage));
1872 memset(template, 0, sizeof(struct CtdlMessage));
1873 template->cm_fields['E'] = strdup(msg->cm_fields['E']);
1875 CtdlForEachMessage(MSGS_ALL, 0L, NULL, template, check_repl, NULL);
1877 CtdlFreeMessage(template);
1878 lprintf(CTDL_DEBUG, "ReplicationChecks() returning %d\n", abort_this);
1886 * Save a message to disk and submit it into the delivery system.
1888 long CtdlSubmitMsg(struct CtdlMessage *msg, /* message to save */
1889 struct recptypes *recps, /* recipients (if mail) */
1890 char *force /* force a particular room? */
1893 char hold_rm[ROOMNAMELEN];
1894 char actual_rm[ROOMNAMELEN];
1895 char force_room[ROOMNAMELEN];
1896 char content_type[SIZ]; /* We have to learn this */
1897 char recipient[SIZ];
1900 struct ctdluser userbuf;
1902 struct MetaData smi;
1903 FILE *network_fp = NULL;
1904 static int seqnum = 1;
1905 struct CtdlMessage *imsg = NULL;
1908 char *hold_R, *hold_D;
1910 lprintf(CTDL_DEBUG, "CtdlSubmitMsg() called\n");
1911 if (is_valid_message(msg) == 0) return(-1); /* self check */
1913 /* If this message has no timestamp, we take the liberty of
1914 * giving it one, right now.
1916 if (msg->cm_fields['T'] == NULL) {
1917 lprintf(CTDL_DEBUG, "Generating timestamp\n");
1918 snprintf(aaa, sizeof aaa, "%ld", (long)time(NULL));
1919 msg->cm_fields['T'] = strdup(aaa);
1922 /* If this message has no path, we generate one.
1924 if (msg->cm_fields['P'] == NULL) {
1925 lprintf(CTDL_DEBUG, "Generating path\n");
1926 if (msg->cm_fields['A'] != NULL) {
1927 msg->cm_fields['P'] = strdup(msg->cm_fields['A']);
1928 for (a=0; a<strlen(msg->cm_fields['P']); ++a) {
1929 if (isspace(msg->cm_fields['P'][a])) {
1930 msg->cm_fields['P'][a] = ' ';
1935 msg->cm_fields['P'] = strdup("unknown");
1939 if (force == NULL) {
1940 strcpy(force_room, "");
1943 strcpy(force_room, force);
1946 /* Learn about what's inside, because it's what's inside that counts */
1947 lprintf(CTDL_DEBUG, "Learning what's inside\n");
1948 if (msg->cm_fields['M'] == NULL) {
1949 lprintf(CTDL_ERR, "ERROR: attempt to save message with NULL body\n");
1953 switch (msg->cm_format_type) {
1955 strcpy(content_type, "text/x-citadel-variformat");
1958 strcpy(content_type, "text/plain");
1961 strcpy(content_type, "text/plain");
1962 mptr = bmstrstr(msg->cm_fields['M'], "Content-type: ", strncasecmp);
1964 strcpy(content_type, &mptr[14]);
1965 for (a = 0; a < strlen(content_type); ++a) {
1966 if ((content_type[a] == ';')
1967 || (content_type[a] == ' ')
1968 || (content_type[a] == 13)
1969 || (content_type[a] == 10)) {
1970 content_type[a] = 0;
1976 /* Goto the correct room */
1977 lprintf(CTDL_DEBUG, "Selected room %s\n", (recps) ? CC->room.QRname : SENTITEMS);
1978 strcpy(hold_rm, CC->room.QRname);
1979 strcpy(actual_rm, CC->room.QRname);
1980 if (recps != NULL) {
1981 strcpy(actual_rm, SENTITEMS);
1984 /* If the user is a twit, move to the twit room for posting */
1985 lprintf(CTDL_DEBUG, "Handling twit stuff: %s\n",
1986 (CC->user.axlevel == 2) ? config.c_twitroom : "OK");
1988 if (CC->user.axlevel == 2) {
1989 strcpy(hold_rm, actual_rm);
1990 strcpy(actual_rm, config.c_twitroom);
1994 /* ...or if this message is destined for Aide> then go there. */
1995 if (strlen(force_room) > 0) {
1996 strcpy(actual_rm, force_room);
1999 lprintf(CTDL_DEBUG, "Final selection: %s\n", actual_rm);
2000 if (strcasecmp(actual_rm, CC->room.QRname)) {
2001 getroom(&CC->room, actual_rm);
2005 * If this message has no O (room) field, generate one.
2007 if (msg->cm_fields['O'] == NULL) {
2008 msg->cm_fields['O'] = strdup(CC->room.QRname);
2011 /* Perform "before save" hooks (aborting if any return nonzero) */
2012 lprintf(CTDL_DEBUG, "Performing before-save hooks\n");
2013 if (PerformMessageHooks(msg, EVT_BEFORESAVE) > 0) return(-3);
2015 /* If this message has an Exclusive ID, perform replication checks */
2016 lprintf(CTDL_DEBUG, "Performing replication checks\n");
2017 if (ReplicationChecks(msg) > 0) return(-4);
2019 /* Save it to disk */
2020 lprintf(CTDL_DEBUG, "Saving to disk\n");
2021 newmsgid = send_message(msg);
2022 if (newmsgid <= 0L) return(-5);
2024 /* Write a supplemental message info record. This doesn't have to
2025 * be a critical section because nobody else knows about this message
2028 lprintf(CTDL_DEBUG, "Creating MetaData record\n");
2029 memset(&smi, 0, sizeof(struct MetaData));
2030 smi.meta_msgnum = newmsgid;
2031 smi.meta_refcount = 0;
2032 safestrncpy(smi.meta_content_type, content_type, sizeof smi.meta_content_type);
2035 /* Now figure out where to store the pointers */
2036 lprintf(CTDL_DEBUG, "Storing pointers\n");
2038 /* If this is being done by the networker delivering a private
2039 * message, we want to BYPASS saving the sender's copy (because there
2040 * is no local sender; it would otherwise go to the Trashcan).
2042 if ((!CC->internal_pgm) || (recps == NULL)) {
2043 if (CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 0) != 0) {
2044 lprintf(CTDL_ERR, "ERROR saving message pointer!\n");
2045 CtdlSaveMsgPointerInRoom(config.c_aideroom, newmsgid, 0);
2049 /* For internet mail, drop a copy in the outbound queue room */
2051 if (recps->num_internet > 0) {
2052 CtdlSaveMsgPointerInRoom(SMTP_SPOOLOUT_ROOM, newmsgid, 0);
2055 /* If other rooms are specified, drop them there too. */
2057 if (recps->num_room > 0)
2058 for (i=0; i<num_tokens(recps->recp_room, '|'); ++i) {
2059 extract(recipient, recps->recp_room, i);
2060 lprintf(CTDL_DEBUG, "Delivering to local room <%s>\n", recipient);
2061 CtdlSaveMsgPointerInRoom(recipient, newmsgid, 0);
2064 /* Bump this user's messages posted counter. */
2065 lprintf(CTDL_DEBUG, "Updating user\n");
2066 lgetuser(&CC->user, CC->curr_user);
2067 CC->user.posted = CC->user.posted + 1;
2068 lputuser(&CC->user);
2070 /* If this is private, local mail, make a copy in the
2071 * recipient's mailbox and bump the reference count.
2074 if (recps->num_local > 0)
2075 for (i=0; i<num_tokens(recps->recp_local, '|'); ++i) {
2076 extract(recipient, recps->recp_local, i);
2077 lprintf(CTDL_DEBUG, "Delivering private local mail to <%s>\n",
2079 if (getuser(&userbuf, recipient) == 0) {
2080 MailboxName(actual_rm, sizeof actual_rm, &userbuf, MAILROOM);
2081 CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 0);
2082 BumpNewMailCounter(userbuf.usernum);
2085 lprintf(CTDL_DEBUG, "No user <%s>\n", recipient);
2086 CtdlSaveMsgPointerInRoom(config.c_aideroom, newmsgid, 0);
2090 /* Perform "after save" hooks */
2091 lprintf(CTDL_DEBUG, "Performing after-save hooks\n");
2092 PerformMessageHooks(msg, EVT_AFTERSAVE);
2094 /* For IGnet mail, we have to save a new copy into the spooler for
2095 * each recipient, with the R and D fields set to the recipient and
2096 * destination-node. This has two ugly side effects: all other
2097 * recipients end up being unlisted in this recipient's copy of the
2098 * message, and it has to deliver multiple messages to the same
2099 * node. We'll revisit this again in a year or so when everyone has
2100 * a network spool receiver that can handle the new style messages.
2103 if (recps->num_ignet > 0)
2104 for (i=0; i<num_tokens(recps->recp_ignet, '|'); ++i) {
2105 extract(recipient, recps->recp_ignet, i);
2107 hold_R = msg->cm_fields['R'];
2108 hold_D = msg->cm_fields['D'];
2109 msg->cm_fields['R'] = malloc(SIZ);
2110 msg->cm_fields['D'] = malloc(SIZ);
2111 extract_token(msg->cm_fields['R'], recipient, 0, '@');
2112 extract_token(msg->cm_fields['D'], recipient, 1, '@');
2114 serialize_message(&smr, msg);
2116 snprintf(aaa, sizeof aaa,
2117 "./network/spoolin/netmail.%04lx.%04x.%04x",
2118 (long) getpid(), CC->cs_pid, ++seqnum);
2119 network_fp = fopen(aaa, "wb+");
2120 if (network_fp != NULL) {
2121 fwrite(smr.ser, smr.len, 1, network_fp);
2127 free(msg->cm_fields['R']);
2128 free(msg->cm_fields['D']);
2129 msg->cm_fields['R'] = hold_R;
2130 msg->cm_fields['D'] = hold_D;
2133 /* Go back to the room we started from */
2134 lprintf(CTDL_DEBUG, "Returning to original room %s\n", hold_rm);
2135 if (strcasecmp(hold_rm, CC->room.QRname))
2136 getroom(&CC->room, hold_rm);
2138 /* For internet mail, generate delivery instructions.
2139 * Yes, this is recursive. Deal with it. Infinite recursion does
2140 * not happen because the delivery instructions message does not
2141 * contain a recipient.
2144 if (recps->num_internet > 0) {
2145 lprintf(CTDL_DEBUG, "Generating delivery instructions\n");
2146 instr = malloc(SIZ * 2);
2147 snprintf(instr, SIZ * 2,
2148 "Content-type: %s\n\nmsgid|%ld\nsubmitted|%ld\n"
2150 SPOOLMIME, newmsgid, (long)time(NULL),
2151 msg->cm_fields['A'], msg->cm_fields['N']
2154 for (i=0; i<num_tokens(recps->recp_internet, '|'); ++i) {
2155 size_t tmp = strlen(instr);
2156 extract(recipient, recps->recp_internet, i);
2157 snprintf(&instr[tmp], SIZ * 2 - tmp,
2158 "remote|%s|0||\n", recipient);
2161 imsg = malloc(sizeof(struct CtdlMessage));
2162 memset(imsg, 0, sizeof(struct CtdlMessage));
2163 imsg->cm_magic = CTDLMESSAGE_MAGIC;
2164 imsg->cm_anon_type = MES_NORMAL;
2165 imsg->cm_format_type = FMT_RFC822;
2166 imsg->cm_fields['A'] = strdup("Citadel");
2167 imsg->cm_fields['M'] = instr;
2168 CtdlSubmitMsg(imsg, NULL, SMTP_SPOOLOUT_ROOM);
2169 CtdlFreeMessage(imsg);
2178 * Convenience function for generating small administrative messages.
2180 void quickie_message(char *from, char *to, char *room, char *text,
2181 int format_type, char *subject)
2183 struct CtdlMessage *msg;
2184 struct recptypes *recp = NULL;
2186 msg = malloc(sizeof(struct CtdlMessage));
2187 memset(msg, 0, sizeof(struct CtdlMessage));
2188 msg->cm_magic = CTDLMESSAGE_MAGIC;
2189 msg->cm_anon_type = MES_NORMAL;
2190 msg->cm_format_type = format_type;
2191 msg->cm_fields['A'] = strdup(from);
2192 if (room != NULL) msg->cm_fields['O'] = strdup(room);
2193 msg->cm_fields['N'] = strdup(NODENAME);
2195 msg->cm_fields['R'] = strdup(to);
2196 recp = validate_recipients(to);
2198 if (subject != NULL) {
2199 msg->cm_fields['U'] = strdup(subject);
2201 msg->cm_fields['M'] = strdup(text);
2203 CtdlSubmitMsg(msg, recp, room);
2204 CtdlFreeMessage(msg);
2205 if (recp != NULL) free(recp);
2211 * Back end function used by CtdlMakeMessage() and similar functions
2213 char *CtdlReadMessageBody(char *terminator, /* token signalling EOT */
2214 size_t maxlen, /* maximum message length */
2215 char *exist, /* if non-null, append to it;
2216 exist is ALWAYS freed */
2217 int crlf /* CRLF newlines instead of LF */
2221 size_t message_len = 0;
2222 size_t buffer_len = 0;
2228 if (exist == NULL) {
2235 message_len = strlen(exist);
2236 buffer_len = message_len + 4096;
2237 m = realloc(exist, buffer_len);
2244 /* flush the input if we have nowhere to store it */
2249 /* read in the lines of message text one by one */
2251 if (client_getln(buf, sizeof buf) < 1) finished = 1;
2252 if (!strcmp(buf, terminator)) finished = 1;
2254 strcat(buf, "\r\n");
2260 if ( (!flushing) && (!finished) ) {
2261 /* Measure the line */
2262 linelen = strlen(buf);
2264 /* augment the buffer if we have to */
2265 if ((message_len + linelen) >= buffer_len) {
2266 ptr = realloc(m, (buffer_len * 2) );
2267 if (ptr == NULL) { /* flush if can't allocate */
2270 buffer_len = (buffer_len * 2);
2272 lprintf(CTDL_DEBUG, "buffer_len is now %ld\n", (long)buffer_len);
2276 /* Add the new line to the buffer. NOTE: this loop must avoid
2277 * using functions like strcat() and strlen() because they
2278 * traverse the entire buffer upon every call, and doing that
2279 * for a multi-megabyte message slows it down beyond usability.
2281 strcpy(&m[message_len], buf);
2282 message_len += linelen;
2285 /* if we've hit the max msg length, flush the rest */
2286 if (message_len >= maxlen) flushing = 1;
2288 } while (!finished);
2296 * Build a binary message to be saved on disk.
2297 * (NOTE: if you supply 'preformatted_text', the buffer you give it
2298 * will become part of the message. This means you are no longer
2299 * responsible for managing that memory -- it will be freed along with
2300 * the rest of the fields when CtdlFreeMessage() is called.)
2303 struct CtdlMessage *CtdlMakeMessage(
2304 struct ctdluser *author, /* author's user structure */
2305 char *recipient, /* NULL if it's not mail */
2306 char *room, /* room where it's going */
2307 int type, /* see MES_ types in header file */
2308 int format_type, /* variformat, plain text, MIME... */
2309 char *fake_name, /* who we're masquerading as */
2310 char *subject, /* Subject (optional) */
2311 char *preformatted_text /* ...or NULL to read text from client */
2313 char dest_node[SIZ];
2315 struct CtdlMessage *msg;
2317 msg = malloc(sizeof(struct CtdlMessage));
2318 memset(msg, 0, sizeof(struct CtdlMessage));
2319 msg->cm_magic = CTDLMESSAGE_MAGIC;
2320 msg->cm_anon_type = type;
2321 msg->cm_format_type = format_type;
2323 /* Don't confuse the poor folks if it's not routed mail. */
2324 strcpy(dest_node, "");
2328 snprintf(buf, sizeof buf, "cit%ld", author->usernum); /* Path */
2329 msg->cm_fields['P'] = strdup(buf);
2331 snprintf(buf, sizeof buf, "%ld", (long)time(NULL)); /* timestamp */
2332 msg->cm_fields['T'] = strdup(buf);
2334 if (fake_name[0]) /* author */
2335 msg->cm_fields['A'] = strdup(fake_name);
2337 msg->cm_fields['A'] = strdup(author->fullname);
2339 if (CC->room.QRflags & QR_MAILBOX) { /* room */
2340 msg->cm_fields['O'] = strdup(&CC->room.QRname[11]);
2343 msg->cm_fields['O'] = strdup(CC->room.QRname);
2346 msg->cm_fields['N'] = strdup(NODENAME); /* nodename */
2347 msg->cm_fields['H'] = strdup(HUMANNODE); /* hnodename */
2349 if (recipient[0] != 0) {
2350 msg->cm_fields['R'] = strdup(recipient);
2352 if (dest_node[0] != 0) {
2353 msg->cm_fields['D'] = strdup(dest_node);
2356 if ( (author == &CC->user) && (strlen(CC->cs_inet_email) > 0) ) {
2357 msg->cm_fields['F'] = strdup(CC->cs_inet_email);
2360 if (subject != NULL) {
2362 if (strlen(subject) > 0) {
2363 msg->cm_fields['U'] = strdup(subject);
2367 if (preformatted_text != NULL) {
2368 msg->cm_fields['M'] = preformatted_text;
2371 msg->cm_fields['M'] = CtdlReadMessageBody("000",
2372 config.c_maxmsglen, NULL, 0);
2380 * Check to see whether we have permission to post a message in the current
2381 * room. Returns a *CITADEL ERROR CODE* and puts a message in errmsgbuf, or
2382 * returns 0 on success.
2384 int CtdlDoIHavePermissionToPostInThisRoom(char *errmsgbuf, size_t n) {
2386 if (!(CC->logged_in)) {
2387 snprintf(errmsgbuf, n, "Not logged in.");
2388 return (ERROR + NOT_LOGGED_IN);
2391 if ((CC->user.axlevel < 2)
2392 && ((CC->room.QRflags & QR_MAILBOX) == 0)) {
2393 snprintf(errmsgbuf, n, "Need to be validated to enter "
2394 "(except in %s> to sysop)", MAILROOM);
2395 return (ERROR + HIGHER_ACCESS_REQUIRED);
2398 if ((CC->user.axlevel < 4)
2399 && (CC->room.QRflags & QR_NETWORK)) {
2400 snprintf(errmsgbuf, n, "Need net privileges to enter here.");
2401 return (ERROR + HIGHER_ACCESS_REQUIRED);
2404 if ((CC->user.axlevel < 6)
2405 && (CC->room.QRflags & QR_READONLY)) {
2406 snprintf(errmsgbuf, n, "Sorry, this is a read-only room.");
2407 return (ERROR + HIGHER_ACCESS_REQUIRED);
2410 strcpy(errmsgbuf, "Ok");
2416 * Check to see if the specified user has Internet mail permission
2417 * (returns nonzero if permission is granted)
2419 int CtdlCheckInternetMailPermission(struct ctdluser *who) {
2421 /* Do not allow twits to send Internet mail */
2422 if (who->axlevel <= 2) return(0);
2424 /* Globally enabled? */
2425 if (config.c_restrict == 0) return(1);
2427 /* User flagged ok? */
2428 if (who->flags & US_INTERNET) return(2);
2430 /* Aide level access? */
2431 if (who->axlevel >= 6) return(3);
2433 /* No mail for you! */
2440 * Validate recipients, count delivery types and errors, and handle aliasing
2441 * FIXME check for dupes!!!!!
2443 struct recptypes *validate_recipients(char *recipients) {
2444 struct recptypes *ret;
2445 char this_recp[SIZ];
2446 char this_recp_cooked[SIZ];
2452 struct ctdluser tempUS;
2453 struct ctdlroom tempQR;
2456 ret = (struct recptypes *) malloc(sizeof(struct recptypes));
2457 if (ret == NULL) return(NULL);
2458 memset(ret, 0, sizeof(struct recptypes));
2461 ret->num_internet = 0;
2466 if (recipients == NULL) {
2469 else if (strlen(recipients) == 0) {
2473 /* Change all valid separator characters to commas */
2474 for (i=0; i<strlen(recipients); ++i) {
2475 if ((recipients[i] == ';') || (recipients[i] == '|')) {
2476 recipients[i] = ',';
2481 num_recps = num_tokens(recipients, ',');
2484 if (num_recps > 0) for (i=0; i<num_recps; ++i) {
2485 extract_token(this_recp, recipients, i, ',');
2487 lprintf(CTDL_DEBUG, "Evaluating recipient #%d <%s>\n", i, this_recp);
2488 mailtype = alias(this_recp);
2489 mailtype = alias(this_recp);
2490 mailtype = alias(this_recp);
2491 for (j=0; j<=strlen(this_recp); ++j) {
2492 if (this_recp[j]=='_') {
2493 this_recp_cooked[j] = ' ';
2496 this_recp_cooked[j] = this_recp[j];
2502 if (!strcasecmp(this_recp, "sysop")) {
2504 strcpy(this_recp, config.c_aideroom);
2505 if (strlen(ret->recp_room) > 0) {
2506 strcat(ret->recp_room, "|");
2508 strcat(ret->recp_room, this_recp);
2510 else if (getuser(&tempUS, this_recp) == 0) {
2512 strcpy(this_recp, tempUS.fullname);
2513 if (strlen(ret->recp_local) > 0) {
2514 strcat(ret->recp_local, "|");
2516 strcat(ret->recp_local, this_recp);
2518 else if (getuser(&tempUS, this_recp_cooked) == 0) {
2520 strcpy(this_recp, tempUS.fullname);
2521 if (strlen(ret->recp_local) > 0) {
2522 strcat(ret->recp_local, "|");
2524 strcat(ret->recp_local, this_recp);
2526 else if ( (!strncasecmp(this_recp, "room_", 5))
2527 && (!getroom(&tempQR, &this_recp_cooked[5])) ) {
2529 if (strlen(ret->recp_room) > 0) {
2530 strcat(ret->recp_room, "|");
2532 strcat(ret->recp_room, &this_recp_cooked[5]);
2540 /* Yes, you're reading this correctly: if the target
2541 * domain points back to the local system or an attached
2542 * Citadel directory, the address is invalid. That's
2543 * because if the address were valid, we would have
2544 * already translated it to a local address by now.
2546 if (IsDirectory(this_recp)) {
2551 ++ret->num_internet;
2552 if (strlen(ret->recp_internet) > 0) {
2553 strcat(ret->recp_internet, "|");
2555 strcat(ret->recp_internet, this_recp);
2560 if (strlen(ret->recp_ignet) > 0) {
2561 strcat(ret->recp_ignet, "|");
2563 strcat(ret->recp_ignet, this_recp);
2571 if (strlen(ret->errormsg) == 0) {
2572 snprintf(append, sizeof append,
2573 "Invalid recipient: %s",
2577 snprintf(append, sizeof append,
2580 if ( (strlen(ret->errormsg) + strlen(append)) < SIZ) {
2581 strcat(ret->errormsg, append);
2585 if (strlen(ret->display_recp) == 0) {
2586 strcpy(append, this_recp);
2589 snprintf(append, sizeof append, ", %s",
2592 if ( (strlen(ret->display_recp)+strlen(append)) < SIZ) {
2593 strcat(ret->display_recp, append);
2598 if ((ret->num_local + ret->num_internet + ret->num_ignet +
2599 ret->num_room + ret->num_error) == 0) {
2601 strcpy(ret->errormsg, "No recipients specified.");
2604 lprintf(CTDL_DEBUG, "validate_recipients()\n");
2605 lprintf(CTDL_DEBUG, " local: %d <%s>\n", ret->num_local, ret->recp_local);
2606 lprintf(CTDL_DEBUG, " room: %d <%s>\n", ret->num_room, ret->recp_room);
2607 lprintf(CTDL_DEBUG, " inet: %d <%s>\n", ret->num_internet, ret->recp_internet);
2608 lprintf(CTDL_DEBUG, " ignet: %d <%s>\n", ret->num_ignet, ret->recp_ignet);
2609 lprintf(CTDL_DEBUG, " error: %d <%s>\n", ret->num_error, ret->errormsg);
2617 * message entry - mode 0 (normal)
2619 void cmd_ent0(char *entargs)
2623 char masquerade_as[SIZ];
2625 int format_type = 0;
2626 char newusername[SIZ];
2627 struct CtdlMessage *msg;
2631 struct recptypes *valid = NULL;
2638 post = extract_int(entargs, 0);
2639 extract(recp, entargs, 1);
2640 anon_flag = extract_int(entargs, 2);
2641 format_type = extract_int(entargs, 3);
2642 extract(subject, entargs, 4);
2643 do_confirm = extract_int(entargs, 6);
2645 /* first check to make sure the request is valid. */
2647 err = CtdlDoIHavePermissionToPostInThisRoom(errmsg, sizeof errmsg);
2649 cprintf("%d %s\n", err, errmsg);
2653 /* Check some other permission type things. */
2656 if (CC->user.axlevel < 6) {
2657 cprintf("%d You don't have permission to masquerade.\n",
2658 ERROR + HIGHER_ACCESS_REQUIRED);
2661 extract(newusername, entargs, 5);
2662 memset(CC->fake_postname, 0, sizeof(CC->fake_postname) );
2663 safestrncpy(CC->fake_postname, newusername,
2664 sizeof(CC->fake_postname) );
2665 cprintf("%d ok\n", CIT_OK);
2668 CC->cs_flags |= CS_POSTING;
2670 /* In the Mail> room we have to behave a little differently --
2671 * make sure the user has specified at least one recipient. Then
2672 * validate the recipient(s).
2674 if ( (CC->room.QRflags & QR_MAILBOX)
2675 && (!strcasecmp(&CC->room.QRname[11], MAILROOM)) ) {
2677 if (CC->user.axlevel < 2) {
2678 strcpy(recp, "sysop");
2681 valid = validate_recipients(recp);
2682 if (valid->num_error > 0) {
2684 ERROR + NO_SUCH_USER, valid->errormsg);
2688 if (valid->num_internet > 0) {
2689 if (CtdlCheckInternetMailPermission(&CC->user)==0) {
2690 cprintf("%d You do not have permission "
2691 "to send Internet mail.\n",
2692 ERROR + HIGHER_ACCESS_REQUIRED);
2698 if ( ( (valid->num_internet + valid->num_ignet) > 0)
2699 && (CC->user.axlevel < 4) ) {
2700 cprintf("%d Higher access required for network mail.\n",
2701 ERROR + HIGHER_ACCESS_REQUIRED);
2706 if ((RESTRICT_INTERNET == 1) && (valid->num_internet > 0)
2707 && ((CC->user.flags & US_INTERNET) == 0)
2708 && (!CC->internal_pgm)) {
2709 cprintf("%d You don't have access to Internet mail.\n",
2710 ERROR + HIGHER_ACCESS_REQUIRED);
2717 /* Is this a room which has anonymous-only or anonymous-option? */
2718 anonymous = MES_NORMAL;
2719 if (CC->room.QRflags & QR_ANONONLY) {
2720 anonymous = MES_ANONONLY;
2722 if (CC->room.QRflags & QR_ANONOPT) {
2723 if (anon_flag == 1) { /* only if the user requested it */
2724 anonymous = MES_ANONOPT;
2728 if ((CC->room.QRflags & QR_MAILBOX) == 0) {
2732 /* If we're only checking the validity of the request, return
2733 * success without creating the message.
2736 cprintf("%d %s\n", CIT_OK,
2737 ((valid != NULL) ? valid->display_recp : "") );
2742 /* Handle author masquerading */
2743 if (CC->fake_postname[0]) {
2744 strcpy(masquerade_as, CC->fake_postname);
2746 else if (CC->fake_username[0]) {
2747 strcpy(masquerade_as, CC->fake_username);
2750 strcpy(masquerade_as, "");
2753 /* Read in the message from the client. */
2755 cprintf("%d send message\n", START_CHAT_MODE);
2757 cprintf("%d send message\n", SEND_LISTING);
2759 msg = CtdlMakeMessage(&CC->user, recp,
2760 CC->room.QRname, anonymous, format_type,
2761 masquerade_as, subject, NULL);
2764 msgnum = CtdlSubmitMsg(msg, valid, "");
2767 cprintf("%ld\n", msgnum);
2769 cprintf("Message accepted.\n");
2772 cprintf("Internal error.\n");
2774 if (msg->cm_fields['E'] != NULL) {
2775 cprintf("%s\n", msg->cm_fields['E']);
2782 CtdlFreeMessage(msg);
2784 CC->fake_postname[0] = '\0';
2792 * API function to delete messages which match a set of criteria
2793 * (returns the actual number of messages deleted)
2795 int CtdlDeleteMessages(char *room_name, /* which room */
2796 long dmsgnum, /* or "0" for any */
2797 char *content_type /* or "" for any */
2801 struct ctdlroom qrbuf;
2802 struct cdbdata *cdbfr;
2803 long *msglist = NULL;
2804 long *dellist = NULL;
2807 int num_deleted = 0;
2809 struct MetaData smi;
2811 lprintf(CTDL_DEBUG, "CtdlDeleteMessages(%s, %ld, %s)\n",
2812 room_name, dmsgnum, content_type);
2814 /* get room record, obtaining a lock... */
2815 if (lgetroom(&qrbuf, room_name) != 0) {
2816 lprintf(CTDL_ERR, "CtdlDeleteMessages(): Room <%s> not found\n",
2818 return (0); /* room not found */
2820 cdbfr = cdb_fetch(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long));
2822 if (cdbfr != NULL) {
2823 msglist = malloc(cdbfr->len);
2824 dellist = malloc(cdbfr->len);
2825 memcpy(msglist, cdbfr->ptr, cdbfr->len);
2826 num_msgs = cdbfr->len / sizeof(long);
2830 for (i = 0; i < num_msgs; ++i) {
2833 /* Set/clear a bit for each criterion */
2835 if ((dmsgnum == 0L) || (msglist[i] == dmsgnum)) {
2836 delete_this |= 0x01;
2838 if (strlen(content_type) == 0) {
2839 delete_this |= 0x02;
2841 GetMetaData(&smi, msglist[i]);
2842 if (!strcasecmp(smi.meta_content_type,
2844 delete_this |= 0x02;
2848 /* Delete message only if all bits are set */
2849 if (delete_this == 0x03) {
2850 dellist[num_deleted++] = msglist[i];
2855 num_msgs = sort_msglist(msglist, num_msgs);
2856 cdb_store(CDB_MSGLISTS, &qrbuf.QRnumber, (int)sizeof(long),
2857 msglist, (int)(num_msgs * sizeof(long)));
2859 qrbuf.QRhighest = msglist[num_msgs - 1];
2863 /* Go through the messages we pulled out of the index, and decrement
2864 * their reference counts by 1. If this is the only room the message
2865 * was in, the reference count will reach zero and the message will
2866 * automatically be deleted from the database. We do this in a
2867 * separate pass because there might be plug-in hooks getting called,
2868 * and we don't want that happening during an S_ROOMS critical
2871 if (num_deleted) for (i=0; i<num_deleted; ++i) {
2872 PerformDeleteHooks(qrbuf.QRname, dellist[i]);
2873 AdjRefCount(dellist[i], -1);
2876 /* Now free the memory we used, and go away. */
2877 if (msglist != NULL) free(msglist);
2878 if (dellist != NULL) free(dellist);
2879 lprintf(CTDL_DEBUG, "%d message(s) deleted.\n", num_deleted);
2880 return (num_deleted);
2886 * Check whether the current user has permission to delete messages from
2887 * the current room (returns 1 for yes, 0 for no)
2889 int CtdlDoIHavePermissionToDeleteMessagesFromThisRoom(void) {
2890 getuser(&CC->user, CC->curr_user);
2891 if ((CC->user.axlevel < 6)
2892 && (CC->user.usernum != CC->room.QRroomaide)
2893 && ((CC->room.QRflags & QR_MAILBOX) == 0)
2894 && (!(CC->internal_pgm))) {
2903 * Delete message from current room
2905 void cmd_dele(char *delstr)
2910 if (CtdlDoIHavePermissionToDeleteMessagesFromThisRoom() == 0) {
2911 cprintf("%d Higher access required.\n",
2912 ERROR + HIGHER_ACCESS_REQUIRED);
2915 delnum = extract_long(delstr, 0);
2917 num_deleted = CtdlDeleteMessages(CC->room.QRname, delnum, "");
2920 cprintf("%d %d message%s deleted.\n", CIT_OK,
2921 num_deleted, ((num_deleted != 1) ? "s" : ""));
2923 cprintf("%d Message %ld not found.\n", ERROR + MESSAGE_NOT_FOUND, delnum);
2929 * Back end API function for moves and deletes
2931 int CtdlCopyMsgToRoom(long msgnum, char *dest) {
2934 err = CtdlSaveMsgPointerInRoom(dest, msgnum,
2935 (SM_VERIFY_GOODNESS | SM_DO_REPL_CHECK) );
2936 if (err != 0) return(err);
2944 * move or copy a message to another room
2946 void cmd_move(char *args)
2950 struct ctdlroom qtemp;
2956 num = extract_long(args, 0);
2957 extract(targ, args, 1);
2958 targ[ROOMNAMELEN - 1] = 0;
2959 is_copy = extract_int(args, 2);
2961 if (getroom(&qtemp, targ) != 0) {
2962 cprintf("%d '%s' does not exist.\n",
2963 ERROR + ROOM_NOT_FOUND, targ);
2967 getuser(&CC->user, CC->curr_user);
2968 CtdlRoomAccess(&qtemp, &CC->user, &ra, NULL);
2970 /* Check for permission to perform this operation.
2971 * Remember: "CC->room" is source, "qtemp" is target.
2975 /* Aides can move/copy */
2976 if (CC->user.axlevel >= 6) permit = 1;
2978 /* Room aides can move/copy */
2979 if (CC->user.usernum == CC->room.QRroomaide) permit = 1;
2981 /* Permit move/copy from personal rooms */
2982 if ((CC->room.QRflags & QR_MAILBOX)
2983 && (qtemp.QRflags & QR_MAILBOX)) permit = 1;
2985 /* Permit only copy from public to personal room */
2987 && (!(CC->room.QRflags & QR_MAILBOX))
2988 && (qtemp.QRflags & QR_MAILBOX)) permit = 1;
2990 /* User must have access to target room */
2991 if (!(ra & UA_KNOWN)) permit = 0;
2994 cprintf("%d Higher access required.\n",
2995 ERROR + HIGHER_ACCESS_REQUIRED);
2999 err = CtdlCopyMsgToRoom(num, targ);
3001 cprintf("%d Cannot store message in %s: error %d\n",
3006 /* Now delete the message from the source room,
3007 * if this is a 'move' rather than a 'copy' operation.
3010 CtdlDeleteMessages(CC->room.QRname, num, "");
3013 cprintf("%d Message %s.\n", CIT_OK, (is_copy ? "copied" : "moved") );
3019 * GetMetaData() - Get the supplementary record for a message
3021 void GetMetaData(struct MetaData *smibuf, long msgnum)
3024 struct cdbdata *cdbsmi;
3027 memset(smibuf, 0, sizeof(struct MetaData));
3028 smibuf->meta_msgnum = msgnum;
3029 smibuf->meta_refcount = 1; /* Default reference count is 1 */
3031 /* Use the negative of the message number for its supp record index */
3032 TheIndex = (0L - msgnum);
3034 cdbsmi = cdb_fetch(CDB_MSGMAIN, &TheIndex, sizeof(long));
3035 if (cdbsmi == NULL) {
3036 return; /* record not found; go with defaults */
3038 memcpy(smibuf, cdbsmi->ptr,
3039 ((cdbsmi->len > sizeof(struct MetaData)) ?
3040 sizeof(struct MetaData) : cdbsmi->len));
3047 * PutMetaData() - (re)write supplementary record for a message
3049 void PutMetaData(struct MetaData *smibuf)
3053 /* Use the negative of the message number for the metadata db index */
3054 TheIndex = (0L - smibuf->meta_msgnum);
3056 lprintf(CTDL_DEBUG, "PutMetaData(%ld) - ref count is %d\n",
3057 smibuf->meta_msgnum, smibuf->meta_refcount);
3059 cdb_store(CDB_MSGMAIN,
3060 &TheIndex, (int)sizeof(long),
3061 smibuf, (int)sizeof(struct MetaData));
3066 * AdjRefCount - change the reference count for a message;
3067 * delete the message if it reaches zero
3069 void AdjRefCount(long msgnum, int incr)
3072 struct MetaData smi;
3075 /* This is a *tight* critical section; please keep it that way, as
3076 * it may get called while nested in other critical sections.
3077 * Complicating this any further will surely cause deadlock!
3079 begin_critical_section(S_SUPPMSGMAIN);
3080 GetMetaData(&smi, msgnum);
3081 lprintf(CTDL_DEBUG, "Ref count for message <%ld> before write is <%d>\n",
3082 msgnum, smi.meta_refcount);
3083 smi.meta_refcount += incr;
3085 end_critical_section(S_SUPPMSGMAIN);
3086 lprintf(CTDL_DEBUG, "Ref count for message <%ld> after write is <%d>\n",
3087 msgnum, smi.meta_refcount);
3089 /* If the reference count is now zero, delete the message
3090 * (and its supplementary record as well).
3092 if (smi.meta_refcount == 0) {
3093 lprintf(CTDL_DEBUG, "Deleting message <%ld>\n", msgnum);
3095 cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long));
3096 cdb_delete(CDB_BIGMSGS, &delnum, (int)sizeof(long));
3098 /* We have to delete the metadata record too! */
3099 delnum = (0L - msgnum);
3100 cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long));
3105 * Write a generic object to this room
3107 * Note: this could be much more efficient. Right now we use two temporary
3108 * files, and still pull the message into memory as with all others.
3110 void CtdlWriteObject(char *req_room, /* Room to stuff it in */
3111 char *content_type, /* MIME type of this object */
3112 char *tempfilename, /* Where to fetch it from */
3113 struct ctdluser *is_mailbox, /* Mailbox room? */
3114 int is_binary, /* Is encoding necessary? */
3115 int is_unique, /* Del others of this type? */
3116 unsigned int flags /* Internal save flags */
3121 struct ctdlroom qrbuf;
3122 char roomname[ROOMNAMELEN];
3123 struct CtdlMessage *msg;
3125 char *raw_message = NULL;
3126 char *encoded_message = NULL;
3127 off_t raw_length = 0;
3129 if (is_mailbox != NULL)
3130 MailboxName(roomname, sizeof roomname, is_mailbox, req_room);
3132 safestrncpy(roomname, req_room, sizeof(roomname));
3133 lprintf(CTDL_DEBUG, "CtdlWriteObject() to <%s> (flags=%d)\n", roomname, flags);
3136 fp = fopen(tempfilename, "rb");
3138 lprintf(CTDL_CRIT, "Cannot open %s: %s\n",
3139 tempfilename, strerror(errno));
3142 fseek(fp, 0L, SEEK_END);
3143 raw_length = ftell(fp);
3145 lprintf(CTDL_DEBUG, "Raw length is %ld\n", (long)raw_length);
3147 raw_message = malloc((size_t)raw_length + 2);
3148 fread(raw_message, (size_t)raw_length, 1, fp);
3152 encoded_message = malloc((size_t)
3153 (((raw_length * 134) / 100) + 4096 ) );
3156 encoded_message = malloc((size_t)(raw_length + 4096));
3159 sprintf(encoded_message, "Content-type: %s\n", content_type);
3162 sprintf(&encoded_message[strlen(encoded_message)],
3163 "Content-transfer-encoding: base64\n\n"
3167 sprintf(&encoded_message[strlen(encoded_message)],
3168 "Content-transfer-encoding: 7bit\n\n"
3174 &encoded_message[strlen(encoded_message)],
3180 raw_message[raw_length] = 0;
3182 &encoded_message[strlen(encoded_message)],
3190 lprintf(CTDL_DEBUG, "Allocating\n");
3191 msg = malloc(sizeof(struct CtdlMessage));
3192 memset(msg, 0, sizeof(struct CtdlMessage));
3193 msg->cm_magic = CTDLMESSAGE_MAGIC;
3194 msg->cm_anon_type = MES_NORMAL;
3195 msg->cm_format_type = 4;
3196 msg->cm_fields['A'] = strdup(CC->user.fullname);
3197 msg->cm_fields['O'] = strdup(req_room);
3198 msg->cm_fields['N'] = strdup(config.c_nodename);
3199 msg->cm_fields['H'] = strdup(config.c_humannode);
3200 msg->cm_flags = flags;
3202 msg->cm_fields['M'] = encoded_message;
3204 /* Create the requested room if we have to. */
3205 if (getroom(&qrbuf, roomname) != 0) {
3206 create_room(roomname,
3207 ( (is_mailbox != NULL) ? 5 : 3 ),
3208 "", 0, 1, 0, VIEW_BBS);
3210 /* If the caller specified this object as unique, delete all
3211 * other objects of this type that are currently in the room.
3214 lprintf(CTDL_DEBUG, "Deleted %d other msgs of this type\n",
3215 CtdlDeleteMessages(roomname, 0L, content_type));
3217 /* Now write the data */
3218 CtdlSubmitMsg(msg, NULL, roomname);
3219 CtdlFreeMessage(msg);
3227 void CtdlGetSysConfigBackend(long msgnum, void *userdata) {
3228 config_msgnum = msgnum;
3232 char *CtdlGetSysConfig(char *sysconfname) {
3233 char hold_rm[ROOMNAMELEN];
3236 struct CtdlMessage *msg;
3239 strcpy(hold_rm, CC->room.QRname);
3240 if (getroom(&CC->room, SYSCONFIGROOM) != 0) {
3241 getroom(&CC->room, hold_rm);
3246 /* We want the last (and probably only) config in this room */
3247 begin_critical_section(S_CONFIG);
3248 config_msgnum = (-1L);
3249 CtdlForEachMessage(MSGS_LAST, 1, sysconfname, NULL,
3250 CtdlGetSysConfigBackend, NULL);
3251 msgnum = config_msgnum;
3252 end_critical_section(S_CONFIG);
3258 msg = CtdlFetchMessage(msgnum, 1);
3260 conf = strdup(msg->cm_fields['M']);
3261 CtdlFreeMessage(msg);
3268 getroom(&CC->room, hold_rm);
3270 if (conf != NULL) do {
3271 extract_token(buf, conf, 0, '\n');
3272 strcpy(conf, &conf[strlen(buf)+1]);
3273 } while ( (strlen(conf)>0) && (strlen(buf)>0) );
3278 void CtdlPutSysConfig(char *sysconfname, char *sysconfdata) {
3279 char temp[PATH_MAX];
3282 strcpy(temp, tmpnam(NULL));
3284 fp = fopen(temp, "w");
3285 if (fp == NULL) return;
3286 fprintf(fp, "%s", sysconfdata);
3289 /* this handy API function does all the work for us */
3290 CtdlWriteObject(SYSCONFIGROOM, sysconfname, temp, NULL, 0, 1, 0);
3296 * Determine whether a given Internet address belongs to the current user
3298 int CtdlIsMe(char *addr) {
3299 struct recptypes *recp;
3302 recp = validate_recipients(addr);
3303 if (recp == NULL) return(0);
3305 if (recp->num_local == 0) {
3310 for (i=0; i<recp->num_local; ++i) {
3311 extract(addr, recp->recp_local, i);
3312 if (!strcasecmp(addr, CC->user.fullname)) {
3324 * Citadel protocol command to do the same
3326 void cmd_isme(char *argbuf) {
3329 if (CtdlAccessCheck(ac_logged_in)) return;
3330 extract(addr, argbuf, 0);
3332 if (CtdlIsMe(addr)) {
3333 cprintf("%d %s\n", CIT_OK, addr);
3336 cprintf("%d Not you.\n", ERROR + ILLEGAL_VALUE);