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 extern struct config config;
59 * This really belongs in serv_network.c, but I don't know how to export
60 * symbols between modules.
62 struct FilterList *filterlist = NULL;
66 * These are the four-character field headers we use when outputting
67 * messages in Citadel format (as opposed to RFC822 format).
70 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
71 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
72 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
73 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
74 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
75 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
76 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
77 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
104 * This function is self explanatory.
105 * (What can I say, I'm in a weird mood today...)
107 void remove_any_whitespace_to_the_left_or_right_of_at_symbol(char *name)
111 for (i = 0; i < strlen(name); ++i) {
112 if (name[i] == '@') {
113 while (isspace(name[i - 1]) && i > 0) {
114 strcpy(&name[i - 1], &name[i]);
117 while (isspace(name[i + 1])) {
118 strcpy(&name[i + 1], &name[i + 2]);
126 * Aliasing for network mail.
127 * (Error messages have been commented out, because this is a server.)
129 int alias(char *name)
130 { /* process alias and routing info for mail */
133 char aaa[SIZ], bbb[SIZ];
134 char *ignetcfg = NULL;
135 char *ignetmap = NULL;
142 remove_any_whitespace_to_the_left_or_right_of_at_symbol(name);
144 fp = fopen("network/mail.aliases", "r");
146 fp = fopen("/dev/null", "r");
153 while (fgets(aaa, sizeof aaa, fp) != NULL) {
154 while (isspace(name[0]))
155 strcpy(name, &name[1]);
156 aaa[strlen(aaa) - 1] = 0;
158 for (a = 0; a < strlen(aaa); ++a) {
160 strcpy(bbb, &aaa[a + 1]);
164 if (!strcasecmp(name, aaa))
169 /* Hit the Global Address Book */
170 if (CtdlDirectoryLookup(aaa, name) == 0) {
174 lprintf(CTDL_INFO, "Mail is being forwarded to %s\n", name);
176 /* Change "user @ xxx" to "user" if xxx is an alias for this host */
177 for (a=0; a<strlen(name); ++a) {
178 if (name[a] == '@') {
179 if (CtdlHostAlias(&name[a+1]) == hostalias_localhost) {
181 lprintf(CTDL_INFO, "Changed to <%s>\n", name);
186 /* determine local or remote type, see citadel.h */
187 at = haschar(name, '@');
188 if (at == 0) return(MES_LOCAL); /* no @'s - local address */
189 if (at > 1) return(MES_ERROR); /* >1 @'s - invalid address */
190 remove_any_whitespace_to_the_left_or_right_of_at_symbol(name);
192 /* figure out the delivery mode */
193 extract_token(node, name, 1, '@', sizeof node);
195 /* If there are one or more dots in the nodename, we assume that it
196 * is an FQDN and will attempt SMTP delivery to the Internet.
198 if (haschar(node, '.') > 0) {
199 return(MES_INTERNET);
202 /* Otherwise we look in the IGnet maps for a valid Citadel node.
203 * Try directly-connected nodes first...
205 ignetcfg = CtdlGetSysConfig(IGNETCFG);
206 for (i=0; i<num_tokens(ignetcfg, '\n'); ++i) {
207 extract_token(buf, ignetcfg, i, '\n', sizeof buf);
208 extract_token(testnode, buf, 0, '|', sizeof testnode);
209 if (!strcasecmp(node, testnode)) {
217 * Then try nodes that are two or more hops away.
219 ignetmap = CtdlGetSysConfig(IGNETMAP);
220 for (i=0; i<num_tokens(ignetmap, '\n'); ++i) {
221 extract_token(buf, ignetmap, i, '\n', sizeof buf);
222 extract_token(testnode, buf, 0, '|', sizeof testnode);
223 if (!strcasecmp(node, testnode)) {
230 /* If we get to this point it's an invalid node name */
239 fp = fopen("citadel.control", "r");
241 lprintf(CTDL_CRIT, "Cannot open citadel.control: %s\n",
245 fread((char *) &CitControl, sizeof(struct CitControl), 1, fp);
251 void simple_listing(long msgnum, void *userdata)
253 cprintf("%ld\n", msgnum);
258 /* Determine if a given message matches the fields in a message template.
259 * Return 0 for a successful match.
261 int CtdlMsgCmp(struct CtdlMessage *msg, struct CtdlMessage *template) {
264 /* If there aren't any fields in the template, all messages will
267 if (template == NULL) return(0);
269 /* Null messages are bogus. */
270 if (msg == NULL) return(1);
272 for (i='A'; i<='Z'; ++i) {
273 if (template->cm_fields[i] != NULL) {
274 if (msg->cm_fields[i] == NULL) {
277 if (strcasecmp(msg->cm_fields[i],
278 template->cm_fields[i])) return 1;
282 /* All compares succeeded: we have a match! */
289 * Retrieve the "seen" message list for the current room.
291 void CtdlGetSeen(char *buf, int which_set) {
294 /* Learn about the user and room in question */
295 CtdlGetRelationship(&vbuf, &CC->user, &CC->room);
297 if (which_set == ctdlsetseen_seen)
298 safestrncpy(buf, vbuf.v_seen, SIZ);
299 if (which_set == ctdlsetseen_answered)
300 safestrncpy(buf, vbuf.v_answered, SIZ);
306 * Manipulate the "seen msgs" string (or other message set strings)
308 void CtdlSetSeen(long target_msgnum, int target_setting, int which_set) {
310 struct cdbdata *cdbfr;
321 lprintf(CTDL_DEBUG, "CtdlSetSeen(%ld, %d, %d)\n",
322 target_msgnum, target_setting, which_set);
324 /* Learn about the user and room in question */
325 CtdlGetRelationship(&vbuf, &CC->user, &CC->room);
327 /* Load the message list */
328 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long));
330 msglist = malloc(cdbfr->len);
331 memcpy(msglist, cdbfr->ptr, cdbfr->len);
332 num_msgs = cdbfr->len / sizeof(long);
335 return; /* No messages at all? No further action. */
338 /* Decide which message set we're manipulating */
339 if (which_set == ctdlsetseen_seen) strcpy(vset, vbuf.v_seen);
340 if (which_set == ctdlsetseen_answered) strcpy(vset, vbuf.v_answered);
342 lprintf(CTDL_DEBUG, "before optimize: %s\n", vset);
345 for (i=0; i<num_msgs; ++i) {
348 if (msglist[i] == target_msgnum) {
349 is_seen = target_setting;
352 if (is_msg_in_mset(vset, msglist[i])) {
358 if (lo < 0L) lo = msglist[i];
361 if ( ((is_seen == 0) && (was_seen == 1))
362 || ((is_seen == 1) && (i == num_msgs-1)) ) {
365 if ( (strlen(newseen) + 20) > SIZ) {
366 strcpy(newseen, &newseen[20]);
369 tmp = strlen(newseen);
371 strcat(newseen, ",");
375 snprintf(&newseen[tmp], sizeof newseen - tmp,
379 snprintf(&newseen[tmp], sizeof newseen - tmp,
388 /* Decide which message set we're manipulating */
389 if (which_set == ctdlsetseen_seen) strcpy(vbuf.v_seen, newseen);
390 if (which_set == ctdlsetseen_answered) strcpy(vbuf.v_answered, newseen);
392 lprintf(CTDL_DEBUG, " after optimize: %s\n", newseen);
394 CtdlSetRelationship(&vbuf, &CC->user, &CC->room);
399 * API function to perform an operation for each qualifying message in the
400 * current room. (Returns the number of messages processed.)
402 int CtdlForEachMessage(int mode, long ref,
404 struct CtdlMessage *compare,
405 void (*CallBack) (long, void *),
411 struct cdbdata *cdbfr;
412 long *msglist = NULL;
414 int num_processed = 0;
417 struct CtdlMessage *msg;
420 int printed_lastold = 0;
422 /* Learn about the user and room in question */
424 getuser(&CC->user, CC->curr_user);
425 CtdlGetRelationship(&vbuf, &CC->user, &CC->room);
427 /* Load the message list */
428 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long));
430 msglist = malloc(cdbfr->len);
431 memcpy(msglist, cdbfr->ptr, cdbfr->len);
432 num_msgs = cdbfr->len / sizeof(long);
435 return 0; /* No messages at all? No further action. */
440 * Now begin the traversal.
442 if (num_msgs > 0) for (a = 0; a < num_msgs; ++a) {
444 /* If the caller is looking for a specific MIME type, filter
445 * out all messages which are not of the type requested.
447 if (content_type != NULL) if (strlen(content_type) > 0) {
449 /* This call to GetMetaData() sits inside this loop
450 * so that we only do the extra database read per msg
451 * if we need to. Doing the extra read all the time
452 * really kills the server. If we ever need to use
453 * metadata for another search criterion, we need to
454 * move the read somewhere else -- but still be smart
455 * enough to only do the read if the caller has
456 * specified something that will need it.
458 GetMetaData(&smi, msglist[a]);
460 if (strcasecmp(smi.meta_content_type, content_type)) {
466 num_msgs = sort_msglist(msglist, num_msgs);
468 /* If a template was supplied, filter out the messages which
469 * don't match. (This could induce some delays!)
472 if (compare != NULL) {
473 for (a = 0; a < num_msgs; ++a) {
474 msg = CtdlFetchMessage(msglist[a], 1);
476 if (CtdlMsgCmp(msg, compare)) {
479 CtdlFreeMessage(msg);
487 * Now iterate through the message list, according to the
488 * criteria supplied by the caller.
491 for (a = 0; a < num_msgs; ++a) {
492 thismsg = msglist[a];
493 is_seen = is_msg_in_mset(vbuf.v_seen, thismsg);
494 if (is_seen) lastold = thismsg;
499 || ((mode == MSGS_OLD) && (is_seen))
500 || ((mode == MSGS_NEW) && (!is_seen))
501 || ((mode == MSGS_LAST) && (a >= (num_msgs - ref)))
502 || ((mode == MSGS_FIRST) && (a < ref))
503 || ((mode == MSGS_GT) && (thismsg > ref))
504 || ((mode == MSGS_EQ) && (thismsg == ref))
507 if ((mode == MSGS_NEW) && (CC->user.flags & US_LASTOLD) && (lastold > 0L) && (printed_lastold == 0) && (!is_seen)) {
509 CallBack(lastold, userdata);
513 if (CallBack) CallBack(thismsg, userdata);
517 free(msglist); /* Clean up */
518 return num_processed;
524 * cmd_msgs() - get list of message #'s in this room
525 * implements the MSGS server command using CtdlForEachMessage()
527 void cmd_msgs(char *cmdbuf)
536 int with_template = 0;
537 struct CtdlMessage *template = NULL;
539 extract_token(which, cmdbuf, 0, '|', sizeof which);
540 cm_ref = extract_int(cmdbuf, 1);
541 with_template = extract_int(cmdbuf, 2);
545 if (!strncasecmp(which, "OLD", 3))
547 else if (!strncasecmp(which, "NEW", 3))
549 else if (!strncasecmp(which, "FIRST", 5))
551 else if (!strncasecmp(which, "LAST", 4))
553 else if (!strncasecmp(which, "GT", 2))
556 if ((!(CC->logged_in)) && (!(CC->internal_pgm))) {
557 cprintf("%d not logged in\n", ERROR + NOT_LOGGED_IN);
563 cprintf("%d Send template then receive message list\n",
565 template = (struct CtdlMessage *)
566 malloc(sizeof(struct CtdlMessage));
567 memset(template, 0, sizeof(struct CtdlMessage));
568 while(client_getln(buf, sizeof buf), strcmp(buf,"000")) {
569 extract_token(tfield, buf, 0, '|', sizeof tfield);
570 extract_token(tvalue, buf, 1, '|', sizeof tvalue);
571 for (i='A'; i<='Z'; ++i) if (msgkeys[i]!=NULL) {
572 if (!strcasecmp(tfield, msgkeys[i])) {
573 template->cm_fields[i] =
581 cprintf("%d Message list...\n", LISTING_FOLLOWS);
584 CtdlForEachMessage(mode, cm_ref,
585 NULL, template, simple_listing, NULL);
586 if (template != NULL) CtdlFreeMessage(template);
594 * help_subst() - support routine for help file viewer
596 void help_subst(char *strbuf, char *source, char *dest)
601 while (p = pattern2(strbuf, source), (p >= 0)) {
602 strcpy(workbuf, &strbuf[p + strlen(source)]);
603 strcpy(&strbuf[p], dest);
604 strcat(strbuf, workbuf);
609 void do_help_subst(char *buffer)
613 help_subst(buffer, "^nodename", config.c_nodename);
614 help_subst(buffer, "^humannode", config.c_humannode);
615 help_subst(buffer, "^fqdn", config.c_fqdn);
616 help_subst(buffer, "^username", CC->user.fullname);
617 snprintf(buf2, sizeof buf2, "%ld", CC->user.usernum);
618 help_subst(buffer, "^usernum", buf2);
619 help_subst(buffer, "^sysadm", config.c_sysadm);
620 help_subst(buffer, "^variantname", CITADEL);
621 snprintf(buf2, sizeof buf2, "%d", config.c_maxsessions);
622 help_subst(buffer, "^maxsessions", buf2);
623 help_subst(buffer, "^bbsdir", BBSDIR);
629 * memfmout() - Citadel text formatter and paginator.
630 * Although the original purpose of this routine was to format
631 * text to the reader's screen width, all we're really using it
632 * for here is to format text out to 80 columns before sending it
633 * to the client. The client software may reformat it again.
636 int width, /* screen width to use */
637 char *mptr, /* where are we going to get our text from? */
638 char subst, /* nonzero if we should do substitutions */
639 char *nl) /* string to terminate lines with */
651 c = 1; /* c is the current pos */
655 while (ch = *mptr, ((ch != 0) && (strlen(buffer) < 126))) {
657 buffer[strlen(buffer) + 1] = 0;
658 buffer[strlen(buffer)] = ch;
661 if (buffer[0] == '^')
662 do_help_subst(buffer);
664 buffer[strlen(buffer) + 1] = 0;
666 strcpy(buffer, &buffer[1]);
674 if (((ch == 13) || (ch == 10)) && (old != 13) && (old != 10))
676 if (((old == 13) || (old == 10)) && (isspace(real))) {
684 if (((strlen(aaa) + c) > (width - 5)) && (strlen(aaa) > (width - 5))) {
685 cprintf("%s%s", nl, aaa);
694 if ((strlen(aaa) + c) > (width - 5)) {
703 if ((ch == 13) || (ch == 10)) {
704 cprintf("%s%s", aaa, nl);
711 cprintf("%s%s", aaa, nl);
717 * Callback function for mime parser that simply lists the part
719 void list_this_part(char *name, char *filename, char *partnum, char *disp,
720 void *content, char *cbtype, size_t length, char *encoding,
724 cprintf("part=%s|%s|%s|%s|%s|%ld\n",
725 name, filename, partnum, disp, cbtype, (long)length);
729 * Callback function for multipart prefix
731 void list_this_pref(char *name, char *filename, char *partnum, char *disp,
732 void *content, char *cbtype, size_t length, char *encoding,
735 cprintf("pref=%s|%s\n", partnum, cbtype);
739 * Callback function for multipart sufffix
741 void list_this_suff(char *name, char *filename, char *partnum, char *disp,
742 void *content, char *cbtype, size_t length, char *encoding,
745 cprintf("suff=%s|%s\n", partnum, cbtype);
750 * Callback function for mime parser that opens a section for downloading
752 void mime_download(char *name, char *filename, char *partnum, char *disp,
753 void *content, char *cbtype, size_t length, char *encoding,
757 /* Silently go away if there's already a download open... */
758 if (CC->download_fp != NULL)
761 /* ...or if this is not the desired section */
762 if (strcasecmp(CC->download_desired_section, partnum))
765 CC->download_fp = tmpfile();
766 if (CC->download_fp == NULL)
769 fwrite(content, length, 1, CC->download_fp);
770 fflush(CC->download_fp);
771 rewind(CC->download_fp);
773 OpenCmdResult(filename, cbtype);
779 * Load a message from disk into memory.
780 * This is used by CtdlOutputMsg() and other fetch functions.
782 * NOTE: Caller is responsible for freeing the returned CtdlMessage struct
783 * using the CtdlMessageFree() function.
785 struct CtdlMessage *CtdlFetchMessage(long msgnum, int with_body)
787 struct cdbdata *dmsgtext;
788 struct CtdlMessage *ret = NULL;
792 cit_uint8_t field_header;
794 lprintf(CTDL_DEBUG, "CtdlFetchMessage(%ld, %d)\n", msgnum, with_body);
796 dmsgtext = cdb_fetch(CDB_MSGMAIN, &msgnum, sizeof(long));
797 if (dmsgtext == NULL) {
800 mptr = dmsgtext->ptr;
801 upper_bound = mptr + dmsgtext->len;
803 /* Parse the three bytes that begin EVERY message on disk.
804 * The first is always 0xFF, the on-disk magic number.
805 * The second is the anonymous/public type byte.
806 * The third is the format type byte (vari, fixed, or MIME).
811 "Message %ld appears to be corrupted.\n",
816 ret = (struct CtdlMessage *) malloc(sizeof(struct CtdlMessage));
817 memset(ret, 0, sizeof(struct CtdlMessage));
819 ret->cm_magic = CTDLMESSAGE_MAGIC;
820 ret->cm_anon_type = *mptr++; /* Anon type byte */
821 ret->cm_format_type = *mptr++; /* Format type byte */
824 * The rest is zero or more arbitrary fields. Load them in.
825 * We're done when we encounter either a zero-length field or
826 * have just processed the 'M' (message text) field.
829 if (mptr >= upper_bound) {
832 field_header = *mptr++;
833 ret->cm_fields[field_header] = strdup(mptr);
835 while (*mptr++ != 0); /* advance to next field */
837 } while ((mptr < upper_bound) && (field_header != 'M'));
841 /* Always make sure there's something in the msg text field. If
842 * it's NULL, the message text is most likely stored separately,
843 * so go ahead and fetch that. Failing that, just set a dummy
844 * body so other code doesn't barf.
846 if ( (ret->cm_fields['M'] == NULL) && (with_body) ) {
847 dmsgtext = cdb_fetch(CDB_BIGMSGS, &msgnum, sizeof(long));
848 if (dmsgtext != NULL) {
849 ret->cm_fields['M'] = strdup(dmsgtext->ptr);
853 if (ret->cm_fields['M'] == NULL) {
854 ret->cm_fields['M'] = strdup("<no text>\n");
857 /* Perform "before read" hooks (aborting if any return nonzero) */
858 if (PerformMessageHooks(ret, EVT_BEFOREREAD) > 0) {
859 CtdlFreeMessage(ret);
868 * Returns 1 if the supplied pointer points to a valid Citadel message.
869 * If the pointer is NULL or the magic number check fails, returns 0.
871 int is_valid_message(struct CtdlMessage *msg) {
874 if ((msg->cm_magic) != CTDLMESSAGE_MAGIC) {
875 lprintf(CTDL_WARNING, "is_valid_message() -- self-check failed\n");
883 * 'Destructor' for struct CtdlMessage
885 void CtdlFreeMessage(struct CtdlMessage *msg)
889 if (is_valid_message(msg) == 0) return;
891 for (i = 0; i < 256; ++i)
892 if (msg->cm_fields[i] != NULL) {
893 free(msg->cm_fields[i]);
896 msg->cm_magic = 0; /* just in case */
902 * Pre callback function for multipart/alternative
904 * NOTE: this differs from the standard behavior for a reason. Normally when
905 * displaying multipart/alternative you want to show the _last_ usable
906 * format in the message. Here we show the _first_ one, because it's
907 * usually text/plain. Since this set of functions is designed for text
908 * output to non-MIME-aware clients, this is the desired behavior.
911 void fixed_output_pre(char *name, char *filename, char *partnum, char *disp,
912 void *content, char *cbtype, size_t length, char *encoding,
917 ma = (struct ma_info *)cbuserdata;
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,
935 ma = (struct ma_info *)cbuserdata;
936 lprintf(CTDL_DEBUG, "fixed_output_post() type=<%s>\n", cbtype);
937 if (!strcasecmp(cbtype, "multipart/alternative")) {
945 * Inline callback function for mime parser that wants to display text
947 void fixed_output(char *name, char *filename, char *partnum, char *disp,
948 void *content, char *cbtype, size_t length, char *encoding,
956 ma = (struct ma_info *)cbuserdata;
958 lprintf(CTDL_DEBUG, "fixed_output() type=<%s>\n", cbtype);
961 * If we're in the middle of a multipart/alternative scope and
962 * we've already printed another section, skip this one.
964 if ( (ma->is_ma == 1) && (ma->did_print == 1) ) {
965 lprintf(CTDL_DEBUG, "Skipping part %s (%s)\n", partnum, cbtype);
970 if ( (!strcasecmp(cbtype, "text/plain"))
971 || (strlen(cbtype)==0) ) {
974 client_write(wptr, length);
975 if (wptr[length-1] != '\n') {
980 else if (!strcasecmp(cbtype, "text/html")) {
981 ptr = html_to_ascii(content, 80, 0);
983 client_write(ptr, wlen);
984 if (ptr[wlen-1] != '\n') {
989 else if (strncasecmp(cbtype, "multipart/", 10)) {
990 cprintf("Part %s: %s (%s) (%ld bytes)\r\n",
991 partnum, filename, cbtype, (long)length);
996 * The client is elegant and sophisticated and wants to be choosy about
997 * MIME content types, so figure out which multipart/alternative part
998 * we're going to send.
1000 void choose_preferred(char *name, char *filename, char *partnum, char *disp,
1001 void *content, char *cbtype, size_t length, char *encoding,
1008 ma = (struct ma_info *)cbuserdata;
1010 if (ma->is_ma > 0) {
1011 for (i=0; i<num_tokens(CC->preferred_formats, '|'); ++i) {
1012 extract_token(buf, CC->preferred_formats, i, '|', sizeof buf);
1013 if (!strcasecmp(buf, cbtype)) {
1014 strcpy(ma->chosen_part, partnum);
1021 * Now that we've chosen our preferred part, output it.
1023 void output_preferred(char *name, char *filename, char *partnum, char *disp,
1024 void *content, char *cbtype, size_t length, char *encoding,
1029 int add_newline = 0;
1033 ma = (struct ma_info *)cbuserdata;
1035 /* This is not the MIME part you're looking for... */
1036 if (strcasecmp(partnum, ma->chosen_part)) return;
1038 /* If the content-type of this part is in our preferred formats
1039 * list, we can simply output it verbatim.
1041 for (i=0; i<num_tokens(CC->preferred_formats, '|'); ++i) {
1042 extract_token(buf, CC->preferred_formats, i, '|', sizeof buf);
1043 if (!strcasecmp(buf, cbtype)) {
1044 /* Yeah! Go! W00t!! */
1046 text_content = (char *)content;
1047 if (text_content[length-1] != '\n') {
1051 cprintf("Content-type: %s\n", cbtype);
1052 cprintf("Content-length: %d\n",
1053 (int)(length + add_newline) );
1054 if (strlen(encoding) > 0) {
1055 cprintf("Content-transfer-encoding: %s\n", encoding);
1058 cprintf("Content-transfer-encoding: 7bit\n");
1061 client_write(content, length);
1062 if (add_newline) cprintf("\n");
1067 /* No translations required or possible: output as text/plain */
1068 cprintf("Content-type: text/plain\n\n");
1069 fixed_output(name, filename, partnum, disp, content, cbtype,
1070 length, encoding, cbuserdata);
1075 * Get a message off disk. (returns om_* values found in msgbase.h)
1078 int CtdlOutputMsg(long msg_num, /* message number (local) to fetch */
1079 int mode, /* how would you like that message? */
1080 int headers_only, /* eschew the message body? */
1081 int do_proto, /* do Citadel protocol responses? */
1082 int crlf /* Use CRLF newlines instead of LF? */
1084 struct CtdlMessage *TheMessage = NULL;
1085 int retcode = om_no_such_msg;
1087 lprintf(CTDL_DEBUG, "CtdlOutputMsg() msgnum=%ld, mode=%d\n",
1090 if ((!(CC->logged_in)) && (!(CC->internal_pgm))) {
1091 if (do_proto) cprintf("%d Not logged in.\n",
1092 ERROR + NOT_LOGGED_IN);
1093 return(om_not_logged_in);
1096 /* FIXME: check message id against msglist for this room */
1099 * Fetch the message from disk. If we're in sooper-fast headers
1100 * only mode, request that we don't even bother loading the body
1103 if (headers_only == HEADERS_FAST) {
1104 TheMessage = CtdlFetchMessage(msg_num, 0);
1107 TheMessage = CtdlFetchMessage(msg_num, 1);
1110 if (TheMessage == NULL) {
1111 if (do_proto) cprintf("%d Can't locate msg %ld on disk\n",
1112 ERROR + MESSAGE_NOT_FOUND, msg_num);
1113 return(om_no_such_msg);
1116 retcode = CtdlOutputPreLoadedMsg(
1117 TheMessage, msg_num, mode,
1118 headers_only, do_proto, crlf);
1120 CtdlFreeMessage(TheMessage);
1127 * Get a message off disk. (returns om_* values found in msgbase.h)
1130 int CtdlOutputPreLoadedMsg(
1131 struct CtdlMessage *TheMessage,
1133 int mode, /* how would you like that message? */
1134 int headers_only, /* eschew the message body? */
1135 int do_proto, /* do Citadel protocol responses? */
1136 int crlf /* Use CRLF newlines instead of LF? */
1142 char display_name[256];
1144 char *nl; /* newline string */
1146 int subject_found = 0;
1149 /* Buffers needed for RFC822 translation. These are all filled
1150 * using functions that are bounds-checked, and therefore we can
1151 * make them substantially smaller than SIZ.
1159 char datestamp[100];
1161 lprintf(CTDL_DEBUG, "CtdlOutputPreLoadedMsg(TheMessage=%s, %ld, %d, %d, %d, %d\n",
1162 ((TheMessage == NULL) ? "NULL" : "not null"),
1164 mode, headers_only, do_proto, crlf);
1166 snprintf(mid, sizeof mid, "%ld", msg_num);
1167 nl = (crlf ? "\r\n" : "\n");
1169 if (!is_valid_message(TheMessage)) {
1171 "ERROR: invalid preloaded message for output\n");
1172 return(om_no_such_msg);
1175 /* Are we downloading a MIME component? */
1176 if (mode == MT_DOWNLOAD) {
1177 if (TheMessage->cm_format_type != FMT_RFC822) {
1179 cprintf("%d This is not a MIME message.\n",
1180 ERROR + ILLEGAL_VALUE);
1181 } else if (CC->download_fp != NULL) {
1182 if (do_proto) cprintf(
1183 "%d You already have a download open.\n",
1184 ERROR + RESOURCE_BUSY);
1186 /* Parse the message text component */
1187 mptr = TheMessage->cm_fields['M'];
1188 ma = malloc(sizeof(struct ma_info));
1189 memset(ma, 0, sizeof(struct ma_info));
1190 mime_parser(mptr, NULL, *mime_download, NULL, NULL, (void *)ma, 0);
1192 /* If there's no file open by this time, the requested
1193 * section wasn't found, so print an error
1195 if (CC->download_fp == NULL) {
1196 if (do_proto) cprintf(
1197 "%d Section %s not found.\n",
1198 ERROR + FILE_NOT_FOUND,
1199 CC->download_desired_section);
1202 return((CC->download_fp != NULL) ? om_ok : om_mime_error);
1205 /* now for the user-mode message reading loops */
1206 if (do_proto) cprintf("%d Message %ld:\n", LISTING_FOLLOWS, msg_num);
1208 /* Does the caller want to skip the headers? */
1209 if (headers_only == HEADERS_NONE) goto START_TEXT;
1211 /* Tell the client which format type we're using. */
1212 if ( (mode == MT_CITADEL) && (do_proto) ) {
1213 cprintf("type=%d\n", TheMessage->cm_format_type);
1216 /* nhdr=yes means that we're only displaying headers, no body */
1217 if ( (TheMessage->cm_anon_type == MES_ANONONLY)
1218 && (mode == MT_CITADEL)
1221 cprintf("nhdr=yes\n");
1224 /* begin header processing loop for Citadel message format */
1226 if ((mode == MT_CITADEL) || (mode == MT_MIME)) {
1228 safestrncpy(display_name, "<unknown>", sizeof display_name);
1229 if (TheMessage->cm_fields['A']) {
1230 strcpy(buf, TheMessage->cm_fields['A']);
1231 if (TheMessage->cm_anon_type == MES_ANONONLY) {
1232 safestrncpy(display_name, "****", sizeof display_name);
1234 else if (TheMessage->cm_anon_type == MES_ANONOPT) {
1235 safestrncpy(display_name, "anonymous", sizeof display_name);
1238 safestrncpy(display_name, buf, sizeof display_name);
1240 if ((is_room_aide())
1241 && ((TheMessage->cm_anon_type == MES_ANONONLY)
1242 || (TheMessage->cm_anon_type == MES_ANONOPT))) {
1243 size_t tmp = strlen(display_name);
1244 snprintf(&display_name[tmp],
1245 sizeof display_name - tmp,
1250 /* Don't show Internet address for users on the
1251 * local Citadel network.
1254 if (TheMessage->cm_fields['N'] != NULL)
1255 if (strlen(TheMessage->cm_fields['N']) > 0)
1256 if (haschar(TheMessage->cm_fields['N'], '.') == 0) {
1260 /* Now spew the header fields in the order we like them. */
1261 safestrncpy(allkeys, FORDER, sizeof allkeys);
1262 for (i=0; i<strlen(allkeys); ++i) {
1263 k = (int) allkeys[i];
1265 if ( (TheMessage->cm_fields[k] != NULL)
1266 && (msgkeys[k] != NULL) ) {
1268 if (do_proto) cprintf("%s=%s\n",
1272 else if ((k == 'F') && (suppress_f)) {
1275 /* Masquerade display name if needed */
1277 if (do_proto) cprintf("%s=%s\n",
1279 TheMessage->cm_fields[k]
1288 /* begin header processing loop for RFC822 transfer format */
1293 strcpy(snode, NODENAME);
1294 strcpy(lnode, HUMANNODE);
1295 if (mode == MT_RFC822) {
1296 for (i = 0; i < 256; ++i) {
1297 if (TheMessage->cm_fields[i]) {
1298 mptr = TheMessage->cm_fields[i];
1301 safestrncpy(luser, mptr, sizeof luser);
1302 safestrncpy(suser, mptr, sizeof suser);
1304 else if (i == 'U') {
1305 cprintf("Subject: %s%s", mptr, nl);
1309 safestrncpy(mid, mptr, sizeof mid);
1311 safestrncpy(lnode, mptr, sizeof lnode);
1313 safestrncpy(fuser, mptr, sizeof fuser);
1315 cprintf("X-Citadel-Room: %s%s",
1318 safestrncpy(snode, mptr, sizeof snode);
1320 cprintf("To: %s%s", mptr, nl);
1321 else if (i == 'T') {
1322 datestring(datestamp, sizeof datestamp,
1323 atol(mptr), DATESTRING_RFC822);
1324 cprintf("Date: %s%s", datestamp, nl);
1328 if (subject_found == 0) {
1329 cprintf("Subject: (no subject)%s", nl);
1333 for (i=0; i<strlen(suser); ++i) {
1334 suser[i] = tolower(suser[i]);
1335 if (!isalnum(suser[i])) suser[i]='_';
1338 if (mode == MT_RFC822) {
1339 if (!strcasecmp(snode, NODENAME)) {
1340 safestrncpy(snode, FQDN, sizeof snode);
1343 /* Construct a fun message id */
1344 cprintf("Message-ID: <%s", mid);
1345 if (strchr(mid, '@')==NULL) {
1346 cprintf("@%s", snode);
1350 if (!is_room_aide() && (TheMessage->cm_anon_type == MES_ANONONLY)) {
1351 cprintf("From: x@x.org (----)%s", nl);
1353 else if (!is_room_aide() && (TheMessage->cm_anon_type == MES_ANONOPT)) {
1354 cprintf("From: x@x.org (anonymous)%s", nl);
1356 else if (strlen(fuser) > 0) {
1357 cprintf("From: %s (%s)%s", fuser, luser, nl);
1360 cprintf("From: %s@%s (%s)%s", suser, snode, luser, nl);
1363 cprintf("Organization: %s%s", lnode, nl);
1365 /* Blank line signifying RFC822 end-of-headers */
1366 if (TheMessage->cm_format_type != FMT_RFC822) {
1371 /* end header processing loop ... at this point, we're in the text */
1373 if (headers_only == HEADERS_FAST) goto DONE;
1374 mptr = TheMessage->cm_fields['M'];
1376 /* Tell the client about the MIME parts in this message */
1377 if (TheMessage->cm_format_type == FMT_RFC822) {
1378 if ( (mode == MT_CITADEL) || (mode == MT_MIME) ) {
1379 mime_parser(mptr, NULL,
1385 else if (mode == MT_RFC822) { /* unparsed RFC822 dump */
1386 /* FIXME ... we have to put some code in here to avoid
1387 * printing duplicate header information when both
1388 * Citadel and RFC822 headers exist. Preference should
1389 * probably be given to the RFC822 headers.
1391 int done_rfc822_hdrs = 0;
1392 while (ch=*(mptr++), ch!=0) {
1397 if (!done_rfc822_hdrs) {
1398 if (headers_only != HEADERS_NONE) {
1403 if (headers_only != HEADERS_ONLY) {
1407 if ((*(mptr) == 13) || (*(mptr) == 10)) {
1408 done_rfc822_hdrs = 1;
1412 if (done_rfc822_hdrs) {
1413 if (headers_only != HEADERS_NONE) {
1418 if (headers_only != HEADERS_ONLY) {
1422 if ((*mptr == 13) || (*mptr == 10)) {
1423 done_rfc822_hdrs = 1;
1431 if (headers_only == HEADERS_ONLY) {
1435 /* signify start of msg text */
1436 if ( (mode == MT_CITADEL) || (mode == MT_MIME) ) {
1437 if (do_proto) cprintf("text\n");
1440 /* If the format type on disk is 1 (fixed-format), then we want
1441 * everything to be output completely literally ... regardless of
1442 * what message transfer format is in use.
1444 if (TheMessage->cm_format_type == FMT_FIXED) {
1445 if (mode == MT_MIME) {
1446 cprintf("Content-type: text/plain\n\n");
1449 while (ch = *mptr++, ch > 0) {
1452 if ((ch == 10) || (strlen(buf) > 250)) {
1453 cprintf("%s%s", buf, nl);
1456 buf[strlen(buf) + 1] = 0;
1457 buf[strlen(buf)] = ch;
1460 if (strlen(buf) > 0)
1461 cprintf("%s%s", buf, nl);
1464 /* If the message on disk is format 0 (Citadel vari-format), we
1465 * output using the formatter at 80 columns. This is the final output
1466 * form if the transfer format is RFC822, but if the transfer format
1467 * is Citadel proprietary, it'll still work, because the indentation
1468 * for new paragraphs is correct and the client will reformat the
1469 * message to the reader's screen width.
1471 if (TheMessage->cm_format_type == FMT_CITADEL) {
1472 if (mode == MT_MIME) {
1473 cprintf("Content-type: text/x-citadel-variformat\n\n");
1475 memfmout(80, mptr, 0, nl);
1478 /* If the message on disk is format 4 (MIME), we've gotta hand it
1479 * off to the MIME parser. The client has already been told that
1480 * this message is format 1 (fixed format), so the callback function
1481 * we use will display those parts as-is.
1483 if (TheMessage->cm_format_type == FMT_RFC822) {
1484 ma = malloc(sizeof(struct ma_info));
1485 memset(ma, 0, sizeof(struct ma_info));
1487 if (mode == MT_MIME) {
1488 strcpy(ma->chosen_part, "1");
1489 mime_parser(mptr, NULL,
1490 *choose_preferred, *fixed_output_pre,
1491 *fixed_output_post, (void *)ma, 0);
1492 mime_parser(mptr, NULL,
1493 *output_preferred, NULL, NULL, (void *)ma, 0);
1496 mime_parser(mptr, NULL,
1497 *fixed_output, *fixed_output_pre,
1498 *fixed_output_post, (void *)ma, 0);
1504 DONE: /* now we're done */
1505 if (do_proto) cprintf("000\n");
1512 * display a message (mode 0 - Citadel proprietary)
1514 void cmd_msg0(char *cmdbuf)
1517 int headers_only = HEADERS_ALL;
1519 msgid = extract_long(cmdbuf, 0);
1520 headers_only = extract_int(cmdbuf, 1);
1522 CtdlOutputMsg(msgid, MT_CITADEL, headers_only, 1, 0);
1528 * display a message (mode 2 - RFC822)
1530 void cmd_msg2(char *cmdbuf)
1533 int headers_only = HEADERS_ALL;
1535 msgid = extract_long(cmdbuf, 0);
1536 headers_only = extract_int(cmdbuf, 1);
1538 CtdlOutputMsg(msgid, MT_RFC822, headers_only, 1, 1);
1544 * display a message (mode 3 - IGnet raw format - internal programs only)
1546 void cmd_msg3(char *cmdbuf)
1549 struct CtdlMessage *msg;
1552 if (CC->internal_pgm == 0) {
1553 cprintf("%d This command is for internal programs only.\n",
1554 ERROR + HIGHER_ACCESS_REQUIRED);
1558 msgnum = extract_long(cmdbuf, 0);
1559 msg = CtdlFetchMessage(msgnum, 1);
1561 cprintf("%d Message %ld not found.\n",
1562 ERROR + MESSAGE_NOT_FOUND, msgnum);
1566 serialize_message(&smr, msg);
1567 CtdlFreeMessage(msg);
1570 cprintf("%d Unable to serialize message\n",
1571 ERROR + INTERNAL_ERROR);
1575 cprintf("%d %ld\n", BINARY_FOLLOWS, (long)smr.len);
1576 client_write((char *)smr.ser, (int)smr.len);
1583 * Display a message using MIME content types
1585 void cmd_msg4(char *cmdbuf)
1589 msgid = extract_long(cmdbuf, 0);
1590 CtdlOutputMsg(msgid, MT_MIME, 0, 1, 0);
1596 * Client tells us its preferred message format(s)
1598 void cmd_msgp(char *cmdbuf)
1600 safestrncpy(CC->preferred_formats, cmdbuf,
1601 sizeof(CC->preferred_formats));
1602 cprintf("%d ok\n", CIT_OK);
1607 * Open a component of a MIME message as a download file
1609 void cmd_opna(char *cmdbuf)
1612 char desired_section[128];
1614 msgid = extract_long(cmdbuf, 0);
1615 extract_token(desired_section, cmdbuf, 1, '|', sizeof desired_section);
1616 safestrncpy(CC->download_desired_section, desired_section, sizeof CC->download_desired_section);
1617 CtdlOutputMsg(msgid, MT_DOWNLOAD, 0, 1, 1);
1622 * Save a message pointer into a specified room
1623 * (Returns 0 for success, nonzero for failure)
1624 * roomname may be NULL to use the current room
1626 int CtdlSaveMsgPointerInRoom(char *roomname, long msgid, int flags) {
1628 char hold_rm[ROOMNAMELEN];
1629 struct cdbdata *cdbfr;
1632 long highest_msg = 0L;
1633 struct CtdlMessage *msg = NULL;
1635 lprintf(CTDL_DEBUG, "CtdlSaveMsgPointerInRoom(%s, %ld, %d)\n",
1636 roomname, msgid, flags);
1638 strcpy(hold_rm, CC->room.QRname);
1640 /* We may need to check to see if this message is real */
1641 if ( (flags & SM_VERIFY_GOODNESS)
1642 || (flags & SM_DO_REPL_CHECK)
1644 msg = CtdlFetchMessage(msgid, 1);
1645 if (msg == NULL) return(ERROR + ILLEGAL_VALUE);
1648 /* Perform replication checks if necessary */
1649 if ( (flags & SM_DO_REPL_CHECK) && (msg != NULL) ) {
1651 if (getroom(&CC->room,
1652 ((roomname != NULL) ? roomname : CC->room.QRname) )
1654 lprintf(CTDL_ERR, "No such room <%s>\n", roomname);
1655 if (msg != NULL) CtdlFreeMessage(msg);
1656 return(ERROR + ROOM_NOT_FOUND);
1659 if (ReplicationChecks(msg) != 0) {
1660 getroom(&CC->room, hold_rm);
1661 if (msg != NULL) CtdlFreeMessage(msg);
1663 "Did replication, and newer exists\n");
1668 /* Now the regular stuff */
1669 if (lgetroom(&CC->room,
1670 ((roomname != NULL) ? roomname : CC->room.QRname) )
1672 lprintf(CTDL_ERR, "No such room <%s>\n", roomname);
1673 if (msg != NULL) CtdlFreeMessage(msg);
1674 return(ERROR + ROOM_NOT_FOUND);
1677 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long));
1678 if (cdbfr == NULL) {
1682 msglist = malloc(cdbfr->len);
1683 if (msglist == NULL)
1684 lprintf(CTDL_ALERT, "ERROR malloc msglist!\n");
1685 num_msgs = cdbfr->len / sizeof(long);
1686 memcpy(msglist, cdbfr->ptr, cdbfr->len);
1691 /* Make sure the message doesn't already exist in this room. It
1692 * is absolutely taboo to have more than one reference to the same
1693 * message in a room.
1695 if (num_msgs > 0) for (i=0; i<num_msgs; ++i) {
1696 if (msglist[i] == msgid) {
1697 lputroom(&CC->room); /* unlock the room */
1698 getroom(&CC->room, hold_rm);
1699 if (msg != NULL) CtdlFreeMessage(msg);
1701 return(ERROR + ALREADY_EXISTS);
1705 /* Now add the new message */
1707 msglist = realloc(msglist, (num_msgs * sizeof(long)));
1709 if (msglist == NULL) {
1710 lprintf(CTDL_ALERT, "ERROR: can't realloc message list!\n");
1712 msglist[num_msgs - 1] = msgid;
1714 /* Sort the message list, so all the msgid's are in order */
1715 num_msgs = sort_msglist(msglist, num_msgs);
1717 /* Determine the highest message number */
1718 highest_msg = msglist[num_msgs - 1];
1720 /* Write it back to disk. */
1721 cdb_store(CDB_MSGLISTS, &CC->room.QRnumber, (int)sizeof(long),
1722 msglist, (int)(num_msgs * sizeof(long)));
1724 /* Free up the memory we used. */
1727 /* Update the highest-message pointer and unlock the room. */
1728 CC->room.QRhighest = highest_msg;
1729 lputroom(&CC->room);
1730 getroom(&CC->room, hold_rm);
1732 /* Bump the reference count for this message. */
1733 if ((flags & SM_DONT_BUMP_REF)==0) {
1734 AdjRefCount(msgid, +1);
1737 /* Return success. */
1738 if (msg != NULL) CtdlFreeMessage(msg);
1745 * Message base operation to save a new message to the message store
1746 * (returns new message number)
1748 * This is the back end for CtdlSubmitMsg() and should not be directly
1749 * called by server-side modules.
1752 long send_message(struct CtdlMessage *msg) {
1760 /* Get a new message number */
1761 newmsgid = get_new_message_number();
1762 snprintf(msgidbuf, sizeof msgidbuf, "%ld@%s", newmsgid, config.c_fqdn);
1764 /* Generate an ID if we don't have one already */
1765 if (msg->cm_fields['I']==NULL) {
1766 msg->cm_fields['I'] = strdup(msgidbuf);
1769 /* If the message is big, set its body aside for storage elsewhere */
1770 if (msg->cm_fields['M'] != NULL) {
1771 if (strlen(msg->cm_fields['M']) > BIGMSG) {
1773 holdM = msg->cm_fields['M'];
1774 msg->cm_fields['M'] = NULL;
1778 /* Serialize our data structure for storage in the database */
1779 serialize_message(&smr, msg);
1782 msg->cm_fields['M'] = holdM;
1786 cprintf("%d Unable to serialize message\n",
1787 ERROR + INTERNAL_ERROR);
1791 /* Write our little bundle of joy into the message base */
1792 if (cdb_store(CDB_MSGMAIN, &newmsgid, (int)sizeof(long),
1793 smr.ser, smr.len) < 0) {
1794 lprintf(CTDL_ERR, "Can't store message\n");
1798 cdb_store(CDB_BIGMSGS,
1808 /* Free the memory we used for the serialized message */
1811 /* Return the *local* message ID to the caller
1812 * (even if we're storing an incoming network message)
1820 * Serialize a struct CtdlMessage into the format used on disk and network.
1822 * This function loads up a "struct ser_ret" (defined in server.h) which
1823 * contains the length of the serialized message and a pointer to the
1824 * serialized message in memory. THE LATTER MUST BE FREED BY THE CALLER.
1826 void serialize_message(struct ser_ret *ret, /* return values */
1827 struct CtdlMessage *msg) /* unserialized msg */
1831 static char *forder = FORDER;
1833 if (is_valid_message(msg) == 0) return; /* self check */
1836 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL)
1837 ret->len = ret->len +
1838 strlen(msg->cm_fields[(int)forder[i]]) + 2;
1840 lprintf(CTDL_DEBUG, "serialize_message() calling malloc(%ld)\n", (long)ret->len);
1841 ret->ser = malloc(ret->len);
1842 if (ret->ser == NULL) {
1848 ret->ser[1] = msg->cm_anon_type;
1849 ret->ser[2] = msg->cm_format_type;
1852 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL) {
1853 ret->ser[wlen++] = (char)forder[i];
1854 strcpy((char *)&ret->ser[wlen], msg->cm_fields[(int)forder[i]]);
1855 wlen = wlen + strlen(msg->cm_fields[(int)forder[i]]) + 1;
1857 if (ret->len != wlen) lprintf(CTDL_ERR, "ERROR: len=%ld wlen=%ld\n",
1858 (long)ret->len, (long)wlen);
1866 * Back end for the ReplicationChecks() function
1868 void check_repl(long msgnum, void *userdata) {
1869 lprintf(CTDL_DEBUG, "check_repl() replacing message %ld\n", msgnum);
1870 CtdlDeleteMessages(CC->room.QRname, msgnum, "");
1875 * Check to see if any messages already exist which carry the same Exclusive ID
1876 * as this one. If any are found, delete them.
1879 int ReplicationChecks(struct CtdlMessage *msg) {
1880 struct CtdlMessage *template;
1883 /* No exclusive id? Don't do anything. */
1884 if (msg->cm_fields['E'] == NULL) return 0;
1885 if (strlen(msg->cm_fields['E']) == 0) return 0;
1886 lprintf(CTDL_DEBUG, "Exclusive ID: <%s>\n", msg->cm_fields['E']);
1888 template = (struct CtdlMessage *) malloc(sizeof(struct CtdlMessage));
1889 memset(template, 0, sizeof(struct CtdlMessage));
1890 template->cm_fields['E'] = strdup(msg->cm_fields['E']);
1892 CtdlForEachMessage(MSGS_ALL, 0L, NULL, template, check_repl, NULL);
1894 CtdlFreeMessage(template);
1895 lprintf(CTDL_DEBUG, "ReplicationChecks() returning %d\n", abort_this);
1903 * Save a message to disk and submit it into the delivery system.
1905 long CtdlSubmitMsg(struct CtdlMessage *msg, /* message to save */
1906 struct recptypes *recps, /* recipients (if mail) */
1907 char *force /* force a particular room? */
1909 char submit_filename[128];
1910 char generated_timestamp[32];
1911 char hold_rm[ROOMNAMELEN];
1912 char actual_rm[ROOMNAMELEN];
1913 char force_room[ROOMNAMELEN];
1914 char content_type[SIZ]; /* We have to learn this */
1915 char recipient[SIZ];
1918 struct ctdluser userbuf;
1920 struct MetaData smi;
1921 FILE *network_fp = NULL;
1922 static int seqnum = 1;
1923 struct CtdlMessage *imsg = NULL;
1926 char *hold_R, *hold_D;
1928 lprintf(CTDL_DEBUG, "CtdlSubmitMsg() called\n");
1929 if (is_valid_message(msg) == 0) return(-1); /* self check */
1931 /* If this message has no timestamp, we take the liberty of
1932 * giving it one, right now.
1934 if (msg->cm_fields['T'] == NULL) {
1935 lprintf(CTDL_DEBUG, "Generating timestamp\n");
1936 snprintf(generated_timestamp, sizeof generated_timestamp, "%ld", (long)time(NULL));
1937 msg->cm_fields['T'] = strdup(generated_timestamp);
1940 /* If this message has no path, we generate one.
1942 if (msg->cm_fields['P'] == NULL) {
1943 lprintf(CTDL_DEBUG, "Generating path\n");
1944 if (msg->cm_fields['A'] != NULL) {
1945 msg->cm_fields['P'] = strdup(msg->cm_fields['A']);
1946 for (a=0; a<strlen(msg->cm_fields['P']); ++a) {
1947 if (isspace(msg->cm_fields['P'][a])) {
1948 msg->cm_fields['P'][a] = ' ';
1953 msg->cm_fields['P'] = strdup("unknown");
1957 if (force == NULL) {
1958 strcpy(force_room, "");
1961 strcpy(force_room, force);
1964 /* Learn about what's inside, because it's what's inside that counts */
1965 lprintf(CTDL_DEBUG, "Learning what's inside\n");
1966 if (msg->cm_fields['M'] == NULL) {
1967 lprintf(CTDL_ERR, "ERROR: attempt to save message with NULL body\n");
1971 switch (msg->cm_format_type) {
1973 strcpy(content_type, "text/x-citadel-variformat");
1976 strcpy(content_type, "text/plain");
1979 strcpy(content_type, "text/plain");
1980 mptr = bmstrstr(msg->cm_fields['M'],
1981 "Content-type: ", strncasecmp);
1983 safestrncpy(content_type, &mptr[14],
1984 sizeof content_type);
1985 for (a = 0; a < strlen(content_type); ++a) {
1986 if ((content_type[a] == ';')
1987 || (content_type[a] == ' ')
1988 || (content_type[a] == 13)
1989 || (content_type[a] == 10)) {
1990 content_type[a] = 0;
1996 /* Goto the correct room */
1997 lprintf(CTDL_DEBUG, "Selected room %s\n",
1998 (recps) ? CC->room.QRname : SENTITEMS);
1999 strcpy(hold_rm, CC->room.QRname);
2000 strcpy(actual_rm, CC->room.QRname);
2001 if (recps != NULL) {
2002 strcpy(actual_rm, SENTITEMS);
2005 /* If the user is a twit, move to the twit room for posting */
2006 lprintf(CTDL_DEBUG, "Handling twit stuff: %s\n",
2007 (CC->user.axlevel == 2) ? config.c_twitroom : "OK");
2009 if (CC->user.axlevel == 2) {
2010 strcpy(hold_rm, actual_rm);
2011 strcpy(actual_rm, config.c_twitroom);
2015 /* ...or if this message is destined for Aide> then go there. */
2016 if (strlen(force_room) > 0) {
2017 strcpy(actual_rm, force_room);
2020 lprintf(CTDL_DEBUG, "Final selection: %s\n", actual_rm);
2021 if (strcasecmp(actual_rm, CC->room.QRname)) {
2022 getroom(&CC->room, actual_rm);
2026 * If this message has no O (room) field, generate one.
2028 if (msg->cm_fields['O'] == NULL) {
2029 msg->cm_fields['O'] = strdup(CC->room.QRname);
2032 /* Perform "before save" hooks (aborting if any return nonzero) */
2033 lprintf(CTDL_DEBUG, "Performing before-save hooks\n");
2034 if (PerformMessageHooks(msg, EVT_BEFORESAVE) > 0) return(-3);
2036 /* If this message has an Exclusive ID, perform replication checks */
2037 lprintf(CTDL_DEBUG, "Performing replication checks\n");
2038 if (ReplicationChecks(msg) > 0) return(-4);
2040 /* Save it to disk */
2041 lprintf(CTDL_DEBUG, "Saving to disk\n");
2042 newmsgid = send_message(msg);
2043 if (newmsgid <= 0L) return(-5);
2045 /* Write a supplemental message info record. This doesn't have to
2046 * be a critical section because nobody else knows about this message
2049 lprintf(CTDL_DEBUG, "Creating MetaData record\n");
2050 memset(&smi, 0, sizeof(struct MetaData));
2051 smi.meta_msgnum = newmsgid;
2052 smi.meta_refcount = 0;
2053 safestrncpy(smi.meta_content_type, content_type, sizeof smi.meta_content_type);
2056 /* Now figure out where to store the pointers */
2057 lprintf(CTDL_DEBUG, "Storing pointers\n");
2059 /* If this is being done by the networker delivering a private
2060 * message, we want to BYPASS saving the sender's copy (because there
2061 * is no local sender; it would otherwise go to the Trashcan).
2063 if ((!CC->internal_pgm) || (recps == NULL)) {
2064 if (CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 0) != 0) {
2065 lprintf(CTDL_ERR, "ERROR saving message pointer!\n");
2066 CtdlSaveMsgPointerInRoom(config.c_aideroom, newmsgid, 0);
2070 /* For internet mail, drop a copy in the outbound queue room */
2072 if (recps->num_internet > 0) {
2073 CtdlSaveMsgPointerInRoom(SMTP_SPOOLOUT_ROOM, newmsgid, 0);
2076 /* If other rooms are specified, drop them there too. */
2078 if (recps->num_room > 0)
2079 for (i=0; i<num_tokens(recps->recp_room, '|'); ++i) {
2080 extract_token(recipient, recps->recp_room, i, '|', sizeof recipient);
2081 lprintf(CTDL_DEBUG, "Delivering to local room <%s>\n", recipient);
2082 CtdlSaveMsgPointerInRoom(recipient, newmsgid, 0);
2085 /* Bump this user's messages posted counter. */
2086 lprintf(CTDL_DEBUG, "Updating user\n");
2087 lgetuser(&CC->user, CC->curr_user);
2088 CC->user.posted = CC->user.posted + 1;
2089 lputuser(&CC->user);
2091 /* If this is private, local mail, make a copy in the
2092 * recipient's mailbox and bump the reference count.
2095 if (recps->num_local > 0)
2096 for (i=0; i<num_tokens(recps->recp_local, '|'); ++i) {
2097 extract_token(recipient, recps->recp_local, i, '|', sizeof recipient);
2098 lprintf(CTDL_DEBUG, "Delivering private local mail to <%s>\n",
2100 if (getuser(&userbuf, recipient) == 0) {
2101 MailboxName(actual_rm, sizeof actual_rm, &userbuf, MAILROOM);
2102 CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 0);
2103 BumpNewMailCounter(userbuf.usernum);
2106 lprintf(CTDL_DEBUG, "No user <%s>\n", recipient);
2107 CtdlSaveMsgPointerInRoom(config.c_aideroom, newmsgid, 0);
2111 /* Perform "after save" hooks */
2112 lprintf(CTDL_DEBUG, "Performing after-save hooks\n");
2113 PerformMessageHooks(msg, EVT_AFTERSAVE);
2115 /* For IGnet mail, we have to save a new copy into the spooler for
2116 * each recipient, with the R and D fields set to the recipient and
2117 * destination-node. This has two ugly side effects: all other
2118 * recipients end up being unlisted in this recipient's copy of the
2119 * message, and it has to deliver multiple messages to the same
2120 * node. We'll revisit this again in a year or so when everyone has
2121 * a network spool receiver that can handle the new style messages.
2124 if (recps->num_ignet > 0)
2125 for (i=0; i<num_tokens(recps->recp_ignet, '|'); ++i) {
2126 extract_token(recipient, recps->recp_ignet, i, '|', sizeof recipient);
2128 hold_R = msg->cm_fields['R'];
2129 hold_D = msg->cm_fields['D'];
2130 msg->cm_fields['R'] = malloc(SIZ);
2131 msg->cm_fields['D'] = malloc(128);
2132 extract_token(msg->cm_fields['R'], recipient, 0, '@', SIZ);
2133 extract_token(msg->cm_fields['D'], recipient, 1, '@', 128);
2135 serialize_message(&smr, msg);
2137 snprintf(submit_filename, sizeof submit_filename,
2138 "./network/spoolin/netmail.%04lx.%04x.%04x",
2139 (long) getpid(), CC->cs_pid, ++seqnum);
2140 network_fp = fopen(submit_filename, "wb+");
2141 if (network_fp != NULL) {
2142 fwrite(smr.ser, smr.len, 1, network_fp);
2148 free(msg->cm_fields['R']);
2149 free(msg->cm_fields['D']);
2150 msg->cm_fields['R'] = hold_R;
2151 msg->cm_fields['D'] = hold_D;
2154 /* Go back to the room we started from */
2155 lprintf(CTDL_DEBUG, "Returning to original room %s\n", hold_rm);
2156 if (strcasecmp(hold_rm, CC->room.QRname))
2157 getroom(&CC->room, hold_rm);
2159 /* For internet mail, generate delivery instructions.
2160 * Yes, this is recursive. Deal with it. Infinite recursion does
2161 * not happen because the delivery instructions message does not
2162 * contain a recipient.
2165 if (recps->num_internet > 0) {
2166 lprintf(CTDL_DEBUG, "Generating delivery instructions\n");
2167 instr = malloc(SIZ * 2);
2168 snprintf(instr, SIZ * 2,
2169 "Content-type: %s\n\nmsgid|%ld\nsubmitted|%ld\n"
2171 SPOOLMIME, newmsgid, (long)time(NULL),
2172 msg->cm_fields['A'], msg->cm_fields['N']
2175 for (i=0; i<num_tokens(recps->recp_internet, '|'); ++i) {
2176 size_t tmp = strlen(instr);
2177 extract_token(recipient, recps->recp_internet, i, '|', sizeof recipient);
2178 snprintf(&instr[tmp], SIZ * 2 - tmp,
2179 "remote|%s|0||\n", recipient);
2182 imsg = malloc(sizeof(struct CtdlMessage));
2183 memset(imsg, 0, sizeof(struct CtdlMessage));
2184 imsg->cm_magic = CTDLMESSAGE_MAGIC;
2185 imsg->cm_anon_type = MES_NORMAL;
2186 imsg->cm_format_type = FMT_RFC822;
2187 imsg->cm_fields['A'] = strdup("Citadel");
2188 imsg->cm_fields['M'] = instr;
2189 CtdlSubmitMsg(imsg, NULL, SMTP_SPOOLOUT_ROOM);
2190 CtdlFreeMessage(imsg);
2199 * Convenience function for generating small administrative messages.
2201 void quickie_message(char *from, char *to, char *room, char *text,
2202 int format_type, char *subject)
2204 struct CtdlMessage *msg;
2205 struct recptypes *recp = NULL;
2207 msg = malloc(sizeof(struct CtdlMessage));
2208 memset(msg, 0, sizeof(struct CtdlMessage));
2209 msg->cm_magic = CTDLMESSAGE_MAGIC;
2210 msg->cm_anon_type = MES_NORMAL;
2211 msg->cm_format_type = format_type;
2212 msg->cm_fields['A'] = strdup(from);
2213 if (room != NULL) msg->cm_fields['O'] = strdup(room);
2214 msg->cm_fields['N'] = strdup(NODENAME);
2216 msg->cm_fields['R'] = strdup(to);
2217 recp = validate_recipients(to);
2219 if (subject != NULL) {
2220 msg->cm_fields['U'] = strdup(subject);
2222 msg->cm_fields['M'] = strdup(text);
2224 CtdlSubmitMsg(msg, recp, room);
2225 CtdlFreeMessage(msg);
2226 if (recp != NULL) free(recp);
2232 * Back end function used by CtdlMakeMessage() and similar functions
2234 char *CtdlReadMessageBody(char *terminator, /* token signalling EOT */
2235 size_t maxlen, /* maximum message length */
2236 char *exist, /* if non-null, append to it;
2237 exist is ALWAYS freed */
2238 int crlf /* CRLF newlines instead of LF */
2242 size_t message_len = 0;
2243 size_t buffer_len = 0;
2249 if (exist == NULL) {
2256 message_len = strlen(exist);
2257 buffer_len = message_len + 4096;
2258 m = realloc(exist, buffer_len);
2265 /* flush the input if we have nowhere to store it */
2270 /* read in the lines of message text one by one */
2272 if (client_getln(buf, (sizeof buf - 3)) < 1) finished = 1;
2273 if (!strcmp(buf, terminator)) finished = 1;
2275 strcat(buf, "\r\n");
2281 if ( (!flushing) && (!finished) ) {
2282 /* Measure the line */
2283 linelen = strlen(buf);
2285 /* augment the buffer if we have to */
2286 if ((message_len + linelen) >= buffer_len) {
2287 ptr = realloc(m, (buffer_len * 2) );
2288 if (ptr == NULL) { /* flush if can't allocate */
2291 buffer_len = (buffer_len * 2);
2293 lprintf(CTDL_DEBUG, "buffer_len is now %ld\n", (long)buffer_len);
2297 /* Add the new line to the buffer. NOTE: this loop must avoid
2298 * using functions like strcat() and strlen() because they
2299 * traverse the entire buffer upon every call, and doing that
2300 * for a multi-megabyte message slows it down beyond usability.
2302 strcpy(&m[message_len], buf);
2303 message_len += linelen;
2306 /* if we've hit the max msg length, flush the rest */
2307 if (message_len >= maxlen) flushing = 1;
2309 } while (!finished);
2317 * Build a binary message to be saved on disk.
2318 * (NOTE: if you supply 'preformatted_text', the buffer you give it
2319 * will become part of the message. This means you are no longer
2320 * responsible for managing that memory -- it will be freed along with
2321 * the rest of the fields when CtdlFreeMessage() is called.)
2324 struct CtdlMessage *CtdlMakeMessage(
2325 struct ctdluser *author, /* author's user structure */
2326 char *recipient, /* NULL if it's not mail */
2327 char *room, /* room where it's going */
2328 int type, /* see MES_ types in header file */
2329 int format_type, /* variformat, plain text, MIME... */
2330 char *fake_name, /* who we're masquerading as */
2331 char *subject, /* Subject (optional) */
2332 char *preformatted_text /* ...or NULL to read text from client */
2334 char dest_node[SIZ];
2336 struct CtdlMessage *msg;
2338 msg = malloc(sizeof(struct CtdlMessage));
2339 memset(msg, 0, sizeof(struct CtdlMessage));
2340 msg->cm_magic = CTDLMESSAGE_MAGIC;
2341 msg->cm_anon_type = type;
2342 msg->cm_format_type = format_type;
2344 /* Don't confuse the poor folks if it's not routed mail. */
2345 strcpy(dest_node, "");
2349 snprintf(buf, sizeof buf, "cit%ld", author->usernum); /* Path */
2350 msg->cm_fields['P'] = strdup(buf);
2352 snprintf(buf, sizeof buf, "%ld", (long)time(NULL)); /* timestamp */
2353 msg->cm_fields['T'] = strdup(buf);
2355 if (fake_name[0]) /* author */
2356 msg->cm_fields['A'] = strdup(fake_name);
2358 msg->cm_fields['A'] = strdup(author->fullname);
2360 if (CC->room.QRflags & QR_MAILBOX) { /* room */
2361 msg->cm_fields['O'] = strdup(&CC->room.QRname[11]);
2364 msg->cm_fields['O'] = strdup(CC->room.QRname);
2367 msg->cm_fields['N'] = strdup(NODENAME); /* nodename */
2368 msg->cm_fields['H'] = strdup(HUMANNODE); /* hnodename */
2370 if (recipient[0] != 0) {
2371 msg->cm_fields['R'] = strdup(recipient);
2373 if (dest_node[0] != 0) {
2374 msg->cm_fields['D'] = strdup(dest_node);
2377 if ( (author == &CC->user) && (strlen(CC->cs_inet_email) > 0) ) {
2378 msg->cm_fields['F'] = strdup(CC->cs_inet_email);
2381 if (subject != NULL) {
2383 if (strlen(subject) > 0) {
2384 msg->cm_fields['U'] = strdup(subject);
2388 if (preformatted_text != NULL) {
2389 msg->cm_fields['M'] = preformatted_text;
2392 msg->cm_fields['M'] = CtdlReadMessageBody("000",
2393 config.c_maxmsglen, NULL, 0);
2401 * Check to see whether we have permission to post a message in the current
2402 * room. Returns a *CITADEL ERROR CODE* and puts a message in errmsgbuf, or
2403 * returns 0 on success.
2405 int CtdlDoIHavePermissionToPostInThisRoom(char *errmsgbuf, size_t n) {
2407 if (!(CC->logged_in)) {
2408 snprintf(errmsgbuf, n, "Not logged in.");
2409 return (ERROR + NOT_LOGGED_IN);
2412 if ((CC->user.axlevel < 2)
2413 && ((CC->room.QRflags & QR_MAILBOX) == 0)) {
2414 snprintf(errmsgbuf, n, "Need to be validated to enter "
2415 "(except in %s> to sysop)", MAILROOM);
2416 return (ERROR + HIGHER_ACCESS_REQUIRED);
2419 if ((CC->user.axlevel < 4)
2420 && (CC->room.QRflags & QR_NETWORK)) {
2421 snprintf(errmsgbuf, n, "Need net privileges to enter here.");
2422 return (ERROR + HIGHER_ACCESS_REQUIRED);
2425 if ((CC->user.axlevel < 6)
2426 && (CC->room.QRflags & QR_READONLY)) {
2427 snprintf(errmsgbuf, n, "Sorry, this is a read-only room.");
2428 return (ERROR + HIGHER_ACCESS_REQUIRED);
2431 strcpy(errmsgbuf, "Ok");
2437 * Check to see if the specified user has Internet mail permission
2438 * (returns nonzero if permission is granted)
2440 int CtdlCheckInternetMailPermission(struct ctdluser *who) {
2442 /* Do not allow twits to send Internet mail */
2443 if (who->axlevel <= 2) return(0);
2445 /* Globally enabled? */
2446 if (config.c_restrict == 0) return(1);
2448 /* User flagged ok? */
2449 if (who->flags & US_INTERNET) return(2);
2451 /* Aide level access? */
2452 if (who->axlevel >= 6) return(3);
2454 /* No mail for you! */
2461 * Validate recipients, count delivery types and errors, and handle aliasing
2462 * FIXME check for dupes!!!!!
2464 struct recptypes *validate_recipients(char *recipients) {
2465 struct recptypes *ret;
2466 char this_recp[SIZ];
2467 char this_recp_cooked[SIZ];
2473 struct ctdluser tempUS;
2474 struct ctdlroom tempQR;
2477 ret = (struct recptypes *) malloc(sizeof(struct recptypes));
2478 if (ret == NULL) return(NULL);
2479 memset(ret, 0, sizeof(struct recptypes));
2482 ret->num_internet = 0;
2487 if (recipients == NULL) {
2490 else if (strlen(recipients) == 0) {
2494 /* Change all valid separator characters to commas */
2495 for (i=0; i<strlen(recipients); ++i) {
2496 if ((recipients[i] == ';') || (recipients[i] == '|')) {
2497 recipients[i] = ',';
2502 num_recps = num_tokens(recipients, ',');
2505 if (num_recps > 0) for (i=0; i<num_recps; ++i) {
2506 extract_token(this_recp, recipients, i, ',', sizeof this_recp);
2508 lprintf(CTDL_DEBUG, "Evaluating recipient #%d <%s>\n", i, this_recp);
2509 mailtype = alias(this_recp);
2510 mailtype = alias(this_recp);
2511 mailtype = alias(this_recp);
2512 for (j=0; j<=strlen(this_recp); ++j) {
2513 if (this_recp[j]=='_') {
2514 this_recp_cooked[j] = ' ';
2517 this_recp_cooked[j] = this_recp[j];
2523 if (!strcasecmp(this_recp, "sysop")) {
2525 strcpy(this_recp, config.c_aideroom);
2526 if (strlen(ret->recp_room) > 0) {
2527 strcat(ret->recp_room, "|");
2529 strcat(ret->recp_room, this_recp);
2531 else if (getuser(&tempUS, this_recp) == 0) {
2533 strcpy(this_recp, tempUS.fullname);
2534 if (strlen(ret->recp_local) > 0) {
2535 strcat(ret->recp_local, "|");
2537 strcat(ret->recp_local, this_recp);
2539 else if (getuser(&tempUS, this_recp_cooked) == 0) {
2541 strcpy(this_recp, tempUS.fullname);
2542 if (strlen(ret->recp_local) > 0) {
2543 strcat(ret->recp_local, "|");
2545 strcat(ret->recp_local, this_recp);
2547 else if ( (!strncasecmp(this_recp, "room_", 5))
2548 && (!getroom(&tempQR, &this_recp_cooked[5])) ) {
2550 if (strlen(ret->recp_room) > 0) {
2551 strcat(ret->recp_room, "|");
2553 strcat(ret->recp_room, &this_recp_cooked[5]);
2561 /* Yes, you're reading this correctly: if the target
2562 * domain points back to the local system or an attached
2563 * Citadel directory, the address is invalid. That's
2564 * because if the address were valid, we would have
2565 * already translated it to a local address by now.
2567 if (IsDirectory(this_recp)) {
2572 ++ret->num_internet;
2573 if (strlen(ret->recp_internet) > 0) {
2574 strcat(ret->recp_internet, "|");
2576 strcat(ret->recp_internet, this_recp);
2581 if (strlen(ret->recp_ignet) > 0) {
2582 strcat(ret->recp_ignet, "|");
2584 strcat(ret->recp_ignet, this_recp);
2592 if (strlen(ret->errormsg) == 0) {
2593 snprintf(append, sizeof append,
2594 "Invalid recipient: %s",
2598 snprintf(append, sizeof append,
2601 if ( (strlen(ret->errormsg) + strlen(append)) < SIZ) {
2602 strcat(ret->errormsg, append);
2606 if (strlen(ret->display_recp) == 0) {
2607 strcpy(append, this_recp);
2610 snprintf(append, sizeof append, ", %s",
2613 if ( (strlen(ret->display_recp)+strlen(append)) < SIZ) {
2614 strcat(ret->display_recp, append);
2619 if ((ret->num_local + ret->num_internet + ret->num_ignet +
2620 ret->num_room + ret->num_error) == 0) {
2622 strcpy(ret->errormsg, "No recipients specified.");
2625 lprintf(CTDL_DEBUG, "validate_recipients()\n");
2626 lprintf(CTDL_DEBUG, " local: %d <%s>\n", ret->num_local, ret->recp_local);
2627 lprintf(CTDL_DEBUG, " room: %d <%s>\n", ret->num_room, ret->recp_room);
2628 lprintf(CTDL_DEBUG, " inet: %d <%s>\n", ret->num_internet, ret->recp_internet);
2629 lprintf(CTDL_DEBUG, " ignet: %d <%s>\n", ret->num_ignet, ret->recp_ignet);
2630 lprintf(CTDL_DEBUG, " error: %d <%s>\n", ret->num_error, ret->errormsg);
2638 * message entry - mode 0 (normal)
2640 void cmd_ent0(char *entargs)
2644 char masquerade_as[SIZ];
2646 int format_type = 0;
2647 char newusername[SIZ];
2648 struct CtdlMessage *msg;
2652 struct recptypes *valid = NULL;
2659 post = extract_int(entargs, 0);
2660 extract_token(recp, entargs, 1, '|', sizeof recp);
2661 anon_flag = extract_int(entargs, 2);
2662 format_type = extract_int(entargs, 3);
2663 extract_token(subject, entargs, 4, '|', sizeof subject);
2664 do_confirm = extract_int(entargs, 6);
2666 /* first check to make sure the request is valid. */
2668 err = CtdlDoIHavePermissionToPostInThisRoom(errmsg, sizeof errmsg);
2670 cprintf("%d %s\n", err, errmsg);
2674 /* Check some other permission type things. */
2677 if (CC->user.axlevel < 6) {
2678 cprintf("%d You don't have permission to masquerade.\n",
2679 ERROR + HIGHER_ACCESS_REQUIRED);
2682 extract_token(newusername, entargs, 5, '|', sizeof newusername);
2683 memset(CC->fake_postname, 0, sizeof(CC->fake_postname) );
2684 safestrncpy(CC->fake_postname, newusername,
2685 sizeof(CC->fake_postname) );
2686 cprintf("%d ok\n", CIT_OK);
2689 CC->cs_flags |= CS_POSTING;
2691 /* In the Mail> room we have to behave a little differently --
2692 * make sure the user has specified at least one recipient. Then
2693 * validate the recipient(s).
2695 if ( (CC->room.QRflags & QR_MAILBOX)
2696 && (!strcasecmp(&CC->room.QRname[11], MAILROOM)) ) {
2698 if (CC->user.axlevel < 2) {
2699 strcpy(recp, "sysop");
2702 valid = validate_recipients(recp);
2703 if (valid->num_error > 0) {
2705 ERROR + NO_SUCH_USER, valid->errormsg);
2709 if (valid->num_internet > 0) {
2710 if (CtdlCheckInternetMailPermission(&CC->user)==0) {
2711 cprintf("%d You do not have permission "
2712 "to send Internet mail.\n",
2713 ERROR + HIGHER_ACCESS_REQUIRED);
2719 if ( ( (valid->num_internet + valid->num_ignet) > 0)
2720 && (CC->user.axlevel < 4) ) {
2721 cprintf("%d Higher access required for network mail.\n",
2722 ERROR + HIGHER_ACCESS_REQUIRED);
2727 if ((RESTRICT_INTERNET == 1) && (valid->num_internet > 0)
2728 && ((CC->user.flags & US_INTERNET) == 0)
2729 && (!CC->internal_pgm)) {
2730 cprintf("%d You don't have access to Internet mail.\n",
2731 ERROR + HIGHER_ACCESS_REQUIRED);
2738 /* Is this a room which has anonymous-only or anonymous-option? */
2739 anonymous = MES_NORMAL;
2740 if (CC->room.QRflags & QR_ANONONLY) {
2741 anonymous = MES_ANONONLY;
2743 if (CC->room.QRflags & QR_ANONOPT) {
2744 if (anon_flag == 1) { /* only if the user requested it */
2745 anonymous = MES_ANONOPT;
2749 if ((CC->room.QRflags & QR_MAILBOX) == 0) {
2753 /* If we're only checking the validity of the request, return
2754 * success without creating the message.
2757 cprintf("%d %s\n", CIT_OK,
2758 ((valid != NULL) ? valid->display_recp : "") );
2763 /* Handle author masquerading */
2764 if (CC->fake_postname[0]) {
2765 strcpy(masquerade_as, CC->fake_postname);
2767 else if (CC->fake_username[0]) {
2768 strcpy(masquerade_as, CC->fake_username);
2771 strcpy(masquerade_as, "");
2774 /* Read in the message from the client. */
2776 cprintf("%d send message\n", START_CHAT_MODE);
2778 cprintf("%d send message\n", SEND_LISTING);
2780 msg = CtdlMakeMessage(&CC->user, recp,
2781 CC->room.QRname, anonymous, format_type,
2782 masquerade_as, subject, NULL);
2785 msgnum = CtdlSubmitMsg(msg, valid, "");
2788 cprintf("%ld\n", msgnum);
2790 cprintf("Message accepted.\n");
2793 cprintf("Internal error.\n");
2795 if (msg->cm_fields['E'] != NULL) {
2796 cprintf("%s\n", msg->cm_fields['E']);
2803 CtdlFreeMessage(msg);
2805 CC->fake_postname[0] = '\0';
2813 * API function to delete messages which match a set of criteria
2814 * (returns the actual number of messages deleted)
2816 int CtdlDeleteMessages(char *room_name, /* which room */
2817 long dmsgnum, /* or "0" for any */
2818 char *content_type /* or "" for any */
2822 struct ctdlroom qrbuf;
2823 struct cdbdata *cdbfr;
2824 long *msglist = NULL;
2825 long *dellist = NULL;
2828 int num_deleted = 0;
2830 struct MetaData smi;
2832 lprintf(CTDL_DEBUG, "CtdlDeleteMessages(%s, %ld, %s)\n",
2833 room_name, dmsgnum, content_type);
2835 /* get room record, obtaining a lock... */
2836 if (lgetroom(&qrbuf, room_name) != 0) {
2837 lprintf(CTDL_ERR, "CtdlDeleteMessages(): Room <%s> not found\n",
2839 return (0); /* room not found */
2841 cdbfr = cdb_fetch(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long));
2843 if (cdbfr != NULL) {
2844 msglist = malloc(cdbfr->len);
2845 dellist = malloc(cdbfr->len);
2846 memcpy(msglist, cdbfr->ptr, cdbfr->len);
2847 num_msgs = cdbfr->len / sizeof(long);
2851 for (i = 0; i < num_msgs; ++i) {
2854 /* Set/clear a bit for each criterion */
2856 if ((dmsgnum == 0L) || (msglist[i] == dmsgnum)) {
2857 delete_this |= 0x01;
2859 if (strlen(content_type) == 0) {
2860 delete_this |= 0x02;
2862 GetMetaData(&smi, msglist[i]);
2863 if (!strcasecmp(smi.meta_content_type,
2865 delete_this |= 0x02;
2869 /* Delete message only if all bits are set */
2870 if (delete_this == 0x03) {
2871 dellist[num_deleted++] = msglist[i];
2876 num_msgs = sort_msglist(msglist, num_msgs);
2877 cdb_store(CDB_MSGLISTS, &qrbuf.QRnumber, (int)sizeof(long),
2878 msglist, (int)(num_msgs * sizeof(long)));
2880 qrbuf.QRhighest = msglist[num_msgs - 1];
2884 /* Go through the messages we pulled out of the index, and decrement
2885 * their reference counts by 1. If this is the only room the message
2886 * was in, the reference count will reach zero and the message will
2887 * automatically be deleted from the database. We do this in a
2888 * separate pass because there might be plug-in hooks getting called,
2889 * and we don't want that happening during an S_ROOMS critical
2892 if (num_deleted) for (i=0; i<num_deleted; ++i) {
2893 PerformDeleteHooks(qrbuf.QRname, dellist[i]);
2894 AdjRefCount(dellist[i], -1);
2897 /* Now free the memory we used, and go away. */
2898 if (msglist != NULL) free(msglist);
2899 if (dellist != NULL) free(dellist);
2900 lprintf(CTDL_DEBUG, "%d message(s) deleted.\n", num_deleted);
2901 return (num_deleted);
2907 * Check whether the current user has permission to delete messages from
2908 * the current room (returns 1 for yes, 0 for no)
2910 int CtdlDoIHavePermissionToDeleteMessagesFromThisRoom(void) {
2911 getuser(&CC->user, CC->curr_user);
2912 if ((CC->user.axlevel < 6)
2913 && (CC->user.usernum != CC->room.QRroomaide)
2914 && ((CC->room.QRflags & QR_MAILBOX) == 0)
2915 && (!(CC->internal_pgm))) {
2924 * Delete message from current room
2926 void cmd_dele(char *delstr)
2931 if (CtdlDoIHavePermissionToDeleteMessagesFromThisRoom() == 0) {
2932 cprintf("%d Higher access required.\n",
2933 ERROR + HIGHER_ACCESS_REQUIRED);
2936 delnum = extract_long(delstr, 0);
2938 num_deleted = CtdlDeleteMessages(CC->room.QRname, delnum, "");
2941 cprintf("%d %d message%s deleted.\n", CIT_OK,
2942 num_deleted, ((num_deleted != 1) ? "s" : ""));
2944 cprintf("%d Message %ld not found.\n", ERROR + MESSAGE_NOT_FOUND, delnum);
2950 * Back end API function for moves and deletes
2952 int CtdlCopyMsgToRoom(long msgnum, char *dest) {
2955 err = CtdlSaveMsgPointerInRoom(dest, msgnum,
2956 (SM_VERIFY_GOODNESS | SM_DO_REPL_CHECK) );
2957 if (err != 0) return(err);
2965 * move or copy a message to another room
2967 void cmd_move(char *args)
2970 char targ[ROOMNAMELEN];
2971 struct ctdlroom qtemp;
2977 num = extract_long(args, 0);
2978 extract_token(targ, args, 1, '|', sizeof targ);
2979 targ[ROOMNAMELEN - 1] = 0;
2980 is_copy = extract_int(args, 2);
2982 if (getroom(&qtemp, targ) != 0) {
2983 cprintf("%d '%s' does not exist.\n",
2984 ERROR + ROOM_NOT_FOUND, targ);
2988 getuser(&CC->user, CC->curr_user);
2989 CtdlRoomAccess(&qtemp, &CC->user, &ra, NULL);
2991 /* Check for permission to perform this operation.
2992 * Remember: "CC->room" is source, "qtemp" is target.
2996 /* Aides can move/copy */
2997 if (CC->user.axlevel >= 6) permit = 1;
2999 /* Room aides can move/copy */
3000 if (CC->user.usernum == CC->room.QRroomaide) permit = 1;
3002 /* Permit move/copy from personal rooms */
3003 if ((CC->room.QRflags & QR_MAILBOX)
3004 && (qtemp.QRflags & QR_MAILBOX)) permit = 1;
3006 /* Permit only copy from public to personal room */
3008 && (!(CC->room.QRflags & QR_MAILBOX))
3009 && (qtemp.QRflags & QR_MAILBOX)) permit = 1;
3011 /* User must have access to target room */
3012 if (!(ra & UA_KNOWN)) permit = 0;
3015 cprintf("%d Higher access required.\n",
3016 ERROR + HIGHER_ACCESS_REQUIRED);
3020 err = CtdlCopyMsgToRoom(num, targ);
3022 cprintf("%d Cannot store message in %s: error %d\n",
3027 /* Now delete the message from the source room,
3028 * if this is a 'move' rather than a 'copy' operation.
3031 CtdlDeleteMessages(CC->room.QRname, num, "");
3034 cprintf("%d Message %s.\n", CIT_OK, (is_copy ? "copied" : "moved") );
3040 * GetMetaData() - Get the supplementary record for a message
3042 void GetMetaData(struct MetaData *smibuf, long msgnum)
3045 struct cdbdata *cdbsmi;
3048 memset(smibuf, 0, sizeof(struct MetaData));
3049 smibuf->meta_msgnum = msgnum;
3050 smibuf->meta_refcount = 1; /* Default reference count is 1 */
3052 /* Use the negative of the message number for its supp record index */
3053 TheIndex = (0L - msgnum);
3055 cdbsmi = cdb_fetch(CDB_MSGMAIN, &TheIndex, sizeof(long));
3056 if (cdbsmi == NULL) {
3057 return; /* record not found; go with defaults */
3059 memcpy(smibuf, cdbsmi->ptr,
3060 ((cdbsmi->len > sizeof(struct MetaData)) ?
3061 sizeof(struct MetaData) : cdbsmi->len));
3068 * PutMetaData() - (re)write supplementary record for a message
3070 void PutMetaData(struct MetaData *smibuf)
3074 /* Use the negative of the message number for the metadata db index */
3075 TheIndex = (0L - smibuf->meta_msgnum);
3077 lprintf(CTDL_DEBUG, "PutMetaData(%ld) - ref count is %d\n",
3078 smibuf->meta_msgnum, smibuf->meta_refcount);
3080 cdb_store(CDB_MSGMAIN,
3081 &TheIndex, (int)sizeof(long),
3082 smibuf, (int)sizeof(struct MetaData));
3087 * AdjRefCount - change the reference count for a message;
3088 * delete the message if it reaches zero
3090 void AdjRefCount(long msgnum, int incr)
3093 struct MetaData smi;
3096 /* This is a *tight* critical section; please keep it that way, as
3097 * it may get called while nested in other critical sections.
3098 * Complicating this any further will surely cause deadlock!
3100 begin_critical_section(S_SUPPMSGMAIN);
3101 GetMetaData(&smi, msgnum);
3102 lprintf(CTDL_DEBUG, "Ref count for message <%ld> before write is <%d>\n",
3103 msgnum, smi.meta_refcount);
3104 smi.meta_refcount += incr;
3106 end_critical_section(S_SUPPMSGMAIN);
3107 lprintf(CTDL_DEBUG, "Ref count for message <%ld> after write is <%d>\n",
3108 msgnum, smi.meta_refcount);
3110 /* If the reference count is now zero, delete the message
3111 * (and its supplementary record as well).
3113 if (smi.meta_refcount == 0) {
3114 lprintf(CTDL_DEBUG, "Deleting message <%ld>\n", msgnum);
3116 cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long));
3117 cdb_delete(CDB_BIGMSGS, &delnum, (int)sizeof(long));
3119 /* We have to delete the metadata record too! */
3120 delnum = (0L - msgnum);
3121 cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long));
3126 * Write a generic object to this room
3128 * Note: this could be much more efficient. Right now we use two temporary
3129 * files, and still pull the message into memory as with all others.
3131 void CtdlWriteObject(char *req_room, /* Room to stuff it in */
3132 char *content_type, /* MIME type of this object */
3133 char *tempfilename, /* Where to fetch it from */
3134 struct ctdluser *is_mailbox, /* Mailbox room? */
3135 int is_binary, /* Is encoding necessary? */
3136 int is_unique, /* Del others of this type? */
3137 unsigned int flags /* Internal save flags */
3142 struct ctdlroom qrbuf;
3143 char roomname[ROOMNAMELEN];
3144 struct CtdlMessage *msg;
3146 char *raw_message = NULL;
3147 char *encoded_message = NULL;
3148 off_t raw_length = 0;
3150 if (is_mailbox != NULL)
3151 MailboxName(roomname, sizeof roomname, is_mailbox, req_room);
3153 safestrncpy(roomname, req_room, sizeof(roomname));
3154 lprintf(CTDL_DEBUG, "CtdlWriteObject() to <%s> (flags=%d)\n", roomname, flags);
3157 fp = fopen(tempfilename, "rb");
3159 lprintf(CTDL_CRIT, "Cannot open %s: %s\n",
3160 tempfilename, strerror(errno));
3163 fseek(fp, 0L, SEEK_END);
3164 raw_length = ftell(fp);
3166 lprintf(CTDL_DEBUG, "Raw length is %ld\n", (long)raw_length);
3168 raw_message = malloc((size_t)raw_length + 2);
3169 fread(raw_message, (size_t)raw_length, 1, fp);
3173 encoded_message = malloc((size_t)
3174 (((raw_length * 134) / 100) + 4096 ) );
3177 encoded_message = malloc((size_t)(raw_length + 4096));
3180 sprintf(encoded_message, "Content-type: %s\n", content_type);
3183 sprintf(&encoded_message[strlen(encoded_message)],
3184 "Content-transfer-encoding: base64\n\n"
3188 sprintf(&encoded_message[strlen(encoded_message)],
3189 "Content-transfer-encoding: 7bit\n\n"
3195 &encoded_message[strlen(encoded_message)],
3201 raw_message[raw_length] = 0;
3203 &encoded_message[strlen(encoded_message)],
3211 lprintf(CTDL_DEBUG, "Allocating\n");
3212 msg = malloc(sizeof(struct CtdlMessage));
3213 memset(msg, 0, sizeof(struct CtdlMessage));
3214 msg->cm_magic = CTDLMESSAGE_MAGIC;
3215 msg->cm_anon_type = MES_NORMAL;
3216 msg->cm_format_type = 4;
3217 msg->cm_fields['A'] = strdup(CC->user.fullname);
3218 msg->cm_fields['O'] = strdup(req_room);
3219 msg->cm_fields['N'] = strdup(config.c_nodename);
3220 msg->cm_fields['H'] = strdup(config.c_humannode);
3221 msg->cm_flags = flags;
3223 msg->cm_fields['M'] = encoded_message;
3225 /* Create the requested room if we have to. */
3226 if (getroom(&qrbuf, roomname) != 0) {
3227 create_room(roomname,
3228 ( (is_mailbox != NULL) ? 5 : 3 ),
3229 "", 0, 1, 0, VIEW_BBS);
3231 /* If the caller specified this object as unique, delete all
3232 * other objects of this type that are currently in the room.
3235 lprintf(CTDL_DEBUG, "Deleted %d other msgs of this type\n",
3236 CtdlDeleteMessages(roomname, 0L, content_type));
3238 /* Now write the data */
3239 CtdlSubmitMsg(msg, NULL, roomname);
3240 CtdlFreeMessage(msg);
3248 void CtdlGetSysConfigBackend(long msgnum, void *userdata) {
3249 config_msgnum = msgnum;
3253 char *CtdlGetSysConfig(char *sysconfname) {
3254 char hold_rm[ROOMNAMELEN];
3257 struct CtdlMessage *msg;
3260 strcpy(hold_rm, CC->room.QRname);
3261 if (getroom(&CC->room, SYSCONFIGROOM) != 0) {
3262 getroom(&CC->room, hold_rm);
3267 /* We want the last (and probably only) config in this room */
3268 begin_critical_section(S_CONFIG);
3269 config_msgnum = (-1L);
3270 CtdlForEachMessage(MSGS_LAST, 1, sysconfname, NULL,
3271 CtdlGetSysConfigBackend, NULL);
3272 msgnum = config_msgnum;
3273 end_critical_section(S_CONFIG);
3279 msg = CtdlFetchMessage(msgnum, 1);
3281 conf = strdup(msg->cm_fields['M']);
3282 CtdlFreeMessage(msg);
3289 getroom(&CC->room, hold_rm);
3291 if (conf != NULL) do {
3292 extract_token(buf, conf, 0, '\n', sizeof buf);
3293 strcpy(conf, &conf[strlen(buf)+1]);
3294 } while ( (strlen(conf)>0) && (strlen(buf)>0) );
3299 void CtdlPutSysConfig(char *sysconfname, char *sysconfdata) {
3300 char temp[PATH_MAX];
3303 strcpy(temp, tmpnam(NULL));
3305 fp = fopen(temp, "w");
3306 if (fp == NULL) return;
3307 fprintf(fp, "%s", sysconfdata);
3310 /* this handy API function does all the work for us */
3311 CtdlWriteObject(SYSCONFIGROOM, sysconfname, temp, NULL, 0, 1, 0);
3317 * Determine whether a given Internet address belongs to the current user
3319 int CtdlIsMe(char *addr, int addr_buf_len)
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_token(addr, recp->recp_local, i, '|', addr_buf_len);
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_token(addr, argbuf, 0, '|', sizeof addr);
3354 if (CtdlIsMe(addr, sizeof addr)) {
3355 cprintf("%d %s\n", CIT_OK, addr);
3358 cprintf("%d Not you.\n", ERROR + ILLEGAL_VALUE);