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;
630 dmsgtext = cdb_fetch(CDB_MSGMAIN, &msgnum, sizeof(long));
631 if (dmsgtext == NULL) {
634 mptr = dmsgtext->ptr;
636 /* Parse the three bytes that begin EVERY message on disk.
637 * The first is always 0xFF, the on-disk magic number.
638 * The second is the anonymous/public type byte.
639 * The third is the format type byte (vari, fixed, or MIME).
643 lprintf(5, "Message %ld appears to be corrupted.\n", msgnum);
647 ret = (struct CtdlMessage *) mallok(sizeof(struct CtdlMessage));
648 memset(ret, 0, sizeof(struct CtdlMessage));
650 ret->cm_magic = CTDLMESSAGE_MAGIC;
651 ret->cm_anon_type = *mptr++; /* Anon type byte */
652 ret->cm_format_type = *mptr++; /* Format type byte */
655 * The rest is zero or more arbitrary fields. Load them in.
656 * We're done when we encounter either a zero-length field or
657 * have just processed the 'M' (message text) field.
660 field_length = strlen(mptr);
661 if (field_length == 0)
663 field_header = *mptr++;
664 ret->cm_fields[field_header] = mallok(field_length);
665 strcpy(ret->cm_fields[field_header], mptr);
667 while (*mptr++ != 0); /* advance to next field */
669 } while ((field_length > 0) && (field_header != 'M'));
673 /* Always make sure there's something in the msg text field */
674 if (ret->cm_fields['M'] == NULL)
675 ret->cm_fields['M'] = strdoop("<no text>\n");
677 /* Perform "before read" hooks (aborting if any return nonzero) */
678 if (PerformMessageHooks(ret, EVT_BEFOREREAD) > 0) {
679 CtdlFreeMessage(ret);
688 * Returns 1 if the supplied pointer points to a valid Citadel message.
689 * If the pointer is NULL or the magic number check fails, returns 0.
691 int is_valid_message(struct CtdlMessage *msg) {
694 if ((msg->cm_magic) != CTDLMESSAGE_MAGIC) {
695 lprintf(3, "is_valid_message() -- self-check failed\n");
703 * 'Destructor' for struct CtdlMessage
705 void CtdlFreeMessage(struct CtdlMessage *msg)
709 if (is_valid_message(msg) == 0) return;
711 for (i = 0; i < 256; ++i)
712 if (msg->cm_fields[i] != NULL)
713 phree(msg->cm_fields[i]);
715 msg->cm_magic = 0; /* just in case */
722 * Get a message off disk. (return value is the message's timestamp)
725 void output_message(char *msgid, int mode, int headers_only)
733 char display_name[256];
735 struct CtdlMessage *TheMessage = NULL;
739 /* buffers needed for RFC822 translation */
747 msg_num = atol(msgid);
749 if ((!(CC->logged_in)) && (!(CC->internal_pgm))) {
750 cprintf("%d Not logged in.\n", ERROR + NOT_LOGGED_IN);
754 /* FIX ... small security issue
755 * We need to check to make sure the requested message is actually
756 * in the current room, and set msg_ok to 1 only if it is. This
757 * functionality is currently missing because I'm in a hurry to replace
758 * broken production code with nonbroken pre-beta code. :( -- ajc
761 cprintf("%d Message %ld is not in this room.\n",
768 * Fetch the message from disk
770 TheMessage = CtdlFetchMessage(msg_num);
771 if (TheMessage == NULL) {
772 cprintf("%d Can't locate msg %ld on disk\n", ERROR, msg_num);
776 /* Are we downloading a MIME component? */
777 if (mode == MT_DOWNLOAD) {
778 if (TheMessage->cm_format_type != 4) {
779 cprintf("%d This is not a MIME message.\n",
781 } else if (CC->download_fp != NULL) {
782 cprintf("%d You already have a download open.\n",
785 /* Parse the message text component */
786 mptr = TheMessage->cm_fields['M'];
787 mime_parser(mptr, NULL, *mime_download);
788 /* If there's no file open by this time, the requested
789 * section wasn't found, so print an error
791 if (CC->download_fp == NULL) {
792 cprintf("%d Section %s not found.\n",
793 ERROR + FILE_NOT_FOUND,
797 CtdlFreeMessage(TheMessage);
801 /* now for the user-mode message reading loops */
802 cprintf("%d Message %ld:\n", LISTING_FOLLOWS, msg_num);
804 /* Tell the client which format type we're using. If this is a
805 * MIME message, *lie* about it and tell the user it's fixed-format.
807 if (mode == MT_CITADEL) {
808 if (TheMessage->cm_format_type == 4)
811 cprintf("type=%d\n", TheMessage->cm_format_type);
814 /* nhdr=yes means that we're only displaying headers, no body */
815 if ((TheMessage->cm_anon_type == MES_ANON) && (mode == MT_CITADEL)) {
816 cprintf("nhdr=yes\n");
819 /* begin header processing loop for Citadel message format */
821 if ((mode == MT_CITADEL) || (mode == MT_MIME)) {
823 strcpy(display_name, "<unknown>");
824 if (TheMessage->cm_fields['A']) {
825 strcpy(buf, TheMessage->cm_fields['A']);
826 PerformUserHooks(buf, (-1L), EVT_OUTPUTMSG);
827 if (TheMessage->cm_anon_type == MES_ANON)
828 strcpy(display_name, "****");
829 else if (TheMessage->cm_anon_type == MES_AN2)
830 strcpy(display_name, "anonymous");
832 strcpy(display_name, buf);
834 && ((TheMessage->cm_anon_type == MES_ANON)
835 || (TheMessage->cm_anon_type == MES_AN2))) {
836 sprintf(&display_name[strlen(display_name)],
841 strcpy(allkeys, FORDER);
842 for (i=0; i<strlen(allkeys); ++i) {
843 k = (int) allkeys[i];
845 if (TheMessage->cm_fields[k] != NULL) {
854 TheMessage->cm_fields[k]
863 /* begin header processing loop for RFC822 transfer format */
867 strcpy(snode, NODENAME);
868 strcpy(lnode, HUMANNODE);
869 if (mode == MT_RFC822) {
870 for (i = 0; i < 256; ++i) {
871 if (TheMessage->cm_fields[i]) {
872 mptr = TheMessage->cm_fields[i];
876 } else if (i == 'P') {
877 cprintf("Path: %s\n", mptr);
878 for (a = 0; a < strlen(mptr); ++a) {
879 if (mptr[a] == '!') {
880 strcpy(mptr, &mptr[a + 1]);
886 cprintf("Subject: %s\n", mptr);
892 cprintf("X-Citadel-Room: %s\n", mptr);
896 cprintf("To: %s\n", mptr);
899 cprintf("Date: %s", asctime(localtime(&xtime)));
905 if (mode == MT_RFC822) {
906 if (!strcasecmp(snode, NODENAME)) {
909 cprintf("Message-ID: <%s@%s>\n", mid, snode);
910 PerformUserHooks(luser, (-1L), EVT_OUTPUTMSG);
911 cprintf("From: %s@%s (%s)\n", suser, snode, luser);
912 cprintf("Organization: %s\n", lnode);
915 /* end header processing loop ... at this point, we're in the text */
917 mptr = TheMessage->cm_fields['M'];
919 /* Tell the client about the MIME parts in this message */
920 if (TheMessage->cm_format_type == 4) { /* legacy textual dump */
921 if (mode == MT_CITADEL) {
922 mime_parser(mptr, NULL, *list_this_part);
924 else if (mode == MT_MIME) { /* list parts only */
925 mime_parser(mptr, NULL, *list_this_part);
927 CtdlFreeMessage(TheMessage);
934 CtdlFreeMessage(TheMessage);
938 /* signify start of msg text */
939 if (mode == MT_CITADEL)
941 if ((mode == MT_RFC822) && (TheMessage->cm_format_type != 4))
944 /* If the format type on disk is 1 (fixed-format), then we want
945 * everything to be output completely literally ... regardless of
946 * what message transfer format is in use.
948 if (TheMessage->cm_format_type == 1) {
950 while (ch = *mptr++, ch > 0) {
953 if ((ch == 10) || (strlen(buf) > 250)) {
954 cprintf("%s\n", buf);
957 buf[strlen(buf) + 1] = 0;
958 buf[strlen(buf)] = ch;
962 cprintf("%s\n", buf);
965 /* If the message on disk is format 0 (Citadel vari-format), we
966 * output using the formatter at 80 columns. This is the final output
967 * form if the transfer format is RFC822, but if the transfer format
968 * is Citadel proprietary, it'll still work, because the indentation
969 * for new paragraphs is correct and the client will reformat the
970 * message to the reader's screen width.
972 if (TheMessage->cm_format_type == 0) {
973 memfmout(80, mptr, 0);
976 /* If the message on disk is format 4 (MIME), we've gotta hand it
977 * off to the MIME parser. The client has already been told that
978 * this message is format 1 (fixed format), so the callback function
979 * we use will display those parts as-is.
981 if (TheMessage->cm_format_type == 4) {
982 CtdlAllocUserData(SYM_MA_INFO, sizeof(struct ma_info));
983 memset(ma, 0, sizeof(struct ma_info));
984 mime_parser(mptr, NULL, *fixed_output);
989 CtdlFreeMessage(TheMessage);
996 * display a message (mode 0 - Citadel proprietary)
998 void cmd_msg0(char *cmdbuf)
1001 int headers_only = 0;
1003 extract(msgid, cmdbuf, 0);
1004 headers_only = extract_int(cmdbuf, 1);
1006 output_message(msgid, MT_CITADEL, headers_only);
1012 * display a message (mode 2 - RFC822)
1014 void cmd_msg2(char *cmdbuf)
1017 int headers_only = 0;
1019 extract(msgid, cmdbuf, 0);
1020 headers_only = extract_int(cmdbuf, 1);
1022 output_message(msgid, MT_RFC822, headers_only);
1028 * display a message (mode 3 - IGnet raw format - internal programs only)
1030 void cmd_msg3(char *cmdbuf)
1033 struct CtdlMessage *msg;
1036 if (CC->internal_pgm == 0) {
1037 cprintf("%d This command is for internal programs only.\n",
1042 msgnum = extract_long(cmdbuf, 0);
1043 msg = CtdlFetchMessage(msgnum);
1045 cprintf("%d Message %ld not found.\n",
1050 serialize_message(&smr, msg);
1051 CtdlFreeMessage(msg);
1054 cprintf("%d Unable to serialize message\n",
1055 ERROR+INTERNAL_ERROR);
1059 cprintf("%d %ld\n", BINARY_FOLLOWS, smr.len);
1060 client_write(smr.ser, smr.len);
1067 * display a message (mode 4 - MIME) (FIX ... still evolving, not complete)
1069 void cmd_msg4(char *cmdbuf)
1073 extract(msgid, cmdbuf, 0);
1075 output_message(msgid, MT_MIME, 0);
1079 * Open a component of a MIME message as a download file
1081 void cmd_opna(char *cmdbuf)
1085 CtdlAllocUserData(SYM_DESIRED_SECTION, 64);
1087 extract(msgid, cmdbuf, 0);
1088 extract(desired_section, cmdbuf, 1);
1090 output_message(msgid, MT_DOWNLOAD, 0);
1095 * Save a message pointer into a specified room
1096 * (Returns 0 for success, nonzero for failure)
1097 * roomname may be NULL to use the current room
1099 int CtdlSaveMsgPointerInRoom(char *roomname, long msgid, int flags) {
1101 char hold_rm[ROOMNAMELEN];
1102 struct cdbdata *cdbfr;
1105 long highest_msg = 0L;
1106 struct CtdlMessage *msg = NULL;
1108 lprintf(9, "CtdlSaveMsgPointerInRoom(%s, %ld, %d)\n",
1109 roomname, msgid, flags);
1111 strcpy(hold_rm, CC->quickroom.QRname);
1113 /* We may need to check to see if this message is real */
1114 if ( (flags & SM_VERIFY_GOODNESS)
1115 || (flags & SM_DO_REPL_CHECK)
1117 msg = CtdlFetchMessage(msgid);
1118 if (msg == NULL) return(ERROR + ILLEGAL_VALUE);
1121 /* Perform replication checks if necessary */
1122 if ( (flags & SM_DO_REPL_CHECK) && (msg != NULL) ) {
1124 if (getroom(&CC->quickroom,
1125 ((roomname != NULL) ? roomname : CC->quickroom.QRname) )
1127 lprintf(9, "No such room <%s>\n", roomname);
1128 if (msg != NULL) CtdlFreeMessage(msg);
1129 return(ERROR + ROOM_NOT_FOUND);
1132 if (ReplicationChecks(msg) != 0) {
1133 getroom(&CC->quickroom, hold_rm);
1134 if (msg != NULL) CtdlFreeMessage(msg);
1135 lprintf(9, "Did replication, and newer exists\n");
1140 /* Now the regular stuff */
1141 if (lgetroom(&CC->quickroom,
1142 ((roomname != NULL) ? roomname : CC->quickroom.QRname) )
1144 lprintf(9, "No such room <%s>\n", roomname);
1145 if (msg != NULL) CtdlFreeMessage(msg);
1146 return(ERROR + ROOM_NOT_FOUND);
1149 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->quickroom.QRnumber, sizeof(long));
1150 if (cdbfr == NULL) {
1154 msglist = mallok(cdbfr->len);
1155 if (msglist == NULL)
1156 lprintf(3, "ERROR malloc msglist!\n");
1157 num_msgs = cdbfr->len / sizeof(long);
1158 memcpy(msglist, cdbfr->ptr, cdbfr->len);
1163 /* Make sure the message doesn't already exist in this room. It
1164 * is absolutely taboo to have more than one reference to the same
1165 * message in a room.
1167 if (num_msgs > 0) for (i=0; i<num_msgs; ++i) {
1168 if (msglist[i] == msgid) {
1169 lputroom(&CC->quickroom); /* unlock the room */
1170 getroom(&CC->quickroom, hold_rm);
1171 if (msg != NULL) CtdlFreeMessage(msg);
1172 return(ERROR + ALREADY_EXISTS);
1176 /* Now add the new message */
1178 msglist = reallok(msglist,
1179 (num_msgs * sizeof(long)));
1181 if (msglist == NULL) {
1182 lprintf(3, "ERROR: can't realloc message list!\n");
1184 msglist[num_msgs - 1] = msgid;
1186 /* Sort the message list, so all the msgid's are in order */
1187 num_msgs = sort_msglist(msglist, num_msgs);
1189 /* Determine the highest message number */
1190 highest_msg = msglist[num_msgs - 1];
1192 /* Write it back to disk. */
1193 cdb_store(CDB_MSGLISTS, &CC->quickroom.QRnumber, sizeof(long),
1194 msglist, num_msgs * sizeof(long));
1196 /* Free up the memory we used. */
1199 /* Update the highest-message pointer and unlock the room. */
1200 CC->quickroom.QRhighest = highest_msg;
1201 lputroom(&CC->quickroom);
1202 getroom(&CC->quickroom, hold_rm);
1204 /* Bump the reference count for this message. */
1205 AdjRefCount(msgid, +1);
1207 /* Return success. */
1208 if (msg != NULL) CtdlFreeMessage(msg);
1215 * Message base operation to send a message to the master file
1216 * (returns new message number)
1218 * This is the back end for CtdlSaveMsg() and should not be directly
1219 * called by server-side modules.
1222 long send_message(struct CtdlMessage *msg, /* pointer to buffer */
1223 int generate_id, /* generate 'I' field? */
1224 FILE *save_a_copy) /* save a copy to disk? */
1231 /* Get a new message number */
1232 newmsgid = get_new_message_number();
1233 sprintf(msgidbuf, "%ld", newmsgid);
1236 msg->cm_fields['I'] = strdoop(msgidbuf);
1239 serialize_message(&smr, msg);
1242 cprintf("%d Unable to serialize message\n",
1243 ERROR+INTERNAL_ERROR);
1247 /* Write our little bundle of joy into the message base */
1248 begin_critical_section(S_MSGMAIN);
1249 if (cdb_store(CDB_MSGMAIN, &newmsgid, sizeof(long),
1250 smr.ser, smr.len) < 0) {
1251 lprintf(2, "Can't store message\n");
1256 end_critical_section(S_MSGMAIN);
1258 /* If the caller specified that a copy should be saved to a particular
1259 * file handle, do that now too.
1261 if (save_a_copy != NULL) {
1262 fwrite(smr.ser, smr.len, 1, save_a_copy);
1265 /* Free the memory we used for the serialized message */
1268 /* Return the *local* message ID to the caller
1269 * (even if we're storing an incoming network message)
1277 * Serialize a struct CtdlMessage into the format used on disk and network.
1279 * This function loads up a "struct ser_ret" (defined in server.h) which
1280 * contains the length of the serialized message and a pointer to the
1281 * serialized message in memory. THE LATTER MUST BE FREED BY THE CALLER.
1283 void serialize_message(struct ser_ret *ret, /* return values */
1284 struct CtdlMessage *msg) /* unserialized msg */
1288 static char *forder = FORDER;
1290 if (is_valid_message(msg) == 0) return; /* self check */
1293 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL)
1294 ret->len = ret->len +
1295 strlen(msg->cm_fields[(int)forder[i]]) + 2;
1297 lprintf(9, "calling malloc\n");
1298 ret->ser = mallok(ret->len);
1299 if (ret->ser == NULL) {
1305 ret->ser[1] = msg->cm_anon_type;
1306 ret->ser[2] = msg->cm_format_type;
1309 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL) {
1310 ret->ser[wlen++] = (char)forder[i];
1311 strcpy(&ret->ser[wlen], msg->cm_fields[(int)forder[i]]);
1312 wlen = wlen + strlen(msg->cm_fields[(int)forder[i]]) + 1;
1314 if (ret->len != wlen) lprintf(3, "ERROR: len=%d wlen=%d\n",
1323 * Back end for the ReplicationChecks() function
1325 void check_repl(long msgnum) {
1326 struct CtdlMessage *msg;
1327 time_t timestamp = (-1L);
1329 lprintf(9, "check_repl() found message %ld\n", msgnum);
1330 msg = CtdlFetchMessage(msgnum);
1331 if (msg == NULL) return;
1332 if (msg->cm_fields['T'] != NULL) {
1333 timestamp = atol(msg->cm_fields['T']);
1335 CtdlFreeMessage(msg);
1337 if (timestamp > msg_repl->highest) {
1338 msg_repl->highest = timestamp; /* newer! */
1339 lprintf(9, "newer!\n");
1342 lprintf(9, "older!\n");
1344 /* Existing isn't newer? Then delete the old one(s). */
1345 CtdlDeleteMessages(CC->quickroom.QRname, msgnum, NULL);
1350 * Check to see if any messages already exist which carry the same Extended ID
1354 * -> With older timestamps: delete them and return 0. Message will be saved.
1355 * -> With newer timestamps: return 1. Message save will be aborted.
1357 int ReplicationChecks(struct CtdlMessage *msg) {
1358 struct CtdlMessage *template;
1361 lprintf(9, "ReplicationChecks() started\n");
1362 /* No extended id? Don't do anything. */
1363 if (msg->cm_fields['E'] == NULL) return 0;
1364 if (strlen(msg->cm_fields['E']) == 0) return 0;
1365 lprintf(9, "Extended ID: <%s>\n", msg->cm_fields['E']);
1367 CtdlAllocUserData(SYM_REPL, sizeof(struct repl));
1368 strcpy(msg_repl->extended_id, msg->cm_fields['E']);
1369 msg_repl->highest = atol(msg->cm_fields['T']);
1371 template = (struct CtdlMessage *) malloc(sizeof(struct CtdlMessage));
1372 memset(template, 0, sizeof(struct CtdlMessage));
1373 template->cm_fields['E'] = strdoop(msg->cm_fields['E']);
1375 CtdlForEachMessage(MSGS_ALL, 0L, NULL, template, check_repl);
1377 /* If a newer message exists with the same Extended ID, abort
1380 if (msg_repl->highest > atol(msg->cm_fields['T']) ) {
1384 CtdlFreeMessage(template);
1385 lprintf(9, "ReplicationChecks() returning %d\n", abort_this);
1393 * Save a message to disk
1395 void CtdlSaveMsg(struct CtdlMessage *msg, /* message to save */
1396 char *rec, /* Recipient (mail) */
1397 char *force, /* force a particular room? */
1398 int mailtype, /* local or remote type */
1399 int generate_id) /* 1 = generate 'I' field */
1402 char hold_rm[ROOMNAMELEN];
1403 char actual_rm[ROOMNAMELEN];
1404 char force_room[ROOMNAMELEN];
1405 char content_type[256]; /* We have to learn this */
1406 char recipient[256];
1409 struct usersupp userbuf;
1411 struct SuppMsgInfo smi;
1412 FILE *network_fp = NULL;
1413 static int seqnum = 1;
1415 lprintf(9, "CtdlSaveMsg() called\n");
1416 if (is_valid_message(msg) == 0) return; /* self check */
1418 /* If this message has no timestamp, we take the liberty of
1419 * giving it one, right now.
1421 if (msg->cm_fields['T'] == NULL) {
1422 sprintf(aaa, "%ld", time(NULL));
1423 msg->cm_fields['T'] = strdoop(aaa);
1426 /* If this message has no path, we generate one.
1428 if (msg->cm_fields['P'] == NULL) {
1429 msg->cm_fields['P'] = strdoop(msg->cm_fields['A']);
1430 for (a=0; a<strlen(msg->cm_fields['P']); ++a) {
1431 if (isspace(msg->cm_fields['P'][a])) {
1432 msg->cm_fields['P'][a] = ' ';
1437 strcpy(force_room, force);
1439 /* Strip non-printable characters out of the recipient name */
1440 strcpy(recipient, rec);
1441 for (a = 0; a < strlen(recipient); ++a)
1442 if (!isprint(recipient[a]))
1443 strcpy(&recipient[a], &recipient[a + 1]);
1445 /* Learn about what's inside, because it's what's inside that counts */
1447 switch (msg->cm_format_type) {
1449 strcpy(content_type, "text/x-citadel-variformat");
1452 strcpy(content_type, "text/plain");
1455 strcpy(content_type, "text/plain");
1456 /* advance past header fields */
1457 mptr = msg->cm_fields['M'];
1460 if (!strncasecmp(mptr, "Content-type: ", 14)) {
1461 safestrncpy(content_type, mptr,
1462 sizeof(content_type));
1463 strcpy(content_type, &content_type[14]);
1464 for (a = 0; a < strlen(content_type); ++a)
1465 if ((content_type[a] == ';')
1466 || (content_type[a] == ' ')
1467 || (content_type[a] == 13)
1468 || (content_type[a] == 10))
1469 content_type[a] = 0;
1476 /* Goto the correct room */
1477 strcpy(hold_rm, CC->quickroom.QRname);
1478 strcpy(actual_rm, CC->quickroom.QRname);
1480 /* If the user is a twit, move to the twit room for posting */
1482 if (CC->usersupp.axlevel == 2) {
1483 strcpy(hold_rm, actual_rm);
1484 strcpy(actual_rm, config.c_twitroom);
1488 /* ...or if this message is destined for Aide> then go there. */
1489 if (strlen(force_room) > 0) {
1490 strcpy(actual_rm, force_room);
1493 if (strcasecmp(actual_rm, CC->quickroom.QRname))
1494 getroom(&CC->quickroom, actual_rm);
1496 /* Perform "before save" hooks (aborting if any return nonzero) */
1497 if (PerformMessageHooks(msg, EVT_BEFORESAVE) > 0) return;
1499 /* If this message has an Extended ID, perform replication checks */
1500 if (ReplicationChecks(msg) > 0) return;
1502 /* Network mail - send a copy to the network program. */
1503 if ((strlen(recipient) > 0) && (mailtype != MES_LOCAL)) {
1504 sprintf(aaa, "./network/spoolin/netmail.%04lx.%04x.%04x",
1505 (long) getpid(), CC->cs_pid, ++seqnum);
1506 lprintf(9, "Saving a copy to %s\n", aaa);
1507 network_fp = fopen(aaa, "ab+");
1508 if (network_fp == NULL)
1509 lprintf(2, "ERROR: %s\n", strerror(errno));
1512 /* Save it to disk */
1513 newmsgid = send_message(msg, generate_id, network_fp);
1514 if (network_fp != NULL) {
1516 system("exec nohup ./netproc -i >/dev/null 2>&1 &");
1519 if (newmsgid <= 0L) return;
1521 /* Write a supplemental message info record. This doesn't have to
1522 * be a critical section because nobody else knows about this message
1525 memset(&smi, 0, sizeof(struct SuppMsgInfo));
1526 smi.smi_msgnum = newmsgid;
1527 smi.smi_refcount = 0;
1528 safestrncpy(smi.smi_content_type, content_type, 64);
1529 PutSuppMsgInfo(&smi);
1531 /* Now figure out where to store the pointers */
1534 /* If this is being done by the networker delivering a private
1535 * message, we want to BYPASS saving the sender's copy (because there
1536 * is no local sender; it would otherwise go to the Trashcan).
1538 if ((!CC->internal_pgm) || (strlen(recipient) == 0)) {
1539 CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 0);
1542 /* Bump this user's messages posted counter. */
1543 lgetuser(&CC->usersupp, CC->curr_user);
1544 CC->usersupp.posted = CC->usersupp.posted + 1;
1545 lputuser(&CC->usersupp);
1547 /* If this is private, local mail, make a copy in the
1548 * recipient's mailbox and bump the reference count.
1550 if ((strlen(recipient) > 0) && (mailtype == MES_LOCAL)) {
1551 if (getuser(&userbuf, recipient) == 0) {
1552 MailboxName(actual_rm, &userbuf, MAILROOM);
1553 CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 0);
1557 /* Perform "after save" hooks */
1558 PerformMessageHooks(msg, EVT_AFTERSAVE);
1561 if (strcasecmp(hold_rm, CC->quickroom.QRname))
1562 getroom(&CC->quickroom, hold_rm);
1568 * Convenience function for generating small administrative messages.
1570 void quickie_message(char *from, char *to, char *room, char *text)
1572 struct CtdlMessage *msg;
1574 msg = mallok(sizeof(struct CtdlMessage));
1575 memset(msg, 0, sizeof(struct CtdlMessage));
1576 msg->cm_magic = CTDLMESSAGE_MAGIC;
1577 msg->cm_anon_type = MES_NORMAL;
1578 msg->cm_format_type = 0;
1579 msg->cm_fields['A'] = strdoop(from);
1580 msg->cm_fields['O'] = strdoop(room);
1581 msg->cm_fields['N'] = strdoop(NODENAME);
1583 msg->cm_fields['R'] = strdoop(to);
1584 msg->cm_fields['M'] = strdoop(text);
1586 CtdlSaveMsg(msg, "", room, MES_LOCAL, 1);
1587 CtdlFreeMessage(msg);
1588 syslog(LOG_NOTICE, text);
1593 * Build a binary message to be saved on disk.
1596 struct CtdlMessage *make_message(
1597 struct usersupp *author, /* author's usersupp structure */
1598 char *recipient, /* NULL if it's not mail */
1599 char *room, /* room where it's going */
1600 int type, /* see MES_ types in header file */
1601 int net_type, /* see MES_ types in header file */
1602 int format_type, /* local or remote (see citadel.h) */
1603 char *fake_name) /* who we're masquerading as */
1609 size_t message_len = 0;
1610 size_t buffer_len = 0;
1612 struct CtdlMessage *msg;
1614 msg = mallok(sizeof(struct CtdlMessage));
1615 memset(msg, 0, sizeof(struct CtdlMessage));
1616 msg->cm_magic = CTDLMESSAGE_MAGIC;
1617 msg->cm_anon_type = type;
1618 msg->cm_format_type = format_type;
1620 /* Don't confuse the poor folks if it's not routed mail. */
1621 strcpy(dest_node, "");
1623 /* If net_type is MES_BINARY, split out the destination node. */
1624 if (net_type == MES_BINARY) {
1625 strcpy(dest_node, NODENAME);
1626 for (a = 0; a < strlen(recipient); ++a) {
1627 if (recipient[a] == '@') {
1629 strcpy(dest_node, &recipient[a + 1]);
1634 /* if net_type is MES_INTERNET, set the dest node to 'internet' */
1635 if (net_type == MES_INTERNET) {
1636 strcpy(dest_node, "internet");
1639 while (isspace(recipient[strlen(recipient) - 1]))
1640 recipient[strlen(recipient) - 1] = 0;
1642 sprintf(buf, "cit%ld", author->usernum); /* Path */
1643 msg->cm_fields['P'] = strdoop(buf);
1645 sprintf(buf, "%ld", time(NULL)); /* timestamp */
1646 msg->cm_fields['T'] = strdoop(buf);
1648 if (fake_name[0]) /* author */
1649 msg->cm_fields['A'] = strdoop(fake_name);
1651 msg->cm_fields['A'] = strdoop(author->fullname);
1653 if (CC->quickroom.QRflags & QR_MAILBOX) /* room */
1654 msg->cm_fields['O'] = strdoop(&CC->quickroom.QRname[11]);
1656 msg->cm_fields['O'] = strdoop(CC->quickroom.QRname);
1658 msg->cm_fields['N'] = strdoop(NODENAME); /* nodename */
1659 msg->cm_fields['H'] = strdoop(HUMANNODE); /* hnodename */
1661 if (recipient[0] != 0)
1662 msg->cm_fields['R'] = strdoop(recipient);
1663 if (dest_node[0] != 0)
1664 msg->cm_fields['D'] = strdoop(dest_node);
1666 msg->cm_fields['M'] = mallok(4096);
1667 if (msg->cm_fields['M'] == NULL) {
1668 while (client_gets(buf), strcmp(buf, "000")) ;; /* flush */
1672 msg->cm_fields['M'][0] = 0;
1676 /* read in the lines of message text one by one */
1678 while (client_gets(buf), strcmp(buf, "000")) {
1680 /* augment the buffer if we have to */
1681 if ((message_len + strlen(buf) + 2) > buffer_len) {
1682 lprintf(9, "realloking\n");
1683 ptr = reallok(msg->cm_fields['M'], (buffer_len * 2) );
1684 if (ptr == NULL) { /* flush if can't allocate */
1685 while (client_gets(buf), strcmp(buf, "000")) ;;
1688 buffer_len = (buffer_len * 2);
1689 msg->cm_fields['M'] = ptr;
1691 lprintf(9, "buffer_len is %d\n", buffer_len);
1695 if (append == NULL) append = msg->cm_fields['M'];
1696 while (strlen(append) > 0) ++append;
1697 strcpy(append, buf);
1698 strcat(append, "\n");
1699 message_len = message_len + strlen(buf) + 1;
1701 /* if we've hit the max msg length, flush the rest */
1702 if (message_len >= config.c_maxmsglen) {
1703 while (client_gets(buf), strcmp(buf, "000")) ;;
1716 * message entry - mode 0 (normal)
1718 void cmd_ent0(char *entargs)
1721 char recipient[256];
1723 int format_type = 0;
1724 char newusername[256];
1725 struct CtdlMessage *msg;
1729 struct usersupp tempUS;
1732 post = extract_int(entargs, 0);
1733 extract(recipient, entargs, 1);
1734 anon_flag = extract_int(entargs, 2);
1735 format_type = extract_int(entargs, 3);
1737 /* first check to make sure the request is valid. */
1739 if (!(CC->logged_in)) {
1740 cprintf("%d Not logged in.\n", ERROR + NOT_LOGGED_IN);
1743 if ((CC->usersupp.axlevel < 2) && ((CC->quickroom.QRflags & QR_MAILBOX) == 0)) {
1744 cprintf("%d Need to be validated to enter ",
1745 ERROR + HIGHER_ACCESS_REQUIRED);
1746 cprintf("(except in %s> to sysop)\n", MAILROOM);
1749 if ((CC->usersupp.axlevel < 4) && (CC->quickroom.QRflags & QR_NETWORK)) {
1750 cprintf("%d Need net privileges to enter here.\n",
1751 ERROR + HIGHER_ACCESS_REQUIRED);
1754 if ((CC->usersupp.axlevel < 6) && (CC->quickroom.QRflags & QR_READONLY)) {
1755 cprintf("%d Sorry, this is a read-only room.\n",
1756 ERROR + HIGHER_ACCESS_REQUIRED);
1763 if (CC->usersupp.axlevel < 6) {
1764 cprintf("%d You don't have permission to masquerade.\n",
1765 ERROR + HIGHER_ACCESS_REQUIRED);
1768 extract(newusername, entargs, 4);
1769 memset(CC->fake_postname, 0, 32);
1770 strcpy(CC->fake_postname, newusername);
1771 cprintf("%d Ok\n", OK);
1774 CC->cs_flags |= CS_POSTING;
1777 if (CC->quickroom.QRflags & QR_MAILBOX) {
1778 if (CC->usersupp.axlevel >= 2) {
1779 strcpy(buf, recipient);
1781 strcpy(buf, "sysop");
1782 e = alias(buf); /* alias and mail type */
1783 if ((buf[0] == 0) || (e == MES_ERROR)) {
1784 cprintf("%d Unknown address - cannot send message.\n",
1785 ERROR + NO_SUCH_USER);
1788 if ((e != MES_LOCAL) && (CC->usersupp.axlevel < 4)) {
1789 cprintf("%d Net privileges required for network mail.\n",
1790 ERROR + HIGHER_ACCESS_REQUIRED);
1793 if ((RESTRICT_INTERNET == 1) && (e == MES_INTERNET)
1794 && ((CC->usersupp.flags & US_INTERNET) == 0)
1795 && (!CC->internal_pgm)) {
1796 cprintf("%d You don't have access to Internet mail.\n",
1797 ERROR + HIGHER_ACCESS_REQUIRED);
1800 if (!strcasecmp(buf, "sysop")) {
1805 goto SKFALL; /* don't search local file */
1806 if (!strcasecmp(buf, CC->usersupp.fullname)) {
1807 cprintf("%d Can't send mail to yourself!\n",
1808 ERROR + NO_SUCH_USER);
1811 /* Check to make sure the user exists; also get the correct
1812 * upper/lower casing of the name.
1814 a = getuser(&tempUS, buf);
1816 cprintf("%d No such user.\n", ERROR + NO_SUCH_USER);
1819 strcpy(buf, tempUS.fullname);
1822 SKFALL: b = MES_NORMAL;
1823 if (CC->quickroom.QRflags & QR_ANONONLY)
1825 if (CC->quickroom.QRflags & QR_ANONOPT) {
1829 if ((CC->quickroom.QRflags & QR_MAILBOX) == 0)
1832 /* If we're only checking the validity of the request, return
1833 * success without creating the message.
1836 cprintf("%d %s\n", OK, buf);
1840 cprintf("%d send message\n", SEND_LISTING);
1842 /* Read in the message from the client. */
1843 if (CC->fake_postname[0])
1844 msg = make_message(&CC->usersupp, buf,
1845 CC->quickroom.QRname, b, e, format_type,
1847 else if (CC->fake_username[0])
1848 msg = make_message(&CC->usersupp, buf,
1849 CC->quickroom.QRname, b, e, format_type,
1852 msg = make_message(&CC->usersupp, buf,
1853 CC->quickroom.QRname, b, e, format_type, "");
1856 CtdlSaveMsg(msg, buf, (mtsflag ? AIDEROOM : ""), e, 1);
1857 CtdlFreeMessage(msg);
1858 CC->fake_postname[0] = '\0';
1865 * message entry - mode 3 (raw)
1867 void cmd_ent3(char *entargs)
1872 unsigned char ch, which_field;
1873 struct usersupp tempUS;
1875 struct CtdlMessage *msg;
1878 if (CC->internal_pgm == 0) {
1879 cprintf("%d This command is for internal programs only.\n",
1884 /* See if there's a recipient, but make sure it's a real one */
1885 extract(recp, entargs, 1);
1886 for (a = 0; a < strlen(recp); ++a)
1887 if (!isprint(recp[a]))
1888 strcpy(&recp[a], &recp[a + 1]);
1889 while (isspace(recp[0]))
1890 strcpy(recp, &recp[1]);
1891 while (isspace(recp[strlen(recp) - 1]))
1892 recp[strlen(recp) - 1] = 0;
1894 /* If we're in Mail, check the recipient */
1895 if (strlen(recp) > 0) {
1896 e = alias(recp); /* alias and mail type */
1897 if ((recp[0] == 0) || (e == MES_ERROR)) {
1898 cprintf("%d Unknown address - cannot send message.\n",
1899 ERROR + NO_SUCH_USER);
1902 if (e == MES_LOCAL) {
1903 a = getuser(&tempUS, recp);
1905 cprintf("%d No such user.\n",
1906 ERROR + NO_SUCH_USER);
1912 /* At this point, message has been approved. */
1913 if (extract_int(entargs, 0) == 0) {
1914 cprintf("%d OK to send\n", OK);
1918 msglen = extract_long(entargs, 2);
1919 msg = mallok(sizeof(struct CtdlMessage));
1921 cprintf("%d Out of memory\n", ERROR+INTERNAL_ERROR);
1925 memset(msg, 0, sizeof(struct CtdlMessage));
1926 tempbuf = mallok(msglen);
1927 if (tempbuf == NULL) {
1928 cprintf("%d Out of memory\n", ERROR+INTERNAL_ERROR);
1933 cprintf("%d %ld\n", SEND_BINARY, msglen);
1935 client_read(&ch, 1); /* 0xFF magic number */
1936 msg->cm_magic = CTDLMESSAGE_MAGIC;
1937 client_read(&ch, 1); /* anon type */
1938 msg->cm_anon_type = ch;
1939 client_read(&ch, 1); /* format type */
1940 msg->cm_format_type = ch;
1941 msglen = msglen - 3;
1943 while (msglen > 0) {
1944 client_read(&which_field, 1);
1948 client_read(&ch, 1);
1950 a = strlen(tempbuf);
1953 } while ( (ch != 0) && (msglen > 0) );
1954 msg->cm_fields[which_field] = strdoop(tempbuf);
1957 msg->cm_flags = CM_SKIP_HOOKS;
1958 CtdlSaveMsg(msg, recp, "", e, 0);
1959 CtdlFreeMessage(msg);
1965 * API function to delete messages which match a set of criteria
1966 * (returns the actual number of messages deleted)
1968 int CtdlDeleteMessages(char *room_name, /* which room */
1969 long dmsgnum, /* or "0" for any */
1970 char *content_type /* or NULL for any */
1974 struct quickroom qrbuf;
1975 struct cdbdata *cdbfr;
1976 long *msglist = NULL;
1979 int num_deleted = 0;
1981 struct SuppMsgInfo smi;
1983 lprintf(9, "CtdlDeleteMessages(%s, %ld, %s)\n",
1984 room_name, dmsgnum, content_type);
1986 /* get room record, obtaining a lock... */
1987 if (lgetroom(&qrbuf, room_name) != 0) {
1988 lprintf(7, "CtdlDeleteMessages(): Room <%s> not found\n",
1990 return (0); /* room not found */
1992 cdbfr = cdb_fetch(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long));
1994 if (cdbfr != NULL) {
1995 msglist = mallok(cdbfr->len);
1996 memcpy(msglist, cdbfr->ptr, cdbfr->len);
1997 num_msgs = cdbfr->len / sizeof(long);
2001 for (i = 0; i < num_msgs; ++i) {
2004 /* Set/clear a bit for each criterion */
2006 if ((dmsgnum == 0L) || (msglist[i] == dmsgnum)) {
2007 delete_this |= 0x01;
2009 if (content_type == NULL) {
2010 delete_this |= 0x02;
2012 GetSuppMsgInfo(&smi, msglist[i]);
2013 if (!strcasecmp(smi.smi_content_type,
2015 delete_this |= 0x02;
2019 /* Delete message only if all bits are set */
2020 if (delete_this == 0x03) {
2021 AdjRefCount(msglist[i], -1);
2027 num_msgs = sort_msglist(msglist, num_msgs);
2028 cdb_store(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long),
2029 msglist, (num_msgs * sizeof(long)));
2031 qrbuf.QRhighest = msglist[num_msgs - 1];
2035 lprintf(9, "%d message(s) deleted.\n", num_deleted);
2036 return (num_deleted);
2042 * Delete message from current room
2044 void cmd_dele(char *delstr)
2049 getuser(&CC->usersupp, CC->curr_user);
2050 if ((CC->usersupp.axlevel < 6)
2051 && (CC->usersupp.usernum != CC->quickroom.QRroomaide)
2052 && ((CC->quickroom.QRflags & QR_MAILBOX) == 0)
2053 && (!(CC->internal_pgm))) {
2054 cprintf("%d Higher access required.\n",
2055 ERROR + HIGHER_ACCESS_REQUIRED);
2058 delnum = extract_long(delstr, 0);
2060 num_deleted = CtdlDeleteMessages(CC->quickroom.QRname, delnum, NULL);
2063 cprintf("%d %d message%s deleted.\n", OK,
2064 num_deleted, ((num_deleted != 1) ? "s" : ""));
2066 cprintf("%d Message %ld not found.\n", ERROR, delnum);
2072 * move or copy a message to another room
2074 void cmd_move(char *args)
2078 struct quickroom qtemp;
2082 num = extract_long(args, 0);
2083 extract(targ, args, 1);
2084 is_copy = extract_int(args, 2);
2086 getuser(&CC->usersupp, CC->curr_user);
2087 if ((CC->usersupp.axlevel < 6)
2088 && (CC->usersupp.usernum != CC->quickroom.QRroomaide)) {
2089 cprintf("%d Higher access required.\n",
2090 ERROR + HIGHER_ACCESS_REQUIRED);
2094 if (getroom(&qtemp, targ) != 0) {
2095 cprintf("%d '%s' does not exist.\n", ERROR, targ);
2099 err = CtdlSaveMsgPointerInRoom(targ, num,
2100 (SM_VERIFY_GOODNESS | SM_DO_REPL_CHECK) );
2102 cprintf("%d Cannot store message in %s: error %d\n",
2107 /* Now delete the message from the source room,
2108 * if this is a 'move' rather than a 'copy' operation.
2110 if (is_copy == 0) CtdlDeleteMessages(CC->quickroom.QRname, num, NULL);
2112 cprintf("%d Message %s.\n", OK, (is_copy ? "copied" : "moved") );
2118 * GetSuppMsgInfo() - Get the supplementary record for a message
2120 void GetSuppMsgInfo(struct SuppMsgInfo *smibuf, long msgnum)
2123 struct cdbdata *cdbsmi;
2126 memset(smibuf, 0, sizeof(struct SuppMsgInfo));
2127 smibuf->smi_msgnum = msgnum;
2128 smibuf->smi_refcount = 1; /* Default reference count is 1 */
2130 /* Use the negative of the message number for its supp record index */
2131 TheIndex = (0L - msgnum);
2133 cdbsmi = cdb_fetch(CDB_MSGMAIN, &TheIndex, sizeof(long));
2134 if (cdbsmi == NULL) {
2135 return; /* record not found; go with defaults */
2137 memcpy(smibuf, cdbsmi->ptr,
2138 ((cdbsmi->len > sizeof(struct SuppMsgInfo)) ?
2139 sizeof(struct SuppMsgInfo) : cdbsmi->len));
2146 * PutSuppMsgInfo() - (re)write supplementary record for a message
2148 void PutSuppMsgInfo(struct SuppMsgInfo *smibuf)
2152 /* Use the negative of the message number for its supp record index */
2153 TheIndex = (0L - smibuf->smi_msgnum);
2155 lprintf(9, "PuttSuppMsgInfo(%ld) - ref count is %d\n",
2156 smibuf->smi_msgnum, smibuf->smi_refcount);
2158 cdb_store(CDB_MSGMAIN,
2159 &TheIndex, sizeof(long),
2160 smibuf, sizeof(struct SuppMsgInfo));
2165 * AdjRefCount - change the reference count for a message;
2166 * delete the message if it reaches zero
2168 void AdjRefCount(long msgnum, int incr)
2171 struct SuppMsgInfo smi;
2174 /* This is a *tight* critical section; please keep it that way, as
2175 * it may get called while nested in other critical sections.
2176 * Complicating this any further will surely cause deadlock!
2178 begin_critical_section(S_SUPPMSGMAIN);
2179 GetSuppMsgInfo(&smi, msgnum);
2180 smi.smi_refcount += incr;
2181 PutSuppMsgInfo(&smi);
2182 end_critical_section(S_SUPPMSGMAIN);
2184 lprintf(9, "Ref count for message <%ld> after write is <%d>\n",
2185 msgnum, smi.smi_refcount);
2187 /* If the reference count is now zero, delete the message
2188 * (and its supplementary record as well).
2190 if (smi.smi_refcount == 0) {
2191 lprintf(9, "Deleting message <%ld>\n", msgnum);
2193 cdb_delete(CDB_MSGMAIN, &delnum, sizeof(long));
2194 delnum = (0L - msgnum);
2195 cdb_delete(CDB_MSGMAIN, &delnum, sizeof(long));
2200 * Write a generic object to this room
2202 * Note: this could be much more efficient. Right now we use two temporary
2203 * files, and still pull the message into memory as with all others.
2205 void CtdlWriteObject(char *req_room, /* Room to stuff it in */
2206 char *content_type, /* MIME type of this object */
2207 char *tempfilename, /* Where to fetch it from */
2208 struct usersupp *is_mailbox, /* Mailbox room? */
2209 int is_binary, /* Is encoding necessary? */
2210 int is_unique, /* Del others of this type? */
2211 unsigned int flags /* Internal save flags */
2216 char filename[PATH_MAX];
2219 struct quickroom qrbuf;
2220 char roomname[ROOMNAMELEN];
2221 struct CtdlMessage *msg;
2224 if (is_mailbox != NULL)
2225 MailboxName(roomname, is_mailbox, req_room);
2227 safestrncpy(roomname, req_room, sizeof(roomname));
2228 lprintf(9, "CtdlWriteObject() to <%s> (flags=%d)\n", roomname, flags);
2230 strcpy(filename, tmpnam(NULL));
2231 fp = fopen(filename, "w");
2235 tempfp = fopen(tempfilename, "r");
2236 if (tempfp == NULL) {
2242 fprintf(fp, "Content-type: %s\n", content_type);
2243 lprintf(9, "Content-type: %s\n", content_type);
2245 if (is_binary == 0) {
2246 fprintf(fp, "Content-transfer-encoding: 7bit\n\n");
2247 while (ch = getc(tempfp), ch > 0)
2253 fprintf(fp, "Content-transfer-encoding: base64\n\n");
2256 sprintf(cmdbuf, "./base64 -e <%s >>%s",
2257 tempfilename, filename);
2261 lprintf(9, "Allocating\n");
2262 msg = mallok(sizeof(struct CtdlMessage));
2263 memset(msg, 0, sizeof(struct CtdlMessage));
2264 msg->cm_magic = CTDLMESSAGE_MAGIC;
2265 msg->cm_anon_type = MES_NORMAL;
2266 msg->cm_format_type = 4;
2267 msg->cm_fields['A'] = strdoop(CC->usersupp.fullname);
2268 msg->cm_fields['O'] = strdoop(req_room);
2269 msg->cm_fields['N'] = strdoop(config.c_nodename);
2270 msg->cm_fields['H'] = strdoop(config.c_humannode);
2271 msg->cm_flags = flags;
2273 lprintf(9, "Loading\n");
2274 fp = fopen(filename, "rb");
2275 fseek(fp, 0L, SEEK_END);
2278 msg->cm_fields['M'] = mallok(len);
2279 fread(msg->cm_fields['M'], len, 1, fp);
2283 /* Create the requested room if we have to. */
2284 if (getroom(&qrbuf, roomname) != 0) {
2285 create_room(roomname, 4, "", 0);
2287 /* If the caller specified this object as unique, delete all
2288 * other objects of this type that are currently in the room.
2291 lprintf(9, "Deleted %d other msgs of this type\n",
2292 CtdlDeleteMessages(roomname, 0L, content_type));
2294 /* Now write the data */
2295 CtdlSaveMsg(msg, "", roomname, MES_LOCAL, 1);
2296 CtdlFreeMessage(msg);