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))
56 #define msg_repl ((struct repl *)CtdlGetUserData(SYM_REPL))
58 extern struct config config;
63 * This really belongs in serv_network.c, but I don't know how to export
64 * symbols between modules.
66 struct FilterList *filterlist = NULL;
70 * These are the four-character field headers we use when outputting
71 * messages in Citadel format (as opposed to RFC822 format).
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,
81 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
108 * This function is self explanatory.
109 * (What can I say, I'm in a weird mood today...)
111 void remove_any_whitespace_to_the_left_or_right_of_at_symbol(char *name)
115 for (i = 0; i < strlen(name); ++i) {
116 if (name[i] == '@') {
117 while (isspace(name[i - 1]) && i > 0) {
118 strcpy(&name[i - 1], &name[i]);
121 while (isspace(name[i + 1])) {
122 strcpy(&name[i + 1], &name[i + 2]);
130 * Aliasing for network mail.
131 * (Error messages have been commented out, because this is a server.)
133 int alias(char *name)
134 { /* process alias and routing info for mail */
137 char aaa[SIZ], bbb[SIZ];
138 char *ignetcfg = NULL;
139 char *ignetmap = NULL;
146 remove_any_whitespace_to_the_left_or_right_of_at_symbol(name);
148 fp = fopen("network/mail.aliases", "r");
150 fp = fopen("/dev/null", "r");
157 while (fgets(aaa, sizeof aaa, fp) != NULL) {
158 while (isspace(name[0]))
159 strcpy(name, &name[1]);
160 aaa[strlen(aaa) - 1] = 0;
162 for (a = 0; a < strlen(aaa); ++a) {
164 strcpy(bbb, &aaa[a + 1]);
168 if (!strcasecmp(name, aaa))
173 /* Hit the Global Address Book */
174 if (CtdlDirectoryLookup(aaa, name) == 0) {
178 lprintf(CTDL_INFO, "Mail is being forwarded to %s\n", name);
180 /* Change "user @ xxx" to "user" if xxx is an alias for this host */
181 for (a=0; a<strlen(name); ++a) {
182 if (name[a] == '@') {
183 if (CtdlHostAlias(&name[a+1]) == hostalias_localhost) {
185 lprintf(CTDL_INFO, "Changed to <%s>\n", name);
190 /* determine local or remote type, see citadel.h */
191 at = haschar(name, '@');
192 if (at == 0) return(MES_LOCAL); /* no @'s - local address */
193 if (at > 1) return(MES_ERROR); /* >1 @'s - invalid address */
194 remove_any_whitespace_to_the_left_or_right_of_at_symbol(name);
196 /* figure out the delivery mode */
197 extract_token(node, name, 1, '@');
199 /* If there are one or more dots in the nodename, we assume that it
200 * is an FQDN and will attempt SMTP delivery to the Internet.
202 if (haschar(node, '.') > 0) {
203 return(MES_INTERNET);
206 /* Otherwise we look in the IGnet maps for a valid Citadel node.
207 * Try directly-connected nodes first...
209 ignetcfg = CtdlGetSysConfig(IGNETCFG);
210 for (i=0; i<num_tokens(ignetcfg, '\n'); ++i) {
211 extract_token(buf, ignetcfg, i, '\n');
212 extract_token(testnode, buf, 0, '|');
213 if (!strcasecmp(node, testnode)) {
221 * Then try nodes that are two or more hops away.
223 ignetmap = CtdlGetSysConfig(IGNETMAP);
224 for (i=0; i<num_tokens(ignetmap, '\n'); ++i) {
225 extract_token(buf, ignetmap, i, '\n');
226 extract_token(testnode, buf, 0, '|');
227 if (!strcasecmp(node, testnode)) {
234 /* If we get to this point it's an invalid node name */
243 fp = fopen("citadel.control", "r");
245 lprintf(CTDL_CRIT, "Cannot open citadel.control: %s\n",
249 fread((char *) &CitControl, sizeof(struct CitControl), 1, fp);
255 void simple_listing(long msgnum, void *userdata)
257 cprintf("%ld\n", msgnum);
262 /* Determine if a given message matches the fields in a message template.
263 * Return 0 for a successful match.
265 int CtdlMsgCmp(struct CtdlMessage *msg, struct CtdlMessage *template) {
268 /* If there aren't any fields in the template, all messages will
271 if (template == NULL) return(0);
273 /* Null messages are bogus. */
274 if (msg == NULL) return(1);
276 for (i='A'; i<='Z'; ++i) {
277 if (template->cm_fields[i] != NULL) {
278 if (msg->cm_fields[i] == NULL) {
281 if (strcasecmp(msg->cm_fields[i],
282 template->cm_fields[i])) return 1;
286 /* All compares succeeded: we have a match! */
293 * Retrieve the "seen" message list for the current room.
295 void CtdlGetSeen(char *buf, int which_set) {
298 /* Learn about the user and room in question */
299 CtdlGetRelationship(&vbuf, &CC->user, &CC->room);
301 if (which_set == ctdlsetseen_seen)
302 safestrncpy(buf, vbuf.v_seen, SIZ);
303 if (which_set == ctdlsetseen_answered)
304 safestrncpy(buf, vbuf.v_answered, SIZ);
310 * Manipulate the "seen msgs" string (or other message set strings)
312 void CtdlSetSeen(long target_msgnum, int target_setting, int which_set) {
314 struct cdbdata *cdbfr;
325 lprintf(CTDL_DEBUG, "CtdlSetSeen(%ld, %d, %d)\n",
326 target_msgnum, target_setting, which_set);
328 /* Learn about the user and room in question */
329 CtdlGetRelationship(&vbuf, &CC->user, &CC->room);
331 /* Load the message list */
332 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long));
334 msglist = malloc(cdbfr->len);
335 memcpy(msglist, cdbfr->ptr, cdbfr->len);
336 num_msgs = cdbfr->len / sizeof(long);
339 return; /* No messages at all? No further action. */
342 /* Decide which message set we're manipulating */
343 if (which_set == ctdlsetseen_seen) strcpy(vset, vbuf.v_seen);
344 if (which_set == ctdlsetseen_answered) strcpy(vset, vbuf.v_answered);
346 lprintf(CTDL_DEBUG, "before optimize: %s\n", vset);
349 for (i=0; i<num_msgs; ++i) {
352 if (msglist[i] == target_msgnum) {
353 is_seen = target_setting;
356 if (is_msg_in_mset(vset, msglist[i])) {
362 if (lo < 0L) lo = msglist[i];
365 if ( ((is_seen == 0) && (was_seen == 1))
366 || ((is_seen == 1) && (i == num_msgs-1)) ) {
369 if ( (strlen(newseen) + 20) > SIZ) {
370 strcpy(newseen, &newseen[20]);
373 tmp = strlen(newseen);
375 strcat(newseen, ",");
379 snprintf(&newseen[tmp], sizeof newseen - tmp,
383 snprintf(&newseen[tmp], sizeof newseen - tmp,
392 /* Decide which message set we're manipulating */
393 if (which_set == ctdlsetseen_seen) strcpy(vbuf.v_seen, newseen);
394 if (which_set == ctdlsetseen_answered) strcpy(vbuf.v_answered, newseen);
396 lprintf(CTDL_DEBUG, " after optimize: %s\n", newseen);
398 CtdlSetRelationship(&vbuf, &CC->user, &CC->room);
403 * API function to perform an operation for each qualifying message in the
404 * current room. (Returns the number of messages processed.)
406 int CtdlForEachMessage(int mode, long ref,
408 struct CtdlMessage *compare,
409 void (*CallBack) (long, void *),
415 struct cdbdata *cdbfr;
416 long *msglist = NULL;
418 int num_processed = 0;
421 struct CtdlMessage *msg;
424 int printed_lastold = 0;
426 /* Learn about the user and room in question */
428 getuser(&CC->user, CC->curr_user);
429 CtdlGetRelationship(&vbuf, &CC->user, &CC->room);
431 /* Load the message list */
432 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long));
434 msglist = malloc(cdbfr->len);
435 memcpy(msglist, cdbfr->ptr, cdbfr->len);
436 num_msgs = cdbfr->len / sizeof(long);
439 return 0; /* No messages at all? No further action. */
444 * Now begin the traversal.
446 if (num_msgs > 0) for (a = 0; a < num_msgs; ++a) {
448 /* If the caller is looking for a specific MIME type, filter
449 * out all messages which are not of the type requested.
451 if (content_type != NULL) if (strlen(content_type) > 0) {
453 /* This call to GetMetaData() sits inside this loop
454 * so that we only do the extra database read per msg
455 * if we need to. Doing the extra read all the time
456 * really kills the server. If we ever need to use
457 * metadata for another search criterion, we need to
458 * move the read somewhere else -- but still be smart
459 * enough to only do the read if the caller has
460 * specified something that will need it.
462 GetMetaData(&smi, msglist[a]);
464 if (strcasecmp(smi.meta_content_type, content_type)) {
470 num_msgs = sort_msglist(msglist, num_msgs);
472 /* If a template was supplied, filter out the messages which
473 * don't match. (This could induce some delays!)
476 if (compare != NULL) {
477 for (a = 0; a < num_msgs; ++a) {
478 msg = CtdlFetchMessage(msglist[a], 1);
480 if (CtdlMsgCmp(msg, compare)) {
483 CtdlFreeMessage(msg);
491 * Now iterate through the message list, according to the
492 * criteria supplied by the caller.
495 for (a = 0; a < num_msgs; ++a) {
496 thismsg = msglist[a];
497 is_seen = is_msg_in_mset(vbuf.v_seen, thismsg);
498 if (is_seen) lastold = thismsg;
503 || ((mode == MSGS_OLD) && (is_seen))
504 || ((mode == MSGS_NEW) && (!is_seen))
505 || ((mode == MSGS_LAST) && (a >= (num_msgs - ref)))
506 || ((mode == MSGS_FIRST) && (a < ref))
507 || ((mode == MSGS_GT) && (thismsg > ref))
508 || ((mode == MSGS_EQ) && (thismsg == ref))
511 if ((mode == MSGS_NEW) && (CC->user.flags & US_LASTOLD) && (lastold > 0L) && (printed_lastold == 0) && (!is_seen)) {
513 CallBack(lastold, userdata);
517 if (CallBack) CallBack(thismsg, userdata);
521 free(msglist); /* Clean up */
522 return num_processed;
528 * cmd_msgs() - get list of message #'s in this room
529 * implements the MSGS server command using CtdlForEachMessage()
531 void cmd_msgs(char *cmdbuf)
540 int with_template = 0;
541 struct CtdlMessage *template = NULL;
543 extract(which, cmdbuf, 0);
544 cm_ref = extract_int(cmdbuf, 1);
545 with_template = extract_int(cmdbuf, 2);
549 if (!strncasecmp(which, "OLD", 3))
551 else if (!strncasecmp(which, "NEW", 3))
553 else if (!strncasecmp(which, "FIRST", 5))
555 else if (!strncasecmp(which, "LAST", 4))
557 else if (!strncasecmp(which, "GT", 2))
560 if ((!(CC->logged_in)) && (!(CC->internal_pgm))) {
561 cprintf("%d not logged in\n", ERROR + NOT_LOGGED_IN);
567 cprintf("%d Send template then receive message list\n",
569 template = (struct CtdlMessage *)
570 malloc(sizeof(struct CtdlMessage));
571 memset(template, 0, sizeof(struct CtdlMessage));
572 while(client_gets(buf), strcmp(buf,"000")) {
573 extract(tfield, buf, 0);
574 extract(tvalue, buf, 1);
575 for (i='A'; i<='Z'; ++i) if (msgkeys[i]!=NULL) {
576 if (!strcasecmp(tfield, msgkeys[i])) {
577 template->cm_fields[i] =
585 cprintf("%d Message list...\n", LISTING_FOLLOWS);
588 CtdlForEachMessage(mode, cm_ref,
589 NULL, template, simple_listing, NULL);
590 if (template != NULL) CtdlFreeMessage(template);
598 * help_subst() - support routine for help file viewer
600 void help_subst(char *strbuf, char *source, char *dest)
605 while (p = pattern2(strbuf, source), (p >= 0)) {
606 strcpy(workbuf, &strbuf[p + strlen(source)]);
607 strcpy(&strbuf[p], dest);
608 strcat(strbuf, workbuf);
613 void do_help_subst(char *buffer)
617 help_subst(buffer, "^nodename", config.c_nodename);
618 help_subst(buffer, "^humannode", config.c_humannode);
619 help_subst(buffer, "^fqdn", config.c_fqdn);
620 help_subst(buffer, "^username", CC->user.fullname);
621 snprintf(buf2, sizeof buf2, "%ld", CC->user.usernum);
622 help_subst(buffer, "^usernum", buf2);
623 help_subst(buffer, "^sysadm", config.c_sysadm);
624 help_subst(buffer, "^variantname", CITADEL);
625 snprintf(buf2, sizeof buf2, "%d", config.c_maxsessions);
626 help_subst(buffer, "^maxsessions", buf2);
627 help_subst(buffer, "^bbsdir", BBSDIR);
633 * memfmout() - Citadel text formatter and paginator.
634 * Although the original purpose of this routine was to format
635 * text to the reader's screen width, all we're really using it
636 * for here is to format text out to 80 columns before sending it
637 * to the client. The client software may reformat it again.
640 int width, /* screen width to use */
641 char *mptr, /* where are we going to get our text from? */
642 char subst, /* nonzero if we should do substitutions */
643 char *nl) /* string to terminate lines with */
655 c = 1; /* c is the current pos */
659 while (ch = *mptr, ((ch != 0) && (strlen(buffer) < 126))) {
661 buffer[strlen(buffer) + 1] = 0;
662 buffer[strlen(buffer)] = ch;
665 if (buffer[0] == '^')
666 do_help_subst(buffer);
668 buffer[strlen(buffer) + 1] = 0;
670 strcpy(buffer, &buffer[1]);
678 if (((ch == 13) || (ch == 10)) && (old != 13) && (old != 10))
680 if (((old == 13) || (old == 10)) && (isspace(real))) {
688 if (((strlen(aaa) + c) > (width - 5)) && (strlen(aaa) > (width - 5))) {
689 cprintf("%s%s", nl, aaa);
698 if ((strlen(aaa) + c) > (width - 5)) {
707 if ((ch == 13) || (ch == 10)) {
708 cprintf("%s%s", aaa, nl);
715 cprintf("%s%s", aaa, nl);
721 * Callback function for mime parser that simply lists the part
723 void list_this_part(char *name, char *filename, char *partnum, char *disp,
724 void *content, char *cbtype, size_t length, char *encoding,
728 cprintf("part=%s|%s|%s|%s|%s|%ld\n",
729 name, filename, partnum, disp, cbtype, (long)length);
733 * Callback function for multipart prefix
735 void list_this_pref(char *name, char *filename, char *partnum, char *disp,
736 void *content, char *cbtype, size_t length, char *encoding,
739 cprintf("pref=%s|%s\n", partnum, cbtype);
743 * Callback function for multipart sufffix
745 void list_this_suff(char *name, char *filename, char *partnum, char *disp,
746 void *content, char *cbtype, size_t length, char *encoding,
749 cprintf("suff=%s|%s\n", partnum, cbtype);
754 * Callback function for mime parser that opens a section for downloading
756 void mime_download(char *name, char *filename, char *partnum, char *disp,
757 void *content, char *cbtype, size_t length, char *encoding,
761 /* Silently go away if there's already a download open... */
762 if (CC->download_fp != NULL)
765 /* ...or if this is not the desired section */
766 if (strcasecmp(desired_section, partnum))
769 CC->download_fp = tmpfile();
770 if (CC->download_fp == NULL)
773 fwrite(content, length, 1, CC->download_fp);
774 fflush(CC->download_fp);
775 rewind(CC->download_fp);
777 OpenCmdResult(filename, cbtype);
783 * Load a message from disk into memory.
784 * This is used by CtdlOutputMsg() and other fetch functions.
786 * NOTE: Caller is responsible for freeing the returned CtdlMessage struct
787 * using the CtdlMessageFree() function.
789 struct CtdlMessage *CtdlFetchMessage(long msgnum, int with_body)
791 struct cdbdata *dmsgtext;
792 struct CtdlMessage *ret = NULL;
795 cit_uint8_t field_header;
798 lprintf(CTDL_DEBUG, "CtdlFetchMessage(%ld, %d)\n", msgnum, with_body);
800 dmsgtext = cdb_fetch(CDB_MSGMAIN, &msgnum, sizeof(long));
801 if (dmsgtext == NULL) {
804 mptr = dmsgtext->ptr;
806 /* Parse the three bytes that begin EVERY message on disk.
807 * The first is always 0xFF, the on-disk magic number.
808 * The second is the anonymous/public type byte.
809 * The third is the format type byte (vari, fixed, or MIME).
814 "Message %ld appears to be corrupted.\n",
819 ret = (struct CtdlMessage *) malloc(sizeof(struct CtdlMessage));
820 memset(ret, 0, sizeof(struct CtdlMessage));
822 ret->cm_magic = CTDLMESSAGE_MAGIC;
823 ret->cm_anon_type = *mptr++; /* Anon type byte */
824 ret->cm_format_type = *mptr++; /* Format type byte */
827 * The rest is zero or more arbitrary fields. Load them in.
828 * We're done when we encounter either a zero-length field or
829 * have just processed the 'M' (message text) field.
832 field_length = strlen(mptr);
833 if (field_length == 0)
835 field_header = *mptr++;
836 ret->cm_fields[field_header] = malloc(field_length);
837 strcpy(ret->cm_fields[field_header], mptr);
839 while (*mptr++ != 0); /* advance to next field */
841 } while ((field_length > 0) && (field_header != 'M'));
845 /* Always make sure there's something in the msg text field. If
846 * it's NULL, the message text is most likely stored separately,
847 * so go ahead and fetch that. Failing that, just set a dummy
848 * body so other code doesn't barf.
850 if ( (ret->cm_fields['M'] == NULL) && (with_body) ) {
851 dmsgtext = cdb_fetch(CDB_BIGMSGS, &msgnum, sizeof(long));
852 if (dmsgtext != NULL) {
853 ret->cm_fields['M'] = strdup(dmsgtext->ptr);
857 if (ret->cm_fields['M'] == NULL) {
858 ret->cm_fields['M'] = strdup("<no text>\n");
861 /* Perform "before read" hooks (aborting if any return nonzero) */
862 if (PerformMessageHooks(ret, EVT_BEFOREREAD) > 0) {
863 CtdlFreeMessage(ret);
872 * Returns 1 if the supplied pointer points to a valid Citadel message.
873 * If the pointer is NULL or the magic number check fails, returns 0.
875 int is_valid_message(struct CtdlMessage *msg) {
878 if ((msg->cm_magic) != CTDLMESSAGE_MAGIC) {
879 lprintf(CTDL_WARNING, "is_valid_message() -- self-check failed\n");
887 * 'Destructor' for struct CtdlMessage
889 void CtdlFreeMessage(struct CtdlMessage *msg)
893 if (is_valid_message(msg) == 0) return;
895 for (i = 0; i < 256; ++i)
896 if (msg->cm_fields[i] != NULL) {
897 free(msg->cm_fields[i]);
900 msg->cm_magic = 0; /* just in case */
906 * Pre callback function for multipart/alternative
908 * NOTE: this differs from the standard behavior for a reason. Normally when
909 * displaying multipart/alternative you want to show the _last_ usable
910 * format in the message. Here we show the _first_ one, because it's
911 * usually text/plain. Since this set of functions is designed for text
912 * output to non-MIME-aware clients, this is the desired behavior.
915 void fixed_output_pre(char *name, char *filename, char *partnum, char *disp,
916 void *content, char *cbtype, size_t length, char *encoding,
919 lprintf(CTDL_DEBUG, "fixed_output_pre() type=<%s>\n", cbtype);
920 if (!strcasecmp(cbtype, "multipart/alternative")) {
928 * Post callback function for multipart/alternative
930 void fixed_output_post(char *name, char *filename, char *partnum, char *disp,
931 void *content, char *cbtype, size_t length, char *encoding,
934 lprintf(CTDL_DEBUG, "fixed_output_post() type=<%s>\n", cbtype);
935 if (!strcasecmp(cbtype, "multipart/alternative")) {
943 * Inline callback function for mime parser that wants to display text
945 void fixed_output(char *name, char *filename, char *partnum, char *disp,
946 void *content, char *cbtype, size_t length, char *encoding,
953 lprintf(CTDL_DEBUG, "fixed_output() type=<%s>\n", cbtype);
956 * If we're in the middle of a multipart/alternative scope and
957 * we've already printed another section, skip this one.
959 if ( (ma->is_ma == 1) && (ma->did_print == 1) ) {
960 lprintf(CTDL_DEBUG, "Skipping part %s (%s)\n", partnum, cbtype);
965 if ( (!strcasecmp(cbtype, "text/plain"))
966 || (strlen(cbtype)==0) ) {
969 client_write(wptr, length);
970 if (wptr[length-1] != '\n') {
975 else if (!strcasecmp(cbtype, "text/html")) {
976 ptr = html_to_ascii(content, 80, 0);
978 client_write(ptr, wlen);
979 if (ptr[wlen-1] != '\n') {
984 else if (strncasecmp(cbtype, "multipart/", 10)) {
985 cprintf("Part %s: %s (%s) (%ld bytes)\r\n",
986 partnum, filename, cbtype, (long)length);
991 * The client is elegant and sophisticated and wants to be choosy about
992 * MIME content types, so figure out which multipart/alternative part
993 * we're going to send.
995 void choose_preferred(char *name, char *filename, char *partnum, char *disp,
996 void *content, char *cbtype, size_t length, char *encoding,
1002 if (ma->is_ma > 0) {
1003 for (i=0; i<num_tokens(CC->preferred_formats, '|'); ++i) {
1004 extract(buf, CC->preferred_formats, i);
1005 if (!strcasecmp(buf, cbtype)) {
1006 strcpy(ma->chosen_part, partnum);
1013 * Now that we've chosen our preferred part, output it.
1015 void output_preferred(char *name, char *filename, char *partnum, char *disp,
1016 void *content, char *cbtype, size_t length, char *encoding,
1021 int add_newline = 0;
1024 /* This is not the MIME part you're looking for... */
1025 if (strcasecmp(partnum, ma->chosen_part)) return;
1027 /* If the content-type of this part is in our preferred formats
1028 * list, we can simply output it verbatim.
1030 for (i=0; i<num_tokens(CC->preferred_formats, '|'); ++i) {
1031 extract(buf, CC->preferred_formats, i);
1032 if (!strcasecmp(buf, cbtype)) {
1033 /* Yeah! Go! W00t!! */
1035 text_content = (char *)content;
1036 if (text_content[length-1] != '\n') {
1040 cprintf("Content-type: %s\n", cbtype);
1041 cprintf("Content-length: %d\n",
1042 (int)(length + add_newline) );
1043 if (strlen(encoding) > 0) {
1044 cprintf("Content-transfer-encoding: %s\n", encoding);
1047 cprintf("Content-transfer-encoding: 7bit\n");
1050 client_write(content, length);
1051 if (add_newline) cprintf("\n");
1056 /* No translations required or possible: output as text/plain */
1057 cprintf("Content-type: text/plain\n\n");
1058 fixed_output(name, filename, partnum, disp, content, cbtype,
1059 length, encoding, cbuserdata);
1064 * Get a message off disk. (returns om_* values found in msgbase.h)
1067 int CtdlOutputMsg(long msg_num, /* message number (local) to fetch */
1068 int mode, /* how would you like that message? */
1069 int headers_only, /* eschew the message body? */
1070 int do_proto, /* do Citadel protocol responses? */
1071 int crlf /* Use CRLF newlines instead of LF? */
1073 struct CtdlMessage *TheMessage;
1076 lprintf(CTDL_DEBUG, "CtdlOutputMsg() msgnum=%ld, mode=%d\n",
1081 if ((!(CC->logged_in)) && (!(CC->internal_pgm))) {
1082 if (do_proto) cprintf("%d Not logged in.\n",
1083 ERROR + NOT_LOGGED_IN);
1084 return(om_not_logged_in);
1087 /* FIXME: check message id against msglist for this room */
1090 * Fetch the message from disk. If we're in sooper-fast headers
1091 * only mode, request that we don't even bother loading the body
1094 if (headers_only == HEADERS_FAST) {
1095 TheMessage = CtdlFetchMessage(msg_num, 0);
1098 TheMessage = CtdlFetchMessage(msg_num, 1);
1101 if (TheMessage == NULL) {
1102 if (do_proto) cprintf("%d Can't locate msg %ld on disk\n",
1103 ERROR + MESSAGE_NOT_FOUND, msg_num);
1104 return(om_no_such_msg);
1107 retcode = CtdlOutputPreLoadedMsg(
1108 TheMessage, msg_num, mode,
1109 headers_only, do_proto, crlf);
1111 CtdlFreeMessage(TheMessage);
1118 * Get a message off disk. (returns om_* values found in msgbase.h)
1121 int CtdlOutputPreLoadedMsg(struct CtdlMessage *TheMessage,
1123 int mode, /* how would you like that message? */
1124 int headers_only, /* eschew the message body? */
1125 int do_proto, /* do Citadel protocol responses? */
1126 int crlf /* Use CRLF newlines instead of LF? */
1132 char display_name[SIZ];
1134 char *nl; /* newline string */
1136 int subject_found = 0;
1138 /* buffers needed for RFC822 translation */
1145 char datestamp[SIZ];
1148 snprintf(mid, sizeof mid, "%ld", msg_num);
1149 nl = (crlf ? "\r\n" : "\n");
1151 if (!is_valid_message(TheMessage)) {
1153 "ERROR: invalid preloaded message for output\n");
1154 return(om_no_such_msg);
1157 /* Are we downloading a MIME component? */
1158 if (mode == MT_DOWNLOAD) {
1159 if (TheMessage->cm_format_type != FMT_RFC822) {
1161 cprintf("%d This is not a MIME message.\n",
1162 ERROR + ILLEGAL_VALUE);
1163 } else if (CC->download_fp != NULL) {
1164 if (do_proto) cprintf(
1165 "%d You already have a download open.\n",
1166 ERROR + RESOURCE_BUSY);
1168 /* Parse the message text component */
1169 mptr = TheMessage->cm_fields['M'];
1170 mime_parser(mptr, NULL,
1171 *mime_download, NULL, NULL,
1173 /* If there's no file open by this time, the requested
1174 * section wasn't found, so print an error
1176 if (CC->download_fp == NULL) {
1177 if (do_proto) cprintf(
1178 "%d Section %s not found.\n",
1179 ERROR + FILE_NOT_FOUND,
1183 return((CC->download_fp != NULL) ? om_ok : om_mime_error);
1186 /* now for the user-mode message reading loops */
1187 if (do_proto) cprintf("%d Message %ld:\n", LISTING_FOLLOWS, msg_num);
1189 /* Does the caller want to skip the headers? */
1190 if (headers_only == HEADERS_NONE) goto START_TEXT;
1192 /* Tell the client which format type we're using. */
1193 if ( (mode == MT_CITADEL) && (do_proto) ) {
1194 cprintf("type=%d\n", TheMessage->cm_format_type);
1197 /* nhdr=yes means that we're only displaying headers, no body */
1198 if ( (TheMessage->cm_anon_type == MES_ANONONLY)
1199 && (mode == MT_CITADEL)
1202 cprintf("nhdr=yes\n");
1205 /* begin header processing loop for Citadel message format */
1207 if ((mode == MT_CITADEL) || (mode == MT_MIME)) {
1209 strcpy(display_name, "<unknown>");
1210 if (TheMessage->cm_fields['A']) {
1211 strcpy(buf, TheMessage->cm_fields['A']);
1212 if (TheMessage->cm_anon_type == MES_ANONONLY) {
1213 strcpy(display_name, "****");
1215 else if (TheMessage->cm_anon_type == MES_ANONOPT) {
1216 strcpy(display_name, "anonymous");
1219 strcpy(display_name, buf);
1221 if ((is_room_aide())
1222 && ((TheMessage->cm_anon_type == MES_ANONONLY)
1223 || (TheMessage->cm_anon_type == MES_ANONOPT))) {
1224 size_t tmp = strlen(display_name);
1225 snprintf(&display_name[tmp],
1226 sizeof display_name - tmp,
1231 /* Don't show Internet address for users on the
1232 * local Citadel network.
1235 if (TheMessage->cm_fields['N'] != NULL)
1236 if (strlen(TheMessage->cm_fields['N']) > 0)
1237 if (haschar(TheMessage->cm_fields['N'], '.') == 0) {
1241 /* Now spew the header fields in the order we like them. */
1242 strcpy(allkeys, FORDER);
1243 for (i=0; i<strlen(allkeys); ++i) {
1244 k = (int) allkeys[i];
1246 if ( (TheMessage->cm_fields[k] != NULL)
1247 && (msgkeys[k] != NULL) ) {
1249 if (do_proto) cprintf("%s=%s\n",
1253 else if ((k == 'F') && (suppress_f)) {
1256 /* Masquerade display name if needed */
1258 if (do_proto) cprintf("%s=%s\n",
1260 TheMessage->cm_fields[k]
1269 /* begin header processing loop for RFC822 transfer format */
1274 strcpy(snode, NODENAME);
1275 strcpy(lnode, HUMANNODE);
1276 if (mode == MT_RFC822) {
1277 cprintf("X-UIDL: %ld%s", msg_num, nl);
1278 for (i = 0; i < 256; ++i) {
1279 if (TheMessage->cm_fields[i]) {
1280 mptr = TheMessage->cm_fields[i];
1283 strcpy(luser, mptr);
1284 strcpy(suser, mptr);
1287 "Path:" removed for now because it confuses brain-dead Microsoft shitware
1288 into thinking that mail messages are newsgroup messages instead. When we
1289 add NNTP support back into Citadel we'll have to add code to only output
1290 this field when appropriate.
1291 else if (i == 'P') {
1292 cprintf("Path: %s%s", mptr, nl);
1295 else if (i == 'U') {
1296 cprintf("Subject: %s%s", mptr, nl);
1300 safestrncpy(mid, mptr, sizeof mid);
1302 safestrncpy(lnode, mptr, sizeof lnode);
1304 safestrncpy(fuser, mptr, sizeof fuser);
1306 cprintf("X-Citadel-Room: %s%s",
1309 safestrncpy(snode, mptr, sizeof snode);
1311 cprintf("To: %s%s", mptr, nl);
1312 else if (i == 'T') {
1313 datestring(datestamp, sizeof datestamp,
1314 atol(mptr), DATESTRING_RFC822);
1315 cprintf("Date: %s%s", datestamp, nl);
1319 if (subject_found == 0) {
1320 cprintf("Subject: (no subject)%s", nl);
1324 for (i=0; i<strlen(suser); ++i) {
1325 suser[i] = tolower(suser[i]);
1326 if (!isalnum(suser[i])) suser[i]='_';
1329 if (mode == MT_RFC822) {
1330 if (!strcasecmp(snode, NODENAME)) {
1331 strcpy(snode, FQDN);
1334 /* Construct a fun message id */
1335 cprintf("Message-ID: <%s", mid);
1336 if (strchr(mid, '@')==NULL) {
1337 cprintf("@%s", snode);
1341 if (!is_room_aide() && (TheMessage->cm_anon_type == MES_ANONONLY)) {
1342 cprintf("From: x@x.org (----)%s", nl);
1344 else if (!is_room_aide() && (TheMessage->cm_anon_type == MES_ANONOPT)) {
1345 cprintf("From: x@x.org (anonymous)%s", nl);
1347 else if (strlen(fuser) > 0) {
1348 cprintf("From: %s (%s)%s", fuser, luser, nl);
1351 cprintf("From: %s@%s (%s)%s", suser, snode, luser, nl);
1354 cprintf("Organization: %s%s", lnode, nl);
1356 /* Blank line signifying RFC822 end-of-headers */
1357 if (TheMessage->cm_format_type != FMT_RFC822) {
1362 /* end header processing loop ... at this point, we're in the text */
1364 if (headers_only == HEADERS_FAST) goto DONE;
1365 mptr = TheMessage->cm_fields['M'];
1367 /* Tell the client about the MIME parts in this message */
1368 if (TheMessage->cm_format_type == FMT_RFC822) {
1369 if ( (mode == MT_CITADEL) || (mode == MT_MIME) ) {
1370 mime_parser(mptr, NULL,
1376 else if (mode == MT_RFC822) { /* unparsed RFC822 dump */
1377 /* FIXME ... we have to put some code in here to avoid
1378 * printing duplicate header information when both
1379 * Citadel and RFC822 headers exist. Preference should
1380 * probably be given to the RFC822 headers.
1382 int done_rfc822_hdrs = 0;
1383 while (ch=*(mptr++), ch!=0) {
1388 if (!done_rfc822_hdrs) {
1389 if (headers_only != HEADERS_NONE) {
1394 if (headers_only != HEADERS_ONLY) {
1398 if ((*(mptr) == 13) || (*(mptr) == 10)) {
1399 done_rfc822_hdrs = 1;
1403 if (done_rfc822_hdrs) {
1404 if (headers_only != HEADERS_NONE) {
1409 if (headers_only != HEADERS_ONLY) {
1413 if ((*mptr == 13) || (*mptr == 10)) {
1414 done_rfc822_hdrs = 1;
1422 if (headers_only == HEADERS_ONLY) {
1426 /* signify start of msg text */
1427 if ( (mode == MT_CITADEL) || (mode == MT_MIME) ) {
1428 if (do_proto) cprintf("text\n");
1431 /* If the format type on disk is 1 (fixed-format), then we want
1432 * everything to be output completely literally ... regardless of
1433 * what message transfer format is in use.
1435 if (TheMessage->cm_format_type == FMT_FIXED) {
1436 if (mode == MT_MIME) {
1437 cprintf("Content-type: text/plain\n\n");
1440 while (ch = *mptr++, ch > 0) {
1443 if ((ch == 10) || (strlen(buf) > 250)) {
1444 cprintf("%s%s", buf, nl);
1447 buf[strlen(buf) + 1] = 0;
1448 buf[strlen(buf)] = ch;
1451 if (strlen(buf) > 0)
1452 cprintf("%s%s", buf, nl);
1455 /* If the message on disk is format 0 (Citadel vari-format), we
1456 * output using the formatter at 80 columns. This is the final output
1457 * form if the transfer format is RFC822, but if the transfer format
1458 * is Citadel proprietary, it'll still work, because the indentation
1459 * for new paragraphs is correct and the client will reformat the
1460 * message to the reader's screen width.
1462 if (TheMessage->cm_format_type == FMT_CITADEL) {
1463 if (mode == MT_MIME) {
1464 cprintf("Content-type: text/x-citadel-variformat\n\n");
1466 memfmout(80, mptr, 0, nl);
1469 /* If the message on disk is format 4 (MIME), we've gotta hand it
1470 * off to the MIME parser. The client has already been told that
1471 * this message is format 1 (fixed format), so the callback function
1472 * we use will display those parts as-is.
1474 if (TheMessage->cm_format_type == FMT_RFC822) {
1475 CtdlAllocUserData(SYM_MA_INFO, sizeof(struct ma_info));
1476 memset(ma, 0, sizeof(struct ma_info));
1478 if (mode == MT_MIME) {
1479 strcpy(ma->chosen_part, "1");
1480 mime_parser(mptr, NULL,
1481 *choose_preferred, *fixed_output_pre,
1482 *fixed_output_post, NULL, 0);
1483 mime_parser(mptr, NULL,
1484 *output_preferred, NULL, NULL, NULL, 0);
1487 mime_parser(mptr, NULL,
1488 *fixed_output, *fixed_output_pre,
1489 *fixed_output_post, NULL, 0);
1493 DONE: /* now we're done */
1494 if (do_proto) cprintf("000\n");
1501 * display a message (mode 0 - Citadel proprietary)
1503 void cmd_msg0(char *cmdbuf)
1506 int headers_only = HEADERS_ALL;
1508 msgid = extract_long(cmdbuf, 0);
1509 headers_only = extract_int(cmdbuf, 1);
1511 CtdlOutputMsg(msgid, MT_CITADEL, headers_only, 1, 0);
1517 * display a message (mode 2 - RFC822)
1519 void cmd_msg2(char *cmdbuf)
1522 int headers_only = HEADERS_ALL;
1524 msgid = extract_long(cmdbuf, 0);
1525 headers_only = extract_int(cmdbuf, 1);
1527 CtdlOutputMsg(msgid, MT_RFC822, headers_only, 1, 1);
1533 * display a message (mode 3 - IGnet raw format - internal programs only)
1535 void cmd_msg3(char *cmdbuf)
1538 struct CtdlMessage *msg;
1541 if (CC->internal_pgm == 0) {
1542 cprintf("%d This command is for internal programs only.\n",
1543 ERROR + HIGHER_ACCESS_REQUIRED);
1547 msgnum = extract_long(cmdbuf, 0);
1548 msg = CtdlFetchMessage(msgnum, 1);
1550 cprintf("%d Message %ld not found.\n",
1551 ERROR + MESSAGE_NOT_FOUND, msgnum);
1555 serialize_message(&smr, msg);
1556 CtdlFreeMessage(msg);
1559 cprintf("%d Unable to serialize message\n",
1560 ERROR + INTERNAL_ERROR);
1564 cprintf("%d %ld\n", BINARY_FOLLOWS, (long)smr.len);
1565 client_write(smr.ser, smr.len);
1572 * Display a message using MIME content types
1574 void cmd_msg4(char *cmdbuf)
1578 msgid = extract_long(cmdbuf, 0);
1579 CtdlOutputMsg(msgid, MT_MIME, 0, 1, 0);
1585 * Client tells us its preferred message format(s)
1587 void cmd_msgp(char *cmdbuf)
1589 safestrncpy(CC->preferred_formats, cmdbuf,
1590 sizeof(CC->preferred_formats));
1591 cprintf("%d ok\n", CIT_OK);
1596 * Open a component of a MIME message as a download file
1598 void cmd_opna(char *cmdbuf)
1602 CtdlAllocUserData(SYM_DESIRED_SECTION, SIZ);
1604 msgid = extract_long(cmdbuf, 0);
1605 extract(desired_section, cmdbuf, 1);
1607 CtdlOutputMsg(msgid, MT_DOWNLOAD, 0, 1, 1);
1612 * Save a message pointer into a specified room
1613 * (Returns 0 for success, nonzero for failure)
1614 * roomname may be NULL to use the current room
1616 int CtdlSaveMsgPointerInRoom(char *roomname, long msgid, int flags) {
1618 char hold_rm[ROOMNAMELEN];
1619 struct cdbdata *cdbfr;
1622 long highest_msg = 0L;
1623 struct CtdlMessage *msg = NULL;
1625 lprintf(CTDL_DEBUG, "CtdlSaveMsgPointerInRoom(%s, %ld, %d)\n",
1626 roomname, msgid, flags);
1628 strcpy(hold_rm, CC->room.QRname);
1630 /* We may need to check to see if this message is real */
1631 if ( (flags & SM_VERIFY_GOODNESS)
1632 || (flags & SM_DO_REPL_CHECK)
1634 msg = CtdlFetchMessage(msgid, 1);
1635 if (msg == NULL) return(ERROR + ILLEGAL_VALUE);
1638 /* Perform replication checks if necessary */
1639 if ( (flags & SM_DO_REPL_CHECK) && (msg != NULL) ) {
1641 if (getroom(&CC->room,
1642 ((roomname != NULL) ? roomname : CC->room.QRname) )
1644 lprintf(CTDL_ERR, "No such room <%s>\n", roomname);
1645 if (msg != NULL) CtdlFreeMessage(msg);
1646 return(ERROR + ROOM_NOT_FOUND);
1649 if (ReplicationChecks(msg) != 0) {
1650 getroom(&CC->room, hold_rm);
1651 if (msg != NULL) CtdlFreeMessage(msg);
1653 "Did replication, and newer exists\n");
1658 /* Now the regular stuff */
1659 if (lgetroom(&CC->room,
1660 ((roomname != NULL) ? roomname : CC->room.QRname) )
1662 lprintf(CTDL_ERR, "No such room <%s>\n", roomname);
1663 if (msg != NULL) CtdlFreeMessage(msg);
1664 return(ERROR + ROOM_NOT_FOUND);
1667 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long));
1668 if (cdbfr == NULL) {
1672 msglist = malloc(cdbfr->len);
1673 if (msglist == NULL)
1674 lprintf(CTDL_ALERT, "ERROR malloc msglist!\n");
1675 num_msgs = cdbfr->len / sizeof(long);
1676 memcpy(msglist, cdbfr->ptr, cdbfr->len);
1681 /* Make sure the message doesn't already exist in this room. It
1682 * is absolutely taboo to have more than one reference to the same
1683 * message in a room.
1685 if (num_msgs > 0) for (i=0; i<num_msgs; ++i) {
1686 if (msglist[i] == msgid) {
1687 lputroom(&CC->room); /* unlock the room */
1688 getroom(&CC->room, hold_rm);
1689 if (msg != NULL) CtdlFreeMessage(msg);
1691 return(ERROR + ALREADY_EXISTS);
1695 /* Now add the new message */
1697 msglist = realloc(msglist, (num_msgs * sizeof(long)));
1699 if (msglist == NULL) {
1700 lprintf(CTDL_ALERT, "ERROR: can't realloc message list!\n");
1702 msglist[num_msgs - 1] = msgid;
1704 /* Sort the message list, so all the msgid's are in order */
1705 num_msgs = sort_msglist(msglist, num_msgs);
1707 /* Determine the highest message number */
1708 highest_msg = msglist[num_msgs - 1];
1710 /* Write it back to disk. */
1711 cdb_store(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long),
1712 msglist, num_msgs * sizeof(long));
1714 /* Free up the memory we used. */
1717 /* Update the highest-message pointer and unlock the room. */
1718 CC->room.QRhighest = highest_msg;
1719 lputroom(&CC->room);
1720 getroom(&CC->room, hold_rm);
1722 /* Bump the reference count for this message. */
1723 if ((flags & SM_DONT_BUMP_REF)==0) {
1724 AdjRefCount(msgid, +1);
1727 /* Return success. */
1728 if (msg != NULL) CtdlFreeMessage(msg);
1735 * Message base operation to save a new message to the message store
1736 * (returns new message number)
1738 * This is the back end for CtdlSubmitMsg() and should not be directly
1739 * called by server-side modules.
1742 long send_message(struct CtdlMessage *msg) {
1750 /* Get a new message number */
1751 newmsgid = get_new_message_number();
1752 snprintf(msgidbuf, sizeof msgidbuf, "%ld@%s", newmsgid, config.c_fqdn);
1754 /* Generate an ID if we don't have one already */
1755 if (msg->cm_fields['I']==NULL) {
1756 msg->cm_fields['I'] = strdup(msgidbuf);
1759 /* If the message is big, set its body aside for storage elsewhere */
1760 if (msg->cm_fields['M'] != NULL) {
1761 if (strlen(msg->cm_fields['M']) > BIGMSG) {
1763 holdM = msg->cm_fields['M'];
1764 msg->cm_fields['M'] = NULL;
1768 /* Serialize our data structure for storage in the database */
1769 serialize_message(&smr, msg);
1772 msg->cm_fields['M'] = holdM;
1776 cprintf("%d Unable to serialize message\n",
1777 ERROR + INTERNAL_ERROR);
1781 /* Write our little bundle of joy into the message base */
1782 if (cdb_store(CDB_MSGMAIN, &newmsgid, sizeof(long),
1783 smr.ser, smr.len) < 0) {
1784 lprintf(CTDL_ERR, "Can't store message\n");
1788 cdb_store(CDB_BIGMSGS,
1798 /* Free the memory we used for the serialized message */
1801 /* Return the *local* message ID to the caller
1802 * (even if we're storing an incoming network message)
1810 * Serialize a struct CtdlMessage into the format used on disk and network.
1812 * This function loads up a "struct ser_ret" (defined in server.h) which
1813 * contains the length of the serialized message and a pointer to the
1814 * serialized message in memory. THE LATTER MUST BE FREED BY THE CALLER.
1816 void serialize_message(struct ser_ret *ret, /* return values */
1817 struct CtdlMessage *msg) /* unserialized msg */
1821 static char *forder = FORDER;
1823 if (is_valid_message(msg) == 0) return; /* self check */
1826 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL)
1827 ret->len = ret->len +
1828 strlen(msg->cm_fields[(int)forder[i]]) + 2;
1830 lprintf(CTDL_DEBUG, "serialize_message() calling malloc(%ld)\n", (long)ret->len);
1831 ret->ser = malloc(ret->len);
1832 if (ret->ser == NULL) {
1838 ret->ser[1] = msg->cm_anon_type;
1839 ret->ser[2] = msg->cm_format_type;
1842 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL) {
1843 ret->ser[wlen++] = (char)forder[i];
1844 strcpy(&ret->ser[wlen], msg->cm_fields[(int)forder[i]]);
1845 wlen = wlen + strlen(msg->cm_fields[(int)forder[i]]) + 1;
1847 if (ret->len != wlen) lprintf(CTDL_ERR, "ERROR: len=%ld wlen=%ld\n",
1848 (long)ret->len, (long)wlen);
1856 * Back end for the ReplicationChecks() function
1858 void check_repl(long msgnum, void *userdata) {
1859 struct CtdlMessage *msg;
1860 time_t timestamp = (-1L);
1862 lprintf(CTDL_DEBUG, "check_repl() found message %ld\n", msgnum);
1863 msg = CtdlFetchMessage(msgnum, 1);
1864 if (msg == NULL) return;
1865 if (msg->cm_fields['T'] != NULL) {
1866 timestamp = atol(msg->cm_fields['T']);
1868 CtdlFreeMessage(msg);
1870 if (timestamp > msg_repl->highest) {
1871 msg_repl->highest = timestamp; /* newer! */
1872 lprintf(CTDL_DEBUG, "newer!\n");
1875 lprintf(CTDL_DEBUG, "older!\n");
1877 /* Existing isn't newer? Then delete the old one(s). */
1878 CtdlDeleteMessages(CC->room.QRname, msgnum, "");
1883 * Check to see if any messages already exist which carry the same Extended ID
1887 * -> With older timestamps: delete them and return 0. Message will be saved.
1888 * -> With newer timestamps: return 1. Message save will be aborted.
1890 int ReplicationChecks(struct CtdlMessage *msg) {
1891 struct CtdlMessage *template;
1894 lprintf(CTDL_DEBUG, "ReplicationChecks() started\n");
1895 /* No extended id? Don't do anything. */
1896 if (msg->cm_fields['E'] == NULL) return 0;
1897 if (strlen(msg->cm_fields['E']) == 0) return 0;
1898 lprintf(CTDL_DEBUG, "Extended ID: <%s>\n", msg->cm_fields['E']);
1900 CtdlAllocUserData(SYM_REPL, sizeof(struct repl));
1901 strcpy(msg_repl->extended_id, msg->cm_fields['E']);
1902 msg_repl->highest = atol(msg->cm_fields['T']);
1904 template = (struct CtdlMessage *) malloc(sizeof(struct CtdlMessage));
1905 memset(template, 0, sizeof(struct CtdlMessage));
1906 template->cm_fields['E'] = strdup(msg->cm_fields['E']);
1908 CtdlForEachMessage(MSGS_ALL, 0L, NULL, template, check_repl, NULL);
1910 /* If a newer message exists with the same Extended ID, abort
1913 if (msg_repl->highest > atol(msg->cm_fields['T']) ) {
1917 CtdlFreeMessage(template);
1918 lprintf(CTDL_DEBUG, "ReplicationChecks() returning %d\n", abort_this);
1926 * Save a message to disk and submit it into the delivery system.
1928 long CtdlSubmitMsg(struct CtdlMessage *msg, /* message to save */
1929 struct recptypes *recps, /* recipients (if mail) */
1930 char *force /* force a particular room? */
1933 char hold_rm[ROOMNAMELEN];
1934 char actual_rm[ROOMNAMELEN];
1935 char force_room[ROOMNAMELEN];
1936 char content_type[SIZ]; /* We have to learn this */
1937 char recipient[SIZ];
1940 struct ctdluser userbuf;
1942 struct MetaData smi;
1943 FILE *network_fp = NULL;
1944 static int seqnum = 1;
1945 struct CtdlMessage *imsg = NULL;
1948 char *hold_R, *hold_D;
1950 lprintf(CTDL_DEBUG, "CtdlSubmitMsg() called\n");
1951 if (is_valid_message(msg) == 0) return(-1); /* self check */
1953 /* If this message has no timestamp, we take the liberty of
1954 * giving it one, right now.
1956 if (msg->cm_fields['T'] == NULL) {
1957 lprintf(CTDL_DEBUG, "Generating timestamp\n");
1958 snprintf(aaa, sizeof aaa, "%ld", (long)time(NULL));
1959 msg->cm_fields['T'] = strdup(aaa);
1962 /* If this message has no path, we generate one.
1964 if (msg->cm_fields['P'] == NULL) {
1965 lprintf(CTDL_DEBUG, "Generating path\n");
1966 if (msg->cm_fields['A'] != NULL) {
1967 msg->cm_fields['P'] = strdup(msg->cm_fields['A']);
1968 for (a=0; a<strlen(msg->cm_fields['P']); ++a) {
1969 if (isspace(msg->cm_fields['P'][a])) {
1970 msg->cm_fields['P'][a] = ' ';
1975 msg->cm_fields['P'] = strdup("unknown");
1979 if (force == NULL) {
1980 strcpy(force_room, "");
1983 strcpy(force_room, force);
1986 /* Learn about what's inside, because it's what's inside that counts */
1987 lprintf(CTDL_DEBUG, "Learning what's inside\n");
1988 if (msg->cm_fields['M'] == NULL) {
1989 lprintf(CTDL_ERR, "ERROR: attempt to save message with NULL body\n");
1993 switch (msg->cm_format_type) {
1995 strcpy(content_type, "text/x-citadel-variformat");
1998 strcpy(content_type, "text/plain");
2001 strcpy(content_type, "text/plain");
2002 /* advance past header fields */
2003 mptr = msg->cm_fields['M'];
2006 if (!strncasecmp(mptr, "Content-type: ", 14)) {
2007 safestrncpy(content_type, mptr,
2008 sizeof(content_type));
2009 strcpy(content_type, &content_type[14]);
2010 for (a = 0; a < strlen(content_type); ++a)
2011 if ((content_type[a] == ';')
2012 || (content_type[a] == ' ')
2013 || (content_type[a] == 13)
2014 || (content_type[a] == 10))
2015 content_type[a] = 0;
2022 /* Goto the correct room */
2023 lprintf(CTDL_DEBUG, "Selected room %s\n", (recps) ? CC->room.QRname : SENTITEMS);
2024 strcpy(hold_rm, CC->room.QRname);
2025 strcpy(actual_rm, CC->room.QRname);
2026 if (recps != NULL) {
2027 strcpy(actual_rm, SENTITEMS);
2030 /* If the user is a twit, move to the twit room for posting */
2031 lprintf(CTDL_DEBUG, "Handling twit stuff: %s\n",
2032 (CC->user.axlevel == 2) ? config.c_twitroom : "OK");
2034 if (CC->user.axlevel == 2) {
2035 strcpy(hold_rm, actual_rm);
2036 strcpy(actual_rm, config.c_twitroom);
2040 /* ...or if this message is destined for Aide> then go there. */
2041 if (strlen(force_room) > 0) {
2042 strcpy(actual_rm, force_room);
2045 lprintf(CTDL_DEBUG, "Final selection: %s\n", actual_rm);
2046 if (strcasecmp(actual_rm, CC->room.QRname)) {
2047 getroom(&CC->room, actual_rm);
2051 * If this message has no O (room) field, generate one.
2053 if (msg->cm_fields['O'] == NULL) {
2054 msg->cm_fields['O'] = strdup(CC->room.QRname);
2057 /* Perform "before save" hooks (aborting if any return nonzero) */
2058 lprintf(CTDL_DEBUG, "Performing before-save hooks\n");
2059 if (PerformMessageHooks(msg, EVT_BEFORESAVE) > 0) return(-1);
2061 /* If this message has an Extended ID, perform replication checks */
2062 lprintf(CTDL_DEBUG, "Performing replication checks\n");
2063 if (ReplicationChecks(msg) > 0) return(-1);
2065 /* Save it to disk */
2066 lprintf(CTDL_DEBUG, "Saving to disk\n");
2067 newmsgid = send_message(msg);
2068 if (newmsgid <= 0L) return(-1);
2070 /* Write a supplemental message info record. This doesn't have to
2071 * be a critical section because nobody else knows about this message
2074 lprintf(CTDL_DEBUG, "Creating MetaData record\n");
2075 memset(&smi, 0, sizeof(struct MetaData));
2076 smi.meta_msgnum = newmsgid;
2077 smi.meta_refcount = 0;
2078 safestrncpy(smi.meta_content_type, content_type, 64);
2081 /* Now figure out where to store the pointers */
2082 lprintf(CTDL_DEBUG, "Storing pointers\n");
2084 /* If this is being done by the networker delivering a private
2085 * message, we want to BYPASS saving the sender's copy (because there
2086 * is no local sender; it would otherwise go to the Trashcan).
2088 if ((!CC->internal_pgm) || (recps == NULL)) {
2089 if (CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 0) != 0) {
2090 lprintf(CTDL_ERR, "ERROR saving message pointer!\n");
2091 CtdlSaveMsgPointerInRoom(config.c_aideroom, newmsgid, 0);
2095 /* For internet mail, drop a copy in the outbound queue room */
2097 if (recps->num_internet > 0) {
2098 CtdlSaveMsgPointerInRoom(SMTP_SPOOLOUT_ROOM, newmsgid, 0);
2101 /* If other rooms are specified, drop them there too. */
2103 if (recps->num_room > 0)
2104 for (i=0; i<num_tokens(recps->recp_room, '|'); ++i) {
2105 extract(recipient, recps->recp_room, i);
2106 lprintf(CTDL_DEBUG, "Delivering to local room <%s>\n", recipient);
2107 CtdlSaveMsgPointerInRoom(recipient, newmsgid, 0);
2110 /* Bump this user's messages posted counter. */
2111 lprintf(CTDL_DEBUG, "Updating user\n");
2112 lgetuser(&CC->user, CC->curr_user);
2113 CC->user.posted = CC->user.posted + 1;
2114 lputuser(&CC->user);
2116 /* If this is private, local mail, make a copy in the
2117 * recipient's mailbox and bump the reference count.
2120 if (recps->num_local > 0)
2121 for (i=0; i<num_tokens(recps->recp_local, '|'); ++i) {
2122 extract(recipient, recps->recp_local, i);
2123 lprintf(CTDL_DEBUG, "Delivering private local mail to <%s>\n",
2125 if (getuser(&userbuf, recipient) == 0) {
2126 MailboxName(actual_rm, sizeof actual_rm, &userbuf, MAILROOM);
2127 CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 0);
2128 BumpNewMailCounter(userbuf.usernum);
2131 lprintf(CTDL_DEBUG, "No user <%s>\n", recipient);
2132 CtdlSaveMsgPointerInRoom(config.c_aideroom, newmsgid, 0);
2136 /* Perform "after save" hooks */
2137 lprintf(CTDL_DEBUG, "Performing after-save hooks\n");
2138 PerformMessageHooks(msg, EVT_AFTERSAVE);
2140 /* For IGnet mail, we have to save a new copy into the spooler for
2141 * each recipient, with the R and D fields set to the recipient and
2142 * destination-node. This has two ugly side effects: all other
2143 * recipients end up being unlisted in this recipient's copy of the
2144 * message, and it has to deliver multiple messages to the same
2145 * node. We'll revisit this again in a year or so when everyone has
2146 * a network spool receiver that can handle the new style messages.
2149 if (recps->num_ignet > 0)
2150 for (i=0; i<num_tokens(recps->recp_ignet, '|'); ++i) {
2151 extract(recipient, recps->recp_ignet, i);
2153 hold_R = msg->cm_fields['R'];
2154 hold_D = msg->cm_fields['D'];
2155 msg->cm_fields['R'] = malloc(SIZ);
2156 msg->cm_fields['D'] = malloc(SIZ);
2157 extract_token(msg->cm_fields['R'], recipient, 0, '@');
2158 extract_token(msg->cm_fields['D'], recipient, 1, '@');
2160 serialize_message(&smr, msg);
2162 snprintf(aaa, sizeof aaa,
2163 "./network/spoolin/netmail.%04lx.%04x.%04x",
2164 (long) getpid(), CC->cs_pid, ++seqnum);
2165 network_fp = fopen(aaa, "wb+");
2166 if (network_fp != NULL) {
2167 fwrite(smr.ser, smr.len, 1, network_fp);
2173 free(msg->cm_fields['R']);
2174 free(msg->cm_fields['D']);
2175 msg->cm_fields['R'] = hold_R;
2176 msg->cm_fields['D'] = hold_D;
2179 /* Go back to the room we started from */
2180 lprintf(CTDL_DEBUG, "Returning to original room %s\n", hold_rm);
2181 if (strcasecmp(hold_rm, CC->room.QRname))
2182 getroom(&CC->room, hold_rm);
2184 /* For internet mail, generate delivery instructions.
2185 * Yes, this is recursive. Deal with it. Infinite recursion does
2186 * not happen because the delivery instructions message does not
2187 * contain a recipient.
2190 if (recps->num_internet > 0) {
2191 lprintf(CTDL_DEBUG, "Generating delivery instructions\n");
2192 instr = malloc(SIZ * 2);
2193 snprintf(instr, SIZ * 2,
2194 "Content-type: %s\n\nmsgid|%ld\nsubmitted|%ld\n"
2196 SPOOLMIME, newmsgid, (long)time(NULL),
2197 msg->cm_fields['A'], msg->cm_fields['N']
2200 for (i=0; i<num_tokens(recps->recp_internet, '|'); ++i) {
2201 size_t tmp = strlen(instr);
2202 extract(recipient, recps->recp_internet, i);
2203 snprintf(&instr[tmp], SIZ * 2 - tmp,
2204 "remote|%s|0||\n", recipient);
2207 imsg = malloc(sizeof(struct CtdlMessage));
2208 memset(imsg, 0, sizeof(struct CtdlMessage));
2209 imsg->cm_magic = CTDLMESSAGE_MAGIC;
2210 imsg->cm_anon_type = MES_NORMAL;
2211 imsg->cm_format_type = FMT_RFC822;
2212 imsg->cm_fields['A'] = strdup("Citadel");
2213 imsg->cm_fields['M'] = instr;
2214 CtdlSubmitMsg(imsg, NULL, SMTP_SPOOLOUT_ROOM);
2215 CtdlFreeMessage(imsg);
2224 * Convenience function for generating small administrative messages.
2226 void quickie_message(char *from, char *to, char *room, char *text,
2227 int format_type, char *subject)
2229 struct CtdlMessage *msg;
2230 struct recptypes *recp = NULL;
2232 msg = malloc(sizeof(struct CtdlMessage));
2233 memset(msg, 0, sizeof(struct CtdlMessage));
2234 msg->cm_magic = CTDLMESSAGE_MAGIC;
2235 msg->cm_anon_type = MES_NORMAL;
2236 msg->cm_format_type = format_type;
2237 msg->cm_fields['A'] = strdup(from);
2238 if (room != NULL) msg->cm_fields['O'] = strdup(room);
2239 msg->cm_fields['N'] = strdup(NODENAME);
2241 msg->cm_fields['R'] = strdup(to);
2242 recp = validate_recipients(to);
2244 if (subject != NULL) {
2245 msg->cm_fields['U'] = strdup(subject);
2247 msg->cm_fields['M'] = strdup(text);
2249 CtdlSubmitMsg(msg, recp, room);
2250 CtdlFreeMessage(msg);
2251 if (recp != NULL) free(recp);
2257 * Back end function used by CtdlMakeMessage() and similar functions
2259 char *CtdlReadMessageBody(char *terminator, /* token signalling EOT */
2260 size_t maxlen, /* maximum message length */
2261 char *exist, /* if non-null, append to it;
2262 exist is ALWAYS freed */
2263 int crlf /* CRLF newlines instead of LF */
2267 size_t message_len = 0;
2268 size_t buffer_len = 0;
2274 if (exist == NULL) {
2281 message_len = strlen(exist);
2282 buffer_len = message_len + 4096;
2283 m = realloc(exist, buffer_len);
2290 /* flush the input if we have nowhere to store it */
2295 /* read in the lines of message text one by one */
2297 if (client_gets(buf) < 1) finished = 1;
2298 if (!strcmp(buf, terminator)) finished = 1;
2300 strcat(buf, "\r\n");
2306 if ( (!flushing) && (!finished) ) {
2307 /* Measure the line */
2308 linelen = strlen(buf);
2310 /* augment the buffer if we have to */
2311 if ((message_len + linelen) >= buffer_len) {
2312 ptr = realloc(m, (buffer_len * 2) );
2313 if (ptr == NULL) { /* flush if can't allocate */
2316 buffer_len = (buffer_len * 2);
2318 lprintf(CTDL_DEBUG, "buffer_len is now %ld\n", (long)buffer_len);
2322 /* Add the new line to the buffer. NOTE: this loop must avoid
2323 * using functions like strcat() and strlen() because they
2324 * traverse the entire buffer upon every call, and doing that
2325 * for a multi-megabyte message slows it down beyond usability.
2327 strcpy(&m[message_len], buf);
2328 message_len += linelen;
2331 /* if we've hit the max msg length, flush the rest */
2332 if (message_len >= maxlen) flushing = 1;
2334 } while (!finished);
2342 * Build a binary message to be saved on disk.
2343 * (NOTE: if you supply 'preformatted_text', the buffer you give it
2344 * will become part of the message. This means you are no longer
2345 * responsible for managing that memory -- it will be freed along with
2346 * the rest of the fields when CtdlFreeMessage() is called.)
2349 struct CtdlMessage *CtdlMakeMessage(
2350 struct ctdluser *author, /* author's user structure */
2351 char *recipient, /* NULL if it's not mail */
2352 char *room, /* room where it's going */
2353 int type, /* see MES_ types in header file */
2354 int format_type, /* variformat, plain text, MIME... */
2355 char *fake_name, /* who we're masquerading as */
2356 char *subject, /* Subject (optional) */
2357 char *preformatted_text /* ...or NULL to read text from client */
2359 char dest_node[SIZ];
2361 struct CtdlMessage *msg;
2363 msg = malloc(sizeof(struct CtdlMessage));
2364 memset(msg, 0, sizeof(struct CtdlMessage));
2365 msg->cm_magic = CTDLMESSAGE_MAGIC;
2366 msg->cm_anon_type = type;
2367 msg->cm_format_type = format_type;
2369 /* Don't confuse the poor folks if it's not routed mail. */
2370 strcpy(dest_node, "");
2374 snprintf(buf, sizeof buf, "cit%ld", author->usernum); /* Path */
2375 msg->cm_fields['P'] = strdup(buf);
2377 snprintf(buf, sizeof buf, "%ld", (long)time(NULL)); /* timestamp */
2378 msg->cm_fields['T'] = strdup(buf);
2380 if (fake_name[0]) /* author */
2381 msg->cm_fields['A'] = strdup(fake_name);
2383 msg->cm_fields['A'] = strdup(author->fullname);
2385 if (CC->room.QRflags & QR_MAILBOX) { /* room */
2386 msg->cm_fields['O'] = strdup(&CC->room.QRname[11]);
2389 msg->cm_fields['O'] = strdup(CC->room.QRname);
2392 msg->cm_fields['N'] = strdup(NODENAME); /* nodename */
2393 msg->cm_fields['H'] = strdup(HUMANNODE); /* hnodename */
2395 if (recipient[0] != 0) {
2396 msg->cm_fields['R'] = strdup(recipient);
2398 if (dest_node[0] != 0) {
2399 msg->cm_fields['D'] = strdup(dest_node);
2402 if ( (author == &CC->user) && (strlen(CC->cs_inet_email) > 0) ) {
2403 msg->cm_fields['F'] = strdup(CC->cs_inet_email);
2406 if (subject != NULL) {
2408 if (strlen(subject) > 0) {
2409 msg->cm_fields['U'] = strdup(subject);
2413 if (preformatted_text != NULL) {
2414 msg->cm_fields['M'] = preformatted_text;
2417 msg->cm_fields['M'] = CtdlReadMessageBody("000",
2418 config.c_maxmsglen, NULL, 0);
2426 * Check to see whether we have permission to post a message in the current
2427 * room. Returns a *CITADEL ERROR CODE* and puts a message in errmsgbuf, or
2428 * returns 0 on success.
2430 int CtdlDoIHavePermissionToPostInThisRoom(char *errmsgbuf, size_t n) {
2432 if (!(CC->logged_in)) {
2433 snprintf(errmsgbuf, n, "Not logged in.");
2434 return (ERROR + NOT_LOGGED_IN);
2437 if ((CC->user.axlevel < 2)
2438 && ((CC->room.QRflags & QR_MAILBOX) == 0)) {
2439 snprintf(errmsgbuf, n, "Need to be validated to enter "
2440 "(except in %s> to sysop)", MAILROOM);
2441 return (ERROR + HIGHER_ACCESS_REQUIRED);
2444 if ((CC->user.axlevel < 4)
2445 && (CC->room.QRflags & QR_NETWORK)) {
2446 snprintf(errmsgbuf, n, "Need net privileges to enter here.");
2447 return (ERROR + HIGHER_ACCESS_REQUIRED);
2450 if ((CC->user.axlevel < 6)
2451 && (CC->room.QRflags & QR_READONLY)) {
2452 snprintf(errmsgbuf, n, "Sorry, this is a read-only room.");
2453 return (ERROR + HIGHER_ACCESS_REQUIRED);
2456 strcpy(errmsgbuf, "Ok");
2462 * Check to see if the specified user has Internet mail permission
2463 * (returns nonzero if permission is granted)
2465 int CtdlCheckInternetMailPermission(struct ctdluser *who) {
2467 /* Do not allow twits to send Internet mail */
2468 if (who->axlevel <= 2) return(0);
2470 /* Globally enabled? */
2471 if (config.c_restrict == 0) return(1);
2473 /* User flagged ok? */
2474 if (who->flags & US_INTERNET) return(2);
2476 /* Aide level access? */
2477 if (who->axlevel >= 6) return(3);
2479 /* No mail for you! */
2486 * Validate recipients, count delivery types and errors, and handle aliasing
2487 * FIXME check for dupes!!!!!
2489 struct recptypes *validate_recipients(char *recipients) {
2490 struct recptypes *ret;
2491 char this_recp[SIZ];
2492 char this_recp_cooked[SIZ];
2498 struct ctdluser tempUS;
2499 struct ctdlroom tempQR;
2502 ret = (struct recptypes *) malloc(sizeof(struct recptypes));
2503 if (ret == NULL) return(NULL);
2504 memset(ret, 0, sizeof(struct recptypes));
2507 ret->num_internet = 0;
2512 if (recipients == NULL) {
2515 else if (strlen(recipients) == 0) {
2519 /* Change all valid separator characters to commas */
2520 for (i=0; i<strlen(recipients); ++i) {
2521 if ((recipients[i] == ';') || (recipients[i] == '|')) {
2522 recipients[i] = ',';
2527 num_recps = num_tokens(recipients, ',');
2530 if (num_recps > 0) for (i=0; i<num_recps; ++i) {
2531 extract_token(this_recp, recipients, i, ',');
2533 lprintf(CTDL_DEBUG, "Evaluating recipient #%d <%s>\n", i, this_recp);
2534 mailtype = alias(this_recp);
2535 mailtype = alias(this_recp);
2536 mailtype = alias(this_recp);
2537 for (j=0; j<=strlen(this_recp); ++j) {
2538 if (this_recp[j]=='_') {
2539 this_recp_cooked[j] = ' ';
2542 this_recp_cooked[j] = this_recp[j];
2548 if (!strcasecmp(this_recp, "sysop")) {
2550 strcpy(this_recp, config.c_aideroom);
2551 if (strlen(ret->recp_room) > 0) {
2552 strcat(ret->recp_room, "|");
2554 strcat(ret->recp_room, this_recp);
2556 else if (getuser(&tempUS, this_recp) == 0) {
2558 strcpy(this_recp, tempUS.fullname);
2559 if (strlen(ret->recp_local) > 0) {
2560 strcat(ret->recp_local, "|");
2562 strcat(ret->recp_local, this_recp);
2564 else if (getuser(&tempUS, this_recp_cooked) == 0) {
2566 strcpy(this_recp, tempUS.fullname);
2567 if (strlen(ret->recp_local) > 0) {
2568 strcat(ret->recp_local, "|");
2570 strcat(ret->recp_local, this_recp);
2572 else if ( (!strncasecmp(this_recp, "room_", 5))
2573 && (!getroom(&tempQR, &this_recp_cooked[5])) ) {
2575 if (strlen(ret->recp_room) > 0) {
2576 strcat(ret->recp_room, "|");
2578 strcat(ret->recp_room, &this_recp_cooked[5]);
2586 /* Yes, you're reading this correctly: if the target
2587 * domain points back to the local system or an attached
2588 * Citadel directory, the address is invalid. That's
2589 * because if the address were valid, we would have
2590 * already translated it to a local address by now.
2592 if (IsDirectory(this_recp)) {
2597 ++ret->num_internet;
2598 if (strlen(ret->recp_internet) > 0) {
2599 strcat(ret->recp_internet, "|");
2601 strcat(ret->recp_internet, this_recp);
2606 if (strlen(ret->recp_ignet) > 0) {
2607 strcat(ret->recp_ignet, "|");
2609 strcat(ret->recp_ignet, this_recp);
2617 if (strlen(ret->errormsg) == 0) {
2618 snprintf(append, sizeof append,
2619 "Invalid recipient: %s",
2623 snprintf(append, sizeof append,
2626 if ( (strlen(ret->errormsg) + strlen(append)) < SIZ) {
2627 strcat(ret->errormsg, append);
2631 if (strlen(ret->display_recp) == 0) {
2632 strcpy(append, this_recp);
2635 snprintf(append, sizeof append, ", %s",
2638 if ( (strlen(ret->display_recp)+strlen(append)) < SIZ) {
2639 strcat(ret->display_recp, append);
2644 if ((ret->num_local + ret->num_internet + ret->num_ignet +
2645 ret->num_room + ret->num_error) == 0) {
2647 strcpy(ret->errormsg, "No recipients specified.");
2650 lprintf(CTDL_DEBUG, "validate_recipients()\n");
2651 lprintf(CTDL_DEBUG, " local: %d <%s>\n", ret->num_local, ret->recp_local);
2652 lprintf(CTDL_DEBUG, " room: %d <%s>\n", ret->num_room, ret->recp_room);
2653 lprintf(CTDL_DEBUG, " inet: %d <%s>\n", ret->num_internet, ret->recp_internet);
2654 lprintf(CTDL_DEBUG, " ignet: %d <%s>\n", ret->num_ignet, ret->recp_ignet);
2655 lprintf(CTDL_DEBUG, " error: %d <%s>\n", ret->num_error, ret->errormsg);
2663 * message entry - mode 0 (normal)
2665 void cmd_ent0(char *entargs)
2669 char masquerade_as[SIZ];
2671 int format_type = 0;
2672 char newusername[SIZ];
2673 struct CtdlMessage *msg;
2677 struct recptypes *valid = NULL;
2682 post = extract_int(entargs, 0);
2683 extract(recp, entargs, 1);
2684 anon_flag = extract_int(entargs, 2);
2685 format_type = extract_int(entargs, 3);
2686 extract(subject, entargs, 4);
2688 /* first check to make sure the request is valid. */
2690 err = CtdlDoIHavePermissionToPostInThisRoom(errmsg, sizeof errmsg);
2692 cprintf("%d %s\n", err, errmsg);
2696 /* Check some other permission type things. */
2699 if (CC->user.axlevel < 6) {
2700 cprintf("%d You don't have permission to masquerade.\n",
2701 ERROR + HIGHER_ACCESS_REQUIRED);
2704 extract(newusername, entargs, 5);
2705 memset(CC->fake_postname, 0, sizeof(CC->fake_postname) );
2706 safestrncpy(CC->fake_postname, newusername,
2707 sizeof(CC->fake_postname) );
2708 cprintf("%d ok\n", CIT_OK);
2711 CC->cs_flags |= CS_POSTING;
2713 /* In the Mail> room we have to behave a little differently --
2714 * make sure the user has specified at least one recipient. Then
2715 * validate the recipient(s).
2717 if ( (CC->room.QRflags & QR_MAILBOX)
2718 && (!strcasecmp(&CC->room.QRname[11], MAILROOM)) ) {
2720 if (CC->user.axlevel < 2) {
2721 strcpy(recp, "sysop");
2724 valid = validate_recipients(recp);
2725 if (valid->num_error > 0) {
2727 ERROR + NO_SUCH_USER, valid->errormsg);
2731 if (valid->num_internet > 0) {
2732 if (CtdlCheckInternetMailPermission(&CC->user)==0) {
2733 cprintf("%d You do not have permission "
2734 "to send Internet mail.\n",
2735 ERROR + HIGHER_ACCESS_REQUIRED);
2741 if ( ( (valid->num_internet + valid->num_ignet) > 0)
2742 && (CC->user.axlevel < 4) ) {
2743 cprintf("%d Higher access required for network mail.\n",
2744 ERROR + HIGHER_ACCESS_REQUIRED);
2749 if ((RESTRICT_INTERNET == 1) && (valid->num_internet > 0)
2750 && ((CC->user.flags & US_INTERNET) == 0)
2751 && (!CC->internal_pgm)) {
2752 cprintf("%d You don't have access to Internet mail.\n",
2753 ERROR + HIGHER_ACCESS_REQUIRED);
2760 /* Is this a room which has anonymous-only or anonymous-option? */
2761 anonymous = MES_NORMAL;
2762 if (CC->room.QRflags & QR_ANONONLY) {
2763 anonymous = MES_ANONONLY;
2765 if (CC->room.QRflags & QR_ANONOPT) {
2766 if (anon_flag == 1) { /* only if the user requested it */
2767 anonymous = MES_ANONOPT;
2771 if ((CC->room.QRflags & QR_MAILBOX) == 0) {
2775 /* If we're only checking the validity of the request, return
2776 * success without creating the message.
2779 cprintf("%d %s\n", CIT_OK,
2780 ((valid != NULL) ? valid->display_recp : "") );
2785 /* Handle author masquerading */
2786 if (CC->fake_postname[0]) {
2787 strcpy(masquerade_as, CC->fake_postname);
2789 else if (CC->fake_username[0]) {
2790 strcpy(masquerade_as, CC->fake_username);
2793 strcpy(masquerade_as, "");
2796 /* Read in the message from the client. */
2797 cprintf("%d send message\n", SEND_LISTING);
2798 msg = CtdlMakeMessage(&CC->user, recp,
2799 CC->room.QRname, anonymous, format_type,
2800 masquerade_as, subject, NULL);
2803 CtdlSubmitMsg(msg, valid, "");
2804 CtdlFreeMessage(msg);
2806 CC->fake_postname[0] = '\0';
2814 * API function to delete messages which match a set of criteria
2815 * (returns the actual number of messages deleted)
2817 int CtdlDeleteMessages(char *room_name, /* which room */
2818 long dmsgnum, /* or "0" for any */
2819 char *content_type /* or "" for any */
2823 struct ctdlroom qrbuf;
2824 struct cdbdata *cdbfr;
2825 long *msglist = NULL;
2826 long *dellist = NULL;
2829 int num_deleted = 0;
2831 struct MetaData smi;
2833 lprintf(CTDL_DEBUG, "CtdlDeleteMessages(%s, %ld, %s)\n",
2834 room_name, dmsgnum, content_type);
2836 /* get room record, obtaining a lock... */
2837 if (lgetroom(&qrbuf, room_name) != 0) {
2838 lprintf(CTDL_ERR, "CtdlDeleteMessages(): Room <%s> not found\n",
2840 return (0); /* room not found */
2842 cdbfr = cdb_fetch(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long));
2844 if (cdbfr != NULL) {
2845 msglist = malloc(cdbfr->len);
2846 dellist = malloc(cdbfr->len);
2847 memcpy(msglist, cdbfr->ptr, cdbfr->len);
2848 num_msgs = cdbfr->len / sizeof(long);
2852 for (i = 0; i < num_msgs; ++i) {
2855 /* Set/clear a bit for each criterion */
2857 if ((dmsgnum == 0L) || (msglist[i] == dmsgnum)) {
2858 delete_this |= 0x01;
2860 if (strlen(content_type) == 0) {
2861 delete_this |= 0x02;
2863 GetMetaData(&smi, msglist[i]);
2864 if (!strcasecmp(smi.meta_content_type,
2866 delete_this |= 0x02;
2870 /* Delete message only if all bits are set */
2871 if (delete_this == 0x03) {
2872 dellist[num_deleted++] = msglist[i];
2877 num_msgs = sort_msglist(msglist, num_msgs);
2878 cdb_store(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long),
2879 msglist, (num_msgs * sizeof(long)));
2881 qrbuf.QRhighest = msglist[num_msgs - 1];
2885 /* Go through the messages we pulled out of the index, and decrement
2886 * their reference counts by 1. If this is the only room the message
2887 * was in, the reference count will reach zero and the message will
2888 * automatically be deleted from the database. We do this in a
2889 * separate pass because there might be plug-in hooks getting called,
2890 * and we don't want that happening during an S_ROOMS critical
2893 if (num_deleted) for (i=0; i<num_deleted; ++i) {
2894 PerformDeleteHooks(qrbuf.QRname, dellist[i]);
2895 AdjRefCount(dellist[i], -1);
2898 /* Now free the memory we used, and go away. */
2899 if (msglist != NULL) free(msglist);
2900 if (dellist != NULL) free(dellist);
2901 lprintf(CTDL_DEBUG, "%d message(s) deleted.\n", num_deleted);
2902 return (num_deleted);
2908 * Check whether the current user has permission to delete messages from
2909 * the current room (returns 1 for yes, 0 for no)
2911 int CtdlDoIHavePermissionToDeleteMessagesFromThisRoom(void) {
2912 getuser(&CC->user, CC->curr_user);
2913 if ((CC->user.axlevel < 6)
2914 && (CC->user.usernum != CC->room.QRroomaide)
2915 && ((CC->room.QRflags & QR_MAILBOX) == 0)
2916 && (!(CC->internal_pgm))) {
2925 * Delete message from current room
2927 void cmd_dele(char *delstr)
2932 if (CtdlDoIHavePermissionToDeleteMessagesFromThisRoom() == 0) {
2933 cprintf("%d Higher access required.\n",
2934 ERROR + HIGHER_ACCESS_REQUIRED);
2937 delnum = extract_long(delstr, 0);
2939 num_deleted = CtdlDeleteMessages(CC->room.QRname, delnum, "");
2942 cprintf("%d %d message%s deleted.\n", CIT_OK,
2943 num_deleted, ((num_deleted != 1) ? "s" : ""));
2945 cprintf("%d Message %ld not found.\n", ERROR + MESSAGE_NOT_FOUND, delnum);
2951 * Back end API function for moves and deletes
2953 int CtdlCopyMsgToRoom(long msgnum, char *dest) {
2956 err = CtdlSaveMsgPointerInRoom(dest, msgnum,
2957 (SM_VERIFY_GOODNESS | SM_DO_REPL_CHECK) );
2958 if (err != 0) return(err);
2966 * move or copy a message to another room
2968 void cmd_move(char *args)
2972 struct ctdlroom qtemp;
2978 num = extract_long(args, 0);
2979 extract(targ, args, 1);
2980 targ[ROOMNAMELEN - 1] = 0;
2981 is_copy = extract_int(args, 2);
2983 if (getroom(&qtemp, targ) != 0) {
2984 cprintf("%d '%s' does not exist.\n",
2985 ERROR + ROOM_NOT_FOUND, targ);
2989 getuser(&CC->user, CC->curr_user);
2990 ra = CtdlRoomAccess(&qtemp, &CC->user);
2992 /* Check for permission to perform this operation.
2993 * Remember: "CC->room" is source, "qtemp" is target.
2997 /* Aides can move/copy */
2998 if (CC->user.axlevel >= 6) permit = 1;
3000 /* Room aides can move/copy */
3001 if (CC->user.usernum == CC->room.QRroomaide) permit = 1;
3003 /* Permit move/copy from personal rooms */
3004 if ((CC->room.QRflags & QR_MAILBOX)
3005 && (qtemp.QRflags & QR_MAILBOX)) permit = 1;
3007 /* Permit only copy from public to personal room */
3009 && (!(CC->room.QRflags & QR_MAILBOX))
3010 && (qtemp.QRflags & QR_MAILBOX)) permit = 1;
3012 /* User must have access to target room */
3013 if (!(ra & UA_KNOWN)) permit = 0;
3016 cprintf("%d Higher access required.\n",
3017 ERROR + HIGHER_ACCESS_REQUIRED);
3021 err = CtdlCopyMsgToRoom(num, targ);
3023 cprintf("%d Cannot store message in %s: error %d\n",
3028 /* Now delete the message from the source room,
3029 * if this is a 'move' rather than a 'copy' operation.
3032 CtdlDeleteMessages(CC->room.QRname, num, "");
3035 cprintf("%d Message %s.\n", CIT_OK, (is_copy ? "copied" : "moved") );
3041 * GetMetaData() - Get the supplementary record for a message
3043 void GetMetaData(struct MetaData *smibuf, long msgnum)
3046 struct cdbdata *cdbsmi;
3049 memset(smibuf, 0, sizeof(struct MetaData));
3050 smibuf->meta_msgnum = msgnum;
3051 smibuf->meta_refcount = 1; /* Default reference count is 1 */
3053 /* Use the negative of the message number for its supp record index */
3054 TheIndex = (0L - msgnum);
3056 cdbsmi = cdb_fetch(CDB_MSGMAIN, &TheIndex, sizeof(long));
3057 if (cdbsmi == NULL) {
3058 return; /* record not found; go with defaults */
3060 memcpy(smibuf, cdbsmi->ptr,
3061 ((cdbsmi->len > sizeof(struct MetaData)) ?
3062 sizeof(struct MetaData) : cdbsmi->len));
3069 * PutMetaData() - (re)write supplementary record for a message
3071 void PutMetaData(struct MetaData *smibuf)
3075 /* Use the negative of the message number for the metadata db index */
3076 TheIndex = (0L - smibuf->meta_msgnum);
3078 lprintf(CTDL_DEBUG, "PutMetaData(%ld) - ref count is %d\n",
3079 smibuf->meta_msgnum, smibuf->meta_refcount);
3081 cdb_store(CDB_MSGMAIN,
3082 &TheIndex, sizeof(long),
3083 smibuf, sizeof(struct MetaData));
3088 * AdjRefCount - change the reference count for a message;
3089 * delete the message if it reaches zero
3091 void AdjRefCount(long msgnum, int incr)
3094 struct MetaData smi;
3097 /* This is a *tight* critical section; please keep it that way, as
3098 * it may get called while nested in other critical sections.
3099 * Complicating this any further will surely cause deadlock!
3101 begin_critical_section(S_SUPPMSGMAIN);
3102 GetMetaData(&smi, msgnum);
3103 lprintf(CTDL_DEBUG, "Ref count for message <%ld> before write is <%d>\n",
3104 msgnum, smi.meta_refcount);
3105 smi.meta_refcount += incr;
3107 end_critical_section(S_SUPPMSGMAIN);
3108 lprintf(CTDL_DEBUG, "Ref count for message <%ld> after write is <%d>\n",
3109 msgnum, smi.meta_refcount);
3111 /* If the reference count is now zero, delete the message
3112 * (and its supplementary record as well).
3114 if (smi.meta_refcount == 0) {
3115 lprintf(CTDL_DEBUG, "Deleting message <%ld>\n", msgnum);
3117 cdb_delete(CDB_MSGMAIN, &delnum, sizeof(long));
3118 cdb_delete(CDB_BIGMSGS, &delnum, sizeof(long));
3120 /* We have to delete the metadata record too! */
3121 delnum = (0L - msgnum);
3122 cdb_delete(CDB_MSGMAIN, &delnum, sizeof(long));
3127 * Write a generic object to this room
3129 * Note: this could be much more efficient. Right now we use two temporary
3130 * files, and still pull the message into memory as with all others.
3132 void CtdlWriteObject(char *req_room, /* Room to stuff it in */
3133 char *content_type, /* MIME type of this object */
3134 char *tempfilename, /* Where to fetch it from */
3135 struct ctdluser *is_mailbox, /* Mailbox room? */
3136 int is_binary, /* Is encoding necessary? */
3137 int is_unique, /* Del others of this type? */
3138 unsigned int flags /* Internal save flags */
3143 struct ctdlroom qrbuf;
3144 char roomname[ROOMNAMELEN];
3145 struct CtdlMessage *msg;
3147 char *raw_message = NULL;
3148 char *encoded_message = NULL;
3149 off_t raw_length = 0;
3151 if (is_mailbox != NULL)
3152 MailboxName(roomname, sizeof roomname, is_mailbox, req_room);
3154 safestrncpy(roomname, req_room, sizeof(roomname));
3155 lprintf(CTDL_DEBUG, "CtdlWriteObject() to <%s> (flags=%d)\n", roomname, flags);
3158 fp = fopen(tempfilename, "rb");
3160 lprintf(CTDL_CRIT, "Cannot open %s: %s\n",
3161 tempfilename, strerror(errno));
3164 fseek(fp, 0L, SEEK_END);
3165 raw_length = ftell(fp);
3167 lprintf(CTDL_DEBUG, "Raw length is %ld\n", (long)raw_length);
3169 raw_message = malloc((size_t)raw_length + 2);
3170 fread(raw_message, (size_t)raw_length, 1, fp);
3174 encoded_message = malloc((size_t)
3175 (((raw_length * 134) / 100) + 4096 ) );
3178 encoded_message = malloc((size_t)(raw_length + 4096));
3181 sprintf(encoded_message, "Content-type: %s\n", content_type);
3184 sprintf(&encoded_message[strlen(encoded_message)],
3185 "Content-transfer-encoding: base64\n\n"
3189 sprintf(&encoded_message[strlen(encoded_message)],
3190 "Content-transfer-encoding: 7bit\n\n"
3196 &encoded_message[strlen(encoded_message)],
3202 raw_message[raw_length] = 0;
3204 &encoded_message[strlen(encoded_message)],
3212 lprintf(CTDL_DEBUG, "Allocating\n");
3213 msg = malloc(sizeof(struct CtdlMessage));
3214 memset(msg, 0, sizeof(struct CtdlMessage));
3215 msg->cm_magic = CTDLMESSAGE_MAGIC;
3216 msg->cm_anon_type = MES_NORMAL;
3217 msg->cm_format_type = 4;
3218 msg->cm_fields['A'] = strdup(CC->user.fullname);
3219 msg->cm_fields['O'] = strdup(req_room);
3220 msg->cm_fields['N'] = strdup(config.c_nodename);
3221 msg->cm_fields['H'] = strdup(config.c_humannode);
3222 msg->cm_flags = flags;
3224 msg->cm_fields['M'] = encoded_message;
3226 /* Create the requested room if we have to. */
3227 if (getroom(&qrbuf, roomname) != 0) {
3228 create_room(roomname,
3229 ( (is_mailbox != NULL) ? 5 : 3 ),
3230 "", 0, 1, 0, VIEW_BBS);
3232 /* If the caller specified this object as unique, delete all
3233 * other objects of this type that are currently in the room.
3236 lprintf(CTDL_DEBUG, "Deleted %d other msgs of this type\n",
3237 CtdlDeleteMessages(roomname, 0L, content_type));
3239 /* Now write the data */
3240 CtdlSubmitMsg(msg, NULL, roomname);
3241 CtdlFreeMessage(msg);
3249 void CtdlGetSysConfigBackend(long msgnum, void *userdata) {
3250 config_msgnum = msgnum;
3254 char *CtdlGetSysConfig(char *sysconfname) {
3255 char hold_rm[ROOMNAMELEN];
3258 struct CtdlMessage *msg;
3261 strcpy(hold_rm, CC->room.QRname);
3262 if (getroom(&CC->room, SYSCONFIGROOM) != 0) {
3263 getroom(&CC->room, hold_rm);
3268 /* We want the last (and probably only) config in this room */
3269 begin_critical_section(S_CONFIG);
3270 config_msgnum = (-1L);
3271 CtdlForEachMessage(MSGS_LAST, 1, sysconfname, NULL,
3272 CtdlGetSysConfigBackend, NULL);
3273 msgnum = config_msgnum;
3274 end_critical_section(S_CONFIG);
3280 msg = CtdlFetchMessage(msgnum, 1);
3282 conf = strdup(msg->cm_fields['M']);
3283 CtdlFreeMessage(msg);
3290 getroom(&CC->room, hold_rm);
3292 if (conf != NULL) do {
3293 extract_token(buf, conf, 0, '\n');
3294 strcpy(conf, &conf[strlen(buf)+1]);
3295 } while ( (strlen(conf)>0) && (strlen(buf)>0) );
3300 void CtdlPutSysConfig(char *sysconfname, char *sysconfdata) {
3301 char temp[PATH_MAX];
3304 strcpy(temp, tmpnam(NULL));
3306 fp = fopen(temp, "w");
3307 if (fp == NULL) return;
3308 fprintf(fp, "%s", sysconfdata);
3311 /* this handy API function does all the work for us */
3312 CtdlWriteObject(SYSCONFIGROOM, sysconfname, temp, NULL, 0, 1, 0);
3318 * Determine whether a given Internet address belongs to the current user
3320 int CtdlIsMe(char *addr) {
3321 struct recptypes *recp;
3324 recp = validate_recipients(addr);
3325 if (recp == NULL) return(0);
3327 if (recp->num_local == 0) {
3332 for (i=0; i<recp->num_local; ++i) {
3333 extract(addr, recp->recp_local, i);
3334 if (!strcasecmp(addr, CC->user.fullname)) {
3346 * Citadel protocol command to do the same
3348 void cmd_isme(char *argbuf) {
3351 if (CtdlAccessCheck(ac_logged_in)) return;
3352 extract(addr, argbuf, 0);
3354 if (CtdlIsMe(addr)) {
3355 cprintf("%d %s\n", CIT_OK, addr);
3358 cprintf("%d Not you.\n", ERROR + ILLEGAL_VALUE);