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'],
1963 "Content-type: ", strncasecmp);
1965 safestrncpy(content_type, &mptr[14],
1966 sizeof content_type);
1967 for (a = 0; a < strlen(content_type); ++a) {
1968 if ((content_type[a] == ';')
1969 || (content_type[a] == ' ')
1970 || (content_type[a] == 13)
1971 || (content_type[a] == 10)) {
1972 content_type[a] = 0;
1978 /* Goto the correct room */
1979 lprintf(CTDL_DEBUG, "Selected room %s\n",
1980 (recps) ? CC->room.QRname : SENTITEMS);
1981 strcpy(hold_rm, CC->room.QRname);
1982 strcpy(actual_rm, CC->room.QRname);
1983 if (recps != NULL) {
1984 strcpy(actual_rm, SENTITEMS);
1987 /* If the user is a twit, move to the twit room for posting */
1988 lprintf(CTDL_DEBUG, "Handling twit stuff: %s\n",
1989 (CC->user.axlevel == 2) ? config.c_twitroom : "OK");
1991 if (CC->user.axlevel == 2) {
1992 strcpy(hold_rm, actual_rm);
1993 strcpy(actual_rm, config.c_twitroom);
1997 /* ...or if this message is destined for Aide> then go there. */
1998 if (strlen(force_room) > 0) {
1999 strcpy(actual_rm, force_room);
2002 lprintf(CTDL_DEBUG, "Final selection: %s\n", actual_rm);
2003 if (strcasecmp(actual_rm, CC->room.QRname)) {
2004 getroom(&CC->room, actual_rm);
2008 * If this message has no O (room) field, generate one.
2010 if (msg->cm_fields['O'] == NULL) {
2011 msg->cm_fields['O'] = strdup(CC->room.QRname);
2014 /* Perform "before save" hooks (aborting if any return nonzero) */
2015 lprintf(CTDL_DEBUG, "Performing before-save hooks\n");
2016 if (PerformMessageHooks(msg, EVT_BEFORESAVE) > 0) return(-3);
2018 /* If this message has an Exclusive ID, perform replication checks */
2019 lprintf(CTDL_DEBUG, "Performing replication checks\n");
2020 if (ReplicationChecks(msg) > 0) return(-4);
2022 /* Save it to disk */
2023 lprintf(CTDL_DEBUG, "Saving to disk\n");
2024 newmsgid = send_message(msg);
2025 if (newmsgid <= 0L) return(-5);
2027 /* Write a supplemental message info record. This doesn't have to
2028 * be a critical section because nobody else knows about this message
2031 lprintf(CTDL_DEBUG, "Creating MetaData record\n");
2032 memset(&smi, 0, sizeof(struct MetaData));
2033 smi.meta_msgnum = newmsgid;
2034 smi.meta_refcount = 0;
2035 safestrncpy(smi.meta_content_type, content_type, sizeof smi.meta_content_type);
2038 /* Now figure out where to store the pointers */
2039 lprintf(CTDL_DEBUG, "Storing pointers\n");
2041 /* If this is being done by the networker delivering a private
2042 * message, we want to BYPASS saving the sender's copy (because there
2043 * is no local sender; it would otherwise go to the Trashcan).
2045 if ((!CC->internal_pgm) || (recps == NULL)) {
2046 if (CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 0) != 0) {
2047 lprintf(CTDL_ERR, "ERROR saving message pointer!\n");
2048 CtdlSaveMsgPointerInRoom(config.c_aideroom, newmsgid, 0);
2052 /* For internet mail, drop a copy in the outbound queue room */
2054 if (recps->num_internet > 0) {
2055 CtdlSaveMsgPointerInRoom(SMTP_SPOOLOUT_ROOM, newmsgid, 0);
2058 /* If other rooms are specified, drop them there too. */
2060 if (recps->num_room > 0)
2061 for (i=0; i<num_tokens(recps->recp_room, '|'); ++i) {
2062 extract(recipient, recps->recp_room, i);
2063 lprintf(CTDL_DEBUG, "Delivering to local room <%s>\n", recipient);
2064 CtdlSaveMsgPointerInRoom(recipient, newmsgid, 0);
2067 /* Bump this user's messages posted counter. */
2068 lprintf(CTDL_DEBUG, "Updating user\n");
2069 lgetuser(&CC->user, CC->curr_user);
2070 CC->user.posted = CC->user.posted + 1;
2071 lputuser(&CC->user);
2073 /* If this is private, local mail, make a copy in the
2074 * recipient's mailbox and bump the reference count.
2077 if (recps->num_local > 0)
2078 for (i=0; i<num_tokens(recps->recp_local, '|'); ++i) {
2079 extract(recipient, recps->recp_local, i);
2080 lprintf(CTDL_DEBUG, "Delivering private local mail to <%s>\n",
2082 if (getuser(&userbuf, recipient) == 0) {
2083 MailboxName(actual_rm, sizeof actual_rm, &userbuf, MAILROOM);
2084 CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 0);
2085 BumpNewMailCounter(userbuf.usernum);
2088 lprintf(CTDL_DEBUG, "No user <%s>\n", recipient);
2089 CtdlSaveMsgPointerInRoom(config.c_aideroom, newmsgid, 0);
2093 /* Perform "after save" hooks */
2094 lprintf(CTDL_DEBUG, "Performing after-save hooks\n");
2095 PerformMessageHooks(msg, EVT_AFTERSAVE);
2097 /* For IGnet mail, we have to save a new copy into the spooler for
2098 * each recipient, with the R and D fields set to the recipient and
2099 * destination-node. This has two ugly side effects: all other
2100 * recipients end up being unlisted in this recipient's copy of the
2101 * message, and it has to deliver multiple messages to the same
2102 * node. We'll revisit this again in a year or so when everyone has
2103 * a network spool receiver that can handle the new style messages.
2106 if (recps->num_ignet > 0)
2107 for (i=0; i<num_tokens(recps->recp_ignet, '|'); ++i) {
2108 extract(recipient, recps->recp_ignet, i);
2110 hold_R = msg->cm_fields['R'];
2111 hold_D = msg->cm_fields['D'];
2112 msg->cm_fields['R'] = malloc(SIZ);
2113 msg->cm_fields['D'] = malloc(SIZ);
2114 extract_token(msg->cm_fields['R'], recipient, 0, '@');
2115 extract_token(msg->cm_fields['D'], recipient, 1, '@');
2117 serialize_message(&smr, msg);
2119 snprintf(aaa, sizeof aaa,
2120 "./network/spoolin/netmail.%04lx.%04x.%04x",
2121 (long) getpid(), CC->cs_pid, ++seqnum);
2122 network_fp = fopen(aaa, "wb+");
2123 if (network_fp != NULL) {
2124 fwrite(smr.ser, smr.len, 1, network_fp);
2130 free(msg->cm_fields['R']);
2131 free(msg->cm_fields['D']);
2132 msg->cm_fields['R'] = hold_R;
2133 msg->cm_fields['D'] = hold_D;
2136 /* Go back to the room we started from */
2137 lprintf(CTDL_DEBUG, "Returning to original room %s\n", hold_rm);
2138 if (strcasecmp(hold_rm, CC->room.QRname))
2139 getroom(&CC->room, hold_rm);
2141 /* For internet mail, generate delivery instructions.
2142 * Yes, this is recursive. Deal with it. Infinite recursion does
2143 * not happen because the delivery instructions message does not
2144 * contain a recipient.
2147 if (recps->num_internet > 0) {
2148 lprintf(CTDL_DEBUG, "Generating delivery instructions\n");
2149 instr = malloc(SIZ * 2);
2150 snprintf(instr, SIZ * 2,
2151 "Content-type: %s\n\nmsgid|%ld\nsubmitted|%ld\n"
2153 SPOOLMIME, newmsgid, (long)time(NULL),
2154 msg->cm_fields['A'], msg->cm_fields['N']
2157 for (i=0; i<num_tokens(recps->recp_internet, '|'); ++i) {
2158 size_t tmp = strlen(instr);
2159 extract(recipient, recps->recp_internet, i);
2160 snprintf(&instr[tmp], SIZ * 2 - tmp,
2161 "remote|%s|0||\n", recipient);
2164 imsg = malloc(sizeof(struct CtdlMessage));
2165 memset(imsg, 0, sizeof(struct CtdlMessage));
2166 imsg->cm_magic = CTDLMESSAGE_MAGIC;
2167 imsg->cm_anon_type = MES_NORMAL;
2168 imsg->cm_format_type = FMT_RFC822;
2169 imsg->cm_fields['A'] = strdup("Citadel");
2170 imsg->cm_fields['M'] = instr;
2171 CtdlSubmitMsg(imsg, NULL, SMTP_SPOOLOUT_ROOM);
2172 CtdlFreeMessage(imsg);
2181 * Convenience function for generating small administrative messages.
2183 void quickie_message(char *from, char *to, char *room, char *text,
2184 int format_type, char *subject)
2186 struct CtdlMessage *msg;
2187 struct recptypes *recp = NULL;
2189 msg = malloc(sizeof(struct CtdlMessage));
2190 memset(msg, 0, sizeof(struct CtdlMessage));
2191 msg->cm_magic = CTDLMESSAGE_MAGIC;
2192 msg->cm_anon_type = MES_NORMAL;
2193 msg->cm_format_type = format_type;
2194 msg->cm_fields['A'] = strdup(from);
2195 if (room != NULL) msg->cm_fields['O'] = strdup(room);
2196 msg->cm_fields['N'] = strdup(NODENAME);
2198 msg->cm_fields['R'] = strdup(to);
2199 recp = validate_recipients(to);
2201 if (subject != NULL) {
2202 msg->cm_fields['U'] = strdup(subject);
2204 msg->cm_fields['M'] = strdup(text);
2206 CtdlSubmitMsg(msg, recp, room);
2207 CtdlFreeMessage(msg);
2208 if (recp != NULL) free(recp);
2214 * Back end function used by CtdlMakeMessage() and similar functions
2216 char *CtdlReadMessageBody(char *terminator, /* token signalling EOT */
2217 size_t maxlen, /* maximum message length */
2218 char *exist, /* if non-null, append to it;
2219 exist is ALWAYS freed */
2220 int crlf /* CRLF newlines instead of LF */
2224 size_t message_len = 0;
2225 size_t buffer_len = 0;
2231 if (exist == NULL) {
2238 message_len = strlen(exist);
2239 buffer_len = message_len + 4096;
2240 m = realloc(exist, buffer_len);
2247 /* flush the input if we have nowhere to store it */
2252 /* read in the lines of message text one by one */
2254 if (client_getln(buf, sizeof buf) < 1) finished = 1;
2255 if (!strcmp(buf, terminator)) finished = 1;
2257 strcat(buf, "\r\n");
2263 if ( (!flushing) && (!finished) ) {
2264 /* Measure the line */
2265 linelen = strlen(buf);
2267 /* augment the buffer if we have to */
2268 if ((message_len + linelen) >= buffer_len) {
2269 ptr = realloc(m, (buffer_len * 2) );
2270 if (ptr == NULL) { /* flush if can't allocate */
2273 buffer_len = (buffer_len * 2);
2275 lprintf(CTDL_DEBUG, "buffer_len is now %ld\n", (long)buffer_len);
2279 /* Add the new line to the buffer. NOTE: this loop must avoid
2280 * using functions like strcat() and strlen() because they
2281 * traverse the entire buffer upon every call, and doing that
2282 * for a multi-megabyte message slows it down beyond usability.
2284 strcpy(&m[message_len], buf);
2285 message_len += linelen;
2288 /* if we've hit the max msg length, flush the rest */
2289 if (message_len >= maxlen) flushing = 1;
2291 } while (!finished);
2299 * Build a binary message to be saved on disk.
2300 * (NOTE: if you supply 'preformatted_text', the buffer you give it
2301 * will become part of the message. This means you are no longer
2302 * responsible for managing that memory -- it will be freed along with
2303 * the rest of the fields when CtdlFreeMessage() is called.)
2306 struct CtdlMessage *CtdlMakeMessage(
2307 struct ctdluser *author, /* author's user structure */
2308 char *recipient, /* NULL if it's not mail */
2309 char *room, /* room where it's going */
2310 int type, /* see MES_ types in header file */
2311 int format_type, /* variformat, plain text, MIME... */
2312 char *fake_name, /* who we're masquerading as */
2313 char *subject, /* Subject (optional) */
2314 char *preformatted_text /* ...or NULL to read text from client */
2316 char dest_node[SIZ];
2318 struct CtdlMessage *msg;
2320 msg = malloc(sizeof(struct CtdlMessage));
2321 memset(msg, 0, sizeof(struct CtdlMessage));
2322 msg->cm_magic = CTDLMESSAGE_MAGIC;
2323 msg->cm_anon_type = type;
2324 msg->cm_format_type = format_type;
2326 /* Don't confuse the poor folks if it's not routed mail. */
2327 strcpy(dest_node, "");
2331 snprintf(buf, sizeof buf, "cit%ld", author->usernum); /* Path */
2332 msg->cm_fields['P'] = strdup(buf);
2334 snprintf(buf, sizeof buf, "%ld", (long)time(NULL)); /* timestamp */
2335 msg->cm_fields['T'] = strdup(buf);
2337 if (fake_name[0]) /* author */
2338 msg->cm_fields['A'] = strdup(fake_name);
2340 msg->cm_fields['A'] = strdup(author->fullname);
2342 if (CC->room.QRflags & QR_MAILBOX) { /* room */
2343 msg->cm_fields['O'] = strdup(&CC->room.QRname[11]);
2346 msg->cm_fields['O'] = strdup(CC->room.QRname);
2349 msg->cm_fields['N'] = strdup(NODENAME); /* nodename */
2350 msg->cm_fields['H'] = strdup(HUMANNODE); /* hnodename */
2352 if (recipient[0] != 0) {
2353 msg->cm_fields['R'] = strdup(recipient);
2355 if (dest_node[0] != 0) {
2356 msg->cm_fields['D'] = strdup(dest_node);
2359 if ( (author == &CC->user) && (strlen(CC->cs_inet_email) > 0) ) {
2360 msg->cm_fields['F'] = strdup(CC->cs_inet_email);
2363 if (subject != NULL) {
2365 if (strlen(subject) > 0) {
2366 msg->cm_fields['U'] = strdup(subject);
2370 if (preformatted_text != NULL) {
2371 msg->cm_fields['M'] = preformatted_text;
2374 msg->cm_fields['M'] = CtdlReadMessageBody("000",
2375 config.c_maxmsglen, NULL, 0);
2383 * Check to see whether we have permission to post a message in the current
2384 * room. Returns a *CITADEL ERROR CODE* and puts a message in errmsgbuf, or
2385 * returns 0 on success.
2387 int CtdlDoIHavePermissionToPostInThisRoom(char *errmsgbuf, size_t n) {
2389 if (!(CC->logged_in)) {
2390 snprintf(errmsgbuf, n, "Not logged in.");
2391 return (ERROR + NOT_LOGGED_IN);
2394 if ((CC->user.axlevel < 2)
2395 && ((CC->room.QRflags & QR_MAILBOX) == 0)) {
2396 snprintf(errmsgbuf, n, "Need to be validated to enter "
2397 "(except in %s> to sysop)", MAILROOM);
2398 return (ERROR + HIGHER_ACCESS_REQUIRED);
2401 if ((CC->user.axlevel < 4)
2402 && (CC->room.QRflags & QR_NETWORK)) {
2403 snprintf(errmsgbuf, n, "Need net privileges to enter here.");
2404 return (ERROR + HIGHER_ACCESS_REQUIRED);
2407 if ((CC->user.axlevel < 6)
2408 && (CC->room.QRflags & QR_READONLY)) {
2409 snprintf(errmsgbuf, n, "Sorry, this is a read-only room.");
2410 return (ERROR + HIGHER_ACCESS_REQUIRED);
2413 strcpy(errmsgbuf, "Ok");
2419 * Check to see if the specified user has Internet mail permission
2420 * (returns nonzero if permission is granted)
2422 int CtdlCheckInternetMailPermission(struct ctdluser *who) {
2424 /* Do not allow twits to send Internet mail */
2425 if (who->axlevel <= 2) return(0);
2427 /* Globally enabled? */
2428 if (config.c_restrict == 0) return(1);
2430 /* User flagged ok? */
2431 if (who->flags & US_INTERNET) return(2);
2433 /* Aide level access? */
2434 if (who->axlevel >= 6) return(3);
2436 /* No mail for you! */
2443 * Validate recipients, count delivery types and errors, and handle aliasing
2444 * FIXME check for dupes!!!!!
2446 struct recptypes *validate_recipients(char *recipients) {
2447 struct recptypes *ret;
2448 char this_recp[SIZ];
2449 char this_recp_cooked[SIZ];
2455 struct ctdluser tempUS;
2456 struct ctdlroom tempQR;
2459 ret = (struct recptypes *) malloc(sizeof(struct recptypes));
2460 if (ret == NULL) return(NULL);
2461 memset(ret, 0, sizeof(struct recptypes));
2464 ret->num_internet = 0;
2469 if (recipients == NULL) {
2472 else if (strlen(recipients) == 0) {
2476 /* Change all valid separator characters to commas */
2477 for (i=0; i<strlen(recipients); ++i) {
2478 if ((recipients[i] == ';') || (recipients[i] == '|')) {
2479 recipients[i] = ',';
2484 num_recps = num_tokens(recipients, ',');
2487 if (num_recps > 0) for (i=0; i<num_recps; ++i) {
2488 extract_token(this_recp, recipients, i, ',');
2490 lprintf(CTDL_DEBUG, "Evaluating recipient #%d <%s>\n", i, this_recp);
2491 mailtype = alias(this_recp);
2492 mailtype = alias(this_recp);
2493 mailtype = alias(this_recp);
2494 for (j=0; j<=strlen(this_recp); ++j) {
2495 if (this_recp[j]=='_') {
2496 this_recp_cooked[j] = ' ';
2499 this_recp_cooked[j] = this_recp[j];
2505 if (!strcasecmp(this_recp, "sysop")) {
2507 strcpy(this_recp, config.c_aideroom);
2508 if (strlen(ret->recp_room) > 0) {
2509 strcat(ret->recp_room, "|");
2511 strcat(ret->recp_room, this_recp);
2513 else if (getuser(&tempUS, this_recp) == 0) {
2515 strcpy(this_recp, tempUS.fullname);
2516 if (strlen(ret->recp_local) > 0) {
2517 strcat(ret->recp_local, "|");
2519 strcat(ret->recp_local, this_recp);
2521 else if (getuser(&tempUS, this_recp_cooked) == 0) {
2523 strcpy(this_recp, tempUS.fullname);
2524 if (strlen(ret->recp_local) > 0) {
2525 strcat(ret->recp_local, "|");
2527 strcat(ret->recp_local, this_recp);
2529 else if ( (!strncasecmp(this_recp, "room_", 5))
2530 && (!getroom(&tempQR, &this_recp_cooked[5])) ) {
2532 if (strlen(ret->recp_room) > 0) {
2533 strcat(ret->recp_room, "|");
2535 strcat(ret->recp_room, &this_recp_cooked[5]);
2543 /* Yes, you're reading this correctly: if the target
2544 * domain points back to the local system or an attached
2545 * Citadel directory, the address is invalid. That's
2546 * because if the address were valid, we would have
2547 * already translated it to a local address by now.
2549 if (IsDirectory(this_recp)) {
2554 ++ret->num_internet;
2555 if (strlen(ret->recp_internet) > 0) {
2556 strcat(ret->recp_internet, "|");
2558 strcat(ret->recp_internet, this_recp);
2563 if (strlen(ret->recp_ignet) > 0) {
2564 strcat(ret->recp_ignet, "|");
2566 strcat(ret->recp_ignet, this_recp);
2574 if (strlen(ret->errormsg) == 0) {
2575 snprintf(append, sizeof append,
2576 "Invalid recipient: %s",
2580 snprintf(append, sizeof append,
2583 if ( (strlen(ret->errormsg) + strlen(append)) < SIZ) {
2584 strcat(ret->errormsg, append);
2588 if (strlen(ret->display_recp) == 0) {
2589 strcpy(append, this_recp);
2592 snprintf(append, sizeof append, ", %s",
2595 if ( (strlen(ret->display_recp)+strlen(append)) < SIZ) {
2596 strcat(ret->display_recp, append);
2601 if ((ret->num_local + ret->num_internet + ret->num_ignet +
2602 ret->num_room + ret->num_error) == 0) {
2604 strcpy(ret->errormsg, "No recipients specified.");
2607 lprintf(CTDL_DEBUG, "validate_recipients()\n");
2608 lprintf(CTDL_DEBUG, " local: %d <%s>\n", ret->num_local, ret->recp_local);
2609 lprintf(CTDL_DEBUG, " room: %d <%s>\n", ret->num_room, ret->recp_room);
2610 lprintf(CTDL_DEBUG, " inet: %d <%s>\n", ret->num_internet, ret->recp_internet);
2611 lprintf(CTDL_DEBUG, " ignet: %d <%s>\n", ret->num_ignet, ret->recp_ignet);
2612 lprintf(CTDL_DEBUG, " error: %d <%s>\n", ret->num_error, ret->errormsg);
2620 * message entry - mode 0 (normal)
2622 void cmd_ent0(char *entargs)
2626 char masquerade_as[SIZ];
2628 int format_type = 0;
2629 char newusername[SIZ];
2630 struct CtdlMessage *msg;
2634 struct recptypes *valid = NULL;
2641 post = extract_int(entargs, 0);
2642 extract(recp, entargs, 1);
2643 anon_flag = extract_int(entargs, 2);
2644 format_type = extract_int(entargs, 3);
2645 extract(subject, entargs, 4);
2646 do_confirm = extract_int(entargs, 6);
2648 /* first check to make sure the request is valid. */
2650 err = CtdlDoIHavePermissionToPostInThisRoom(errmsg, sizeof errmsg);
2652 cprintf("%d %s\n", err, errmsg);
2656 /* Check some other permission type things. */
2659 if (CC->user.axlevel < 6) {
2660 cprintf("%d You don't have permission to masquerade.\n",
2661 ERROR + HIGHER_ACCESS_REQUIRED);
2664 extract(newusername, entargs, 5);
2665 memset(CC->fake_postname, 0, sizeof(CC->fake_postname) );
2666 safestrncpy(CC->fake_postname, newusername,
2667 sizeof(CC->fake_postname) );
2668 cprintf("%d ok\n", CIT_OK);
2671 CC->cs_flags |= CS_POSTING;
2673 /* In the Mail> room we have to behave a little differently --
2674 * make sure the user has specified at least one recipient. Then
2675 * validate the recipient(s).
2677 if ( (CC->room.QRflags & QR_MAILBOX)
2678 && (!strcasecmp(&CC->room.QRname[11], MAILROOM)) ) {
2680 if (CC->user.axlevel < 2) {
2681 strcpy(recp, "sysop");
2684 valid = validate_recipients(recp);
2685 if (valid->num_error > 0) {
2687 ERROR + NO_SUCH_USER, valid->errormsg);
2691 if (valid->num_internet > 0) {
2692 if (CtdlCheckInternetMailPermission(&CC->user)==0) {
2693 cprintf("%d You do not have permission "
2694 "to send Internet mail.\n",
2695 ERROR + HIGHER_ACCESS_REQUIRED);
2701 if ( ( (valid->num_internet + valid->num_ignet) > 0)
2702 && (CC->user.axlevel < 4) ) {
2703 cprintf("%d Higher access required for network mail.\n",
2704 ERROR + HIGHER_ACCESS_REQUIRED);
2709 if ((RESTRICT_INTERNET == 1) && (valid->num_internet > 0)
2710 && ((CC->user.flags & US_INTERNET) == 0)
2711 && (!CC->internal_pgm)) {
2712 cprintf("%d You don't have access to Internet mail.\n",
2713 ERROR + HIGHER_ACCESS_REQUIRED);
2720 /* Is this a room which has anonymous-only or anonymous-option? */
2721 anonymous = MES_NORMAL;
2722 if (CC->room.QRflags & QR_ANONONLY) {
2723 anonymous = MES_ANONONLY;
2725 if (CC->room.QRflags & QR_ANONOPT) {
2726 if (anon_flag == 1) { /* only if the user requested it */
2727 anonymous = MES_ANONOPT;
2731 if ((CC->room.QRflags & QR_MAILBOX) == 0) {
2735 /* If we're only checking the validity of the request, return
2736 * success without creating the message.
2739 cprintf("%d %s\n", CIT_OK,
2740 ((valid != NULL) ? valid->display_recp : "") );
2745 /* Handle author masquerading */
2746 if (CC->fake_postname[0]) {
2747 strcpy(masquerade_as, CC->fake_postname);
2749 else if (CC->fake_username[0]) {
2750 strcpy(masquerade_as, CC->fake_username);
2753 strcpy(masquerade_as, "");
2756 /* Read in the message from the client. */
2758 cprintf("%d send message\n", START_CHAT_MODE);
2760 cprintf("%d send message\n", SEND_LISTING);
2762 msg = CtdlMakeMessage(&CC->user, recp,
2763 CC->room.QRname, anonymous, format_type,
2764 masquerade_as, subject, NULL);
2767 msgnum = CtdlSubmitMsg(msg, valid, "");
2770 cprintf("%ld\n", msgnum);
2772 cprintf("Message accepted.\n");
2775 cprintf("Internal error.\n");
2777 if (msg->cm_fields['E'] != NULL) {
2778 cprintf("%s\n", msg->cm_fields['E']);
2785 CtdlFreeMessage(msg);
2787 CC->fake_postname[0] = '\0';
2795 * API function to delete messages which match a set of criteria
2796 * (returns the actual number of messages deleted)
2798 int CtdlDeleteMessages(char *room_name, /* which room */
2799 long dmsgnum, /* or "0" for any */
2800 char *content_type /* or "" for any */
2804 struct ctdlroom qrbuf;
2805 struct cdbdata *cdbfr;
2806 long *msglist = NULL;
2807 long *dellist = NULL;
2810 int num_deleted = 0;
2812 struct MetaData smi;
2814 lprintf(CTDL_DEBUG, "CtdlDeleteMessages(%s, %ld, %s)\n",
2815 room_name, dmsgnum, content_type);
2817 /* get room record, obtaining a lock... */
2818 if (lgetroom(&qrbuf, room_name) != 0) {
2819 lprintf(CTDL_ERR, "CtdlDeleteMessages(): Room <%s> not found\n",
2821 return (0); /* room not found */
2823 cdbfr = cdb_fetch(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long));
2825 if (cdbfr != NULL) {
2826 msglist = malloc(cdbfr->len);
2827 dellist = malloc(cdbfr->len);
2828 memcpy(msglist, cdbfr->ptr, cdbfr->len);
2829 num_msgs = cdbfr->len / sizeof(long);
2833 for (i = 0; i < num_msgs; ++i) {
2836 /* Set/clear a bit for each criterion */
2838 if ((dmsgnum == 0L) || (msglist[i] == dmsgnum)) {
2839 delete_this |= 0x01;
2841 if (strlen(content_type) == 0) {
2842 delete_this |= 0x02;
2844 GetMetaData(&smi, msglist[i]);
2845 if (!strcasecmp(smi.meta_content_type,
2847 delete_this |= 0x02;
2851 /* Delete message only if all bits are set */
2852 if (delete_this == 0x03) {
2853 dellist[num_deleted++] = msglist[i];
2858 num_msgs = sort_msglist(msglist, num_msgs);
2859 cdb_store(CDB_MSGLISTS, &qrbuf.QRnumber, (int)sizeof(long),
2860 msglist, (int)(num_msgs * sizeof(long)));
2862 qrbuf.QRhighest = msglist[num_msgs - 1];
2866 /* Go through the messages we pulled out of the index, and decrement
2867 * their reference counts by 1. If this is the only room the message
2868 * was in, the reference count will reach zero and the message will
2869 * automatically be deleted from the database. We do this in a
2870 * separate pass because there might be plug-in hooks getting called,
2871 * and we don't want that happening during an S_ROOMS critical
2874 if (num_deleted) for (i=0; i<num_deleted; ++i) {
2875 PerformDeleteHooks(qrbuf.QRname, dellist[i]);
2876 AdjRefCount(dellist[i], -1);
2879 /* Now free the memory we used, and go away. */
2880 if (msglist != NULL) free(msglist);
2881 if (dellist != NULL) free(dellist);
2882 lprintf(CTDL_DEBUG, "%d message(s) deleted.\n", num_deleted);
2883 return (num_deleted);
2889 * Check whether the current user has permission to delete messages from
2890 * the current room (returns 1 for yes, 0 for no)
2892 int CtdlDoIHavePermissionToDeleteMessagesFromThisRoom(void) {
2893 getuser(&CC->user, CC->curr_user);
2894 if ((CC->user.axlevel < 6)
2895 && (CC->user.usernum != CC->room.QRroomaide)
2896 && ((CC->room.QRflags & QR_MAILBOX) == 0)
2897 && (!(CC->internal_pgm))) {
2906 * Delete message from current room
2908 void cmd_dele(char *delstr)
2913 if (CtdlDoIHavePermissionToDeleteMessagesFromThisRoom() == 0) {
2914 cprintf("%d Higher access required.\n",
2915 ERROR + HIGHER_ACCESS_REQUIRED);
2918 delnum = extract_long(delstr, 0);
2920 num_deleted = CtdlDeleteMessages(CC->room.QRname, delnum, "");
2923 cprintf("%d %d message%s deleted.\n", CIT_OK,
2924 num_deleted, ((num_deleted != 1) ? "s" : ""));
2926 cprintf("%d Message %ld not found.\n", ERROR + MESSAGE_NOT_FOUND, delnum);
2932 * Back end API function for moves and deletes
2934 int CtdlCopyMsgToRoom(long msgnum, char *dest) {
2937 err = CtdlSaveMsgPointerInRoom(dest, msgnum,
2938 (SM_VERIFY_GOODNESS | SM_DO_REPL_CHECK) );
2939 if (err != 0) return(err);
2947 * move or copy a message to another room
2949 void cmd_move(char *args)
2953 struct ctdlroom qtemp;
2959 num = extract_long(args, 0);
2960 extract(targ, args, 1);
2961 targ[ROOMNAMELEN - 1] = 0;
2962 is_copy = extract_int(args, 2);
2964 if (getroom(&qtemp, targ) != 0) {
2965 cprintf("%d '%s' does not exist.\n",
2966 ERROR + ROOM_NOT_FOUND, targ);
2970 getuser(&CC->user, CC->curr_user);
2971 CtdlRoomAccess(&qtemp, &CC->user, &ra, NULL);
2973 /* Check for permission to perform this operation.
2974 * Remember: "CC->room" is source, "qtemp" is target.
2978 /* Aides can move/copy */
2979 if (CC->user.axlevel >= 6) permit = 1;
2981 /* Room aides can move/copy */
2982 if (CC->user.usernum == CC->room.QRroomaide) permit = 1;
2984 /* Permit move/copy from personal rooms */
2985 if ((CC->room.QRflags & QR_MAILBOX)
2986 && (qtemp.QRflags & QR_MAILBOX)) permit = 1;
2988 /* Permit only copy from public to personal room */
2990 && (!(CC->room.QRflags & QR_MAILBOX))
2991 && (qtemp.QRflags & QR_MAILBOX)) permit = 1;
2993 /* User must have access to target room */
2994 if (!(ra & UA_KNOWN)) permit = 0;
2997 cprintf("%d Higher access required.\n",
2998 ERROR + HIGHER_ACCESS_REQUIRED);
3002 err = CtdlCopyMsgToRoom(num, targ);
3004 cprintf("%d Cannot store message in %s: error %d\n",
3009 /* Now delete the message from the source room,
3010 * if this is a 'move' rather than a 'copy' operation.
3013 CtdlDeleteMessages(CC->room.QRname, num, "");
3016 cprintf("%d Message %s.\n", CIT_OK, (is_copy ? "copied" : "moved") );
3022 * GetMetaData() - Get the supplementary record for a message
3024 void GetMetaData(struct MetaData *smibuf, long msgnum)
3027 struct cdbdata *cdbsmi;
3030 memset(smibuf, 0, sizeof(struct MetaData));
3031 smibuf->meta_msgnum = msgnum;
3032 smibuf->meta_refcount = 1; /* Default reference count is 1 */
3034 /* Use the negative of the message number for its supp record index */
3035 TheIndex = (0L - msgnum);
3037 cdbsmi = cdb_fetch(CDB_MSGMAIN, &TheIndex, sizeof(long));
3038 if (cdbsmi == NULL) {
3039 return; /* record not found; go with defaults */
3041 memcpy(smibuf, cdbsmi->ptr,
3042 ((cdbsmi->len > sizeof(struct MetaData)) ?
3043 sizeof(struct MetaData) : cdbsmi->len));
3050 * PutMetaData() - (re)write supplementary record for a message
3052 void PutMetaData(struct MetaData *smibuf)
3056 /* Use the negative of the message number for the metadata db index */
3057 TheIndex = (0L - smibuf->meta_msgnum);
3059 lprintf(CTDL_DEBUG, "PutMetaData(%ld) - ref count is %d\n",
3060 smibuf->meta_msgnum, smibuf->meta_refcount);
3062 cdb_store(CDB_MSGMAIN,
3063 &TheIndex, (int)sizeof(long),
3064 smibuf, (int)sizeof(struct MetaData));
3069 * AdjRefCount - change the reference count for a message;
3070 * delete the message if it reaches zero
3072 void AdjRefCount(long msgnum, int incr)
3075 struct MetaData smi;
3078 /* This is a *tight* critical section; please keep it that way, as
3079 * it may get called while nested in other critical sections.
3080 * Complicating this any further will surely cause deadlock!
3082 begin_critical_section(S_SUPPMSGMAIN);
3083 GetMetaData(&smi, msgnum);
3084 lprintf(CTDL_DEBUG, "Ref count for message <%ld> before write is <%d>\n",
3085 msgnum, smi.meta_refcount);
3086 smi.meta_refcount += incr;
3088 end_critical_section(S_SUPPMSGMAIN);
3089 lprintf(CTDL_DEBUG, "Ref count for message <%ld> after write is <%d>\n",
3090 msgnum, smi.meta_refcount);
3092 /* If the reference count is now zero, delete the message
3093 * (and its supplementary record as well).
3095 if (smi.meta_refcount == 0) {
3096 lprintf(CTDL_DEBUG, "Deleting message <%ld>\n", msgnum);
3098 cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long));
3099 cdb_delete(CDB_BIGMSGS, &delnum, (int)sizeof(long));
3101 /* We have to delete the metadata record too! */
3102 delnum = (0L - msgnum);
3103 cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long));
3108 * Write a generic object to this room
3110 * Note: this could be much more efficient. Right now we use two temporary
3111 * files, and still pull the message into memory as with all others.
3113 void CtdlWriteObject(char *req_room, /* Room to stuff it in */
3114 char *content_type, /* MIME type of this object */
3115 char *tempfilename, /* Where to fetch it from */
3116 struct ctdluser *is_mailbox, /* Mailbox room? */
3117 int is_binary, /* Is encoding necessary? */
3118 int is_unique, /* Del others of this type? */
3119 unsigned int flags /* Internal save flags */
3124 struct ctdlroom qrbuf;
3125 char roomname[ROOMNAMELEN];
3126 struct CtdlMessage *msg;
3128 char *raw_message = NULL;
3129 char *encoded_message = NULL;
3130 off_t raw_length = 0;
3132 if (is_mailbox != NULL)
3133 MailboxName(roomname, sizeof roomname, is_mailbox, req_room);
3135 safestrncpy(roomname, req_room, sizeof(roomname));
3136 lprintf(CTDL_DEBUG, "CtdlWriteObject() to <%s> (flags=%d)\n", roomname, flags);
3139 fp = fopen(tempfilename, "rb");
3141 lprintf(CTDL_CRIT, "Cannot open %s: %s\n",
3142 tempfilename, strerror(errno));
3145 fseek(fp, 0L, SEEK_END);
3146 raw_length = ftell(fp);
3148 lprintf(CTDL_DEBUG, "Raw length is %ld\n", (long)raw_length);
3150 raw_message = malloc((size_t)raw_length + 2);
3151 fread(raw_message, (size_t)raw_length, 1, fp);
3155 encoded_message = malloc((size_t)
3156 (((raw_length * 134) / 100) + 4096 ) );
3159 encoded_message = malloc((size_t)(raw_length + 4096));
3162 sprintf(encoded_message, "Content-type: %s\n", content_type);
3165 sprintf(&encoded_message[strlen(encoded_message)],
3166 "Content-transfer-encoding: base64\n\n"
3170 sprintf(&encoded_message[strlen(encoded_message)],
3171 "Content-transfer-encoding: 7bit\n\n"
3177 &encoded_message[strlen(encoded_message)],
3183 raw_message[raw_length] = 0;
3185 &encoded_message[strlen(encoded_message)],
3193 lprintf(CTDL_DEBUG, "Allocating\n");
3194 msg = malloc(sizeof(struct CtdlMessage));
3195 memset(msg, 0, sizeof(struct CtdlMessage));
3196 msg->cm_magic = CTDLMESSAGE_MAGIC;
3197 msg->cm_anon_type = MES_NORMAL;
3198 msg->cm_format_type = 4;
3199 msg->cm_fields['A'] = strdup(CC->user.fullname);
3200 msg->cm_fields['O'] = strdup(req_room);
3201 msg->cm_fields['N'] = strdup(config.c_nodename);
3202 msg->cm_fields['H'] = strdup(config.c_humannode);
3203 msg->cm_flags = flags;
3205 msg->cm_fields['M'] = encoded_message;
3207 /* Create the requested room if we have to. */
3208 if (getroom(&qrbuf, roomname) != 0) {
3209 create_room(roomname,
3210 ( (is_mailbox != NULL) ? 5 : 3 ),
3211 "", 0, 1, 0, VIEW_BBS);
3213 /* If the caller specified this object as unique, delete all
3214 * other objects of this type that are currently in the room.
3217 lprintf(CTDL_DEBUG, "Deleted %d other msgs of this type\n",
3218 CtdlDeleteMessages(roomname, 0L, content_type));
3220 /* Now write the data */
3221 CtdlSubmitMsg(msg, NULL, roomname);
3222 CtdlFreeMessage(msg);
3230 void CtdlGetSysConfigBackend(long msgnum, void *userdata) {
3231 config_msgnum = msgnum;
3235 char *CtdlGetSysConfig(char *sysconfname) {
3236 char hold_rm[ROOMNAMELEN];
3239 struct CtdlMessage *msg;
3242 strcpy(hold_rm, CC->room.QRname);
3243 if (getroom(&CC->room, SYSCONFIGROOM) != 0) {
3244 getroom(&CC->room, hold_rm);
3249 /* We want the last (and probably only) config in this room */
3250 begin_critical_section(S_CONFIG);
3251 config_msgnum = (-1L);
3252 CtdlForEachMessage(MSGS_LAST, 1, sysconfname, NULL,
3253 CtdlGetSysConfigBackend, NULL);
3254 msgnum = config_msgnum;
3255 end_critical_section(S_CONFIG);
3261 msg = CtdlFetchMessage(msgnum, 1);
3263 conf = strdup(msg->cm_fields['M']);
3264 CtdlFreeMessage(msg);
3271 getroom(&CC->room, hold_rm);
3273 if (conf != NULL) do {
3274 extract_token(buf, conf, 0, '\n');
3275 strcpy(conf, &conf[strlen(buf)+1]);
3276 } while ( (strlen(conf)>0) && (strlen(buf)>0) );
3281 void CtdlPutSysConfig(char *sysconfname, char *sysconfdata) {
3282 char temp[PATH_MAX];
3285 strcpy(temp, tmpnam(NULL));
3287 fp = fopen(temp, "w");
3288 if (fp == NULL) return;
3289 fprintf(fp, "%s", sysconfdata);
3292 /* this handy API function does all the work for us */
3293 CtdlWriteObject(SYSCONFIGROOM, sysconfname, temp, NULL, 0, 1, 0);
3299 * Determine whether a given Internet address belongs to the current user
3301 int CtdlIsMe(char *addr) {
3302 struct recptypes *recp;
3305 recp = validate_recipients(addr);
3306 if (recp == NULL) return(0);
3308 if (recp->num_local == 0) {
3313 for (i=0; i<recp->num_local; ++i) {
3314 extract(addr, recp->recp_local, i);
3315 if (!strcasecmp(addr, CC->user.fullname)) {
3327 * Citadel protocol command to do the same
3329 void cmd_isme(char *argbuf) {
3332 if (CtdlAccessCheck(ac_logged_in)) return;
3333 extract(addr, argbuf, 0);
3335 if (CtdlIsMe(addr)) {
3336 cprintf("%d %s\n", CIT_OK, addr);
3339 cprintf("%d Not you.\n", ERROR + ILLEGAL_VALUE);