4 * Implements the message store.
18 #if TIME_WITH_SYS_TIME
19 # include <sys/time.h>
23 # include <sys/time.h>
39 #include "serv_extensions.h"
43 #include "sysdep_decls.h"
44 #include "citserver.h"
50 #include "mime_parser.h"
53 #include "internet_addressing.h"
55 #define desired_section ((char *)CtdlGetUserData(SYM_DESIRED_SECTION))
56 #define ma ((struct ma_info *)CtdlGetUserData(SYM_MA_INFO))
57 #define msg_repl ((struct repl *)CtdlGetUserData(SYM_REPL))
59 extern struct config config;
64 * This really belongs in serv_network.c, but I don't know how to export
65 * symbols between modules.
67 struct FilterList *filterlist = NULL;
71 * These are the four-character field headers we use when outputting
72 * messages in Citadel format (as opposed to RFC822 format).
75 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
76 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
77 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
78 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
79 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
80 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
81 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
82 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
109 * This function is self explanatory.
110 * (What can I say, I'm in a weird mood today...)
112 void remove_any_whitespace_to_the_left_or_right_of_at_symbol(char *name)
116 for (i = 0; i < strlen(name); ++i) {
117 if (name[i] == '@') {
118 while (isspace(name[i - 1]) && i > 0) {
119 strcpy(&name[i - 1], &name[i]);
122 while (isspace(name[i + 1])) {
123 strcpy(&name[i + 1], &name[i + 2]);
131 * Aliasing for network mail.
132 * (Error messages have been commented out, because this is a server.)
134 int alias(char *name)
135 { /* process alias and routing info for mail */
138 char aaa[SIZ], bbb[SIZ];
139 char *ignetcfg = NULL;
140 char *ignetmap = NULL;
147 remove_any_whitespace_to_the_left_or_right_of_at_symbol(name);
149 fp = fopen("network/mail.aliases", "r");
151 fp = fopen("/dev/null", "r");
158 while (fgets(aaa, sizeof aaa, fp) != NULL) {
159 while (isspace(name[0]))
160 strcpy(name, &name[1]);
161 aaa[strlen(aaa) - 1] = 0;
163 for (a = 0; a < strlen(aaa); ++a) {
165 strcpy(bbb, &aaa[a + 1]);
169 if (!strcasecmp(name, aaa))
174 /* Hit the Global Address Book */
175 if (CtdlDirectoryLookup(aaa, name) == 0) {
179 lprintf(7, "Mail is being forwarded to %s\n", name);
181 /* Change "user @ xxx" to "user" if xxx is an alias for this host */
182 for (a=0; a<strlen(name); ++a) {
183 if (name[a] == '@') {
184 if (CtdlHostAlias(&name[a+1]) == hostalias_localhost) {
186 lprintf(7, "Changed to <%s>\n", name);
191 /* determine local or remote type, see citadel.h */
192 at = haschar(name, '@');
193 if (at == 0) return(MES_LOCAL); /* no @'s - local address */
194 if (at > 1) return(MES_ERROR); /* >1 @'s - invalid address */
195 remove_any_whitespace_to_the_left_or_right_of_at_symbol(name);
197 /* figure out the delivery mode */
198 extract_token(node, name, 1, '@');
200 /* If there are one or more dots in the nodename, we assume that it
201 * is an FQDN and will attempt SMTP delivery to the Internet.
203 if (haschar(node, '.') > 0) {
204 return(MES_INTERNET);
207 /* Otherwise we look in the IGnet maps for a valid Citadel node.
208 * Try directly-connected nodes first...
210 ignetcfg = CtdlGetSysConfig(IGNETCFG);
211 for (i=0; i<num_tokens(ignetcfg, '\n'); ++i) {
212 extract_token(buf, ignetcfg, i, '\n');
213 extract_token(testnode, buf, 0, '|');
214 if (!strcasecmp(node, testnode)) {
222 * Then try nodes that are two or more hops away.
224 ignetmap = CtdlGetSysConfig(IGNETMAP);
225 for (i=0; i<num_tokens(ignetmap, '\n'); ++i) {
226 extract_token(buf, ignetmap, i, '\n');
227 extract_token(testnode, buf, 0, '|');
228 if (!strcasecmp(node, testnode)) {
235 /* If we get to this point it's an invalid node name */
244 fp = fopen("citadel.control", "r");
245 fread((char *) &CitControl, sizeof(struct CitControl), 1, fp);
251 void simple_listing(long msgnum, void *userdata)
253 cprintf("%ld\n", msgnum);
258 /* Determine if a given message matches the fields in a message template.
259 * Return 0 for a successful match.
261 int CtdlMsgCmp(struct CtdlMessage *msg, struct CtdlMessage *template) {
264 /* If there aren't any fields in the template, all messages will
267 if (template == NULL) return(0);
269 /* Null messages are bogus. */
270 if (msg == NULL) return(1);
272 for (i='A'; i<='Z'; ++i) {
273 if (template->cm_fields[i] != NULL) {
274 if (msg->cm_fields[i] == NULL) {
277 if (strcasecmp(msg->cm_fields[i],
278 template->cm_fields[i])) return 1;
282 /* All compares succeeded: we have a match! */
289 * Retrieve the "seen" message list for the current room.
291 void CtdlGetSeen(char *buf, int which_set) {
294 /* Learn about the user and room in question */
295 CtdlGetRelationship(&vbuf, &CC->user, &CC->room);
297 if (which_set == ctdlsetseen_seen)
298 safestrncpy(buf, vbuf.v_seen, SIZ);
299 if (which_set == ctdlsetseen_answered)
300 safestrncpy(buf, vbuf.v_answered, SIZ);
306 * Manipulate the "seen msgs" string (or other message set strings)
308 void CtdlSetSeen(long target_msgnum, int target_setting, int which_set) {
310 struct cdbdata *cdbfr;
321 /* Learn about the user and room in question */
322 CtdlGetRelationship(&vbuf, &CC->user, &CC->room);
324 /* Load the message list */
325 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long));
327 msglist = mallok(cdbfr->len);
328 memcpy(msglist, cdbfr->ptr, cdbfr->len);
329 num_msgs = cdbfr->len / sizeof(long);
332 return; /* No messages at all? No further action. */
335 /* Decide which message set we're manipulating */
336 if (which_set == ctdlsetseen_seen) strcpy(vset, vbuf.v_seen);
337 if (which_set == ctdlsetseen_answered) strcpy(vset, vbuf.v_answered);
339 lprintf(9, "before optimize: %s\n", vset);
342 for (i=0; i<num_msgs; ++i) {
345 if (msglist[i] == target_msgnum) {
346 is_seen = target_setting;
349 if (is_msg_in_mset(vset, msglist[i])) {
355 if (lo < 0L) lo = msglist[i];
358 if ( ((is_seen == 0) && (was_seen == 1))
359 || ((is_seen == 1) && (i == num_msgs-1)) ) {
362 if ( (strlen(newseen) + 20) > SIZ) {
363 strcpy(newseen, &newseen[20]);
366 tmp = strlen(newseen);
368 strcat(newseen, ",");
372 snprintf(&newseen[tmp], sizeof newseen - tmp,
376 snprintf(&newseen[tmp], sizeof newseen - tmp,
385 /* Decide which message set we're manipulating */
386 if (which_set == ctdlsetseen_seen) strcpy(vbuf.v_seen, newseen);
387 if (which_set == ctdlsetseen_answered) strcpy(vbuf.v_answered, newseen);
389 lprintf(9, " after optimize: %s\n", newseen);
391 CtdlSetRelationship(&vbuf, &CC->user, &CC->room);
396 * API function to perform an operation for each qualifying message in the
397 * current room. (Returns the number of messages processed.)
399 int CtdlForEachMessage(int mode, long ref,
401 struct CtdlMessage *compare,
402 void (*CallBack) (long, void *),
408 struct cdbdata *cdbfr;
409 long *msglist = NULL;
411 int num_processed = 0;
414 struct CtdlMessage *msg;
417 int printed_lastold = 0;
419 /* Learn about the user and room in question */
421 getuser(&CC->user, CC->curr_user);
422 CtdlGetRelationship(&vbuf, &CC->user, &CC->room);
424 /* Load the message list */
425 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long));
427 msglist = mallok(cdbfr->len);
428 memcpy(msglist, cdbfr->ptr, cdbfr->len);
429 num_msgs = cdbfr->len / sizeof(long);
432 return 0; /* No messages at all? No further action. */
437 * Now begin the traversal.
439 if (num_msgs > 0) for (a = 0; a < num_msgs; ++a) {
441 /* If the caller is looking for a specific MIME type, filter
442 * out all messages which are not of the type requested.
444 if (content_type != NULL) if (strlen(content_type) > 0) {
446 /* This call to GetMetaData() sits inside this loop
447 * so that we only do the extra database read per msg
448 * if we need to. Doing the extra read all the time
449 * really kills the server. If we ever need to use
450 * metadata for another search criterion, we need to
451 * move the read somewhere else -- but still be smart
452 * enough to only do the read if the caller has
453 * specified something that will need it.
455 GetMetaData(&smi, msglist[a]);
457 if (strcasecmp(smi.meta_content_type, content_type)) {
463 num_msgs = sort_msglist(msglist, num_msgs);
465 /* If a template was supplied, filter out the messages which
466 * don't match. (This could induce some delays!)
469 if (compare != NULL) {
470 for (a = 0; a < num_msgs; ++a) {
471 msg = CtdlFetchMessage(msglist[a]);
473 if (CtdlMsgCmp(msg, compare)) {
476 CtdlFreeMessage(msg);
484 * Now iterate through the message list, according to the
485 * criteria supplied by the caller.
488 for (a = 0; a < num_msgs; ++a) {
489 thismsg = msglist[a];
490 is_seen = is_msg_in_mset(vbuf.v_seen, thismsg);
491 if (is_seen) lastold = thismsg;
496 || ((mode == MSGS_OLD) && (is_seen))
497 || ((mode == MSGS_NEW) && (!is_seen))
498 || ((mode == MSGS_LAST) && (a >= (num_msgs - ref)))
499 || ((mode == MSGS_FIRST) && (a < ref))
500 || ((mode == MSGS_GT) && (thismsg > ref))
501 || ((mode == MSGS_EQ) && (thismsg == ref))
504 if ((mode == MSGS_NEW) && (CC->user.flags & US_LASTOLD) && (lastold > 0L) && (printed_lastold == 0) && (!is_seen)) {
506 CallBack(lastold, userdata);
510 if (CallBack) CallBack(thismsg, userdata);
514 phree(msglist); /* Clean up */
515 return num_processed;
521 * cmd_msgs() - get list of message #'s in this room
522 * implements the MSGS server command using CtdlForEachMessage()
524 void cmd_msgs(char *cmdbuf)
533 int with_template = 0;
534 struct CtdlMessage *template = NULL;
536 extract(which, cmdbuf, 0);
537 cm_ref = extract_int(cmdbuf, 1);
538 with_template = extract_int(cmdbuf, 2);
542 if (!strncasecmp(which, "OLD", 3))
544 else if (!strncasecmp(which, "NEW", 3))
546 else if (!strncasecmp(which, "FIRST", 5))
548 else if (!strncasecmp(which, "LAST", 4))
550 else if (!strncasecmp(which, "GT", 2))
553 if ((!(CC->logged_in)) && (!(CC->internal_pgm))) {
554 cprintf("%d not logged in\n", ERROR + NOT_LOGGED_IN);
559 cprintf("%d Send template then receive message list\n",
561 template = (struct CtdlMessage *)
562 mallok(sizeof(struct CtdlMessage));
563 memset(template, 0, sizeof(struct CtdlMessage));
564 while(client_gets(buf), strcmp(buf,"000")) {
565 extract(tfield, buf, 0);
566 extract(tvalue, buf, 1);
567 for (i='A'; i<='Z'; ++i) if (msgkeys[i]!=NULL) {
568 if (!strcasecmp(tfield, msgkeys[i])) {
569 template->cm_fields[i] =
576 cprintf("%d Message list...\n", LISTING_FOLLOWS);
579 CtdlForEachMessage(mode, cm_ref,
580 NULL, template, simple_listing, NULL);
581 if (template != NULL) CtdlFreeMessage(template);
589 * help_subst() - support routine for help file viewer
591 void help_subst(char *strbuf, char *source, char *dest)
596 while (p = pattern2(strbuf, source), (p >= 0)) {
597 strcpy(workbuf, &strbuf[p + strlen(source)]);
598 strcpy(&strbuf[p], dest);
599 strcat(strbuf, workbuf);
604 void do_help_subst(char *buffer)
608 help_subst(buffer, "^nodename", config.c_nodename);
609 help_subst(buffer, "^humannode", config.c_humannode);
610 help_subst(buffer, "^fqdn", config.c_fqdn);
611 help_subst(buffer, "^username", CC->user.fullname);
612 snprintf(buf2, sizeof buf2, "%ld", CC->user.usernum);
613 help_subst(buffer, "^usernum", buf2);
614 help_subst(buffer, "^sysadm", config.c_sysadm);
615 help_subst(buffer, "^variantname", CITADEL);
616 snprintf(buf2, sizeof buf2, "%d", config.c_maxsessions);
617 help_subst(buffer, "^maxsessions", buf2);
618 help_subst(buffer, "^bbsdir", BBSDIR);
624 * memfmout() - Citadel text formatter and paginator.
625 * Although the original purpose of this routine was to format
626 * text to the reader's screen width, all we're really using it
627 * for here is to format text out to 80 columns before sending it
628 * to the client. The client software may reformat it again.
631 int width, /* screen width to use */
632 char *mptr, /* where are we going to get our text from? */
633 char subst, /* nonzero if we should do substitutions */
634 char *nl) /* string to terminate lines with */
646 c = 1; /* c is the current pos */
650 while (ch = *mptr, ((ch != 0) && (strlen(buffer) < 126))) {
652 buffer[strlen(buffer) + 1] = 0;
653 buffer[strlen(buffer)] = ch;
656 if (buffer[0] == '^')
657 do_help_subst(buffer);
659 buffer[strlen(buffer) + 1] = 0;
661 strcpy(buffer, &buffer[1]);
669 if (((ch == 13) || (ch == 10)) && (old != 13) && (old != 10))
671 if (((old == 13) || (old == 10)) && (isspace(real))) {
679 if (((strlen(aaa) + c) > (width - 5)) && (strlen(aaa) > (width - 5))) {
680 cprintf("%s%s", nl, aaa);
689 if ((strlen(aaa) + c) > (width - 5)) {
698 if ((ch == 13) || (ch == 10)) {
699 cprintf("%s%s", aaa, nl);
706 cprintf("%s%s", aaa, nl);
712 * Callback function for mime parser that simply lists the part
714 void list_this_part(char *name, char *filename, char *partnum, char *disp,
715 void *content, char *cbtype, size_t length, char *encoding,
719 cprintf("part=%s|%s|%s|%s|%s|%ld\n",
720 name, filename, partnum, disp, cbtype, (long)length);
724 * Callback function for multipart prefix
726 void list_this_pref(char *name, char *filename, char *partnum, char *disp,
727 void *content, char *cbtype, size_t length, char *encoding,
730 cprintf("pref=%s|%s\n", partnum, cbtype);
734 * Callback function for multipart sufffix
736 void list_this_suff(char *name, char *filename, char *partnum, char *disp,
737 void *content, char *cbtype, size_t length, char *encoding,
740 cprintf("suff=%s|%s\n", partnum, cbtype);
745 * Callback function for mime parser that opens a section for downloading
747 void mime_download(char *name, char *filename, char *partnum, char *disp,
748 void *content, char *cbtype, size_t length, char *encoding,
752 /* Silently go away if there's already a download open... */
753 if (CC->download_fp != NULL)
756 /* ...or if this is not the desired section */
757 if (strcasecmp(desired_section, partnum))
760 CC->download_fp = tmpfile();
761 if (CC->download_fp == NULL)
764 fwrite(content, length, 1, CC->download_fp);
765 fflush(CC->download_fp);
766 rewind(CC->download_fp);
768 OpenCmdResult(filename, cbtype);
774 * Load a message from disk into memory.
775 * This is used by CtdlOutputMsg() and other fetch functions.
777 * NOTE: Caller is responsible for freeing the returned CtdlMessage struct
778 * using the CtdlMessageFree() function.
780 struct CtdlMessage *CtdlFetchMessage(long msgnum)
782 struct cdbdata *dmsgtext;
783 struct CtdlMessage *ret = NULL;
786 cit_uint8_t field_header;
789 dmsgtext = cdb_fetch(CDB_MSGMAIN, &msgnum, sizeof(long));
790 if (dmsgtext == NULL) {
793 mptr = dmsgtext->ptr;
795 /* Parse the three bytes that begin EVERY message on disk.
796 * The first is always 0xFF, the on-disk magic number.
797 * The second is the anonymous/public type byte.
798 * The third is the format type byte (vari, fixed, or MIME).
802 lprintf(5, "Message %ld appears to be corrupted.\n", msgnum);
806 ret = (struct CtdlMessage *) mallok(sizeof(struct CtdlMessage));
807 memset(ret, 0, sizeof(struct CtdlMessage));
809 ret->cm_magic = CTDLMESSAGE_MAGIC;
810 ret->cm_anon_type = *mptr++; /* Anon type byte */
811 ret->cm_format_type = *mptr++; /* Format type byte */
814 * The rest is zero or more arbitrary fields. Load them in.
815 * We're done when we encounter either a zero-length field or
816 * have just processed the 'M' (message text) field.
819 field_length = strlen(mptr);
820 if (field_length == 0)
822 field_header = *mptr++;
823 ret->cm_fields[field_header] = mallok(field_length);
824 strcpy(ret->cm_fields[field_header], mptr);
826 while (*mptr++ != 0); /* advance to next field */
828 } while ((field_length > 0) && (field_header != 'M'));
832 /* Always make sure there's something in the msg text field */
833 if (ret->cm_fields['M'] == NULL)
834 ret->cm_fields['M'] = strdoop("<no text>\n");
836 /* Perform "before read" hooks (aborting if any return nonzero) */
837 if (PerformMessageHooks(ret, EVT_BEFOREREAD) > 0) {
838 CtdlFreeMessage(ret);
847 * Returns 1 if the supplied pointer points to a valid Citadel message.
848 * If the pointer is NULL or the magic number check fails, returns 0.
850 int is_valid_message(struct CtdlMessage *msg) {
853 if ((msg->cm_magic) != CTDLMESSAGE_MAGIC) {
854 lprintf(3, "is_valid_message() -- self-check failed\n");
862 * 'Destructor' for struct CtdlMessage
864 void CtdlFreeMessage(struct CtdlMessage *msg)
868 if (is_valid_message(msg) == 0) return;
870 for (i = 0; i < 256; ++i)
871 if (msg->cm_fields[i] != NULL) {
872 phree(msg->cm_fields[i]);
875 msg->cm_magic = 0; /* just in case */
881 * Pre callback function for multipart/alternative
883 * NOTE: this differs from the standard behavior for a reason. Normally when
884 * displaying multipart/alternative you want to show the _last_ usable
885 * format in the message. Here we show the _first_ one, because it's
886 * usually text/plain. Since this set of functions is designed for text
887 * output to non-MIME-aware clients, this is the desired behavior.
890 void fixed_output_pre(char *name, char *filename, char *partnum, char *disp,
891 void *content, char *cbtype, size_t length, char *encoding,
894 lprintf(9, "fixed_output_pre() type=<%s>\n", cbtype);
895 if (!strcasecmp(cbtype, "multipart/alternative")) {
903 * Post callback function for multipart/alternative
905 void fixed_output_post(char *name, char *filename, char *partnum, char *disp,
906 void *content, char *cbtype, size_t length, char *encoding,
909 lprintf(9, "fixed_output_post() type=<%s>\n", cbtype);
910 if (!strcasecmp(cbtype, "multipart/alternative")) {
918 * Inline callback function for mime parser that wants to display text
920 void fixed_output(char *name, char *filename, char *partnum, char *disp,
921 void *content, char *cbtype, size_t length, char *encoding,
928 lprintf(9, "fixed_output() type=<%s>\n", cbtype);
931 * If we're in the middle of a multipart/alternative scope and
932 * we've already printed another section, skip this one.
934 if ( (ma->is_ma == 1) && (ma->did_print == 1) ) {
935 lprintf(9, "Skipping part %s (%s)\n", partnum, cbtype);
940 if ( (!strcasecmp(cbtype, "text/plain"))
941 || (strlen(cbtype)==0) ) {
944 client_write(wptr, length);
945 if (wptr[length-1] != '\n') {
950 else if (!strcasecmp(cbtype, "text/html")) {
951 ptr = html_to_ascii(content, 80, 0);
953 client_write(ptr, wlen);
954 if (ptr[wlen-1] != '\n') {
959 else if (strncasecmp(cbtype, "multipart/", 10)) {
960 cprintf("Part %s: %s (%s) (%ld bytes)\r\n",
961 partnum, filename, cbtype, (long)length);
966 * The client is elegant and sophisticated and wants to be choosy about
967 * MIME content types, so figure out which multipart/alternative part
968 * we're going to send.
970 void choose_preferred(char *name, char *filename, char *partnum, char *disp,
971 void *content, char *cbtype, size_t length, char *encoding,
978 for (i=0; i<num_tokens(CC->preferred_formats, '|'); ++i) {
979 extract(buf, CC->preferred_formats, i);
980 if (!strcasecmp(buf, cbtype)) {
981 strcpy(ma->chosen_part, partnum);
988 * Now that we've chosen our preferred part, output it.
990 void output_preferred(char *name, char *filename, char *partnum, char *disp,
991 void *content, char *cbtype, size_t length, char *encoding,
999 /* This is not the MIME part you're looking for... */
1000 if (strcasecmp(partnum, ma->chosen_part)) return;
1002 /* If the content-type of this part is in our preferred formats
1003 * list, we can simply output it verbatim.
1005 for (i=0; i<num_tokens(CC->preferred_formats, '|'); ++i) {
1006 extract(buf, CC->preferred_formats, i);
1007 if (!strcasecmp(buf, cbtype)) {
1008 /* Yeah! Go! W00t!! */
1010 text_content = (char *)content;
1011 if (text_content[length-1] != '\n') {
1015 cprintf("Content-type: %s\n", cbtype);
1016 cprintf("Content-length: %d\n",
1017 (int)(length + add_newline) );
1018 if (strlen(encoding) > 0) {
1019 cprintf("Content-transfer-encoding: %s\n", encoding);
1022 cprintf("Content-transfer-encoding: 7bit\n");
1025 client_write(content, length);
1026 if (add_newline) cprintf("\n");
1031 /* No translations required or possible: output as text/plain */
1032 cprintf("Content-type: text/plain\n\n");
1033 fixed_output(name, filename, partnum, disp, content, cbtype,
1034 length, encoding, cbuserdata);
1039 * Get a message off disk. (returns om_* values found in msgbase.h)
1042 int CtdlOutputMsg(long msg_num, /* message number (local) to fetch */
1043 int mode, /* how would you like that message? */
1044 int headers_only, /* eschew the message body? */
1045 int do_proto, /* do Citadel protocol responses? */
1046 int crlf /* Use CRLF newlines instead of LF? */
1048 struct CtdlMessage *TheMessage;
1051 lprintf(7, "CtdlOutputMsg() msgnum=%ld, mode=%d\n",
1056 if ((!(CC->logged_in)) && (!(CC->internal_pgm))) {
1057 if (do_proto) cprintf("%d Not logged in.\n",
1058 ERROR + NOT_LOGGED_IN);
1059 return(om_not_logged_in);
1062 /* FIXME ... small security issue
1063 * We need to check to make sure the requested message is actually
1064 * in the current room, and set msg_ok to 1 only if it is. This
1065 * functionality is currently missing because I'm in a hurry to replace
1066 * broken production code with nonbroken pre-beta code. :( -- ajc
1069 if (do_proto) cprintf("%d Message %ld is not in this room.\n",
1071 return(om_no_such_msg);
1076 * Fetch the message from disk.
1078 TheMessage = CtdlFetchMessage(msg_num);
1080 if (TheMessage == NULL) {
1081 if (do_proto) cprintf("%d Can't locate msg %ld on disk\n",
1083 return(om_no_such_msg);
1086 retcode = CtdlOutputPreLoadedMsg(
1087 TheMessage, msg_num, mode,
1088 headers_only, do_proto, crlf);
1090 CtdlFreeMessage(TheMessage);
1097 * Get a message off disk. (returns om_* values found in msgbase.h)
1100 int CtdlOutputPreLoadedMsg(struct CtdlMessage *TheMessage,
1102 int mode, /* how would you like that message? */
1103 int headers_only, /* eschew the message body? */
1104 int do_proto, /* do Citadel protocol responses? */
1105 int crlf /* Use CRLF newlines instead of LF? */
1111 char display_name[SIZ];
1113 char *nl; /* newline string */
1115 int subject_found = 0;
1117 /* buffers needed for RFC822 translation */
1124 char datestamp[SIZ];
1127 snprintf(mid, sizeof mid, "%ld", msg_num);
1128 nl = (crlf ? "\r\n" : "\n");
1130 if (!is_valid_message(TheMessage)) {
1131 lprintf(1, "ERROR: invalid preloaded message for output\n");
1132 return(om_no_such_msg);
1135 /* Are we downloading a MIME component? */
1136 if (mode == MT_DOWNLOAD) {
1137 if (TheMessage->cm_format_type != FMT_RFC822) {
1139 cprintf("%d This is not a MIME message.\n",
1141 } else if (CC->download_fp != NULL) {
1142 if (do_proto) cprintf(
1143 "%d You already have a download open.\n",
1146 /* Parse the message text component */
1147 mptr = TheMessage->cm_fields['M'];
1148 mime_parser(mptr, NULL,
1149 *mime_download, NULL, NULL,
1151 /* If there's no file open by this time, the requested
1152 * section wasn't found, so print an error
1154 if (CC->download_fp == NULL) {
1155 if (do_proto) cprintf(
1156 "%d Section %s not found.\n",
1157 ERROR + FILE_NOT_FOUND,
1161 return((CC->download_fp != NULL) ? om_ok : om_mime_error);
1164 /* now for the user-mode message reading loops */
1165 if (do_proto) cprintf("%d Message %ld:\n", LISTING_FOLLOWS, msg_num);
1167 /* Does the caller want to skip the headers? */
1168 if (headers_only == HEADERS_NONE) goto START_TEXT;
1170 /* Tell the client which format type we're using. */
1171 if ( (mode == MT_CITADEL) && (do_proto) ) {
1172 cprintf("type=%d\n", TheMessage->cm_format_type);
1175 /* nhdr=yes means that we're only displaying headers, no body */
1176 if ( (TheMessage->cm_anon_type == MES_ANONONLY)
1177 && (mode == MT_CITADEL)
1180 cprintf("nhdr=yes\n");
1183 /* begin header processing loop for Citadel message format */
1185 if ((mode == MT_CITADEL) || (mode == MT_MIME)) {
1187 strcpy(display_name, "<unknown>");
1188 if (TheMessage->cm_fields['A']) {
1189 strcpy(buf, TheMessage->cm_fields['A']);
1190 PerformUserHooks(buf, (-1L), EVT_OUTPUTMSG);
1191 if (TheMessage->cm_anon_type == MES_ANONONLY) {
1192 strcpy(display_name, "****");
1194 else if (TheMessage->cm_anon_type == MES_ANONOPT) {
1195 strcpy(display_name, "anonymous");
1198 strcpy(display_name, buf);
1200 if ((is_room_aide())
1201 && ((TheMessage->cm_anon_type == MES_ANONONLY)
1202 || (TheMessage->cm_anon_type == MES_ANONOPT))) {
1203 size_t tmp = strlen(display_name);
1204 snprintf(&display_name[tmp],
1205 sizeof display_name - tmp,
1210 /* Don't show Internet address for users on the
1211 * local Citadel network.
1214 if (TheMessage->cm_fields['N'] != NULL)
1215 if (strlen(TheMessage->cm_fields['N']) > 0)
1216 if (haschar(TheMessage->cm_fields['N'], '.') == 0) {
1220 /* Now spew the header fields in the order we like them. */
1221 strcpy(allkeys, FORDER);
1222 for (i=0; i<strlen(allkeys); ++i) {
1223 k = (int) allkeys[i];
1225 if ( (TheMessage->cm_fields[k] != NULL)
1226 && (msgkeys[k] != NULL) ) {
1228 if (do_proto) cprintf("%s=%s\n",
1232 else if ((k == 'F') && (suppress_f)) {
1235 /* Masquerade display name if needed */
1237 if (do_proto) cprintf("%s=%s\n",
1239 TheMessage->cm_fields[k]
1248 /* begin header processing loop for RFC822 transfer format */
1253 strcpy(snode, NODENAME);
1254 strcpy(lnode, HUMANNODE);
1255 if (mode == MT_RFC822) {
1256 cprintf("X-UIDL: %ld%s", msg_num, nl);
1257 for (i = 0; i < 256; ++i) {
1258 if (TheMessage->cm_fields[i]) {
1259 mptr = TheMessage->cm_fields[i];
1262 strcpy(luser, mptr);
1263 strcpy(suser, mptr);
1266 "Path:" removed for now because it confuses brain-dead Microsoft shitware
1267 into thinking that mail messages are newsgroup messages instead. When we
1268 add NNTP support back into Citadel we'll have to add code to only output
1269 this field when appropriate.
1270 else if (i == 'P') {
1271 cprintf("Path: %s%s", mptr, nl);
1274 else if (i == 'U') {
1275 cprintf("Subject: %s%s", mptr, nl);
1279 safestrncpy(mid, mptr, sizeof mid);
1281 safestrncpy(lnode, mptr, sizeof lnode);
1283 safestrncpy(fuser, mptr, sizeof fuser);
1285 cprintf("X-Citadel-Room: %s%s",
1288 safestrncpy(snode, mptr, sizeof snode);
1290 cprintf("To: %s%s", mptr, nl);
1291 else if (i == 'T') {
1292 datestring(datestamp, sizeof datestamp,
1293 atol(mptr), DATESTRING_RFC822);
1294 cprintf("Date: %s%s", datestamp, nl);
1298 if (subject_found == 0) {
1299 cprintf("Subject: (no subject)%s", nl);
1303 for (i=0; i<strlen(suser); ++i) {
1304 suser[i] = tolower(suser[i]);
1305 if (!isalnum(suser[i])) suser[i]='_';
1308 if (mode == MT_RFC822) {
1309 if (!strcasecmp(snode, NODENAME)) {
1310 strcpy(snode, FQDN);
1313 /* Construct a fun message id */
1314 cprintf("Message-ID: <%s", mid);
1315 if (strchr(mid, '@')==NULL) {
1316 cprintf("@%s", snode);
1320 PerformUserHooks(luser, (-1L), EVT_OUTPUTMSG);
1322 if (!is_room_aide() && (TheMessage->cm_anon_type == MES_ANONONLY)) {
1323 cprintf("From: x@x.org (----)%s", nl);
1325 else if (!is_room_aide() && (TheMessage->cm_anon_type == MES_ANONOPT)) {
1326 cprintf("From: x@x.org (anonymous)%s", nl);
1328 else if (strlen(fuser) > 0) {
1329 cprintf("From: %s (%s)%s", fuser, luser, nl);
1332 cprintf("From: %s@%s (%s)%s", suser, snode, luser, nl);
1335 cprintf("Organization: %s%s", lnode, nl);
1337 /* Blank line signifying RFC822 end-of-headers */
1338 if (TheMessage->cm_format_type != FMT_RFC822) {
1343 /* end header processing loop ... at this point, we're in the text */
1345 mptr = TheMessage->cm_fields['M'];
1347 /* Tell the client about the MIME parts in this message */
1348 if (TheMessage->cm_format_type == FMT_RFC822) {
1349 if ( (mode == MT_CITADEL) || (mode == MT_MIME) ) {
1350 mime_parser(mptr, NULL,
1356 else if (mode == MT_RFC822) { /* unparsed RFC822 dump */
1357 /* FIXME ... we have to put some code in here to avoid
1358 * printing duplicate header information when both
1359 * Citadel and RFC822 headers exist. Preference should
1360 * probably be given to the RFC822 headers.
1362 int done_rfc822_hdrs = 0;
1363 while (ch=*(mptr++), ch!=0) {
1368 if (!done_rfc822_hdrs) {
1369 if (headers_only != HEADERS_NONE) {
1374 if (headers_only != HEADERS_ONLY) {
1378 if ((*(mptr) == 13) || (*(mptr) == 10)) {
1379 done_rfc822_hdrs = 1;
1383 if (done_rfc822_hdrs) {
1384 if (headers_only != HEADERS_NONE) {
1389 if (headers_only != HEADERS_ONLY) {
1393 if ((*mptr == 13) || (*mptr == 10)) {
1394 done_rfc822_hdrs = 1;
1402 if (headers_only == HEADERS_ONLY) {
1406 /* signify start of msg text */
1407 if ( (mode == MT_CITADEL) || (mode == MT_MIME) ) {
1408 if (do_proto) cprintf("text\n");
1411 /* If the format type on disk is 1 (fixed-format), then we want
1412 * everything to be output completely literally ... regardless of
1413 * what message transfer format is in use.
1415 if (TheMessage->cm_format_type == FMT_FIXED) {
1416 if (mode == MT_MIME) {
1417 cprintf("Content-type: text/plain\n\n");
1420 while (ch = *mptr++, ch > 0) {
1423 if ((ch == 10) || (strlen(buf) > 250)) {
1424 cprintf("%s%s", buf, nl);
1427 buf[strlen(buf) + 1] = 0;
1428 buf[strlen(buf)] = ch;
1431 if (strlen(buf) > 0)
1432 cprintf("%s%s", buf, nl);
1435 /* If the message on disk is format 0 (Citadel vari-format), we
1436 * output using the formatter at 80 columns. This is the final output
1437 * form if the transfer format is RFC822, but if the transfer format
1438 * is Citadel proprietary, it'll still work, because the indentation
1439 * for new paragraphs is correct and the client will reformat the
1440 * message to the reader's screen width.
1442 if (TheMessage->cm_format_type == FMT_CITADEL) {
1443 if (mode == MT_MIME) {
1444 cprintf("Content-type: text/x-citadel-variformat\n\n");
1446 memfmout(80, mptr, 0, nl);
1449 /* If the message on disk is format 4 (MIME), we've gotta hand it
1450 * off to the MIME parser. The client has already been told that
1451 * this message is format 1 (fixed format), so the callback function
1452 * we use will display those parts as-is.
1454 if (TheMessage->cm_format_type == FMT_RFC822) {
1455 CtdlAllocUserData(SYM_MA_INFO, sizeof(struct ma_info));
1456 memset(ma, 0, sizeof(struct ma_info));
1458 if (mode == MT_MIME) {
1459 strcpy(ma->chosen_part, "1");
1460 mime_parser(mptr, NULL,
1461 *choose_preferred, *fixed_output_pre,
1462 *fixed_output_post, NULL, 0);
1463 mime_parser(mptr, NULL,
1464 *output_preferred, NULL, NULL, NULL, 0);
1467 mime_parser(mptr, NULL,
1468 *fixed_output, *fixed_output_pre,
1469 *fixed_output_post, NULL, 0);
1473 DONE: /* now we're done */
1474 if (do_proto) cprintf("000\n");
1481 * display a message (mode 0 - Citadel proprietary)
1483 void cmd_msg0(char *cmdbuf)
1486 int headers_only = HEADERS_ALL;
1488 msgid = extract_long(cmdbuf, 0);
1489 headers_only = extract_int(cmdbuf, 1);
1491 CtdlOutputMsg(msgid, MT_CITADEL, headers_only, 1, 0);
1497 * display a message (mode 2 - RFC822)
1499 void cmd_msg2(char *cmdbuf)
1502 int headers_only = HEADERS_ALL;
1504 msgid = extract_long(cmdbuf, 0);
1505 headers_only = extract_int(cmdbuf, 1);
1507 CtdlOutputMsg(msgid, MT_RFC822, headers_only, 1, 1);
1513 * display a message (mode 3 - IGnet raw format - internal programs only)
1515 void cmd_msg3(char *cmdbuf)
1518 struct CtdlMessage *msg;
1521 if (CC->internal_pgm == 0) {
1522 cprintf("%d This command is for internal programs only.\n",
1527 msgnum = extract_long(cmdbuf, 0);
1528 msg = CtdlFetchMessage(msgnum);
1530 cprintf("%d Message %ld not found.\n",
1535 serialize_message(&smr, msg);
1536 CtdlFreeMessage(msg);
1539 cprintf("%d Unable to serialize message\n",
1540 ERROR+INTERNAL_ERROR);
1544 cprintf("%d %ld\n", BINARY_FOLLOWS, (long)smr.len);
1545 client_write(smr.ser, smr.len);
1552 * Display a message using MIME content types
1554 void cmd_msg4(char *cmdbuf)
1558 msgid = extract_long(cmdbuf, 0);
1559 CtdlOutputMsg(msgid, MT_MIME, 0, 1, 0);
1565 * Client tells us its preferred message format(s)
1567 void cmd_msgp(char *cmdbuf)
1569 safestrncpy(CC->preferred_formats, cmdbuf,
1570 sizeof(CC->preferred_formats));
1571 cprintf("%d ok\n", CIT_OK);
1576 * Open a component of a MIME message as a download file
1578 void cmd_opna(char *cmdbuf)
1582 CtdlAllocUserData(SYM_DESIRED_SECTION, SIZ);
1584 msgid = extract_long(cmdbuf, 0);
1585 extract(desired_section, cmdbuf, 1);
1587 CtdlOutputMsg(msgid, MT_DOWNLOAD, 0, 1, 1);
1592 * Save a message pointer into a specified room
1593 * (Returns 0 for success, nonzero for failure)
1594 * roomname may be NULL to use the current room
1596 int CtdlSaveMsgPointerInRoom(char *roomname, long msgid, int flags) {
1598 char hold_rm[ROOMNAMELEN];
1599 struct cdbdata *cdbfr;
1602 long highest_msg = 0L;
1603 struct CtdlMessage *msg = NULL;
1605 lprintf(9, "CtdlSaveMsgPointerInRoom(%s, %ld, %d)\n",
1606 roomname, msgid, flags);
1608 strcpy(hold_rm, CC->room.QRname);
1610 /* We may need to check to see if this message is real */
1611 if ( (flags & SM_VERIFY_GOODNESS)
1612 || (flags & SM_DO_REPL_CHECK)
1614 msg = CtdlFetchMessage(msgid);
1615 if (msg == NULL) return(ERROR + ILLEGAL_VALUE);
1618 /* Perform replication checks if necessary */
1619 if ( (flags & SM_DO_REPL_CHECK) && (msg != NULL) ) {
1621 if (getroom(&CC->room,
1622 ((roomname != NULL) ? roomname : CC->room.QRname) )
1624 lprintf(9, "No such room <%s>\n", roomname);
1625 if (msg != NULL) CtdlFreeMessage(msg);
1626 return(ERROR + ROOM_NOT_FOUND);
1629 if (ReplicationChecks(msg) != 0) {
1630 getroom(&CC->room, hold_rm);
1631 if (msg != NULL) CtdlFreeMessage(msg);
1632 lprintf(9, "Did replication, and newer exists\n");
1637 /* Now the regular stuff */
1638 if (lgetroom(&CC->room,
1639 ((roomname != NULL) ? roomname : CC->room.QRname) )
1641 lprintf(9, "No such room <%s>\n", roomname);
1642 if (msg != NULL) CtdlFreeMessage(msg);
1643 return(ERROR + ROOM_NOT_FOUND);
1646 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long));
1647 if (cdbfr == NULL) {
1651 msglist = mallok(cdbfr->len);
1652 if (msglist == NULL)
1653 lprintf(3, "ERROR malloc msglist!\n");
1654 num_msgs = cdbfr->len / sizeof(long);
1655 memcpy(msglist, cdbfr->ptr, cdbfr->len);
1660 /* Make sure the message doesn't already exist in this room. It
1661 * is absolutely taboo to have more than one reference to the same
1662 * message in a room.
1664 if (num_msgs > 0) for (i=0; i<num_msgs; ++i) {
1665 if (msglist[i] == msgid) {
1666 lputroom(&CC->room); /* unlock the room */
1667 getroom(&CC->room, hold_rm);
1668 if (msg != NULL) CtdlFreeMessage(msg);
1669 return(ERROR + ALREADY_EXISTS);
1673 /* Now add the new message */
1675 msglist = reallok(msglist,
1676 (num_msgs * sizeof(long)));
1678 if (msglist == NULL) {
1679 lprintf(3, "ERROR: can't realloc message list!\n");
1681 msglist[num_msgs - 1] = msgid;
1683 /* Sort the message list, so all the msgid's are in order */
1684 num_msgs = sort_msglist(msglist, num_msgs);
1686 /* Determine the highest message number */
1687 highest_msg = msglist[num_msgs - 1];
1689 /* Write it back to disk. */
1690 cdb_store(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long),
1691 msglist, num_msgs * sizeof(long));
1693 /* Free up the memory we used. */
1696 /* Update the highest-message pointer and unlock the room. */
1697 CC->room.QRhighest = highest_msg;
1698 lputroom(&CC->room);
1699 getroom(&CC->room, hold_rm);
1701 /* Bump the reference count for this message. */
1702 if ((flags & SM_DONT_BUMP_REF)==0) {
1703 AdjRefCount(msgid, +1);
1706 /* Return success. */
1707 if (msg != NULL) CtdlFreeMessage(msg);
1714 * Message base operation to send a message to the master file
1715 * (returns new message number)
1717 * This is the back end for CtdlSubmitMsg() and should not be directly
1718 * called by server-side modules.
1721 long send_message(struct CtdlMessage *msg, /* pointer to buffer */
1722 FILE *save_a_copy) /* save a copy to disk? */
1729 /* Get a new message number */
1730 newmsgid = get_new_message_number();
1731 snprintf(msgidbuf, sizeof msgidbuf, "%ld@%s", newmsgid, config.c_fqdn);
1733 /* Generate an ID if we don't have one already */
1734 if (msg->cm_fields['I']==NULL) {
1735 msg->cm_fields['I'] = strdoop(msgidbuf);
1738 serialize_message(&smr, msg);
1741 cprintf("%d Unable to serialize message\n",
1742 ERROR+INTERNAL_ERROR);
1746 /* Write our little bundle of joy into the message base */
1747 if (cdb_store(CDB_MSGMAIN, &newmsgid, sizeof(long),
1748 smr.ser, smr.len) < 0) {
1749 lprintf(2, "Can't store message\n");
1755 /* If the caller specified that a copy should be saved to a particular
1756 * file handle, do that now too.
1758 if (save_a_copy != NULL) {
1759 fwrite(smr.ser, smr.len, 1, save_a_copy);
1762 /* Free the memory we used for the serialized message */
1765 /* Return the *local* message ID to the caller
1766 * (even if we're storing an incoming network message)
1774 * Serialize a struct CtdlMessage into the format used on disk and network.
1776 * This function loads up a "struct ser_ret" (defined in server.h) which
1777 * contains the length of the serialized message and a pointer to the
1778 * serialized message in memory. THE LATTER MUST BE FREED BY THE CALLER.
1780 void serialize_message(struct ser_ret *ret, /* return values */
1781 struct CtdlMessage *msg) /* unserialized msg */
1785 static char *forder = FORDER;
1787 if (is_valid_message(msg) == 0) return; /* self check */
1790 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL)
1791 ret->len = ret->len +
1792 strlen(msg->cm_fields[(int)forder[i]]) + 2;
1794 lprintf(9, "serialize_message() calling malloc(%ld)\n", (long)ret->len);
1795 ret->ser = mallok(ret->len);
1796 if (ret->ser == NULL) {
1802 ret->ser[1] = msg->cm_anon_type;
1803 ret->ser[2] = msg->cm_format_type;
1806 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL) {
1807 ret->ser[wlen++] = (char)forder[i];
1808 strcpy(&ret->ser[wlen], msg->cm_fields[(int)forder[i]]);
1809 wlen = wlen + strlen(msg->cm_fields[(int)forder[i]]) + 1;
1811 if (ret->len != wlen) lprintf(3, "ERROR: len=%ld wlen=%ld\n",
1812 (long)ret->len, (long)wlen);
1820 * Back end for the ReplicationChecks() function
1822 void check_repl(long msgnum, void *userdata) {
1823 struct CtdlMessage *msg;
1824 time_t timestamp = (-1L);
1826 lprintf(9, "check_repl() found message %ld\n", msgnum);
1827 msg = CtdlFetchMessage(msgnum);
1828 if (msg == NULL) return;
1829 if (msg->cm_fields['T'] != NULL) {
1830 timestamp = atol(msg->cm_fields['T']);
1832 CtdlFreeMessage(msg);
1834 if (timestamp > msg_repl->highest) {
1835 msg_repl->highest = timestamp; /* newer! */
1836 lprintf(9, "newer!\n");
1839 lprintf(9, "older!\n");
1841 /* Existing isn't newer? Then delete the old one(s). */
1842 CtdlDeleteMessages(CC->room.QRname, msgnum, "");
1847 * Check to see if any messages already exist which carry the same Extended ID
1851 * -> With older timestamps: delete them and return 0. Message will be saved.
1852 * -> With newer timestamps: return 1. Message save will be aborted.
1854 int ReplicationChecks(struct CtdlMessage *msg) {
1855 struct CtdlMessage *template;
1858 lprintf(9, "ReplicationChecks() started\n");
1859 /* No extended id? Don't do anything. */
1860 if (msg->cm_fields['E'] == NULL) return 0;
1861 if (strlen(msg->cm_fields['E']) == 0) return 0;
1862 lprintf(9, "Extended ID: <%s>\n", msg->cm_fields['E']);
1864 CtdlAllocUserData(SYM_REPL, sizeof(struct repl));
1865 strcpy(msg_repl->extended_id, msg->cm_fields['E']);
1866 msg_repl->highest = atol(msg->cm_fields['T']);
1868 template = (struct CtdlMessage *) malloc(sizeof(struct CtdlMessage));
1869 memset(template, 0, sizeof(struct CtdlMessage));
1870 template->cm_fields['E'] = strdoop(msg->cm_fields['E']);
1872 CtdlForEachMessage(MSGS_ALL, 0L, NULL, template, check_repl, NULL);
1874 /* If a newer message exists with the same Extended ID, abort
1877 if (msg_repl->highest > atol(msg->cm_fields['T']) ) {
1881 CtdlFreeMessage(template);
1882 lprintf(9, "ReplicationChecks() returning %d\n", abort_this);
1890 * Save a message to disk and submit it into the delivery system.
1892 long CtdlSubmitMsg(struct CtdlMessage *msg, /* message to save */
1893 struct recptypes *recps, /* recipients (if mail) */
1894 char *force /* force a particular room? */
1897 char hold_rm[ROOMNAMELEN];
1898 char actual_rm[ROOMNAMELEN];
1899 char force_room[ROOMNAMELEN];
1900 char content_type[SIZ]; /* We have to learn this */
1901 char recipient[SIZ];
1904 struct ctdluser userbuf;
1906 struct MetaData smi;
1907 FILE *network_fp = NULL;
1908 static int seqnum = 1;
1909 struct CtdlMessage *imsg = NULL;
1912 char *hold_R, *hold_D;
1914 lprintf(9, "CtdlSubmitMsg() called\n");
1915 if (is_valid_message(msg) == 0) return(-1); /* self check */
1917 /* If this message has no timestamp, we take the liberty of
1918 * giving it one, right now.
1920 if (msg->cm_fields['T'] == NULL) {
1921 lprintf(9, "Generating timestamp\n");
1922 snprintf(aaa, sizeof aaa, "%ld", (long)time(NULL));
1923 msg->cm_fields['T'] = strdoop(aaa);
1926 /* If this message has no path, we generate one.
1928 if (msg->cm_fields['P'] == NULL) {
1929 lprintf(9, "Generating path\n");
1930 if (msg->cm_fields['A'] != NULL) {
1931 msg->cm_fields['P'] = strdoop(msg->cm_fields['A']);
1932 for (a=0; a<strlen(msg->cm_fields['P']); ++a) {
1933 if (isspace(msg->cm_fields['P'][a])) {
1934 msg->cm_fields['P'][a] = ' ';
1939 msg->cm_fields['P'] = strdoop("unknown");
1943 if (force == NULL) {
1944 strcpy(force_room, "");
1947 strcpy(force_room, force);
1950 /* Learn about what's inside, because it's what's inside that counts */
1951 lprintf(9, "Learning what's inside\n");
1952 if (msg->cm_fields['M'] == NULL) {
1953 lprintf(1, "ERROR: attempt to save message with NULL body\n");
1956 switch (msg->cm_format_type) {
1958 strcpy(content_type, "text/x-citadel-variformat");
1961 strcpy(content_type, "text/plain");
1964 strcpy(content_type, "text/plain");
1965 /* advance past header fields */
1966 mptr = msg->cm_fields['M'];
1969 if (!strncasecmp(mptr, "Content-type: ", 14)) {
1970 safestrncpy(content_type, mptr,
1971 sizeof(content_type));
1972 strcpy(content_type, &content_type[14]);
1973 for (a = 0; a < strlen(content_type); ++a)
1974 if ((content_type[a] == ';')
1975 || (content_type[a] == ' ')
1976 || (content_type[a] == 13)
1977 || (content_type[a] == 10))
1978 content_type[a] = 0;
1985 /* Goto the correct room */
1986 lprintf(9, "Selected room %s\n", (recps) ? CC->room.QRname : SENTITEMS);
1987 strcpy(hold_rm, CC->room.QRname);
1988 strcpy(actual_rm, CC->room.QRname);
1989 if (recps != NULL) {
1990 strcpy(actual_rm, SENTITEMS);
1993 /* If the user is a twit, move to the twit room for posting */
1994 lprintf(9, "Handling twit stuff: %s\n",
1995 (CC->user.axlevel == 2) ? config.c_twitroom : "OK");
1997 if (CC->user.axlevel == 2) {
1998 strcpy(hold_rm, actual_rm);
1999 strcpy(actual_rm, config.c_twitroom);
2003 /* ...or if this message is destined for Aide> then go there. */
2004 if (strlen(force_room) > 0) {
2005 strcpy(actual_rm, force_room);
2008 lprintf(9, "Final selection: %s\n", actual_rm);
2009 if (strcasecmp(actual_rm, CC->room.QRname)) {
2010 getroom(&CC->room, actual_rm);
2014 * If this message has no O (room) field, generate one.
2016 if (msg->cm_fields['O'] == NULL) {
2017 msg->cm_fields['O'] = strdoop(CC->room.QRname);
2020 /* Perform "before save" hooks (aborting if any return nonzero) */
2021 lprintf(9, "Performing before-save hooks\n");
2022 if (PerformMessageHooks(msg, EVT_BEFORESAVE) > 0) return(-1);
2024 /* If this message has an Extended ID, perform replication checks */
2025 lprintf(9, "Performing replication checks\n");
2026 if (ReplicationChecks(msg) > 0) return(-1);
2028 /* Save it to disk */
2029 lprintf(9, "Saving to disk\n");
2030 newmsgid = send_message(msg, NULL);
2031 if (newmsgid <= 0L) return(-1);
2033 /* Write a supplemental message info record. This doesn't have to
2034 * be a critical section because nobody else knows about this message
2037 lprintf(9, "Creating MetaData record\n");
2038 memset(&smi, 0, sizeof(struct MetaData));
2039 smi.meta_msgnum = newmsgid;
2040 smi.meta_refcount = 0;
2041 safestrncpy(smi.meta_content_type, content_type, 64);
2044 /* Now figure out where to store the pointers */
2045 lprintf(9, "Storing pointers\n");
2047 /* If this is being done by the networker delivering a private
2048 * message, we want to BYPASS saving the sender's copy (because there
2049 * is no local sender; it would otherwise go to the Trashcan).
2051 if ((!CC->internal_pgm) || (recps == NULL)) {
2052 if (CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 0) != 0) {
2053 lprintf(3, "ERROR saving message pointer!\n");
2054 CtdlSaveMsgPointerInRoom(config.c_aideroom, newmsgid, 0);
2058 /* For internet mail, drop a copy in the outbound queue room */
2060 if (recps->num_internet > 0) {
2061 CtdlSaveMsgPointerInRoom(SMTP_SPOOLOUT_ROOM, newmsgid, 0);
2064 /* If other rooms are specified, drop them there too. */
2066 if (recps->num_room > 0)
2067 for (i=0; i<num_tokens(recps->recp_room, '|'); ++i) {
2068 extract(recipient, recps->recp_room, i);
2069 lprintf(9, "Delivering to local room <%s>\n", recipient);
2070 CtdlSaveMsgPointerInRoom(recipient, newmsgid, 0);
2073 /* Bump this user's messages posted counter. */
2074 lprintf(9, "Updating user\n");
2075 lgetuser(&CC->user, CC->curr_user);
2076 CC->user.posted = CC->user.posted + 1;
2077 lputuser(&CC->user);
2079 /* If this is private, local mail, make a copy in the
2080 * recipient's mailbox and bump the reference count.
2083 if (recps->num_local > 0)
2084 for (i=0; i<num_tokens(recps->recp_local, '|'); ++i) {
2085 extract(recipient, recps->recp_local, i);
2086 lprintf(9, "Delivering private local mail to <%s>\n",
2088 if (getuser(&userbuf, recipient) == 0) {
2089 MailboxName(actual_rm, sizeof actual_rm, &userbuf, MAILROOM);
2090 CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 0);
2091 BumpNewMailCounter(userbuf.usernum);
2094 lprintf(9, "No user <%s>\n", recipient);
2095 CtdlSaveMsgPointerInRoom(config.c_aideroom, newmsgid, 0);
2099 /* Perform "after save" hooks */
2100 lprintf(9, "Performing after-save hooks\n");
2101 PerformMessageHooks(msg, EVT_AFTERSAVE);
2103 /* For IGnet mail, we have to save a new copy into the spooler for
2104 * each recipient, with the R and D fields set to the recipient and
2105 * destination-node. This has two ugly side effects: all other
2106 * recipients end up being unlisted in this recipient's copy of the
2107 * message, and it has to deliver multiple messages to the same
2108 * node. We'll revisit this again in a year or so when everyone has
2109 * a network spool receiver that can handle the new style messages.
2112 if (recps->num_ignet > 0)
2113 for (i=0; i<num_tokens(recps->recp_ignet, '|'); ++i) {
2114 extract(recipient, recps->recp_ignet, i);
2116 hold_R = msg->cm_fields['R'];
2117 hold_D = msg->cm_fields['D'];
2118 msg->cm_fields['R'] = mallok(SIZ);
2119 msg->cm_fields['D'] = mallok(SIZ);
2120 extract_token(msg->cm_fields['R'], recipient, 0, '@');
2121 extract_token(msg->cm_fields['D'], recipient, 1, '@');
2123 serialize_message(&smr, msg);
2125 snprintf(aaa, sizeof aaa,
2126 "./network/spoolin/netmail.%04lx.%04x.%04x",
2127 (long) getpid(), CC->cs_pid, ++seqnum);
2128 network_fp = fopen(aaa, "wb+");
2129 if (network_fp != NULL) {
2130 fwrite(smr.ser, smr.len, 1, network_fp);
2136 phree(msg->cm_fields['R']);
2137 phree(msg->cm_fields['D']);
2138 msg->cm_fields['R'] = hold_R;
2139 msg->cm_fields['D'] = hold_D;
2142 /* Go back to the room we started from */
2143 lprintf(9, "Returning to original room %s\n", hold_rm);
2144 if (strcasecmp(hold_rm, CC->room.QRname))
2145 getroom(&CC->room, hold_rm);
2147 /* For internet mail, generate delivery instructions.
2148 * Yes, this is recursive. Deal with it. Infinite recursion does
2149 * not happen because the delivery instructions message does not
2150 * contain a recipient.
2153 if (recps->num_internet > 0) {
2154 lprintf(9, "Generating delivery instructions\n");
2155 instr = mallok(SIZ * 2);
2156 snprintf(instr, SIZ * 2,
2157 "Content-type: %s\n\nmsgid|%ld\nsubmitted|%ld\n"
2159 SPOOLMIME, newmsgid, (long)time(NULL),
2160 msg->cm_fields['A'], msg->cm_fields['N']
2163 for (i=0; i<num_tokens(recps->recp_internet, '|'); ++i) {
2164 size_t tmp = strlen(instr);
2165 extract(recipient, recps->recp_internet, i);
2166 snprintf(&instr[tmp], SIZ * 2 - tmp,
2167 "remote|%s|0||\n", recipient);
2170 imsg = mallok(sizeof(struct CtdlMessage));
2171 memset(imsg, 0, sizeof(struct CtdlMessage));
2172 imsg->cm_magic = CTDLMESSAGE_MAGIC;
2173 imsg->cm_anon_type = MES_NORMAL;
2174 imsg->cm_format_type = FMT_RFC822;
2175 imsg->cm_fields['A'] = strdoop("Citadel");
2176 imsg->cm_fields['M'] = instr;
2177 CtdlSubmitMsg(imsg, NULL, SMTP_SPOOLOUT_ROOM);
2178 CtdlFreeMessage(imsg);
2187 * Convenience function for generating small administrative messages.
2189 void quickie_message(char *from, char *to, char *room, char *text,
2190 int format_type, char *subject)
2192 struct CtdlMessage *msg;
2193 struct recptypes *recp = NULL;
2195 msg = mallok(sizeof(struct CtdlMessage));
2196 memset(msg, 0, sizeof(struct CtdlMessage));
2197 msg->cm_magic = CTDLMESSAGE_MAGIC;
2198 msg->cm_anon_type = MES_NORMAL;
2199 msg->cm_format_type = format_type;
2200 msg->cm_fields['A'] = strdoop(from);
2201 if (room != NULL) msg->cm_fields['O'] = strdoop(room);
2202 msg->cm_fields['N'] = strdoop(NODENAME);
2204 msg->cm_fields['R'] = strdoop(to);
2205 recp = validate_recipients(to);
2207 if (subject != NULL) {
2208 msg->cm_fields['U'] = strdoop(subject);
2210 msg->cm_fields['M'] = strdoop(text);
2212 CtdlSubmitMsg(msg, recp, room);
2213 CtdlFreeMessage(msg);
2214 if (recp != NULL) phree(recp);
2220 * Back end function used by CtdlMakeMessage() and similar functions
2222 char *CtdlReadMessageBody(char *terminator, /* token signalling EOT */
2223 size_t maxlen, /* maximum message length */
2224 char *exist, /* if non-null, append to it;
2225 exist is ALWAYS freed */
2226 int crlf /* CRLF newlines instead of LF */
2230 size_t message_len = 0;
2231 size_t buffer_len = 0;
2237 if (exist == NULL) {
2244 message_len = strlen(exist);
2245 buffer_len = message_len + 4096;
2246 m = reallok(exist, buffer_len);
2253 /* flush the input if we have nowhere to store it */
2258 /* read in the lines of message text one by one */
2260 if (client_gets(buf) < 1) finished = 1;
2261 if (!strcmp(buf, terminator)) finished = 1;
2263 strcat(buf, "\r\n");
2269 if ( (!flushing) && (!finished) ) {
2270 /* Measure the line */
2271 linelen = strlen(buf);
2273 /* augment the buffer if we have to */
2274 if ((message_len + linelen) >= buffer_len) {
2275 ptr = reallok(m, (buffer_len * 2) );
2276 if (ptr == NULL) { /* flush if can't allocate */
2279 buffer_len = (buffer_len * 2);
2281 lprintf(9, "buffer_len is now %ld\n", (long)buffer_len);
2285 /* Add the new line to the buffer. NOTE: this loop must avoid
2286 * using functions like strcat() and strlen() because they
2287 * traverse the entire buffer upon every call, and doing that
2288 * for a multi-megabyte message slows it down beyond usability.
2290 strcpy(&m[message_len], buf);
2291 message_len += linelen;
2294 /* if we've hit the max msg length, flush the rest */
2295 if (message_len >= maxlen) flushing = 1;
2297 } while (!finished);
2305 * Build a binary message to be saved on disk.
2306 * (NOTE: if you supply 'preformatted_text', the buffer you give it
2307 * will become part of the message. This means you are no longer
2308 * responsible for managing that memory -- it will be freed along with
2309 * the rest of the fields when CtdlFreeMessage() is called.)
2312 struct CtdlMessage *CtdlMakeMessage(
2313 struct ctdluser *author, /* author's user structure */
2314 char *recipient, /* NULL if it's not mail */
2315 char *room, /* room where it's going */
2316 int type, /* see MES_ types in header file */
2317 int format_type, /* variformat, plain text, MIME... */
2318 char *fake_name, /* who we're masquerading as */
2319 char *subject, /* Subject (optional) */
2320 char *preformatted_text /* ...or NULL to read text from client */
2322 char dest_node[SIZ];
2324 struct CtdlMessage *msg;
2326 msg = mallok(sizeof(struct CtdlMessage));
2327 memset(msg, 0, sizeof(struct CtdlMessage));
2328 msg->cm_magic = CTDLMESSAGE_MAGIC;
2329 msg->cm_anon_type = type;
2330 msg->cm_format_type = format_type;
2332 /* Don't confuse the poor folks if it's not routed mail. */
2333 strcpy(dest_node, "");
2337 snprintf(buf, sizeof buf, "cit%ld", author->usernum); /* Path */
2338 msg->cm_fields['P'] = strdoop(buf);
2340 snprintf(buf, sizeof buf, "%ld", (long)time(NULL)); /* timestamp */
2341 msg->cm_fields['T'] = strdoop(buf);
2343 if (fake_name[0]) /* author */
2344 msg->cm_fields['A'] = strdoop(fake_name);
2346 msg->cm_fields['A'] = strdoop(author->fullname);
2348 if (CC->room.QRflags & QR_MAILBOX) { /* room */
2349 msg->cm_fields['O'] = strdoop(&CC->room.QRname[11]);
2352 msg->cm_fields['O'] = strdoop(CC->room.QRname);
2355 msg->cm_fields['N'] = strdoop(NODENAME); /* nodename */
2356 msg->cm_fields['H'] = strdoop(HUMANNODE); /* hnodename */
2358 if (recipient[0] != 0) {
2359 msg->cm_fields['R'] = strdoop(recipient);
2361 if (dest_node[0] != 0) {
2362 msg->cm_fields['D'] = strdoop(dest_node);
2365 if ( (author == &CC->user) && (strlen(CC->cs_inet_email) > 0) ) {
2366 msg->cm_fields['F'] = strdoop(CC->cs_inet_email);
2369 if (subject != NULL) {
2371 if (strlen(subject) > 0) {
2372 msg->cm_fields['U'] = strdoop(subject);
2376 if (preformatted_text != NULL) {
2377 msg->cm_fields['M'] = preformatted_text;
2380 msg->cm_fields['M'] = CtdlReadMessageBody("000",
2381 config.c_maxmsglen, NULL, 0);
2389 * Check to see whether we have permission to post a message in the current
2390 * room. Returns a *CITADEL ERROR CODE* and puts a message in errmsgbuf, or
2391 * returns 0 on success.
2393 int CtdlDoIHavePermissionToPostInThisRoom(char *errmsgbuf, size_t n) {
2395 if (!(CC->logged_in)) {
2396 snprintf(errmsgbuf, n, "Not logged in.");
2397 return (ERROR + NOT_LOGGED_IN);
2400 if ((CC->user.axlevel < 2)
2401 && ((CC->room.QRflags & QR_MAILBOX) == 0)) {
2402 snprintf(errmsgbuf, n, "Need to be validated to enter "
2403 "(except in %s> to sysop)", MAILROOM);
2404 return (ERROR + HIGHER_ACCESS_REQUIRED);
2407 if ((CC->user.axlevel < 4)
2408 && (CC->room.QRflags & QR_NETWORK)) {
2409 snprintf(errmsgbuf, n, "Need net privileges to enter here.");
2410 return (ERROR + HIGHER_ACCESS_REQUIRED);
2413 if ((CC->user.axlevel < 6)
2414 && (CC->room.QRflags & QR_READONLY)) {
2415 snprintf(errmsgbuf, n, "Sorry, this is a read-only room.");
2416 return (ERROR + HIGHER_ACCESS_REQUIRED);
2419 strcpy(errmsgbuf, "Ok");
2425 * Check to see if the specified user has Internet mail permission
2426 * (returns nonzero if permission is granted)
2428 int CtdlCheckInternetMailPermission(struct ctdluser *who) {
2430 /* Globally enabled? */
2431 if (config.c_restrict == 0) return(1);
2433 /* User flagged ok? */
2434 if (who->flags & US_INTERNET) return(2);
2436 /* Aide level access? */
2437 if (who->axlevel >= 6) return(3);
2439 /* No mail for you! */
2446 * Validate recipients, count delivery types and errors, and handle aliasing
2447 * FIXME check for dupes!!!!!
2449 struct recptypes *validate_recipients(char *recipients) {
2450 struct recptypes *ret;
2451 char this_recp[SIZ];
2452 char this_recp_cooked[SIZ];
2458 struct ctdluser tempUS;
2459 struct ctdlroom tempQR;
2462 ret = (struct recptypes *) malloc(sizeof(struct recptypes));
2463 if (ret == NULL) return(NULL);
2464 memset(ret, 0, sizeof(struct recptypes));
2467 ret->num_internet = 0;
2472 if (recipients == NULL) {
2475 else if (strlen(recipients) == 0) {
2479 /* Change all valid separator characters to commas */
2480 for (i=0; i<strlen(recipients); ++i) {
2481 if ((recipients[i] == ';') || (recipients[i] == '|')) {
2482 recipients[i] = ',';
2487 num_recps = num_tokens(recipients, ',');
2490 if (num_recps > 0) for (i=0; i<num_recps; ++i) {
2491 extract_token(this_recp, recipients, i, ',');
2493 lprintf(9, "Evaluating recipient #%d <%s>\n", i, this_recp);
2494 mailtype = alias(this_recp);
2495 mailtype = alias(this_recp);
2496 mailtype = alias(this_recp);
2497 for (j=0; j<=strlen(this_recp); ++j) {
2498 if (this_recp[j]=='_') {
2499 this_recp_cooked[j] = ' ';
2502 this_recp_cooked[j] = this_recp[j];
2508 if (!strcasecmp(this_recp, "sysop")) {
2510 strcpy(this_recp, config.c_aideroom);
2511 if (strlen(ret->recp_room) > 0) {
2512 strcat(ret->recp_room, "|");
2514 strcat(ret->recp_room, this_recp);
2516 else if (getuser(&tempUS, this_recp) == 0) {
2518 strcpy(this_recp, tempUS.fullname);
2519 if (strlen(ret->recp_local) > 0) {
2520 strcat(ret->recp_local, "|");
2522 strcat(ret->recp_local, this_recp);
2524 else if (getuser(&tempUS, this_recp_cooked) == 0) {
2526 strcpy(this_recp, tempUS.fullname);
2527 if (strlen(ret->recp_local) > 0) {
2528 strcat(ret->recp_local, "|");
2530 strcat(ret->recp_local, this_recp);
2532 else if ( (!strncasecmp(this_recp, "room_", 5))
2533 && (!getroom(&tempQR, &this_recp_cooked[5])) ) {
2535 if (strlen(ret->recp_room) > 0) {
2536 strcat(ret->recp_room, "|");
2538 strcat(ret->recp_room, &this_recp_cooked[5]);
2546 /* Yes, you're reading this correctly: if the target
2547 * domain points back to the local system or an attached
2548 * Citadel directory, the address is invalid. That's
2549 * because if the address were valid, we would have
2550 * already translated it to a local address by now.
2552 if (IsDirectory(this_recp)) {
2557 ++ret->num_internet;
2558 if (strlen(ret->recp_internet) > 0) {
2559 strcat(ret->recp_internet, "|");
2561 strcat(ret->recp_internet, this_recp);
2566 if (strlen(ret->recp_ignet) > 0) {
2567 strcat(ret->recp_ignet, "|");
2569 strcat(ret->recp_ignet, this_recp);
2577 if (strlen(ret->errormsg) == 0) {
2578 snprintf(append, sizeof append,
2579 "Invalid recipient: %s",
2583 snprintf(append, sizeof append,
2586 if ( (strlen(ret->errormsg) + strlen(append)) < SIZ) {
2587 strcat(ret->errormsg, append);
2591 if (strlen(ret->display_recp) == 0) {
2592 strcpy(append, this_recp);
2595 snprintf(append, sizeof append, ", %s",
2598 if ( (strlen(ret->display_recp)+strlen(append)) < SIZ) {
2599 strcat(ret->display_recp, append);
2604 if ((ret->num_local + ret->num_internet + ret->num_ignet +
2605 ret->num_room + ret->num_error) == 0) {
2607 strcpy(ret->errormsg, "No recipients specified.");
2610 lprintf(9, "validate_recipients()\n");
2611 lprintf(9, " local: %d <%s>\n", ret->num_local, ret->recp_local);
2612 lprintf(9, " room: %d <%s>\n", ret->num_room, ret->recp_room);
2613 lprintf(9, " inet: %d <%s>\n", ret->num_internet, ret->recp_internet);
2614 lprintf(9, " ignet: %d <%s>\n", ret->num_ignet, ret->recp_ignet);
2615 lprintf(9, " error: %d <%s>\n", ret->num_error, ret->errormsg);
2623 * message entry - mode 0 (normal)
2625 void cmd_ent0(char *entargs)
2629 char masquerade_as[SIZ];
2631 int format_type = 0;
2632 char newusername[SIZ];
2633 struct CtdlMessage *msg;
2637 struct recptypes *valid = NULL;
2640 post = extract_int(entargs, 0);
2641 extract(recp, entargs, 1);
2642 anon_flag = extract_int(entargs, 2);
2643 format_type = extract_int(entargs, 3);
2644 extract(subject, entargs, 4);
2646 /* first check to make sure the request is valid. */
2648 err = CtdlDoIHavePermissionToPostInThisRoom(errmsg, sizeof errmsg);
2650 cprintf("%d %s\n", err, errmsg);
2654 /* Check some other permission type things. */
2657 if (CC->user.axlevel < 6) {
2658 cprintf("%d You don't have permission to masquerade.\n",
2659 ERROR + HIGHER_ACCESS_REQUIRED);
2662 extract(newusername, entargs, 5);
2663 memset(CC->fake_postname, 0, sizeof(CC->fake_postname) );
2664 safestrncpy(CC->fake_postname, newusername,
2665 sizeof(CC->fake_postname) );
2666 cprintf("%d ok\n", CIT_OK);
2669 CC->cs_flags |= CS_POSTING;
2671 /* In the Mail> room we have to behave a little differently --
2672 * make sure the user has specified at least one recipient. Then
2673 * validate the recipient(s).
2675 if ( (CC->room.QRflags & QR_MAILBOX)
2676 && (!strcasecmp(&CC->room.QRname[11], MAILROOM)) ) {
2678 if (CC->user.axlevel < 2) {
2679 strcpy(recp, "sysop");
2682 valid = validate_recipients(recp);
2683 if (valid->num_error > 0) {
2685 ERROR + NO_SUCH_USER, valid->errormsg);
2689 if (valid->num_internet > 0) {
2690 if (CtdlCheckInternetMailPermission(&CC->user)==0) {
2691 cprintf("%d You do not have permission "
2692 "to send Internet mail.\n",
2693 ERROR + HIGHER_ACCESS_REQUIRED);
2699 if ( ( (valid->num_internet + valid->num_ignet) > 0)
2700 && (CC->user.axlevel < 4) ) {
2701 cprintf("%d Higher access required for network mail.\n",
2702 ERROR + HIGHER_ACCESS_REQUIRED);
2707 if ((RESTRICT_INTERNET == 1) && (valid->num_internet > 0)
2708 && ((CC->user.flags & US_INTERNET) == 0)
2709 && (!CC->internal_pgm)) {
2710 cprintf("%d You don't have access to Internet mail.\n",
2711 ERROR + HIGHER_ACCESS_REQUIRED);
2718 /* Is this a room which has anonymous-only or anonymous-option? */
2719 anonymous = MES_NORMAL;
2720 if (CC->room.QRflags & QR_ANONONLY) {
2721 anonymous = MES_ANONONLY;
2723 if (CC->room.QRflags & QR_ANONOPT) {
2724 if (anon_flag == 1) { /* only if the user requested it */
2725 anonymous = MES_ANONOPT;
2729 if ((CC->room.QRflags & QR_MAILBOX) == 0) {
2733 /* If we're only checking the validity of the request, return
2734 * success without creating the message.
2737 cprintf("%d %s\n", CIT_OK,
2738 ((valid != NULL) ? valid->display_recp : "") );
2743 /* Handle author masquerading */
2744 if (CC->fake_postname[0]) {
2745 strcpy(masquerade_as, CC->fake_postname);
2747 else if (CC->fake_username[0]) {
2748 strcpy(masquerade_as, CC->fake_username);
2751 strcpy(masquerade_as, "");
2754 /* Read in the message from the client. */
2755 cprintf("%d send message\n", SEND_LISTING);
2756 msg = CtdlMakeMessage(&CC->user, recp,
2757 CC->room.QRname, anonymous, format_type,
2758 masquerade_as, subject, NULL);
2761 CtdlSubmitMsg(msg, valid, "");
2762 CtdlFreeMessage(msg);
2764 CC->fake_postname[0] = '\0';
2772 * API function to delete messages which match a set of criteria
2773 * (returns the actual number of messages deleted)
2775 int CtdlDeleteMessages(char *room_name, /* which room */
2776 long dmsgnum, /* or "0" for any */
2777 char *content_type /* or "" for any */
2781 struct ctdlroom qrbuf;
2782 struct cdbdata *cdbfr;
2783 long *msglist = NULL;
2784 long *dellist = NULL;
2787 int num_deleted = 0;
2789 struct MetaData smi;
2791 lprintf(9, "CtdlDeleteMessages(%s, %ld, %s)\n",
2792 room_name, dmsgnum, content_type);
2794 /* get room record, obtaining a lock... */
2795 if (lgetroom(&qrbuf, room_name) != 0) {
2796 lprintf(7, "CtdlDeleteMessages(): Room <%s> not found\n",
2798 return (0); /* room not found */
2800 cdbfr = cdb_fetch(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long));
2802 if (cdbfr != NULL) {
2803 msglist = mallok(cdbfr->len);
2804 dellist = mallok(cdbfr->len);
2805 memcpy(msglist, cdbfr->ptr, cdbfr->len);
2806 num_msgs = cdbfr->len / sizeof(long);
2810 for (i = 0; i < num_msgs; ++i) {
2813 /* Set/clear a bit for each criterion */
2815 if ((dmsgnum == 0L) || (msglist[i] == dmsgnum)) {
2816 delete_this |= 0x01;
2818 if (strlen(content_type) == 0) {
2819 delete_this |= 0x02;
2821 GetMetaData(&smi, msglist[i]);
2822 if (!strcasecmp(smi.meta_content_type,
2824 delete_this |= 0x02;
2828 /* Delete message only if all bits are set */
2829 if (delete_this == 0x03) {
2830 dellist[num_deleted++] = msglist[i];
2835 num_msgs = sort_msglist(msglist, num_msgs);
2836 cdb_store(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long),
2837 msglist, (num_msgs * sizeof(long)));
2839 qrbuf.QRhighest = msglist[num_msgs - 1];
2843 /* Go through the messages we pulled out of the index, and decrement
2844 * their reference counts by 1. If this is the only room the message
2845 * was in, the reference count will reach zero and the message will
2846 * automatically be deleted from the database. We do this in a
2847 * separate pass because there might be plug-in hooks getting called,
2848 * and we don't want that happening during an S_ROOMS critical
2851 if (num_deleted) for (i=0; i<num_deleted; ++i) {
2852 PerformDeleteHooks(qrbuf.QRname, dellist[i]);
2853 AdjRefCount(dellist[i], -1);
2856 /* Now free the memory we used, and go away. */
2857 if (msglist != NULL) phree(msglist);
2858 if (dellist != NULL) phree(dellist);
2859 lprintf(9, "%d message(s) deleted.\n", num_deleted);
2860 return (num_deleted);
2866 * Check whether the current user has permission to delete messages from
2867 * the current room (returns 1 for yes, 0 for no)
2869 int CtdlDoIHavePermissionToDeleteMessagesFromThisRoom(void) {
2870 getuser(&CC->user, CC->curr_user);
2871 if ((CC->user.axlevel < 6)
2872 && (CC->user.usernum != CC->room.QRroomaide)
2873 && ((CC->room.QRflags & QR_MAILBOX) == 0)
2874 && (!(CC->internal_pgm))) {
2883 * Delete message from current room
2885 void cmd_dele(char *delstr)
2890 if (CtdlDoIHavePermissionToDeleteMessagesFromThisRoom() == 0) {
2891 cprintf("%d Higher access required.\n",
2892 ERROR + HIGHER_ACCESS_REQUIRED);
2895 delnum = extract_long(delstr, 0);
2897 num_deleted = CtdlDeleteMessages(CC->room.QRname, delnum, "");
2900 cprintf("%d %d message%s deleted.\n", CIT_OK,
2901 num_deleted, ((num_deleted != 1) ? "s" : ""));
2903 cprintf("%d Message %ld not found.\n", ERROR, delnum);
2909 * Back end API function for moves and deletes
2911 int CtdlCopyMsgToRoom(long msgnum, char *dest) {
2914 err = CtdlSaveMsgPointerInRoom(dest, msgnum,
2915 (SM_VERIFY_GOODNESS | SM_DO_REPL_CHECK) );
2916 if (err != 0) return(err);
2924 * move or copy a message to another room
2926 void cmd_move(char *args)
2930 struct ctdlroom qtemp;
2934 num = extract_long(args, 0);
2935 extract(targ, args, 1);
2936 targ[ROOMNAMELEN - 1] = 0;
2937 is_copy = extract_int(args, 2);
2939 if (getroom(&qtemp, targ) != 0) {
2940 cprintf("%d '%s' does not exist.\n", ERROR, targ);
2944 getuser(&CC->user, CC->curr_user);
2945 /* Aides can move/copy */
2946 if ((CC->user.axlevel < 6)
2947 /* Roomaides can move/copy */
2948 && (CC->user.usernum != CC->room.QRroomaide)
2949 /* Permit move/copy to/from personal rooms */
2950 && (!((CC->room.QRflags & QR_MAILBOX)
2951 && (qtemp.QRflags & QR_MAILBOX)))
2952 /* Permit only copy from public to personal room */
2953 && (!(is_copy && !(CC->room.QRflags & QR_MAILBOX)
2954 && (qtemp.QRflags & QR_MAILBOX)))) {
2955 cprintf("%d Higher access required.\n",
2956 ERROR + HIGHER_ACCESS_REQUIRED);
2960 err = CtdlCopyMsgToRoom(num, targ);
2962 cprintf("%d Cannot store message in %s: error %d\n",
2967 /* Now delete the message from the source room,
2968 * if this is a 'move' rather than a 'copy' operation.
2971 CtdlDeleteMessages(CC->room.QRname, num, "");
2974 cprintf("%d Message %s.\n", CIT_OK, (is_copy ? "copied" : "moved") );
2980 * GetMetaData() - Get the supplementary record for a message
2982 void GetMetaData(struct MetaData *smibuf, long msgnum)
2985 struct cdbdata *cdbsmi;
2988 memset(smibuf, 0, sizeof(struct MetaData));
2989 smibuf->meta_msgnum = msgnum;
2990 smibuf->meta_refcount = 1; /* Default reference count is 1 */
2992 /* Use the negative of the message number for its supp record index */
2993 TheIndex = (0L - msgnum);
2995 cdbsmi = cdb_fetch(CDB_MSGMAIN, &TheIndex, sizeof(long));
2996 if (cdbsmi == NULL) {
2997 return; /* record not found; go with defaults */
2999 memcpy(smibuf, cdbsmi->ptr,
3000 ((cdbsmi->len > sizeof(struct MetaData)) ?
3001 sizeof(struct MetaData) : cdbsmi->len));
3008 * PutMetaData() - (re)write supplementary record for a message
3010 void PutMetaData(struct MetaData *smibuf)
3014 /* Use the negative of the message number for the metadata db index */
3015 TheIndex = (0L - smibuf->meta_msgnum);
3017 lprintf(9, "PutMetaData(%ld) - ref count is %d\n",
3018 smibuf->meta_msgnum, smibuf->meta_refcount);
3020 cdb_store(CDB_MSGMAIN,
3021 &TheIndex, sizeof(long),
3022 smibuf, sizeof(struct MetaData));
3027 * AdjRefCount - change the reference count for a message;
3028 * delete the message if it reaches zero
3030 void AdjRefCount(long msgnum, int incr)
3033 struct MetaData smi;
3036 /* This is a *tight* critical section; please keep it that way, as
3037 * it may get called while nested in other critical sections.
3038 * Complicating this any further will surely cause deadlock!
3040 begin_critical_section(S_SUPPMSGMAIN);
3041 GetMetaData(&smi, msgnum);
3042 lprintf(9, "Ref count for message <%ld> before write is <%d>\n",
3043 msgnum, smi.meta_refcount);
3044 smi.meta_refcount += incr;
3046 end_critical_section(S_SUPPMSGMAIN);
3047 lprintf(9, "Ref count for message <%ld> after write is <%d>\n",
3048 msgnum, smi.meta_refcount);
3050 /* If the reference count is now zero, delete the message
3051 * (and its supplementary record as well).
3053 if (smi.meta_refcount == 0) {
3054 lprintf(9, "Deleting message <%ld>\n", msgnum);
3056 cdb_delete(CDB_MSGMAIN, &delnum, sizeof(long));
3058 /* We have to delete the metadata record too! */
3059 delnum = (0L - msgnum);
3060 cdb_delete(CDB_MSGMAIN, &delnum, sizeof(long));
3065 * Write a generic object to this room
3067 * Note: this could be much more efficient. Right now we use two temporary
3068 * files, and still pull the message into memory as with all others.
3070 void CtdlWriteObject(char *req_room, /* Room to stuff it in */
3071 char *content_type, /* MIME type of this object */
3072 char *tempfilename, /* Where to fetch it from */
3073 struct ctdluser *is_mailbox, /* Mailbox room? */
3074 int is_binary, /* Is encoding necessary? */
3075 int is_unique, /* Del others of this type? */
3076 unsigned int flags /* Internal save flags */
3081 struct ctdlroom qrbuf;
3082 char roomname[ROOMNAMELEN];
3083 struct CtdlMessage *msg;
3085 char *raw_message = NULL;
3086 char *encoded_message = NULL;
3087 off_t raw_length = 0;
3089 if (is_mailbox != NULL)
3090 MailboxName(roomname, sizeof roomname, is_mailbox, req_room);
3092 safestrncpy(roomname, req_room, sizeof(roomname));
3093 lprintf(9, "CtdlWriteObject() to <%s> (flags=%d)\n", roomname, flags);
3096 fp = fopen(tempfilename, "rb");
3098 lprintf(5, "Cannot open %s: %s\n",
3099 tempfilename, strerror(errno));
3102 fseek(fp, 0L, SEEK_END);
3103 raw_length = ftell(fp);
3105 lprintf(9, "Raw length is %ld\n", (long)raw_length);
3107 raw_message = mallok((size_t)raw_length + 2);
3108 fread(raw_message, (size_t)raw_length, 1, fp);
3112 encoded_message = mallok((size_t)
3113 (((raw_length * 134) / 100) + 4096 ) );
3116 encoded_message = mallok((size_t)(raw_length + 4096));
3119 sprintf(encoded_message, "Content-type: %s\n", content_type);
3122 sprintf(&encoded_message[strlen(encoded_message)],
3123 "Content-transfer-encoding: base64\n\n"
3127 sprintf(&encoded_message[strlen(encoded_message)],
3128 "Content-transfer-encoding: 7bit\n\n"
3134 &encoded_message[strlen(encoded_message)],
3140 raw_message[raw_length] = 0;
3142 &encoded_message[strlen(encoded_message)],
3150 lprintf(9, "Allocating\n");
3151 msg = mallok(sizeof(struct CtdlMessage));
3152 memset(msg, 0, sizeof(struct CtdlMessage));
3153 msg->cm_magic = CTDLMESSAGE_MAGIC;
3154 msg->cm_anon_type = MES_NORMAL;
3155 msg->cm_format_type = 4;
3156 msg->cm_fields['A'] = strdoop(CC->user.fullname);
3157 msg->cm_fields['O'] = strdoop(req_room);
3158 msg->cm_fields['N'] = strdoop(config.c_nodename);
3159 msg->cm_fields['H'] = strdoop(config.c_humannode);
3160 msg->cm_flags = flags;
3162 msg->cm_fields['M'] = encoded_message;
3164 /* Create the requested room if we have to. */
3165 if (getroom(&qrbuf, roomname) != 0) {
3166 create_room(roomname,
3167 ( (is_mailbox != NULL) ? 5 : 3 ),
3170 /* If the caller specified this object as unique, delete all
3171 * other objects of this type that are currently in the room.
3174 lprintf(9, "Deleted %d other msgs of this type\n",
3175 CtdlDeleteMessages(roomname, 0L, content_type));
3177 /* Now write the data */
3178 CtdlSubmitMsg(msg, NULL, roomname);
3179 CtdlFreeMessage(msg);
3187 void CtdlGetSysConfigBackend(long msgnum, void *userdata) {
3188 config_msgnum = msgnum;
3192 char *CtdlGetSysConfig(char *sysconfname) {
3193 char hold_rm[ROOMNAMELEN];
3196 struct CtdlMessage *msg;
3199 strcpy(hold_rm, CC->room.QRname);
3200 if (getroom(&CC->room, SYSCONFIGROOM) != 0) {
3201 getroom(&CC->room, hold_rm);
3206 /* We want the last (and probably only) config in this room */
3207 begin_critical_section(S_CONFIG);
3208 config_msgnum = (-1L);
3209 CtdlForEachMessage(MSGS_LAST, 1, sysconfname, NULL,
3210 CtdlGetSysConfigBackend, NULL);
3211 msgnum = config_msgnum;
3212 end_critical_section(S_CONFIG);
3218 msg = CtdlFetchMessage(msgnum);
3220 conf = strdoop(msg->cm_fields['M']);
3221 CtdlFreeMessage(msg);
3228 getroom(&CC->room, hold_rm);
3230 if (conf != NULL) do {
3231 extract_token(buf, conf, 0, '\n');
3232 strcpy(conf, &conf[strlen(buf)+1]);
3233 } while ( (strlen(conf)>0) && (strlen(buf)>0) );
3238 void CtdlPutSysConfig(char *sysconfname, char *sysconfdata) {
3239 char temp[PATH_MAX];
3242 strcpy(temp, tmpnam(NULL));
3244 fp = fopen(temp, "w");
3245 if (fp == NULL) return;
3246 fprintf(fp, "%s", sysconfdata);
3249 /* this handy API function does all the work for us */
3250 CtdlWriteObject(SYSCONFIGROOM, sysconfname, temp, NULL, 0, 1, 0);
3256 * Determine whether a given Internet address belongs to the current user
3258 int CtdlIsMe(char *addr) {
3259 struct recptypes *recp;
3262 recp = validate_recipients(addr);
3263 if (recp == NULL) return(0);
3265 if (recp->num_local == 0) {
3270 for (i=0; i<recp->num_local; ++i) {
3271 extract(addr, recp->recp_local, i);
3272 if (!strcasecmp(addr, CC->user.fullname)) {
3284 * Citadel protocol command to do the same
3286 void cmd_isme(char *argbuf) {
3289 if (CtdlAccessCheck(ac_logged_in)) return;
3290 extract(addr, argbuf, 0);
3292 if (CtdlIsMe(addr)) {
3293 cprintf("%d %s\n", CIT_OK, addr);
3296 cprintf("%d Not you.\n", ERROR);