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 /* advance past header fields */
1963 mptr = msg->cm_fields['M'];
1966 if (!strncasecmp(mptr, "Content-type: ", 14)) {
1967 safestrncpy(content_type, mptr,
1968 sizeof(content_type));
1969 strcpy(content_type, &content_type[14]);
1970 for (a = 0; a < strlen(content_type); ++a)
1971 if ((content_type[a] == ';')
1972 || (content_type[a] == ' ')
1973 || (content_type[a] == 13)
1974 || (content_type[a] == 10))
1975 content_type[a] = 0;
1982 /* Goto the correct room */
1983 lprintf(CTDL_DEBUG, "Selected room %s\n", (recps) ? CC->room.QRname : SENTITEMS);
1984 strcpy(hold_rm, CC->room.QRname);
1985 strcpy(actual_rm, CC->room.QRname);
1986 if (recps != NULL) {
1987 strcpy(actual_rm, SENTITEMS);
1990 /* If the user is a twit, move to the twit room for posting */
1991 lprintf(CTDL_DEBUG, "Handling twit stuff: %s\n",
1992 (CC->user.axlevel == 2) ? config.c_twitroom : "OK");
1994 if (CC->user.axlevel == 2) {
1995 strcpy(hold_rm, actual_rm);
1996 strcpy(actual_rm, config.c_twitroom);
2000 /* ...or if this message is destined for Aide> then go there. */
2001 if (strlen(force_room) > 0) {
2002 strcpy(actual_rm, force_room);
2005 lprintf(CTDL_DEBUG, "Final selection: %s\n", actual_rm);
2006 if (strcasecmp(actual_rm, CC->room.QRname)) {
2007 getroom(&CC->room, actual_rm);
2011 * If this message has no O (room) field, generate one.
2013 if (msg->cm_fields['O'] == NULL) {
2014 msg->cm_fields['O'] = strdup(CC->room.QRname);
2017 /* Perform "before save" hooks (aborting if any return nonzero) */
2018 lprintf(CTDL_DEBUG, "Performing before-save hooks\n");
2019 if (PerformMessageHooks(msg, EVT_BEFORESAVE) > 0) return(-3);
2021 /* If this message has an Exclusive ID, perform replication checks */
2022 lprintf(CTDL_DEBUG, "Performing replication checks\n");
2023 if (ReplicationChecks(msg) > 0) return(-4);
2025 /* Save it to disk */
2026 lprintf(CTDL_DEBUG, "Saving to disk\n");
2027 newmsgid = send_message(msg);
2028 if (newmsgid <= 0L) return(-5);
2030 /* Write a supplemental message info record. This doesn't have to
2031 * be a critical section because nobody else knows about this message
2034 lprintf(CTDL_DEBUG, "Creating MetaData record\n");
2035 memset(&smi, 0, sizeof(struct MetaData));
2036 smi.meta_msgnum = newmsgid;
2037 smi.meta_refcount = 0;
2038 safestrncpy(smi.meta_content_type, content_type, sizeof smi.meta_content_type);
2041 /* Now figure out where to store the pointers */
2042 lprintf(CTDL_DEBUG, "Storing pointers\n");
2044 /* If this is being done by the networker delivering a private
2045 * message, we want to BYPASS saving the sender's copy (because there
2046 * is no local sender; it would otherwise go to the Trashcan).
2048 if ((!CC->internal_pgm) || (recps == NULL)) {
2049 if (CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 0) != 0) {
2050 lprintf(CTDL_ERR, "ERROR saving message pointer!\n");
2051 CtdlSaveMsgPointerInRoom(config.c_aideroom, newmsgid, 0);
2055 /* For internet mail, drop a copy in the outbound queue room */
2057 if (recps->num_internet > 0) {
2058 CtdlSaveMsgPointerInRoom(SMTP_SPOOLOUT_ROOM, newmsgid, 0);
2061 /* If other rooms are specified, drop them there too. */
2063 if (recps->num_room > 0)
2064 for (i=0; i<num_tokens(recps->recp_room, '|'); ++i) {
2065 extract(recipient, recps->recp_room, i);
2066 lprintf(CTDL_DEBUG, "Delivering to local room <%s>\n", recipient);
2067 CtdlSaveMsgPointerInRoom(recipient, newmsgid, 0);
2070 /* Bump this user's messages posted counter. */
2071 lprintf(CTDL_DEBUG, "Updating user\n");
2072 lgetuser(&CC->user, CC->curr_user);
2073 CC->user.posted = CC->user.posted + 1;
2074 lputuser(&CC->user);
2076 /* If this is private, local mail, make a copy in the
2077 * recipient's mailbox and bump the reference count.
2080 if (recps->num_local > 0)
2081 for (i=0; i<num_tokens(recps->recp_local, '|'); ++i) {
2082 extract(recipient, recps->recp_local, i);
2083 lprintf(CTDL_DEBUG, "Delivering private local mail to <%s>\n",
2085 if (getuser(&userbuf, recipient) == 0) {
2086 MailboxName(actual_rm, sizeof actual_rm, &userbuf, MAILROOM);
2087 CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 0);
2088 BumpNewMailCounter(userbuf.usernum);
2091 lprintf(CTDL_DEBUG, "No user <%s>\n", recipient);
2092 CtdlSaveMsgPointerInRoom(config.c_aideroom, newmsgid, 0);
2096 /* Perform "after save" hooks */
2097 lprintf(CTDL_DEBUG, "Performing after-save hooks\n");
2098 PerformMessageHooks(msg, EVT_AFTERSAVE);
2100 /* For IGnet mail, we have to save a new copy into the spooler for
2101 * each recipient, with the R and D fields set to the recipient and
2102 * destination-node. This has two ugly side effects: all other
2103 * recipients end up being unlisted in this recipient's copy of the
2104 * message, and it has to deliver multiple messages to the same
2105 * node. We'll revisit this again in a year or so when everyone has
2106 * a network spool receiver that can handle the new style messages.
2109 if (recps->num_ignet > 0)
2110 for (i=0; i<num_tokens(recps->recp_ignet, '|'); ++i) {
2111 extract(recipient, recps->recp_ignet, i);
2113 hold_R = msg->cm_fields['R'];
2114 hold_D = msg->cm_fields['D'];
2115 msg->cm_fields['R'] = malloc(SIZ);
2116 msg->cm_fields['D'] = malloc(SIZ);
2117 extract_token(msg->cm_fields['R'], recipient, 0, '@');
2118 extract_token(msg->cm_fields['D'], recipient, 1, '@');
2120 serialize_message(&smr, msg);
2122 snprintf(aaa, sizeof aaa,
2123 "./network/spoolin/netmail.%04lx.%04x.%04x",
2124 (long) getpid(), CC->cs_pid, ++seqnum);
2125 network_fp = fopen(aaa, "wb+");
2126 if (network_fp != NULL) {
2127 fwrite(smr.ser, smr.len, 1, network_fp);
2133 free(msg->cm_fields['R']);
2134 free(msg->cm_fields['D']);
2135 msg->cm_fields['R'] = hold_R;
2136 msg->cm_fields['D'] = hold_D;
2139 /* Go back to the room we started from */
2140 lprintf(CTDL_DEBUG, "Returning to original room %s\n", hold_rm);
2141 if (strcasecmp(hold_rm, CC->room.QRname))
2142 getroom(&CC->room, hold_rm);
2144 /* For internet mail, generate delivery instructions.
2145 * Yes, this is recursive. Deal with it. Infinite recursion does
2146 * not happen because the delivery instructions message does not
2147 * contain a recipient.
2150 if (recps->num_internet > 0) {
2151 lprintf(CTDL_DEBUG, "Generating delivery instructions\n");
2152 instr = malloc(SIZ * 2);
2153 snprintf(instr, SIZ * 2,
2154 "Content-type: %s\n\nmsgid|%ld\nsubmitted|%ld\n"
2156 SPOOLMIME, newmsgid, (long)time(NULL),
2157 msg->cm_fields['A'], msg->cm_fields['N']
2160 for (i=0; i<num_tokens(recps->recp_internet, '|'); ++i) {
2161 size_t tmp = strlen(instr);
2162 extract(recipient, recps->recp_internet, i);
2163 snprintf(&instr[tmp], SIZ * 2 - tmp,
2164 "remote|%s|0||\n", recipient);
2167 imsg = malloc(sizeof(struct CtdlMessage));
2168 memset(imsg, 0, sizeof(struct CtdlMessage));
2169 imsg->cm_magic = CTDLMESSAGE_MAGIC;
2170 imsg->cm_anon_type = MES_NORMAL;
2171 imsg->cm_format_type = FMT_RFC822;
2172 imsg->cm_fields['A'] = strdup("Citadel");
2173 imsg->cm_fields['M'] = instr;
2174 CtdlSubmitMsg(imsg, NULL, SMTP_SPOOLOUT_ROOM);
2175 CtdlFreeMessage(imsg);
2184 * Convenience function for generating small administrative messages.
2186 void quickie_message(char *from, char *to, char *room, char *text,
2187 int format_type, char *subject)
2189 struct CtdlMessage *msg;
2190 struct recptypes *recp = NULL;
2192 msg = malloc(sizeof(struct CtdlMessage));
2193 memset(msg, 0, sizeof(struct CtdlMessage));
2194 msg->cm_magic = CTDLMESSAGE_MAGIC;
2195 msg->cm_anon_type = MES_NORMAL;
2196 msg->cm_format_type = format_type;
2197 msg->cm_fields['A'] = strdup(from);
2198 if (room != NULL) msg->cm_fields['O'] = strdup(room);
2199 msg->cm_fields['N'] = strdup(NODENAME);
2201 msg->cm_fields['R'] = strdup(to);
2202 recp = validate_recipients(to);
2204 if (subject != NULL) {
2205 msg->cm_fields['U'] = strdup(subject);
2207 msg->cm_fields['M'] = strdup(text);
2209 CtdlSubmitMsg(msg, recp, room);
2210 CtdlFreeMessage(msg);
2211 if (recp != NULL) free(recp);
2217 * Back end function used by CtdlMakeMessage() and similar functions
2219 char *CtdlReadMessageBody(char *terminator, /* token signalling EOT */
2220 size_t maxlen, /* maximum message length */
2221 char *exist, /* if non-null, append to it;
2222 exist is ALWAYS freed */
2223 int crlf /* CRLF newlines instead of LF */
2227 size_t message_len = 0;
2228 size_t buffer_len = 0;
2234 if (exist == NULL) {
2241 message_len = strlen(exist);
2242 buffer_len = message_len + 4096;
2243 m = realloc(exist, buffer_len);
2250 /* flush the input if we have nowhere to store it */
2255 /* read in the lines of message text one by one */
2257 if (client_getln(buf, sizeof buf) < 1) finished = 1;
2258 if (!strcmp(buf, terminator)) finished = 1;
2260 strcat(buf, "\r\n");
2266 if ( (!flushing) && (!finished) ) {
2267 /* Measure the line */
2268 linelen = strlen(buf);
2270 /* augment the buffer if we have to */
2271 if ((message_len + linelen) >= buffer_len) {
2272 ptr = realloc(m, (buffer_len * 2) );
2273 if (ptr == NULL) { /* flush if can't allocate */
2276 buffer_len = (buffer_len * 2);
2278 lprintf(CTDL_DEBUG, "buffer_len is now %ld\n", (long)buffer_len);
2282 /* Add the new line to the buffer. NOTE: this loop must avoid
2283 * using functions like strcat() and strlen() because they
2284 * traverse the entire buffer upon every call, and doing that
2285 * for a multi-megabyte message slows it down beyond usability.
2287 strcpy(&m[message_len], buf);
2288 message_len += linelen;
2291 /* if we've hit the max msg length, flush the rest */
2292 if (message_len >= maxlen) flushing = 1;
2294 } while (!finished);
2302 * Build a binary message to be saved on disk.
2303 * (NOTE: if you supply 'preformatted_text', the buffer you give it
2304 * will become part of the message. This means you are no longer
2305 * responsible for managing that memory -- it will be freed along with
2306 * the rest of the fields when CtdlFreeMessage() is called.)
2309 struct CtdlMessage *CtdlMakeMessage(
2310 struct ctdluser *author, /* author's user structure */
2311 char *recipient, /* NULL if it's not mail */
2312 char *room, /* room where it's going */
2313 int type, /* see MES_ types in header file */
2314 int format_type, /* variformat, plain text, MIME... */
2315 char *fake_name, /* who we're masquerading as */
2316 char *subject, /* Subject (optional) */
2317 char *preformatted_text /* ...or NULL to read text from client */
2319 char dest_node[SIZ];
2321 struct CtdlMessage *msg;
2323 msg = malloc(sizeof(struct CtdlMessage));
2324 memset(msg, 0, sizeof(struct CtdlMessage));
2325 msg->cm_magic = CTDLMESSAGE_MAGIC;
2326 msg->cm_anon_type = type;
2327 msg->cm_format_type = format_type;
2329 /* Don't confuse the poor folks if it's not routed mail. */
2330 strcpy(dest_node, "");
2334 snprintf(buf, sizeof buf, "cit%ld", author->usernum); /* Path */
2335 msg->cm_fields['P'] = strdup(buf);
2337 snprintf(buf, sizeof buf, "%ld", (long)time(NULL)); /* timestamp */
2338 msg->cm_fields['T'] = strdup(buf);
2340 if (fake_name[0]) /* author */
2341 msg->cm_fields['A'] = strdup(fake_name);
2343 msg->cm_fields['A'] = strdup(author->fullname);
2345 if (CC->room.QRflags & QR_MAILBOX) { /* room */
2346 msg->cm_fields['O'] = strdup(&CC->room.QRname[11]);
2349 msg->cm_fields['O'] = strdup(CC->room.QRname);
2352 msg->cm_fields['N'] = strdup(NODENAME); /* nodename */
2353 msg->cm_fields['H'] = strdup(HUMANNODE); /* hnodename */
2355 if (recipient[0] != 0) {
2356 msg->cm_fields['R'] = strdup(recipient);
2358 if (dest_node[0] != 0) {
2359 msg->cm_fields['D'] = strdup(dest_node);
2362 if ( (author == &CC->user) && (strlen(CC->cs_inet_email) > 0) ) {
2363 msg->cm_fields['F'] = strdup(CC->cs_inet_email);
2366 if (subject != NULL) {
2368 if (strlen(subject) > 0) {
2369 msg->cm_fields['U'] = strdup(subject);
2373 if (preformatted_text != NULL) {
2374 msg->cm_fields['M'] = preformatted_text;
2377 msg->cm_fields['M'] = CtdlReadMessageBody("000",
2378 config.c_maxmsglen, NULL, 0);
2386 * Check to see whether we have permission to post a message in the current
2387 * room. Returns a *CITADEL ERROR CODE* and puts a message in errmsgbuf, or
2388 * returns 0 on success.
2390 int CtdlDoIHavePermissionToPostInThisRoom(char *errmsgbuf, size_t n) {
2392 if (!(CC->logged_in)) {
2393 snprintf(errmsgbuf, n, "Not logged in.");
2394 return (ERROR + NOT_LOGGED_IN);
2397 if ((CC->user.axlevel < 2)
2398 && ((CC->room.QRflags & QR_MAILBOX) == 0)) {
2399 snprintf(errmsgbuf, n, "Need to be validated to enter "
2400 "(except in %s> to sysop)", MAILROOM);
2401 return (ERROR + HIGHER_ACCESS_REQUIRED);
2404 if ((CC->user.axlevel < 4)
2405 && (CC->room.QRflags & QR_NETWORK)) {
2406 snprintf(errmsgbuf, n, "Need net privileges to enter here.");
2407 return (ERROR + HIGHER_ACCESS_REQUIRED);
2410 if ((CC->user.axlevel < 6)
2411 && (CC->room.QRflags & QR_READONLY)) {
2412 snprintf(errmsgbuf, n, "Sorry, this is a read-only room.");
2413 return (ERROR + HIGHER_ACCESS_REQUIRED);
2416 strcpy(errmsgbuf, "Ok");
2422 * Check to see if the specified user has Internet mail permission
2423 * (returns nonzero if permission is granted)
2425 int CtdlCheckInternetMailPermission(struct ctdluser *who) {
2427 /* Do not allow twits to send Internet mail */
2428 if (who->axlevel <= 2) return(0);
2430 /* Globally enabled? */
2431 if (config.c_restrict == 0) return(1);
2433 /* User flagged ok? */
2434 if (who->flags & US_INTERNET) return(2);
2436 /* Aide level access? */
2437 if (who->axlevel >= 6) return(3);
2439 /* No mail for you! */
2446 * Validate recipients, count delivery types and errors, and handle aliasing
2447 * FIXME check for dupes!!!!!
2449 struct recptypes *validate_recipients(char *recipients) {
2450 struct recptypes *ret;
2451 char this_recp[SIZ];
2452 char this_recp_cooked[SIZ];
2458 struct ctdluser tempUS;
2459 struct ctdlroom tempQR;
2462 ret = (struct recptypes *) malloc(sizeof(struct recptypes));
2463 if (ret == NULL) return(NULL);
2464 memset(ret, 0, sizeof(struct recptypes));
2467 ret->num_internet = 0;
2472 if (recipients == NULL) {
2475 else if (strlen(recipients) == 0) {
2479 /* Change all valid separator characters to commas */
2480 for (i=0; i<strlen(recipients); ++i) {
2481 if ((recipients[i] == ';') || (recipients[i] == '|')) {
2482 recipients[i] = ',';
2487 num_recps = num_tokens(recipients, ',');
2490 if (num_recps > 0) for (i=0; i<num_recps; ++i) {
2491 extract_token(this_recp, recipients, i, ',');
2493 lprintf(CTDL_DEBUG, "Evaluating recipient #%d <%s>\n", i, this_recp);
2494 mailtype = alias(this_recp);
2495 mailtype = alias(this_recp);
2496 mailtype = alias(this_recp);
2497 for (j=0; j<=strlen(this_recp); ++j) {
2498 if (this_recp[j]=='_') {
2499 this_recp_cooked[j] = ' ';
2502 this_recp_cooked[j] = this_recp[j];
2508 if (!strcasecmp(this_recp, "sysop")) {
2510 strcpy(this_recp, config.c_aideroom);
2511 if (strlen(ret->recp_room) > 0) {
2512 strcat(ret->recp_room, "|");
2514 strcat(ret->recp_room, this_recp);
2516 else if (getuser(&tempUS, this_recp) == 0) {
2518 strcpy(this_recp, tempUS.fullname);
2519 if (strlen(ret->recp_local) > 0) {
2520 strcat(ret->recp_local, "|");
2522 strcat(ret->recp_local, this_recp);
2524 else if (getuser(&tempUS, this_recp_cooked) == 0) {
2526 strcpy(this_recp, tempUS.fullname);
2527 if (strlen(ret->recp_local) > 0) {
2528 strcat(ret->recp_local, "|");
2530 strcat(ret->recp_local, this_recp);
2532 else if ( (!strncasecmp(this_recp, "room_", 5))
2533 && (!getroom(&tempQR, &this_recp_cooked[5])) ) {
2535 if (strlen(ret->recp_room) > 0) {
2536 strcat(ret->recp_room, "|");
2538 strcat(ret->recp_room, &this_recp_cooked[5]);
2546 /* Yes, you're reading this correctly: if the target
2547 * domain points back to the local system or an attached
2548 * Citadel directory, the address is invalid. That's
2549 * because if the address were valid, we would have
2550 * already translated it to a local address by now.
2552 if (IsDirectory(this_recp)) {
2557 ++ret->num_internet;
2558 if (strlen(ret->recp_internet) > 0) {
2559 strcat(ret->recp_internet, "|");
2561 strcat(ret->recp_internet, this_recp);
2566 if (strlen(ret->recp_ignet) > 0) {
2567 strcat(ret->recp_ignet, "|");
2569 strcat(ret->recp_ignet, this_recp);
2577 if (strlen(ret->errormsg) == 0) {
2578 snprintf(append, sizeof append,
2579 "Invalid recipient: %s",
2583 snprintf(append, sizeof append,
2586 if ( (strlen(ret->errormsg) + strlen(append)) < SIZ) {
2587 strcat(ret->errormsg, append);
2591 if (strlen(ret->display_recp) == 0) {
2592 strcpy(append, this_recp);
2595 snprintf(append, sizeof append, ", %s",
2598 if ( (strlen(ret->display_recp)+strlen(append)) < SIZ) {
2599 strcat(ret->display_recp, append);
2604 if ((ret->num_local + ret->num_internet + ret->num_ignet +
2605 ret->num_room + ret->num_error) == 0) {
2607 strcpy(ret->errormsg, "No recipients specified.");
2610 lprintf(CTDL_DEBUG, "validate_recipients()\n");
2611 lprintf(CTDL_DEBUG, " local: %d <%s>\n", ret->num_local, ret->recp_local);
2612 lprintf(CTDL_DEBUG, " room: %d <%s>\n", ret->num_room, ret->recp_room);
2613 lprintf(CTDL_DEBUG, " inet: %d <%s>\n", ret->num_internet, ret->recp_internet);
2614 lprintf(CTDL_DEBUG, " ignet: %d <%s>\n", ret->num_ignet, ret->recp_ignet);
2615 lprintf(CTDL_DEBUG, " error: %d <%s>\n", ret->num_error, ret->errormsg);
2623 * message entry - mode 0 (normal)
2625 void cmd_ent0(char *entargs)
2629 char masquerade_as[SIZ];
2631 int format_type = 0;
2632 char newusername[SIZ];
2633 struct CtdlMessage *msg;
2637 struct recptypes *valid = NULL;
2644 post = extract_int(entargs, 0);
2645 extract(recp, entargs, 1);
2646 anon_flag = extract_int(entargs, 2);
2647 format_type = extract_int(entargs, 3);
2648 extract(subject, entargs, 4);
2649 do_confirm = extract_int(entargs, 6);
2651 /* first check to make sure the request is valid. */
2653 err = CtdlDoIHavePermissionToPostInThisRoom(errmsg, sizeof errmsg);
2655 cprintf("%d %s\n", err, errmsg);
2659 /* Check some other permission type things. */
2662 if (CC->user.axlevel < 6) {
2663 cprintf("%d You don't have permission to masquerade.\n",
2664 ERROR + HIGHER_ACCESS_REQUIRED);
2667 extract(newusername, entargs, 5);
2668 memset(CC->fake_postname, 0, sizeof(CC->fake_postname) );
2669 safestrncpy(CC->fake_postname, newusername,
2670 sizeof(CC->fake_postname) );
2671 cprintf("%d ok\n", CIT_OK);
2674 CC->cs_flags |= CS_POSTING;
2676 /* In the Mail> room we have to behave a little differently --
2677 * make sure the user has specified at least one recipient. Then
2678 * validate the recipient(s).
2680 if ( (CC->room.QRflags & QR_MAILBOX)
2681 && (!strcasecmp(&CC->room.QRname[11], MAILROOM)) ) {
2683 if (CC->user.axlevel < 2) {
2684 strcpy(recp, "sysop");
2687 valid = validate_recipients(recp);
2688 if (valid->num_error > 0) {
2690 ERROR + NO_SUCH_USER, valid->errormsg);
2694 if (valid->num_internet > 0) {
2695 if (CtdlCheckInternetMailPermission(&CC->user)==0) {
2696 cprintf("%d You do not have permission "
2697 "to send Internet mail.\n",
2698 ERROR + HIGHER_ACCESS_REQUIRED);
2704 if ( ( (valid->num_internet + valid->num_ignet) > 0)
2705 && (CC->user.axlevel < 4) ) {
2706 cprintf("%d Higher access required for network mail.\n",
2707 ERROR + HIGHER_ACCESS_REQUIRED);
2712 if ((RESTRICT_INTERNET == 1) && (valid->num_internet > 0)
2713 && ((CC->user.flags & US_INTERNET) == 0)
2714 && (!CC->internal_pgm)) {
2715 cprintf("%d You don't have access to Internet mail.\n",
2716 ERROR + HIGHER_ACCESS_REQUIRED);
2723 /* Is this a room which has anonymous-only or anonymous-option? */
2724 anonymous = MES_NORMAL;
2725 if (CC->room.QRflags & QR_ANONONLY) {
2726 anonymous = MES_ANONONLY;
2728 if (CC->room.QRflags & QR_ANONOPT) {
2729 if (anon_flag == 1) { /* only if the user requested it */
2730 anonymous = MES_ANONOPT;
2734 if ((CC->room.QRflags & QR_MAILBOX) == 0) {
2738 /* If we're only checking the validity of the request, return
2739 * success without creating the message.
2742 cprintf("%d %s\n", CIT_OK,
2743 ((valid != NULL) ? valid->display_recp : "") );
2748 /* Handle author masquerading */
2749 if (CC->fake_postname[0]) {
2750 strcpy(masquerade_as, CC->fake_postname);
2752 else if (CC->fake_username[0]) {
2753 strcpy(masquerade_as, CC->fake_username);
2756 strcpy(masquerade_as, "");
2759 /* Read in the message from the client. */
2761 cprintf("%d send message\n", START_CHAT_MODE);
2763 cprintf("%d send message\n", SEND_LISTING);
2765 msg = CtdlMakeMessage(&CC->user, recp,
2766 CC->room.QRname, anonymous, format_type,
2767 masquerade_as, subject, NULL);
2770 msgnum = CtdlSubmitMsg(msg, valid, "");
2773 cprintf("%ld\n", msgnum);
2775 cprintf("Message accepted.\n");
2778 cprintf("Internal error.\n");
2780 if (msg->cm_fields['E'] != NULL) {
2781 cprintf("%s\n", msg->cm_fields['E']);
2788 CtdlFreeMessage(msg);
2790 CC->fake_postname[0] = '\0';
2798 * API function to delete messages which match a set of criteria
2799 * (returns the actual number of messages deleted)
2801 int CtdlDeleteMessages(char *room_name, /* which room */
2802 long dmsgnum, /* or "0" for any */
2803 char *content_type /* or "" for any */
2807 struct ctdlroom qrbuf;
2808 struct cdbdata *cdbfr;
2809 long *msglist = NULL;
2810 long *dellist = NULL;
2813 int num_deleted = 0;
2815 struct MetaData smi;
2817 lprintf(CTDL_DEBUG, "CtdlDeleteMessages(%s, %ld, %s)\n",
2818 room_name, dmsgnum, content_type);
2820 /* get room record, obtaining a lock... */
2821 if (lgetroom(&qrbuf, room_name) != 0) {
2822 lprintf(CTDL_ERR, "CtdlDeleteMessages(): Room <%s> not found\n",
2824 return (0); /* room not found */
2826 cdbfr = cdb_fetch(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long));
2828 if (cdbfr != NULL) {
2829 msglist = malloc(cdbfr->len);
2830 dellist = malloc(cdbfr->len);
2831 memcpy(msglist, cdbfr->ptr, cdbfr->len);
2832 num_msgs = cdbfr->len / sizeof(long);
2836 for (i = 0; i < num_msgs; ++i) {
2839 /* Set/clear a bit for each criterion */
2841 if ((dmsgnum == 0L) || (msglist[i] == dmsgnum)) {
2842 delete_this |= 0x01;
2844 if (strlen(content_type) == 0) {
2845 delete_this |= 0x02;
2847 GetMetaData(&smi, msglist[i]);
2848 if (!strcasecmp(smi.meta_content_type,
2850 delete_this |= 0x02;
2854 /* Delete message only if all bits are set */
2855 if (delete_this == 0x03) {
2856 dellist[num_deleted++] = msglist[i];
2861 num_msgs = sort_msglist(msglist, num_msgs);
2862 cdb_store(CDB_MSGLISTS, &qrbuf.QRnumber, (int)sizeof(long),
2863 msglist, (int)(num_msgs * sizeof(long)));
2865 qrbuf.QRhighest = msglist[num_msgs - 1];
2869 /* Go through the messages we pulled out of the index, and decrement
2870 * their reference counts by 1. If this is the only room the message
2871 * was in, the reference count will reach zero and the message will
2872 * automatically be deleted from the database. We do this in a
2873 * separate pass because there might be plug-in hooks getting called,
2874 * and we don't want that happening during an S_ROOMS critical
2877 if (num_deleted) for (i=0; i<num_deleted; ++i) {
2878 PerformDeleteHooks(qrbuf.QRname, dellist[i]);
2879 AdjRefCount(dellist[i], -1);
2882 /* Now free the memory we used, and go away. */
2883 if (msglist != NULL) free(msglist);
2884 if (dellist != NULL) free(dellist);
2885 lprintf(CTDL_DEBUG, "%d message(s) deleted.\n", num_deleted);
2886 return (num_deleted);
2892 * Check whether the current user has permission to delete messages from
2893 * the current room (returns 1 for yes, 0 for no)
2895 int CtdlDoIHavePermissionToDeleteMessagesFromThisRoom(void) {
2896 getuser(&CC->user, CC->curr_user);
2897 if ((CC->user.axlevel < 6)
2898 && (CC->user.usernum != CC->room.QRroomaide)
2899 && ((CC->room.QRflags & QR_MAILBOX) == 0)
2900 && (!(CC->internal_pgm))) {
2909 * Delete message from current room
2911 void cmd_dele(char *delstr)
2916 if (CtdlDoIHavePermissionToDeleteMessagesFromThisRoom() == 0) {
2917 cprintf("%d Higher access required.\n",
2918 ERROR + HIGHER_ACCESS_REQUIRED);
2921 delnum = extract_long(delstr, 0);
2923 num_deleted = CtdlDeleteMessages(CC->room.QRname, delnum, "");
2926 cprintf("%d %d message%s deleted.\n", CIT_OK,
2927 num_deleted, ((num_deleted != 1) ? "s" : ""));
2929 cprintf("%d Message %ld not found.\n", ERROR + MESSAGE_NOT_FOUND, delnum);
2935 * Back end API function for moves and deletes
2937 int CtdlCopyMsgToRoom(long msgnum, char *dest) {
2940 err = CtdlSaveMsgPointerInRoom(dest, msgnum,
2941 (SM_VERIFY_GOODNESS | SM_DO_REPL_CHECK) );
2942 if (err != 0) return(err);
2950 * move or copy a message to another room
2952 void cmd_move(char *args)
2956 struct ctdlroom qtemp;
2962 num = extract_long(args, 0);
2963 extract(targ, args, 1);
2964 targ[ROOMNAMELEN - 1] = 0;
2965 is_copy = extract_int(args, 2);
2967 if (getroom(&qtemp, targ) != 0) {
2968 cprintf("%d '%s' does not exist.\n",
2969 ERROR + ROOM_NOT_FOUND, targ);
2973 getuser(&CC->user, CC->curr_user);
2974 CtdlRoomAccess(&qtemp, &CC->user, &ra, NULL);
2976 /* Check for permission to perform this operation.
2977 * Remember: "CC->room" is source, "qtemp" is target.
2981 /* Aides can move/copy */
2982 if (CC->user.axlevel >= 6) permit = 1;
2984 /* Room aides can move/copy */
2985 if (CC->user.usernum == CC->room.QRroomaide) permit = 1;
2987 /* Permit move/copy from personal rooms */
2988 if ((CC->room.QRflags & QR_MAILBOX)
2989 && (qtemp.QRflags & QR_MAILBOX)) permit = 1;
2991 /* Permit only copy from public to personal room */
2993 && (!(CC->room.QRflags & QR_MAILBOX))
2994 && (qtemp.QRflags & QR_MAILBOX)) permit = 1;
2996 /* User must have access to target room */
2997 if (!(ra & UA_KNOWN)) permit = 0;
3000 cprintf("%d Higher access required.\n",
3001 ERROR + HIGHER_ACCESS_REQUIRED);
3005 err = CtdlCopyMsgToRoom(num, targ);
3007 cprintf("%d Cannot store message in %s: error %d\n",
3012 /* Now delete the message from the source room,
3013 * if this is a 'move' rather than a 'copy' operation.
3016 CtdlDeleteMessages(CC->room.QRname, num, "");
3019 cprintf("%d Message %s.\n", CIT_OK, (is_copy ? "copied" : "moved") );
3025 * GetMetaData() - Get the supplementary record for a message
3027 void GetMetaData(struct MetaData *smibuf, long msgnum)
3030 struct cdbdata *cdbsmi;
3033 memset(smibuf, 0, sizeof(struct MetaData));
3034 smibuf->meta_msgnum = msgnum;
3035 smibuf->meta_refcount = 1; /* Default reference count is 1 */
3037 /* Use the negative of the message number for its supp record index */
3038 TheIndex = (0L - msgnum);
3040 cdbsmi = cdb_fetch(CDB_MSGMAIN, &TheIndex, sizeof(long));
3041 if (cdbsmi == NULL) {
3042 return; /* record not found; go with defaults */
3044 memcpy(smibuf, cdbsmi->ptr,
3045 ((cdbsmi->len > sizeof(struct MetaData)) ?
3046 sizeof(struct MetaData) : cdbsmi->len));
3053 * PutMetaData() - (re)write supplementary record for a message
3055 void PutMetaData(struct MetaData *smibuf)
3059 /* Use the negative of the message number for the metadata db index */
3060 TheIndex = (0L - smibuf->meta_msgnum);
3062 lprintf(CTDL_DEBUG, "PutMetaData(%ld) - ref count is %d\n",
3063 smibuf->meta_msgnum, smibuf->meta_refcount);
3065 cdb_store(CDB_MSGMAIN,
3066 &TheIndex, (int)sizeof(long),
3067 smibuf, (int)sizeof(struct MetaData));
3072 * AdjRefCount - change the reference count for a message;
3073 * delete the message if it reaches zero
3075 void AdjRefCount(long msgnum, int incr)
3078 struct MetaData smi;
3081 /* This is a *tight* critical section; please keep it that way, as
3082 * it may get called while nested in other critical sections.
3083 * Complicating this any further will surely cause deadlock!
3085 begin_critical_section(S_SUPPMSGMAIN);
3086 GetMetaData(&smi, msgnum);
3087 lprintf(CTDL_DEBUG, "Ref count for message <%ld> before write is <%d>\n",
3088 msgnum, smi.meta_refcount);
3089 smi.meta_refcount += incr;
3091 end_critical_section(S_SUPPMSGMAIN);
3092 lprintf(CTDL_DEBUG, "Ref count for message <%ld> after write is <%d>\n",
3093 msgnum, smi.meta_refcount);
3095 /* If the reference count is now zero, delete the message
3096 * (and its supplementary record as well).
3098 if (smi.meta_refcount == 0) {
3099 lprintf(CTDL_DEBUG, "Deleting message <%ld>\n", msgnum);
3101 cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long));
3102 cdb_delete(CDB_BIGMSGS, &delnum, (int)sizeof(long));
3104 /* We have to delete the metadata record too! */
3105 delnum = (0L - msgnum);
3106 cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long));
3111 * Write a generic object to this room
3113 * Note: this could be much more efficient. Right now we use two temporary
3114 * files, and still pull the message into memory as with all others.
3116 void CtdlWriteObject(char *req_room, /* Room to stuff it in */
3117 char *content_type, /* MIME type of this object */
3118 char *tempfilename, /* Where to fetch it from */
3119 struct ctdluser *is_mailbox, /* Mailbox room? */
3120 int is_binary, /* Is encoding necessary? */
3121 int is_unique, /* Del others of this type? */
3122 unsigned int flags /* Internal save flags */
3127 struct ctdlroom qrbuf;
3128 char roomname[ROOMNAMELEN];
3129 struct CtdlMessage *msg;
3131 char *raw_message = NULL;
3132 char *encoded_message = NULL;
3133 off_t raw_length = 0;
3135 if (is_mailbox != NULL)
3136 MailboxName(roomname, sizeof roomname, is_mailbox, req_room);
3138 safestrncpy(roomname, req_room, sizeof(roomname));
3139 lprintf(CTDL_DEBUG, "CtdlWriteObject() to <%s> (flags=%d)\n", roomname, flags);
3142 fp = fopen(tempfilename, "rb");
3144 lprintf(CTDL_CRIT, "Cannot open %s: %s\n",
3145 tempfilename, strerror(errno));
3148 fseek(fp, 0L, SEEK_END);
3149 raw_length = ftell(fp);
3151 lprintf(CTDL_DEBUG, "Raw length is %ld\n", (long)raw_length);
3153 raw_message = malloc((size_t)raw_length + 2);
3154 fread(raw_message, (size_t)raw_length, 1, fp);
3158 encoded_message = malloc((size_t)
3159 (((raw_length * 134) / 100) + 4096 ) );
3162 encoded_message = malloc((size_t)(raw_length + 4096));
3165 sprintf(encoded_message, "Content-type: %s\n", content_type);
3168 sprintf(&encoded_message[strlen(encoded_message)],
3169 "Content-transfer-encoding: base64\n\n"
3173 sprintf(&encoded_message[strlen(encoded_message)],
3174 "Content-transfer-encoding: 7bit\n\n"
3180 &encoded_message[strlen(encoded_message)],
3186 raw_message[raw_length] = 0;
3188 &encoded_message[strlen(encoded_message)],
3196 lprintf(CTDL_DEBUG, "Allocating\n");
3197 msg = malloc(sizeof(struct CtdlMessage));
3198 memset(msg, 0, sizeof(struct CtdlMessage));
3199 msg->cm_magic = CTDLMESSAGE_MAGIC;
3200 msg->cm_anon_type = MES_NORMAL;
3201 msg->cm_format_type = 4;
3202 msg->cm_fields['A'] = strdup(CC->user.fullname);
3203 msg->cm_fields['O'] = strdup(req_room);
3204 msg->cm_fields['N'] = strdup(config.c_nodename);
3205 msg->cm_fields['H'] = strdup(config.c_humannode);
3206 msg->cm_flags = flags;
3208 msg->cm_fields['M'] = encoded_message;
3210 /* Create the requested room if we have to. */
3211 if (getroom(&qrbuf, roomname) != 0) {
3212 create_room(roomname,
3213 ( (is_mailbox != NULL) ? 5 : 3 ),
3214 "", 0, 1, 0, VIEW_BBS);
3216 /* If the caller specified this object as unique, delete all
3217 * other objects of this type that are currently in the room.
3220 lprintf(CTDL_DEBUG, "Deleted %d other msgs of this type\n",
3221 CtdlDeleteMessages(roomname, 0L, content_type));
3223 /* Now write the data */
3224 CtdlSubmitMsg(msg, NULL, roomname);
3225 CtdlFreeMessage(msg);
3233 void CtdlGetSysConfigBackend(long msgnum, void *userdata) {
3234 config_msgnum = msgnum;
3238 char *CtdlGetSysConfig(char *sysconfname) {
3239 char hold_rm[ROOMNAMELEN];
3242 struct CtdlMessage *msg;
3245 strcpy(hold_rm, CC->room.QRname);
3246 if (getroom(&CC->room, SYSCONFIGROOM) != 0) {
3247 getroom(&CC->room, hold_rm);
3252 /* We want the last (and probably only) config in this room */
3253 begin_critical_section(S_CONFIG);
3254 config_msgnum = (-1L);
3255 CtdlForEachMessage(MSGS_LAST, 1, sysconfname, NULL,
3256 CtdlGetSysConfigBackend, NULL);
3257 msgnum = config_msgnum;
3258 end_critical_section(S_CONFIG);
3264 msg = CtdlFetchMessage(msgnum, 1);
3266 conf = strdup(msg->cm_fields['M']);
3267 CtdlFreeMessage(msg);
3274 getroom(&CC->room, hold_rm);
3276 if (conf != NULL) do {
3277 extract_token(buf, conf, 0, '\n');
3278 strcpy(conf, &conf[strlen(buf)+1]);
3279 } while ( (strlen(conf)>0) && (strlen(buf)>0) );
3284 void CtdlPutSysConfig(char *sysconfname, char *sysconfdata) {
3285 char temp[PATH_MAX];
3288 strcpy(temp, tmpnam(NULL));
3290 fp = fopen(temp, "w");
3291 if (fp == NULL) return;
3292 fprintf(fp, "%s", sysconfdata);
3295 /* this handy API function does all the work for us */
3296 CtdlWriteObject(SYSCONFIGROOM, sysconfname, temp, NULL, 0, 1, 0);
3302 * Determine whether a given Internet address belongs to the current user
3304 int CtdlIsMe(char *addr) {
3305 struct recptypes *recp;
3308 recp = validate_recipients(addr);
3309 if (recp == NULL) return(0);
3311 if (recp->num_local == 0) {
3316 for (i=0; i<recp->num_local; ++i) {
3317 extract(addr, recp->recp_local, i);
3318 if (!strcasecmp(addr, CC->user.fullname)) {
3330 * Citadel protocol command to do the same
3332 void cmd_isme(char *argbuf) {
3335 if (CtdlAccessCheck(ac_logged_in)) return;
3336 extract(addr, argbuf, 0);
3338 if (CtdlIsMe(addr)) {
3339 cprintf("%d %s\n", CIT_OK, addr);
3342 cprintf("%d Not you.\n", ERROR + ILLEGAL_VALUE);