22 #include "sysdep_decls.h"
23 #include "citserver.h"
28 #include "dynloader.h"
30 #include "mime_parser.h"
33 #define desired_section ((char *)CtdlGetUserData(SYM_DESIRED_SECTION))
34 #define ma ((struct ma_info *)CtdlGetUserData(SYM_MA_INFO))
36 extern struct config config;
40 * This function is self explanatory.
41 * (What can I say, I'm in a weird mood today...)
43 void remove_any_whitespace_to_the_left_or_right_of_at_symbol(char *name)
47 for (i = 0; i < strlen(name); ++i)
50 if (isspace(name[i - 1])) {
51 strcpy(&name[i - 1], &name[i]);
54 while (isspace(name[i + 1])) {
55 strcpy(&name[i + 1], &name[i + 2]);
62 * Aliasing for network mail.
63 * (Error messages have been commented out, because this is a server.)
66 { /* process alias and routing info for mail */
69 char aaa[300], bbb[300];
71 lprintf(9, "alias() called for <%s>\n", name);
73 remove_any_whitespace_to_the_left_or_right_of_at_symbol(name);
75 fp = fopen("network/mail.aliases", "r");
77 fp = fopen("/dev/null", "r");
82 while (fgets(aaa, sizeof aaa, fp) != NULL) {
83 while (isspace(name[0]))
84 strcpy(name, &name[1]);
85 aaa[strlen(aaa) - 1] = 0;
87 for (a = 0; a < strlen(aaa); ++a) {
89 strcpy(bbb, &aaa[a + 1]);
93 if (!strcasecmp(name, aaa))
97 lprintf(7, "Mail is being forwarded to %s\n", name);
99 /* determine local or remote type, see citadel.h */
100 for (a = 0; a < strlen(name); ++a)
102 return (MES_INTERNET);
103 for (a = 0; a < strlen(name); ++a)
105 for (b = a; b < strlen(name); ++b)
107 return (MES_INTERNET);
109 for (a = 0; a < strlen(name); ++a)
113 lprintf(7, "Too many @'s in address\n");
117 for (a = 0; a < strlen(name); ++a)
119 strcpy(bbb, &name[a + 1]);
121 strcpy(bbb, &bbb[1]);
122 fp = fopen("network/mail.sysinfo", "r");
126 a = getstring(fp, aaa);
127 } while ((a >= 0) && (strcasecmp(aaa, bbb)));
128 a = getstring(fp, aaa);
129 if (!strncmp(aaa, "use ", 4)) {
130 strcpy(bbb, &aaa[4]);
135 if (!strncmp(aaa, "uum", 3)) {
137 for (a = 0; a < strlen(bbb); ++a) {
143 while (bbb[strlen(bbb) - 1] == '_')
144 bbb[strlen(bbb) - 1] = 0;
145 sprintf(name, &aaa[4], bbb);
146 return (MES_INTERNET);
148 if (!strncmp(aaa, "bin", 3)) {
151 while (aaa[strlen(aaa) - 1] != '@')
152 aaa[strlen(aaa) - 1] = 0;
153 aaa[strlen(aaa) - 1] = 0;
154 while (aaa[strlen(aaa) - 1] == ' ')
155 aaa[strlen(aaa) - 1] = 0;
156 while (bbb[0] != '@')
157 strcpy(bbb, &bbb[1]);
158 strcpy(bbb, &bbb[1]);
159 while (bbb[0] == ' ')
160 strcpy(bbb, &bbb[1]);
161 sprintf(name, "%s @%s", aaa, bbb);
174 fp = fopen("citadel.control", "r");
175 fread((char *) &CitControl, sizeof(struct CitControl), 1, fp);
181 void simple_listing(long msgnum)
183 cprintf("%ld\n", msgnum);
188 * API function to perform an operation for each qualifying message in the
191 void CtdlForEachMessage(int mode, long ref,
193 void (*CallBack) (long msgnum))
198 struct cdbdata *cdbfr;
199 long *msglist = NULL;
202 struct SuppMsgInfo smi;
204 /* Learn about the user and room in question */
206 getuser(&CC->usersupp, CC->curr_user);
207 CtdlGetRelationship(&vbuf, &CC->usersupp, &CC->quickroom);
209 /* Load the message list */
210 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->quickroom.QRnumber, sizeof(long));
212 msglist = mallok(cdbfr->len);
213 memcpy(msglist, cdbfr->ptr, cdbfr->len);
214 num_msgs = cdbfr->len / sizeof(long);
217 return; /* No messages at all? No further action. */
221 /* If the caller is looking for a specific MIME type, then filter
222 * out all messages which are not of the type requested.
225 if (content_type != NULL)
226 if (strlen(content_type) > 0)
227 for (a = 0; a < num_msgs; ++a) {
228 GetSuppMsgInfo(&smi, msglist[a]);
229 if (strcasecmp(smi.smi_content_type, content_type)) {
234 num_msgs = sort_msglist(msglist, num_msgs);
237 * Now iterate through the message list, according to the
238 * criteria supplied by the caller.
241 for (a = 0; a < num_msgs; ++a) {
242 thismsg = msglist[a];
247 || ((mode == MSGS_OLD) && (thismsg <= vbuf.v_lastseen))
248 || ((mode == MSGS_NEW) && (thismsg > vbuf.v_lastseen))
249 || ((mode == MSGS_NEW) && (thismsg >= vbuf.v_lastseen)
250 && (CC->usersupp.flags & US_LASTOLD))
251 || ((mode == MSGS_LAST) && (a >= (num_msgs - ref)))
252 || ((mode == MSGS_FIRST) && (a < ref))
253 || ((mode == MSGS_GT) && (thismsg > ref))
259 phree(msglist); /* Clean up */
265 * cmd_msgs() - get list of message #'s in this room
266 * implements the MSGS server command using CtdlForEachMessage()
268 void cmd_msgs(char *cmdbuf)
274 extract(which, cmdbuf, 0);
275 cm_ref = extract_int(cmdbuf, 1);
279 if (!strncasecmp(which, "OLD", 3))
281 else if (!strncasecmp(which, "NEW", 3))
283 else if (!strncasecmp(which, "FIRST", 5))
285 else if (!strncasecmp(which, "LAST", 4))
287 else if (!strncasecmp(which, "GT", 2))
290 if ((!(CC->logged_in)) && (!(CC->internal_pgm))) {
291 cprintf("%d not logged in\n", ERROR + NOT_LOGGED_IN);
294 cprintf("%d Message list...\n", LISTING_FOLLOWS);
295 CtdlForEachMessage(mode, cm_ref, NULL, simple_listing);
303 * help_subst() - support routine for help file viewer
305 void help_subst(char *strbuf, char *source, char *dest)
310 while (p = pattern2(strbuf, source), (p >= 0)) {
311 strcpy(workbuf, &strbuf[p + strlen(source)]);
312 strcpy(&strbuf[p], dest);
313 strcat(strbuf, workbuf);
318 void do_help_subst(char *buffer)
322 help_subst(buffer, "^nodename", config.c_nodename);
323 help_subst(buffer, "^humannode", config.c_humannode);
324 help_subst(buffer, "^fqdn", config.c_fqdn);
325 help_subst(buffer, "^username", CC->usersupp.fullname);
326 sprintf(buf2, "%ld", CC->usersupp.usernum);
327 help_subst(buffer, "^usernum", buf2);
328 help_subst(buffer, "^sysadm", config.c_sysadm);
329 help_subst(buffer, "^variantname", CITADEL);
330 sprintf(buf2, "%d", config.c_maxsessions);
331 help_subst(buffer, "^maxsessions", buf2);
337 * memfmout() - Citadel text formatter and paginator.
338 * Although the original purpose of this routine was to format
339 * text to the reader's screen width, all we're really using it
340 * for here is to format text out to 80 columns before sending it
341 * to the client. The client software may reformat it again.
343 void memfmout(int width, char *mptr, char subst)
344 /* screen width to use */
345 /* where are we going to get our text from? */
346 /* nonzero if we should use hypertext mode */
358 c = 1; /* c is the current pos */
361 while (ch = *mptr, ((ch != 0) && (strlen(buffer) < 126))) {
363 buffer[strlen(buffer) + 1] = 0;
364 buffer[strlen(buffer)] = ch;
367 if (buffer[0] == '^')
368 do_help_subst(buffer);
370 buffer[strlen(buffer) + 1] = 0;
372 strcpy(buffer, &buffer[1]);
381 if (((ch == 13) || (ch == 10)) && (old != 13) && (old != 10))
383 if (((old == 13) || (old == 10)) && (isspace(real))) {
391 if (((strlen(aaa) + c) > (width - 5)) && (strlen(aaa) > (width - 5))) {
392 cprintf("\n%s", aaa);
401 if ((strlen(aaa) + c) > (width - 5)) {
411 if ((ch == 13) || (ch == 10)) {
412 cprintf("%s\n", aaa);
419 FMTEND: cprintf("%s\n", aaa);
425 * Callback function for mime parser that simply lists the part
427 void list_this_part(char *name, char *filename, char *partnum, char *disp,
428 void *content, char *cbtype, size_t length)
431 cprintf("part=%s|%s|%s|%s|%s|%d\n",
432 name, filename, partnum, disp, cbtype, length);
437 * Callback function for mime parser that wants to display text
439 void fixed_output(char *name, char *filename, char *partnum, char *disp,
440 void *content, char *cbtype, size_t length)
444 if (!strcasecmp(cbtype, "multipart/alternative")) {
445 strcpy(ma->prefix, partnum);
446 strcat(ma->prefix, ".");
452 if ( (!strncasecmp(partnum, ma->prefix, strlen(ma->prefix)))
454 && (ma->did_print == 1) ) {
455 lprintf(9, "Skipping part %s (%s)\n", partnum, cbtype);
461 if (!strcasecmp(cbtype, "text/plain")) {
462 client_write(content, length);
464 else if (!strcasecmp(cbtype, "text/html")) {
465 ptr = html_to_ascii(content, 80, 0);
466 client_write(ptr, strlen(ptr));
469 else if (strncasecmp(cbtype, "multipart/", 10)) {
470 cprintf("Part %s: %s (%s) (%d bytes)\n",
471 partnum, filename, cbtype, length);
477 * Callback function for mime parser that opens a section for downloading
479 void mime_download(char *name, char *filename, char *partnum, char *disp,
480 void *content, char *cbtype, size_t length)
483 /* Silently go away if there's already a download open... */
484 if (CC->download_fp != NULL)
487 /* ...or if this is not the desired section */
488 if (strcasecmp(desired_section, partnum))
491 CC->download_fp = tmpfile();
492 if (CC->download_fp == NULL)
495 fwrite(content, length, 1, CC->download_fp);
496 fflush(CC->download_fp);
497 rewind(CC->download_fp);
499 OpenCmdResult(filename, cbtype);
505 * Load a message from disk into memory.
506 * This is used by output_message() and other fetch functions.
508 * NOTE: Caller is responsible for freeing the returned CtdlMessage struct
509 * using the CtdlMessageFree() function.
511 struct CtdlMessage *CtdlFetchMessage(long msgnum)
513 struct cdbdata *dmsgtext;
514 struct CtdlMessage *ret = NULL;
517 CIT_UBYTE field_header;
521 dmsgtext = cdb_fetch(CDB_MSGMAIN, &msgnum, sizeof(long));
522 if (dmsgtext == NULL) {
523 lprintf(9, "CtdlFetchMessage(%ld) failed.\n");
526 mptr = dmsgtext->ptr;
528 /* Parse the three bytes that begin EVERY message on disk.
529 * The first is always 0xFF, the on-disk magic number.
530 * The second is the anonymous/public type byte.
531 * The third is the format type byte (vari, fixed, or MIME).
535 lprintf(5, "Message %ld appears to be corrupted.\n", msgnum);
539 ret = (struct CtdlMessage *) mallok(sizeof(struct CtdlMessage));
540 memset(ret, 0, sizeof(struct CtdlMessage));
542 ret->cm_magic = CTDLMESSAGE_MAGIC;
543 ret->cm_anon_type = *mptr++; /* Anon type byte */
544 ret->cm_format_type = *mptr++; /* Format type byte */
547 * The rest is zero or more arbitrary fields. Load them in.
548 * We're done when we encounter either a zero-length field or
549 * have just processed the 'M' (message text) field.
552 field_length = strlen(mptr);
553 if (field_length == 0)
555 field_header = *mptr++;
556 ret->cm_fields[field_header] = mallok(field_length);
557 strcpy(ret->cm_fields[field_header], mptr);
559 while (*mptr++ != 0); /* advance to next field */
561 } while ((field_length > 0) && (field_header != 'M'));
568 * 'Destructor' for struct CtdlMessage
570 void CtdlFreeMessage(struct CtdlMessage *msg)
576 if ((msg->cm_magic) != CTDLMESSAGE_MAGIC) {
577 lprintf(3, "CtdlFreeMessage() -- self-check failed\n");
580 for (i = 0; i < 256; ++i)
581 if (msg->cm_fields[i] != NULL)
582 phree(msg->cm_fields[i]);
590 * Get a message off disk. (return value is the message's timestamp)
593 void output_message(char *msgid, int mode, int headers_only)
601 struct CtdlMessage *TheMessage = NULL;
605 /* buffers needed for RFC822 translation */
613 msg_num = atol(msgid);
615 if ((!(CC->logged_in)) && (!(CC->internal_pgm))) {
616 cprintf("%d Not logged in.\n", ERROR + NOT_LOGGED_IN);
620 /* FIX ... small security issue
621 * We need to check to make sure the requested message is actually
622 * in the current room, and set msg_ok to 1 only if it is. This
623 * functionality is currently missing because I'm in a hurry to replace
624 * broken production code with nonbroken pre-beta code. :( -- ajc
627 cprintf("%d Message %ld is not in this room.\n",
634 * Fetch the message from disk
636 TheMessage = CtdlFetchMessage(msg_num);
637 if (TheMessage == NULL) {
638 cprintf("%d Can't locate msg %ld on disk\n", ERROR, msg_num);
642 /* Are we downloading a MIME component? */
643 if (mode == MT_DOWNLOAD) {
644 if (TheMessage->cm_format_type != 4) {
645 cprintf("%d This is not a MIME message.\n",
647 } else if (CC->download_fp != NULL) {
648 cprintf("%d You already have a download open.\n",
651 /* Parse the message text component */
652 mptr = TheMessage->cm_fields['M'];
653 mime_parser(mptr, NULL, *mime_download);
654 /* If there's no file open by this time, the requested
655 * section wasn't found, so print an error
657 if (CC->download_fp == NULL) {
658 cprintf("%d Section %s not found.\n",
659 ERROR + FILE_NOT_FOUND,
663 CtdlFreeMessage(TheMessage);
667 /* now for the user-mode message reading loops */
668 cprintf("%d Message %ld:\n", LISTING_FOLLOWS, msg_num);
670 /* Tell the client which format type we're using. If this is a
671 * MIME message, *lie* about it and tell the user it's fixed-format.
673 if (mode == MT_CITADEL) {
674 if (TheMessage->cm_format_type == 4)
677 cprintf("type=%d\n", TheMessage->cm_format_type);
680 /* nhdr=yes means that we're only displaying headers, no body */
681 if ((TheMessage->cm_anon_type == MES_ANON) && (mode == MT_CITADEL)) {
682 cprintf("nhdr=yes\n");
685 /* begin header processing loop for Citadel message format */
687 if ((mode == MT_CITADEL) || (mode == MT_MIME)) {
689 if (TheMessage->cm_fields['P']) {
690 cprintf("path=%s\n", TheMessage->cm_fields['P']);
692 if (TheMessage->cm_fields['I']) {
693 cprintf("msgn=%s\n", TheMessage->cm_fields['I']);
695 if (TheMessage->cm_fields['T']) {
696 cprintf("time=%s\n", TheMessage->cm_fields['T']);
698 if (TheMessage->cm_fields['A']) {
699 strcpy(buf, TheMessage->cm_fields['A']);
700 PerformUserHooks(buf, (-1L), EVT_OUTPUTMSG);
701 if (TheMessage->cm_anon_type == MES_ANON)
702 cprintf("from=****");
703 else if (TheMessage->cm_anon_type == MES_AN2)
704 cprintf("from=anonymous");
706 cprintf("from=%s", buf);
708 && ((TheMessage->cm_anon_type == MES_ANON)
709 || (TheMessage->cm_anon_type == MES_AN2))) {
710 cprintf(" [%s]", buf);
714 if (TheMessage->cm_fields['O']) {
715 cprintf("room=%s\n", TheMessage->cm_fields['O']);
717 if (TheMessage->cm_fields['N']) {
718 cprintf("node=%s\n", TheMessage->cm_fields['N']);
720 if (TheMessage->cm_fields['H']) {
721 cprintf("hnod=%s\n", TheMessage->cm_fields['H']);
723 if (TheMessage->cm_fields['R']) {
724 cprintf("rcpt=%s\n", TheMessage->cm_fields['R']);
726 if (TheMessage->cm_fields['U']) {
727 cprintf("subj=%s\n", TheMessage->cm_fields['U']);
731 /* begin header processing loop for RFC822 transfer format */
735 strcpy(snode, NODENAME);
736 strcpy(lnode, HUMANNODE);
737 if (mode == MT_RFC822) {
738 for (i = 0; i < 256; ++i) {
739 if (TheMessage->cm_fields[i]) {
740 mptr = TheMessage->cm_fields[i];
744 } else if (i == 'P') {
745 cprintf("Path: %s\n", mptr);
746 for (a = 0; a < strlen(mptr); ++a) {
747 if (mptr[a] == '!') {
748 strcpy(mptr, &mptr[a + 1]);
754 cprintf("Subject: %s\n", mptr);
760 cprintf("X-Citadel-Room: %s\n", mptr);
764 cprintf("To: %s\n", mptr);
767 cprintf("Date: %s", asctime(localtime(&xtime)));
773 if (mode == MT_RFC822) {
774 if (!strcasecmp(snode, NODENAME)) {
777 cprintf("Message-ID: <%s@%s>\n", mid, snode);
778 PerformUserHooks(luser, (-1L), EVT_OUTPUTMSG);
779 cprintf("From: %s@%s (%s)\n", suser, snode, luser);
780 cprintf("Organization: %s\n", lnode);
783 /* end header processing loop ... at this point, we're in the text */
785 mptr = TheMessage->cm_fields['M'];
787 /* Tell the client about the MIME parts in this message */
788 if (TheMessage->cm_format_type == 4) { /* legacy textual dump */
789 if (mode == MT_CITADEL) {
790 mime_parser(mptr, NULL, *list_this_part);
792 else if (mode == MT_MIME) { /* list parts only */
793 mime_parser(mptr, NULL, *list_this_part);
795 CtdlFreeMessage(TheMessage);
802 CtdlFreeMessage(TheMessage);
806 /* signify start of msg text */
807 if (mode == MT_CITADEL)
809 if ((mode == MT_RFC822) && (TheMessage->cm_format_type != 4))
812 /* If the format type on disk is 1 (fixed-format), then we want
813 * everything to be output completely literally ... regardless of
814 * what message transfer format is in use.
816 if (TheMessage->cm_format_type == 1) {
818 while (ch = *mptr++, ch > 0) {
821 if ((ch == 10) || (strlen(buf) > 250)) {
822 cprintf("%s\n", buf);
825 buf[strlen(buf) + 1] = 0;
826 buf[strlen(buf)] = ch;
830 cprintf("%s\n", buf);
833 /* If the message on disk is format 0 (Citadel vari-format), we
834 * output using the formatter at 80 columns. This is the final output
835 * form if the transfer format is RFC822, but if the transfer format
836 * is Citadel proprietary, it'll still work, because the indentation
837 * for new paragraphs is correct and the client will reformat the
838 * message to the reader's screen width.
840 if (TheMessage->cm_format_type == 0) {
841 memfmout(80, mptr, 0);
844 /* If the message on disk is format 4 (MIME), we've gotta hand it
845 * off to the MIME parser. The client has already been told that
846 * this message is format 1 (fixed format), so the callback function
847 * we use will display those parts as-is.
849 if (TheMessage->cm_format_type == 4) {
850 CtdlAllocUserData(SYM_MA_INFO, sizeof(struct ma_info));
851 memset(ma, 0, sizeof(struct ma_info));
852 mime_parser(mptr, NULL, *fixed_output);
857 CtdlFreeMessage(TheMessage);
864 * display a message (mode 0 - Citadel proprietary)
866 void cmd_msg0(char *cmdbuf)
869 int headers_only = 0;
871 extract(msgid, cmdbuf, 0);
872 headers_only = extract_int(cmdbuf, 1);
874 output_message(msgid, MT_CITADEL, headers_only);
880 * display a message (mode 2 - RFC822)
882 void cmd_msg2(char *cmdbuf)
885 int headers_only = 0;
887 extract(msgid, cmdbuf, 0);
888 headers_only = extract_int(cmdbuf, 1);
890 output_message(msgid, MT_RFC822, headers_only);
896 * display a message (mode 3 - IGnet raw format - internal programs only)
898 void cmd_msg3(char *cmdbuf)
901 struct cdbdata *dmsgtext;
903 if (CC->internal_pgm == 0) {
904 cprintf("%d This command is for internal programs only.\n",
909 msgnum = extract_long(cmdbuf, 0);
911 dmsgtext = cdb_fetch(CDB_MSGMAIN, &msgnum, sizeof(long));
912 if (dmsgtext == NULL) {
913 cprintf("%d Message %ld not found\n", ERROR, msgnum);
916 cprintf("%d %ld\n", BINARY_FOLLOWS, dmsgtext->len);
917 client_write(dmsgtext->ptr, dmsgtext->len);
924 * display a message (mode 4 - MIME) (FIX ... still evolving, not complete)
926 void cmd_msg4(char *cmdbuf)
930 extract(msgid, cmdbuf, 0);
932 output_message(msgid, MT_MIME, 0);
936 * Open a component of a MIME message as a download file
938 void cmd_opna(char *cmdbuf)
942 CtdlAllocUserData(SYM_DESIRED_SECTION, 64);
944 extract(msgid, cmdbuf, 0);
945 extract(desired_section, cmdbuf, 1);
947 output_message(msgid, MT_DOWNLOAD, 0);
951 * Message base operation to send a message to the master file
952 * (returns new message number)
954 long send_message(char *message_in_memory,
955 /* pointer to buffer */
956 size_t message_length, /* length of buffer */
958 { /* 1 to generate an I field */
961 char *actual_message;
962 size_t actual_length;
966 /* Get a new message number */
967 newmsgid = get_new_message_number();
970 sprintf(msgidbuf, "I%ld", newmsgid);
971 actual_length = message_length + strlen(msgidbuf) + 1;
972 actual_message = mallok(actual_length);
973 memcpy(actual_message, message_in_memory, 3);
974 memcpy(&actual_message[3], msgidbuf, (strlen(msgidbuf) + 1));
975 memcpy(&actual_message[strlen(msgidbuf) + 4],
976 &message_in_memory[3], message_length - 3);
978 actual_message = message_in_memory;
979 actual_length = message_length;
982 /* Write our little bundle of joy into the message base */
983 begin_critical_section(S_MSGMAIN);
984 if (cdb_store(CDB_MSGMAIN, &newmsgid, sizeof(long),
985 actual_message, actual_length) < 0) {
986 lprintf(2, "Can't store message\n");
991 end_critical_section(S_MSGMAIN);
994 phree(actual_message);
996 /* Finally, return the pointers */
1003 * this is a simple file copy routine.
1005 void copy_file(char *from, char *to)
1010 ffp = fopen(from, "r");
1013 tfp = fopen(to, "w");
1018 while (a = getc(ffp), a >= 0) {
1029 * Serialize a struct CtdlMessage into the format used on disk and network
1031 char *serialize_message(struct CtdlMessage *msg) {
1036 static char *forder = FORDER;
1038 lprintf(9, "serialize_message() called\n");
1040 if ((msg->cm_magic) != CTDLMESSAGE_MAGIC) {
1041 lprintf(3, "serialize_message() -- self-check failed\n");
1046 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL)
1047 len = len + strlen(msg->cm_fields[(int)forder[i]]) + 2;
1049 lprintf(9, "calling malloc\n");
1051 if (ser == NULL) return NULL;
1054 ser[1] = msg->cm_anon_type;
1055 ser[2] = msg->cm_format_type;
1057 lprintf(9, "stuff\n");
1059 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL) {
1060 ser[wlen++] = (char)forder[i];
1061 strcpy(&ser[wlen], msg->cm_fields[(int)forder[i]]);
1062 wlen = wlen + strlen(msg->cm_fields[(int)forder[i]]) + 1;
1064 if (len != wlen) lprintf(3, "ERROR: len=%d wlen=%d\n", len, wlen);
1076 * message base operation to save a message and install its pointers
1078 void save_message(char *mtmp, /* file containing proper message */
1079 char *rec, /* Recipient (if mail) */
1080 char *force, /* if non-zero length, force a room */
1081 int mailtype, /* local or remote type, see citadel.h */
1083 { /* set to 1 to generate an 'I' field */
1085 char hold_rm[ROOMNAMELEN];
1086 char actual_rm[ROOMNAMELEN];
1087 char force_room[ROOMNAMELEN];
1088 char content_type[256]; /* We have to learn this */
1090 char recipient[256];
1092 char *message_in_memory;
1094 struct stat statbuf;
1097 struct usersupp userbuf;
1099 static int seqnum = 0;
1100 int successful_local_recipients = 0;
1101 struct quickroom qtemp;
1102 struct SuppMsgInfo smi;
1104 lprintf(9, "save_message(%s,%s,%s,%d,%d)\n",
1105 mtmp, rec, force, mailtype, generate_id);
1107 strcpy(force_room, force);
1109 /* Strip non-printable characters out of the recipient name */
1110 strcpy(recipient, rec);
1111 for (a = 0; a < strlen(recipient); ++a)
1112 if (!isprint(recipient[a]))
1113 strcpy(&recipient[a], &recipient[a + 1]);
1115 /* Measure the message */
1116 stat(mtmp, &statbuf);
1117 templen = statbuf.st_size;
1119 /* Now read it into memory */
1120 message_in_memory = (char *) mallok(templen);
1121 if (message_in_memory == NULL) {
1122 lprintf(2, "Can't allocate memory to save message!\n");
1125 fp = fopen(mtmp, "rb");
1126 fread(message_in_memory, templen, 1, fp);
1129 /* Learn about what's inside, because it's what's inside that counts */
1130 mptr = message_in_memory;
1131 ++mptr; /* advance past 0xFF header */
1132 ++mptr; /* advance past anon flag */
1136 strcpy(content_type, "text/x-citadel-variformat");
1139 strcpy(content_type, "text/plain");
1142 strcpy(content_type, "text/plain");
1143 /* advance past header fields */
1144 while (ch = *mptr++, (ch != 'M' && ch != 0)) {
1151 if (!strncasecmp(mptr, "Content-type: ", 14)) {
1152 safestrncpy(content_type, mptr,
1153 sizeof(content_type));
1154 strcpy(content_type, &content_type[14]);
1155 for (a = 0; a < strlen(content_type); ++a)
1156 if ((content_type[a] == ';')
1157 || (content_type[a] == ' ')
1158 || (content_type[a] == 13)
1159 || (content_type[a] == 10))
1160 content_type[a] = 0;
1167 /* Save it to disk */
1168 newmsgid = send_message(message_in_memory, templen, generate_id);
1169 phree(message_in_memory);
1173 strcpy(actual_rm, CC->quickroom.QRname);
1174 strcpy(hold_rm, "");
1176 /* If this is being done by the networker delivering a private
1177 * message, we want to BYPASS saving the sender's copy (because there
1178 * is no local sender; it would otherwise go to the Trashcan).
1180 if ((!CC->internal_pgm) || (strlen(recipient) == 0)) {
1181 /* If the user is a twit, move to the twit room for posting */
1183 if (CC->usersupp.axlevel == 2) {
1184 strcpy(hold_rm, actual_rm);
1185 strcpy(actual_rm, config.c_twitroom);
1187 /* ...or if this message is destined for Aide> then go there. */
1188 if (strlen(force_room) > 0) {
1189 strcpy(hold_rm, actual_rm);
1190 strcpy(actual_rm, force_room);
1192 /* This call to usergoto() changes rooms if necessary. It also
1193 * causes the latest message list to be read into memory.
1195 usergoto(actual_rm, 0);
1197 /* read in the quickroom record, obtaining a lock... */
1198 lgetroom(&CC->quickroom, actual_rm);
1200 /* Fix an obscure bug */
1201 if (!strcasecmp(CC->quickroom.QRname, AIDEROOM)) {
1202 CC->quickroom.QRflags =
1203 CC->quickroom.QRflags & ~QR_MAILBOX;
1205 /* Add the message pointer to the room */
1206 CC->quickroom.QRhighest =
1207 AddMessageToRoom(&CC->quickroom, newmsgid);
1209 /* update quickroom */
1210 lputroom(&CC->quickroom);
1211 ++successful_local_recipients;
1213 /* Network mail - send a copy to the network program. */
1214 if ((strlen(recipient) > 0) && (mailtype != MES_LOCAL)) {
1215 sprintf(aaa, "./network/spoolin/netmail.%04lx.%04x.%04x",
1216 (long) getpid(), CC->cs_pid, ++seqnum);
1217 copy_file(mtmp, aaa);
1218 system("exec nohup ./netproc -i >/dev/null 2>&1 &");
1220 /* Bump this user's messages posted counter. */
1221 lgetuser(&CC->usersupp, CC->curr_user);
1222 CC->usersupp.posted = CC->usersupp.posted + 1;
1223 lputuser(&CC->usersupp);
1225 /* If this is private, local mail, make a copy in the
1226 * recipient's mailbox and bump the reference count.
1228 if ((strlen(recipient) > 0) && (mailtype == MES_LOCAL)) {
1229 if (getuser(&userbuf, recipient) == 0) {
1230 MailboxName(actual_rm, &userbuf, MAILROOM);
1231 if (lgetroom(&qtemp, actual_rm) == 0) {
1233 AddMessageToRoom(&qtemp, newmsgid);
1235 ++successful_local_recipients;
1239 /* If we've posted in a room other than the current room, then we
1240 * have to now go back to the current room...
1242 if (strlen(hold_rm) > 0) {
1243 usergoto(hold_rm, 0);
1245 unlink(mtmp); /* delete the temporary file */
1247 /* Write a supplemental message info record. This doesn't have to
1248 * be a critical section because nobody else knows about this message
1251 memset(&smi, 0, sizeof(struct SuppMsgInfo));
1252 smi.smi_msgnum = newmsgid;
1253 smi.smi_refcount = successful_local_recipients;
1254 safestrncpy(smi.smi_content_type, content_type, 64);
1255 PutSuppMsgInfo(&smi);
1260 * Generate an administrative message and post it in the Aide> room.
1262 void aide_message(char *text)
1266 fp = fopen(CC->temp, "wb");
1267 fprintf(fp, "%c%c%c", 255, MES_NORMAL, 0);
1268 fprintf(fp, "Psysop%c", 0);
1269 fprintf(fp, "T%ld%c", (long) time(NULL), 0);
1270 fprintf(fp, "ACitadel%c", 0);
1271 fprintf(fp, "OAide%c", 0);
1272 fprintf(fp, "N%s%c", NODENAME, 0);
1273 fprintf(fp, "M%s\n%c", text, 0);
1275 save_message(CC->temp, "", AIDEROOM, MES_LOCAL, 1);
1276 syslog(LOG_NOTICE, text);
1281 * Build a binary message to be saved on disk.
1285 char *filename, /* temporary file name */
1286 struct usersupp *author, /* author's usersupp structure */
1287 char *recipient, /* NULL if it's not mail */
1288 char *room, /* room where it's going */
1289 int type, /* see MES_ types in header file */
1290 int net_type, /* see MES_ types in header file */
1291 int format_type, /* local or remote (see citadel.h) */
1292 char *fake_name) /* who we're masquerading as */
1302 /* Don't confuse the poor folks if it's not routed mail. */
1303 strcpy(dest_node, "");
1305 /* If net_type is MES_BINARY, split out the destination node. */
1306 if (net_type == MES_BINARY) {
1307 strcpy(dest_node, NODENAME);
1308 for (a = 0; a < strlen(recipient); ++a) {
1309 if (recipient[a] == '@') {
1311 strcpy(dest_node, &recipient[a + 1]);
1316 /* if net_type is MES_INTERNET, set the dest node to 'internet' */ if (net_type == MES_INTERNET) {
1317 strcpy(dest_node, "internet");
1320 while (isspace(recipient[strlen(recipient) - 1]))
1321 recipient[strlen(recipient) - 1] = 0;
1324 fp = fopen(filename, "w");
1326 putc(type, fp); /* Normal or anonymous, see MES_ flags */
1327 putc(format_type, fp); /* Formatted or unformatted */
1328 fprintf(fp, "Pcit%ld%c", author->usernum, 0); /* path */
1329 fprintf(fp, "T%ld%c", (long) now, 0); /* date/time */
1332 fprintf(fp, "A%s%c", fake_name, 0);
1334 fprintf(fp, "A%s%c", author->fullname, 0); /* author */
1336 if (CC->quickroom.QRflags & QR_MAILBOX) { /* room */
1337 fprintf(fp, "O%s%c", &CC->quickroom.QRname[11], 0);
1339 fprintf(fp, "O%s%c", CC->quickroom.QRname, 0);
1342 fprintf(fp, "N%s%c", NODENAME, 0); /* nodename */
1343 fprintf(fp, "H%s%c", HUMANNODE, 0); /* human nodename */
1345 if (recipient[0] != 0)
1346 fprintf(fp, "R%s%c", recipient, 0);
1347 if (dest_node[0] != 0)
1348 fprintf(fp, "D%s%c", dest_node, 0);
1352 while (client_gets(buf), strcmp(buf, "000")) {
1353 if (msglen < config.c_maxmsglen)
1354 fprintf(fp, "%s\n", buf);
1356 lprintf(7, "Message exceeded %d byte limit\n",
1357 config.c_maxmsglen);
1358 msglen = msglen + strlen(buf) + 1;
1370 * message entry - mode 0 (normal)
1372 void cmd_ent0(char *entargs)
1375 char recipient[256];
1377 int format_type = 0;
1378 char newusername[256];
1383 struct usersupp tempUS;
1386 post = extract_int(entargs, 0);
1387 extract(recipient, entargs, 1);
1388 anon_flag = extract_int(entargs, 2);
1389 format_type = extract_int(entargs, 3);
1391 /* first check to make sure the request is valid. */
1393 if (!(CC->logged_in)) {
1394 cprintf("%d Not logged in.\n", ERROR + NOT_LOGGED_IN);
1397 if ((CC->usersupp.axlevel < 2) && ((CC->quickroom.QRflags & QR_MAILBOX) == 0)) {
1398 cprintf("%d Need to be validated to enter ",
1399 ERROR + HIGHER_ACCESS_REQUIRED);
1400 cprintf("(except in %s> to sysop)\n", MAILROOM);
1403 if ((CC->usersupp.axlevel < 4) && (CC->quickroom.QRflags & QR_NETWORK)) {
1404 cprintf("%d Need net privileges to enter here.\n",
1405 ERROR + HIGHER_ACCESS_REQUIRED);
1408 if ((CC->usersupp.axlevel < 6) && (CC->quickroom.QRflags & QR_READONLY)) {
1409 cprintf("%d Sorry, this is a read-only room.\n",
1410 ERROR + HIGHER_ACCESS_REQUIRED);
1417 if (CC->usersupp.axlevel < 6) {
1418 cprintf("%d You don't have permission to masquerade.\n",
1419 ERROR + HIGHER_ACCESS_REQUIRED);
1422 extract(newusername, entargs, 4);
1423 memset(CC->fake_postname, 0, 32);
1424 strcpy(CC->fake_postname, newusername);
1425 cprintf("%d Ok\n", OK);
1428 CC->cs_flags |= CS_POSTING;
1431 if (CC->quickroom.QRflags & QR_MAILBOX) {
1432 if (CC->usersupp.axlevel >= 2) {
1433 strcpy(buf, recipient);
1435 strcpy(buf, "sysop");
1436 e = alias(buf); /* alias and mail type */
1437 if ((buf[0] == 0) || (e == MES_ERROR)) {
1438 cprintf("%d Unknown address - cannot send message.\n",
1439 ERROR + NO_SUCH_USER);
1442 if ((e != MES_LOCAL) && (CC->usersupp.axlevel < 4)) {
1443 cprintf("%d Net privileges required for network mail.\n",
1444 ERROR + HIGHER_ACCESS_REQUIRED);
1447 if ((RESTRICT_INTERNET == 1) && (e == MES_INTERNET)
1448 && ((CC->usersupp.flags & US_INTERNET) == 0)
1449 && (!CC->internal_pgm)) {
1450 cprintf("%d You don't have access to Internet mail.\n",
1451 ERROR + HIGHER_ACCESS_REQUIRED);
1454 if (!strcasecmp(buf, "sysop")) {
1459 goto SKFALL; /* don't search local file */
1460 if (!strcasecmp(buf, CC->usersupp.fullname)) {
1461 cprintf("%d Can't send mail to yourself!\n",
1462 ERROR + NO_SUCH_USER);
1465 /* Check to make sure the user exists; also get the correct
1466 * upper/lower casing of the name.
1468 a = getuser(&tempUS, buf);
1470 cprintf("%d No such user.\n", ERROR + NO_SUCH_USER);
1473 strcpy(buf, tempUS.fullname);
1476 SKFALL: b = MES_NORMAL;
1477 if (CC->quickroom.QRflags & QR_ANONONLY)
1479 if (CC->quickroom.QRflags & QR_ANONOPT) {
1483 if ((CC->quickroom.QRflags & QR_MAILBOX) == 0)
1486 /* If we're only checking the validity of the request, return
1487 * success without creating the message.
1490 cprintf("%d %s\n", OK, buf);
1494 cprintf("%d send message\n", SEND_LISTING);
1496 if (CC->fake_postname[0])
1497 make_message(CC->temp, &CC->usersupp, buf,
1498 CC->quickroom.QRname, b, e, format_type,
1500 else if (CC->fake_username[0])
1501 make_message(CC->temp, &CC->usersupp, buf,
1502 CC->quickroom.QRname, b, e, format_type,
1505 make_message(CC->temp, &CC->usersupp, buf,
1506 CC->quickroom.QRname, b, e, format_type, "");
1508 save_message(CC->temp, buf, (mtsflag ? AIDEROOM : ""), e, 1);
1509 CC->fake_postname[0] = '\0';
1516 * message entry - mode 3 (raw)
1518 void cmd_ent3(char *entargs)
1524 struct usersupp tempUS;
1529 if (CC->internal_pgm == 0) {
1530 cprintf("%d This command is for internal programs only.\n",
1534 /* See if there's a recipient, but make sure it's a real one */ extract(recp, entargs, 1);
1535 for (a = 0; a < strlen(recp); ++a)
1536 if (!isprint(recp[a]))
1537 strcpy(&recp[a], &recp[a + 1]);
1538 while (isspace(recp[0]))
1539 strcpy(recp, &recp[1]);
1540 while (isspace(recp[strlen(recp) - 1]))
1541 recp[strlen(recp) - 1] = 0;
1543 /* If we're in Mail, check the recipient */
1544 if (strlen(recp) > 0) {
1545 e = alias(recp); /* alias and mail type */
1546 if ((recp[0] == 0) || (e == MES_ERROR)) {
1547 cprintf("%d Unknown address - cannot send message.\n",
1548 ERROR + NO_SUCH_USER);
1551 if (e == MES_LOCAL) {
1552 a = getuser(&tempUS, recp);
1554 cprintf("%d No such user.\n",
1555 ERROR + NO_SUCH_USER);
1560 /* At this point, message has been approved. */
1561 if (extract_int(entargs, 0) == 0) {
1562 cprintf("%d OK to send\n", OK);
1565 /* open a temp file to hold the message */
1566 fp = fopen(CC->temp, "wb");
1568 cprintf("%d Cannot open %s: %s\n",
1569 ERROR + INTERNAL_ERROR,
1570 CC->temp, strerror(errno));
1573 msglen = extract_long(entargs, 2);
1574 cprintf("%d %ld\n", SEND_BINARY, msglen);
1575 while (msglen > 0L) {
1576 bloklen = ((msglen >= 255L) ? 255 : msglen);
1577 client_read(buf, (int) bloklen);
1578 fwrite(buf, (int) bloklen, 1, fp);
1579 msglen = msglen - bloklen;
1583 save_message(CC->temp, recp, "", e, 0);
1588 * API function to delete messages which match a set of criteria
1589 * (returns the actual number of messages deleted)
1590 * FIX ... still need to implement delete by content type
1592 int CtdlDeleteMessages(char *room_name, /* which room */
1593 long dmsgnum, /* or "0" for any */
1594 char *content_type /* or NULL for any */
1598 struct quickroom qrbuf;
1599 struct cdbdata *cdbfr;
1600 long *msglist = NULL;
1603 int num_deleted = 0;
1605 struct SuppMsgInfo smi;
1607 lprintf(9, "CtdlDeleteMessages(%s, %ld, %s)\n",
1608 room_name, dmsgnum, content_type);
1610 /* get room record, obtaining a lock... */
1611 if (lgetroom(&qrbuf, room_name) != 0) {
1612 lprintf(7, "CtdlDeleteMessages(): Room <%s> not found\n",
1614 return (0); /* room not found */
1616 cdbfr = cdb_fetch(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long));
1618 if (cdbfr != NULL) {
1619 msglist = mallok(cdbfr->len);
1620 memcpy(msglist, cdbfr->ptr, cdbfr->len);
1621 num_msgs = cdbfr->len / sizeof(long);
1625 for (i = 0; i < num_msgs; ++i) {
1628 /* Set/clear a bit for each criterion */
1630 if ((dmsgnum == 0L) || (msglist[i] == dmsgnum)) {
1631 delete_this |= 0x01;
1633 if (content_type == NULL) {
1634 delete_this |= 0x02;
1636 GetSuppMsgInfo(&smi, msglist[i]);
1637 if (!strcasecmp(smi.smi_content_type,
1639 delete_this |= 0x02;
1643 /* Delete message only if all bits are set */
1644 if (delete_this == 0x03) {
1645 AdjRefCount(msglist[i], -1);
1651 num_msgs = sort_msglist(msglist, num_msgs);
1652 cdb_store(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long),
1653 msglist, (num_msgs * sizeof(long)));
1655 qrbuf.QRhighest = msglist[num_msgs - 1];
1659 lprintf(9, "%d message(s) deleted.\n", num_deleted);
1660 return (num_deleted);
1666 * Delete message from current room
1668 void cmd_dele(char *delstr)
1673 getuser(&CC->usersupp, CC->curr_user);
1674 if ((CC->usersupp.axlevel < 6)
1675 && (CC->usersupp.usernum != CC->quickroom.QRroomaide)
1676 && ((CC->quickroom.QRflags & QR_MAILBOX) == 0)) {
1677 cprintf("%d Higher access required.\n",
1678 ERROR + HIGHER_ACCESS_REQUIRED);
1681 delnum = extract_long(delstr, 0);
1683 num_deleted = CtdlDeleteMessages(CC->quickroom.QRname, delnum, NULL);
1686 cprintf("%d %d message%s deleted.\n", OK,
1687 num_deleted, ((num_deleted != 1) ? "s" : ""));
1689 cprintf("%d Message %ld not found.\n", ERROR, delnum);
1695 * move a message to another room
1697 void cmd_move(char *args)
1701 struct quickroom qtemp;
1704 num = extract_long(args, 0);
1705 extract(targ, args, 1);
1707 getuser(&CC->usersupp, CC->curr_user);
1708 if ((CC->usersupp.axlevel < 6)
1709 && (CC->usersupp.usernum != CC->quickroom.QRroomaide)) {
1710 cprintf("%d Higher access required.\n",
1711 ERROR + HIGHER_ACCESS_REQUIRED);
1714 if (getroom(&qtemp, targ) != 0) {
1715 cprintf("%d '%s' does not exist.\n", ERROR, targ);
1718 /* Bump the reference count, otherwise the message will be deleted
1719 * from disk when we remove it from the source room.
1721 AdjRefCount(num, 1);
1723 /* yank the message out of the current room... */
1724 foundit = CtdlDeleteMessages(CC->quickroom.QRname, num, NULL);
1727 /* put the message into the target room */
1728 lgetroom(&qtemp, targ);
1729 qtemp.QRhighest = AddMessageToRoom(&qtemp, num);
1731 cprintf("%d Message moved.\n", OK);
1733 AdjRefCount(num, (-1)); /* oops */
1734 cprintf("%d msg %ld does not exist.\n", ERROR, num);
1741 * GetSuppMsgInfo() - Get the supplementary record for a message
1743 void GetSuppMsgInfo(struct SuppMsgInfo *smibuf, long msgnum)
1746 struct cdbdata *cdbsmi;
1749 memset(smibuf, 0, sizeof(struct SuppMsgInfo));
1750 smibuf->smi_msgnum = msgnum;
1751 smibuf->smi_refcount = 1; /* Default reference count is 1 */
1753 /* Use the negative of the message number for its supp record index */
1754 TheIndex = (0L - msgnum);
1756 cdbsmi = cdb_fetch(CDB_MSGMAIN, &TheIndex, sizeof(long));
1757 if (cdbsmi == NULL) {
1758 return; /* record not found; go with defaults */
1760 memcpy(smibuf, cdbsmi->ptr,
1761 ((cdbsmi->len > sizeof(struct SuppMsgInfo)) ?
1762 sizeof(struct SuppMsgInfo) : cdbsmi->len));
1769 * PutSuppMsgInfo() - (re)write supplementary record for a message
1771 void PutSuppMsgInfo(struct SuppMsgInfo *smibuf)
1775 /* Use the negative of the message number for its supp record index */
1776 TheIndex = (0L - smibuf->smi_msgnum);
1778 lprintf(9, "PuttSuppMsgInfo(%ld) - ref count is %d\n",
1779 smibuf->smi_msgnum, smibuf->smi_refcount);
1781 cdb_store(CDB_MSGMAIN,
1782 &TheIndex, sizeof(long),
1783 smibuf, sizeof(struct SuppMsgInfo));
1787 * AdjRefCount - change the reference count for a message;
1788 * delete the message if it reaches zero
1789 */ void AdjRefCount(long msgnum, int incr)
1792 struct SuppMsgInfo smi;
1795 /* This is a *tight* critical section; please keep it that way, as
1796 * it may get called while nested in other critical sections.
1797 * Complicating this any further will surely cause deadlock!
1799 begin_critical_section(S_SUPPMSGMAIN);
1800 GetSuppMsgInfo(&smi, msgnum);
1801 smi.smi_refcount += incr;
1802 PutSuppMsgInfo(&smi);
1803 end_critical_section(S_SUPPMSGMAIN);
1805 lprintf(9, "Ref count for message <%ld> after write is <%d>\n",
1806 msgnum, smi.smi_refcount);
1808 /* If the reference count is now zero, delete the message
1809 * (and its supplementary record as well).
1811 if (smi.smi_refcount == 0) {
1812 lprintf(9, "Deleting message <%ld>\n", msgnum);
1814 cdb_delete(CDB_MSGMAIN, &delnum, sizeof(long));
1815 delnum = (0L - msgnum);
1816 cdb_delete(CDB_MSGMAIN, &delnum, sizeof(long));
1821 * Write a generic object to this room
1823 void CtdlWriteObject(char *req_room, /* Room to stuff it in */
1824 char *content_type, /* MIME type of this object */
1825 char *tempfilename, /* Where to fetch it from */
1826 int is_mailbox, /* Private mailbox room? */
1827 int is_binary, /* Is encoding necessary? */
1828 int is_unique /* Del others of this type? */
1833 char filename[PATH_MAX];
1836 struct quickroom qrbuf;
1837 char roomname[ROOMNAMELEN];
1840 MailboxName(roomname, &CC->usersupp, req_room);
1842 safestrncpy(roomname, req_room, sizeof(roomname));
1844 strcpy(filename, tmpnam(NULL));
1845 fp = fopen(filename, "w");
1849 fprintf(fp, "%c%c%c", 0xFF, MES_NORMAL, 4);
1850 fprintf(fp, "T%ld%c", time(NULL), 0);
1851 fprintf(fp, "A%s%c", CC->usersupp.fullname, 0);
1852 fprintf(fp, "O%s%c", roomname, 0);
1853 fprintf(fp, "N%s%c", config.c_nodename, 0);
1854 fprintf(fp, "MContent-type: %s\n", content_type);
1856 tempfp = fopen(tempfilename, "r");
1857 if (tempfp == NULL) {
1862 if (is_binary == 0) {
1863 fprintf(fp, "Content-transfer-encoding: 7bit\n\n");
1864 while (ch = getc(tempfp), ch > 0)
1870 fprintf(fp, "Content-transfer-encoding: base64\n\n");
1873 sprintf(cmdbuf, "./base64 -e <%s >>%s",
1874 tempfilename, filename);
1878 /* Create the requested room if we have to. */
1879 if (getroom(&qrbuf, roomname) != 0) {
1880 create_room(roomname, 4, "", 0);
1882 /* If the caller specified this object as unique, delete all
1883 * other objects of this type that are currently in the room.
1886 lprintf(9, "Deleted %d other msgs of this type\n",
1887 CtdlDeleteMessages(roomname, 0L, content_type));
1889 /* Now write the data */
1890 save_message(filename, "", roomname, MES_LOCAL, 1);