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) {
309 struct cdbdata *cdbfr;
319 char *is_set; /* actually an array of booleans */
322 char setstr[SIZ], lostr[SIZ], histr[SIZ];
324 lprintf(CTDL_DEBUG, "CtdlSetSeen(%ld, %d, %d)\n",
325 target_msgnum, target_setting, which_set);
327 /* Learn about the user and room in question */
328 CtdlGetRelationship(&vbuf, &CC->user, &CC->room);
330 /* Load the message list */
331 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long));
333 msglist = malloc(cdbfr->len);
334 memcpy(msglist, cdbfr->ptr, cdbfr->len);
335 num_msgs = cdbfr->len / sizeof(long);
338 return; /* No messages at all? No further action. */
341 is_set = malloc(num_msgs * sizeof(char));
342 memset(is_set, 0, (num_msgs * sizeof(char)) );
344 /* Decide which message set we're manipulating */
345 if (which_set == ctdlsetseen_seen) safestrncpy(vset, vbuf.v_seen, sizeof vset);
346 if (which_set == ctdlsetseen_answered) safestrncpy(vset, vbuf.v_answered, sizeof vset);
348 lprintf(CTDL_DEBUG, "before optimize: %s\n", vset);
350 /* Translate the existing sequence set into an array of booleans */
351 num_sets = num_tokens(vset, ',');
352 for (s=0; s<num_sets; ++s) {
353 extract_token(setstr, vset, s, ',', sizeof setstr);
355 extract_token(lostr, setstr, 0, ':', sizeof lostr);
356 if (num_tokens(setstr, ':') >= 2) {
357 extract_token(histr, setstr, 1, ':', sizeof histr);
358 if (!strcmp(histr, "*")) {
359 snprintf(histr, sizeof histr, "%ld", LONG_MAX);
363 strcpy(histr, lostr);
368 for (i = 0; i < num_msgs; ++i) {
369 if ((msglist[i] >= lo) && (msglist[i] <= hi)) {
375 /* Now translate the array of booleans back into a sequence set */
378 for (i=0; i<num_msgs; ++i) {
381 if (msglist[i] == target_msgnum) {
382 is_seen = target_setting;
391 if (lo < 0L) lo = msglist[i];
394 if ( ((is_seen == 0) && (was_seen == 1))
395 || ((is_seen == 1) && (i == num_msgs-1)) ) {
398 if ( (strlen(vset) + 20) > sizeof vset) {
399 strcpy(vset, &vset[20]);
408 snprintf(&vset[tmp], sizeof vset - tmp,
412 snprintf(&vset[tmp], sizeof vset - tmp,
421 /* Decide which message set we're manipulating */
422 if (which_set == ctdlsetseen_seen) safestrncpy(vbuf.v_seen, vset, sizeof vbuf.v_seen);
423 if (which_set == ctdlsetseen_answered) safestrncpy(vbuf.v_answered, vset, sizeof vbuf.v_answered);
426 lprintf(CTDL_DEBUG, " after optimize: %s\n", vset);
428 CtdlSetRelationship(&vbuf, &CC->user, &CC->room);
433 * API function to perform an operation for each qualifying message in the
434 * current room. (Returns the number of messages processed.)
436 int CtdlForEachMessage(int mode, long ref,
438 struct CtdlMessage *compare,
439 void (*CallBack) (long, void *),
445 struct cdbdata *cdbfr;
446 long *msglist = NULL;
448 int num_processed = 0;
451 struct CtdlMessage *msg;
454 int printed_lastold = 0;
456 /* Learn about the user and room in question */
458 getuser(&CC->user, CC->curr_user);
459 CtdlGetRelationship(&vbuf, &CC->user, &CC->room);
461 /* Load the message list */
462 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long));
464 msglist = malloc(cdbfr->len);
465 memcpy(msglist, cdbfr->ptr, cdbfr->len);
466 num_msgs = cdbfr->len / sizeof(long);
469 return 0; /* No messages at all? No further action. */
474 * Now begin the traversal.
476 if (num_msgs > 0) for (a = 0; a < num_msgs; ++a) {
478 /* If the caller is looking for a specific MIME type, filter
479 * out all messages which are not of the type requested.
481 if (content_type != NULL) if (strlen(content_type) > 0) {
483 /* This call to GetMetaData() sits inside this loop
484 * so that we only do the extra database read per msg
485 * if we need to. Doing the extra read all the time
486 * really kills the server. If we ever need to use
487 * metadata for another search criterion, we need to
488 * move the read somewhere else -- but still be smart
489 * enough to only do the read if the caller has
490 * specified something that will need it.
492 GetMetaData(&smi, msglist[a]);
494 if (strcasecmp(smi.meta_content_type, content_type)) {
500 num_msgs = sort_msglist(msglist, num_msgs);
502 /* If a template was supplied, filter out the messages which
503 * don't match. (This could induce some delays!)
506 if (compare != NULL) {
507 for (a = 0; a < num_msgs; ++a) {
508 msg = CtdlFetchMessage(msglist[a], 1);
510 if (CtdlMsgCmp(msg, compare)) {
513 CtdlFreeMessage(msg);
521 * Now iterate through the message list, according to the
522 * criteria supplied by the caller.
525 for (a = 0; a < num_msgs; ++a) {
526 thismsg = msglist[a];
527 is_seen = is_msg_in_sequence_set(vbuf.v_seen, thismsg);
528 if (is_seen) lastold = thismsg;
533 || ((mode == MSGS_OLD) && (is_seen))
534 || ((mode == MSGS_NEW) && (!is_seen))
535 || ((mode == MSGS_LAST) && (a >= (num_msgs - ref)))
536 || ((mode == MSGS_FIRST) && (a < ref))
537 || ((mode == MSGS_GT) && (thismsg > ref))
538 || ((mode == MSGS_EQ) && (thismsg == ref))
541 if ((mode == MSGS_NEW) && (CC->user.flags & US_LASTOLD) && (lastold > 0L) && (printed_lastold == 0) && (!is_seen)) {
543 CallBack(lastold, userdata);
547 if (CallBack) CallBack(thismsg, userdata);
551 free(msglist); /* Clean up */
552 return num_processed;
558 * cmd_msgs() - get list of message #'s in this room
559 * implements the MSGS server command using CtdlForEachMessage()
561 void cmd_msgs(char *cmdbuf)
570 int with_template = 0;
571 struct CtdlMessage *template = NULL;
573 extract_token(which, cmdbuf, 0, '|', sizeof which);
574 cm_ref = extract_int(cmdbuf, 1);
575 with_template = extract_int(cmdbuf, 2);
579 if (!strncasecmp(which, "OLD", 3))
581 else if (!strncasecmp(which, "NEW", 3))
583 else if (!strncasecmp(which, "FIRST", 5))
585 else if (!strncasecmp(which, "LAST", 4))
587 else if (!strncasecmp(which, "GT", 2))
590 if ((!(CC->logged_in)) && (!(CC->internal_pgm))) {
591 cprintf("%d not logged in\n", ERROR + NOT_LOGGED_IN);
597 cprintf("%d Send template then receive message list\n",
599 template = (struct CtdlMessage *)
600 malloc(sizeof(struct CtdlMessage));
601 memset(template, 0, sizeof(struct CtdlMessage));
602 while(client_getln(buf, sizeof buf), strcmp(buf,"000")) {
603 extract_token(tfield, buf, 0, '|', sizeof tfield);
604 extract_token(tvalue, buf, 1, '|', sizeof tvalue);
605 for (i='A'; i<='Z'; ++i) if (msgkeys[i]!=NULL) {
606 if (!strcasecmp(tfield, msgkeys[i])) {
607 template->cm_fields[i] =
615 cprintf("%d Message list...\n", LISTING_FOLLOWS);
618 CtdlForEachMessage(mode, cm_ref,
619 NULL, template, simple_listing, NULL);
620 if (template != NULL) CtdlFreeMessage(template);
628 * help_subst() - support routine for help file viewer
630 void help_subst(char *strbuf, char *source, char *dest)
635 while (p = pattern2(strbuf, source), (p >= 0)) {
636 strcpy(workbuf, &strbuf[p + strlen(source)]);
637 strcpy(&strbuf[p], dest);
638 strcat(strbuf, workbuf);
643 void do_help_subst(char *buffer)
647 help_subst(buffer, "^nodename", config.c_nodename);
648 help_subst(buffer, "^humannode", config.c_humannode);
649 help_subst(buffer, "^fqdn", config.c_fqdn);
650 help_subst(buffer, "^username", CC->user.fullname);
651 snprintf(buf2, sizeof buf2, "%ld", CC->user.usernum);
652 help_subst(buffer, "^usernum", buf2);
653 help_subst(buffer, "^sysadm", config.c_sysadm);
654 help_subst(buffer, "^variantname", CITADEL);
655 snprintf(buf2, sizeof buf2, "%d", config.c_maxsessions);
656 help_subst(buffer, "^maxsessions", buf2);
657 help_subst(buffer, "^bbsdir", CTDLDIR);
663 * memfmout() - Citadel text formatter and paginator.
664 * Although the original purpose of this routine was to format
665 * text to the reader's screen width, all we're really using it
666 * for here is to format text out to 80 columns before sending it
667 * to the client. The client software may reformat it again.
670 int width, /* screen width to use */
671 char *mptr, /* where are we going to get our text from? */
672 char subst, /* nonzero if we should do substitutions */
673 char *nl) /* string to terminate lines with */
685 c = 1; /* c is the current pos */
689 while (ch = *mptr, ((ch != 0) && (strlen(buffer) < 126))) {
691 buffer[strlen(buffer) + 1] = 0;
692 buffer[strlen(buffer)] = ch;
695 if (buffer[0] == '^')
696 do_help_subst(buffer);
698 buffer[strlen(buffer) + 1] = 0;
700 strcpy(buffer, &buffer[1]);
708 if (((ch == 13) || (ch == 10)) && (old != 13) && (old != 10))
710 if (((old == 13) || (old == 10)) && (isspace(real))) {
718 if (((strlen(aaa) + c) > (width - 5)) && (strlen(aaa) > (width - 5))) {
719 cprintf("%s%s", nl, aaa);
728 if ((strlen(aaa) + c) > (width - 5)) {
737 if ((ch == 13) || (ch == 10)) {
738 cprintf("%s%s", aaa, nl);
745 cprintf("%s%s", aaa, nl);
751 * Callback function for mime parser that simply lists the part
753 void list_this_part(char *name, char *filename, char *partnum, char *disp,
754 void *content, char *cbtype, size_t length, char *encoding,
758 cprintf("part=%s|%s|%s|%s|%s|%ld\n",
759 name, filename, partnum, disp, cbtype, (long)length);
763 * Callback function for multipart prefix
765 void list_this_pref(char *name, char *filename, char *partnum, char *disp,
766 void *content, char *cbtype, size_t length, char *encoding,
769 cprintf("pref=%s|%s\n", partnum, cbtype);
773 * Callback function for multipart sufffix
775 void list_this_suff(char *name, char *filename, char *partnum, char *disp,
776 void *content, char *cbtype, size_t length, char *encoding,
779 cprintf("suff=%s|%s\n", partnum, cbtype);
784 * Callback function for mime parser that opens a section for downloading
786 void mime_download(char *name, char *filename, char *partnum, char *disp,
787 void *content, char *cbtype, size_t length, char *encoding,
791 /* Silently go away if there's already a download open... */
792 if (CC->download_fp != NULL)
795 /* ...or if this is not the desired section */
796 if (strcasecmp(CC->download_desired_section, partnum))
799 CC->download_fp = tmpfile();
800 if (CC->download_fp == NULL)
803 fwrite(content, length, 1, CC->download_fp);
804 fflush(CC->download_fp);
805 rewind(CC->download_fp);
807 OpenCmdResult(filename, cbtype);
813 * Load a message from disk into memory.
814 * This is used by CtdlOutputMsg() and other fetch functions.
816 * NOTE: Caller is responsible for freeing the returned CtdlMessage struct
817 * using the CtdlMessageFree() function.
819 struct CtdlMessage *CtdlFetchMessage(long msgnum, int with_body)
821 struct cdbdata *dmsgtext;
822 struct CtdlMessage *ret = NULL;
826 cit_uint8_t field_header;
828 lprintf(CTDL_DEBUG, "CtdlFetchMessage(%ld, %d)\n", msgnum, with_body);
830 dmsgtext = cdb_fetch(CDB_MSGMAIN, &msgnum, sizeof(long));
831 if (dmsgtext == NULL) {
834 mptr = dmsgtext->ptr;
835 upper_bound = mptr + dmsgtext->len;
837 /* Parse the three bytes that begin EVERY message on disk.
838 * The first is always 0xFF, the on-disk magic number.
839 * The second is the anonymous/public type byte.
840 * The third is the format type byte (vari, fixed, or MIME).
845 "Message %ld appears to be corrupted.\n",
850 ret = (struct CtdlMessage *) malloc(sizeof(struct CtdlMessage));
851 memset(ret, 0, sizeof(struct CtdlMessage));
853 ret->cm_magic = CTDLMESSAGE_MAGIC;
854 ret->cm_anon_type = *mptr++; /* Anon type byte */
855 ret->cm_format_type = *mptr++; /* Format type byte */
858 * The rest is zero or more arbitrary fields. Load them in.
859 * We're done when we encounter either a zero-length field or
860 * have just processed the 'M' (message text) field.
863 if (mptr >= upper_bound) {
866 field_header = *mptr++;
867 ret->cm_fields[field_header] = strdup(mptr);
869 while (*mptr++ != 0); /* advance to next field */
871 } while ((mptr < upper_bound) && (field_header != 'M'));
875 /* Always make sure there's something in the msg text field. If
876 * it's NULL, the message text is most likely stored separately,
877 * so go ahead and fetch that. Failing that, just set a dummy
878 * body so other code doesn't barf.
880 if ( (ret->cm_fields['M'] == NULL) && (with_body) ) {
881 dmsgtext = cdb_fetch(CDB_BIGMSGS, &msgnum, sizeof(long));
882 if (dmsgtext != NULL) {
883 ret->cm_fields['M'] = strdup(dmsgtext->ptr);
887 if (ret->cm_fields['M'] == NULL) {
888 ret->cm_fields['M'] = strdup("<no text>\n");
891 /* Perform "before read" hooks (aborting if any return nonzero) */
892 if (PerformMessageHooks(ret, EVT_BEFOREREAD) > 0) {
893 CtdlFreeMessage(ret);
902 * Returns 1 if the supplied pointer points to a valid Citadel message.
903 * If the pointer is NULL or the magic number check fails, returns 0.
905 int is_valid_message(struct CtdlMessage *msg) {
908 if ((msg->cm_magic) != CTDLMESSAGE_MAGIC) {
909 lprintf(CTDL_WARNING, "is_valid_message() -- self-check failed\n");
917 * 'Destructor' for struct CtdlMessage
919 void CtdlFreeMessage(struct CtdlMessage *msg)
923 if (is_valid_message(msg) == 0) return;
925 for (i = 0; i < 256; ++i)
926 if (msg->cm_fields[i] != NULL) {
927 free(msg->cm_fields[i]);
930 msg->cm_magic = 0; /* just in case */
936 * Pre callback function for multipart/alternative
938 * NOTE: this differs from the standard behavior for a reason. Normally when
939 * displaying multipart/alternative you want to show the _last_ usable
940 * format in the message. Here we show the _first_ one, because it's
941 * usually text/plain. Since this set of functions is designed for text
942 * output to non-MIME-aware clients, this is the desired behavior.
945 void fixed_output_pre(char *name, char *filename, char *partnum, char *disp,
946 void *content, char *cbtype, size_t length, char *encoding,
951 ma = (struct ma_info *)cbuserdata;
952 lprintf(CTDL_DEBUG, "fixed_output_pre() type=<%s>\n", cbtype);
953 if (!strcasecmp(cbtype, "multipart/alternative")) {
961 * Post callback function for multipart/alternative
963 void fixed_output_post(char *name, char *filename, char *partnum, char *disp,
964 void *content, char *cbtype, size_t length, char *encoding,
969 ma = (struct ma_info *)cbuserdata;
970 lprintf(CTDL_DEBUG, "fixed_output_post() type=<%s>\n", cbtype);
971 if (!strcasecmp(cbtype, "multipart/alternative")) {
979 * Inline callback function for mime parser that wants to display text
981 void fixed_output(char *name, char *filename, char *partnum, char *disp,
982 void *content, char *cbtype, size_t length, char *encoding,
990 ma = (struct ma_info *)cbuserdata;
992 lprintf(CTDL_DEBUG, "fixed_output() type=<%s>\n", cbtype);
995 * If we're in the middle of a multipart/alternative scope and
996 * we've already printed another section, skip this one.
998 if ( (ma->is_ma == 1) && (ma->did_print == 1) ) {
999 lprintf(CTDL_DEBUG, "Skipping part %s (%s)\n", partnum, cbtype);
1004 if ( (!strcasecmp(cbtype, "text/plain"))
1005 || (strlen(cbtype)==0) ) {
1008 client_write(wptr, length);
1009 if (wptr[length-1] != '\n') {
1014 else if (!strcasecmp(cbtype, "text/html")) {
1015 ptr = html_to_ascii(content, 80, 0);
1017 client_write(ptr, wlen);
1018 if (ptr[wlen-1] != '\n') {
1023 else if (strncasecmp(cbtype, "multipart/", 10)) {
1024 cprintf("Part %s: %s (%s) (%ld bytes)\r\n",
1025 partnum, filename, cbtype, (long)length);
1030 * The client is elegant and sophisticated and wants to be choosy about
1031 * MIME content types, so figure out which multipart/alternative part
1032 * we're going to send.
1034 void choose_preferred(char *name, char *filename, char *partnum, char *disp,
1035 void *content, char *cbtype, size_t length, char *encoding,
1042 ma = (struct ma_info *)cbuserdata;
1044 if (ma->is_ma > 0) {
1045 for (i=0; i<num_tokens(CC->preferred_formats, '|'); ++i) {
1046 extract_token(buf, CC->preferred_formats, i, '|', sizeof buf);
1047 if (!strcasecmp(buf, cbtype)) {
1048 strcpy(ma->chosen_part, partnum);
1055 * Now that we've chosen our preferred part, output it.
1057 void output_preferred(char *name, char *filename, char *partnum, char *disp,
1058 void *content, char *cbtype, size_t length, char *encoding,
1063 int add_newline = 0;
1067 ma = (struct ma_info *)cbuserdata;
1069 /* This is not the MIME part you're looking for... */
1070 if (strcasecmp(partnum, ma->chosen_part)) return;
1072 /* If the content-type of this part is in our preferred formats
1073 * list, we can simply output it verbatim.
1075 for (i=0; i<num_tokens(CC->preferred_formats, '|'); ++i) {
1076 extract_token(buf, CC->preferred_formats, i, '|', sizeof buf);
1077 if (!strcasecmp(buf, cbtype)) {
1078 /* Yeah! Go! W00t!! */
1080 text_content = (char *)content;
1081 if (text_content[length-1] != '\n') {
1085 cprintf("Content-type: %s\n", cbtype);
1086 cprintf("Content-length: %d\n",
1087 (int)(length + add_newline) );
1088 if (strlen(encoding) > 0) {
1089 cprintf("Content-transfer-encoding: %s\n", encoding);
1092 cprintf("Content-transfer-encoding: 7bit\n");
1095 client_write(content, length);
1096 if (add_newline) cprintf("\n");
1101 /* No translations required or possible: output as text/plain */
1102 cprintf("Content-type: text/plain\n\n");
1103 fixed_output(name, filename, partnum, disp, content, cbtype,
1104 length, encoding, cbuserdata);
1109 * Get a message off disk. (returns om_* values found in msgbase.h)
1112 int CtdlOutputMsg(long msg_num, /* message number (local) to fetch */
1113 int mode, /* how would you like that message? */
1114 int headers_only, /* eschew the message body? */
1115 int do_proto, /* do Citadel protocol responses? */
1116 int crlf /* Use CRLF newlines instead of LF? */
1118 struct CtdlMessage *TheMessage = NULL;
1119 int retcode = om_no_such_msg;
1121 lprintf(CTDL_DEBUG, "CtdlOutputMsg() msgnum=%ld, mode=%d\n",
1124 if ((!(CC->logged_in)) && (!(CC->internal_pgm))) {
1125 if (do_proto) cprintf("%d Not logged in.\n",
1126 ERROR + NOT_LOGGED_IN);
1127 return(om_not_logged_in);
1130 /* FIXME: check message id against msglist for this room */
1133 * Fetch the message from disk. If we're in any sort of headers
1134 * only mode, request that we don't even bother loading the body
1137 if ( (headers_only == HEADERS_FAST) || (headers_only == HEADERS_ONLY) ) {
1138 TheMessage = CtdlFetchMessage(msg_num, 0);
1141 TheMessage = CtdlFetchMessage(msg_num, 1);
1144 if (TheMessage == NULL) {
1145 if (do_proto) cprintf("%d Can't locate msg %ld on disk\n",
1146 ERROR + MESSAGE_NOT_FOUND, msg_num);
1147 return(om_no_such_msg);
1150 retcode = CtdlOutputPreLoadedMsg(
1151 TheMessage, msg_num, mode,
1152 headers_only, do_proto, crlf);
1154 CtdlFreeMessage(TheMessage);
1161 * Get a message off disk. (returns om_* values found in msgbase.h)
1164 int CtdlOutputPreLoadedMsg(
1165 struct CtdlMessage *TheMessage,
1167 int mode, /* how would you like that message? */
1168 int headers_only, /* eschew the message body? */
1169 int do_proto, /* do Citadel protocol responses? */
1170 int crlf /* Use CRLF newlines instead of LF? */
1176 char display_name[256];
1178 char *nl; /* newline string */
1180 int subject_found = 0;
1183 /* Buffers needed for RFC822 translation. These are all filled
1184 * using functions that are bounds-checked, and therefore we can
1185 * make them substantially smaller than SIZ.
1193 char datestamp[100];
1195 lprintf(CTDL_DEBUG, "CtdlOutputPreLoadedMsg(TheMessage=%s, %ld, %d, %d, %d, %d\n",
1196 ((TheMessage == NULL) ? "NULL" : "not null"),
1198 mode, headers_only, do_proto, crlf);
1200 snprintf(mid, sizeof mid, "%ld", msg_num);
1201 nl = (crlf ? "\r\n" : "\n");
1203 if (!is_valid_message(TheMessage)) {
1205 "ERROR: invalid preloaded message for output\n");
1206 return(om_no_such_msg);
1209 /* Are we downloading a MIME component? */
1210 if (mode == MT_DOWNLOAD) {
1211 if (TheMessage->cm_format_type != FMT_RFC822) {
1213 cprintf("%d This is not a MIME message.\n",
1214 ERROR + ILLEGAL_VALUE);
1215 } else if (CC->download_fp != NULL) {
1216 if (do_proto) cprintf(
1217 "%d You already have a download open.\n",
1218 ERROR + RESOURCE_BUSY);
1220 /* Parse the message text component */
1221 mptr = TheMessage->cm_fields['M'];
1222 ma = malloc(sizeof(struct ma_info));
1223 memset(ma, 0, sizeof(struct ma_info));
1224 mime_parser(mptr, NULL, *mime_download, NULL, NULL, (void *)ma, 0);
1226 /* If there's no file open by this time, the requested
1227 * section wasn't found, so print an error
1229 if (CC->download_fp == NULL) {
1230 if (do_proto) cprintf(
1231 "%d Section %s not found.\n",
1232 ERROR + FILE_NOT_FOUND,
1233 CC->download_desired_section);
1236 return((CC->download_fp != NULL) ? om_ok : om_mime_error);
1239 /* now for the user-mode message reading loops */
1240 if (do_proto) cprintf("%d Message %ld:\n", LISTING_FOLLOWS, msg_num);
1242 /* Does the caller want to skip the headers? */
1243 if (headers_only == HEADERS_NONE) goto START_TEXT;
1245 /* Tell the client which format type we're using. */
1246 if ( (mode == MT_CITADEL) && (do_proto) ) {
1247 cprintf("type=%d\n", TheMessage->cm_format_type);
1250 /* nhdr=yes means that we're only displaying headers, no body */
1251 if ( (TheMessage->cm_anon_type == MES_ANONONLY)
1252 && (mode == MT_CITADEL)
1255 cprintf("nhdr=yes\n");
1258 /* begin header processing loop for Citadel message format */
1260 if ((mode == MT_CITADEL) || (mode == MT_MIME)) {
1262 safestrncpy(display_name, "<unknown>", sizeof display_name);
1263 if (TheMessage->cm_fields['A']) {
1264 strcpy(buf, TheMessage->cm_fields['A']);
1265 if (TheMessage->cm_anon_type == MES_ANONONLY) {
1266 safestrncpy(display_name, "****", sizeof display_name);
1268 else if (TheMessage->cm_anon_type == MES_ANONOPT) {
1269 safestrncpy(display_name, "anonymous", sizeof display_name);
1272 safestrncpy(display_name, buf, sizeof display_name);
1274 if ((is_room_aide())
1275 && ((TheMessage->cm_anon_type == MES_ANONONLY)
1276 || (TheMessage->cm_anon_type == MES_ANONOPT))) {
1277 size_t tmp = strlen(display_name);
1278 snprintf(&display_name[tmp],
1279 sizeof display_name - tmp,
1284 /* Don't show Internet address for users on the
1285 * local Citadel network.
1288 if (TheMessage->cm_fields['N'] != NULL)
1289 if (strlen(TheMessage->cm_fields['N']) > 0)
1290 if (haschar(TheMessage->cm_fields['N'], '.') == 0) {
1294 /* Now spew the header fields in the order we like them. */
1295 safestrncpy(allkeys, FORDER, sizeof allkeys);
1296 for (i=0; i<strlen(allkeys); ++i) {
1297 k = (int) allkeys[i];
1299 if ( (TheMessage->cm_fields[k] != NULL)
1300 && (msgkeys[k] != NULL) ) {
1302 if (do_proto) cprintf("%s=%s\n",
1306 else if ((k == 'F') && (suppress_f)) {
1309 /* Masquerade display name if needed */
1311 if (do_proto) cprintf("%s=%s\n",
1313 TheMessage->cm_fields[k]
1322 /* begin header processing loop for RFC822 transfer format */
1327 strcpy(snode, NODENAME);
1328 strcpy(lnode, HUMANNODE);
1329 if (mode == MT_RFC822) {
1330 for (i = 0; i < 256; ++i) {
1331 if (TheMessage->cm_fields[i]) {
1332 mptr = TheMessage->cm_fields[i];
1335 safestrncpy(luser, mptr, sizeof luser);
1336 safestrncpy(suser, mptr, sizeof suser);
1338 else if (i == 'U') {
1339 cprintf("Subject: %s%s", mptr, nl);
1343 safestrncpy(mid, mptr, sizeof mid);
1345 safestrncpy(lnode, mptr, sizeof lnode);
1347 safestrncpy(fuser, mptr, sizeof fuser);
1348 /* else if (i == 'O')
1349 cprintf("X-Citadel-Room: %s%s",
1352 safestrncpy(snode, mptr, sizeof snode);
1354 cprintf("To: %s%s", mptr, nl);
1355 else if (i == 'T') {
1356 datestring(datestamp, sizeof datestamp,
1357 atol(mptr), DATESTRING_RFC822);
1358 cprintf("Date: %s%s", datestamp, nl);
1362 if (subject_found == 0) {
1363 cprintf("Subject: (no subject)%s", nl);
1367 for (i=0; i<strlen(suser); ++i) {
1368 suser[i] = tolower(suser[i]);
1369 if (!isalnum(suser[i])) suser[i]='_';
1372 if (mode == MT_RFC822) {
1373 if (!strcasecmp(snode, NODENAME)) {
1374 safestrncpy(snode, FQDN, sizeof snode);
1377 /* Construct a fun message id */
1378 cprintf("Message-ID: <%s", mid);
1379 if (strchr(mid, '@')==NULL) {
1380 cprintf("@%s", snode);
1384 if (!is_room_aide() && (TheMessage->cm_anon_type == MES_ANONONLY)) {
1385 // cprintf("From: x@x.org (----)%s", nl);
1386 cprintf("From: \"----\" <x@x.org>%s", nl);
1388 else if (!is_room_aide() && (TheMessage->cm_anon_type == MES_ANONOPT)) {
1389 // cprintf("From: x@x.org (anonymous)%s", nl);
1390 cprintf("From: \"anonymous\" <x@x.org>%s", nl);
1392 else if (strlen(fuser) > 0) {
1393 // cprintf("From: %s (%s)%s", fuser, luser, nl);
1394 cprintf("From: \"%s\" <%s>%s", luser, fuser, nl);
1397 // cprintf("From: %s@%s (%s)%s", suser, snode, luser, nl);
1398 cprintf("From: \"%s\" <%s@%s>%s", luser, suser, snode, nl);
1401 cprintf("Organization: %s%s", lnode, nl);
1403 /* Blank line signifying RFC822 end-of-headers */
1404 if (TheMessage->cm_format_type != FMT_RFC822) {
1409 /* end header processing loop ... at this point, we're in the text */
1411 if (headers_only == HEADERS_FAST) goto DONE;
1412 mptr = TheMessage->cm_fields['M'];
1414 /* Tell the client about the MIME parts in this message */
1415 if (TheMessage->cm_format_type == FMT_RFC822) {
1416 if ( (mode == MT_CITADEL) || (mode == MT_MIME) ) {
1417 mime_parser(mptr, NULL,
1418 (do_proto ? *list_this_part : NULL),
1419 (do_proto ? *list_this_pref : NULL),
1420 (do_proto ? *list_this_suff : NULL),
1423 else if (mode == MT_RFC822) { /* unparsed RFC822 dump */
1424 /* FIXME ... we have to put some code in here to avoid
1425 * printing duplicate header information when both
1426 * Citadel and RFC822 headers exist. Preference should
1427 * probably be given to the RFC822 headers.
1429 int done_rfc822_hdrs = 0;
1430 while (ch=*(mptr++), ch!=0) {
1435 if (!done_rfc822_hdrs) {
1436 if (headers_only != HEADERS_NONE) {
1441 if (headers_only != HEADERS_ONLY) {
1445 if ((*(mptr) == 13) || (*(mptr) == 10)) {
1446 done_rfc822_hdrs = 1;
1450 if (done_rfc822_hdrs) {
1451 if (headers_only != HEADERS_NONE) {
1456 if (headers_only != HEADERS_ONLY) {
1460 if ((*mptr == 13) || (*mptr == 10)) {
1461 done_rfc822_hdrs = 1;
1469 if (headers_only == HEADERS_ONLY) {
1473 /* signify start of msg text */
1474 if ( (mode == MT_CITADEL) || (mode == MT_MIME) ) {
1475 if (do_proto) cprintf("text\n");
1478 /* If the format type on disk is 1 (fixed-format), then we want
1479 * everything to be output completely literally ... regardless of
1480 * what message transfer format is in use.
1482 if (TheMessage->cm_format_type == FMT_FIXED) {
1483 if (mode == MT_MIME) {
1484 cprintf("Content-type: text/plain\n\n");
1487 while (ch = *mptr++, ch > 0) {
1490 if ((ch == 10) || (strlen(buf) > 250)) {
1491 cprintf("%s%s", buf, nl);
1494 buf[strlen(buf) + 1] = 0;
1495 buf[strlen(buf)] = ch;
1498 if (strlen(buf) > 0)
1499 cprintf("%s%s", buf, nl);
1502 /* If the message on disk is format 0 (Citadel vari-format), we
1503 * output using the formatter at 80 columns. This is the final output
1504 * form if the transfer format is RFC822, but if the transfer format
1505 * is Citadel proprietary, it'll still work, because the indentation
1506 * for new paragraphs is correct and the client will reformat the
1507 * message to the reader's screen width.
1509 if (TheMessage->cm_format_type == FMT_CITADEL) {
1510 if (mode == MT_MIME) {
1511 cprintf("Content-type: text/x-citadel-variformat\n\n");
1513 memfmout(80, mptr, 0, nl);
1516 /* If the message on disk is format 4 (MIME), we've gotta hand it
1517 * off to the MIME parser. The client has already been told that
1518 * this message is format 1 (fixed format), so the callback function
1519 * we use will display those parts as-is.
1521 if (TheMessage->cm_format_type == FMT_RFC822) {
1522 ma = malloc(sizeof(struct ma_info));
1523 memset(ma, 0, sizeof(struct ma_info));
1525 if (mode == MT_MIME) {
1526 strcpy(ma->chosen_part, "1");
1527 mime_parser(mptr, NULL,
1528 *choose_preferred, *fixed_output_pre,
1529 *fixed_output_post, (void *)ma, 0);
1530 mime_parser(mptr, NULL,
1531 *output_preferred, NULL, NULL, (void *)ma, 0);
1534 mime_parser(mptr, NULL,
1535 *fixed_output, *fixed_output_pre,
1536 *fixed_output_post, (void *)ma, 0);
1542 DONE: /* now we're done */
1543 if (do_proto) cprintf("000\n");
1550 * display a message (mode 0 - Citadel proprietary)
1552 void cmd_msg0(char *cmdbuf)
1555 int headers_only = HEADERS_ALL;
1557 msgid = extract_long(cmdbuf, 0);
1558 headers_only = extract_int(cmdbuf, 1);
1560 CtdlOutputMsg(msgid, MT_CITADEL, headers_only, 1, 0);
1566 * display a message (mode 2 - RFC822)
1568 void cmd_msg2(char *cmdbuf)
1571 int headers_only = HEADERS_ALL;
1573 msgid = extract_long(cmdbuf, 0);
1574 headers_only = extract_int(cmdbuf, 1);
1576 CtdlOutputMsg(msgid, MT_RFC822, headers_only, 1, 1);
1582 * display a message (mode 3 - IGnet raw format - internal programs only)
1584 void cmd_msg3(char *cmdbuf)
1587 struct CtdlMessage *msg;
1590 if (CC->internal_pgm == 0) {
1591 cprintf("%d This command is for internal programs only.\n",
1592 ERROR + HIGHER_ACCESS_REQUIRED);
1596 msgnum = extract_long(cmdbuf, 0);
1597 msg = CtdlFetchMessage(msgnum, 1);
1599 cprintf("%d Message %ld not found.\n",
1600 ERROR + MESSAGE_NOT_FOUND, msgnum);
1604 serialize_message(&smr, msg);
1605 CtdlFreeMessage(msg);
1608 cprintf("%d Unable to serialize message\n",
1609 ERROR + INTERNAL_ERROR);
1613 cprintf("%d %ld\n", BINARY_FOLLOWS, (long)smr.len);
1614 client_write((char *)smr.ser, (int)smr.len);
1621 * Display a message using MIME content types
1623 void cmd_msg4(char *cmdbuf)
1627 msgid = extract_long(cmdbuf, 0);
1628 CtdlOutputMsg(msgid, MT_MIME, 0, 1, 0);
1634 * Client tells us its preferred message format(s)
1636 void cmd_msgp(char *cmdbuf)
1638 safestrncpy(CC->preferred_formats, cmdbuf,
1639 sizeof(CC->preferred_formats));
1640 cprintf("%d ok\n", CIT_OK);
1645 * Open a component of a MIME message as a download file
1647 void cmd_opna(char *cmdbuf)
1650 char desired_section[128];
1652 msgid = extract_long(cmdbuf, 0);
1653 extract_token(desired_section, cmdbuf, 1, '|', sizeof desired_section);
1654 safestrncpy(CC->download_desired_section, desired_section, sizeof CC->download_desired_section);
1655 CtdlOutputMsg(msgid, MT_DOWNLOAD, 0, 1, 1);
1660 * Save a message pointer into a specified room
1661 * (Returns 0 for success, nonzero for failure)
1662 * roomname may be NULL to use the current room
1664 int CtdlSaveMsgPointerInRoom(char *roomname, long msgid, int flags) {
1666 char hold_rm[ROOMNAMELEN];
1667 struct cdbdata *cdbfr;
1670 long highest_msg = 0L;
1671 struct CtdlMessage *msg = NULL;
1673 lprintf(CTDL_DEBUG, "CtdlSaveMsgPointerInRoom(%s, %ld, %d)\n",
1674 roomname, msgid, flags);
1676 strcpy(hold_rm, CC->room.QRname);
1678 /* We may need to check to see if this message is real */
1679 if ( (flags & SM_VERIFY_GOODNESS)
1680 || (flags & SM_DO_REPL_CHECK)
1682 msg = CtdlFetchMessage(msgid, 1);
1683 if (msg == NULL) return(ERROR + ILLEGAL_VALUE);
1686 /* Perform replication checks if necessary */
1687 if ( (flags & SM_DO_REPL_CHECK) && (msg != NULL) ) {
1689 if (getroom(&CC->room,
1690 ((roomname != NULL) ? roomname : CC->room.QRname) )
1692 lprintf(CTDL_ERR, "No such room <%s>\n", roomname);
1693 if (msg != NULL) CtdlFreeMessage(msg);
1694 return(ERROR + ROOM_NOT_FOUND);
1697 if (ReplicationChecks(msg) != 0) {
1698 getroom(&CC->room, hold_rm);
1699 if (msg != NULL) CtdlFreeMessage(msg);
1701 "Did replication, and newer exists\n");
1706 /* Now the regular stuff */
1707 if (lgetroom(&CC->room,
1708 ((roomname != NULL) ? roomname : CC->room.QRname) )
1710 lprintf(CTDL_ERR, "No such room <%s>\n", roomname);
1711 if (msg != NULL) CtdlFreeMessage(msg);
1712 return(ERROR + ROOM_NOT_FOUND);
1715 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long));
1716 if (cdbfr == NULL) {
1720 msglist = malloc(cdbfr->len);
1721 if (msglist == NULL)
1722 lprintf(CTDL_ALERT, "ERROR malloc msglist!\n");
1723 num_msgs = cdbfr->len / sizeof(long);
1724 memcpy(msglist, cdbfr->ptr, cdbfr->len);
1729 /* Make sure the message doesn't already exist in this room. It
1730 * is absolutely taboo to have more than one reference to the same
1731 * message in a room.
1733 if (num_msgs > 0) for (i=0; i<num_msgs; ++i) {
1734 if (msglist[i] == msgid) {
1735 lputroom(&CC->room); /* unlock the room */
1736 getroom(&CC->room, hold_rm);
1737 if (msg != NULL) CtdlFreeMessage(msg);
1739 return(ERROR + ALREADY_EXISTS);
1743 /* Now add the new message */
1745 msglist = realloc(msglist, (num_msgs * sizeof(long)));
1747 if (msglist == NULL) {
1748 lprintf(CTDL_ALERT, "ERROR: can't realloc message list!\n");
1750 msglist[num_msgs - 1] = msgid;
1752 /* Sort the message list, so all the msgid's are in order */
1753 num_msgs = sort_msglist(msglist, num_msgs);
1755 /* Determine the highest message number */
1756 highest_msg = msglist[num_msgs - 1];
1758 /* Write it back to disk. */
1759 cdb_store(CDB_MSGLISTS, &CC->room.QRnumber, (int)sizeof(long),
1760 msglist, (int)(num_msgs * sizeof(long)));
1762 /* Free up the memory we used. */
1765 /* Update the highest-message pointer and unlock the room. */
1766 CC->room.QRhighest = highest_msg;
1767 lputroom(&CC->room);
1768 getroom(&CC->room, hold_rm);
1770 /* Bump the reference count for this message. */
1771 if ((flags & SM_DONT_BUMP_REF)==0) {
1772 AdjRefCount(msgid, +1);
1775 /* Return success. */
1776 if (msg != NULL) CtdlFreeMessage(msg);
1783 * Message base operation to save a new message to the message store
1784 * (returns new message number)
1786 * This is the back end for CtdlSubmitMsg() and should not be directly
1787 * called by server-side modules.
1790 long send_message(struct CtdlMessage *msg) {
1798 /* Get a new message number */
1799 newmsgid = get_new_message_number();
1800 snprintf(msgidbuf, sizeof msgidbuf, "%ld@%s", newmsgid, config.c_fqdn);
1802 /* Generate an ID if we don't have one already */
1803 if (msg->cm_fields['I']==NULL) {
1804 msg->cm_fields['I'] = strdup(msgidbuf);
1807 /* If the message is big, set its body aside for storage elsewhere */
1808 if (msg->cm_fields['M'] != NULL) {
1809 if (strlen(msg->cm_fields['M']) > BIGMSG) {
1811 holdM = msg->cm_fields['M'];
1812 msg->cm_fields['M'] = NULL;
1816 /* Serialize our data structure for storage in the database */
1817 serialize_message(&smr, msg);
1820 msg->cm_fields['M'] = holdM;
1824 cprintf("%d Unable to serialize message\n",
1825 ERROR + INTERNAL_ERROR);
1829 /* Write our little bundle of joy into the message base */
1830 if (cdb_store(CDB_MSGMAIN, &newmsgid, (int)sizeof(long),
1831 smr.ser, smr.len) < 0) {
1832 lprintf(CTDL_ERR, "Can't store message\n");
1836 cdb_store(CDB_BIGMSGS,
1846 /* Free the memory we used for the serialized message */
1849 /* Return the *local* message ID to the caller
1850 * (even if we're storing an incoming network message)
1858 * Serialize a struct CtdlMessage into the format used on disk and network.
1860 * This function loads up a "struct ser_ret" (defined in server.h) which
1861 * contains the length of the serialized message and a pointer to the
1862 * serialized message in memory. THE LATTER MUST BE FREED BY THE CALLER.
1864 void serialize_message(struct ser_ret *ret, /* return values */
1865 struct CtdlMessage *msg) /* unserialized msg */
1869 static char *forder = FORDER;
1871 if (is_valid_message(msg) == 0) return; /* self check */
1874 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL)
1875 ret->len = ret->len +
1876 strlen(msg->cm_fields[(int)forder[i]]) + 2;
1878 lprintf(CTDL_DEBUG, "serialize_message() calling malloc(%ld)\n", (long)ret->len);
1879 ret->ser = malloc(ret->len);
1880 if (ret->ser == NULL) {
1886 ret->ser[1] = msg->cm_anon_type;
1887 ret->ser[2] = msg->cm_format_type;
1890 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL) {
1891 ret->ser[wlen++] = (char)forder[i];
1892 strcpy((char *)&ret->ser[wlen], msg->cm_fields[(int)forder[i]]);
1893 wlen = wlen + strlen(msg->cm_fields[(int)forder[i]]) + 1;
1895 if (ret->len != wlen) lprintf(CTDL_ERR, "ERROR: len=%ld wlen=%ld\n",
1896 (long)ret->len, (long)wlen);
1904 * Back end for the ReplicationChecks() function
1906 void check_repl(long msgnum, void *userdata) {
1907 lprintf(CTDL_DEBUG, "check_repl() replacing message %ld\n", msgnum);
1908 CtdlDeleteMessages(CC->room.QRname, msgnum, "");
1913 * Check to see if any messages already exist which carry the same Exclusive ID
1914 * as this one. If any are found, delete them.
1917 int ReplicationChecks(struct CtdlMessage *msg) {
1918 struct CtdlMessage *template;
1921 /* No exclusive id? Don't do anything. */
1922 if (msg->cm_fields['E'] == NULL) return 0;
1923 if (strlen(msg->cm_fields['E']) == 0) return 0;
1924 lprintf(CTDL_DEBUG, "Exclusive ID: <%s>\n", msg->cm_fields['E']);
1926 template = (struct CtdlMessage *) malloc(sizeof(struct CtdlMessage));
1927 memset(template, 0, sizeof(struct CtdlMessage));
1928 template->cm_fields['E'] = strdup(msg->cm_fields['E']);
1930 CtdlForEachMessage(MSGS_ALL, 0L, NULL, template, check_repl, NULL);
1932 CtdlFreeMessage(template);
1933 lprintf(CTDL_DEBUG, "ReplicationChecks() returning %d\n", abort_this);
1941 * Save a message to disk and submit it into the delivery system.
1943 long CtdlSubmitMsg(struct CtdlMessage *msg, /* message to save */
1944 struct recptypes *recps, /* recipients (if mail) */
1945 char *force /* force a particular room? */
1947 char submit_filename[128];
1948 char generated_timestamp[32];
1949 char hold_rm[ROOMNAMELEN];
1950 char actual_rm[ROOMNAMELEN];
1951 char force_room[ROOMNAMELEN];
1952 char content_type[SIZ]; /* We have to learn this */
1953 char recipient[SIZ];
1956 struct ctdluser userbuf;
1958 struct MetaData smi;
1959 FILE *network_fp = NULL;
1960 static int seqnum = 1;
1961 struct CtdlMessage *imsg = NULL;
1964 char *hold_R, *hold_D;
1966 lprintf(CTDL_DEBUG, "CtdlSubmitMsg() called\n");
1967 if (is_valid_message(msg) == 0) return(-1); /* self check */
1969 /* If this message has no timestamp, we take the liberty of
1970 * giving it one, right now.
1972 if (msg->cm_fields['T'] == NULL) {
1973 lprintf(CTDL_DEBUG, "Generating timestamp\n");
1974 snprintf(generated_timestamp, sizeof generated_timestamp, "%ld", (long)time(NULL));
1975 msg->cm_fields['T'] = strdup(generated_timestamp);
1978 /* If this message has no path, we generate one.
1980 if (msg->cm_fields['P'] == NULL) {
1981 lprintf(CTDL_DEBUG, "Generating path\n");
1982 if (msg->cm_fields['A'] != NULL) {
1983 msg->cm_fields['P'] = strdup(msg->cm_fields['A']);
1984 for (a=0; a<strlen(msg->cm_fields['P']); ++a) {
1985 if (isspace(msg->cm_fields['P'][a])) {
1986 msg->cm_fields['P'][a] = ' ';
1991 msg->cm_fields['P'] = strdup("unknown");
1995 if (force == NULL) {
1996 strcpy(force_room, "");
1999 strcpy(force_room, force);
2002 /* Learn about what's inside, because it's what's inside that counts */
2003 lprintf(CTDL_DEBUG, "Learning what's inside\n");
2004 if (msg->cm_fields['M'] == NULL) {
2005 lprintf(CTDL_ERR, "ERROR: attempt to save message with NULL body\n");
2009 switch (msg->cm_format_type) {
2011 strcpy(content_type, "text/x-citadel-variformat");
2014 strcpy(content_type, "text/plain");
2017 strcpy(content_type, "text/plain");
2018 mptr = bmstrstr(msg->cm_fields['M'],
2019 "Content-type: ", strncasecmp);
2021 safestrncpy(content_type, &mptr[14],
2022 sizeof content_type);
2023 for (a = 0; a < strlen(content_type); ++a) {
2024 if ((content_type[a] == ';')
2025 || (content_type[a] == ' ')
2026 || (content_type[a] == 13)
2027 || (content_type[a] == 10)) {
2028 content_type[a] = 0;
2034 /* Goto the correct room */
2035 lprintf(CTDL_DEBUG, "Selected room %s\n",
2036 (recps) ? CC->room.QRname : SENTITEMS);
2037 strcpy(hold_rm, CC->room.QRname);
2038 strcpy(actual_rm, CC->room.QRname);
2039 if (recps != NULL) {
2040 strcpy(actual_rm, SENTITEMS);
2043 /* If the user is a twit, move to the twit room for posting */
2044 lprintf(CTDL_DEBUG, "Handling twit stuff: %s\n",
2045 (CC->user.axlevel == 2) ? config.c_twitroom : "OK");
2047 if (CC->user.axlevel == 2) {
2048 strcpy(hold_rm, actual_rm);
2049 strcpy(actual_rm, config.c_twitroom);
2053 /* ...or if this message is destined for Aide> then go there. */
2054 if (strlen(force_room) > 0) {
2055 strcpy(actual_rm, force_room);
2058 lprintf(CTDL_DEBUG, "Final selection: %s\n", actual_rm);
2059 if (strcasecmp(actual_rm, CC->room.QRname)) {
2060 getroom(&CC->room, actual_rm);
2064 * If this message has no O (room) field, generate one.
2066 if (msg->cm_fields['O'] == NULL) {
2067 msg->cm_fields['O'] = strdup(CC->room.QRname);
2070 /* Perform "before save" hooks (aborting if any return nonzero) */
2071 lprintf(CTDL_DEBUG, "Performing before-save hooks\n");
2072 if (PerformMessageHooks(msg, EVT_BEFORESAVE) > 0) return(-3);
2074 /* If this message has an Exclusive ID, perform replication checks */
2075 lprintf(CTDL_DEBUG, "Performing replication checks\n");
2076 if (ReplicationChecks(msg) > 0) return(-4);
2078 /* Save it to disk */
2079 lprintf(CTDL_DEBUG, "Saving to disk\n");
2080 newmsgid = send_message(msg);
2081 if (newmsgid <= 0L) return(-5);
2083 /* Write a supplemental message info record. This doesn't have to
2084 * be a critical section because nobody else knows about this message
2087 lprintf(CTDL_DEBUG, "Creating MetaData record\n");
2088 memset(&smi, 0, sizeof(struct MetaData));
2089 smi.meta_msgnum = newmsgid;
2090 smi.meta_refcount = 0;
2091 safestrncpy(smi.meta_content_type, content_type,
2092 sizeof smi.meta_content_type);
2094 /* As part of the new metadata record, measure how
2095 * big this message will be when displayed as RFC822.
2096 * Both POP and IMAP use this, and it's best to just take the hit now
2097 * instead of having to potentially measure thousands of messages when
2098 * a mailbox is opened later.
2101 if (CC->redirect_buffer != NULL) {
2102 lprintf(CTDL_ALERT, "CC->redirect_buffer is not NULL during message submission!\n");
2105 CC->redirect_buffer = malloc(SIZ);
2106 CC->redirect_len = 0;
2107 CC->redirect_alloc = SIZ;
2108 CtdlOutputPreLoadedMsg(msg, 0L, MT_RFC822, HEADERS_ALL, 0, 1);
2109 smi.meta_rfc822_length = CC->redirect_len;
2110 free(CC->redirect_buffer);
2111 CC->redirect_buffer = NULL;
2112 CC->redirect_len = 0;
2113 CC->redirect_alloc = 0;
2117 /* Now figure out where to store the pointers */
2118 lprintf(CTDL_DEBUG, "Storing pointers\n");
2120 /* If this is being done by the networker delivering a private
2121 * message, we want to BYPASS saving the sender's copy (because there
2122 * is no local sender; it would otherwise go to the Trashcan).
2124 if ((!CC->internal_pgm) || (recps == NULL)) {
2125 if (CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 0) != 0) {
2126 lprintf(CTDL_ERR, "ERROR saving message pointer!\n");
2127 CtdlSaveMsgPointerInRoom(config.c_aideroom,
2132 /* For internet mail, drop a copy in the outbound queue room */
2134 if (recps->num_internet > 0) {
2135 CtdlSaveMsgPointerInRoom(SMTP_SPOOLOUT_ROOM, newmsgid, 0);
2138 /* If other rooms are specified, drop them there too. */
2140 if (recps->num_room > 0)
2141 for (i=0; i<num_tokens(recps->recp_room, '|'); ++i) {
2142 extract_token(recipient, recps->recp_room, i,
2143 '|', sizeof recipient);
2144 lprintf(CTDL_DEBUG, "Delivering to room <%s>\n", recipient);
2145 CtdlSaveMsgPointerInRoom(recipient, newmsgid, 0);
2148 /* Bump this user's messages posted counter. */
2149 lprintf(CTDL_DEBUG, "Updating user\n");
2150 lgetuser(&CC->user, CC->curr_user);
2151 CC->user.posted = CC->user.posted + 1;
2152 lputuser(&CC->user);
2154 /* If this is private, local mail, make a copy in the
2155 * recipient's mailbox and bump the reference count.
2158 if (recps->num_local > 0)
2159 for (i=0; i<num_tokens(recps->recp_local, '|'); ++i) {
2160 extract_token(recipient, recps->recp_local, i,
2161 '|', sizeof recipient);
2162 lprintf(CTDL_DEBUG, "Delivering private local mail to <%s>\n",
2164 if (getuser(&userbuf, recipient) == 0) {
2165 MailboxName(actual_rm, sizeof actual_rm,
2166 &userbuf, MAILROOM);
2167 CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 0);
2168 BumpNewMailCounter(userbuf.usernum);
2171 lprintf(CTDL_DEBUG, "No user <%s>\n", recipient);
2172 CtdlSaveMsgPointerInRoom(config.c_aideroom,
2177 /* Perform "after save" hooks */
2178 lprintf(CTDL_DEBUG, "Performing after-save hooks\n");
2179 PerformMessageHooks(msg, EVT_AFTERSAVE);
2181 /* For IGnet mail, we have to save a new copy into the spooler for
2182 * each recipient, with the R and D fields set to the recipient and
2183 * destination-node. This has two ugly side effects: all other
2184 * recipients end up being unlisted in this recipient's copy of the
2185 * message, and it has to deliver multiple messages to the same
2186 * node. We'll revisit this again in a year or so when everyone has
2187 * a network spool receiver that can handle the new style messages.
2190 if (recps->num_ignet > 0)
2191 for (i=0; i<num_tokens(recps->recp_ignet, '|'); ++i) {
2192 extract_token(recipient, recps->recp_ignet, i,
2193 '|', sizeof recipient);
2195 hold_R = msg->cm_fields['R'];
2196 hold_D = msg->cm_fields['D'];
2197 msg->cm_fields['R'] = malloc(SIZ);
2198 msg->cm_fields['D'] = malloc(128);
2199 extract_token(msg->cm_fields['R'], recipient, 0, '@', SIZ);
2200 extract_token(msg->cm_fields['D'], recipient, 1, '@', 128);
2202 serialize_message(&smr, msg);
2204 snprintf(submit_filename, sizeof submit_filename,
2205 "./network/spoolin/netmail.%04lx.%04x.%04x",
2206 (long) getpid(), CC->cs_pid, ++seqnum);
2207 network_fp = fopen(submit_filename, "wb+");
2208 if (network_fp != NULL) {
2209 fwrite(smr.ser, smr.len, 1, network_fp);
2215 free(msg->cm_fields['R']);
2216 free(msg->cm_fields['D']);
2217 msg->cm_fields['R'] = hold_R;
2218 msg->cm_fields['D'] = hold_D;
2221 /* Go back to the room we started from */
2222 lprintf(CTDL_DEBUG, "Returning to original room %s\n", hold_rm);
2223 if (strcasecmp(hold_rm, CC->room.QRname))
2224 getroom(&CC->room, hold_rm);
2226 /* For internet mail, generate delivery instructions.
2227 * Yes, this is recursive. Deal with it. Infinite recursion does
2228 * not happen because the delivery instructions message does not
2229 * contain a recipient.
2232 if (recps->num_internet > 0) {
2233 lprintf(CTDL_DEBUG, "Generating delivery instructions\n");
2234 instr = malloc(SIZ * 2);
2235 snprintf(instr, SIZ * 2,
2236 "Content-type: %s\n\nmsgid|%ld\nsubmitted|%ld\n"
2238 SPOOLMIME, newmsgid, (long)time(NULL),
2239 msg->cm_fields['A'], msg->cm_fields['N']
2242 for (i=0; i<num_tokens(recps->recp_internet, '|'); ++i) {
2243 size_t tmp = strlen(instr);
2244 extract_token(recipient, recps->recp_internet, i, '|', sizeof recipient);
2245 snprintf(&instr[tmp], SIZ * 2 - tmp,
2246 "remote|%s|0||\n", recipient);
2249 imsg = malloc(sizeof(struct CtdlMessage));
2250 memset(imsg, 0, sizeof(struct CtdlMessage));
2251 imsg->cm_magic = CTDLMESSAGE_MAGIC;
2252 imsg->cm_anon_type = MES_NORMAL;
2253 imsg->cm_format_type = FMT_RFC822;
2254 imsg->cm_fields['A'] = strdup("Citadel");
2255 imsg->cm_fields['M'] = instr;
2256 CtdlSubmitMsg(imsg, NULL, SMTP_SPOOLOUT_ROOM);
2257 CtdlFreeMessage(imsg);
2266 * Convenience function for generating small administrative messages.
2268 void quickie_message(char *from, char *to, char *room, char *text,
2269 int format_type, char *subject)
2271 struct CtdlMessage *msg;
2272 struct recptypes *recp = NULL;
2274 msg = malloc(sizeof(struct CtdlMessage));
2275 memset(msg, 0, sizeof(struct CtdlMessage));
2276 msg->cm_magic = CTDLMESSAGE_MAGIC;
2277 msg->cm_anon_type = MES_NORMAL;
2278 msg->cm_format_type = format_type;
2279 msg->cm_fields['A'] = strdup(from);
2280 if (room != NULL) msg->cm_fields['O'] = strdup(room);
2281 msg->cm_fields['N'] = strdup(NODENAME);
2283 msg->cm_fields['R'] = strdup(to);
2284 recp = validate_recipients(to);
2286 if (subject != NULL) {
2287 msg->cm_fields['U'] = strdup(subject);
2289 msg->cm_fields['M'] = strdup(text);
2291 CtdlSubmitMsg(msg, recp, room);
2292 CtdlFreeMessage(msg);
2293 if (recp != NULL) free(recp);
2299 * Back end function used by CtdlMakeMessage() and similar functions
2301 char *CtdlReadMessageBody(char *terminator, /* token signalling EOT */
2302 size_t maxlen, /* maximum message length */
2303 char *exist, /* if non-null, append to it;
2304 exist is ALWAYS freed */
2305 int crlf /* CRLF newlines instead of LF */
2309 size_t message_len = 0;
2310 size_t buffer_len = 0;
2316 if (exist == NULL) {
2323 message_len = strlen(exist);
2324 buffer_len = message_len + 4096;
2325 m = realloc(exist, buffer_len);
2332 /* flush the input if we have nowhere to store it */
2337 /* read in the lines of message text one by one */
2339 if (client_getln(buf, (sizeof buf - 3)) < 1) finished = 1;
2340 if (!strcmp(buf, terminator)) finished = 1;
2342 strcat(buf, "\r\n");
2348 if ( (!flushing) && (!finished) ) {
2349 /* Measure the line */
2350 linelen = strlen(buf);
2352 /* augment the buffer if we have to */
2353 if ((message_len + linelen) >= buffer_len) {
2354 ptr = realloc(m, (buffer_len * 2) );
2355 if (ptr == NULL) { /* flush if can't allocate */
2358 buffer_len = (buffer_len * 2);
2360 lprintf(CTDL_DEBUG, "buffer_len is now %ld\n", (long)buffer_len);
2364 /* Add the new line to the buffer. NOTE: this loop must avoid
2365 * using functions like strcat() and strlen() because they
2366 * traverse the entire buffer upon every call, and doing that
2367 * for a multi-megabyte message slows it down beyond usability.
2369 strcpy(&m[message_len], buf);
2370 message_len += linelen;
2373 /* if we've hit the max msg length, flush the rest */
2374 if (message_len >= maxlen) flushing = 1;
2376 } while (!finished);
2384 * Build a binary message to be saved on disk.
2385 * (NOTE: if you supply 'preformatted_text', the buffer you give it
2386 * will become part of the message. This means you are no longer
2387 * responsible for managing that memory -- it will be freed along with
2388 * the rest of the fields when CtdlFreeMessage() is called.)
2391 struct CtdlMessage *CtdlMakeMessage(
2392 struct ctdluser *author, /* author's user structure */
2393 char *recipient, /* NULL if it's not mail */
2394 char *room, /* room where it's going */
2395 int type, /* see MES_ types in header file */
2396 int format_type, /* variformat, plain text, MIME... */
2397 char *fake_name, /* who we're masquerading as */
2398 char *subject, /* Subject (optional) */
2399 char *preformatted_text /* ...or NULL to read text from client */
2401 char dest_node[SIZ];
2403 struct CtdlMessage *msg;
2405 msg = malloc(sizeof(struct CtdlMessage));
2406 memset(msg, 0, sizeof(struct CtdlMessage));
2407 msg->cm_magic = CTDLMESSAGE_MAGIC;
2408 msg->cm_anon_type = type;
2409 msg->cm_format_type = format_type;
2411 /* Don't confuse the poor folks if it's not routed mail. */
2412 strcpy(dest_node, "");
2416 snprintf(buf, sizeof buf, "cit%ld", author->usernum); /* Path */
2417 msg->cm_fields['P'] = strdup(buf);
2419 snprintf(buf, sizeof buf, "%ld", (long)time(NULL)); /* timestamp */
2420 msg->cm_fields['T'] = strdup(buf);
2422 if (fake_name[0]) /* author */
2423 msg->cm_fields['A'] = strdup(fake_name);
2425 msg->cm_fields['A'] = strdup(author->fullname);
2427 if (CC->room.QRflags & QR_MAILBOX) { /* room */
2428 msg->cm_fields['O'] = strdup(&CC->room.QRname[11]);
2431 msg->cm_fields['O'] = strdup(CC->room.QRname);
2434 msg->cm_fields['N'] = strdup(NODENAME); /* nodename */
2435 msg->cm_fields['H'] = strdup(HUMANNODE); /* hnodename */
2437 if (recipient[0] != 0) {
2438 msg->cm_fields['R'] = strdup(recipient);
2440 if (dest_node[0] != 0) {
2441 msg->cm_fields['D'] = strdup(dest_node);
2444 if ( (author == &CC->user) && (strlen(CC->cs_inet_email) > 0) ) {
2445 msg->cm_fields['F'] = strdup(CC->cs_inet_email);
2448 if (subject != NULL) {
2450 if (strlen(subject) > 0) {
2451 msg->cm_fields['U'] = strdup(subject);
2455 if (preformatted_text != NULL) {
2456 msg->cm_fields['M'] = preformatted_text;
2459 msg->cm_fields['M'] = CtdlReadMessageBody("000",
2460 config.c_maxmsglen, NULL, 0);
2468 * Check to see whether we have permission to post a message in the current
2469 * room. Returns a *CITADEL ERROR CODE* and puts a message in errmsgbuf, or
2470 * returns 0 on success.
2472 int CtdlDoIHavePermissionToPostInThisRoom(char *errmsgbuf, size_t n) {
2474 if (!(CC->logged_in)) {
2475 snprintf(errmsgbuf, n, "Not logged in.");
2476 return (ERROR + NOT_LOGGED_IN);
2479 if ((CC->user.axlevel < 2)
2480 && ((CC->room.QRflags & QR_MAILBOX) == 0)) {
2481 snprintf(errmsgbuf, n, "Need to be validated to enter "
2482 "(except in %s> to sysop)", MAILROOM);
2483 return (ERROR + HIGHER_ACCESS_REQUIRED);
2486 if ((CC->user.axlevel < 4)
2487 && (CC->room.QRflags & QR_NETWORK)) {
2488 snprintf(errmsgbuf, n, "Need net privileges to enter here.");
2489 return (ERROR + HIGHER_ACCESS_REQUIRED);
2492 if ((CC->user.axlevel < 6)
2493 && (CC->room.QRflags & QR_READONLY)) {
2494 snprintf(errmsgbuf, n, "Sorry, this is a read-only room.");
2495 return (ERROR + HIGHER_ACCESS_REQUIRED);
2498 strcpy(errmsgbuf, "Ok");
2504 * Check to see if the specified user has Internet mail permission
2505 * (returns nonzero if permission is granted)
2507 int CtdlCheckInternetMailPermission(struct ctdluser *who) {
2509 /* Do not allow twits to send Internet mail */
2510 if (who->axlevel <= 2) return(0);
2512 /* Globally enabled? */
2513 if (config.c_restrict == 0) return(1);
2515 /* User flagged ok? */
2516 if (who->flags & US_INTERNET) return(2);
2518 /* Aide level access? */
2519 if (who->axlevel >= 6) return(3);
2521 /* No mail for you! */
2528 * Validate recipients, count delivery types and errors, and handle aliasing
2529 * FIXME check for dupes!!!!!
2531 struct recptypes *validate_recipients(char *recipients) {
2532 struct recptypes *ret;
2533 char this_recp[SIZ];
2534 char this_recp_cooked[SIZ];
2540 struct ctdluser tempUS;
2541 struct ctdlroom tempQR;
2544 ret = (struct recptypes *) malloc(sizeof(struct recptypes));
2545 if (ret == NULL) return(NULL);
2546 memset(ret, 0, sizeof(struct recptypes));
2549 ret->num_internet = 0;
2554 if (recipients == NULL) {
2557 else if (strlen(recipients) == 0) {
2561 /* Change all valid separator characters to commas */
2562 for (i=0; i<strlen(recipients); ++i) {
2563 if ((recipients[i] == ';') || (recipients[i] == '|')) {
2564 recipients[i] = ',';
2569 num_recps = num_tokens(recipients, ',');
2572 if (num_recps > 0) for (i=0; i<num_recps; ++i) {
2573 extract_token(this_recp, recipients, i, ',', sizeof this_recp);
2575 lprintf(CTDL_DEBUG, "Evaluating recipient #%d <%s>\n", i, this_recp);
2576 mailtype = alias(this_recp);
2577 mailtype = alias(this_recp);
2578 mailtype = alias(this_recp);
2579 for (j=0; j<=strlen(this_recp); ++j) {
2580 if (this_recp[j]=='_') {
2581 this_recp_cooked[j] = ' ';
2584 this_recp_cooked[j] = this_recp[j];
2590 if (!strcasecmp(this_recp, "sysop")) {
2592 strcpy(this_recp, config.c_aideroom);
2593 if (strlen(ret->recp_room) > 0) {
2594 strcat(ret->recp_room, "|");
2596 strcat(ret->recp_room, this_recp);
2598 else if (getuser(&tempUS, this_recp) == 0) {
2600 strcpy(this_recp, tempUS.fullname);
2601 if (strlen(ret->recp_local) > 0) {
2602 strcat(ret->recp_local, "|");
2604 strcat(ret->recp_local, this_recp);
2606 else if (getuser(&tempUS, this_recp_cooked) == 0) {
2608 strcpy(this_recp, tempUS.fullname);
2609 if (strlen(ret->recp_local) > 0) {
2610 strcat(ret->recp_local, "|");
2612 strcat(ret->recp_local, this_recp);
2614 else if ( (!strncasecmp(this_recp, "room_", 5))
2615 && (!getroom(&tempQR, &this_recp_cooked[5])) ) {
2617 if (strlen(ret->recp_room) > 0) {
2618 strcat(ret->recp_room, "|");
2620 strcat(ret->recp_room, &this_recp_cooked[5]);
2628 /* Yes, you're reading this correctly: if the target
2629 * domain points back to the local system or an attached
2630 * Citadel directory, the address is invalid. That's
2631 * because if the address were valid, we would have
2632 * already translated it to a local address by now.
2634 if (IsDirectory(this_recp)) {
2639 ++ret->num_internet;
2640 if (strlen(ret->recp_internet) > 0) {
2641 strcat(ret->recp_internet, "|");
2643 strcat(ret->recp_internet, this_recp);
2648 if (strlen(ret->recp_ignet) > 0) {
2649 strcat(ret->recp_ignet, "|");
2651 strcat(ret->recp_ignet, this_recp);
2659 if (strlen(ret->errormsg) == 0) {
2660 snprintf(append, sizeof append,
2661 "Invalid recipient: %s",
2665 snprintf(append, sizeof append,
2668 if ( (strlen(ret->errormsg) + strlen(append)) < SIZ) {
2669 strcat(ret->errormsg, append);
2673 if (strlen(ret->display_recp) == 0) {
2674 strcpy(append, this_recp);
2677 snprintf(append, sizeof append, ", %s",
2680 if ( (strlen(ret->display_recp)+strlen(append)) < SIZ) {
2681 strcat(ret->display_recp, append);
2686 if ((ret->num_local + ret->num_internet + ret->num_ignet +
2687 ret->num_room + ret->num_error) == 0) {
2689 strcpy(ret->errormsg, "No recipients specified.");
2692 lprintf(CTDL_DEBUG, "validate_recipients()\n");
2693 lprintf(CTDL_DEBUG, " local: %d <%s>\n", ret->num_local, ret->recp_local);
2694 lprintf(CTDL_DEBUG, " room: %d <%s>\n", ret->num_room, ret->recp_room);
2695 lprintf(CTDL_DEBUG, " inet: %d <%s>\n", ret->num_internet, ret->recp_internet);
2696 lprintf(CTDL_DEBUG, " ignet: %d <%s>\n", ret->num_ignet, ret->recp_ignet);
2697 lprintf(CTDL_DEBUG, " error: %d <%s>\n", ret->num_error, ret->errormsg);
2705 * message entry - mode 0 (normal)
2707 void cmd_ent0(char *entargs)
2711 char masquerade_as[SIZ];
2713 int format_type = 0;
2714 char newusername[SIZ];
2715 struct CtdlMessage *msg;
2719 struct recptypes *valid = NULL;
2726 post = extract_int(entargs, 0);
2727 extract_token(recp, entargs, 1, '|', sizeof recp);
2728 anon_flag = extract_int(entargs, 2);
2729 format_type = extract_int(entargs, 3);
2730 extract_token(subject, entargs, 4, '|', sizeof subject);
2731 do_confirm = extract_int(entargs, 6);
2733 /* first check to make sure the request is valid. */
2735 err = CtdlDoIHavePermissionToPostInThisRoom(errmsg, sizeof errmsg);
2737 cprintf("%d %s\n", err, errmsg);
2741 /* Check some other permission type things. */
2744 if (CC->user.axlevel < 6) {
2745 cprintf("%d You don't have permission to masquerade.\n",
2746 ERROR + HIGHER_ACCESS_REQUIRED);
2749 extract_token(newusername, entargs, 5, '|', sizeof newusername);
2750 memset(CC->fake_postname, 0, sizeof(CC->fake_postname) );
2751 safestrncpy(CC->fake_postname, newusername,
2752 sizeof(CC->fake_postname) );
2753 cprintf("%d ok\n", CIT_OK);
2756 CC->cs_flags |= CS_POSTING;
2758 /* In the Mail> room we have to behave a little differently --
2759 * make sure the user has specified at least one recipient. Then
2760 * validate the recipient(s).
2762 if ( (CC->room.QRflags & QR_MAILBOX)
2763 && (!strcasecmp(&CC->room.QRname[11], MAILROOM)) ) {
2765 if (CC->user.axlevel < 2) {
2766 strcpy(recp, "sysop");
2769 valid = validate_recipients(recp);
2770 if (valid->num_error > 0) {
2772 ERROR + NO_SUCH_USER, valid->errormsg);
2776 if (valid->num_internet > 0) {
2777 if (CtdlCheckInternetMailPermission(&CC->user)==0) {
2778 cprintf("%d You do not have permission "
2779 "to send Internet mail.\n",
2780 ERROR + HIGHER_ACCESS_REQUIRED);
2786 if ( ( (valid->num_internet + valid->num_ignet) > 0)
2787 && (CC->user.axlevel < 4) ) {
2788 cprintf("%d Higher access required for network mail.\n",
2789 ERROR + HIGHER_ACCESS_REQUIRED);
2794 if ((RESTRICT_INTERNET == 1) && (valid->num_internet > 0)
2795 && ((CC->user.flags & US_INTERNET) == 0)
2796 && (!CC->internal_pgm)) {
2797 cprintf("%d You don't have access to Internet mail.\n",
2798 ERROR + HIGHER_ACCESS_REQUIRED);
2805 /* Is this a room which has anonymous-only or anonymous-option? */
2806 anonymous = MES_NORMAL;
2807 if (CC->room.QRflags & QR_ANONONLY) {
2808 anonymous = MES_ANONONLY;
2810 if (CC->room.QRflags & QR_ANONOPT) {
2811 if (anon_flag == 1) { /* only if the user requested it */
2812 anonymous = MES_ANONOPT;
2816 if ((CC->room.QRflags & QR_MAILBOX) == 0) {
2820 /* If we're only checking the validity of the request, return
2821 * success without creating the message.
2824 cprintf("%d %s\n", CIT_OK,
2825 ((valid != NULL) ? valid->display_recp : "") );
2830 /* Handle author masquerading */
2831 if (CC->fake_postname[0]) {
2832 strcpy(masquerade_as, CC->fake_postname);
2834 else if (CC->fake_username[0]) {
2835 strcpy(masquerade_as, CC->fake_username);
2838 strcpy(masquerade_as, "");
2841 /* Read in the message from the client. */
2843 cprintf("%d send message\n", START_CHAT_MODE);
2845 cprintf("%d send message\n", SEND_LISTING);
2847 msg = CtdlMakeMessage(&CC->user, recp,
2848 CC->room.QRname, anonymous, format_type,
2849 masquerade_as, subject, NULL);
2852 msgnum = CtdlSubmitMsg(msg, valid, "");
2855 cprintf("%ld\n", msgnum);
2857 cprintf("Message accepted.\n");
2860 cprintf("Internal error.\n");
2862 if (msg->cm_fields['E'] != NULL) {
2863 cprintf("%s\n", msg->cm_fields['E']);
2870 CtdlFreeMessage(msg);
2872 CC->fake_postname[0] = '\0';
2880 * API function to delete messages which match a set of criteria
2881 * (returns the actual number of messages deleted)
2883 int CtdlDeleteMessages(char *room_name, /* which room */
2884 long dmsgnum, /* or "0" for any */
2885 char *content_type /* or "" for any */
2889 struct ctdlroom qrbuf;
2890 struct cdbdata *cdbfr;
2891 long *msglist = NULL;
2892 long *dellist = NULL;
2895 int num_deleted = 0;
2897 struct MetaData smi;
2899 lprintf(CTDL_DEBUG, "CtdlDeleteMessages(%s, %ld, %s)\n",
2900 room_name, dmsgnum, content_type);
2902 /* get room record, obtaining a lock... */
2903 if (lgetroom(&qrbuf, room_name) != 0) {
2904 lprintf(CTDL_ERR, "CtdlDeleteMessages(): Room <%s> not found\n",
2906 return (0); /* room not found */
2908 cdbfr = cdb_fetch(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long));
2910 if (cdbfr != NULL) {
2911 msglist = malloc(cdbfr->len);
2912 dellist = malloc(cdbfr->len);
2913 memcpy(msglist, cdbfr->ptr, cdbfr->len);
2914 num_msgs = cdbfr->len / sizeof(long);
2918 for (i = 0; i < num_msgs; ++i) {
2921 /* Set/clear a bit for each criterion */
2923 if ((dmsgnum == 0L) || (msglist[i] == dmsgnum)) {
2924 delete_this |= 0x01;
2926 if (strlen(content_type) == 0) {
2927 delete_this |= 0x02;
2929 GetMetaData(&smi, msglist[i]);
2930 if (!strcasecmp(smi.meta_content_type,
2932 delete_this |= 0x02;
2936 /* Delete message only if all bits are set */
2937 if (delete_this == 0x03) {
2938 dellist[num_deleted++] = msglist[i];
2943 num_msgs = sort_msglist(msglist, num_msgs);
2944 cdb_store(CDB_MSGLISTS, &qrbuf.QRnumber, (int)sizeof(long),
2945 msglist, (int)(num_msgs * sizeof(long)));
2947 qrbuf.QRhighest = msglist[num_msgs - 1];
2951 /* Go through the messages we pulled out of the index, and decrement
2952 * their reference counts by 1. If this is the only room the message
2953 * was in, the reference count will reach zero and the message will
2954 * automatically be deleted from the database. We do this in a
2955 * separate pass because there might be plug-in hooks getting called,
2956 * and we don't want that happening during an S_ROOMS critical
2959 if (num_deleted) for (i=0; i<num_deleted; ++i) {
2960 PerformDeleteHooks(qrbuf.QRname, dellist[i]);
2961 AdjRefCount(dellist[i], -1);
2964 /* Now free the memory we used, and go away. */
2965 if (msglist != NULL) free(msglist);
2966 if (dellist != NULL) free(dellist);
2967 lprintf(CTDL_DEBUG, "%d message(s) deleted.\n", num_deleted);
2968 return (num_deleted);
2974 * Check whether the current user has permission to delete messages from
2975 * the current room (returns 1 for yes, 0 for no)
2977 int CtdlDoIHavePermissionToDeleteMessagesFromThisRoom(void) {
2978 getuser(&CC->user, CC->curr_user);
2979 if ((CC->user.axlevel < 6)
2980 && (CC->user.usernum != CC->room.QRroomaide)
2981 && ((CC->room.QRflags & QR_MAILBOX) == 0)
2982 && (!(CC->internal_pgm))) {
2991 * Delete message from current room
2993 void cmd_dele(char *delstr)
2998 if (CtdlDoIHavePermissionToDeleteMessagesFromThisRoom() == 0) {
2999 cprintf("%d Higher access required.\n",
3000 ERROR + HIGHER_ACCESS_REQUIRED);
3003 delnum = extract_long(delstr, 0);
3005 num_deleted = CtdlDeleteMessages(CC->room.QRname, delnum, "");
3008 cprintf("%d %d message%s deleted.\n", CIT_OK,
3009 num_deleted, ((num_deleted != 1) ? "s" : ""));
3011 cprintf("%d Message %ld not found.\n", ERROR + MESSAGE_NOT_FOUND, delnum);
3017 * Back end API function for moves and deletes
3019 int CtdlCopyMsgToRoom(long msgnum, char *dest) {
3022 err = CtdlSaveMsgPointerInRoom(dest, msgnum,
3023 (SM_VERIFY_GOODNESS | SM_DO_REPL_CHECK) );
3024 if (err != 0) return(err);
3032 * move or copy a message to another room
3034 void cmd_move(char *args)
3037 char targ[ROOMNAMELEN];
3038 struct ctdlroom qtemp;
3044 num = extract_long(args, 0);
3045 extract_token(targ, args, 1, '|', sizeof targ);
3046 targ[ROOMNAMELEN - 1] = 0;
3047 is_copy = extract_int(args, 2);
3049 if (getroom(&qtemp, targ) != 0) {
3050 cprintf("%d '%s' does not exist.\n",
3051 ERROR + ROOM_NOT_FOUND, targ);
3055 getuser(&CC->user, CC->curr_user);
3056 CtdlRoomAccess(&qtemp, &CC->user, &ra, NULL);
3058 /* Check for permission to perform this operation.
3059 * Remember: "CC->room" is source, "qtemp" is target.
3063 /* Aides can move/copy */
3064 if (CC->user.axlevel >= 6) permit = 1;
3066 /* Room aides can move/copy */
3067 if (CC->user.usernum == CC->room.QRroomaide) permit = 1;
3069 /* Permit move/copy from personal rooms */
3070 if ((CC->room.QRflags & QR_MAILBOX)
3071 && (qtemp.QRflags & QR_MAILBOX)) permit = 1;
3073 /* Permit only copy from public to personal room */
3075 && (!(CC->room.QRflags & QR_MAILBOX))
3076 && (qtemp.QRflags & QR_MAILBOX)) permit = 1;
3078 /* User must have access to target room */
3079 if (!(ra & UA_KNOWN)) permit = 0;
3082 cprintf("%d Higher access required.\n",
3083 ERROR + HIGHER_ACCESS_REQUIRED);
3087 err = CtdlCopyMsgToRoom(num, targ);
3089 cprintf("%d Cannot store message in %s: error %d\n",
3094 /* Now delete the message from the source room,
3095 * if this is a 'move' rather than a 'copy' operation.
3098 CtdlDeleteMessages(CC->room.QRname, num, "");
3101 cprintf("%d Message %s.\n", CIT_OK, (is_copy ? "copied" : "moved") );
3107 * GetMetaData() - Get the supplementary record for a message
3109 void GetMetaData(struct MetaData *smibuf, long msgnum)
3112 struct cdbdata *cdbsmi;
3115 memset(smibuf, 0, sizeof(struct MetaData));
3116 smibuf->meta_msgnum = msgnum;
3117 smibuf->meta_refcount = 1; /* Default reference count is 1 */
3119 /* Use the negative of the message number for its supp record index */
3120 TheIndex = (0L - msgnum);
3122 cdbsmi = cdb_fetch(CDB_MSGMAIN, &TheIndex, sizeof(long));
3123 if (cdbsmi == NULL) {
3124 return; /* record not found; go with defaults */
3126 memcpy(smibuf, cdbsmi->ptr,
3127 ((cdbsmi->len > sizeof(struct MetaData)) ?
3128 sizeof(struct MetaData) : cdbsmi->len));
3135 * PutMetaData() - (re)write supplementary record for a message
3137 void PutMetaData(struct MetaData *smibuf)
3141 /* Use the negative of the message number for the metadata db index */
3142 TheIndex = (0L - smibuf->meta_msgnum);
3144 lprintf(CTDL_DEBUG, "PutMetaData(%ld) - ref count is %d\n",
3145 smibuf->meta_msgnum, smibuf->meta_refcount);
3147 cdb_store(CDB_MSGMAIN,
3148 &TheIndex, (int)sizeof(long),
3149 smibuf, (int)sizeof(struct MetaData));
3154 * AdjRefCount - change the reference count for a message;
3155 * delete the message if it reaches zero
3157 void AdjRefCount(long msgnum, int incr)
3160 struct MetaData smi;
3163 /* This is a *tight* critical section; please keep it that way, as
3164 * it may get called while nested in other critical sections.
3165 * Complicating this any further will surely cause deadlock!
3167 begin_critical_section(S_SUPPMSGMAIN);
3168 GetMetaData(&smi, msgnum);
3169 lprintf(CTDL_DEBUG, "Ref count for message <%ld> before write is <%d>\n",
3170 msgnum, smi.meta_refcount);
3171 smi.meta_refcount += incr;
3173 end_critical_section(S_SUPPMSGMAIN);
3174 lprintf(CTDL_DEBUG, "Ref count for message <%ld> after write is <%d>\n",
3175 msgnum, smi.meta_refcount);
3177 /* If the reference count is now zero, delete the message
3178 * (and its supplementary record as well).
3180 if (smi.meta_refcount == 0) {
3181 lprintf(CTDL_DEBUG, "Deleting message <%ld>\n", msgnum);
3183 cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long));
3184 cdb_delete(CDB_BIGMSGS, &delnum, (int)sizeof(long));
3186 /* We have to delete the metadata record too! */
3187 delnum = (0L - msgnum);
3188 cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long));
3193 * Write a generic object to this room
3195 * Note: this could be much more efficient. Right now we use two temporary
3196 * files, and still pull the message into memory as with all others.
3198 void CtdlWriteObject(char *req_room, /* Room to stuff it in */
3199 char *content_type, /* MIME type of this object */
3200 char *tempfilename, /* Where to fetch it from */
3201 struct ctdluser *is_mailbox, /* Mailbox room? */
3202 int is_binary, /* Is encoding necessary? */
3203 int is_unique, /* Del others of this type? */
3204 unsigned int flags /* Internal save flags */
3209 struct ctdlroom qrbuf;
3210 char roomname[ROOMNAMELEN];
3211 struct CtdlMessage *msg;
3213 char *raw_message = NULL;
3214 char *encoded_message = NULL;
3215 off_t raw_length = 0;
3217 if (is_mailbox != NULL)
3218 MailboxName(roomname, sizeof roomname, is_mailbox, req_room);
3220 safestrncpy(roomname, req_room, sizeof(roomname));
3221 lprintf(CTDL_DEBUG, "CtdlWriteObject() to <%s> (flags=%d)\n", roomname, flags);
3224 fp = fopen(tempfilename, "rb");
3226 lprintf(CTDL_CRIT, "Cannot open %s: %s\n",
3227 tempfilename, strerror(errno));
3230 fseek(fp, 0L, SEEK_END);
3231 raw_length = ftell(fp);
3233 lprintf(CTDL_DEBUG, "Raw length is %ld\n", (long)raw_length);
3235 raw_message = malloc((size_t)raw_length + 2);
3236 fread(raw_message, (size_t)raw_length, 1, fp);
3240 encoded_message = malloc((size_t)
3241 (((raw_length * 134) / 100) + 4096 ) );
3244 encoded_message = malloc((size_t)(raw_length + 4096));
3247 sprintf(encoded_message, "Content-type: %s\n", content_type);
3250 sprintf(&encoded_message[strlen(encoded_message)],
3251 "Content-transfer-encoding: base64\n\n"
3255 sprintf(&encoded_message[strlen(encoded_message)],
3256 "Content-transfer-encoding: 7bit\n\n"
3262 &encoded_message[strlen(encoded_message)],
3268 raw_message[raw_length] = 0;
3270 &encoded_message[strlen(encoded_message)],
3278 lprintf(CTDL_DEBUG, "Allocating\n");
3279 msg = malloc(sizeof(struct CtdlMessage));
3280 memset(msg, 0, sizeof(struct CtdlMessage));
3281 msg->cm_magic = CTDLMESSAGE_MAGIC;
3282 msg->cm_anon_type = MES_NORMAL;
3283 msg->cm_format_type = 4;
3284 msg->cm_fields['A'] = strdup(CC->user.fullname);
3285 msg->cm_fields['O'] = strdup(req_room);
3286 msg->cm_fields['N'] = strdup(config.c_nodename);
3287 msg->cm_fields['H'] = strdup(config.c_humannode);
3288 msg->cm_flags = flags;
3290 msg->cm_fields['M'] = encoded_message;
3292 /* Create the requested room if we have to. */
3293 if (getroom(&qrbuf, roomname) != 0) {
3294 create_room(roomname,
3295 ( (is_mailbox != NULL) ? 5 : 3 ),
3296 "", 0, 1, 0, VIEW_BBS);
3298 /* If the caller specified this object as unique, delete all
3299 * other objects of this type that are currently in the room.
3302 lprintf(CTDL_DEBUG, "Deleted %d other msgs of this type\n",
3303 CtdlDeleteMessages(roomname, 0L, content_type));
3305 /* Now write the data */
3306 CtdlSubmitMsg(msg, NULL, roomname);
3307 CtdlFreeMessage(msg);
3315 void CtdlGetSysConfigBackend(long msgnum, void *userdata) {
3316 config_msgnum = msgnum;
3320 char *CtdlGetSysConfig(char *sysconfname) {
3321 char hold_rm[ROOMNAMELEN];
3324 struct CtdlMessage *msg;
3327 strcpy(hold_rm, CC->room.QRname);
3328 if (getroom(&CC->room, SYSCONFIGROOM) != 0) {
3329 getroom(&CC->room, hold_rm);
3334 /* We want the last (and probably only) config in this room */
3335 begin_critical_section(S_CONFIG);
3336 config_msgnum = (-1L);
3337 CtdlForEachMessage(MSGS_LAST, 1, sysconfname, NULL,
3338 CtdlGetSysConfigBackend, NULL);
3339 msgnum = config_msgnum;
3340 end_critical_section(S_CONFIG);
3346 msg = CtdlFetchMessage(msgnum, 1);
3348 conf = strdup(msg->cm_fields['M']);
3349 CtdlFreeMessage(msg);
3356 getroom(&CC->room, hold_rm);
3358 if (conf != NULL) do {
3359 extract_token(buf, conf, 0, '\n', sizeof buf);
3360 strcpy(conf, &conf[strlen(buf)+1]);
3361 } while ( (strlen(conf)>0) && (strlen(buf)>0) );
3366 void CtdlPutSysConfig(char *sysconfname, char *sysconfdata) {
3367 char temp[PATH_MAX];
3370 strcpy(temp, tmpnam(NULL));
3372 fp = fopen(temp, "w");
3373 if (fp == NULL) return;
3374 fprintf(fp, "%s", sysconfdata);
3377 /* this handy API function does all the work for us */
3378 CtdlWriteObject(SYSCONFIGROOM, sysconfname, temp, NULL, 0, 1, 0);
3384 * Determine whether a given Internet address belongs to the current user
3386 int CtdlIsMe(char *addr, int addr_buf_len)
3388 struct recptypes *recp;
3391 recp = validate_recipients(addr);
3392 if (recp == NULL) return(0);
3394 if (recp->num_local == 0) {
3399 for (i=0; i<recp->num_local; ++i) {
3400 extract_token(addr, recp->recp_local, i, '|', addr_buf_len);
3401 if (!strcasecmp(addr, CC->user.fullname)) {
3413 * Citadel protocol command to do the same
3415 void cmd_isme(char *argbuf) {
3418 if (CtdlAccessCheck(ac_logged_in)) return;
3419 extract_token(addr, argbuf, 0, '|', sizeof addr);
3421 if (CtdlIsMe(addr, sizeof addr)) {
3422 cprintf("%d %s\n", CIT_OK, addr);
3425 cprintf("%d Not you.\n", ERROR + ILLEGAL_VALUE);