19 #include "sysdep_decls.h"
20 #include "citserver.h"
25 #include "dynloader.h"
27 #include "mime_parser.h"
30 #define desired_section ((char *)CtdlGetUserData(SYM_DESIRED_SECTION))
31 #define ma ((struct ma_info *)CtdlGetUserData(SYM_MA_INFO))
32 #define msg_repl ((struct repl *)CtdlGetUserData(SYM_REPL))
34 extern struct config config;
37 "", "", "", "", "", "", "", "",
38 "", "", "", "", "", "", "", "",
39 "", "", "", "", "", "", "", "",
40 "", "", "", "", "", "", "", "",
41 "", "", "", "", "", "", "", "",
42 "", "", "", "", "", "", "", "",
43 "", "", "", "", "", "", "", "",
44 "", "", "", "", "", "", "", "",
70 * This function is self explanatory.
71 * (What can I say, I'm in a weird mood today...)
73 void remove_any_whitespace_to_the_left_or_right_of_at_symbol(char *name)
77 for (i = 0; i < strlen(name); ++i)
80 if (isspace(name[i - 1])) {
81 strcpy(&name[i - 1], &name[i]);
84 while (isspace(name[i + 1])) {
85 strcpy(&name[i + 1], &name[i + 2]);
92 * Aliasing for network mail.
93 * (Error messages have been commented out, because this is a server.)
96 { /* process alias and routing info for mail */
99 char aaa[300], bbb[300];
101 remove_any_whitespace_to_the_left_or_right_of_at_symbol(name);
103 fp = fopen("network/mail.aliases", "r");
105 fp = fopen("/dev/null", "r");
110 while (fgets(aaa, sizeof aaa, fp) != NULL) {
111 while (isspace(name[0]))
112 strcpy(name, &name[1]);
113 aaa[strlen(aaa) - 1] = 0;
115 for (a = 0; a < strlen(aaa); ++a) {
117 strcpy(bbb, &aaa[a + 1]);
121 if (!strcasecmp(name, aaa))
125 lprintf(7, "Mail is being forwarded to %s\n", name);
127 /* determine local or remote type, see citadel.h */
128 for (a = 0; a < strlen(name); ++a)
130 return (MES_INTERNET);
131 for (a = 0; a < strlen(name); ++a)
133 for (b = a; b < strlen(name); ++b)
135 return (MES_INTERNET);
137 for (a = 0; a < strlen(name); ++a)
141 lprintf(7, "Too many @'s in address\n");
145 for (a = 0; a < strlen(name); ++a)
147 strcpy(bbb, &name[a + 1]);
149 strcpy(bbb, &bbb[1]);
150 fp = fopen("network/mail.sysinfo", "r");
154 a = getstring(fp, aaa);
155 } while ((a >= 0) && (strcasecmp(aaa, bbb)));
156 a = getstring(fp, aaa);
157 if (!strncmp(aaa, "use ", 4)) {
158 strcpy(bbb, &aaa[4]);
163 if (!strncmp(aaa, "uum", 3)) {
165 for (a = 0; a < strlen(bbb); ++a) {
171 while (bbb[strlen(bbb) - 1] == '_')
172 bbb[strlen(bbb) - 1] = 0;
173 sprintf(name, &aaa[4], bbb);
174 return (MES_INTERNET);
176 if (!strncmp(aaa, "bin", 3)) {
179 while (aaa[strlen(aaa) - 1] != '@')
180 aaa[strlen(aaa) - 1] = 0;
181 aaa[strlen(aaa) - 1] = 0;
182 while (aaa[strlen(aaa) - 1] == ' ')
183 aaa[strlen(aaa) - 1] = 0;
184 while (bbb[0] != '@')
185 strcpy(bbb, &bbb[1]);
186 strcpy(bbb, &bbb[1]);
187 while (bbb[0] == ' ')
188 strcpy(bbb, &bbb[1]);
189 sprintf(name, "%s @%s", aaa, bbb);
202 fp = fopen("citadel.control", "r");
203 fread((char *) &CitControl, sizeof(struct CitControl), 1, fp);
209 void simple_listing(long msgnum)
211 cprintf("%ld\n", msgnum);
216 /* Determine if a given message matches the fields in a message template.
217 * Return 0 for a successful match.
219 int CtdlMsgCmp(struct CtdlMessage *msg, struct CtdlMessage *template) {
222 /* If there aren't any fields in the template, all messages will
225 if (template == NULL) return(0);
227 /* Null messages are bogus. */
228 if (msg == NULL) return(1);
230 for (i='A'; i<='Z'; ++i) {
231 if (template->cm_fields[i] != NULL) {
232 if (msg->cm_fields[i] == NULL) {
235 if (strcasecmp(msg->cm_fields[i],
236 template->cm_fields[i])) return 1;
240 /* All compares succeeded: we have a match! */
248 * API function to perform an operation for each qualifying message in the
251 void CtdlForEachMessage(int mode, long ref,
253 struct CtdlMessage *compare,
254 void (*CallBack) (long msgnum))
259 struct cdbdata *cdbfr;
260 long *msglist = NULL;
263 struct SuppMsgInfo smi;
264 struct CtdlMessage *msg;
266 /* Learn about the user and room in question */
268 getuser(&CC->usersupp, CC->curr_user);
269 CtdlGetRelationship(&vbuf, &CC->usersupp, &CC->quickroom);
271 /* Load the message list */
272 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->quickroom.QRnumber, sizeof(long));
274 msglist = mallok(cdbfr->len);
275 memcpy(msglist, cdbfr->ptr, cdbfr->len);
276 num_msgs = cdbfr->len / sizeof(long);
279 return; /* No messages at all? No further action. */
283 /* If the caller is looking for a specific MIME type, then filter
284 * out all messages which are not of the type requested.
287 if (content_type != NULL)
288 if (strlen(content_type) > 0)
289 for (a = 0; a < num_msgs; ++a) {
290 GetSuppMsgInfo(&smi, msglist[a]);
291 if (strcasecmp(smi.smi_content_type, content_type)) {
296 num_msgs = sort_msglist(msglist, num_msgs);
298 /* If a template was supplied, filter out the messages which
299 * don't match. (This could induce some delays!)
302 if (compare != NULL) {
303 for (a = 0; a < num_msgs; ++a) {
304 msg = CtdlFetchMessage(msglist[a]);
306 if (CtdlMsgCmp(msg, compare)) {
309 CtdlFreeMessage(msg);
317 * Now iterate through the message list, according to the
318 * criteria supplied by the caller.
321 for (a = 0; a < num_msgs; ++a) {
322 thismsg = msglist[a];
327 || ((mode == MSGS_OLD) && (thismsg <= vbuf.v_lastseen))
328 || ((mode == MSGS_NEW) && (thismsg > vbuf.v_lastseen))
329 || ((mode == MSGS_NEW) && (thismsg >= vbuf.v_lastseen)
330 && (CC->usersupp.flags & US_LASTOLD))
331 || ((mode == MSGS_LAST) && (a >= (num_msgs - ref)))
332 || ((mode == MSGS_FIRST) && (a < ref))
333 || ((mode == MSGS_GT) && (thismsg > ref))
339 phree(msglist); /* Clean up */
345 * cmd_msgs() - get list of message #'s in this room
346 * implements the MSGS server command using CtdlForEachMessage()
348 void cmd_msgs(char *cmdbuf)
357 int with_template = 0;
358 struct CtdlMessage *template = NULL;
360 extract(which, cmdbuf, 0);
361 cm_ref = extract_int(cmdbuf, 1);
362 with_template = extract_int(cmdbuf, 2);
366 if (!strncasecmp(which, "OLD", 3))
368 else if (!strncasecmp(which, "NEW", 3))
370 else if (!strncasecmp(which, "FIRST", 5))
372 else if (!strncasecmp(which, "LAST", 4))
374 else if (!strncasecmp(which, "GT", 2))
377 if ((!(CC->logged_in)) && (!(CC->internal_pgm))) {
378 cprintf("%d not logged in\n", ERROR + NOT_LOGGED_IN);
383 cprintf("%d Send template then receive message list\n",
385 template = (struct CtdlMessage *)
386 mallok(sizeof(struct CtdlMessage));
387 memset(template, 0, sizeof(struct CtdlMessage));
388 while(client_gets(buf), strcmp(buf,"000")) {
389 extract(tfield, buf, 0);
390 extract(tvalue, buf, 1);
391 for (i='A'; i<='Z'; ++i) if (msgkeys[i]!=NULL) {
392 if (!strcasecmp(tfield, msgkeys[i])) {
393 template->cm_fields[i] =
400 cprintf("%d Message list...\n", LISTING_FOLLOWS);
403 CtdlForEachMessage(mode, cm_ref, NULL, template, simple_listing);
404 if (template != NULL) CtdlFreeMessage(template);
412 * help_subst() - support routine for help file viewer
414 void help_subst(char *strbuf, char *source, char *dest)
419 while (p = pattern2(strbuf, source), (p >= 0)) {
420 strcpy(workbuf, &strbuf[p + strlen(source)]);
421 strcpy(&strbuf[p], dest);
422 strcat(strbuf, workbuf);
427 void do_help_subst(char *buffer)
431 help_subst(buffer, "^nodename", config.c_nodename);
432 help_subst(buffer, "^humannode", config.c_humannode);
433 help_subst(buffer, "^fqdn", config.c_fqdn);
434 help_subst(buffer, "^username", CC->usersupp.fullname);
435 sprintf(buf2, "%ld", CC->usersupp.usernum);
436 help_subst(buffer, "^usernum", buf2);
437 help_subst(buffer, "^sysadm", config.c_sysadm);
438 help_subst(buffer, "^variantname", CITADEL);
439 sprintf(buf2, "%d", config.c_maxsessions);
440 help_subst(buffer, "^maxsessions", buf2);
446 * memfmout() - Citadel text formatter and paginator.
447 * Although the original purpose of this routine was to format
448 * text to the reader's screen width, all we're really using it
449 * for here is to format text out to 80 columns before sending it
450 * to the client. The client software may reformat it again.
452 void memfmout(int width, char *mptr, char subst)
453 /* screen width to use */
454 /* where are we going to get our text from? */
455 /* nonzero if we should use hypertext mode */
467 c = 1; /* c is the current pos */
470 while (ch = *mptr, ((ch != 0) && (strlen(buffer) < 126))) {
472 buffer[strlen(buffer) + 1] = 0;
473 buffer[strlen(buffer)] = ch;
476 if (buffer[0] == '^')
477 do_help_subst(buffer);
479 buffer[strlen(buffer) + 1] = 0;
481 strcpy(buffer, &buffer[1]);
491 if (((ch == 13) || (ch == 10)) && (old != 13) && (old != 10))
493 if (((old == 13) || (old == 10)) && (isspace(real))) {
501 if (((strlen(aaa) + c) > (width - 5)) && (strlen(aaa) > (width - 5))) {
502 cprintf("\n%s", aaa);
511 if ((strlen(aaa) + c) > (width - 5)) {
521 if ((ch == 13) || (ch == 10)) {
522 cprintf("%s\n", aaa);
529 FMTEND: cprintf("%s\n", aaa);
535 * Callback function for mime parser that simply lists the part
537 void list_this_part(char *name, char *filename, char *partnum, char *disp,
538 void *content, char *cbtype, size_t length)
541 cprintf("part=%s|%s|%s|%s|%s|%d\n",
542 name, filename, partnum, disp, cbtype, length);
547 * Callback function for mime parser that wants to display text
549 void fixed_output(char *name, char *filename, char *partnum, char *disp,
550 void *content, char *cbtype, size_t length)
554 if (!strcasecmp(cbtype, "multipart/alternative")) {
555 strcpy(ma->prefix, partnum);
556 strcat(ma->prefix, ".");
562 if ( (!strncasecmp(partnum, ma->prefix, strlen(ma->prefix)))
564 && (ma->did_print == 1) ) {
565 lprintf(9, "Skipping part %s (%s)\n", partnum, cbtype);
571 if (!strcasecmp(cbtype, "text/plain")) {
572 client_write(content, length);
574 else if (!strcasecmp(cbtype, "text/html")) {
575 ptr = html_to_ascii(content, 80, 0);
576 client_write(ptr, strlen(ptr));
579 else if (strncasecmp(cbtype, "multipart/", 10)) {
580 cprintf("Part %s: %s (%s) (%d bytes)\n",
581 partnum, filename, cbtype, length);
587 * Callback function for mime parser that opens a section for downloading
589 void mime_download(char *name, char *filename, char *partnum, char *disp,
590 void *content, char *cbtype, size_t length)
593 /* Silently go away if there's already a download open... */
594 if (CC->download_fp != NULL)
597 /* ...or if this is not the desired section */
598 if (strcasecmp(desired_section, partnum))
601 CC->download_fp = tmpfile();
602 if (CC->download_fp == NULL)
605 fwrite(content, length, 1, CC->download_fp);
606 fflush(CC->download_fp);
607 rewind(CC->download_fp);
609 OpenCmdResult(filename, cbtype);
615 * Load a message from disk into memory.
616 * This is used by output_message() and other fetch functions.
618 * NOTE: Caller is responsible for freeing the returned CtdlMessage struct
619 * using the CtdlMessageFree() function.
621 struct CtdlMessage *CtdlFetchMessage(long msgnum)
623 struct cdbdata *dmsgtext;
624 struct CtdlMessage *ret = NULL;
627 CIT_UBYTE field_header;
631 dmsgtext = cdb_fetch(CDB_MSGMAIN, &msgnum, sizeof(long));
632 if (dmsgtext == NULL) {
635 mptr = dmsgtext->ptr;
637 /* Parse the three bytes that begin EVERY message on disk.
638 * The first is always 0xFF, the on-disk magic number.
639 * The second is the anonymous/public type byte.
640 * The third is the format type byte (vari, fixed, or MIME).
644 lprintf(5, "Message %ld appears to be corrupted.\n", msgnum);
648 ret = (struct CtdlMessage *) mallok(sizeof(struct CtdlMessage));
649 memset(ret, 0, sizeof(struct CtdlMessage));
651 ret->cm_magic = CTDLMESSAGE_MAGIC;
652 ret->cm_anon_type = *mptr++; /* Anon type byte */
653 ret->cm_format_type = *mptr++; /* Format type byte */
656 * The rest is zero or more arbitrary fields. Load them in.
657 * We're done when we encounter either a zero-length field or
658 * have just processed the 'M' (message text) field.
661 field_length = strlen(mptr);
662 if (field_length == 0)
664 field_header = *mptr++;
665 ret->cm_fields[field_header] = mallok(field_length);
666 strcpy(ret->cm_fields[field_header], mptr);
668 while (*mptr++ != 0); /* advance to next field */
670 } while ((field_length > 0) && (field_header != 'M'));
674 /* Perform "before read" hooks (aborting if any return nonzero) */
675 if (PerformMessageHooks(ret, EVT_BEFOREREAD) > 0) {
676 CtdlFreeMessage(ret);
685 * Returns 1 if the supplied pointer points to a valid Citadel message.
686 * If the pointer is NULL or the magic number check fails, returns 0.
688 int is_valid_message(struct CtdlMessage *msg) {
691 if ((msg->cm_magic) != CTDLMESSAGE_MAGIC) {
692 lprintf(3, "is_valid_message() -- self-check failed\n");
700 * 'Destructor' for struct CtdlMessage
702 void CtdlFreeMessage(struct CtdlMessage *msg)
706 if (is_valid_message(msg) == 0) return;
708 for (i = 0; i < 256; ++i)
709 if (msg->cm_fields[i] != NULL)
710 phree(msg->cm_fields[i]);
712 msg->cm_magic = 0; /* just in case */
719 * Get a message off disk. (return value is the message's timestamp)
722 void output_message(char *msgid, int mode, int headers_only)
730 char display_name[256];
732 struct CtdlMessage *TheMessage = NULL;
736 /* buffers needed for RFC822 translation */
744 msg_num = atol(msgid);
746 if ((!(CC->logged_in)) && (!(CC->internal_pgm))) {
747 cprintf("%d Not logged in.\n", ERROR + NOT_LOGGED_IN);
751 /* FIX ... small security issue
752 * We need to check to make sure the requested message is actually
753 * in the current room, and set msg_ok to 1 only if it is. This
754 * functionality is currently missing because I'm in a hurry to replace
755 * broken production code with nonbroken pre-beta code. :( -- ajc
758 cprintf("%d Message %ld is not in this room.\n",
765 * Fetch the message from disk
767 TheMessage = CtdlFetchMessage(msg_num);
768 if (TheMessage == NULL) {
769 cprintf("%d Can't locate msg %ld on disk\n", ERROR, msg_num);
773 /* Are we downloading a MIME component? */
774 if (mode == MT_DOWNLOAD) {
775 if (TheMessage->cm_format_type != 4) {
776 cprintf("%d This is not a MIME message.\n",
778 } else if (CC->download_fp != NULL) {
779 cprintf("%d You already have a download open.\n",
782 /* Parse the message text component */
783 mptr = TheMessage->cm_fields['M'];
784 mime_parser(mptr, NULL, *mime_download);
785 /* If there's no file open by this time, the requested
786 * section wasn't found, so print an error
788 if (CC->download_fp == NULL) {
789 cprintf("%d Section %s not found.\n",
790 ERROR + FILE_NOT_FOUND,
794 CtdlFreeMessage(TheMessage);
798 /* now for the user-mode message reading loops */
799 cprintf("%d Message %ld:\n", LISTING_FOLLOWS, msg_num);
801 /* Tell the client which format type we're using. If this is a
802 * MIME message, *lie* about it and tell the user it's fixed-format.
804 if (mode == MT_CITADEL) {
805 if (TheMessage->cm_format_type == 4)
808 cprintf("type=%d\n", TheMessage->cm_format_type);
811 /* nhdr=yes means that we're only displaying headers, no body */
812 if ((TheMessage->cm_anon_type == MES_ANON) && (mode == MT_CITADEL)) {
813 cprintf("nhdr=yes\n");
816 /* begin header processing loop for Citadel message format */
818 if ((mode == MT_CITADEL) || (mode == MT_MIME)) {
820 strcpy(display_name, "<unknown>");
821 if (TheMessage->cm_fields['A']) {
822 strcpy(buf, TheMessage->cm_fields['A']);
823 PerformUserHooks(buf, (-1L), EVT_OUTPUTMSG);
824 if (TheMessage->cm_anon_type == MES_ANON)
825 strcpy(display_name, "****");
826 else if (TheMessage->cm_anon_type == MES_AN2)
827 strcpy(display_name, "anonymous");
829 strcpy(display_name, buf);
831 && ((TheMessage->cm_anon_type == MES_ANON)
832 || (TheMessage->cm_anon_type == MES_AN2))) {
833 sprintf(&display_name[strlen(display_name)],
838 strcpy(allkeys, FORDER);
839 for (i=0; i<strlen(allkeys); ++i) {
840 k = (int) allkeys[i];
842 if (TheMessage->cm_fields[k] != NULL) {
851 TheMessage->cm_fields[k]
860 /* begin header processing loop for RFC822 transfer format */
864 strcpy(snode, NODENAME);
865 strcpy(lnode, HUMANNODE);
866 if (mode == MT_RFC822) {
867 for (i = 0; i < 256; ++i) {
868 if (TheMessage->cm_fields[i]) {
869 mptr = TheMessage->cm_fields[i];
873 } else if (i == 'P') {
874 cprintf("Path: %s\n", mptr);
875 for (a = 0; a < strlen(mptr); ++a) {
876 if (mptr[a] == '!') {
877 strcpy(mptr, &mptr[a + 1]);
883 cprintf("Subject: %s\n", mptr);
889 cprintf("X-Citadel-Room: %s\n", mptr);
893 cprintf("To: %s\n", mptr);
896 cprintf("Date: %s", asctime(localtime(&xtime)));
902 if (mode == MT_RFC822) {
903 if (!strcasecmp(snode, NODENAME)) {
906 cprintf("Message-ID: <%s@%s>\n", mid, snode);
907 PerformUserHooks(luser, (-1L), EVT_OUTPUTMSG);
908 cprintf("From: %s@%s (%s)\n", suser, snode, luser);
909 cprintf("Organization: %s\n", lnode);
912 /* end header processing loop ... at this point, we're in the text */
914 mptr = TheMessage->cm_fields['M'];
916 /* Tell the client about the MIME parts in this message */
917 if (TheMessage->cm_format_type == 4) { /* legacy textual dump */
918 if (mode == MT_CITADEL) {
919 mime_parser(mptr, NULL, *list_this_part);
921 else if (mode == MT_MIME) { /* list parts only */
922 mime_parser(mptr, NULL, *list_this_part);
924 CtdlFreeMessage(TheMessage);
931 CtdlFreeMessage(TheMessage);
935 /* signify start of msg text */
936 if (mode == MT_CITADEL)
938 if ((mode == MT_RFC822) && (TheMessage->cm_format_type != 4))
941 /* If the format type on disk is 1 (fixed-format), then we want
942 * everything to be output completely literally ... regardless of
943 * what message transfer format is in use.
945 if (TheMessage->cm_format_type == 1) {
947 while (ch = *mptr++, ch > 0) {
950 if ((ch == 10) || (strlen(buf) > 250)) {
951 cprintf("%s\n", buf);
954 buf[strlen(buf) + 1] = 0;
955 buf[strlen(buf)] = ch;
959 cprintf("%s\n", buf);
962 /* If the message on disk is format 0 (Citadel vari-format), we
963 * output using the formatter at 80 columns. This is the final output
964 * form if the transfer format is RFC822, but if the transfer format
965 * is Citadel proprietary, it'll still work, because the indentation
966 * for new paragraphs is correct and the client will reformat the
967 * message to the reader's screen width.
969 if (TheMessage->cm_format_type == 0) {
970 memfmout(80, mptr, 0);
973 /* If the message on disk is format 4 (MIME), we've gotta hand it
974 * off to the MIME parser. The client has already been told that
975 * this message is format 1 (fixed format), so the callback function
976 * we use will display those parts as-is.
978 if (TheMessage->cm_format_type == 4) {
979 CtdlAllocUserData(SYM_MA_INFO, sizeof(struct ma_info));
980 memset(ma, 0, sizeof(struct ma_info));
981 mime_parser(mptr, NULL, *fixed_output);
986 CtdlFreeMessage(TheMessage);
993 * display a message (mode 0 - Citadel proprietary)
995 void cmd_msg0(char *cmdbuf)
998 int headers_only = 0;
1000 extract(msgid, cmdbuf, 0);
1001 headers_only = extract_int(cmdbuf, 1);
1003 output_message(msgid, MT_CITADEL, headers_only);
1009 * display a message (mode 2 - RFC822)
1011 void cmd_msg2(char *cmdbuf)
1014 int headers_only = 0;
1016 extract(msgid, cmdbuf, 0);
1017 headers_only = extract_int(cmdbuf, 1);
1019 output_message(msgid, MT_RFC822, headers_only);
1025 * display a message (mode 3 - IGnet raw format - internal programs only)
1027 void cmd_msg3(char *cmdbuf)
1030 struct CtdlMessage *msg;
1033 if (CC->internal_pgm == 0) {
1034 cprintf("%d This command is for internal programs only.\n",
1039 msgnum = extract_long(cmdbuf, 0);
1040 msg = CtdlFetchMessage(msgnum);
1042 cprintf("%d Message %ld not found.\n",
1047 serialize_message(&smr, msg);
1048 CtdlFreeMessage(msg);
1051 cprintf("%d Unable to serialize message\n",
1052 ERROR+INTERNAL_ERROR);
1056 cprintf("%d %ld\n", BINARY_FOLLOWS, smr.len);
1057 client_write(smr.ser, smr.len);
1064 * display a message (mode 4 - MIME) (FIX ... still evolving, not complete)
1066 void cmd_msg4(char *cmdbuf)
1070 extract(msgid, cmdbuf, 0);
1072 output_message(msgid, MT_MIME, 0);
1076 * Open a component of a MIME message as a download file
1078 void cmd_opna(char *cmdbuf)
1082 CtdlAllocUserData(SYM_DESIRED_SECTION, 64);
1084 extract(msgid, cmdbuf, 0);
1085 extract(desired_section, cmdbuf, 1);
1087 output_message(msgid, MT_DOWNLOAD, 0);
1092 * Save a message pointer into a specified room
1093 * (Returns 0 for success, nonzero for failure)
1094 * roomname may be NULL to use the current room
1096 int CtdlSaveMsgPointerInRoom(char *roomname, long msgid, int flags) {
1098 char hold_rm[ROOMNAMELEN];
1099 struct cdbdata *cdbfr;
1102 long highest_msg = 0L;
1103 struct CtdlMessage *msg = NULL;
1105 lprintf(9, "CtdlSaveMsgPointerInRoom(%s, %ld, %d)\n",
1106 roomname, msgid, flags);
1108 strcpy(hold_rm, CC->quickroom.QRname);
1110 /* We may need to check to see if this message is real */
1111 if ( (flags & SM_VERIFY_GOODNESS)
1112 || (flags & SM_DO_REPL_CHECK)
1114 msg = CtdlFetchMessage(msgid);
1115 if (msg == NULL) return(ERROR + ILLEGAL_VALUE);
1118 /* Perform replication checks if necessary */
1119 if ( (flags & SM_DO_REPL_CHECK) && (msg != NULL) ) {
1121 if (getroom(&CC->quickroom,
1122 ((roomname != NULL) ? roomname : CC->quickroom.QRname) )
1124 lprintf(9, "No such room <%s>\n", roomname);
1125 if (msg != NULL) CtdlFreeMessage(msg);
1126 return(ERROR + ROOM_NOT_FOUND);
1129 if (ReplicationChecks(msg) != 0) {
1130 getroom(&CC->quickroom, hold_rm);
1131 if (msg != NULL) CtdlFreeMessage(msg);
1132 lprintf(9, "Did replication, and newer exists\n");
1137 /* Now the regular stuff */
1138 if (lgetroom(&CC->quickroom,
1139 ((roomname != NULL) ? roomname : CC->quickroom.QRname) )
1141 lprintf(9, "No such room <%s>\n", roomname);
1142 if (msg != NULL) CtdlFreeMessage(msg);
1143 return(ERROR + ROOM_NOT_FOUND);
1146 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->quickroom.QRnumber, sizeof(long));
1147 if (cdbfr == NULL) {
1151 msglist = mallok(cdbfr->len);
1152 if (msglist == NULL)
1153 lprintf(3, "ERROR malloc msglist!\n");
1154 num_msgs = cdbfr->len / sizeof(long);
1155 memcpy(msglist, cdbfr->ptr, cdbfr->len);
1160 /* Make sure the message doesn't already exist in this room. It
1161 * is absolutely taboo to have more than one reference to the same
1162 * message in a room.
1164 if (num_msgs > 0) for (i=0; i<num_msgs; ++i) {
1165 if (msglist[i] == msgid) {
1166 lputroom(&CC->quickroom); /* unlock the room */
1167 getroom(&CC->quickroom, hold_rm);
1168 if (msg != NULL) CtdlFreeMessage(msg);
1169 return(ERROR + ALREADY_EXISTS);
1173 /* Now add the new message */
1175 msglist = reallok(msglist,
1176 (num_msgs * sizeof(long)));
1178 if (msglist == NULL) {
1179 lprintf(3, "ERROR: can't realloc message list!\n");
1181 msglist[num_msgs - 1] = msgid;
1183 /* Sort the message list, so all the msgid's are in order */
1184 num_msgs = sort_msglist(msglist, num_msgs);
1186 /* Determine the highest message number */
1187 highest_msg = msglist[num_msgs - 1];
1189 /* Write it back to disk. */
1190 cdb_store(CDB_MSGLISTS, &CC->quickroom.QRnumber, sizeof(long),
1191 msglist, num_msgs * sizeof(long));
1193 /* Free up the memory we used. */
1196 /* Update the highest-message pointer and unlock the room. */
1197 CC->quickroom.QRhighest = highest_msg;
1198 lputroom(&CC->quickroom);
1199 getroom(&CC->quickroom, hold_rm);
1201 /* Bump the reference count for this message. */
1202 AdjRefCount(msgid, +1);
1204 /* Return success. */
1205 if (msg != NULL) CtdlFreeMessage(msg);
1212 * Message base operation to send a message to the master file
1213 * (returns new message number)
1215 * This is the back end for CtdlSaveMsg() and should not be directly
1216 * called by server-side modules.
1219 long send_message(struct CtdlMessage *msg, /* pointer to buffer */
1220 int generate_id, /* generate 'I' field? */
1221 FILE *save_a_copy) /* save a copy to disk? */
1228 /* Get a new message number */
1229 newmsgid = get_new_message_number();
1230 sprintf(msgidbuf, "%ld", newmsgid);
1233 msg->cm_fields['I'] = strdoop(msgidbuf);
1236 serialize_message(&smr, msg);
1239 cprintf("%d Unable to serialize message\n",
1240 ERROR+INTERNAL_ERROR);
1244 /* Write our little bundle of joy into the message base */
1245 begin_critical_section(S_MSGMAIN);
1246 if (cdb_store(CDB_MSGMAIN, &newmsgid, sizeof(long),
1247 smr.ser, smr.len) < 0) {
1248 lprintf(2, "Can't store message\n");
1253 end_critical_section(S_MSGMAIN);
1255 /* If the caller specified that a copy should be saved to a particular
1256 * file handle, do that now too.
1258 if (save_a_copy != NULL) {
1259 fwrite(smr.ser, smr.len, 1, save_a_copy);
1262 /* Free the memory we used for the serialized message */
1265 /* Return the *local* message ID to the caller
1266 * (even if we're storing an incoming network message)
1274 * Serialize a struct CtdlMessage into the format used on disk and network.
1276 * This function loads up a "struct ser_ret" (defined in server.h) which
1277 * contains the length of the serialized message and a pointer to the
1278 * serialized message in memory. THE LATTER MUST BE FREED BY THE CALLER.
1280 void serialize_message(struct ser_ret *ret, /* return values */
1281 struct CtdlMessage *msg) /* unserialized msg */
1285 static char *forder = FORDER;
1287 if (is_valid_message(msg) == 0) return; /* self check */
1290 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL)
1291 ret->len = ret->len +
1292 strlen(msg->cm_fields[(int)forder[i]]) + 2;
1294 lprintf(9, "calling malloc\n");
1295 ret->ser = mallok(ret->len);
1296 if (ret->ser == NULL) {
1302 ret->ser[1] = msg->cm_anon_type;
1303 ret->ser[2] = msg->cm_format_type;
1306 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL) {
1307 ret->ser[wlen++] = (char)forder[i];
1308 strcpy(&ret->ser[wlen], msg->cm_fields[(int)forder[i]]);
1309 wlen = wlen + strlen(msg->cm_fields[(int)forder[i]]) + 1;
1311 if (ret->len != wlen) lprintf(3, "ERROR: len=%d wlen=%d\n",
1320 * Back end for the ReplicationChecks() function
1322 void check_repl(long msgnum) {
1323 struct CtdlMessage *msg;
1324 time_t timestamp = (-1L);
1326 lprintf(9, "check_repl() found message %ld\n", msgnum);
1327 msg = CtdlFetchMessage(msgnum);
1328 if (msg == NULL) return;
1329 if (msg->cm_fields['T'] != NULL) {
1330 timestamp = atol(msg->cm_fields['T']);
1332 CtdlFreeMessage(msg);
1334 if (timestamp > msg_repl->highest) {
1335 msg_repl->highest = timestamp; /* newer! */
1336 lprintf(9, "newer!\n");
1339 lprintf(9, "older!\n");
1341 /* Existing isn't newer? Then delete the old one(s). */
1342 CtdlDeleteMessages(CC->quickroom.QRname, msgnum, NULL);
1347 * Check to see if any messages already exist which carry the same Extended ID
1351 * -> With older timestamps: delete them and return 0. Message will be saved.
1352 * -> With newer timestamps: return 1. Message save will be aborted.
1354 int ReplicationChecks(struct CtdlMessage *msg) {
1355 struct CtdlMessage *template;
1358 lprintf(9, "ReplicationChecks() started\n");
1359 /* No extended id? Don't do anything. */
1360 if (msg->cm_fields['E'] == NULL) return 0;
1361 if (strlen(msg->cm_fields['E']) == 0) return 0;
1362 lprintf(9, "Extended ID: <%s>\n", msg->cm_fields['E']);
1364 CtdlAllocUserData(SYM_REPL, sizeof(struct repl));
1365 strcpy(msg_repl->extended_id, msg->cm_fields['E']);
1366 msg_repl->highest = atol(msg->cm_fields['T']);
1368 template = (struct CtdlMessage *) malloc(sizeof(struct CtdlMessage));
1369 memset(template, 0, sizeof(struct CtdlMessage));
1370 template->cm_fields['E'] = strdoop(msg->cm_fields['E']);
1372 CtdlForEachMessage(MSGS_ALL, 0L, NULL, template, check_repl);
1374 /* If a newer message exists with the same Extended ID, abort
1377 if (msg_repl->highest > atol(msg->cm_fields['T']) ) {
1381 CtdlFreeMessage(template);
1382 lprintf(9, "ReplicationChecks() returning %d\n", abort_this);
1390 * Save a message to disk
1392 void CtdlSaveMsg(struct CtdlMessage *msg, /* message to save */
1393 char *rec, /* Recipient (mail) */
1394 char *force, /* force a particular room? */
1395 int mailtype, /* local or remote type */
1396 int generate_id) /* 1 = generate 'I' field */
1399 char hold_rm[ROOMNAMELEN];
1400 char actual_rm[ROOMNAMELEN];
1401 char force_room[ROOMNAMELEN];
1402 char content_type[256]; /* We have to learn this */
1403 char recipient[256];
1406 struct usersupp userbuf;
1408 struct SuppMsgInfo smi;
1409 FILE *network_fp = NULL;
1410 static int seqnum = 1;
1412 lprintf(9, "CtdlSaveMsg() called\n");
1413 if (is_valid_message(msg) == 0) return; /* self check */
1415 /* If this message has no timestamp, we take the liberty of
1416 * giving it one, right now.
1418 if (msg->cm_fields['T'] == NULL) {
1419 sprintf(aaa, "%ld", time(NULL));
1420 msg->cm_fields['T'] = strdoop(aaa);
1423 /* If this message has no path, we generate one.
1425 if (msg->cm_fields['P'] == NULL) {
1426 msg->cm_fields['P'] = strdoop(msg->cm_fields['A']);
1427 for (a=0; a<strlen(msg->cm_fields['P']); ++a) {
1428 if (isspace(msg->cm_fields['P'][a])) {
1429 msg->cm_fields['P'][a] = ' ';
1434 strcpy(force_room, force);
1436 /* Strip non-printable characters out of the recipient name */
1437 strcpy(recipient, rec);
1438 for (a = 0; a < strlen(recipient); ++a)
1439 if (!isprint(recipient[a]))
1440 strcpy(&recipient[a], &recipient[a + 1]);
1442 /* Learn about what's inside, because it's what's inside that counts */
1444 switch (msg->cm_format_type) {
1446 strcpy(content_type, "text/x-citadel-variformat");
1449 strcpy(content_type, "text/plain");
1452 strcpy(content_type, "text/plain");
1453 /* advance past header fields */
1454 mptr = msg->cm_fields['M'];
1457 if (!strncasecmp(mptr, "Content-type: ", 14)) {
1458 safestrncpy(content_type, mptr,
1459 sizeof(content_type));
1460 strcpy(content_type, &content_type[14]);
1461 for (a = 0; a < strlen(content_type); ++a)
1462 if ((content_type[a] == ';')
1463 || (content_type[a] == ' ')
1464 || (content_type[a] == 13)
1465 || (content_type[a] == 10))
1466 content_type[a] = 0;
1473 /* Goto the correct room */
1474 strcpy(hold_rm, CC->quickroom.QRname);
1475 strcpy(actual_rm, CC->quickroom.QRname);
1477 /* If the user is a twit, move to the twit room for posting */
1479 if (CC->usersupp.axlevel == 2) {
1480 strcpy(hold_rm, actual_rm);
1481 strcpy(actual_rm, config.c_twitroom);
1485 /* ...or if this message is destined for Aide> then go there. */
1486 if (strlen(force_room) > 0) {
1487 strcpy(actual_rm, force_room);
1490 if (strcasecmp(actual_rm, CC->quickroom.QRname))
1491 getroom(&CC->quickroom, actual_rm);
1493 /* Perform "before save" hooks (aborting if any return nonzero) */
1494 if (PerformMessageHooks(msg, EVT_BEFORESAVE) > 0) return;
1496 /* If this message has an Extended ID, perform replication checks */
1497 if (ReplicationChecks(msg) > 0) return;
1499 /* Network mail - send a copy to the network program. */
1500 if ((strlen(recipient) > 0) && (mailtype != MES_LOCAL)) {
1501 sprintf(aaa, "./network/spoolin/netmail.%04lx.%04x.%04x",
1502 (long) getpid(), CC->cs_pid, ++seqnum);
1503 lprintf(9, "Saving a copy to %s\n", aaa);
1504 network_fp = fopen(aaa, "ab+");
1505 if (network_fp == NULL)
1506 lprintf(2, "ERROR: %s\n", strerror(errno));
1509 /* Save it to disk */
1510 newmsgid = send_message(msg, generate_id, network_fp);
1511 if (network_fp != NULL) {
1513 system("exec nohup ./netproc -i >/dev/null 2>&1 &");
1516 if (newmsgid <= 0L) return;
1518 /* Write a supplemental message info record. This doesn't have to
1519 * be a critical section because nobody else knows about this message
1522 memset(&smi, 0, sizeof(struct SuppMsgInfo));
1523 smi.smi_msgnum = newmsgid;
1524 smi.smi_refcount = 0;
1525 safestrncpy(smi.smi_content_type, content_type, 64);
1526 PutSuppMsgInfo(&smi);
1528 /* Now figure out where to store the pointers */
1531 /* If this is being done by the networker delivering a private
1532 * message, we want to BYPASS saving the sender's copy (because there
1533 * is no local sender; it would otherwise go to the Trashcan).
1535 if ((!CC->internal_pgm) || (strlen(recipient) == 0)) {
1536 CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 0);
1539 /* Bump this user's messages posted counter. */
1540 lgetuser(&CC->usersupp, CC->curr_user);
1541 CC->usersupp.posted = CC->usersupp.posted + 1;
1542 lputuser(&CC->usersupp);
1544 /* If this is private, local mail, make a copy in the
1545 * recipient's mailbox and bump the reference count.
1547 if ((strlen(recipient) > 0) && (mailtype == MES_LOCAL)) {
1548 if (getuser(&userbuf, recipient) == 0) {
1549 MailboxName(actual_rm, &userbuf, MAILROOM);
1550 CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 0);
1554 /* Perform "after save" hooks */
1555 PerformMessageHooks(msg, EVT_AFTERSAVE);
1558 if (strcasecmp(hold_rm, CC->quickroom.QRname))
1559 getroom(&CC->quickroom, hold_rm);
1565 * Convenience function for generating small administrative messages.
1567 void quickie_message(char *from, char *to, char *room, char *text)
1569 struct CtdlMessage *msg;
1571 msg = mallok(sizeof(struct CtdlMessage));
1572 memset(msg, 0, sizeof(struct CtdlMessage));
1573 msg->cm_magic = CTDLMESSAGE_MAGIC;
1574 msg->cm_anon_type = MES_NORMAL;
1575 msg->cm_format_type = 0;
1576 msg->cm_fields['A'] = strdoop(from);
1577 msg->cm_fields['O'] = strdoop(room);
1578 msg->cm_fields['N'] = strdoop(NODENAME);
1580 msg->cm_fields['R'] = strdoop(to);
1581 msg->cm_fields['M'] = strdoop(text);
1583 CtdlSaveMsg(msg, "", room, MES_LOCAL, 1);
1584 CtdlFreeMessage(msg);
1585 syslog(LOG_NOTICE, text);
1590 * Build a binary message to be saved on disk.
1593 struct CtdlMessage *make_message(
1594 struct usersupp *author, /* author's usersupp structure */
1595 char *recipient, /* NULL if it's not mail */
1596 char *room, /* room where it's going */
1597 int type, /* see MES_ types in header file */
1598 int net_type, /* see MES_ types in header file */
1599 int format_type, /* local or remote (see citadel.h) */
1600 char *fake_name) /* who we're masquerading as */
1606 size_t message_len = 0;
1607 size_t buffer_len = 0;
1609 struct CtdlMessage *msg;
1611 msg = mallok(sizeof(struct CtdlMessage));
1612 memset(msg, 0, sizeof(struct CtdlMessage));
1613 msg->cm_magic = CTDLMESSAGE_MAGIC;
1614 msg->cm_anon_type = type;
1615 msg->cm_format_type = format_type;
1617 /* Don't confuse the poor folks if it's not routed mail. */
1618 strcpy(dest_node, "");
1620 /* If net_type is MES_BINARY, split out the destination node. */
1621 if (net_type == MES_BINARY) {
1622 strcpy(dest_node, NODENAME);
1623 for (a = 0; a < strlen(recipient); ++a) {
1624 if (recipient[a] == '@') {
1626 strcpy(dest_node, &recipient[a + 1]);
1631 /* if net_type is MES_INTERNET, set the dest node to 'internet' */
1632 if (net_type == MES_INTERNET) {
1633 strcpy(dest_node, "internet");
1636 while (isspace(recipient[strlen(recipient) - 1]))
1637 recipient[strlen(recipient) - 1] = 0;
1639 sprintf(buf, "cit%ld", author->usernum); /* Path */
1640 msg->cm_fields['P'] = strdoop(buf);
1642 sprintf(buf, "%ld", time(NULL)); /* timestamp */
1643 msg->cm_fields['T'] = strdoop(buf);
1645 if (fake_name[0]) /* author */
1646 msg->cm_fields['A'] = strdoop(fake_name);
1648 msg->cm_fields['A'] = strdoop(author->fullname);
1650 if (CC->quickroom.QRflags & QR_MAILBOX) /* room */
1651 msg->cm_fields['O'] = strdoop(&CC->quickroom.QRname[11]);
1653 msg->cm_fields['O'] = strdoop(CC->quickroom.QRname);
1655 msg->cm_fields['N'] = strdoop(NODENAME); /* nodename */
1656 msg->cm_fields['H'] = strdoop(HUMANNODE); /* hnodename */
1658 if (recipient[0] != 0)
1659 msg->cm_fields['R'] = strdoop(recipient);
1660 if (dest_node[0] != 0)
1661 msg->cm_fields['D'] = strdoop(dest_node);
1663 msg->cm_fields['M'] = mallok(4096);
1664 if (msg->cm_fields['M'] == NULL) {
1665 while (client_gets(buf), strcmp(buf, "000")) ;; /* flush */
1669 msg->cm_fields['M'][0] = 0;
1673 /* read in the lines of message text one by one */
1674 while (client_gets(buf), strcmp(buf, "000")) {
1676 /* augment the buffer if we have to */
1677 if ((message_len + strlen(buf) + 2) > buffer_len) {
1678 ptr = reallok(msg->cm_fields['M'], (buffer_len * 2) );
1679 if (ptr == NULL) { /* flush if can't allocate */
1680 while (client_gets(buf), strcmp(buf, "000")) ;;
1683 buffer_len = (buffer_len * 2);
1684 msg->cm_fields['M'] = ptr;
1688 strcat(msg->cm_fields['M'], buf);
1689 strcat(msg->cm_fields['M'], "\n");
1691 /* if we've hit the max msg length, flush the rest */
1692 if (message_len >= config.c_maxmsglen) {
1693 while (client_gets(buf), strcmp(buf, "000")) ;;
1706 * message entry - mode 0 (normal)
1708 void cmd_ent0(char *entargs)
1711 char recipient[256];
1713 int format_type = 0;
1714 char newusername[256];
1715 struct CtdlMessage *msg;
1719 struct usersupp tempUS;
1722 post = extract_int(entargs, 0);
1723 extract(recipient, entargs, 1);
1724 anon_flag = extract_int(entargs, 2);
1725 format_type = extract_int(entargs, 3);
1727 /* first check to make sure the request is valid. */
1729 if (!(CC->logged_in)) {
1730 cprintf("%d Not logged in.\n", ERROR + NOT_LOGGED_IN);
1733 if ((CC->usersupp.axlevel < 2) && ((CC->quickroom.QRflags & QR_MAILBOX) == 0)) {
1734 cprintf("%d Need to be validated to enter ",
1735 ERROR + HIGHER_ACCESS_REQUIRED);
1736 cprintf("(except in %s> to sysop)\n", MAILROOM);
1739 if ((CC->usersupp.axlevel < 4) && (CC->quickroom.QRflags & QR_NETWORK)) {
1740 cprintf("%d Need net privileges to enter here.\n",
1741 ERROR + HIGHER_ACCESS_REQUIRED);
1744 if ((CC->usersupp.axlevel < 6) && (CC->quickroom.QRflags & QR_READONLY)) {
1745 cprintf("%d Sorry, this is a read-only room.\n",
1746 ERROR + HIGHER_ACCESS_REQUIRED);
1753 if (CC->usersupp.axlevel < 6) {
1754 cprintf("%d You don't have permission to masquerade.\n",
1755 ERROR + HIGHER_ACCESS_REQUIRED);
1758 extract(newusername, entargs, 4);
1759 memset(CC->fake_postname, 0, 32);
1760 strcpy(CC->fake_postname, newusername);
1761 cprintf("%d Ok\n", OK);
1764 CC->cs_flags |= CS_POSTING;
1767 if (CC->quickroom.QRflags & QR_MAILBOX) {
1768 if (CC->usersupp.axlevel >= 2) {
1769 strcpy(buf, recipient);
1771 strcpy(buf, "sysop");
1772 e = alias(buf); /* alias and mail type */
1773 if ((buf[0] == 0) || (e == MES_ERROR)) {
1774 cprintf("%d Unknown address - cannot send message.\n",
1775 ERROR + NO_SUCH_USER);
1778 if ((e != MES_LOCAL) && (CC->usersupp.axlevel < 4)) {
1779 cprintf("%d Net privileges required for network mail.\n",
1780 ERROR + HIGHER_ACCESS_REQUIRED);
1783 if ((RESTRICT_INTERNET == 1) && (e == MES_INTERNET)
1784 && ((CC->usersupp.flags & US_INTERNET) == 0)
1785 && (!CC->internal_pgm)) {
1786 cprintf("%d You don't have access to Internet mail.\n",
1787 ERROR + HIGHER_ACCESS_REQUIRED);
1790 if (!strcasecmp(buf, "sysop")) {
1795 goto SKFALL; /* don't search local file */
1796 if (!strcasecmp(buf, CC->usersupp.fullname)) {
1797 cprintf("%d Can't send mail to yourself!\n",
1798 ERROR + NO_SUCH_USER);
1801 /* Check to make sure the user exists; also get the correct
1802 * upper/lower casing of the name.
1804 a = getuser(&tempUS, buf);
1806 cprintf("%d No such user.\n", ERROR + NO_SUCH_USER);
1809 strcpy(buf, tempUS.fullname);
1812 SKFALL: b = MES_NORMAL;
1813 if (CC->quickroom.QRflags & QR_ANONONLY)
1815 if (CC->quickroom.QRflags & QR_ANONOPT) {
1819 if ((CC->quickroom.QRflags & QR_MAILBOX) == 0)
1822 /* If we're only checking the validity of the request, return
1823 * success without creating the message.
1826 cprintf("%d %s\n", OK, buf);
1830 cprintf("%d send message\n", SEND_LISTING);
1832 /* Read in the message from the client. */
1833 if (CC->fake_postname[0])
1834 msg = make_message(&CC->usersupp, buf,
1835 CC->quickroom.QRname, b, e, format_type,
1837 else if (CC->fake_username[0])
1838 msg = make_message(&CC->usersupp, buf,
1839 CC->quickroom.QRname, b, e, format_type,
1842 msg = make_message(&CC->usersupp, buf,
1843 CC->quickroom.QRname, b, e, format_type, "");
1846 CtdlSaveMsg(msg, buf, (mtsflag ? AIDEROOM : ""), e, 1);
1847 CtdlFreeMessage(msg);
1848 CC->fake_postname[0] = '\0';
1855 * message entry - mode 3 (raw)
1857 void cmd_ent3(char *entargs)
1862 unsigned char ch, which_field;
1863 struct usersupp tempUS;
1865 struct CtdlMessage *msg;
1868 if (CC->internal_pgm == 0) {
1869 cprintf("%d This command is for internal programs only.\n",
1874 /* See if there's a recipient, but make sure it's a real one */
1875 extract(recp, entargs, 1);
1876 for (a = 0; a < strlen(recp); ++a)
1877 if (!isprint(recp[a]))
1878 strcpy(&recp[a], &recp[a + 1]);
1879 while (isspace(recp[0]))
1880 strcpy(recp, &recp[1]);
1881 while (isspace(recp[strlen(recp) - 1]))
1882 recp[strlen(recp) - 1] = 0;
1884 /* If we're in Mail, check the recipient */
1885 if (strlen(recp) > 0) {
1886 e = alias(recp); /* alias and mail type */
1887 if ((recp[0] == 0) || (e == MES_ERROR)) {
1888 cprintf("%d Unknown address - cannot send message.\n",
1889 ERROR + NO_SUCH_USER);
1892 if (e == MES_LOCAL) {
1893 a = getuser(&tempUS, recp);
1895 cprintf("%d No such user.\n",
1896 ERROR + NO_SUCH_USER);
1902 /* At this point, message has been approved. */
1903 if (extract_int(entargs, 0) == 0) {
1904 cprintf("%d OK to send\n", OK);
1908 msglen = extract_long(entargs, 2);
1909 msg = mallok(sizeof(struct CtdlMessage));
1911 cprintf("%d Out of memory\n", ERROR+INTERNAL_ERROR);
1915 memset(msg, 0, sizeof(struct CtdlMessage));
1916 tempbuf = mallok(msglen);
1917 if (tempbuf == NULL) {
1918 cprintf("%d Out of memory\n", ERROR+INTERNAL_ERROR);
1923 cprintf("%d %ld\n", SEND_BINARY, msglen);
1925 client_read(&ch, 1); /* 0xFF magic number */
1926 msg->cm_magic = CTDLMESSAGE_MAGIC;
1927 client_read(&ch, 1); /* anon type */
1928 msg->cm_anon_type = ch;
1929 client_read(&ch, 1); /* format type */
1930 msg->cm_format_type = ch;
1931 msglen = msglen - 3;
1933 while (msglen > 0) {
1934 client_read(&which_field, 1);
1938 client_read(&ch, 1);
1940 a = strlen(tempbuf);
1943 } while ( (ch != 0) && (msglen > 0) );
1944 msg->cm_fields[which_field] = strdoop(tempbuf);
1947 msg->cm_flags = CM_SKIP_HOOKS;
1948 CtdlSaveMsg(msg, recp, "", e, 0);
1949 CtdlFreeMessage(msg);
1955 * API function to delete messages which match a set of criteria
1956 * (returns the actual number of messages deleted)
1958 int CtdlDeleteMessages(char *room_name, /* which room */
1959 long dmsgnum, /* or "0" for any */
1960 char *content_type /* or NULL for any */
1964 struct quickroom qrbuf;
1965 struct cdbdata *cdbfr;
1966 long *msglist = NULL;
1969 int num_deleted = 0;
1971 struct SuppMsgInfo smi;
1973 lprintf(9, "CtdlDeleteMessages(%s, %ld, %s)\n",
1974 room_name, dmsgnum, content_type);
1976 /* get room record, obtaining a lock... */
1977 if (lgetroom(&qrbuf, room_name) != 0) {
1978 lprintf(7, "CtdlDeleteMessages(): Room <%s> not found\n",
1980 return (0); /* room not found */
1982 cdbfr = cdb_fetch(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long));
1984 if (cdbfr != NULL) {
1985 msglist = mallok(cdbfr->len);
1986 memcpy(msglist, cdbfr->ptr, cdbfr->len);
1987 num_msgs = cdbfr->len / sizeof(long);
1991 for (i = 0; i < num_msgs; ++i) {
1994 /* Set/clear a bit for each criterion */
1996 if ((dmsgnum == 0L) || (msglist[i] == dmsgnum)) {
1997 delete_this |= 0x01;
1999 if (content_type == NULL) {
2000 delete_this |= 0x02;
2002 GetSuppMsgInfo(&smi, msglist[i]);
2003 if (!strcasecmp(smi.smi_content_type,
2005 delete_this |= 0x02;
2009 /* Delete message only if all bits are set */
2010 if (delete_this == 0x03) {
2011 AdjRefCount(msglist[i], -1);
2017 num_msgs = sort_msglist(msglist, num_msgs);
2018 cdb_store(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long),
2019 msglist, (num_msgs * sizeof(long)));
2021 qrbuf.QRhighest = msglist[num_msgs - 1];
2025 lprintf(9, "%d message(s) deleted.\n", num_deleted);
2026 return (num_deleted);
2032 * Delete message from current room
2034 void cmd_dele(char *delstr)
2039 getuser(&CC->usersupp, CC->curr_user);
2040 if ((CC->usersupp.axlevel < 6)
2041 && (CC->usersupp.usernum != CC->quickroom.QRroomaide)
2042 && ((CC->quickroom.QRflags & QR_MAILBOX) == 0)
2043 && (!(CC->internal_pgm))) {
2044 cprintf("%d Higher access required.\n",
2045 ERROR + HIGHER_ACCESS_REQUIRED);
2048 delnum = extract_long(delstr, 0);
2050 num_deleted = CtdlDeleteMessages(CC->quickroom.QRname, delnum, NULL);
2053 cprintf("%d %d message%s deleted.\n", OK,
2054 num_deleted, ((num_deleted != 1) ? "s" : ""));
2056 cprintf("%d Message %ld not found.\n", ERROR, delnum);
2062 * move or copy a message to another room
2064 void cmd_move(char *args)
2068 struct quickroom qtemp;
2072 num = extract_long(args, 0);
2073 extract(targ, args, 1);
2074 is_copy = extract_int(args, 2);
2076 getuser(&CC->usersupp, CC->curr_user);
2077 if ((CC->usersupp.axlevel < 6)
2078 && (CC->usersupp.usernum != CC->quickroom.QRroomaide)) {
2079 cprintf("%d Higher access required.\n",
2080 ERROR + HIGHER_ACCESS_REQUIRED);
2084 if (getroom(&qtemp, targ) != 0) {
2085 cprintf("%d '%s' does not exist.\n", ERROR, targ);
2089 err = CtdlSaveMsgPointerInRoom(targ, num,
2090 (SM_VERIFY_GOODNESS | SM_DO_REPL_CHECK) );
2092 cprintf("%d Cannot store message in %s: error %d\n",
2097 /* Now delete the message from the source room,
2098 * if this is a 'move' rather than a 'copy' operation.
2100 if (is_copy == 0) CtdlDeleteMessages(CC->quickroom.QRname, num, NULL);
2102 cprintf("%d Message %s.\n", OK, (is_copy ? "copied" : "moved") );
2108 * GetSuppMsgInfo() - Get the supplementary record for a message
2110 void GetSuppMsgInfo(struct SuppMsgInfo *smibuf, long msgnum)
2113 struct cdbdata *cdbsmi;
2116 memset(smibuf, 0, sizeof(struct SuppMsgInfo));
2117 smibuf->smi_msgnum = msgnum;
2118 smibuf->smi_refcount = 1; /* Default reference count is 1 */
2120 /* Use the negative of the message number for its supp record index */
2121 TheIndex = (0L - msgnum);
2123 cdbsmi = cdb_fetch(CDB_MSGMAIN, &TheIndex, sizeof(long));
2124 if (cdbsmi == NULL) {
2125 return; /* record not found; go with defaults */
2127 memcpy(smibuf, cdbsmi->ptr,
2128 ((cdbsmi->len > sizeof(struct SuppMsgInfo)) ?
2129 sizeof(struct SuppMsgInfo) : cdbsmi->len));
2136 * PutSuppMsgInfo() - (re)write supplementary record for a message
2138 void PutSuppMsgInfo(struct SuppMsgInfo *smibuf)
2142 /* Use the negative of the message number for its supp record index */
2143 TheIndex = (0L - smibuf->smi_msgnum);
2145 lprintf(9, "PuttSuppMsgInfo(%ld) - ref count is %d\n",
2146 smibuf->smi_msgnum, smibuf->smi_refcount);
2148 cdb_store(CDB_MSGMAIN,
2149 &TheIndex, sizeof(long),
2150 smibuf, sizeof(struct SuppMsgInfo));
2155 * AdjRefCount - change the reference count for a message;
2156 * delete the message if it reaches zero
2158 void AdjRefCount(long msgnum, int incr)
2161 struct SuppMsgInfo smi;
2164 /* This is a *tight* critical section; please keep it that way, as
2165 * it may get called while nested in other critical sections.
2166 * Complicating this any further will surely cause deadlock!
2168 begin_critical_section(S_SUPPMSGMAIN);
2169 GetSuppMsgInfo(&smi, msgnum);
2170 smi.smi_refcount += incr;
2171 PutSuppMsgInfo(&smi);
2172 end_critical_section(S_SUPPMSGMAIN);
2174 lprintf(9, "Ref count for message <%ld> after write is <%d>\n",
2175 msgnum, smi.smi_refcount);
2177 /* If the reference count is now zero, delete the message
2178 * (and its supplementary record as well).
2180 if (smi.smi_refcount == 0) {
2181 lprintf(9, "Deleting message <%ld>\n", msgnum);
2183 cdb_delete(CDB_MSGMAIN, &delnum, sizeof(long));
2184 delnum = (0L - msgnum);
2185 cdb_delete(CDB_MSGMAIN, &delnum, sizeof(long));
2190 * Write a generic object to this room
2192 * Note: this could be much more efficient. Right now we use two temporary
2193 * files, and still pull the message into memory as with all others.
2195 void CtdlWriteObject(char *req_room, /* Room to stuff it in */
2196 char *content_type, /* MIME type of this object */
2197 char *tempfilename, /* Where to fetch it from */
2198 struct usersupp *is_mailbox, /* Mailbox room? */
2199 int is_binary, /* Is encoding necessary? */
2200 int is_unique, /* Del others of this type? */
2201 unsigned int flags /* Internal save flags */
2206 char filename[PATH_MAX];
2209 struct quickroom qrbuf;
2210 char roomname[ROOMNAMELEN];
2211 struct CtdlMessage *msg;
2214 if (is_mailbox != NULL)
2215 MailboxName(roomname, is_mailbox, req_room);
2217 safestrncpy(roomname, req_room, sizeof(roomname));
2218 lprintf(9, "CtdlWriteObject() to <%s> (flags=%d)\n", roomname, flags);
2220 strcpy(filename, tmpnam(NULL));
2221 fp = fopen(filename, "w");
2225 tempfp = fopen(tempfilename, "r");
2226 if (tempfp == NULL) {
2232 fprintf(fp, "Content-type: %s\n", content_type);
2233 lprintf(9, "Content-type: %s\n", content_type);
2235 if (is_binary == 0) {
2236 fprintf(fp, "Content-transfer-encoding: 7bit\n\n");
2237 while (ch = getc(tempfp), ch > 0)
2243 fprintf(fp, "Content-transfer-encoding: base64\n\n");
2246 sprintf(cmdbuf, "./base64 -e <%s >>%s",
2247 tempfilename, filename);
2251 lprintf(9, "Allocating\n");
2252 msg = mallok(sizeof(struct CtdlMessage));
2253 memset(msg, 0, sizeof(struct CtdlMessage));
2254 msg->cm_magic = CTDLMESSAGE_MAGIC;
2255 msg->cm_anon_type = MES_NORMAL;
2256 msg->cm_format_type = 4;
2257 msg->cm_fields['A'] = strdoop(CC->usersupp.fullname);
2258 msg->cm_fields['O'] = strdoop(req_room);
2259 msg->cm_fields['N'] = strdoop(config.c_nodename);
2260 msg->cm_fields['H'] = strdoop(config.c_humannode);
2261 msg->cm_flags = flags;
2263 lprintf(9, "Loading\n");
2264 fp = fopen(filename, "rb");
2265 fseek(fp, 0L, SEEK_END);
2268 msg->cm_fields['M'] = mallok(len);
2269 fread(msg->cm_fields['M'], len, 1, fp);
2273 /* Create the requested room if we have to. */
2274 if (getroom(&qrbuf, roomname) != 0) {
2275 create_room(roomname, 4, "", 0);
2277 /* If the caller specified this object as unique, delete all
2278 * other objects of this type that are currently in the room.
2281 lprintf(9, "Deleted %d other msgs of this type\n",
2282 CtdlDeleteMessages(roomname, 0L, content_type));
2284 /* Now write the data */
2285 CtdlSaveMsg(msg, "", roomname, MES_LOCAL, 1);
2286 CtdlFreeMessage(msg);