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 sooper-fast headers
1134 * only mode, request that we don't even bother loading the body
1137 if (headers_only == HEADERS_FAST) {
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);
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);
1387 else if (!is_room_aide() && (TheMessage->cm_anon_type == MES_ANONOPT)) {
1388 cprintf("From: x@x.org (anonymous)%s", nl);
1390 else if (strlen(fuser) > 0) {
1391 cprintf("From: %s (%s)%s", fuser, luser, nl);
1394 cprintf("From: %s@%s (%s)%s", suser, snode, luser, nl);
1397 cprintf("Organization: %s%s", lnode, nl);
1399 /* Blank line signifying RFC822 end-of-headers */
1400 if (TheMessage->cm_format_type != FMT_RFC822) {
1405 /* end header processing loop ... at this point, we're in the text */
1407 if (headers_only == HEADERS_FAST) goto DONE;
1408 mptr = TheMessage->cm_fields['M'];
1410 /* Tell the client about the MIME parts in this message */
1411 if (TheMessage->cm_format_type == FMT_RFC822) {
1412 if ( (mode == MT_CITADEL) || (mode == MT_MIME) ) {
1413 mime_parser(mptr, NULL,
1419 else if (mode == MT_RFC822) { /* unparsed RFC822 dump */
1420 /* FIXME ... we have to put some code in here to avoid
1421 * printing duplicate header information when both
1422 * Citadel and RFC822 headers exist. Preference should
1423 * probably be given to the RFC822 headers.
1425 int done_rfc822_hdrs = 0;
1426 while (ch=*(mptr++), ch!=0) {
1431 if (!done_rfc822_hdrs) {
1432 if (headers_only != HEADERS_NONE) {
1437 if (headers_only != HEADERS_ONLY) {
1441 if ((*(mptr) == 13) || (*(mptr) == 10)) {
1442 done_rfc822_hdrs = 1;
1446 if (done_rfc822_hdrs) {
1447 if (headers_only != HEADERS_NONE) {
1452 if (headers_only != HEADERS_ONLY) {
1456 if ((*mptr == 13) || (*mptr == 10)) {
1457 done_rfc822_hdrs = 1;
1465 if (headers_only == HEADERS_ONLY) {
1469 /* signify start of msg text */
1470 if ( (mode == MT_CITADEL) || (mode == MT_MIME) ) {
1471 if (do_proto) cprintf("text\n");
1474 /* If the format type on disk is 1 (fixed-format), then we want
1475 * everything to be output completely literally ... regardless of
1476 * what message transfer format is in use.
1478 if (TheMessage->cm_format_type == FMT_FIXED) {
1479 if (mode == MT_MIME) {
1480 cprintf("Content-type: text/plain\n\n");
1483 while (ch = *mptr++, ch > 0) {
1486 if ((ch == 10) || (strlen(buf) > 250)) {
1487 cprintf("%s%s", buf, nl);
1490 buf[strlen(buf) + 1] = 0;
1491 buf[strlen(buf)] = ch;
1494 if (strlen(buf) > 0)
1495 cprintf("%s%s", buf, nl);
1498 /* If the message on disk is format 0 (Citadel vari-format), we
1499 * output using the formatter at 80 columns. This is the final output
1500 * form if the transfer format is RFC822, but if the transfer format
1501 * is Citadel proprietary, it'll still work, because the indentation
1502 * for new paragraphs is correct and the client will reformat the
1503 * message to the reader's screen width.
1505 if (TheMessage->cm_format_type == FMT_CITADEL) {
1506 if (mode == MT_MIME) {
1507 cprintf("Content-type: text/x-citadel-variformat\n\n");
1509 memfmout(80, mptr, 0, nl);
1512 /* If the message on disk is format 4 (MIME), we've gotta hand it
1513 * off to the MIME parser. The client has already been told that
1514 * this message is format 1 (fixed format), so the callback function
1515 * we use will display those parts as-is.
1517 if (TheMessage->cm_format_type == FMT_RFC822) {
1518 ma = malloc(sizeof(struct ma_info));
1519 memset(ma, 0, sizeof(struct ma_info));
1521 if (mode == MT_MIME) {
1522 strcpy(ma->chosen_part, "1");
1523 mime_parser(mptr, NULL,
1524 *choose_preferred, *fixed_output_pre,
1525 *fixed_output_post, (void *)ma, 0);
1526 mime_parser(mptr, NULL,
1527 *output_preferred, NULL, NULL, (void *)ma, 0);
1530 mime_parser(mptr, NULL,
1531 *fixed_output, *fixed_output_pre,
1532 *fixed_output_post, (void *)ma, 0);
1538 DONE: /* now we're done */
1539 if (do_proto) cprintf("000\n");
1546 * display a message (mode 0 - Citadel proprietary)
1548 void cmd_msg0(char *cmdbuf)
1551 int headers_only = HEADERS_ALL;
1553 msgid = extract_long(cmdbuf, 0);
1554 headers_only = extract_int(cmdbuf, 1);
1556 CtdlOutputMsg(msgid, MT_CITADEL, headers_only, 1, 0);
1562 * display a message (mode 2 - RFC822)
1564 void cmd_msg2(char *cmdbuf)
1567 int headers_only = HEADERS_ALL;
1569 msgid = extract_long(cmdbuf, 0);
1570 headers_only = extract_int(cmdbuf, 1);
1572 CtdlOutputMsg(msgid, MT_RFC822, headers_only, 1, 1);
1578 * display a message (mode 3 - IGnet raw format - internal programs only)
1580 void cmd_msg3(char *cmdbuf)
1583 struct CtdlMessage *msg;
1586 if (CC->internal_pgm == 0) {
1587 cprintf("%d This command is for internal programs only.\n",
1588 ERROR + HIGHER_ACCESS_REQUIRED);
1592 msgnum = extract_long(cmdbuf, 0);
1593 msg = CtdlFetchMessage(msgnum, 1);
1595 cprintf("%d Message %ld not found.\n",
1596 ERROR + MESSAGE_NOT_FOUND, msgnum);
1600 serialize_message(&smr, msg);
1601 CtdlFreeMessage(msg);
1604 cprintf("%d Unable to serialize message\n",
1605 ERROR + INTERNAL_ERROR);
1609 cprintf("%d %ld\n", BINARY_FOLLOWS, (long)smr.len);
1610 client_write((char *)smr.ser, (int)smr.len);
1617 * Display a message using MIME content types
1619 void cmd_msg4(char *cmdbuf)
1623 msgid = extract_long(cmdbuf, 0);
1624 CtdlOutputMsg(msgid, MT_MIME, 0, 1, 0);
1630 * Client tells us its preferred message format(s)
1632 void cmd_msgp(char *cmdbuf)
1634 safestrncpy(CC->preferred_formats, cmdbuf,
1635 sizeof(CC->preferred_formats));
1636 cprintf("%d ok\n", CIT_OK);
1641 * Open a component of a MIME message as a download file
1643 void cmd_opna(char *cmdbuf)
1646 char desired_section[128];
1648 msgid = extract_long(cmdbuf, 0);
1649 extract_token(desired_section, cmdbuf, 1, '|', sizeof desired_section);
1650 safestrncpy(CC->download_desired_section, desired_section, sizeof CC->download_desired_section);
1651 CtdlOutputMsg(msgid, MT_DOWNLOAD, 0, 1, 1);
1656 * Save a message pointer into a specified room
1657 * (Returns 0 for success, nonzero for failure)
1658 * roomname may be NULL to use the current room
1660 int CtdlSaveMsgPointerInRoom(char *roomname, long msgid, int flags) {
1662 char hold_rm[ROOMNAMELEN];
1663 struct cdbdata *cdbfr;
1666 long highest_msg = 0L;
1667 struct CtdlMessage *msg = NULL;
1669 lprintf(CTDL_DEBUG, "CtdlSaveMsgPointerInRoom(%s, %ld, %d)\n",
1670 roomname, msgid, flags);
1672 strcpy(hold_rm, CC->room.QRname);
1674 /* We may need to check to see if this message is real */
1675 if ( (flags & SM_VERIFY_GOODNESS)
1676 || (flags & SM_DO_REPL_CHECK)
1678 msg = CtdlFetchMessage(msgid, 1);
1679 if (msg == NULL) return(ERROR + ILLEGAL_VALUE);
1682 /* Perform replication checks if necessary */
1683 if ( (flags & SM_DO_REPL_CHECK) && (msg != NULL) ) {
1685 if (getroom(&CC->room,
1686 ((roomname != NULL) ? roomname : CC->room.QRname) )
1688 lprintf(CTDL_ERR, "No such room <%s>\n", roomname);
1689 if (msg != NULL) CtdlFreeMessage(msg);
1690 return(ERROR + ROOM_NOT_FOUND);
1693 if (ReplicationChecks(msg) != 0) {
1694 getroom(&CC->room, hold_rm);
1695 if (msg != NULL) CtdlFreeMessage(msg);
1697 "Did replication, and newer exists\n");
1702 /* Now the regular stuff */
1703 if (lgetroom(&CC->room,
1704 ((roomname != NULL) ? roomname : CC->room.QRname) )
1706 lprintf(CTDL_ERR, "No such room <%s>\n", roomname);
1707 if (msg != NULL) CtdlFreeMessage(msg);
1708 return(ERROR + ROOM_NOT_FOUND);
1711 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long));
1712 if (cdbfr == NULL) {
1716 msglist = malloc(cdbfr->len);
1717 if (msglist == NULL)
1718 lprintf(CTDL_ALERT, "ERROR malloc msglist!\n");
1719 num_msgs = cdbfr->len / sizeof(long);
1720 memcpy(msglist, cdbfr->ptr, cdbfr->len);
1725 /* Make sure the message doesn't already exist in this room. It
1726 * is absolutely taboo to have more than one reference to the same
1727 * message in a room.
1729 if (num_msgs > 0) for (i=0; i<num_msgs; ++i) {
1730 if (msglist[i] == msgid) {
1731 lputroom(&CC->room); /* unlock the room */
1732 getroom(&CC->room, hold_rm);
1733 if (msg != NULL) CtdlFreeMessage(msg);
1735 return(ERROR + ALREADY_EXISTS);
1739 /* Now add the new message */
1741 msglist = realloc(msglist, (num_msgs * sizeof(long)));
1743 if (msglist == NULL) {
1744 lprintf(CTDL_ALERT, "ERROR: can't realloc message list!\n");
1746 msglist[num_msgs - 1] = msgid;
1748 /* Sort the message list, so all the msgid's are in order */
1749 num_msgs = sort_msglist(msglist, num_msgs);
1751 /* Determine the highest message number */
1752 highest_msg = msglist[num_msgs - 1];
1754 /* Write it back to disk. */
1755 cdb_store(CDB_MSGLISTS, &CC->room.QRnumber, (int)sizeof(long),
1756 msglist, (int)(num_msgs * sizeof(long)));
1758 /* Free up the memory we used. */
1761 /* Update the highest-message pointer and unlock the room. */
1762 CC->room.QRhighest = highest_msg;
1763 lputroom(&CC->room);
1764 getroom(&CC->room, hold_rm);
1766 /* Bump the reference count for this message. */
1767 if ((flags & SM_DONT_BUMP_REF)==0) {
1768 AdjRefCount(msgid, +1);
1771 /* Return success. */
1772 if (msg != NULL) CtdlFreeMessage(msg);
1779 * Message base operation to save a new message to the message store
1780 * (returns new message number)
1782 * This is the back end for CtdlSubmitMsg() and should not be directly
1783 * called by server-side modules.
1786 long send_message(struct CtdlMessage *msg) {
1794 /* Get a new message number */
1795 newmsgid = get_new_message_number();
1796 snprintf(msgidbuf, sizeof msgidbuf, "%ld@%s", newmsgid, config.c_fqdn);
1798 /* Generate an ID if we don't have one already */
1799 if (msg->cm_fields['I']==NULL) {
1800 msg->cm_fields['I'] = strdup(msgidbuf);
1803 /* If the message is big, set its body aside for storage elsewhere */
1804 if (msg->cm_fields['M'] != NULL) {
1805 if (strlen(msg->cm_fields['M']) > BIGMSG) {
1807 holdM = msg->cm_fields['M'];
1808 msg->cm_fields['M'] = NULL;
1812 /* Serialize our data structure for storage in the database */
1813 serialize_message(&smr, msg);
1816 msg->cm_fields['M'] = holdM;
1820 cprintf("%d Unable to serialize message\n",
1821 ERROR + INTERNAL_ERROR);
1825 /* Write our little bundle of joy into the message base */
1826 if (cdb_store(CDB_MSGMAIN, &newmsgid, (int)sizeof(long),
1827 smr.ser, smr.len) < 0) {
1828 lprintf(CTDL_ERR, "Can't store message\n");
1832 cdb_store(CDB_BIGMSGS,
1842 /* Free the memory we used for the serialized message */
1845 /* Return the *local* message ID to the caller
1846 * (even if we're storing an incoming network message)
1854 * Serialize a struct CtdlMessage into the format used on disk and network.
1856 * This function loads up a "struct ser_ret" (defined in server.h) which
1857 * contains the length of the serialized message and a pointer to the
1858 * serialized message in memory. THE LATTER MUST BE FREED BY THE CALLER.
1860 void serialize_message(struct ser_ret *ret, /* return values */
1861 struct CtdlMessage *msg) /* unserialized msg */
1865 static char *forder = FORDER;
1867 if (is_valid_message(msg) == 0) return; /* self check */
1870 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL)
1871 ret->len = ret->len +
1872 strlen(msg->cm_fields[(int)forder[i]]) + 2;
1874 lprintf(CTDL_DEBUG, "serialize_message() calling malloc(%ld)\n", (long)ret->len);
1875 ret->ser = malloc(ret->len);
1876 if (ret->ser == NULL) {
1882 ret->ser[1] = msg->cm_anon_type;
1883 ret->ser[2] = msg->cm_format_type;
1886 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL) {
1887 ret->ser[wlen++] = (char)forder[i];
1888 strcpy((char *)&ret->ser[wlen], msg->cm_fields[(int)forder[i]]);
1889 wlen = wlen + strlen(msg->cm_fields[(int)forder[i]]) + 1;
1891 if (ret->len != wlen) lprintf(CTDL_ERR, "ERROR: len=%ld wlen=%ld\n",
1892 (long)ret->len, (long)wlen);
1900 * Back end for the ReplicationChecks() function
1902 void check_repl(long msgnum, void *userdata) {
1903 lprintf(CTDL_DEBUG, "check_repl() replacing message %ld\n", msgnum);
1904 CtdlDeleteMessages(CC->room.QRname, msgnum, "");
1909 * Check to see if any messages already exist which carry the same Exclusive ID
1910 * as this one. If any are found, delete them.
1913 int ReplicationChecks(struct CtdlMessage *msg) {
1914 struct CtdlMessage *template;
1917 /* No exclusive id? Don't do anything. */
1918 if (msg->cm_fields['E'] == NULL) return 0;
1919 if (strlen(msg->cm_fields['E']) == 0) return 0;
1920 lprintf(CTDL_DEBUG, "Exclusive ID: <%s>\n", msg->cm_fields['E']);
1922 template = (struct CtdlMessage *) malloc(sizeof(struct CtdlMessage));
1923 memset(template, 0, sizeof(struct CtdlMessage));
1924 template->cm_fields['E'] = strdup(msg->cm_fields['E']);
1926 CtdlForEachMessage(MSGS_ALL, 0L, NULL, template, check_repl, NULL);
1928 CtdlFreeMessage(template);
1929 lprintf(CTDL_DEBUG, "ReplicationChecks() returning %d\n", abort_this);
1937 * Save a message to disk and submit it into the delivery system.
1939 long CtdlSubmitMsg(struct CtdlMessage *msg, /* message to save */
1940 struct recptypes *recps, /* recipients (if mail) */
1941 char *force /* force a particular room? */
1943 char submit_filename[128];
1944 char generated_timestamp[32];
1945 char hold_rm[ROOMNAMELEN];
1946 char actual_rm[ROOMNAMELEN];
1947 char force_room[ROOMNAMELEN];
1948 char content_type[SIZ]; /* We have to learn this */
1949 char recipient[SIZ];
1952 struct ctdluser userbuf;
1954 struct MetaData smi;
1955 FILE *network_fp = NULL;
1956 static int seqnum = 1;
1957 struct CtdlMessage *imsg = NULL;
1960 char *hold_R, *hold_D;
1962 lprintf(CTDL_DEBUG, "CtdlSubmitMsg() called\n");
1963 if (is_valid_message(msg) == 0) return(-1); /* self check */
1965 /* If this message has no timestamp, we take the liberty of
1966 * giving it one, right now.
1968 if (msg->cm_fields['T'] == NULL) {
1969 lprintf(CTDL_DEBUG, "Generating timestamp\n");
1970 snprintf(generated_timestamp, sizeof generated_timestamp, "%ld", (long)time(NULL));
1971 msg->cm_fields['T'] = strdup(generated_timestamp);
1974 /* If this message has no path, we generate one.
1976 if (msg->cm_fields['P'] == NULL) {
1977 lprintf(CTDL_DEBUG, "Generating path\n");
1978 if (msg->cm_fields['A'] != NULL) {
1979 msg->cm_fields['P'] = strdup(msg->cm_fields['A']);
1980 for (a=0; a<strlen(msg->cm_fields['P']); ++a) {
1981 if (isspace(msg->cm_fields['P'][a])) {
1982 msg->cm_fields['P'][a] = ' ';
1987 msg->cm_fields['P'] = strdup("unknown");
1991 if (force == NULL) {
1992 strcpy(force_room, "");
1995 strcpy(force_room, force);
1998 /* Learn about what's inside, because it's what's inside that counts */
1999 lprintf(CTDL_DEBUG, "Learning what's inside\n");
2000 if (msg->cm_fields['M'] == NULL) {
2001 lprintf(CTDL_ERR, "ERROR: attempt to save message with NULL body\n");
2005 switch (msg->cm_format_type) {
2007 strcpy(content_type, "text/x-citadel-variformat");
2010 strcpy(content_type, "text/plain");
2013 strcpy(content_type, "text/plain");
2014 mptr = bmstrstr(msg->cm_fields['M'],
2015 "Content-type: ", strncasecmp);
2017 safestrncpy(content_type, &mptr[14],
2018 sizeof content_type);
2019 for (a = 0; a < strlen(content_type); ++a) {
2020 if ((content_type[a] == ';')
2021 || (content_type[a] == ' ')
2022 || (content_type[a] == 13)
2023 || (content_type[a] == 10)) {
2024 content_type[a] = 0;
2030 /* Goto the correct room */
2031 lprintf(CTDL_DEBUG, "Selected room %s\n",
2032 (recps) ? CC->room.QRname : SENTITEMS);
2033 strcpy(hold_rm, CC->room.QRname);
2034 strcpy(actual_rm, CC->room.QRname);
2035 if (recps != NULL) {
2036 strcpy(actual_rm, SENTITEMS);
2039 /* If the user is a twit, move to the twit room for posting */
2040 lprintf(CTDL_DEBUG, "Handling twit stuff: %s\n",
2041 (CC->user.axlevel == 2) ? config.c_twitroom : "OK");
2043 if (CC->user.axlevel == 2) {
2044 strcpy(hold_rm, actual_rm);
2045 strcpy(actual_rm, config.c_twitroom);
2049 /* ...or if this message is destined for Aide> then go there. */
2050 if (strlen(force_room) > 0) {
2051 strcpy(actual_rm, force_room);
2054 lprintf(CTDL_DEBUG, "Final selection: %s\n", actual_rm);
2055 if (strcasecmp(actual_rm, CC->room.QRname)) {
2056 getroom(&CC->room, actual_rm);
2060 * If this message has no O (room) field, generate one.
2062 if (msg->cm_fields['O'] == NULL) {
2063 msg->cm_fields['O'] = strdup(CC->room.QRname);
2066 /* Perform "before save" hooks (aborting if any return nonzero) */
2067 lprintf(CTDL_DEBUG, "Performing before-save hooks\n");
2068 if (PerformMessageHooks(msg, EVT_BEFORESAVE) > 0) return(-3);
2070 /* If this message has an Exclusive ID, perform replication checks */
2071 lprintf(CTDL_DEBUG, "Performing replication checks\n");
2072 if (ReplicationChecks(msg) > 0) return(-4);
2074 /* Save it to disk */
2075 lprintf(CTDL_DEBUG, "Saving to disk\n");
2076 newmsgid = send_message(msg);
2077 if (newmsgid <= 0L) return(-5);
2079 /* Write a supplemental message info record. This doesn't have to
2080 * be a critical section because nobody else knows about this message
2083 lprintf(CTDL_DEBUG, "Creating MetaData record\n");
2084 memset(&smi, 0, sizeof(struct MetaData));
2085 smi.meta_msgnum = newmsgid;
2086 smi.meta_refcount = 0;
2087 safestrncpy(smi.meta_content_type, content_type,
2088 sizeof smi.meta_content_type);
2090 /* As part of the new metadata record, measure how
2091 * big this message will be when displayed as RFC822.
2092 * Both POP and IMAP use this, and it's best to just take the hit now
2093 * instead of having to potentially measure thousands of messages when
2094 * a mailbox is opened later.
2097 if (CC->redirect_buffer != NULL) {
2098 lprintf(CTDL_ALERT, "CC->redirect_buffer is not NULL during message submission!\n");
2101 CC->redirect_buffer = malloc(SIZ);
2102 CC->redirect_len = 0;
2103 CC->redirect_alloc = SIZ;
2104 CtdlOutputPreLoadedMsg(msg, 0L, MT_RFC822, HEADERS_ALL, 0, 1);
2105 smi.meta_rfc822_length = CC->redirect_len;
2106 free(CC->redirect_buffer);
2107 CC->redirect_buffer = NULL;
2108 CC->redirect_len = 0;
2109 CC->redirect_alloc = 0;
2113 /* Now figure out where to store the pointers */
2114 lprintf(CTDL_DEBUG, "Storing pointers\n");
2116 /* If this is being done by the networker delivering a private
2117 * message, we want to BYPASS saving the sender's copy (because there
2118 * is no local sender; it would otherwise go to the Trashcan).
2120 if ((!CC->internal_pgm) || (recps == NULL)) {
2121 if (CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 0) != 0) {
2122 lprintf(CTDL_ERR, "ERROR saving message pointer!\n");
2123 CtdlSaveMsgPointerInRoom(config.c_aideroom,
2128 /* For internet mail, drop a copy in the outbound queue room */
2130 if (recps->num_internet > 0) {
2131 CtdlSaveMsgPointerInRoom(SMTP_SPOOLOUT_ROOM, newmsgid, 0);
2134 /* If other rooms are specified, drop them there too. */
2136 if (recps->num_room > 0)
2137 for (i=0; i<num_tokens(recps->recp_room, '|'); ++i) {
2138 extract_token(recipient, recps->recp_room, i,
2139 '|', sizeof recipient);
2140 lprintf(CTDL_DEBUG, "Delivering to room <%s>\n", recipient);
2141 CtdlSaveMsgPointerInRoom(recipient, newmsgid, 0);
2144 /* Bump this user's messages posted counter. */
2145 lprintf(CTDL_DEBUG, "Updating user\n");
2146 lgetuser(&CC->user, CC->curr_user);
2147 CC->user.posted = CC->user.posted + 1;
2148 lputuser(&CC->user);
2150 /* If this is private, local mail, make a copy in the
2151 * recipient's mailbox and bump the reference count.
2154 if (recps->num_local > 0)
2155 for (i=0; i<num_tokens(recps->recp_local, '|'); ++i) {
2156 extract_token(recipient, recps->recp_local, i,
2157 '|', sizeof recipient);
2158 lprintf(CTDL_DEBUG, "Delivering private local mail to <%s>\n",
2160 if (getuser(&userbuf, recipient) == 0) {
2161 MailboxName(actual_rm, sizeof actual_rm,
2162 &userbuf, MAILROOM);
2163 CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 0);
2164 BumpNewMailCounter(userbuf.usernum);
2167 lprintf(CTDL_DEBUG, "No user <%s>\n", recipient);
2168 CtdlSaveMsgPointerInRoom(config.c_aideroom,
2173 /* Perform "after save" hooks */
2174 lprintf(CTDL_DEBUG, "Performing after-save hooks\n");
2175 PerformMessageHooks(msg, EVT_AFTERSAVE);
2177 /* For IGnet mail, we have to save a new copy into the spooler for
2178 * each recipient, with the R and D fields set to the recipient and
2179 * destination-node. This has two ugly side effects: all other
2180 * recipients end up being unlisted in this recipient's copy of the
2181 * message, and it has to deliver multiple messages to the same
2182 * node. We'll revisit this again in a year or so when everyone has
2183 * a network spool receiver that can handle the new style messages.
2186 if (recps->num_ignet > 0)
2187 for (i=0; i<num_tokens(recps->recp_ignet, '|'); ++i) {
2188 extract_token(recipient, recps->recp_ignet, i,
2189 '|', sizeof recipient);
2191 hold_R = msg->cm_fields['R'];
2192 hold_D = msg->cm_fields['D'];
2193 msg->cm_fields['R'] = malloc(SIZ);
2194 msg->cm_fields['D'] = malloc(128);
2195 extract_token(msg->cm_fields['R'], recipient, 0, '@', SIZ);
2196 extract_token(msg->cm_fields['D'], recipient, 1, '@', 128);
2198 serialize_message(&smr, msg);
2200 snprintf(submit_filename, sizeof submit_filename,
2201 "./network/spoolin/netmail.%04lx.%04x.%04x",
2202 (long) getpid(), CC->cs_pid, ++seqnum);
2203 network_fp = fopen(submit_filename, "wb+");
2204 if (network_fp != NULL) {
2205 fwrite(smr.ser, smr.len, 1, network_fp);
2211 free(msg->cm_fields['R']);
2212 free(msg->cm_fields['D']);
2213 msg->cm_fields['R'] = hold_R;
2214 msg->cm_fields['D'] = hold_D;
2217 /* Go back to the room we started from */
2218 lprintf(CTDL_DEBUG, "Returning to original room %s\n", hold_rm);
2219 if (strcasecmp(hold_rm, CC->room.QRname))
2220 getroom(&CC->room, hold_rm);
2222 /* For internet mail, generate delivery instructions.
2223 * Yes, this is recursive. Deal with it. Infinite recursion does
2224 * not happen because the delivery instructions message does not
2225 * contain a recipient.
2228 if (recps->num_internet > 0) {
2229 lprintf(CTDL_DEBUG, "Generating delivery instructions\n");
2230 instr = malloc(SIZ * 2);
2231 snprintf(instr, SIZ * 2,
2232 "Content-type: %s\n\nmsgid|%ld\nsubmitted|%ld\n"
2234 SPOOLMIME, newmsgid, (long)time(NULL),
2235 msg->cm_fields['A'], msg->cm_fields['N']
2238 for (i=0; i<num_tokens(recps->recp_internet, '|'); ++i) {
2239 size_t tmp = strlen(instr);
2240 extract_token(recipient, recps->recp_internet, i, '|', sizeof recipient);
2241 snprintf(&instr[tmp], SIZ * 2 - tmp,
2242 "remote|%s|0||\n", recipient);
2245 imsg = malloc(sizeof(struct CtdlMessage));
2246 memset(imsg, 0, sizeof(struct CtdlMessage));
2247 imsg->cm_magic = CTDLMESSAGE_MAGIC;
2248 imsg->cm_anon_type = MES_NORMAL;
2249 imsg->cm_format_type = FMT_RFC822;
2250 imsg->cm_fields['A'] = strdup("Citadel");
2251 imsg->cm_fields['M'] = instr;
2252 CtdlSubmitMsg(imsg, NULL, SMTP_SPOOLOUT_ROOM);
2253 CtdlFreeMessage(imsg);
2262 * Convenience function for generating small administrative messages.
2264 void quickie_message(char *from, char *to, char *room, char *text,
2265 int format_type, char *subject)
2267 struct CtdlMessage *msg;
2268 struct recptypes *recp = NULL;
2270 msg = malloc(sizeof(struct CtdlMessage));
2271 memset(msg, 0, sizeof(struct CtdlMessage));
2272 msg->cm_magic = CTDLMESSAGE_MAGIC;
2273 msg->cm_anon_type = MES_NORMAL;
2274 msg->cm_format_type = format_type;
2275 msg->cm_fields['A'] = strdup(from);
2276 if (room != NULL) msg->cm_fields['O'] = strdup(room);
2277 msg->cm_fields['N'] = strdup(NODENAME);
2279 msg->cm_fields['R'] = strdup(to);
2280 recp = validate_recipients(to);
2282 if (subject != NULL) {
2283 msg->cm_fields['U'] = strdup(subject);
2285 msg->cm_fields['M'] = strdup(text);
2287 CtdlSubmitMsg(msg, recp, room);
2288 CtdlFreeMessage(msg);
2289 if (recp != NULL) free(recp);
2295 * Back end function used by CtdlMakeMessage() and similar functions
2297 char *CtdlReadMessageBody(char *terminator, /* token signalling EOT */
2298 size_t maxlen, /* maximum message length */
2299 char *exist, /* if non-null, append to it;
2300 exist is ALWAYS freed */
2301 int crlf /* CRLF newlines instead of LF */
2305 size_t message_len = 0;
2306 size_t buffer_len = 0;
2312 if (exist == NULL) {
2319 message_len = strlen(exist);
2320 buffer_len = message_len + 4096;
2321 m = realloc(exist, buffer_len);
2328 /* flush the input if we have nowhere to store it */
2333 /* read in the lines of message text one by one */
2335 if (client_getln(buf, (sizeof buf - 3)) < 1) finished = 1;
2336 if (!strcmp(buf, terminator)) finished = 1;
2338 strcat(buf, "\r\n");
2344 if ( (!flushing) && (!finished) ) {
2345 /* Measure the line */
2346 linelen = strlen(buf);
2348 /* augment the buffer if we have to */
2349 if ((message_len + linelen) >= buffer_len) {
2350 ptr = realloc(m, (buffer_len * 2) );
2351 if (ptr == NULL) { /* flush if can't allocate */
2354 buffer_len = (buffer_len * 2);
2356 lprintf(CTDL_DEBUG, "buffer_len is now %ld\n", (long)buffer_len);
2360 /* Add the new line to the buffer. NOTE: this loop must avoid
2361 * using functions like strcat() and strlen() because they
2362 * traverse the entire buffer upon every call, and doing that
2363 * for a multi-megabyte message slows it down beyond usability.
2365 strcpy(&m[message_len], buf);
2366 message_len += linelen;
2369 /* if we've hit the max msg length, flush the rest */
2370 if (message_len >= maxlen) flushing = 1;
2372 } while (!finished);
2380 * Build a binary message to be saved on disk.
2381 * (NOTE: if you supply 'preformatted_text', the buffer you give it
2382 * will become part of the message. This means you are no longer
2383 * responsible for managing that memory -- it will be freed along with
2384 * the rest of the fields when CtdlFreeMessage() is called.)
2387 struct CtdlMessage *CtdlMakeMessage(
2388 struct ctdluser *author, /* author's user structure */
2389 char *recipient, /* NULL if it's not mail */
2390 char *room, /* room where it's going */
2391 int type, /* see MES_ types in header file */
2392 int format_type, /* variformat, plain text, MIME... */
2393 char *fake_name, /* who we're masquerading as */
2394 char *subject, /* Subject (optional) */
2395 char *preformatted_text /* ...or NULL to read text from client */
2397 char dest_node[SIZ];
2399 struct CtdlMessage *msg;
2401 msg = malloc(sizeof(struct CtdlMessage));
2402 memset(msg, 0, sizeof(struct CtdlMessage));
2403 msg->cm_magic = CTDLMESSAGE_MAGIC;
2404 msg->cm_anon_type = type;
2405 msg->cm_format_type = format_type;
2407 /* Don't confuse the poor folks if it's not routed mail. */
2408 strcpy(dest_node, "");
2412 snprintf(buf, sizeof buf, "cit%ld", author->usernum); /* Path */
2413 msg->cm_fields['P'] = strdup(buf);
2415 snprintf(buf, sizeof buf, "%ld", (long)time(NULL)); /* timestamp */
2416 msg->cm_fields['T'] = strdup(buf);
2418 if (fake_name[0]) /* author */
2419 msg->cm_fields['A'] = strdup(fake_name);
2421 msg->cm_fields['A'] = strdup(author->fullname);
2423 if (CC->room.QRflags & QR_MAILBOX) { /* room */
2424 msg->cm_fields['O'] = strdup(&CC->room.QRname[11]);
2427 msg->cm_fields['O'] = strdup(CC->room.QRname);
2430 msg->cm_fields['N'] = strdup(NODENAME); /* nodename */
2431 msg->cm_fields['H'] = strdup(HUMANNODE); /* hnodename */
2433 if (recipient[0] != 0) {
2434 msg->cm_fields['R'] = strdup(recipient);
2436 if (dest_node[0] != 0) {
2437 msg->cm_fields['D'] = strdup(dest_node);
2440 if ( (author == &CC->user) && (strlen(CC->cs_inet_email) > 0) ) {
2441 msg->cm_fields['F'] = strdup(CC->cs_inet_email);
2444 if (subject != NULL) {
2446 if (strlen(subject) > 0) {
2447 msg->cm_fields['U'] = strdup(subject);
2451 if (preformatted_text != NULL) {
2452 msg->cm_fields['M'] = preformatted_text;
2455 msg->cm_fields['M'] = CtdlReadMessageBody("000",
2456 config.c_maxmsglen, NULL, 0);
2464 * Check to see whether we have permission to post a message in the current
2465 * room. Returns a *CITADEL ERROR CODE* and puts a message in errmsgbuf, or
2466 * returns 0 on success.
2468 int CtdlDoIHavePermissionToPostInThisRoom(char *errmsgbuf, size_t n) {
2470 if (!(CC->logged_in)) {
2471 snprintf(errmsgbuf, n, "Not logged in.");
2472 return (ERROR + NOT_LOGGED_IN);
2475 if ((CC->user.axlevel < 2)
2476 && ((CC->room.QRflags & QR_MAILBOX) == 0)) {
2477 snprintf(errmsgbuf, n, "Need to be validated to enter "
2478 "(except in %s> to sysop)", MAILROOM);
2479 return (ERROR + HIGHER_ACCESS_REQUIRED);
2482 if ((CC->user.axlevel < 4)
2483 && (CC->room.QRflags & QR_NETWORK)) {
2484 snprintf(errmsgbuf, n, "Need net privileges to enter here.");
2485 return (ERROR + HIGHER_ACCESS_REQUIRED);
2488 if ((CC->user.axlevel < 6)
2489 && (CC->room.QRflags & QR_READONLY)) {
2490 snprintf(errmsgbuf, n, "Sorry, this is a read-only room.");
2491 return (ERROR + HIGHER_ACCESS_REQUIRED);
2494 strcpy(errmsgbuf, "Ok");
2500 * Check to see if the specified user has Internet mail permission
2501 * (returns nonzero if permission is granted)
2503 int CtdlCheckInternetMailPermission(struct ctdluser *who) {
2505 /* Do not allow twits to send Internet mail */
2506 if (who->axlevel <= 2) return(0);
2508 /* Globally enabled? */
2509 if (config.c_restrict == 0) return(1);
2511 /* User flagged ok? */
2512 if (who->flags & US_INTERNET) return(2);
2514 /* Aide level access? */
2515 if (who->axlevel >= 6) return(3);
2517 /* No mail for you! */
2524 * Validate recipients, count delivery types and errors, and handle aliasing
2525 * FIXME check for dupes!!!!!
2527 struct recptypes *validate_recipients(char *recipients) {
2528 struct recptypes *ret;
2529 char this_recp[SIZ];
2530 char this_recp_cooked[SIZ];
2536 struct ctdluser tempUS;
2537 struct ctdlroom tempQR;
2540 ret = (struct recptypes *) malloc(sizeof(struct recptypes));
2541 if (ret == NULL) return(NULL);
2542 memset(ret, 0, sizeof(struct recptypes));
2545 ret->num_internet = 0;
2550 if (recipients == NULL) {
2553 else if (strlen(recipients) == 0) {
2557 /* Change all valid separator characters to commas */
2558 for (i=0; i<strlen(recipients); ++i) {
2559 if ((recipients[i] == ';') || (recipients[i] == '|')) {
2560 recipients[i] = ',';
2565 num_recps = num_tokens(recipients, ',');
2568 if (num_recps > 0) for (i=0; i<num_recps; ++i) {
2569 extract_token(this_recp, recipients, i, ',', sizeof this_recp);
2571 lprintf(CTDL_DEBUG, "Evaluating recipient #%d <%s>\n", i, this_recp);
2572 mailtype = alias(this_recp);
2573 mailtype = alias(this_recp);
2574 mailtype = alias(this_recp);
2575 for (j=0; j<=strlen(this_recp); ++j) {
2576 if (this_recp[j]=='_') {
2577 this_recp_cooked[j] = ' ';
2580 this_recp_cooked[j] = this_recp[j];
2586 if (!strcasecmp(this_recp, "sysop")) {
2588 strcpy(this_recp, config.c_aideroom);
2589 if (strlen(ret->recp_room) > 0) {
2590 strcat(ret->recp_room, "|");
2592 strcat(ret->recp_room, this_recp);
2594 else if (getuser(&tempUS, this_recp) == 0) {
2596 strcpy(this_recp, tempUS.fullname);
2597 if (strlen(ret->recp_local) > 0) {
2598 strcat(ret->recp_local, "|");
2600 strcat(ret->recp_local, this_recp);
2602 else if (getuser(&tempUS, this_recp_cooked) == 0) {
2604 strcpy(this_recp, tempUS.fullname);
2605 if (strlen(ret->recp_local) > 0) {
2606 strcat(ret->recp_local, "|");
2608 strcat(ret->recp_local, this_recp);
2610 else if ( (!strncasecmp(this_recp, "room_", 5))
2611 && (!getroom(&tempQR, &this_recp_cooked[5])) ) {
2613 if (strlen(ret->recp_room) > 0) {
2614 strcat(ret->recp_room, "|");
2616 strcat(ret->recp_room, &this_recp_cooked[5]);
2624 /* Yes, you're reading this correctly: if the target
2625 * domain points back to the local system or an attached
2626 * Citadel directory, the address is invalid. That's
2627 * because if the address were valid, we would have
2628 * already translated it to a local address by now.
2630 if (IsDirectory(this_recp)) {
2635 ++ret->num_internet;
2636 if (strlen(ret->recp_internet) > 0) {
2637 strcat(ret->recp_internet, "|");
2639 strcat(ret->recp_internet, this_recp);
2644 if (strlen(ret->recp_ignet) > 0) {
2645 strcat(ret->recp_ignet, "|");
2647 strcat(ret->recp_ignet, this_recp);
2655 if (strlen(ret->errormsg) == 0) {
2656 snprintf(append, sizeof append,
2657 "Invalid recipient: %s",
2661 snprintf(append, sizeof append,
2664 if ( (strlen(ret->errormsg) + strlen(append)) < SIZ) {
2665 strcat(ret->errormsg, append);
2669 if (strlen(ret->display_recp) == 0) {
2670 strcpy(append, this_recp);
2673 snprintf(append, sizeof append, ", %s",
2676 if ( (strlen(ret->display_recp)+strlen(append)) < SIZ) {
2677 strcat(ret->display_recp, append);
2682 if ((ret->num_local + ret->num_internet + ret->num_ignet +
2683 ret->num_room + ret->num_error) == 0) {
2685 strcpy(ret->errormsg, "No recipients specified.");
2688 lprintf(CTDL_DEBUG, "validate_recipients()\n");
2689 lprintf(CTDL_DEBUG, " local: %d <%s>\n", ret->num_local, ret->recp_local);
2690 lprintf(CTDL_DEBUG, " room: %d <%s>\n", ret->num_room, ret->recp_room);
2691 lprintf(CTDL_DEBUG, " inet: %d <%s>\n", ret->num_internet, ret->recp_internet);
2692 lprintf(CTDL_DEBUG, " ignet: %d <%s>\n", ret->num_ignet, ret->recp_ignet);
2693 lprintf(CTDL_DEBUG, " error: %d <%s>\n", ret->num_error, ret->errormsg);
2701 * message entry - mode 0 (normal)
2703 void cmd_ent0(char *entargs)
2707 char masquerade_as[SIZ];
2709 int format_type = 0;
2710 char newusername[SIZ];
2711 struct CtdlMessage *msg;
2715 struct recptypes *valid = NULL;
2722 post = extract_int(entargs, 0);
2723 extract_token(recp, entargs, 1, '|', sizeof recp);
2724 anon_flag = extract_int(entargs, 2);
2725 format_type = extract_int(entargs, 3);
2726 extract_token(subject, entargs, 4, '|', sizeof subject);
2727 do_confirm = extract_int(entargs, 6);
2729 /* first check to make sure the request is valid. */
2731 err = CtdlDoIHavePermissionToPostInThisRoom(errmsg, sizeof errmsg);
2733 cprintf("%d %s\n", err, errmsg);
2737 /* Check some other permission type things. */
2740 if (CC->user.axlevel < 6) {
2741 cprintf("%d You don't have permission to masquerade.\n",
2742 ERROR + HIGHER_ACCESS_REQUIRED);
2745 extract_token(newusername, entargs, 5, '|', sizeof newusername);
2746 memset(CC->fake_postname, 0, sizeof(CC->fake_postname) );
2747 safestrncpy(CC->fake_postname, newusername,
2748 sizeof(CC->fake_postname) );
2749 cprintf("%d ok\n", CIT_OK);
2752 CC->cs_flags |= CS_POSTING;
2754 /* In the Mail> room we have to behave a little differently --
2755 * make sure the user has specified at least one recipient. Then
2756 * validate the recipient(s).
2758 if ( (CC->room.QRflags & QR_MAILBOX)
2759 && (!strcasecmp(&CC->room.QRname[11], MAILROOM)) ) {
2761 if (CC->user.axlevel < 2) {
2762 strcpy(recp, "sysop");
2765 valid = validate_recipients(recp);
2766 if (valid->num_error > 0) {
2768 ERROR + NO_SUCH_USER, valid->errormsg);
2772 if (valid->num_internet > 0) {
2773 if (CtdlCheckInternetMailPermission(&CC->user)==0) {
2774 cprintf("%d You do not have permission "
2775 "to send Internet mail.\n",
2776 ERROR + HIGHER_ACCESS_REQUIRED);
2782 if ( ( (valid->num_internet + valid->num_ignet) > 0)
2783 && (CC->user.axlevel < 4) ) {
2784 cprintf("%d Higher access required for network mail.\n",
2785 ERROR + HIGHER_ACCESS_REQUIRED);
2790 if ((RESTRICT_INTERNET == 1) && (valid->num_internet > 0)
2791 && ((CC->user.flags & US_INTERNET) == 0)
2792 && (!CC->internal_pgm)) {
2793 cprintf("%d You don't have access to Internet mail.\n",
2794 ERROR + HIGHER_ACCESS_REQUIRED);
2801 /* Is this a room which has anonymous-only or anonymous-option? */
2802 anonymous = MES_NORMAL;
2803 if (CC->room.QRflags & QR_ANONONLY) {
2804 anonymous = MES_ANONONLY;
2806 if (CC->room.QRflags & QR_ANONOPT) {
2807 if (anon_flag == 1) { /* only if the user requested it */
2808 anonymous = MES_ANONOPT;
2812 if ((CC->room.QRflags & QR_MAILBOX) == 0) {
2816 /* If we're only checking the validity of the request, return
2817 * success without creating the message.
2820 cprintf("%d %s\n", CIT_OK,
2821 ((valid != NULL) ? valid->display_recp : "") );
2826 /* Handle author masquerading */
2827 if (CC->fake_postname[0]) {
2828 strcpy(masquerade_as, CC->fake_postname);
2830 else if (CC->fake_username[0]) {
2831 strcpy(masquerade_as, CC->fake_username);
2834 strcpy(masquerade_as, "");
2837 /* Read in the message from the client. */
2839 cprintf("%d send message\n", START_CHAT_MODE);
2841 cprintf("%d send message\n", SEND_LISTING);
2843 msg = CtdlMakeMessage(&CC->user, recp,
2844 CC->room.QRname, anonymous, format_type,
2845 masquerade_as, subject, NULL);
2848 msgnum = CtdlSubmitMsg(msg, valid, "");
2851 cprintf("%ld\n", msgnum);
2853 cprintf("Message accepted.\n");
2856 cprintf("Internal error.\n");
2858 if (msg->cm_fields['E'] != NULL) {
2859 cprintf("%s\n", msg->cm_fields['E']);
2866 CtdlFreeMessage(msg);
2868 CC->fake_postname[0] = '\0';
2876 * API function to delete messages which match a set of criteria
2877 * (returns the actual number of messages deleted)
2879 int CtdlDeleteMessages(char *room_name, /* which room */
2880 long dmsgnum, /* or "0" for any */
2881 char *content_type /* or "" for any */
2885 struct ctdlroom qrbuf;
2886 struct cdbdata *cdbfr;
2887 long *msglist = NULL;
2888 long *dellist = NULL;
2891 int num_deleted = 0;
2893 struct MetaData smi;
2895 lprintf(CTDL_DEBUG, "CtdlDeleteMessages(%s, %ld, %s)\n",
2896 room_name, dmsgnum, content_type);
2898 /* get room record, obtaining a lock... */
2899 if (lgetroom(&qrbuf, room_name) != 0) {
2900 lprintf(CTDL_ERR, "CtdlDeleteMessages(): Room <%s> not found\n",
2902 return (0); /* room not found */
2904 cdbfr = cdb_fetch(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long));
2906 if (cdbfr != NULL) {
2907 msglist = malloc(cdbfr->len);
2908 dellist = malloc(cdbfr->len);
2909 memcpy(msglist, cdbfr->ptr, cdbfr->len);
2910 num_msgs = cdbfr->len / sizeof(long);
2914 for (i = 0; i < num_msgs; ++i) {
2917 /* Set/clear a bit for each criterion */
2919 if ((dmsgnum == 0L) || (msglist[i] == dmsgnum)) {
2920 delete_this |= 0x01;
2922 if (strlen(content_type) == 0) {
2923 delete_this |= 0x02;
2925 GetMetaData(&smi, msglist[i]);
2926 if (!strcasecmp(smi.meta_content_type,
2928 delete_this |= 0x02;
2932 /* Delete message only if all bits are set */
2933 if (delete_this == 0x03) {
2934 dellist[num_deleted++] = msglist[i];
2939 num_msgs = sort_msglist(msglist, num_msgs);
2940 cdb_store(CDB_MSGLISTS, &qrbuf.QRnumber, (int)sizeof(long),
2941 msglist, (int)(num_msgs * sizeof(long)));
2943 qrbuf.QRhighest = msglist[num_msgs - 1];
2947 /* Go through the messages we pulled out of the index, and decrement
2948 * their reference counts by 1. If this is the only room the message
2949 * was in, the reference count will reach zero and the message will
2950 * automatically be deleted from the database. We do this in a
2951 * separate pass because there might be plug-in hooks getting called,
2952 * and we don't want that happening during an S_ROOMS critical
2955 if (num_deleted) for (i=0; i<num_deleted; ++i) {
2956 PerformDeleteHooks(qrbuf.QRname, dellist[i]);
2957 AdjRefCount(dellist[i], -1);
2960 /* Now free the memory we used, and go away. */
2961 if (msglist != NULL) free(msglist);
2962 if (dellist != NULL) free(dellist);
2963 lprintf(CTDL_DEBUG, "%d message(s) deleted.\n", num_deleted);
2964 return (num_deleted);
2970 * Check whether the current user has permission to delete messages from
2971 * the current room (returns 1 for yes, 0 for no)
2973 int CtdlDoIHavePermissionToDeleteMessagesFromThisRoom(void) {
2974 getuser(&CC->user, CC->curr_user);
2975 if ((CC->user.axlevel < 6)
2976 && (CC->user.usernum != CC->room.QRroomaide)
2977 && ((CC->room.QRflags & QR_MAILBOX) == 0)
2978 && (!(CC->internal_pgm))) {
2987 * Delete message from current room
2989 void cmd_dele(char *delstr)
2994 if (CtdlDoIHavePermissionToDeleteMessagesFromThisRoom() == 0) {
2995 cprintf("%d Higher access required.\n",
2996 ERROR + HIGHER_ACCESS_REQUIRED);
2999 delnum = extract_long(delstr, 0);
3001 num_deleted = CtdlDeleteMessages(CC->room.QRname, delnum, "");
3004 cprintf("%d %d message%s deleted.\n", CIT_OK,
3005 num_deleted, ((num_deleted != 1) ? "s" : ""));
3007 cprintf("%d Message %ld not found.\n", ERROR + MESSAGE_NOT_FOUND, delnum);
3013 * Back end API function for moves and deletes
3015 int CtdlCopyMsgToRoom(long msgnum, char *dest) {
3018 err = CtdlSaveMsgPointerInRoom(dest, msgnum,
3019 (SM_VERIFY_GOODNESS | SM_DO_REPL_CHECK) );
3020 if (err != 0) return(err);
3028 * move or copy a message to another room
3030 void cmd_move(char *args)
3033 char targ[ROOMNAMELEN];
3034 struct ctdlroom qtemp;
3040 num = extract_long(args, 0);
3041 extract_token(targ, args, 1, '|', sizeof targ);
3042 targ[ROOMNAMELEN - 1] = 0;
3043 is_copy = extract_int(args, 2);
3045 if (getroom(&qtemp, targ) != 0) {
3046 cprintf("%d '%s' does not exist.\n",
3047 ERROR + ROOM_NOT_FOUND, targ);
3051 getuser(&CC->user, CC->curr_user);
3052 CtdlRoomAccess(&qtemp, &CC->user, &ra, NULL);
3054 /* Check for permission to perform this operation.
3055 * Remember: "CC->room" is source, "qtemp" is target.
3059 /* Aides can move/copy */
3060 if (CC->user.axlevel >= 6) permit = 1;
3062 /* Room aides can move/copy */
3063 if (CC->user.usernum == CC->room.QRroomaide) permit = 1;
3065 /* Permit move/copy from personal rooms */
3066 if ((CC->room.QRflags & QR_MAILBOX)
3067 && (qtemp.QRflags & QR_MAILBOX)) permit = 1;
3069 /* Permit only copy from public to personal room */
3071 && (!(CC->room.QRflags & QR_MAILBOX))
3072 && (qtemp.QRflags & QR_MAILBOX)) permit = 1;
3074 /* User must have access to target room */
3075 if (!(ra & UA_KNOWN)) permit = 0;
3078 cprintf("%d Higher access required.\n",
3079 ERROR + HIGHER_ACCESS_REQUIRED);
3083 err = CtdlCopyMsgToRoom(num, targ);
3085 cprintf("%d Cannot store message in %s: error %d\n",
3090 /* Now delete the message from the source room,
3091 * if this is a 'move' rather than a 'copy' operation.
3094 CtdlDeleteMessages(CC->room.QRname, num, "");
3097 cprintf("%d Message %s.\n", CIT_OK, (is_copy ? "copied" : "moved") );
3103 * GetMetaData() - Get the supplementary record for a message
3105 void GetMetaData(struct MetaData *smibuf, long msgnum)
3108 struct cdbdata *cdbsmi;
3111 memset(smibuf, 0, sizeof(struct MetaData));
3112 smibuf->meta_msgnum = msgnum;
3113 smibuf->meta_refcount = 1; /* Default reference count is 1 */
3115 /* Use the negative of the message number for its supp record index */
3116 TheIndex = (0L - msgnum);
3118 cdbsmi = cdb_fetch(CDB_MSGMAIN, &TheIndex, sizeof(long));
3119 if (cdbsmi == NULL) {
3120 return; /* record not found; go with defaults */
3122 memcpy(smibuf, cdbsmi->ptr,
3123 ((cdbsmi->len > sizeof(struct MetaData)) ?
3124 sizeof(struct MetaData) : cdbsmi->len));
3131 * PutMetaData() - (re)write supplementary record for a message
3133 void PutMetaData(struct MetaData *smibuf)
3137 /* Use the negative of the message number for the metadata db index */
3138 TheIndex = (0L - smibuf->meta_msgnum);
3140 lprintf(CTDL_DEBUG, "PutMetaData(%ld) - ref count is %d\n",
3141 smibuf->meta_msgnum, smibuf->meta_refcount);
3143 cdb_store(CDB_MSGMAIN,
3144 &TheIndex, (int)sizeof(long),
3145 smibuf, (int)sizeof(struct MetaData));
3150 * AdjRefCount - change the reference count for a message;
3151 * delete the message if it reaches zero
3153 void AdjRefCount(long msgnum, int incr)
3156 struct MetaData smi;
3159 /* This is a *tight* critical section; please keep it that way, as
3160 * it may get called while nested in other critical sections.
3161 * Complicating this any further will surely cause deadlock!
3163 begin_critical_section(S_SUPPMSGMAIN);
3164 GetMetaData(&smi, msgnum);
3165 lprintf(CTDL_DEBUG, "Ref count for message <%ld> before write is <%d>\n",
3166 msgnum, smi.meta_refcount);
3167 smi.meta_refcount += incr;
3169 end_critical_section(S_SUPPMSGMAIN);
3170 lprintf(CTDL_DEBUG, "Ref count for message <%ld> after write is <%d>\n",
3171 msgnum, smi.meta_refcount);
3173 /* If the reference count is now zero, delete the message
3174 * (and its supplementary record as well).
3176 if (smi.meta_refcount == 0) {
3177 lprintf(CTDL_DEBUG, "Deleting message <%ld>\n", msgnum);
3179 cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long));
3180 cdb_delete(CDB_BIGMSGS, &delnum, (int)sizeof(long));
3182 /* We have to delete the metadata record too! */
3183 delnum = (0L - msgnum);
3184 cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long));
3189 * Write a generic object to this room
3191 * Note: this could be much more efficient. Right now we use two temporary
3192 * files, and still pull the message into memory as with all others.
3194 void CtdlWriteObject(char *req_room, /* Room to stuff it in */
3195 char *content_type, /* MIME type of this object */
3196 char *tempfilename, /* Where to fetch it from */
3197 struct ctdluser *is_mailbox, /* Mailbox room? */
3198 int is_binary, /* Is encoding necessary? */
3199 int is_unique, /* Del others of this type? */
3200 unsigned int flags /* Internal save flags */
3205 struct ctdlroom qrbuf;
3206 char roomname[ROOMNAMELEN];
3207 struct CtdlMessage *msg;
3209 char *raw_message = NULL;
3210 char *encoded_message = NULL;
3211 off_t raw_length = 0;
3213 if (is_mailbox != NULL)
3214 MailboxName(roomname, sizeof roomname, is_mailbox, req_room);
3216 safestrncpy(roomname, req_room, sizeof(roomname));
3217 lprintf(CTDL_DEBUG, "CtdlWriteObject() to <%s> (flags=%d)\n", roomname, flags);
3220 fp = fopen(tempfilename, "rb");
3222 lprintf(CTDL_CRIT, "Cannot open %s: %s\n",
3223 tempfilename, strerror(errno));
3226 fseek(fp, 0L, SEEK_END);
3227 raw_length = ftell(fp);
3229 lprintf(CTDL_DEBUG, "Raw length is %ld\n", (long)raw_length);
3231 raw_message = malloc((size_t)raw_length + 2);
3232 fread(raw_message, (size_t)raw_length, 1, fp);
3236 encoded_message = malloc((size_t)
3237 (((raw_length * 134) / 100) + 4096 ) );
3240 encoded_message = malloc((size_t)(raw_length + 4096));
3243 sprintf(encoded_message, "Content-type: %s\n", content_type);
3246 sprintf(&encoded_message[strlen(encoded_message)],
3247 "Content-transfer-encoding: base64\n\n"
3251 sprintf(&encoded_message[strlen(encoded_message)],
3252 "Content-transfer-encoding: 7bit\n\n"
3258 &encoded_message[strlen(encoded_message)],
3264 raw_message[raw_length] = 0;
3266 &encoded_message[strlen(encoded_message)],
3274 lprintf(CTDL_DEBUG, "Allocating\n");
3275 msg = malloc(sizeof(struct CtdlMessage));
3276 memset(msg, 0, sizeof(struct CtdlMessage));
3277 msg->cm_magic = CTDLMESSAGE_MAGIC;
3278 msg->cm_anon_type = MES_NORMAL;
3279 msg->cm_format_type = 4;
3280 msg->cm_fields['A'] = strdup(CC->user.fullname);
3281 msg->cm_fields['O'] = strdup(req_room);
3282 msg->cm_fields['N'] = strdup(config.c_nodename);
3283 msg->cm_fields['H'] = strdup(config.c_humannode);
3284 msg->cm_flags = flags;
3286 msg->cm_fields['M'] = encoded_message;
3288 /* Create the requested room if we have to. */
3289 if (getroom(&qrbuf, roomname) != 0) {
3290 create_room(roomname,
3291 ( (is_mailbox != NULL) ? 5 : 3 ),
3292 "", 0, 1, 0, VIEW_BBS);
3294 /* If the caller specified this object as unique, delete all
3295 * other objects of this type that are currently in the room.
3298 lprintf(CTDL_DEBUG, "Deleted %d other msgs of this type\n",
3299 CtdlDeleteMessages(roomname, 0L, content_type));
3301 /* Now write the data */
3302 CtdlSubmitMsg(msg, NULL, roomname);
3303 CtdlFreeMessage(msg);
3311 void CtdlGetSysConfigBackend(long msgnum, void *userdata) {
3312 config_msgnum = msgnum;
3316 char *CtdlGetSysConfig(char *sysconfname) {
3317 char hold_rm[ROOMNAMELEN];
3320 struct CtdlMessage *msg;
3323 strcpy(hold_rm, CC->room.QRname);
3324 if (getroom(&CC->room, SYSCONFIGROOM) != 0) {
3325 getroom(&CC->room, hold_rm);
3330 /* We want the last (and probably only) config in this room */
3331 begin_critical_section(S_CONFIG);
3332 config_msgnum = (-1L);
3333 CtdlForEachMessage(MSGS_LAST, 1, sysconfname, NULL,
3334 CtdlGetSysConfigBackend, NULL);
3335 msgnum = config_msgnum;
3336 end_critical_section(S_CONFIG);
3342 msg = CtdlFetchMessage(msgnum, 1);
3344 conf = strdup(msg->cm_fields['M']);
3345 CtdlFreeMessage(msg);
3352 getroom(&CC->room, hold_rm);
3354 if (conf != NULL) do {
3355 extract_token(buf, conf, 0, '\n', sizeof buf);
3356 strcpy(conf, &conf[strlen(buf)+1]);
3357 } while ( (strlen(conf)>0) && (strlen(buf)>0) );
3362 void CtdlPutSysConfig(char *sysconfname, char *sysconfdata) {
3363 char temp[PATH_MAX];
3366 strcpy(temp, tmpnam(NULL));
3368 fp = fopen(temp, "w");
3369 if (fp == NULL) return;
3370 fprintf(fp, "%s", sysconfdata);
3373 /* this handy API function does all the work for us */
3374 CtdlWriteObject(SYSCONFIGROOM, sysconfname, temp, NULL, 0, 1, 0);
3380 * Determine whether a given Internet address belongs to the current user
3382 int CtdlIsMe(char *addr, int addr_buf_len)
3384 struct recptypes *recp;
3387 recp = validate_recipients(addr);
3388 if (recp == NULL) return(0);
3390 if (recp->num_local == 0) {
3395 for (i=0; i<recp->num_local; ++i) {
3396 extract_token(addr, recp->recp_local, i, '|', addr_buf_len);
3397 if (!strcasecmp(addr, CC->user.fullname)) {
3409 * Citadel protocol command to do the same
3411 void cmd_isme(char *argbuf) {
3414 if (CtdlAccessCheck(ac_logged_in)) return;
3415 extract_token(addr, argbuf, 0, '|', sizeof addr);
3417 if (CtdlIsMe(addr, sizeof addr)) {
3418 cprintf("%d %s\n", CIT_OK, addr);
3421 cprintf("%d Not you.\n", ERROR + ILLEGAL_VALUE);